Clinical researchers spend a disproportionate amount of time on the mechanics of generating summary tables: choosing the right test, formatting P values, cleaning variable names, writing the methods paragraph, and wrestling with Word. For descriptive statistics and standard univariate comparisons, this work is largely procedural — it follows well-established rules — yet it consistently becomes a bottleneck that slows down manuscript preparation.
TernTables handles that procedural layer automatically. Variable type detection, statistical test selection, P value formatting, Word document export, and methods text generation are all handled in a single function call. The output tables are ready to paste directly into a submission.
The time savings come entirely from automating the administrative work (formatting, test selection bookkeeping, and methods writing), not from bypassing statistical rigour. Every decision follows established criteria: normality is assessed with the Shapiro-Wilk test applied per group; the Fisher’s exact / Chi-squared switch follows the Cochran (1954) expected-cell criterion; odds ratios are unadjusted, with the first factor level of the grouping variable as the reference; and the auto-generated methods paragraph covers the statistical approach used, ready to serve as a starting draft for a manuscript’s statistical methods section. The output is appropriate for submission to peer-reviewed clinical journals.
For analyses that go beyond descriptive statistics and standard group comparisons, a biostatistician remains the right resource. TernTables is designed to free up that time for the work that actually requires it.
Raw data from CSV or XLSX files can be standardized with
ternP() before analysis — string NA values, whitespace,
blank rows, capitalization inconsistencies, and PHI column names are all
handled automatically with full preprocessing feedback.
Descriptive summaries (Table 1), two-group comparisons (with optional
odds ratios), and three-group comparisons are all supported, for
continuous, binary, and categorical variables. Numeric variables can be
designated as ordinal via force_ordinal, bypassing
normality testing in favour of median/IQR and nonparametric tests.
TernTables is available as a free, point-and-click web application at tern-tables.com — no R installation required. Upload a CSV or XLSX file, configure your analysis through a simple interface, and download a publication-ready Word table in minutes.
The web application is powered directly by this R package. The
statistical methods, test selection logic (including ROBUST normality
routing), and Word output format are identical to calling
ternG(), ternD(), and ternP() in
R.
The web app is transparent by design: a built-in side panel displays the exact R commands being executed in the background as you work, and the full script can be downloaded at the end of your session. Every analysis produced on the web app is therefore fully auditable and reproducible — the downloaded script runs as-is in R and produces identical output. This makes it suitable for sharing with statistical reviewers, co-authors, or IRB documentation, and serves as a practical entry point for researchers who want to transition to scripted R workflows.
For batch processing, custom formatting, or programmatic integration with other analyses, the R package (this repository) is the canonical reference.
Install from R-universe (no additional tools required):
install.packages("TernTables",
repos = c("https://jdpreston30.r-universe.dev",
"https://cloud.r-project.org"))Or install the development version directly from GitHub:
# install.packages("devtools")
devtools::install_github("jdpreston30/TernTables", build_vignettes = TRUE)A full walkthrough is available in the package vignette (built
automatically when installing from R-universe; use
build_vignettes = TRUE with the GitHub install):
vignette("getting-started", package = "TernTables")The bundled tern_colon dataset is derived from
survival::colon (Moertel et al.,
1990). It is a reformatted subset restricted to the recurrence
endpoint (etype == 1), providing one row per patient (n =
929) with clinically labelled factor levels and descriptive column
names. See ?tern_colon and
data-raw/tern_colon.R for the full pre-processing
pipeline.
library(TernTables)
data(tern_colon)ternP() — Preprocess raw
dataCleans a raw CSV or XLSX file before passing it to
ternG() or ternD(). Converts string NA values
("NA", "na", "Na",
"unk"), trims whitespace, drops 100% empty columns, removes
blank rows, and normalizes capitalization inconsistencies. Hard stops
with a descriptive error if any column name matches a PHI pattern
(e.g. MRN, DOB, FirstName) or if
any unnamed column contains data.
raw <- readr::read_csv("my_data.csv", show_col_types = FALSE)
result <- ternP(raw)
# Prints a cleaning summary automatically:
# ✔ No transformations required. Data passed through unchanged.
# ─────────────────────────────────────────────────────────────
# ℹ Cleaned data: 929 rows × 14 columns.
result$clean_data # analysis-ready tibble → pass to ternG() or ternD()
result$sparse_rows # rows with >50% missing, retained but flagged
result$feedback # named list of what changed (NULL = no action)write_cleaning_doc()
— Data cleaning audit documentWrites a Word document recording every transformation applied by
ternP(). Only paragraphs for triggered transformations are
included; if the data was already clean, a single sentence stating that
is written instead. Suitable for data management logs, IRB
documentation, or supplemental materials.
write_cleaning_doc(result, filename = "cleaning_summary.docx")ternD() —
Descriptive summary tableGenerates a single-column summary of all variables without group comparisons.
tbl_descriptive <- ternD(
data = tern_colon,
exclude_vars = c("ID"),
output_docx = "descriptive.docx"
)force_ordinal to treat specific numeric variables
as ordinalternG() — Grouped
comparison tableUse ternG() to compare variables between two or more
groups. Set OR_col = TRUE to add unadjusted odds ratios
with 95% CI for any two-level variable in two-group comparisons —
including binary variables (Y/N, 0/1) and two-level categoricals such as
Male/Female or Present/Absent. The reference level (factor level 1, or
alphabetical first for non-factors) shows 1.00 (ref.); the
non-reference level shows the computed OR with 95% CI. Fisher’s exact or
Wald method is chosen automatically based on expected cell counts
(Cochran criterion). Odds ratios are not available for 3+ groups.
Two-group comparison:
tbl_2group <- ternG(
data = tern_colon,
exclude_vars = c("ID"),
group_var = "Recurrence",
consider_normality = TRUE,
OR_col = TRUE,
output_docx = "two_group.docx"
)Three-group comparison with post-hoc testing:
tbl_3group <- ternG(
data = tern_colon,
exclude_vars = c("ID"),
group_var = "Treatment_Arm",
group_order = c("Observation", "Levamisole", "Levamisole + 5FU"),
consider_normality = TRUE,
post_hoc = TRUE,
output_docx = "three_group.docx"
)Omnibus P values are reported for 3+ group comparisons. When
post_hoc = TRUE and the omnibus P < 0.05,
pairwise post-hoc tests are run automatically for continuous and ordinal
variables: Games-Howell following Welch ANOVA, and Dunn’s test with Holm
correction following Kruskal-Wallis. Results appear as compact letter
display (CLD) superscripts appended to each cell value — groups sharing
a letter are not significantly different. Categorical variables never
receive post-hoc testing. Odds ratios are not available for 3+
groups.
Statistical tests applied automatically:
| Variable type | 2 groups | 3+ groups | Post-hoc (3+ groups, post_hoc = TRUE, omnibus
p < 0.05) |
|---|---|---|---|
| Continuous, normal | Welch’s t-test | Welch ANOVA | Games-Howell |
| Continuous, non-normal | Wilcoxon rank-sum | Kruskal-Wallis | Dunn’s + Holm |
| Binary / Categorical | Fisher’s exact or Chi-squared | Fisher’s exact or Chi-squared | — |
Ordinal (force_ordinal) |
Wilcoxon rank-sum | Kruskal-Wallis | Dunn’s + Holm |
Fisher’s exact is used when any expected cell count is < 5
(Cochran criterion). If the exact algorithm cannot complete (workspace
limit exceeded for large tables), Fisher’s exact with Monte Carlo
simulation (B = 10,000; seed fixed via
getOption("TernTables.seed"), default 42) is used
automatically.
Normality routing uses consider_normality = "ROBUST"
(default) — a four-gate decision: (1) any group n < 3 →
non-parametric (conservative fail-safe); (2) absolute skewness > 2 in
any group → non-parametric; (3) all groups n ≥ 30 → parametric via the
Central Limit Theorem; (4) otherwise Shapiro-Wilk p > 0.05 in all
groups → parametric. Set consider_normality = TRUE to use
Shapiro-Wilk alone (original behaviour).
word_export() —
Format and export to WordFormats any TernTables result tibble as a flextable and writes to
.docx. Use category_start to insert bold
section-header rows between variable groups.
word_export(
tbl = tbl_2group,
filename = "two_group.docx",
category_start = c(
"Patient Demographics" = "Age (yr)",
"Surgical Findings" = "Colonic Obstruction",
"Tumor Characteristics" = "Positive Lymph Nodes (n)"
)
)write_methods_doc()
— Auto-generated methods paragraphInspects the results tibble and writes a Word document containing a standard statistical methods paragraph covering the normality assessment approach, test types used, significance threshold, and (when odds ratios are reported) the estimation method and reference group. Suitable as a starting draft for a manuscript methods section.
write_methods_doc(
tbl = tbl_2group,
filename = "methods.docx"
)val_p_format()
/ val_format() — Formatting utilitiesval_p_format(0.0432) # "0.043"
val_p_format(0.000012) # "1E-5"
val_format(72.4, 8.1) # "72.4 ± 8.1"Every ternD() and ternG() call returns a
tibble and, when output_docx is specified, writes a
publication-ready .docx file directly. The Word output uses
Arial, consistent padding, bold significant P values, and
optional bold category-section headers. No post-processing is required
before pasting into a manuscript.
The tibble can also be:
word_export() for additional formatting
control (e.g. category_start)table_caption = "Table 1. Patient demographics."table_footnote = "† P values from Wilcoxon rank-sum test."
(accepts a character vector for multiple lines)ternB(list(T1, T2, T3), output_docx = "tables.docx")writexl::write_xlsx()This project is licensed under the MIT License.
Web Application: tern-tables.com — the full TernTables workflow in a point-and-click interface, powered by this package.
Developed and maintained by:
Feedback and contributions are welcome.