cardinal is on CRAN

The cardinal catalog of harmonized FDA safety-table templates is now on CRAN as {cardinalfda} — built on {gtsummary}, {cards} and {crane}, and shipping an ARD with every table.
TLG
Community
Author

Joe Zhu

Published

September 23, 2026

A few years ago {cardinal} started life as a quarto catalog of copy-paste template scripts — a cross-company effort (Boehringer Ingelheim, Moderna, Roche, Sanofi and friends) to open-source a harmonized set of the FDA safety tables everyone in clinical reporting keeps rebuilding from scratch.

It just grew up. cardinal is now on CRAN — installable, versioned, dependency-managed:

install.packages("cardinalfda")

Yes, the package is cardinalfda on CRAN (plain cardinal was already taken). Same initiative, same website, now a proper release you can pin in a lockfile and trust to still install next year.

What changed under the hood

If you looked at cardinal a while back, the tables were {rtables}. That’s no longer the engine. The catalog has moved to the {gtsummary} + {cards}/{cardx} + {crane} stack:

  • {gtsummary} builds the table,
  • {cards} / {cardx} build the ARD (Analysis Results Dataset) behind it,
  • {crane} supplies the pharma-specific bits (e.g. tbl_listing() for listings).

And that middle point is the part I’m most excited about: every cardinal table now comes with an ARD. More on that below.

The anatomy of a cardinal template

The nicest thing about the new layout is that every template tells the same story — and it maps onto how a statistical programmer actually thinks. Each catalog page has the same four tabs: Table Preview → Setup → Build Table → Build ARD. Let’s walk FDA Table 2 (Baseline Demographic and Clinical Characteristics, Safety Population) through it.

1. The IG — the FDA Integrated Guide

cardinal’s templates are drawn from the FDA’s Standard Safety Tables and Figures Integrated Guide (the “IG”), and are indexed by its table numbers — fda-table_02 is IG Table 2. The IG is the specification: it says exactly what the table should contain — the rows, the summary statistics, the population, the footnotes. So there’s never any doubt about what the output is supposed to be.

2. The output snapshot — the preview

Every catalog page opens with a Table Preview — a snapshot of the target output, the “here’s what we’re building” picture. It’s the contract between the IG spec and the code.

FDA Table 2 preview

3. The actual code

Then the part programmers came for — the actual, runnable code. cardinal ships each IG template as a self-contained script, so nothing is a black box: the template gives you the layout, you supply your ADaM data. Here is the real fda-table_02 template, run against the public {pharmaverseadam} ADSL:

library(dplyr)

adsl <- pharmaverseadam::adsl |>
  # removing screen failure observations
  filter(TRT01A != "Screen Failure")

tbl <- adsl |>
  dplyr::filter(SAFFL == "Y") |>
  gtsummary::tbl_summary(
    by = dplyr::all_of("TRT01A"),
    include = dplyr::all_of(c("SEX", "AGE", "AGEGR1", "ETHNIC", "RACE")),
    type = gtsummary::all_continuous() ~ "continuous2",
    statistic = list(
      gtsummary::all_continuous() ~ c(
        "{mean} ({sd})",
        "{median} ({min}, {max})"
      ),
      gtsummary::all_categorical() ~ "{n} ({p}%)"
    ),
    label = list(AGEGR1 = "Age Group, Years")
  ) |>
  gtsummary::add_overall(last = TRUE, col_label = "**Total Population**  \nN = {N}") |>
  gtsummary::remove_footnote_header(columns = dplyr::everything())

4. The real output

Because the template is real code over real (public) data, the last step is just to print it — and that same table is what you’d drop into your report:

tbl
Characteristic Placebo
N = 86
Xanomeline High Dose
N = 72
Xanomeline Low Dose
N = 96
Total Population
N = 254
Sex



    F 53 (62%) 35 (49%) 55 (57%) 143 (56%)
    M 33 (38%) 37 (51%) 41 (43%) 111 (44%)
Age



    Mean (SD) 75 (9) 74 (8) 76 (8) 75 (8)
    Median (Min, Max) 76 (52, 89) 76 (56, 88) 78 (51, 88) 77 (51, 89)
Age Group, Years



    18-64 14 (16%) 11 (15%) 8 (8.3%) 33 (13%)
    >64 72 (84%) 61 (85%) 88 (92%) 221 (87%)
Ethnicity



    HISPANIC OR LATINO 3 (3.5%) 3 (4.2%) 6 (6.3%) 12 (4.7%)
    NOT HISPANIC OR LATINO 83 (97%) 69 (96%) 90 (94%) 242 (95%)
Race



    AMERICAN INDIAN OR ALASKA NATIVE 0 (0%) 1 (1.4%) 0 (0%) 1 (0.4%)
    BLACK OR AFRICAN AMERICAN 8 (9.3%) 9 (13%) 6 (6.3%) 23 (9.1%)
    WHITE 78 (91%) 62 (86%) 90 (94%) 230 (91%)

Prefer not to read the whole script? Every template is also runnable in one line, which sources the shipped script and hands you back the built objects:

env <- run_template("fda-table_02")
env$tbl # the gtsummary table
env$ard # the ARD (see below)

The bit I like most: an ARD falls out for free

An ARD (Analysis Results Dataset) is the tidy, machine-readable version of a table — every statistic as a row, before any formatting. It’s the emerging CDISC direction for analysis results, and it’s what makes results reusable, comparable and reviewable rather than locked inside a rendered table.

With the {cards}-based approach, you don’t build the ARD separately — it’s gathered straight from the table you already made:

ard <- gtsummary::gather_ard(tbl)
ard$tbl_summary |>
  dplyr::select(dplyr::any_of(
    c("group1", "group1_level", "variable", "stat_name", "stat")
  )) |>
  head(8)
# An ARD data frame: 8 × 5
  group1 group1_level variable stat_name   stat
  <chr>  <list>       <chr>    <chr>     <list>
1 TRT01A Placebo      SEX      n         53    
2 TRT01A Placebo      SEX      N         86    
3 TRT01A Placebo      SEX      p          0.616
4 TRT01A Placebo      SEX      n         33    
5 TRT01A Placebo      SEX      N         86    
6 TRT01A Placebo      SEX      p          0.384
7 TRT01A Placebo      RACE     n          0    
8 TRT01A Placebo      RACE     N         86    

Same numbers as the table, now as data you can filter, diff against a previous run, or feed into a meta-analysis. Table and ARD, from one template — that’s the direction cardinal is heading.

Try it, and come help

Install it, run a template, and if you spot a table your team builds differently, that’s an invitation — cardinal is a collaboration, and new templates are exactly the kind of contribution the initiative wants. The cardinal website has the full catalog, and the pharmaverse examples site is a great next stop for seeing these packages used end to end.

Last updated

2026-10-20 15:02:04.247126

Details

Reuse

Citation

BibTeX citation:
@online{zhu2026,
  author = {Zhu, Joe},
  title = {Cardinal Is on {CRAN}},
  date = {2026-09-23},
  url = {https://pharmaverse.github.io/blog/posts/2026-09-23-cardinal-is-on-cran/cardinal-is-on-cran.html},
  langid = {en}
}
For attribution, please cite this work as:
Zhu, Joe. 2026. “Cardinal Is on CRAN.” September 23, 2026. https://pharmaverse.github.io/blog/posts/2026-09-23-cardinal-is-on-cran/cardinal-is-on-cran.html.