Creating a BDS Finding ADaM

Introduction

This article describes creating a BDS finding ADaM. Examples are currently presented and tested in the context of ADVS. However, the examples could be applied to other BDS Finding ADaMs such as ADEG, ADLB, etc. where a single result is captured in an SDTM Finding domain on a single date and/or time.

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 ADVS should be read into the environment. This will be a company specific process. Some of the data frames needed may be VS and ADSL.

For example purpose, the CDISC Pilot SDTM and ADaM datasets—which are included in {pharmaversesdtm}—are used.

library(admiral)
library(dplyr, warn.conflicts = FALSE)
library(pharmaversesdtm)
library(lubridate)
library(stringr)
library(tibble)

data("admiral_adsl")
data("vs")

adsl <- admiral_adsl
vs <- convert_blanks_to_na(vs)

At this step, it may be useful to join ADSL to your VS 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(TRTSDT, TRTEDT, TRT01A, TRT01P)

advs <- derive_vars_merged(
  vs,
  dataset_add = adsl,
  new_vars = adsl_vars,
  by_vars = exprs(STUDYID, USUBJID)
)
USUBJID VSTESTCD VSDTC VISIT TRTSDT TRTEDT TRT01A TRT01P
01-701-1015 DIABP 2014-01-16 WEEK 2 2014-01-02 2014-07-02 Placebo Placebo
01-701-1015 DIABP 2014-01-16 WEEK 2 2014-01-02 2014-07-02 Placebo Placebo
01-701-1015 DIABP 2014-01-16 WEEK 2 2014-01-02 2014-07-02 Placebo Placebo
01-701-1023 DIABP 2012-08-27 WEEK 2 2012-08-05 2012-09-01 Placebo Placebo
01-701-1023 DIABP 2012-08-27 WEEK 2 2012-08-05 2012-09-01 Placebo Placebo
01-701-1023 DIABP 2012-08-27 WEEK 2 2012-08-05 2012-09-01 Placebo Placebo
01-703-1086 DIABP 2012-09-16 WEEK 2 2012-09-02 2012-12-04 Xanomeline Low Dose Xanomeline Low Dose
01-703-1086 DIABP 2012-09-16 WEEK 2 2012-09-02 2012-12-04 Xanomeline Low Dose Xanomeline Low Dose
01-703-1086 DIABP 2012-09-16 WEEK 2 2012-09-02 2012-12-04 Xanomeline Low Dose Xanomeline Low Dose
01-703-1096 DIABP 2013-02-09 WEEK 2 2013-01-25 2013-03-16 Placebo Placebo

Derive/Impute Numeric Date/Time and Analysis Day (ADT, ADTM, ADY, ADTF, ATMF)

The function derive_vars_dt() can be used to derive ADT. This function allows the user to impute the date as well.

Example calls:

advs <- derive_vars_dt(advs, new_vars_prefix = "A", dtc = VSDTC)
USUBJID VISIT VSDTC ADT
01-701-1015 SCREENING 1 2013-12-26 2013-12-26
01-701-1015 SCREENING 1 2013-12-26 2013-12-26
01-701-1015 SCREENING 1 2013-12-26 2013-12-26
01-701-1015 SCREENING 2 2013-12-31 2013-12-31
01-701-1015 SCREENING 2 2013-12-31 2013-12-31
01-701-1015 SCREENING 2 2013-12-31 2013-12-31
01-701-1015 BASELINE 2014-01-02 2014-01-02
01-701-1015 BASELINE 2014-01-02 2014-01-02
01-701-1015 BASELINE 2014-01-02 2014-01-02
01-701-1015 AMBUL ECG PLACEMENT 2014-01-14 2014-01-14

If imputation is needed and the date is to be imputed to the first of the month, the call would be:

advs <- derive_vars_dt(
  advs,
  new_vars_prefix = "A",
  dtc = VSDTC,
  highest_imputation = "M"
)
USUBJID VISIT VSDTC ADT ADTF
01-716-1024 SCREENING 1 2012-07 2012-07-01 D
01-716-1024 SCREENING 1 2012-07 2012-07-01 D
01-716-1024 SCREENING 1 2012-07 2012-07-01 D
01-716-1024 SCREENING 2 2012-07-07 2012-07-07 NA
01-716-1024 SCREENING 2 2012-07-07 2012-07-07 NA
01-716-1024 SCREENING 2 2012-07-07 2012-07-07 NA
01-716-1024 BASELINE 2012-07-09 2012-07-09 NA
01-716-1024 BASELINE 2012-07-09 2012-07-09 NA
01-716-1024 BASELINE 2012-07-09 2012-07-09 NA
01-716-1024 AMBUL ECG PLACEMENT 2012-07-22 2012-07-22 NA

Similarly, ADTM may be created using the function derive_vars_dtm(). Imputation may be done on both the date and time components of ADTM.

# CDISC Pilot data does not contain times and the output of the derivation
# ADTM is not presented.
advs <- derive_vars_dtm(
  advs,
  new_vars_prefix = "A",
  dtc = VSDTC,
  highest_imputation = "M"
)

By default, the variable ADTF for derive_vars_dt() or ADTF and ATMF for derive_vars_dtm() will be created and populated with the controlled terminology outlined in the ADaM IG for date imputations.

See also Date and Time Imputation.

Once ADT is derived, the function derive_vars_dy() can be used to derive ADY. This example assumes both ADT and TRTSDT exist on the data frame.

advs <-
  derive_vars_dy(advs, reference_date = TRTSDT, source_vars = exprs(ADT))
USUBJID VISIT ADT ADY TRTSDT
01-716-1024 SCREENING 1 2012-07-06 -3 2012-07-09
01-716-1024 SCREENING 1 2012-07-06 -3 2012-07-09
01-716-1024 SCREENING 1 2012-07-06 -3 2012-07-09
01-716-1024 SCREENING 2 2012-07-07 -2 2012-07-09
01-716-1024 SCREENING 2 2012-07-07 -2 2012-07-09
01-716-1024 SCREENING 2 2012-07-07 -2 2012-07-09
01-716-1024 BASELINE 2012-07-09 1 2012-07-09
01-716-1024 BASELINE 2012-07-09 1 2012-07-09
01-716-1024 BASELINE 2012-07-09 1 2012-07-09
01-716-1024 AMBUL ECG PLACEMENT 2012-07-22 14 2012-07-09

