arframe TFL Gallery
  1. Tables
  2. Common Adverse Events
  • 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. Common Adverse Events

Common Adverse Events

Treatment-Emergent AEs Occurring in ≥5% of Any Treatment Arm

Setup

See Prerequisites for installation instructions.

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

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

adae_teae <- pharmaverseadam::adae |>
  blank_to_na() |>
  filter(SAFFL == "Y", TRTEMFL == "Y")

arm_levels <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")
arm_n <- adsl_saf |>
  filter(TRT01A %in% arm_levels) |>
  count(TRT01A) |> pull(n, name = TRT01A)
arm_n <- arm_n[arm_levels]
N_total <- nrow(adsl_saf)
n_vec <- c(arm_n, Total = N_total)

threshold <- 5  # ≥5% in any arm

Data Preparation

  • dplyr
  • cards
n_pct <- function(n, denom) sprintf("%d (%.1f)", n, n / denom * 100)

# ── Subject-level PT counts per arm ──
pt_counts <- adae_teae |>
  filter(TRT01A %in% arm_levels) |>
  distinct(USUBJID, TRT01A, AEDECOD) |>
  count(TRT01A, AEDECOD, name = "n_subj") |>
  mutate(pct = n_subj / arm_n[TRT01A] * 100)

# ── Filter PTs meeting threshold in any arm ──
common_pts <- pt_counts |>
  group_by(AEDECOD) |>
  filter(any(pct >= threshold)) |>
  ungroup()

# ── Pivot to wide ──
common_wide <- common_pts |>
  mutate(value = n_pct(n_subj, arm_n[TRT01A])) |>
  select(TRT01A, AEDECOD, value) |>
  pivot_wider(names_from = TRT01A, values_from = value)

# ── Add Total column ──
pt_total <- adae_teae |>
  distinct(USUBJID, AEDECOD) |>
  count(AEDECOD) |>
  mutate(Total = n_pct(n, N_total)) |>
  select(AEDECOD, Total)

common_wide <- left_join(common_wide, pt_total, by = "AEDECOD")

# ── Sort by max frequency across arms (descending) ──
sort_key <- common_pts |>
  group_by(AEDECOD) |>
  summarise(max_pct = max(pct), .groups = "drop") |>
  arrange(desc(max_pct))

ae_common_wide <- common_wide |>
  mutate(stat_label = AEDECOD) |>
  select(-AEDECOD) |>
  slice(match(sort_key$AEDECOD, stat_label)) |>
  mutate(across(all_of(c(arm_levels, "Total")),
                ~ replace_na(.x, "0 (0.0)")))
# Use ard_stack_hierarchical for flat PT list (no SOC grouping)
common_ard <- ard_stack_hierarchical(
  data        = adae_teae,
  variables   = AEDECOD,
  by          = TRT01A,
  denominator = adsl_saf,
  id          = USUBJID,
  overall     = TRUE,
  over_variables = TRUE
) |>
  sort_ard_hierarchical(sort = "descending")

ae_common_cards <- fr_wide_ard(
  common_ard,
  statistic = "{n} ({p}%)",
  decimals  = c(p = 1)
)

# Filter to PTs meeting threshold in any arm
if (nrow(ae_common_cards) > 0) {
  pct_cols <- intersect(c(arm_levels, "Total"), names(ae_common_cards))
  ae_common_cards <- ae_common_cards |>
    filter(if_any(all_of(pct_cols), function(x) {
      nums <- suppressWarnings(as.numeric(gsub(".*\\(([0-9.]+)\\).*", "\\1", x)))
      !is.na(nums) & nums >= threshold
    }))
}

arframe Pipeline

ae_common_wide |>
  fr_table() |>
  fr_titles(
    "Table 14.3.1",
    sprintf("Patients With Common Adverse Events Occurring at %s%% Frequency", "\u2265"),
    "Safety Population"
  ) |>
  fr_cols(
    stat_label = fr_col("Preferred Term", width = 3.0),
    !!!setNames(
      lapply(arm_levels, function(a) fr_col(a, align = "decimal")),
      arm_levels
    ),
    Total = fr_col("Total", align = "decimal"),
    .n = n_vec
  ) |>
  fr_header(bold = TRUE, align = "center") |>
  fr_footnotes(
    sprintf("Includes preferred terms occurring in %s5%% of subjects in any treatment arm.", "\u2265"),
    "Subjects counted once per preferred term.",
    "Percentages based on N per treatment arm (Safety Population).",
    "Table sorted by descending maximum frequency across arms."
  )

