30 Days of Pharmaverse
  • Week 1: SDTM Fundamentals
  • Week 2: Production SDTM
  • Week 3: ADaM Deep Dive
  • Week 4: Tables, Listings and Figures
  1. Day 27: rtables, tern, and r2rtf - Structured Clinical Tables
  • Day 22: Demography Table with gtsummary + gt
  • Day 23: ADCM and ADRS - Concomitant Meds and Oncology Response
  • Day 24: ARD-First Reporting with cards and cardx
  • Day 25: gtsummary and tfrmt - ARD-Backed Production Tables
  • Day 26: flextable and officer - Word and RTF Clinical Tables
  • Day 27: rtables, tern, and r2rtf - Structured Clinical Tables
  • Day 28: Tplyr - Declarative Clinical Table Programming
  • Day 29: ggsurvfit + gtsummary - Survival Plots and Clinical Figures
  • Day 30: Capstone - Full Clinical Reporting Workflow

On this page

  • 1 Overview
  • 2 Setup
  • 3 Part 1: rtables
    • 3.1 Step 1: Minimal rtables table
    • 3.2 Step 2: Demographics table with rtables
  • 4 Part 2: tern
    • 4.1 Step 3: Demographics table with tern
    • 4.2 Step 4: AE frequency table with pre-sorted factor levels
    • 4.3 Step 5: Lab shift table with tern
  • 5 Part 3: r2rtf
    • 5.1 Step 6: Prepare data frames for RTF
    • 5.2 Step 7: Write an RTF demographics table
    • 5.3 Step 8: Combine two RTF files with assemble_rtf()
  • 6 Validation Checks
  • 7 Key Takeaways
  • 8 Resources

Day 27: rtables, tern, and r2rtf - Structured Clinical Tables

Declarative table layout and RTF output

Back to Roadmap

1 Overview

rtables provides a declarative layout engine for clinical tables. tern adds pre-built clinical analysis functions on top of rtables. r2rtf produces RTF output suitable for clinical reporting.


2 Setup

library(rtables)
library(tern)
library(r2rtf)
library(pharmaverseadam)
library(dplyr)
library(tidyr)

adsl <- pharmaverseadam::adsl
adae  <- pharmaverseadam::adae
adlb  <- pharmaverseadam::adlb

# Factor all grouping variables -- rtables requires identical levels per column
adsl_safe <- adsl |>
  dplyr::filter(SAFFL == "Y") |>
  dplyr::mutate(
    TRT01A = factor(TRT01A),
    SEX    = factor(SEX),
    RACE   = factor(RACE),
    AGEGR1 = factor(AGEGR1)
  )

adae_safe <- adae |>
  dplyr::filter(SAFFL == "Y", TRTEMFL == "Y") |>
  dplyr::mutate(
    TRT01A   = factor(TRT01A,   levels = levels(adsl_safe$TRT01A)),
    AEBODSYS = factor(AEBODSYS),
    AEDECOD  = factor(AEDECOD)
  )

adlb_safe <- adlb |>
  dplyr::filter(SAFFL == "Y", !is.na(AVAL)) |>
  dplyr::mutate(
    TRT01A = factor(TRT01A, levels = levels(adsl_safe$TRT01A))
  )

cat("ADSL safety N:", nrow(adsl_safe), "\n")
ADSL safety N: 254 
cat("Treatment arms:", levels(adsl_safe$TRT01A), "\n")
Treatment arms: Placebo Xanomeline High Dose Xanomeline Low Dose 

Key rules: 1. Factor all categorical variables before build_table(). 2. Add split_fun = drop_split_levels to every split_rows_by() call. 3. Pre-order factor levels in dplyr for sorting – sort_at_path() with cont_n_allcols requires content rows that count_occurrences() does not produce. 4. rtf_encode() only accepts doc_type = "table" or "figure" – use assemble_rtf() to merge multiple RTF files into one document.


3 Part 1: rtables

3.1 Step 1: Minimal rtables table

afun_mean_sd <- function(x, ...) {
  list(
    "Mean (SD)" = rtables::rcell(
      c(mean(x, na.rm = TRUE), sd(x, na.rm = TRUE)),
      format = "xx.x (xx.xx)"
    )
  )
}

lyt_simple <- rtables::basic_table() |>
  rtables::split_cols_by("TRT01A") |>
  rtables::add_colcounts() |>
  rtables::analyze("AGE", afun = afun_mean_sd, var_labels = "Age (years)")

tbl_simple <- rtables::build_table(lyt_simple, adsl_safe)
print(tbl_simple)
              Placebo     Xanomeline High Dose   Xanomeline Low Dose
              (N=86)             (N=72)                (N=96)       
--------------------------------------------------------------------
Mean (SD)   75.2 (8.59)       73.8 (7.94)            76.0 (8.11)    

3.2 Step 2: Demographics table with rtables

# Pre-format multi-value cells with sprintf() -- rcell() accepts plain character
afun_age <- function(x, ...) {
  rtables::in_rows(
    "Mean (SD)"       = rtables::rcell(
      sprintf("%.1f (%.2f)", mean(x, na.rm = TRUE), sd(x, na.rm = TRUE))
    ),
    "Median (Q1, Q3)" = rtables::rcell(
      sprintf("%.1f (%.1f, %.1f)",
              median(x, na.rm = TRUE),
              stats::quantile(x, 0.25, na.rm = TRUE),
              stats::quantile(x, 0.75, na.rm = TRUE))
    ),
    "Min, Max"        = rtables::rcell(
      sprintf("%.1f, %.1f", min(x, na.rm = TRUE), max(x, na.rm = TRUE))
    )
  )
}

# Closure captures all factor levels so every column emits identical rows
make_afun_cat <- function(var_levels) {
  function(x, ...) {
    tab <- table(factor(x, levels = var_levels), useNA = "no")
    tot <- sum(tab)
    rtables::in_rows(
      .list  = lapply(var_levels, function(v) {
        rtables::rcell(
          sprintf("%d (%.1f%%)", tab[[v]], tab[[v]] / tot * 100)
        )
      }),
      .names = var_levels
    )
  }
}

