Creating ADTTE

Introduction

This article describes creating an ADTTE (time-to-event) ADaM with common oncology endpoint parameters.

The main part in programming a time-to-event dataset is the definition of the events and censoring times. {admiral}/{admiralonco} supports single events like death (Overall Survival) or composite events like disease progression or death (Progression Free Survival). More than one source dataset can be used for the definition of the event and censoring times.

The majority of the functions used here exist from {admiral}, except for the tte_sources helper object, provided as an example from {admiralonco}. In practice, each company would create their own version of this, as likely the exact specifications such as filtering condition or description metadata will vary.

Note: All examples assume CDISC SDTM and/or ADaM format as input unless otherwise specified.

Required Packages

The examples of this vignette require the following packages.

library(admiral)
library(admiralonco)
library(pharmaverseadam)
library(dplyr)
library(lubridate)

Programming Workflow

Read in Data

To start, all datasets needed for the creation of the time-to-event dataset should be read into the environment. This will be a company specific process.

For example purpose, the ADaM datasets—which are included in {pharmaverseadam}—are used. An alternative might be to use ADEVENT as input1.

adsl <- pharmaverseadam::adsl
adrs <- pharmaverseadam::adrs_onco

Derive Parameters (CNSR, ADT, STARTDT)

To derive the parameter dependent variables like CNSR, ADT, STARTDT, EVNTDESC, SRCDOM, PARAMCD, … the admiral::derive_param_tte() function can be used. It adds one parameter to the input dataset with one observation per subject. Usually it is called several times.

For each subject it is determined if an event occurred. In the affirmative the analysis date ADT is set to the earliest event date. If no event occurred, the analysis date is set to the latest censoring date.

The events and censorings are defined by the admiral::event_source() and the admiral::censor_source() class respectively. It defines

The date can be provided as a date (*DT variable) or a datetime (*DTM variable).

CDISC strongly recommends CNSR = 0 for events and positive integers for censorings. {admiral}/{admiralonco} enforce this recommendation. Therefore the censor parameter is available for admiral::censor_source() only. It is defaulted to 1.

The dataset_name parameter expects a character value which is used as an identifier. The actual data which is used for the derivation of the parameter is provided via the source_datasets parameter of admiral::derive_param_tte(). It expects a named list of datasets. The names correspond to the identifiers specified for the dataset_name parameter. This allows to define events and censoring independent of the data.

Pre-Defined Time-to-Event Source Objects

The table below shows all pre-defined tte_source objects which should cover the most common oncology use cases.

object dataset_name filter date censor set_values_to
lastalive_censor adsl NULL LSTALVDT 1 EVNTDESC: “Alive”
CNSDTDSC: “Alive During Study”
SRCDOM: “ADSL”
SRCVAR: “LSTALVDT”
trts_censor adsl NULL TRTSDT 1 EVNTDESC: “Treatment Start”
CNSDTDSC: “Treatment Start”
SRCDOM: “ADSL”
SRCVAR: “TRTSDT”
pd_event adrs PARAMCD == “PD” & AVALC == “Y” & ANL01FL == “Y” ADT 0 EVNTDESC: “Disease Progression”
SRCDOM: “ADRS”
SRCVAR: “ADT”
SRCSEQ: ASEQ
death_event adrs PARAMCD == “DEATH” & AVALC == “Y” & ANL01FL == “Y” ADT 0 EVNTDESC: “Death”
SRCDOM: “ADRS”
SRCVAR: “ADT”
SRCSEQ: ASEQ
lasta_censor adrs PARAMCD == “LSTA” & ANL01FL == “Y” ADT 1 EVNTDESC: “Last Tumor Assessment”
CNSDTDSC: “Last Tumor Assessment”
SRCDOM: “ADRS”
SRCVAR: “ADT”
SRCSEQ: ASEQ
rand_censor adsl NULL RANDDT 1 EVNTDESC: “Randomization”
CNSDTDSC: “Randomization”
SRCDOM: “ADSL”
SRCVAR: “RANDDT”