Rendered Table

Table 14.3.1
Patients With Common Adverse Events Occurring at ≥% Frequency
Safety Population
Placebo
(N=86)
Xanomeline High Dose
(N=72)
Xanomeline Low Dose
(N=96)
Total
(N=254)
Preferred Term
8 ( 9.3)25 (34.7)21 (21.9)54 (21.3)PRURITUS
6 ( 7.0)21 (29.2)23 (24.0)50 (19.7)APPLICATION SITE PRURITUS
3 ( 3.5)14 (19.4)13 (13.5)30 (11.8)APPLICATION SITE ERYTHEMA
8 ( 9.3)14 (19.4)14 (14.6)36 (14.2)ERYTHEMA
2 ( 2.3)10 (13.9) 9 ( 9.4)21 ( 8.3)DIZZINESS
5 ( 5.8) 8 (11.1)13 (13.5)26 (10.2)RASH
3 ( 3.5) 9 (12.5) 9 ( 9.4)21 ( 8.3)APPLICATION SITE IRRITATION
2 ( 2.3) 8 (11.1) 4 ( 4.2)14 ( 5.5)HYPERHIDROSIS
2 ( 2.3) 8 (11.1) 7 ( 7.3)17 ( 6.7)SINUS BRADYCARDIA
9 (10.5) 3 ( 4.2) 5 ( 5.2)17 ( 6.7)DIARRHOEA
5 ( 5.8) 7 ( 9.7) 9 ( 9.4)21 ( 8.3)APPLICATION SITE DERMATITIS
2 ( 2.3) 6 ( 8.3) 4 ( 4.2)12 ( 4.7)NASOPHARYNGITIS
3 ( 3.5) 6 ( 8.3) 3 ( 3.1)12 ( 4.7)NAUSEA
3 ( 3.5) 6 ( 8.3) 4 ( 4.2)13 ( 5.1)VOMITING
6 ( 7.0) 3 ( 4.2) 1 ( 1.0)10 ( 3.9)UPPER RESPIRATORY TRACT INFECTION
1 ( 1.2) 5 ( 6.9) 5 ( 5.2)11 ( 4.3)APPLICATION SITE VESICLES
1 ( 1.2) 5 ( 6.9) 5 ( 5.2)11 ( 4.3)COUGH
1 ( 1.2) 5 ( 6.9) 5 ( 5.2)11 ( 4.3)FATIGUE
3 ( 3.5) 5 ( 6.9) 3 ( 3.1)11 ( 4.3)HEADACHE
3 ( 3.5) 5 ( 6.9) 6 ( 6.2)14 ( 5.5)SKIN IRRITATION
4 ( 4.7) 4 ( 5.6) 2 ( 2.1)10 ( 3.9)MYOCARDIAL INFARCTION
0 4 ( 5.6) 0 4 ( 1.6)SALIVARY HYPERSECRETION
0 1 ( 1.4) 5 ( 5.2) 6 ( 2.4)BLISTER
0 2 ( 2.8) 5 ( 5.2) 7 ( 2.8)SYNCOPE
Includes preferred terms occurring in ≥5% of subjects in any treatment arm.
Subjects counted once per preferred term.
Percentages based on N per treatment arm (Safety Population).
Table sorted by descending maximum frequency across arms.
/opt/quarto/share/rmd/rmd.R 01APR2026 09:51:46
Source Code
---
title: "Common Adverse Events"
subtitle: "Treatment-Emergent AEs Occurring in ≥5% of Any Treatment Arm"
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)
library(cards)

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

adae_teae <- pharmaverseadam::adae |>
  blank_to_na() |>
  filter(SAFFL == "Y", TRTEMFL == "Y")

arm_levels <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")
arm_n <- adsl_saf |>
  filter(TRT01A %in% arm_levels) |>
  count(TRT01A) |> pull(n, name = TRT01A)
