Skip to contents

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:

Exposure

In this study, all 80 subject received the same dose level:

nif_poc %>%
  dose_levels() %>% 
  kable(caption="Dose levels")
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:

nif_poc %>% 
  filter(EVID==1) %>% 
  group_by(DOSE) %>% 
  summarize(n=n()) %>% 
  kable()
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:

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")
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)