Generates 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,
  ...
)

Arguments

data

(data.frame)
The main analysis dataset (e.g., adae).

pools

(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.

by

(character)
The treatment arm variable name.

denominator

(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).

keep_original

(logical)
Keep the unpooled treatment arms. Default is TRUE.

.tbl_fun

(function)
The gtsummary function to apply (e.g., tbl_summary).

...

Additional arguments passed directly to .tbl_fun.

Value

A merged gtsummary object of class tbl_merge and tbl_with_pools.

See also

df_add_poolings() for a more dangerous pre-processing approach that modifies the underlying datasets to create pooled rows (not recommended).

Examples

# 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 = 3
1
AGE 50.0 (45.0, 60.0)
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 = 3
1
Drug C
N = 2
1
AEBODSYS

    SOC1 2 (66.7%) 1 (50.0%)
    SOC2 1 (33.3%) 1 (50.0%)
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 = 3
1
Drug A Flagged
N = 1
1
AGE 55.0 (45.0, 60.0) 45.0000 (45.0000, 45.0000)
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 } }