tern Tabulation

The tern R package provides functions to create common analyses from clinical trials in R. The core functionality for tabulation is built on the more general purpose rtables package. New users should first begin by reading the “Introduction to tern” and “Introduction to rtables vignettes.

The packages used in this vignette are:

library(rtables)
library(tern)
library(dplyr)

tables.R

The datasets used in this vignette are:

adsl <- ex_adsl
adae <- ex_adae
adrs <- ex_adrs

tables.R

tern Analyze Functions

Analyze functions are used in combination with the rtables layout functions, in the pipeline which creates the rtables table. They apply some statistical logic to the layout of the rtables table. The table layout is materialized with the rtables::build_table function and the data.

The tern analyze functions are wrappers around rtables::analyze function, they offer various methods useful from the perspective of clinical trials and other statistical projects.

Examples of the tern analyze functions are count_occurrences, summarize_ancova or analyze_vars. As there is no one prefix to identify all tern analyze functions it is recommended to use the the tern website functions reference.

Internals of tern Analyze Functions

Please skip this subsection if you are not interested in the internals of tern analyze functions.

Internally tern analyze functions like summarize_ancova are mainly built in the 4 elements chain:

h_ancova() -> tern:::s_ancova() -> tern:::a_ancova() -> summarize_ancova()

The descriptions for each function type:

  • analysis helper functions h_*. These functions are useful to help define the analysis.
  • statistics function s_*. Statistics functions should do the computation of the numbers that are tabulated later. In order to separate computation from formatting, they should not take care of rcell type formatting themselves.
  • formatted analysis functions a_*. These have the same arguments as the corresponding statistics functions, and can be further customized by calling rtables::make_afun() on them. They are used as afun in rtables::analyze().
  • analyze functions rtables::analyze(..., afun = make_afun(tern::a_*)). Analyze functions are used in combination with the rtables layout functions, in the pipeline which creates the table. They are the last element of the chain.

We will use the native rtables::analyze function with the tern formatted analysis functions as a afun parameter.

l <- basic_table() %>%
    split_cols_by(var = "ARM") %>%
    split_rows_by(var = "AVISIT") %>%
    analyze(vars = "AVAL", afun = a_summary)

build_table(l, df = adrs)

The rtables::make_afun function is helpful when somebody wants to attach some format to the formatted analysis function.

afun <- make_afun(
    a_summary,
    .stats = NULL,
    .formats = c(median = "xx."),
    .labels = c(median = "My median"),
    .indent_mods = c(median = 1L)
)

l2 <- basic_table() %>%
    split_cols_by(var = "ARM") %>%
    split_rows_by(var = "AVISIT") %>%
    analyze(vars = "AVAL", afun = afun)

build_table(l2, df = adrs)

Tabulation Examples

We are going to create 3 different tables using tern analyze functions and the rtables interface.

Table tern analyze functions
Demographic Table analyze_vars() and summarize_num_patients()
Adverse event Table count_occurrences()
Response Table estimate_proportion(), estimate_proportion_diff() and test_proportion_diff()

Demographic Table

Demographic tables provide a summary of the characteristics of patients enrolled in a clinical trial. Typically the table columns represent treatment arms and variables summarized in the table are demographic properties such as age, sex, race, etc.

In the example below the only function from tern is analyze_vars() and the remaining layout functions are from rtables.

# Select variables to include in table.
vars <- c("AGE", "SEX")
var_labels <- c("Age (yr)", "Sex")

basic_table() %>%
  split_cols_by(var = "ARM") %>%
  add_overall_col("All Patients") %>%
  add_colcounts() %>%
  analyze_vars(
    vars = vars,
    var_labels = var_labels
  ) %>%
  build_table(adsl)
#>                       A: Drug X    B: Placebo    C: Combination   All Patients
#>                        (N=134)       (N=134)        (N=132)         (N=400)   
#> ——————————————————————————————————————————————————————————————————————————————
#> Age (yr)                                                                      
#>   n                      134           134            132             400     
#>   Mean (SD)          33.8 (6.6)    35.4 (7.9)      35.4 (7.7)      34.9 (7.4) 
#>   Median                33.0          35.0            35.0            34.0    
#>   Min - Max          21.0 - 50.0   21.0 - 62.0    20.0 - 69.0     20.0 - 69.0 
#> Sex                                                                           
#>   n                      134           134            132             400     
#>   F                   79 (59%)     77 (57.5%)       66 (50%)      222 (55.5%) 
#>   M                  51 (38.1%)     55 (41%)       60 (45.5%)     166 (41.5%) 
#>   U                   3 (2.2%)      2 (1.5%)         4 (3%)         9 (2.2%)  
#>   UNDIFFERENTIATED    1 (0.7%)          0           2 (1.5%)        3 (0.8%)

tables.R

To change the display order of categorical variables in a table use factor variables and explicitly set the order of the levels. This is the case for the display order in columns and rows. Note that the forcats package has many useful functions to help with these types of data processing steps (not used below).

# Reorder the levels in the ARM variable.
adsl$ARM <- factor(adsl$ARM, levels = c("B: Placebo", "A: Drug X", "C: Combination"))

# Reorder the levels in the SEX variable.
adsl$SEX <- factor(adsl$SEX, levels = c("M", "F", "U", "UNDIFFERENTIATED"))

basic_table() %>%
  split_cols_by(var = "ARM") %>%