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} and {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}

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

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()

cards to tfrmt

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

library(cards)
library(tfrmt)

my_ard <- pharmaverseadam::adsl |> 
  dplyr::filter(SAFFL=="Y") |> 
  ard_categorical(
    by = TRT01A,
    variables = c("AGEGR1","SEX")
  ) |> 
   rename_ard_columns(columns = all_ard_groups(),
                      unlist = c("variable_level", "stat")) |> 
  dplyr::select(-c(fmt_fn, warning, error)) |> 
  dplyr::filter(stat_name %in% c("n","p")) |> 
  dplyr::select(-c(context, stat_label))

tfrmt(
  group = variable,
  label = variable_level ,
  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 Xanomeline High Dose Xanomeline Low Dose
AGEGR1


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


  F 53 (62%) 35 (49%) 55 (57%)
  M 33 (38%) 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

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} and {tfrmt}

Learning more

Thank you for coming! 🎉