Implements a grammar of summary data for clinical reports. Clinical summary tables are decomposed into modular layers, each representing an independent summary block. Supports count, descriptive statistics, and shift layer types with a declarative spec-based API built on data.table for performance.
{tplyr2} is a grammar of clinical summary tables. Clinical reports -- demographics tables, adverse event summaries, lab shift tables -- all share a common structural pattern. Each section is some kind of summary: a set of counts, a block of descriptive statistics, or a cross-tabulation. Rather than writing bespoke data manipulation code for every table, {tplyr2} lets you describe what the table should contain using a declarative specification, and handles the computing, formatting, and assembly for you.
{tplyr2} is a ground-up rewrite of {Tplyr}, built on data.table for performance. The spec-based API separates configuration from data, making specs portable, reusable, and serializable to JSON/YAML.
You can install the development version of {tplyr2} from GitHub:
# install.packages("devtools")
devtools::install_github("atorus-research/tplyr2")
Every table starts with a tplyr_spec() that declares the column structure and layers. Data is supplied at build time with tplyr_build().
library(tplyr2)
spec <- tplyr_spec(
cols = "TRT01P",
layers = tplyr_layers(
group_count("SEX", by = "Sex n (%)"),
group_desc(
"AGE",
by = "Age (Years)",
settings = layer_settings(
format_strings = list(
"n" = f_str("xxx", "n"),
"Mean (SD)" = f_str("xx.x (xx.xx)", "mean", "sd"),
"Median" = f_str("xx.x", "median"),
"Min, Max" = f_str("xx, xx", "min", "max")
)
)
)
)
)
result <- tplyr_build(spec, tplyr_adsl)
knitr::kable(result[, !grepl("^ord", names(result))])
| rowlabel1 | rowlabel2 | res1 | res2 | res3 |
|---|---|---|---|---|
| Sex n (%) | F | 53 (61.6%) | 40 (47.6%) | 50 (59.5%) |
| Sex n (%) | M | 33 (38.4%) | 44 (52.4%) | 34 (40.5%) |
| Age (Years) | n | 86 | 84 | 84 |
| Age (Years) | Mean (SD) | 75.2 ( 8.59) | 74.4 ( 7.89) | 75.7 ( 8.29) |
| Age (Years) | Median | 76.0 | 76.0 | 77.5 |
| Age (Years) | Min, Max | 52, 89 | 56, 88 | 51, 88 |
tplyr_spec() object is pure configuration -- no data, no side effects. It describes the column variable, filters, population data, and layers.group_count(), group_desc(), group_shift(), or group_analyze().tplyr_build(spec, data) executes the spec against a dataset and returns a formatted data frame.f_str() declarations control numeric precision and alignment (e.g., f_str("xx.x (xx.xx)", "mean", "sd")).Tabulate frequencies of categorical variables, with support for nested counts, distinct subject counting, and total rows.
spec <- tplyr_spec(
cols = "TRTA",
layers = tplyr_layers(
group_count(
c("AEBODSYS", "AEDECOD"),
settings = layer_settings(
distinct_by = "USUBJID",
format_strings = list(
n_counts = f_str("xxx (xx.x%)", "distinct_n", "distinct_pct")
)
)
)
)
)
result <- tplyr_build(spec, tplyr_adae)
knitr::kable(head(result[, !grepl("^ord", names(result))], 10))
| rowlabel1 | rowlabel2 | res1 | res2 | res3 |
|---|---|---|---|---|
| CARDIAC DISORDERS | 4 (12.5%) | 6 (14.0%) | 5 (10.0%) | |
| CARDIAC DISORDERS | ATRIAL FIBRILLATION | 0 ( 0.0%) | 0 ( 0.0%) | 1 ( 2.0%) |
| CARDIAC DISORDERS | ATRIAL FLUTTER | 0 ( 0.0%) | 1 ( 2.3%) | 0 ( 0.0%) |
| CARDIAC DISORDERS | ATRIAL HYPERTROPHY | 1 ( 3.1%) | 0 ( 0.0%) | 0 ( 0.0%) |
| CARDIAC DISORDERS | BUNDLE BRANCH BLOCK RIGHT | 1 ( 3.1%) | 0 ( 0.0%) | 0 ( 0.0%) |
| CARDIAC DISORDERS | CARDIAC FAILURE CONGESTIVE | 1 ( 3.1%) | 0 ( 0.0%) | 0 ( 0.0%) |
| CARDIAC DISORDERS | MYOCARDIAL INFARCTION | 0 ( 0.0%) | 1 ( 2.3%) | 2 ( 4.0%) |
| CARDIAC DISORDERS | SINUS BRADYCARDIA | 0 ( 0.0%) | 3 ( 7.0%) | 1 ( 2.0%) |
| CARDIAC DISORDERS | SUPRAVENTRICULAR EXTRASYSTOLES | 1 ( 3.1%) | 0 ( 0.0%) | 1 ( 2.0%) |
| CARDIAC DISORDERS | SUPRAVENTRICULAR TACHYCARDIA | 0 ( 0.0%) | 0 ( 0.0%) | 1 ( 2.0%) |
Summarize continuous variables with built-in statistics (n, mean, sd, median, min, max, q1, q3, var, iqr, missing) or custom summary functions.
Cross-tabulate a baseline value against a post-baseline value within each treatment arm using group_shift().
Run user-defined analysis functions with group_analyze() for full flexibility.
pop_data() and display (N=n) header counts with tplyr_header_n()total_group() and custom_group()tplyr_numeric_data()tplyr_write_spec() / tplyr_read_spec()tplyr_to_ard() / tplyr_from_ard()Getting started
vignette("tplyr2") -- Getting startedvignette("table") -- Spec-level table propertiesLayers
vignette("count") -- Count layers in depthvignette("desc") -- Descriptive statistics layersvignette("shift") -- Shift layersvignette("analyze") -- Custom analyze layersvignette("adverse-events") -- A full adverse event table, end to endFormatting
vignette("format_strings") -- The format string systemvignette("precision_alignment") -- Data-driven precision and parenthesis huggingvignette("display_conventions") -- <1%, zero-count suppression, statistics as columnsTable customization
vignette("denom") -- Denominators, population data, header Nvignette("sort") -- Ordering rows and layersvignette("options") -- Session optionsvignette("post_processing") -- Post-processing helpersStatistics and traceability
vignette("riskdiff") -- Risk difference columnsvignette("binding-statistics") -- Association tests and binding external model resultsvignette("metadata") -- Cell-level metadata and traceabilityvignette("serialization") -- Saving and loading specsvignette("ard") -- Analysis Results Data conversionvignette("migration") -- Moving from Tplyr v1