Creating ADRS with IMWG Criteria

Introduction

This article describes creating an ADRS ADaM dataset in multiple myeloma (MM) studies based on International Myeloma Working Group (IMWG) criteria. It shows a similar way of deriving the endpoints presented in Creating ADRS (Including Non-standard Endpoints). Most of the endpoints are derived by calling admiral::derive_extreme_event().

The hallmark of MM is the production of monoclonal immunoglobulins and/or light chains by the clonal plasma cells. Numerous parameters need to be considered while assessing response:

  1. Monoclonal protein level (confirmatory assessment required):
    • SPEP - serum protein electrophoresis,
    • SIFE - serum immunofixation electrophoresis,
    • UPEP - urine protein electrophoresis,
    • UIFE - urine immunofixation electrophoresis,
    • SFLC - serum free light chains.
  2. Marrow plasma cells: bone marrow aspirate/biopsy.
  3. Plasmacytoma: imaging (PET/CT, CT or MRI).
  4. Bone lesions: skeletal survey.

It is worth to mention:
Whenever more than one parameter is used to assess response, the overall assigned level of response is determined by the lowest level of response.
If a critical data point to establish a level of response is missing, the evaluation is downgraded to the next lower level.

For more information user may visit International Myeloma Working Group consensus criteria for response and minimal residual disease assessment in multiple myeloma.

Examples are currently presented and tested using ADSL (ADaM), RS and SUPPRS (SDTM) inputs. However, other domains could be used.

In IMWG criteria each status should be confirmed by second tests giving consistent results. Confirmation should be obtained for biochemical markers but is not necessary for bone marrow or imaging studies.

Two scenarios of response data collection in a clinical trial are possible:

In our example we will consider the second scenario.

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

Programming Workflow

Read in Data

To start, all data frames needed for the creation of ADRS should be read into the environment. This will be a company specific process. Some of the data frames needed may be ADSL, RS and TU. For this vignette we assume that RS provides the response values sCR, CR, VGPR, PR,MR, SD, PD, and NE.

Label for non-evaluable response can vary between studies, i.e. it can be NE, NA, UTD, etc. In further considerations for non-evaluable responses, we use label NE. User will overwrite this value if necessary.

For example purpose, the SDTM and ADaM datasets (based on CDISC Pilot test data)—which are included in {pharmaversesdtm} and {pharmaverseadam}—are used.

library(admiral)
library(admiralonco)
library(dplyr)
library(pharmaverseadam)
library(pharmaversesdtm)
library(lubridate)
library(stringr)
library(metatools)
library(cli)
data("adsl")
# IMWG sdtm data
data("rs_onco_imwg")
data("supprs_onco_imwg")

rs <- rs_onco_imwg
supprs <- supprs_onco_imwg

rs <- combine_supp(rs, supprs)

rs <- convert_blanks_to_na(rs)
USUBJID RSTESTCD RSSTRESC VISIT RSDTC RSDY PDOFL DTHPDFL NACTDT PDIFL
01-701-1015 OVRLRESP PD WEEK 6 2014-02-12 42 Y Y NA NA
01-701-1028 OVRLRESP sCR WEEK 6 2013-08 NA NA NA NA NA
01-701-1028 OVRLRESP sCR WEEK 12 2013-10-09 84 NA NA NA NA
01-701-1028 OVRLRESP CR WEEK 18 2013-11-20 126 NA NA NA NA
01-701-1034 OVRLRESP CR WEEK 6 2014-08-11 42 NA NA NA NA
01-701-1034 OVRLRESP CR WEEK 12 2014-09-25 84 NA NA NA NA
01-701-1034 OVRLRESP sCR WEEK 18 2014-11-04 126 NA NA NA NA
01-701-1097 OVRLRESP PD WEEK 6 2014-02-11 42 Y NA 2014-02-10 NA
01-701-1115 OVRLRESP PD WEEK 6 2013-01-10 42 NA NA NA Y
01-701-1118 OVRLRESP sCR WEEK 6 2014-04-23 42 NA NA NA NA

At this step, it may be useful to join ADSL to your RS domain. Only the ADSL variables used for derivations are selected at this step. The rest of the relevant ADSL variables would be added later.

adsl_vars <- exprs(RANDDT, TRTSDT)
adrs <- derive_vars_merged(
  rs,
  dataset_add = adsl,
  new_vars = adsl_vars,
  by_vars = get_admiral_option("subject_keys")
)
USUBJID RSTESTCD VISIT RSDTC RANDDT TRTSDT
01-701-1015 OVRLRESP WEEK 6 2014-02-12 2014-01-02 2014-01-02
01-701-1028 OVRLRESP WEEK 6 2013-08 2013-07-19 2013-07-19
01-701-1028 OVRLRESP WEEK 12 2013-10-09 2013-07-19 2013-07-19
01-701-1028 OVRLRESP WEEK 18 2013-11-20 2013-07-19 2013-07-19
01-701-1034 OVRLRESP WEEK 6 2014-08-11 2014-07-01 2014-07-01
01-701-1034 OVRLRESP WEEK 12 2014-09-25 2014-07-01 2014-07-01
01-701-1034 OVRLRESP WEEK 18 2014-11-04 2014-07-01 2014-07-01
01-701-1097 OVRLRESP WEEK 6 2014-02-11 2014-01-01 2014-01-01
01-701-1115 OVRLRESP WEEK 6 2013-01-10 2012-11-30 2012-11-30
01-701-1118 OVRLRESP WEEK 6 2014-04-23 2014-03-12 2014-03-12