Assign PARAMCD, PARAM, PARAMN, PARCAT1

To assign parameter level values such as PARAMCD, PARAM, PARAMN, PARCAT1, etc., a lookup can be created to join to the source data.

For example, when creating ADVS, a lookup based on the SDTM --TESTCD value may be created:

VSTESTCD PARAMCD PARAM PARAMN PARCAT1 PARCAT1N
HEIGHT HEIGHT Height (cm) 1 Subject Characteristic 1
WEIGHT WEIGHT Weight (kg) 2 Subject Characteristic 1
DIABP DIABP Diastolic Blood Pressure (mmHg) 3 Vital Sign 2
MAP MAP Mean Arterial Pressure 4 Vital Sign 2
PULSE PULSE Pulse Rate (beats/min) 5 Vital Sign 2
SYSBP SYSBP Systolic Blood Pressure (mmHg) 6 Vital Sign 2
TEMP TEMP Temperature (C) 7 Vital Sign 2

This lookup may now be joined to the source data:

At this stage, only PARAMCD is required to perform the derivations. Additional derived parameters may be added, so only PARAMCD is joined to the datasets at this point. All other variables related to PARAMCD (e.g. PARAM, PARAMCAT1, …) will be added when all PARAMCD are derived.

advs <- derive_vars_merged_lookup(
  advs,
  dataset_add = param_lookup,
  new_vars = exprs(PARAMCD),
  by_vars = exprs(VSTESTCD)
)
#> All `VSTESTCD` are mapped.
USUBJID VSTESTCD PARAMCD
01-701-1015 DIABP DIABP
01-701-1015 HEIGHT HEIGHT
01-701-1015 PULSE PULSE
01-701-1015 SYSBP SYSBP
01-701-1015 TEMP TEMP
01-701-1015 WEIGHT WEIGHT
01-701-1023 DIABP DIABP
01-701-1023 HEIGHT HEIGHT
01-701-1023 PULSE PULSE
01-701-1023 SYSBP SYSBP

Please note, it may be necessary to include other variables in the join. For example, perhaps the PARAMCD is based on VSTESTCD and VSPOS, it may be necessary to expand this lookup or create a separate look up for PARAMCD.

If more than one lookup table, e.g., company parameter mappings and project parameter mappings, are available, consolidate_metadata() can be used to consolidate these into a single lookup table.

Derive Results (AVAL, AVALC)

The mapping of AVAL and AVALC is left to the ADaM programmer. An example mapping may be:

advs <- mutate(
  advs,
  AVAL = VSSTRESN,
  AVALC = VSSTRESC
)
VSTESTCD PARAMCD VSSTRESN VSSTRESC AVAL AVALC
DIABP DIABP 74 74 74 74
DIABP DIABP 74 74 74 74
DIABP DIABP 72 72 72 72
DIABP DIABP 78 78 78 78
DIABP DIABP 84 84 84 84
DIABP DIABP 78 78 78 78
DIABP DIABP 80 80 80 80
DIABP DIABP 80 80 80 80
DIABP DIABP 84 84 84 84
DIABP DIABP 90 90 90 90

Derive Additional Parameters (e.g. BSA, BMI or MAP for ADVS)

Optionally derive new parameters creating PARAMCD and AVAL. Note that only variables specified in the by_vars argument will be populated in the newly created records. This is relevant to the functions derive_param_map, derive_param_bsa, derive_param_bmi, and derive_param_qtc.

Below is an example of creating Mean Arterial Pressure for ADVS, see also Example 3 in section below Derive New Rows for alternative way of creating new parameters.

advs <- derive_param_map(
  advs,
  by_vars = exprs(STUDYID, USUBJID, !!!adsl_vars, VISIT, VISITNUM, ADT, ADY, VSTPT, VSTPTNUM),
  set_values_to = exprs(PARAMCD = "MAP"),
  get_unit_expr = VSSTRESU,
  filter = VSSTAT != "NOT DONE" | is.na(VSSTAT)
)
VSTESTCD PARAMCD VISIT VSTPT AVAL AVALC
DIABP DIABP SCREENING 1 AFTER LYING DOWN FOR 5 MINUTES 64.00000 64
NA MAP SCREENING 1 AFTER LYING DOWN FOR 5 MINUTES 86.33333 NA
SYSBP SYSBP SCREENING 1 AFTER LYING DOWN FOR 5 MINUTES 131.00000 131
DIABP DIABP SCREENING 1 AFTER STANDING FOR 1 MINUTE 83.00000 83
NA MAP SCREENING 1 AFTER STANDING FOR 1 MINUTE 98.33333 NA
SYSBP SYSBP SCREENING 1 AFTER STANDING FOR 1 MINUTE 129.00000 129
DIABP DIABP SCREENING 1 AFTER STANDING FOR 3 MINUTES 57.00000 57
NA MAP SCREENING 1 AFTER STANDING FOR 3 MINUTES 87.00000 NA
SYSBP SYSBP SCREENING 1 AFTER STANDING FOR 3 MINUTES 147.00000 147
DIABP DIABP SCREENING 2 AFTER LYING DOWN FOR 5 MINUTES 68.00000 68

Likewise, function call below, to create parameter Body Surface Area (BSA) and Body Mass Index (BMI) for ADVS domain. Note that if height is collected only once use constant_by_vars to specify the subject-level variable to merge on. Otherwise BSA and BMI are only calculated for visits where both are collected.

advs <- derive_param_bsa(
  advs,
  by_vars = exprs(STUDYID, USUBJID, !!!adsl_vars, VISIT, VISITNUM, ADT, ADY, VSTPT, VSTPTNUM),
  method = "Mosteller",
  set_values_to = exprs(PARAMCD = "BSA"),
  get_unit_expr = VSSTRESU,
  filter = VSSTAT != "NOT DONE" | is.na(VSSTAT),
  constant_by_vars = exprs(USUBJID)
)

