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:
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:
RS
contains Confirmed Response,RS
contains an unconfirmed response and the Confirmed
Response should be derived.In our example we will consider the second scenario.
Note: All examples assume CDISC SDTM and/or ADaM format as input unless otherwise specified.
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 |
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.
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 |
ADT
,
ADTF
, AVISIT
etcIf 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 |
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 |
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) |
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
.
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.
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.
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
.
derive_confirmed_response
Functionderive_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:
NE
and derive
intermediate response.confirmed_period
apart. User can set
confirmed_period
freely - there is no time limit on the
sample we use to confirm the response.NE
.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 |
NE
Values Between ResponsesIf user would like to receive a warning that there is a certain
number of NE
s 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.")
}
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.
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).
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 |
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 |
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.
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
)
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 |
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:
PARAMCD
=
CRRSP
),PARAMCD
=VGPRRSP
).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 |
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 |
For examples on the additional endpoints, please see Creating ADRS (Including Non-standard Endpoints).