7  Intravascular (IV) Examples

7.1 The IV dataset: Indomethacin

We use datasets::Indometh — IV bolus indomethacin in 6 subjects, sampled over 8 hours.

head(Indometh)
Grouped Data: conc ~ time | Subject
  Subject time conc
1       1 0.25 1.50
2       1 0.50 0.94
3       1 0.75 0.78
4       1 1.00 0.48
5       1 1.25 0.37
6       1 2.00 0.19
str(Indometh)
Classes 'nfnGroupedData', 'nfGroupedData', 'groupedData' and 'data.frame':  66 obs. of  3 variables:
 $ Subject: Ord.factor w/ 6 levels "1"<"4"<"2"<"5"<..: 1 1 1 1 1 1 1 1 1 1 ...
 $ time   : num  0.25 0.5 0.75 1 1.25 2 3 4 5 6 ...
 $ conc   : num  1.5 0.94 0.78 0.48 0.37 0.19 0.12 0.11 0.08 0.07 ...
 - attr(*, "formula")=Class 'formula'  language conc ~ time | Subject
  .. ..- attr(*, ".Environment")=<environment: R_EmptyEnv> 
 - attr(*, "labels")=List of 2
  ..$ x: chr "Time since drug administration"
  ..$ y: chr "Indomethacin concentration"
 - attr(*, "units")=List of 2
  ..$ x: chr "(hr)"
  ..$ y: chr "(mcg/ml)"

Columns: Subject (factor, 6 levels), time (h), conc (mcg/mL).

We add dose information. The standard IV bolus dose for this study was 25 mg.

d_conc <- as.data.frame(Indometh)

d_dose <- data.frame(
  Subject = unique(d_conc$Subject),
  dose    = 25,   # mg
  time    = 0
)

# Quick look at concentration profiles
ggplot(d_conc, aes(x = time, y = conc, group = Subject, colour = Subject)) +
  geom_line() + geom_point() +
  scale_y_log10() +
  labs(title = "Indomethacin IV — log-linear concentration profiles",
       x = "Time (h)", y = "Concentration (mcg/mL)") +
  theme_minimal()

The log-scale profiles show a biexponential decline — a rapid distribution phase followed by a log-linear terminal phase — typical of IV bolus data; NCA’s λz applies to the terminal portion.


7.2 Basic IV analysis

o_conc <- PKNCAconc(d_conc, conc ~ time | Subject)

o_dose <- PKNCAdose(d_dose, dose ~ time | Subject, route = "intravascular")

# Indometh: no sample at t = 0 (first sample is at 0.25 h).
# Use impute = "start_conc0" to add conc = 0 at t = 0 so the interval can start at 0.
# This slightly underestimates AUClast vs. true C0 back-extrapolation (see C0 section below).
# Request the core IV parameters
iv_intervals <- data.frame(
  start       = 0,
  end         = Inf,
  auclast     = TRUE,
  aucinf.obs  = TRUE,
  half.life   = TRUE,
  cmax        = TRUE,
  tmax        = TRUE,
  lambda.z    = TRUE,
  cl.obs      = TRUE,
  vz.obs      = TRUE,
  mrt.iv.last = TRUE,
  mrt.iv.obs  = TRUE
)

o_data <- PKNCAdata(o_conc, o_dose, intervals = iv_intervals, impute = "start_conc0")
o_nca  <- pk.nca(o_data)

as.data.frame(o_nca) |>
  select(Subject, PPTESTCD, PPORRES) |>
  arrange(Subject, PPTESTCD)
# A tibble: 132 × 3
   Subject PPTESTCD      PPORRES
   <ord>   <chr>           <dbl>
 1 1       adj.r.squared  0.994 
 2 1       aucinf.obs     2.04  
 3 1       auclast        1.72  
 4 1       aumcinf.obs    7.82  
 5 1       aumclast       3.30  
 6 1       cl.obs        12.3   
 7 1       clast.obs      0.05  
 8 1       clast.pred     0.0502
 9 1       cmax           1.5   
10 1       half.life      4.38  
# ℹ 122 more rows

Imputation methods, including start_conc0, are described in the Data Imputation vignette.


7.3 Parameter reference

7.3.1 Concentration and time landmarks

For IV bolus, Cmax is the first measured concentration (no absorption phase). Tmax is the time of that first sample.