advs <- derive_param_bmi(
  advs,
  by_vars = exprs(STUDYID, USUBJID, !!!adsl_vars, VISIT, VISITNUM, ADT, ADY, VSTPT, VSTPTNUM),
  set_values_to = exprs(PARAMCD = "BMI"),
  get_unit_expr = VSSTRESU,
  filter = VSSTAT != "NOT DONE" | is.na(VSSTAT),
  constant_by_vars = exprs(USUBJID)
)
USUBJID VSTESTCD PARAMCD VISIT VSTPT AVAL AVALC
01-701-1015 NA BMI SCREENING 1 NA 24.871928 NA
01-701-1015 NA BSA SCREENING 1 NA 1.486264 NA
01-701-1015 NA BMI BASELINE NA 25.079271 NA
01-701-1015 NA BSA BASELINE NA 1.492447 NA
01-701-1015 NA BMI WEEK 2 NA 24.452635 NA
01-701-1015 NA BSA WEEK 2 NA 1.473683 NA
01-701-1015 NA BMI WEEK 4 NA 24.871928 NA
01-701-1015 NA BSA WEEK 4 NA 1.486264 NA
01-701-1015 NA BMI WEEK 6 NA 24.452635 NA
01-701-1015 NA BSA WEEK 6 NA 1.473683 NA

Similarly, for ADEG, the parameters QTCBF QTCBS and QTCL can be created with a function call. See example below for PARAMCD = QTCF.

adeg <- tibble::tribble(
  ~USUBJID, ~EGSTRESU, ~PARAMCD, ~AVAL, ~VISIT,
  "P01", "msec", "QT", 350, "CYCLE 1 DAY 1",
  "P01", "msec", "QT", 370, "CYCLE 2 DAY 1",
  "P01", "msec", "RR", 842, "CYCLE 1 DAY 1",
  "P01", "msec", "RR", 710, "CYCLE 2 DAY 1"
)

adeg <- derive_param_qtc(
  adeg,
  by_vars = exprs(USUBJID, VISIT),
  method = "Fridericia",
  set_values_to = exprs(PARAMCD = "QTCFR"),
  get_unit_expr = EGSTRESU
)

Similarly, for ADLB, the function derive_param_wbc_abs() can be used to create new parameter for lab differentials converted to absolute values. See example below:

adlb <- tibble::tribble(
  ~USUBJID, ~PARAMCD, ~AVAL, ~PARAM, ~VISIT,
  "P01", "WBC", 33, "Leukocyte Count (10^9/L)", "CYCLE 1 DAY 1",
  "P01", "WBC", 38, "Leukocyte Count (10^9/L)", "CYCLE 2 DAY 1",
  "P01", "LYMLE", 0.90, "Lymphocytes (fraction of 1)", "CYCLE 1 DAY 1",
  "P01", "LYMLE", 0.70, "Lymphocytes (fraction of 1)", "CYCLE 2 DAY 1"
)

derive_param_wbc_abs(
  dataset = adlb,
  by_vars = exprs(USUBJID, VISIT),
  set_values_to = exprs(
    PARAMCD = "LYMPH",
    PARAM = "Lymphocytes Abs (10^9/L)",
    DTYPE = "CALCULATION"
  ),
  get_unit_expr = extract_unit(PARAM),
  wbc_code = "WBC",
  diff_code = "LYMLE",
  diff_type = "fraction"
)

When all PARAMCD have been derived and added to the dataset, the other information from the look-up table (PARAM, PARAMCAT1,…) should be added.

# Derive PARAM and PARAMN
advs <- derive_vars_merged(
  advs,
  dataset_add = select(param_lookup, -VSTESTCD),
  by_vars = exprs(PARAMCD)
)
VSTESTCD PARAMCD PARAM PARAMN PARCAT1 PARCAT1N
DIABP DIABP Diastolic Blood Pressure (mmHg) 3 Vital Sign 2
DIABP DIABP Diastolic Blood Pressure (mmHg) 3 Vital Sign 2
DIABP DIABP Diastolic Blood Pressure (mmHg) 3 Vital Sign 2
DIABP DIABP Diastolic Blood Pressure (mmHg) 3 Vital Sign 2
DIABP DIABP Diastolic Blood Pressure (mmHg) 3 Vital Sign 2
DIABP DIABP Diastolic Blood Pressure (mmHg) 3 Vital Sign 2
DIABP DIABP Diastolic Blood Pressure (mmHg) 3 Vital Sign 2
DIABP DIABP Diastolic Blood Pressure (mmHg) 3 Vital Sign 2
DIABP DIABP Diastolic Blood Pressure (mmHg) 3 Vital Sign 2
DIABP DIABP Diastolic Blood Pressure (mmHg) 3 Vital Sign 2

Derive Timing Variables (e.g. APHASE, AVISIT, APERIOD)

Categorical timing variables are protocol and analysis dependent. Below is a simple example.

advs <- advs %>%
  mutate(
    AVISIT = case_when(
      str_detect(VISIT, "SCREEN") ~ NA_character_,
      str_detect(VISIT, "UNSCHED") ~ NA_character_,
      str_detect(VISIT, "RETRIEVAL") ~ NA_character_,
      str_detect(VISIT, "AMBUL") ~ NA_character_,
      !is.na(VISIT) ~ str_to_title(VISIT)
    ),
    AVISITN = as.numeric(case_when(
      VISIT == "BASELINE" ~ "0",
      str_detect(VISIT, "WEEK") ~ str_trim(str_replace(VISIT, "WEEK", ""))
    )),
    ATPT = VSTPT,
    ATPTN = VSTPTNUM
  )

count(advs, VISITNUM, VISIT, AVISITN, AVISIT)
#> # A tibble: 15 × 5
#>    VISITNUM VISIT               AVISITN AVISIT       n
#>       <dbl> <chr>                 <dbl> <chr>    <int>
#>  1      1   SCREENING 1              NA <NA>       102
#>  2      2   SCREENING 2              NA <NA>        78
#>  3      3   BASELINE                  0 Baseline    96
#>  4      3.5 AMBUL ECG PLACEMENT      NA <NA>        65
#>  5      4   WEEK 2                    2 Week 2      96
#>  6      5   WEEK 4                    4 Week 4      80
#>  7      6   AMBUL ECG REMOVAL        NA <NA>        52
#>  8      7   WEEK 6                    6 Week 6      48
#>  9      8   WEEK 8                    8 Week 8      48
#> 10      9   WEEK 12                  12 Week 12     48
#> 11     10   WEEK 16                  16 Week 16     48
#> 12     11   WEEK 20                  20 Week 20     32
#> 13     12   WEEK 24                  24 Week 24     32
#> 14     13   WEEK 26                  26 Week 26     32
#> 15    201   RETRIEVAL                NA <NA>        26

