arframe TFL Gallery
  1. Tables
  2. Adverse Events by Subgroup
  • Getting Started
    • Installation

  • Tables
    • Study Conduct
    • Enrollment by Country and Site

    • Study Population
    • Demographics Summary
    • Medical History
    • Prior Medication
    • Disposition Summary
    • Analysis Populations

    • Extent of Exposure
    • Concomitant Medications
    • Extent of Exposure

    • Safety
    • Adverse Events by System Organ Class and Preferred Term
    • AEs Related to Study Drug
    • Common Adverse Events
    • Adverse Events by Grade / Intensity
    • Overall Safety Summary
    • Adverse Events with Event Counts
    • Exposure-Adjusted Adverse Events
    • Adverse Events by Subgroup
    • Serious Adverse Events by SOC and PT
    • AEs Leading to Study Drug Discontinuation
    • Death Summary
    • Vital Signs
    • Laboratory Results - Chemistry
    • Laboratory Shift Table
    • Laboratory Worst Toxicity Grade
    • Laboratory Marked Abnormalities
    • Electrocardiogram Summary

    • Efficacy
    • Time to Event Summary
    • Best Overall Response

  • Listings
    • Adverse Event Listing
    • Demographic Characteristics Listing
    • Medical History Listing
    • Vital Signs Listing
    • Laboratory Test Results Listing
    • Concomitant Medications Listing

  • Figures
    • Kaplan-Meier Plot
    • Swimmer Plot
    • Waterfall Plot

On this page

  • Setup
  • Data Preparation
  • arframe Pipeline
  • Rendered Table
  1. Tables
  2. Adverse Events by Subgroup

Adverse Events by Subgroup

Treatment-Emergent AE Summary by Age Group

Setup

See Prerequisites for installation instructions.

library(arframe)
library(pharmaverseadam)
library(dplyr, warn.conflicts = FALSE)
library(tidyr)

adsl_raw <- pharmaverseadam::adsl |>
  blank_to_na() |>
  filter(SAFFL == "Y", TRT01A != "Screen Failure")

# Standardize AGEGR1 to <65 / >=65
adsl_saf <- adsl_raw |>
  mutate(AGEGR1 = ifelse(AGE < 65, "<65", ">=65"))

# Derive AGEGR1 in adae consistently with adsl
adae_teae <- pharmaverseadam::adae |>
  blank_to_na() |>
  filter(SAFFL == "Y", TRTEMFL == "Y") |>
  mutate(AGEGR1 = ifelse(AGE < 65, "<65", ">=65"))

arm_levels  <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")
agegr_levels <- c("<65", ">=65")

# N per arm x subgroup (denominator)
arm_agegr_n <- adsl_saf |>
  filter(TRT01A %in% arm_levels) |>
  count(TRT01A, AGEGR1) |>
  filter(AGEGR1 %in% agegr_levels) |>
  mutate(col_id = paste0(TRT01A, "__", AGEGR1)) |>
  pull(n, name = col_id)

Data Preparation

# ── AE category definitions ──
ae_categories <- list(
  list(label = "Any TEAE",                     flag = "TRTEMFL", value = "Y"),
  list(label = "Any Serious AE (SAE)",          flag = "AESER",   value = "Y"),
  list(label = "Any AE Leading to Death",       flag = "AESDTH",  value = "Y"),
  list(label = "Any Related AE",
       flag = "AEREL", value = "PROBABLE")
)

# ── Helper: count unique subjects per arm x subgroup for a flag condition ──
ae_subgroup_row <- function(ae_data, adsl_data, flag_var, flag_value, row_label,
                             arm_levels, agegr_levels, denominators) {
  flag_subjs <- ae_data |>
    filter(.data[[flag_var]] == flag_value) |>
    distinct(USUBJID, TRT01A, AGEGR1) |>
    filter(TRT01A %in% arm_levels, AGEGR1 %in% agegr_levels)

  counts <- flag_subjs |>
    count(TRT01A, AGEGR1, name = "n") |>
    complete(TRT01A = arm_levels, AGEGR1 = agegr_levels, fill = list(n = 0L)) |>
    mutate(
      col_id = paste0(TRT01A, "__", AGEGR1),
      denom  = denominators[col_id],
      value  = sprintf("%d (%.1f)", n, n / denom * 100)
    ) |>
    select(col_id, value) |>
    pivot_wider(names_from = col_id, values_from = value)

  counts |> mutate(stat_label = row_label, .before = 1)
}