Parameter Meaning
cmax Maximum observed concentration
tmax Time of Cmax
cmin Minimum observed concentration in the interval
tmin Time of the minimum observed concentration (≥ 0.12.2)
tfirst First time with a non-zero (non-BLQ) concentration
tlast Last time with a measurable concentration
clast.obs Observed concentration at tlast
clast.pred Predicted concentration at tlast (from λz fit)
count_conc Total observations (including BLQ)
count_conc_measured Observations above LOQ (non-BLQ)
lambda.z.time.last Last timepoint used in the λz regression
landmark_params <- data.frame(
  start               = 0,
  end                 = Inf,
  cmax                = TRUE,
  tmax                = TRUE,
  cmin                = TRUE,
  tmin                = TRUE,
  tfirst              = TRUE,
  tlast               = TRUE,
  clast.obs           = TRUE,
  clast.pred          = TRUE,
  count_conc          = TRUE,
  count_conc_measured = TRUE
)

o_nca_lm <- pk.nca(PKNCAdata(o_conc, o_dose, intervals = landmark_params, impute = "start_conc0"))

as.data.frame(o_nca_lm) |>
  filter(PPTESTCD %in% c("cmax","tmax","cmin","tmin","tfirst","tlast",
                          "clast.obs","clast.pred","count_conc","count_conc_measured")) |>
  select(Subject, PPTESTCD, PPORRES) |>
  arrange(Subject, PPTESTCD)
# A tibble: 60 × 3
   Subject PPTESTCD            PPORRES
   <ord>   <chr>                 <dbl>
 1 1       clast.obs            0.05  
 2 1       clast.pred           0.0502
 3 1       cmax                 1.5   
 4 1       cmin                 0     
 5 1       count_conc          12     
 6 1       count_conc_measured 11     
 7 1       tfirst               0.25  
 8 1       tlast                8     
 9 1       tmax                 0.25  
10 1       tmin                 0     
# ℹ 50 more rows

7.3.2 C0 — extrapolated initial concentration

For IV bolus, the true concentration at time 0 is not directly measured (the first sample is taken minutes later). When c0 is requested, PKNCA tries a chain of methods and uses the first that succeeds: a measured, nonzero concentration at the dose time (c0); otherwise back-extrapolation of the semilog line through the first two measured post-dose concentrations (logslope); otherwise the first post-dose concentration (c1), then the minimum concentration (cmin), then zero (set0). Here the value at t = 0 is an imputed 0 rather than a measured concentration, so logslope supplies C0.

iv_with_c0 <- data.frame(
  start    = 0,
  end      = Inf,
  c0       = TRUE,
  cmax     = TRUE,
  auclast  = TRUE,
  aucinf.obs = TRUE
)

o_data_c0 <- PKNCAdata(o_conc, o_dose, intervals = iv_with_c0, impute = "start_conc0")
o_nca_c0  <- pk.nca(o_data_c0)

as.data.frame(o_nca_c0) |>
  filter(PPTESTCD %in% c("c0", "cmax", "auclast", "aucinf.obs")) |>
  select(Subject, PPTESTCD, PPORRES) |>
  arrange(Subject, PPTESTCD)
# A tibble: 24 × 3
   Subject PPTESTCD   PPORRES
   <ord>   <chr>        <dbl>
 1 1       aucinf.obs    2.04
 2 1       auclast       1.72
 3 1       c0            2.39
 4 1       cmax          1.5 
 5 4       aucinf.obs    2.61
 6 4       auclast       2.44
 7 4       c0            2.46
 8 4       cmax          1.85
 9 2       aucinf.obs    3.15
10 2       auclast       2.89
# ℹ 14 more rows

Note: C0 > Cmax is expected for IV bolus — Cmax is the first measured point, C0 is extrapolated back to time 0.

7.3.3 AUC variants

Parameter Meaning
auclast AUC from 0 to last measurable concentration (trapezoidal)
aucinf.obs AUC extrapolated to ∞, using observed Clast
aucinf.pred AUC extrapolated to ∞, using predicted Clast from λz fit
aucpext.obs % of AUCinf that is extrapolated (quality check)

All of these are computed with the current integration method (default lin up/log down) — see the AUC chapter for the available methods and how to choose one. AUC families are covered in AUC Calculation with PKNCA.

auc_params <- data.frame(
  start       = 0,
  end         = Inf,
  auclast     = TRUE,
  aucinf.obs  = TRUE,
  aucinf.pred = TRUE,
  aucpext.obs = TRUE
)

o_data_auc <- PKNCAdata(o_conc, o_dose, intervals = auc_params, impute = "start_conc0")
o_nca_auc  <- pk.nca(o_data_auc)

as.data.frame(o_nca_auc) |>
  filter(PPTESTCD %in% c("auclast", "aucinf.obs", "aucinf.pred", "aucpext.obs")) |>
  select(Subject, PPTESTCD, PPORRES) |>
  arrange(Subject, PPTESTCD)
# A tibble: 24 × 3
   Subject PPTESTCD    PPORRES
   <ord>   <chr>         <dbl>
 1 1       aucinf.obs     2.04
 2 1       aucinf.pred    2.04
 3 1       auclast        1.72
 4 1       aucpext.obs   15.5 
 5 4       aucinf.obs     2.61
 6 4       aucinf.pred    2.52
 7 4       auclast        2.44
 8 4       aucpext.obs    6.26
 9 2       aucinf.obs     3.15
