8  Intravascular (IV) Examples

8.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.


8.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).
# auclast/aucinf.obs need a concentration at the interval start, so
# impute = "start_conc0" adds conc = 0 at t = 0. For an IV bolus this
# underestimates the AUC; the auciv* family below uses the back-extrapolated C0.
# 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. The zero imputed at the dose time keeps this chapter’s examples short, but it is not the concentration after an IV bolus: the auciv* family and the clearance and volume built on it (cl.iv.*, vz.iv.*) replace the first segment using the back-extrapolated C0. When intervals is omitted, the automatically generated intervals (≥ 0.13.0) treat an IV bolus the same way: they impute with start_predose_conc0 (a predose concentration if there is one, else 0, keeping any concentration measured at the dose time) and add a separate request for c0.


8.3 Parameter reference

8.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.13.0)
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

8.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.

8.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.

8.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 in the fit; then exclude fits with
# R² < 0.95 (min.hl.r.squared is applied by the exclusion rule, not by the fit)
o_data_strict <- PKNCAdata(
  o_conc, o_dose,
  intervals = hl_params,
  impute = "start_conc0",
  options = list(min.hl.points = 4)
)
o_nca_strict <- pk.nca(o_data_strict) |>
  exclude(FUN = exclude_nca_min.hl.r.squared(0.95))

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

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. The half-life parameters need no concentration at the dose time, so this interval is calculated without imputation:

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

hl_params_times <- hl_params |>
  mutate(lambda.z.time.first = TRUE, lambda.z.time.last = TRUE)

o_nca_hl_all  <- pk.nca(PKNCAdata(o_conc, o_dose, intervals = hl_params_times))
o_nca_excl_hl <- pk.nca(PKNCAdata(o_conc_excl, o_dose, intervals = hl_params_times))

# Subject 1's terminal fit changes when the 8 h point is excluded
bind_rows(
  as.data.frame(o_nca_hl_all)  |> mutate(version = "all points"),
  as.data.frame(o_nca_excl_hl) |> mutate(version = "8 h excluded")
) |>
  filter(Subject == "1",
         PPTESTCD %in% c("lambda.z.time.first", "lambda.z.time.last",
                         "lambda.z.n.points", "half.life")) |>
  select(version, PPTESTCD, PPORRES) |>
  tidyr::pivot_wider(names_from = PPTESTCD, values_from = PPORRES)
# A tibble: 2 × 5
  version     lambda.z.time.first lambda.z.time.last lambda.z.n.points half.life
  <chr>                     <dbl>              <dbl>             <dbl>     <dbl>
1 all points                    5                  8                 3      4.38
2 8 h exclud…                   2                  6                 5      2.89

With all points, the best fit uses the last three samples (5 to 8 h). Without the 8 h sample, the selected fit runs from 2 to 6 h with five points, and the half-life is shorter.

Warning

In the PKNCA development version this book documents, combining exclude_half.life (or include_half.life) with an imputation that adds a concentration, such as start_conc0, misaligns the per-point flags with the concentrations: the flags stay attached to the original row positions while the imputed point shifts the concentrations by one. Calculate half-life with point selection in an interval without imputation, as here.

8.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.obs Vss with the IV MRT (infusion-duration correction)
cl.iv.obs Dose / AUCiv,inf,obs CL from the AUC with C0 back-extrapolation (≥ 0.13.0)
cl.iv.last Dose / AUCiv,last As above, from aucivlast (≥ 0.13.0)
vz.iv.obs CL.iv.obs / λz Vz from the back-extrapolated AUC (≥ 0.13.0)

PKNCA 0.13.0 completed these families across AUC bases: each of cl.*, vz.*, and vss.* has .obs, .pred, .last, .all, and interval-AUC (.int.last, .int.all, .int.inf.obs, .int.inf.pred) forms, and the IV bolus variants cl.iv.*, vz.iv.* (built on auciv*) and vss.iv.* (built on mrt.iv.*) come in the matching forms (cl.iv.all, cl.ivint.last, vz.iv.last, vss.ivint.all, …). The full list is printed at the end of this chapter.

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,
  cl.iv.obs  = TRUE,
  vz.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",
                         "cl.iv.obs", "vz.iv.obs")) |>
  select(Subject, PPTESTCD, PPORRES) |>
  arrange(Subject, PPTESTCD)