sex_levels  <- levels(adsl_safe$SEX)
race_levels <- levels(adsl_safe$RACE)

lyt_demo <- rtables::basic_table(show_colcounts = TRUE) |>
  rtables::split_cols_by("TRT01A") |>
  rtables::add_overall_col("All Subjects") |>
  rtables::analyze("AGE",  afun = afun_age,  var_labels = "Age (years)") |>
  rtables::analyze("SEX",  afun = make_afun_cat(sex_levels),  var_labels = "Sex") |>
  rtables::analyze("RACE", afun = make_afun_cat(race_levels), var_labels = "Race")

tbl_demo_rt <- rtables::build_table(lyt_demo, adsl_safe)
print(tbl_demo_rt)
                                          Placebo        Xanomeline High Dose   Xanomeline Low Dose     All Subjects   
                                          (N=86)                (N=72)                (N=96)               (N=254)     
-----------------------------------------------------------------------------------------------------------------------
Age (years)                                                                                                            
  Mean (SD)                             75.2 (8.59)          73.8 (7.94)            76.0 (8.11)          75.1 (8.25)   
  Median (Q1, Q3)                    76.0 (69.2, 81.8)    75.5 (70.5, 79.0)      78.0 (71.0, 82.0)    77.0 (70.0, 81.0)
  Min, Max                              52.0, 89.0            56.0, 88.0            51.0, 88.0           51.0, 89.0    
Sex                                                                                                                    
  F                                     53 (61.6%)            35 (48.6%)            55 (57.3%)           143 (56.3%)   
  M                                     33 (38.4%)            37 (51.4%)            41 (42.7%)           111 (43.7%)   
Race                                                                                                                   
  AMERICAN INDIAN OR ALASKA NATIVE       0 (0.0%)              1 (1.4%)              0 (0.0%)             1 (0.4%)     
  BLACK OR AFRICAN AMERICAN              8 (9.3%)             9 (12.5%)              6 (6.2%)             23 (9.1%)    
  WHITE                                 78 (90.7%)            62 (86.1%)            90 (93.8%)           230 (90.6%)   

4 Part 2: tern

4.1 Step 3: Demographics table with tern

# analyze_vars() is the correct exported tern function for variable summaries
lyt_tern_demo <- rtables::basic_table(show_colcounts = TRUE) |>
  rtables::split_cols_by("TRT01A") |>
  rtables::add_overall_col("All Subjects") |>
  tern::analyze_vars(
    vars       = c("AGE", "SEX", "RACE"),
    var_labels = c("Age (years)", "Sex", "Race")
  )

tbl_tern_demo <- rtables::build_table(lyt_tern_demo, adsl_safe)
print(tbl_tern_demo)
                                       Placebo     Xanomeline High Dose   Xanomeline Low Dose   All Subjects
                                       (N=86)             (N=72)                (N=96)            (N=254)   
------------------------------------------------------------------------------------------------------------
Age (years)                                                                                                 
  n                                      86                 72                    96                254     
  Mean (SD)                          75.2 (8.6)         73.8 (7.9)            76.0 (8.1)         75.1 (8.2) 
  Median                                76.0               75.5                  78.0               77.0    
  Min - Max                          52.0 - 89.0       56.0 - 88.0            51.0 - 88.0       51.0 - 89.0 
Sex                                                                                                         
  n                                      86                 72                    96                254     
  F                                  53 (61.6%)         35 (48.6%)            55 (57.3%)        143 (56.3%) 
  M                                  33 (38.4%)         37 (51.4%)            41 (42.7%)        111 (43.7%) 
Race                                                                                                        
  n                                      86                 72                    96                254     
  AMERICAN INDIAN OR ALASKA NATIVE        0              1 (1.4%)                  0              1 (0.4%)  
  BLACK OR AFRICAN AMERICAN           8 (9.3%)          9 (12.5%)              6 (6.2%)          23 (9.1%)  
  WHITE                              78 (90.7%)         62 (86.1%)            90 (93.8%)        230 (90.6%) 

4.2 Step 4: AE frequency table with pre-sorted factor levels

# Pre-order AEBODSYS and AEDECOD by descending frequency via dplyr.
# This is the reliable alternative to sort_at_path(cont_n_allcols),
# which needs content rows that count_occurrences() does not produce.
soc_order <- adae_safe |>
  dplyr::distinct(USUBJID, AEBODSYS) |>
  dplyr::count(AEBODSYS, sort = TRUE) |>
  dplyr::pull(AEBODSYS) |>
  as.character()

pt_order <- adae_safe |>
  dplyr::distinct(USUBJID, AEBODSYS, AEDECOD) |>
  dplyr::count(AEBODSYS, AEDECOD, sort = TRUE) |>
  dplyr::pull(AEDECOD) |>
  as.character() |>
  unique()

adae_sorted <- adae_safe |>
  dplyr::mutate(
    AEBODSYS = factor(AEBODSYS, levels = soc_order),
    AEDECOD  = factor(AEDECOD,  levels = pt_order)
  )

lyt_ae <- rtables::basic_table(show_colcounts = TRUE) |>
  rtables::split_cols_by("TRT01A") |>
  rtables::add_overall_col("All Subjects") |>
  rtables::split_rows_by(
    "AEBODSYS",
    label_pos   = "topleft",
    split_label = "System Organ Class",
    split_fun   = drop_split_levels
  ) |>
  tern::count_occurrences(vars = "AEDECOD", .indent_mods = 1L)

tbl_ae <- rtables::build_table(
  lyt_ae,
  df            = adae_sorted,
  alt_counts_df = adsl_safe
)
tbl_ae_pruned <- rtables::prune_table(tbl_ae)
print(tbl_ae_pruned)
                                                                       Placebo    Xanomeline High Dose   Xanomeline Low Dose   All Subjects
