Caspofungin (Martson 2020)
Source:vignettes/articles/Martson_2020_caspofungin.Rmd
Martson_2020_caspofungin.RmdModel and source
ui <- rxode2::rxode(readModelDb("Martson_2020_caspofungin"))
#> ℹ parameter labels from comments will be replaced by 'label()'- Citation: Martson A-G, van der Elst KCM, Veringa A, Zijlstra JG, Beishuizen A, van der Werf TS, Kosterink JGW, Neely M, Alffenaar J-W. Caspofungin weight-based dosing supported by a population pharmacokinetic model in critically ill patients. Antimicrob Agents Chemother. 2020;64(9):e00905-20. doi:10.1128/AAC.00905-20. PMCID: PMC7449215.
- Article: https://doi.org/10.1128/AAC.00905-20 (open access, CC BY 4.0)
- Model:
Martson_2020_caspofungin
Two-compartment population PK model for intravenous caspofungin in critically ill adults in the intensive care unit with (suspected) invasive candidiasis (Groningen / Enschede, the Netherlands). Fitted non-parametrically with the NPAG algorithm in Pmetrics 1.5.2 on primary parameters Ke (elimination rate constant), V (central volume) and the intercompartmental rate constants kcp and kpc. Body weight is the only retained covariate and scales the central volume linearly, V = V0 * WT / 78 with 78 kg the cohort median; since Ke carries no covariate, clearance CL = Ke * V is also proportional to weight, which is the basis of the paper’s weight-based (2 mg/kg loading, 1.25 mg/kg maintenance) dosing recommendation. Typical values are the means of the NPAG marginal distributions (Table 2); inter-individual variability is a log-normal approximation built from the Table 2 CV% column. Table 2 prints V0 in ‘liters/kg’, but the equation V = V0 * weight/78 and the Discussion’s CL ~ 0.7 L/h (= 0.09 x 7.71) show V0 is the volume in litres of a 78 kg patient; see the vignette Errata.
Population
Twenty adult intensive-care patients with suspected or proven invasive candidiasis were treated with caspofungin in a therapeutic-drug-monitoring study at two Dutch hospitals (Table 1). All received a 70 mg loading dose on day 1, then 50 mg daily (70 mg above 80 kg; 35 mg or 50 mg with moderate hepatic impairment), each as a 1 h infusion. Nine samples were drawn over one dosing interval on day 3 (range 2-4): pre-dose and 1, 2, 3, 4, 6, 8, 12 and 24 h after the start of the infusion. Five patients whose dose was raised because the measured AUC0-24 was below 98 mg*h/L were sampled a second time, giving 25 occasions and 219 concentrations.
Median age was 56 years (range 25-83) and median weight 78 kg (48-139); 11 of 20 (55%) were male. Forty percent were on continuous venovenous hemofiltration and 55% received prednisolone or hydrocortisone. Median SAPS 3 was 59 and median serum albumin 20 g/L. Two patients had Child-Pugh C liver impairment.
str(ui$population)
#> List of 15
#> $ species : chr "human"
#> $ n_subjects : int 20
#> $ n_studies : int 1
#> $ n_occasions : int 25
#> $ n_concentrations: int 219
#> $ age_range : chr "25-83 years"
#> $ age_median : chr "56 years"
#> $ weight_range : chr "48-139 kg"
#> $ weight_median : chr "78 kg"
#> $ sex_female_pct : num 45
#> $ race_ethnicity : chr "Not reported"
#> $ disease_state : chr "Adult critically ill ICU patients treated with caspofungin for (suspected) invasive candidiasis. Median SAPS 3 "| __truncated__
#> $ dose_range : chr "70 mg loading dose on day 1, then 50 mg daily (<= 80 kg) or 70 mg daily (> 80 kg); 35 mg / 50 mg with moderate "| __truncated__
#> $ regions : chr "The Netherlands (University Medical Center Groningen; Medisch Spectrum Twente, Enschede)"
#> $ notes : chr "Demographics from Martson 2020 Table 1 (reproduced from the parent TDM study). Nine samples per dosing occasion"| __truncated__Source trace
| Element | Value | Source |
|---|---|---|
| Structure | 2-compartment, IV infusion, first-order elimination | Results, ‘Population pharmacokinetic model’; Table 2 |
| Estimation | NPAG, Pmetrics 1.5.2 | Methods, ‘Population pharmacokinetic modeling’ |
lkel |
log(0.09) 1/h (mean; SD 0.04, median 0.08, CV 42.38%) | Table 2, Abstract |
lvc |
log(7.71) L at 78 kg (mean; SD 2.70, median 7.20, CV 34.98%) | Table 2 (unit printed as liters/kg, see Errata) |
lk12 (kcp) |
log(0.44) 1/h (mean; SD 0.38, median 0.28, CV 88.02%) | Table 2 |
lk21 (kpc) |
log(0.46) 1/h (mean; SD 0.35, median 0.34, CV 75.98%) | Table 2 |
| Weight effect | V = V0 * WT / 78 | Results text; Table S1 model 6 (‘Pop median: 78kg’) |
etalkel, etalvc, etalk12,
etalk21
|
log(CV^2 + 1) of the Table 2 CV% | Table 2 (log-normal approximation, see Assumptions) |
| Assay polynomial | SD = 0.05 + 0.08 * C | Methods, ‘Model diagnostics’ |
| gamma | 0.654 (multiplicative) | Results, ‘Population pharmacokinetic model’ |
addSd |
0.654 * 0.05 = 0.0327 mg/L | derived |
propSd |
0.654 * 0.08 = 0.05232 | derived |
| Parameter ranges of the final NPAG run | Ke 0-0.4, V0 0.01-18, kcp 0-2, kpc 0-5 | Table S1, model 24 |
Typical-value checks
The model is linear, so every exposure metric of a typical patient
follows from CL = Ke * V0 * WT / 78 exactly. These checks
use a single typical-value solve (no IIV, no residual error) and
therefore carry tight bounds.
mod_typ <- rxode2::zeroRe(ui)
# Clearance of the typical 78 kg patient. The Discussion states: 'with a Ke of
# 0.09, our CL is approximately 0.7 liters' (i.e. L/h).
cl_typ <- 0.09 * 7.71
cl_typ
#> [1] 0.6939
# Regimen helper: loading dose on day 1, maintenance daily thereafter, all as
# 1 h infusions, observations on a fine grid for trapezoidal AUCs.
make_ev <- function(ld, md, days = 14, id = 1L) {
ev <- rxode2::et(amt = ld, dur = 1, cmt = "central")
if (md > 0) {
ev <- rxode2::et(ev, amt = md, dur = 1, ii = 24, addl = days - 2, time = 24, cmt = "central")
}
ev <- rxode2::et(ev, seq(0, 24 * days, by = 0.25), cmt = "central")
d <- as.data.frame(ev)
d$id <- id
d
}
auc_window <- function(sim, from, to) {
s <- sim[sim$time >= from & sim$time <= to, ]
sum(diff(s$time) * (head(s$Cc, -1) + tail(s$Cc, -1)) / 2)
}
solve_typ <- function(ld, md, wt, days = 14) {
d <- make_ev(ld, md, days)
d$WT <- wt
sim <- suppressMessages(rxode2::rxSolve(mod_typ, d, returnType = "data.frame",
rtol = 1e-10, atol = 1e-12))
if (is.null(sim$id)) sim$id <- 1L
sim
}A mg/kg regimen gives a weight-independent AUC
Because both V and CL are proportional to weight, a dose in mg/kg produces the same AUC and the same concentration profile at every weight. That is the pharmacological basis of the paper’s weight-based recommendation, and it is an exact property of the model.
wts <- c(50, 78, 120)
inv <- lapply(wts, function(w) {
sim <- solve_typ(2 * w, 1.25 * w, w, days = 3)
data.frame(WT = w,
AUC_day1 = auc_window(sim, 0, 24),
AUC_day3 = auc_window(sim, 48, 72))
}) |> dplyr::bind_rows()
knitr::kable(inv, digits = 3,
caption = "Typical AUC (mg*h/L) under 2 mg/kg loading, 1.25 mg/kg maintenance.")| WT | AUC_day1 | AUC_day3 |
|---|---|---|
| 50 | 148.522 | 141.18 |
| 78 | 148.522 | 141.18 |
| 120 | 148.522 | 141.18 |
Replicating Figure 2 (VPC median on day 3)
Figure 2 is a prediction-corrected VPC over the day-3 sampling occasion. The red median line was read from the figure by the maintainers (to about 0.3 mg/L). The cohort received 50 mg or 70 mg maintenance doses depending on weight, so the typical 78 kg profiles for those two doses should bracket the observed median.
vpc_median <- data.frame(
tad = c(1, 4, 8, 12, 24),
Cc_obs = c(7.6, 4.9, 3.2, 2.6, 1.9)
)
typ50 <- solve_typ(70, 50, 78, days = 4)
typ70 <- solve_typ(70, 70, 78, days = 4)
prof <- dplyr::bind_rows(
typ50 |> dplyr::mutate(regimen = "70 mg LD, 50 mg daily"),
typ70 |> dplyr::mutate(regimen = "70 mg LD, 70 mg daily")
) |>
dplyr::filter(time >= 48, time <= 72) |>
dplyr::mutate(tad = time - 48)
ggplot(prof, aes(tad, Cc, colour = regimen)) +
geom_line() +
geom_point(data = vpc_median, aes(tad, Cc_obs), inherit.aes = FALSE, colour = "red", size = 2) +
labs(x = "Time after the day-3 dose (h)", y = "Caspofungin (mg/L)", colour = NULL,
title = "Typical 78 kg profiles on day 3 vs the Figure 2 observed median (red points)",
caption = "Replicates Figure 2 of Martson 2020 (median line only).") +
theme_bw() + theme(legend.position = "bottom")
bracket <- vpc_median |>
dplyr::mutate(
lo = typ50$Cc[match(48 + tad, typ50$time)],
hi = typ70$Cc[match(48 + tad, typ70$time)]
)
knitr::kable(bracket, digits = 2,
caption = "Observed VPC median vs typical 50 mg and 70 mg maintenance profiles (mg/L).")| tad | Cc_obs | lo | hi |
|---|---|---|---|
| 1 | 7.6 | 6.71 | 9.20 |
| 4 | 4.9 | 4.04 | 5.48 |
| 8 | 3.2 | 3.28 | 4.45 |
| 12 | 2.6 | 2.75 | 3.73 |
| 24 | 1.9 | 1.63 | 2.21 |
stopifnot(
nrow(bracket) == 5L, !anyNA(bracket$lo), !anyNA(bracket$hi),
# Deterministic typical values vs digitised medians. Allow 10% outside the
# 50-70 mg bracket for reading error. A volume misread as L/kg (V = 601 L)
# or a 10-fold Ke error would miss by an order of magnitude.
all(bracket$Cc_obs >= 0.9 * bracket$lo),
all(bracket$Cc_obs <= 1.1 * bracket$hi)
)Target attainment of the typical patient (Tables 3 and 4)
Tables 3 and 4 report the percentage of simulated patients whose AUC exceeds 98 mgh/L. A PTA above 50% means the median patient is above the target, so for every cell far from 50% the typical patient’s AUC must sit on the same side of 98 mgh/L. Cells between 35% and 65% are too close to call and are listed but not gated.
pta_paper <- tibble::tribble(
~table, ~regimen, ~ld, ~md, ~wt, ~day, ~pta98,
"3", "70/50 mg", 70, 50, 50, 1, 73,
"3", "70/50 mg", 70, 50, 78, 1, 14,
"3", "70/50 mg", 70, 50, 120, 1, 0,
"3", "70/50 mg", 70, 50, 50, 3, 79,
"3", "70/50 mg", 70, 50, 78, 3, 19,
"3", "70/50 mg", 70, 50, 120, 3, 0,
"3", "100/70 mg", 100, 70, 50, 1, 98,
"3", "100/70 mg", 100, 70, 78, 1, 57,
"3", "100/70 mg", 100, 70, 120, 1, 10,
"3", "100/70 mg", 100, 70, 50, 3, 99,
"3", "100/70 mg", 100, 70, 78, 3, 61,
"3", "100/70 mg", 100, 70, 120, 3, 12,
"3", "70 mg daily", 70, 70, 50, 3, 98,
"3", "70 mg daily", 70, 70, 78, 3, 53,
"3", "70 mg daily", 70, 70, 120, 3, 11,
"3", "100 mg daily", 100, 100, 50, 3, 100,
"3", "100 mg daily", 100, 100, 78, 3, 98,
"3", "100 mg daily", 100, 100, 120, 3, 37,
"4", "2/1 mg/kg", 2, 1, 78, 1, 98,
"4", "2/1 mg/kg", 2, 1, 78, 3, 91,
"4", "1.5/1.25 mg/kg", 1.5, 1.25, 78, 1, 83,
"4", "1.5/1.25 mg/kg", 1.5, 1.25, 78, 3, 99,
"4", "2/1.25 mg/kg", 2, 1.25, 78, 1, 98,
"4", "2/1.25 mg/kg", 2, 1.25, 78, 3, 100,
"4", "1 mg/kg daily", 1, 1, 78, 1, 22,
"4", "1 mg/kg daily", 1, 1, 78, 3, 77,
"4", "1.5 mg/kg daily", 1.5, 1.5, 78, 1, 83,
"4", "1.5 mg/kg daily", 1.5, 1.5, 78, 3, 100
)
pta_typ <- pta_paper |>
dplyr::rowwise() |>
dplyr::mutate(
mult = if (table == "4") wt else 1,
auc_typ = {
sim <- solve_typ(ld * mult, md * mult, wt, days = 3)
auc_window(sim, 24 * (day - 1), 24 * day)
}
) |>
dplyr::ungroup() |>
dplyr::mutate(
gated = pta98 <= 35 | pta98 >= 65,
agree = (pta98 > 50) == (auc_typ > 98)
)
pta_typ |>
dplyr::select(table, regimen, wt, day, pta98, auc_typ, gated, agree) |>
dplyr::rename(
"Table" = table, "Regimen" = regimen, "Weight (kg)" = wt, "Day" = day,
"Paper PTA > 98 (%)" = pta98, "Typical AUC0-24 (mg*h/L)" = auc_typ,
"Gated" = gated, "Same side of 98" = agree
) |>
knitr::kable(digits = 1)| Table | Regimen | Weight (kg) | Day | Paper PTA > 98 (%) | Typical AUC0-24 (mg*h/L) | Gated | Same side of 98 |
|---|---|---|---|---|---|---|---|
| 3 | 70/50 mg | 50 | 1 | 73 | 104.0 | TRUE | TRUE |
| 3 | 70/50 mg | 78 | 1 | 14 | 66.6 | TRUE | TRUE |
| 3 | 70/50 mg | 120 | 1 | 0 | 43.3 | TRUE | TRUE |
| 3 | 70/50 mg | 50 | 3 | 79 | 111.2 | TRUE | TRUE |
| 3 | 70/50 mg | 78 | 3 | 19 | 71.3 | TRUE | TRUE |
| 3 | 70/50 mg | 120 | 3 | 0 | 46.3 | TRUE | TRUE |
| 3 | 100/70 mg | 50 | 1 | 98 | 148.5 | TRUE | TRUE |
| 3 | 100/70 mg | 78 | 1 | 57 | 95.2 | FALSE | FALSE |
| 3 | 100/70 mg | 120 | 1 | 10 | 61.9 | TRUE | TRUE |
| 3 | 100/70 mg | 50 | 3 | 99 | 156.0 | TRUE | TRUE |
| 3 | 100/70 mg | 78 | 3 | 61 | 100.0 | FALSE | TRUE |
| 3 | 100/70 mg | 120 | 3 | 12 | 65.0 | TRUE | TRUE |
| 3 | 70 mg daily | 50 | 3 | 98 | 150.8 | TRUE | TRUE |
| 3 | 70 mg daily | 78 | 3 | 53 | 96.7 | FALSE | FALSE |
| 3 | 70 mg daily | 120 | 3 | 11 | 62.8 | TRUE | TRUE |
| 3 | 100 mg daily | 50 | 3 | 100 | 215.5 | TRUE | TRUE |
| 3 | 100 mg daily | 78 | 3 | 98 | 138.1 | TRUE | TRUE |
| 3 | 100 mg daily | 120 | 3 | 37 | 89.8 | FALSE | TRUE |
| 4 | 2/1 mg/kg | 78 | 1 | 98 | 148.5 | TRUE | TRUE |
| 4 | 2/1 mg/kg | 78 | 3 | 91 | 116.4 | TRUE | TRUE |
| 4 | 1.5/1.25 mg/kg | 78 | 1 | 83 | 111.4 | TRUE | TRUE |
| 4 | 1.5/1.25 mg/kg | 78 | 3 | 99 | 136.8 | TRUE | TRUE |
| 4 | 2/1.25 mg/kg | 78 | 1 | 98 | 148.5 | TRUE | TRUE |
| 4 | 2/1.25 mg/kg | 78 | 3 | 100 | 141.2 | TRUE | TRUE |
| 4 | 1 mg/kg daily | 78 | 1 | 22 | 74.3 | TRUE | TRUE |
| 4 | 1 mg/kg daily | 78 | 3 | 77 | 107.7 | TRUE | TRUE |
| 4 | 1.5 mg/kg daily | 78 | 1 | 83 | 111.4 | TRUE | TRUE |
| 4 | 1.5 mg/kg daily | 78 | 3 | 100 | 161.6 | TRUE | TRUE |
Stochastic simulation
The model file carries a log-normal approximation of the NPAG distribution (see Assumptions). The chunk below simulates 200 patients per weight band under the licensed 70 mg / 50 mg regimen, for a visual comparison with Figure 2 and Table 3. It is illustrative and not gated: the approximation over-disperses clearance (see below).
rxode2::rxSetSeed(20200820)
n_arm <- 200L
arms <- data.frame(arm = 1:3, WT = c(50, 78, 120))
ev_all <- lapply(seq_len(nrow(arms)), function(a) {
lapply(seq_len(n_arm), function(i) {
d <- make_ev(70, 50, days = 3, id = (a - 1L) * n_arm + i)
d$WT <- arms$WT[a]
d
}) |> dplyr::bind_rows()
}) |> dplyr::bind_rows()
sim <- rxode2::rxSolve(ui, ev_all, returnType = "data.frame")
stoch <- sim |>
dplyr::group_by(id, WT) |>
dplyr::summarise(
AUC_day1 = sum(diff(time[time <= 24]) * (head(Cc[time <= 24], -1) + tail(Cc[time <= 24], -1)) / 2),
AUC_day3 = sum(diff(time[time >= 48 & time <= 72]) *
(head(Cc[time >= 48 & time <= 72], -1) + tail(Cc[time >= 48 & time <= 72], -1)) / 2),
.groups = "drop"
) |>
dplyr::group_by(WT) |>
dplyr::summarise(
`Median AUC day 1` = median(AUC_day1),
`PTA day 1 (%)` = 100 * mean(AUC_day1 > 98),
`Median AUC day 3` = median(AUC_day3),
`PTA day 3 (%)` = 100 * mean(AUC_day3 > 98),
.groups = "drop"
) |>
dplyr::mutate(
`Paper PTA day 1 (%)` = c(73, 14, 0),
`Paper PTA day 3 (%)` = c(79, 19, 0)
)
knitr::kable(stoch, digits = 1,
caption = "70 mg loading, 50 mg daily: simulated vs Table 3 (AUC > 98 mg*h/L).")| WT | Median AUC day 1 | PTA day 1 (%) | Median AUC day 3 | PTA day 3 (%) | Paper PTA day 1 (%) | Paper PTA day 3 (%) |
|---|---|---|---|---|---|---|
| 50 | 92.6 | 47.5 | 106.7 | 56.5 | 73 | 79 |
| 78 | 61.9 | 13.5 | 70.7 | 22.5 | 14 | 19 |
| 120 | 37.6 | 2.0 | 41.6 | 3.5 | 0 | 0 |
sim |>
dplyr::filter(time >= 48, time <= 72, WT == 78) |>
dplyr::group_by(time) |>
dplyr::summarise(
p05 = quantile(Cc, 0.05), p50 = median(Cc), p95 = quantile(Cc, 0.95),
.groups = "drop"
) |>
ggplot(aes(time - 48, p50)) +
geom_ribbon(aes(ymin = p05, ymax = p95), alpha = 0.3, fill = "steelblue") +
geom_line() +
geom_point(data = vpc_median, aes(tad, Cc_obs), inherit.aes = FALSE, colour = "red") +
labs(x = "Time after the day-3 dose (h)", y = "Caspofungin (mg/L)",
title = "Simulated 5th-50th-95th percentiles, 78 kg, 50 mg maintenance",
caption = "Red points: observed median, Figure 2 of Martson 2020.") +
theme_bw()
PKNCA validation
The paper reports no NCA table, so PKNCA is checked against the exact
linear identity for a steady-state dosing interval,
AUCtau = Dose / CL, on the typical-value day-14 interval of
each weight band under the licensed regimen. After 13 maintenance doses
the loading-dose excess is negligible: the typical terminal (beta)
half-life is about 16 h, so 288 h since the loading dose is about 18
half-lives.
nca_sim <- lapply(c(50, 78, 120), function(w) {
solve_typ(70, 50, w, days = 14) |>
dplyr::mutate(treatment = paste0(w, " kg"), WT = w)
}) |>
dplyr::bind_rows() |>
dplyr::filter(time >= 312, time <= 336) |>
dplyr::mutate(id = 1L)
conc_df <- nca_sim |>
dplyr::filter(!is.na(Cc)) |>
dplyr::select(id, treatment, time, Cc)
stopifnot(all(tapply(conc_df$time, conc_df$treatment, min) == 312))
dose_df <- data.frame(
id = 1L, treatment = c("50 kg", "78 kg", "120 kg"), time = 312, amt = 50
)
conc_obj <- PKNCA::PKNCAconc(conc_df, Cc ~ time | treatment + id)
dose_obj <- PKNCA::PKNCAdose(dose_df, amt ~ time | treatment + id)
intervals <- data.frame(start = 312, end = 336, cmax = TRUE, tmax = TRUE,
cmin = TRUE, auclast = TRUE)
nca_res <- PKNCA::pk.nca(PKNCA::PKNCAdata(conc_obj, dose_obj, intervals = intervals))
nca_tab <- as.data.frame(nca_res) |>
dplyr::select(treatment, PPTESTCD, PPORRES) |>
tidyr::pivot_wider(names_from = PPTESTCD, values_from = PPORRES) |>
dplyr::mutate(
WT = as.numeric(sub(" kg", "", treatment)),
auc_identity = 50 / (0.09 * 7.71 * WT / 78),
pct_err = 100 * (auclast / auc_identity - 1)
) |>
dplyr::arrange(WT)
nca_tab |>
dplyr::select(treatment, cmax, tmax, cmin, auclast, auc_identity, pct_err) |>
dplyr::rename(
"Weight band" = treatment, "Cmax (mg/L)" = cmax, "Tmax (h)" = tmax,
"Cmin (mg/L)" = cmin, "AUCtau PKNCA (mg*h/L)" = auclast,
"Dose/CL (mg*h/L)" = auc_identity, "Difference (%)" = pct_err
) |>
knitr::kable(digits = 3, caption = "Day-14 dosing interval, 50 mg maintenance, typical patient.")| Weight band | Cmax (mg/L) | Tmax (h) | Cmin (mg/L) | AUCtau PKNCA (mg*h/L) | Dose/CL (mg*h/L) | Difference (%) |
|---|---|---|---|---|---|---|
| 50 kg | 10.547 | 1 | 2.567 | 112.402 | 112.408 | -0.005 |
| 78 kg | 6.761 | 1 | 1.646 | 72.053 | 72.056 | -0.005 |
| 120 kg | 4.395 | 1 | 1.070 | 46.834 | 46.837 | -0.005 |
Errata
Table 2 prints V0 in ‘liters/kg’; it is litres at 78 kg
Table 2 (and the Abstract) give the central volume as 7.71 ‘liters/kg’, but three statements in the same paper show that V0 is the central volume, in litres, of a patient at the 78 kg cohort median:
- The model equation printed in Results is
V = V0 * weight/78. Multiplying a per-kilogram volume by a dimensionless weight ratio would give L/kg, not a volume. - The Discussion states ‘with a Ke of 0.09, our CL is approximately 0.7’, which is 0.09 x 7.71 = 0.69 L/h, and compares it with a published V of 7.03 L.
- The final NPAG run bounded V0 to 0.01-18 (Table S1, model 24). Read as L/kg, the typical patient would have a 601 L central volume and the licensed 70 mg dose would give a day-1 AUC near 1 mg*h/L, against the 14%-above-98 of Table 3.
The model therefore uses V0 = 7.71 L. The typical-value target-attainment check above confirms it independently.
Other transcription notes
- Table 2 prints the kcp SD as 0.38; the Abstract prints 0.39. The CV column (88.02%) corresponds to 0.387, so both are roundings of the same value.
- Table 1 prints bilirubin in ‘mmol/liter’ (median 7.5); this can only be micromol/L. Bilirubin is not a model covariate.
- Table 3’s column headers lost the 50 kg and 120 kg labels in the typeset table. The Results text (‘73% of ~50-kg patients, 14% of ~78-kg patients, and 0% of ~120-kg patients’) fixes the column order used above. The day-1 cells of the 70 mg and 100 mg daily regimens (no loading dose) equal those of the 70/50 mg and 100/70 mg regimens, as they must: the first dose is the same. They are listed once above.
Assumptions and deviations
-
Non-parametric distribution approximated as
log-normal. NPAG estimates a discrete joint distribution of
support points; the support points and their correlations were not
published. The model carries independent log-normal etas with
omega^2 = log(CV^2 + 1)from the Table 2 CV% column, the same convention used for other Pmetrics models in nlmixr2lib. - Typical values are the NPAG means. The medians are lower (Ke 0.08, V0 7.20, kcp 0.28, kpc 0.34). A log-normal centred on the mean overstates the mean of the distribution, and assuming independence between Ke and V0 overstates the spread of clearance. As a result the stochastic simulation above spreads the AUC much more widely than Tables 3-4. For example, at 2/1.25 mg/kg the paper reports 100% of patients above 98 mg*h/L on day 3, while a 1,000-patient log-normal cohort at 78 kg put 72% there (70 mg / 50 mg at 78 kg: 16% on day 1 against the paper’s 14%, so the centre agrees). For target-attainment work, treat the stochastic layer with caution. The typical-value predictions reproduce the paper (Figure 2 median, the side-of-target of every PTA cell, and the Discussion’s CL of 0.7 L/h).
- Log-normal draws are unbounded. The final NPAG run bounded the parameters (Ke 0-0.4 1/h, V0 0.01-18 L, kcp 0-2 1/h, kpc 0-5 1/h), and the log-normal tails can exceed these ranges.
-
Residual error. Pmetrics weights observations by
gamma * (C0 + C1 * C)with the observed concentration; nlmixr2 evaluates it on the prediction. The linear sum is reproduced withcombined1(). - Weight is time-fixed. The source used one weight per patient.
Session information
sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.5 LTS
#>
#> Matrix products: default
#> BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=C.UTF-8 LC_NUMERIC=C LC_TIME=C.UTF-8
#> [4] LC_COLLATE=C.UTF-8 LC_MONETARY=C.UTF-8 LC_MESSAGES=C.UTF-8
#> [7] LC_PAPER=C.UTF-8 LC_NAME=C LC_ADDRESS=C
#> [10] LC_TELEPHONE=C LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: UTC
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] ggplot2_4.0.3 tidyr_1.3.2 dplyr_1.2.1
#> [4] rxode2_5.1.8 PKNCA_0.12.1 nlmixr2lib_0.3.2.9000
#>
#> loaded via a namespace (and not attached):
#> [1] gtable_0.3.6 xfun_0.61 bslib_0.12.0
#> [4] rxode2lincmt_0.1.0 lattice_0.22-9 vctrs_0.7.3
#> [7] tools_4.6.1 generics_0.1.4 parallel_4.6.1
#> [10] tibble_3.3.1 symengine_0.2.13 pkgconfig_2.0.3
#> [13] data.table_1.18.6.1 checkmate_2.3.4 RColorBrewer_1.1-3
#> [16] S7_0.2.2 desc_1.4.3 lifecycle_1.0.5
#> [19] compiler_4.6.1 farver_2.1.2 textshaping_1.0.5
#> [22] fontawesome_0.5.3 htmltools_0.5.9 sys_3.4.3
#> [25] sass_0.4.10 yaml_2.3.12 pillar_1.11.1
#> [28] pkgdown_2.2.1 crayon_1.5.3 jquerylib_0.1.4
#> [31] whisker_0.4.1 openssl_2.4.2 cachem_1.1.0
#> [34] nlme_3.1-169 tidyselect_1.2.1 digest_0.6.39
#> [37] lotri_1.0.5 purrr_1.2.2 labeling_0.4.3
#> [40] rxode2ll_2.0.18 fastmap_1.2.0 grid_4.6.1
#> [43] cli_3.6.6 dparser_1.3.1-13 magrittr_2.0.5
#> [46] withr_3.0.3 scales_1.4.0 backports_1.5.1
#> [49] rmarkdown_2.32 otel_0.2.0 askpass_1.2.1
#> [52] ragg_1.5.2 memoise_2.0.1 evaluate_1.0.5
#> [55] knitr_1.52 rex_1.2.2 PreciseSums_0.7
#> [58] rlang_1.3.0 downlit_0.4.5 Rcpp_1.1.2
#> [61] glue_1.8.1 xml2_1.6.0 jsonlite_2.0.0
#> [64] R6_2.6.1 systemfonts_1.3.2 fs_2.1.0