gtsummary for beginners: build a Table 1 from clinical data, step by step
clinical
biostatistics
gtsummary
r-tutorial
A gentle, hands-on gtsummary tutorial in R. Starting from a clinical trial dataset, we build a publication-ready Table 1 one verb at a time — then you can run it yourself in the browser.
Author
Rverse Analytics
Published
July 10, 2026
If you’ve ever built a “Table 1” — the baseline-characteristics table at the start of every clinical paper — by hand, you know it’s slow and easy to get wrong. The gtsummary package builds it correctly from your data with a few readable lines. This tutorial goes slowly, one step at a time, so you can see exactly what each piece does. Prefer to experiment as you read? Skip to the live demo and run gtsummary in your browser — no install.
Note
Already comfortable with the basics and just want the recipe? Our one-line Table 1 reference is the quick version. This post is the slow, first-time walkthrough.
The data
We’ll use trial, a small clinical-trial dataset that ships with gtsummary — 200 patients, with treatment, age, tumour marker, stage and grade. Because it comes with the package, you can follow along with no downloads.
# A tibble: 6 × 5
trt age marker stage grade
<chr> <dbl> <dbl> <fct> <fct>
1 Drug A 23 0.16 T1 II
2 Drug B 9 1.11 T2 I
3 Drug A 31 0.277 T1 II
4 Drug A NA 2.07 T3 III
5 Drug A 51 2.77 T4 III
6 Drug B 39 0.613 T4 I
Each column already carries a human-readable label (for example age is labelled “Age”) — gtsummary uses these automatically, which is part of why its tables look finished.
Notice what happened without any instructions from us: continuous variables (age, marker) are summarised as median (IQR), categorical ones (stage, grade) as n (%), and missing values are counted. gtsummary chose a sensible default for every variable type.
Step 2 — split by treatment group
A Table 1 almost always compares groups. Add by = trt to get one column per treatment arm:
Now each characteristic is shown separately for the two treatment groups — the shape every reader expects from a Table 1.
Step 3 — add a p-value
Reviewers usually want a statistical test comparing the groups. add_p() picks an appropriate test for each row automatically — Wilcoxon for continuous, chi-squared or Fisher for categorical:
2 Wilcoxon rank sum test; Pearson’s Chi-squared test
You didn’t choose the tests — gtsummary matched each one to the variable type. That removes a common source of error (running the wrong test on the wrong kind of variable).
Step 4 — an overall column
To also show the whole sample alongside the groups, add add_overall():
2 Wilcoxon rank sum test; Pearson’s Chi-squared test
Step 5 — polish for publication
A few finishing touches make it manuscript-ready: relabel variables, switch continuous summaries to mean (SD), show missing counts explicitly, bold the row labels, and add a caption.
Table 1. Baseline characteristics by treatment group
Characteristic
Overall
N = 2001
Drug A
N = 981
Drug B
N = 1021
p-value2
Age (years)
47 (14)
47 (15)
47 (14)
0.7
Missing
11
7
4
Marker (ng/mL)
0.92 (0.86)
1.02 (0.89)
0.82 (0.83)
0.085
Missing
10
6
4
T Stage
0.9
T1
53 (27%)
28 (29%)
25 (25%)
T2
54 (27%)
25 (26%)
29 (28%)
T3
43 (22%)
22 (22%)
21 (21%)
T4
50 (25%)
23 (23%)
27 (26%)
Grade
0.9
I
68 (34%)
35 (36%)
33 (32%)
II
68 (34%)
32 (33%)
36 (35%)
III
64 (32%)
31 (32%)
33 (32%)
1 Mean (SD); n (%)
2 Wilcoxon rank sum test; Pearson’s Chi-squared test
Read the pipeline top to bottom and it says exactly what it does: summarise by treatment, add a p-value, add an overall column, bold the labels, add a caption. That readability is the point — anyone can check the table against the code.
Why build it this way
The real win isn’t saving a few minutes once. It’s that the table is code. Fix a data point, rerun, and every number — including the tests and percentages — updates together. There’s never a moment where the manuscript and the data disagree.
Try it yourself
Two ways to run gtsummary right now, in your browser, on this same clinical dataset — no R install:
Point-and-click Table 1 explorer — choose the grouping variable and which characteristics to include, and watch the table rebuild. A real Shiny app running on WebAssembly.
Editable code demo — edit the gtsummary code and run it live, so you can try each verb from this tutorial yourself.