# You could parametrize paths via YAML; here we keep inline defaults.
dm_path <- "data/sdtm/dm.sas7bdat"
ex_path <- "data/sdtm/ex.sas7bdat"13 Capstone: End-to-End Mini Workflow
This chapter ties everything together: read data → derive ADSL → produce TLFs → render a report.
13.1 Parameters
13.2 1) Read (or Synthesize) SDTM
library(haven); library(dplyr); library(lubridate)
Attaching package: 'dplyr'
The following objects are masked from 'package:stats':
filter, lag
The following objects are masked from 'package:base':
intersect, setdiff, setequal, union
Attaching package: 'lubridate'
The following objects are masked from 'package:base':
date, intersect, setdiff, union
if (file.exists(dm_path)) {
dm <- read_sas(dm_path)
} else {
dm <- tibble::tibble(
STUDYID = "XYZ123",
USUBJID = sprintf("XYZ-%03d", 1:60),
ARM = sample(c("Placebo","Active"), 60, replace=TRUE),
AGE = round(rnorm(60, 60, 8)),
SEX = sample(c("M","F"), 60, replace=TRUE),
RANDDT = as.Date("2025-01-15") + sample(0:40, 60, replace=TRUE)
)
}
if (file.exists(ex_path)) {
ex <- read_sas(ex_path)
} else {
ex <- tibble::tibble(
USUBJID = dm$USUBJID,
EXSTDTC = dm$RANDDT + sample(0:3, nrow(dm), replace=TRUE)
)
}13.3 2) Derive ADSL (Minimal Demo)
adsl <- dm |>
left_join(ex, by="USUBJID") |>
mutate(
TRT01P = ARM,
TRT01PN = as.integer(factor(ARM, levels=c("Placebo","Active"))),
TRT01A = TRT01P,
TRT01AN = TRT01PN,
SAFFL = "Y", # demo only; define rules in real life
FASFL = "Y"
) |>
dplyr::select(STUDYID.x, USUBJID, TRT01P, TRT01PN, TRT01A, TRT01AN, AGE, SEX, EXSTDTC, SAFFL, FASFL)13.4 3) TLFs
library(gt); library(ggplot2); library(survival)
tbl1 <- adsl |>
group_by(TRT01P) |>
summarise(N=n(),
mean_age = mean(AGE), sd_age = sd(AGE),
pct_female = mean(SEX=="F")*100)
tbl1_gt <- gt(tbl1) |> tab_header(title="Table 1. Baseline by Treatment")
tbl1_gt| Table 1. Baseline by Treatment | ||||
| Description of Planned Arm | N | mean_age | sd_age | pct_female |
|---|---|---|---|---|
| Placebo | 226 | 75.04867 | 8.503715 | 60.61947 |
| Screen Failure | 52 | 75.09615 | 9.699928 | 69.23077 |
| Xanomeline High Dose | 184 | 74.01087 | 7.939656 | 48.36957 |
| Xanomeline Low Dose | 181 | 75.29834 | 8.277778 | 60.77348 |
set.seed(123)
adsl$time <- rexp(nrow(adsl), rate=ifelse(adsl$TRT01P=="Active", 0.08, 0.1))
adsl$status <- rbinom(nrow(adsl), 1, 0.7)
fit <- survfit(Surv(time, status) ~ TRT01P, data=adsl)
# reuse plotting function from prior chapter
ggsurv <- function(fit) {
ss <- summary(fit)
dd <- data.frame(time=ss$time, surv=ss$surv, strata=rep(names(fit$strata), fit$strata))
ggplot(dd, aes(x=time, y=surv, linetype=strata)) + geom_step() + theme_minimal() +
labs(title="KM Curve (Toy)", x="Time", y="Survival", linetype="Treatment")
}
#ggsurv(fit)13.5 4) Save Outputs
# Example: Save Table 1 as PNG
#gtsave(tbl1_gt, "tlf-table1.png")Challenge: Convert this chapter into a parameterized report (e.g., treatment subset or different cohort) and render multiple outputs.