count(advs, VSTPTNUM, VSTPT, ATPTN, ATPT)
#> # A tibble: 4 × 5
#>   VSTPTNUM VSTPT                          ATPTN ATPT                           n
#>      <dbl> <chr>                          <dbl> <chr>                      <int>
#> 1      815 AFTER LYING DOWN FOR 5 MINUTES   815 AFTER LYING DOWN FOR 5 MI…   232
#> 2      816 AFTER STANDING FOR 1 MINUTE      816 AFTER STANDING FOR 1 MINU…   232
#> 3      817 AFTER STANDING FOR 3 MINUTES     817 AFTER STANDING FOR 3 MINU…   232
#> 4       NA <NA>                              NA <NA>                         187

For assigning visits based on time windows and deriving periods, subperiods, and phase variables see the “Visit and Period Variables” vignette.

Timing Flag Variables (e.g. ONTRTFL)

In some analyses, it may be necessary to flag an observation as on-treatment. The admiral function derive_var_ontrtfl() can be used.

For example, if on-treatment is defined as any observation between treatment start and treatment end, the flag may be derived as:

advs <- derive_var_ontrtfl(
  advs,
  start_date = ADT,
  ref_start_date = TRTSDT,
  ref_end_date = TRTEDT
)
USUBJID PARAMCD ADT TRTSDT TRTEDT ONTRTFL
01-701-1015 DIABP 2014-01-16 2014-01-02 2014-07-02 Y
01-701-1015 DIABP 2014-01-16 2014-01-02 2014-07-02 Y
01-701-1015 DIABP 2014-01-16 2014-01-02 2014-07-02 Y
01-701-1023 DIABP 2012-08-27 2012-08-05 2012-09-01 Y
01-701-1023 DIABP 2012-08-27 2012-08-05 2012-09-01 Y
01-701-1023 DIABP 2012-08-27 2012-08-05 2012-09-01 Y
01-703-1086 DIABP 2012-09-16 2012-09-02 2012-12-04 Y
01-703-1086 DIABP 2012-09-16 2012-09-02 2012-12-04 Y
01-703-1086 DIABP 2012-09-16 2012-09-02 2012-12-04 Y
01-703-1096 DIABP 2013-02-09 2013-01-25 2013-03-16 Y

This function returns the original data frame with the column ONTRTFL added. Additionally, this function does have functionality to handle a window on the ref_end_date. For example, if on-treatment is defined as between treatment start and treatment end plus 60 days, the call would be:

advs <- derive_var_ontrtfl(
  advs,
  start_date = ADT,
  ref_start_date = TRTSDT,
  ref_end_date = TRTEDT,
  ref_end_window = 60
)

In addition, the function does allow you to filter out pre-treatment observations that occurred on the start date. For example, if observations with VSTPT == PRE should not be considered on-treatment when the observation date falls between the treatment start and end date, the user may specify this using the filter_pre_timepoint parameter:

advs <- derive_var_ontrtfl(
  advs,
  start_date = ADT,
  ref_start_date = TRTSDT,
  ref_end_date = TRTEDT,
  filter_pre_timepoint = ATPT == "AFTER LYING DOWN FOR 5 MINUTES"
)

Lastly, the function does allow you to create any on-treatment flag based on the analysis needs. For example, if variable ONTR01FL is needed, showing the on-treatment flag during Period 01, you need to set new var = ONTR01FL. In addition, for Period 01 Start Date and Period 01 End Date, you need ref_start_date = AP01SDT and ref_end_date = AP01EDT.

advs <- derive_var_ontrtfl(
  advs,
  new_var = ONTR01FL,
  start_date = ASTDT,
  end_date = AENDT,
  ref_start_date = AP01SDT,
  ref_end_date = AP01EDT,
  span_period = TRUE
)
USUBJID ASTDT AENDT AP01SDT AP01EDT ONTR01FL
P01 2020-03-15 2020-12-01 2020-01-01 2020-03-01 NA
P02 2019-04-30 2020-03-15 2020-01-01 2020-03-01 Y
P03 2019-04-30 NA 2020-01-01 2020-03-01 Y

Assign Reference Range Indicator (ANRIND)

The admiral function derive_var_anrind() may be used to derive the reference range indicator ANRIND.

This function requires the reference range boundaries to exist on the data frame (ANRLO, ANRHI) and also accommodates the additional boundaries A1LO and A1HI.

The function is called as:

advs <- derive_var_anrind(advs)
USUBJID PARAMCD AVAL ANRLO ANRHI A1LO A1HI ANRIND
01-701-1015 DIABP 56 60 80 40 90 LOW
01-701-1015 DIABP 50 60 80 40 90 LOW
01-701-1015 DIABP 54 60 80 40 90 LOW
01-701-1023 DIABP 88 60 80 40 90 HIGH
01-701-1023 DIABP 86 60 80 40 90 HIGH
01-701-1023 DIABP 90 60 80 40 90 HIGH
01-703-1086 DIABP 68 60 80 40 90 NORMAL
01-703-1086 DIABP 74 60 80 40 90 NORMAL
01-703-1086 DIABP 70 60 80 40 90 NORMAL
01-703-1096 DIABP 74 60 80 40 90 NORMAL

Derive Baseline (BASETYPE, ABLFL, BASE, BASEC, BNRIND)

The BASETYPE should be derived using the function derive_basetype_records(). The parameter basetypes of this function requires a named list of expression detailing how the BASETYPE should be assigned. Note, if a record falls into multiple expressions within the basetypes expression, a row will be produced for each BASETYPE.

advs <- derive_basetype_records(
  dataset = advs,
  basetypes = exprs(
    "LAST: AFTER LYING DOWN FOR 5 MINUTES" = ATPTN == 815,
    "LAST: AFTER STANDING FOR 1 MINUTE" = ATPTN == 816,
    "LAST: AFTER STANDING FOR 3 MINUTES" = ATPTN == 817,
    "LAST" = is.na(ATPTN)
  )
)