Pre-processing of Input Records

The first step involves company-specific pre-processing of records for the required input to the downstream parameter functions. Note that this could be needed multiple times (e.g. once for investigator and once for Independent Review Facility (IRF)/Blinded Independent Central Review (BICR) records). It could even involve merging input data from other sources besides RS, such as ADTR/TR/TU.

This step would include any required selection/derivation of ADT or applying any necessary partial date imputations and updating AVAL (e.g. this should be ordered from worst to best response).

The below shows an example of a possible company-specific implementation of this step.

Select Overall Response Records and Set Parameter Details

In this case we use the overall response records from RS from the investigator as our starting point. It is worth emphasizing again that responses are not confirmed. Confirmed values will be derived after further pre-processing.

The parameter details such as PARAMCD, PARAM etc will always be company-specific, but an example is shown below so that you can trace through how these records feed into the other parameter derivations.

adrs <- adrs %>%
  filter(RSEVAL == "INVESTIGATOR" & RSTESTCD == "OVRLRESP") %>%
  mutate(
    PARAMCD = "OVR",
    PARAM = "Overall Response by Investigator",
    PARCAT1 = "Investigator",
    PARCAT2 = "IMWG"
  )
USUBJID VISIT RSTESTCD RSEVAL PARAMCD PARAM PARCAT1 PARCAT2
01-701-1015 WEEK 6 OVRLRESP INVESTIGATOR OVR Overall Response by Investigator Investigator IMWG
01-701-1028 WEEK 6 OVRLRESP INVESTIGATOR OVR Overall Response by Investigator Investigator IMWG
01-701-1028 WEEK 12 OVRLRESP INVESTIGATOR OVR Overall Response by Investigator Investigator IMWG
01-701-1028 WEEK 18 OVRLRESP INVESTIGATOR OVR Overall Response by Investigator Investigator IMWG
01-701-1034 WEEK 6 OVRLRESP INVESTIGATOR OVR Overall Response by Investigator Investigator IMWG
01-701-1034 WEEK 12 OVRLRESP INVESTIGATOR OVR Overall Response by Investigator Investigator IMWG
01-701-1034 WEEK 18 OVRLRESP INVESTIGATOR OVR Overall Response by Investigator Investigator IMWG
01-701-1097 WEEK 6 OVRLRESP INVESTIGATOR OVR Overall Response by Investigator Investigator IMWG
01-701-1115 WEEK 6 OVRLRESP INVESTIGATOR OVR Overall Response by Investigator Investigator IMWG
01-701-1118 WEEK 6 OVRLRESP INVESTIGATOR OVR Overall Response by Investigator Investigator IMWG

Partial Date Imputation and Deriving ADT, ADTF, AVISIT etc

If your data collection allows for partial dates, you could apply a company-specific imputation rule at this stage when deriving ADT. For this example, here we impute missing day to last possible date.

adrs <- adrs %>%
  derive_vars_dt(
    dtc = RSDTC,
    new_vars_prefix = "A",
    highest_imputation = "D",
    date_imputation = "last"
  ) %>%
  derive_vars_dy(
    reference_date = TRTSDT,
    source_vars = exprs(ADT)
  ) %>%
  derive_vars_dtm(
    dtc = RSDTC,
    new_vars_prefix = "A",
    highest_imputation = "D",
    date_imputation = "last",
    flag_imputation = "time"
  ) %>%
  mutate(AVISIT = VISIT)
USUBJID PARAMCD VISIT AVISIT RSDTC ADT ADTF ADY
01-701-1015 OVR WEEK 6 WEEK 6 2014-02-12 2014-02-12 NA 42
01-701-1028 OVR WEEK 6 WEEK 6 2013-08 2013-08-31 D 44
01-701-1028 OVR WEEK 12 WEEK 12 2013-10-09 2013-10-09 NA 83
01-701-1028 OVR WEEK 18 WEEK 18 2013-11-20 2013-11-20 NA 125
01-701-1034 OVR WEEK 6 WEEK 6 2014-08-11 2014-08-11 NA 42
01-701-1034 OVR WEEK 12 WEEK 12 2014-09-25 2014-09-25 NA 87
01-701-1034 OVR WEEK 18 WEEK 18 2014-11-04 2014-11-04 NA 127
01-701-1097 OVR WEEK 6 WEEK 6 2014-02-11 2014-02-11 NA 42
01-701-1115 OVR WEEK 6 WEEK 6 2013-01-10 2013-01-10 NA 42
01-701-1118 OVR WEEK 6 WEEK 6 2014-04-23 2014-04-23 NA 43

