R/deparse_formula.R

Defines functions get_variables_from_list get_variables deparse_formula_partial deparse_formula

#' deparse_formula
#'
#' Translates an R formula into nested lists for both the left and the right
#' hand side to create table rows and banners. Importantly, this function
#' can only parse a specific subset of formulas that allows for the following
#' grammar:
#'
#' 1) Braces group columns
#' 2) Equal signs assign the left hand side as name for multiple columns
#' 3) plus signs combine columns
#'
#' @param formula an R formula following the pattern above
#' @returns nested lists for the left hand side and right hand side of the formula
#' @noRd
#' @examples
#' library(tablespan)
#' deparsed <- tablespan:::deparse_formula(formula =
#'  (`Row Name` = `Row 1` + `Row 2`) ~ `Column 1` + (`Column Banner` = `Column 2` + `Column 3`))
#' str(deparsed)
deparse_formula <- function(formula) {
  if (formula[[2]] == "1") {
    lhs <- NULL
  } else {
    lhs <- deparse_formula_partial(formula_partial = formula[[2]])
  }

  if (formula[[3]] == "1") {
    stop("Missing right hand side of formula.")
  } else {
    rhs <- deparse_formula_partial(formula_partial = formula[[3]])
  }

  return(list(lhs = lhs, rhs = rhs))
}

#' deparse_formula_partial
#'
#' Translates the left of right hand side of an R formula into a nested list. Importantly, this function
#' can only parse a specific subset of formulas that allows for the following
#' grammar:
#'
#' 1) Braces group columns
#' 2) Equal signs assign the left hand side as name for multiple columns
#' 3) plus signs combine columns
#'
#' @param formula_partial the left or right hand side of an R formula following the pattern above
#' @param deparsed this function is a recursive function and makes use of the deparsed list.
#' Don't change this manually.
#' @returns a nested list with formula elements
#' @importFrom methods is
#' @noRd
#' @examples
#' library(tablespan)
#' formula <- (`Row Name` = `Row 1` + `Row 2`) ~
#'   `Column 1` + (`Column Banner` = `Column 2` + `Column 3`)
#'
#' deparsed <- tablespan:::deparse_formula_partial(formula_partial = formula[[2]])
#' str(deparsed)
deparse_formula_partial <- function(
  formula_partial,
  deparsed = list(name = "_BASE_LEVEL_", entries = list())
) {
  # There are three types of symbols on the right hand side:
  # 1) Braces group columns
  # 2) Equal signs assign the left hand side as name for multiple columns
  # 3) plus signs combine columns
  # We want to translate the above in a list. For example, we want
  # (a = b1 + b2)
  # to become
  # list(a = c("b1", "b2"))

  if (length(formula_partial) == 1) {
    deparsed$entries <- c(
      deparsed$entries,
      list(list(
        name = as.character(formula_partial),
        item_name = as.character(formula_partial),
        entries = NULL
      ))
    )
    return(deparsed)
  }

  if ((formula_partial[[1]] == ":")) {
    if (
      (length(formula_partial[[2]]) == 1) & (length(formula_partial[[3]]) == 1)
    ) {
      deparsed$entries <- c(
        deparsed$entries,
        list(list(
          name = as.character(formula_partial[[2]]),
          item_name = as.character(formula_partial[[3]]),
          entries = NULL
        ))
      )
      return(deparsed)
    } else {
      stop(paste0(
        "Renaming with ",
        as.character(formula_partial),
        " is not allowed. Both sides of the colon must be single names (e.g., a:b)."
      ))
    }
  }

  if (formula_partial[[1]] == "+") {
    deparsed <- deparse_formula_partial(formula_partial[[2]], deparsed)
    deparsed <- deparse_formula_partial(formula_partial[[3]], deparsed)
    return(deparsed)
  } else if (formula_partial[[1]] == "=") {
    # The left hand side is the name of the split, the right hand side
    # specifies the elements
    deparsed$name <- as.character(formula_partial[[2]])
    deparsed <- deparse_formula_partial(formula_partial[[3]], deparsed)
    return(deparsed)
  } else if (formula_partial[[1]] == "(") {
    # Check if there is a name for the current spanner
    try_name <- try(formula_partial[[2]][[1]] != "=", silent = TRUE)
    if (is(try_name, "try-error") | try_name) {
      stop("The following spanner has no label: ", formula_partial, ".")
    }
    # In case of a brace, we have to go one step deeper
    deparsed$entries <- c(
      deparsed$entries,
      list(deparse_formula_partial(
        formula_partial[[2]],
        list(name = NULL, entries = list())
      ))
    )
    return(deparsed)
  } else {
    stop(paste0(
      "Unknown symbol detected: ",
      paste0(as.character(formula_partial), collapse = "")
    ))
  }
}


#' get_variables
#'
#' Extracts the variable names from a deparsed formula (see ?tablespan:::deparse_formula).
#'
#' @param deparsed_formula result from tablespan:::deparse_formula
#' @returns a list with the names of the variables that build the rows and columns
#' @noRd
#' @examples
#' library(tablespan)
#' deparsed <- tablespan:::deparse_formula(formula =
#'  (`Row Name` = `Row 1` + `Row 2`) ~ `Column 1` + (`Column Banner` = `Column 2` + `Column 3`))
#' str(deparsed)
#' tablespan:::get_variables(deparsed)
get_variables <- function(deparsed_formula) {
  if (is.null(deparsed_formula$lhs)) {
    # no row variable
    row_variables <- NULL
  } else {
    row_variables <- get_variables_from_list(
      deparsed_formula_element = deparsed_formula$lhs
    )
  }

  col_variables <- get_variables_from_list(
    deparsed_formula_element = deparsed_formula$rhs
  )

  return(
    list(row_variables = row_variables, col_variables = col_variables)
  )
}

#' get_variables_from_list
#'
#' Extracts the variable names from the left or right hand side of a
#' deparsed formula (see ?tablespan:::deparse_formula).
#'
#' @param deparsed_formula_element left or right hand side of the result from
#' tablespan:::deparse_formula
#' @param variables the function is recursive and fills the variable vector
#' @returns a vector with the names of the variables
#' @noRd
#' @examples
#' library(tablespan)
#' deparsed <- tablespan:::deparse_formula(formula =
#'  (`Row Name` = `Row 1` + `Row 2`) ~ `Column 1` + (`Column Banner` = `Column 2` + `Column 3`))
#' str(deparsed)
#' tablespan:::get_variables_from_list(deparsed$lhs)
get_variables_from_list <- function(deparsed_formula_element, variables = c()) {
  if (is.null(deparsed_formula_element$entries)) {
    variables <- c(variables, deparsed_formula_element$item_name)
    return(variables)
  } else {
    for (entry in deparsed_formula_element$entries) {
      variables <- get_variables_from_list(entry, variables)
    }
    return(variables)
  }
}

Try the tablespan package in your browser

Any scripts or data that you put into this service are public.

tablespan documentation built on Sept. 10, 2025, 10:35 a.m.