8  Multiple-Dose and Steady-State

8.1 Multiple-dose NCA concepts

At steady state, the dosing interval τ (tau) is the key unit of analysis. Instead of analyzing from 0 to ∞, you analyze within one dosing interval [0, τ]. Additional parameters describe how concentrations fluctuate over the interval.

Term Meaning
τ (tau) Dosing interval (h)
Ctrough Concentration at end of τ (= concentration at time = end of interval)
Cmin Minimum observed concentration within τ
Cmax Peak concentration within τ
Cav Average concentration over τ = AUCτ / τ
Peak-trough ratio (PTR) Cmax / Ctrough
Degree of fluctuation (Cmax − Cmin) / Cav × 100%
Swing (Cmax − Cmin) / Cmin × 100%

8.2 Synthetic steady-state dataset

We simulate steady-state oral theophylline to demonstrate all parameters. The key is that start and end define a single dosing interval.

set.seed(42)

# Theoph-like PK: Ka=1.5/h, ke=0.08/h, V=30L, dose=400mg
ka <- 1.5; ke <- 0.08; V <- 30; dose <- 400; tau <- 12

# One-compartment oral steady-state: C(t) = [dose/V * ka/(ka-ke)] * [e^(-ke*t)/(1-e^(-ke*tau)) - e^(-ka*t)/(1-e^(-ka*tau))]
C_ss <- function(t, ka, ke, V, dose, tau) {
  A <- (dose / V) * (ka / (ka - ke))
  A * (exp(-ke * t) / (1 - exp(-ke * tau)) - exp(-ka * t) / (1 - exp(-ka * tau)))
}

timepoints <- c(0, 0.5, 1, 1.5, 2, 3, 4, 6, 8, 10, 12)
n_subjects <- 4

d_conc_ss <- expand.grid(Subject = factor(1:n_subjects), time = timepoints) |>
  mutate(
    # Add ~20% log-normal IIV on clearance (ke)
    ke_i  = ke * exp(rnorm(n(), 0, 0.2)),
    conc  = mapply(C_ss, time, ka, ke_i, V, dose, tau) + rnorm(n(), 0, 0.05),
    conc  = pmax(conc, 0)
  ) |>
  select(Subject, time, conc)

d_dose_ss <- data.frame(
  Subject = factor(1:n_subjects),
  dose    = dose,
  time    = 0,
  route   = "extravascular"
)

ggplot(d_conc_ss, aes(x = time, y = conc, group = Subject, colour = Subject)) +
  geom_line() + geom_point() +
  labs(title = "Simulated steady-state oral profiles (one dosing interval)",
       x = "Time within interval (h)", y = "Concentration (mg/L)") +
  theme_minimal()


8.3 Basic steady-state NCA

For a single dosing interval, set end = tau. Parameters like cav, ctrough, ptr, deg.fluc, and swing all require a finite interval end.

o_conc_ss <- PKNCAconc(d_conc_ss, conc ~ time | Subject)
o_dose_ss <- PKNCAdose(d_dose_ss, dose ~ time | Subject, route = "extravascular")
Found column named route, using it for the attribute of the same name.
ss_intervals <- data.frame(
  start    = 0,
  end      = tau,
  auclast  = TRUE,
  cmax     = TRUE,
  tmax     = TRUE,
  ctrough  = TRUE,
  cstart   = TRUE,
  cav      = TRUE,
  ptr      = TRUE,
  deg.fluc = TRUE,
  swing    = TRUE
)

o_data_ss <- PKNCAdata(o_conc_ss, o_dose_ss, intervals = ss_intervals)
o_nca_ss  <- pk.nca(o_data_ss)

as.data.frame(o_nca_ss) |>
  filter(PPTESTCD %in% c("auclast", "cmax", "ctrough", "cstart", "cav",
                          "ptr", "deg.fluc", "swing")) |>
  select(Subject, PPTESTCD, PPORRES) |>
  tidyr::pivot_wider(names_from = PPTESTCD, values_from = PPORRES) |>
  arrange(Subject)
