ARD to Tables with {tfrmt}

Daniel D. Sjoberg and Becca Krouse

Workshop outline

  1. Introduction to the Analysis Results Standard and {cards}

  2. Introduction to the {cardx} Package and ARD Extras

  3. ARD to Tables with {gtsummary}

  4. ARD to Tables with {tfrmt}

ARD-first Tables with {tfrmt}

  • Metadata-driven table formatting

  • Easily create new and modify existing tables

  • Input: ARD with raw, numeric values ({cards}!)

  • Output: Formatted table via {gt}

The {tfrmt} object

  • Pre-define the non-data components of your table
  • Pre-define how the data will be handled once added

A quick tour of the many uses of {tfrmt}

Use #1: Study planning (mocks)

library(tfrmt)

print_mock_gt(
  tfrmt = tfrmt_demog, # tfrmt
  .data = d_demog_mock # sample ARD
)|> 
   gt_style_slides()
Demographics Table
Placebo
N = xx
Low Dose
N = xx
High Dose
N = xx
Age (y)     


  Mean       xxx.x        xxx.x        xxx.x       
  SD         xxx.xx       xxx.xx       xxx.xx      
  Median     xxx.x        xxx.x        xxx.x       
  Min        xxx.x        xxx.x        xxx.x       
  Max        xxx.x        xxx.x        xxx.x       
  <65 yrs    xxx (xx.x %) xxx (xx.x %) xxx (xx.x %)
  65-80 yrs  xxx (xx.x %) xxx (xx.x %) xxx (xx.x %)
  >80 yrs    xxx (xx.x %) xxx (xx.x %) xxx (xx.x %)
Sex         


  Male       xxx (xx.x %) xxx (xx.x %) xxx (xx.x %)
  Female     xxx (xx.x %) xxx (xx.x %) xxx (xx.x %)
Baseline BMI


  Mean       xxx.x        xxx.x        xxx.x       
  SD         xxx.xx       xxx.xx       xxx.xx      
  Median     xxx.x        xxx.x        xxx.x       
  Min        xxx.x        xxx.x        xxx.x       
  Max        xxx.x        xxx.x        xxx.x       
  <25        xxx (xx.x %) xxx (xx.x %) xxx (xx.x %)
  25-<30     xxx (xx.x %) xxx (xx.x %) xxx (xx.x %)
  >=30       xxx (xx.x %) xxx (xx.x %) xxx (xx.x %)
  • If no data is supplied, {tfrmt} will generate some under the hood

Use #2: Final analysis

library(tfrmt)

print_to_gt(
  tfrmt = tfrmt_demog,
  .data = d_demog # true ARD
)|> 
   gt_style_slides()
Demographics Table
Placebo
N = 86
Low Dose
N = 84
High Dose
N = 84
Age (y)     


  Mean       75.2        75.7        74.4       
  SD          8.59        8.29        7.89      
  Median     76.0        77.5        76.0       
  Min        52.0        51.0        56.0       
  Max        89.0        88.0        88.0       
  <65 yrs    14 (16.3 %)  8 ( 9.5 %) 11 (13.1 %)
  65-80 yrs  42 (48.8 %) 47 (56.0 %) 55 (65.5 %)
  >80 yrs    30 (34.9 %) 29 (34.5 %) 18 (21.4 %)
Sex         


  Male       33 (38.4 %) 34 (40.5 %) 44 (52.4 %)
  Female     53 (61.6 %) 50 (59.5 %) 40 (47.6 %)
Baseline BMI


  Mean       23.6        25.1        25.3       
  SD          3.67        4.27        4.16      
  Median     23.4        24.3        24.8       
  Min        15.1        17.7        13.7       
  Max        33.3        40.1        34.5       
  <25        59 (68.6 %) 47 (56.0 %) 44 (52.4 %)
  25-<30     21 (24.4 %) 27 (32.1 %) 28 (33.3 %)
  >=30        6 ( 7.0 %) 10 (11.9 %) 12 (14.3 %)
  • Full reuse of the original {tfrmt} object = reduced rework!

Use #3: Repurposed final table

library(tfrmt)

tfrmt_demog |> 
  layer_tfrmt(
    tfrmt_demog_custom  
  )|> 
  print_to_gt( 
    .data = d_demog
  ) |> 
  gt::grp_pull(1)|> 
   gt_style_slides()
