Creating ADNV

Introduction

This article describes the creation of the ADNV (Nervous System Analysis Dataset) ADaM.

The ADNV dataset focuses on olfactory function assessments, specifically the University of Pennsylvania Smell Identification Test (UPSIT). This vignette demonstrates the neuro-specific steps for creating this BDS finding dataset.

We advise you first consult the {admiral} Creating a BDS Finding ADaM vignette. The programming workflow around creating the general set-up of a BDS ADaM dataset using {admiral} functions is the same. In this vignette, we focus on the neuro-specific steps to avoid repeating information and maintaining the same content in two places. As such, the code in this vignette is not completely executable; we recommend consulting the ADNV template script to view the full workflow.

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

Required Packages

The examples of this vignette require the following packages.

library(admiral)
library(admiralneuro)
library(pharmaversesdtm)
library(dplyr)
library(lubridate)
library(stringr)

Programming Workflow

Read in Data

To start, all data frames needed for the creation of ADNV should be loaded into the global environment. Reading data will usually be a company specific process, however, for the purpose of this vignette, we will use example data from {pharmaversesdtm} and {admiralneuro}. We will utilize NV and ADSL for the basis of the Nervous System Analysis dataset.

nv <- pharmaversesdtm::nv_neuro %>%
  convert_blanks_to_na()

adsl <- admiralneuro::adsl_neuro %>%
  convert_blanks_to_na()

Initial Setup

The following steps are used to merge ADSL variables with the source data NV, followed with filtering to retain only UPSIT records. Common analysis variables such as Analysis Date (ADT) and Relative Analysis Day (ADY) can also be derived during this process. Note that only the sections relevant to this vignette are included in the steps below. To get detailed guidance on all the steps, refer to the {admiral} Creating a BDS Finding ADaM vignette.

adsl_vars <- exprs(AGE, SEX, TRTSDT, TRTEDT, TRT01A, TRT01P)

adnv <- nv %>%
  derive_vars_merged(
    dataset_add = adsl,
    new_vars = adsl_vars,
    by_vars = exprs(STUDYID, USUBJID)
  ) %>%
  filter(NVTESTCD == "UPSIT") %>%
  derive_vars_dt(
    new_vars_prefix = "A",
    dtc = NVDTC
  ) %>%
  derive_vars_dy(reference_date = TRTSDT, source_vars = exprs(ADT))

Assign PARAMCD, PARAM, and PARAMN variables

The next step is to create and assign parameter level variables such as PARAMCD, PARAM, and PARAMN. For this, a lookup can be created based on the SDTM --TESTCD and --CAT values to join to the source data.

param_lookup <- tibble::tribble(
  ~NVTESTCD, ~NVCAT, ~PARAMCD, ~PARAM, ~PARAMN,
  "UPSIT", "OLFACTORY FUNCTION", "UPSITTS", "UPSIT Combined Score from 40 Odorant", 1
)

This lookup may now be joined to the data:

adnv <- adnv %>%
  derive_vars_merged_lookup(
    dataset_add = param_lookup,
    new_vars = exprs(PARAMCD, PARAM, PARAMN),
    by_vars = exprs(NVTESTCD, NVCAT)
  )
USUBJID NVTESTCD NVCAT PARAMCD PARAM PARAMN
01-701-1015 UPSIT OLFACTORY FUNCTION UPSITTS UPSIT Combined Score from 40 Odorant 1
01-701-1023 UPSIT OLFACTORY FUNCTION UPSITTS UPSIT Combined Score from 40 Odorant 1
01-701-1028 UPSIT OLFACTORY FUNCTION UPSITTS UPSIT Combined Score from 40 Odorant 1
01-701-1034 UPSIT OLFACTORY FUNCTION UPSITTS UPSIT Combined Score from 40 Odorant 1
01-701-1146 UPSIT OLFACTORY FUNCTION UPSITTS UPSIT Combined Score from 40 Odorant 1
01-701-1153 UPSIT OLFACTORY FUNCTION UPSITTS UPSIT Combined Score from 40 Odorant 1
01-701-1181 UPSIT OLFACTORY FUNCTION UPSITTS UPSIT Combined Score from 40 Odorant 1
01-701-1234 UPSIT OLFACTORY FUNCTION UPSITTS UPSIT Combined Score from 40 Odorant 1
01-701-1275 UPSIT OLFACTORY FUNCTION UPSITTS UPSIT Combined Score from 40 Odorant 1
01-701-1302 UPSIT OLFACTORY FUNCTION UPSITTS UPSIT Combined Score from 40 Odorant 1

