Tutorial: tbl_summary

Last Updated: January 13, 2020


The tbl_summary() function calculates descriptive statistics for continuous, categorical, and dichotomous variables in R, and presents the results in a beautiful, customizable summary table perfect for creating tables ready for publication (for example, Table 1 or demographic tables).

This vignette will walk a reader through the tbl_summary() function, and the various functions available to modify and make additions to an existing table summary object.

To start, a quick note on the {magrittr} package’s pipe function, %>%. By default the pipe operator puts whatever is on the left hand side of %>% into the first argument of the function on the right hand side. The pipe function can be used to make the code relating to tbl_summary() easier to use, but it is not required. Here are a few examples of how %>% translates into typical R notation.

x %>% f() is equivalent to f(x)
x %>% f(y) is equivalent to f(x, y)
y %>% f(x, .) is equivalent to f(x, y)
z %>% f(x, y, arg = .) is equivalent to f(x, y, arg = z)

Here’s how this translates into the use of tbl_summary().

mtcars %>% tbl_summary() is equivalent to tbl_summary(mtcars)
mtcars %>% tbl_summary(by = am) is equivalent to tbl_summary(mtcars, by = am)
tbl_summary(mtcars, by = am) %>% add_p() is equivalent to
    tbl = tbl_summary(mtcars, by = am)


Before going through the tutorial, install {gtsummary} and {gt}.

remotes::install_github("rstudio/gt", ref = gtsummary::gt_sha)


Example data set

We’ll be using the trial data set throughout this example.

#> # A tibble: 6 x 8
#>   trt      age marker stage grade response death ttdeath
#>   <chr>  <dbl>  <dbl> <fct> <fct>    <int> <int>   <dbl>
#> 1 Drug A    23  0.16  T1    II           0     0    24  
#> 2 Drug B     9  1.11  T2    I            1     0    24  
#> 3 Drug A    31  0.277 T1    II           0     0    24  
#> 4 Drug A    NA  2.07  T3    III          1     1    17.6
#> 5 Drug A    51  2.77  T4    III          1     1    16.4
#> 6 Drug B    39  0.613 T4    I            0     1    15.6

For brevity in the tutorial, let’s keep a subset of the variables from the trial data set.

trial2 =
  trial %>%
  dplyr::select(trt, marker, stage)

Basic Usage

The default output from tbl_summary() is meant to be publication ready.

Let’s start by creating a table of summary statistics from the trial data set. The tbl_summary() function can take, at minimum, a data frame as the only input, and returns descriptive statistics for each column in the data frame.

Characteristic N = 2001
Chemotherapy Treatment
Drug A 98 (49%)
Drug B 102 (51%)
Marker Level, ng/mL 0.64 (0.21, 1.39)
Unknown 10
T Stage
T1 53 (26%)
T2 54 (27%)
T3 43 (22%)
T4 50 (25%)

1 Statistics presented: n (%); median (IQR)

Note the sensible defaults with this basic usage (that can be customized later):

This is a great basic table, but for this study data the summary statistics should be split by treatment group, which can be done by using the by = argument. To compare two or more groups, include add_p() with the function call, which detects variable type and uses an appropriate test.

tbl_summary(trial2, by = trt) %>% add_p()
Characteristic Drug A, N = 981 Drug B, N = 1021 p-value2
Marker Level, ng/mL 0.84 (0.24, 1.57) 0.52 (0.19, 1.20) 0.085
Unknown 6 4
T Stage 0.9
T1 28 (29%) 25 (25%)
T2 25 (26%) 29 (28%)
T3 22 (22%) 21 (21%)
T4 23 (23%) 27 (26%)

1 Statistics presented: median (IQR); n (%)

2 Statistical tests performed: Wilcoxon rank-sum test; chi-square test of independence

Customize Output

There are four primary ways to customize the output of the summary table.

  1. Modify tbl_summary() function input arguments
  2. Add additional data/information to a summary table with add_*() functions
  3. Modify summary table appearance with the {gtsummary} functions
  4. Modify table appearance with {gt} package functions

Modifying tbl_summary() function arguments

The tbl_summary() function includes many input options for modifying the appearance.