System Organ Class                                                     (N=86)            (N=72)                (N=96)            (N=254)   
-------------------------------------------------------------------------------------------------------------------------------------------
GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS                                                                                       
    APPLICATION SITE PRURITUS                                         6 (7.0%)         21 (29.2%)            23 (24.0%)         50 (19.7%) 
    APPLICATION SITE ERYTHEMA                                         3 (3.5%)         14 (19.4%)            13 (13.5%)         30 (11.8%) 
    APPLICATION SITE DERMATITIS                                       5 (5.8%)          7 (9.7%)              9 (9.4%)          21 (8.3%)  
    APPLICATION SITE IRRITATION                                       3 (3.5%)         9 (12.5%)              9 (9.4%)          21 (8.3%)  
    APPLICATION SITE VESICLES                                         1 (1.2%)          5 (6.9%)              5 (5.2%)          11 (4.3%)  
    FATIGUE                                                           1 (1.2%)          5 (6.9%)              5 (5.2%)          11 (4.3%)  
    OEDEMA PERIPHERAL                                                 2 (2.3%)          2 (2.8%)              1 (1.0%)           5 (2.0%)  
    APPLICATION SITE SWELLING                                             0             2 (2.8%)              1 (1.0%)           3 (1.2%)  
    APPLICATION SITE URTICARIA                                            0             1 (1.4%)              2 (2.1%)           3 (1.2%)  
    CHILLS                                                            1 (1.2%)          1 (1.4%)              1 (1.0%)           3 (1.2%)  
    MALAISE                                                               0             2 (2.8%)              1 (1.0%)           3 (1.2%)  
    PYREXIA                                                           2 (2.3%)             0                  1 (1.0%)           3 (1.2%)  
    APPLICATION SITE PAIN                                                 0             2 (2.8%)                  0              2 (0.8%)  
    APPLICATION SITE PERSPIRATION                                         0             2 (2.8%)                  0              2 (0.8%)  
    APPLICATION SITE REACTION                                         1 (1.2%)          1 (1.4%)                  0              2 (0.8%)  
    ASTHENIA                                                          1 (1.2%)             0                  1 (1.0%)           2 (0.8%)  
    CHEST DISCOMFORT                                                      0             1 (1.4%)              1 (1.0%)           2 (0.8%)  
    CHEST PAIN                                                            0             2 (2.8%)                  0              2 (0.8%)  
    OEDEMA                                                                0                0                  2 (2.1%)           2 (0.8%)  
    PAIN                                                                  0             1 (1.4%)              1 (1.0%)           2 (0.8%)  
    APPLICATION SITE BLEEDING                                             0                0                  1 (1.0%)           1 (0.4%)  
    APPLICATION SITE DESQUAMATION                                         0                0                  1 (1.0%)           1 (0.4%)  
    APPLICATION SITE DISCHARGE                                            0             1 (1.4%)                  0              1 (0.4%)  
    APPLICATION SITE DISCOLOURATION                                       0                0                  1 (1.0%)           1 (0.4%)  
    APPLICATION SITE INDURATION                                       1 (1.2%)             0                      0              1 (0.4%)  
    APPLICATION SITE WARMTH                                               0                0                  1 (1.0%)           1 (0.4%)  
    FEELING ABNORMAL                                                      0             1 (1.4%)                  0              1 (0.4%)  
    FEELING COLD                                                          0             1 (1.4%)                  0              1 (0.4%)  
    INFLAMMATION                                                          0                0                  1 (1.0%)           1 (0.4%)  
    SECRETION DISCHARGE                                                   0                0                  1 (1.0%)           1 (0.4%)  
    SUDDEN DEATH                                                          0                0                  1 (1.0%)           1 (0.4%)  
    SWELLING                                                              0                0                  1 (1.0%)           1 (0.4%)  
    ULCER                                                                 0                0                  1 (1.0%)           1 (0.4%)  
SKIN AND SUBCUTANEOUS TISSUE DISORDERS                                                                                                     
    PRURITUS                                                          8 (9.3%)         25 (34.7%)            21 (21.9%)         54 (21.3%) 
    ERYTHEMA                                                          8 (9.3%)         14 (19.4%)            14 (14.6%)         36 (14.2%) 
    RASH                                                              5 (5.8%)         8 (11.1%)             13 (13.5%)         26 (10.2%) 
    HYPERHIDROSIS                                                     2 (2.3%)         8 (11.1%)              4 (4.2%)          14 (5.5%)  
    SKIN IRRITATION                                                   3 (3.5%)          5 (6.9%)              6 (6.2%)          14 (5.5%)  
    BLISTER                                                               0             1 (1.4%)              5 (5.2%)           6 (2.4%)  
    RASH PRURITIC                                                         0             2 (2.8%)              1 (1.0%)           3 (1.2%)  
    PRURITUS GENERALISED                                                  0             1 (1.4%)              1 (1.0%)           2 (0.8%)  
    URTICARIA                                                             0             1 (1.4%)              1 (1.0%)           2 (0.8%)  
    ACTINIC KERATOSIS                                                     0             1 (1.4%)                  0              1 (0.4%)  
    ALOPECIA                                                          1 (1.2%)             0                      0              1 (0.4%)  
    COLD SWEAT                                                        1 (1.2%)             0                      0              1 (0.4%)  
    DERMATITIS CONTACT                                                    0                0                  1 (1.0%)           1 (0.4%)  
    DRUG ERUPTION                                                     1 (1.2%)             0                      0              1 (0.4%)  
    RASH ERYTHEMATOUS                                                     0                0                  1 (1.0%)           1 (0.4%)  
    RASH MACULO-PAPULAR                                                   0             1 (1.4%)                  0              1 (0.4%)  
    SKIN EXFOLIATION                                                      0                0                  1 (1.0%)           1 (0.4%)  
    SKIN ODOUR ABNORMAL                                                   0             1 (1.4%)                  0              1 (0.4%)  
    SKIN ULCER                                                        1 (1.2%)             0                      0              1 (0.4%)  
