Linezolid (Mockeliunas 2022)
Source:vignettes/articles/Mockeliunas_2022_linezolid.Rmd
Mockeliunas_2022_linezolid.RmdModel and source
- Citation: Mockeliunas L, Keutzer L, Sturkenboom MGG, Bolhuis MS, Hulskotte LMG, Akkerman OW, Simonsson USH (2022). Model-Informed Precision Dosing of Linezolid in Patients with Drug-Resistant Tuberculosis. Pharmaceutics 14(4):753. doi:10.3390/pharmaceutics14040753.
- Description: One-compartment population PK model for oral linezolid in adults with multidrug- and extensively drug-resistant tuberculosis (Mockeliunas 2022), with transit absorption (the dose passes through five transit compartments at ktr = 6/MTT into an absorption compartment emptied at first-order ka) and concentration- and time-dependent auto-inhibition of elimination after Plock et al.: an empirical inhibition compartment equilibrates with the plasma concentration at rate kIC and scales the uninhibited apparent clearance by RCLF + (1 - RCLF) * IC50 / (IC50 + Ci), with kIC (0.0005 /h) and IC50 (0.38 mg/L) fixed to literature values from Keel et al. Body weight enters allometrically (exponents 0.75 on CL/F and 1 on V/F, reference 70 kg). HIV co-infection raises CL/F, female sex raises ka, and concomitant P-glycoprotein inhibitors lengthen MTT. Inter-individual variability on CL/F and MTT; inter-occasion variability over seven sampling occasions on CL/F, V/F, ka and MTT; combined additive and proportional residual error.
- Article: https://doi.org/10.3390/pharmaceutics14040753 (open access)
- Supplement (NONMEM control stream as Text S1; Tables S1-S2): https://www.mdpi.com/article/10.3390/pharmaceutics14040753/s1
Mockeliunas et al. fitted a population PK model to routine therapeutic drug monitoring (TDM) data from adults treated with linezolid for multidrug- or extensively drug-resistant tuberculosis. They then used it in a model-informed precision dosing (MIPD) algorithm. The algorithm targets an unbound AUC0-24h / MIC above 119 for efficacy and an unbound trough concentration below 1.38 mg/L for safety.
The model has three parts:
- Absorption passes through five transit compartments into an absorption compartment.
- Disposition is one-compartment.
- Clearance auto-inhibits over time and with concentration. The
mechanism is the empirical inhibition compartment of Plock et al.: a
slowly equilibrating concentration
Ciscales the uninhibited clearance byRCLF + (1 - RCLF) * IC50 / (IC50 + Ci).
The inhibition parameters kIC and IC50
could not be identified from data collected mostly at steady state. They
were therefore fixed to literature values.
Population
The analysis used 70 patients (811 plasma concentrations). They were treated at the Tuberculosis Center Beatrixoord (University Medical Center Groningen, the Netherlands) between 2007 and 2019. Table 1 of the paper reports the demographics:
- Weight: mean 61.2 kg (range 35.3-88.9).
- Age: mean 32 years (range 15-70).
- Sex: 38 men (54.5% as printed; 38/70 is 54.3%).
- Cockcroft-Gault creatinine clearance: mean 116.1 mL/min, truncated at 150 mL/min.
- Comorbidities: HIV co-infection 5 (7.1%), diabetes 9 (12.9%).
- Origin of birth spanned all six WHO regions.
Linezolid was given orally once or twice daily at 150-1200 mg per day, for up to 542 days. The commonest regimens (Table S1) were 300 mg BID (34% of sampling occasions), 600 mg QD (22%) and 300 mg QD (21%). Concentrations came from up to seven sampling occasions per patient.
The same information is available programmatically:
str(readModelDb("Mockeliunas_2022_linezolid")()$population, max.level = 1)
#> Warning: some etas defaulted to non-mu referenced, possible parsing error: etaiov_cl_1, etaiov_cl_2, etaiov_cl_3, etaiov_cl_4, etaiov_cl_5, etaiov_cl_6, etaiov_cl_7, etaiov_vc_1, etaiov_vc_2, etaiov_vc_3, etaiov_vc_4, etaiov_vc_5, etaiov_vc_6, etaiov_vc_7, etaiov_ka_1, etaiov_ka_2, etaiov_ka_3, etaiov_ka_4, etaiov_ka_5, etaiov_ka_6, etaiov_ka_7, etaiov_mtt_1, etaiov_mtt_2, etaiov_mtt_3, etaiov_mtt_4, etaiov_mtt_5, etaiov_mtt_6, etaiov_mtt_7
#> as a work-around try putting the mu-referenced expression on a simple line
#> List of 16
#> $ species : chr "human"
#> $ n_subjects : int 70
#> $ n_studies : int 1
#> $ age_range : chr "15-70 years"
#> $ age_mean : chr "32 years"
#> $ weight_range : chr "35.3-88.9 kg"
#> $ weight_mean : chr "61.2 kg"
#> $ height_mean : chr "1.70 m (range 1.50-1.93)"
#> $ bmi_mean : chr "21.2 kg/m^2 (range 15.5-32.6)"
#> $ sex_female_pct: num 45.7
#> $ race_ethnicity: chr "Origin of birth by WHO region (Table 1): African 10 (14.3%), Americas 2 (2.9%), South-East Asia 6 (8.6%), Europ"| __truncated__
#> $ disease_state : chr "Multidrug- or extensively drug-resistant tuberculosis. HIV co-infection 5 (7.1%), diabetes 9 (12.9%), smoking 2"| __truncated__
#> $ renal_function: chr "Cockcroft-Gault creatinine clearance mean 116.1 mL/min (range 40.7-150.0), using lean body weight when BMI > 25"| __truncated__
#> $ dose_range : chr "Oral linezolid 150-1200 mg per day, once or twice daily, for up to 542 days in combination with other anti-TB d"| __truncated__
#> $ regions : chr "The Netherlands (Tuberculosis Center Beatrixoord, University Medical Center Groningen)."
#> $ notes : chr "Retrospective routine therapeutic-drug-monitoring data collected 2007-2019: 811 total plasma linezolid concentr"| __truncated__Source trace
Every ini() value carries an in-file comment pointing at
its source. The final estimates come from Table 2. The structure and
covariate parameterisation come from the NONMEM control stream in Text
S1, whose $THETA and $OMEGA values are initial
estimates and were not used.
| Equation / parameter | Value | Source location |
|---|---|---|
lcl (CL/F, uninhibited, 70 kg) |
log(6.3) L/h | Table 2; THETA(1) |
lvc (V/F, 70 kg) |
log(50.6) L | Table 2; THETA(2) |
lka (ka, males) |
log(1.8) 1/h | Table 2; THETA(3) |
lmtt (MTT) |
log(0.53) h | Table 2; THETA(6) |
lke0 (kIC) |
fixed(log(0.0005)) 1/h | Table 2 (FIX, Keel et al.); Results 3.1 |
lic50 (IC50) |
fixed(log(0.38)) mg/L | Table 2 (FIX, Keel et al.); Results 3.1 |
lfcl_noinh (RCLF) |
log(0.798) | Table 2; THETA(8) |
e_wt_cl, e_wt_vc
|
fixed(0.75), fixed(1) | Methods 2.2.3; Text S1 TVCL / TVV |
e_hiv_pos_cl |
0.43 | Table 2; Text S1 CLHIV = (1 + THETA(10))
|
e_sexf_ka |
0.95 | Table 2; Text S1 KASEX = (1 + THETA(11)), SEX = 1
female (Discussion) |
e_conmed_pgp_inh_mtt |
0.96 | Table 2; Text S1 MTTPGP_INH = (1 + THETA(12))
|
etalcl, etalmtt
|
0.26^2, 0.62^2 | Table 2 IIV rows, footnote a (SD) |
etaiov_cl_1..7 |
0.27^2 | Table 2 IOV row, footnote b (SD); Text S1 BLOCK(1) SAME x 7 |
etaiov_vc_1..7 |
0.26^2 | Table 2 IOV row; Text S1 |
etaiov_ka_1..7 |
0.93^2 | Table 2 IOV row; Text S1 |
etaiov_mtt_1..7 |
0.69^2 | Table 2 IOV row; Text S1 |
propSd, addSd
|
0.054, 0.53 mg/L | Table 2; Text S1
W = SQRT((THETA(4)*IPRED)**2 + THETA(5)**2)
|
Transit chain, ktr = (NN + 1) / MTT, NN = 5 |
n/a | Methods 2.2.1 Equations (1)-(2); Results 3.1; Text S1
$DES
|
d/dt(central) with inhibition factor |
n/a | Equation (6); Text S1 DADT(2)
|
d/dt(effect) <- ke0 * (Cc - effect) |
n/a | Equation (7); Text S1 DADT(8)
|
Typical-value checks
The checks in this section are deterministic. Every random effect is
suppressed with omega = NA, and the typical patient is a
70-kg HIV-negative man on no P-gp inhibitor.
mod <- readModelDb("Mockeliunas_2022_linezolid")
typical_events <- function(dose, tau, n_doses, obs_times) {
dose_times <- tau * (seq_len(n_doses) - 1)
dplyr::bind_rows(
data.frame(time = dose_times, evid = 1L, amt = dose, cmt = "depot"),
data.frame(time = obs_times, evid = 0L, amt = 0, cmt = "central")
) |>
dplyr::mutate(
id = 1L, WT = 70, SEXF = 0, HIV_POS = 0, CONMED_PGP_INH = 0, OCC = 1
) |>
dplyr::arrange(time, dplyr::desc(evid))
}Mass balance of the absorption chain
The inhibition factor can be switched off by setting
RCLF = 1 (lfcl_noinh = 0). The model is then
linear, and the AUC0-inf of a single dose must equal
Dose / CL exactly, whatever the transit-chain and
absorption constants are. This checks that the six-compartment
absorption path loses no drug and that clearance is wired correctly.
ev_sd <- typical_events(600, 24, 1, c(seq(0, 12, by = 0.05), seq(12.5, 240, by = 0.5)))
sim_noinh <- rxode2::rxSolve(
mod, events = ev_sd, params = c(lfcl_noinh = 0),
omega = NA, sigma = NA, rtol = 1e-10, atol = 1e-12
) |> as.data.frame()
#> Warning: some etas defaulted to non-mu referenced, possible parsing error: etaiov_cl_1, etaiov_cl_2, etaiov_cl_3, etaiov_cl_4, etaiov_cl_5, etaiov_cl_6, etaiov_cl_7, etaiov_vc_1, etaiov_vc_2, etaiov_vc_3, etaiov_vc_4, etaiov_vc_5, etaiov_vc_6, etaiov_vc_7, etaiov_ka_1, etaiov_ka_2, etaiov_ka_3, etaiov_ka_4, etaiov_ka_5, etaiov_ka_6, etaiov_ka_7, etaiov_mtt_1, etaiov_mtt_2, etaiov_mtt_3, etaiov_mtt_4, etaiov_mtt_5, etaiov_mtt_6, etaiov_mtt_7
#> as a work-around try putting the mu-referenced expression on a simple line
stopifnot(isTRUE(all.equal(unique(sim_noinh$inh), 1)))
auc_trap <- function(t, y) sum(diff(t) * (head(y, -1) + tail(y, -1)) / 2)
auc_noinh <- auc_trap(sim_noinh$time, sim_noinh$Cc) +
tail(sim_noinh$Cc, 1) / tail(sim_noinh$kel, 1)
auc_expected <- 600 / 6.3
c(simulated = auc_noinh, expected = auc_expected)
#> simulated expected
#> 95.24585 95.23810
# Measured 8e-5 relative error (trapezoidal error on the 0.05-h grid), so
# the bound keeps more than 10x headroom and still fails on any lost drug.
stopifnot(abs(auc_noinh / auc_expected - 1) < 1e-3)With inhibition on, the first dose is barely affected:
kIC is 0.0005 1/h, so Ci fills with a
half-life of log(2) / 0.0005 = 1386 h, about 58 days.
sim_sd <- rxode2::rxSolve(
mod, events = ev_sd, omega = NA, sigma = NA, rtol = 1e-10, atol = 1e-12
) |> as.data.frame()
auc_sd <- auc_trap(sim_sd$time, sim_sd$Cc)
c(auc_0_240 = auc_sd, dose_over_cl = auc_expected,
inh_at_240h = tail(sim_sd$inh, 1))
#> auc_0_240 dose_over_cl inh_at_240h
#> 96.3806481 95.2380952 0.9794939
# Inhibition raises the single-dose AUC by a few percent at most.
stopifnot(auc_sd > 0.99 * auc_expected, auc_sd < 1.10 * auc_expected)Long-term auto-inhibition
Under repeated dosing, Ci approaches the average
steady-state concentration Cavg = Dose / (tau * CL * inh).
At the same time the inhibition factor is
inh = RCLF + (1 - RCLF) * IC50 / (IC50 + Ci). Solving these
two equations together gives the fully inhibited steady state. A 400-day
simulation of 600 mg once daily, about seven half-lives of
Ci, must converge to that solution.
rclf <- 0.798
ic50 <- 0.38
cl_typ <- 6.3
inh_fixed_point <- uniroot(
function(inh) {
cavg <- 600 / (24 * cl_typ * inh)
rclf + (1 - rclf) * ic50 / (ic50 + cavg) - inh
},
c(rclf, 1), tol = 1e-12
)$root
n_days <- 400
ev_md <- typical_events(
600, 24, n_days,
sort(unique(c(seq(0, n_days * 24, by = 24), seq((n_days - 1) * 24, n_days * 24, by = 0.1))))
)
sim_md <- rxode2::rxSolve(
mod, events = ev_md, omega = NA, sigma = NA, maxsteps = 1e6
) |> as.data.frame()
inh_end <- tail(sim_md$inh, 1)
last <- sim_md |> dplyr::filter(time >= (n_days - 1) * 24)
auc_tau_end <- auc_trap(last$time, last$Cc)
c(inh_simulated = inh_end, inh_fixed_point = inh_fixed_point,
auc_tau_day400 = auc_tau_end,
dose_over_cl_inh = 600 / (cl_typ * inh_fixed_point))
#> inh_simulated inh_fixed_point auc_tau_day400 dose_over_cl_inh
#> 0.8127360 0.8125834 117.1855523 117.2040805
# exp(-0.0005 * 9600) = 0.8% of the initial gap in Ci is left at day 400.
stopifnot(
abs(inh_end / inh_fixed_point - 1) < 0.005,
abs(auc_tau_end / (600 / (cl_typ * inh_fixed_point)) - 1) < 0.01
)The typical uninhibited clearance of 6.3 L/h falls to 5.12 L/h at
full inhibition, a 19% reduction. That is close to the largest reduction
the model allows, 1 - RCLF = 20.2%.
sim_md |>
dplyr::filter(time %% 24 == 0) |>
dplyr::mutate(day = time / 24, cl_eff = cl * inh) |>
ggplot(aes(day, cl_eff)) +
geom_line() +
geom_hline(yintercept = cl_typ * rclf, linetype = "dashed") +
labs(
x = "Day of treatment (600 mg once daily)",
y = "Effective CL/F (L/h)",
title = "Typical-value time course of clearance auto-inhibition",
caption = "Dashed line: the RCLF floor, 0.798 x 6.3 L/h."
)
Virtual cohort
The observed data are not public. The cohort below samples covariates to match Table 1:
- Weight: normal, mean 61.2 kg, truncated to the observed 35.3-88.9 kg range.
- Female sex: 45.7% (32 of 70).
- HIV co-infection: 7.1%.
- Concomitant P-gp inhibitors: none, because the paper does not report how common they were.
Three of the regimens in Table S1 are simulated, with 200 patients each, for 21 days. The occasion index follows the sampling design of the paper’s MIPD algorithm, with occasions on days 1, 8 and 15. Occasion 1 covers days 1-7, occasion 2 days 8-14 and occasion 3 days 15-21. The occasion switches on a dose record, so each week draws its own inter-occasion random effects.
rxode2::rxSetSeed(20220330)
set.seed(20220330)
n_per_arm <- 200
regimens <- data.frame(
regimen = c("300 mg QD", "300 mg BID", "600 mg QD"),
dose = c(300, 300, 600),
tau = c(24, 12, 24)
)
r_trunc_norm <- function(n, mean, sd, lo, hi) {
x <- rnorm(n, mean, sd)
while (any(out <- x < lo | x > hi)) x[out] <- rnorm(sum(out), mean, sd)
x
}
make_cohort <- function(regimen, dose, tau, n, id_offset) {
subj <- data.frame(
id = id_offset + seq_len(n),
WT = r_trunc_norm(n, 61.2, 11, 35.3, 88.9),
SEXF = rbinom(n, 1, 32 / 70),
HIV_POS = rbinom(n, 1, 0.071),
CONMED_PGP_INH = 0
)
dose_times <- seq(0, 21 * 24 - tau, by = tau)
# Dense sampling over the first dosing day and over day 21; troughs daily.
obs_times <- sort(unique(c(
seq(0, 24, by = 0.25),
seq(20 * 24, 21 * 24, by = 0.25),
seq(0, 21 * 24, by = 24)
)))
rows <- dplyr::bind_rows(
data.frame(time = dose_times, evid = 1L, amt = dose, cmt = "depot"),
data.frame(time = obs_times, evid = 0L, amt = 0, cmt = "central")
)
tidyr::crossing(subj, rows) |>
dplyr::mutate(
OCC = pmin(floor(time / (7 * 24)) + 1, 3),
regimen = regimen,
ii = 0
) |>
dplyr::arrange(id, time, dplyr::desc(evid))
}
events <- dplyr::bind_rows(lapply(seq_len(nrow(regimens)), function(i) {
make_cohort(
regimens$regimen[i], regimens$dose[i], regimens$tau[i],
n_per_arm, id_offset = (i - 1L) * n_per_arm
)
}))
stopifnot(!anyDuplicated(unique(events[, c("id", "time", "evid")])))
stopifnot(all(tapply(events$regimen, events$id, function(x) length(unique(x))) == 1))Simulation
sim <- rxode2::rxSolve(
mod, events = events,
keep = c("regimen", "WT", "SEXF", "HIV_POS"),
maxsteps = 1e6
) |> as.data.frame()
stopifnot(!anyNA(sim$Cc))
# Numerical undershoot at a trough is bounded relative to the peak.
stopifnot(all(sim$Cc >= -1e-6 * max(sim$Cc)))
sim$regimen <- factor(sim$regimen, levels = regimens$regimen)Concentration-time profiles
The paper’s Figure 2 is a prediction-corrected VPC of the pooled TDM data. That cannot be rebuilt without the observed concentrations. The figure below shows the simulated 5th, 50th and 95th percentiles of total plasma linezolid on day 1 and on day 21 of each regimen.
sim |>
dplyr::filter(time <= 24 | time >= 20 * 24) |>
dplyr::mutate(
day = ifelse(time <= 24, "Day 1", "Day 21"),
tad_day = ifelse(time <= 24, time, time - 20 * 24)
) |>
dplyr::group_by(regimen, day, tad_day) |>
dplyr::summarise(
Q05 = quantile(Cc, 0.05), Q50 = median(Cc), Q95 = quantile(Cc, 0.95),
.groups = "drop"
) |>
ggplot(aes(tad_day, Q50)) +
geom_ribbon(aes(ymin = Q05, ymax = Q95), alpha = 0.25) +
geom_line() +
facet_grid(day ~ regimen) +
labs(
x = "Time within the day (h)", y = "Total linezolid Cc (mg/L)",
title = "Simulated linezolid profiles (median and 90% interval)",
caption = "Model of Mockeliunas 2022; compare with the pooled pcVPC of Figure 2."
)
PKNCA validation
NCA runs over the first dosing interval and over the last dosing
interval on day 21. The paper reports no NCA table. Instead, the check
is a per-subject identity that holds at steady state: over a dosing
interval, AUC0-tau = Dose / (CL * inh). On day 21 each
subject has been on the same occasion (occasion 3) for six days, more
than 20 elimination half-lives. The inhibition factor drifts by well
under 1% within a day.
sim_nca <- sim |>
dplyr::filter(!is.na(Cc)) |>
dplyr::mutate(Cc = pmax(Cc, 0)) |>
dplyr::select(id, time, Cc, regimen)
dose_df <- events |>
dplyr::filter(evid == 1) |>
dplyr::select(id, time, amt, regimen)
conc_obj <- PKNCA::PKNCAconc(sim_nca, Cc ~ time | regimen + id)
dose_obj <- PKNCA::PKNCAdose(dose_df, amt ~ time | regimen + id)
intervals <- dplyr::bind_rows(lapply(seq_len(nrow(regimens)), function(i) {
tau <- regimens$tau[i]
data.frame(
regimen = regimens$regimen[i],
start = c(0, 21 * 24 - tau),
end = c(tau, 21 * 24),
cmax = TRUE, tmax = TRUE, auclast = TRUE, cmin = TRUE
)
}))
nca_res <- PKNCA::pk.nca(PKNCA::PKNCAdata(conc_obj, dose_obj, intervals = intervals))
nca_summary <- as.data.frame(nca_res$result) |>
dplyr::filter(PPTESTCD %in% c("cmax", "tmax", "auclast", "cmin")) |>
dplyr::mutate(interval = ifelse(start == 0, "First dose", "Day 21")) |>
dplyr::group_by(regimen, interval, PPTESTCD) |>
dplyr::summarise(median = signif(median(PPORRES), 3), .groups = "drop") |>
tidyr::pivot_wider(names_from = PPTESTCD, values_from = median) |>
dplyr::arrange(regimen, dplyr::desc(interval))
nca_summary |>
dplyr::rename(
"Regimen" = regimen, "Interval" = interval,
"Cmax (mg/L)" = cmax, "Tmax (h)" = tmax,
"AUCtau (mg*h/L)" = auclast, "Cmin (mg/L)" = cmin
) |>
knitr::kable(caption = "Median simulated NCA parameters by regimen and dosing interval.")| Regimen | Interval | AUCtau (mg*h/L) | Cmax (mg/L) | Cmin (mg/L) | Tmax (h) |
|---|---|---|---|---|---|
| 300 mg BID | First dose | 37.3 | 5.20 | 0.000 | 2.00 |
| 300 mg BID | Day 21 | 62.4 | 7.82 | 2.770 | 2.00 |
| 300 mg QD | First dose | 47.3 | 5.26 | 0.000 | 2.00 |
| 300 mg QD | Day 21 | 58.3 | 6.05 | 0.521 | 2.00 |
| 600 mg QD | First dose | 96.6 | 10.80 | 0.000 | 1.75 |
| 600 mg QD | Day 21 | 116.0 | 12.40 | 1.020 | 2.00 |
auc_day21 <- as.data.frame(nca_res$result) |>
dplyr::filter(PPTESTCD == "auclast", start > 0) |>
dplyr::select(id, regimen, auc = PPORRES)
cl_day21 <- sim |>
dplyr::filter(time >= 20 * 24) |>
dplyr::group_by(id) |>
dplyr::summarise(cl_eff = mean(cl * inh), .groups = "drop")
chk <- auc_day21 |>
dplyr::inner_join(cl_day21, by = "id") |>
dplyr::left_join(
dplyr::mutate(regimens, regimen = factor(regimen, levels = regimen)),
by = "regimen"
) |>
dplyr::mutate(pct_diff = 100 * (auc / (dose / cl_eff) - 1))
stopifnot(nrow(chk) == 3 * n_per_arm)
summary(chk$pct_diff)
#> Min. 1st Qu. Median Mean 3rd Qu. Max.
#> -1.82234 -0.11317 -0.07536 -0.09222 -0.04178 0.17475
# Both sides use each subject's own drawn parameters, so the difference is
# trapezoidal error on the 0.25-h grid plus the residual non-stationarity
# of the previous occasion. The latter is largest for the rare subjects
# whose occasion-3 ka and MTT make absorption very slow.
stopifnot(
abs(median(chk$pct_diff)) < 1,
quantile(abs(chk$pct_diff), 0.95) < 3
)Safety-target attainment under flat dosing
Figure 4 of the paper reports the proportions for 600 mg once daily. The paper converts total exposure to unbound exposure with a fraction unbound of 0.69, so the safety limit fCmin < 1.38 mg/L is a total trough below 2.0 mg/L. Of the simulated patients, 67.2% met both targets, 17.6% met only the efficacy target, 14.0% met only the safety target and 1.2% met neither. That puts 18.8% of patients above the safety limit.
The efficacy target also needs each patient’s MIC. The MIC values were bootstrapped from the study data and are not reported, so only the safety half of Figure 4 is compared here.
fu <- 0.69
troughs <- sim |>
dplyr::filter(time %in% c(8 * 24, 15 * 24, 21 * 24)) |>
dplyr::mutate(day = time / 24, fCmin = fu * Cc) |>
dplyr::group_by(regimen, day) |>
dplyr::summarise(
pct_unsafe = 100 * mean(fCmin >= 1.38),
median_fCmin = median(fCmin),
.groups = "drop"
)
troughs |>
dplyr::mutate(pct_unsafe = round(pct_unsafe, 1), median_fCmin = signif(median_fCmin, 3)) |>
dplyr::rename(
"Regimen" = regimen, "Day (pre-dose)" = day,
"% with fCmin >= 1.38 mg/L" = pct_unsafe, "Median fCmin (mg/L)" = median_fCmin
) |>
knitr::kable(caption = "Simulated safety-target failure by regimen and trough day.")| Regimen | Day (pre-dose) | % with fCmin >= 1.38 mg/L | Median fCmin (mg/L) |
|---|---|---|---|
| 300 mg QD | 8 | 4.5 | 0.306 |
| 300 mg QD | 15 | 5.5 | 0.342 |
| 300 mg QD | 21 | 8.0 | 0.361 |
| 300 mg BID | 8 | 65.0 | 1.750 |
| 300 mg BID | 15 | 68.5 | 1.850 |
| 300 mg BID | 21 | 69.5 | 1.920 |
| 600 mg QD | 8 | 20.0 | 0.603 |
| 600 mg QD | 15 | 26.5 | 0.703 |
| 600 mg QD | 21 | 28.5 | 0.729 |
unsafe_600 <- troughs$pct_unsafe[troughs$regimen == "600 mg QD" & troughs$day == 15]
stopifnot(length(unsafe_600) == 1L)
unsafe_600
#> [1] 26.5With full variability, meaning between-subject plus a fresh inter-occasion draw each week, the simulated failure rate is higher than the 18.8% of Figure 4. A 2000-patient run gave 30.4%.
The paper derived its reference exposures from “true individual PK parameters”. Individual parameters of that kind carry the between-subject random effects, not an occasion-specific draw. Re-solving the same 600 mg QD patients with the inter-occasion variances set to zero brings the failure rate close to the published value. The 2000-patient run gave 20.3%.
ui <- rxode2::rxode(mod)
#> Warning: some etas defaulted to non-mu referenced, possible parsing error: etaiov_cl_1, etaiov_cl_2, etaiov_cl_3, etaiov_cl_4, etaiov_cl_5, etaiov_cl_6, etaiov_cl_7, etaiov_vc_1, etaiov_vc_2, etaiov_vc_3, etaiov_vc_4, etaiov_vc_5, etaiov_vc_6, etaiov_vc_7, etaiov_ka_1, etaiov_ka_2, etaiov_ka_3, etaiov_ka_4, etaiov_ka_5, etaiov_ka_6, etaiov_ka_7, etaiov_mtt_1, etaiov_mtt_2, etaiov_mtt_3, etaiov_mtt_4, etaiov_mtt_5, etaiov_mtt_6, etaiov_mtt_7
#> as a work-around try putting the mu-referenced expression on a simple line
omega_iiv <- ui$omega
iov_names <- grep("^etaiov_", rownames(omega_iiv), value = TRUE)
omega_iiv[iov_names, ] <- 0
omega_iiv[, iov_names] <- 0
diag(omega_iiv)[match(iov_names, rownames(omega_iiv))] <- 1e-12
ev_600 <- events |>
dplyr::filter(regimen == "600 mg QD", time <= 15 * 24)
sim_iiv <- rxode2::rxSolve(
mod, events = ev_600, omega = omega_iiv, keep = "regimen", maxsteps = 1e6
) |> as.data.frame()
stopifnot(all(abs(sim_iiv$iov_cl) < 1e-4), length(unique(sim_iiv$id)) == n_per_arm)
unsafe_600_iiv <- 100 * mean(fu * sim_iiv$Cc[sim_iiv$time == 15 * 24] >= 1.38)
c(full_variability = unsafe_600, iiv_only = unsafe_600_iiv, published = 17.6 + 1.2)
#> full_variability iiv_only published
#> 26.5 14.5 18.8With 200 patients the binomial standard error of a proportion near 20% is about 2.8 percentage points, and near 30% it is about 3.2. Both assertions allow more than three standard errors either side of the long-run value. They still fail if the clearance, volume, dose or unit were mis-transcribed, since any of those moves the trough distribution by tens of percent.
Assumptions and deviations
-
Final estimates from Table 2, structure from Text
S1. The
$THETAand$OMEGArecords of the supplementary control stream hold initial estimates. For example, CL is 6.11 there and 6.3 in Table 2, and the proportional error is 0.268 there and 0.054 in Table 2. Only the structure, the covariate parameterisation and the number of occasions were taken from Text S1. - IIV and IOV are standard deviations. Table 2 labels the IIV and IOV rows “(%CV)”, but footnotes a and b state they are “expressed as the standard deviation”. The etas enter exponentially, so each variance is the printed value squared. For MTT, the control-stream initial IIV of 0.403 is closer to 0.62^2 = 0.384 than to the %CV reading, log(1 + 0.62^2) = 0.325, which supports the footnotes.
-
Proportional error. Table 2 prints the proportional
error as “0.054” under a “(%)” heading. The control stream uses it as
the SD of a proportional term (
THETA(4) * IPREDwith$SIGMA 1held constant), so it is encoded as the fraction 0.054 (5.4%). -
Transit-chain sign. Equation (2) of the paper
prints the transit inflow as
-ktr * A(n-1). The control stream (DADT(3)toDADT(6)) uses the physically correct+ktr * A(n-1), and that form is implemented. -
Compartment mapping. Text S1 numbers the first
transit compartment 1 (the dose compartment), the central compartment 2,
transit compartments 2-5 as 3-6, the absorption compartment 7 and the
inhibition compartment 8. The model uses
depot,transit1-transit4,transit5(the absorption compartment, emptied atka),centralandeffect(holdingCiin mg/L). Doses go todepot. -
Inhibition-compartment naming.
kICis encoded aslke0and RCLF aslfcl_noinh, following the same Plock-type auto-inhibition inKim_2019_voriconazole. In this paperCiequilibrates with plasma (dCi/dt = kIC * (Cc - Ci)), which is the standard effect-compartment form. -
Occasions. Seven occasions are encoded, as in Text
S1. Records with
OCCoutside 1-7 carry no IOV. The weekly occasion schedule in the cohort follows the day 1, 8 and 15 sampling design of the paper’s MIPD algorithm. It is an illustration, not the study’s occasion structure. - Virtual cohort. Weight is drawn from a normal distribution with an assumed SD of 11 kg, since Table 1 gives only the mean and range. No patient receives a P-gp inhibitor, because the paper does not report how many did.
- Target attainment. The paper’s MIPD simulations bootstrapped covariates and MIC values from the study data and do not say on which day the flat-dose exposures were evaluated. The safety comparison uses the day-15 trough. The published 18.8% is reproduced when inter-occasion variability is left out of the simulated exposures. With it included, the model predicts about 30%. This reading of how the paper built its reference exposures is the maintainers’ inference; the paper does not state it. Efficacy-target attainment is not reproduced, because the MIC distribution is not reported.
- Errata. No correction notice for this article was found on Europe PMC as of 2026-09-30.