Skip to contents

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 \n for 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. An md() / 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 by cols() / cols_apply(), so one spec can carry a variable-N arm header. See cols_apply() for the loop-free idiom.

# Two-line header with arm name and BigN from cdisc_saf_n.
n <- stats::setNames(cdisc_saf_n$n, cdisc_saf_n$arm_short)
col_spec(
  label = "Placebo\nN={n['placebo']}",
  align = "decimal"
)

format

Post-cell formatter. <character(1) | function | NULL>: default NULL. A sprintf template applied per cell, OR a unary function(x) -> character of the same length, OR NULL for backend default.

Restriction: Character templates are probed with sprintf(format, 0) at construction; malformed templates fail fast. Tip: Use a function for non-sprintf formatting (locale- aware numbers, thousand separators, conditional symbols).

# sprintf template vs. function form.
col_spec(format = "%.1f")
col_spec(format = function(x) formatC(x, format = "f", digits = 1, big.mark = ","))

visible

Whether the column renders. <logical(1)>: default NA. FALSE hides the column from output but keeps it in spec@data so sort_rows() and style() predicates can still reference it. NA (default) is the merge "unset" sentinel — it resolves to visible at render and, crucially, is mergeable: a later cols() call with visible = TRUE can 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 indent and every column named by subgroup(by = ...) or referenced via a {col} placeholder in the subgroup banner template are flipped to visible = FALSE automatically 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 = FALSE here AND name the column in group_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 (tabularray Q[wd=...], HTML style="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: when width = "auto" (default), the browser auto-sizes the column and cells wrap when the viewport narrows.

Note: NA and NULL are rejected. In pre-v0.1.0 tabular NA deferred 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 carrying width = "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 to preset(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's decimal_metrics knob (see preset()): the default "afm" measures real glyph widths so the anchor holds in proportional fonts as well as monospace.

Default behaviour. When align is 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)"), so is.numeric()-based auto-detection would mis-classify those columns as text and align them left — the opposite of intent. Use explicit align = "decimal" for NBSP-padded numeric columns (centred header over the padded centroid) or align = "right" for plain right-aligned numeric columns. The default cascade is body → preset(alignment = list( body_halign = ...)) → CSS text-align: left; header → preset(alignment = list(header_halign = ...)) → CSS text-align: center.

valign

Vertical alignment within the cell. <character(1) | NULL>: default NULL. One of "top", "middle", "bottom". NULL falls through to preset(alignment = list(body_valign = ...)) (baked default "top"). Per-cell overrides via style(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 NA cells. <character(1) | NA>: default NA. Substituted BEFORE the format step, so format does not need to anticipate NA. NA (default) inherits the preset's table-wide na_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_size space-widths). indent = 1 is the common "nudge this stub in one level" case; indent = 0 is 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 with strrep(" ", preset@indent_size * depth). The referenced depth column is auto-hidden — no need to set visible = FALSE on 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 indent on the host column of a group_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 no indent at all, and adding indent = 1 there yields a single, not double, indent.

Per-row SOC / PT pattern (the bundled cdisc_saf_aesocpt ships 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, …) produce 0, 1, 2, … levels. Negative values clamp to 0 (warn); fractional numerics floor (warn); NA → 0 (silent). Works in flat listings too — a character indent does not require any group_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().

Entry / terminal verbs: tabular(), emit(), as_grid().

Inline label formatting: md(), html().

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

 

StatisticPlacebo
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)
Median76.0       78.0       75.5        77.0       
Min, Max52  , 89   51  , 88   56  , 88    51  , 89   
Q1, Q369.2, 81.8 71.0, 82.0 70.5, 79.0  70.0, 81.0 
n86         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)  
WHITE78 (90.7)  90 (93.8)  62 (86.1)  230 (90.6)  
 
Sex, n (%)
F53 (61.6)  55 (57.3)  35 (48.6)  143 (56.3)  
M33 (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 TermPlacebo
N=86
Drug 50
N=96
Drug 100
N=72
Total
N=254
TOTAL SUBJECTS WITH AN EVENT52 (60.5)81 (84.4)66 (91.7)199 (78.3)
SKIN AND SUBCUTANEOUS TISSUE DISORDERS19 (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 CONDITIONS15 (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 DISORDERS13 (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 INFESTATIONS12 (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

 

EstimateLower
95% CI
Upper
95% CI
p-value
ANCOVA               -3.42-1.200.0042
 
MMRM                 --   --   0.0061
 
Cox PH                0.68 0.970.0087
 
Bootstrap (1000 reps)-3.50-1.100.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

 

StatisticPlacebo
N=86
Drug 50
N=96
Drug 100
N=72
Diastolic Blood Pressure (mmHg)
Baseline
n340         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
n292         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
n272         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
n222         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
n340         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
n292         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
n272         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
n222         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
n340         384         288         
Mean (SD)136.8 (17.6)137.9 (18.5)137.8 (17.2)
Median136.3       138.0       138.0       
Min, Max 80  , 184  100  , 194  100  , 192  
 
Week 8
n292         240         224         
Mean (SD)136.3 (17.0)134.9 (17.8)135.1 (15.5)
Median136.5       132.3       134.0       
Min, Max 90  , 189   92  , 200   91  , 198  
 
Week 16
n272         168         148         
Mean (SD)134.6 (18.3)132.5 (14.3)133.7 (16.0)
Median134.0       130.0       132.0       
Min, Max 76  , 190  100  , 168   99  , 186  
 
End of Treatment
n222         177         168         
Mean (SD)132.7 (15.4)133.0 (17.1)132.3 (15.6)
Median131.0       130.0       131.0       
Min, Max 78  , 172   92  , 178  100  , 177  
 
Temperature (C)
Baseline
n172         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
n146         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
n136          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 1Panel 2
ParameterStatisticPlacebo
N=86
Drug 50
N=96
Drug 100
N=72
Total
N=254
Age (years)n86          96          72          254          
Mean (SD)75.2 ( 8.59)76.0 ( 8.11)73.8 ( 7.94) 75.1 ( 8.25)
Median76.0        78.0        75.5         77.0        
Q1, Q369.2, 81.8  71.0, 82.0  70.5, 79.0   70.0, 81.0  
Min, Max52  , 89    51  , 88    56  , 88     51  , 89    
Sex, n (%)F53   (61.6 )55   (57.3 )35   (48.6 )143   (56.3 )
M33   (38.4 )41   (42.7 )37   (51.4 )111   (43.7 )
Race, n (%)WHITE78   (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 )