Derive AVALC and AVAL

Here we populate AVALC and create the numeric version as AVAL (ordered from worst to best response, followed by NE). The AVAL values are not considered in the parameter derivations below, and so changing AVAL here would not change the result of those derivations. However, please note that the ordering of AVAL will be used to determine ANL01FL in the subsequent step, ensure that the appropriate mode is being set in the admiral::derive_var_extreme_flag().

IMWG ordering will be used or if you’d like to provide your own company-specific ordering here you could do this as follows:

aval_resp_imwg <- function(arg) {
  case_match(
    arg,
    "NE" ~ 8,
    "sCR" ~ 7,
    "CR" ~ 6,
    "VGPR" ~ 5,
    "PR" ~ 4,
    "MR" ~ 3,
    "SD" ~ 2,
    "PD" ~ 1,
    NA ~ NA_real_
  )
}

adrs <- adrs %>%
  mutate(
    AVALC = RSSTRESC,
    AVAL = aval_resp_imwg(AVALC)
  )
USUBJID PARAMCD AVISIT ADT AVAL AVALC
01-701-1015 OVR WEEK 6 2014-02-12 1 PD
01-701-1028 OVR WEEK 6 2013-08-31 7 sCR
01-701-1028 OVR WEEK 12 2013-10-09 7 sCR
01-701-1028 OVR WEEK 18 2013-11-20 6 CR
01-701-1034 OVR WEEK 6 2014-08-11 6 CR
01-701-1034 OVR WEEK 12 2014-09-25 6 CR
01-701-1034 OVR WEEK 18 2014-11-04 7 sCR
01-701-1097 OVR WEEK 6 2014-02-11 1 PD
01-701-1115 OVR WEEK 6 2013-01-10 1 PD
01-701-1118 OVR WEEK 6 2014-04-23 7 sCR

Derive Confirmed Response Parameter

Confirmation of response require two consecutive readings of applicable disease parameters (biochemical analyses). No minimal time interval, but a different sample is required for the confirmation assessment. Bone marrow assessments and imaging do not need to be confirmed.

If RS contains unconfirmed response and confirmation is performed at next scheduled visit we can derive Confirmed Response based on response at subsequent visit.

While deriving Confirmed Response, the following should be taken into consideration:

Note: Patients will continue in the last Confirmed Response category until there is confirmation of progression or improvement to a higher response status.

Let’s define a function aval_resp_conf that maps numerical values to the responses, so that PD is prioritized and concept of “higher response status” is understandable:

aval_resp_conf <- function(arg) {
  case_match(
    arg,
    "PD" ~ 8,
    "sCR" ~ 7,
    "CR" ~ 6,
    "VGPR" ~ 5,
    "PR" ~ 4,
    "MR" ~ 3,
    "SD" ~ 2,
    "NE" ~ 1,
    NA ~ 0
  )
}

Below table provides a summary of the Confirmed Response status calculation at each time point. The maximum refers to numeric values previously defined in aval_resp_conf function.

Response at 1st time point Response at 2nd time point Confirmed Response at 1st time point
sCR sCR sCR
CR sCR/CR max(CR, last Confirmed Response)
VGPR sCR/CR/VGPR max(VGPR, last Confirmed Response)
PR sCR/CR/VGPR/PR max(PR, last Confirmed Response)
MR sCR/CR/VGPR/PR/MR max(MR, last Confirmed Response)
sCR CR max(CR, last Confirmed Response)
sCR/CR VGPR max(VGPR, last Confirmed Response)
sCR/CR/VGPR PR max(PR, last Confirmed Response)
sCR/CR/VGPR/PR MR max(MR, last Confirmed Response)
sCR/CR/VGPR/PR/MR SD/PD/NE/NA max(SD, last Confirmed Response)
SD any max(SD, last Confirmed Response)
NE any max(NE, last Confirmed Response)
PD reason imaging any PD
PD reason serum/urine PD/death PD
PD reason serum/urine sCR/CR/VGPR/PR/MR/SD/NE/NA max(NE, last Confirmed Response)

Handling Non-Evaluable Responses

IMWG criteria article does not define exactly what is the time interval needed to confirm a response and whether non-evaluable (NE) records can be ignored when confirming a response. Detailed guidelines on this topic should be specified in SAP.

We assumed in our next steps that non-evaluable records are ignored when deriving Confirmed Response. That is, to confirm response at a visit we use the response from the first subsequent visit, which had an answer other than NE.

Additional Variables

To derive Confirmed Response we are using variables from SUPPRS dataset included in {pharmaversesdtm} package.

Variable Name Variable Label
PDOFL Progressive Disease: Other
PDIFL Progressive Disease: Imaging
DTHPDFL Death Due to Progressive Disease
NACTDT New Anti-Cancer Therapy Date

