library(arframe)
library(pharmaverseadam)
library(dplyr, warn.conflicts = FALSE)
library(tidyr)
library(cards)
adex_saf <- pharmaverseadam::adex |>
blank_to_na() |>
filter(
SAFFL == "Y",
TRT01A != "Screen Failure",
PARAMCD %in% c("TDURD", "TDOSE", "AVDDSE")
)
arm_levels <- c("Placebo", "Xanomeline Low Dose", "Xanomeline High Dose")
# N per arm from TDURD (one row per subject)
arm_n <- adex_saf |>
filter(PARAMCD == "TDURD") |>
count(TRT01A) |>
pull(n, name = TRT01A)
arm_n <- arm_n[arm_levels]Extent of Exposure
Study Drug Exposure Summary
Setup
See Prerequisites for installation instructions.
Data Preparation
param_labels <- c(
TDURD = "Total Duration (days)",
TDOSE = "Total Dose (mg)",
AVDDSE = "Average Daily Dose (mg)"
)
# ── Continuous stats per PARAMCD × TRT01A ──
cont_stats <- adex_saf |>
group_by(PARAMCD, TRT01A) |>
summarise(
N = as.character(n()),
Mean = sprintf("%.1f", mean(AVAL, na.rm = TRUE)),
SD = sprintf("%.2f", sd(AVAL, na.rm = TRUE)),
Median = sprintf("%.1f", median(AVAL, na.rm = TRUE)),
Min = sprintf("%.1f", min(AVAL, na.rm = TRUE)),
Max = sprintf("%.1f", max(AVAL, na.rm = TRUE)),
.groups = "drop"
) |>
pivot_longer(c(N, Mean, SD, Median, Min, Max),
names_to = "stat_label", values_to = "value") |>
pivot_wider(names_from = TRT01A, values_from = value) |>
mutate(
variable = unname(param_labels[PARAMCD]),
.before = 1
) |>
select(-PARAMCD)
# ── Duration category rows (derived from TDURD) ──
tdurd_data <- adex_saf |> filter(PARAMCD == "TDURD")
dur_thresholds <- c(
"\u22651 day" = 1,
"\u226530 days" = 30,
"\u226560 days" = 60,
"\u226590 days" = 90,
"\u2265180 days" = 180
)
dur_rows <- lapply(names(dur_thresholds), function(lbl) {
thresh <- dur_thresholds[[lbl]]
by_arm <- tdurd_data |>
group_by(TRT01A) |>
summarise(
n = sum(AVAL >= thresh, na.rm = TRUE),
N = n(),
pct = sprintf("%d (%.1f)", n, n / N * 100),
.groups = "drop"
) |>
select(TRT01A, pct) |>
pivot_wider(names_from = TRT01A, values_from = pct)
by_arm |>
mutate(
variable = "Duration Categories, n (%)",
stat_label = lbl,
.before = 1
)
})
dur_wide <- bind_rows(dur_rows)
# ── Combine all rows ──
# Order: TDURD stats, TDOSE stats, AVDDSE stats, duration categories
param_order <- c("Total Duration (days)", "Total Dose (mg)", "Average Daily Dose (mg)",
"Duration Categories, n (%)")
exposure_wide <- bind_rows(cont_stats, dur_wide) |>
mutate(
variable = factor(variable, levels = param_order),
stat_label = factor(stat_label, levels = c("N", "Mean", "SD", "Median", "Min", "Max",
names(dur_thresholds)))
) |>
arrange(variable, stat_label) |>
mutate(
variable = as.character(variable),
stat_label = as.character(stat_label)
) |>
mutate(across(all_of(arm_levels), ~ replace_na(.x, "")))# ── Continuous stats via cards ──
# .by = c("TRT01A", "PARAMCD"): TRT01A becomes arm columns, PARAMCD is preserved
# as an extra group column in fr_wide_ard output alongside variable/stat_label.
ex_ard <- ard_stack(
data = adex_saf,
.by = c("TRT01A", "PARAMCD"),
ard_continuous(
variables = "AVAL",
statistic = ~ continuous_summary_fns(c("N", "mean", "sd", "median", "min", "max"))
),
.overall = FALSE
) |>
filter(variable == "AVAL")
param_labels <- c(
TDURD = "Total Duration (days)",
TDOSE = "Total Dose (mg)",
AVDDSE = "Average Daily Dose (mg)"
)
exposure_cards <- fr_wide_ard(
ex_ard,
statistic = list(
continuous = c(
"N" = "{N}",
"Mean" = "{mean}",
"SD" = "{sd}",
"Median" = "{median}",
"Min" = "{min}",
"Max" = "{max}"
)
),
decimals = c(mean = 1, sd = 2, median = 1, min = 1, max = 1),
column = "TRT01A"
) |>
mutate(variable = unname(param_labels[PARAMCD])) |>
select(-PARAMCD) |>
mutate(across(where(is.factor), as.character))arframe Pipeline
The rendered table below uses the dplyr data prep (exposure_wide). The cards tab produces an equivalent exposure_cards — swap it in to use the cards path instead.
exposure_wide |>
fr_table() |>
fr_titles(
"Extent of Exposure",
"Study Drug Exposure Summary",
"Safety Population"
) |>
fr_cols(
variable = fr_col(visible = FALSE),
stat_label = fr_col("", width = 2.2),
!!!setNames(
lapply(arm_levels, function(a) fr_col(a, align = "decimal")),
arm_levels
),
.n = arm_n
) |>
fr_header(bold = TRUE, align = "center") |>
fr_rows(
group_by = list(cols = "variable", label = "stat_label"),
blank_after = "variable",
group_style = list(bold = TRUE)
) |>
fr_footnotes(
"Safety Population: subjects who received at least one dose of study drug.",
"Duration categories are based on Total Duration (TDURD). Percentages based on N per treatment group.",
"SD = Standard Deviation."
)Rendered Table
Extent of Exposure
Study Drug Exposure Summary
Safety Population
| Placebo (N=86) | Xanomeline High Dose (N=72) | Xanomeline Low Dose (N=96) | |
|---|---|---|---|
| Total Duration (days) | |||
| N | 86 | 72 | 96 |
| Mean | 147.8 | 112.2 | 85.9 |
| SD | 62.13 | 65.52 | 70.66 |
| Median | 182.0 | 96.5 | 62.5 |
| Min | 0.0 | 15.0 | 0.0 |
| Max | 210.0 | 200.0 | 212.0 |
| Total Dose (mg) | |||
| N | 86 | 72 | 96 |
| Mean | 0.0 | 8341.5 | 4638.9 |
| SD | 0.00 | 5182.95 | 3815.61 |
| Median | 0.0 | 7114.5 | 3375.0 |
| Min | 0.0 | 810.0 | 0.0 |
| Max | 0.0 | 15417.0 | 11448.0 |
| Average Daily Dose (mg) | |||
| N | 86 | 72 | 96 |
| Mean | 0.0 | 72.9 | 54.0 |
| SD | 0.00 | 9.68 | 0.00 |
| Median | 0.0 | 75.9 | 54.0 |
| Min | 0.0 | 4.4 | 54.0 |
| Max | 0.0 | 78.6 | 54.0 |
| Duration Categories, n (%) | |||
| ≥1 day | 85 (98.8) | 72 (100.0) | 95 (99.0) |
| ≥30 days | 78 (90.7) | 67 ( 93.1) | 65 (67.7) |
| ≥60 days | 71 (82.6) | 49 ( 68.1) | 51 (53.1) |
| ≥90 days | 67 (77.9) | 38 ( 52.8) | 40 (41.7) |
| ≥180 days | 55 (64.0) | 26 ( 36.1) | 25 (26.0) |
Safety Population: subjects who received at least one dose of study drug.
Duration categories are based on Total Duration (TDURD). Percentages based on N per treatment group.
SD = Standard Deviation.
/opt/quarto/share/rmd/rmd.R
01APR2026 09:53:08