Overview
Today covers two supporting ADaM datasets:
ADCM - Concomitant Medications (OCCDS) with pre/concomitant/prior period flags
ADRS - Oncology Response (OCCDS) with RECIST 1.1 Best Overall Response using admiralonco
Note: pharmaversesdtm exports rs_onco (not rs) for oncology response data.
Setup
library (admiral)
library (admiralonco)
library (pharmaversesdtm)
library (pharmaverseadam)
library (dplyr)
library (lubridate)
library (xportr)
# Load SDTM source data
cm <- pharmaversesdtm:: cm
rs_onco <- pharmaversesdtm:: rs_onco # oncology-specific RS domain
# Load ADSL for merging
adsl <- pharmaverseadam:: adsl
cat ("Loaded CM:" , nrow (cm), "records \n " )
cat ("Loaded RS (oncology):" , nrow (rs_onco), "records \n " )
Loaded RS (oncology): 5808 records
cat ("Loaded ADSL:" , nrow (adsl), "subjects \n " )
Loaded ADSL: 306 subjects
ADCM - Concomitant Medications
Step 1: Check CM Structure
cm %>%
dplyr:: select (STUDYID, USUBJID, CMTRT, CMDECOD, CMSTDTC, CMENDTC) %>%
head (5 )
# A tibble: 5 × 6
STUDYID USUBJID CMTRT CMDECOD CMSTDTC CMENDTC
<chr> <chr> <chr> <chr> <chr> <chr>
1 CDISCPILOT01 01-701-1015 ASPIRIN ACETYLSALICYLIC ACID 2003 <NA>
2 CDISCPILOT01 01-701-1015 ASPIRIN ACETYLSALICYLIC ACID 2003 <NA>
3 CDISCPILOT01 01-701-1015 ASPIRIN ACETYLSALICYLIC ACID 2003 <NA>
4 CDISCPILOT01 01-701-1015 ASPIRIN ACETYLSALICYLIC ACID 2003 <NA>
5 CDISCPILOT01 01-701-1015 ASPIRIN ACETYLSALICYLIC ACID 2003 <NA>
Key CM Variables:
CMTRT: Verbatim medication name
CMDECOD: Standardised medication name (WHODrug)
CMSTDTC / CMENDTC: Start / end dates (ISO 8601)
Step 2: Merge ADSL into CM
adcm <- cm %>%
admiral:: derive_vars_merged (
dataset_add = adsl,
new_vars = exprs (TRTSDT, TRTEDT, TRT01A),
by_vars = exprs (STUDYID, USUBJID)
)
cat ("ADCM after ADSL merge:" , nrow (adcm), "records \n " )
ADCM after ADSL merge: 7510 records
Step 3: Derive Start and End Dates
adcm <- adcm %>%
admiral:: derive_vars_dt (
new_vars_prefix = "AST" ,
dtc = CMSTDTC,
highest_imputation = "M"
) %>%
admiral:: derive_vars_dt (
new_vars_prefix = "AEN" ,
dtc = CMENDTC,
highest_imputation = "M"
)
cat ("Dates derived \n " )
adcm %>%
dplyr:: select (USUBJID, CMTRT, ASTDT, AENDT) %>%
head (5 )
# A tibble: 5 × 4
USUBJID CMTRT ASTDT AENDT
<chr> <chr> <date> <date>
1 01-701-1015 ASPIRIN 2003-01-01 NA
2 01-701-1015 ASPIRIN 2003-01-01 NA
3 01-701-1015 ASPIRIN 2003-01-01 NA
4 01-701-1015 ASPIRIN 2003-01-01 NA
5 01-701-1015 ASPIRIN 2003-01-01 NA
Step 4: Derive Period Flags
adcm <- adcm %>%
dplyr:: mutate (
# Pre-treatment: started before TRTSDT and ongoing at TRTSDT
PREFL = dplyr:: case_when (
! is.na (ASTDT) & ASTDT < TRTSDT &
(is.na (AENDT) | AENDT >= TRTSDT) ~ "Y" ,
TRUE ~ NA_character_
),
# Concomitant: overlaps with treatment period
CONFL = dplyr:: case_when (
! is.na (ASTDT) &
ASTDT <= TRTEDT &
(is.na (AENDT) | AENDT >= TRTSDT) ~ "Y" ,
TRUE ~ NA_character_
),
# Prior: ended strictly before treatment start
PRVFL = dplyr:: case_when (
! is.na (AENDT) & AENDT < TRTSDT ~ "Y" ,
TRUE ~ NA_character_
)
)
cat ("Period flags derived \n " )
adcm %>%
dplyr:: summarise (
N_PREFL = sum (PREFL == "Y" , na.rm = TRUE ),
N_CONFL = sum (CONFL == "Y" , na.rm = TRUE ),
N_PRVFL = sum (PRVFL == "Y" , na.rm = TRUE )
)
# A tibble: 1 × 3
N_PREFL N_CONFL N_PRVFL
<int> <int> <int>
1 6155 7337 72
Period Flag Logic:
PREFL
Started before TRTSDT, ongoing at TRTSDT
CONFL
Overlapping with treatment window
PRVFL
Ended strictly before TRTSDT
Step 5: Derive Analysis Sequence
adcm <- adcm %>%
dplyr:: arrange (USUBJID, ASTDT, CMSEQ) %>%
dplyr:: group_by (USUBJID) %>%
dplyr:: mutate (ASEQ = dplyr:: row_number ()) %>%
dplyr:: ungroup ()
cat ("ADCM records:" , nrow (adcm), " \n " )
Step 6: ADCM Validation
cat (" \n === ADCM Validation === \n\n " )
check1 <- adcm %>% dplyr:: filter (is.na (ASTDT))
cat ("Check 1 - Missing ASTDT:" , nrow (check1), " \n " )
Check 1 - Missing ASTDT: 21
check2 <- adcm %>% dplyr:: filter (! is.na (PREFL) & PREFL != "Y" )
cat ("Check 2 - Invalid PREFL values:" , nrow (check2), " \n " )
Check 2 - Invalid PREFL values: 0
check3 <- adcm %>%
dplyr:: count (USUBJID, ASEQ) %>%
dplyr:: filter (n > 1 )
cat ("Check 3 - Duplicate ASEQ per subject:" , nrow (check3), " \n " )
Check 3 - Duplicate ASEQ per subject: 0
cat (" \n ✓ ADCM validation complete \n " )
✓ ADCM validation complete
ADRS - Oncology Tumor Response
Step 7: Check RS Oncology Structure
# pharmaversesdtm exports rs_onco (not rs)
names (rs_onco)
[1] "STUDYID" "DOMAIN" "USUBJID" "RSSEQ" "RSLNKGRP" "RSTESTCD"
[7] "RSTEST" "RSCAT" "RSORRES" "RSSTRESC" "RSSTAT" "RSREASND"
[13] "RSEVAL" "RSEVALID" "RSACPTFL" "VISITNUM" "VISIT" "RSDTC"
[19] "RSDY"
rs_onco %>%
dplyr:: select (USUBJID, RSTESTCD, RSSTRESC, VISIT, RSDTC) %>%
head (5 )
# A tibble: 5 × 5
USUBJID RSTESTCD RSSTRESC VISIT RSDTC
<chr> <chr> <chr> <chr> <chr>
1 01-701-1015 OVRLRESP PD WEEK 6 2014-02-12
2 01-701-1015 NTRGRESP PD WEEK 6 2014-02-12
3 01-701-1015 TRGRESP PR WEEK 6 2014-02-12
4 01-701-1015 OVRLRESP SD WEEK 6 2014-02-12
5 01-701-1015 NTRGRESP NON-CR/NON-PD WEEK 6 2014-02-12
Step 8: Merge ADSL into RS
adrs <- rs_onco %>%
admiral:: derive_vars_merged (
dataset_add = adsl,
new_vars = exprs (RANDDT, TRTSDT, TRT01A),
by_vars = exprs (STUDYID, USUBJID)
) %>%
admiral:: derive_vars_dt (
new_vars_prefix = "A" ,
dtc = RSDTC
)
cat ("ADRS base created:" , nrow (adrs), "records \n " )
ADRS base created: 5808 records
adrs %>%
dplyr:: select (USUBJID, RSTESTCD, RSSTRESC, ADT, TRTSDT) %>%
head (5 )
# A tibble: 5 × 5
USUBJID RSTESTCD RSSTRESC ADT TRTSDT
<chr> <chr> <chr> <date> <date>
1 01-701-1015 OVRLRESP PD 2014-02-12 2014-01-02
2 01-701-1015 NTRGRESP PD 2014-02-12 2014-01-02
3 01-701-1015 TRGRESP PR 2014-02-12 2014-01-02
4 01-701-1015 OVRLRESP SD 2014-02-12 2014-01-02
5 01-701-1015 NTRGRESP NON-CR/NON-PD 2014-02-12 2014-01-02
Step 9: Derive AVAL from Response
# admiralonco::aval_resp() maps RECIST 1.1 labels to numeric values
adrs <- adrs %>%
dplyr:: mutate (
PARAMCD = RSTESTCD,
PARAM = RSTEST,
AVALC = RSSTRESC,
AVAL = admiralonco:: aval_resp (RSSTRESC)
)
cat ("AVAL derived \n " )
adrs %>%
dplyr:: count (AVALC, AVAL) %>%
dplyr:: arrange (AVAL)
# A tibble: 9 × 3
AVALC AVAL n
<chr> <dbl> <int>
1 CR 1 563
2 PR 2 939
3 SD 3 592
4 NON-CR/NON-PD 4 767
5 PD 5 2588
6 NE 6 242
7 CHECK NA 3
8 EQUIVOCAL NA 81
9 UNEQUIVOCAL NA 33
RECIST 1.1 Response Coding:
Step 10: Filter to Overall Visit Response
# Filter to overall response records and rename parameter
adrs_ovr <- adrs %>%
dplyr:: filter (RSTESTCD == "OVRLRESP" ) %>%
dplyr:: mutate (
PARAMCD = "OVR" ,
PARAM = "Overall Response by Visit"
)
cat ("OVR records:" , nrow (adrs_ovr), " \n " )
adrs_ovr %>%
dplyr:: count (AVALC, AVAL)
# A tibble: 5 × 3
AVALC AVAL n
<chr> <dbl> <int>
1 CHECK NA 3
2 CR 1 172
3 PD 5 1144
4 PR 2 359
5 SD 3 221
Step 11: Derive Best Overall Response
# BOR = best (lowest AVAL) response across all visits per subject
adrs_bor <- adrs_ovr %>%
dplyr:: group_by (STUDYID, USUBJID) %>%
dplyr:: slice_min (AVAL, n = 1 , with_ties = FALSE ) %>%
dplyr:: ungroup () %>%
dplyr:: mutate (
PARAMCD = "BOR" ,
PARAM = "Best Overall Response"
)
cat ("BOR records:" , nrow (adrs_bor), " \n " )
adrs_bor %>%
dplyr:: count (AVALC, AVAL)
# A tibble: 4 × 3
AVALC AVAL n
<chr> <dbl> <int>
1 CR 1 36
2 PD 5 50
3 PR 2 92
4 SD 3 27
Step 12: Derive Responder Flag
# Responder = CR (AVAL=1) or PR (AVAL=2)
adrs_rsp <- adrs_bor %>%
dplyr:: mutate (
PARAMCD = "RSP" ,
PARAM = "Responder (CR or PR)" ,
AVALC = dplyr:: if_else (AVAL <= 2 , "Y" , "N" ),
AVAL = dplyr:: if_else (AVALC == "Y" , 1 , 0 )
)
cat ("RSP records:" , nrow (adrs_rsp), " \n " )
adrs_rsp %>%
dplyr:: count (AVALC, AVAL)
# A tibble: 2 × 3
AVALC AVAL n
<chr> <dbl> <int>
1 N 0 77
2 Y 1 128
Step 13: Combine ADRS Parameters
adrs_final <- dplyr:: bind_rows (adrs_ovr, adrs_bor, adrs_rsp) %>%
dplyr:: arrange (USUBJID, PARAMCD, ADT) %>%
dplyr:: group_by (USUBJID) %>%
dplyr:: mutate (ASEQ = dplyr:: row_number ()) %>%
dplyr:: ungroup ()
cat ("ADRS final:" , nrow (adrs_final), "records \n " )
adrs_final %>%
dplyr:: count (PARAMCD, PARAM)
# A tibble: 3 × 3
PARAMCD PARAM n
<chr> <chr> <int>
1 BOR Best Overall Response 205
2 OVR Overall Response by Visit 1899
3 RSP Responder (CR or PR) 205
Step 14: ADRS Validation
cat (" \n === ADRS Validation === \n\n " )
# One BOR per subject
check1 <- adrs_final %>%
dplyr:: filter (PARAMCD == "BOR" ) %>%
dplyr:: count (USUBJID) %>%
dplyr:: filter (n > 1 )
cat ("Check 1 - Multiple BOR per subject:" , nrow (check1), " \n " )
Check 1 - Multiple BOR per subject: 0
# Valid AVAL range for OVR (1-5)
check2 <- adrs_final %>%
dplyr:: filter (PARAMCD == "OVR" , ! AVAL %in% 1 : 5 )
cat ("Check 2 - Invalid OVR AVAL:" , nrow (check2), " \n " )
Check 2 - Invalid OVR AVAL: 3
# RSP only 0 or 1
check3 <- adrs_final %>%
dplyr:: filter (PARAMCD == "RSP" , ! AVAL %in% c (0 , 1 ))
cat ("Check 3 - Invalid RSP AVAL:" , nrow (check3), " \n " )
Check 3 - Invalid RSP AVAL: 0
cat (" \n ✓ ADRS validation complete \n " )
✓ ADRS validation complete
Summary
cat (" \n === Day 23 Summary === \n\n " )
cat (" Records:" , nrow (adcm), " \n " )
cat (" Concomitant (CONFL=Y):" , sum (adcm$ CONFL == "Y" , na.rm = TRUE ), " \n " )
Concomitant (CONFL=Y): 7337
cat (" Prior (PRVFL=Y):" , sum (adcm$ PRVFL == "Y" , na.rm = TRUE ), " \n\n " )
adrs_final %>%
dplyr:: count (PARAMCD, PARAM)
# A tibble: 3 × 3
PARAMCD PARAM n
<chr> <chr> <int>
1 BOR Best Overall Response 205
2 OVR Overall Response by Visit 1899
3 RSP Responder (CR or PR) 205
Export
attr (adcm$ ASTDT, "label" ) <- "Analysis Start Date"
attr (adcm$ AENDT, "label" ) <- "Analysis End Date"
attr (adcm$ PREFL, "label" ) <- "Pre-treatment Flag"
attr (adcm$ CONFL, "label" ) <- "Concomitant Flag"
attr (adcm$ PRVFL, "label" ) <- "Prior Flag"
attr (adcm$ ASEQ, "label" ) <- "Analysis Sequence Number"
attr (adrs_final$ PARAMCD, "label" ) <- "Parameter Code"
attr (adrs_final$ PARAM, "label" ) <- "Parameter"
attr (adrs_final$ AVAL, "label" ) <- "Analysis Value"
attr (adrs_final$ AVALC, "label" ) <- "Analysis Value (C)"
attr (adrs_final$ ADT, "label" ) <- "Analysis Date"
attr (adrs_final$ ASEQ, "label" ) <- "Analysis Sequence Number"
xportr_write (adcm, path = "adcm.xpt" , domain = "ADCM" )
xportr_write (adrs_final, path = "adrs.xpt" , domain = "ADRS" )
cat (" \n ✓ adcm.xpt exported \n " )
cat ("✓ adrs.xpt exported \n " )
Key Takeaways
pharmaversesdtm exports rs_onco for oncology response - not rs
ADCM period flags describe timing relative to TRTSDT/TRTEDT
admiralonco::aval_resp() converts RECIST labels to numeric AVAL
BOR = slice_min(AVAL) per subject across OVR records
RSP = 1 if BOR is CR or PR (AVAL ≤ 2), else 0
Next Steps
Day 24: ARD-first reporting with cards and cardx Day 25: gtsummary + tfrmt ARD-backed tables Day 26: flextable + officer Word/RTF output