# A tibble: 4 × 9
  Subject auclast  cmax   cav ctrough cstart   ptr deg.fluc swing
  <fct>     <dbl> <dbl> <dbl>   <dbl>  <dbl> <dbl>    <dbl> <dbl>
1 1          156.  23.1  13.0    8.15   5.59  2.84     135.  314.
2 2          196.  28.4  16.3    9.75  10.3   2.91     114.  191.
3 3          179.  27.4  14.9    6.90   7.78  3.97     138.  297.
4 4          174.  22.5  14.5   10.8    7.27  2.08     105.  210.

8.4 Parameter reference

8.4.1 Cstart and Ctrough

cstart is the concentration at the start of the interval (time = start). For steady state this equals the pre-dose trough. ctrough is the concentration at the end of the interval (time = end). For steady state, cstartctrough.

as.data.frame(o_nca_ss) |>
  filter(PPTESTCD %in% c("cstart", "ctrough")) |>
  select(Subject, PPTESTCD, PPORRES) |>
  arrange(Subject, PPTESTCD)
# A tibble: 8 × 3
  Subject PPTESTCD PPORRES
  <fct>   <chr>      <dbl>
1 1       cstart      5.59
2 1       ctrough     8.15
3 2       cstart     10.3 
4 2       ctrough     9.75
5 3       cstart      7.78
6 3       ctrough     6.90
7 4       cstart      7.27
8 4       ctrough    10.8 

8.4.2 Cav — average concentration

cav = AUCτ / τ. Useful for computing clearance at steady state (CL/F = dose / (AUCτ)).

as.data.frame(o_nca_ss) |>
  filter(PPTESTCD %in% c("cav", "auclast")) |>
  select(Subject, PPTESTCD, PPORRES) |>
  tidyr::pivot_wider(names_from = PPTESTCD, values_from = PPORRES) |>
  mutate(cav_check = auclast / tau) |>
  arrange(Subject)
# A tibble: 4 × 4
  Subject auclast   cav cav_check
  <fct>     <dbl> <dbl>     <dbl>
1 1          156.  13.0      13.0
2 2          196.  16.3      16.3
3 3          179.  14.9      14.9
4 4          174.  14.5      14.5

8.4.3 PTR, degree of fluctuation, swing

These describe how much concentrations vary within a dosing interval — important for characterizing exposure variability and clinical relevance.

Parameter Formula
ptr Cmax / Ctrough
deg.fluc (Cmax − Cmin) / Cav × 100%
swing (Cmax − Cmin) / Cmin × 100%
as.data.frame(o_nca_ss) |>
  filter(PPTESTCD %in% c("ptr", "deg.fluc", "swing")) |>
  select(Subject, PPTESTCD, PPORRES) |>
  tidyr::pivot_wider(names_from = PPTESTCD, values_from = PPORRES) |>
  arrange(Subject)
# A tibble: 4 × 4
  Subject   ptr deg.fluc swing
  <fct>   <dbl>    <dbl> <dbl>
1 1        2.84     135.  314.
2 2        2.91     114.  191.
3 3        3.97     138.  297.
4 4        2.08     105.  210.

8.4.4 Ceoi — concentration at end of infusion

For IV infusions, ceoi is the concentration at the moment the infusion ends (= start + duration). For bolus or oral dosing, it equals cmax at the appropriate time.

ceoi_interval <- data.frame(
  start  = 0,
  end    = tau,
  ceoi   = TRUE,
  cmax   = TRUE
)

o_nca_ceoi <- pk.nca(PKNCAdata(o_conc_ss, o_dose_ss, intervals = ceoi_interval))

as.data.frame(o_nca_ceoi) |>
  filter(PPTESTCD %in% c("ceoi", "cmax")) |>
  select(Subject, PPTESTCD, PPORRES) |>
  tidyr::pivot_wider(names_from = PPTESTCD, values_from = PPORRES) |>
  arrange(Subject)
# A tibble: 4 × 3
  Subject  cmax  ceoi
  <fct>   <dbl> <dbl>