count(advs, ATPT, ATPTN, BASETYPE)
#> # A tibble: 4 × 4
#>   ATPT                           ATPTN BASETYPE                                n
#>   <chr>                          <dbl> <chr>                               <int>
#> 1 AFTER LYING DOWN FOR 5 MINUTES   815 LAST: AFTER LYING DOWN FOR 5 MINUT…   232
#> 2 AFTER STANDING FOR 1 MINUTE      816 LAST: AFTER STANDING FOR 1 MINUTE     232
#> 3 AFTER STANDING FOR 3 MINUTES     817 LAST: AFTER STANDING FOR 3 MINUTES    232
#> 4 <NA>                              NA LAST                                  187

It is important to derive BASETYPE first so that it can be utilized in subsequent derivations. This will be important if the data frame contains multiple values for BASETYPE.

Next, the analysis baseline flag ABLFL can be derived using the {admiral} function derive_var_extreme_flag(). For example, if baseline is defined as the last non-missing AVAL prior or on TRTSDT, the function call for ABLFL would be:

advs <- restrict_derivation(
  advs,
  derivation = derive_var_extreme_flag,
  args = params(
    by_vars = exprs(STUDYID, USUBJID, BASETYPE, PARAMCD),
    order = exprs(ADT, ATPTN, VISITNUM),
    new_var = ABLFL,
    mode = "last"
  ),
  filter = (!is.na(AVAL) & ADT <= TRTSDT & !is.na(BASETYPE))
)
USUBJID BASETYPE PARAMCD ADT TRTSDT ATPTN ABLFL
01-701-1015 LAST: AFTER LYING DOWN FOR 5 MINUTES DIABP 2014-01-02 2014-01-02 815 Y
01-701-1015 LAST: AFTER STANDING FOR 1 MINUTE DIABP 2014-01-02 2014-01-02 816 Y
01-701-1015 LAST: AFTER STANDING FOR 3 MINUTES DIABP 2014-01-02 2014-01-02 817 Y
01-701-1023 LAST: AFTER LYING DOWN FOR 5 MINUTES DIABP 2012-08-05 2012-08-05 815 Y
01-701-1023 LAST: AFTER STANDING FOR 1 MINUTE DIABP 2012-08-05 2012-08-05 816 Y
01-701-1023 LAST: AFTER STANDING FOR 3 MINUTES DIABP 2012-08-05 2012-08-05 817 Y
01-703-1086 LAST: AFTER LYING DOWN FOR 5 MINUTES DIABP 2012-09-02 2012-09-02 815 Y
01-703-1086 LAST: AFTER STANDING FOR 1 MINUTE DIABP 2012-09-02 2012-09-02 816 Y
01-703-1086 LAST: AFTER STANDING FOR 3 MINUTES DIABP 2012-09-02 2012-09-02 817 Y
01-703-1096 LAST: AFTER LYING DOWN FOR 5 MINUTES DIABP 2013-01-25 2013-01-25 815 Y

Note: Additional examples of the derive_var_extreme_flag() function can be found above.

Lastly, the BASE, BASEC and BNRIND columns can be derived using the {admiral} function derive_var_base(). Example calls are:

advs <- derive_var_base(
  advs,
  by_vars = exprs(STUDYID, USUBJID, PARAMCD, BASETYPE),
  source_var = AVAL,
  new_var = BASE
)

advs <- derive_var_base(
  advs,
  by_vars = exprs(STUDYID, USUBJID, PARAMCD, BASETYPE),
  source_var = AVALC,
  new_var = BASEC
)

advs <- derive_var_base(
  advs,
  by_vars = exprs(STUDYID, USUBJID, PARAMCD, BASETYPE),
  source_var = ANRIND,
  new_var = BNRIND
)
USUBJID BASETYPE PARAMCD ABLFL BASE BASEC ANRIND BNRIND
01-701-1015 LAST: AFTER LYING DOWN FOR 5 MINUTES DIABP Y 56 56 LOW LOW
01-701-1015 LAST: AFTER STANDING FOR 1 MINUTE DIABP Y 51 51 LOW LOW
01-701-1015 LAST: AFTER STANDING FOR 3 MINUTES DIABP Y 61 61 NORMAL NORMAL
01-701-1023 LAST: AFTER LYING DOWN FOR 5 MINUTES DIABP Y 84 84 HIGH HIGH
01-701-1023 LAST: AFTER STANDING FOR 1 MINUTE DIABP Y 86 86 HIGH HIGH
01-701-1023 LAST: AFTER STANDING FOR 3 MINUTES DIABP Y 88 88 HIGH HIGH
01-703-1086 LAST: AFTER LYING DOWN FOR 5 MINUTES DIABP Y 80 80 NORMAL NORMAL
01-703-1086 LAST: AFTER STANDING FOR 1 MINUTE DIABP Y 82 82 HIGH HIGH
01-703-1086 LAST: AFTER STANDING FOR 3 MINUTES DIABP Y 72 72 NORMAL NORMAL
01-703-1096 LAST: AFTER LYING DOWN FOR 5 MINUTES DIABP Y 70 70 NORMAL NORMAL

Derive Change from Baseline (CHG, PCHG)

Change and percent change from baseline can be derived using the {admiral} functions derive_var_chg() and derive_var_pchg(). These functions expect AVAL and BASE to exist in the data frame. The CHG is simply AVAL - BASE and the PCHG is (AVAL - BASE) / absolute value (BASE) * 100. Examples calls are:

advs <- derive_var_chg(advs)

advs <- derive_var_pchg(advs)
USUBJID VISIT BASE AVAL CHG PCHG
01-701-1015 WEEK 2 56 56 0 0.000000
01-701-1015 WEEK 8 56 67 11 19.642857
01-701-1023 WEEK 2 84 88 4 4.761905
01-703-1086 WEEK 2 80 68 -12 -15.000000
01-703-1086 WEEK 8 80 80 0 0.000000
01-703-1096 WEEK 2 70 74 4 5.714286
01-707-1037 WEEK 2 88 72 -16 -18.181818
01-716-1024 WEEK 2 80 86 6 7.500000
01-716-1024 WEEK 8 80 78 -2 -2.500000
01-701-1015 WEEK 2 51 50 -1 -1.960784