# A tibble: 48 × 3
   Subject PPTESTCD   PPORRES
   <ord>   <chr>        <dbl>
 1 1       cl.iv.obs    10.7 
 2 1       cl.last      14.5 
 3 1       cl.obs       12.3 
 4 1       cl.pred      12.3 
 5 1       vss.iv.obs   47.2 
 6 1       vss.obs      47.2 
 7 1       vz.iv.obs    67.9 
 8 1       vz.obs       77.6 
 9 4       cl.iv.obs     8.59
10 4       cl.last      10.2 
# ℹ 38 more rows

cl.iv.obs is lower than cl.obs for every subject because its AUC includes the back-extrapolated area between the dose and the first sample, which the imputed zero leaves out of aucinf.obs.

8.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 other AUC bases follow the same naming: mrt.all / mrt.iv.all (AUCall), mrt.int.* / mrt.ivint.* (interval AUCs; ≥ 0.13.0), and the steady-state mrt.md.* and mrt.ivmd.* (see the infusion section).

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

8.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

8.3.8 IV-route AUC (auciv*)

auciv* parameters compute the AUC of an IV bolus with C0: the first segment, from the dose to the first sample, is integrated from the back-extrapolated c0 (see C0 above) rather than from whatever concentration the data hold at time 0. Since PKNCA 0.13.0 they do not need a concentration at time 0 at all: when the profile starts after the dose, c0 supplies it. The aumciv* parameters (≥ 0.13.0) do the same for the AUMC.

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
aumcivlast, aumcivall, aumcivinf.obs, aumcivinf.pred, aumcivint.last, aumcivint.all The matching AUMCs, IV method
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

The percent back-extrapolated, 100 × (1 − AUC / AUCiv), compares the IV AUC with the ordinary AUC calculated from the data, so it needs a concentration at time 0 in the data: one that was measured, or one imputed as here. Without any concentration at time 0, aucivpbext* is NA (≥ 0.13.0) with the reason “Percent back-extrapolated requires a measured concentration at time 0”, because the ordinary AUC then either cannot be calculated or already extrapolates back to time 0 and does not describe the observed part.

This example uses start_predose_conc0, the imputation that the automatic intervals use for an IV bolus (Indometh has no predose samples, so it imputes 0 at the dose time, as start_conc0 would):

auciv_params <- data.frame(
  start              = 0,
  end                = Inf,
  aucivlast          = TRUE,
  aucivall           = TRUE,
  aucivinf.obs       = TRUE,
  aucivinf.pred      = TRUE,
  aumcivinf.obs      = 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_predose_conc0"))

as.data.frame(o_nca_auciv) |>
  filter(grepl("^(auciv|aumciv)", PPTESTCD)) |>
  select(Subject, PPTESTCD, PPORRES) |>
  arrange(Subject, PPTESTCD)
# A tibble: 54 × 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 1       aumcivinf.obs         7.83
10 4       aucivall              2.75
# ℹ 44 more rows

When to use auciv* vs auclast/aucinf.obs: For an IV bolus sampled after the dose, aucivlast and aucivinf.obs give the more theoretically correct AUC, and cl.iv.* / vz.iv.* carry it into clearance and volume. aucivpbext* shows how much of that AUC came from back-extrapolation; for Indometh it is 9–20% depending on the subject and AUC, so the choice matters here.


8.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
)

8.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

8.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.

At steady state after repeated infusions, use mrt.ivmd.obs / mrt.ivmd.pred and vss.ivmd.obs / vss.ivmd.pred (≥ 0.13.0). They subtract half the infusion duration, which mrt.md.* and vss.md.* do not, so for an infusion those are high by duration/2 (MRT) and by cl.last × duration/2 (Vss). All of them need the dosing interval tau, which is detected from the dose times or given as a tau column of the intervals; see the Multiple-Dose chapter.


8.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

auclast integrates only the measured points inside the window. For an AUC over exactly 0–4 h whether or not a sample falls at 4 h, request aucint.last (or aucint.all, aucint.inf.obs, aucint.inf.pred), which interpolate or extrapolate to the window ends; aucivint.last and aucivint.all do the same with the IV C0. Every aucint parameter is dose-aware since PKNCA 0.13.0, so the former aucint.*.dose variants are retired (requesting one is an error naming its replacement); see the AUC chapter and Dose-Aware Interpolation chapter.

Interval semantics are explained in Selection of Calculation Intervals.


8.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 

8.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
1       2    2 0.36 suspected contamination

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.


8.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.


