Build a single column's display attributes — label, format,
visibility, width, alignment, NA text, indent. The result feeds
cols(), which stamps the input column name onto the spec from
its named-argument position and attaches it to the parent
tabular_spec. Row structure (section headers, repeat
suppression, blank spacers) is not a column attribute — declare it
once with group_rows().
Usage
col_spec(
label = NA_character_,
format = NULL,
visible = NA,
width = "auto",
align = NULL,
valign = NULL,
na_text = NA_character_,
indent = NA
)Arguments
- label
Display label for the column header.
<character(1)>: default NA_character_. Embed\nfor multi-line headers (arm name on row 1, BigN denominator on row 2 is the clinical convention).NA_character_means use the input column name verbatim.Restriction: Empty string and whitespace-only labels are accepted here, unlike
headers()band labels which are strict.Supports glue-style
{expr}interpolation: braces are evaluated as R code in the calling environment at build time, so a BigN value folds inline,label = "Placebo (N={n['placebo']})". Double a brace ({{or}}) for a literal one. Anmd()/html()label is passed through without interpolation.Per-column token.
{.name}(alias{.col}) inside a{expr}is deferred and resolved to the matched column's name when the spec is stamped bycols()/cols_apply(), so one spec can carry a variable-N arm header. Seecols_apply()for the loop-free idiom.- format
Post-cell formatter.
<character(1) | function | NULL>: default NULL. Asprintftemplate applied per cell, OR a unaryfunction(x) -> characterof the same length, ORNULLfor backend default.Restriction: Character templates are probed with
sprintf(format, 0)at construction; malformed templates fail fast. Tip: Use a function for non-sprintfformatting (locale- aware numbers, thousand separators, conditional symbols).- visible
Whether the column renders.
<logical(1)>: default NA.FALSEhides the column from output but keeps it inspec@datasosort_rows()andstyle()predicates can still reference it.NA(default) is the merge "unset" sentinel — it resolves to visible at render and, crucially, is mergeable: a latercols()call withvisible = TRUEcan re-show a column an earlier call hid.Interaction: Hidden columns are the standard pattern for sort-key helpers (
row_type,n_total) and for the numeric counts behind formatted-text percentage cells.Auto-hide. The depth column named by a character
indentand every column named bysubgroup(by = ...)or referenced via a{col}placeholder in the subgroup banner template are flipped tovisible = FALSEautomatically at engine time — restating it here is redundant.Break-only grouping key. To drop a blank line wherever a hidden marker column changes (e.g. continuous stats vs. categorical groups inside one characteristic), set
visible = FALSEhere AND name the column ingroup_rows()(by = ). A hidden grouping key is break-only: it renders nothing and contributes only its group transitions (the blank spacer and the decimal-section reset).- width
Column width — auto-sized, pinned, or proportional.
<character(1) | numeric(1)>: default "auto"."auto"(default) — engine measures the widest cell (header + body) using bundled Adobe AFM Core 13 glyph metrics and distributes against the available content width. The header is sized to its widest word, so a multi-word header (e.g."n, median") wraps at spaces; a non-breaking space () keeps a run whole. The body is sized to its widest line and never wraps, so numeric values stay intact. Pin a numeric width to wrap the body too.<number>— pinned in inches. Backends wrap content inside the pinned width (tabularrayQ[wd=...], HTMLstyle="width:...", RTF / DOCX after twips conversion)."2.5in"/"60mm"/"4cm"/"30pt"/"5pc"— pinned dimension with an explicit TeX unit. Same behaviour as a bare numeric."30%"— proportional width, percent of available content width. Resolved at engine time against the printable area.
Tip: Mix freely. Pinned and percent widths take priority;
"auto"columns distribute whatever space remains. If pinned widths together exceed the available content width, the engine warns and leaves"auto"columns at their natural fit (layout may overflow).Restriction: Must be positive. Percent values must fall in
[0, 100]. Font-relative units (em,ex,rem) are rejected (no font-size context at parse time).Cross-format semantics (gt convention). The width value is the user's source-of-truth. HTML emits it verbatim into
<col style="width:...">(CSS accepts every unit:%,in,px,pt,cm,mm). Paper backends (LaTeX / RTF / PDF / DOCX) convert to their native unit via the AFM / distribute-widths pipeline. HTML is unconditionally responsive: whenwidth = "auto"(default), the browser auto-sizes the column and cells wrap when the viewport narrows.Note:
NAandNULLare rejected. In pre-v0.1.0 tabularNAdeferred to backend auto-fit; that path was inconsistent across backends and is replaced by the"auto"default, which produces identical widths across RTF / LaTeX / HTML.Merge sentinel. For the field-merge across repeated
cols()/cols_apply()calls,"auto"is treated as the default: a later call carryingwidth = "auto"leaves a previously pinned width intact, and only an explicit non-"auto"width overrides.- align
Horizontal alignment within the column.
<character(1) | NULL>: default NULL. One of:"left"— character columns; row labels."center"— column-header band; rarely on data cells."right"— numeric content without decimals."decimal"— numeric or mixed-format cells aligned on the decimal mark. Use for"5 (3.2%)"next to"54 (32.1%)".NULL(default) — falls through topreset(alignment = list(body_halign = ...))and then to the baked default"left".
Tip:
"decimal"pads numerics with non-breaking spaces so the decimal mark falls on a single column-wide anchor. Pad counts follow the active preset'sdecimal_metricsknob (seepreset()): the default"afm"measures real glyph widths so the anchor holds in proportional fonts as well as monospace.Default behaviour. When
alignis unset (NULL/NA), every column emits with body left-aligned and header centred, regardless of the column's R data type. tabular's canonical input is pre-summarised wide data frames where numeric content is already formatted as character strings (e.g."52 (60.5)"), sois.numeric()-based auto-detection would mis-classify those columns as text and align them left — the opposite of intent. Use explicitalign = "decimal"for NBSP-padded numeric columns (centred header over the padded centroid) oralign = "right"for plain right-aligned numeric columns. The default cascade is body →preset(alignment = list( body_halign = ...))→ CSStext-align: left; header →preset(alignment = list(header_halign = ...))→ CSStext-align: center.- valign
Vertical alignment within the cell.
<character(1) | NULL>: default NULL. One of"top","middle","bottom".NULLfalls through topreset(alignment = list(body_valign = ...))(baked default"top"). Per-cell overrides viastyle(valign = ...)still win over the column setting.Tip: Set
"middle"on the row-label column of a banded- row table so the label stays centred against the multi-line stat-block in the adjacent cell.- na_text
Text substituted for
NAcells.<character(1) | NA>: default NA. Substituted BEFORE theformatstep, soformatdoes not need to anticipateNA.NA(default) inherits the preset's table-widena_text; any string overrides it for this column, including""to force blank cells even when the preset uses a non-empty token.Tip: Use a sentinel (
"-","NR",".") when blank cells would be ambiguous, e.g. when "not applicable" and "not reported" both render blank.- indent
Cosmetic indent depth on this column.
<numeric(1) | character(1) | NA>: default NA. Two modes by type:A non-negative whole number — every body row of this column is indented that many levels (each level is
preset@indent_sizespace-widths).indent = 1is the common "nudge this stub in one level" case;indent = 0is a real value that flattens children under a"section"header.A column name (character) — per-row depth: the engine reads
spec@data[[indent]], coerces each row to a non-negative integer, and prefixes that row's text + AST withstrrep(" ", preset@indent_size * depth). The referenced depth column is auto-hidden — no need to setvisible = FALSEon it.
NA(default) means no indent. Backends with native padding-left (HTML / LaTeX / RTF / DOCX / PDF) emit the depth as cell padding so wrapped continuation lines align with the indented baseline; Markdown carries the literal space-prefix. Synthesised group-header rows are never indented — they are the parent at depth 0.Interaction: an explicit
indenton the host column of agroup_rows()(display = "section")section suppresses that section's automatic one-level child indent (you take control of the depth) — so a stub under a section needs noindentat all, and addingindent = 1there yields a single, not double, indent.Per-row SOC / PT pattern (the bundled
cdisc_saf_aesocptships the canonical depth column, so no upstream construction is needed):cols( label = col_spec(label = "Category", indent = "indent_level"), soc = col_spec(visible = FALSE), row_type = col_spec(visible = FALSE) )Depth-column values
c(0L, 1L, 2L, …)produce0,1,2, … levels. Negative values clamp to 0 (warn); fractional numerics floor (warn); NA → 0 (silent). Works in flat listings too — a characterindentdoes not require anygroup_rows()keys.
Value
A col_spec S7 object. Pass it to cols() keyed by
the input column name; the constructor itself does not stamp
a name.
Details
Constructor-only. col_spec() does not know which input
column it belongs to until cols() stamps the name. Build
reusable specs as ordinary R objects (e.g.
arm_col <- col_spec(align = "decimal")) and apply them to
multiple inputs without restating the name.
Merge semantics across repeated cols() calls. When
cols() is called twice for the same column, the engine merges
field-by-field: any field set to a non-default value on the new spec
overrides; a field left at its "unset" sentinel (NA / NULL /
"auto") leaves the existing value intact. Because every mergeable
field has a genuine unset sentinel, a later call can also restore a
default — e.g. visible = TRUE re-shows a column an earlier call
hid. Build a column's spec in stages without re-stating earlier
attributes.
Validation timing. Argument shapes are validated eagerly —
a malformed sprintf template is probed at construction
(sprintf(format, 0)) and fails fast at write time, not at
render time.
See also
Companion verb: cols() attaches col_spec entries to a
tabular_spec keyed by input column name.
Row structure: group_rows() declares the grouping keys and
section rendering at table level.
Sibling build verbs: headers(), sort_rows(),
style(), paginate(), preset().
Examples
# ---- Example 1: Demographics with every col_spec field exercised ----
#
# Demographics table where every `col_spec` field is in play:
# the row-label columns are pinned to a fixed width and aligned
# left, the four arm columns embed BigN inline in the header,
# decimal-align numeric content, and render `NA` cells as "-".
n <- stats::setNames(cdisc_saf_n$n, cdisc_saf_n$arm_short)
tabular(
cdisc_saf_demo,
titles = c(
"Table 14.1.1",
"Demographics and Baseline Characteristics",
"Safety Population"
),
footnotes = "Percentages based on N per treatment group."
) |>
cols(
variable = col_spec(
label = "Parameter",
width = 2.0, align = "left"
),
stat_label = col_spec(label = "Statistic", align = "left"),
placebo = col_spec(
label = "Placebo\nN={n['placebo']}",
align = "decimal", na_text = "-"
),
drug_50 = col_spec(
label = "Drug 50\nN={n['drug_50']}",
align = "decimal", na_text = "-"
),
drug_100 = col_spec(
label = "Drug 100\nN={n['drug_100']}",
align = "decimal", na_text = "-"
),
Total = col_spec(
label = "Total\nN={n['Total']}",
align = "decimal", na_text = "-"
)
) |>
group_rows(by = "variable") |>
sort_rows(by = c("variable", "stat_label"))
Table 14.1.1
Demographics and Baseline Characteristics
Safety Population
Statistic Placebo
N=86 Drug 50
N=96 Drug 100
N=72 Total
N=254 Age (years) Mean (SD) 75.2 (8.59) 76.0 (8.11) 73.8 (7.94) 75.1 (8.25) Median 76.0 78.0 75.5 77.0 Min, Max 52 , 89 51 , 88 56 , 88 51 , 89 Q1, Q3 69.2, 81.8 71.0, 82.0 70.5, 79.0 70.0, 81.0 n 86 96 72 254 Race, n (%) AMERICAN INDIAN OR ALASKA NATIVE 0 0 1 ( 1.4) 1 ( 0.4) ASIAN 0 0 0 0 BLACK OR AFRICAN AMERICAN 8 ( 9.3) 6 ( 6.2) 9 (12.5) 23 ( 9.1) WHITE 78 (90.7) 90 (93.8) 62 (86.1) 230 (90.6) Sex, n (%) F 53 (61.6) 55 (57.3) 35 (48.6) 143 (56.3) M 33 (38.4) 41 (42.7) 37 (51.4) 111 (43.7)
Percentages based on N per treatment group.
# ---- Example 2: AE table with indented label + hidden helpers ----
#
# AE-by-SOC/PT table where `label` carries both the SOC and the PT
# text in one column, each PT indented one level under its parent
# SOC via `indent_level`. The hidden numeric helpers `soc_n` (the
# parent SOC's count, broadcast across its PT children) and
# `n_total` (each row's own count) drive the sort: ordering by
# `soc_n` descending keeps every SOC cluster together, and the
# `n_total` descending tiebreak floats the SOC summary row above
# its PTs, so the table reads SOC then its PTs, next SOC then its
# PTs. Demonstrates `indent` plus `visible = FALSE` for sort-only
# columns, fixed width on the wide label column, and decimal
# alignment on all four arm columns.
n <- stats::setNames(cdisc_saf_n$n, cdisc_saf_n$arm_short)
tabular(
cdisc_saf_aesocpt,
titles = c(
"Table 14.3.1",
"Adverse Events by SOC and Preferred Term",
"Safety Population"
)
) |>
cols(
label = col_spec(label = "SOC / Preferred Term",
indent = "indent_level", width = 2.5),
soc = col_spec(visible = FALSE),
row_type = col_spec(visible = FALSE),
soc_n = col_spec(visible = FALSE),
n_total = col_spec(visible = FALSE),
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"),
Total = col_spec(label = "Total\nN={n['Total']}", align = "decimal")
) |>
sort_rows(by = c("soc_n", "n_total"), descending = c(TRUE, TRUE))
Table 14.3.1
Adverse Events by SOC and Preferred Term
Safety Population
SOC / Preferred Term 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) GASTROINTESTINAL DISORDERS 13 (15.1) 12 (12.5) 17 (23.6) 42 (16.5) DIARRHOEA 9 (10.5) 5 ( 5.2) 3 ( 4.2) 17 ( 6.7) VOMITING 3 ( 3.5) 4 ( 4.2) 6 ( 8.3) 13 ( 5.1) NAUSEA 3 ( 3.5) 3 ( 3.1) 6 ( 8.3) 12 ( 4.7) ABDOMINAL PAIN 1 ( 1.2) 3 ( 3.1) 1 ( 1.4) 5 ( 2.0) SALIVARY HYPERSECRETION 0 0 4 ( 5.6) 4 ( 1.6) NERVOUS SYSTEM DISORDERS 6 ( 7.0) 18 (18.8) 17 (23.6) 41 (16.1) DIZZINESS 2 ( 2.3) 9 ( 9.4) 10 (13.9) 21 ( 8.3) HEADACHE 3 ( 3.5) 3 ( 3.1) 5 ( 6.9) 11 ( 4.3) SYNCOPE 0 5 ( 5.2) 2 ( 2.8) 7 ( 2.8) SOMNOLENCE 2 ( 2.3) 3 ( 3.1) 1 ( 1.4) 6 ( 2.4) TRANSIENT ISCHAEMIC ATTACK 0 2 ( 2.1) 1 ( 1.4) 3 ( 1.2) CARDIAC DISORDERS 7 ( 8.1) 12 (12.5) 14 (19.4) 33 (13.0) SINUS BRADYCARDIA 2 ( 2.3) 7 ( 7.3) 8 (11.1) 17 ( 6.7) MYOCARDIAL INFARCTION 4 ( 4.7) 2 ( 2.1) 4 ( 5.6) 10 ( 3.9) ATRIAL FIBRILLATION 1 ( 1.2) 2 ( 2.1) 2 ( 2.8) 5 ( 2.0) SUPRAVENTRICULAR EXTRASYSTOLES 1 ( 1.2) 1 ( 1.0) 1 ( 1.4) 3 ( 1.2) VENTRICULAR EXTRASYSTOLES 0 2 ( 2.1) 1 ( 1.4) 3 ( 1.2) INFECTIONS AND INFESTATIONS 12 (14.0) 6 ( 6.2) 11 (15.3) 29 (11.4) NASOPHARYNGITIS 2 ( 2.3) 4 ( 4.2) 6 ( 8.3) 12 ( 4.7) UPPER RESPIRATORY TRACT INFECTION 6 ( 7.0) 1 ( 1.0) 3 ( 4.2) 10 ( 3.9) INFLUENZA 1 ( 1.2) 1 ( 1.0) 1 ( 1.4) 3 ( 1.2) URINARY TRACT INFECTION 2 ( 2.3) 0 1 ( 1.4) 3 ( 1.2) CYSTITIS 1 ( 1.2) 0 1 ( 1.4) 2 ( 0.8) RESPIRATORY, THORACIC AND MEDIASTINAL DISORDERS 5 ( 5.8) 8 ( 8.3) 9 (12.5) 22 ( 8.7) COUGH 1 ( 1.2) 5 ( 5.2) 5 ( 6.9) 11 ( 4.3) NASAL CONGESTION 3 ( 3.5) 1 ( 1.0) 3 ( 4.2) 7 ( 2.8) DYSPNOEA 1 ( 1.2) 1 ( 1.0) 1 ( 1.4) 3 ( 1.2) EPISTAXIS 0 1 ( 1.0) 2 ( 2.8) 3 ( 1.2) PHARYNGOLARYNGEAL PAIN 0 1 ( 1.0) 1 ( 1.4) 2 ( 0.8) PSYCHIATRIC DISORDERS 7 ( 8.1) 9 ( 9.4) 3 ( 4.2) 19 ( 7.5) CONFUSIONAL STATE 2 ( 2.3) 3 ( 3.1) 1 ( 1.4) 6 ( 2.4) AGITATION 2 ( 2.3) 3 ( 3.1) 0 5 ( 2.0) INSOMNIA 2 ( 2.3) 0 2 ( 2.8) 4 ( 1.6) ANXIETY 0 3 ( 3.1) 0 3 ( 1.2) DELUSION 1 ( 1.2) 0 1 ( 1.4) 2 ( 0.8) MUSCULOSKELETAL AND CONNECTIVE TISSUE DISORDERS 3 ( 3.5) 6 ( 6.2) 5 ( 6.9) 14 ( 5.5) BACK PAIN 1 ( 1.2) 1 ( 1.0) 3 ( 4.2) 5 ( 2.0) ARTHRALGIA 1 ( 1.2) 2 ( 2.1) 1 ( 1.4) 4 ( 1.6) SHOULDER PAIN 1 ( 1.2) 2 ( 2.1) 0 3 ( 1.2) MUSCLE SPASMS 0 1 ( 1.0) 1 ( 1.4) 2 ( 0.8) ARTHRITIS 0 0 1 ( 1.4) 1 ( 0.4) INVESTIGATIONS 5 ( 5.8) 4 ( 4.2) 3 ( 4.2) 12 ( 4.7) ELECTROCARDIOGRAM ST SEGMENT DEPRESSION 4 ( 4.7) 1 ( 1.0) 0 5 ( 2.0) ELECTROCARDIOGRAM T WAVE INVERSION 2 ( 2.3) 1 ( 1.0) 1 ( 1.4) 4 ( 1.6) BLOOD GLUCOSE INCREASED 0 1 ( 1.0) 1 ( 1.4) 2 ( 0.8) ELECTROCARDIOGRAM T WAVE AMPLITUDE DECREASED 1 ( 1.2) 1 ( 1.0) 0 2 ( 0.8) BIOPSY 0 0 1 ( 1.4) 1 ( 0.4)
# ---- Example 3: Format string + na_text for clean numeric display ----
#
# `cdisc_eff_estimates` ships four competing efficacy models with
# pre-computed numeric estimates, 95% CI bounds (NA on the MMRM
# row), and a nominal p-value. `format =` pins the printed
# precision; `na_text` renders the missing CI bounds as a dash
# rather than a literal "NA". `valign = "top"` keeps the multi-
# line cell text aligned to the top.
tabular(cdisc_eff_estimates, titles = "Treatment-effect estimates by model") |>
group_rows(by = "model") |>
cols(
model = col_spec(label = "Model", valign = "top"),
estimate = col_spec(label = "Estimate", align = "decimal", format = "%.2f"),
lower_ci = col_spec(
label = "Lower\n95% CI",
align = "decimal",
format = "%.2f",
na_text = "--"
),
upper_ci = col_spec(
label = "Upper\n95% CI",
align = "decimal",
format = "%.2f",
na_text = "--"
),
p_value = col_spec(
label = "p-value",
align = "decimal",
format = "%.4f"
)
)
Treatment-effect estimates by model
Estimate Lower
95% CI Upper
95% CI p-value ANCOVA -3.42 -1.20 0.0042 MMRM -- -- 0.0061 Cox PH 0.68 0.97 0.0087 Bootstrap (1000 reps) -3.50 -1.10 0.0050
# ---- Example 4: Per-column width + halign override for vitals ----
#
# `width` accepts a numeric (inches), a CSS-style string ("1.5in",
# "20%"), or `"auto"`. Centering the visit column under a wider
# group-column setup demonstrates the alignment cascade —
# col_spec@align beats the engine default but yields to a more
# specific style() rule downstream.
n <- stats::setNames(cdisc_saf_n$n, cdisc_saf_n$arm_short)
tabular(
cdisc_saf_vital,
titles = "Vital Signs at Baseline and End of Treatment"
) |>
cols(
paramcd = col_spec(visible = FALSE),
param = col_spec(label = "Parameter", width = "1.6in"),
visit = col_spec(label = "Visit", width = "1.2in",
align = "center"),
stat_label = col_spec(label = "Statistic", width = "1.0in"),
placebo = col_spec(
label = "Placebo\nN={n['placebo']}",
align = "decimal", width = "0.9in"
),
drug_50 = col_spec(
label = "Drug 50\nN={n['drug_50']}",
align = "decimal", width = "0.9in"
),
drug_100 = col_spec(
label = "Drug 100\nN={n['drug_100']}",
align = "decimal", width = "0.9in"
)
) |>
group_rows(by = c("param", "visit"))
Vital Signs at Baseline and End of Treatment
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 Pulse Rate (beats/min) Baseline n 340 384 288 Mean (SD) 73.5 (11.6) 72.1 (10.8) 72.4 (9.7) Median 72.3 70.0 71.7 Min, Max 51 , 134 50 , 104 52 , 100 Week 8 n 292 240 224 Mean (SD) 71.8 (9.0) 72.6 (11.1) 74.0 (8.9) Median 72.0 72.0 73.2 Min, Max 52 , 102 49 , 104 50 , 104 Week 16 n 272 168 148 Mean (SD) 70.6 (8.8) 68.8 (9.4) 73.2 (9.5) Median 70.2 68.0 72.0 Min, Max 50 , 90 48 , 104 51 , 96 End of Treatment n 222 177 168 Mean (SD) 75.2 (11.5) 74.1 (9.4) 73.6 (9.6) Median 74.0 75.0 73.0 Min, Max 51 , 106 50 , 94 50 , 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 Temperature (C) Baseline n 172 190 144 Mean (SD) 36.6 (0.4) 36.5 (0.4) 36.6 (0.4) Median 36.7 36.6 36.6 Min, Max 35 , 37 35 , 37 36 , 37 Week 8 n 146 118 112 Mean (SD) 36.6 (0.4) 36.6 (0.4) 36.6 (0.4) Median 36.6 36.7 36.7 Min, Max 36 , 37 36 , 37 36 , 37 Week 16 n 136 82 74 Mean (SD) 36.7 (0.3) 36.6 (0.4) 36.6 (0.4) Median 36.7 36.6 36.7 Min, Max 36 , 37 36 , 37 36 , 37 End of Treatment n 74 59 56 Mean (SD) 36.7 (0.4) 36.6 (0.4) 36.6 (0.4) Median 36.8 36.7 36.7 Min, Max 35 , 37 35 , 38 36 , 37
# ---- Example 5: Repeating stub columns on a panelled table ----
#
# `paginate(repeat_cols = )` names the stub that repeats on each
# horizontal panel created by `paginate(panels = 2)` — here the
# grouping key `variable` plus the per-row statistic label
# `stat_label`, so both stay legible on every panel. On HTML /
# Markdown (no page width) the panels collapse into one scrollable
# table with a "Panel 1 / Panel 2" header note; on RTF / Word each
# panel is its own page with the stub repeated.
n <- stats::setNames(cdisc_saf_n$n, cdisc_saf_n$arm_short)
tabular(
cdisc_saf_demo,
titles = c("Table 14.1.1", "Demographics", "Safety Population")
) |>
cols(
variable = col_spec(label = "Parameter"),
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"),
Total = col_spec(label = "Total\nN={n['Total']}", align = "decimal")
) |>
group_rows(by = "variable", display = "collapse") |>
paginate(panels = 2, repeat_cols = c("variable", "stat_label"))
Table 14.1.1
Demographics
Safety Population
Panel 1 Panel 2 Parameter Statistic Placebo
N=86 Drug 50
N=96 Drug 100
N=72 Total
N=254 Age (years) n 86 96 72 254 Mean (SD) 75.2 ( 8.59) 76.0 ( 8.11) 73.8 ( 7.94) 75.1 ( 8.25) Median 76.0 78.0 75.5 77.0 Q1, Q3 69.2, 81.8 71.0, 82.0 70.5, 79.0 70.0, 81.0 Min, Max 52 , 89 51 , 88 56 , 88 51 , 89 Sex, n (%) F 53 (61.6 ) 55 (57.3 ) 35 (48.6 ) 143 (56.3 ) M 33 (38.4 ) 41 (42.7 ) 37 (51.4 ) 111 (43.7 ) Race, n (%) WHITE 78 (90.7 ) 90 (93.8 ) 62 (86.1 ) 230 (90.6 ) BLACK OR AFRICAN AMERICAN 8 ( 9.3 ) 6 ( 6.2 ) 9 (12.5 ) 23 ( 9.1 ) ASIAN 0 0 0 0 AMERICAN INDIAN OR ALASKA NATIVE 0 0 1 ( 1.4 ) 1 ( 0.4 )