Day 30 is the capstone. A single Quarto document drives a complete Efficacy · Safety · Lab reporting workflow using five pharmaverse packages and eight figures.
# Show what treatment columns adae actually carriesae_trt_cols <-intersect(names(adae), c("TRTA", "TRTP", "TRT01A", "TRT01P", "ARM"))cat("Treatment columns in adae:", if (length(ae_trt_cols)) ae_trt_cols else"NONE", "\n")
Treatment columns in adae: ARM TRT01P TRT01A
# ── Subject-to-treatment lookup from ADSL ───────────────────────────────────# pharmaverseadam::adae may not carry a treatment column at all.# Safe pattern: distinct(USUBJID, event) first, THEN left_join treatment.# This avoids any dependency on adae containing a treatment variable,# and avoids .x/.y conflicts from joining a column that already exists.trt_lookup <- adsl |> dplyr::filter(SAFFL =="Y") |> dplyr::select(USUBJID, TRT01A) |> dplyr::distinct()cat("trt_lookup rows:", nrow(trt_lookup), "\n")
# ── Safety-population N per arm (denominator for AE %) ──────────────────────trt_n <- adsl |> dplyr::filter(SAFFL =="Y") |> dplyr::count(TRT01A, name ="N_TRT")# ── First available lab PARAMCD ──────────────────────────────────────────────lab_param <-unique(adlb$PARAMCD)[1]cat("\nLab parameter for Figures 6 & 7:", lab_param, "\n")
Lab parameter for Figures 6 & 7: ALB
3 Efficacy
3.1 Figure 1 · Overall Survival - KM Curve
# survfit2() is required for add_pvalue().# Layering order: geoms → scales → theme → add_risktable() last.km_fit <-survfit2(Surv_CNSR(AVAL, CNSR) ~ TRTP, data = adtte)km_fit
Call: survfit(formula = Surv_CNSR(AVAL, CNSR) ~ TRTP, data = adtte)
n events median 0.95LCL 0.95UCL
TRTP=Placebo 85 2 NA NA NA
TRTP=Xanomeline High Dose 83 0 NA NA NA
TRTP=Xanomeline Low Dose 84 1 NA NA NA
cat("Check 13 - Log-rank p :", format(survfit2_p(km_fit), digits =4), "\n")
Check 13 - Log-rank p : p=0.5
cat("\nAll checks complete\n")
All checks complete
9 Key Takeaways
pharmaverseadam::adae carries no treatment column – never pre-join it into adae; instead use distinct(USUBJID, event) first, then left_join(trt_lookup, by = "USUBJID") to add TRT01A right before count()
Pre-joining creates .x/.y duplicates if the column already exists in adae – the safe pattern is: filter → distinct on event columns only → join → count
adtte inherits all adsl columns via filter() + mutate(), not transmute() – AGE, AGEGR1, SEX, RACE are already present; never re-join adsl into adtte
survfit2() + Surv_CNSR() required for add_pvalue(); Surv_CNSR() handles ADaM CNSR coding in survfit2() and coxph()
add_risktable() must be last in the ggsurvfit chain – it wraps the plot with patchwork and cannot be further modified
theme_clin() + trt_col defined once in setup, applied everywhere with scale_fill_manual(values = trt_col) and scale_colour_manual(values = trt_col)
Dynamic lab_param from unique(adlb$PARAMCD)[1] avoids hard-coded parameter codes – the code ports to any study without changes