Creating Tables in R with Tern

Introduction

tern is an R package designed for streamlined clinical trial data analysis and reporting. Built on top of rtables, it provides ready-made statistical summaries and standardized formats to create accurate, regulatory-compliant tables with ease. Whether you’re summarizing safety or efficacy data, tern helps deliver clear, reproducible, and audit-ready clinical reports efficiently.

Import the data

data <- read.csv("C:/Users/Admin/OneDrive/Desktop/Office/MyStudyApp/merge.csv",header = TRUE)

About the data set

head(data)
  Patient_ID  Day VAS PGA Age Gender Treatment Center ITT  PP
1    PID/001 DAY1  72   2  24 Female   PLACEBO     C1 YES YES
2    PID/001 DAY5  56   2  24 Female   PLACEBO     C1 YES YES
3    PID/001 DAY6  67   2  24 Female   PLACEBO     C1 YES YES
4    PID/001 DAY4  56   2  24 Female   PLACEBO     C1 YES YES
5    PID/001 DAY2  70   2  24 Female   PLACEBO     C1 YES YES
6    PID/001 DAY7  62   2  24 Female   PLACEBO     C1 YES YES

Create a summary table to analyse VAS for each Day grouped by treatment

# Load required packages
library(tern)    # Provides clinical trial-specific statistical functions and reporting tools built on rtables
library(rtables) # Core package for building complex summary tables in R
library(dplyr)   # Useful for data manipulation and preparation (optional but commonly used)

# Make sure 'Day' is a factor with the correct order
data$Day <- factor(data$Day, levels = c("DAY1", "DAY2", "DAY3", "DAY4", "DAY5", "DAY6", "DAY7"))

# Define the layout of the summary table
lyt <- basic_table() %>% 
  # Split the columns of the table based on the 'Treatment' variable,
  # so each treatment group appears as a separate column
  split_cols_by("Treatment") %>%     
  
  # Split the rows of the table by 'Day', so each day is a separate row
  split_rows_by("Day") %>%           
  
  # Specify the variables to analyze — here 'VAS' (Visual Analog Scale score)
  # Define the statistics to compute: 
  #   'n' = number of non-missing observations
  #   'mean_sd' = mean and standard deviation
  #   'median' = median value
  #   'min' = minimum value
  #   'max' = maximum value
  analyze_vars(
    vars = "VAS",
    
    .stats = c("n", "mean_sd", "median", "min", "max"),
    
    # Format how each statistic is displayed in the table
    # 'mean_sd' will be shown as "xx.xx (xx.xx)" i.e., mean (SD)
    # median, min, and max will be shown as whole numbers ('xx')
    .formats = c(
      "mean_sd" = "xx.xx (xx.xx)",
      "median" = "xx",
      "min" = "xx",
      "max" = "xx"
    ),
    
    # Label the variable 'VAS' as "VAS Score" in the table header
    var_labels = c(VAS = "VAS Score"),
    
    # String to use for missing values (NA)
    na_str = "NA"
  )

# Build the table using the defined layout and the dataset
tbl <- build_table(lyt, data)

# Print the resulting summary table to the console
tbl
                 PLACEBO          TEST             REF     
———————————————————————————————————————————————————————————
DAY1                                                       
  n                272             247             261     
  Mean (SD)   64.56 (14.56)   62.74 (17.61)   63.41 (16.75)
  Median           66              72              72      
  Minimum          22              16              16      
  Maximum          91              87              87      
DAY2                                                       
  n                272             247             261     
  Mean (SD)   57.36 (14.18)   45.76 (20.66)   46.74 (19.88)
  Median           58              48              48      
  Minimum          18               0               0      
  Maximum          86              80              81      
DAY3                                                       
  n                272             247             261     
  Mean (SD)   49.02 (16.32)   29.11 (21.70)   29.59 (21.48)
  Median           51              30              29      
  Minimum           0               0               0      
  Maximum          77              75              77      
DAY4                                                       
  n                272             247             261     
  Mean (SD)   41.11 (15.08)   21.17 (17.79)   21.82 (17.68)
  Median           45              23              24      
  Minimum           0               0               0      
  Maximum          68              65              66      
DAY5                                                       
  n                272             247             261     
  Mean (SD)   35.22 (15.21)   17.06 (15.82)   17.57 (15.76)
  Median          36.5             17              19      
  Minimum           0               0               0      
  Maximum          60              64              65      
DAY6                                                       
  n                272             247             261     
  Mean (SD)   31.36 (17.27)   12.96 (14.36)   13.34 (14.63)
  Median           29               4               4      
  Minimum           0               0               0      
  Maximum          68              65              64      
DAY7                                                       
  n                272             247             261     
  Mean (SD)   28.00 (18.21)   10.73 (13.71)   11.11 (13.99)
  Median           26               3               3      
  Minimum           0               0               0      
  Maximum          69              62              62      

Key Reasons to Use tern

  • Predefined Clinical Statistics : tern provides built-in summaries commonly required in clinical trials (e.g., n, mean, SD, median, min, max) without needing to manually write analysis functions, as you’d have to with rtables.

  • Standardized Formatting for Regulatory Tables : tern ensures your statistical outputs (like “mean (SD)”, “xx.x%”) follow CDISC/ICH-friendly formats — critical in clinical studies.

  • Less Manual Coding : In rtables, you’d need to write custom analysis functions, define custom format strings, handle NA cases manually while tern handles all of this with simple, ready-made calls like summarize_vars() or analyze_colvars().

  • Purpose-Built for Clinical Trial Workflows : tern is designed specifically to support TFL (Tables, Figures, Listings) generation — aligning with how real-world clinical reporting is done.

Why Choose tern Over gt, rtables, or flextable for Clinical Reporting?

The tern package is specifically designed for clinical trial reporting and regulatory submissions, offering built-in support for CDISC-compliant data and statistical summaries. Unlike gt and flextable, which focus on table styling for reports and presentations, and rtables, which provides a flexible framework for general-purpose tabulations, tern adds clinical-specific functionality on top of rtables. This makes it uniquely suited for generating tables with statistical rigor in regulated clinical research environments.

Conclusion:

The tern package empowers clinical researchers with a powerful, reliable tool to transform complex trial data into clear, standardized, and regulatory-ready reports. By combining ease of use with robust statistical methods and formatting, tern streamlines the clinical reporting workflow — helping teams deliver accurate insights faster and with confidence. It’s an essential asset for modern clinical data analysis in R.