NERVOUS SYSTEM DISORDERS                                                                                                                   
    DIZZINESS                                                         2 (2.3%)         10 (13.9%)             9 (9.4%)          21 (8.3%)  
    HEADACHE                                                          3 (3.5%)          5 (6.9%)              3 (3.1%)          11 (4.3%)  
    SYNCOPE                                                               0             2 (2.8%)              5 (5.2%)           7 (2.8%)  
    SOMNOLENCE                                                        2 (2.3%)          1 (1.4%)              3 (3.1%)           6 (2.4%)  
    TRANSIENT ISCHAEMIC ATTACK                                            0             1 (1.4%)              2 (2.1%)           3 (1.2%)  
    BURNING SENSATION                                                     0             2 (2.8%)                  0              2 (0.8%)  
    LETHARGY                                                              0             1 (1.4%)              1 (1.0%)           2 (0.8%)  
    AMNESIA                                                               0             1 (1.4%)                  0              1 (0.4%)  
    BALANCE DISORDER                                                      0                0                  1 (1.0%)           1 (0.4%)  
    COGNITIVE DISORDER                                                    0             1 (1.4%)                  0              1 (0.4%)  
    COMPLEX PARTIAL SEIZURES                                              0                0                  1 (1.0%)           1 (0.4%)  
    COORDINATION ABNORMAL                                                 0                0                  1 (1.0%)           1 (0.4%)  
    HEMIANOPIA HOMONYMOUS                                                 0                0                  1 (1.0%)           1 (0.4%)  
    HYPERSOMNIA                                                           0             1 (1.4%)                  0              1 (0.4%)  
    PARAESTHESIA                                                          0             1 (1.4%)                  0              1 (0.4%)  
    PARAESTHESIA ORAL                                                     0                0                  1 (1.0%)           1 (0.4%)  
    PARKINSON'S DISEASE                                               1 (1.2%)             0                      0              1 (0.4%)  
    PAROSMIA                                                              0             1 (1.4%)                  0              1 (0.4%)  
    PARTIAL SEIZURES WITH SECONDARY GENERALISATION                        0             1 (1.4%)                  0              1 (0.4%)  
    PSYCHOMOTOR HYPERACTIVITY                                         1 (1.2%)             0                      0              1 (0.4%)  
    STUPOR                                                                0                0                  1 (1.0%)           1 (0.4%)  
    SYNCOPE VASOVAGAL                                                     0             1 (1.4%)                  0              1 (0.4%)  
GASTROINTESTINAL DISORDERS                                                                                                                 
    DIARRHOEA                                                         9 (10.5%)         3 (4.2%)              5 (5.2%)          17 (6.7%)  
    VOMITING                                                          3 (3.5%)          6 (8.3%)              4 (4.2%)          13 (5.1%)  
    NAUSEA                                                            3 (3.5%)          6 (8.3%)              3 (3.1%)          12 (4.7%)  
    ABDOMINAL PAIN                                                    1 (1.2%)          1 (1.4%)              3 (3.1%)           5 (2.0%)  
    SALIVARY HYPERSECRETION                                               0             4 (5.6%)                  0              4 (1.6%)  
    DYSPEPSIA                                                         1 (1.2%)             0                  1 (1.0%)           2 (0.8%)  
    ABDOMINAL DISCOMFORT                                                  0             1 (1.4%)                  0              1 (0.4%)  
    CONSTIPATION                                                      1 (1.2%)             0                      0              1 (0.4%)  
    DYSPHAGIA                                                             0                0                  1 (1.0%)           1 (0.4%)  
    FLATULENCE                                                        1 (1.2%)             0                      0              1 (0.4%)  
    GASTROINTESTINAL HAEMORRHAGE                                          0             1 (1.4%)                  0              1 (0.4%)  
    GASTROOESOPHAGEAL REFLUX DISEASE                                  1 (1.2%)             0                      0              1 (0.4%)  
    GLOSSITIS                                                         1 (1.2%)             0                      0              1 (0.4%)  
    HIATUS HERNIA                                                     1 (1.2%)             0                      0              1 (0.4%)  
    RECTAL HAEMORRHAGE                                                    0                0                  1 (1.0%)           1 (0.4%)  
    STOMACH DISCOMFORT                                                    0             1 (1.4%)                  0              1 (0.4%)  
CARDIAC DISORDERS                                                                                                                          
    SINUS BRADYCARDIA                                                 2 (2.3%)         8 (11.1%)              7 (7.3%)          17 (6.7%)  
    MYOCARDIAL INFARCTION                                             4 (4.7%)          4 (5.6%)              2 (2.1%)          10 (3.9%)  
    ATRIAL FIBRILLATION                                               1 (1.2%)          2 (2.8%)              2 (2.1%)           5 (2.0%)  
    SUPRAVENTRICULAR EXTRASYSTOLES                                    1 (1.2%)          1 (1.4%)              1 (1.0%)           3 (1.2%)  
    VENTRICULAR EXTRASYSTOLES                                             0             1 (1.4%)              2 (2.1%)           3 (1.2%)  
    ATRIAL FLUTTER                                                        0             1 (1.4%)              1 (1.0%)           2 (0.8%)  
    ATRIOVENTRICULAR BLOCK FIRST DEGREE                               1 (1.2%)             0                  1 (1.0%)           2 (0.8%)  
    BUNDLE BRANCH BLOCK RIGHT                                         1 (1.2%)             0                  1 (1.0%)           2 (0.8%)  
    PALPITATIONS                                                          0                0                  2 (2.1%)           2 (0.8%)  
    ATRIAL HYPERTROPHY                                                1 (1.2%)             0                      0              1 (0.4%)  
    ATRIOVENTRICULAR BLOCK SECOND DEGREE                              1 (1.2%)             0                      0              1 (0.4%)  
    BRADYCARDIA                                                       1 (1.2%)             0                      0              1 (0.4%)  
    BUNDLE BRANCH BLOCK LEFT                                          1 (1.2%)             0                      0              1 (0.4%)  
    CARDIAC DISORDER                                                      0             1 (1.4%)                  0              1 (0.4%)  
    CARDIAC FAILURE CONGESTIVE                                        1 (1.2%)             0                      0              1 (0.4%)  
    SINUS ARRHYTHMIA                                                  1 (1.2%)             0                      0              1 (0.4%)  
    SUPRAVENTRICULAR TACHYCARDIA                                          0                0                  1 (1.0%)           1 (0.4%)  
    TACHYCARDIA                                                       1 (1.2%)             0                      0              1 (0.4%)  
    VENTRICULAR HYPERTROPHY                                           1 (1.2%)             0                      0              1 (0.4%)  
    WOLFF-PARKINSON-WHITE SYNDROME                                        0                0                  1 (1.0%)           1 (0.4%)  