Map AVAL and AVALC variables

Now that the parameter-level variables have been created, we can map the Analysis Value variables. For these parameters, no complex derivation is required. Note that AVALC should only be mapped if it contains non-redundant information.

adnv <- adnv %>%
  mutate(
    AVAL = NVSTRESN,
    AVALC = if_else(
      is.na(NVSTRESN) | as.character(NVSTRESN) != NVSTRESC,
      NVSTRESC,
      NA
    )
  )

We then drop AVALC column because it contains no non-missing values

if (all(is.na(adnv$AVALC))) {
  adnv <- adnv %>% select(-AVALC)
}

Convert UPSIT Score to Percentile

The UPSIT (University of Pennsylvania Smell Identification Test) is a standardized test that measures olfactory function using 40 odor identification items. The raw UPSIT score ranges from 0 to 40, with higher scores indicating better olfactory function.

To enable meaningful interpretation across different age and sex groups, raw UPSIT scores can be converted to percentiles based on normative data. These percentiles represent where an individual’s performance ranks relative to age- and sex-matched peers.

In the following steps, we will incorporate the conversion from UPSIT total score to percentile using the function admiralneuro::compute_upsit_percentile().

1. Define the Source Variables

The first step is to define keep_vars, which lists the source variables to retain and copy into newly derived records in the next step; all other variables will remain in the dataset but will be set to NA in those derived rows.

2. Convert UPSIT Score to Percentile

The second step creates new analysis records in which the original UPSITTS values are converted into percentiles using admiralneuro::compute_upsit_percentile(). The conversion is based on the patient’s sex and age at the time of assessment.

The new parameter values which include AVAL, PARAMCD, PARAM, and PARAMN are assigned for the percentile parameter.

keep_vars <- c(
  get_admiral_option("subject_keys"),
  adsl_vars,
  exprs(ADT, ADY, VISIT)
)

# Prepare derived rows
upsit_percentile <- adnv %>%
  filter(PARAMCD == "UPSITTS") %>%
  mutate(
    AVAL = compute_upsit_percentile(
      sex = SEX,
      age = AGE,
      upsit_score = AVAL
    ),
    PARAMCD = "UPSITPC",
    PARAM = "Percentile derived from UPSIT total score",
    PARAMN = 2,
    AVALU = NA
  )

# Bind with original dataset
adnv <- bind_rows(adnv, upsit_percentile)

# Drop AVALU because it contains no non-missing values
if (all(is.na(adnv$AVALU))) {
  adnv <- adnv %>% select(-AVALU)
}

This is how the parameters and newly converted parameters will look like:

USUBJID PARAMCD PARAM AVAL ADT ADY VISIT
01-701-1015 UPSITTS UPSIT Combined Score from 40 Odorant 13.0 2014-01-02 1 BASELINE
01-701-1015 UPSITPC Percentile derived from UPSIT total score 2.0 2014-01-02 1 BASELINE
01-701-1023 UPSITTS UPSIT Combined Score from 40 Odorant 32.0 2012-08-05 1 BASELINE
01-701-1023 UPSITPC Percentile derived from UPSIT total score 39.5 2012-08-05 1 BASELINE
01-701-1028 UPSITTS UPSIT Combined Score from 40 Odorant 16.0 2013-07-19 NA BASELINE
01-701-1028 UPSITPC Percentile derived from UPSIT total score 6.0 2013-07-19 NA BASELINE
01-701-1034 UPSITTS UPSIT Combined Score from 40 Odorant 1.0 2014-07-01 1 BASELINE
01-701-1034 UPSITPC Percentile derived from UPSIT total score 1.0 2014-07-01 1 BASELINE
01-701-1146 UPSITTS UPSIT Combined Score from 40 Odorant 14.0 2013-05-20 1 BASELINE
01-701-1146 UPSITPC Percentile derived from UPSIT total score 3.0 2013-05-20 1 BASELINE

