From 1a935d9d5ad21c1f6c46bf35974b0b14394c62a9 Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Wed, 30 Aug 2023 18:39:16 -0400 Subject: [PATCH 01/27] Change formula for geom_mean --- R/analyze_variables.R | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/R/analyze_variables.R b/R/analyze_variables.R index d198ce999c..c0f44b0307 100644 --- a/R/analyze_variables.R +++ b/R/analyze_variables.R @@ -186,7 +186,11 @@ s_summary.numeric <- function(x, # Convert negative values to NA for log calculation. x_no_negative_vals <- x x_no_negative_vals[x_no_negative_vals <= 0] <- NA - y$geom_mean <- c("geom_mean" = exp(mean(log(x_no_negative_vals), na.rm = FALSE))) + y$geom_mean <- if (all(x == 0) && length(x) > 0) { + c("geom_mean" = 0) + } else { + c("geom_mean" = exp(mean(log(x_no_negative_vals), na.rm = FALSE))) + } geom_mean_ci <- stat_mean_ci(x, conf_level = control$conf_level, na.rm = FALSE, gg_helper = FALSE, geom_mean = TRUE) y$geom_mean_ci <- formatters::with_label(geom_mean_ci, paste("Geometric Mean", f_conf_level(control$conf_level))) From 6dbb8209f83c06ac1f87462d86f40bf1fa899bb8 Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Wed, 30 Aug 2023 18:39:43 -0400 Subject: [PATCH 02/27] Add imputation rule function --- DESCRIPTION | 1 + NAMESPACE | 1 + R/imputation_rule.R | 27 +++++++++++++++++++++++++++ man/imputation_rule.Rd | 11 +++++++++++ 4 files changed, 40 insertions(+) create mode 100644 R/imputation_rule.R create mode 100644 man/imputation_rule.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 1949e55f72..75aaf7a27b 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 54f9aef782..bd0b9ae0d9 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -210,6 +210,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/R/imputation_rule.R b/R/imputation_rule.R new file mode 100644 index 0000000000..dba7514ad2 --- /dev/null +++ b/R/imputation_rule.R @@ -0,0 +1,27 @@ +#' Apply 1/3 or 1/2 Imputation Rule to Data +#' +#' @export +imputation_rule <- function(df, x_stats, stat, imp, post = FALSE) { + n_blq <- sum(df$AVALCAT1 %in% c("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 == "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/man/imputation_rule.Rd b/man/imputation_rule.Rd new file mode 100644 index 0000000000..19899faea7 --- /dev/null +++ b/man/imputation_rule.Rd @@ -0,0 +1,11 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/imputation_rule.R +\name{imputation_rule} +\alias{imputation_rule} +\title{Apply 1/3 or 1/2 Imputation Rule to Data} +\usage{ +imputation_rule(df, x_stats, stat, imp, post = FALSE) +} +\description{ +Apply 1/3 or 1/2 Imputation Rule to Data +} From 60ad7370a30595e51dbb4ed6f35bcc3137ab244d Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Wed, 30 Aug 2023 18:40:05 -0400 Subject: [PATCH 03/27] Refactor analyze_vars_in_cols to allow imputation rule --- R/analyze_vars_in_cols.R | 54 ++++++++++++++++++++++++++++++------- man/analyze_vars_in_cols.Rd | 9 ++++++- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/R/analyze_vars_in_cols.R b/R/analyze_vars_in_cols.R index 91eea31965..1fe8f817c0 100644 --- a/R/analyze_vars_in_cols.R +++ b/R/analyze_vars_in_cols.R @@ -8,6 +8,10 @@ #' #' @inheritParams argument_convention #' @inheritParams rtables::analyze_colvars +#' @param imp_rule (`character`)\cr imputation rule setting. Defaults to `NULL` for no imputation rule. Can +#' also be `"1/3"` to implement 1/3 imputation rule or `"1/2"` to implement 1/2 imputation rule. In order +#' to use an imputation rule, the `AVALCAT1` variable must be present in the data. See [imputation_rule()] +#' for more details on imputation. #' @param row_labels (`character`)\cr as this function works in columns space, usual `.labels` #' character vector applies on the column space. You can change the row labels by defining this #' parameter to a named character vector with names corresponding to the split values. It defaults @@ -143,10 +147,12 @@ analyze_vars_in_cols <- function(lyt, row_labels = NULL, do_summarize_row_groups = FALSE, split_col_vars = TRUE, + imp_rule = NULL, .indent_mods = NULL, nested = TRUE, na_level = NULL, - .formats = NULL) { + .formats = NULL, + .aligns = NULL) { checkmate::assert_string(na_level, null.ok = TRUE) checkmate::assert_character(row_labels, null.ok = TRUE) checkmate::assert_int(.indent_mods, null.ok = TRUE) @@ -175,13 +181,16 @@ analyze_vars_in_cols <- function(lyt, } if (split_col_vars) { + env <- new.env() # create caching environment + # Checking there is not a previous identical column split clyt <- tail(clayout(lyt), 1)[[1]] dummy_lyt <- split_cols_by_multivar( lyt = basic_table(), vars = vars, - varlabels = .labels[.stats] + varlabels = .labels[.stats], + extra_args = list(cache_env = replicate(length(.stats), list(env))) ) if (any(sapply(clyt, identical, y = get_last_col_split(dummy_lyt)))) { @@ -197,7 +206,8 @@ analyze_vars_in_cols <- function(lyt, lyt <- split_cols_by_multivar( lyt = lyt, vars = vars, - varlabels = .labels[.stats] + varlabels = .labels[.stats], + extra_args = list(cache_env = replicate(length(.stats), list(env))) ) } @@ -209,9 +219,21 @@ 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(u, .spl_context, labelstr, .df_row, .var, cache_env = NULL, ...) { # Statistic - res <- s_summary(u, ...)[[stat]] + var_row_val <- paste(.var, tail(.spl_context$value, 1), sep = "_") + if (is.null(cache_env[[var_row_val]])) cache_env[[var_row_val]] <- s_summary(u, ...) + x_stats <- cache_env[[var_row_val]] + + 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 = imp_rule, post = as.numeric(tail(.spl_context$value, 1)) > 0 + ) + res <- res_imp[["val"]] + na_level <- res_imp[["na_level"]] + } # Label check and replacement if (length(row_labels) > 1) { @@ -234,7 +256,8 @@ 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 ) } }, @@ -252,9 +275,21 @@ analyze_vars_in_cols <- function(lyt, # Function list for analyze_colvars afun_list <- Map( function(stat) { - function(u, .spl_context, ...) { + function(u, .spl_context, .df_row, .var, cache_env = NULL, ...) { # Main statistics - res <- s_summary(u, ...)[[stat]] + var_row_val <- paste(.var, tail(.spl_context$value, 1), sep = "_") + if (is.null(cache_env[[var_row_val]])) cache_env[[var_row_val]] <- s_summary(u, ...) + x_stats <- cache_env[[var_row_val]] + + 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 = imp_rule, post = as.numeric(tail(.spl_context$value, 1)) > 0 + ) + res <- res_imp[["val"]] + na_level <- res_imp[["na_level"]] + } if (is.list(res)) { if (length(res) > 1) { @@ -292,7 +327,8 @@ 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 ) } }, diff --git a/man/analyze_vars_in_cols.Rd b/man/analyze_vars_in_cols.Rd index 5a6d7eaf3f..aa5f2c6330 100644 --- a/man/analyze_vars_in_cols.Rd +++ b/man/analyze_vars_in_cols.Rd @@ -14,10 +14,12 @@ analyze_vars_in_cols( row_labels = NULL, do_summarize_row_groups = FALSE, split_col_vars = TRUE, + imp_rule = NULL, .indent_mods = NULL, nested = TRUE, na_level = NULL, - .formats = NULL + .formats = NULL, + .aligns = NULL ) } \arguments{ @@ -44,6 +46,11 @@ 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{AVALCAT1} variable must be present in the data. See \code{\link[=imputation_rule]{imputation_rule()}} +for more details on imputation.} + \item{.indent_mods}{(named \code{integer})\cr indent modifiers for the labels. Defaults to 0, which corresponds to the unmodified default behavior. Can be negative.} From 5a1d25ec0b1a9bc7c33aa43a76880b95e982d497 Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Wed, 30 Aug 2023 18:40:21 -0400 Subject: [PATCH 04/27] Add formatting function for significant figures --- NAMESPACE | 1 + R/formatting_functions.R | 29 +++++++++++++++++ man/extreme_format.Rd | 1 + man/format_count_fraction.Rd | 1 + man/format_count_fraction_fixed_dp.Rd | 1 + man/format_extreme_values.Rd | 1 + man/format_extreme_values_ci.Rd | 1 + man/format_fraction.Rd | 1 + man/format_fraction_fixed_dp.Rd | 1 + man/format_fraction_threshold.Rd | 1 + man/format_sigfig.Rd | 45 +++++++++++++++++++++++++++ man/format_xx.Rd | 1 + man/formatting_functions.Rd | 1 + 13 files changed, 85 insertions(+) create mode 100644 man/format_sigfig.Rd diff --git a/NAMESPACE b/NAMESPACE index bd0b9ae0d9..fc451faea8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -119,6 +119,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) diff --git a/R/formatting_functions.R b/R/formatting_functions.R index d98fc16e06..8a0f678fda 100644 --- a/R/formatting_functions.R +++ b/R/formatting_functions.R @@ -209,6 +209,35 @@ 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. +#' @param x (`numeric`)\cr value. +#' @param ... required for `rtables` interface. +#' +#' @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.") + as.character(signif(x, sigfig)) + } +} + #' Formatting Fraction with Lower Threshold #' #' @description `r lifecycle::badge("stable")` diff --git a/man/extreme_format.Rd b/man/extreme_format.Rd index db8b6c1c3f..6fe7b73830 100644 --- a/man/extreme_format.Rd +++ b/man/extreme_format.Rd @@ -62,6 +62,7 @@ Other formatting functions: \code{\link{format_fraction_fixed_dp}()}, \code{\link{format_fraction_threshold}()}, \code{\link{format_fraction}()}, +\code{\link{format_sigfig}()}, \code{\link{format_xx}()}, \code{\link{formatting_functions}} } diff --git a/man/format_count_fraction.Rd b/man/format_count_fraction.Rd index d4f67af8e3..c64338236a 100644 --- a/man/format_count_fraction.Rd +++ b/man/format_count_fraction.Rd @@ -33,6 +33,7 @@ Other formatting functions: \code{\link{format_fraction_fixed_dp}()}, \code{\link{format_fraction_threshold}()}, \code{\link{format_fraction}()}, +\code{\link{format_sigfig}()}, \code{\link{format_xx}()}, \code{\link{formatting_functions}} } diff --git a/man/format_count_fraction_fixed_dp.Rd b/man/format_count_fraction_fixed_dp.Rd index b625a649d2..4f6e877f0b 100644 --- a/man/format_count_fraction_fixed_dp.Rd +++ b/man/format_count_fraction_fixed_dp.Rd @@ -34,6 +34,7 @@ Other formatting functions: \code{\link{format_fraction_fixed_dp}()}, \code{\link{format_fraction_threshold}()}, \code{\link{format_fraction}()}, +\code{\link{format_sigfig}()}, \code{\link{format_xx}()}, \code{\link{formatting_functions}} } diff --git a/man/format_extreme_values.Rd b/man/format_extreme_values.Rd index 1efddac5b3..55d59077e5 100644 --- a/man/format_extreme_values.Rd +++ b/man/format_extreme_values.Rd @@ -34,6 +34,7 @@ Other formatting functions: \code{\link{format_fraction_fixed_dp}()}, \code{\link{format_fraction_threshold}()}, \code{\link{format_fraction}()}, +\code{\link{format_sigfig}()}, \code{\link{format_xx}()}, \code{\link{formatting_functions}} } diff --git a/man/format_extreme_values_ci.Rd b/man/format_extreme_values_ci.Rd index b12660ccc2..5aea87b518 100644 --- a/man/format_extreme_values_ci.Rd +++ b/man/format_extreme_values_ci.Rd @@ -34,6 +34,7 @@ Other formatting functions: \code{\link{format_fraction_fixed_dp}()}, \code{\link{format_fraction_threshold}()}, \code{\link{format_fraction}()}, +\code{\link{format_sigfig}()}, \code{\link{format_xx}()}, \code{\link{formatting_functions}} } diff --git a/man/format_fraction.Rd b/man/format_fraction.Rd index 2048fbf2b2..355d014a6e 100644 --- a/man/format_fraction.Rd +++ b/man/format_fraction.Rd @@ -33,6 +33,7 @@ Other formatting functions: \code{\link{format_extreme_values}()}, \code{\link{format_fraction_fixed_dp}()}, \code{\link{format_fraction_threshold}()}, +\code{\link{format_sigfig}()}, \code{\link{format_xx}()}, \code{\link{formatting_functions}} } diff --git a/man/format_fraction_fixed_dp.Rd b/man/format_fraction_fixed_dp.Rd index 69a8e30e8b..d49d3f6423 100644 --- a/man/format_fraction_fixed_dp.Rd +++ b/man/format_fraction_fixed_dp.Rd @@ -35,6 +35,7 @@ Other formatting functions: \code{\link{format_extreme_values}()}, \code{\link{format_fraction_threshold}()}, \code{\link{format_fraction}()}, +\code{\link{format_sigfig}()}, \code{\link{format_xx}()}, \code{\link{formatting_functions}} } diff --git a/man/format_fraction_threshold.Rd b/man/format_fraction_threshold.Rd index deabe3702d..eb8b27982c 100644 --- a/man/format_fraction_threshold.Rd +++ b/man/format_fraction_threshold.Rd @@ -37,6 +37,7 @@ Other formatting functions: \code{\link{format_extreme_values}()}, \code{\link{format_fraction_fixed_dp}()}, \code{\link{format_fraction}()}, +\code{\link{format_sigfig}()}, \code{\link{format_xx}()}, \code{\link{formatting_functions}} } diff --git a/man/format_sigfig.Rd b/man/format_sigfig.Rd new file mode 100644 index 0000000000..96e4bf14c8 --- /dev/null +++ b/man/format_sigfig.Rd @@ -0,0 +1,45 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/formatting_functions.R +\name{format_sigfig} +\alias{format_sigfig} +\title{Formatting Numeric Values By Significant Figures} +\usage{ +format_sigfig(sigfig) +} +\arguments{ +\item{sigfig}{(\code{integer})\cr number of significant figures to display.} + +\item{x}{(\code{numeric})\cr value.} + +\item{...}{required for \code{rtables} interface.} +} +\value{ +An \code{rtables} formatting function. +} +\description{ +Format numeric values to print with a specified number of significant figures. +} +\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) + +} +\seealso{ +Other formatting functions: +\code{\link{extreme_format}}, +\code{\link{format_count_fraction_fixed_dp}()}, +\code{\link{format_count_fraction}()}, +\code{\link{format_extreme_values_ci}()}, +\code{\link{format_extreme_values}()}, +\code{\link{format_fraction_fixed_dp}()}, +\code{\link{format_fraction_threshold}()}, +\code{\link{format_fraction}()}, +\code{\link{format_xx}()}, +\code{\link{formatting_functions}} +} +\concept{formatting functions} diff --git a/man/format_xx.Rd b/man/format_xx.Rd index db9b1be013..5da74756b1 100644 --- a/man/format_xx.Rd +++ b/man/format_xx.Rd @@ -39,6 +39,7 @@ Other formatting functions: \code{\link{format_fraction_fixed_dp}()}, \code{\link{format_fraction_threshold}()}, \code{\link{format_fraction}()}, +\code{\link{format_sigfig}()}, \code{\link{formatting_functions}} } \concept{formatting functions} diff --git a/man/formatting_functions.Rd b/man/formatting_functions.Rd index 253d630cd8..3a31dede96 100644 --- a/man/formatting_functions.Rd +++ b/man/formatting_functions.Rd @@ -21,6 +21,7 @@ Other formatting functions: \code{\link{format_fraction_fixed_dp}()}, \code{\link{format_fraction_threshold}()}, \code{\link{format_fraction}()}, +\code{\link{format_sigfig}()}, \code{\link{format_xx}()} } \concept{formatting functions} From 79c0cb233566e27ad80436d8f7961ee38676d9bb Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Wed, 30 Aug 2023 19:34:48 -0400 Subject: [PATCH 05/27] Update docs --- R/argument_convention.R | 2 ++ R/imputation_rule.R | 35 ++++++++++++++++++++++++++++++++--- man/analyze_vars_in_cols.Rd | 3 +++ man/argument_convention.Rd | 3 +++ man/imputation_rule.Rd | 36 ++++++++++++++++++++++++++++++++++-- 5 files changed, 74 insertions(+), 5 deletions(-) diff --git a/R/argument_convention.R b/R/argument_convention.R index eb6b248034..e64af13831 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 .df_row (`data.frame`)\cr data frame across all of the columns for the given row split. #' @param .in_ref_col (`logical`)\cr `TRUE` when working with the reference level, `FALSE` otherwise. #' @param .N_col (`integer`)\cr column-wise N (column count) for the full column being analyzed that is typically diff --git a/R/imputation_rule.R b/R/imputation_rule.R index dba7514ad2..ab5bde7249 100644 --- a/R/imputation_rule.R +++ b/R/imputation_rule.R @@ -1,7 +1,36 @@ #' Apply 1/3 or 1/2 Imputation Rule to Data #' +#' @description `r lifecycle::badge("stable")` +#' +#' @inheritParams argument_convention +#' @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 timepoint (defaults to `FALSE`). +#' This parameter is only used when `imp_rule` is set to `"1/3"`. +#' +#' @note `AVALCAT1` must be present in `df`. +#' +#' @return A `list` containing statistic value (`val`) and NA level (`na_level`) that should be displayed +#' according to the specified imputation rule. +#' +#' @seealso [analyze_vars_in_cols()] where this function can be implemented by setting the `imp_rule` +#' argument. +#' +#' @examples +#' set.seed(1) +#' df <- data.frame( +#' AVAL = runif(50, 0, 1), +#' AVALCAT1 = sample(c(1, "BLQ"), 50, replace = TRUE, prob = c(0.5, 0.5)) +#' ) +#' x_stats <- s_summary(df$AVAL) +#' imputation_rule(df, x_stats, "max", "1/3") +#' imputation_rule(df, x_stats, "geom_mean", "1/3") +#' imputation_rule(df, x_stats, "mean", "1/2") +#' #' @export -imputation_rule <- function(df, x_stats, stat, imp, post = FALSE) { +imputation_rule <- function(df, x_stats, stat, imp_rule, post = FALSE) { + checkmate::assert_choice("AVALCAT1", names(df)) + checkmate::assert_choice(imp_rule, c("1/3", "1/2")) n_blq <- sum(df$AVALCAT1 %in% c("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 == "1/2") { + } 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 diff --git a/man/analyze_vars_in_cols.Rd b/man/analyze_vars_in_cols.Rd index aa5f2c6330..d82653c7bb 100644 --- a/man/analyze_vars_in_cols.Rd +++ b/man/analyze_vars_in_cols.Rd @@ -61,6 +61,9 @@ underneath analyses, which is not allowed.} \item{na_level}{(\code{string})\cr string used to replace all \code{NA} or empty values in the output.} \item{.formats}{(named \code{character} or \code{list})\cr formats for the statistics.} + +\item{.aligns}{(\code{character})\cr alignment for table contents (not including labels). When \code{NULL}, \code{"center"} +is applied. See \code{\link[formatters:list_formats]{formatters::list_valid_aligns()}} for a list of all currently supported alignments.} } \value{ A layout object suitable for passing to further layouting functions, or to \code{\link[rtables:build_table]{rtables::build_table()}}. diff --git a/man/argument_convention.Rd b/man/argument_convention.Rd index cd34bd9ad4..29cc3c4e97 100644 --- a/man/argument_convention.Rd +++ b/man/argument_convention.Rd @@ -6,6 +6,9 @@ \arguments{ \item{...}{additional arguments for the lower level functions.} +\item{.aligns}{(\code{character})\cr alignment for table contents (not including labels). When \code{NULL}, \code{"center"} +is applied. See \code{\link[formatters:list_formats]{formatters::list_valid_aligns()}} for a list of all currently supported alignments.} + \item{.df_row}{(\code{data.frame})\cr data frame across all of the columns for the given row split.} \item{.in_ref_col}{(\code{logical})\cr \code{TRUE} when working with the reference level, \code{FALSE} otherwise.} diff --git a/man/imputation_rule.Rd b/man/imputation_rule.Rd index 19899faea7..22f6ac1e2d 100644 --- a/man/imputation_rule.Rd +++ b/man/imputation_rule.Rd @@ -4,8 +4,40 @@ \alias{imputation_rule} \title{Apply 1/3 or 1/2 Imputation Rule to Data} \usage{ -imputation_rule(df, x_stats, stat, imp, post = FALSE) +imputation_rule(df, x_stats, stat, imp_rule, post = FALSE) +} +\arguments{ +\item{df}{(\code{data.frame})\cr data set containing all analysis variables.} + +\item{imp_rule}{(\code{character})\cr imputation rule setting. Set to \code{"1/3"} to implement 1/3 imputation +rule or \code{"1/2"} to implement 1/2 imputation rule.} + +\item{post}{(\code{flag})\cr whether the data corresponds to a post-dose timepoint (defaults to \code{FALSE}). +This parameter is only used when \code{imp_rule} is set to \code{"1/3"}.} +} +\value{ +A \code{list} containing statistic value (\code{val}) and NA level (\code{na_level}) that should be displayed +according to the specified imputation rule. } \description{ -Apply 1/3 or 1/2 Imputation Rule to Data +\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#stable}{\figure{lifecycle-stable.svg}{options: alt='[Stable]'}}}{\strong{[Stable]}} +} +\note{ +\code{AVALCAT1} must be present in \code{df}. +} +\examples{ +set.seed(1) +df <- data.frame( + AVAL = runif(50, 0, 1), + AVALCAT1 = sample(c(1, "BLQ"), 50, replace = TRUE, prob = c(0.5, 0.5)) +) +x_stats <- s_summary(df$AVAL) +imputation_rule(df, x_stats, "max", "1/3") +imputation_rule(df, x_stats, "geom_mean", "1/3") +imputation_rule(df, x_stats, "mean", "1/2") + +} +\seealso{ +\code{\link[=analyze_vars_in_cols]{analyze_vars_in_cols()}} where this function can be implemented by setting the \code{imp_rule} +argument. } From c25890b93dba1586fd20d04a71058288b199bd2d Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Wed, 30 Aug 2023 21:04:44 -0400 Subject: [PATCH 06/27] Add tests, update analyze_vars_in_cols --- R/analyze_vars_in_cols.R | 16 ++- R/imputation_rule.R | 2 +- tests/testthat/_snaps/analyze_vars_in_cols.md | 38 +++++++ tests/testthat/_snaps/formats.md | 7 ++ tests/testthat/_snaps/imputation_rule.md | 103 ++++++++++++++++++ tests/testthat/test-analyze_vars_in_cols.R | 72 ++++++++++++ tests/testthat/test-formats.R | 9 ++ tests/testthat/test-imputation_rule.R | 44 ++++++++ 8 files changed, 286 insertions(+), 5 deletions(-) create mode 100644 tests/testthat/_snaps/imputation_rule.md create mode 100644 tests/testthat/test-imputation_rule.R diff --git a/R/analyze_vars_in_cols.R b/R/analyze_vars_in_cols.R index 1fe8f817c0..a5cec14521 100644 --- a/R/analyze_vars_in_cols.R +++ b/R/analyze_vars_in_cols.R @@ -219,9 +219,13 @@ 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, .df_row, .var, cache_env = NULL, ...) { + function(u, .spl_context, labelstr, .df_row, cache_env = NULL, ...) { # Statistic - var_row_val <- paste(.var, tail(.spl_context$value, 1), sep = "_") + 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 (is.null(cache_env[[var_row_val]])) cache_env[[var_row_val]] <- s_summary(u, ...) x_stats <- cache_env[[var_row_val]] @@ -275,9 +279,13 @@ analyze_vars_in_cols <- function(lyt, # Function list for analyze_colvars afun_list <- Map( function(stat) { - function(u, .spl_context, .df_row, .var, cache_env = NULL, ...) { + function(u, .spl_context, .df_row, cache_env = NULL, ...) { # Main statistics - var_row_val <- paste(.var, tail(.spl_context$value, 1), sep = "_") + 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 (is.null(cache_env[[var_row_val]])) cache_env[[var_row_val]] <- s_summary(u, ...) x_stats <- cache_env[[var_row_val]] diff --git a/R/imputation_rule.R b/R/imputation_rule.R index ab5bde7249..e182ca46d2 100644 --- a/R/imputation_rule.R +++ b/R/imputation_rule.R @@ -20,7 +20,7 @@ #' set.seed(1) #' df <- data.frame( #' AVAL = runif(50, 0, 1), -#' AVALCAT1 = sample(c(1, "BLQ"), 50, replace = TRUE, prob = c(0.5, 0.5)) +#' AVALCAT1 = sample(c(1, "BLQ"), 50, replace = TRUE) #' ) #' x_stats <- s_summary(df$AVAL) #' imputation_rule(df, x_stats, "max", "1/3") diff --git a/tests/testthat/_snaps/analyze_vars_in_cols.md b/tests/testthat/_snaps/analyze_vars_in_cols.md index f6d6bc55ae..b8dc84ea36 100644 --- a/tests/testthat/_snaps/analyze_vars_in_cols.md +++ b/tests/testthat/_snaps/analyze_vars_in_cols.md @@ -164,3 +164,41 @@ F 0 0 117 (22%) M 0 0 72 (14%) +# analyze_vars_in_cols works with imputation rule + + Code + res + Output + n Number of BLQs Mean SD Geometric Mean Minimum Maximum + —————————————————————————————————————————————————————————————————————————————————— + A: Drug X + Day 1 + 0 36 17 ND ND NE ND 92.4 + 6 18 8 ND ND 40.2 ND 97.6 + 12 18 4 65.6 23.3 60.8 21.2 99.2 + 18 18 12 ND ND 44.6 ND 99.3 + Day 2 + 24 18 7 ND ND 42.0 ND 89.8 + 30 18 11 ND ND 35.6 ND 94.5 + 36 18 11 ND ND 36.1 ND 96.1 + 42 18 9 ND ND 41.8 ND 99.2 + +--- + + Code + res + Output + n Number of BLQs Mean SD Geometric Mean Minimum Maximum + —————————————————————————————————————————————————————————————————————————————————— + A: Drug X + Day 1 + 0 36 17 43.4 25.6 32.8 2.3 92.4 + 6 18 8 46.2 24.8 40.2 17.5 97.6 + 12 18 4 65.6 23.3 60.8 21.2 99.2 + 18 18 12 ND ND ND ND 99.3 + Day 2 + 24 18 7 51.4 25.8 42.0 7.7 89.8 + 30 18 11 ND ND ND ND 94.5 + 36 18 11 ND ND ND ND 96.1 + 42 18 9 54.6 28.4 41.8 1.3 99.2 + diff --git a/tests/testthat/_snaps/formats.md b/tests/testthat/_snaps/formats.md index e5d5bbcb89..dc0aa5657d 100644 --- a/tests/testthat/_snaps/formats.md +++ b/tests/testthat/_snaps/formats.md @@ -75,6 +75,13 @@ Output [1] "2 (0.6)" "10 (785.6)" +# format_sigfig works with easy inputs + + Code + res + Output + [1] "1.66" "0.576" "0.1" "78.6" "0.00123" + # format_fraction_threshold works with easy inputs Code diff --git a/tests/testthat/_snaps/imputation_rule.md b/tests/testthat/_snaps/imputation_rule.md new file mode 100644 index 0000000000..3b05de04c5 --- /dev/null +++ b/tests/testthat/_snaps/imputation_rule.md @@ -0,0 +1,103 @@ +# imputation_rule works correctly for 1/3 imputation rule + + Code + result + Output + $val + max + 92.40745 + + $na_level + [1] "ND" + + +--- + + Code + result + Output + $val + [1] NA + + $na_level + [1] "ND" + + +--- + + Code + result + Output + $val + geom_mean + 40.22144 + + $na_level + [1] "NE" + + +--- + + Code + result + Output + $val + max + 99.26841 + + $na_level + [1] "ND" + + +# imputation_rule works correctly for 1/2 imputation rule + + Code + result + Output + $val + max + 92.40745 + + $na_level + [1] "NE" + + +--- + + Code + result + Output + $val + mean + 43.38858 + + $na_level + [1] "NE" + + +--- + + Code + result + Output + $val + geom_mean + 40.22144 + + $na_level + [1] "NE" + + +--- + + Code + result + Output + $val + max + 99.26841 + + $na_level + [1] "NE" + + diff --git a/tests/testthat/test-analyze_vars_in_cols.R b/tests/testthat/test-analyze_vars_in_cols.R index 7985b6fb11..8c2467989e 100644 --- a/tests/testthat/test-analyze_vars_in_cols.R +++ b/tests/testthat/test-analyze_vars_in_cols.R @@ -270,3 +270,75 @@ testthat::test_that("analyze_vars_in_cols works well with categorical data", { append_topleft(" SEX") %>% 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)), + 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/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) +}) + 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) +}) From 000ee1f6261fd9a8e70afe9e6981936e0be75f25 Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Wed, 30 Aug 2023 21:09:52 -0400 Subject: [PATCH 07/27] Update NEWS --- NEWS.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/NEWS.md b/NEWS.md index 35eff551b5..ae7f053b36 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,11 +4,15 @@ * Refactored `a_summary` to no longer use helper function `create_afun_summary`. * Refactored `summarize_vars` and `compare_vars` to use refactored `a_summary`. * Created new internal helper functions `ungroup_stats` to ungroup statistics calculated for factor variables, and `a_summary_internal` to perform calculations for `a_summary`. +* Added `imputation_rule` function to apply imputation rule to data. +* 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. +* Added new format function `format_sigfig` to allow for numeric value formatting by a specified number of significant figures. ### Bug Fixes * Fixed bug in `s_count_occurrences_by_grade` so that "missing" grade always appears as the final level. * Fixed bug in `analyze_vars_in_cols` when categorical data was used. * Fixed bug in `s_count_occurrences_by_grade` so that levels are not relabeled when reordering to account for "missing" grades. +* Updated `s_summary` calculation of `geom_mean` statistic to return 0 instead of `NA` in cases of all-zero data. ### Miscellaneous * Fixed swapped descriptions for the `.N_row` and `.N_col` parameters. From 9637a9953d69e974cd0f194d2c34878bddde449f Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 31 Aug 2023 01:19:31 +0000 Subject: [PATCH 08/27] [skip actions] Restyle files --- R/analyze_vars_in_cols.R | 6 ++++-- R/imputation_rule.R | 2 +- tests/testthat/test-analyze_vars_in_cols.R | 1 - 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/R/analyze_vars_in_cols.R b/R/analyze_vars_in_cols.R index a5cec14521..e8ca3da93e 100644 --- a/R/analyze_vars_in_cols.R +++ b/R/analyze_vars_in_cols.R @@ -233,7 +233,8 @@ analyze_vars_in_cols <- function(lyt, res <- x_stats[[stat]] } else { res_imp <- imputation_rule( - .df_row, x_stats, stat, imp = imp_rule, post = as.numeric(tail(.spl_context$value, 1)) > 0 + .df_row, x_stats, stat, + imp = imp_rule, post = as.numeric(tail(.spl_context$value, 1)) > 0 ) res <- res_imp[["val"]] na_level <- res_imp[["na_level"]] @@ -293,7 +294,8 @@ analyze_vars_in_cols <- function(lyt, res <- x_stats[[stat]] } else { res_imp <- imputation_rule( - .df_row, x_stats, stat, imp = imp_rule, post = as.numeric(tail(.spl_context$value, 1)) > 0 + .df_row, x_stats, stat, + imp = imp_rule, post = as.numeric(tail(.spl_context$value, 1)) > 0 ) res <- res_imp[["val"]] na_level <- res_imp[["na_level"]] diff --git a/R/imputation_rule.R b/R/imputation_rule.R index e182ca46d2..6269b73a06 100644 --- a/R/imputation_rule.R +++ b/R/imputation_rule.R @@ -36,7 +36,7 @@ imputation_rule <- function(df, x_stats, stat, imp_rule, post = FALSE) { # defaults val <- x_stats[[stat]] - na_level = "NE" + na_level <- "NE" if (imp_rule == "1/3") { if (!post && stat == "geom_mean") val <- NA # 1/3_pre_LT, 1/3_pre_GT diff --git a/tests/testthat/test-analyze_vars_in_cols.R b/tests/testthat/test-analyze_vars_in_cols.R index 8c2467989e..c972fbbea3 100644 --- a/tests/testthat/test-analyze_vars_in_cols.R +++ b/tests/testthat/test-analyze_vars_in_cols.R @@ -341,4 +341,3 @@ testthat::test_that("analyze_vars_in_cols works with imputation rule", { res <- testthat::expect_silent(result) testthat::expect_snapshot(res) }) - From 69511b0c77492710f1513c57398ed7267998866d Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Wed, 30 Aug 2023 21:23:09 -0400 Subject: [PATCH 09/27] Update docs --- R/imputation_rule.R | 2 +- _pkgdown.yml | 1 + man/imputation_rule.Rd | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/R/imputation_rule.R b/R/imputation_rule.R index 6269b73a06..c8276d33e1 100644 --- a/R/imputation_rule.R +++ b/R/imputation_rule.R @@ -5,7 +5,7 @@ #' @inheritParams argument_convention #' @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 timepoint (defaults to `FALSE`). +#' @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"`. #' #' @note `AVALCAT1` must be present in `df`. diff --git a/_pkgdown.yml b/_pkgdown.yml index 4ba71db905..95869b630d 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/imputation_rule.Rd b/man/imputation_rule.Rd index 22f6ac1e2d..5345625369 100644 --- a/man/imputation_rule.Rd +++ b/man/imputation_rule.Rd @@ -12,7 +12,7 @@ imputation_rule(df, x_stats, stat, imp_rule, post = FALSE) \item{imp_rule}{(\code{character})\cr imputation rule setting. Set to \code{"1/3"} to implement 1/3 imputation rule or \code{"1/2"} to implement 1/2 imputation rule.} -\item{post}{(\code{flag})\cr whether the data corresponds to a post-dose timepoint (defaults to \code{FALSE}). +\item{post}{(\code{flag})\cr whether the data corresponds to a post-dose time-point (defaults to \code{FALSE}). This parameter is only used when \code{imp_rule} is set to \code{"1/3"}.} } \value{ @@ -29,7 +29,7 @@ according to the specified imputation rule. set.seed(1) df <- data.frame( AVAL = runif(50, 0, 1), - AVALCAT1 = sample(c(1, "BLQ"), 50, replace = TRUE, prob = c(0.5, 0.5)) + AVALCAT1 = sample(c(1, "BLQ"), 50, replace = TRUE) ) x_stats <- s_summary(df$AVAL) imputation_rule(df, x_stats, "max", "1/3") From 8aa8c98dc233d18d822c9c38a825356c9d9ec982 Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Thu, 31 Aug 2023 12:49:27 -0400 Subject: [PATCH 10/27] Fix caching --- R/analyze_vars_in_cols.R | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/R/analyze_vars_in_cols.R b/R/analyze_vars_in_cols.R index e8ca3da93e..e7d805b38c 100644 --- a/R/analyze_vars_in_cols.R +++ b/R/analyze_vars_in_cols.R @@ -181,16 +181,13 @@ analyze_vars_in_cols <- function(lyt, } if (split_col_vars) { - env <- new.env() # create caching environment - # Checking there is not a previous identical column split clyt <- tail(clayout(lyt), 1)[[1]] dummy_lyt <- split_cols_by_multivar( lyt = basic_table(), vars = vars, - varlabels = .labels[.stats], - extra_args = list(cache_env = replicate(length(.stats), list(env))) + varlabels = .labels[.stats] ) if (any(sapply(clyt, identical, y = get_last_col_split(dummy_lyt)))) { @@ -206,11 +203,12 @@ analyze_vars_in_cols <- function(lyt, lyt <- split_cols_by_multivar( lyt = lyt, vars = vars, - varlabels = .labels[.stats], - extra_args = list(cache_env = replicate(length(.stats), list(env))) + varlabels = .labels[.stats] ) } + env <- new.env() # create caching environment + if (do_summarize_row_groups) { if (length(unique(vars)) > 1) { stop("When using do_summarize_row_groups only one label level var should be inserted.") @@ -218,8 +216,8 @@ 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, .df_row, cache_env = NULL, ...) { + function(stat, cache_env) { + function(u, .spl_context, labelstr, .df_row, ...) { # Statistic var_row_val <- paste( gsub("\\._\\[\\[[0-9]+\\]\\]_\\.", "", paste(tail(.spl_context$cur_col_split_val, 1)[[1]], collapse = "_")), @@ -266,7 +264,8 @@ analyze_vars_in_cols <- function(lyt, ) } }, - stat = .stats + stat = .stats, + cache_env = replicate(length(.stats), env) ) # Main call to rtables @@ -279,8 +278,8 @@ analyze_vars_in_cols <- function(lyt, } else { # Function list for analyze_colvars afun_list <- Map( - function(stat) { - function(u, .spl_context, .df_row, cache_env = NULL, ...) { + function(stat, cache_env) { + function(u, .spl_context, .df_row, ...) { # Main statistics var_row_val <- paste( gsub("\\._\\[\\[[0-9]+\\]\\]_\\.", "", paste(tail(.spl_context$cur_col_split_val, 1)[[1]], collapse = "_")), @@ -342,7 +341,8 @@ analyze_vars_in_cols <- function(lyt, ) } }, - stat = .stats + stat = .stats, + cache_env = replicate(length(.stats), env) ) # Main call to rtables From 8a573c126e3ecf726fcb83d3f7ee3cce68fc5488 Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Thu, 31 Aug 2023 13:28:05 -0400 Subject: [PATCH 11/27] Update docs --- R/analyze_variables.R | 2 +- R/analyze_vars_in_cols.R | 4 ++-- R/formatting_functions.R | 2 -- R/imputation_rule.R | 3 +++ man/format_sigfig.Rd | 4 ---- man/imputation_rule.Rd | 5 +++++ 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/R/analyze_variables.R b/R/analyze_variables.R index c0f44b0307..3cfe949364 100644 --- a/R/analyze_variables.R +++ b/R/analyze_variables.R @@ -186,7 +186,7 @@ s_summary.numeric <- function(x, # Convert negative values to NA for log calculation. x_no_negative_vals <- x x_no_negative_vals[x_no_negative_vals <= 0] <- NA - y$geom_mean <- if (all(x == 0) && length(x) > 0) { + y$geom_mean <- if (all(!is.na(x)) && all(x == 0) && length(x) > 0) { c("geom_mean" = 0) } else { c("geom_mean" = exp(mean(log(x_no_negative_vals), na.rm = FALSE))) diff --git a/R/analyze_vars_in_cols.R b/R/analyze_vars_in_cols.R index e7d805b38c..68f7ea5e89 100644 --- a/R/analyze_vars_in_cols.R +++ b/R/analyze_vars_in_cols.R @@ -232,7 +232,7 @@ analyze_vars_in_cols <- function(lyt, } else { res_imp <- imputation_rule( .df_row, x_stats, stat, - imp = imp_rule, post = as.numeric(tail(.spl_context$value, 1)) > 0 + imp_rule = imp_rule, post = as.numeric(tail(.spl_context$value, 1)) > 0 ) res <- res_imp[["val"]] na_level <- res_imp[["na_level"]] @@ -294,7 +294,7 @@ analyze_vars_in_cols <- function(lyt, } else { res_imp <- imputation_rule( .df_row, x_stats, stat, - imp = imp_rule, post = as.numeric(tail(.spl_context$value, 1)) > 0 + imp_rule = imp_rule, post = as.numeric(tail(.spl_context$value, 1)) > 0 ) res <- res_imp[["val"]] na_level <- res_imp[["na_level"]] diff --git a/R/formatting_functions.R b/R/formatting_functions.R index 8a0f678fda..82d48bc6b7 100644 --- a/R/formatting_functions.R +++ b/R/formatting_functions.R @@ -214,8 +214,6 @@ format_xx <- function(str) { #' Format numeric values to print with a specified number of significant figures. #' #' @param sigfig (`integer`)\cr number of significant figures to display. -#' @param x (`numeric`)\cr value. -#' @param ... required for `rtables` interface. #' #' @return An `rtables` formatting function. #' diff --git a/R/imputation_rule.R b/R/imputation_rule.R index c8276d33e1..f1926a4876 100644 --- a/R/imputation_rule.R +++ b/R/imputation_rule.R @@ -3,6 +3,9 @@ #' @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`). diff --git a/man/format_sigfig.Rd b/man/format_sigfig.Rd index 96e4bf14c8..40227283c1 100644 --- a/man/format_sigfig.Rd +++ b/man/format_sigfig.Rd @@ -8,10 +8,6 @@ format_sigfig(sigfig) } \arguments{ \item{sigfig}{(\code{integer})\cr number of significant figures to display.} - -\item{x}{(\code{numeric})\cr value.} - -\item{...}{required for \code{rtables} interface.} } \value{ An \code{rtables} formatting function. diff --git a/man/imputation_rule.Rd b/man/imputation_rule.Rd index 5345625369..5d30654aca 100644 --- a/man/imputation_rule.Rd +++ b/man/imputation_rule.Rd @@ -9,6 +9,11 @@ imputation_rule(df, x_stats, stat, imp_rule, post = FALSE) \arguments{ \item{df}{(\code{data.frame})\cr data set containing all analysis variables.} +\item{x_stats}{(\verb{named list})\cr a named list of statistics, typically the results of \code{\link[=s_summary]{s_summary()}}.} + +\item{stat}{(\code{character})\cr statistic to return the value/NA level of according to the imputation +rule applied.} + \item{imp_rule}{(\code{character})\cr imputation rule setting. Set to \code{"1/3"} to implement 1/3 imputation rule or \code{"1/2"} to implement 1/2 imputation rule.} From 3102431b4e246171eb99ff30e408c6d06c076a14 Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Wed, 6 Sep 2023 17:22:48 -0400 Subject: [PATCH 12/27] Remove hard-coding of AVALCAT1 --- R/imputation_rule.R | 11 ++++++----- man/imputation_rule.Rd | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/R/imputation_rule.R b/R/imputation_rule.R index f1926a4876..8dd7d79638 100644 --- a/R/imputation_rule.R +++ b/R/imputation_rule.R @@ -10,8 +10,9 @@ #' 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"`. -#' -#' @note `AVALCAT1` must be present in `df`. +#' @param avalcat_var (`character`)\cr name of variable that indicates whether a row in `df` corresponds +#' to an analysis value in category `"BLQ"`, `"LTR"`, `" Date: Wed, 6 Sep 2023 17:44:09 -0400 Subject: [PATCH 13/27] Use FLAGSUM variable explicitly --- R/imputation_rule.R | 19 ++++++++++--------- man/imputation_rule.Rd | 9 +++++---- tests/testthat/test-analyze_vars_in_cols.R | 1 + tests/testthat/test-imputation_rule.R | 1 + 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/R/imputation_rule.R b/R/imputation_rule.R index 8dd7d79638..0ac3eafd27 100644 --- a/R/imputation_rule.R +++ b/R/imputation_rule.R @@ -10,9 +10,9 @@ #' 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 (flagsum_ratio > 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") { + if (flagsum_ratio > 1 / 2 && !stat == "max") { val <- NA # 1/2_GT na_level <- "ND" # 1/2_GT } diff --git a/man/imputation_rule.Rd b/man/imputation_rule.Rd index 73a981c293..3539d33340 100644 --- a/man/imputation_rule.Rd +++ b/man/imputation_rule.Rd @@ -10,7 +10,7 @@ imputation_rule( stat, imp_rule, post = FALSE, - avalcat_var = "AVALCAT1" + avalcat_flagvar = "FLAGSUM" ) } \arguments{ @@ -27,9 +27,9 @@ rule or \code{"1/2"} to implement 1/2 imputation rule.} \item{post}{(\code{flag})\cr whether the data corresponds to a post-dose time-point (defaults to \code{FALSE}). This parameter is only used when \code{imp_rule} is set to \code{"1/3"}.} -\item{avalcat_var}{(\code{character})\cr name of variable that indicates whether a row in \code{df} corresponds -to an analysis value in category \code{"BLQ"}, \code{"LTR"}, \code{"% diff --git a/tests/testthat/test-imputation_rule.R b/tests/testthat/test-imputation_rule.R index 81003526ab..d939365b4b 100644 --- a/tests/testthat/test-imputation_rule.R +++ b/tests/testthat/test-imputation_rule.R @@ -6,6 +6,7 @@ df <- data.frame( 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") ) +df$FLAGSUM <- df$AVALCAT1 == "BLQ" testthat::test_that("imputation_rule works correctly for 1/3 imputation rule", { x_stats <- s_summary(df$AVAL[df$NFRLT == 0]) From 73a1164744f5746985657603308932959e5386cb Mon Sep 17 00:00:00 2001 From: Davide Garolini Date: Thu, 7 Sep 2023 15:24:07 +0200 Subject: [PATCH 14/27] Update R/formatting_functions.R Signed-off-by: Davide Garolini --- R/formatting_functions.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/formatting_functions.R b/R/formatting_functions.R index 82d48bc6b7..ab63b11d72 100644 --- a/R/formatting_functions.R +++ b/R/formatting_functions.R @@ -232,7 +232,7 @@ 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.") - as.character(signif(x, sigfig)) + formatC(x, digits = sigfig, format = "f") } } From 685b01df51936cfe145a22813932ac676ff3a3e1 Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Thu, 7 Sep 2023 11:00:20 -0400 Subject: [PATCH 15/27] Revert to use AVALCAT1 instead of FLAGSUM --- R/imputation_rule.R | 19 +++++++++---------- man/imputation_rule.Rd | 9 ++++----- tests/testthat/test-analyze_vars_in_cols.R | 1 - tests/testthat/test-imputation_rule.R | 1 - 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/R/imputation_rule.R b/R/imputation_rule.R index 0ac3eafd27..8dd7d79638 100644 --- a/R/imputation_rule.R +++ b/R/imputation_rule.R @@ -10,9 +10,9 @@ #' 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_flagvar (`character`)\cr name of variable that indicates whether or not a row in `df` is -#' flagged as `"BLQ"`, `"LTR"`, `" 1 / 3) { + if (ltr_blq_ratio > 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 (flagsum_ratio > 1 / 2 && !stat == "max") { + if (ltr_blq_ratio > 1 / 2 && !stat == "max") { val <- NA # 1/2_GT na_level <- "ND" # 1/2_GT } diff --git a/man/imputation_rule.Rd b/man/imputation_rule.Rd index 3539d33340..73a981c293 100644 --- a/man/imputation_rule.Rd +++ b/man/imputation_rule.Rd @@ -10,7 +10,7 @@ imputation_rule( stat, imp_rule, post = FALSE, - avalcat_flagvar = "FLAGSUM" + avalcat_var = "AVALCAT1" ) } \arguments{ @@ -27,9 +27,9 @@ rule or \code{"1/2"} to implement 1/2 imputation rule.} \item{post}{(\code{flag})\cr whether the data corresponds to a post-dose time-point (defaults to \code{FALSE}). This parameter is only used when \code{imp_rule} is set to \code{"1/3"}.} -\item{avalcat_flagvar}{(\code{character})\cr name of variable that indicates whether or not a row in \code{df} is -flagged as \code{"BLQ"}, \code{"LTR"}, \code{"% diff --git a/tests/testthat/test-imputation_rule.R b/tests/testthat/test-imputation_rule.R index d939365b4b..81003526ab 100644 --- a/tests/testthat/test-imputation_rule.R +++ b/tests/testthat/test-imputation_rule.R @@ -6,7 +6,6 @@ df <- data.frame( 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") ) -df$FLAGSUM <- df$AVALCAT1 == "BLQ" testthat::test_that("imputation_rule works correctly for 1/3 imputation rule", { x_stats <- s_summary(df$AVAL[df$NFRLT == 0]) From 6e3a794fe813ab5ef762641f544f135280fdd0e3 Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Thu, 7 Sep 2023 12:23:43 -0400 Subject: [PATCH 16/27] Update add_rowcounts to allow counts from alt_counts_df --- R/utils_rtables.R | 50 ++++++++++++++++++++------ man/add_rowcounts.Rd | 5 ++- man/c_label_n.Rd | 10 ++++-- man/c_label_n_alt.Rd | 28 +++++++++++++++ tests/testthat/_snaps/utils_rtables.md | 35 +++++++++++++++--- tests/testthat/test-utils_rtables.R | 22 ++++++++++++ 6 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 man/c_label_n_alt.Rd 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/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/c_label_n.Rd b/man/c_label_n.Rd index 9470832cf2..6c4c73f77e 100644 --- a/man/c_label_n.Rd +++ b/man/c_label_n.Rd @@ -17,13 +17,19 @@ for more information.} (i.e. with no column-based subsetting) that is typically passed by \code{rtables}.} } \value{ -A \code{list} containing "row_count" with the row count value and the correct label. +A list with formatted \code{\link[rtables:CellValue]{rtables::CellValue()}} with the row count value and the correct label. } \description{ -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 \code{df} in parentheses. +This function differs from \code{\link[=c_label_n_alt]{c_label_n_alt()}} by taking row counts from \code{df} rather than +\code{alt_counts_df}, and is used by \code{\link[=add_rowcounts]{add_rowcounts()}} when \code{alt_counts} is set to \code{FALSE}. } \note{ It is important here to not use \code{df} but rather \code{.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{ +\code{\link[=c_label_n_alt]{c_label_n_alt()}} which performs the same function but retrieves row counts from +\code{alt_counts_df} instead of \code{df}. +} \keyword{internal} diff --git a/man/c_label_n_alt.Rd b/man/c_label_n_alt.Rd new file mode 100644 index 0000000000..75010c9d2d --- /dev/null +++ b/man/c_label_n_alt.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/utils_rtables.R +\name{c_label_n_alt} +\alias{c_label_n_alt} +\title{Content Row Function to Add \code{alt_counts_df} Row Total to Labels} +\usage{ +c_label_n_alt(df, labelstr, .alt_df_row) +} +\arguments{ +\item{df}{(\code{data.frame})\cr data set containing all analysis variables.} + +\item{labelstr}{(\code{character})\cr label of the level of the parent split currently being summarized +(must be present as second argument in Content Row Functions). See \code{\link[rtables:summarize_row_groups]{rtables::summarize_row_groups()}} +for more information.} +} +\value{ +A list with formatted \code{\link[rtables:CellValue]{rtables::CellValue()}} with the row count value and the correct label. +} +\description{ +This takes the label of the latest row split level and adds the row total from \code{alt_counts_df} +in parentheses. This function differs from \code{\link[=c_label_n]{c_label_n()}} by taking row counts from \code{alt_counts_df} +rather than \code{df}, and is used by \code{\link[=add_rowcounts]{add_rowcounts()}} when \code{alt_counts} is set to \code{TRUE}. +} +\seealso{ +\code{\link[=c_label_n]{c_label_n()}} which performs the same function but retrieves row counts from \code{df} instead +of \code{alt_counts_df}. +} +\keyword{internal} diff --git a/tests/testthat/_snaps/utils_rtables.md b/tests/testthat/_snaps/utils_rtables.md index d7268c0a8e..80ad82cf17 100644 --- a/tests/testthat/_snaps/utils_rtables.md +++ b/tests/testthat/_snaps/utils_rtables.md @@ -174,11 +174,20 @@ Code res Output - $row_count - [1] 4 4 - attr(,"label") - [1] "female (N=4)" - + RowsVerticalSection (in_rows) object print method: + ---------------------------- + row_name formatted_cell indent_mod row_label + 1 row_count 0 female (N=4) + +# c_label_n_alt works as expected + + Code + res + Output + RowsVerticalSection (in_rows) object print method: + ---------------------------- + row_name formatted_cell indent_mod row_label + 1 row_count 0 female (N=10) # add_rowcounts works with one row split @@ -272,6 +281,22 @@ BLACK OR AFRICAN AMERICAN 18 12 13 WHITE 8 7 8 +# add_rowcounts works with alt_counts = TRUE + + Code + res + Output + A: Drug X B: Placebo C: Combination + ————————————————————————————————————————————————————————————————————— + F (N=52) + ASIAN 44 37 40 + BLACK OR AFRICAN AMERICAN 18 12 13 + WHITE 8 7 8 + M (N=48) + ASIAN 35 31 44 + BLACK OR AFRICAN AMERICAN 10 12 14 + WHITE 6 7 10 + # h_col_indices works as expected Code diff --git a/tests/testthat/test-utils_rtables.R b/tests/testthat/test-utils_rtables.R index 7a82b465bc..fd37897d0d 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, ] + + 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") %>% From a4eb03c71c6589fb0e7d0359a1040c501309067f Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Thu, 7 Sep 2023 12:36:34 -0400 Subject: [PATCH 17/27] Fix format_sigfig --- R/formatting_functions.R | 2 +- tests/testthat/_snaps/formats.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/formatting_functions.R b/R/formatting_functions.R index ab63b11d72..ec39143f0b 100644 --- a/R/formatting_functions.R +++ b/R/formatting_functions.R @@ -232,7 +232,7 @@ 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(x, digits = sigfig, format = "f") + formatC(signif(x, digits = sigfig), digits = sigfig, format = "fg", flag = "#") } } diff --git a/tests/testthat/_snaps/formats.md b/tests/testthat/_snaps/formats.md index dc0aa5657d..54d6f94742 100644 --- a/tests/testthat/_snaps/formats.md +++ b/tests/testthat/_snaps/formats.md @@ -80,7 +80,7 @@ Code res Output - [1] "1.66" "0.576" "0.1" "78.6" "0.00123" + [1] "1.66" "0.576" "0.100" "78.6" "0.00123" # format_fraction_threshold works with easy inputs From ba19f7559290c955808774276e6fc9435f9058b9 Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Thu, 7 Sep 2023 12:43:46 -0400 Subject: [PATCH 18/27] Update NEWS --- NEWS.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index 19725f5140..74a7cf3d41 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,12 @@ # tern 0.9.0.9000 +### 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 +* 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 `s_summary` calculation of `geom_mean` statistic to return 0 instead of `NA` in cases of all-zero data. +* Updated `add_rowcounts` to allow addition of row counts from `alt_counts_df` using the `alt_counts` argument. # tern 0.9.0 ### New Features @@ -9,15 +17,11 @@ * Refactored the function `a_summary` to no longer use the helper function `create_afun_summary`. * Refactored functions `summarize_vars` and `compare_vars` to use the refactored `a_summary` function. * Created new internal helper functions `ungroup_stats` to ungroup statistics calculated for factor variables, and `a_summary_internal` to perform calculations for `a_summary`. -* Added `imputation_rule` function to apply imputation rule to data. -* 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. -* Added new format function `format_sigfig` to allow for numeric value formatting by a specified number of significant figures. ### Bug Fixes * Fixed bug in `s_count_occurrences_by_grade` so that "missing" grade always appears as the final level. * Fixed bug in `analyze_vars_in_cols` when categorical data was used. * Fixed bug in `s_count_occurrences_by_grade` so that levels are not relabeled when reordering to account for "missing" grades. -* Updated `s_summary` calculation of `geom_mean` statistic to return 0 instead of `NA` in cases of all-zero data. ### Miscellaneous * Fixed swapped descriptions for the `.N_row` and `.N_col` parameters. From 28bf2c0ab88006d99ec7b4041e8bed93915ba897 Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Thu, 7 Sep 2023 12:56:14 -0400 Subject: [PATCH 19/27] Fix lint --- tests/testthat/test-utils_rtables.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testthat/test-utils_rtables.R b/tests/testthat/test-utils_rtables.R index fd37897d0d..5e5300816e 100644 --- a/tests/testthat/test-utils_rtables.R +++ b/tests/testthat/test-utils_rtables.R @@ -145,7 +145,7 @@ testthat::test_that("add_rowcounts works with pruning", { }) testthat::test_that("add_rowcounts works with alt_counts = TRUE", { - DM_alt <- DM[1:100, ] + DM_alt <- DM[1:100, ] # nolint result <- basic_table() %>% split_cols_by("ARM") %>% From 185bf8bfa8e50f009813f0966c230fc9da583a93 Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Fri, 8 Sep 2023 10:49:42 -0400 Subject: [PATCH 20/27] NE geom_mean when all zeros --- NEWS.md | 1 - R/analyze_variables.R | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/NEWS.md b/NEWS.md index 74a7cf3d41..a1583f88fc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,7 +5,6 @@ ### Enhancements * 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 `s_summary` calculation of `geom_mean` statistic to return 0 instead of `NA` in cases of all-zero data. * Updated `add_rowcounts` to allow addition of row counts from `alt_counts_df` using the `alt_counts` argument. # tern 0.9.0 diff --git a/R/analyze_variables.R b/R/analyze_variables.R index 3cfe949364..d198ce999c 100644 --- a/R/analyze_variables.R +++ b/R/analyze_variables.R @@ -186,11 +186,7 @@ s_summary.numeric <- function(x, # Convert negative values to NA for log calculation. x_no_negative_vals <- x x_no_negative_vals[x_no_negative_vals <= 0] <- NA - y$geom_mean <- if (all(!is.na(x)) && all(x == 0) && length(x) > 0) { - c("geom_mean" = 0) - } else { - c("geom_mean" = exp(mean(log(x_no_negative_vals), na.rm = FALSE))) - } + y$geom_mean <- c("geom_mean" = exp(mean(log(x_no_negative_vals), na.rm = FALSE))) geom_mean_ci <- stat_mean_ci(x, conf_level = control$conf_level, na.rm = FALSE, gg_helper = FALSE, geom_mean = TRUE) y$geom_mean_ci <- formatters::with_label(geom_mean_ci, paste("Geometric Mean", f_conf_level(control$conf_level))) From cbfb9aa095fa5a0da13dda7f3c4aaaccaeb9be4d Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Fri, 8 Sep 2023 12:38:33 -0400 Subject: [PATCH 21/27] Add control arg for whether or not to use caching --- R/analyze_vars_in_cols.R | 26 +++++++++--- man/analyze_vars_in_cols.Rd | 5 +++ tests/testthat/_snaps/analyze_vars_in_cols.md | 19 +++++++++ tests/testthat/test-analyze_vars_in_cols.R | 40 +++++++++++++++++++ 4 files changed, 84 insertions(+), 6 deletions(-) diff --git a/R/analyze_vars_in_cols.R b/R/analyze_vars_in_cols.R index 68f7ea5e89..06ed06c723 100644 --- a/R/analyze_vars_in_cols.R +++ b/R/analyze_vars_in_cols.R @@ -12,6 +12,9 @@ #' also be `"1/3"` to implement 1/3 imputation rule or `"1/2"` to implement 1/2 imputation rule. In order #' to use an imputation rule, the `AVALCAT1` variable must be present in the data. See [imputation_rule()] #' for more details on imputation. +#' @param cache (`flag`)\cr whether to store computed values in a temporary caching environment. This will +#' speed up calculations in large tables, but should be set to `FALSE` if the same `rtable` layout is +#' used for multiple tables with different data. Defaults to `FALSE`. #' @param row_labels (`character`)\cr as this function works in columns space, usual `.labels` #' character vector applies on the column space. You can change the row labels by defining this #' parameter to a named character vector with names corresponding to the split values. It defaults @@ -148,6 +151,7 @@ analyze_vars_in_cols <- function(lyt, do_summarize_row_groups = FALSE, split_col_vars = TRUE, imp_rule = NULL, + cache = FALSE, .indent_mods = NULL, nested = TRUE, na_level = NULL, @@ -216,7 +220,7 @@ analyze_vars_in_cols <- function(lyt, # Function list for do_summarize_row_groups. Slightly different handling of labels cfun_list <- Map( - function(stat, cache_env) { + function(stat, use_cache, cache_env) { function(u, .spl_context, labelstr, .df_row, ...) { # Statistic var_row_val <- paste( @@ -224,8 +228,12 @@ analyze_vars_in_cols <- function(lyt, paste(.spl_context$value, collapse = "_"), sep = "_" ) - if (is.null(cache_env[[var_row_val]])) cache_env[[var_row_val]] <- s_summary(u, ...) - x_stats <- cache_env[[var_row_val]] + 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]] @@ -265,6 +273,7 @@ analyze_vars_in_cols <- function(lyt, } }, stat = .stats, + use_cache = cache, cache_env = replicate(length(.stats), env) ) @@ -278,7 +287,7 @@ analyze_vars_in_cols <- function(lyt, } else { # Function list for analyze_colvars afun_list <- Map( - function(stat, cache_env) { + function(stat, use_cache, cache_env) { function(u, .spl_context, .df_row, ...) { # Main statistics var_row_val <- paste( @@ -286,8 +295,12 @@ analyze_vars_in_cols <- function(lyt, paste(.spl_context$value, collapse = "_"), sep = "_" ) - if (is.null(cache_env[[var_row_val]])) cache_env[[var_row_val]] <- s_summary(u, ...) - x_stats <- cache_env[[var_row_val]] + 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]] @@ -342,6 +355,7 @@ analyze_vars_in_cols <- function(lyt, } }, stat = .stats, + use_cache = cache, cache_env = replicate(length(.stats), env) ) diff --git a/man/analyze_vars_in_cols.Rd b/man/analyze_vars_in_cols.Rd index d82653c7bb..9d742b4d72 100644 --- a/man/analyze_vars_in_cols.Rd +++ b/man/analyze_vars_in_cols.Rd @@ -15,6 +15,7 @@ analyze_vars_in_cols( do_summarize_row_groups = FALSE, split_col_vars = TRUE, imp_rule = NULL, + cache = FALSE, .indent_mods = NULL, nested = TRUE, na_level = NULL, @@ -51,6 +52,10 @@ also be \code{"1/3"} to implement 1/3 imputation rule or \code{"1/2"} to impleme to use an imputation rule, the \code{AVALCAT1} variable must be present in the data. See \code{\link[=imputation_rule]{imputation_rule()}} for more details on imputation.} +\item{cache}{(\code{flag})\cr whether to store computed values in a temporary caching environment. This will +speed up calculations in large tables, but should be set to \code{FALSE} if the same \code{rtable} layout is +used for multiple tables with different data. Defaults to \code{FALSE}.} + \item{.indent_mods}{(named \code{integer})\cr indent modifiers for the labels. Defaults to 0, which corresponds to the unmodified default behavior. Can be negative.} diff --git a/tests/testthat/_snaps/analyze_vars_in_cols.md b/tests/testthat/_snaps/analyze_vars_in_cols.md index b8dc84ea36..db134bfb06 100644 --- a/tests/testthat/_snaps/analyze_vars_in_cols.md +++ b/tests/testthat/_snaps/analyze_vars_in_cols.md @@ -202,3 +202,22 @@ 36 18 11 ND ND ND ND 96.1 42 18 9 54.6 28.4 41.8 1.3 99.2 +# analyze_vars_in_cols works with caching + + Code + res + Output + n Number of BLQs Mean SD Geometric Mean Minimum Maximum + —————————————————————————————————————————————————————————————————————————————————— + A: Drug X + Day 1 + 0 36 17 43.4 25.6 32.8 2.3 92.4 + 6 18 8 46.2 24.8 40.2 17.5 97.6 + 12 18 4 65.6 23.3 60.8 21.2 99.2 + 18 18 12 54.1 27.1 44.6 7.5 99.3 + Day 2 + 24 18 7 51.4 25.8 42.0 7.7 89.8 + 30 18 11 48.2 31.8 35.6 3.6 94.5 + 36 18 11 47.0 25.6 36.1 1.3 96.1 + 42 18 9 54.6 28.4 41.8 1.3 99.2 + diff --git a/tests/testthat/test-analyze_vars_in_cols.R b/tests/testthat/test-analyze_vars_in_cols.R index c972fbbea3..43aa121d7e 100644 --- a/tests/testthat/test-analyze_vars_in_cols.R +++ b/tests/testthat/test-analyze_vars_in_cols.R @@ -341,3 +341,43 @@ testthat::test_that("analyze_vars_in_cols works with imputation rule", { 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) +}) From b8ef94c774391a47d535afe960a987df0b3361ef Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Fri, 8 Sep 2023 12:39:13 -0400 Subject: [PATCH 22/27] Expose avalcat_var argument in `analyze_vars_in_cols` --- R/analyze_vars_in_cols.R | 9 ++++-- man/analyze_vars_in_cols.Rd | 5 +++ tests/testthat/_snaps/analyze_vars_in_cols.md | 19 +++++++++++ tests/testthat/test-analyze_vars_in_cols.R | 32 +++++++++++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/R/analyze_vars_in_cols.R b/R/analyze_vars_in_cols.R index 06ed06c723..d567a6b24c 100644 --- a/R/analyze_vars_in_cols.R +++ b/R/analyze_vars_in_cols.R @@ -12,6 +12,10 @@ #' also be `"1/3"` to implement 1/3 imputation rule or `"1/2"` to implement 1/2 imputation rule. In order #' to use an imputation rule, the `AVALCAT1` variable must be present in the data. See [imputation_rule()] #' for more details on imputation. +#' @param avalcat_var (`character`)\cr if `imp_rule` is not `NULL`, name of variable that indicates whether a +#' row in the data corresponds to an analysis value in category `"BLQ"`, `"LTR"`, `" 0 + 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"]] @@ -307,7 +312,7 @@ analyze_vars_in_cols <- function(lyt, } else { res_imp <- imputation_rule( .df_row, x_stats, stat, - imp_rule = imp_rule, post = as.numeric(tail(.spl_context$value, 1)) > 0 + 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"]] diff --git a/man/analyze_vars_in_cols.Rd b/man/analyze_vars_in_cols.Rd index 9d742b4d72..0edef7210c 100644 --- a/man/analyze_vars_in_cols.Rd +++ b/man/analyze_vars_in_cols.Rd @@ -15,6 +15,7 @@ analyze_vars_in_cols( do_summarize_row_groups = FALSE, split_col_vars = TRUE, imp_rule = NULL, + avalcat_var = "AVALCAT1", cache = FALSE, .indent_mods = NULL, nested = TRUE, @@ -52,6 +53,10 @@ also be \code{"1/3"} to implement 1/3 imputation rule or \code{"1/2"} to impleme to use an imputation rule, the \code{AVALCAT1} variable must be present in the data. 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{"% + 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( From 36652eaf527022800bc7596f71f32ca92ae15303 Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Fri, 8 Sep 2023 12:40:25 -0400 Subject: [PATCH 23/27] Match calculation of n_blq in s_summary and imputation_rule --- R/analyze_variables.R | 2 +- R/imputation_rule.R | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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]| Date: Fri, 8 Sep 2023 12:50:18 -0400 Subject: [PATCH 24/27] Update docs --- man/analyze_vars_in_cols.Rd | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/man/analyze_vars_in_cols.Rd b/man/analyze_vars_in_cols.Rd index 0edef7210c..99a7c427e7 100644 --- a/man/analyze_vars_in_cols.Rd +++ b/man/analyze_vars_in_cols.Rd @@ -55,7 +55,8 @@ 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{" Date: Fri, 8 Sep 2023 13:33:07 -0400 Subject: [PATCH 25/27] Apply suggestion from review Co-authored-by: Jana Stoilova <43623360+anajens@users.noreply.github.com> Signed-off-by: Emily de la Rua <59304861+edelarua@users.noreply.github.com> --- R/analyze_vars_in_cols.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/analyze_vars_in_cols.R b/R/analyze_vars_in_cols.R index d567a6b24c..b45a3c8434 100644 --- a/R/analyze_vars_in_cols.R +++ b/R/analyze_vars_in_cols.R @@ -10,7 +10,7 @@ #' @inheritParams rtables::analyze_colvars #' @param imp_rule (`character`)\cr imputation rule setting. Defaults to `NULL` for no imputation rule. Can #' also be `"1/3"` to implement 1/3 imputation rule or `"1/2"` to implement 1/2 imputation rule. In order -#' to use an imputation rule, the `AVALCAT1` variable must be present in the data. See [imputation_rule()] +#' to use an imputation rule, the `avalcat_var` argument must be specified. See [imputation_rule()] #' for more details on imputation. #' @param avalcat_var (`character`)\cr if `imp_rule` is not `NULL`, name of variable that indicates whether a #' row in the data corresponds to an analysis value in category `"BLQ"`, `"LTR"`, `" Date: Fri, 8 Sep 2023 17:36:42 +0000 Subject: [PATCH 26/27] [skip actions] Roxygen Man Pages Auto Update --- man/analyze_vars_in_cols.Rd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/man/analyze_vars_in_cols.Rd b/man/analyze_vars_in_cols.Rd index 99a7c427e7..7593f1c00d 100644 --- a/man/analyze_vars_in_cols.Rd +++ b/man/analyze_vars_in_cols.Rd @@ -50,7 +50,7 @@ without adding more splits. This split must happen only one time on a single lay \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{AVALCAT1} variable must be present in the data. See \code{\link[=imputation_rule]{imputation_rule()}} +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 From 82d0184d8ab289bba8128c23e1db605530854b39 Mon Sep 17 00:00:00 2001 From: Emily de la Rua Date: Fri, 8 Sep 2023 14:30:50 -0400 Subject: [PATCH 27/27] Empty commit