arframe is a regulatory frame for TFLs — submission-ready tables, listings, and figures in RTF, PDF, and HTML from a single specification. You describe the output once; arframe renders it. Change the file extension, the output adapts. No dplyr, no gt, no RMarkdown — just data in, document out.
Your first table
spec <- tbl_demog |>
fr_table() |>
fr_titles("Table 14.1.1", "Demographics and Baseline Characteristics") |>
fr_hlines("header")
spec| characteristic | placebo | zom_50mg | zom_100mg | total | group |
|---|---|---|---|---|---|
| Subjects, n | 45 | 45 | 45 | 135 | n |
| Age (years) | age_cont | ||||
| Mean (SD) | 75.0 (6.75) | 73.1 (8.43) | 75.3 (7.09) | 74.4 (7.46) | age_cont |
| Median | 74.0 | 74.0 | 73.0 | 74.0 | age_cont |
| Min, Max | 65.0, 88.0 | 55.0, 88.0 | 55.0, 88.0 | 55.0, 88.0 | age_cont |
| Age Group, n (%) | age_cat | ||||
| <65 | 0 | 7 (15.6) | 1 (2.2) | 8 (5.9) | age_cat |
| 65-80 | 36 (80.0) | 29 (64.4) | 31 (68.9) | 96 (71.1) | age_cat |
| >80 | 9 (20.0) | 9 (20.0) | 13 (28.9) | 31 (23.0) | age_cat |
| Sex, n (%) | sex | ||||
| Female | 27 (60.0) | 28 (62.2) | 20 (44.4) | 75 (55.6) | sex |
| Male | 18 (40.0) | 17 (37.8) | 25 (55.6) | 60 (44.4) | sex |
| Race, n (%) | race | ||||
| White | 38 (84.4) | 32 (71.1) | 35 (77.8) | 105 (77.8) | race |
| Black or African American | 1 (2.2) | 5 (11.1) | 5 (11.1) | 11 (8.1) | race |
| Asian | 6 (13.3) | 6 (13.3) | 5 (11.1) | 17 (12.6) | race |
| American Indian or Alaska Native | 0 | 2 (4.4) | 0 | 2 (1.5) | race |
| BMI (kg/m2) | bmi | ||||
| Mean (SD) | 27.1 (4.76) | 26.5 (4.60) | 24.9 (5.81) | 26.1 (5.13) | bmi |
| Median | 27.8 | 25.9 | 24.6 | 25.8 | bmi |
| Min, Max | 18.7, 42.3 | 17.3, 36.5 | 14.2, 44.5 | 14.2, 44.5 | bmi |
| MMSE Score at Baseline | mmse | ||||
| Mean (SD) | 19.9 (3.51) | 19.8 (3.36) | 19.5 (3.55) | 19.7 (3.45) | mmse |
| Median | 20.0 | 20.0 | 19.0 | 20.0 | mmse |
| Min, Max | 13.0, 26.0 | 13.0, 26.0 | 13.0, 26.0 | 13.0, 26.0 | mmse |
| Study Completion, n (%) | completion | ||||
| Completed | 44 (97.8) | 41 (91.1) | 37 (82.2) | 122 (90.4) | completion |
| Discontinued | 1 (2.2) | 4 (8.9) | 8 (17.8) | 13 (9.6) | completion |
That’s a complete, renderable table. To produce a file:
spec |> fr_render("Table_14_1_1.rtf")
spec |> fr_render("Table_14_1_1.pdf")
spec |> fr_render("Table_14_1_1.html")Same spec, any format. The file extension controls the output.
Production-ready table
Real pharma tables need column labels, N-counts, footnotes, and page
chrome. Start by setting shared formatting once with
fr_theme() — every table in the session inherits it:
# ── Set once per study program (or use _arframe.yml) ──
fr_theme(
font_size = 9,
font_family = "Times New Roman",
orientation = "landscape",
hlines = "header",
header = list(bold = TRUE, align = "center"),
n_format = "{label}\n(N={n})",
footnote_separator = FALSE,
pagehead = list(left = "TFRM-2024-001", right = "CONFIDENTIAL"),
pagefoot = list(left = "{program}",
right = "Page {thepage} of {total_pages}")
)
n_itt <- c(placebo = 45, zom_50mg = 45, zom_100mg = 45, total = 135)Now each table only specifies what is unique to it:
spec <- tbl_demog |>
fr_table() |>
fr_titles(
"Table 14.1.1",
"Demographics and Baseline Characteristics",
"Intent-to-Treat Population"
) |>
fr_cols(
.width = "fit",
characteristic = fr_col("", width = 2.5),
placebo = fr_col("Placebo", align = "decimal"),
zom_50mg = fr_col("Zomerane 50mg", align = "decimal"),
zom_100mg = fr_col("Zomerane 100mg", align = "decimal"),
total = fr_col("Total", align = "decimal"),
group = fr_col(visible = FALSE),
.n = n_itt
) |>
fr_rows(group_by = "group", blank_after = "group") |>
fr_footnotes("Percentages based on number of subjects per treatment group.")
fr_validate(spec)| Placebo (N=45) |
Zomerane 50mg (N=45) |
Zomerane 100mg (N=45) |
Total (N=135) |
|
|---|---|---|---|---|
| Subjects, n | 45 | 45 | 45 | 135 |
| Age (years) | ||||
| Mean (SD) | 75.0 (6.75) | 73.1 (8.43) | 75.3 (7.09) | 74.4 (7.46) |
| Median | 74.0 | 74.0 | 73.0 | 74.0 |
| Min, Max | 65.0, 88.0 | 55.0, 88.0 | 55.0, 88.0 | 55.0, 88.0 |
| Age Group, n (%) | ||||
| <65 | 0 | 7 (15.6) | 1 ( 2.2) | 8 ( 5.9) |
| 65-80 | 36 (80.0) | 29 (64.4) | 31 (68.9) | 96 (71.1) |
| >80 | 9 (20.0) | 9 (20.0) | 13 (28.9) | 31 (23.0) |
| Sex, n (%) | ||||
| Female | 27 (60.0) | 28 (62.2) | 20 (44.4) | 75 (55.6) |
| Male | 18 (40.0) | 17 (37.8) | 25 (55.6) | 60 (44.4) |
| Race, n (%) | ||||
| White | 38 (84.4) | 32 (71.1) | 35 (77.8) | 105 (77.8) |
| Black or African American | 1 ( 2.2) | 5 (11.1) | 5 (11.1) | 11 ( 8.1) |
| Asian | 6 (13.3) | 6 (13.3) | 5 (11.1) | 17 (12.6) |
| American Indian or Alaska Native | 0 | 2 ( 4.4) | 0 | 2 ( 1.5) |
| BMI (kg/m2) | ||||
| Mean (SD) | 27.1 (4.76) | 26.5 (4.60) | 24.9 (5.81) | 26.1 (5.13) |
| Median | 27.8 | 25.9 | 24.6 | 25.8 |
| Min, Max | 18.7, 42.3 | 17.3, 36.5 | 14.2, 44.5 | 14.2, 44.5 |
| MMSE Score at Baseline | ||||
| Mean (SD) | 19.9 (3.51) | 19.8 (3.36) | 19.5 (3.55) | 19.7 (3.45) |
| Median | 20.0 | 20.0 | 19.0 | 20.0 |
| Min, Max | 13.0, 26.0 | 13.0, 26.0 | 13.0, 26.0 | 13.0, 26.0 |
| Study Completion, n (%) | ||||
| Completed | 44 (97.8) | 41 (91.1) | 37 (82.2) | 122 (90.4) |
| Discontinued | 1 ( 2.2) | 4 ( 8.9) | 8 (17.8) | 13 ( 9.6) |
Notice what is not in the table program: header styling, hlines, page headers/footers, N-format, footnote separator — the theme handles all of it. This is how production study programs work. See Automation & Batch for the full pattern.
Table anatomy
Every pharma table has the same structural regions. Here’s how they map to arframe verbs:
+------------------------------------------------------------------------+
| Protocol TFRM-2024-001 CONFIDENTIAL |<- fr_pagehead()
+------------------------------------------------------------------------+
| |
| Table 14.1.1 |<- fr_titles()
| Demographics and Baseline Characteristics |
| |
| ---------------------------------------------------------------------- |
| Placebo Zomerane 50mg Zomerane 100mg Total |<- fr_cols() +
| (N=45) (N=45) (N=45) (N=135) | fr_cols(.n=)
| ====================================================================== |<- fr_hlines()
| Age (years) |
| Mean (SD) 68.2 (7.1) 67.8 (6.9) 68.0 (7.3) 68.0 (7.1) |<- data rows
| |
| Sex, n (%) |<- fr_rows(blank_after=)
| Male 26 (57.8) 24 (53.3) 25 (55.6) 75 (55.6) |
| ---------------------------------------------------------------------- |
| Percentages based on number of subjects per treatment group. |<- fr_footnotes()
+------------------------------------------------------------------------+
| t_demog.R Page 1 of 1 |<- fr_pagefoot()
+------------------------------------------------------------------------+
10 verbs, 10 regions. Learn the verbs, build any table.
The pipeline
Every verb takes a spec, returns a spec. Verb order doesn’t matter — arframe resolves everything (widths, N-count labels, page breaks) at render time.
What’s next
| I want to… | Read |
|---|---|
| Configure columns, widths, decimal alignment | Columns & Headers |
| Add N-counts to column headers | Columns & Headers |
| Write titles, footnotes, superscripts | Titles & Footnotes |
| Group rows, paginate, indent, group_label | Rows & Page Layout |
| Add borders, spanning headers, cell colors | Rules, Spans & Styles |
| Copy a complete table recipe | Table Cookbook |
| Create listings or embed figures | Listings & Figures |
| Set up study-wide defaults, batch render | Automation & Batch |
| Understand the internals | Architecture |