1 1        23.1  5.59
2 2        28.4 10.3 
3 3        27.4  7.78
4 4        22.5  7.27

8.5 Multiple dose intervals in one dataset

When data span multiple dose occasions, define one interval row per occasion per group. PKNCA applies each interval independently.

# Two dosing intervals: 0-12h and 12-24h
# The t=12 sample belongs to the START of the second interval; exclude it from the first
d_conc_occ1 <- d_conc_ss |> filter(time < 12)   # first interval: 0 to <12
d_conc_occ2 <- d_conc_ss |> mutate(time = time + 12)  # second interval: 12 to 24

d_conc_2dose <- bind_rows(d_conc_occ1, d_conc_occ2)

# Two dose events
d_dose_2dose <- bind_rows(
  d_dose_ss |> mutate(time = 0),
  d_dose_ss |> mutate(time = 12)
)

o_conc_2d <- PKNCAconc(d_conc_2dose, conc ~ time | Subject)
o_dose_2d <- PKNCAdose(d_dose_2dose, dose ~ time | Subject, route = "extravascular")
Found column named route, using it for the attribute of the same name.
# One interval per occasion
two_dose_intervals <- data.frame(
  start    = c(0, 12),
  end      = c(12, 24),
  auclast  = TRUE,
  cmax     = TRUE,
  ctrough  = TRUE,
  deg.fluc = TRUE
)

o_nca_2d <- pk.nca(PKNCAdata(o_conc_2d, o_dose_2d, intervals = two_dose_intervals))

as.data.frame(o_nca_2d) |>
  filter(PPTESTCD %in% c("auclast", "cmax", "ctrough")) |>
  select(Subject, start, end, PPTESTCD, PPORRES) |>
  arrange(Subject, start, PPTESTCD)
# A tibble: 24 × 5
   Subject start   end PPTESTCD PPORRES
   <fct>   <dbl> <dbl> <chr>      <dbl>
 1 1           0    12 auclast   152.  
 2 1           0    12 cmax       23.1 
 3 1           0    12 ctrough     5.59
 4 1          12    24 auclast   156.  
 5 1          12    24 cmax       23.1 
 6 1          12    24 ctrough    NA   
 7 2           0    12 auclast   197.  
 8 2           0    12 cmax       28.4 
 9 2           0    12 ctrough    10.3 
10 2          12    24 auclast   196.  
# ℹ 14 more rows

8.6 Finding tau automatically: find.tau()

When the dosing interval is not known a priori, find.tau() estimates it from a numeric vector of dose times. It returns the most common (modal) inter-dose interval.

# Dose times for one subject: 0, 12, 24, 36, 48, 60 h
dose_times <- c(0, 12, 24, 36, 48, 60)
find.tau(dose_times)
[1] 12

Use the result to construct intervals programmatically:

# find.tau() expects one subject's dose times — filter before calling
tau_detected <- find.tau(d_dose_2dose$time[d_dose_2dose$Subject == 1])
cat("Detected tau:", tau_detected, "h\n")
Detected tau: 12 h

8.7 Vss at multiple dose (vss.md.*)

vss.md.obs and vss.md.pred are the volume of distribution at steady state using the multiple-dose MRT formula:

\[V_{ss} = CL \times MRT_{md}\]

where MRT_md accounts for the accumulation over repeated dosing.

Parameter Meaning
mrt.md.obs Multiple-dose MRT (observed Clast)
mrt.md.pred Multiple-dose MRT (predicted Clast)
vss.md.obs Vss from mrt.md.obs and cl.last
vss.md.pred Vss from mrt.md.pred and cl.last

Note: vss.md.* and mrt.md.* require the tau argument internally but PKNCA 0.12.1 does not correctly resolve it from the interval specification for general use. These parameters work correctly when PKNCA can determine tau from the dosing schedule. Use them in conjunction with explicitly specified tau intervals and verify output against manual calculations.


pkgdown reference: PKNCAconc() · PKNCAdose() · PKNCAdata() · pk.nca() · find.tau()