As mentioned in the introduction, each company would create their own version of this with the required filtering conditions and metadata as per your company approach. An example of a possible different approach could be as follows, where death is sourced from ADSL, instead of ADRS, and the given EVNTDESC is different.

adsl_death_event <- event_source(
  dataset_name = "adsl",
  date = DTHDT,
  set_values_to = exprs(
    EVNTDESC = "STUDY DEATH",
    SRCDOM = "ADSL",
    SRCVAR = "DTHDT"
  )
)

An optional step at this stage would be required to enable derivation of duration of response: If using ADRS / ADEVENT parameters as input for any response dates (instead of a variable in ADSL) then you would need to use admiral::derive_vars_merged() to add the response date as a temporary variable (e.g. TEMP_RESPDT) to be able to feed into admiral::derive_param_tte() as the start date. You would also need to use this to filter the source ADSL dataset so as to only derive the records for responders. This could also be repeated as needed for IRF/BICR and confirmed responses.

Here is an example of the code needed.

adsl <- adsl %>%
  derive_vars_merged(
    dataset_add = adrs,
    filter_add = PARAMCD == "RSP" & AVALC == "Y" & ANL01FL == "Y",
    by_vars = get_admiral_option("subject_keys"),
    new_vars = exprs(TEMP_RESPDT = ADT)
  )
USUBJID RANDDT TEMP_RESPDT NACTDT EOSDT
01-701-1015 2014-01-02 2014-03-06 2014-06-19 2014-07-02
01-701-1028 2013-07-19 NA 2014-01-14 2014-01-14
01-701-1034 2014-07-01 NA 2014-12-30 2014-12-30
01-701-1097 2014-01-01 NA 2013-12-31 2014-07-09
01-701-1115 2012-11-30 2013-01-11 NA 2013-01-23
01-701-1118 2014-03-12 2014-04-23 NA 2014-09-09
01-701-1130 2014-02-15 NA NA 2014-08-16
01-701-1133 2012-10-28 2012-11-18 NA 2013-04-29
01-701-1146 2013-05-20 NA NA 2013-06-30
01-701-1211 2012-11-15 NA NA 2013-01-14

The pre-defined objects can be passed directly to admiral::derive_param_tte() to create a new time-to-event parameter. Below shows example calls for Overall Survival (OS), Progression Free Survival (PFS), and duration of response (as above, this is only derived for responder patients so we have to filter source ADSL dataset). Note that the reason for including a randomization date censor is to catch those patients that never have a tumor assessment.

adtte <- derive_param_tte(
  dataset_adsl = adsl,
  start_date = RANDDT,
  event_conditions = list(death_event),
  censor_conditions = list(lastalive_censor, rand_censor),
  source_datasets = list(adsl = adsl, adrs = adrs),
  set_values_to = exprs(PARAMCD = "OS", PARAM = "Overall Survival")
) %>%
  derive_param_tte(
    dataset_adsl = adsl,
    start_date = RANDDT,
    event_conditions = list(pd_event, death_event),
    censor_conditions = list(lasta_censor, rand_censor),
    source_datasets = list(adsl = adsl, adrs = adrs),
    set_values_to = exprs(PARAMCD = "PFS", PARAM = "Progression Free Survival")
  ) %>%
  derive_param_tte(
    dataset_adsl = filter(adsl, !is.na(TEMP_RESPDT)),
    start_date = TEMP_RESPDT,
    event_conditions = list(pd_event, death_event),
    censor_conditions = list(lasta_censor),
    source_datasets = list(adsl = adsl, adrs = adrs),
    set_values_to = exprs(PARAMCD = "RSD", PARAM = "Duration of Response")
  )