Demographics Table
Safety Population
High Dose
(N = 84)
Low Dose
(N = 84)
Placebo
(N = 86)
Age (y)    


  Mean      74.4        75.7        75.2       
  SD         7.89        8.29        8.59      
  Median    76.0        77.5        76.0       
  Min       56.0        51.0        52.0       
  Max       88.0        88.0        89.0       
                                               
  <65 yrs   11 (13.1 %)  8 ( 9.5 %) 14 (16.3 %)
  65-80 yrs 55 (65.5 %) 47 (56.0 %) 42 (48.8 %)
  >80 yrs   18 (21.4 %) 29 (34.5 %) 30 (34.9 %)
                                               
Sex        


  Male      44 (52.4 %) 34 (40.5 %) 33 (38.4 %)
  Female    40 (47.6 %) 50 (59.5 %) 53 (61.6 %)
                                               
Data collected at Screening Visit
  • Layering allows for custom tweaks while preserving the original metadata

Templates: the possibilities

  • Organization standards can be capture as templates
# create a template as a function
tfrmt_demog_org <- function(tfrmt_obj){
  
  tfrmt_demog_org <- tfrmt( 
    # define standard formatting for org
  )
  
  layer_tfrmt(x = tfrmt_obj, y = tfrmt_demog_ta)
}

# Make a standard table
tfrmt_demog_org() |>  
  print_to_gt(
    .data = demog_data
  )

Templates: the possibilities

  • Organization standards can be capture as templates

  • With layering, teams can customize only the bits that need changing

# create a template as a function
tfrmt_demog_ta <- function(tfrmt_obj){
  
  tfrmt_demog_ta <- tfrmt( 
    # define the formatting specific to the therapeutic area
  )
  
  layer_tfrmt(x = tfrmt_obj, y = tfrmt_demog_ta)
}

# Layering multiple templates
tfrmt_demog_org() |> 
  tfrmt_demog_ta() |>  
  print_to_gt(
    .data = demog_data
  )

Templates: the possibilities

  • Organization standards can be capture as templates

  • With layering, teams can customize only the bits that need changing

# create a template as a function
tfrmt_demog_study <- function(tfrmt_obj){
  
  tfrmt_demog_study <- tfrmt( 
    # define the formatting specific to the study
  )
  
  layer_tfrmt(x = tfrmt_obj, y = tfrmt_demog_study)
}

# Layering multiple templates
tfrmt_demog_org() |> 
  tfrmt_demog_ta() |>  
  tfrmt_demog_study() |> 
  print_to_gt(
    .data = demog_data
  )

Save metadata for reuse

library(tfrmt)

tfrmt_demog |> 
  tfrmt_to_json()
{
  "group": ["rowlbl1", "grp"],
  "label": ["rowlbl2"],
  "param": ["param"],
  "value": ["value"],
  "column": ["column"],
  "title": ["Demographics Table"],
  "body_plan": [
    {
      "group_val": [".default"],
      "label_val": [".default"],
      "param_val": ["n", "pct"],
      "frmt_combine": {
        "expression": ["{n} {pct}"],
        "frmt_ls": {
          "n": {
            "frmt": {
              "expression": ["xxx"],
              "missing": {},
              "scientific": {},
              "transform": {}
            }
          },
          "pct": {
            "frmt_when": {
              "frmt_ls": {
                "==100": [""],
                "==0": [""],
                "TRUE": {
                  "frmt": {
                    "expression": ["(xx.x %)"],
                    "missing": {},
                    "scientific": {},
                    "transform": {}
                  }
                }
              },
              "missing": {}
            }
          }
        },
        "missing": {}
      }
    },
    {
      "group_val": [".default"],
      "label_val": ["n"],
      "param_val": [".default"],
      "frmt": {
        "expression": ["xxx"],
        "missing": {},
        "scientific": {},
        "transform": {}
      }
    },
    {
      "group_val": [".default"],
      "label_val": ["Mean", "Median", "Min", "Max"],
      "param_val": [".default"],
      "frmt": {
        "expression": ["xxx.x"],
        "missing": {},
        "scientific": {},
        "transform": {}
      }
    },
    {
      "group_val": [".default"],
      "label_val": ["SD"],
      "param_val": [".default"],
      "frmt": {
        "expression": ["xxx.xx"],
        "missing": {},
        "scientific": {},
        "transform": {}
      }
    }
  ],
  "col_style_plan": [
    {
      "cols": [
        ["Placebo"],
        ["Low Dose"],
        ["High Dose"]
      ],
      "align": [".", ",", " "],
      "type": ["char"],
      "width": {}
    },
    {
      "cols": [
        ["rowlbl1"],
        ["rowlbl2"]
      ],
      "align": ["left"],
      "type": ["char"],
      "width": {}
    }
  ],
  "col_plan": {
    "col_plan": {
      "dots": [
        ["-grp"],
        ["-starts_with(\"ord\")"]
      ],
      ".drop": [false]
    }
  },
  "sorting_cols": ["ord1", "ord2"],
  "big_n": {
    "param_val": ["bigN"],
    "n_frmt": {
      "expression": ["<br>N = xx"],
      "missing": {},
      "scientific": {},
      "transform": {}
    },
    "by_page": [false]
  }
} 
  • Create a language-agnostic JSON file

  • Load JSON back into R and recreate the table with json_to_tfrmt()