label       specify the variable labels printed in table  
type        specify the variable type (e.g. continuous, categorical, etc.)
statistic   change the summary statistics presented  
digits      number of digits the summary statistics will be rounded to  
missing     whether to display a row with the number of missing observations 
sort        change the sorting of categorical levels by frequency
percent     print column, row, or cell percentages

{gtsummary} functions to add information

The {gtsummary} package has built-in functions for adding to results from tbl_summary(). The following functions add columns and/or information to the summary table.

add_p()           add p-values to the output comparing values across groups   
add_overall()     add a column with overall summary statistics   
add_n()           add a column with N (or N missing) for each variable   
add_stat_label()  add a column showing a label for the summary statistics shown in each row   
add_q()           add a column of q values to control for multiple comparisons   

{gtsummary} functions to format table

The {gtsummary} package comes with functions specifically made to modify and format summary tables.

modify_header()         relabel columns in summary table  
bold_labels()           bold variable labels  
bold_levels()           bold variable levels  
italicize_labels()      italicize variable labels  
italicize_levels()      italicize variable levels  
bold_p()                bold significant p-values  

{gt} functions to format table

The {gt} package is packed with many great functions for modifying table output—too many to list here. Review the package’s website for a full listing. https://gt.rstudio.com/index.html

To use the {gt} package functions with {gtsummary} tables, the summary table must first be converted into a gt object. To this end, use the as_gt() function after modifications have been completed with {gtsummary} functions.


The code below calculates the standard table with summary statistics split by treatment with the following modifications

Characteristic Statistic Drug A, N = 98 (49%)1 Drug B, N = 102 (51%)1 p-value2
Marker, ng/mL mean (SD) 1.0 (0.89) 0.8 (0.83) 0.12
Unknown n 6 4
Clinical T Stage 0.87
T1 n / N (%) 28 / 98 (29%) 25 / 102 (25%)
T2 n / N (%) 25 / 98 (26%) 29 / 102 (28%)
T3 n / N (%) 22 / 98 (22%) 21 / 102 (21%)
T4 n / N (%) 23 / 98 (23%) 27 / 102 (26%)

1 Statistics presented: mean (SD); n / N (%)

2 Statistical tests performed: t-test; chi-square test of independence

Each of the modification functions have additional options outlined in their respective help files.

Select Helpers

There is flexibility in how you select variables for {gtsummary} arguments, which allows for many customization opportunities! For example, if you want to show age and the marker levels to one decimal place in tbl_summary(), you can pass digits = c(age, marker) ~ 1. The selecting input is flexible, and you may also pass quoted column names.

Going beyond typing out specific variables in your dataset, you can use:

  1. All {tidyselect} helpers available throughout the tidyverse, such as starts_with(), contains(), and everything() (i.e. anything you can use with the dplyr::select() function can be used with {gtsummary}).

  2. Additional {gtsummary} selectors that are included in the package to supplement tidyselect functions.

Most variable selecting can be accomplished in a single R object (e.g. c(age, stage)). However, you may need more complex compound select structure, which cannot be represented by a single object. For example, if you want to select all columns except "stage", you’ll need to utilize the vars(...) function. When the vars(...) function is used, all arguments from vars() are passed directly to dplyr::select(). vars(everything(), -stage) is the dplyr equivalent of select(everything(), -stage).


In the example below, we report the mean and standard deviation for continuous variables, and percent for all categorical. We’ll report t-tests rather than Wilcoxon rank-sum test for continuous variables, and report Fisher’s exact test for response.

Note that dichotomous variables are, by default, included with all_categorical(). Use all_categorical(dichotomous = FALSE) to exclude dichotomous variables.

Characteristic Drug A, N = 981 Drug B, N = 1021 p-value2
Tumor Response 0.5
0 71% 66%
1 29% 34%
Unknown 3 4
Age, yrs 47 (15) 47 (14) 0.8
Unknown 7 4
T Stage 0.9
T1 29% 25%
T2 26% 28%
T3 22% 21%
T4 23% 26%
Marker Level, ng/mL 1.02 (0.89) 0.82 (0.83) 0.12
Unknown 6 4
Grade 0.9
I 36% 32%
II 33% 35%
III 32% 32%

1 Statistics presented: %; mean (SD)

