
Reference Group Handling with get_ref_info
2026-10-14
Source:vignettes/get_ref_info.Rmd
get_ref_info.RmdOverview
Many clinical tables require statistics computed relative to a
reference (control) group — for example, a risk difference versus
placebo. In rtables, the reference group can be injected
automatically via .ref_group and .in_ref_col,
but this breaks down when:
- the reference column is nested under a spanning header,
- a
split_cols_by_multivaris present, or - an
add_overall_colis used alongside treatment splits.
get_ref_info() solves this by letting the analysis
function look up the reference group itself, using an explicit column
path (ref_path).
The ref_path Convention
ref_path is a character vector of alternating
split-variable names and their values, following the same convention as
rtables::col_paths():
c("var1", "level1", "var2", "level2", ...)
For a simple layout with split_cols_by("ARM"), the
placebo reference path is:
ref_path <- c("ARM", "B: Placebo")For a layout with a spanning header variable colspan_trt
above ARM, the path must include both levels:
ref_path <- c("colspan_trt", " ", "ARM", "B: Placebo")The path must exactly match the column-split hierarchy in the layout.
Use col_paths(build_table(lyt, df)) to inspect the
available paths.
Basic Usage
get_ref_info() returns a list with two elements:
-
in_ref_col:TRUEif the current column matchesref_path,FALSEotherwise,NULLif the reference split variable is not present in the current column hierarchy. -
ref_group: the subset of the row data belonging to the reference group (a data frame, or a vector if.varis supplied).
Working Example: AE Table with Risk Difference Columns
This example demonstrates the standard pattern for a table with
spanning headers and risk difference columns — the primary use case for
get_ref_info().
trtvar <- "TRT01A"
ctrl_grp <- "Placebo"
adsl <- pharmaverseadamjnj::adsl |>
filter(SAFFL == "Y") |>
select(STUDYID, USUBJID, all_of(trtvar), SAFFL) |>
mutate(!!trtvar := factor(
.data[[trtvar]],
levels = c("Xanomeline Low Dose", "Xanomeline High Dose", "Placebo")
))
adae <- pharmaverseadamjnj::adae |>
filter(TRTEMFL == "Y") |>
select(USUBJID, TRTEMFL, AEBODSYS, AEDECOD)
# Add spanning header and risk difference variables
adsl$colspan_trt <- factor(
ifelse(adsl[[trtvar]] == ctrl_grp, " ", "Active Study Agent"),
levels = c("Active Study Agent", " ")
)
adsl$rrisk_header <- "Risk Difference (%) (95% CI)"
adsl$rrisk_label <- paste(adsl[[trtvar]], "vs", ctrl_grp)
ae <- adae |> right_join(adsl, by = "USUBJID")The ref_path must trace the full column-split hierarchy
down to the reference group:
colspan_trt_map <- create_colspan_map(
adsl,
non_active_grp = ctrl_grp,
non_active_grp_span_lbl = " ",
active_grp_span_lbl = "Active Study Agent",
colspan_var = "colspan_trt",
trt_var = trtvar
)
ref_path <- c("colspan_trt", " ", trtvar, ctrl_grp)Now build the layout. The key point: a_freq_j uses
get_ref_info() internally to identify the reference group
and compute risk differences in the dedicated difference columns:
extra_args <- list(
denom = "n_altdf",
riskdiff = TRUE,
ref_path = ref_path,
method = "wald",
.stats = "count_unique_fraction",
.formats = c(rr_ci_3d = jjcsformat_xx("xx.x (xx.x, xx.x)"))
)
lyt <- basic_table(show_colcounts = TRUE, colcount_format = "N=xx") |>
split_cols_by("colspan_trt",
split_fun = trim_levels_to_map(map = colspan_trt_map)
) |>
split_cols_by(trtvar) |>
split_cols_by("rrisk_header", nested = FALSE) |>
split_cols_by(trtvar,
labels_var = "rrisk_label",
split_fun = remove_split_levels(ctrl_grp)
) |>
analyze("TRTEMFL",
afun = a_freq_j,
extra_args = append(extra_args, list(val = "Y", label = "Subjects with >=1 AE"))
) |>
split_rows_by("AEBODSYS",
split_label = "System Organ Class",
split_fun = trim_levels_in_group("AEDECOD"),
label_pos = "topleft",
section_div = " ",
nested = FALSE
) |>
summarize_row_groups("AEBODSYS", cfun = a_freq_j, extra_args = extra_args) |>
analyze("AEDECOD", afun = a_freq_j, extra_args = extra_args) |>
append_topleft(" Preferred Term, n (%)")
result <- build_table(lyt, ae, alt_counts_df = adsl)
head(result, 10)
#> Active Study Agent Risk Difference (%) (95% CI)
#> System Organ Class Xanomeline Low Dose Xanomeline High Dose Placebo Xanomeline Low Dose vs Placebo Xanomeline High Dose vs Placebo
#> Preferred Term, n (%) N=67 N=51 N=64 N=67 N=51
#> ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
#> Subjects with >=1 AE 56 (83.6%) 48 (94.1%) 49 (76.6%) 7.0 (-6.6, 20.7) 17.6 (5.3, 29.8)
#> Cardiac disorders 10 (14.9%) 10 (19.6%) 9 (14.1%) 0.9 (-11.2, 12.9) 5.5 (-8.3, 19.4)
#> Atrial fibrillation 1 (1.5%) 1 (2.0%) 1 (1.6%) -0.1 (-4.3, 4.1) 0.4 (-4.5, 5.3)
#> Atrial flutter 1 (1.5%) 1 (2.0%) 0 1.5 (-1.4, 4.4) 2.0 (-1.8, 5.8)
#> Atrial hypertrophy 0 0 1 (1.6%) -1.6 (-4.6, 1.5) -1.6 (-4.6, 1.5)
#> Atrioventricular block first degree 1 (1.5%) 0 0 1.5 (-1.4, 4.4) 0.0 (0.0, 0.0)
#> Atrioventricular block second degree 0 0 1 (1.6%) -1.6 (-4.6, 1.5) -1.6 (-4.6, 1.5)
#> Bradycardia 0 0 1 (1.6%) -1.6 (-4.6, 1.5) -1.6 (-4.6, 1.5)
#> Bundle branch block left 0 0 1 (1.6%) -1.6 (-4.6, 1.5) -1.6 (-4.6, 1.5)
#> Bundle branch block right 1 (1.5%) 0 1 (1.6%) -0.1 (-4.3, 4.1) -1.6 (-4.6, 1.5)How get_ref_info Works Internally
When a_freq_j (or any analysis function) calls
get_ref_info(ref_path, .spl_context):
- It checks whether the split variables in
ref_pathare present in the current column hierarchy (usingin_column()with wildcards). - If present, it subsets the row data to the reference group using the
pre-computed column facet indices stored in
.spl_context. - It returns
in_ref_col = TRUE/FALSEindicating whether the current column IS the reference column (so the function can skip computing a difference against itself).
When the reference split is absent from the current column hierarchy
(e.g. in an add_overall_col("Total") column), both
in_ref_col and ref_group are
NULL.
Wildcard Matching with in_column()
in_column() — the underlying helper used by
get_ref_info() — supports "*" as a wildcard
for any single split variable name or value:
# Simulate a split context for ARM == "B: Placebo" under colspan_trt == " "
spl_ctx <- data.frame(
cur_col_split = I(list(c("colspan_trt", "ARM"))),
cur_col_split_val = I(list(c(" ", "B: Placebo")))
)
# Exact match
in_column(c("colspan_trt", " ", "ARM", "B: Placebo"), spl_ctx)
#> [1] TRUE
# Wildcard on spanning header — still TRUE
in_column(c("*", "*", "ARM", "B: Placebo"), spl_ctx)
#> [1] TRUE
# Different ARM level — FALSE
in_column(c("*", "*", "ARM", "A: Drug X"), spl_ctx)
#> [1] FALSERelationship to h_get_cur_trt_grp
get_ref_info() and h_get_cur_trt_grp()
serve different purposes:
get_ref_info() |
h_get_cur_trt_grp() |
|
|---|---|---|
| Returns | reference-group data + in_ref_col flag |
current treatment group value (single string) |
| Used for | obtaining .ref_group / .in_ref_col for
statistics |
identifying which treatment group the current column belongs to |
| Typical callers |
a_freq_j, a_eair_j,
s_ancova_j
|
a_summarize_aval_chg_diff_j, a_freq_j,
a_freq_resp_var_j
|
In layouts that combine split_cols_by_multivar with
difference columns, h_get_cur_trt_grp() resolves the
current treatment group from the interleaved column path, and
get_ref_info() obtains the reference-group data.
Inspecting Column Paths
When constructing ref_path, inspect the column paths of
a built table:
col_paths(result)
#> [[1]]
#> [1] "colspan_trt" "Active Study Agent" "TRT01A"
#> [4] "Xanomeline Low Dose"
#>
#> [[2]]
#> [1] "colspan_trt" "Active Study Agent" "TRT01A"
#> [4] "Xanomeline High Dose"
#>
#> [[3]]
#> [1] "colspan_trt" " " "TRT01A" "Placebo"
#>
#> [[4]]
#> [1] "rrisk_header" "Risk Difference (%) (95% CI)"
#> [3] "TRT01A" "Xanomeline Low Dose"
#>
#> [[5]]
#> [1] "rrisk_header" "Risk Difference (%) (95% CI)"
#> [3] "TRT01A" "Xanomeline High Dose"Each path corresponds to a leaf column. The ref_path
should match one of these paths exactly (or a prefix thereof when the
reference column has further nested splits below it).