User will overwrite variable names if necessary.

  1. PDOFL, PDIFL, DTHPDFL variables are used in derivation of PD as Confirmed Response. If PD comes from imaging assessment (PDIFL = "Y") or participant died due to disease under study before further adequate assessment could be performed (DTHPDFL = "Y") or PD comes from biochemical markers and is followed by another PD (PDOFL = "Y" and AVALC.next = "PD"), we report PD as Confirmed Progression.

  2. NACTDT variable is used to exclude assessments after start day of subsequent therapy while confirming responses (sCR, CR, VGPR. PR, MR). However, testing during subsequent therapy can be used to confirm PD.

Definition of derive_confirmed_response Function

derive_confirmed_response function defined below takes as an argument dataset with Overall Responses and returns dataset with Confirmed Responses.

In brief, the function performs the following steps:

  1. Remove records with response of NE and derive intermediate response.
  2. Display a warning if responses used for confirmation are more than confirmed_period apart. User can set confirmed_period freely - there is no time limit on the sample we use to confirm the response.
  3. Add records with response of NE.
  4. Derive best Confirmed Response so far.
  5. Assign AVAL and AVALC, remove unnecessary variables.
confirmation_period <- 84

derive_confirmed_response <- function(datain) {
  data_adrs <- datain %>%
    arrange(USUBJID, ADTM) %>%
    filter(AVALC != "NE") %>%
    group_by(USUBJID) %>%
    mutate(
      AVALC.next = lead(AVALC),
      AVAL.next = lead(AVAL),
      ADT.next = lead(ADT)
    ) %>%
    ungroup() %>%
    mutate(AVALC.confirmed = case_when(
      # better response
      AVALC %in% c("sCR", "CR", "VGPR", "PR", "MR") &
        AVALC.next %in% c("sCR", "CR", "VGPR", "PR", "MR") &
        (is.na(NACTDT) | ADT.next <= NACTDT) &
        AVAL.next >= AVAL ~ AVALC,
      # worse response
      AVALC %in% c("sCR", "CR", "VGPR", "PR", "MR") &
        AVALC.next %in% c("sCR", "CR", "VGPR", "PR", "MR", "SD") &
        (is.na(NACTDT) | ADT.next <= NACTDT) &
        AVAL.next < AVAL ~ AVALC.next,
      # next assessment PD, NA or after subsequent therapy
      AVALC %in% c("sCR", "CR", "VGPR", "PR", "MR") &
        (AVALC.next == "PD" | is.na(AVALC.next) |
          !is.na(NACTDT) & ADT.next > NACTDT) ~ "SD",
      # no need to confirm SD
      AVALC %in% c("SD") ~ AVALC,
      # confirmed progression
      AVALC == "PD" &
        (PDIFL == "Y" | DTHPDFL == "Y" | PDOFL == "Y" & AVALC.next == "PD") ~
        AVALC,
      # unconfirmed progression
      AVALC == "PD" & is.na(DTHPDFL) & PDOFL == "Y" &
        (AVALC.next %in% c("sCR", "CR", "VGPR", "PR", "MR", "SD") |
          is.na(AVALC.next)) ~ "NE"
    ))

  data_adrs_check <- data_adrs %>%
    mutate(diff_days = as.numeric(difftime(ADT.next, ADT, units = "days"))) %>%
    filter(diff_days > confirmation_period) %>%
    mutate(warn = paste(
      "For USUBJID", USUBJID, "to confirm", AVISIT,
      "visit, a visit that took place", diff_days, "days later was used."
    )) %>%
    pull(warn)

  if (length(data_adrs_check) > 0) {
    cli_warn("{data_adrs_check}")
  }

  data_adrs_ne <- datain %>%
    filter(AVALC == "NE") %>%
    mutate(AVALC.confirmed = AVALC)

  data_adrs_all <- bind_rows(data_adrs, data_adrs_ne) %>%
    arrange(USUBJID, ADTM) %>%
    mutate(AVAL.confirmed = aval_resp_conf(AVALC.confirmed)) %>%
    group_by(USUBJID) %>%
    # best Confirmed Response so far
    mutate(AVAL.confirmed = cummax(AVAL.confirmed)) %>%
    ungroup(USUBJID)

  # char mapping to go back to AVALC values
  avalc_resp_conf <- function(arg) {
    case_match(
      arg,
      8 ~ "PD",
      7 ~ "sCR",
      6 ~ "CR",
      5 ~ "VGPR",
      4 ~ "PR",
      3 ~ "MR",
      2 ~ "SD",
      1 ~ "NE",
      NA_real_ ~ NA
    )
  }

  data_adrs_all <- data_adrs_all %>%
    mutate(AVALC.confirmed = avalc_resp_conf(AVAL.confirmed)) %>%
    select(
      -AVAL, -AVALC, -AVAL.next, -AVALC.next, -ADT.next,
      -AVAL.confirmed
    ) %>%
    rename(AVALC = AVALC.confirmed) %>%
    mutate(AVAL = aval_resp_imwg(AVALC)) %>%
    mutate(
      PARAMCD = "COVR",
      PARAM = "Confirmed Response at Time Point by Investigator",
      PARCAT1 = "Investigator",
      PARCAT2 = "IMWG"
    )
}
adrs_imwg <- derive_confirmed_response(adrs)
USUBJID PARAMCD AVISIT ADT AVALC
01-701-1015 COVR WEEK 6 2014-02-12 PD
01-701-1028 COVR WEEK 6 2013-08-31 sCR
01-701-1028 COVR WEEK 12 2013-10-09 sCR
01-701-1028 COVR WEEK 18 2013-11-20 sCR
01-701-1034 COVR WEEK 6 2014-08-11 CR
01-701-1034 COVR WEEK 12 2014-09-25 CR
01-701-1034 COVR WEEK 18 2014-11-04 CR
01-701-1097 COVR WEEK 6 2014-02-11 NE
01-701-1115 COVR WEEK 6 2013-01-10 PD
01-701-1118 COVR WEEK 6 2014-04-23 NA