Saving display

  • tfrmt::print_to_gt() creates a {gt} object under the hood

  • You can export the table to a variety of formats using gt::gt_save()

Now, let’s format a display, 1 piece at a time

Creating a {tfrmt} table step-by-step

head(ard_demog)
# A tibble: 6 × 7
  TRT01A  variable    label    stat_name  stat  ord1  ord2
  <chr>   <chr>       <chr>    <chr>     <dbl> <dbl> <dbl>
1 Active  Age (years) Median   median       77     1    NA
2 Active  Age (years) [Q1, Q3] p25          71     1    NA
3 Active  Age (years) [Q1, Q3] p75          81     1    NA
4 Placebo Age (years) Median   median       76     1    NA
5 Placebo Age (years) [Q1, Q3] p25          69     1    NA
6 Placebo Age (years) [Q1, Q3] p75          82     1    NA


tfrmt_demog <- tfrmt(
  group = variable, 
  label = label, 
  column = TRT01A,
  param = stat_name,
  value = stat, 
  sorting_cols = c(ord1, ord2) 
)

Ensure placement of all values (Main)

tfrmt_demog <- tfrmt(
  group = variable, 
  label = label, 
  column = TRT01A,
  param = stat_name,
  value = stat, 
  sorting_cols = c(ord1, ord2) 
)

print_to_gt(
  tfrmt = tfrmt_demog,
  .data = ard_demog
) |> 
   gt_style_slides()
ord1 ord2 Active Placebo
Age (years)



  Median 1 NA 77 76
  [Q1, Q3] 1 NA 71, 81 69, 82
Age Group



  18-64 2 1 19, 11.3095238095238 14, 16.2790697674419
  >64 2 2 149, 88.6904761904762 72, 83.7209302325581
TRT01A



  Active NA NA 168
  Placebo NA NA 86

Ensure placement of all values (Big N)

tfrmt_demog <- tfrmt_demog |>
  tfrmt(
    big_n = big_n_structure(
      param_val = "bigN",
      n_frmt = frmt("<br>N = xx")
      )
  )

print_to_gt(
  tfrmt = tfrmt_demog, 
  .data = ard_demog
  )|> 
   gt_style_slides()
ord1 ord2 Active
N = 168
Placebo
N = 86
Age (years)



  Median 1 NA 77 76
  [Q1, Q3] 1 NA 71, 81 69, 82
Age Group



  18-64 2 1 19, 11.3095238095238 14, 16.2790697674419
  >64 2 2 149, 88.6904761904762 72, 83.7209302325581

Select and reorder columns

tfrmt_demog <- tfrmt_demog |>
  tfrmt(
    col_plan = col_plan(
      Placebo,
      Active,
      - starts_with("ord")
    )
  )

print_to_gt(
  tfrmt = tfrmt_demog, 
  .data = ard_demog
)|> 
   gt_style_slides()
Placebo
N = 86
Active
N = 168
Age (years)

  Median 76 77
  [Q1, Q3] 69, 82 71, 81
Age Group

  18-64 14, 16.2790697674419 19, 11.3095238095238
  >64 72, 83.7209302325581 149, 88.6904761904762

Format the data values - Basic

tfrmt_demog <- tfrmt_demog |>
  tfrmt(
    body_plan = body_plan(
      frmt_structure(
        group_val = ".default", 
        label_val = ".default", 
        frmt("x.xx"))
    )
  )

print_to_gt(
  tfrmt = tfrmt_demog, 
  .data = ard_demog
) |> 
   gt_style_slides()
Placebo
N = 86
Active
N = 168
Age (years)

  Median 76.00 77.00
  [Q1, Q3] 69.00, 82.00 71.00, 81.00
