---
title: "Multiple-Dose and Steady-State"
---
```{r setup, include=FALSE}
library(PKNCA)
library(dplyr)
library(ggplot2)
conflicted::conflicts_prefer(dplyr::filter, dplyr::select, .quiet = TRUE)
```
## 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. Two companion chapters complete the steady-state picture: [Superposition](superposition.qmd) predicts steady-state profiles (and accumulation ratios) from single-dose data, and [Time to Steady State](tss.qmd) estimates when steady state is reached.
| 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% |
---
## 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.
```{r}
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 inter-individual variability (IIV) on the elimination rate constant (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()
```
---
## 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.
```{r}
o_conc_ss <- PKNCAconc(d_conc_ss, conc ~ time | Subject)
o_dose_ss <- PKNCAdose(d_dose_ss, dose ~ time | Subject, route = "extravascular")
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)
```
---
## Parameter reference
### 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, `cstart` ≈ `ctrough`.
```{r}
as.data.frame(o_nca_ss) |>
filter(PPTESTCD %in% c("cstart", "ctrough")) |>
select(Subject, PPTESTCD, PPORRES) |>
arrange(Subject, PPTESTCD)
```
### Cav — average concentration
`cav = AUCτ / τ`. Useful for computing clearance at steady state (CL/F = dose / (AUCτ)).
```{r}
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)
```
### 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% |
```{r}
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)
```
### Ceoi — concentration at end of infusion
`ceoi` is the concentration at the end of infusion (time = dose time + `duration`). It is meaningful only for infusion dosing with a nonzero duration; for bolus or oral dosing (duration 0) it is generally not meaningful — it simply returns the concentration at the dose time.
```{r}
# 100 mg infused over 1 h into V = 30 L with CL = 5 L/h (plateau = rate/CL = 20 mg/L)
ke_inf <- 5 / 30
t_inf <- c(0, 0.5, 1, 1.5, 2, 4, 8, 12)
c_inf <- ifelse(t_inf <= 1, 20 * (1 - exp(-ke_inf * t_inf)),
20 * (1 - exp(-ke_inf)) * exp(-ke_inf * (t_inf - 1)))
d_inf <- data.frame(Subject = 1, time = t_inf, conc = round(c_inf, 3))
d_dose_inf <- data.frame(Subject = 1, dose = 100, time = 0, duration = 1)
o_conc_inf <- PKNCAconc(d_inf, conc ~ time | Subject)
o_dose_inf <- PKNCAdose(d_dose_inf, dose ~ time | Subject,
route = "intravascular", duration = "duration")
o_nca_inf <- pk.nca(PKNCAdata(o_conc_inf, o_dose_inf,
intervals = data.frame(start = 0, end = 12,
ceoi = TRUE, cmax = TRUE)))
as.data.frame(o_nca_inf) |>
filter(PPTESTCD %in% c("ceoi", "cmax")) |>
select(PPTESTCD, PPORRES)
```
Here `ceoi` equals `cmax` because an infusion peaks exactly when the pump stops. After bolus or oral dosing there is no infusion end, so request `cmax`/`tmax` instead.
---
## Multiple dosing occasions in one dataset
When data span multiple dose occasions, define one interval row per occasion per group. PKNCA applies each interval independently. For the full interval-specification rules, see the vignette [Selection of Calculation Intervals](https://humanpred.github.io/pknca/articles/v03-selection-of-calculation-intervals.html).
```{r}
# Two dosing intervals: 0-12h and 12-24h
# Drop the duplicate t=12 row before shifting; PKNCA uses a boundary sample for both adjacent intervals
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")
# 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)
```
---
## 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. If the doses are equally spaced it returns that spacing; otherwise it searches the candidate intervals in `tau.choices` for the repeating interval most consistent with the dose times. Sentinel values flag special cases: `0` when all doses occur at a single time, `-1` when no candidate interval repeats, and `NA` when tau cannot be determined.
```{r}
# 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)
```
Detect tau from the two-occasion dataset above:
```{r}
# 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")
```
---
## Multiple-dose Vss (`vss.md.*`)
`vss.md.obs` and `vss.md.pred` are estimates of 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:** As of the version this book was built against, `vss.md.*` may fail to resolve its MRT inputs from the interval specification (the calculation errors rather than returning a value). If you need Vss at steady state, compute it manually from `cl.*` and `mrt.md.*` results.
---
::: {.callout-note icon=false appearance="minimal"}
**pkgdown reference:** [PKNCAconc()](https://humanpred.github.io/pknca/reference/PKNCAconc.html) · [PKNCAdose()](https://humanpred.github.io/pknca/reference/PKNCAdose.html) · [PKNCAdata()](https://humanpred.github.io/pknca/reference/PKNCAdata.html) · [pk.nca()](https://humanpred.github.io/pknca/reference/pk.nca.html) · [find.tau()](https://humanpred.github.io/pknca/reference/find.tau.html)
:::