ADPC
The Non-compartmental analysis (NCA) ADaM uses the CDISC Implementation Guide (https://www.cdisc.org/standards/foundational/adam/adamig-non-compartmental-analysis-input-data-v1-0). This example presented uses underlying EX
and PC
domains where the EX
and PC
domains represent data as collected and the ADPC
ADaM is output. For more details see the {admiral}
vignette.
First Load Packages
First we will load the packages required for our project. We will use {admiral}
for the creation of analysis data. {admiral}
requires {dplyr}
, {lubridate}
and {stringr}
. We will use {metacore}
and {metatools}
to store and manipulate metadata from our specifications. We will use {xportr}
to perform checks on the final data and export to a transport file.
The source SDTM data will come from the CDISC pilot study data stored in {pharmaversesdtm}
.
Next Load Specifications for Metacore
We have saved our specifications in an Excel file and will load them into {metacore}
with the metacore::spec_to_metacore()
function.
Load Source Datasets
We will load are SDTM data from {pharmaversesdtm}
. The main components of this will be exposure data from EX
and pharmacokinetic concentration data from PC
. We will use ADSL
for baseline characteristics and we will derive additional baselines from vital signs VS
.
Derivations
Derive PC Dates
Here we use {admiral}
functions for working with dates and we will also create a nominal time from first dose NFRLT
for PC
data based on PCTPTNUM
.
# Get list of ADSL vars required for derivations
adsl_vars <- exprs(TRTSDT, TRTSDTM, TRT01P, TRT01A)
pc_dates <- pc %>%
# Join ADSL with PC (need TRTSDT for ADY derivation)
derive_vars_merged(
dataset_add = adsl,
new_vars = adsl_vars,
by_vars = exprs(STUDYID, USUBJID)
) %>%
# Derive analysis date/time
# Impute missing time to 00:00:00
derive_vars_dtm(
new_vars_prefix = "A",
dtc = PCDTC,
time_imputation = "00:00:00"
) %>%
# Derive dates and times from date/times
derive_vars_dtm_to_dt(exprs(ADTM)) %>%
derive_vars_dtm_to_tm(exprs(ADTM)) %>%
derive_vars_dy(reference_date = TRTSDT, source_vars = exprs(ADT)) %>%
# Derive event ID and nominal relative time from first dose (NFRLT)
mutate(
EVID = 0,
DRUG = PCTEST,
NFRLT = if_else(PCTPTNUM < 0, 0, PCTPTNUM), .after = USUBJID
)
Get Dosing Information
Here we also create nomimal time from first dose NFRLT
for EX
data based on VISITDY
.
ex_dates <- ex %>%
derive_vars_merged(
dataset_add = adsl,
new_vars = adsl_vars,
by_vars = exprs(STUDYID, USUBJID)
) %>%
# Keep records with nonzero dose
filter(EXDOSE > 0) %>%
# Add time and set missing end date to start date
# Impute missing time to 00:00:00
# Note all times are missing for dosing records in this example data
# Derive Analysis Start and End Dates
derive_vars_dtm(
new_vars_prefix = "AST",
dtc = EXSTDTC,
time_imputation = "00:00:00"
) %>%
derive_vars_dtm(
new_vars_prefix = "AEN",
dtc = EXENDTC,
time_imputation = "00:00:00"
) %>%
# Derive event ID and nominal relative time from first dose (NFRLT)
mutate(
EVID = 1,
NFRLT = case_when(
VISITDY == 1 ~ 0,
TRUE ~ 24 * VISITDY
)
) %>%
# Set missing end dates to start date
mutate(AENDTM = case_when(
is.na(AENDTM) ~ ASTDTM,
TRUE ~ AENDTM
)) %>%
# Derive dates from date/times
derive_vars_dtm_to_dt(exprs(ASTDTM)) %>%
derive_vars_dtm_to_dt(exprs(AENDTM))
Expand Dosing Records
Since there is a start date and end date for dosing records we need to expand the dosing records between the start date and end date using the function admiral::create_single_dose_dataset()
.
ex_exp <- ex_dates %>%
create_single_dose_dataset(
dose_freq = EXDOSFRQ,
start_date = ASTDT,
start_datetime = ASTDTM,
end_date = AENDT,
end_datetime = AENDTM,
nominal_time = NFRLT,
lookup_table = dose_freq_lookup,
lookup_column = CDISC_VALUE,
keep_source_vars = exprs(
STUDYID, USUBJID, EVID, EXDOSFRQ, EXDOSFRM,
NFRLT, EXDOSE, EXDOSU, EXTRT, ASTDT, ASTDTM, AENDT, AENDTM,
VISIT, VISITNUM, VISITDY,
TRT01A, TRT01P, DOMAIN, EXSEQ, !!!adsl_vars
)
) %>%
# Derive AVISIT based on nominal relative time
# Derive AVISITN to nominal time in whole days using integer division
# Define AVISIT based on nominal day
mutate(
AVISITN = NFRLT %/% 24 + 1,
AVISIT = paste("Day", AVISITN),
ADTM = ASTDTM,
DRUG = EXTRT
) %>%
# Derive dates and times from datetimes
derive_vars_dtm_to_dt(exprs(ADTM)) %>%
derive_vars_dtm_to_tm(exprs(ADTM)) %>%
derive_vars_dtm_to_tm(exprs(ASTDTM)) %>%
derive_vars_dtm_to_tm(exprs(AENDTM)) %>%
derive_vars_dy(reference_date = TRTSDT, source_vars = exprs(ADT))
Find First Dose
In this section we will find the first dose for each subject and drug.
adpc_first_dose <- pc_dates %>%
derive_vars_merged(
dataset_add = ex_exp,
filter_add = (EXDOSE > 0 & !is.na(ADTM)),
new_vars = exprs(FANLDTM = ADTM),
order = exprs(ADTM, EXSEQ),
mode = "first",
by_vars = exprs(STUDYID, USUBJID, DRUG)
) %>%
filter(!is.na(FANLDTM)) %>%
# Derive AVISIT based on nominal relative time
# Derive AVISITN to nominal time in whole days using integer division
# Define AVISIT based on nominal day
mutate(
AVISITN = NFRLT %/% 24 + 1,
AVISIT = paste("Day", AVISITN),
)
Find Previous Dose and Next Dose
Use derive_vars_joined()
to find the previous dose and the next dose.
adpc_prev <- adpc_first_dose %>%
derive_vars_joined(
dataset_add = ex_exp,
by_vars = exprs(USUBJID),
order = exprs(ADTM),
new_vars = exprs(
ADTM_prev = ADTM, EXDOSE_prev = EXDOSE, AVISIT_prev = AVISIT,
AENDTM_prev = AENDTM
),
join_vars = exprs(ADTM),
join_type = "all",
filter_add = NULL,
filter_join = ADTM > ADTM.join,
mode = "last",
check_type = "none"
)
adpc_next <- adpc_prev %>%
derive_vars_joined(
dataset_add = ex_exp,
by_vars = exprs(USUBJID),
order = exprs(ADTM),
new_vars = exprs(
ADTM_next = ADTM, EXDOSE_next = EXDOSE, AVISIT_next = AVISIT,
AENDTM_next = AENDTM
),
join_vars = exprs(ADTM),
join_type = "all",
filter_add = NULL,
filter_join = ADTM <= ADTM.join,
mode = "first",
check_type = "none"
)
Find Previous and Next Nominal Dose
Use the same method to find the previous and next nominal times.
adpc_nom_prev <- adpc_next %>%
derive_vars_joined(
dataset_add = ex_exp,
by_vars = exprs(USUBJID),
order = exprs(NFRLT),
new_vars = exprs(NFRLT_prev = NFRLT),
join_vars = exprs(NFRLT),
join_type = "all",
filter_add = NULL,
filter_join = NFRLT > NFRLT.join,
mode = "last",
check_type = "none"
)
adpc_nom_next <- adpc_nom_prev %>%
derive_vars_joined(
dataset_add = ex_exp,
by_vars = exprs(USUBJID),
order = exprs(NFRLT),
new_vars = exprs(NFRLT_next = NFRLT),
join_vars = exprs(NFRLT),
join_type = "all",
filter_add = NULL,
filter_join = NFRLT <= NFRLT.join,
mode = "first",
check_type = "none"
)
Combine PC and EX Data
Combine PC
and EX
records and derive the additional relative time variables.
adpc_arrlt <- bind_rows(adpc_nom_next, ex_exp) %>%
group_by(USUBJID, DRUG) %>%
mutate(
FANLDTM = min(FANLDTM, na.rm = TRUE),
min_NFRLT = min(NFRLT_prev, na.rm = TRUE),
maxdate = max(ADT[EVID == 0], na.rm = TRUE), .after = USUBJID
) %>%
arrange(USUBJID, ADTM) %>%
ungroup() %>%
filter(ADT <= maxdate) %>%
# Derive Actual Relative Time from First Dose (AFRLT)
derive_vars_duration(
new_var = AFRLT,
start_date = FANLDTM,
end_date = ADTM,
out_unit = "hours",
floor_in = FALSE,
add_one = FALSE
) %>%
# Derive Actual Relative Time from Reference Dose (ARRLT)
derive_vars_duration(
new_var = ARRLT,
start_date = ADTM_prev,
end_date = ADTM,
out_unit = "hours",
floor_in = FALSE,
add_one = FALSE
) %>%
# Derive Actual Relative Time from Next Dose (AXRLT not kept)
derive_vars_duration(
new_var = AXRLT,
start_date = ADTM_next,
end_date = ADTM,
out_unit = "hours",
floor_in = FALSE,
add_one = FALSE
) %>%
mutate(
ARRLT = case_when(
EVID == 1 ~ 0,
is.na(ARRLT) ~ AXRLT,
TRUE ~ ARRLT
),
# Derive Reference Dose Date
PCRFTDTM = case_when(
EVID == 1 ~ ADTM,
is.na(ADTM_prev) ~ ADTM_next,
TRUE ~ ADTM_prev
)
) %>%
# Derive dates and times from datetimes
derive_vars_dtm_to_dt(exprs(FANLDTM)) %>%
derive_vars_dtm_to_tm(exprs(FANLDTM)) %>%
derive_vars_dtm_to_dt(exprs(PCRFTDTM)) %>%
derive_vars_dtm_to_tm(exprs(PCRFTDTM))
Derive Nominal Reference
For nominal relative times we calculate the nominal relative time to reference dose NRRLT
.
Derive Analysis Variables
Here we derive the analysis variables such as AVAL
and ATPTREF
.
adpc_aval <- adpc_nrrlt %>%
mutate(
PARCAT1 = PCSPEC,
ATPTN = case_when(
EVID == 1 ~ 0,
TRUE ~ PCTPTNUM
),
ATPT = case_when(
EVID == 1 ~ "Dose",
TRUE ~ PCTPT
),
ATPTREF = case_when(
EVID == 1 ~ AVISIT,
is.na(AVISIT_prev) ~ AVISIT_next,
TRUE ~ AVISIT_prev
),
# Derive baseline flag for pre-dose records
ABLFL = case_when(
ATPT == "Pre-dose" ~ "Y",
TRUE ~ NA_character_
),
# Derive BASETYPE
BASETYPE = paste(ATPTREF, "Baseline"),
# Derive Actual Dose
DOSEA = case_when(
EVID == 1 ~ EXDOSE,
is.na(EXDOSE_prev) ~ EXDOSE_next,
TRUE ~ EXDOSE_prev
),
# Derive Planned Dose
DOSEP = case_when(
TRT01P == "Xanomeline High Dose" ~ 81,
TRT01P == "Xanomeline Low Dose" ~ 54
),
DOSEU = "mg",
) %>%
# Derive relative time units
mutate(
FRLTU = "h",
RRLTU = "h",
# Derive PARAMCD
PARAMCD = coalesce(PCTESTCD, "DOSE"),
ALLOQ = PCLLOQ,
# Derive AVAL
AVAL = case_when(
EVID == 1 ~ EXDOSE,
PCSTRESC == "<BLQ" & NFRLT == 0 ~ 0,
PCSTRESC == "<BLQ" & NFRLT > 0 ~ 0.5 * ALLOQ,
TRUE ~ PCSTRESN
),
AVALU = case_when(
EVID == 1 ~ EXDOSU,
TRUE ~ PCSTRESU
),
AVALCAT1 = if_else(PCSTRESC == "<BLQ", PCSTRESC, prettyNum(signif(AVAL, digits = 3))),
) %>%
# Add SRCSEQ
mutate(
SRCDOM = DOMAIN,
SRCVAR = "SEQ",
SRCSEQ = coalesce(PCSEQ, EXSEQ)
)
Derive DTYPE Copy Records
The CDISC ADaM Implementation Guide for Non-compartmental Analysis uses duplicated records for analysis when a record needs to be used in more than one way. In this example the 24 hour post-dose record will also be used a the pre-dose record for the “Day 2” dose.
dtype <- adpc_aval %>%
filter(NFRLT > 0 & NXRLT == 0 & EVID == 0 & !is.na(AVISIT_next)) %>%
select(-PCRFTDT, -PCRFTTM) %>%
# Re-derive variables in for DTYPE copy records
mutate(
ABLFL = NA_character_,
ATPTREF = AVISIT_next,
ARRLT = AXRLT,
NRRLT = NXRLT,
PCRFTDTM = ADTM_next,
DOSEA = EXDOSE_next,
BASETYPE = paste(AVISIT_next, "Baseline"),
ATPT = "Pre-dose",
ATPTN = NFRLT,
ABLFL = "Y",
DTYPE = "COPY"
) %>%
derive_vars_dtm_to_dt(exprs(PCRFTDTM)) %>%
derive_vars_dtm_to_tm(exprs(PCRFTDTM))
Combine Original and DTYPE Copy
Now the duplicated records are combined with the original records.
Derive BASE and CHG
Derive PARAM
with {metatools}
Here we derive PARAM
and PARAMN
using create_var_from_codelist()
from {metatools}
.
# ---- Add ASEQ ----
adpc_aseq <- adpc_chg %>%
# Calculate ASEQ
derive_var_obs_number(
new_var = ASEQ,
by_vars = exprs(STUDYID, USUBJID),
order = exprs(ADTM, BASETYPE, EVID, AVISITN, ATPTN, PARCAT1, DTYPE),
check_type = "error"
) %>%
# Derive PARAM and PARAMN using metatools
create_var_from_codelist(metacore, input_var = PARAMCD, out_var = PARAM) %>%
create_var_from_codelist(metacore, input_var = PARAMCD, out_var = PARAMN)
Derive Additional Baselines
Here we derive additional baseline values from VS
for baseline height HTBL
and weight WTBL
and compute the body mass index (BMI) with compute_bmi()
.
#---- Derive additional baselines from VS ----
adpc_baselines <- adpc_aseq %>%
derive_vars_merged(
dataset_add = vs,
filter_add = VSTESTCD == "HEIGHT",
by_vars = exprs(STUDYID, USUBJID),
new_vars = exprs(HTBL = VSSTRESN, HTBLU = VSSTRESU)
) %>%
derive_vars_merged(
dataset_add = vs,
filter_add = VSTESTCD == "WEIGHT" & VSBLFL == "Y",
by_vars = exprs(STUDYID, USUBJID),
new_vars = exprs(WTBL = VSSTRESN, WTBLU = VSSTRESU)
) %>%
mutate(
BMIBL = compute_bmi(height = HTBL, weight = WTBL),
BMIBLU = "kg/m^2"
)
Combine with ADSL
If needed, the other ADSL
variables can now be added:
Check Data With metacore and metatools
We use {metacore}
objects with {metatools}
functions to perform a number of checks on the data. We will drop variables not in the specs and make sure all the variables from the specs are included.
# Apply metadata and perform associated checks ----
adpc <- adpc_prefinal %>%
drop_unspec_vars(metacore) %>% # Drop unspecified variables from specs
check_variables(metacore) %>% # Check all variables specified are present and no more
check_ct_data(metacore) %>% # Checks all variables with CT only contain values within the CT
order_cols(metacore) %>% # Orders the columns according to the spec
sort_by_key(metacore) # Sorts the rows by the sort keys
Apply Labels and Formats with xportr
Using {xportr}
we check variable type, assign variable lenght, add variable labels, add variable formats, and save a transport file. At the end you could add a call to xportr::xportr_write()
to produce the XPT file.
dir <- tempdir() # Change to whichever directory you want to save the dataset in
adpc_xpt <- adpc %>%
xportr_type(metacore, domain = "ADPC") %>% # Coerce variable type to match spec
xportr_length(metacore) %>% # Assigns SAS length from a variable level metadata
xportr_label(metacore) %>% # Assigns variable label from metacore specifications
xportr_format(metacore) %>% # Assigns variable format from metacore specifications
xportr_df_label(metacore) %>% # Assigns dataset label from metacore specifications
xportr_write(file.path(dir, "adpc.xpt")) # Write xpt v5 transport file