INFECTIONS AND INFESTATIONS                                                                                                                
    NASOPHARYNGITIS                                                   2 (2.3%)          6 (8.3%)              4 (4.2%)          12 (4.7%)  
    UPPER RESPIRATORY TRACT INFECTION                                 6 (7.0%)          3 (4.2%)              1 (1.0%)          10 (3.9%)  
    INFLUENZA                                                         1 (1.2%)          1 (1.4%)              1 (1.0%)           3 (1.2%)  
    URINARY TRACT INFECTION                                           2 (2.3%)          1 (1.4%)                  0              3 (1.2%)  
    CYSTITIS                                                          1 (1.2%)          1 (1.4%)                  0              2 (0.8%)  
    EAR INFECTION                                                     2 (2.3%)             0                      0              2 (0.8%)  
    BRONCHITIS                                                        1 (1.2%)             0                      0              1 (0.4%)  
    CELLULITIS                                                            0                0                  1 (1.0%)           1 (0.4%)  
    CERVICITIS                                                        1 (1.2%)             0                      0              1 (0.4%)  
    GASTROENTERITIS VIRAL                                             1 (1.2%)             0                      0              1 (0.4%)  
    HORDEOLUM                                                             0             1 (1.4%)                  0              1 (0.4%)  
    LOCALISED INFECTION                                               1 (1.2%)             0                      0              1 (0.4%)  
    LOWER RESPIRATORY TRACT INFECTION                                     0             1 (1.4%)                  0              1 (0.4%)  
    PNEUMONIA                                                             0                0                  1 (1.0%)           1 (0.4%)  
    RHINITIS                                                              0             1 (1.4%)                  0              1 (0.4%)  
    VAGINAL MYCOSIS                                                   1 (1.2%)             0                      0              1 (0.4%)  
    VIRAL INFECTION                                                       0                0                  1 (1.0%)           1 (0.4%)  
PSYCHIATRIC DISORDERS                                                                                                                      
    CONFUSIONAL STATE                                                 2 (2.3%)          1 (1.4%)              3 (3.1%)           6 (2.4%)  
    AGITATION                                                         2 (2.3%)             0                  3 (3.1%)           5 (2.0%)  
    INSOMNIA                                                          2 (2.3%)          2 (2.8%)                  0              4 (1.6%)  
    ANXIETY                                                               0                0                  3 (3.1%)           3 (1.2%)  
    DELUSION                                                          1 (1.2%)          1 (1.4%)                  0              2 (0.8%)  
    IRRITABILITY                                                      1 (1.2%)             0                  1 (1.0%)           2 (0.8%)  
    COMPLETED SUICIDE                                                 1 (1.2%)             0                      0              1 (0.4%)  
    DELIRIUM                                                              0             1 (1.4%)                  0              1 (0.4%)  
    DEPRESSED MOOD                                                        0                0                  1 (1.0%)           1 (0.4%)  
    DISORIENTATION                                                    1 (1.2%)             0                      0              1 (0.4%)  
    HALLUCINATION                                                         0             1 (1.4%)                  0              1 (0.4%)  
    HALLUCINATION, VISUAL                                                 0             1 (1.4%)                  0              1 (0.4%)  
    LIBIDO DECREASED                                                      0             1 (1.4%)                  0              1 (0.4%)  
    LISTLESS                                                              0             1 (1.4%)                  0              1 (0.4%)  
    NIGHTMARE                                                             0             1 (1.4%)                  0              1 (0.4%)  
    RESTLESSNESS                                                          0                0                  1 (1.0%)           1 (0.4%)  
RESPIRATORY, THORACIC AND MEDIASTINAL DISORDERS                                                                                            
    COUGH                                                             1 (1.2%)          5 (6.9%)              5 (5.2%)          11 (4.3%)  
    NASAL CONGESTION                                                  3 (3.5%)          3 (4.2%)              1 (1.0%)           7 (2.8%)  
    DYSPNOEA                                                          1 (1.2%)          1 (1.4%)              1 (1.0%)           3 (1.2%)  
    EPISTAXIS                                                             0             2 (2.8%)              1 (1.0%)           3 (1.2%)  
    PHARYNGOLARYNGEAL PAIN                                                0             1 (1.4%)              1 (1.0%)           2 (0.8%)  
    RHINORRHOEA                                                           0             1 (1.4%)              1 (1.0%)           2 (0.8%)  
    ALLERGIC GRANULOMATOUS ANGIITIS                                       0             1 (1.4%)                  0              1 (0.4%)  
    DYSPHONIA                                                             0                0                  1 (1.0%)           1 (0.4%)  
    EMPHYSEMA                                                         1 (1.2%)             0                      0              1 (0.4%)  
    HAEMOPTYSIS                                                       1 (1.2%)             0                      0              1 (0.4%)  
    PHARYNGEAL ERYTHEMA                                                   0             1 (1.4%)                  0              1 (0.4%)  
    POSTNASAL DRIP                                                    1 (1.2%)             0                      0              1 (0.4%)  
    PRODUCTIVE COUGH                                                      0             1 (1.4%)                  0              1 (0.4%)  
    RALES                                                             1 (1.2%)             0                      0              1 (0.4%)  
    RESPIRATORY TRACT CONGESTION                                          0             1 (1.4%)                  0              1 (0.4%)  