Check Number of NE Values Between Responses

If user would like to receive a warning that there is a certain number of NEs between responses, this can be done using filter_consecutive_vals function defined below.

filter_consecutive_vals <- function(dataset, by_vars, order, var, val, n) {
  var <- enexpr(var)
  var_join <- sym(paste0(as.character(var), ".join"))
  data <- derive_var_obs_number(dataset, by_vars = by_vars, order = order, new_var = temp_seq)

  left_join(
    data, select(data, !!!by_vars, temp_seq, !!var),
    by = admiraldev::vars2chr(by_vars),
    suffix = c("", ".join"),
    relationship = "many-to-many"
  ) %>%
    filter(temp_seq <= temp_seq.join & !!var == !!val) %>%
    filter_relative(
      by_vars = c(by_vars, expr(temp_seq)),
      order = exprs(temp_seq.join),
      condition = !!var_join != !!val,
      mode = "first",
      selection = "before",
      keep_no_ref_groups = TRUE,
      inclusive = FALSE
    ) %>%
    derive_var_merged_summary(
      dataset = .,
      dataset_add = .,
      by_vars = c(by_vars, expr(temp_seq)),
      new_vars = exprs(nr_vals = n())
    ) %>%
    filter(nr_vals >= n) %>%
    filter_extreme(
      by_vars = c(by_vars, expr(temp_seq)),
      order = exprs(temp_seq.join),
      mode = "first",
      check_type = "none"
    ) %>%
    select(-temp_seq, -temp_seq.join, -!!var_join, -nr_vals)
}

# filter on three or more NEs in a row
many_nes <- filter_consecutive_vals(
  adrs,
  by_vars = get_admiral_option("subject_keys"),
  order = exprs(ADTM),
  var = AVALC,
  val = "NE",
  n = 3
)

if (nrow(many_nes) > 0) {
  cli_warn("There are subjects with more than three NEs in a row.")
}

Analysis Flag Derivation

Flag One Assessment at Each Analysis Visit (ANL01FL)

To get Confirm Responses on each visit, we took into account all assessments - including those that took place after a new therapy was started or after progression.

When deriving ANL01FL this is an opportunity to exclude any records that should not contribute to any downstream parameter derivations.

Common options for ANL01FL would be to set null for invalid assessments or those occurring after new anti-cancer therapy, or to only flag assessments on or after date of first treatment/randomization, or rules to cover the case when a patient has multiple observations per visit/date (e.g. by selecting the worst value). Another consideration could be extra potential protocol-specific sources of Progressive Disease such as radiological assessments, which could be pre-processed here to create a PD record to feed downstream derivations.

For the derivation of the parameters it is expected that the subject identifier variables (usually STUDYID and USUBJID) and ADT are a unique key.

In the below example we consider only valid assessments and those occurring on or after randomization date. If there is more than one assessment at a date, the worst one is flagged.

adrs_imwg <- adrs_imwg %>%
  restrict_derivation(
    derivation = derive_var_extreme_flag,
    args = params(
      by_vars = exprs(!!!get_admiral_option("subject_keys"), ADT),
      order = exprs(AVAL, RSSEQ),
      new_var = ANL01FL,
      mode = "first"
    ),
    filter = !is.na(AVAL) & AVALC != "MISSING" & ADT >= RANDDT
  )

Exclude Assessments After New Anti-Cancer Therapy (ANL02FL)

Here is an alternative example where those records occurring after new anti-cancer therapy are additionally excluded (where NACTDT would be pre-derived as first date of new anti-cancer therapy.
In our example NACTDT is present in SUPPRS domain. If not available, see {admiralonco} Creating and Using New Anti-Cancer Start Date for deriving this variable).

adrs_imwg <- adrs_imwg %>%
  mutate(
    ANL02FL = case_when(
      !is.na(AVAL) & ADT >= RANDDT & ADT < NACTDT ~ "Y",
      is.na(NACTDT) ~ "Y",
      TRUE ~ NA_character_
    )
  )

Flag Assessments up to First PD (ANL03FL)

To restrict response data up to and including first reported progressive disease ANL03FL flag could be created by using {admiral} function admiral::derive_var_relative_flag().

adrs_imwg <- adrs_imwg %>%
  derive_var_relative_flag(
    by_vars = get_admiral_option("subject_keys"),
    order = exprs(ADT, RSSEQ),
    new_var = ANL03FL,
    condition = AVALC == "PD",
    mode = "first",
    selection = "before",
    inclusive = TRUE
  )