If the variables should not be derived for all records, e.g., for post-baseline records only, restrict_derivation() can be used.

Derive Shift (e.g. SHIFT1)

Shift variables can be derived using the {admiral} function derive_var_shift(). This function derives a character shift variable concatenating shift in values based on a user-defined pairing, e.g., shift from baseline reference range BNRIND to analysis reference range ANRIND. Examples calls are:

advs <- derive_var_shift(advs,
  new_var = SHIFT1,
  from_var = BNRIND,
  to_var = ANRIND
)

If the variables should not be derived for all records, e.g., for post-baseline records only, restrict_derivation() can be used.

Derive Analysis Ratio (R2BASE)

Analysis ratio variables can be derived using the {admiral} function derive_var_analysis_ratio(). This function derives a ratio variable based on user-specified pair. For example, Ratio to Baseline is calculated by AVAL / BASE and the function appends a new variable R2BASE to the dataset. Examples calls are:

advs <- derive_var_analysis_ratio(advs,
  numer_var = AVAL,
  denom_var = BASE
)

advs <- derive_var_analysis_ratio(advs,
  numer_var = AVAL,
  denom_var = ANRLO,
  new_var = R01ANRLO
)
USUBJID VISIT BASE AVAL ANRLO R2BASE R01ANRLO
01-701-1015 WEEK 2 56 56 60 1.0000000 0.9333333
01-701-1015 WEEK 8 56 67 60 1.1964286 1.1166667
01-701-1023 WEEK 2 84 88 60 1.0476190 1.4666667
01-703-1086 WEEK 2 80 68 60 0.8500000 1.1333333
01-703-1086 WEEK 8 80 80 60 1.0000000 1.3333333
01-703-1096 WEEK 2 70 74 60 1.0571429 1.2333333
01-707-1037 WEEK 2 88 72 60 0.8181818 1.2000000
01-716-1024 WEEK 2 80 86 60 1.0750000 1.4333333
01-716-1024 WEEK 8 80 78 60 0.9750000 1.3000000
01-701-1015 WEEK 2 51 50 60 0.9803922 0.8333333

If the variables should not be derived for all records, e.g., for post-baseline records only, restrict_derivation() can be used.

Derive Analysis Flags (e.g. ANL01FL)

In most finding ADaMs, an analysis flag is derived to identify the appropriate observation(s) to use for a particular analysis when a subject has multiple observations within a particular timing period.

In this situation, an analysis flag (e.g. ANLxxFL) may be used to choose the appropriate record for analysis.

This flag may be derived using the {admiral} function derive_var_extreme_flag(). For this example, we will assume we would like to choose the latest and highest value by USUBJID, PARAMCD, AVISIT, and ATPT.

advs <- restrict_derivation(
  advs,
  derivation = derive_var_extreme_flag,
  args = params(
    by_vars = exprs(STUDYID, USUBJID, BASETYPE, PARAMCD, AVISIT),
    order = exprs(ADT, ATPTN, AVAL),
    new_var = ANL01FL,
    mode = "last"
  ),
  filter = !is.na(AVISITN)
)
USUBJID PARAMCD AVISIT ATPTN ADT AVAL ANL01FL
01-701-1015 DIABP Week 2 815 2014-01-16 56 Y
01-701-1015 DIABP Week 8 815 2014-03-05 67 Y
01-701-1015 DIABP Week 2 816 2014-01-16 50 Y
01-701-1015 DIABP Week 8 816 2014-03-05 62 Y
01-701-1015 DIABP Week 2 817 2014-01-16 54 Y
01-701-1015 DIABP Week 8 817 2014-03-05 71 Y
01-701-1023 DIABP Week 2 815 2012-08-27 88 Y
01-701-1023 DIABP Week 2 816 2012-08-27 86 Y
01-701-1023 DIABP Week 2 817 2012-08-27 90 Y
01-703-1086 DIABP Week 2 815 2012-09-16 68 Y

Another common example would be flagging the worst value for a subject, parameter, and visit. For this example, we will assume we have 3 PARAMCD values (SYSBP, DIABP, and RESP). We will also assume high is worst for SYSBP and DIABP and low is worst for RESP.

advs <- slice_derivation(
  advs,
  derivation = derive_var_extreme_flag,
  args = params(
    by_vars = exprs(STUDYID, USUBJID, BASETYPE, PARAMCD, AVISIT),
    order = exprs(ADT, ATPTN),
    new_var = WORSTFL,
    mode = "first"
  ),
  derivation_slice(
    filter = PARAMCD %in% c("SYSBP", "DIABP") & (!is.na(AVISIT) & !is.na(AVAL))
  ),
  derivation_slice(
    filter = PARAMCD %in% "PULSE" & (!is.na(AVISIT) & !is.na(AVAL)),
    args = params(mode = "last")
  )
) %>%
  arrange(STUDYID, USUBJID, BASETYPE, PARAMCD, AVISIT)
USUBJID PARAMCD AVISIT AVAL ADT ATPTN WORSTFL
01-701-1015 DIABP Baseline 56 2014-01-02 815 Y
01-701-1015 DIABP Week 12 68 2014-03-26 815 Y
01-701-1015 DIABP Week 16 66 2014-05-07 815 Y
01-701-1015 DIABP Week 2 56 2014-01-16 815 Y
01-701-1015 DIABP Week 20 67 2014-05-21 815 Y
01-701-1015 DIABP Week 24 63 2014-06-18 815 Y
01-701-1015 DIABP Week 26 61 2014-07-02 815 Y
01-701-1015 DIABP Week 4 64 2014-01-30 815 Y
01-701-1015 DIABP Week 6 55 2014-02-12 815 Y
01-701-1015 DIABP Week 8 67 2014-03-05 815 Y

Assign Treatment (TRTA, TRTP)

TRTA and TRTP must match at least one value of the character treatment variables in ADSL (e.g., TRTxxA/TRTxxP, TRTSEQA/TRTSEQP, TRxxAGy/TRxxPGy).

An example of a simple implementation for a study without periods could be:

