Welcome to ADTTE
ADTTE is the dataset for survival analysis - time-to-event endpoints like overall survival, progression-free survival, and time to first AE.
Today’s focus: Build ADTTE with event/censor indicators and time-to-event calculations.
Setup
library (admiral)
library (pharmaversesdtm)
library (pharmaverseadam)
library (dplyr)
library (lubridate)
library (stringr)
library (xportr)
# Load SDTM and ADaM
dm <- pharmaversesdtm:: dm
ae <- pharmaversesdtm:: ae
adsl <- pharmaverseadam:: adsl
cat ("Loaded DM:" , nrow (dm), "subjects \n " )
cat ("Loaded AE:" , nrow (ae), "AE records \n " )
Loaded AE: 1191 AE records
cat ("Loaded ADSL:" , nrow (adsl), "subjects \n " )
Loaded ADSL: 306 subjects
Step 1: Start with ADSL
# ADTTE starts with one row per subject from ADSL
adtte <- adsl %>%
select (STUDYID, USUBJID, TRTSDT, TRT01A, AGE, SEX)
cat (" \n ADTTE initialized:" , nrow (adtte), "subjects \n " )
ADTTE initialized: 306 subjects
# Check
adtte %>%
select (USUBJID, TRTSDT, TRT01A) %>%
head (5 )
# A tibble: 5 × 3
USUBJID TRTSDT TRT01A
<chr> <date> <chr>
1 01-701-1015 2014-01-02 Placebo
2 01-701-1023 2012-08-05 Placebo
3 01-701-1028 2013-07-19 Xanomeline High Dose
4 01-701-1033 2014-03-18 Xanomeline Low Dose
5 01-701-1034 2014-07-01 Xanomeline High Dose
ADTTE Structure:
One row per subject per parameter
PARAMCD identifies the endpoint (OS, TTAE, etc.)
Each parameter has event/censor logic
Step 2: Prepare AE Data
# Get first AE date per subject
ae_first <- ae %>%
admiral:: derive_vars_dt (
new_vars_prefix = "AE" ,
dtc = AESTDTC
) %>%
filter (! is.na (AEDT)) %>%
group_by (STUDYID, USUBJID) %>%
summarise (
FIRST_AEDT = min (AEDT),
.groups = "drop"
)
cat (" \n First AE dates prepared \n " )
# Check
ae_first %>%
head (5 )
# A tibble: 5 × 3
STUDYID USUBJID FIRST_AEDT
<chr> <chr> <date>
1 CDISCPILOT01 01-701-1015 2014-01-03
2 CDISCPILOT01 01-701-1023 2012-08-07
3 CDISCPILOT01 01-701-1028 2013-07-21
4 CDISCPILOT01 01-701-1034 2014-08-27
5 CDISCPILOT01 01-701-1047 2013-02-12
Step 3: Merge AE Dates
# Merge first AE date to ADTTE
adtte <- adtte %>%
left_join (
ae_first,
by = c ("STUDYID" , "USUBJID" )
)
cat (" \n AE dates merged \n " )
# Check
adtte %>%
select (USUBJID, TRTSDT, FIRST_AEDT) %>%
head (10 )
# A tibble: 10 × 3
USUBJID TRTSDT FIRST_AEDT
<chr> <date> <date>
1 01-701-1015 2014-01-02 2014-01-03
2 01-701-1023 2012-08-05 2012-08-07
3 01-701-1028 2013-07-19 2013-07-21
4 01-701-1033 2014-03-18 NA
5 01-701-1034 2014-07-01 2014-08-27
6 01-701-1047 2013-02-12 2013-02-12
7 01-701-1057 NA NA
8 01-701-1097 2014-01-01 2014-01-03
9 01-701-1111 2012-09-07 2012-07-08
10 01-701-1115 2012-11-30 2012-12-02
Step 4: Create TTAE Parameter
# Time to First AE parameter
adtte_ttae <- adtte %>%
mutate (
PARAMCD = "TTAE" ,
PARAM = "Time to First Adverse Event" ,
# Event if AE occurred after treatment start
CNSR = case_when (
! is.na (FIRST_AEDT) & FIRST_AEDT >= TRTSDT ~ 0 , # Event
TRUE ~ 1 # Censored
),
# Analysis date
ADT = case_when (
CNSR == 0 ~ FIRST_AEDT, # Event date
TRUE ~ TRTSDT + 365 # Censor at 1 year (example)
),
# Time to event in days
AVAL = case_when (
! is.na (ADT) & ! is.na (TRTSDT) ~ as.numeric (ADT - TRTSDT) + 1 ,
TRUE ~ NA_real_
),
AVALU = "DAYS"
)
cat (" \n TTAE parameter created \n " )
# Summary
adtte_ttae %>%
count (CNSR) %>%
mutate (
Label = case_when (
CNSR == 0 ~ "Event" ,
CNSR == 1 ~ "Censored"
)
)
# A tibble: 2 × 3
CNSR n Label
<dbl> <int> <chr>
1 0 201 Event
2 1 105 Censored
CNSR Convention:
CNSR = 0: Event occurred
CNSR = 1: Censored (no event)
Step 5: Add Event Description
# Add event description
adtte_ttae <- adtte_ttae %>%
mutate (
EVNTDESC = case_when (
CNSR == 0 ~ "Adverse Event" ,
TRUE ~ NA_character_
),
CNSDTDSC = case_when (
CNSR == 1 ~ "No Event by Study End" ,
TRUE ~ NA_character_
)
)
cat (" \n Event descriptions added \n " )
# Check
adtte_ttae %>%
select (USUBJID, PARAMCD, AVAL, CNSR, EVNTDESC, CNSDTDSC) %>%
head (10 )
# A tibble: 10 × 6
USUBJID PARAMCD AVAL CNSR EVNTDESC CNSDTDSC
<chr> <chr> <dbl> <dbl> <chr> <chr>
1 01-701-1015 TTAE 2 0 Adverse Event <NA>
2 01-701-1023 TTAE 3 0 Adverse Event <NA>
3 01-701-1028 TTAE 3 0 Adverse Event <NA>
4 01-701-1033 TTAE 366 1 <NA> No Event by Study End
5 01-701-1034 TTAE 58 0 Adverse Event <NA>
6 01-701-1047 TTAE 1 0 Adverse Event <NA>
7 01-701-1057 TTAE NA 1 <NA> No Event by Study End
8 01-701-1097 TTAE 3 0 Adverse Event <NA>
9 01-701-1111 TTAE 366 1 <NA> No Event by Study End
10 01-701-1115 TTAE 3 0 Adverse Event <NA>
Step 6: Create Serious AE Parameter
# Get first serious AE
ae_serious <- ae %>%
admiral:: derive_vars_dt (
new_vars_prefix = "AE" ,
dtc = AESTDTC
) %>%
filter (AESER == "Y" , ! is.na (AEDT)) %>%
group_by (STUDYID, USUBJID) %>%
summarise (
FIRST_SERAEDT = min (AEDT),
.groups = "drop"
)
# Merge to base
adtte_base <- adtte %>%
left_join (ae_serious, by = c ("STUDYID" , "USUBJID" ))
# Create parameter
adtte_ttserae <- adtte_base %>%
mutate (
PARAMCD = "TTSERAE" ,
PARAM = "Time to First Serious Adverse Event" ,
CNSR = case_when (
! is.na (FIRST_SERAEDT) & FIRST_SERAEDT >= TRTSDT ~ 0 ,
TRUE ~ 1
),
ADT = case_when (
CNSR == 0 ~ FIRST_SERAEDT,
TRUE ~ TRTSDT + 365
),
AVAL = case_when (
! is.na (ADT) & ! is.na (TRTSDT) ~ as.numeric (ADT - TRTSDT) + 1 ,
TRUE ~ NA_real_
),
AVALU = "DAYS" ,
EVNTDESC = case_when (
CNSR == 0 ~ "Serious Adverse Event" ,
TRUE ~ NA_character_
),
CNSDTDSC = case_when (
CNSR == 1 ~ "No Serious Event by Study End" ,
TRUE ~ NA_character_
)
)
cat (" \n TTSERAE parameter created \n " )
TTSERAE parameter created
# Summary
adtte_ttserae %>%
count (CNSR) %>%
mutate (
Label = case_when (
CNSR == 0 ~ "Event" ,
CNSR == 1 ~ "Censored"
)
)
# A tibble: 2 × 3
CNSR n Label
<dbl> <int> <chr>
1 0 3 Event
2 1 303 Censored
Step 7: Combine Parameters
# Combine TTAE and TTSERAE
adtte_final <- bind_rows (adtte_ttae, adtte_ttserae)
cat (" \n Parameters combined \n " )
cat ("Total records:" , nrow (adtte_final), " \n " )
# Distribution
adtte_final %>%
count (PARAMCD, PARAM)
# A tibble: 2 × 3
PARAMCD PARAM n
<chr> <chr> <int>
1 TTAE Time to First Adverse Event 306
2 TTSERAE Time to First Serious Adverse Event 306
Step 8: Derive Analysis Sequence
# Create sequence number
adtte_final <- adtte_final %>%
arrange (USUBJID, PARAMCD) %>%
group_by (USUBJID) %>%
mutate (ASEQ = row_number ()) %>%
ungroup ()
cat (" \n Analysis sequence derived \n " )
Analysis sequence derived
Step 9: Derive Study Day
# Calculate study day for analysis date
adtte_final <- adtte_final %>%
mutate (
ADY = case_when (
! is.na (ADT) & ! is.na (TRTSDT) ~ as.numeric (ADT - TRTSDT) + 1 ,
TRUE ~ NA_real_
)
)
cat (" \n Study day derived \n " )
# Check
adtte_final %>%
select (USUBJID, PARAMCD, ADT, TRTSDT, ADY, AVAL) %>%
head (10 )
# A tibble: 10 × 6
USUBJID PARAMCD ADT TRTSDT ADY AVAL
<chr> <chr> <date> <date> <dbl> <dbl>
1 01-701-1015 TTAE 2014-01-03 2014-01-02 2 2
2 01-701-1015 TTSERAE 2015-01-02 2014-01-02 366 366
3 01-701-1023 TTAE 2012-08-07 2012-08-05 3 3
4 01-701-1023 TTSERAE 2013-08-05 2012-08-05 366 366
5 01-701-1028 TTAE 2013-07-21 2013-07-19 3 3
6 01-701-1028 TTSERAE 2014-07-19 2013-07-19 366 366
7 01-701-1033 TTAE 2015-03-18 2014-03-18 366 366
8 01-701-1033 TTSERAE 2015-03-18 2014-03-18 366 366
9 01-701-1034 TTAE 2014-08-27 2014-07-01 58 58
10 01-701-1034 TTSERAE 2015-07-01 2014-07-01 366 366
Validation
cat (" \n === ADTTE Validation === \n\n " )
# Check 1: CNSR values
check1 <- adtte_final %>%
filter (! CNSR %in% c (0 , 1 ))
cat ("Check 1 - Invalid CNSR values:" , nrow (check1), " \n " )
Check 1 - Invalid CNSR values: 0
# Check 2: AVAL consistency
check2 <- adtte_final %>%
filter (! is.na (AVAL) & ! is.na (ADY) & abs (AVAL - ADY) > 0.1 )
cat ("Check 2 - AVAL != ADY:" , nrow (check2), " \n " )
# Check 3: Event has EVNTDESC
check3 <- adtte_final %>%
filter (CNSR == 0 , is.na (EVNTDESC))
cat ("Check 3 - Event missing EVNTDESC:" , nrow (check3), " \n " )
Check 3 - Event missing EVNTDESC: 0
# Check 4: Censored has CNSDTDSC
check4 <- adtte_final %>%
filter (CNSR == 1 , is.na (CNSDTDSC))
cat ("Check 4 - Censored missing CNSDTDSC:" , nrow (check4), " \n " )
Check 4 - Censored missing CNSDTDSC: 0
cat (" \n ✓ Validation complete \n " )
Summary
cat (" \n === ADTTE Summary === \n\n " )
cat ("Total records:" , nrow (adtte_final), " \n " )
cat ("Parameters:" , n_distinct (adtte_final$ PARAMCD), " \n " )
cat ("Subjects:" , n_distinct (adtte_final$ USUBJID), " \n\n " )
# By parameter
adtte_final %>%
group_by (PARAMCD, PARAM) %>%
summarise (
N_Subjects = n (),
N_Events = sum (CNSR == 0 ),
N_Censored = sum (CNSR == 1 ),
Median_AVAL = median (AVAL, na.rm = TRUE ),
.groups = "drop"
)
# A tibble: 2 × 6
PARAMCD PARAM N_Subjects N_Events N_Censored Median_AVAL
<chr> <chr> <int> <int> <int> <dbl>
1 TTAE Time to First Adverse Event 306 201 105 25
2 TTSERAE Time to First Serious Adve… 306 3 303 366
# AVAL distribution
adtte_final %>%
group_by (PARAMCD) %>%
summarise (
N = sum (! is.na (AVAL)),
Mean = round (mean (AVAL, na.rm = TRUE ), 1 ),
Median = round (median (AVAL, na.rm = TRUE ), 1 ),
Min = min (AVAL, na.rm = TRUE ),
Max = max (AVAL, na.rm = TRUE ),
.groups = "drop"
)
# A tibble: 2 × 6
PARAMCD N Mean Median Min Max
<chr> <int> <dbl> <dbl> <dbl> <dbl>
1 TTAE 254 99.1 25 1 366
2 TTSERAE 254 362 366 5 366
Export
# Add labels
attr (adtte_final$ PARAMCD, "label" ) <- "Parameter Code"
attr (adtte_final$ PARAM, "label" ) <- "Parameter"
attr (adtte_final$ AVAL, "label" ) <- "Analysis Value"
attr (adtte_final$ AVALU, "label" ) <- "Analysis Value Unit"
attr (adtte_final$ ADT, "label" ) <- "Analysis Date"
attr (adtte_final$ ADY, "label" ) <- "Analysis Relative Day"
attr (adtte_final$ CNSR, "label" ) <- "Censor"
attr (adtte_final$ EVNTDESC, "label" ) <- "Event Description"
attr (adtte_final$ CNSDTDSC, "label" ) <- "Censored Description"
attr (adtte_final$ ASEQ, "label" ) <- "Analysis Sequence Number"
# Export
xportr_write (adtte_final, path = "adtte.xpt" , domain = "ADTTE" )
cat (" \n ✓ ADTTE exported to: adtte.xpt \n " )
✓ ADTTE exported to: adtte.xpt
Key Takeaways
ADTTE has one row per subject per parameter
CNSR = 0 (event), CNSR = 1 (censored)
AVAL = time to event in days (ADT - TRTSDT + 1)
Events need EVNTDESC, censored need CNSDTDSC
Multiple parameters: TTAE, TTSERAE, etc.
Simple dplyr approach without complex admiral TTE functions
Next Steps
Day 22: Tables and Listings with gt
Day 23: Figures with ggplot2
Day 24: Complete TLF Package
Resources
End of Day 21
Week 3 Complete! You’ve built: - ADSL (Days 16-17) - ADAE (Day 18) - ADLB (Day 19) - ADVS (Day 20) - ADTTE (Day 21)
Ready for TLF production in Week 4!