10 2       aucinf.pred    3.14
# ℹ 14 more rows

Quality check: aucpext.obs > 20% (the default max.aucinf.pext threshold) means too much of the AUC is extrapolated — the study may not have sampled long enough.

7.3.4 Half-life and λz

λz (lambda.z) is the terminal elimination rate constant estimated from log-linear regression of the terminal phase. Half-life = ln(2) / λz.

hl_params <- data.frame(
  start        = 0,
  end          = Inf,
  lambda.z     = TRUE,
  half.life    = TRUE,
  r.squared    = TRUE,
  lambda.z.n.points = TRUE
)

o_data_hl <- PKNCAdata(o_conc, o_dose, intervals = hl_params, impute = "start_conc0")
o_nca_hl  <- pk.nca(o_data_hl)

as.data.frame(o_nca_hl) |>
  filter(PPTESTCD %in% c("lambda.z", "half.life", "r.squared", "lambda.z.n.points")) |>
  select(Subject, PPTESTCD, PPORRES) |>
  arrange(Subject, PPTESTCD)
# A tibble: 24 × 3
   Subject PPTESTCD          PPORRES
   <ord>   <chr>               <dbl>
 1 1       half.life           4.38 
 2 1       lambda.z            0.158
 3 1       lambda.z.n.points   3    
 4 1       r.squared           0.997
 5 4       half.life           1.62 
 6 4       lambda.z            0.429
 7 4       lambda.z.n.points  10    
 8 4       r.squared           0.867
 9 2       half.life           2.29 
10 2       lambda.z            0.302
# ℹ 14 more rows

Controlling half-life quality:

# Stricter: require at least 4 points and R² ≥ 0.95
o_data_strict <- PKNCAdata(
  o_conc, o_dose,
  intervals = hl_params,
  impute = "start_conc0",
  options = list(
    min.hl.points    = 4,
    min.hl.r.squared = 0.95
  )
)
o_nca_strict <- pk.nca(o_data_strict)

as.data.frame(o_nca_strict) |>
  filter(PPTESTCD %in% c("half.life", "r.squared", "lambda.z.n.points")) |>
  select(Subject, PPTESTCD, PPORRES) |>
  arrange(Subject, PPTESTCD)
# A tibble: 18 × 3
   Subject PPTESTCD          PPORRES
   <ord>   <chr>               <dbl>
 1 1       half.life           3.80 
 2 1       lambda.z.n.points   5    
 3 1       r.squared           0.980
 4 4       half.life           1.62 
 5 4       lambda.z.n.points  10    
 6 4       r.squared           0.867
 7 2       half.life           2.29 
 8 2       lambda.z.n.points   9    
 9 2       r.squared           0.948
10 5       half.life           2.74 
11 5       lambda.z.n.points   8    
12 5       r.squared           0.875
13 6       half.life           1.96 
14 6       lambda.z.n.points   9    
15 6       r.squared           0.904
16 3       half.life           1.64 
17 3       lambda.z.n.points  10    
18 3       r.squared           0.876

The λz point-selection algorithm and its quality criteria are detailed in the Half-life chapter and the Half-Life Calculation vignette.

Excluding specific points from λz:

Use the exclude_half.life argument of PKNCAconc() to name a logical column (here excl_hl) marking points that should not be included in the terminal regression. Below, subject 1’s 8 h sample is dropped from the λz fit, and the regression is re-selected without it:

d_conc_excl <- d_conc |>
  mutate(excl_hl = ifelse(Subject == "1" & time == 8, TRUE, NA))

o_conc_excl <- PKNCAconc(
  d_conc_excl,
  conc ~ time | Subject,
  exclude_half.life = "excl_hl"
)

o_nca_excl_hl <- pk.nca(PKNCAdata(o_conc_excl, o_dose, intervals = hl_params,
                                  impute = "start_conc0"))

# Subject 1's terminal fit changes when the 8 h point is excluded
bind_rows(
  as.data.frame(o_nca_hl) |> mutate(version = "all points"),
  as.data.frame(o_nca_excl_hl) |> mutate(version = "8 h point excluded")
) |>
  filter(Subject == "1", PPTESTCD %in% c("lambda.z.n.points", "half.life")) |>
  select(version, PPTESTCD, PPORRES)
# A tibble: 4 × 3
  version            PPTESTCD          PPORRES
  <chr>              <chr>               <dbl>
1 all points         lambda.z.n.points    3   
2 all points         half.life            4.38
3 8 h point excluded lambda.z.n.points    4   
4 8 h point excluded half.life            3.82

