Exercises + Solutions

Exercises

Setup

Run this once, before any exercise.

# Setup: run this first, before any exercise ----------------------------

library(cards)
library(gtsummary)
library(tidyverse)

adsl <- pharmaverseadam::adsl |>
  filter(SAFFL == "Y") |>
  mutate(ARM2 = word(ARM))

adae <- pharmaverseadam::adae |>
  filter(SAFFL == "Y") |>
  filter(AESOC %in% unique(AESOC)[1:3]) |>
  group_by(AESOC) |>
  filter(AEDECOD %in% unique(AEDECOD)[1:3]) |>
  ungroup()

Exercise 1: ARDs with {cards}

# Exercise 1: ARDs with {cards}
#
# Run `00-setup.R` first.

# A. Calculate the number and percentage of *unique* subjects with at least
# one AE:
#  - By each SOC (AESOC)
#  - By each Preferred term (AEDECOD) within SOC (AESOC)
# By every combination of treatment group (ARM)

ard_ae <-
  ard_stack_hierarchical(
    data = ,
    variables = ,
    by = ,
    id = ,
    denominator =
  )

ard_ae

# B. Using the ARD you just built, report the rate of subjects who
# experienced fatigue in each treatment group.
# HINT: `filter()` the ARD down to the rows you need, and
# `apply_fmt_fun()` formats the statistics for reporting

Exercise 2: a theme for your organization

# Exercise 2: a theme for your organization
#
# Run `00-setup.R` first.

# A. Finish the theme below. It should:
#  - be named "R/Pharma 2026"
#  - print every table with {flextable}
#  - round percentages to one decimal place
#  - round p-values to three decimal places
# HINT: the element names are catalogued in the themes vignette,
# https://www.danieldsjoberg.com/gtsummary/articles/themes.html

theme_gtsummary_rpharma <- function(set_theme = TRUE) {
  lst_theme <-
    utils::modifyList(
      # start from the shipped compact theme, then layer ours on top
      theme_gtsummary_compact(set_theme = FALSE),
      list(
        "pkgwide-str:theme_name"     = ,
        "pkgwide-str:print_engine"   = ,
        "tbl_summary-fn:percent_fun" = ,
        "pkgwide-fn:pvalue_fun"      =
      )
    )

  if (set_theme == TRUE) set_gtsummary_theme(lst_theme)
  invisible(lst_theme)
}

# B. Check the theme, then set it and build a table to see it applied.

check_gtsummary_theme()

# C. Clear the theme again before moving on.

reset_gtsummary_theme()

Exercise 3: tbl_pharma_summary()

# Exercise 3: tbl_pharma_summary()
#
# Run `00-setup.R` first.

# A. Finish the wrapper below. Continuous variables should default to
# multi-line summaries showing mean (sd), median, Q1/Q3 and min/max;
# categorical variables to n (%).
#
# Note `type` stays NULL, exactly as in tbl_summary() — the continuous2
# default comes from the theme element instead, so an explicit `type =`
# from the caller still wins.

tbl_pharma_summary <- function(
    data,
    by = NULL,
    type = NULL,
    # TODO: replace NULL with the default statistics described above
    statistic = NULL,
    ...) {
  with_gtsummary_theme(
    # TODO: the element that makes continuous variables multi-line
    list("tbl_summary-str:default_con_type" = ),
    tbl_summary(
      data = data,
      by = {{ by }},
      type = type,
      statistic = statistic,
      ...
    )
  )
}

# B. Use it to summarize AGE and ETHNIC in adsl by ARM2.

Solutions

Exercise 1 solution

# Exercise 1 solution: ARDs with {cards}
#
# Run `00-setup.R` first.

# A. Calculate the number and percentage of *unique* subjects with at least
# one AE:
#  - By each SOC (AESOC)
#  - By each Preferred term (AEDECOD) within SOC (AESOC)
# By every combination of treatment group (ARM)

ard_ae <-
  ard_stack_hierarchical(
    data = adae,
    variables = c(AESOC, AEDECOD),
    by = ARM,
    id = USUBJID,
    denominator = adsl
  )

ard_ae

# B. Report the rate of subjects who experienced fatigue in each treatment
# group. We used `filter()` to keep the fatigue rows, and `apply_fmt_fun()`
# to format the statistics.

ard_ae |>
  filter(variable == "AEDECOD", variable_level == "FATIGUE") |>
  apply_fmt_fun()

Exercise 2 solution

# Exercise 2 solution: a theme for your organization
#
# Run `00-setup.R` first.

# A. Finish the theme below.

theme_gtsummary_rpharma <- function(set_theme = TRUE) {
  lst_theme <-
    utils::modifyList(
      # start from the shipped compact theme, then layer ours on top
      theme_gtsummary_compact(set_theme = FALSE),
      list(
        "pkgwide-str:theme_name"     = "R/Pharma 2026",
        "pkgwide-str:print_engine"   = "flextable",
        "tbl_summary-fn:percent_fun" = scales::label_number(scale = 100, accuracy = 0.1),
        "pkgwide-fn:pvalue_fun"      = scales::label_pvalue()
      )
    )

  if (set_theme == TRUE) set_gtsummary_theme(lst_theme)
  invisible(lst_theme)
}

# B. Check the theme, then set it and build a table to see it applied.

check_gtsummary_theme(theme_gtsummary_rpharma(set_theme = FALSE))

theme_gtsummary_rpharma()

adsl |>
  tbl_summary(by = ARM2, include = c(AGE, ETHNIC)) |>
  add_p()

# C. Clear the theme again before moving on.

reset_gtsummary_theme()

Exercise 3 solution

# Exercise 3 solution: tbl_pharma_summary()
#
# Run `00-setup.R` first.

# A. Finish the wrapper below.

tbl_pharma_summary <- function(
    data,
    by = NULL,
    type = NULL,
    statistic = list(
      all_continuous() ~ c("{mean} ({sd})", "{median}",
                           "{p25}, {p75}", "{min}, {max}"),
      all_categorical() ~ "{n} ({p}%)"
    ),
    ...) {
  with_gtsummary_theme(
    list("tbl_summary-str:default_con_type" = "continuous2"),
    tbl_summary(
      data = data,
      by = {{ by }},
      type = type,
      statistic = statistic,
      ...
    )
  )
}

# B. Use it to summarize AGE and ETHNIC in adsl by ARM2.

adsl |>
  tbl_pharma_summary(
    by = ARM2,
    include = c(AGE, ETHNIC)
  )