arm_n <- arm_n[arm_levels]
N_total <- nrow(adsl_saf)
n_vec <- c(arm_n, Total = N_total)

threshold <- 5  # ≥5% in any arm
```


## Data Preparation

::: {.panel-tabset}

### dplyr

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

n_pct <- function(n, denom) sprintf("%d (%.1f)", n, n / denom * 100)

# ── Subject-level PT counts per arm ──
pt_counts <- adae_teae |>
  filter(TRT01A %in% arm_levels) |>
  distinct(USUBJID, TRT01A, AEDECOD) |>
  count(TRT01A, AEDECOD, name = "n_subj") |>
  mutate(pct = n_subj / arm_n[TRT01A] * 100)

# ── Filter PTs meeting threshold in any arm ──
common_pts <- pt_counts |>
  group_by(AEDECOD) |>
  filter(any(pct >= threshold)) |>
  ungroup()

# ── Pivot to wide ──
common_wide <- common_pts |>
  mutate(value = n_pct(n_subj, arm_n[TRT01A])) |>
  select(TRT01A, AEDECOD, value) |>
  pivot_wider(names_from = TRT01A, values_from = value)

# ── Add Total column ──
pt_total <- adae_teae |>
  distinct(USUBJID, AEDECOD) |>
  count(AEDECOD) |>
  mutate(Total = n_pct(n, N_total)) |>
  select(AEDECOD, Total)

common_wide <- left_join(common_wide, pt_total, by = "AEDECOD")

# ── Sort by max frequency across arms (descending) ──
sort_key <- common_pts |>
  group_by(AEDECOD) |>
  summarise(max_pct = max(pct), .groups = "drop") |>
  arrange(desc(max_pct))

ae_common_wide <- common_wide |>
  mutate(stat_label = AEDECOD) |>
  select(-AEDECOD) |>
  slice(match(sort_key$AEDECOD, stat_label)) |>
  mutate(across(all_of(c(arm_levels, "Total")),
                ~ replace_na(.x, "0 (0.0)")))
```

### cards

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

# Use ard_stack_hierarchical for flat PT list (no SOC grouping)
common_ard <- ard_stack_hierarchical(
  data        = adae_teae,
  variables   = AEDECOD,
  by          = TRT01A,
  denominator = adsl_saf,
  id          = USUBJID,
  overall     = TRUE,
  over_variables = TRUE
) |>
  sort_ard_hierarchical(sort = "descending")

ae_common_cards <- fr_wide_ard(
  common_ard,
  statistic = "{n} ({p}%)",
  decimals  = c(p = 1)
)

# Filter to PTs meeting threshold in any arm
if (nrow(ae_common_cards) > 0) {
  pct_cols <- intersect(c(arm_levels, "Total"), names(ae_common_cards))
  ae_common_cards <- ae_common_cards |>
    filter(if_any(all_of(pct_cols), function(x) {
      nums <- suppressWarnings(as.numeric(gsub(".*\\(([0-9.]+)\\).*", "\\1", x)))
      !is.na(nums) & nums >= threshold
    }))
}
```

:::


## arframe Pipeline

```{r}
#| label: pipeline
#| eval: false
ae_common_wide |>
  fr_table() |>
  fr_titles(
    "Table 14.3.1",
    sprintf("Patients With Common Adverse Events Occurring at %s%% Frequency", "\u2265"),
    "Safety Population"
  ) |>
  fr_cols(
    stat_label = fr_col("Preferred Term", width = 3.0),
    !!!setNames(
      lapply(arm_levels, function(a) fr_col(a, align = "decimal")),
      arm_levels
    ),
    Total = fr_col("Total", align = "decimal"),
    .n = n_vec
  ) |>
  fr_header(bold = TRUE, align = "center") |>
  fr_footnotes(
    sprintf("Includes preferred terms occurring in %s5%% of subjects in any treatment arm.", "\u2265"),
    "Subjects counted once per preferred term.",
    "Percentages based on N per treatment arm (Safety Population).",
    "Table sorted by descending maximum frequency across arms."
  )
```


## Rendered Table

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

Open-source TFL reference collection

 

CDISC Pilot Study (CDISCPILOT01) • pharmaverseadam datasets