2 Statistical tests performed: Fisher's exact test; t-test; chi-square test of independence

Advanced Customization

When you print output from the tbl_summary() function into the R console or into an R markdown, there are default printing functions that are called in the background: print.tbl_summary() and knit_print.tbl_summary(). The true output from tbl_summary() is a named list, but when you print the object, a formatted version of .$table_body is displayed. All formatting and modifications are made using the {gt} package.

tbl_summary(trial2) %>% names()
#> [1] "gt_calls"     "kable_calls"  "table_body"   "table_header" "meta_data"   
#> [6] "inputs"       "N"            "call_list"    "fmt_fun"

These are the additional data stored in the tbl_summary() output list.

table_body   data frame with summary statistics  
meta_data    data frame that is one row per variable with data about each  
by, df_by    the by variable name, and a  data frame with information about the by variable  
call_list    named list of each function called on the `tbl_summary` object  
inputs       inputs from the `tbl_summary()` function call  
gt_calls     named list of {gt} function calls  
kable_calls  named list of function calls to be applied before knitr::kable()  

gt_calls is a named list of saved {gt} function calls. The {gt} calls are run when the object is printed to the console or in an R markdown document. Here’s an example of the first few calls saved with tbl_summary():

tbl_summary(trial2) %>% purrr::pluck("gt_calls") %>% head(n = 5)
#> $gt
#> gt::gt(data = x$table_body)
#> $cols_align
#> gt::cols_align(align = 'center') %>% gt::cols_align(align = 'left', columns = gt::vars(label))
#> $fmt_missing
#> gt::fmt_missing(columns = gt::everything(), missing_text = '')
#> $tab_style_text_indent
#> gt::tab_style(style = gt::cell_text(indent = gt::px(10), align = 'left'),locations = gt::cells_body(columns = gt::vars(label),rows = row_type != 'label'))
#> $cols_label
#> gt::cols_label(label = gt::md("**Characteristic**"), stat_0 = gt::md("**N = 200**"))

The {gt} functions are called in the order they appear, always beginning with the gt() function.

If the user does not want a specific {gt} function to run (i.e. would like to change default printing), any {gt} call can be excluded in the as_gt() function. For example, the tbl_summary() call creates many named {gt} function calls: gt, cols_align, fmt_missing, tab_style_text_indent, cols_label, cols_hide, tab_footnote. Any of these can be excluded. In the example below, the default footnote will be excluded from the output.

After the as_gt() function is run, additional formatting may be added to the table using {gt} formatting functions. In the example below, a spanning header for the by = variable is included with the {gt} function tab_spanner().

tbl_summary(trial2, by = trt) %>%
  as_gt(include = -tab_footnote) %>%
  gt::tab_spanner(label = gt::md("**Treatment Group**"),
                  columns = gt::starts_with("stat_"))
Characteristic Treatment Group
Drug A, N = 98 Drug B, N = 102
Marker Level, ng/mL 0.84 (0.24, 1.57) 0.52 (0.19, 1.20)
Unknown 6 4
T Stage
T1 28 (29%) 25 (25%)
T2 25 (26%) 29 (28%)
T3 22 (22%) 21 (21%)
T4 23 (23%) 27 (26%)

Setting Default Options

The tbl_summary() function and it’s related functions have sensible defaults for rounding and formatting results. If you, however, would like to change the defaults there are a few options. The default options can be changed for a single script with addition an options() command in the script. The defaults can also be set on the project- or user-level R profile, .Rprofile.



The following parameters are available to be set:

Description Example Functions
Formatting and rounding p-values options(gtsummary.pvalue_fun = function(x) gtsummary::style_pvalue(x, digits = 2)) add_p(), tbl_regression(), tbl_uvregression()
Formatting and rounding percentages options(gtsummary.tbl_summary.percent_fun = function(x) sprintf("%.2f", 100 * x)) tbl_summary()
Print tables with gt or kable options(gtsummary.print_engine = "kable") options(gtsummary.print_engine = "gt") All tbl_*() functions

When setting default rounding/formatting functions, set the default to a function object rather than an evaluated function. For example, if you want to round percentages to 3 significant figures use,

options(gtsummary.tbl_summary.percent_fun = function(x) sigfig(x, digits = 3))