
Clinical Tables, Figures, and Listings —
from R code to validated DOCX, in one pipeline.
Why ksTFL
ksTFL is a professional R package designed to fill a long-standing gap in the R ecosystem: the lack of a dedicated, end-to-end solution for producing well-formatted, regulatory-compliant clinical Tables, Figures, and Listings (TFLs). While R excels at statistical analysis, generating submission-quality DOCX outputs that meet pharmaceutical industry standards has traditionally required fragile workarounds or external tooling. ksTFL solves this by distilling the best ideas from existing reporting solutions into a simple, minimalistic, yet highly flexible declarative language — a compact set of composable functions whose combinations can produce virtually any clinical output format.
The core philosophy is data–presentation separation: input data stays clean and planar, free of reporting artefacts such as merged cells, indentation columns, or display-only rows. Formatting, pagination, grouping, and styling are declared independently and applied at render time. This keeps datasets maintainable, traceable, and validation-ready — exactly what regulated environments demand.
Under the hood, a built-in rendering engine with text shaping converts declarative specs into styled DOCX documents with deterministic, pixel-perfect pagination. Rendering is extremely fast even on large datasets, making ksTFL practical for batch production of hundreds of outputs in a single pipeline run.
Key design principles
- Separation of concerns — metadata generation (R) is decoupled from document rendering; input data remain clean and analysis-ready
- Declarative syntax — describe what to render, not how to render it; a small function vocabulary covers the full range of clinical outputs
- Deterministic pagination — font shaping guarantees pixel-perfect, reproducible layouts and page breaks
- High performance — the C++ engine renders large multi-spec reports in seconds
- Type safety — comprehensive input validation with informative error messages
- Reproducibility — specifications are serializable, so stored metadata can be replayed years later without re-running the analysis pipeline
What the output looks like
Five verbs stand between a data frame and a submission-ready document:
create_*() -> define / add_* / compute_* -> create_report() -> write_doc() -> .docx
specify style & compose validate & merge render
No external tools, no Word macros, no “final_v7_reallyfinal.docx”. One table, one figure, one listing — or all of them, dozens per run, assembled into a single report whose clickable table of contents writes itself. And the endless argument with Word over pagination simply ends: pages break where your spec says they should break, not where Word decides to, so the layout you validated today is the layout on screen a year from now. Every page below was rendered by ksTFL itself. Click a page to open it full size; click its caption to jump to the example walked through line by line in Real Examples.
Quick start
This script is all it takes — and yes, iris really is enough:
library(ksTFL)
# any messy source becomes one tidy summary frame - your usual R, nothing exotic
summary <- do.call(rbind, lapply(split(iris, iris$Species), function(g)
data.frame(Species = g$Species[1], N = nrow(g),
SL_mean = mean(g$Sepal.Length), SL_sd = sd(g$Sepal.Length),
PL_mean = mean(g$Petal.Length), PL_sd = sd(g$Petal.Length))))
spec <- create_table(summary) |>
add_span_header(c(SL_mean, SL_sd), "Sepal", stubOrder = 1) |>
add_span_header(c(PL_mean, PL_sd), "Petal", stubOrder = 1) |>
define_cols(c(SL_mean, PL_mean),
label = c("mean", "mean"), valueStyleRef = "ar", format = "%.2f") |>
compute_cols(PL_mean < 2.5,
c_style(c(PL_mean, PL_sd), styleRef = f_combine("fc_green", "b"))) |>
add_title("Iris, Measured Flower by Flower", styleRef = "b") |>
add_subtitle("Mean separation of the three species", styleRef = "i") |>
add_header("Fisher Herbarium", "IRIS STUDY", "Page {PAGE} of {NUMPAGES}") |>
add_footer("Collected 1936", "", format(Sys.Date(), "Compiled %Y-%m-%d")) |>
add_footnote("Fisher (1936). Green: petals so distinct the species almost name themselves.") |>
set_document(contentWidth = "70%")
write_doc(create_report(spec), name = "iris_summary")That’s a print-ready Word document — but look at what those lines actually did:
-
add_span_header()grouped the columns underSepalandPetalbanners, each stretched over its ownmean/SDpair. This is the header lattice reviewers expect in a real report — and you declared it in two lines instead of merging cells by hand in Word. Pass the samestubOrderto siblings and a higher one to the umbrella above them: the geometry is your call, the drawing is ksTFL’s. -
define_cols()set the look of whole column families at once —valueStyleRef = "ar"right-aligns both means,format = "%.2f"pins every number to two decimals. The style rides with the definition, so it survives whatever the data become next quarter; no per-cell babysitting. -
compute_cols()is where it gets fun: “whenPL_mean < 2.5, applyc_style()with thefc_green+batoms” is not an edit, it is a rule. Setosa’s petals turn green now, and every future run re-dyes them automatically — thresholds, colors, and groups stay in the code where they can be reviewed, not lost in a Word session nobody remembers. The style itself is snapped together from built-in atoms viaf_combine(): over 120 ready-made pieces (fonts, colors, indents, borders), zero custom style boilerplate. -
add_title()/add_subtitle()/add_header()/add_footer()wrote the document’s paperwork — including a livePage {PAGE} of {NUMPAGES}counter that updates itself when the report re-paginates. What you see in the screenshot’s top and bottom bands is exactly the three-slot header/footer API, no Word section wizardry. -
set_document(contentWidth = "70%")sized the table to a comfortable reading column, andcreate_report()+write_doc()did the rest: validation, style consolidation, shaping, pagination —iris_summary.docxlands in your working directory, done.
Run the script, open the file, compare it with the screenshot — that gap between “analysis finished” and “report ready” you’ve been living with? Just closed.
Installation
ksTFL ships pre-compiled binaries for R 4.5 and R 4.6 on Windows, Ubuntu/Debian, Fedora/RHEL, and macOS (ARM) — no compilers or system libraries needed for most users.
The simplest method, works on every platform:
install.packages("ksTFL",
repos = c("https://crow16384.r-universe.dev", "https://cloud.r-project.org"))Windows binaries are also available directly:
install.packages("ksTFL",
repos = "https://crow16384.github.io/ksTFL-release",
type = "binary")Or download finished packages (.zip, .tar.gz, .tgz) from the release repository and install with install.packages(file, repos = NULL).
Building from source requires a C++20 compiler and R development tools (Rtools on Windows, Xcode Command Line Tools on macOS, build-essential on Linux):
remotes::install_github("crow16384/ksTFL")What else it can do
- One look for the whole company. Corporate layouts come built in — pick a template and every table, figure and listing in the run instantly wears the same face: same fonts, same margins, same navy header. Hand a client a submission where nothing drifts from page to page.
- It writes around your missing fonts. The package reads the fonts on your machine and quietly substitutes a metric-compatible open-source one when a proprietary font (Arial, Times New Roman, …) isn’t there. The report lays out the same whether you ran it on your laptop or on a Linux build server.
- A report that re-builds itself. Every output saves its specification, so you can regenerate the exact same Word document months later, on another machine, without touching the original analysis. Auditors love this; so do you, at 2 a.m. before a submission.
- Highlight what matters, by rule. Color a cell red when a value crosses a threshold, bold the first row of every group, drop a section header where the category changes — described once, applied forever, never by hand in Word.
-
Rich text inside cells. Bold, italic, superscripts, subscripts and line breaks right in your values and titles (
H<sub>2</sub>O,p<0.05<sup>*</sup>), the way the final document needs them. - Point and click, if you like. RStudio add-ins give you a template editor and a style picker, so you can compose layouts visually instead of memorizing option names.
- Any language, all at once. Cyrillic, CJK, Greek letters and ordinary text share a page without turning into boxes.
Documentation & resources
| I want to… | Go to |
|---|---|
| Learn the workflow in one sitting | Getting Started |
| Solve a specific problem | FAQ & Troubleshooting |
| See real examples, table by table | Real Examples and the examples repository |
| Fine-tune colors, fonts, borders | Styling Guide |
| Look up any function | Reference |
| Print a cheat sheet | Cheatsheet (PDF) |
| Give a talk | Slides (PDF) |
License. GPL-3. Authors. Igor Aleschenkov, Vladimir Larchenko — ksTFL Team ©