7.3.5 Clearance and volume

For IV dosing, clearance and volume are obtained directly (no bioavailability factor involved), unlike the apparent CL/F and Vz/F of extravascular routes.

Parameter Formula Meaning
cl.obs Dose / AUCinf.obs Total body clearance (L/h)
cl.pred Dose / AUCinf.pred CL using predicted Clast
cl.last Dose / AUClast CL using AUClast only (no extrapolation)
cl.all Dose / AUCall CL using AUCall (BLQs as zero)
vz.obs CL.obs / λz Volume of distribution — terminal phase
vz.pred CL.pred / λz Vz using predicted Clast
vss.obs CL.obs × MRT Volume at steady state (observed)
vss.pred CL.pred × MRT Volume at steady state (predicted)
vss.iv.obs CL.obs × MRT.iv Vss corrected for IV bolus input
cl_params <- data.frame(
  start      = 0,
  end        = Inf,
  cl.obs     = TRUE,
  cl.pred    = TRUE,
  cl.last    = TRUE,
  vz.obs     = TRUE,
  vz.pred    = TRUE,
  vss.obs    = TRUE,
  vss.iv.obs = TRUE
)

o_data_cl <- PKNCAdata(o_conc, o_dose, intervals = cl_params, impute = "start_conc0")
o_nca_cl  <- pk.nca(o_data_cl)

as.data.frame(o_nca_cl) |>
  filter(PPTESTCD %in% c("cl.obs", "cl.pred", "cl.last", "vz.obs", "vss.obs", "vss.iv.obs")) |>
  select(Subject, PPTESTCD, PPORRES) |>
  arrange(Subject, PPTESTCD)
# A tibble: 36 × 3
   Subject PPTESTCD   PPORRES
   <ord>   <chr>        <dbl>
 1 1       cl.last      14.5 
 2 1       cl.obs       12.3 
 3 1       cl.pred      12.3 
 4 1       vss.iv.obs   47.2 
 5 1       vss.obs      47.2 
 6 1       vz.obs       77.6 
 7 4       cl.last      10.2 
 8 4       cl.obs        9.59
 9 4       cl.pred       9.90
10 4       vss.iv.obs   22.4 
# ℹ 26 more rows

7.3.6 Mean residence time (MRT)

MRT quantifies the average time a drug molecule spends in the body.

For IV, the AUMC (area under the first moment curve) method gives MRT = AUMC / AUC.

All MRT variants for IV:

Parameter Meaning
mrt.iv.last MRT using AUMClast and AUClast (IV correction)
mrt.iv.obs MRT extrapolated to ∞, observed Clast (IV correction)
mrt.iv.pred MRT extrapolated to ∞, predicted Clast (IV correction)
mrt.last MRT using AUMClast (no IV correction)
mrt.obs MRT extrapolated to ∞, observed Clast (no IV correction)
mrt.pred MRT extrapolated to ∞, predicted Clast (no IV correction)

The IV-corrected variants (mrt.iv.*) subtract half the infusion duration from the raw AUMC/AUC ratio, while the non-IV variants (mrt.*) use the raw ratio — see MRT correction for infusions below for the formula and a worked example.

mrt_params <- data.frame(
  start       = 0,
  end         = Inf,
  mrt.iv.last = TRUE,
  mrt.iv.obs  = TRUE,
  mrt.iv.pred = TRUE,
  mrt.last    = TRUE,
  mrt.obs     = TRUE,
  mrt.pred    = TRUE
)

o_data_mrt <- PKNCAdata(o_conc, o_dose, intervals = mrt_params, impute = "start_conc0")
o_nca_mrt  <- pk.nca(o_data_mrt)

as.data.frame(o_nca_mrt) |>
  filter(grepl("^mrt", PPTESTCD)) |>
  select(Subject, PPTESTCD, PPORRES) |>
  arrange(Subject, PPTESTCD)
# A tibble: 36 × 3
   Subject PPTESTCD    PPORRES
   <ord>   <chr>         <dbl>
 1 1       mrt.iv.last    1.92
 2 1       mrt.iv.obs     3.84
 3 1       mrt.iv.pred    3.85
 4 1       mrt.last       1.92
 5 1       mrt.obs        3.84
 6 1       mrt.pred       3.85
 7 4       mrt.iv.last    1.80
 8 4       mrt.iv.obs     2.33
 9 4       mrt.iv.pred    2.07
10 4       mrt.last       1.80
# ℹ 26 more rows

7.3.7 Effective half-life and elimination rate constant

Effective half-life (thalf.eff) is derived from MRT rather than from the terminal regression: thalf.eff = ln(2) × MRT. It reflects the half-life that would produce the same accumulation as the observed MRT, useful for multiple-dose design.

