Creating ADGA

Introduction

This article describes creating an ADGA ADaM dataset containing Geographic Atrophy (GA) data for ophthalmology endpoints. These endpoints are derived from Ophthalmic Examination (OE) records and include both observed and derived measurements for Geographic Atrophy. It is to be used in conjunction with the article on creating a BDS dataset from SDTM. As such, derivations and processes that are not specific to ADGA are absent, and the user is invited to consult the aforementioned article for guidance.

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

Dataset Contents

As the name ADGA implies, {admiralophtha} suggests to populate ADGA solely with GA records from the OE SDTM.

Acronyms

This vignette uses several ophthalmology-specific abbreviations related to Geographic Atrophy (GA) imaging. The table below summarizes the most commonly used terms.

Acronym Definition
ADGA Geographic Atrophy Analysis Dataset
FAF Fundus Autofluorescence
GA Geographic Atrophy
GAFLOC GA Foveal Location
SGAFOCAL Study Eye GA Lesion Focality
FGAFOCAL Fellow Eye GA Lesion Focality
SAREAFAF Study Eye GA Area Measured by FAF
FAREAFAF Fellow Eye GA Area Measured by FAF
SSQRTFAF Square Root Transformed Study Eye GA Area Measured by FAF
FSQRTFAF Square Root Transformed Fellow Eye GA Area Measured by FAF

The parameter names used throughout this vignette follow the nomenclature adopted by {admiralophtha} examples, where the prefixes S and F denote the study eye and fellow eye, respectively.

Required Packages

The examples of this vignette require the following packages.

library(dplyr)
library(admiral)
library(pharmaversesdtm)
library(admiraldev)
library(admiralophtha)
library(stringr)

Programming Workflow

Initial set up of ADGA

As with all BDS ADaM datasets, one should start from the OE SDTM, where only records related to geographic atrophy are of interest. For the purposes of the next two sections, we shall be using the {admiral} OE and ADSL test data. We will also require a lookup table for the mapping of parameter codes. An SAREAFAF, FAREAFAF, SGAFOCAL and FGAFOCAL definition expression is created first - this expression can also be stored within another (sourced) program.

Note: to simulate an ophthalmology study, we add a randomly generated STUDYEYE variable to ADSL, but in practice STUDYEYE will already have been derived using derive_var_studyeye().

data("oe_ophtha")
data("admiral_adsl")

# Add STUDYEYE to ADSL to simulate an ophtha dataset
# Set seed to ensure reproducible STUDYEYE assignment across runs
set.seed(1234)

adsl <- admiral_adsl %>%
  as.data.frame() %>%
  mutate(STUDYEYE = sample(c("LEFT", "RIGHT"), n(), replace = TRUE)) %>%
  convert_blanks_to_na()

oe <- convert_blanks_to_na(oe_ophtha)

# ---- Lookup tables ----

# Assign PARAMCD, PARAM, and PARAMN
# nolint start
param_lookup <- tibble::tribble(
  ~OETESTCD, ~OECAT, ~OESCAT, ~AFEYE, ~PARAMCD, ~PARAM, ~PARAMN,
  "AREA", "OPHTHALMIC ASSESSMENTS", NA_character_, "Study Eye", "SAREAFAF", "Study Eye GA Area measured by FAF (mm2)", 1,
  "AREA", "OPHTHALMIC ASSESSMENTS", NA_character_, "Fellow Eye", "FAREAFAF", "Fellow Eye GA Area measured by FAF (mm2)", 2,
  "GAFLOC", "OPHTHALMIC ASSESSMENTS", NA_character_, "Study Eye", "SGAFOCAL", "Study Eye GA Lesion Focality", 3,
  "GAFLOC", "OPHTHALMIC ASSESSMENTS", NA_character_, "Fellow Eye", "FGAFOCAL", "Fellow Eye GA Lesion Focality", 4
)
# nolint end

Following this setup, the programmer can start constructing ADGA. The first step is to subset OE to only GA parameters like “AREA”, “GAFLOC” and merge with ADSL. This is required for two reasons: firstly, STUDYEYE is crucial in the mapping of AFEYE and PARAMCD’s. Secondly, the treatment start date (TRTSDT) is also a prerequisite for the derivation of variables such as Analysis Day (ADY).

# Get list of ADSL vars required for derivations
adsl_vars <- exprs(TRTSDT, TRTEDT, TRT01A, TRT01P, STUDYEYE)

