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 armCommon Adverse Events
Treatment-Emergent AEs Occurring in ≥5% of Any Treatment Arm
Setup
See Prerequisites for installation instructions.
Data Preparation
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