kel in PKNCA is computed as 1 / MRT, not as λz (the terminal slope from the half-life regression). kel and lambda.z answer different questions: lambda.z is the terminal log-linear slope; kel is the MRT-derived rate constant used for multiple-dose accumulation calculations.

All variants:

Parameter Formula Based on
thalf.eff.iv.last ln(2) × MRT mrt.iv.last
thalf.eff.iv.obs ln(2) × MRT mrt.iv.obs
thalf.eff.iv.pred ln(2) × MRT mrt.iv.pred
thalf.eff.pred ln(2) × MRT mrt.pred
thalf.eff.last ln(2) × MRT mrt.last
thalf.eff.obs ln(2) × MRT mrt.obs
kel.obs 1 / MRT mrt.obs
kel.pred 1 / MRT mrt.pred
kel.last 1 / MRT mrt.last
kel.iv.obs 1 / MRT mrt.iv.obs
kel.iv.pred 1 / MRT mrt.iv.pred
kel.iv.last 1 / MRT mrt.iv.last
eff_params <- data.frame(
  start              = 0,
  end                = Inf,
  thalf.eff.iv.last  = TRUE,
  thalf.eff.iv.obs   = TRUE,
  thalf.eff.iv.pred  = TRUE,
  thalf.eff.pred     = TRUE,
  thalf.eff.last     = TRUE,
  thalf.eff.obs      = TRUE,
  kel.obs            = TRUE,
  kel.pred           = TRUE,
  kel.last           = TRUE,
  kel.iv.obs         = TRUE,
  kel.iv.pred        = TRUE,
  kel.iv.last        = TRUE
)

o_nca_eff <- pk.nca(PKNCAdata(o_conc, o_dose, intervals = eff_params, impute = "start_conc0"))

as.data.frame(o_nca_eff) |>
  filter(grepl("^(thalf|kel)", PPTESTCD)) |>
  select(Subject, PPTESTCD, PPORRES) |>
  arrange(Subject, PPTESTCD)
# A tibble: 72 × 3
   Subject PPTESTCD          PPORRES
   <ord>   <chr>               <dbl>
 1 1       kel.iv.last         0.522
 2 1       kel.iv.obs          0.260
 3 1       kel.iv.pred         0.260
 4 1       kel.last            0.522
 5 1       kel.obs             0.260
 6 1       kel.pred            0.260
 7 1       thalf.eff.iv.last   1.33 
 8 1       thalf.eff.iv.obs    2.66 
 9 1       thalf.eff.iv.pred   2.67 
10 1       thalf.eff.last      1.33 
# ℹ 62 more rows

7.3.8 IV-route AUC (auciv*)

auciv* parameters compute AUC using IV-specific formulas. Unlike the general auclast/aucinf.obs, these treat the dose as a bolus at time 0 and back-extrapolate C0 before integrating.

Complete auciv* family:

Parameter Meaning
aucivlast AUC(0–last), IV method (C0 back-extrapolated)
aucivall AUC(0–last), IV method, BLQ treated as 0
aucivinf.obs AUC(0–∞) IV method, observed Clast
aucivinf.pred AUC(0–∞) IV method, predicted Clast
aucivint.last AUC over interval [start,end], IV method, AUClast extrapolation
aucivint.all AUC over interval [start,end], IV method, AUCall extrapolation
aucivpbextlast % of aucivlast contributed by back-extrapolation — the C0 segment before the first measured sample
aucivpbextall % of aucivall contributed by the back-extrapolated C0 segment
aucivpbextinf.obs % of aucivinf.obs contributed by the back-extrapolated C0 segment
aucivpbextinf.pred % of aucivinf.pred contributed by the back-extrapolated C0 segment
aucivpbextint.last % of aucivint.last contributed by the back-extrapolated C0 segment
aucivpbextint.all % of aucivint.all contributed by the back-extrapolated C0 segment
auciv_params <- data.frame(
  start              = 0,
  end                = Inf,
  aucivlast          = TRUE,
  aucivall           = TRUE,
  aucivinf.obs       = TRUE,
  aucivinf.pred      = TRUE,
  aucivpbextlast     = TRUE,
  aucivpbextall      = TRUE,
  aucivpbextinf.obs  = TRUE,
  aucivpbextinf.pred = TRUE
)

o_nca_auciv <- pk.nca(PKNCAdata(o_conc, o_dose, intervals = auciv_params, impute = "start_conc0"))

as.data.frame(o_nca_auciv) |>
  filter(grepl("^auciv", PPTESTCD)) |>
  select(Subject, PPTESTCD, PPORRES) |>
  arrange(Subject, PPTESTCD)