Age Group

  18-64 14.00, 16.28 19.00, 11.31
  >64 72.00, 83.72 149.00, 88.69

Format the data values - Advanced

tfrmt_demog <- tfrmt_demog |>
  tfrmt(
    body_plan = body_plan(
      frmt_structure(
        group_val = ".default", 
        label_val = ".default",
        frmt_combine("{n} ({p}%)",
                     n = frmt("xx"),
                     p = frmt("xx.x")
                     
        )
      )
    )
  )

print_to_gt(
  tfrmt = tfrmt_demog, 
  .data = ard_demog
  ) |> 
   gt_style_slides()
Placebo
N = 86
Active
N = 168
Age (years)

  Median 76.00 77.00
  [Q1, Q3] 69.00, 82.00 71.00, 81.00
Age Group

  18-64 14 (16.3%) 19 (11.3%)
  >64 72 (83.7%) 149 (88.7%)

Format the data values - Advanced

tfrmt_demog <- tfrmt_demog |>
  tfrmt(
    body_plan = body_plan(
      
      frmt_structure(
        group_val = ".default", 
        label_val = "Median",
        frmt("xx.x")
      ),
      
      frmt_structure(
        group_val = ".default",
        label_val = ".default",
        frmt_combine(
          expression = "[{p25}, {p75}]",                
          p25 = frmt("xx.x"),                     
          p75 = frmt("xx.x")                      
        )
      )
      
    )
  )
  
print_to_gt(
  tfrmt = tfrmt_demog, 
  .data = ard_demog
  ) |> 
   gt_style_slides()
Placebo
N = 86
Active
N = 168
Age (years)

  Median 76.0 77.0
  [Q1, Q3] [69.0, 82.0] [71.0, 81.0]
Age Group

  18-64 14 (16.3%) 19 (11.3%)
  >64 72 (83.7%) 149 (88.7%)

Align the columns

tfrmt_demog <- tfrmt_demog |>
  tfrmt(
    col_style_plan = col_style_plan(
      col_style_structure(
        col = c("Placebo", 
                "Active"),
        align = " "
      )
    )
  )
  
print_to_gt(
  tfrmt = tfrmt_demog, 
  .data = ard_demog
  ) |> 
   gt_style_slides()
Placebo
N = 86
Active
N = 168
Age (years)

  Median   76.0           77.0        
  [Q1, Q3] [69.0, 82.0]   [71.0, 81.0]  
Age Group

  18-64     14 (16.3%)     19 (11.3%)
  >64     72 (83.7%)    149 (88.7%)

Add footnotes

tfrmt_demog <- tfrmt_demog |>
  tfrmt(
    footnote_plan = footnote_plan(
      footnote_structure(
        "Pooled High and Low Dose",
        column_val = "Active"
      )
    )
  )
  
print_to_gt(
  tfrmt = tfrmt_demog, 
  .data = ard_demog
  ) |> 
   gt_style_slides()
Placebo
N = 86
Active
N = 1681
Age (years)

  Median   76.0           77.0        
  [Q1, Q3] [69.0, 82.0]   [71.0, 81.0]  
Age Group

  18-64     14 (16.3%)     19 (11.3%)
  >64     72 (83.7%)    149 (88.7%)
1 Pooled High and Low Dose

Add titles

tfrmt_demog <- tfrmt_demog |>
  tfrmt(
    title = "Demographic Table",
    subtitle = "Safety Population"
  )
  
print_to_gt(
  tfrmt = tfrmt_demog, 
  .data = ard_demog
  ) |> 
   gt_style_slides()
Demographic Table
Safety Population
Placebo
N = 86
Active
N = 1681
Age (years)

  Median   76.0           77.0        
  [Q1, Q3] [69.0, 82.0]   [71.0, 81.0]  
Age Group

  18-64     14 (16.3%)     19 (11.3%)
  >64     72 (83.7%)    149 (88.7%)
1 Pooled High and Low Dose

Other features include:

  • Transform values in the formatting

  • Row group formatting

  • Pagination

  • Multi-positional column alignment

  • Templating

cards to tfrmt

  • {cards} includes a helper function shuffle_ard() to transform native {cards} output to display-ready data

  • This is also available as an argument .shuffle in ard_stack()

library(cards)

pharmaverseadam::adsl |> 
  ard_categorical(
    by = TRT01A,
    variables = c("AGEGR1","SEX")
  )
   group1 group1_level variable variable_level stat_name stat_label  stat