advs <- mutate(advs, TRTP = TRT01P, TRTA = TRT01A)

count(advs, TRTP, TRTA, TRT01P, TRT01A)
#> # A tibble: 2 × 5
#>   TRTP                TRTA                TRT01P              TRT01A           n
#>   <chr>               <chr>               <chr>               <chr>        <int>
#> 1 Placebo             Placebo             Placebo             Placebo        640
#> 2 Xanomeline Low Dose Xanomeline Low Dose Xanomeline Low Dose Xanomeline …   243

For studies with periods see the “Visit and Period Variables” vignette.

Assign ASEQ

The {admiral} function derive_var_obs_number() can be used to derive ASEQ. An example call is:

advs <- derive_var_obs_number(
  advs,
  new_var = ASEQ,
  by_vars = exprs(STUDYID, USUBJID),
  order = exprs(PARAMCD, ADT, AVISITN, VISITNUM, ATPTN),
  check_type = "error"
)
USUBJID PARAMCD ADT AVISITN ATPTN VISIT ASEQ
01-701-1015 BMI 2013-12-26 NA NA SCREENING 1 1
01-701-1015 BMI 2014-01-02 0 NA BASELINE 2
01-701-1015 BMI 2014-01-16 2 NA WEEK 2 3
01-701-1015 BMI 2014-01-30 4 NA WEEK 4 4
01-701-1015 BMI 2014-02-12 6 NA WEEK 6 5
01-701-1015 BMI 2014-03-05 8 NA WEEK 8 6
01-701-1015 BMI 2014-03-26 12 NA WEEK 12 7
01-701-1015 BMI 2014-05-07 16 NA WEEK 16 8
01-701-1015 BMI 2014-05-21 20 NA WEEK 20 9
01-701-1015 BMI 2014-06-18 24 NA WEEK 24 10

Derive Categorization Variables (AVALCATx)

Admiral does not currently have a generic function to aid in assigning AVALCATx/ AVALCAxN values. Below is a simple example of how these values may be assigned:

avalcat_lookup <- tibble::tribble(
  ~PARAMCD, ~AVALCA1N, ~AVALCAT1,
  "HEIGHT",         1, ">140 cm",
  "HEIGHT",         2, "<= 140 cm"
)

format_avalcat1n <- function(param, aval) {
  case_when(
    param == "HEIGHT" & aval > 140 ~ 1,
    param == "HEIGHT" & aval <= 140 ~ 2
  )
}

advs <- advs %>%
  mutate(AVALCA1N = format_avalcat1n(param = PARAMCD, aval = AVAL)) %>%
  derive_vars_merged(
    avalcat_lookup,
    by = exprs(PARAMCD, AVALCA1N)
  )
USUBJID PARAMCD AVAL AVALCA1N AVALCAT1
01-701-1015 HEIGHT 147.32 1 >140 cm
01-701-1023 HEIGHT 162.56 1 >140 cm
01-703-1086 HEIGHT 195.58 1 >140 cm
01-703-1096 HEIGHT 160.02 1 >140 cm
01-707-1037 HEIGHT 152.40 1 >140 cm
01-716-1024 HEIGHT 154.94 1 >140 cm

Add ADSL variables

If needed, the other ADSL variables can now be added. List of ADSL variables already merged held in vector adsl_vars

advs <- advs %>%
  derive_vars_merged(
    dataset_add = select(adsl, !!!negate_vars(adsl_vars)),
    by_vars = exprs(STUDYID, USUBJID)
  )
USUBJID RFSTDTC RFENDTC DTHDTC DTHFL AGE AGEU
01-701-1015 2014-01-02 2014-07-02 NA NA 63 YEARS
01-701-1015 2014-01-02 2014-07-02 NA NA 63 YEARS
01-701-1015 2014-01-02 2014-07-02 NA NA 63 YEARS
01-701-1015 2014-01-02 2014-07-02 NA NA 63 YEARS
01-701-1015 2014-01-02 2014-07-02 NA NA 63 YEARS
01-701-1015 2014-01-02 2014-07-02 NA NA 63 YEARS
01-701-1015 2014-01-02 2014-07-02 NA NA 63 YEARS
01-701-1015 2014-01-02 2014-07-02 NA NA 63 YEARS
01-701-1015 2014-01-02 2014-07-02 NA NA 63 YEARS
01-701-1015 2014-01-02 2014-07-02 NA NA 63 YEARS

Derive New Rows

When deriving new rows for a data frame, it is essential the programmer takes time to insert this derivation in the correct location of the code. The location will vary depending on what previous computations should be retained on the new record and what computations must be done with the new records.

Example 1 (Creating a New Record):

To add a new record based on the selection of a certain criterion (e.g. minimum, maximum) derive_extreme_records() can be used. The new records include all variables of the selected records.

Adding a New Record for the Last Value

For each subject and Vital Signs parameter, add a record holding last valid observation before end of treatment. Set AVISIT to "End of Treatment" and assign a unique AVISITN value.

advs_ex1 <- advs %>%
  derive_extreme_records(
    dataset_add = advs,
    by_vars = exprs(STUDYID, USUBJID, PARAMCD),
    order = exprs(ADT, AVISITN, ATPTN, AVAL),
    mode = "last",
    filter_add = (4 < AVISITN & AVISITN <= 12 & ANL01FL == "Y"),
    set_values_to = exprs(
      AVISIT = "End of Treatment",
      AVISITN = 99,
      DTYPE = "LOV"
    )
  )
USUBJID PARAMCD ADT AVISITN AVISIT ATPTN AVAL DTYPE ANL01FL
01-701-1015 BMI 2014-03-26 99 End of Treatment NA 24.45264 LOV Y
01-701-1015 BMI 2014-07-02 26 Week 26 NA 24.65998 NA Y
01-701-1015 BMI 2014-06-18 24 Week 24 NA 24.45264 NA Y
01-701-1015 BMI 2014-05-21 20 Week 20 NA 24.45264 NA Y
01-701-1015 BMI 2014-05-07 16 Week 16 NA 24.45264 NA Y
01-701-1015 BMI 2014-03-26 12 Week 12 NA 24.45264 NA Y
01-701-1015 BMI 2014-03-05 8 Week 8 NA 24.45264 NA Y
01-701-1015 BMI 2014-02-12 6 Week 6 NA 24.45264 NA Y
01-701-1015 BMI 2014-01-30 4 Week 4 NA 24.87193 NA Y
01-701-1015 BMI 2014-01-16 2 Week 2 NA 24.45264 NA Y