USUBJID AVISIT PARAMCD AVALC ADT ANL01FL ANL02FL ANL03FL
01-701-1015 WEEK 6 COVR PD 2014-02-12 Y Y Y
01-701-1028 WEEK 6 COVR sCR 2013-08-31 Y Y Y
01-701-1028 WEEK 12 COVR sCR 2013-10-09 Y Y Y
01-701-1028 WEEK 18 COVR sCR 2013-11-20 Y Y Y
01-701-1034 WEEK 6 COVR CR 2014-08-11 Y Y Y
01-701-1034 WEEK 12 COVR CR 2014-09-25 Y Y Y
01-701-1034 WEEK 18 COVR CR 2014-11-04 Y Y Y
01-701-1097 WEEK 6 COVR NE 2014-02-11 Y NA Y
01-701-1115 WEEK 6 COVR PD 2013-01-10 Y Y Y
01-701-1118 WEEK 6 COVR NA 2014-04-23 NA Y Y

Select Source Assessments for Parameter derivations

For next parameter derivations we consider only Confirmed Responses (PARAMCD = "COVR"). We take post-baseline records (ANL01FL = "Y") before start of new anti-cancer therapy (ANL02FL = "Y") and up to and including first PD (ANL03FL = "Y").

ovr <- filter(adrs_imwg, PARAMCD == "COVR" & ANL01FL == "Y" & ANL02FL == "Y" & ANL03FL == "Y")

adrs <- bind_rows(adrs, adrs_imwg)
USUBJID AVISIT AVALC ADT RANDDT
01-701-1015 WEEK 6 PD 2014-02-12 2014-01-02
01-701-1028 WEEK 6 sCR 2013-08-31 2013-07-19
01-701-1028 WEEK 12 sCR 2013-10-09 2013-07-19
01-701-1028 WEEK 18 sCR 2013-11-20 2013-07-19
01-701-1034 WEEK 6 CR 2014-08-11 2014-07-01
01-701-1034 WEEK 12 CR 2014-09-25 2014-07-01
01-701-1034 WEEK 18 CR 2014-11-04 2014-07-01
01-701-1115 WEEK 6 PD 2013-01-10 2012-11-30
01-701-1133 WEEK 6 PR 2012-12-11 2012-10-28
01-701-1133 WEEK 12 PR 2013-01-22 2012-10-28

Derive Progressive Disease Parameter

Now that we have the input records prepared above with any company-specific requirements, we can start to derive new parameter records. For the parameter derivations, all values except those overwritten by set_values_to argument are kept from the earliest occurring input record fulfilling the required criteria.

The function admiral::derive_extreme_records() can be used to find the date of first PD.

adrs <- adrs %>%
  derive_extreme_records(
    dataset_ref = adsl,
    dataset_add = ovr,
    by_vars = get_admiral_option("subject_keys"),
    filter_add = PARAMCD == "COVR" & AVALC == "PD",
    order = exprs(ADT),
    mode = "first",
    exist_flag = AVALC,
    false_value = "N",
    set_values_to = exprs(
      PARAMCD = "PD",
      PARAM = "Disease Progression by Investigator",
      PARCAT1 = "Investigator",
      PARCAT2 = "IMWG",
      AVAL = yn_to_numeric(AVALC),
      ANL01FL = "Y",
      ANL02FL = "Y",
      ANL03FL = "Y"
    )
  )
USUBJID AVISIT PARAMCD AVALC ADT
01-701-1015 WEEK 6 PD Y 2014-02-12
01-701-1115 WEEK 6 PD Y 2013-01-10
01-701-1287 WEEK 18 PD Y 2014-05-29
01-701-1302 WEEK 6 PD Y 2013-10-08
01-701-1023 NA PD N NA
01-701-1028 NA PD N NA
01-701-1033 NA PD N NA
01-701-1034 NA PD N NA
01-701-1047 NA PD N NA
01-701-1057 NA PD N NA

For progressive disease and response shown in steps here and below, in our examples we show these as ADRS parameters, but they could equally be achieved via ADSL dates or ADEVENT parameters. If you prefer to store as an ADSL date, then the function admiral::derive_var_extreme_dt() could be used to find the date of first PD as a variable, rather than as a new parameter record.

Define Events

The building blocks for the events that contribute to deriving common endpoints like what constitutes a responder, or a Best Overall Response of complete response (CR), … are predefined in admiralonco for RECIST 1.1 (see Pre-Defined Response Event Objects).

New events need to be defined for the IMWG criteria.
Below are definitions of non-response events used in the derivations of all parameters.
Parameter-specific events are defined right before the parameter derivation.

no_data_n <- event(
  description = "Define no response for all patients in adsl (should be used as last event)",
  dataset_name = "adsl",
  condition = TRUE,
  set_values_to = exprs(AVALC = "N"),
  keep_source_vars = adsl_vars
)