8.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.all", "aucint.inf.obs", "aucint.inf.pred",
  # IV-specific AUC and AUMC
  "aucivlast", "aucivall", "aucivinf.obs", "aucivinf.pred",
  "aucivint.last", "aucivint.all",
  "aucivpbextlast", "aucivpbextall", "aucivpbextinf.obs", "aucivpbextinf.pred",
  "aucivpbextint.last", "aucivpbextint.all",
  "aumcivlast", "aumcivall", "aumcivinf.obs", "aumcivinf.pred",
  "aumcivint.last", "aumcivint.all",
  # AUMC
  "aumclast", "aumcall", "aumcinf.obs", "aumcinf.pred",
  "aumcint.last", "aumcint.all", "aumcint.inf.obs", "aumcint.inf.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",
  "cl.int.last", "cl.int.all", "cl.int.inf.obs", "cl.int.inf.pred",
  "cl.iv.obs", "cl.iv.pred", "cl.iv.last", "cl.iv.all", "cl.ivint.last", "cl.ivint.all",
  # Volume
  "vz.obs", "vz.pred", "vz.last", "vz.all",
  "vz.int.last", "vz.int.all", "vz.int.inf.obs", "vz.int.inf.pred",
  "vz.iv.obs", "vz.iv.pred", "vz.iv.last", "vz.iv.all", "vz.ivint.last", "vz.ivint.all",
  "vss.obs", "vss.pred", "vss.last", "vss.all",
  "vss.int.last", "vss.int.all", "vss.int.inf.obs", "vss.int.inf.pred",
  "vss.iv.obs", "vss.iv.pred", "vss.iv.last", "vss.iv.all", "vss.ivint.last", "vss.ivint.all",
  "vss.md.obs", "vss.md.pred", "vss.ivmd.obs", "vss.ivmd.pred",
  # MRT
  "mrt.last", "mrt.obs", "mrt.pred", "mrt.all",
  "mrt.int.last", "mrt.int.all", "mrt.int.inf.obs", "mrt.int.inf.pred",
  "mrt.iv.last", "mrt.iv.obs", "mrt.iv.pred", "mrt.iv.all", "mrt.ivint.last", "mrt.ivint.all",
  "mrt.md.obs", "mrt.md.pred", "mrt.ivmd.obs", "mrt.ivmd.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.all",
  "kel.int.last", "kel.int.all", "kel.int.inf.obs", "kel.int.inf.pred",
  "kel.iv.obs", "kel.iv.pred", "kel.iv.last", "kel.iv.all", "kel.ivint.last", "kel.ivint.all",
  # Dose-normalized
  "auclast.dn", "aucinf.obs.dn", "cmax.dn", "clast.obs.dn"
)

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.inf.obs"      "aucint.inf.pred"     "aucint.last"        
 [10] "aucivall"            "aucivinf.obs"        "aucivinf.pred"      
 [13] "aucivint.all"        "aucivint.last"       "aucivlast"          
 [16] "aucivpbextall"       "aucivpbextinf.obs"   "aucivpbextinf.pred" 
 [19] "aucivpbextint.all"   "aucivpbextint.last"  "aucivpbextlast"     
 [22] "auclast"             "auclast.dn"          "aucpext.obs"        
 [25] "aucpext.pred"        "aumcall"             "aumcinf.obs"        
 [28] "aumcinf.pred"        "aumcint.all"         "aumcint.inf.obs"    
 [31] "aumcint.inf.pred"    "aumcint.last"        "aumcivall"          
 [34] "aumcivinf.obs"       "aumcivinf.pred"      "aumcivint.all"      
 [37] "aumcivint.last"      "aumcivlast"          "aumclast"           
 [40] "c0"                  "cl.all"              "cl.int.all"         
 [43] "cl.int.inf.obs"      "cl.int.inf.pred"     "cl.int.last"        
 [46] "cl.iv.all"           "cl.iv.last"          "cl.iv.obs"          
 [49] "cl.iv.pred"          "cl.ivint.all"        "cl.ivint.last"      
 [52] "cl.last"             "cl.obs"              "cl.pred"            
 [55] "clast.obs"           "clast.obs.dn"        "clast.pred"         
 [58] "cmax"                "cmax.dn"             "cmin"               
 [61] "count_conc"          "count_conc_measured" "half.life"          
 [64] "kel.all"             "kel.int.all"         "kel.int.inf.obs"    
 [67] "kel.int.inf.pred"    "kel.int.last"        "kel.iv.all"         
 [70] "kel.iv.last"         "kel.iv.obs"          "kel.iv.pred"        
 [73] "kel.ivint.all"       "kel.ivint.last"      "kel.last"           
 [76] "kel.obs"             "kel.pred"            "lambda.z"           
 [79] "lambda.z.n.points"   "lambda.z.time.first" "lambda.z.time.last" 
 [82] "mrt.all"             "mrt.int.all"         "mrt.int.inf.obs"    
 [85] "mrt.int.inf.pred"    "mrt.int.last"        "mrt.iv.all"         
 [88] "mrt.iv.last"         "mrt.iv.obs"          "mrt.iv.pred"        
 [91] "mrt.ivint.all"       "mrt.ivint.last"      "mrt.ivmd.obs"       
 [94] "mrt.ivmd.pred"       "mrt.last"            "mrt.md.obs"         
 [97] "mrt.md.pred"         "mrt.obs"             "mrt.pred"           
