10  Superposition

10.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→τ)"]


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

10.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()


10.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()


10.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()


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


10.7 Predicted 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). With superposition, the steady-state Cmax is a prediction, so Rac is computed by hand from the predicted profile:

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

When both the first-dose and the steady-state profiles were observed, PKNCA calculates the accumulation ratio itself with the ratio.cmax, ratio.auclast, or ratio.aucint.last secondary parameters (≥ 0.13.0); see Secondary Parameters.


10.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"
method No (default the auc.method option) Interpolation method: "lin up/log down", "linear", or "lin-log"
lambda.z, clast.pred, tlast No Supply the terminal elimination rate, predicted Clast (or TRUE to calculate it), and Tlast instead of having them calculated
additional.times No Extra timepoints to include in the output

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

10.8.1 Input checks and edge cases (≥ 0.13.0)

  • Missing (NA) concentrations are dropped before calculating; they used to stop the half-life fit with an error.
  • method and auc.type are checked for every input, even when the calculation needs neither interpolation nor extrapolation.
  • With n.tau = Inf and concentrations extrapolated as zero after tlast (for example auc.type = "AUClast" when tlast < tau), steady state is judged on the concentrations that can become nonzero, and a warning says that zero concentrations remain in the profile. If steady state is not reached within 10000 dosing intervals, superposition() stops with an error. Earlier versions looped forever in both cases.
  • Warnings and messages from superposition() on a PKNCAconc object now reach the caller on every platform. Each group runs in a forked worker, and its conditions used to be lost outside Windows.
  • Date-time sample times are an error (pknca_error_datetime_superposition). Superposition needs numeric times; PKNCAdata() converts date-times to numbers relative to the first dose.

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