
Column Spanners and Summary Rows
Ronald (Ryy) G. Thomas
2026-05-02
Source:vignettes/spanners_and_summaries.Rmd
spanners_and_summaries.RmdOverview
This vignette demonstrates two structural extensions to the zztable1 blueprint framework:
- Column spanners group columns under hierarchical headers
- Summary rows insert computed subtotals at group boundaries
Both features are applied post-hoc to an existing blueprint using a pipeline-style API.
Sample Data
set.seed(42)
n <- 120
trial <- data.frame(
arm = factor(
sample(c("Placebo", "Treatment"), n, replace = TRUE),
levels = c("Placebo", "Treatment")
),
age = round(rnorm(n, 62, 14)),
sex = factor(
sample(c("Male", "Female"), n, replace = TRUE)
),
bmi = round(rnorm(n, 27, 5), 1),
diabetes = factor(
sample(c("No", "Yes"), n, replace = TRUE,
prob = c(0.7, 0.3))
)
)Column Spanners
Basic spanner
A single-level spanner groups the treatment arm columns under a shared label.
bp <- table1(
arm ~ age + sex + bmi + diabetes,
data = trial,
pvalue = TRUE,
theme = "nejm"
)
add_spanner(bp, label = "Treatment Arms",
columns = c(2L, 3L))
render_bp(bp)| Variable | Treatment Arms | P value | |
|---|---|---|---|
|
Placebo (N=53) |
Treatment (N=67) |
||
| age | 62.8 ± 12 | 60.8 ± 13.1 | 0.403 |
| sex – no. (%) | 29 (54.7) | 31 (46.3) | 0.462 |
| bmi | 26.6 ± 4.9 | 27.2 ± 4.6 | 0.437 |
| diabetes – no. (%) | 15 (28.3) | 14 (20.9) | 0.394 |
Multiple spanners
Two separate spanners, each covering different column ranges.
bp2 <- table1(
arm ~ age + sex + bmi + diabetes,
data = trial,
pvalue = TRUE,
totals = TRUE,
theme = "lancet"
)
add_spanner(bp2, label = "By Arm",
columns = c(2L, 3L), id = "arms")
add_spanner(bp2, label = "Overall",
columns = 4L, id = "overall")
render_bp(bp2, "lancet")| Variable | By Arm | Overall | P value | |
|---|---|---|---|---|
|
Placebo (N=53) |
Treatment (N=67) |
Total (N=120) |
||
| age | 62.8 (12) | 60.8 (13.1) | 61.7 (12.6) | 0.403 |
| sex | ||||
| Female | 24 (45%) | 36 (54%) | 60 (50%) | 0.462 |
| Male | 29 (55%) | 31 (46%) | 60 (50%) | |
| bmi | 26.6 (4.9) | 27.2 (4.6) | 26.9 (4.7) | 0.437 |
| diabetes | ||||
| No | 38 (72%) | 53 (79%) | 91 (76%) | 0.394 |
| Yes | 15 (28%) | 14 (21%) | 29 (24%) | |
Nested (hierarchical) spanners
A parent spanner groups two child spanners, producing a two-level column hierarchy.
bp3 <- table1(
arm ~ age + sex + bmi + diabetes,
data = trial,
pvalue = TRUE,
totals = TRUE,
theme = "jama"
)
add_spanner(bp3, label = "By Arm",
columns = c(2L, 3L), id = "by_arm")
add_spanner(bp3, label = "Overall",
columns = 4L, id = "overall")
add_spanner(bp3, label = "Patient Characteristics",
spanners = c("by_arm", "overall"))
render_bp(bp3, "jama")| Variable | Patient Characteristics | P value | ||
|---|---|---|---|---|
| By Arm | Overall | |||
|
Placebo (N=53) |
Treatment (N=67) |
Total (N=120) |
||
| age | 62.8 (12) | 60.8 (13.1) | 61.7 (12.6) | 0.403 |
| sex | ||||
| Female | 24 (45%) | 36 (54%) | 60 (50%) | 0.462 |
| Male | 29 (55%) | 31 (46%) | 60 (50%) | |
| bmi | 26.6 (4.9) | 27.2 (4.6) | 26.9 (4.7) | 0.437 |
| diabetes | ||||
| No | 38 (72%) | 53 (79%) | 91 (76%) | 0.394 |
| Yes | 15 (28%) | 14 (21%) | 29 (24%) | |
Summary Rows
Per-group summaries
Summary rows are computed over the numeric values in each variable group and inserted at the group boundary.
bp4 <- table1(
arm ~ age + sex + bmi,
data = trial,
pvalue = FALSE,
theme = "nejm"
)
add_summary_rows(bp4,
fns = list(
"Group n" = function(x) sum(!is.na(x))
),
side = "bottom"
)
render_bp(bp4)| Variable |
Placebo (N=53) |
Treatment (N=67) |
|---|---|---|
| age | 62.8 ± 12 | 60.8 ± 13.1 |
| Group n | 53.0 | 67.0 |
| sex – no. (%) | 29 (54.7) | 31 (46.3) |
| Group n | 1.0 | 1.0 |
| bmi | 26.6 ± 4.9 | 27.2 ± 4.6 |
| Group n | 53.0 | 67.0 |
Grand summary
A grand summary row aggregates across the entire table.
bp5 <- table1(
arm ~ age + bmi,
data = trial,
pvalue = TRUE,
theme = "lancet"
)
add_grand_summary_rows(bp5,
fns = list(
"Overall Mean" = function(x) round(mean(x, na.rm = TRUE), 1)
),
side = "bottom"
)
render_bp(bp5, "lancet")| Variable |
Placebo (N=53) |
Treatment (N=67) |
P value |
|---|---|---|---|
| age | 62.8 (12) | 60.8 (13.1) | 0.403 |
| bmi | 26.6 (4.9) | 27.2 (4.6) | 0.437 |
| Overall Mean | 44.7 | 44.0 |
Combined: subtotals and grand total
Per-group counts and a grand total in the same table.
bp6 <- table1(
arm ~ age + sex + bmi + diabetes,
data = trial,
pvalue = TRUE,
totals = TRUE,
theme = "jama"
)
add_summary_rows(bp6,
fns = list(
"Subtotal n" = function(x) sum(!is.na(x))
),
side = "bottom"
)
add_grand_summary_rows(bp6,
fns = list(
"Grand Total n" = function(x) sum(!is.na(x))
),
side = "bottom"
)
render_bp(bp6, "jama")| Variable |
Placebo (N=53) |
Treatment (N=67) |
Total (N=120) |
P value |
|---|---|---|---|---|
| age | 62.8 (12) | 60.8 (13.1) | 61.7 (12.6) | 0.403 |
| Subtotal n | 53.0 | 67.0 | 120.0 | |
| sex | ||||
| Female | 24 (45%) | 36 (54%) | 60 (50%) | 0.462 |
| Male | 29 (55%) | 31 (46%) | 60 (50%) | |
| Subtotal n | 2.0 | 2.0 | 2.0 | |
| bmi | 26.6 (4.9) | 27.2 (4.6) | 26.9 (4.7) | 0.437 |
| Subtotal n | 53.0 | 67.0 | 120.0 | |
| diabetes | ||||
| No | 38 (72%) | 53 (79%) | 91 (76%) | 0.394 |
| Yes | 15 (28%) | 14 (21%) | 29 (24%) | |
| Subtotal n | 2.0 | 2.0 | 2.0 | |
| Grand Total n | 110.0 | 138.0 | 244.0 |
Both Features Together
Spanners and summary rows on the same blueprint.
bp7 <- table1(
arm ~ age + sex + bmi + diabetes,
data = trial,
pvalue = TRUE,
totals = TRUE,
theme = "nejm"
)
add_spanner(bp7, label = "Randomised Arms",
columns = c(2L, 3L), id = "arms")
add_spanner(bp7, label = "All",
columns = 4L, id = "all")
add_spanner(bp7, label = "Participants",
spanners = c("arms", "all"))
add_summary_rows(bp7,
fns = list(
"n obs" = function(x) sum(!is.na(x))
),
side = "bottom"
)
add_grand_summary_rows(bp7,
fns = list(
"Total n" = function(x) sum(!is.na(x))
),
side = "bottom"
)
render_bp(bp7)| Variable | Participants | P value | ||
|---|---|---|---|---|
| Randomised Arms | All | |||
|
Placebo (N=53) |
Treatment (N=67) |
Total (N=120) |
||
| age | 62.8 ± 12 | 60.8 ± 13.1 | 61.7 ± 12.6 | 0.403 |
| n obs | 53.0 | 67.0 | 120.0 | |
| sex – no. (%) | 29 (54.7) | 31 (46.3) | 60 (50) | 0.462 |
| n obs | 1.0 | 1.0 | 1.0 | |
| bmi | 26.6 ± 4.9 | 27.2 ± 4.6 | 26.9 ± 4.7 | 0.437 |
| n obs | 53.0 | 67.0 | 120.0 | |
| diabetes – no. (%) | 15 (28.3) | 14 (20.9) | 29 (24.2) | 0.394 |
| n obs | 1.0 | 1.0 | 1.0 | |
| Total n | 108.0 | 136.0 | 242.0 | |
API Reference
| Function | Purpose |
|---|---|
add_spanner() |
Add a column spanner to a blueprint |
add_spanner_delim() |
Auto-generate spanners from column name delimiters |
add_summary_rows() |
Add per-group summary rows |
add_grand_summary_rows() |
Add table-wide grand summary rows |