ae_subgr_wide <- lapply(ae_categories, function(cat) {
  ae_subgroup_row(
    ae_data     = adae_teae,
    adsl_data   = adsl_saf,
    flag_var    = cat$flag,
    flag_value  = cat$value,
    row_label   = cat$label,
    arm_levels  = arm_levels,
    agegr_levels = agegr_levels,
    denominators = arm_agegr_n
  )
}) |>
  bind_rows() |>
  mutate(across(where(is.character) & !stat_label, ~ replace_na(.x, "0 (0.0)")))

# ── N-count vector for column headers ──
n_vec <- arm_agegr_n

arframe Pipeline

# Build fr_col() specs: one per arm x subgroup combination
col_specs <- setNames(
  lapply(agegr_levels, function(g) fr_col(g, align = "decimal", width = 1.2)),
  paste0(arm_levels[[1]], "__", agegr_levels)
)
for (a in arm_levels[-1]) {
  more <- setNames(
    lapply(agegr_levels, function(g) fr_col(g, align = "decimal", width = 1.2)),
    paste0(a, "__", agegr_levels)
  )
  col_specs <- c(col_specs, more)
}

ae_subgr_wide |>
  fr_table() |>
  fr_titles(
    "Adverse Events by Subgroup",
    "Treatment-Emergent AE Summary by Age Group",
    "Safety Population"
  ) |>
  fr_cols(
    stat_label = fr_col("", width = 2.8),
    !!!col_specs,
    .n = n_vec
  ) |>
  fr_spans(
    !!!setNames(
      lapply(arm_levels, function(a) {
        paste0(a, "__", agegr_levels)
      }),
      arm_levels
    )
  ) |>
  fr_header(bold = TRUE, align = "center") |>
  fr_footnotes(
    "TEAE = Treatment-Emergent Adverse Event.",
    "Subjects counted once per AE category.",
    "Percentages based on N per treatment arm x age group (Safety Population).",
    "Age group: <65 = under 65 years; >=65 = 65 years or older."
  )

Rendered Table

Adverse Events by Subgroup
Treatment-Emergent AE Summary by Age Group
Safety Population
PlaceboXanomeline High DoseXanomeline Low Dose
<65
(N=14)
>=65
(N=72)
<65
(N=11)
>=65
(N=61)
<65
(N=8)
>=65
(N=88)
Any TEAE10 (71.4)55 (76.4)10 (90.9)58 (95.1)8 (100.0)76 (86.4)
Any Serious AE (SAE) 0 0 0 1 ( 1.6)0 2 ( 2.3)
Any AE Leading to Death 0 2 ( 2.8) 0 0 0 1 ( 1.1)
Any Related AE 4 (28.6)19 (26.4) 7 (63.6)41 (67.2)6 ( 75.0)45 (51.1)
TEAE = Treatment-Emergent Adverse Event.
Subjects counted once per AE category.
Percentages based on N per treatment arm x age group (Safety Population).
Age group: <65 = under 65 years; >=65 = 65 years or older.
/opt/quarto/share/rmd/rmd.R 01APR2026 09:52:30
Source Code
---
title: "Adverse Events by Subgroup"
subtitle: "Treatment-Emergent AE Summary by Age Group"
execute:
  echo: true
  eval: true
---


```{r}
#| label: prereqs
#| include: false
library(arframe)
fr_theme(hlines = "header", font_family = "Courier New")
blank_to_na <- function(df) {
  df[] <- lapply(df, function(x) {
    if (is.character(x)) x[x == ""] <- NA_character_
    x
  })
  df
}
```

## Setup

See [Prerequisites](../install.qmd) for installation instructions.

```{r}
#| label: setup
library(arframe)
library(pharmaverseadam)
library(dplyr, warn.conflicts = FALSE)
library(tidyr)

adsl_raw <- pharmaverseadam::adsl |>
  blank_to_na() |>
  filter(SAFFL == "Y", TRT01A != "Screen Failure")

# Standardize AGEGR1 to <65 / >=65
adsl_saf <- adsl_raw |>
  mutate(AGEGR1 = ifelse(AGE < 65, "<65", ">=65"))

# Derive AGEGR1 in adae consistently with adsl
adae_teae <- pharmaverseadam::adae |>
  blank_to_na() |>
  filter(SAFFL == "Y", TRTEMFL == "Y") |>
  mutate(AGEGR1 = ifelse(AGE < 65, "<65", ">=65"))

arm_levels  <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")
agegr_levels <- c("<65", ">=65")

# N per arm x subgroup (denominator)
arm_agegr_n <- adsl_saf |>
  filter(TRT01A %in% arm_levels) |>
  count(TRT01A, AGEGR1) |>
  filter(AGEGR1 %in% agegr_levels) |>
  mutate(col_id = paste0(TRT01A, "__", AGEGR1)) |>
  pull(n, name = col_id)
```