adga <- oe %>%
  # Keep only GA related OE parameters
  filter(
    OETESTCD %in% c("AREA", "GAFLOC")
  ) %>%
  # Join ADSL with OE (need TRTSDT and STUDYEYE for ADY, AFEYE, and PARAMCD derivation)
  derive_vars_merged(
    dataset_add = adsl,
    new_vars = adsl_vars,
    by_vars = get_admiral_option("subject_keys")
  )

The next item of business is to derive AVAL,AVALC, AVALU, and DTYPE. In this example, derivation for AVALC is trivial, whereas for lesion focality, AVAL is derived using a combination of OE character standard results and decoded to 1 for S (single), and 2 for NS (multifocal). AFEYE is also created in this step using the function derive_var_afeye(). This variable identifies the analysis eye based on the OELOC, and OELAT information. In this example, "RETINA" is included in loc_vals because the GA assessments are recorded as retinal observations. Including both "EYE" and "RETINA" ensures that OELAT information is correctly carried forward for all ophthalmic assessments.

adga <- adga %>%
  # Calculate AVAL, AVALC, AVALU and DTYPE
  mutate(
    AVAL = case_when(
      OETESTCD == "GAFLOC" & OESTRESC == "S" ~ 1,
      OETESTCD == "GAFLOC" & OESTRESC == "NS" ~ 2,
      TRUE ~ OESTRESN
    ),
    AVALC = OESTRESC,
    AVALU = OESTRESU,
    DTYPE = NA_character_
  ) %>%
  # Derive AFEYE needed for PARAMCD derivation
  derive_var_afeye(loc_var = OELOC, lat_var = OELAT, loc_vals = c("EYE", "RETINA"))
USUBJID OETESTCD AVAL AVALC AVALU AFEYE
01-701-1028 AREA 10.55496 10.554957 mm2 Fellow Eye
01-701-1028 AREA 31.02957 31.029568 mm2 Study Eye
01-701-1028 GAFLOC 1.00000 S NA Fellow Eye
01-701-1028 GAFLOC 2.00000 NS NA Study Eye

Creating Square Root Transformed GA Area measured by FAF derived parameter

Next, PARAM, PARAMCD, and PARAMN are assigned using derive_vars_merged_lookup() from {admiral} together with the lookup table param_lookup generated above. Using a lookup table separates study-specific parameter metadata from the derivation code and provides an additional validation step. Since all records included in ADGA are expected to map to a defined analysis parameter, any not mapped records may indicate missing parameter metadata, unexpected source data, or an issue in an upstream derivation such as AFEYE. Therefore, check_not_mapped_type = “warning” is specified to issue a warning if any records are not mapped.

ADGA datasets often contain square root transformed GA records for FAF with units as millimeters (mm). In GA studies, square-root transformed lesion area is commonly used to reduce the dependence of lesion growth on baseline lesion size. Therefore, additional derived parameters are often included in ADGA representing the square root of the measured lesion area.

These derived parameters can be created using a single call to call_derivation() with derive_param_computed(). Using a single call is more efficient because the same grouping variables and source data are used for both study-eye and fellow-eye derivations. The parameter specifications differ only in the source parameter (SAREAFAF versus FAREAFAF), assigned parameter metadata (PARAM, PARAMCD, and PARAMN) and derived results (AVAL, and AVALC).

Note that the original lesion area measurements are recorded in square millimeters (mm²). Applying the square-root transformation changes the units from square millimeters (mm²) to millimeters (mm), since sqrt(mm²) = mm. Therefore, the derived parameters use mm as the analysis unit (AVALU).

Once these extra parameters are added, all the records that will be in the end dataset are now present, so day/date variables such as ADY and ADT can be derived.

adga <- adga %>%
  # Add PARAM, PARAMCD, PARAMN
  derive_vars_merged_lookup(
    dataset_add = param_lookup,
    new_vars = exprs(PARAM, PARAMCD, PARAMN),
    by_vars = exprs(OETESTCD, AFEYE)
  ) %>%
  # Add derived parameters for Square Root Transformed GA Area measured by FAF
  call_derivation(
    derivation = derive_param_computed,
    by_vars = c(
      get_admiral_option("subject_keys"),
      exprs(VISIT, VISITNUM, OEDY, OEDTC, AFEYE, !!!adsl_vars)
    ),
    variable_params = list(
      # Study eye
      params(
        # Users may need to update this code to identify the correct records to use.
        parameters = exprs(SESQRT = PARAMCD == "SAREAFAF"),
        set_values_to = exprs(
          PARAMCD = "SSQRTFAF",
          PARAM = "Study Eye Square Root Transformed GA Area measured by FAF (mm)",
          PARAMN = 5,
          AVAL = sqrt(AVAL.SESQRT),
          AVALC = as.character(AVAL),
          AVALU = "mm",
          DTYPE = "SQRT"
        )
      ),
      # Fellow eye
      params(
        # Users may need to update this code to identify the correct records to use.
        parameters = exprs(FESQRT = PARAMCD == "FAREAFAF"),
        set_values_to = exprs(
          PARAMCD = "FSQRTFAF",
          PARAM = "Fellow Eye Square Root Transformed GA Area measured by FAF (mm)",
          PARAMN = 6,
          AVAL = sqrt(AVAL.FESQRT),
          AVALC = as.character(AVAL),
          AVALU = "mm",
          DTYPE = "SQRT"
        )
      )
    )
  ) %>%
  # Calculate ADT, ADY
  derive_vars_dt(
    new_vars_prefix = "A",
    dtc = OEDTC,
    flag_imputation = "none"
  ) %>%
  derive_vars_dy(reference_date = TRTSDT, source_vars = exprs(ADT))
