9  Superposition

9.1 What superposition does

Superposition predicts steady-state concentration-time profiles from a single-dose profile by linearly summing lagged copies of the single-dose curve. This is the NCA equivalent of multi-dose simulation, and it requires no compartmental model. Observed steady-state parameters (Cav, PTR, fluctuation) are covered in Multiple-Dose and Steady-State; estimating when steady state is reached is covered in Time to Steady State.

The core assumption is linear, time-invariant pharmacokinetics: the drug does not accumulate non-linearly, and clearance and volume do not change over time. See also the package vignette Superposition of Pharmacokinetic Data.

flowchart LR
    A["Single dose<br/>profile (0→∞)"] --> B["Shift by τ,<br/>shift by 2τ, ..."]
    B --> C["Sum contributions<br/>at each time t"]
    C --> D["Predicted SS<br/>profile (0→τ)"]


9.2 Basic superposition

Theoph records dose in mg/kg; converting it to each subject’s total dose in mg gives the value that dose.input expects:

d_conc <- as.data.frame(Theoph) |> rename(time = Time, subject = Subject)

# Theoph dose is mg/kg — multiply by body weight for the total mg dose
d_dose <- Theoph |> as.data.frame() |>
  group_by(Subject) |>
  summarise(dose = Dose[1] * Wt[1], .groups = "drop") |>
  rename(subject = Subject) |>
  mutate(time = 0)
# Use Theoph subject 1 as single-dose reference
subj1 <- d_conc |> filter(subject == "1")
actual_dose <- d_dose |> filter(subject == "1") |> pull(dose)

o_conc_1 <- PKNCAconc(subj1, conc ~ time | subject)

# Profile during the dosing interval after the 3rd dose (τ = 24 h)
ss_profile <- superposition(
  o_conc_1,
  tau         = 24,           # dosing interval (h)
  dose.input  = actual_dose,  # dose used to generate the single-dose data
  dose.amount = actual_dose,  # dose to simulate at SS (same here)
  n.tau       = 3,            # simulate 3 intervals
  check.blq   = FALSE         # Theoph has non-zero first sample
)

head(ss_profile, 12)
# A tibble: 12 × 3
   subject  conc   time
   <ord>   <dbl>  <dbl>
 1 1        5.12  0    
 2 1        7.17  0.25 
 3 1        8.54  0.370
 4 1       10.8   0.57 
 5 1       14.7   1.12 
 6 1       13.6   2.02 
 7 1       12.2   3.82 
 8 1       11.8   5.1  
 9 1       10.6   7.03 
10 1        9.72  9.05 
11 1        8.38 12.1  
12 1        4.71 24    

9.3 Visualizing the approach to steady state

With a finite n.tau, superposition() returns the concentration profile over the dosing interval after that many doses; with n.tau = Inf it accumulates doses until the profile converges to steady state (within steady.state.tol). Overlaying the profiles after 1, 2, and 3 doses on the converged profile makes the approach to steady state visible.

approach <- lapply(1:3, function(k) {
  superposition(o_conc_1, tau = 24, dose.input = actual_dose,
                dose.amount = actual_dose, n.tau = k, check.blq = FALSE) |>
    mutate(profile = paste("After dose", k))
}) |> bind_rows()

ss_converged <- superposition(
  o_conc_1,
  tau         = 24,
  dose.input  = actual_dose,
  dose.amount = actual_dose,
  n.tau       = Inf,
  check.blq   = FALSE
)

bind_rows(approach, ss_converged |> mutate(profile = "Steady state")) |>
  mutate(profile = factor(profile, levels = c(paste("After dose", 1:3), "Steady state"))) |>
  ggplot(aes(x = time, y = conc, colour = profile)) +
  geom_line() +
  geom_point(size = 1.5) +
  labs(title = "Superposition: approach to steady state (τ = 24 h)",
       x = "Time within interval (h)", y = "Predicted concentration (mg/L)",
       colour = NULL) +
  theme_minimal()


9.4 Changing dose at steady state

To predict the effect of a dose change, set dose.amount to the new dose while keeping dose.input as the original dose. PKNCA scales the concentrations proportionally.

ss_half_dose <- superposition(
  o_conc_1,
  tau         = 24,
  dose.input  = actual_dose,
  dose.amount = actual_dose / 2,   # half dose
  n.tau       = Inf,
  check.blq   = FALSE
)

