NIF data for a multiple-dose study
Source:vignettes/multiple-dose-example.Rmd
multiple-dose-example.Rmd
OVERVIEW
This vignette walks through the creation of a NONMEM input file (NIF) data set for a prototypical multiple dose study (study ‘RS2023-0022’), followed by some basic exploratory analyses.
The (fictional) raw study data are included as part of the NIF
package as examplinib_poc
. Custom SDTM data can be loaded
using read_sdtm()
.
Study design
Study ‘RS2023-0022’ is a single-arm study in which subjects received multiple doses of ‘examplinib’ (substance code ‘RS2023’). The treatment duration is different across subjects. PK sampling was on Days 1 and 8 of the treatment period. The PK sampling schedule was rich in the initial subset of subjects and sparse in the others.
Study SDTM data
The package provides the ‘sdtm’ class as a wrapper to keep all SDTM
domain tables of a clinical study together in one object. The
examplinib_poc
sdtm object contains the DM, EX, PC and VS
domains. Let’s print the examplinib_poc
sdtm object for a
high-level overview:
examplinib_poc
#> -------- SDTM data set summary --------
#> Study 2023000022
#>
#> Data disposition
#> DOMAIN SUBJECTS OBSERVATIONS
#> dm 103 103
#> vs 103 206
#> ex 80 468
#> pc 80 1344
#> lb 103 103
#> pp 13 432
#>
#> Arms (DM):
#> ACTARMCD ACTARM
#> TREATMENT Single Arm Treatment
#> SCRNFAIL Screen Faillure
#>
#> Treatments (EX):
#> EXAMPLINIB
#>
#> PK sample specimens (PC):
#> PLASMA
#>
#> PK analytes (PC):
#> PCTEST PCTESTCD
#> RS2023 RS2023
#> RS2023487A RS2023487A
Note that in the EX domain, the administered drug is given as ‘EXAMPLINIB’ while in PC, the corresponding analyte is ‘RS2023’. The other analyte, ‘RS2023487A’ is a metabolite.
To clearly specify these correlations, the SDTM object includes the
fields examplinib_poc$analyte_mapping
and
examplinib_poc$metabolite_mapping
. Both can be defined when
the SDTM data set is created. See the documentation to
add_analyte_mapping()
and
add_metabolite_mapping()
for details.
For this SDTM object, both mappings have been added already.
CREATING A NIF DATA SET
To enable more complex analyses and analyses in the time domain, the
data from different SDTM domains must be integrated into a single data
frame in long table format. For modeling analyses, a common format
specification is the NONMEM input file (NIF) format that is at the core
of the nif
package.
nif
objects are minimally composed from administrations
and observations. Following the NONMEM nomenclature, the former have an
‘EVID’ of 1, the latter an ‘EVID’ of 0. In the following code, an empty
nif object is created using new_nif()
, to which
‘EXAMPLINIB’ administrations are then added using
add_administration()
. Note that the analyte field for this
drug is set to ‘RS2023’. In a second step, pharmacokinetic
concentrations for ‘RS2023’ are added from the ‘PC’ SDTM domain. To
match the administration, the analyte field is again ‘RS2023’. The
compartment is automatically set to 2. Both functions have as their
first argument the ‘sdtm’ object:
sdtm <- examplinib_poc
nif_poc <- new_nif() %>%
add_administration(sdtm, extrt = "EXAMPLINIB", analyte = "RS2023") %>%
add_observation(sdtm, domain = "pc", testcd = "RS2023", analyte = "RS2023", cmt = 2)
The ‘LB’ domain within this sdtm object contains baseline creatinine data which can be used to calculate the individual baseline creatinine clearance as an estimate for glomerular filtration rate (eGFR). Baseline covariates are created using ’add_baseline()`.
In the first step, baseline creatinine is added from ‘LB’, and then the creatinine clearance is calculated using further demographic parameters (sex, age, race, weight). The standard method is Cockcroft-Gault but other methods can be specified:
nif_poc <- nif_poc %>%
add_baseline(sdtm, domain = "lb", testcd = "CREAT") %>%
add_bl_crcl()
In fact, the nif data set that we have just created, including the
baseline creatinine clearance data, is already included in the
nif
package as examplinib_poc_nif
.
In case NIF data sets have been generated externally, they can be
converted into nif objects using new_nif()
, see the
documentation to this function for details.
Note that a nif object is essentially a wrapper around a data frame with the set of variables expected by NONMEM or other population analysis tools. This becomes visible when the object is converted into a data frame:
nif_poc %>%
as.data.frame() %>%
head()
# REF ID STUDYID USUBJID AGE SEX RACE HEIGHT WEIGHT BMI
# 1 1 1 2023000022 20230000221010001 58 1 WHITE 185.3 91.4 26.61922
# 2 2 1 2023000022 20230000221010001 58 1 WHITE 185.3 91.4 26.61922
# 3 3 1 2023000022 20230000221010001 58 1 WHITE 185.3 91.4 26.61922
# 4 4 1 2023000022 20230000221010001 58 1 WHITE 185.3 91.4 26.61922
# 5 5 1 2023000022 20230000221010001 58 1 WHITE 185.3 91.4 26.61922
# 6 6 1 2023000022 20230000221010001 58 1 WHITE 185.3 91.4 26.61922
# DTC TIME NTIME TAFD TAD EVID AMT ANALYTE CMT PARENT TRTDY
# 1 2001-01-13 10:36:00 0.000 0.0 0.000 0.000 1 500 RS2023 1 RS2023 1
# 2 2001-01-13 10:36:00 0.000 0.0 0.000 0.000 0 0 RS2023 2 RS2023 1
# 3 2001-01-13 11:36:00 1.000 0.5 1.000 1.000 0 0 RS2023 2 RS2023 1
# 4 2001-01-13 12:04:00 1.467 1.0 1.467 1.467 0 0 RS2023 2 RS2023 1
# 5 2001-01-13 12:36:00 2.000 1.5 2.000 2.000 0 0 RS2023 2 RS2023 1
# 6 2001-01-13 13:04:00 2.467 2.0 2.467 2.467 0 0 RS2023 2 RS2023 1
# METABOLITE DOSE MDV ACTARMCD IMPUTATION DV BL_CREAT BL_CRCL
# 1 FALSE 500 1 TREATMENT NA 72.78062 107.4689
# 2 FALSE 500 0 TREATMENT 0.0000 72.78062 107.4689
# 3 FALSE 500 0 TREATMENT 636.6833 72.78062 107.4689
# 4 FALSE 500 0 TREATMENT 1844.8225 72.78062 107.4689
# 5 FALSE 500 0 TREATMENT 2773.3083 72.78062 107.4689
# 6 FALSE 500 0 TREATMENT 2539.5401 72.78062 107.4689
When the NIF object itself is printed, a summary of the data is shown instead:
nif_poc
# ----- NONMEM input file (NIF) object -----
# 672 observations from 80 subjects across 1 study
# Analytes: RS2023
# 47 males (58.8%), 33 females (41.2%)
#
# Columns:
# REF, ID, STUDYID, USUBJID, AGE, SEX, RACE, HEIGHT, WEIGHT, BMI, DTC, TIME,
# NTIME, TAFD, TAD, EVID, AMT, ANALYTE, CMT, PARENT, TRTDY, METABOLITE, DOSE,
# MDV, ACTARMCD, IMPUTATION, DV, BL_CREAT, BL_CRCL
#
# Data (selected columns):
# ID NTIME TIME TAD ANALYTE EVID CMT AMT DOSE DV
# 1 0 0 0 RS2023 1 1 500 500 NA
# 1 0 0 0 RS2023 0 2 0 500 0
# 1 0.5 1 1 RS2023 0 2 0 500 636.683
# 1 1 1.467 1.467 RS2023 0 2 0 500 1844.823
# 1 1.5 2 2 RS2023 0 2 0 500 2773.308
# 1 2 2.467 2.467 RS2023 0 2 0 500 2539.54
# 1 3 3.533 3.533 RS2023 0 2 0 500 2447.598
# 1 4 4.55 4.55 RS2023 0 2 0 500 1868.594
# 1 6 6.5 6.5 RS2023 0 2 0 500 1229.613
# 1 8 8.367 8.367 RS2023 0 2 0 500 750.583
# 6940 more rows
EXPLORATION
Demographics
For an initial overview on the distribution of baseline parameters,
the administered drugs, analytes, observations, etc., NIF objects can be
inspected with the summary()
function. Note that since we
have added baseline eGFR to the data set, the output also summarizes the
number of patients with normal renal function or impaired renal
function:
summary(nif_poc)
# ----- NONMEM input file (NIF) object summary -----
# Data from 80 subjects across one study:
# STUDYID N
# 2023000022 80
#
# Sex distribution:
# SEX N percent
# male 47 58.8
# female 33 41.2
#
# Renal impairment class:
# CLASS N percent
# normal 31 38.8
# mild 34 42.5
# moderate 15 18.8
# severe 0 0
#
# Treatments:
# RS2023
#
# Analytes:
# RS2023
#
# Subjects per dose level:
# RS2023 N
# 500 80
#
# 672 observations:
# CMT ANALYTE N
# 2 RS2023 672
#
# Subjects with dose reductions
# RS2023
# 2
#
# Treatment duration overview:
# PARENT min max mean median
# RS2023 56 99 78.5 79
For a visual overview of the NIF data set plot()
can be
applied to the summary:
invisible(capture.output(
plot(summary(nif_poc))
))
Exposure
In this study, all 80 subject received the same dose level:
nif_poc %>%
dose_levels() %>%
kable(caption="Dose levels")
RS2023 | N |
---|---|
500 | 80 |
However, there were subjects with dose reductions, as we can see when filtering the nif data set for EVID == 1 (administrations) and summarizing the administered dose:
DOSE | n |
---|---|
250 | 916 |
500 | 5362 |
To identify the subjects with dose reductions:
nif_poc %>%
dose_red_sbs()
# # A tibble: 25 × 2
# ID USUBJID
# <dbl> <chr>
# 1 1 20230000221010001
# 2 3 20230000221010005
# 3 9 20230000221020006
# 4 10 20230000221020007
# 5 17 20230000221030004
# 6 19 20230000221030007
# 7 20 20230000221030008
# 8 21 20230000221030009
# 9 22 20230000221030010
# 10 29 20230000221040002
# # ℹ 15 more rows
Let’s have a plot of the doses over time in these subjects:
nif_poc %>%
filter(ID %in% (dose_red_sbs(nif_poc))$ID) %>%
filter(EVID == 1) %>%
ggplot(aes(x = TIME, y = DOSE, color = as.factor(ID))) +
geom_point() +
geom_line() +
theme(legend.position="none")
We see that dose reductions happened at different times during
treatment. Another way of visualizing this is per the
mean_dose_plot()
function:
nif_poc %>%
mean_dose_plot()
The upper panel shows the mean dose over time, and we can see that after ~Day 13, the mean dose across all treated subjects drops due to dose reductions in some subjects. To put this into context, the lower panel shows the number of subjects on treatment over time, and we see that most subjects had treatment durations of around 30 days. Note the fluctuations that indicate single missed doses in individual subjects!
PK sampling
The PK sampling time points in this study were:
nif_poc %>%
filter(EVID == 0) %>%
group_by(NTIME, ANALYTE) %>%
summarize(n = n(), .groups = "drop") %>%
tidyr::pivot_wider(names_from = "ANALYTE", values_from = "n") %>%
kable(caption = "Observations by time point and analyte")
NTIME | RS2023 |
---|---|
0.0 | 160 |
0.5 | 24 |
1.0 | 24 |
1.5 | 160 |
2.0 | 24 |
3.0 | 24 |
4.0 | 160 |
6.0 | 24 |
8.0 | 24 |
10.0 | 24 |
12.0 | 24 |
From the different numbers of samplings per nominal time point, we see that only a subset of subjects had a rich sampling scheme. Let’s identify those:
nif_poc %>%
rich_sampling_sbs(analyte = "RS2023", max_time = 24, n = 6)
# [1] 1 4 5 15 28 29 40 50 51 52 63 64
In this code, the function rich_sampling_sbs()
receives
as input the analyte of interest, the time interval across which the
number of samples is evaluated, and the minimum number of samples to
qualify the schedule as rich.
Plasma concentration data
Let’s plot the individual and mean plasma concentration profiles on Day 1 for the parent, RS2023, and the metabolite, RS2023487A:
temp <- nif_poc %>%
filter(ID %in% (rich_sampling_sbs(nif_poc, analyte = "RS2023", n=4)))
temp %>% plot(dose = 500, points = TRUE, title = "Rich sampling subjects")
For single and multiple dose administrations separately:
temp <- temp %>%
index_rich_sampling_intervals()
temp %>% filter(RICH_N == 1) %>%
plot(analyte = "RS2023", mean = TRUE, title = "Single-dose PK")
temp %>% filter(RICH_N == 2) %>%
plot(analyte = "RS2023", title = "Multiple-dose PK", time = "NTIME", mean = T)
Non-compartmental analysis
nca <- examplinib_poc_nif %>%
index_rich_sampling_intervals(analyte = "RS2023", min_n = 4) %>%
nca("RS2023", group = "RICH_N")
nca %>%
nca_summary_table(group = "RICH_N") %>%
kable()
RICH_N | DOSE | n | aucinf.obs | auclast | cmax | half.life | tmax |
---|---|---|---|---|---|---|---|
1 | 250 | 4 | 16962.71 (18) | 15764.51 (19) | 3050.03 (23) | 2.82 (10) | 2.37 (2; 2.67) |
1 | 500 | 12 | 21147.25 (37) | 19382.1 (37) | 3530.09 (37) | 3 (14) | 2.53 (2; 2.78) |
2 | 250 | 4 | NA | NA | 3051.88 (24) | 2.85 (6) | 2.07 (1.7; 2.75) |
2 | 500 | 12 | NA | NA | 3559.39 (36) | 3.05 (12) | 2.51 (1.68; 3.03) |