USUBJID PARAMCD PARAM STARTDT ADT CNSR
01-701-1015 OS Overall Survival 2014-01-02 2014-07-02 1
01-701-1028 OS Overall Survival 2013-07-19 2014-01-14 1
01-701-1034 OS Overall Survival 2014-07-01 2014-12-30 1
01-701-1097 OS Overall Survival 2014-01-01 2014-07-09 1
01-701-1115 OS Overall Survival 2012-11-30 2013-01-23 1
01-701-1118 OS Overall Survival 2014-03-12 2014-09-09 1
01-701-1130 OS Overall Survival 2014-02-15 2014-08-16 1
01-701-1133 OS Overall Survival 2012-10-28 2013-04-29 1
01-701-1146 OS Overall Survival 2013-05-20 2013-06-30 1
01-701-1211 OS Overall Survival 2012-11-15 2013-01-14 0

Creating Your Own Time-to-Event Source Objects

We advise you to consult the {admiral} Creating a BDS Time-to-Event ADaM vignette and Time-to-Event Analyses for further guidance on the different options available and more examples.

One additional common oncology use case described here involves PFS when censoring at new anti-cancer therapy. This can be controlled either by using ANLzzFL as explained in the ADRS vignette, so that records after new anti-cancer therapy never contribute to the PD and DEATH parameters, or by using the end_dates argument of derive_param_tte() on the ADTTE side.

For end_dates, you can specify whichever date your analysis requires, e.g., the start date of new anti-cancer therapy or the date of surgery. The argument expects a list of censor_source() objects. The earliest date is used as the end of the observation period, and events or censorings occurring after this date are not considered. If the end_dates argument is used, “Overall Response by Investigator” parameter (OVR) should be used instead of the “Last Disease Assessment by Investigator” parameter (LSTA) to ensure all valid assessments are considered.

In the example below, the end_dates argument is used to restrict the events and censorings to those occurring on or before the start of new anti-cancer therapy. NACTDT would be pre-derived as the first date of new anti-cancer therapy. See {admiralonco} Creating and Using New Anti-Cancer Start Date for deriving NACTDT. In addition, EOSDT is included in end_dates to populate EVNTDESC when no new anti-cancer therapy was started. For censored subjects, EVNTDESC will be set to "Start of New Anti-Cancer Therapy" if the subject started new anti-cancer therapy and set to "End of Study" otherwise.

eosdt <- censor_source(
  dataset_name = "adsl",
  date = EOSDT,
  set_values_to = exprs(
    EVNTDESC = "End of Study"
  )
)

nactdt <- censor_source(
  dataset_name = "adsl",
  date = NACTDT,
  set_values_to = exprs(
    EVNTDESC = "Start of New Anti-Cancer Therapy"
  )
)

valid_assessment <- censor_source(
  dataset_name = "adrs",
  filter = PARAMCD == "OVR" & AVALC != "NE",
  date = ADT,
  set_values_to = exprs(
    CNSDTDSC = "Last Valid Tumor Assessment",
    SRCDOM = "ADRS",
    SRCVAR = "ADT"
  )
)

adtte <- adtte %>% derive_param_tte(
  dataset_adsl = adsl,
  start_date = RANDDT,
  end_dates = list(nactdt, eosdt),
  event_conditions = list(pd_event, death_event),
  censor_conditions = list(valid_assessment, rand_censor),
  source_datasets = list(adsl = adsl, adrs = adrs),
  set_values_to = exprs(
    PARAMCD = "PFSNACT",
    PARAM = "Progression Free Survival prior to NACT"
  )
)
USUBJID PARAMCD PARAM STARTDT ADT CNSR EVNTDESC CNSDTDSC
01-701-1015 PFSNACT Progression Free Survival prior to NACT 2014-01-02 2014-03-06 1 Start of New Anti-Cancer Therapy Last Valid Tumor Assessment
01-701-1028 PFSNACT Progression Free Survival prior to NACT 2013-07-19 2013-08-30 0 Disease Progression NA
01-701-1034 PFSNACT Progression Free Survival prior to NACT 2014-07-01 2014-08-12 1 Start of New Anti-Cancer Therapy Last Valid Tumor Assessment
01-701-1097 PFSNACT Progression Free Survival prior to NACT 2014-01-01 2014-01-01 1 Randomization Randomization
01-701-1115 PFSNACT Progression Free Survival prior to NACT 2012-11-30 2013-01-11 1 End of Study Last Valid Tumor Assessment
01-701-1118 PFSNACT Progression Free Survival prior to NACT 2014-03-12 2014-06-04 1 End of Study Last Valid Tumor Assessment
01-701-1130 PFSNACT Progression Free Survival prior to NACT 2014-02-15 2014-04-19 0 Disease Progression NA
01-701-1133 PFSNACT Progression Free Survival prior to NACT 2012-10-28 2012-12-30 0 Disease Progression NA
01-701-1146 PFSNACT Progression Free Survival prior to NACT 2013-05-20 2013-05-20 1 Randomization Randomization
01-701-1211 PFSNACT Progression Free Survival prior to NACT 2012-11-15 2013-01-14 0 Death NA

