tabular turns one pre-summarised, wide data frame into a publication-ready table and renders it natively to RTF, HTML, DOCX, PDF/LaTeX and Markdown — from a single spec, with no Java or Office dependency.
Two things to internalise up front:
tabular is display-only. It never aggregates, filters, or computes statistics. You bring a summarised data frame (one input row = one display row); tabular lays it out and renders it. (Producing that frame from a cards ARD is the Data in article.)
One immutable spec, built with verbs. You pipe a tabular() object through verbs (cols(), headers(), paginate(), preset(), …) and finish with emit(). Each verb returns a new spec; nothing renders until emit().
Your first table
Start from a wide frame — here the bundled demographics summary, one row per statistic, one column per treatment arm:
data(cdisc_saf_demo, package ="tabular")head(cdisc_saf_demo)#> variable stat_label placebo drug_50 drug_100 Total#> 1 Age (years) n 86 96 72 254#> 2 Age (years) Mean (SD) 75.2 (8.59) 76.0 (8.11) 73.8 (7.94) 75.1 (8.25)#> 3 Age (years) Median 76.0 78.0 75.5 77.0#> 4 Age (years) Q1, Q3 69.2, 81.8 71.0, 82.0 70.5, 79.0 70.0, 81.0#> 5 Age (years) Min, Max 52, 89 51, 88 56, 88 51, 89#> 6 Sex, n (%) F 53 (61.6) 55 (57.3) 35 (48.6) 143 (56.3)
Describe the columns. The spec prints as a live HTML table — this is the same render emit() produces, shown inline:
To write a file, hand the spec to emit(); the backend is chosen by the file extension (or an explicit format =):
out <-tempfile(fileext =".rtf")emit(spec, out) # RTF here; swap to .docx / .pdf / .html / .mdfile.exists(out)#> [1] TRUE
That is the whole loop: wide frame → tabular() → cols() → emit() — one spec, any backend.
The pipeline at a glance
Read it left to right. You summarise upstream — with cards/cardx, dplyr, or SAS — into a long ARD, widen that to a display-ready frame with pivot_across(), then hand it to tabular. Inside the package the work happens in three phases (Build → Resolve → Emit), and the same resolved spec emits to every backend, so the HTML you preview and the RTF you ship can never disagree.
tabular’s pipeline: summarise upstream into a long ARD, widen it with pivot_across(), then build, resolve, and emit one immutable spec to every backend.
Anatomy of a clinical table page
A submission table is not just a grid of numbers — it is a page with four stacked sections, and a reviewer expects each one in its place. Every tabular verb maps onto a piece of this picture:
The four-section clinical page and the verb that fills each section.
Header section — the running protocol, optional status, and page x of y, set as page chrome with preset(pagehead =, pagefoot =).
Title lines — the table number and up to four centred titles, passed to tabular(titles =).
Data section — an optional subgroup() banner, the column-header band built by cols() and headers(), then the decimal-aligned data.
Footnote lines — your static footnotes = plus any auto-numbered footnote() markers, then the program path, name, and timestamp.
preset() frames all four by controlling the page geometry (paper, orientation, margins, fonts).
Where to next
The rest of the docs are task-oriented — read the one that matches what you are doing:
Data in — turn a cards/cardx ARD (or any long ARD) into the wide frame, with pivot_across().
Structure — columns, headers, BigN, and splitting wide or long tables across pages (cols(), headers(), paginate(), subgroup()).