Skip to contents

Overview

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

add_spanner() Parameters

Parameter Description
label Display text for the spanner header
columns Column indices or names to group
id Unique identifier (for nesting)
spanners Child spanner ids (for nested spanners)
level Explicit level override

add_summary_rows() Parameters

Parameter Description
fns Named list of aggregation functions
columns Columns to summarise (default: all data columns)
side "bottom" or "top" of each group
groups Restrict to specific groups
fmt_fn Custom formatting function