# A tibble: 48 × 3
   Subject PPTESTCD           PPORRES
   <ord>   <chr>                <dbl>
 1 1       aucivall              2.01
 2 1       aucivinf.obs          2.33
 3 1       aucivinf.pred         2.33
 4 1       aucivlast             2.01
 5 1       aucivpbextall        14.5 
 6 1       aucivpbextinf.obs    12.5 
 7 1       aucivpbextinf.pred   12.5 
 8 1       aucivpbextlast       14.5 
 9 4       aucivall              2.75
10 4       aucivinf.obs          2.91
# ℹ 38 more rows

When to use auciv* vs auclast/aucinf.obs: For a true IV bolus where C0 is back-extrapolated, aucivlast and aucivinf.obs give the more theoretically correct AUC. For practical purposes the numerical difference is usually small.


7.4 IV infusion (non-bolus)

For IV infusions, tell PKNCA the infusion duration via a duration column in the dose data frame and the duration argument to PKNCAdose(). PKNCA then:

  • Interpolates the concentration at end of infusion (ceoi) from the surrounding measured timepoints
  • Applies the IV correction to MRT: MRT_infusion = AUMC/AUC − duration/2

The Indomethacin dataset is IV bolus, but we can re-label the dose as a 30-minute (0.5 h) infusion to illustrate the workflow. The concentration data are unchanged; only the dose metadata differs.

# Re-label the bolus dose as a 30-minute infusion
d_dose_inf <- d_dose |>
  mutate(duration = 0.5)  # 0.5 hours = 30 min

o_dose_inf <- PKNCAdose(
  d_dose_inf,
  dose ~ time | Subject,
  route    = "intravascular",
  duration = "duration"   # column name in d_dose_inf
)

7.4.1 Infusion-specific parameter: ceoi

ceoi is the concentration at the end of infusion — for an infusion, ceoi plays the role that Cmax plays for extravascular dosing. PKNCA interpolates it from the surrounding observed timepoints using the current AUC method. Interpolation rules are documented in the dose-aware interpolation vignette.

inf_intervals <- data.frame(
  start      = 0,
  end        = Inf,
  ceoi       = TRUE,    # concentration at end of infusion (interpolated)
  auclast    = TRUE,
  aucinf.obs = TRUE,
  half.life  = TRUE,
  cl.obs     = TRUE,
  mrt.iv.obs = TRUE     # MRT with infusion duration correction
)

o_data_inf <- PKNCAdata(o_conc, o_dose_inf, intervals = inf_intervals,
                         impute = "start_conc0")
o_nca_inf  <- pk.nca(o_data_inf)

as.data.frame(o_nca_inf) |>
  filter(PPTESTCD %in% c("ceoi", "auclast", "aucinf.obs",
                          "half.life", "cl.obs", "mrt.iv.obs")) |>
  select(Subject, PPTESTCD, PPORRES) |>
  tidyr::pivot_wider(names_from = PPTESTCD, values_from = PPORRES) |>
  arrange(Subject)
# A tibble: 6 × 7
  Subject auclast  ceoi half.life aucinf.obs cl.obs mrt.iv.obs
  <ord>     <dbl> <dbl>     <dbl>      <dbl>  <dbl>      <dbl>
1 1          1.72  0.94      4.38       2.04  12.3        3.59
2 4          2.44  1.39      1.73       2.62   9.55       2.13
3 2          2.89  1.63      2.29       3.15   7.93       2.73
4 5          1.92  1.04      2.74       2.16  11.6        2.79
5 6          2.84  1.44      1.96       3.10   8.08       2.44
6 3          2.88  1.49      1.75       3.08   8.11       2.07

7.4.2 MRT correction for infusions

The mrt.iv.* parameters subtract duration/2 from the raw AUMC/AUC ratio:

\[\text{MRT}_{IV} = \frac{\text{AUMC}}{\text{AUC}} - \frac{\text{duration}}{2}\]

For an IV bolus (duration = 0) the correction is zero. For a 0.5 h infusion, the correction is 0.25 h. Any deviation from theory comes from the trapezoidal AUMC/AUC estimate itself, not from the correction term.

Use mrt.iv.obs (extrapolated to ∞) when the terminal phase is well characterized and mrt.iv.last (to last measurable) otherwise.


7.5 AUC over a specific interval

For steady-state or partial AUC windows:

# AUC from 0 to 4 hours
partial_auc <- data.frame(
  start   = 0,
  end     = 4,
  auclast = TRUE,
  cmax    = TRUE
)

o_data_partial <- PKNCAdata(o_conc, o_dose, intervals = partial_auc, impute = "start_conc0")
o_nca_partial  <- pk.nca(o_data_partial)

as.data.frame(o_nca_partial) |>
  select(Subject, PPTESTCD, PPORRES) |>
  arrange(Subject)
