pivot_across() is tabular's input-side helper: it consumes a
long Analysis Results Data (ARD) data frame (typically produced by
cards::ard_stack() or cards::ard_stack_hierarchical()) and
returns a wide display data.frame ready to pass to tabular().
Usage
pivot_across(
data,
statistic = list(continuous = "{mean} ({sd})", categorical = "{n} ({p}%)"),
column = NULL,
row_group = NULL,
label = NULL,
overall = "Total",
decimals = NULL,
fmt = NULL
)Arguments
- data
Long ARD input data.
<data.frame>: required. At minimum needsstat_nameandstat. Cards-style group columns (group1,group1_level, ...) andvariable/variable_levelare auto-detected. Tibbles /cardobjects / arrow tables are coerced viaas.data.frame().- statistic
Format spec for cell composition.
<character(1) | named list>: required. Combines one or more ARD stats into one display cell. Three accepted forms — each illustrated below. Inside a format string,{stat_name}substitutes that stat's value from the ARD (for example,"{n} ({p}%)"interpolates thenandpstats into a"53 (62%)"cell). The lookup order when a value is needed for a variable is: per-variable -> per-context ->default-> the literal"{n}".Form 1: single string
One format string applied to every variable regardless of context. Use when your ARD is homogeneous (e.g. all categorical).
# Every variable rendered as "n (p%)" — categorical-only slice. cat_only <- cdisc_saf_demo_ard[cdisc_saf_demo_ard$context == "categorical", ] pivot_across( cat_only, statistic = "{n} ({p}%)" )Form 2: named list by context
Different formats per context. This is the typical clinical-table form because demographics mix continuous and categorical variables.
The list names must match the values in the ARD's
contextcolumn verbatim. Which strings appear there depends on how the ARD was built:cards::ard_continuous()/ard_categorical()emit"continuous"/"categorical".cards::ard_summary()/ard_tabulate()emit"summary"/"tabulate".
So an ARD assembled with
ard_stack(ard_summary(...), ard_tabulate(...))is keyedsummary/tabulate, notcontinuous/categorical. Inspectunique(ard$context)when unsure.# AGE (continuous) -> "75.2 (8.59)"; SEX (categorical) -> "53 (62%)" pivot_across( cdisc_saf_demo_ard, statistic = list( continuous = "{mean} ({sd})", categorical = "{n} ({p}%)" ) )Form 3: named list by variable
Override on a per-variable basis; fall back to
defaultor context. Use when one variable needs a custom format.# AGE shows just the mean; SEX / RACE keep the categorical default. pivot_across( cdisc_saf_demo_ard, statistic = list( AGE = "{mean}", categorical = "{n} ({p}%)", default = "{mean} ({sd})" ) )Multi-row continuous spec
Any single entry can itself be a named character vector — each element becomes one display row, with the name as the row label. Use for
N / Mean (SD) / Median / Min, Max-style blocks.pivot_across( cdisc_saf_demo_ard, statistic = list( continuous = c( N = "{N}", "Mean (SD)" = "{mean} ({sd})", Median = "{median}", "Min, Max" = "{min}, {max}" ), categorical = "{n} ({p}%)" ) )- column
Grouping column whose unique values become arms.
<character(1) | NULL>: default NULL.NULLauto-detects from the ARD'sgroup1value or — for renamed input — picks the single non-standard column. Pass a string when multiple group columns exist.- row_group
Second, non-column grouping dimension.
<character(1) | NULL>: default NULL. Names the non-arm group variable of a two-variable.by(e.g.SEXinard_stack(.by = c(ARM, SEX))). It widens into a leading row column (not a pivoted arm column), so the result composes withsubgroup(by = ...)orcol_spec(usage = "group")downstream.Why it is required. cards encodes a crossing factor and a SOC/PT hierarchy identically (the second group variable appears in
variableon its by-marginal rows), so the two cannot be told apart automatically. Namingrow_groupdeclares "this is a crossing factor": the by-marginal rows are dropped and the flat path is used. Leave itNULLfor a genuine hierarchy.Restriction: Must name a second grouping variable present in the ARD and must differ from
column.- label
Variable-name to display-label map.
<character> | NULL: default NULL. Named character vector mapping variable names to display labels (e.g.c(AGE = "Age (years)", SEX = "Sex")). Applies tovariable,soc, andlabelcolumns of the output.NULLleaves the upstream variable names verbatim.Renaming the hierarchical "overall" row. A
cards::ard_stack_hierarchical(overall = TRUE)ARD carries an internal..ard_hierarchical_overall..sentinel for the grand-total ("any event") row. It is relabelled to"Overall"by default; map the sentinel key to override, e.g.label = c("..ard_hierarchical_overall.." = "TOTAL SUBJECTS WITH AN EVENT"). The raw sentinel never reaches the output at any hierarchy depth.- overall
Column name for
NA-arm (overall / total) rows.<character(1) | NULL>: default "Total". PassNULLto drop overall rows entirely (per-arm only output).- decimals
Per-stat decimal precision.
<named integer | named list>: defaultc()“. Accepts two forms:named integer vector — global per-stat overrides (
c(mean = 1, sd = 2, p = 0)).named list — per-variable plus
.default(list(AGE = c(mean = 2), .default = c(p = 1))).
Built-in defaults apply when neither sets a stat.
- fmt
Per-stat custom formatter functions.
<named list of function>: defaultlist()“. Each function takes a numeric value and returns a character string; overrides built-ins anddecimalsfor that stat. Useful for p-value styling and other domain-specific formatting.
Value
A wide data.frame ready for tabular(). Schema:
variable— variable name (or label afterlabel = ...).stat_label— display-row label.One column per arm level (named after the
group1_levelvalues or the renamed arm column).Total(or whateveroverallis set to) when applicable.A leading column named after
row_groupwhen set (the second grouping dimension).Hierarchical ARD adds
soc,label,row_typeinstead ofvariable.
Pass the result straight into tabular() to start the
render pipeline.
Details
tabular's package boundary is display-only: pre-summarised
data in, rendered file out. pivot_across() is the canonical
bridge between the cards aggregation backend and that boundary.
It does not aggregate — it pivots arms to columns, interpolates
per-cell display strings from the stat values, and applies
decimal precision. Filtering, weighting, and aggregation happen
upstream in cards or your own data-prep step.
Key statistic by the ARD context
statistic (and fmt) are matched against the ARD's context
column verbatim, and that value differs per generating function.
Keying by the wrong name silently drops the format. Inspect
unique(ard$context) first and key to match (or pass a single
format string / default = to cover everything). When an
explicitly-supplied statistic matches no context at all,
pivot_across() warns rather than silently emitting {n}.
| Generating function | context to key on |
cards::ard_summary() | summary |
cards::ard_tabulate() | tabulate |
cards::ard_continuous() | continuous |
cards::ard_categorical() | categorical |
cards::ard_stack_hierarchical() | tabulate + hierarchical |
cardx::ard_categorical_ci() | proportion_ci |
cardx::ard_continuous_ci() | continuous_ci |
Indentation of stat_label
Categorical levels and the multi-row continuous stat labels come
back already indented with two leading spaces, ready to render as a
plain display column. Do not also set col_spec(indent = ...) on
stat_label — that stacks the engine indent on top of the string
indent (a double indent). Use one or the other.
Zero-suppression (always-on default)
A row whose n value equals zero renders the whole cell as the
bare n value instead of fully interpolating the format string.
For a categorical level with n = 0, the cell shows "0", not
"0 (0.0%)". This is clinical convention — empty cells should
read as a single zero, not advertise a meaningless rate.
How the default fires (chain of events). During cell assembly,
before format-string interpolation, the engine checks the row's
n stat. If it is zero, the engine short-circuits and returns the
formatted n value ("0") as the entire cell — {p} is never
substituted, so the (0.0%) half of the format string is dropped.
How to opt out: supply a custom fmt$n. Setting any function
under fmt$n is the engine's signal that the user owns the n
rendering. The short-circuit is disabled for the whole table; for
every row the full format string interpolates, so {n} becomes
your formatter's output and {p} becomes the standard percentage.
For n = 0, that's "0 (0.0%)".
# Force "0 (0.0%)" for n = 0 rows by attaching a custom n formatter.
# The body of fmt$n can be the default integer rendering — its
# presence alone is what disables the zero-suppression branch.
pivot_across(
cdisc_saf_demo_ard,
statistic = list(
continuous = "{mean} ({sd})",
categorical = "{n} ({p}%)"
),
fmt = list(n = function(x) sprintf("%d", as.integer(x)))
)Pharma rounding (always-on default)
A percentage that would otherwise round to 0 (when the value
is positive but smaller than the chosen precision) renders as
<0.1; one that would round to 100 (positive but smaller than
100) renders as >99.9. The threshold is precision-aware:
decimals = c(p = 2) produces <0.01 / >99.99. This matches
the pharma convention of never claiming exactly 0% or 100%
when at least one subject contributed.
Override per-stat via fmt:
# Show exact rounded percentages even at the extremes
pivot_across(
data,
statistic = "{n} ({p}%)",
decimals = c(p = 1),
fmt = list(p = function(x) sprintf("%.1f", x * 100))
)Your fmt$p receives the raw stat value (a proportion between
0 and 1) and returns the displayed string. The pharma-threshold
branch only fires inside the built-in p formatter and the
decimals-driven path, so any custom fmt$p bypasses it.
See also
Pipeline entry consumer: tabular() — wraps the wide data
frame this helper returns.
Downstream spec-build verbs: cols() / col_spec(),
headers(), sort_rows(), style(),
paginate(), preset().
Examples
# ---- Example 1: Demographics — long ARD to rendered spec ----
#
# Full pipeline from a `cards::ard_stack()`-style long ARD to a
# sorted `tabular_spec`. The multi-row continuous block (N /
# Mean (SD) / Median / Min, Max) sits above each categorical
# block; decimals are set per-stat (mean 1, sd 2, p 1) to match
# the CDISC convention.
n <- stats::setNames(cdisc_saf_n$n, cdisc_saf_n$arm_short)
cdisc_saf_demo_ard |>
pivot_across(
statistic = list(
continuous = c(
N = "{N}",
"Mean (SD)" = "{mean} ({sd})",
Median = "{median}",
"Min, Max" = "{min}, {max}"
),
categorical = "{n} ({p}%)"
),
decimals = c(mean = 1, sd = 2, p = 1, median = 1, min = 0, max = 0),
label = c(AGE = "Age (years)", SEX = "Sex", RACE = "Race")
) |>
tabular(
titles = c(
"Table 14.1.1",
"Demographics and Baseline Characteristics",
"Safety Population"
),
footnotes = "Percentages based on N per treatment group."
) |>
cols(
variable = col_spec(usage = "group", label = "Parameter"),
stat_label = col_spec(label = "Statistic"),
Placebo = col_spec(
label = "Placebo\nN={n['placebo']}",
align = "decimal"
),
`Xanomeline Low Dose` = col_spec(
label = "Drug 50\nN={n['drug_50']}",
align = "decimal"
),
`Xanomeline High Dose` = col_spec(
label = "Drug 100\nN={n['drug_100']}",
align = "decimal"
),
Total = col_spec(
label = "Total\nN={n['Total']}",
align = "decimal"
)
)
Table 14.1.1
Demographics and Baseline Characteristics
Safety Population
Statistic Placebo
N=86 Drug 100
N=72 Drug 50
N=96 Total
N=254 Age (years) N 86 72 96 254 Mean (SD) 75.2 (8.59) 73.8 (7.94) 76.0 (8.11) 75.1 (8.25) Median 76.0 75.5 78.0 77.0 Min, Max 52 , 89 56 , 88 51 , 88 51 , 89 WEIGHT N 86 72 95 253 Mean (SD) 62.8 (12.77) 69.5 (14.35) 68.0 (14.50) 66.6 (14.13) Median 60.6 69.0 66.7 66.7 Min, Max 34 , 86 44 , 108 42 , 106 34 , 108 HEIGHT N 86 72 96 254 Mean (SD) 162.6 (11.52) 165.9 (10.28) 163.7 (10.30) 163.9 (10.76) Median 162.6 165.1 162.6 162.8 Min, Max 137 , 185 146 , 190 136 , 196 136 , 196 BMI N 86 72 95 253 Mean (SD) 23.6 (3.67) 25.2 (3.97) 25.2 (4.40) 24.7 (4.09) Median 23.4 24.8 24.8 24.2 Min, Max 15 , 33 14 , 35 15 , 40 14 , 40 AGEGR1 18-64 14 (16.3%) 11 (15.3%) 8 ( 8.3%) 33 (13.0%) >64 72 (83.7%) 61 (84.7%) 88 (91.7%) 221 (87.0%) Sex F 53 (61.6%) 35 (48.6%) 55 (57.3%) 143 (56.3%) M 33 (38.4%) 37 (51.4%) 41 (42.7%) 111 (43.7%) Race WHITE 78 (90.7%) 62 (86.1%) 90 (93.8%) 230 (90.6%) BLACK OR AFRICAN AMERICAN 8 ( 9.3%) 9 (12.5%) 6 ( 6.2%) 23 ( 9.1%) ASIAN 0 0 0 0 AMERICAN INDIAN OR ALASKA NATIVE 0 1 ( 1.4%) 0 1 ( 0.4%) ETHNIC HISPANIC OR LATINO 3 ( 3.5%) 3 ( 4.2%) 6 ( 6.2%) 12 ( 4.7%) NOT HISPANIC OR LATINO 83 (96.5%) 69 (95.8%) 90 (93.8%) 242 (95.3%) NOT REPORTED 0 0 0 0 BMI_CAT Underweight (<18.5) 3 ( 3.5%) 1 ( 1.4%) 4 ( 4.2%) 8 ( 3.2%) Normal (18.5-24.9) 57 (66.3%) 39 (54.2%) 46 (48.4%) 142 (56.1%) Overweight (25-29.9) 20 (23.3%) 23 (31.9%) 32 (33.7%) 75 (29.6%) Obese (>=30) 6 ( 7.0%) 9 (12.5%) 13 (13.7%) 28 (11.1%)
Percentages based on N per treatment group.
# ---- Example 2: Hierarchical SOC/PT AE table ----
#
# Hierarchical `cards::ard_stack_hierarchical()` output threaded
# through `pivot_across()`. The hierarchical ARD emits a
# (soc, label, row_type) triple plus one stat row per (arm, SOC, PT);
# `pivot_across()` folds the arm dimension to columns and preserves
# the hierarchy markers. Derive `indent_level` from `row_type` so
# `col_spec(indent = "indent_level")` drives the SOC -> PT
# indent on the `label` column.
wide <- cdisc_saf_aesocpt_ard |>
pivot_across(statistic = "{n} ({p}%)")
wide$indent_level <- as.integer(wide$row_type == "pt")
tabular(
wide,
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),
row_type = col_spec(visible = FALSE),
Placebo = col_spec(
label = "Placebo\nN={n['placebo']}",
align = "decimal"
),
`Xanomeline Low Dose` = col_spec(
label = "Drug 50\nN={n['drug_50']}",
align = "decimal"
),
`Xanomeline High Dose` = col_spec(
label = "Drug 100\nN={n['drug_100']}",
align = "decimal"
)
)
Table 14.3.1
Adverse Events by System Organ Class and Preferred Term
Safety Population
SOC / PT Placebo
N=86 Drug 100
N=72 Drug 50
N=96 Overall 52 (60%) 66 (92%) 81 (84%) SKIN AND SUBCUTANEOUS TISSUE DISORDERS 19 (22%) 35 (49%) 36 (38%) PRURITUS 8 ( 9%) 25 (35%) 21 (22%) ERYTHEMA 8 ( 9%) 14 (19%) 14 (15%) RASH 5 ( 6%) 8 (11%) 13 (14%) HYPERHIDROSIS 2 ( 2%) 8 (11%) 4 ( 4%) SKIN IRRITATION 3 ( 3%) 5 ( 7%) 6 ( 6%) GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS 15 (17%) 30 (42%) 36 (38%) APPLICATION SITE PRURITUS 6 ( 7%) 21 (29%) 23 (24%) APPLICATION SITE ERYTHEMA 3 ( 3%) 14 (19%) 13 (14%) APPLICATION SITE DERMATITIS 5 ( 6%) 7 (10%) 9 ( 9%) APPLICATION SITE IRRITATION 3 ( 3%) 9 (12%) 9 ( 9%) APPLICATION SITE VESICLES 1 ( 1%) 5 ( 7%) 5 ( 5%) GASTROINTESTINAL DISORDERS 13 (15%) 17 (24%) 12 (12%) DIARRHOEA 9 (10%) 3 ( 4%) 5 ( 5%) VOMITING 3 ( 3%) 6 ( 8%) 4 ( 4%) NAUSEA 3 ( 3%) 6 ( 8%) 3 ( 3%) ABDOMINAL PAIN 1 ( 1%) 1 ( 1%) 3 ( 3%) SALIVARY HYPERSECRETION 0 4 ( 6%) 0 NERVOUS SYSTEM DISORDERS 6 ( 7%) 17 (24%) 18 (19%) DIZZINESS 2 ( 2%) 10 (14%) 9 ( 9%) HEADACHE 3 ( 3%) 5 ( 7%) 3 ( 3%) SYNCOPE 0 2 ( 3%) 5 ( 5%) SOMNOLENCE 2 ( 2%) 1 ( 1%) 3 ( 3%) TRANSIENT ISCHAEMIC ATTACK 0 1 ( 1%) 2 ( 2%) CARDIAC DISORDERS 7 ( 8%) 14 (19%) 12 (12%) SINUS BRADYCARDIA 2 ( 2%) 8 (11%) 7 ( 7%) MYOCARDIAL INFARCTION 4 ( 5%) 4 ( 6%) 2 ( 2%) ATRIAL FIBRILLATION 1 ( 1%) 2 ( 3%) 2 ( 2%) SUPRAVENTRICULAR EXTRASYSTOLES 1 ( 1%) 1 ( 1%) 1 ( 1%) VENTRICULAR EXTRASYSTOLES 0 1 ( 1%) 2 ( 2%) INFECTIONS AND INFESTATIONS 12 (14%) 11 (15%) 6 ( 6%) NASOPHARYNGITIS 2 ( 2%) 6 ( 8%) 4 ( 4%) UPPER RESPIRATORY TRACT INFECTION 6 ( 7%) 3 ( 4%) 1 ( 1%) INFLUENZA 1 ( 1%) 1 ( 1%) 1 ( 1%) URINARY TRACT INFECTION 2 ( 2%) 1 ( 1%) 0 CYSTITIS 1 ( 1%) 1 ( 1%) 0 RESPIRATORY, THORACIC AND MEDIASTINAL DISORDERS 5 ( 6%) 9 (12%) 8 ( 8%) COUGH 1 ( 1%) 5 ( 7%) 5 ( 5%) NASAL CONGESTION 3 ( 3%) 3 ( 4%) 1 ( 1%) DYSPNOEA 1 ( 1%) 1 ( 1%) 1 ( 1%) EPISTAXIS 0 2 ( 3%) 1 ( 1%) PHARYNGOLARYNGEAL PAIN 0 1 ( 1%) 1 ( 1%) PSYCHIATRIC DISORDERS 7 ( 8%) 3 ( 4%) 9 ( 9%) CONFUSIONAL STATE 2 ( 2%) 1 ( 1%) 3 ( 3%) AGITATION 2 ( 2%) 0 3 ( 3%) INSOMNIA 2 ( 2%) 2 ( 3%) 0 ANXIETY 0 0 3 ( 3%) DELUSION 1 ( 1%) 1 ( 1%) 0 MUSCULOSKELETAL AND CONNECTIVE TISSUE DISORDERS 3 ( 3%) 5 ( 7%) 6 ( 6%) BACK PAIN 1 ( 1%) 3 ( 4%) 1 ( 1%) ARTHRALGIA 1 ( 1%) 1 ( 1%) 2 ( 2%) SHOULDER PAIN 1 ( 1%) 0 2 ( 2%) MUSCLE SPASMS 0 1 ( 1%) 1 ( 1%) ARTHRITIS 0 1 ( 1%) 0 INVESTIGATIONS 5 ( 6%) 3 ( 4%) 4 ( 4%) ELECTROCARDIOGRAM ST SEGMENT DEPRESSION 4 ( 5%) 0 1 ( 1%) ELECTROCARDIOGRAM T WAVE INVERSION 2 ( 2%) 1 ( 1%) 1 ( 1%) BLOOD GLUCOSE INCREASED 0 1 ( 1%) 1 ( 1%) ELECTROCARDIOGRAM T WAVE AMPLITUDE DECREASED 1 ( 1%) 0 1 ( 1%) BIOPSY 0 1 ( 1%) 0
Subjects are counted once per SOC and once per PT.
Percentages based on N per treatment group.
# ---- Example 3: Hierarchical ARD (SOC / PT) ----
#
# `cdisc_saf_aesocpt_ard` carries an `ard_stack_hierarchical` shape with
# two grouping variables (AEBODSYS / AEDECOD). `pivot_across()`
# recognises the hierarchical structure and emits dedicated `soc`,
# `label`, and `row_type` columns so the SOC -> PT nesting survives
# the pivot. The result is ready for `tabular()` plus `sort_rows()`.
head(cdisc_saf_aesocpt_ard, 3)
#> {cards} data frame: 3 x 10
#> group1 group1_level group2 group2_level variable variable_level
#> 1 <NA> <NA> TRT01A Placebo
#> 2 <NA> <NA> TRT01A Placebo
#> 3 <NA> <NA> TRT01A Placebo
#> stat_name stat_label stat
#> 1 n n 86
#> 2 N N 254
#> 3 p % 0.339
#> ℹ 1 more variable: context
wide <- cdisc_saf_aesocpt_ard |>
pivot_across(statistic = "{n} ({p}%)")
head(wide, 3)
#> soc
#> 1 Overall
#> 2 SKIN AND SUBCUTANEOUS TISSUE DISORDERS
#> 3 SKIN AND SUBCUTANEOUS TISSUE DISORDERS
#> label row_type Placebo
#> 1 Overall overall 52 (60%)
#> 2 SKIN AND SUBCUTANEOUS TISSUE DISORDERS soc 19 (22%)
#> 3 PRURITUS pt 8 (9%)
#> Xanomeline High Dose Xanomeline Low Dose
#> 1 66 (92%) 81 (84%)
#> 2 35 (49%) 36 (38%)
#> 3 25 (35%) 21 (22%)
# ---- Example 4: Multi-row continuous spec + label re-labelling ----
#
# `statistic = c(<label> = <template>, ...)` produces one display
# row per named entry — the canonical "N / Mean (SD) / Median /
# Min, Max" block for continuous variables. `label = c(...)`
# renames the variable headings emitted into the wide output.
cdisc_saf_demo_ard |>
pivot_across(
statistic = list(
continuous = c(
N = "{N}",
"Mean (SD)" = "{mean} ({sd})",
Median = "{median}",
"Q1, Q3" = "{p25}, {p75}",
"Min, Max" = "{min}, {max}"
),
categorical = "{n} ({p}%)"
),
label = c(
AGE = "Age (years)",
WEIGHT = "Weight (kg)",
HEIGHT = "Height (cm)",
BMI = "BMI (kg/m^2)"
)
)
#> variable stat_label Placebo
#> 1 Age (years) N 86
#> 2 Age (years) Mean (SD) 75.2 (8.59)
#> 3 Age (years) Median 76.0
#> 4 Age (years) Q1, Q3 69.0, 82.0
#> 5 Age (years) Min, Max 52.0, 89.0
#> 6 Weight (kg) N 86
#> 7 Weight (kg) Mean (SD) 62.8 (12.77)
#> 8 Weight (kg) Median 60.6
#> 9 Weight (kg) Q1, Q3 53.5, 74.4
#> 10 Weight (kg) Min, Max 34.0, 86.2
#> 11 Height (cm) N 86
#> 12 Height (cm) Mean (SD) 162.6 (11.52)
#> 13 Height (cm) Median 162.6
#> 14 Height (cm) Q1, Q3 153.7, 171.4
#> 15 Height (cm) Min, Max 137.2, 185.4
#> 16 BMI (kg/m^2) N 86
#> 17 BMI (kg/m^2) Mean (SD) 23.6 (3.67)
#> 18 BMI (kg/m^2) Median 23.4
#> 19 BMI (kg/m^2) Q1, Q3 21.2, 25.7
#> 20 BMI (kg/m^2) Min, Max 15.1, 33.3
#> 21 AGEGR1 18-64 14 (16%)
#> 22 AGEGR1 >64 72 (84%)
#> 23 SEX F 53 (62%)
#> 24 SEX M 33 (38%)
#> 25 RACE WHITE 78 (91%)
#> 26 RACE BLACK OR AFRICAN AMERICAN 8 (9%)
#> 27 RACE ASIAN 0
#> 28 RACE AMERICAN INDIAN OR ALASKA NATIVE 0
#> 29 ETHNIC HISPANIC OR LATINO 3 (3%)
#> 30 ETHNIC NOT HISPANIC OR LATINO 83 (97%)
#> 31 ETHNIC NOT REPORTED 0
#> 32 BMI_CAT Underweight (<18.5) 3 (3%)
#> 33 BMI_CAT Normal (18.5-24.9) 57 (66%)
#> 34 BMI_CAT Overweight (25-29.9) 20 (23%)
#> 35 BMI_CAT Obese (>=30) 6 (7%)
#> Xanomeline High Dose Xanomeline Low Dose Total
#> 1 72 96 254
#> 2 73.8 (7.94) 76.0 (8.11) 75.1 (8.25)
#> 3 75.5 78.0 77.0
#> 4 70.0, 79.0 71.0, 82.0 70.0, 81.0
#> 5 56.0, 88.0 51.0, 88.0 51.0, 89.0
#> 6 72 95 253
#> 7 69.5 (14.35) 68.0 (14.50) 66.6 (14.13)
#> 8 69.0 66.7 66.7
#> 9 56.7, 80.3 55.8, 78.5 55.3, 77.1
#> 10 44.5, 108.0 41.7, 106.1 34.0, 108.0
#> 11 72 96 254
#> 12 165.9 (10.28) 163.7 (10.30) 163.9 (10.76)
#> 13 165.1 162.6 162.8
#> 14 157.5, 172.9 157.5, 170.2 156.2, 171.4
#> 15 146.1, 190.5 135.9, 195.6 135.9, 195.6
#> 16 72 95 253
#> 17 25.2 (3.97) 25.2 (4.40) 24.7 (4.09)
#> 18 24.8 24.8 24.2
#> 19 22.7, 27.6 22.2, 28.3 21.9, 27.3
#> 20 13.7, 34.6 15.3, 40.2 13.7, 40.2
#> 21 11 (15%) 8 (8%) 33 (13%)
#> 22 61 (85%) 88 (92%) 221 (87%)
#> 23 35 (49%) 55 (57%) 143 (56%)
#> 24 37 (51%) 41 (43%) 111 (44%)
#> 25 62 (86%) 90 (94%) 230 (91%)
#> 26 9 (12%) 6 (6%) 23 (9%)
#> 27 0 0 0
#> 28 1 (1%) 0 1 (0%)
#> 29 3 (4%) 6 (6%) 12 (5%)
#> 30 69 (96%) 90 (94%) 242 (95%)
#> 31 0 0 0
#> 32 1 (1%) 4 (4%) 8 (3%)
#> 33 39 (54%) 46 (48%) 142 (56%)
#> 34 23 (32%) 32 (34%) 75 (30%)
#> 35 9 (12%) 13 (14%) 28 (11%)
# ---- Example 5: ARD keyed by summary / tabulate contexts ----
#
# The `statistic` list names must match the ARD's `context` column
# verbatim. `cards::ard_summary()` / `ard_tabulate()` emit `"summary"` /
# `"tabulate"` (not the `"continuous"` / `"categorical"` of
# `ard_continuous()` / `ard_categorical()`), so a list keyed
# `continuous`/`categorical` would silently match nothing. Always check
# `unique(ard$context)` first. Here the bundled `cdisc_saf_demo_ard` is
# relabelled to mimic `ard_summary()` + `ard_tabulate()` output; the
# by-variable's own row drops automatically and both the summary and
# the tabulate variables survive.
card_st <- cdisc_saf_demo_ard
card_st$context[card_st$context == "continuous"] <- "summary"
card_st$context[card_st$context == "categorical"] <- "tabulate"
pivot_across(
card_st,
statistic = list(
summary = "{mean} ({sd})",
tabulate = "{n} ({p}%)"
)
)
#> variable stat_label Placebo
#> 1 AGE AGE 75.2 (8.59)
#> 2 WEIGHT WEIGHT 62.8 (12.77)
#> 3 HEIGHT HEIGHT 162.6 (11.52)
#> 4 BMI BMI 23.6 (3.67)
#> 5 AGEGR1 18-64 14 (16%)
#> 6 AGEGR1 >64 72 (84%)
#> 7 SEX F 53 (62%)
#> 8 SEX M 33 (38%)
#> 9 RACE WHITE 78 (91%)
#> 10 RACE BLACK OR AFRICAN AMERICAN 8 (9%)
#> 11 RACE ASIAN 0
#> 12 RACE AMERICAN INDIAN OR ALASKA NATIVE 0
#> 13 ETHNIC HISPANIC OR LATINO 3 (3%)
#> 14 ETHNIC NOT HISPANIC OR LATINO 83 (97%)
#> 15 ETHNIC NOT REPORTED 0
#> 16 BMI_CAT Underweight (<18.5) 3 (3%)
#> 17 BMI_CAT Normal (18.5-24.9) 57 (66%)
#> 18 BMI_CAT Overweight (25-29.9) 20 (23%)
#> 19 BMI_CAT Obese (>=30) 6 (7%)
#> Xanomeline High Dose Xanomeline Low Dose Total
#> 1 73.8 (7.94) 76.0 (8.11) 75.1 (8.25)
#> 2 69.5 (14.35) 68.0 (14.50) 66.6 (14.13)
#> 3 165.9 (10.28) 163.7 (10.30) 163.9 (10.76)
#> 4 25.2 (3.97) 25.2 (4.40) 24.7 (4.09)
#> 5 11 (15%) 8 (8%) 33 (13%)
#> 6 61 (85%) 88 (92%) 221 (87%)
#> 7 35 (49%) 55 (57%) 143 (56%)
#> 8 37 (51%) 41 (43%) 111 (44%)
#> 9 62 (86%) 90 (94%) 230 (91%)
#> 10 9 (12%) 6 (6%) 23 (9%)
#> 11 0 0 0
#> 12 1 (1%) 0 1 (0%)
#> 13 3 (4%) 6 (6%) 12 (5%)
#> 14 69 (96%) 90 (94%) 242 (95%)
#> 15 0 0 0
#> 16 1 (1%) 4 (4%) 8 (3%)
#> 17 39 (54%) 46 (48%) 142 (56%)
#> 18 23 (32%) 32 (34%) 75 (30%)
#> 19 9 (12%) 13 (14%) 28 (11%)