Derive Criterion Flag Variables for UPSIT Percentile

Having converted the UPSIT score to percentile, we can now derive the criterion flags for olfactory impairment categories.

We will use a percentile cutoff of 10 to define olfactory impairment. The 10th percentile cutoff represents a sex- and age-adjusted threshold for olfactory impairment based on normative data. UPSIT percentile ≤ 10 is considered olfactory impairment, while UPSIT percentile > 10 is considered normal olfactory function.

To do this, we will use the admiral::derive_vars_crit_flag() function. Since we want to derive these flags specifically for the UPSIT percentile parameter, we will apply the derivation only to those records using admiral::restrict_derivation().

adnv <- adnv %>%
  restrict_derivation(
    derivation = derive_vars_crit_flag,
    args = params(
      crit_nr = 1,
      condition = if_else(PARAMCD == "UPSITPC", AVAL <= 10, NA),
      description = "UPSITPC <= 10",
      values_yn = TRUE # To get "Y", "N", and NA for the flag
    ),
    filter = PARAMCD == "UPSITPC"
  )

This is how the criterion flags will look like:

USUBJID PARAMCD PARAM AVAL ADT ADY VISIT CRIT1 CRIT1FL
01-701-1015 UPSITPC Percentile derived from UPSIT total score 2.0 2014-01-02 1 BASELINE UPSITPC <= 10 Y
01-701-1015 UPSITTS UPSIT Combined Score from 40 Odorant 13.0 2014-01-02 1 BASELINE NA NA
01-701-1023 UPSITPC Percentile derived from UPSIT total score 39.5 2012-08-05 1 BASELINE UPSITPC <= 10 N
01-701-1023 UPSITTS UPSIT Combined Score from 40 Odorant 32.0 2012-08-05 1 BASELINE NA NA
01-701-1028 UPSITPC Percentile derived from UPSIT total score 6.0 2013-07-19 NA BASELINE UPSITPC <= 10 Y
01-701-1028 UPSITTS UPSIT Combined Score from 40 Odorant 16.0 2013-07-19 NA BASELINE NA NA
01-701-1034 UPSITPC Percentile derived from UPSIT total score 1.0 2014-07-01 1 BASELINE UPSITPC <= 10 Y
01-701-1034 UPSITTS UPSIT Combined Score from 40 Odorant 1.0 2014-07-01 1 BASELINE NA NA
01-701-1146 UPSITPC Percentile derived from UPSIT total score 3.0 2013-05-20 1 BASELINE UPSITPC <= 10 Y
01-701-1146 UPSITTS UPSIT Combined Score from 40 Odorant 14.0 2013-05-20 1 BASELINE NA NA

Remaining Analysis Dataset Set-up

The {admiral} Creating a BDS Finding ADaM vignette covers all the steps that are not shown here, such as merging the timing variables, analysis flags, baseline derivations, change from baseline calculations, etc.

Example Scripts

ADaM Sourcing Command
ADNV admiral::use_ad_template("ADNV", package = "admiralneuro")

References

University of Pennsylvania Smell Identification Test. (n.d.). In Wikipedia. Retrieved from https://en.wikipedia.org/wiki/University_of_Pennsylvania_Smell_Identification_Test

Brumm, M. C. et al. (2023). Updated Percentiles for the University of Pennsylvania Smell Identification Test in Adults 50 Years of Age and Older. Neurology, 100(16), e1691–e1701.

Mastenbroek, S. E. et al. (2025). Two-step detection of Lewy body pathology via smell-function testing and CSF α-synuclein seed amplification. Nature Communications, 16, 7182.