Wrap a pre-summarised data frame into a tabular_spec ready for
the verb chain. tabular() is the entry verb — it owns the
data, titles, and footnotes slots; every downstream verb
(cols(), headers(), sort_rows(),
style(), paginate(), preset()) returns an updated
spec for further chaining, terminating in emit() (write to
file) or as_grid() (resolve without writing).
Arguments
- data
The display rows.
<data.frame>: required. Pre-summarised wide-format data; tibbles, data.tables, and arrow tables are coerced viaas.data.frame(). Factor columns are preserved (their levels drivesort_rows()).Restriction: At least one column; column names must be unique. Zero rows is accepted (engine renders a "No data" stub). Interaction: The
cards-format counterparts (cdisc_saf_demo_ard,cdisc_saf_aesocpt_ard) are NOT accepted directly; pipe throughpivot_across()first.- titles
Page-title block, one element per row.
<character> | NULL: default NULL. Each element renders on its own centred line; embedded\nwraps within that row. The backend collapses unused rows so the column-header band sits flush against the lowest used title.Restriction: No NAs.
Each element supports glue-style
{expr}interpolation: braces are evaluated as R code in the calling environment at build time, e.g."N total = {sum(n)}". Double a brace ({{or}}) for a literal one. Anmd()/html()element is passed through without interpolation.- footnotes
Page-footnote block, one element per row.
<character> | NULL: default NULL. User-supplied prose rows only; the backend appends its own program-path / program-name / timestamp band below them at render time.Restriction: No NAs.
Each element supports glue-style
{expr}interpolation (seetitles).# Canonical 3-line footnote block. footnotes = c( "Subjects are counted once per SOC and once per PT.", "Percentages based on N per treatment group.", "TEAE = treatment-emergent adverse event." )
Value
A tabular_spec S7 object. Pipe it into cols(),
headers(), sort_rows(), style(),
paginate(), and preset() to build the display, then
into emit() to render or as_grid() to resolve without
writing.
Details
Pre-summarised input contract. data is one row per displayed
row of the final table. tabular() does not aggregate, filter,
weight, or generate subtotal rows — those happen upstream in
cards, dplyr, or SAS. If the upstream is a long
cards::ard_stack() ARD, pipe through pivot_across() first
to land in the wide shape tabular() accepts.
Multi-line titles and footnotes by contract. Clinical tables routinely carry 2-4 title rows and 1-4 user footnote rows. Pass each row as one element of the character vector; the backend renders each element on its own line, collapsing unused rows so the column-header band sits flush against the lowest used title.
See also
Downstream build verbs: cols() / col_spec(),
headers(), sort_rows(), style(),
paginate(), preset().
Terminal verbs: emit() (write), as_grid() (resolve
without I/O).
Input helper: pivot_across() (cards ARD -> wide).
Demo data: cdisc_saf_demo, cdisc_saf_aesocpt, cdisc_eff_resp, cdisc_saf_n,
cdisc_eff_n.
Examples
# ---- Example 1: Adverse-event table by SOC and Preferred Term ----
#
# The regulatory work-horse layout: AE-by-SOC/PT with the
# canonical 3-line title block (table number, description,
# population qualifier with BigN drawn inline from `cdisc_saf_n`) and a
# two-line footnote block explaining the denominator. The
# downstream pipeline hides the hierarchy markers (`row_type`,
# `soc_n`, `n_total`) but keeps them in the data so `sort_rows()`
# can arrange SOCs and PTs in descending order of subject count.
# The dataset already ships `n_total` and `soc_n`; here we slice to
# the overall row plus the two highest-incidence SOCs to keep the
# preview compact.
ae <- cdisc_saf_aesocpt
keep_soc <- head(unique(ae$soc[ae$row_type == "soc"]), 2L)
ae <- ae[ae$row_type == "overall" | ae$soc %in% keep_soc, ]
n <- stats::setNames(cdisc_saf_n$n, cdisc_saf_n$arm_short)
tabular(
ae,
titles = c(
"Table 14.3.1",
"Adverse Events by System Organ Class and Preferred Term",
"Safety Population"
),
footnotes = c(
"Subjects are counted once per SOC and once per PT.",
"Percentages based on N per treatment group."
)
) |>
cols(
label = col_spec(label = "SOC / PT", indent = "indent_level"),
soc = col_spec(visible = FALSE),
soc_n = col_spec(visible = FALSE),
row_type = col_spec(visible = FALSE),
n_total = col_spec(visible = FALSE),
placebo = col_spec(label = "Placebo\nN={n['placebo']}"),
drug_50 = col_spec(label = "Drug 50\nN={n['drug_50']}"),
drug_100 = col_spec(label = "Drug 100\nN={n['drug_100']}"),
Total = col_spec(label = "Total\nN={n['Total']}")
) |>
sort_rows(by = c("soc_n", "n_total"), descending = c(TRUE, TRUE))
Table 14.3.1
Adverse Events by System Organ Class and Preferred Term
Safety Population
SOC / PT Placebo
N=86 Drug 50
N=96 Drug 100
N=72 Total
N=254 TOTAL SUBJECTS WITH AN EVENT 52 (60.5) 81 (84.4) 66 (91.7) 199 (78.3) SKIN AND SUBCUTANEOUS TISSUE DISORDERS 19 (22.1) 36 (37.5) 35 (48.6) 90 (35.4) PRURITUS 8 (9.3) 21 (21.9) 25 (34.7) 54 (21.3) ERYTHEMA 8 (9.3) 14 (14.6) 14 (19.4) 36 (14.2) RASH 5 (5.8) 13 (13.5) 8 (11.1) 26 (10.2) HYPERHIDROSIS 2 (2.3) 4 (4.2) 8 (11.1) 14 (5.5) SKIN IRRITATION 3 (3.5) 6 (6.2) 5 (6.9) 14 (5.5) GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS 15 (17.4) 36 (37.5) 30 (41.7) 81 (31.9) APPLICATION SITE PRURITUS 6 (7.0) 23 (24.0) 21 (29.2) 50 (19.7) APPLICATION SITE ERYTHEMA 3 (3.5) 13 (13.5) 14 (19.4) 30 (11.8) APPLICATION SITE DERMATITIS 5 (5.8) 9 (9.4) 7 (9.7) 21 (8.3) APPLICATION SITE IRRITATION 3 (3.5) 9 (9.4) 9 (12.5) 21 (8.3) APPLICATION SITE VESICLES 1 (1.2) 5 (5.2) 5 (6.9) 11 (4.3)
Subjects are counted once per SOC and once per PT.
Percentages based on N per treatment group.
# ---- Example 2: Best overall response with CDISC factor ordering ----
#
# Efficacy table where response categories must appear in CDISC
# clinical order (CR < PR < SD < NON-CR/NON-PD < PD < NE <
# MISSING), then the derived ORR / CBR / DCR rate rows, not
# alphabetical. `groupid` keeps the four sections ordered while the
# `stat_label` factor orders the response block; `sort_rows()` does
# both in one pass. `groupid` / `group_label` ride along hidden.
bor_levels <- c(
"CR", "PR", "SD", "NON-CR/NON-PD", "PD", "NE", "MISSING",
"ORR (CR + PR)", "CBR (CR + PR + SD)",
"DCR (CR + PR + SD + NON-CR/NON-PD)", "95% CI (Clopper-Pearson)"
)
eff <- cdisc_eff_resp
eff$stat_label <- factor(eff$stat_label, levels = bor_levels)
ne <- stats::setNames(cdisc_eff_n$n, cdisc_eff_n$arm_short)
tabular(
eff,
titles = c(
"Table 14.2.1",
"Best Overall Response and Response Rates",
"Efficacy Evaluable Population"
),
footnotes = "Response per RECIST 1.1, investigator assessment."
) |>
cols(
stat_label = col_spec(label = "Response"),
row_type = col_spec(visible = FALSE),
groupid = col_spec(visible = FALSE),
group_label = col_spec(visible = FALSE),
placebo = col_spec(label = "Placebo\nN={ne['placebo']}"),
drug_50 = col_spec(label = "Drug 50\nN={ne['drug_50']}"),
drug_100 = col_spec(label = "Drug 100\nN={ne['drug_100']}")
) |>
sort_rows(by = c("groupid", "stat_label"))
Table 14.2.1
Best Overall Response and Response Rates
Efficacy Evaluable Population
Response Placebo
N=86 Drug 50
N=84 Drug 100
N=84 CR 1 (1.2) 1 (1.2) 1 (1.2) PR 1 (1.2) 0 0 SD 1 (1.2) 0 0 NON-CR/NON-PD 0 0 1 (1.2) PD 0 0 1 (1.2) NE 0 1 (1.2) 0 MISSING 83 (96.5) 82 (97.6) 81 (96.4) ORR (CR + PR) 2 (2.3) 1 (1.2) 1 (1.2) 95% CI (Clopper-Pearson) (0.3, 8.1) (0.0, 6.5) (0.0, 6.5) CBR (CR + PR + SD) 3 (3.5) 1 (1.2) 1 (1.2) 95% CI (Clopper-Pearson) (0.7, 9.9) (0.0, 6.5) (0.0, 6.5) DCR (CR + PR + SD + NON-CR/NON-PD) 3 (3.5) 1 (1.2) 2 (2.4) 95% CI (Clopper-Pearson) (0.7, 9.9) (0.0, 6.5) (0.3, 8.3)
Response per RECIST 1.1, investigator assessment.
# ---- Example 3: Minimal three-line BigN table from cdisc_saf_n ----
#
# The smallest viable `tabular()` call: the bundled `cdisc_saf_n` 4-row
# BigN table, a single-line title, no footnotes. The default
# `col_spec` per column kicks in, giving sensible labels (the
# data frame's column names) and left-aligned text. Useful when
# teaching the core API shape without the clinical-context
# surface noise.
tabular(cdisc_saf_n, titles = "Safety-population BigN per arm")
Safety-population BigN per arm
arm arm_short n Placebo placebo 86 Xanomeline Low Dose drug_50 96 Xanomeline High Dose drug_100 72 Total Total 254
# ---- Example 4: Nested vital-signs panel — two group levels ----
#
# The canonical by-visit vitals shape: each `param` nests its
# `visit` blocks, and each visit nests the statistic rows. Two
# columns carry `usage = "group"` (`param` then `visit`), so the
# engine renders two levels of nested section headers above the
# `stat_label` stub. The CDISC `paramcd` rides along as the natural
# sort key but hides at render via `col_spec(visible = FALSE)`.
# Sliced to the two blood-pressure parameters for a compact preview;
# the full 4-parameter frame nests the same way.
n <- stats::setNames(cdisc_saf_n$n, cdisc_saf_n$arm_short)
vs <- cdisc_saf_vital[cdisc_saf_vital$paramcd %in% c("DIABP", "SYSBP"), ]
tabular(
vs,
titles = c(
"Table 14.4.1",
"Summary of Vital Signs",
"Safety Population"
),
footnotes = "Statistics computed on observed cases."
) |>
cols(
paramcd = col_spec(visible = FALSE),
param = col_spec(usage = "group", label = "Parameter"),
visit = col_spec(usage = "group", label = "Visit"),
stat_label = col_spec(label = "Statistic"),
placebo = col_spec(
label = "Placebo\nN={n['placebo']}",
align = "decimal"
),
drug_50 = col_spec(
label = "Drug 50\nN={n['drug_50']}",
align = "decimal"
),
drug_100 = col_spec(
label = "Drug 100\nN={n['drug_100']}",
align = "decimal"
)
)
Table 14.4.1
Summary of Vital Signs
Safety Population
Statistic Placebo
N=86 Drug 50
N=96 Drug 100
N=72 Diastolic Blood Pressure (mmHg) Baseline n 340 384 288 Mean (SD) 77.1 (10.7) 76.6 (9.8) 78.2 (10.3) Median 77.7 76.7 78.8 Min, Max 40 , 110 48 , 108 51 , 108 Week 8 n 292 240 224 Mean (SD) 75.2 (9.1) 75.4 (10.6) 77.4 (9.1) Median 76.0 74.0 78.3 Min, Max 49 , 101 52 , 100 54 , 98 Week 16 n 272 168 148 Mean (SD) 75.1 (10.9) 75.2 (10.0) 76.0 (9.0) Median 76.0 75.7 77.3 Min, Max 49 , 98 55 , 98 50 , 92 End of Treatment n 222 177 168 Mean (SD) 74.4 (10.7) 76.0 (11.2) 76.0 (9.9) Median 73.5 76.0 78.0 Min, Max 49 , 104 50 , 100 56 , 98 Systolic Blood Pressure (mmHg) Baseline n 340 384 288 Mean (SD) 136.8 (17.6) 137.9 (18.5) 137.8 (17.2) Median 136.3 138.0 138.0 Min, Max 80 , 184 100 , 194 100 , 192 Week 8 n 292 240 224 Mean (SD) 136.3 (17.0) 134.9 (17.8) 135.1 (15.5) Median 136.5 132.3 134.0 Min, Max 90 , 189 92 , 200 91 , 198 Week 16 n 272 168 148 Mean (SD) 134.6 (18.3) 132.5 (14.3) 133.7 (16.0) Median 134.0 130.0 132.0 Min, Max 76 , 190 100 , 168 99 , 186 End of Treatment n 222 177 168 Mean (SD) 132.7 (15.4) 133.0 (17.1) 132.3 (15.6) Median 131.0 130.0 131.0 Min, Max 78 , 172 92 , 178 100 , 177
Statistics computed on observed cases.