## Data Preparation

```{r}
#| label: dplyr-code

# ── AE category definitions ──
ae_categories <- list(
  list(label = "Any TEAE",                     flag = "TRTEMFL", value = "Y"),
  list(label = "Any Serious AE (SAE)",          flag = "AESER",   value = "Y"),
  list(label = "Any AE Leading to Death",       flag = "AESDTH",  value = "Y"),
  list(label = "Any Related AE",
       flag = "AEREL", value = "PROBABLE")
)

# ── Helper: count unique subjects per arm x subgroup for a flag condition ──
ae_subgroup_row <- function(ae_data, adsl_data, flag_var, flag_value, row_label,
                             arm_levels, agegr_levels, denominators) {
  flag_subjs <- ae_data |>
    filter(.data[[flag_var]] == flag_value) |>
    distinct(USUBJID, TRT01A, AGEGR1) |>
    filter(TRT01A %in% arm_levels, AGEGR1 %in% agegr_levels)

  counts <- flag_subjs |>
    count(TRT01A, AGEGR1, name = "n") |>
    complete(TRT01A = arm_levels, AGEGR1 = agegr_levels, fill = list(n = 0L)) |>
    mutate(
      col_id = paste0(TRT01A, "__", AGEGR1),
      denom  = denominators[col_id],
      value  = sprintf("%d (%.1f)", n, n / denom * 100)
    ) |>
    select(col_id, value) |>
    pivot_wider(names_from = col_id, values_from = value)

  counts |> mutate(stat_label = row_label, .before = 1)
}

ae_subgr_wide <- lapply(ae_categories, function(cat) {
  ae_subgroup_row(
    ae_data     = adae_teae,
    adsl_data   = adsl_saf,
    flag_var    = cat$flag,
    flag_value  = cat$value,
    row_label   = cat$label,
    arm_levels  = arm_levels,
    agegr_levels = agegr_levels,
    denominators = arm_agegr_n
  )
}) |>
  bind_rows() |>
  mutate(across(where(is.character) & !stat_label, ~ replace_na(.x, "0 (0.0)")))

# ── N-count vector for column headers ──
n_vec <- arm_agegr_n
```


## arframe Pipeline

```{r}
#| label: pipeline
#| eval: false

# Build fr_col() specs: one per arm x subgroup combination
col_specs <- setNames(
  lapply(agegr_levels, function(g) fr_col(g, align = "decimal", width = 1.2)),
  paste0(arm_levels[[1]], "__", agegr_levels)
)
for (a in arm_levels[-1]) {
  more <- setNames(
    lapply(agegr_levels, function(g) fr_col(g, align = "decimal", width = 1.2)),
    paste0(a, "__", agegr_levels)
  )
  col_specs <- c(col_specs, more)
}

ae_subgr_wide |>
  fr_table() |>
  fr_titles(
    "Adverse Events by Subgroup",
    "Treatment-Emergent AE Summary by Age Group",
    "Safety Population"
  ) |>
  fr_cols(
    stat_label = fr_col("", width = 2.8),
    !!!col_specs,
    .n = n_vec
  ) |>
  fr_spans(
    !!!setNames(
      lapply(arm_levels, function(a) {
        paste0(a, "__", agegr_levels)
      }),
      arm_levels
    )
  ) |>
  fr_header(bold = TRUE, align = "center") |>
  fr_footnotes(
    "TEAE = Treatment-Emergent Adverse Event.",
    "Subjects counted once per AE category.",
    "Percentages based on N per treatment arm x age group (Safety Population).",
    "Age group: <65 = under 65 years; >=65 = 65 years or older."
  )
```


## Rendered Table

```{r}
#| label: rendered-table
#| echo: false
#| ref.label: pipeline
```

Open-source TFL reference collection

 

CDISC Pilot Study (CDISCPILOT01) • pharmaverseadam datasets