INVESTIGATIONS                                                                                                                             
    ELECTROCARDIOGRAM ST SEGMENT DEPRESSION                           4 (4.7%)             0                  1 (1.0%)           5 (2.0%)  
    ELECTROCARDIOGRAM T WAVE INVERSION                                2 (2.3%)          1 (1.4%)              1 (1.0%)           4 (1.6%)  
    BLOOD GLUCOSE INCREASED                                               0             1 (1.4%)              1 (1.0%)           2 (0.8%)  
    ELECTROCARDIOGRAM T WAVE AMPLITUDE DECREASED                      1 (1.2%)             0                  1 (1.0%)           2 (0.8%)  
    BIOPSY                                                                0             1 (1.4%)                  0              1 (0.4%)  
    BIOPSY PROSTATE                                                       0             1 (1.4%)                  0              1 (0.4%)  
    BLOOD ALKALINE PHOSPHATASE INCREASED                              1 (1.2%)             0                      0              1 (0.4%)  
    BLOOD CHOLESTEROL INCREASED                                           0             1 (1.4%)                  0              1 (0.4%)  
    BLOOD CREATINE PHOSPHOKINASE INCREASED                            1 (1.2%)             0                      0              1 (0.4%)  
    BLOOD URINE PRESENT                                               1 (1.2%)             0                      0              1 (0.4%)  
    BODY TEMPERATURE INCREASED                                            0                0                  1 (1.0%)           1 (0.4%)  
    CYSTOSCOPY                                                        1 (1.2%)             0                      0              1 (0.4%)  
    HEART RATE INCREASED                                              1 (1.2%)             0                      0              1 (0.4%)  
    HEART RATE IRREGULAR                                              1 (1.2%)             0                      0              1 (0.4%)  
    NASAL MUCOSA BIOPSY                                                   0                0                  1 (1.0%)           1 (0.4%)  
    WEIGHT DECREASED                                                      0                0                  1 (1.0%)           1 (0.4%)  
MUSCULOSKELETAL AND CONNECTIVE TISSUE DISORDERS                                                                                            
    BACK PAIN                                                         1 (1.2%)          3 (4.2%)              1 (1.0%)           5 (2.0%)  
    ARTHRALGIA                                                        1 (1.2%)          1 (1.4%)              2 (2.1%)           4 (1.6%)  
    SHOULDER PAIN                                                     1 (1.2%)             0                  2 (2.1%)           3 (1.2%)  
    MUSCLE SPASMS                                                         0             1 (1.4%)              1 (1.0%)           2 (0.8%)  
    ARTHRITIS                                                             0             1 (1.4%)                  0              1 (0.4%)  
    FLANK PAIN                                                            0             1 (1.4%)                  0              1 (0.4%)  
    MUSCULAR WEAKNESS                                                     0                0                  1 (1.0%)           1 (0.4%)  
    MYALGIA                                                               0             1 (1.4%)                  0              1 (0.4%)  
    PAIN IN EXTREMITY                                                 1 (1.2%)             0                      0              1 (0.4%)  
INJURY, POISONING AND PROCEDURAL COMPLICATIONS                                                                                             
    CONTUSION                                                         1 (1.2%)          2 (2.8%)              1 (1.0%)           4 (1.6%)  
    EXCORIATION                                                       2 (2.3%)          1 (1.4%)              1 (1.0%)           4 (1.6%)  
    FALL                                                              1 (1.2%)          1 (1.4%)              2 (2.1%)           4 (1.6%)  
    HIP FRACTURE                                                      1 (1.2%)          2 (2.8%)                  0              3 (1.2%)  
    SKIN LACERATION                                                   1 (1.2%)             0                  2 (2.1%)           3 (1.2%)  
    FACIAL BONES FRACTURE                                                 0             1 (1.4%)                  0              1 (0.4%)  
    JOINT DISLOCATION                                                     0                0                  1 (1.0%)           1 (0.4%)  
    WOUND                                                                 0                0                  1 (1.0%)           1 (0.4%)  
RENAL AND URINARY DISORDERS                                                                                                                
    MICTURITION URGENCY                                               1 (1.2%)          1 (1.4%)              1 (1.0%)           3 (1.2%)  
    DYSURIA                                                           1 (1.2%)             0                  1 (1.0%)           2 (0.8%)  
    NEPHROLITHIASIS                                                   1 (1.2%)          1 (1.4%)                  0              2 (0.8%)  
    CALCULUS URETHRAL                                                     0             1 (1.4%)                  0              1 (0.4%)  
    INCONTINENCE                                                          0                0                  1 (1.0%)           1 (0.4%)  
    POLLAKIURIA                                                       1 (1.2%)             0                      0              1 (0.4%)  
METABOLISM AND NUTRITION DISORDERS                                                                                                         
    DECREASED APPETITE                                                1 (1.2%)          1 (1.4%)                  0              2 (0.8%)  
    FOOD CRAVING                                                      1 (1.2%)             0                  1 (1.0%)           2 (0.8%)  
    INCREASED APPETITE                                                1 (1.2%)          1 (1.4%)                  0              2 (0.8%)  
    DEHYDRATION                                                       1 (1.2%)             0                      0              1 (0.4%)  
    DIABETES MELLITUS                                                 1 (1.2%)             0                      0              1 (0.4%)  
    HYPONATRAEMIA                                                     1 (1.2%)             0                      0              1 (0.4%)  
VASCULAR DISORDERS                                                                                                                         
    HYPOTENSION                                                       2 (2.3%)             0                  1 (1.0%)           3 (1.2%)  
    HYPERTENSION                                                      1 (1.2%)             0                  1 (1.0%)           2 (0.8%)  
    HOT FLUSH                                                             0                0                  1 (1.0%)           1 (0.4%)  
    ORTHOSTATIC HYPOTENSION                                           1 (1.2%)             0                      0              1 (0.4%)  
    WOUND HAEMORRHAGE                                                     0             1 (1.4%)                  0              1 (0.4%)  
EYE DISORDERS                                                                                                                              
    VISION BLURRED                                                        0             1 (1.4%)              1 (1.0%)           2 (0.8%)  
    CONJUNCTIVAL HAEMORRHAGE                                              0                0                  1 (1.0%)           1 (0.4%)  
    CONJUNCTIVITIS                                                    1 (1.2%)             0                      0              1 (0.4%)  
    EYE ALLERGY                                                       1 (1.2%)             0                      0              1 (0.4%)  
    EYE PRURITUS                                                      1 (1.2%)             0                      0              1 (0.4%)  
    EYE SWELLING                                                      1 (1.2%)             0                      0              1 (0.4%)  