no_data_missing <- event(
  description = paste(
    "Define missing response (MISSING) for all patients in adsl (should be used",
    "as last event)"
  ),
  dataset_name = "adsl",
  condition = TRUE,
  set_values_to = exprs(AVALC = "MISSING"),
  keep_source_vars = adsl_vars
)

Derive Response Parameter

The function admiral::derive_extreme_event() can then be used to find the date of first response. In the below example, the response condition has been defined as PR or better via the event rsp_y_imwg that was created for IMWG.

rsp_y_imwg <- event(
  description = "Define sCR, CR, VGPR or PR as response",
  dataset_name = "ovr",
  condition = AVALC %in% c("sCR", "CR", "VGPR", "PR"),
  set_values_to = exprs(AVALC = "Y")
)

adrs <- adrs %>%
  derive_extreme_event(
    by_vars = get_admiral_option("subject_keys"),
    order = exprs(ADT),
    mode = "first",
    events = list(rsp_y_imwg, no_data_n),
    source_datasets = list(
      ovr = ovr,
      adsl = adsl
    ),
    set_values_to = exprs(
      PARAMCD = "RSP",
      PARAM = "IMWG Response by Investigator",
      PARCAT1 = "Investigator",
      PARCAT2 = "IMWG",
      AVAL = yn_to_numeric(AVALC),
      ANL01FL = "Y",
      ANL02FL = "Y",
      ANL03FL = "Y"
    )
  )
USUBJID RSORRES AVISIT PARAMCD AVALC ADT
01-701-1028 sCR WEEK 6 RSP Y 2013-08-31
01-701-1034 CR WEEK 6 RSP Y 2014-08-11
01-701-1133 sCR WEEK 6 RSP Y 2012-12-11
01-701-1148 PR WEEK 12 RSP Y 2013-11-17
01-701-1287 PR WEEK 6 RSP Y 2014-03-06

Derive Clinical Benefit Parameter

The function admiral::derive_extreme_event() can then be used to derive the clinical benefit parameter, which we define as a patient having had a response or a sustained period of time before first PD. This could also be known as disease control. In this example the “sustained period” has been defined as 42 days after randomization date via the created cb_y_imwg event.

sustained_period <- 42

cb_y_imwg <- event(
  description = paste(
    "Define sCR, CR, VGPR, PR, MR or SD occuring at least",
    sustained_period,
    "days after randomization as clinical benefit"
  ),
  dataset_name = "ovr",
  condition = AVALC %in% c("sCR", "CR", "VGPR", "PR", "MR", "SD") &
    ADT >= RANDDT + days(sustained_period),
  set_values_to = exprs(AVALC = "Y")
)

Please note that the result AVALC = "Y" is defined by the first two events specified for events. For subjects with observations fulfilling both events the one with the earlier date should be selected (and not the first one in the list). Thus ignore_event_order and tmp_event_nr_var are not specified.

adrs <- adrs %>%
  derive_extreme_event(
    by_vars = get_admiral_option("subject_keys"),
    order = exprs(desc(AVALC), ADT),
    mode = "first",
    events = list(rsp_y_imwg, cb_y_imwg, no_data_n),
    source_datasets = list(
      ovr = ovr,
      adsl = adsl
    ),
    set_values_to = exprs(
      PARAMCD = "CB",
      PARAM = "IMWG Clinical Benefit by Investigator",
      PARCAT1 = "Investigator",
      PARCAT2 = "IMWG",
      AVAL = yn_to_numeric(AVALC),
      ANL01FL = "Y",
      ANL02FL = "Y",
      ANL03FL = "Y"
    ),
    check_type = "none"
  )
USUBJID RSORRES AVISIT PARAMCD AVALC ADT
01-701-1028 sCR WEEK 6 CB Y 2013-08-31
01-701-1034 CR WEEK 6 CB Y 2014-08-11
01-701-1133 sCR WEEK 6 CB Y 2012-12-11
01-701-1148 PR WEEK 12 CB Y 2013-11-17
01-701-1153 sCR WEEK 6 CB Y 2013-11-04
01-701-1203 CR WEEK 6 CB Y 2013-03-16
01-701-1211 MR WEEK 12 CB Y 2013-01-14
01-701-1239 MR WEEK 12 CB Y 2014-04-02
01-701-1275 MR WEEK 6 CB Y 2014-03-22
01-701-1287 PR WEEK 6 CB Y 2014-03-06

Similarly, we can define the parameters:

cr_y_imwg <- event(
  description = "Define sCR or CR as response",
  dataset_name = "ovr",
  condition = AVALC %in% c("sCR", "CR"),
  set_values_to = exprs(AVALC = "Y")
)

adrs <- adrs %>%
  derive_extreme_event(
    by_vars = get_admiral_option("subject_keys"),
    order = exprs(desc(AVALC), ADT),
    mode = "first",
    events = list(cr_y_imwg, no_data_n),
    source_datasets = list(
      ovr = ovr,
      adsl = adsl
    ),
    set_values_to = exprs(
      PARAMCD = "CRRSP",
      PARAM = "IMWG Complete Response by Investigator",
      PARCAT1 = "Investigator",
      PARCAT2 = "IMWG",
      AVAL = yn_to_numeric(AVALC),
      ANL01FL = "Y",
      ANL02FL = "Y",
      ANL03FL = "Y"
    ),
    check_type = "none"
  )