[100] "r.squared"           "span.ratio"          "tfirst"             
[103] "thalf.eff.iv.last"   "thalf.eff.iv.obs"    "thalf.eff.iv.pred"  
[106] "thalf.eff.last"      "thalf.eff.obs"       "thalf.eff.pred"     
[109] "tlast"               "tmax"                "tmin"               
[112] "vss.all"             "vss.int.all"         "vss.int.inf.obs"    
[115] "vss.int.inf.pred"    "vss.int.last"        "vss.iv.all"         
[118] "vss.iv.last"         "vss.iv.obs"          "vss.iv.pred"        
[121] "vss.ivint.all"       "vss.ivint.last"      "vss.ivmd.obs"       
[124] "vss.ivmd.pred"       "vss.last"            "vss.md.obs"         
[127] "vss.md.pred"         "vss.obs"             "vss.pred"           
[130] "vz.all"              "vz.int.all"          "vz.int.inf.obs"     
[133] "vz.int.inf.pred"     "vz.int.last"         "vz.iv.all"          
[136] "vz.iv.last"          "vz.iv.obs"           "vz.iv.pred"         
[139] "vz.ivint.all"        "vz.ivint.last"       "vz.last"            
[142] "vz.obs"              "vz.pred"            
# Every name above is registered
setdiff(iv_relevant, names(interval_cols))
character(0)

This list is curated. pknca_parameter_table() (≥ 0.13.0) records the routes each registered parameter applies to, so the parameters for an IV bolus can be listed directly; for example, those that apply to an IV bolus but not to extravascular dosing:

d_param_table <- pknca_parameter_table()
d_param_table$parameter[grepl("iv_bolus", d_param_table$route) &
                        !grepl("extravascular", d_param_table$route)]
 [1] "c0"                 "mrt.iv.last"        "vss.iv.last"       
 [4] "aucivlast"          "aucivlast_se"       "aucivlast_df"      
 [7] "aucivall"           "aucivall_se"        "aucivall_df"       
[10] "aucivint.last"      "aucivint.all"       "aucivpbextlast"    
[13] "aucivpbextall"      "aucivpbextint.last" "aucivpbextint.all" 
[16] "aumcivlast"         "aumcivlast_se"      "aumcivlast_df"     
[19] "aumcivall"          "aumcivall_se"       "aumcivall_df"      
[22] "aumcivint.last"     "aumcivint.all"      "thalf.eff.iv.last" 
[25] "kel.iv.last"        "cl.iv.all"          "cl.iv.last"        
[28] "cl.ivint.all"       "cl.ivint.last"      "mrt.iv.all"        
[31] "mrt.ivint.all"      "mrt.ivint.last"     "vz.iv.all"         
[34] "vz.iv.last"         "vz.ivint.all"       "vz.ivint.last"     
[37] "vss.iv.all"         "vss.ivint.all"      "vss.ivint.last"    
[40] "aucivinf.obs"       "aucivinf.obs_se"    "aucivinf.obs_df"   
[43] "aucivinf.pred"      "aucivpbextinf.obs"  "aucivpbextinf.pred"
[46] "aumcivinf.obs"      "aumcivinf.obs_se"   "aumcivinf.obs_df"  
[49] "aumcivinf.pred"     "kel.iv.all"         "kel.ivint.all"     
[52] "kel.ivint.last"     "cl.iv.obs"          "cl.iv.pred"        
[55] "mrt.iv.obs"         "mrt.iv.pred"        "mrt.ivmd.obs"      
[58] "mrt.ivmd.pred"      "vz.iv.obs"          "vz.iv.pred"        
[61] "vss.iv.obs"         "vss.iv.pred"        "vss.ivmd.obs"      
[64] "vss.ivmd.pred"      "thalf.eff.iv.obs"   "thalf.eff.iv.pred" 
[67] "kel.iv.obs"         "kel.iv.pred"