diff --git a/R/assign_summary_type.R b/R/assign_summary_type.R index 8501dcbbb3..59ead89017 100644 --- a/R/assign_summary_type.R +++ b/R/assign_summary_type.R @@ -115,13 +115,3 @@ assign_summary_type <- function(data, variables, value, type = NULL, cat_thresho NULL } -.add_summary_type_as_attr <- function(data, type) { - type <- type[names(type) %in% names(data)] - type_names <- names(type) - - for (i in seq_along(type)) { - attr(data[[type_names[i]]], "gtsummary.type") <- type[[i]] - } - - data -} diff --git a/R/select_helpers.R b/R/select_helpers.R index 274ffc7c2b..df3d6f955a 100644 --- a/R/select_helpers.R +++ b/R/select_helpers.R @@ -38,13 +38,13 @@ NULL all_continuous <- function(continuous2 = TRUE) { types <- if (continuous2) c("continuous", "continuous2") else "continuous" - where(function(x) isTRUE(attr(x, "gtsummary.type") %in% types)) + where(function(x) isTRUE(attr(x, "gtsummary.summary_type") %in% types)) } #' @rdname select_helpers #' @export all_continuous2 <- function() { - where(function(x) isTRUE(attr(x, "gtsummary.type") %in% "continuous2")) + where(function(x) isTRUE(attr(x, "gtsummary.summary_type") %in% "continuous2")) } #' @rdname select_helpers @@ -52,13 +52,13 @@ all_continuous2 <- function() { all_categorical <- function(dichotomous = TRUE) { types <- if (dichotomous) c("categorical", "dichotomous") else "categorical" - where(function(x) isTRUE(attr(x, "gtsummary.type") %in% types)) + where(function(x) isTRUE(attr(x, "gtsummary.summary_type") %in% types)) } #' @rdname select_helpers #' @export all_dichotomous <- function() { - where(function(x) isTRUE(attr(x, "gtsummary.type") %in% "dichotomous")) + where(function(x) isTRUE(attr(x, "gtsummary.summary_type") %in% "dichotomous")) } # #' @rdname select_helpers @@ -101,6 +101,7 @@ all_stat_cols <- function(stat_0 = TRUE) { } } + # #' @rdname select_helpers # #' @export # all_interaction <- broom.helpers::all_interaction @@ -112,3 +113,60 @@ all_stat_cols <- function(stat_0 = TRUE) { # #' @rdname select_helpers # #' @export # all_contrasts <- broom.helpers::all_contrasts + + + +#' Table Body Select Prep +#' +#' This function uses the information in `.$table_body` and adds them +#' as attributes to `data` (if passed). Once they've been assigned as +#' proper gtsummary attributes, gtsummary selectors like `all_continuous()` +#' will work properly. +#' +#' @param table_body a data frame from `.$table_body` +#' @param data an optional data frame the attributes will be added to +#' +#' @return a data frame +#' @keywords internal +select_prep <- function(table_body, data = NULL) { + # if data not passed, use table_body to construct one + if (is.null(data)) { + data <- dplyr::tibble(!!!rep_named(table_body$variable, character(0L))) + } + + # only keeping rows that have corresponding column names in data + table_body <- table_body |> dplyr::filter(.data$variable %in% names(data)) + + # if table_body passed, add columns as attr to data + if (!is.null(table_body)) { + attr_cols <- intersect(names(table_body), c("summary_type", "test_name")) + for (v in attr_cols) { + df_attr <- table_body[c("variable", v)] |> unique() |> tidyr::drop_na() + for (i in seq_len(nrow(df_attr))) { + attr(data[[df_attr$variable[i]]], paste0("gtsummary.", v)) <- df_attr[[v]][i] + } + } + } + + data +} + + +# converts a named list to a table_body format. +# the result of this fn will often be passed to `select_prep()` +#' Convert Named List to Table Body +#' +#' Many arguments in 'gtsummary' accept named lists. This function converts +#' a named list to the `.$table_body` format expected in `select_prep()` +#' +#' @param x named list +#' @param colname string of column name to assign. Default is `caller_arg(x)` +#' +#' @return `.$table_body` data frame +#' @keywords internal +#' @examples +#' type <- list(age = "continuous", response = "dichotomous") +#' gtsummary:::.list2tb(type, "summary_type") +.list2tb <- function(x, colname = caller_arg(x)) { + enframe(unlist(x), "variable", colname) +} diff --git a/R/tbl_summary.R b/R/tbl_summary.R index b4685c4094..111fcaa22b 100644 --- a/R/tbl_summary.R +++ b/R/tbl_summary.R @@ -193,58 +193,61 @@ tbl_summary <- function(data, # first set default types, so selectors like `all_continuous()` can be used # to recast the summary type, e.g. make all continuous type "continuous2" default_types <- assign_summary_type(data, include, value) - data <- .add_summary_type_as_attr(data, default_types) # process the user-passed type argument - cards::process_formula_selectors(data = data[include], type = type) + cards::process_formula_selectors( + data = select_prep(.list2tb(default_types, "summary_type"), data[include]), + type = type + ) # fill in any types not specified by user type <- utils::modifyList(default_types, type) } else { type <- assign_summary_type(data, include, value) } - data <- .add_summary_type_as_attr(data, type) - value <- .assign_default_values(data[include], value, type) + value <- + select_prep(.list2tb(type, "summary_type"), data[include]) |> + .assign_default_values(value, type) # evaluate the remaining list-formula arguments ------------------------------ # processed arguments are saved into this env - cards::process_formula_selectors( - data = data[include], - statistic = - .ifelse1( - missing(statistic), - get_theme_element("TODO:fill-this-in", default = statistic), - statistic - ), - include_env = TRUE - ) + select_prep(.list2tb(type, "summary_type"), data[include]) |> + cards::process_formula_selectors( + statistic = + .ifelse1( + missing(statistic), + get_theme_element("TODO:fill-this-in", default = statistic), + statistic + ), + include_env = TRUE + ) # add the calling env to the statistics statistic <- .add_env_to_list_elements(statistic, env = caller_env()) - cards::process_formula_selectors( - data = data[include], - label = label, - sort = - .ifelse1( - missing(sort), - get_theme_element("TODO:fill-this-in", default = sort), - sort - ) - ) + select_prep(.list2tb(type, "summary_type"), data[include]) |> + cards::process_formula_selectors( + label = label, + sort = + .ifelse1( + missing(sort), + get_theme_element("TODO:fill-this-in", default = sort), + sort + ) + ) - cards::process_formula_selectors( - data = data[include], - digits = - .ifelse1( - is_empty(digits), - get_theme_element("TODO:fill-this-in", default = assign_summary_digits(data, statistic, type)), - digits - ) - ) + select_prep(.list2tb(type, "summary_type"), data[include]) |> + cards::process_formula_selectors( + digits = + .ifelse1( + is_empty(digits), + get_theme_element("TODO:fill-this-in", default = assign_summary_digits(data, statistic, type)), + digits + ) + ) # fill in unspecified variables cards::fill_formula_selectors( - data[include], + select_prep(.list2tb(type, "summary_type"), data[include]), statistic = get_theme_element("TODO:fill-this-in", default = eval(formals(gtsummary::tbl_summary)[["statistic"]])), sort = @@ -256,7 +259,9 @@ tbl_summary <- function(data, # fill each element of digits argument # TODO: this needs to be updated to account for the scenario where there is a template override that may not fill in all the values if (!missing(digits)) { - digits <- assign_summary_digits(data[include], statistic, type, digits = digits) + digits <- + select_prep(.list2tb(type, "summary_type"), data[include]) |> + assign_summary_digits(statistic, type, digits = digits) } # check inputs --------------------------------------------------------------- @@ -271,7 +276,7 @@ tbl_summary <- function(data, ) # sort requested columns by frequency - data <- .sort_data_infreq(data, sort, type) + data <- .sort_data_infreq(data, sort) # save processed function inputs --------------------------------------------- tbl_summary_inputs <- as.list(environment()) @@ -297,7 +302,7 @@ tbl_summary <- function(data, }, # tabulate categorical summaries cards::ard_categorical( - data, + select_prep(.list2tb(type, "summary_type"), data), by = all_of(by), variables = all_categorical(FALSE), fmt_fn = digits, @@ -306,7 +311,7 @@ tbl_summary <- function(data, ), # tabulate dichotomous summaries cards::ard_dichotomous( - data, + select_prep(.list2tb(type, "summary_type"), data), by = all_of(by), variables = all_dichotomous(), fmt_fn = digits, @@ -316,11 +321,13 @@ tbl_summary <- function(data, ), # calculate categorical summaries cards::ard_continuous( - data, + select_prep(.list2tb(type, "summary_type"), data), by = all_of(by), variables = all_continuous(), statistic = - .continuous_statistics_chr_to_fun(statistic[select(data, all_continuous()) |> names()]), + .continuous_statistics_chr_to_fun( + statistic[select(select_prep(.list2tb(type, "summary_type"), data), all_continuous()) |> names()] + ), fmt_fn = digits, stat_label = ~ default_stat_labels() ) @@ -409,7 +416,7 @@ tbl_summary <- function(data, ) } -.sort_data_infreq <- function(data, sort, type) { +.sort_data_infreq <- function(data, sort) { # if no frequency sorts requested, just return data frame if (every(sort, function(x) x %in% "alphanumeric")) { return(data) @@ -421,7 +428,7 @@ tbl_summary <- function(data, } } - .add_summary_type_as_attr(data, type) + data } .construct_summary_footnote <- function(card, include, statistic, type) { diff --git a/man/dot-list2tb.Rd b/man/dot-list2tb.Rd new file mode 100644 index 0000000000..6601a72d61 --- /dev/null +++ b/man/dot-list2tb.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/select_helpers.R +\name{.list2tb} +\alias{.list2tb} +\title{Convert Named List to Table Body} +\usage{ +.list2tb(x, colname = caller_arg(x)) +} +\arguments{ +\item{x}{named list} + +\item{colname}{string of column name to assign. Default is \code{caller_arg(x)}} +} +\value{ +\code{.$table_body} data frame +} +\description{ +Many arguments in 'gtsummary' accept named lists. This function converts +a named list to the \code{.$table_body} format expected in \code{select_prep()} +} +\examples{ +type <- list(age = "continuous", response = "dichotomous") +gtsummary:::.list2tb(type, "summary_type") +} +\keyword{internal} diff --git a/man/select_prep.Rd b/man/select_prep.Rd new file mode 100644 index 0000000000..2623363331 --- /dev/null +++ b/man/select_prep.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/select_helpers.R +\name{select_prep} +\alias{select_prep} +\title{Table Body Select Prep} +\usage{ +select_prep(table_body, data = NULL) +} +\arguments{ +\item{table_body}{a data frame from \code{.$table_body}} + +\item{data}{an optional data frame the attributes will be added to} +} +\value{ +a data frame +} +\description{ +This function uses the information in \code{.$table_body} and adds them +as attributes to \code{data} (if passed). Once they've been assigned as +proper gtsummary attributes, gtsummary selectors like \code{all_continuous()} +will work properly. +} +\keyword{internal}