# A tibble: 12 × 3
   Subject PPTESTCD PPORRES
   <ord>   <chr>      <dbl>
 1 1       auclast     1.43
 2 1       cmax        1.5 
 3 4       auclast     2.12
 4 4       cmax        1.85
 5 2       auclast     2.29
 6 2       cmax        2.03
 7 5       auclast     1.58
 8 5       cmax        2.05
 9 6       auclast     2.39
10 6       cmax        2.31
11 3       auclast     2.51
12 3       cmax        2.72

Interval semantics are explained in Selection of Calculation Intervals.


7.6 Units

Assign units to get automatic unit propagation through derived parameters. See the Units chapter and Unit Assignment and Conversion with PKNCA for the full units workflow.

units_table <- pknca_units_table(
  concu  = "mcg/mL",
  timeu  = "h",
  doseu  = "mg",
  amountu = "mg"
)

o_conc_u <- PKNCAconc(d_conc, conc ~ time | Subject, concu = "mcg/mL", timeu = "h")
o_dose_u <- PKNCAdose(d_dose, dose ~ time | Subject, route = "intravascular",
                      doseu = "mg")
o_data_u <- PKNCAdata(o_conc_u, o_dose_u, intervals = iv_intervals, units = units_table,
                     impute = "start_conc0")
o_nca_u  <- pk.nca(o_data_u)

as.data.frame(o_nca_u) |>
  filter(PPTESTCD %in% c("auclast", "cl.obs", "vz.obs")) |>
  select(Subject, PPTESTCD, PPORRES) |>
  arrange(Subject, PPTESTCD)
# A tibble: 18 × 3
   Subject PPTESTCD PPORRES
   <ord>   <chr>      <dbl>
 1 1       auclast     1.72
 2 1       cl.obs     12.3 
 3 1       vz.obs     77.6 
 4 4       auclast     2.44
 5 4       cl.obs      9.59
 6 4       vz.obs     22.3 
 7 2       auclast     2.89
 8 2       cl.obs      7.93
 9 2       vz.obs     26.2 
10 5       auclast     1.92
11 5       cl.obs     11.6 
12 5       vz.obs     45.8 
13 6       auclast     2.84
14 6       cl.obs      8.08
15 6       vz.obs     22.8 
16 3       auclast     2.88
17 3       cl.obs      8.14
18 3       vz.obs     19.3 

7.7 Excluding observations

Mark individual observations to exclude from calculations (e.g. suspected sampling errors):

d_conc_with_excl <- d_conc |>
  mutate(excl = ifelse(Subject == "2" & time == 2, "suspected contamination", NA_character_))

o_conc_excl_gen <- PKNCAconc(d_conc_with_excl, conc ~ time | Subject, exclude = "excl")

# The flagged row stays in the data with its exclusion reason
as.data.frame(o_conc_excl_gen) |> filter(!is.na(excl))
  Subject time conc                    excl volume duration
1       2    2 0.36 suspected contamination     NA        0

The exclusion reason travels with the data (shown above), and the excluded point is dropped from every parameter calculation. This is broader than the exclude_half.life example in the half-life section, which removed a point from only the λz regression.

Below-limit-of-quantification (BLQ) values are handled separately from exclusions, via the conc.blq option — see the BLQ section of the Extravascular chapter.


7.8 Summary with custom statistics

# Default summary uses geometric mean ± CV% for most parameters
summary(o_nca)
 start end N     auclast        cmax                 tmax mrt.iv.last
     0 Inf 6 2.40 [23.0] 2.04 [20.3] 0.250 [0.250, 0.250] 1.93 [8.32]
   half.life     lambda.z  aucinf.obs      cl.obs  mrt.iv.obs      vz.obs
 2.44 [1.04] 0.303 [39.0] 2.65 [19.6] 9.45 [19.6] 2.82 [19.6] 31.2 [57.9]

Caption: auclast, cmax, mrt.iv.last, lambda.z, aucinf.obs, cl.obs, mrt.iv.obs, vz.obs: geometric mean and geometric coefficient of variation; tmax: median and range; half.life: arithmetic mean and standard deviation; N: number of subjects

Customize the summary function — for example, report arithmetic mean (SD) for Tmax instead of its default median [range]:

PKNCA.set.summary(
  "tmax",
  description = "arithmetic mean (SD)",
  point  = business.mean,
  spread = business.sd
)

summary(o_nca)
 start end N     auclast        cmax          tmax mrt.iv.last   half.life
     0 Inf 6 2.40 [23.0] 2.04 [20.3] 0.250 [0.000] 1.93 [8.32] 2.44 [1.04]
     lambda.z  aucinf.obs      cl.obs  mrt.iv.obs      vz.obs
 0.303 [39.0] 2.65 [19.6] 9.45 [19.6] 2.82 [19.6] 31.2 [57.9]