1  TRT01A      Placebo   AGEGR1            >64         n          n    72
2  TRT01A      Placebo   AGEGR1            >64         N          N    86
3  TRT01A      Placebo   AGEGR1            >64         p          % 0.837
4  TRT01A    Screen F…   AGEGR1            >64         n          n    43
5  TRT01A    Screen F…   AGEGR1            >64         N          N    52
6  TRT01A    Screen F…   AGEGR1            >64         p          % 0.827
7  TRT01A    Xanomeli…   AGEGR1            >64         n          n    61
8  TRT01A    Xanomeli…   AGEGR1            >64         N          N    72
9  TRT01A    Xanomeli…   AGEGR1            >64         p          % 0.847
10 TRT01A    Xanomeli…   AGEGR1            >64         n          n    88

cards to tfrmt

  • {cards} includes a helper function shuffle_ard() to transform native {cards} output to display-ready data

  • This is also available as an argument .shuffle in ard_stack()

library(cards)

pharmaverseadam::adsl |> 
  ard_categorical(
    by = TRT01A,
    variables = c("AGEGR1","SEX")
  ) |> 
  shuffle_ard()
# A tibble: 48 × 6
   TRT01A         variable label context     stat_name   stat
   <chr>          <chr>    <chr> <chr>       <chr>      <dbl>
 1 Placebo        AGEGR1   >64   categorical n         72    
 2 Placebo        AGEGR1   >64   categorical N         86    
 3 Placebo        AGEGR1   >64   categorical p          0.837
 4 Placebo        AGEGR1   18-64 categorical n         14    
 5 Placebo        AGEGR1   18-64 categorical N         86    
 6 Placebo        AGEGR1   18-64 categorical p          0.163
 7 Screen Failure AGEGR1   >64   categorical n         43    
 8 Screen Failure AGEGR1   >64   categorical N         52    
 9 Screen Failure AGEGR1   >64   categorical p          0.827
10 Screen Failure AGEGR1   18-64 categorical n          9    
# ℹ 38 more rows

cards to tfrmt

  • {cards} includes a helper function shuffle_ard() to transform native {cards} output to display-ready data

  • This is also available as an argument .shuffle in ard_stack()

library(cards)
library(tfrmt)

my_ard <- pharmaverseadam::adsl |> 
  ard_categorical(
    by = TRT01A,
    variables = c("AGEGR1","SEX")
  ) |>  
  shuffle_ard() |> 
  dplyr::filter(stat_name %in% c("n","p")) |> 
  dplyr::select(-context)

tfrmt(
  group = variable,
  label = label,
  column = TRT01A,
  param = stat_name,
  value = stat,
  body_plan = body_plan(
    frmt_structure(group_val = ".default", 
                   label_val = ".default", 
                   frmt_combine("{n} ({p}%)",
                                n = frmt("xx"),
                                p = frmt("xx", 
                                         transform = ~.*100)))
  )
) |> 
  print_to_gt(my_ard)|> 
  gt_style_slides()|> 
    gt::tab_options(
      table.font.size = 15
    )
Placebo Screen Failure Xanomeline High Dose Xanomeline Low Dose
AGEGR1



  >64 72 (84%) 43 (83%) 61 (85%) 88 (92%)
  18-64 14 (16%)  9 (17%) 11 (15%)  8 ( 8%)
SEX



  F 53 (62%) 36 (69%) 35 (49%) 55 (57%)
  M 33 (38%) 16 (31%) 37 (51%) 41 (43%)

The {tfrmtbuilder} App

  • Point-and-click interface for {tfrmt}

  • Ability to create new or modify existing display

  • Eases learning curve for new users

  • Empowers non-programmers

Exercise 🏃‍➡️

  • Open R file exercises/04-tables-tfrmt.R

  • Run the provided code, which will open the app in your browser

  • Complete the steps to further customize the table (hint: Edit tab):

    1. Switch the order of treatment columns so Active is first (hint: Column Plan)

    2. Change the footnote marks to letters instead of numbers (hint: Footnote Plan)

    3. Split the table into 2 parts by setting a maximum of 3 rows (hint: Page Plan)

    4. Export the table (hint: Export Tab)

Workshop recap

Workshop recap

  1. Introduction to the Analysis Results Standard and {cards}

  2. Introduction to the {cardx} Package and ARD Extras

  3. ARD to Tables with {gtsummary}

  4. ARD to Tables with {tfrmt}

Slido

Wrap-up slido quiz

Learning more

Thank you for coming! 🎉