SURGICAL AND MEDICAL PROCEDURES                                                                                                            
    CATARACT OPERATION                                                1 (1.2%)             0                  1 (1.0%)           2 (0.8%)  
    ACROCHORDON EXCISION                                                  0             1 (1.4%)                  0              1 (0.4%)  
    EYE LASER SURGERY                                                 1 (1.2%)             0                      0              1 (0.4%)  
    SKIN LESION EXCISION                                                  0             1 (1.4%)                  0              1 (0.4%)  
EAR AND LABYRINTH DISORDERS                                                                                                                
    VERTIGO                                                               0             1 (1.4%)              1 (1.0%)           2 (0.8%)  
    CERUMEN IMPACTION                                                     0                0                  1 (1.0%)           1 (0.4%)  
    EAR PAIN                                                          1 (1.2%)             0                      0              1 (0.4%)  
CONGENITAL, FAMILIAL AND GENETIC DISORDERS                                                                                                 
    VENTRICULAR SEPTAL DEFECT                                             0             2 (2.8%)              1 (1.0%)           3 (1.2%)  
NEOPLASMS BENIGN, MALIGNANT AND UNSPECIFIED (INCL CYSTS AND POLYPS)                                                                        
    COLON CANCER                                                          0                0                  1 (1.0%)           1 (0.4%)  
    MALIGNANT FIBROUS HISTIOCYTOMA                                        0                0                  1 (1.0%)           1 (0.4%)  
    PROSTATE CANCER                                                       0             1 (1.4%)                  0              1 (0.4%)  
REPRODUCTIVE SYSTEM AND BREAST DISORDERS                                                                                                   
    BENIGN PROSTATIC HYPERPLASIA                                      1 (1.2%)          1 (1.4%)                  0              2 (0.8%)  
    PELVIC PAIN                                                       1 (1.2%)             0                      0              1 (0.4%)  
HEPATOBILIARY DISORDERS                                                                                                                    
    HYPERBILIRUBINAEMIA                                               1 (1.2%)             0                      0              1 (0.4%)  
IMMUNE SYSTEM DISORDERS                                                                                                                    
    HYPERSENSITIVITY                                                      0                0                  1 (1.0%)           1 (0.4%)  
SOCIAL CIRCUMSTANCES                                                                                                                       
    ALCOHOL USE                                                           0             1 (1.4%)                  0              1 (0.4%)  

4.3 Step 5: Lab shift table with tern

adlb_alt <- adlb_safe |>
  dplyr::filter(PARAMCD == "ALT", ANL01FL == "Y",
                !is.na(BNRIND), !is.na(ANRIND)) |>
  dplyr::mutate(
    BNRIND = factor(BNRIND, levels = c("LOW", "NORMAL", "HIGH")),
    ANRIND = factor(ANRIND, levels = c("LOW", "NORMAL", "HIGH"))
  )

cat("ALT records for shift table:", nrow(adlb_alt), "\n")
ALT records for shift table: 2094 
# split_fun = drop_split_levels silently removes levels absent from data
lyt_shift <- rtables::basic_table(show_colcounts = TRUE) |>
  rtables::split_cols_by("TRT01A") |>
  rtables::add_overall_col("All Subjects") |>
  rtables::split_rows_by(
    "BNRIND",
    label_pos   = "topleft",
    split_label = "Baseline Reference Range",
    split_fun   = drop_split_levels
  ) |>
  tern::count_occurrences(vars = "ANRIND", .indent_mods = 1L)

tbl_shift <- rtables::build_table(
  lyt_shift,
  df            = adlb_alt,
  alt_counts_df = adsl_safe
)
print(tbl_shift)
                            Placebo     Xanomeline High Dose   Xanomeline Low Dose   All Subjects
Baseline Reference Range     (N=86)            (N=72)                (N=96)            (N=254)   
-------------------------------------------------------------------------------------------------
NORMAL                                                                                           
    LOW                     1 (1.2%)             0                  5 (5.2%)           6 (2.4%)  
    NORMAL                 79 (91.9%)        68 (94.4%)            72 (75.0%)        219 (86.2%) 
    HIGH                    6 (7.0%)          7 (9.7%)              8 (8.3%)          21 (8.3%)  
HIGH                                                                                             
    NORMAL                  3 (3.5%)          4 (5.6%)              1 (1.0%)           8 (3.1%)  
    HIGH                    3 (3.5%)          4 (5.6%)              2 (2.1%)           9 (3.5%)  

5 Part 3: r2rtf

5.1 Step 6: Prepare data frames for RTF

demo_df <- adsl_safe |>
  dplyr::group_by(TRT01A) |>
  dplyr::summarise(
    N        = dplyr::n(),
    age_mean = mean(AGE, na.rm = TRUE),
    age_sd   = sd(AGE, na.rm = TRUE),
    female_n = sum(SEX == "F", na.rm = TRUE),
    .groups  = "drop"
  ) |>
  dplyr::mutate(
    `Age, Mean (SD)` = sprintf("%.1f (%.2f)", age_mean, age_sd),
    `Female, n (%)`  = sprintf("%d (%.1f%%)", female_n, female_n / N * 100)
  ) |>
  dplyr::select(
    Treatment = TRT01A, N, `Age, Mean (SD)`, `Female, n (%)`
  )
print(demo_df)
# A tibble: 3 × 4
  Treatment                N `Age, Mean (SD)` `Female, n (%)`
  <fct>                <int> <chr>            <chr>          
1 Placebo                 86 75.2 (8.59)      53 (61.6%)     
2 Xanomeline High Dose    72 73.8 (7.94)      35 (48.6%)     
3 Xanomeline Low Dose     96 76.0 (8.11)      55 (57.3%)     

5.2 Step 7: Write an RTF demographics table

