@@ -14,8 +14,9 @@ knitr::knit_hooks$set(purl = invisible_hook_purl)
1414## Introduction
1515
1616This guide demonstrates how pharmaverse packages, along with tools from the
17- tidyverse, can be used to create standard oncology survival Tables, Listings,
18- and Graphs (TLGs) using the ` {pharmaverseadam} ` ` ADTTE_ONCO ` dataset as input.
17+ tidyverse, can be used to create standard oncology efficacy Tables, Listings,
18+ and Graphs (TLGs) using the ` {pharmaverseadam} ` ` ADTTE_ONCO ` and ` ADRS_ONCO `
19+ datasets as input.
1920
2021The packages used, with a brief description of their purpose, are as follows:
2122
@@ -25,10 +26,12 @@ The packages used, with a brief description of their purpose, are as follows:
2526 of publication-ready time-to-event (survival) figures built on ` {ggplot2} ` .
2627 Includes ` Surv_CNSR() ` to handle CDISC ADTTE censoring conventions natively.
2728* [ ` {gtsummary} ` ] ( https://www.danieldsjoberg.com/gtsummary/ ) : creates
28- publication-ready summary and analytical tables. Works seamlessly with
29- ` {ggsurvfit} ` to generate median survival tables .
29+ publication-ready summary and analytical tables. Used here for survival and
30+ tumor response summaries .
3031* [ ` {dplyr} ` ] ( https://dplyr.tidyverse.org/ ) : provides data manipulation
31- functions used to prepare and filter the ADTTE data.
32+ functions used to prepare and filter the ADaM data.
33+ * [ ` {forcats} ` ] ( https://forcats.tidyverse.org/ ) : provides factor manipulation
34+ utilities used to order RECIST response categories for table display.
3235
3336The outputs produced in this example are:
3437
@@ -39,19 +42,21 @@ The outputs produced in this example are:
39423 . ** Median survival table** with 95% confidence interval, suitable for
4043 inclusion in a clinical study report (CSR).
41444 . ** Survival probability table** at selected time points.
42- 5 . ** Stratified KM plot** using the built-in ` ggsurvfit::adtte ` four-arm trial
45+ 5 . ** Best Overall Response table** with category counts, percentages, and
46+ inline ORR (CR + PR) summary from ` ADRS_ONCO ` .
47+ 6 . ** Stratified KM plot** using the built-in ` ggsurvfit::adtte ` four-arm trial
4348 dataset to illustrate multi-arm analyses.
4449
4550---
4651
4752## Setup
4853
49- We load the required packages and read ` ADTTE_ONCO ` from ` {pharmaverseadam} ` .
50- This is the oncology-specific TTE dataset generated from the ` {admiralonco} `
51- template. It contains three endpoints — Overall Survival ( ` OS ` ), Progression
52- Free Survival ( ` PFS ` ), and Duration of Response ( ` RSD ` ) — with treatment arm
53- variables ( ` ARM ` , ` ARMCD ` ) already merged in, making it ready for stratified
54- analyses without a separate ADSL join .
54+ We load the required packages and read ` ADTTE_ONCO ` and ` ADRS_ONCO ` from
55+ ` {pharmaverseadam} ` . ` ADTTE_ONCO ` is the oncology-specific TTE dataset generated
56+ from the ` {admiralonco} ` template — it contains three endpoints (OS, PFS, RSD)
57+ with treatment arm variables already merged in. ` ADRS_ONCO ` is the tumor response
58+ dataset containing per-visit and summary response parameters (BOR, CBOR, RSP)
59+ derived from RECIST 1.1 assessments .
5560
5661Because the CDISC ADTTE censoring variable ` CNSR ` is coded `1 = censored /
57620 = event` (the reverse of base R's ` survival::Surv()` convention), we use
@@ -65,10 +70,15 @@ library(ggsurvfit)
6570library(ggplot2)
6671library(gtsummary)
6772library(dplyr)
73+ library(forcats)
6874
6975# ── Read data ──────────────────────────────────────────────────────────────────
7076adtte_onco <- pharmaverseadam::adtte_onco
7177
78+ # ── ADRS_ONCO: tumor response data ───────────────────────────────────────────
79+ adrs_onco <- pharmaverseadam::adrs_onco |>
80+ filter(ARMCD != "Scrnfail")
81+
7282# Overview of available endpoints and their event rates
7383adtte_onco |>
7484 group_by(PARAMCD, PARAM) |>
@@ -92,7 +102,7 @@ adtte_onco |>
92102#| message: false
93103#| warning: false
94104
95- # ── Filter to PFS endpoint ───────────────── ────────────────────────────────────
105+ # ── PFS endpoint ────────────────────────────────────
96106adtte_pfs <- adtte_onco |>
97107 filter(PARAMCD == "PFS")
98108
@@ -154,7 +164,7 @@ km_fit |>
154164 scale_ggsurvfit() +
155165 labs(
156166 title = paste0(unique(adtte_pfs$PARAM), "\nKaplan-Meier Estimate"),
157- x = paste0( "Time (", unique(adtte_pfs$ AVALU), ")"),
167+ x = "Time (Years) ", # AVALU not present in adtte_onco; AVAL is in years
158168 y = "Progression-Free Survival Probability",
159169 caption = paste0(
160170 "Analysis dataset: ADTTE_ONCO | PARAMCD: ", unique(adtte_pfs$PARAMCD),
@@ -210,7 +220,7 @@ tbl_survfit(
210220This table shows estimated PFS probabilities at clinically meaningful time
211221points. For PFS these are typically shorter intervals than OS — 3, 6, 9, and
21222212 months are standard in many oncology trials. Adjust ` times ` to match the
213- ` AVALU ` units in your dataset.
223+ ` AVALU ` units in your dataset. Note that ` adtte_onco ` does not include ` AVALU ` , so the x-axis label is hardcoded as ` "Time (Years)" ` .
214224
215225``` {r prob-table-classic}
216226#| message: false
@@ -275,6 +285,83 @@ The `y` argument must be passed as a character string when using the data frame
275285method. See the [ ` {cardx} ` documentation] ( https://insightsengineering.github.io/cardx/main/reference/ard_survival_survfit.html )
276286for full details.
277287:::
288+ ---
289+
290+ ## Best Overall Response Table
291+
292+ The Best Overall Response (BOR) table is a standard oncology efficacy output
293+ summarising each subject's best RECIST response category across all post-baseline
294+ assessments. Using ` PARAMCD == "CBOR" ` (Confirmed Best Overall Response) aligns
295+ with the primary regulatory definition of ORR as confirmed CR + PR.
296+
297+ The ` {gtsummary} ` ` tbl_summary() ` function produces the category counts and
298+ percentages directly from ` AVALC ` . Response categories are ordered from best to
299+ worst (CR → PR → SD → NON-CR/NON-PD → PD → NE → MISSING) using a factor, and
300+ the ORR row (CR + PR) is highlighted with ` add_stat_label() ` .
301+
302+ ``` {r bor-setup}
303+ #| message: false
304+ #| warning: false
305+
306+ # ── Filter to CBOR parameter ──────────────────────────────────
307+ # ANL01FL == "Y" restricts to the primary analysis flag records.
308+ adrs_bor <- adrs_onco |>
309+ filter(PARAMCD == "CBOR" & ANL01FL == "Y") |>
310+ mutate(
311+ # Order AVALC from best to worst response for table display
312+ AVALC = fct_relevel(
313+ AVALC,
314+ "CR", "PR", "SD", "NON-CR/NON-PD", "PD", "NE", "MISSING"
315+ )
316+ )
317+ ```
318+
319+ ``` {r bor-table}
320+ #| message: false
321+ #| warning: false
322+
323+ # ── Best Overall Response table by treatment arm ───────────────────────────────
324+ adrs_bor |>
325+ tbl_summary(
326+ by = ARM,
327+ include = AVALC,
328+ label = list(AVALC = "Best Overall Response"),
329+ statistic = list(AVALC = "{n} ({p}%)"),
330+ digits = list(AVALC = list(0, 1))
331+ ) |>
332+ add_overall(last = TRUE) |>
333+ add_n() |>
334+ bold_labels() |>
335+ modify_header(label = "**Response**") |>
336+ modify_caption(
337+ paste0(
338+ "**Table 3. Confirmed Best Overall Response (RECIST 1.1)**",
339+ "\nADRS_ONCO | PARAMCD: CBOR | ANL01FL = Y"
340+ )
341+ )
342+ ```
343+
344+ The ORR (overall response rate, CR + PR) is not directly produced by
345+ ` tbl_summary() ` as a combined row, but can be derived and appended using
346+ ` tbl_stack() ` or reported inline:
347+
348+ ``` {r orr-inline}
349+ #| message: false
350+ #| warning: false
351+
352+ # ── ORR: proportion with CR or PR, by arm ─────────────────────────────────────
353+ adrs_bor |>
354+ summarise(
355+ .by = ARM,
356+ n_resp = sum(AVALC %in% c("CR", "PR"), na.rm = TRUE),
357+ n_tot = n(),
358+ orr = round(100 * n_resp / n_tot, 1)
359+ ) |>
360+ mutate(label = paste0(n_resp, "/", n_tot, " (", orr, "%)")) |>
361+ select(ARM, ORR = label) |>
362+ knitr::kable(caption = "Overall Response Rate (CR + PR) by Arm")
363+ ```
364+
278365
279366---
280367
@@ -328,6 +415,8 @@ template, no ADSL join is needed to produce a stratified KM plot:
328415``` {r strata-note}
329416#| eval: true
330417
418+ # With two arms (ARM), add_pvalue() computes and annotates a log-rank test p-value.
419+ # Not applicable for single-arm fits (~ 1) — only add when comparing groups.
331420survfit2(Surv_CNSR(AVAL, CNSR) ~ ARM, data = adtte_pfs) |>
332421 ggsurvfit(linewidth = 1) +
333422 scale_color_brewer(palette = "Dark2") +
@@ -351,7 +440,8 @@ showing a **stratified KM plot**, since it has four well-separated treatment arm
351440with a good event rate and an estimable median.
352441
353442The treatment variable is ` TRT01P ` (planned treatment at randomisation), which
354- contains the four arm labels directly.
443+ contains the four arm labels directly. ` STR01 ` is hormone receptor status — a
444+ stratification covariate, not the treatment assignment.
355445
356446``` {r km-plot-adtte}
357447#| message: false
0 commit comments