Derive Analysis Value (AVAL)

The analysis value (AVAL) can be derived by calling admiral::derive_vars_duration().

This example derives the time to event in days.

adtte <- adtte %>%
  derive_vars_duration(
    new_var = AVAL,
    start_date = STARTDT,
    end_date = ADT
  )
STUDYID USUBJID ADT EVNTDESC SRCDOM SRCVAR SRCSEQ CNSR CNSDTDSC STARTDT PARAMCD PARAM AVAL
CDISCPILOT01 01-701-1015 2014-07-02 Alive ADSL LSTALVDT NA 1 Alive During Study 2014-01-02 OS Overall Survival 182
CDISCPILOT01 01-701-1028 2014-01-14 Alive ADSL LSTALVDT NA 1 Alive During Study 2013-07-19 OS Overall Survival 180
CDISCPILOT01 01-701-1034 2014-12-30 Alive ADSL LSTALVDT NA 1 Alive During Study 2014-07-01 OS Overall Survival 183
CDISCPILOT01 01-701-1097 2014-07-09 Alive ADSL LSTALVDT NA 1 Alive During Study 2014-01-01 OS Overall Survival 190
CDISCPILOT01 01-701-1115 2013-01-23 Alive ADSL LSTALVDT NA 1 Alive During Study 2012-11-30 OS Overall Survival 55
CDISCPILOT01 01-701-1118 2014-09-09 Alive ADSL LSTALVDT NA 1 Alive During Study 2014-03-12 OS Overall Survival 182
CDISCPILOT01 01-701-1130 2014-08-16 Alive ADSL LSTALVDT NA 1 Alive During Study 2014-02-15 OS Overall Survival 183
CDISCPILOT01 01-701-1133 2013-04-29 Alive ADSL LSTALVDT NA 1 Alive During Study 2012-10-28 OS Overall Survival 184
CDISCPILOT01 01-701-1146 2013-06-30 Alive ADSL LSTALVDT NA 1 Alive During Study 2013-05-20 OS Overall Survival 42
CDISCPILOT01 01-701-1211 2013-01-14 Death ADRS ADT 8 0 NA 2012-11-15 OS Overall Survival 61

Other time units, such as months that we commonly see in oncology analyses, can be requested by specifying the out_unit parameter. See the example below. Note that because of the underlying lubridate::time_length() function that is used here this may perform slightly differently to your expectations, e.g. both time_length(ymd("2021-01-01") %--% ymd("2021-02-01"), "month") and time_length(ymd("2021-02-01") %--% ymd("2021-03-01"), "month") results in exactly 1 month, which is a logical approach but it gives a different result to the convention of assuming every month has exactly equal days and just using /30.4375 here or some other such convention. The difference would only be noticed for small durations, but if the user prefers an alternative approach they could calculate in the default days and then add extra processing to convert to months with their company-specific convention.

adtte_months <- adtte %>%
  derive_vars_duration(
    new_var = AVAL,
    start_date = STARTDT,
    end_date = ADT,
    out_unit = "months"
  )

Derive Analysis Sequence Number (ASEQ)