#> All `OETESTCD` and `AFEYE` are mapped.
USUBJID PARAMCD AVAL AVALC DTYPE ADT ADY
01-701-1028 FAREAFAF 10.554957 10.554957 NA 2013-07-19 1
01-701-1028 SAREAFAF 31.029568 31.029568 NA 2013-07-19 1
01-701-1028 FGAFOCAL 1.000000 S NA 2013-07-19 1
01-701-1028 SGAFOCAL 2.000000 NS NA 2013-07-19 1
01-701-1028 SSQRTFAF 5.570419 5.57041901476002 SQRT 2013-07-19 1
01-701-1028 FSQRTFAF 3.248839 3.24883933120738 SQRT 2013-07-19 1

Importantly, the above calls to derive_param_computed() list the SDTM variables VISIT, VISITNUM, OEDY and OEDTC as by_vars for the function. This is because they will be necessary to derive ADaM variables such as AVISIT and ADY in successive steps.

Assigning AVISIT/AVISITN

Moving forwards, AVISIT, AVISITN, and related timepoint variables can be derived soon after, though their derivation is generally study-specific. A simple option is included below; please consult the {admiral} BDS findings vignette for a more detailed discussion.

adga <- adga %>%
  # Derive visit info and BASETYPE
  mutate(
    ATPTN = OETPTNUM,
    ATPT = OETPT,
    AVISIT = case_when(
      str_detect(VISIT, "SCREEN") ~ "Screening",
      !is.na(VISIT) ~ str_to_title(VISIT),
      TRUE ~ NA_character_
    ),
    AVISITN = round(VISITNUM, 0),
    ABLFL = if_else(AVISIT == "Baseline", "Y", NA_character_)
    # In actual studies, ABLFL derivation will likely be more nuanced
    # and leverage derive_var_extreme_flag()
  )
USUBJID PARAMCD AVISIT AVAL
01-701-1028 FAREAFAF Baseline 10.554957
01-701-1028 SAREAFAF Baseline 31.029568
01-701-1028 FGAFOCAL Baseline 1.000000
01-701-1028 SGAFOCAL Baseline 2.000000
01-701-1028 SSQRTFAF Baseline 5.570419
01-701-1028 FSQRTFAF Baseline 3.248839
01-701-1028 FAREAFAF Week 4 3.103899
01-701-1028 SAREAFAF Week 4 28.483511
01-701-1028 FGAFOCAL Week 4 1.000000
01-701-1028 SGAFOCAL Week 4 2.000000

Deriving Worst Post-Baseline Assessment Flag

For some analyses, it may be useful to identify the worst observed lesion size for each parameter across baseline and post-baseline assessments. The ADaM variable WORS01FL can be used for this purpose. In the example below, derive_var_extreme_flag() is used to flag the record with the highest observed value (AVAL) within each subject, parameter, and timepoint combination (PARAMCD, ATPT). Since disease progression in geographic atrophy is reflected by increasing lesion area, the highest observed FAF area (or square-root transformed FAF area) is considered the worst result.

The derivation is applied only to the quantitative FAF area parameters (SAREAFAF, FAREAFAF) and their square-root transformed counterparts (SSQRTFAF, FSQRTFAF). Categorical focality parameters (SGAFOCAL, FGAFOCAL) are excluded, as the concept of a worst numeric result is not applicable.

The order argument specifies that records are ranked first by AVAL and then by ADT. With mode = "last", the flag is assigned to the record with the largest value, and if multiple records share the same value, the latest assessment date is selected. If a parameter is instead interpreted such that lower values represent worse outcomes, mode = "first" can be used.