bind_rows(
  ss_converged   |> mutate(regimen = "Full dose"),
  ss_half_dose   |> mutate(regimen = "Half dose")
) |>
  ggplot(aes(x = time, y = conc, colour = regimen)) +
  geom_line() + geom_point(size = 2) +
  labs(title = "Dose scaling via superposition",
       x = "Time (h)", y = "Predicted SS concentration (mg/L)") +
  scale_colour_manual(values = c("Full dose" = "steelblue", "Half dose" = "coral")) +
  theme_minimal()


9.5 Changing the dosing interval (τ)

Shortening τ to 8 h raises troughs and reduces peak-to-trough fluctuation:

ss_8h <- superposition(
  o_conc_1,
  tau         = 8,
  dose.input  = actual_dose,
  dose.amount = actual_dose,
  n.tau       = Inf,
  check.blq   = FALSE
)

ggplot(ss_8h, aes(x = time, y = conc)) +
  geom_line(colour = "firebrick") +
  geom_point(size = 2, colour = "firebrick") +
  labs(title = "Superposition with τ = 8 h (more frequent dosing)",
       x = "Time within interval (h)", y = "Predicted SS concentration (mg/L)") +
  theme_minimal()


9.6 Multiple doses within an interval

dose.times (default 0) sets the dose time(s) within each interval, and a vector places several doses inside one τ: tau = 24 with dose.times = c(0, 12) is twice-daily dosing described as a single 24 h interval. dose.amount applies to each administration, so halving it here keeps the total daily dose equal to the once-daily regimen above:

ss_bid <- superposition(
  o_conc_1,
  tau         = 24,
  dose.input  = actual_dose,
  dose.amount = actual_dose / 2,  # half dose per administration
  dose.times  = c(0, 12),         # doses at 0 h and 12 h in each interval
  n.tau       = Inf,
  check.blq   = FALSE
)

bind_rows(
  ss_converged |> mutate(regimen = "Once daily, full dose"),
  ss_bid       |> mutate(regimen = "Twice daily, half dose")
) |>
  ggplot(aes(x = time, y = conc, colour = regimen)) +
  geom_line() + geom_point(size = 2) +
  labs(title = "Two doses per interval: dose.times = c(0, 12), τ = 24 h",
       x = "Time within interval (h)", y = "Predicted SS concentration (mg/L)",
       colour = NULL) +
  theme_minimal()

At the same total daily dose, splitting the dose lowers Cmax and raises the trough. The dose times need not be evenly spaced: dose.times = c(0, 8) with tau = 24 describes a staggered morning/evening regimen (doses 8 h apart, then a 16 h overnight gap).


9.7 Extracting accumulation ratio

One common definition of the accumulation ratio (Rac) is Cmax at steady state divided by Cmax after the first dose (an AUC-based Rac, AUCτ,ss / AUCτ,first, is equally common):

cmax_sd <- max(subj1$conc, na.rm = TRUE)
cmax_ss <- max(ss_converged$conc, na.rm = TRUE)
rac      <- cmax_ss / cmax_sd
cat("Accumulation ratio (Rac):", round(rac, 2), "\n")
Accumulation ratio (Rac): 1.44 
cat("Single-dose Cmax:", round(cmax_sd, 2), "mg/L\n")
Single-dose Cmax: 10.5 mg/L
cat("Steady-state Cmax:", round(cmax_ss, 2), "mg/L\n")
Steady-state Cmax: 15.1 mg/L

9.8 Key arguments reference

Argument Required Meaning
tau Yes Dosing interval (same time units as data)
dose.input No Dose that generated the observed profile; supply together with dose.amount to dose-scale
dose.amount No Dose to simulate; if omitted, the output dose equals the input dose
dose.times No (default 0) Dose time(s) within the interval; a vector gives multiple/staggered doses per τ
n.tau No (default Inf) Number of intervals to simulate before stopping
steady.state.tol No (default 0.001) Convergence tolerance for n.tau = Inf
check.blq No (default TRUE) Require first concentration = 0; set FALSE if first sample is non-zero
auc.type No (default "AUCinf") How to extrapolate the single-dose tail: "AUCinf", "AUClast", or "AUCall"
additional.times No Extra timepoints to include in the output

Interpolation and tail extrapolation follow the dose-aware interpolation methods.

Limitation: Superposition assumes linear PK. It should not be used for drugs with non-linear kinetics (e.g. Michaelis-Menten elimination, time-varying clearance, or saturable protein binding).


pkgdown reference: PKNCAconc() · superposition()