demo_df |>
  r2rtf::rtf_page(orientation = "portrait") |>
  r2rtf::rtf_title(
    title    = "Table 14.1.1",
    subtitle = "Demographic and Baseline Characteristics (Safety Population)"
  ) |>
  r2rtf::rtf_colheader(
    colheader     = "Treatment | N | Age, Mean (SD) | Female, n (%)",
    col_rel_width = c(3, 1, 2, 2)
  ) |>
  r2rtf::rtf_body(
    col_rel_width      = c(3, 1, 2, 2),
    text_justification = c("l", "c", "c", "c")
  ) |>
  r2rtf::rtf_footnote(footnote = "SD = standard deviation.") |>
  r2rtf::rtf_encode() |>
  r2rtf::write_rtf("table14_1_1_r2rtf.rtf")
cat("Exported: table14_1_1_r2rtf.rtf\n")
Exported: table14_1_1_r2rtf.rtf

5.3 Step 8: Combine two RTF files with assemble_rtf()

# rtf_encode() only accepts doc_type = "table" or "figure".
# To produce a single combined RTF, write each table separately then
# use r2rtf::assemble_rtf() to merge them into one file.

arm_names <- levels(adsl_safe$TRT01A)

ae_df <- adae_safe |>
  dplyr::count(TRT01A, AEBODSYS, AEDECOD, name = "n") |>
  dplyr::left_join(
    adsl_safe |> dplyr::count(TRT01A, name = "N"),
    by = "TRT01A"
  ) |>
  dplyr::mutate(cell = sprintf("%d (%.1f%%)", n, n / N * 100)) |>
  dplyr::select(AEBODSYS, AEDECOD, TRT01A, cell) |>
  tidyr::pivot_wider(
    names_from  = TRT01A,
    values_from = cell,
    values_fill = "0 (0.0%)"
  ) |>
  dplyr::arrange(AEBODSYS, AEDECOD) |>
  dplyr::slice_head(n = 12)

col_header <- paste(
  c("System Organ Class", "Preferred Term", arm_names),
  collapse = " | "
)

# Write table 1 (portrait)
file1 <- tempfile(fileext = ".rtf")
demo_df |>
  r2rtf::rtf_page(orientation = "portrait") |>
  r2rtf::rtf_title(title = "Table 14.1.1", subtitle = "Demographics") |>
  r2rtf::rtf_colheader(
    colheader     = "Treatment | N | Age, Mean (SD) | Female, n (%)",
    col_rel_width = c(3, 1, 2, 2)
  ) |>
  r2rtf::rtf_body(
    col_rel_width      = c(3, 1, 2, 2),
    text_justification = c("l", "c", "c", "c")
  ) |>
  r2rtf::rtf_encode() |>
  r2rtf::write_rtf(file1)

# Write table 2 (landscape)
file2 <- tempfile(fileext = ".rtf")
ae_df |>
  r2rtf::rtf_page(orientation = "landscape") |>
  r2rtf::rtf_title(title = "Table 14.3.1",
                   subtitle = "TEAEs by SOC and Preferred Term") |>
  r2rtf::rtf_colheader(
    colheader     = col_header,
    col_rel_width = c(3, 3, rep(2, length(arm_names)))
  ) |>
  r2rtf::rtf_body(
    col_rel_width      = c(3, 3, rep(2, length(arm_names))),
    text_justification = c("l", "l", rep("c", length(arm_names)))
  ) |>
  r2rtf::rtf_encode() |>
  r2rtf::write_rtf(file2)

# Assemble into one combined RTF file
r2rtf::assemble_rtf(
  input  = c(file1, file2),
  output = "tlf_package_day27.rtf"
)
cat("Exported: tlf_package_day27.rtf\n")
Exported: tlf_package_day27.rtf

6 Validation Checks

cat("\n=== Day 27 Validation ===\n\n")

=== Day 27 Validation ===
cat("Check 1 - rtables simple table:",    class(tbl_simple),    "\n")
Check 1 - rtables simple table: ElementaryTable 
cat("Check 2 - tern demo table:",         class(tbl_tern_demo), "\n")
Check 2 - tern demo table: TableTree 
cat("Check 3 - AE pruned rows:",          nrow(tbl_ae_pruned),  "\n")
Check 3 - AE pruned rows: 253 
cat("Check 4 - SOC order (top 3):",       soc_order[1:3],       "\n")
Check 4 - SOC order (top 3): GENERAL DISORDERS AND ADMINISTRATION SITE CONDITIONS SKIN AND SUBCUTANEOUS TISSUE DISORDERS NERVOUS SYSTEM DISORDERS 
files_out <- c("table14_1_1_r2rtf.rtf", "tlf_package_day27.rtf")
cat("\nOutput files:\n")

Output files:
for (f in files_out) {
  cat(sprintf("  %s: %s\n", f, if (file.exists(f)) "EXISTS" else "MISSING"))
}
  table14_1_1_r2rtf.rtf: EXISTS
  tlf_package_day27.rtf: EXISTS
cat("\nValidation complete\n")

Validation complete

7 Key Takeaways

  1. Factor all categorical variables before build_table() – rtables requires identical levels per column
  2. split_fun = drop_split_levels on every split_rows_by() – silently drops factor levels absent from data
  3. Pre-order factor levels via dplyr for sorted output – sort_at_path(cont_n_allcols) requires content rows that count_occurrences() does not produce
  4. rcell() with sprintf() avoids the format-label validator for multi-value cells
  5. tern::analyze_vars() is the correct exported demographics function (not summarize_vars())
  6. rtf_encode() only accepts doc_type = "table" or "figure" – use assemble_rtf(input, output) to merge separate RTF files into one document

8 Resources

  • rtables: https://insightsengineering.github.io/rtables/
  • tern: https://insightsengineering.github.io/tern/
  • r2rtf: https://merck.github.io/r2rtf/
  • r4csr book: https://r4csr.org

End of Day 27


 

30 Days of Pharmaverse  ·  Disclaimer  ·  Indraneel Chakraborty  ·  © 2026