diff --git a/DESCRIPTION b/DESCRIPTION index abeb65ab40..d4bcf6e5f8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -122,6 +122,7 @@ Collate: 'h_step.R' 'h_survival_biomarkers_subgroups.R' 'h_survival_duration_subgroups.R' + 'imputation_rule.R' 'incidence_rate.R' 'individual_patient_plot.R' 'kaplan_meier_plot.R' diff --git a/NAMESPACE b/NAMESPACE index e1dadcff6a..026707e077 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -120,6 +120,7 @@ export(format_extreme_values_ci) export(format_fraction) export(format_fraction_fixed_dp) export(format_fraction_threshold) +export(format_sigfig) export(format_xx) export(g_forest) export(g_ipp) @@ -211,6 +212,7 @@ export(has_counts_difference) export(has_fraction_in_any_col) export(has_fraction_in_cols) export(has_fractions_difference) +export(imputation_rule) export(keep_content_rows) export(keep_rows) export(logistic_regression_cols) diff --git a/NEWS.md b/NEWS.md index bd1d7c197c..6d123d6996 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,12 @@ # tern 0.9.0.9001 +### New Features +* Added `imputation_rule` function to apply imputation rule to data. +* Added new format function `format_sigfig` to allow for numeric value formatting by a specified number of significant figures. + ### Enhancements * Refactored `tabulate_rsp_subgroups` to pass sanitation checks by preventing creation of degenerate subtables. +* Updated `analyze_vars_in_cols` to use caching, allow implementation of imputation rule via the `imp_rule` argument, and allow user to specify cell alignment via the `.aligns` argument. +* Updated `add_rowcounts` to allow addition of row counts from `alt_counts_df` using the `alt_counts` argument. # tern 0.9.0 ### New Features diff --git a/R/analyze_variables.R b/R/analyze_variables.R index d198ce999c..7033f3084e 100644 --- a/R/analyze_variables.R +++ b/R/analyze_variables.R @@ -271,7 +271,7 @@ s_summary.factor <- function(x, } ) - y$n_blq <- sum(grepl("BLQ|LTR|<[1-9]", x)) + y$n_blq <- sum(grepl("BLQ|LTR|<[1-9]| 1) { stop("When using do_summarize_row_groups only one label level var should be inserted.") @@ -208,10 +225,31 @@ analyze_vars_in_cols <- function(lyt, # Function list for do_summarize_row_groups. Slightly different handling of labels cfun_list <- Map( - function(stat) { - function(u, .spl_context, labelstr, ...) { + function(stat, use_cache, cache_env) { + function(u, .spl_context, labelstr, .df_row, ...) { # Statistic - res <- s_summary(u, ...)[[stat]] + var_row_val <- paste( + gsub("\\._\\[\\[[0-9]+\\]\\]_\\.", "", paste(tail(.spl_context$cur_col_split_val, 1)[[1]], collapse = "_")), + paste(.spl_context$value, collapse = "_"), + sep = "_" + ) + if (use_cache) { + if (is.null(cache_env[[var_row_val]])) cache_env[[var_row_val]] <- s_summary(u, ...) + x_stats <- cache_env[[var_row_val]] + } else { + x_stats <- s_summary(u, ...) + } + + if (is.null(imp_rule) || !stat %in% c("mean", "sd", "cv", "geom_mean", "geom_cv", "median", "min", "max")) { + res <- x_stats[[stat]] + } else { + res_imp <- imputation_rule( + .df_row, x_stats, stat, + imp_rule = imp_rule, post = as.numeric(tail(.spl_context$value, 1)) > 0, avalcat_var = avalcat_var + ) + res <- res_imp[["val"]] + na_level <- res_imp[["na_level"]] + } # Label check and replacement if (length(row_labels) > 1) { @@ -234,11 +272,14 @@ analyze_vars_in_cols <- function(lyt, label = lbl, format = formats_v[names(formats_v) == stat][[1]], format_na_str = na_level, - indent_mod = ifelse(is.null(.indent_mods), 0L, .indent_mods) + indent_mod = ifelse(is.null(.indent_mods), 0L, .indent_mods), + align = .aligns ) } }, - stat = .stats + stat = .stats, + use_cache = cache, + cache_env = replicate(length(.stats), env) ) # Main call to rtables @@ -251,10 +292,31 @@ analyze_vars_in_cols <- function(lyt, } else { # Function list for analyze_colvars afun_list <- Map( - function(stat) { - function(u, .spl_context, ...) { + function(stat, use_cache, cache_env) { + function(u, .spl_context, .df_row, ...) { # Main statistics - res <- s_summary(u, ...)[[stat]] + var_row_val <- paste( + gsub("\\._\\[\\[[0-9]+\\]\\]_\\.", "", paste(tail(.spl_context$cur_col_split_val, 1)[[1]], collapse = "_")), + paste(.spl_context$value, collapse = "_"), + sep = "_" + ) + if (use_cache) { + if (is.null(cache_env[[var_row_val]])) cache_env[[var_row_val]] <- s_summary(u, ...) + x_stats <- cache_env[[var_row_val]] + } else { + x_stats <- s_summary(u, ...) + } + + if (is.null(imp_rule) || !stat %in% c("mean", "sd", "cv", "geom_mean", "geom_cv", "median", "min", "max")) { + res <- x_stats[[stat]] + } else { + res_imp <- imputation_rule( + .df_row, x_stats, stat, + imp_rule = imp_rule, post = as.numeric(tail(.spl_context$value, 1)) > 0, avalcat_var = avalcat_var + ) + res <- res_imp[["val"]] + na_level <- res_imp[["na_level"]] + } if (is.list(res)) { if (length(res) > 1) { @@ -292,11 +354,14 @@ analyze_vars_in_cols <- function(lyt, label = lbl, format = formats_v[names(formats_v) == stat][[1]], format_na_str = na_level, - indent_mod = ifelse(is.null(.indent_mods), 0L, .indent_mods) + indent_mod = ifelse(is.null(.indent_mods), 0L, .indent_mods), + align = .aligns ) } }, - stat = .stats + stat = .stats, + use_cache = cache, + cache_env = replicate(length(.stats), env) ) # Main call to rtables diff --git a/R/argument_convention.R b/R/argument_convention.R index c8f0d839d2..1f36def62c 100644 --- a/R/argument_convention.R +++ b/R/argument_convention.R @@ -4,6 +4,8 @@ #' that are used repeatedly to express an analysis. #' #' @param ... additional arguments for the lower level functions. +#' @param .aligns (`character`)\cr alignment for table contents (not including labels). When `NULL`, `"center"` +#' is applied. See [formatters::list_valid_aligns()] for a list of all currently supported alignments. #' @param .all_col_counts (`vector` of `integer`)\cr each value represents a global count for a column. Values are #' taken from `alt_counts_df` if specified (see [rtables::build_table()]). #' @param .df_row (`data.frame`)\cr data frame across all of the columns for the given row split. diff --git a/R/formatting_functions.R b/R/formatting_functions.R index d98fc16e06..ec39143f0b 100644 --- a/R/formatting_functions.R +++ b/R/formatting_functions.R @@ -209,6 +209,33 @@ format_xx <- function(str) { return(rtable_format) } +#' Formatting Numeric Values By Significant Figures +#' +#' Format numeric values to print with a specified number of significant figures. +#' +#' @param sigfig (`integer`)\cr number of significant figures to display. +#' +#' @return An `rtables` formatting function. +#' +#' @examples +#' fmt_3sf <- format_sigfig(3) +#' fmt_3sf(1.658) +#' fmt_3sf(1e1) +#' +#' fmt_5sf <- format_sigfig(5) +#' fmt_5sf(0.57) +#' fmt_5sf(0.000025645) +#' +#' @family formatting functions +#' @export +format_sigfig <- function(sigfig) { + checkmate::assert_integerish(sigfig) + function(x, ...) { + if (!is.numeric(x)) stop("`format_sigfig` cannot be used for non-numeric values. Please choose another format.") + formatC(signif(x, digits = sigfig), digits = sigfig, format = "fg", flag = "#") + } +} + #' Formatting Fraction with Lower Threshold #' #' @description `r lifecycle::badge("stable")` diff --git a/R/imputation_rule.R b/R/imputation_rule.R new file mode 100644 index 0000000000..2a87b4ae85 --- /dev/null +++ b/R/imputation_rule.R @@ -0,0 +1,60 @@ +#' Apply 1/3 or 1/2 Imputation Rule to Data +#' +#' @description `r lifecycle::badge("stable")` +#' +#' @inheritParams argument_convention +#' @param x_stats (`named list`)\cr a named list of statistics, typically the results of [s_summary()]. +#' @param stat (`character`)\cr statistic to return the value/NA level of according to the imputation +#' rule applied. +#' @param imp_rule (`character`)\cr imputation rule setting. Set to `"1/3"` to implement 1/3 imputation +#' rule or `"1/2"` to implement 1/2 imputation rule. +#' @param post (`flag`)\cr whether the data corresponds to a post-dose time-point (defaults to `FALSE`). +#' This parameter is only used when `imp_rule` is set to `"1/3"`. +#' @param avalcat_var (`character`)\cr name of variable that indicates whether a row in `df` corresponds +#' to an analysis value in category `"BLQ"`, `"LTR"`, `" 1 / 3) { + if (stat != "geom_mean") na_level <- "ND" # 1/3_pre_GT, 1/3_post_GT + if (!post && !stat %in% c("median", "max")) val <- NA # 1/3_pre_GT + if (post && !stat %in% c("median", "max", "geom_mean")) val <- NA # 1/3_post_GT + } + } else if (imp_rule == "1/2") { + if (ltr_blq_ratio > 1 / 2 && !stat == "max") { + val <- NA # 1/2_GT + na_level <- "ND" # 1/2_GT + } + } + + list(val = val, na_level = na_level) +} diff --git a/R/utils_rtables.R b/R/utils_rtables.R index e600dc7f29..5cbcebd6d6 100644 --- a/R/utils_rtables.R +++ b/R/utils_rtables.R @@ -89,21 +89,54 @@ cfun_by_flag <- function(analysis_var, #' Content Row Function to Add Row Total to Labels #' -#' This takes the label of the latest row split level and adds the row total in parentheses. +#' This takes the label of the latest row split level and adds the row total from `df` in parentheses. +#' This function differs from [c_label_n_alt()] by taking row counts from `df` rather than +#' `alt_counts_df`, and is used by [add_rowcounts()] when `alt_counts` is set to `FALSE`. #' #' @inheritParams argument_convention #' -#' @return A `list` containing "row_count" with the row count value and the correct label. +#' @return A list with formatted [rtables::CellValue()] with the row count value and the correct label. #' #' @note It is important here to not use `df` but rather `.N_row` in the implementation, because #' the former is already split by columns and will refer to the first column of the data only. #' +#' @seealso [c_label_n_alt()] which performs the same function but retrieves row counts from +#' `alt_counts_df` instead of `df`. +#' #' @keywords internal c_label_n <- function(df, labelstr, .N_row) { # nolint label <- paste0(labelstr, " (N=", .N_row, ")") - list(row_count = formatters::with_label(c(.N_row, .N_row), label)) + in_rows( + .list = list(row_count = formatters::with_label(c(.N_row, .N_row), label)), + .formats = c(row_count = function(x, ...) "") + ) +} + +#' Content Row Function to Add `alt_counts_df` Row Total to Labels +#' +#' This takes the label of the latest row split level and adds the row total from `alt_counts_df` +#' in parentheses. This function differs from [c_label_n()] by taking row counts from `alt_counts_df` +#' rather than `df`, and is used by [add_rowcounts()] when `alt_counts` is set to `TRUE`. +#' +#' @inheritParams argument_convention +#' +#' @return A list with formatted [rtables::CellValue()] with the row count value and the correct label. +#' +#' @seealso [c_label_n()] which performs the same function but retrieves row counts from `df` instead +#' of `alt_counts_df`. +#' +#' @keywords internal +c_label_n_alt <- function(df, + labelstr, + .alt_df_row) { + N_row_alt <- nrow(.alt_df_row) # nolint + label <- paste0(labelstr, " (N=", N_row_alt, ")") + in_rows( + .list = list(row_count = formatters::with_label(c(N_row_alt, N_row_alt), label)), + .formats = c(row_count = function(x, ...) "") + ) } #' Layout Creating Function to Add Row Total Counts @@ -114,6 +147,8 @@ c_label_n <- function(df, #' is a wrapper for [rtables::summarize_row_groups()]. #' #' @inheritParams argument_convention +#' @param alt_counts (`flag`)\cr whether row counts should be taken from `alt_counts_df` (`TRUE`) +#' or from `df` (`FALSE`). Defaults to `FALSE`. #' #' @return A modified layout where the latest row split labels now have the row-wise #' total counts (i.e. without column-based subsetting) attached in parentheses. @@ -131,15 +166,10 @@ c_label_n <- function(df, #' build_table(DM) #' #' @export -add_rowcounts <- function(lyt) { - c_lbl_n_fun <- make_afun( - c_label_n, - .stats = c("row_count"), - .formats = c(row_count = function(x, ...) "") - ) +add_rowcounts <- function(lyt, alt_counts = FALSE) { summarize_row_groups( lyt, - cfun = c_lbl_n_fun + cfun = if (alt_counts) c_label_n_alt else c_label_n ) } diff --git a/_pkgdown.yml b/_pkgdown.yml index 2baa80d8ce..e1022acbee 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -68,6 +68,7 @@ reference: desc: These functions are useful in defining an analysis. contents: - starts_with("h_") + - imputation_rule - starts_with("or_") - starts_with("prop_") - summary_stats diff --git a/man/add_rowcounts.Rd b/man/add_rowcounts.Rd index 6d144b9375..cd69aa94d2 100644 --- a/man/add_rowcounts.Rd +++ b/man/add_rowcounts.Rd @@ -4,10 +4,13 @@ \alias{add_rowcounts} \title{Layout Creating Function to Add Row Total Counts} \usage{ -add_rowcounts(lyt) +add_rowcounts(lyt, alt_counts = FALSE) } \arguments{ \item{lyt}{(\code{layout})\cr input layout where analyses will be added to.} + +\item{alt_counts}{(\code{flag})\cr whether row counts should be taken from \code{alt_counts_df} (\code{TRUE}) +or from \code{df} (\code{FALSE}). Defaults to \code{FALSE}.} } \value{ A modified layout where the latest row split labels now have the row-wise diff --git a/man/analyze_vars_in_cols.Rd b/man/analyze_vars_in_cols.Rd index 5a6d7eaf3f..7593f1c00d 100644 --- a/man/analyze_vars_in_cols.Rd +++ b/man/analyze_vars_in_cols.Rd @@ -14,10 +14,14 @@ analyze_vars_in_cols( row_labels = NULL, do_summarize_row_groups = FALSE, split_col_vars = TRUE, + imp_rule = NULL, + avalcat_var = "AVALCAT1", + cache = FALSE, .indent_mods = NULL, nested = TRUE, na_level = NULL, - .formats = NULL + .formats = NULL, + .aligns = NULL ) } \arguments{ @@ -44,6 +48,20 @@ to define row labels. This behavior is not supported as we never need to overloa This option allows you to add multiple instances of this functions, also in a nested fashion, without adding more splits. This split must happen only one time on a single layout.} +\item{imp_rule}{(\code{character})\cr imputation rule setting. Defaults to \code{NULL} for no imputation rule. Can +also be \code{"1/3"} to implement 1/3 imputation rule or \code{"1/2"} to implement 1/2 imputation rule. In order +to use an imputation rule, the \code{avalcat_var} argument must be specified. See \code{\link[=imputation_rule]{imputation_rule()}} +for more details on imputation.} + +\item{avalcat_var}{(\code{character})\cr if \code{imp_rule} is not \code{NULL}, name of variable that indicates whether a +row in the data corresponds to an analysis value in category \code{"BLQ"}, \code{"LTR"}, \code{"% build_table(adpp)) }) + +testthat::test_that("analyze_vars_in_cols works with imputation rule", { + set.seed(1) + df <- data.frame( + ARM = with_label(rep("A: Drug X", 162), "Arm"), + AVAL = runif(162, 0, 100), + AVALCAT1 = as.factor(sample(c(1, "BLQ"), 162, replace = TRUE)), + AVALCAT2 = as.factor(sample(c(1, "BLQ"), 162, replace = TRUE, prob = c(0.25, 0.75))), + VISIT = with_label(as.factor(rep(c(rep("Day 1", 5), rep("Day 2", 4)), 18)), "Visit"), + NFRLT = with_label(as.factor(rep(c(0, seq(0, 42, 6)), 18)), "Nominal Time") + ) + + # 1/3 imputation rule + lyt <- basic_table() %>% + split_rows_by( + var = "ARM", + split_fun = drop_split_levels + ) %>% + split_rows_by( + var = "VISIT", + split_fun = drop_split_levels + ) %>% + split_rows_by( + var = "NFRLT", + split_fun = drop_split_levels, + child_labels = "hidden" + ) %>% + analyze_vars_in_cols( + vars = c("AVAL", "AVALCAT1", rep("AVAL", 5)), + .stats = c("n", "n_blq", "mean", "sd", "geom_mean", "min", "max"), + .labels = c( + n = "n", n_blq = "Number of BLQs", mean = "Mean", sd = "SD", + geom_mean = "Geometric Mean", min = "Minimum", max = "Maximum" + ), + imp_rule = "1/3" + ) + + result <- build_table(lyt = lyt, df = df) + + res <- testthat::expect_silent(result) + testthat::expect_snapshot(res) + + # 1/3 imputation rule, custom avalcat_var + lyt <- basic_table() %>% + split_rows_by( + var = "ARM", + split_fun = drop_split_levels + ) %>% + split_rows_by( + var = "VISIT", + split_fun = drop_split_levels + ) %>% + split_rows_by( + var = "NFRLT", + split_fun = drop_split_levels, + child_labels = "hidden" + ) %>% + analyze_vars_in_cols( + vars = c("AVAL", "AVALCAT2", rep("AVAL", 5)), + .stats = c("n", "n_blq", "mean", "sd", "geom_mean", "min", "max"), + .labels = c( + n = "n", n_blq = "Number of BLQs", mean = "Mean", sd = "SD", + geom_mean = "Geometric Mean", min = "Minimum", max = "Maximum" + ), + imp_rule = "1/3", + avalcat_var = "AVALCAT2" + ) + + result <- build_table(lyt = lyt, df = df) + + res <- testthat::expect_silent(result) + testthat::expect_snapshot(res) + + # 1/2 imputation rule + lyt <- basic_table() %>% + split_rows_by( + var = "ARM", + split_fun = drop_split_levels + ) %>% + split_rows_by( + var = "VISIT", + split_fun = drop_split_levels + ) %>% + split_rows_by( + var = "NFRLT", + split_fun = drop_split_levels, + child_labels = "hidden" + ) %>% + analyze_vars_in_cols( + vars = c("AVAL", "AVALCAT1", rep("AVAL", 5)), + .stats = c("n", "n_blq", "mean", "sd", "geom_mean", "min", "max"), + .labels = c( + n = "n", n_blq = "Number of BLQs", mean = "Mean", sd = "SD", + geom_mean = "Geometric Mean", min = "Minimum", max = "Maximum" + ), + imp_rule = "1/2" + ) + + result <- build_table(lyt = lyt, df = df) + + res <- testthat::expect_silent(result) + testthat::expect_snapshot(res) +}) + +testthat::test_that("analyze_vars_in_cols works with caching", { + set.seed(1) + df <- data.frame( + ARM = with_label(rep("A: Drug X", 162), "Arm"), + AVAL = runif(162, 0, 100), + AVALCAT1 = as.factor(sample(c(1, "BLQ"), 162, replace = TRUE)), + VISIT = with_label(as.factor(rep(c(rep("Day 1", 5), rep("Day 2", 4)), 18)), "Visit"), + NFRLT = with_label(as.factor(rep(c(0, seq(0, 42, 6)), 18)), "Nominal Time") + ) + + lyt <- basic_table() %>% + split_rows_by( + var = "ARM", + split_fun = drop_split_levels + ) %>% + split_rows_by( + var = "VISIT", + split_fun = drop_split_levels + ) %>% + split_rows_by( + var = "NFRLT", + split_fun = drop_split_levels, + child_labels = "hidden" + ) %>% + analyze_vars_in_cols( + vars = c("AVAL", "AVALCAT1", rep("AVAL", 5)), + .stats = c("n", "n_blq", "mean", "sd", "geom_mean", "min", "max"), + .labels = c( + n = "n", n_blq = "Number of BLQs", mean = "Mean", sd = "SD", + geom_mean = "Geometric Mean", min = "Minimum", max = "Maximum" + ), + cache = TRUE + ) + + result <- build_table(lyt = lyt, df = df) + + res <- testthat::expect_silent(result) + testthat::expect_snapshot(res) +}) diff --git a/tests/testthat/test-formats.R b/tests/testthat/test-formats.R index ff66572576..4fc1ea1c9c 100644 --- a/tests/testthat/test-formats.R +++ b/tests/testthat/test-formats.R @@ -92,6 +92,15 @@ testthat::test_that("format_xx works with easy inputs", { testthat::expect_snapshot(res) }) +testthat::test_that("format_sigfig works with easy inputs", { + test <- list(1.658, 0.5761, 1e-1, 78.6, 1234e-6) + z <- format_sigfig(3) + result <- sapply(test, z) + + res <- testthat::expect_silent(result) + testthat::expect_snapshot(res) +}) + testthat::test_that("format_fraction_threshold works with easy inputs", { test <- list(c(100, 0.1), c(10, 0.01), c(0, 0)) format_fun <- format_fraction_threshold(0.02) diff --git a/tests/testthat/test-imputation_rule.R b/tests/testthat/test-imputation_rule.R new file mode 100644 index 0000000000..81003526ab --- /dev/null +++ b/tests/testthat/test-imputation_rule.R @@ -0,0 +1,44 @@ +set.seed(1) +df <- data.frame( + ARM = with_label(rep("A: Drug X", 162), "Arm"), + AVAL = runif(162, 0, 100), + AVALCAT1 = as.factor(sample(c(1, "BLQ"), 162, replace = TRUE)), + VISIT = with_label(as.factor(rep(c(rep("Day 1", 5), rep("Day 2", 4)), 18)), "Visit"), + NFRLT = with_label(as.factor(rep(c(0, seq(0, 42, 6)), 18)), "Nominal Time") +) + +testthat::test_that("imputation_rule works correctly for 1/3 imputation rule", { + x_stats <- s_summary(df$AVAL[df$NFRLT == 0]) + result <- imputation_rule(df, x_stats, "max", "1/3") + res <- testthat::expect_snapshot(result) + + x_stats <- s_summary(df$AVAL[df$NFRLT == 0]) + result <- imputation_rule(df, x_stats, "mean", "1/3") + res <- testthat::expect_snapshot(result) + + x_stats <- s_summary(df$AVAL[df$NFRLT == 6]) + result <- imputation_rule(df, x_stats, "geom_mean", "1/3", post = TRUE) + res <- testthat::expect_snapshot(result) + + x_stats <- s_summary(df$AVAL[df$NFRLT == 18]) + result <- imputation_rule(df, x_stats, "max", "1/3", post = TRUE) + res <- testthat::expect_snapshot(result) +}) + +testthat::test_that("imputation_rule works correctly for 1/2 imputation rule", { + x_stats <- s_summary(df$AVAL[df$NFRLT == 0]) + result <- imputation_rule(df, x_stats, "max", "1/2") + res <- testthat::expect_snapshot(result) + + x_stats <- s_summary(df$AVAL[df$NFRLT == 0]) + result <- imputation_rule(df, x_stats, "mean", "1/2") + res <- testthat::expect_snapshot(result) + + x_stats <- s_summary(df$AVAL[df$NFRLT == 6]) + result <- imputation_rule(df, x_stats, "geom_mean", "1/2", post = TRUE) + res <- testthat::expect_snapshot(result) + + x_stats <- s_summary(df$AVAL[df$NFRLT == 18]) + result <- imputation_rule(df, x_stats, "max", "1/2", post = TRUE) + res <- testthat::expect_snapshot(result) +}) diff --git a/tests/testthat/test-utils_rtables.R b/tests/testthat/test-utils_rtables.R index 7a82b465bc..5e5300816e 100644 --- a/tests/testthat/test-utils_rtables.R +++ b/tests/testthat/test-utils_rtables.R @@ -87,6 +87,13 @@ testthat::test_that("c_label_n works as expected", { testthat::expect_snapshot(res) }) +testthat::test_that("c_label_n_alt works as expected", { + result <- c_label_n_alt(data.frame(a = c(1, 2)), "female", .alt_df_row = data.frame(a = 1:10)) + + res <- testthat::expect_silent(result) + testthat::expect_snapshot(res) +}) + testthat::test_that("add_rowcounts works with one row split", { result <- basic_table() %>% split_rows_by("SEX", split_fun = drop_split_levels) %>% @@ -137,6 +144,21 @@ testthat::test_that("add_rowcounts works with pruning", { testthat::expect_snapshot(res) }) +testthat::test_that("add_rowcounts works with alt_counts = TRUE", { + DM_alt <- DM[1:100, ] # nolint + + result <- basic_table() %>% + split_cols_by("ARM") %>% + split_rows_by("SEX", split_fun = drop_split_levels) %>% + add_rowcounts(alt_counts = TRUE) %>% + analyze("RACE") %>% + build_table(DM, alt_counts_df = DM_alt) %>% + prune_table() + + res <- testthat::expect_silent(result) + testthat::expect_snapshot(res) +}) + testthat::test_that("h_col_indices works as expected", { tab <- basic_table() %>% split_cols_by("ARM") %>%