R/tbl_with_pools.R
tbl_with_pools.RdGenerates a gtsummary table that includes both the original treatment arms
and user-defined pooled treatment arms. It does this by creating individual
tables for the original data and each pool, then merging them together
seamlessly using gtsummary::tbl_merge(). This approach keeps the underlying
dataset rows completely unique, preserving standard ADaM integrity while
bypassing the need for complex pre-processing.
tbl_with_pools(
data,
pools,
by = "TRT01A",
denominator = NULL,
keep_original = TRUE,
.tbl_fun,
...
)(data.frame)
The main analysis dataset (e.g., adae).
(list)
Named list of custom pools. Values can be character vectors of arm names,
logical expressions wrapped in rlang::expr(), or the keyword "all" to
include a total column.
(character)
The treatment arm variable name.
(data.frame or NULL)
The denominator dataset (e.g., adsl). Leave as NULL for functions that do not
use a denominator (like tbl_summary). Provide the dataset for functions that
require it (like tbl_hierarchical_rate_and_count).
(logical)
Keep the unpooled treatment arms. Default is TRUE.
(function)
The gtsummary function to apply (e.g., tbl_summary).
Additional arguments passed directly to .tbl_fun.
A merged gtsummary object of class tbl_merge and tbl_with_pools.
df_add_poolings() for a more dangerous pre-processing approach that
modifies the underlying datasets to create pooled rows (not recommended).
# Create minimal dummy ADaM data
adsl <- data.frame(
USUBJID = c("001", "002", "003", "004", "005"),
TRT01A = c("Drug A", "Drug A", "Drug B", "Drug C", "Drug C"),
AGE = c(45, 50, 60, 65, 55),
FLAG = c("Y", "N", "Y", "N", "Y"),
stringsAsFactors = FALSE
)
adae <- data.frame(
USUBJID = c("001", "001", "002", "004", "005"),
TRT01A = c("Drug A", "Drug A", "Drug A", "Drug C", "Drug C"),
AEBODSYS = c("SOC1", "SOC1", "SOC2", "SOC1", "SOC2"),
AEDECOD = c("PT1", "PT2", "PT3", "PT1", "PT4"),
FLAG = c("Y", "Y", "N", "N", "Y"),
stringsAsFactors = FALSE
)
# Define the requested pools
my_pools <- list(
"Drugs A and B" = c("Drug A", "Drug B"),
"All Patients" = "all"
)
# Example A: Safe pooling with standard gtsummary (no denominator) ---------------
safe_pools <- list("Drugs A and B" = c("Drug A", "Drug B"))
tbl_safe <- tbl_with_pools(
data = adsl,
pools = safe_pools,
by = "TRT01A",
denominator = NULL,
keep_original = FALSE,
.tbl_fun = tbl_summary,
include = AGE
)
tbl_safe
Characteristic
Drugs A and B
N = 31
1 Median (Q1, Q3)
# Example B: Triggering the skipped pool warning ---------------------------------
# This throws a warning because 'Drug Z' has zero patients in the denominator
warning_pools <- list("Drug Z Pool" = c("Drug Z"))
tbl_warning <- tbl_with_pools(
data = adae,
pools = warning_pools,
by = "TRT01A",
keep_original = TRUE,
.tbl_fun = tbl_summary,
include = AEBODSYS
)
#> Warning: Pool "Drug Z Pool" has 0 rows in the data. Skipping.
tbl_warning
Characteristic
Drug A
N = 31
Drug C
N = 21
1 n (%)
# Example C: Complex pooling using logical expressions ---------------------------
complex_pools <- list(
"Flagged Patients" = rlang::expr(FLAG == "Y"),
"Drug A Flagged" = rlang::expr(TRT01A == "Drug A" & FLAG == "Y")
)
tbl_complex <- tbl_with_pools(
data = adsl,
pools = complex_pools,
by = "TRT01A",
denominator = NULL,
keep_original = FALSE,
.tbl_fun = tbl_summary,
include = AGE
)
tbl_complex
Characteristic
Flagged Patients
N = 31
Drug A Flagged
N = 11
1 Median (Q1, Q3)
if (FALSE) { # identical(Sys.getenv("NOT_CRAN"), "true") && requireNamespace("yaml", quietly = TRUE)
# Example D: Use yaml to define the pools config and run the function ------------
# Define the config as a standard R list
config_to_write <- list(
tbl_with_pools_config = list(
keep_original = FALSE,
arm_var = "TRT01A",
pools = list(
"Drug A + B" = c("Drug A", "Drug B"),
"Drug C + B" = c("Drug C", "Drug B"),
"All Patients" = "all"
)
)
)
# Write it to a file (using a temp file for this example)
yaml_path <- tempfile(fileext = ".yaml")
yaml::write_yaml(config_to_write, yaml_path)
# Print out what the physical YAML file looks like
cat("--- Contents of the generated YAML file ---\n")
cat(readLines(yaml_path), sep = "\n")
cat("-------------------------------------------\n\n")
# Read the YAML file back into R
arg_specs <- yaml::read_yaml(yaml_path)
# Extract just the poolings config block
pool_args <- arg_specs$tbl_with_pools_config
# Run the function using tbl_hierarchical_rate_and_count
if (!is.null(pool_args)) {
tbl_pooled_yaml <- tbl_with_pools(
data = adae,
pools = pool_args$pools,
by = pool_args$arm_var,
denominator = adsl,
keep_original = pool_args$keep_original,
.tbl_fun = tbl_hierarchical_rate_and_count,
variables = c(AEBODSYS, AEDECOD)
)
tbl_pooled_yaml
}
}