Adding a New Record for the Minimum Value

For each subject and Vital Signs parameter, add a record holding the minimum value before end of treatment. If the minimum is attained by multiple observations the first one is selected. Set AVISIT to "Minimum on Treatment" and assign a unique AVISITN value.

advs_ex1 <- advs %>%
  derive_extreme_records(
    dataset_add = advs,
    by_vars = exprs(STUDYID, USUBJID, PARAMCD),
    order = exprs(AVAL, ADT, AVISITN, ATPTN),
    mode = "first",
    filter_add = (4 < AVISITN & AVISITN <= 12 & ANL01FL == "Y" & !is.na(AVAL)),
    set_values_to = exprs(
      AVISIT = "Minimum on Treatment",
      AVISITN = 98,
      DTYPE = "MINIMUM"
    )
  )
USUBJID PARAMCD ADT AVISITN AVISIT ATPTN AVAL DTYPE ANL01FL
01-701-1015 BMI 2014-02-12 98 Minimum on Treatment NA 24.45264 MINIMUM Y
01-701-1015 BMI 2014-07-02 26 Week 26 NA 24.65998 NA Y
01-701-1015 BMI 2014-06-18 24 Week 24 NA 24.45264 NA Y
01-701-1015 BMI 2014-05-21 20 Week 20 NA 24.45264 NA Y
01-701-1015 BMI 2014-05-07 16 Week 16 NA 24.45264 NA Y
01-701-1015 BMI 2014-03-26 12 Week 12 NA 24.45264 NA Y
01-701-1015 BMI 2014-03-05 8 Week 8 NA 24.45264 NA Y
01-701-1015 BMI 2014-02-12 6 Week 6 NA 24.45264 NA Y
01-701-1015 BMI 2014-01-30 4 Week 4 NA 24.87193 NA Y
01-701-1015 BMI 2014-01-16 2 Week 2 NA 24.45264 NA Y

Example 2 (Deriving a Summary Record)

For adding new records based on aggregating records derive_summary_records() can be used. For the new records only the variables specified by by_vars, analysis_var, and set_values_to are populated.

For each subject, Vital Signs parameter, visit, and date add a record holding the average value for observations on that date. Set DTYPE to AVERAGE.

advs_ex2 <- derive_summary_records(
  advs,
  dataset_add = advs,
  by_vars = exprs(STUDYID, USUBJID, PARAMCD, VISITNUM, ADT),
  set_values_to = exprs(
    AVAL = mean(AVAL, na.rm = TRUE),
    DTYPE = "AVERAGE"
  )
)
USUBJID PARAMCD VISITNUM ADT AVAL DTYPE
01-701-1015 BMI 1 2013-12-26 24.87193 AVERAGE
01-701-1015 BMI 1 2013-12-26 24.87193 NA
01-701-1015 BMI 3 2014-01-02 25.07927 AVERAGE
01-701-1015 BMI 3 2014-01-02 25.07927 NA
01-701-1015 BMI 4 2014-01-16 24.45264 AVERAGE
01-701-1015 BMI 4 2014-01-16 24.45264 NA
01-701-1015 BMI 5 2014-01-30 24.87193 AVERAGE
01-701-1015 BMI 5 2014-01-30 24.87193 NA
01-701-1015 BMI 7 2014-02-12 24.45264 AVERAGE
01-701-1015 BMI 7 2014-02-12 24.45264 NA

Example 3 (Deriving a New PARAMCD)

Use function derive_param_computed() to create a new PARAMCD. Note that only variables specified in the by_vars argument will be populated in the newly created records.

Below is an example of creating Mean Arterial Pressure (PARAMCD = MAP2) with an alternative formula.

advs_ex3 <- derive_param_computed(
  advs,
  by_vars = exprs(USUBJID, VISIT, ATPT),
  parameters = c("SYSBP", "DIABP"),
  set_values_to = exprs(
    AVAL = (AVAL.SYSBP - AVAL.DIABP) / 3 + AVAL.DIABP,
    PARAMCD = "MAP2",
    PARAM = "Mean Arterial Pressure 2 (mmHg)"
  )
)
USUBJID PARAMCD VISIT ATPT AVAL
01-701-1015 DIABP AMBUL ECG PLACEMENT AFTER LYING DOWN FOR 5 MINUTES 67.00000
01-701-1015 MAP2 AMBUL ECG PLACEMENT AFTER LYING DOWN FOR 5 MINUTES 90.33333
01-701-1015 SYSBP AMBUL ECG PLACEMENT AFTER LYING DOWN FOR 5 MINUTES 137.00000
01-701-1015 DIABP AMBUL ECG PLACEMENT AFTER STANDING FOR 1 MINUTE 61.00000
01-701-1015 MAP2 AMBUL ECG PLACEMENT AFTER STANDING FOR 1 MINUTE 83.00000
01-701-1015 SYSBP AMBUL ECG PLACEMENT AFTER STANDING FOR 1 MINUTE 127.00000
01-701-1015 DIABP AMBUL ECG PLACEMENT AFTER STANDING FOR 3 MINUTES 65.00000
01-701-1015 MAP2 AMBUL ECG PLACEMENT AFTER STANDING FOR 3 MINUTES 92.00000
01-701-1015 SYSBP AMBUL ECG PLACEMENT AFTER STANDING FOR 3 MINUTES 146.00000
01-701-1015 DIABP AMBUL ECG REMOVAL AFTER LYING DOWN FOR 5 MINUTES 72.00000

Add Labels and Attributes

Adding labels and attributes for SAS transport files is supported by the following packages:

NOTE: All these packages are in the experimental phase, but the vision is to have them associated with an End to End pipeline under the umbrella of the pharmaverse. An example of applying metadata and perform associated checks can be found at the pharmaverse E2E example.

Example Scripts

ADaM Sourcing Command
ADEG use_ad_template("ADEG")
ADVS use_ad_template("ADVS")
ADLB use_ad_template("ADLB")