# WORS01FL: Flag worst result within a PARAMCD for baseline & post-baseline records
# If worst result is lowest result, change mode to "first"
adga <- adga %>%
  restrict_derivation(
    derivation = derive_var_extreme_flag,
    args = params(
      by_vars = c(get_admiral_option("subject_keys"), exprs(PARAMCD, ATPT)),
      order = exprs(AVAL, ADT),
      new_var = WORS01FL,
      mode = "last"
    ),
    filter = !is.na(AVISITN) & !(AVISITN) == 1 & PARAMCD %in% c("SSQRTFAF", "FSQRTFAF", "FAREAFAF", "SAREAFAF")
    # In actual studies, ONTRTFL == "Y" | ABLFL == "Y" will be used to consider baseline and post baseline assessments
  )

The resulting output is shown below (limited to the one patient and one parameter only):

USUBJID PARAMCD AVAL AVISIT ADT ATPT WORS01FL
01-701-1028 FSQRTFAF 5.590724 Screening 2013-07-11 NA NA
01-701-1028 FSQRTFAF 3.248839 Baseline 2013-07-19 NA NA
01-701-1028 FSQRTFAF 1.761789 Week 4 2013-08-14 NA NA
01-701-1028 FSQRTFAF 6.309217 Week 6 2013-08-29 NA Y
01-701-1028 FSQRTFAF 4.268653 Week 8 2013-09-10 NA NA
01-701-1028 FSQRTFAF 5.594640 Week 10 (T) 2013-09-24 NA NA
01-701-1028 FSQRTFAF 1.183439 Week 12 2013-10-09 NA NA
01-701-1028 FSQRTFAF 1.693498 Week 16 2013-11-06 NA NA
01-701-1028 FSQRTFAF 3.712640 Week 20 2013-12-04 NA NA
01-701-1028 FSQRTFAF 1.620306 Week 24 2014-01-06 NA NA

Additionally, it should be noted that SGAFOCAL and FGAFOCAL represent categorical assessments of lesion focality. Although these parameters are mapped to numeric values in AVAL for consistency with ADaM conventions, deriving numeric change metrics such as CHG and PCHG is generally not meaningful. Therefore, BASE is not derived for these parameters.

However, baseline character values may still be clinically informative. For this reason, BASEC can be derived from AVALC for SGAFOCAL and FGAFOCAL, alongside the area-based FAF parameters. This can be achieved by using restrict_derivation() to selectively apply derive_var_base() based on parameter type. In the example below, BASE is derived only for parameters where numeric change-from-baseline analyses are appropriate, while SGAFOCAL and FGAFOCAL are excluded. Since BASE remains missing for SGAFOCAL and FGAFOCAL, subsequent calls to derive_var_chg() derive_var_pchg() will automatically leave CHG and PCHG missing for these parameters.

adga <- adga %>%
  # Calculate BASE (do not derive for GA Lesion Focality params)
  restrict_derivation(
    derivation = derive_var_base,
    args = params(
      by_vars = c(get_admiral_option("subject_keys"), exprs(PARAMCD, ATPT)),
      source_var = AVAL,
      new_var = BASE
    ),
    filter = !PARAMCD %in% c("SGAFOCAL", "FGAFOCAL")
  ) %>%
  # Calculate BASEC
  restrict_derivation(
    derivation = derive_var_base,
    args = params(
      by_vars = c(get_admiral_option("subject_keys"), exprs(PARAMCD, ATPT)),
      source_var = AVALC,
      new_var = BASEC
    ),
    filter = PARAMCD %in% c("SGAFOCAL", "FGAFOCAL", "SSQRTFAF", "FSQRTFAF", "FAREAFAF", "SAREAFAF")
  )
USUBJID PARAMCD AVISIT BASE BASEC
01-701-1015 FAREAFAF Screening 25.188860 25.18886
01-701-1015 FAREAFAF Baseline 25.188860 25.18886
01-701-1015 FAREAFAF Week 4 25.188860 25.18886
01-701-1015 FGAFOCAL Screening NA NS
01-701-1015 FGAFOCAL Baseline NA NS
01-701-1015 FGAFOCAL Week 4 NA NS
01-701-1015 FSQRTFAF Screening 5.018851 5.01885046599318
01-701-1015 FSQRTFAF Baseline 5.018851 5.01885046599318
01-701-1015 FSQRTFAF Week 4 5.018851 5.01885046599318
01-701-1015 SAREAFAF Screening 1.468605 1.468605

Further Derivations of Standard BDS Variables

The user is invited to consult the article on creating a BDS dataset from SDTM to learn how to add standard BDS variables to ADGA.

Example Script

ADaM Sample Code
ADGA use_ad_template("adga", package = "admiralophtha")