Caption: auclast, cmax, mrt.iv.last, lambda.z, aucinf.obs, cl.obs, mrt.iv.obs, vz.obs: geometric mean and geometric coefficient of variation; tmax: arithmetic mean (SD); half.life: arithmetic mean and standard deviation; N: number of subjects

Summary customization and results exclusion are covered in the Post-processing chapter and the Post-Processing vignette.


7.9 Full parameter list for IV

All IV-relevant parameters you can request in an interval:

interval_cols <- get.interval.cols()
iv_relevant <- c(
  # Concentration / time landmarks
  "c0", "cmax", "cmin", "tmax", "tmin", "tfirst", "tlast",
  "clast.obs", "clast.pred", "count_conc", "count_conc_measured",
  # AUC family
  "auclast", "aucall", "aucinf.obs", "aucinf.pred",
  "aucpext.obs", "aucpext.pred",
  "aucint.last", "aucint.last.dose", "aucint.all", "aucint.all.dose",
  "aucint.inf.obs", "aucint.inf.obs.dose",
  # IV-specific AUC
  "aucivlast", "aucivall", "aucivinf.obs", "aucivinf.pred",
  "aucivint.last", "aucivint.all",
  "aucivpbextlast", "aucivpbextall", "aucivpbextinf.obs", "aucivpbextinf.pred",
  # AUMC
  "aumclast", "aumcall", "aumcinf.obs", "aumcinf.pred",
  # Half-life / λz
  "half.life", "lambda.z", "lambda.z.n.points", "lambda.z.time.first", "lambda.z.time.last",
  "r.squared", "adj.r.squared", "span.ratio",
  # Clearance
  "cl.obs", "cl.pred", "cl.last", "cl.all",
  # Volume
  "vz.obs", "vz.pred", "vss.obs", "vss.pred", "vss.iv.obs", "vss.iv.pred",
  "vss.last", "vss.iv.last",
  # MRT
  "mrt.iv.last", "mrt.iv.obs", "mrt.iv.pred", "mrt.last", "mrt.obs", "mrt.pred",
  # Effective half-life / kel
  "thalf.eff.iv.last", "thalf.eff.iv.obs", "thalf.eff.iv.pred", "thalf.eff.pred",
  "thalf.eff.last", "thalf.eff.obs",
  "kel.obs", "kel.pred", "kel.last", "kel.iv.obs", "kel.iv.pred", "kel.iv.last",
  # Dose-normalized
  "auclast.dn", "aucinf.obs.dn", "cmax.dn", "clast.obs.dn"
)

# All registered in PKNCA
sort(names(interval_cols)[names(interval_cols) %in% iv_relevant])
 [1] "adj.r.squared"       "aucall"              "aucinf.obs"         
 [4] "aucinf.obs.dn"       "aucinf.pred"         "aucint.all"         
 [7] "aucint.all.dose"     "aucint.inf.obs"      "aucint.inf.obs.dose"
[10] "aucint.last"         "aucint.last.dose"    "aucivall"           
[13] "aucivinf.obs"        "aucivinf.pred"       "aucivint.all"       
[16] "aucivint.last"       "aucivlast"           "aucivpbextall"      
[19] "aucivpbextinf.obs"   "aucivpbextinf.pred"  "aucivpbextlast"     
[22] "auclast"             "auclast.dn"          "aucpext.obs"        
[25] "aucpext.pred"        "aumcall"             "aumcinf.obs"        
[28] "aumcinf.pred"        "aumclast"            "c0"                 
[31] "cl.all"              "cl.last"             "cl.obs"             
[34] "cl.pred"             "clast.obs"           "clast.obs.dn"       
[37] "clast.pred"          "cmax"                "cmax.dn"            
[40] "cmin"                "count_conc"          "count_conc_measured"
[43] "half.life"           "kel.iv.last"         "kel.iv.obs"         
[46] "kel.iv.pred"         "kel.last"            "kel.obs"            
[49] "kel.pred"            "lambda.z"            "lambda.z.n.points"  
[52] "lambda.z.time.first" "lambda.z.time.last"  "mrt.iv.last"        
[55] "mrt.iv.obs"          "mrt.iv.pred"         "mrt.last"           
[58] "mrt.obs"             "mrt.pred"            "r.squared"          
[61] "span.ratio"          "tfirst"              "thalf.eff.iv.last"  
[64] "thalf.eff.iv.obs"    "thalf.eff.iv.pred"   "thalf.eff.last"     
[67] "thalf.eff.obs"       "thalf.eff.pred"      "tlast"              
[70] "tmax"                "tmin"                "vss.iv.last"        
[73] "vss.iv.obs"          "vss.iv.pred"         "vss.last"           
[76] "vss.obs"             "vss.pred"            "vz.obs"             
[79] "vz.pred"