The {admiral} function admiral::derive_var_obs_number() can be used to derive ASEQ:

adtte <- adtte %>%
  derive_var_obs_number(
    by_vars = get_admiral_option("subject_keys"),
    order = exprs(PARAMCD),
    check_type = "error"
  )
STUDYID USUBJID ADT EVNTDESC SRCDOM SRCVAR SRCSEQ CNSR CNSDTDSC STARTDT PARAMCD PARAM AVAL ASEQ
CDISCPILOT01 01-701-1015 2014-07-02 Alive ADSL LSTALVDT NA 1 Alive During Study 2014-01-02 OS Overall Survival 182 1
CDISCPILOT01 01-701-1015 2014-03-06 Last Tumor Assessment ADRS ADT 9 1 Last Tumor Assessment 2014-01-02 PFS Progression Free Survival 64 2
CDISCPILOT01 01-701-1015 2014-03-06 Start of New Anti-Cancer Therapy ADRS ADT NA 1 Last Valid Tumor Assessment 2014-01-02 PFSNACT Progression Free Survival prior to NACT 64 3
CDISCPILOT01 01-701-1015 2014-03-06 Last Tumor Assessment ADRS ADT 9 1 Last Tumor Assessment 2014-03-06 RSD Duration of Response 1 4
CDISCPILOT01 01-701-1028 2014-01-14 Alive ADSL LSTALVDT NA 1 Alive During Study 2013-07-19 OS Overall Survival 180 1
CDISCPILOT01 01-701-1028 2013-08-30 Disease Progression ADRS ADT 14 0 NA 2013-07-19 PFS Progression Free Survival 43 2
CDISCPILOT01 01-701-1028 2013-08-30 Disease Progression ADRS ADT 14 0 NA 2013-07-19 PFSNACT Progression Free Survival prior to NACT 43 3
CDISCPILOT01 01-701-1034 2014-12-30 Alive ADSL LSTALVDT NA 1 Alive During Study 2014-07-01 OS Overall Survival 183 1
CDISCPILOT01 01-701-1034 2014-08-12 Last Tumor Assessment ADRS ADT 9 1 Last Tumor Assessment 2014-07-01 PFS Progression Free Survival 43 2
CDISCPILOT01 01-701-1034 2014-08-12 Start of New Anti-Cancer Therapy ADRS ADT NA 1 Last Valid Tumor Assessment 2014-07-01 PFSNACT Progression Free Survival prior to NACT 43 3

Add ADSL Variables

Variables from ADSL which are required for time-to-event analyses, e.g., treatment variables or covariates can be added using admiral::derive_vars_merged().

adtte <- adtte %>%
  derive_vars_merged(
    dataset_add = adsl,
    new_vars = exprs(ARMCD, ARM, ACTARMCD, ACTARM, AGE, SEX),
    by_vars = get_admiral_option("subject_keys")
  )
USUBJID PARAMCD CNSR AVAL ARMCD AGE SEX
01-701-1015 OS 1 182 Pbo 63 F
01-701-1015 PFS 1 64 Pbo 63 F
01-701-1015 PFSNACT 1 64 Pbo 63 F
01-701-1015 RSD 1 1 Pbo 63 F
01-701-1028 OS 1 180 Xan_Hi 71 M
01-701-1028 PFS 0 43 Xan_Hi 71 M
01-701-1028 PFSNACT 0 43 Xan_Hi 71 M
01-701-1034 OS 1 183 Xan_Hi 77 F
01-701-1034 PFS 1 43 Xan_Hi 77 F
01-701-1034 PFSNACT 1 43 Xan_Hi 77 F

Example Script

ADaM Sample Code
ADTTE admiral::use_ad_template("ADTTE", package = "admiralonco")

  1. The subjects are restricted to "01-701-1015", "01-701-1028", "01-701-1034", "01-701-1097", "01-701-1115", "01-701-1118", "01-701-1130", "01-701-1133", "01-701-1146", "01-701-1211".↩︎