vgpr_y_imwg <- event(
  description = "Define sCR, CR or VGPR as response",
  dataset_name = "ovr",
  condition = AVALC %in% c("sCR", "CR", "VGPR"),
  set_values_to = exprs(AVALC = "Y")
)

adrs <- adrs %>%
  derive_extreme_event(
    by_vars = get_admiral_option("subject_keys"),
    order = exprs(desc(AVALC), ADT),
    mode = "first",
    events = list(vgpr_y_imwg, no_data_n),
    source_datasets = list(
      ovr = ovr,
      adsl = adsl
    ),
    set_values_to = exprs(
      PARAMCD = "VGPRRSP",
      PARAM = "IMWG VGPR Response by Investigator",
      PARCAT1 = "Investigator",
      PARCAT2 = "IMWG",
      AVAL = yn_to_numeric(AVALC),
      ANL01FL = "Y",
      ANL02FL = "Y",
      ANL03FL = "Y"
    ),
    check_type = "none"
  )
USUBJID RSORRES AVISIT PARAMCD AVALC ADT
01-701-1028 sCR WEEK 6 CRRSP Y 2013-08-31
01-701-1034 CR WEEK 6 CRRSP Y 2014-08-11
01-701-1028 sCR WEEK 6 VGPRRSP Y 2013-08-31
01-701-1034 CR WEEK 6 VGPRRSP Y 2014-08-11

Derive Best Confirmed Overall Response Parameter

The function admiral::derive_extreme_event() can be used to derive the best confirmed overall response parameter.

Please note that the order of the events specified for events is important. For example, a subject with PR, PR, CR qualifies for both bor_cr and bor_pr. As bor_cr is listed before bor_pr, CR is selected as best overall response for this subject.

Some events such as bor_cr, bor_pr have been defined in {admiralonco}. Missing events specific to IMWG criteria are defined below.
Note: For SD, it is not required as for RECIST1.1 that the response occurs after a protocol-defined number of days.

bor_scr <- event(
  description = "Define stringent complete response (sCR) for best overall response
  (BOR)",
  dataset_name = "ovr",
  condition = AVALC == "sCR",
  set_values_to = exprs(AVALC = "sCR")
)

bor_vgpr <- event(
  description = "Define very good partial response (VGPR) for best overall response
  (BOR)",
  dataset_name = "ovr",
  condition = AVALC == "VGPR",
  set_values_to = exprs(AVALC = "VGPR")
)

bor_mr <- event(
  description = "Define minimal response (MR) for best overall response (BOR)",
  dataset_name = "ovr",
  condition = AVALC == "MR",
  set_values_to = exprs(AVALC = "MR")
)

bor_sd_imwg <- event(
  description = "Define stable disease (SD) for best overall response (BOR)",
  dataset_name = "ovr",
  condition = AVALC == "SD",
  set_values_to = exprs(AVALC = "SD")
)

bor_ne_imwg <- event(
  description = "Define not evaluable (NE) for best overall response (BOR)",
  dataset_name = "ovr",
  condition = AVALC == "NE",
  set_values_to = exprs(AVALC = "NE")
)

adrs <- adrs %>%
  derive_extreme_event(
    by_vars = get_admiral_option("subject_keys"),
    tmp_event_nr_var = event_nr,
    order = exprs(event_nr, ADT),
    mode = "first",
    source_datasets = list(
      ovr = ovr,
      adsl = adsl
    ),
    events = list(
      bor_scr, bor_cr, bor_vgpr, bor_pr, bor_mr, bor_sd_imwg, bor_pd, bor_ne_imwg,
      no_data_missing
    ),
    set_values_to = exprs(
      PARAMCD = "CBOR",
      PARAM = "IMWG Best Confirmed Overall Response by Investigator",
      PARCAT1 = "Investigator",
      PARCAT2 = "IMWG",
      AVAL = aval_resp_imwg(AVALC),
      ANL01FL = "Y",
      ANL02FL = "Y",
      ANL03FL = "Y"
    )
  )
USUBJID AVISIT PARAMCD AVALC ADT
01-701-1015 WEEK 6 CBOR PD 2014-02-12
01-701-1028 WEEK 6 CBOR sCR 2013-08-31
01-701-1034 WEEK 6 CBOR CR 2014-08-11
01-701-1115 WEEK 6 CBOR PD 2013-01-10
01-701-1133 WEEK 6 CBOR PR 2012-12-11
01-701-1146 WEEK 6 CBOR NE 2013-06-30
01-701-1148 WEEK 12 CBOR PR 2013-11-17
01-701-1153 WEEK 6 CBOR MR 2013-11-04
01-701-1203 WEEK 6 CBOR MR 2013-03-16
01-701-1211 WEEK 6 CBOR MR 2012-12-25

Other Endpoints

For examples on the additional endpoints, please see Creating ADRS (Including Non-standard Endpoints).