Skip to contents

Model and source

  • Citation: Carrothers TJ, Chittenden JT, Critchley I. Dalbavancin Population Pharmacokinetic Modeling and Target Attainment Analysis. Clin Pharmacol Drug Dev. 2020;9(1):21-31. doi:10.1002/cpdd.695
  • Description: Three-compartment intravenous population PK model for dalbavancin in adults with acute bacterial skin and skin structure infections or catheter-related bloodstream infections (pooled phase 2/3 studies VER001-4, VER001-5, VER001-9 and DUR001-303). Zero-order infusion into the central compartment and first-order elimination. Power covariate effects: albumin, creatinine clearance and body weight on CL; albumin and weight on V1; age, albumin and weight on V2; albumin and weight on V3. IIV on CL, V1, V2 and V3 as two correlated blocks (CL with V2, V1 with V3); no IIV on Q2 or Q3. Proportional residual error.
  • Article (open access): https://doi.org/10.1002/cpdd.695
  • Supplement (model-building trail, Tables S1-S3, Figure S1): Supporting Information file CPDD-9-21-s001.pdf of the same article.

Carrothers 2020 re-estimated the dalbavancin population PK model after adding the phase 3b single-dose study DUR001-303 to the three earlier phase 2/3 studies used in the prior 2-compartment analysis. Its denser early sampling supported a third compartment. The final model (supplement Run 1005) is a 3-compartment model with zero-order intravenous input, first-order elimination, power covariate effects on CL and all three volumes, two correlated IIV blocks and a proportional residual error.

Population

703 adults from four studies (Table 1, Table 2): VER001-4 (catheter-related bloodstream infection, n = 30), VER001-5 (skin and soft tissue infection, n = 34), VER001-9 (complicated skin and soft tissue infection, n = 468) and DUR001-303 (acute bacterial skin and skin structure infection, n = 171), with 2310 plasma concentrations. Pooled median (range) age 47 (18-93) years, weight 85 (43-320) kg, creatinine clearance 113 (22-440) mL/min and albumin 3.7 (1.1-5.1) g/dL; 59.2% male; 71.7% Caucasian, 14.7% Hispanic, 11.1% Black. Regimens were 1000 mg day 1 + 500 mg day 8, 1100 mg day 1, or 1500 mg single dose, with dose reduction for creatinine clearance below 30 mL/min in DUR001-303.

The same information is available programmatically via readModelDb("Carrothers_2020_dalbavancin")()$population.

Source trace

Equation / parameter Value Source location
Structure: 3 compartments, zero-order input, first-order elimination - Results, Structural Model; Figure S1; Table S1 Run 103
CL = CLTV (ALB/3.7)^th7 (CLCR/100)^th8 (WT/85.5)^th9 exp(eta) - Covariate equation 1 under Table 3
V1 = V1TV (ALB/3.7)^th10 (WT/85.5)^th11 exp(eta) - Covariate equation 2 under Table 3
V2 = V2TV (AGE/47)^th12 (ALB/3.7)^th13 (WT/85.5)^th14 exp(eta) - Covariate equation 3 under Table 3
V3 = V3TV (ALB/3.7)^th15 (WT/85.5)^th16 exp(eta) - Covariate equation 4 under Table 3 (exponents are Table 3’s theta16/theta17; see Assumptions)
lcl 0.0531 L/h Table 3 theta1
lvc 3.04 L Table 3 theta2
lvp 8.78 L Table 3 theta3
lvp2 3.28 L Table 3 theta4
lq (Q2, to V2) 0.288 L/h Table 3 theta5
lq2 (Q3, to V3) 2.11 L/h Table 3 theta6
e_alb_cl, e_crcl_cl, e_wt_cl -0.477, 0.273, 0.391 Table 3 theta7-theta9
e_alb_vc, e_wt_vc -0.340, 0.683 Table 3 theta10-theta11
e_age_vp, e_alb_vp, e_wt_vp 0.486, -0.413, 0.365 Table 3 theta12-theta14
e_alb_vp2, e_wt_vp2 -0.551, 0.518 Table 3 theta16-theta17
etalcl, etalvp block 0.0489, 0.0823, 0.153 Table 3 omega1.1, omega2.1, omega2.2; block structure from Table S3 Run 1005
etalvc, etalvp2 block 0.0566, 0.111, 0.437 Table 3 omega3.3, omega4.3, omega4.4
propSd 0.190 (= sqrt(0.0362)) Table 3 sigma1.1 (variance)

The Table 3 omegas are log-scale variances: the printed coefficients of variation (22%, 24%, 41%, 74%) are sqrt(omega) for CL and sqrt(exp(omega) - 1) for V1, V2 and V3, which confirms they are variances and not SDs. Both blocks are positive definite (correlations 0.95 and 0.71).

mod <- readModelDb("Carrothers_2020_dalbavancin")
ref_cov <- data.frame(WT = 85.5, ALB = 37, CRCL = 100, AGE = 47)

Typical-value checks

Terminal half-life (Table 3)

Table 3 reports a terminal half-life of 8.77 days (95% CI 8.51-9.02), computed from parameter-uncertainty samples. At the reference covariates the terminal half-life is fixed by the smallest eigenvalue of the rate-constant matrix, which needs no simulation.

th <- list(cl = 0.0531, vc = 3.04, vp = 8.78, vp2 = 3.28, q = 0.288, q2 = 2.11)
kmat <- function(p) {
  with(p, matrix(c(
    -(cl + q + q2) / vc, q / vp, q2 / vp2,
    q / vc, -q / vp, 0,
    q2 / vc, 0, -q2 / vp2
  ), nrow = 3, byrow = TRUE))
}
lambda <- -eigen(kmat(th))$values
thalf_days <- log(2) / lambda / 24
thalf_days
#> [1] 0.02064091 0.36164404 8.75598635
# Deterministic identity from the published point estimates -- no sampling.
stopifnot(abs(min(lambda) * 24 / log(2) * 8.77 - 1) < 0.01)

# The same gate discriminates the Q-to-V pairing: pairing Q2 with V3 and Q3
# with V2 instead gives a clearly different terminal half-life.
th_swap <- modifyList(th, list(q = 2.11, q2 = 0.288))
log(2) / min(-eigen(kmat(th_swap))$values) / 24
#> [1] 8.355694

The printed pairing gives 8.76 days against the published 8.77, inside the 8.51-9.02 interval. The swapped pairing falls outside it. Total volume is V1 + V2 + V3 = 15.1 L, matching the abstract’s “approximately 15 L”, and CL is 0.053 L/h (abstract “0.05 L/h”).

Figure 4: population profiles of the two DUR001-303 regimens

fig4_events <- bind_rows(
  data.frame(id = 1L, treatment = "1500 mg day 1", time = 0, amt = 1500),
  data.frame(id = 2L, treatment = "1000 mg day 1 + 500 mg day 8",
             time = c(0, 168), amt = c(1000, 500))
) |>
  mutate(evid = 1L, dur = 0.5, cmt = "central")
fig4_obs <- data.frame(id = rep(1:2, each = 1),
                       treatment = c("1500 mg day 1", "1000 mg day 1 + 500 mg day 8")) |>
  tidyr::crossing(time = sort(unique(c(seq(0, 14 * 24, by = 1), seq(0, 2, by = 0.01),
                                       168 + seq(0, 2, by = 0.01))))) |>
  mutate(evid = 0L, amt = 0, dur = 0, cmt = "central")
fig4_data <- bind_rows(fig4_events, fig4_obs) |>
  cross_join(ref_cov) |>
  arrange(id, time, desc(evid))

fig4_sim <- rxSolve(zeroRe(mod), fig4_data, keep = "treatment",
                    returnType = "data.frame")
#> ℹ parameter labels from comments will be replaced by 'label()'
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalvp', 'etalvc', 'etalvp2'
#> Warning: multi-subject simulation without without 'omega'

fig4_cmax <- fig4_sim |>
  filter(time < 24) |>
  group_by(treatment) |>
  summarise(cmax = max(Cc), .groups = "drop") |>
  mutate(published = c(275.4, 413.2)[match(treatment, c("1000 mg day 1 + 500 mg day 8",
                                                        "1500 mg day 1"))])
knitr::kable(
  fig4_cmax |>
    dplyr::rename("Regimen" = treatment, "Simulated Cmax (mg/L)" = cmax,
                  "Figure 4 Cmax (mg/L)" = published),
  digits = 1,
  caption = "Typical-value first-dose Cmax at the reference covariates vs. the values printed in Figure 4."
)
Typical-value first-dose Cmax at the reference covariates vs. the values printed in Figure 4.
Regimen Simulated Cmax (mg/L) Figure 4 Cmax (mg/L)
1000 mg day 1 + 500 mg day 8 275.4 275.4
1500 mg day 1 413.1 413.2
# Deterministic (typical-value) solve against the printed labels.
stopifnot(all(abs(fig4_cmax$cmax / fig4_cmax$published - 1) < 0.01))

ggplot(fig4_sim, aes(time / 24, Cc, colour = treatment)) +
  geom_line() +
  labs(x = "Time (days)", y = "Dalbavancin concentration (mg/L)", colour = "Regimen") +
  theme_bw() +
  theme(legend.position = "top")

Replicates Figure 4 of Carrothers 2020. The printed Cmax labels (413.2 and 275.4 mg/L) are reproduced to within 0.1 mg/L with a 30-minute infusion at the reference covariates, which pins V1, Q3 and the infusion duration jointly.

Virtual cohort

The paper does not publish individual covariates, so a cohort is drawn from the Table 2 pooled summaries, independently for each covariate and truncated to the observed ranges: weight log-normal (median 85 kg, range 43-320), albumin normal (mean 3.7, SD 0.7 g/dL, range 1.1-5.1; supplied to the model in g/L), creatinine clearance log-normal (median 113 mL/min, range 22-440) and age normal (mean 47, SD 17 years, range 18-93). 200 subjects per regimen.

set.seed(20200101) # covariate draws (base R)
rxode2::rxSetSeed(20200101) # rxode2 eta and residual draws
n_per_arm <- 200
rtrunc <- function(n, mean, sd, lo, hi) {
  x <- numeric(0)
  while (length(x) < n) {
    y <- rnorm(n, mean, sd)
    x <- c(x, y[y >= lo & y <= hi])
  }
  x[seq_len(n)]
}
make_cohort <- function(n, id0) {
  data.frame(
    id = id0 + seq_len(n),
    WT = exp(rtrunc(n, log(85), 0.28, log(43), log(320))),
    ALB = rtrunc(n, 3.7, 0.7, 1.1, 5.1) * 10,
    CRCL = exp(rtrunc(n, log(113), 0.42, log(22), log(440))),
    AGE = rtrunc(n, 47, 17, 18, 93)
  )
}
cohort <- bind_rows(
  make_cohort(n_per_arm, 0L) |> mutate(treatment = "1500 mg day 1"),
  make_cohort(n_per_arm, n_per_arm) |> mutate(treatment = "1000 mg day 1 + 500 mg day 8")
)
cohort |>
  group_by(treatment) |>
  summarise(across(c(WT, ALB, CRCL, AGE), median), .groups = "drop") |>
  knitr::kable(digits = 1, caption = "Cohort medians (ALB in g/L).")
Cohort medians (ALB in g/L).
treatment WT ALB CRCL AGE
1000 mg day 1 + 500 mg day 8 87.1 36.6 118.6 47.1
1500 mg day 1 81.4 36.5 114.0 47.5

Simulation

doses <- bind_rows(
  cohort |> filter(treatment == "1500 mg day 1") |> mutate(time = 0, amt = 1500),
  cohort |> filter(treatment != "1500 mg day 1") |>
    tidyr::crossing(data.frame(time = c(0, 168), amt = c(1000, 500)))
) |>
  mutate(evid = 1L, dur = 0.5, cmt = "central")
obs_times <- sort(unique(c(0, 0.5, 1, 2, 4, 8, 12, 18, 24, seq(36, 120, by = 12),
                           seq(144, 28 * 24, by = 24))))
obs <- cohort |>
  tidyr::crossing(time = obs_times) |>
  mutate(evid = 0L, amt = 0, dur = 0, cmt = "central")
sim_data <- bind_rows(doses, obs) |> arrange(id, time, desc(evid))

sim <- rxSolve(mod, sim_data, keep = "treatment", returnType = "data.frame")
#> ℹ parameter labels from comments will be replaced by 'label()'

vpc <- sim |>
  filter(time > 0) |>
  group_by(treatment, time) |>
  summarise(p05 = quantile(Cc, 0.05), p50 = median(Cc), p95 = quantile(Cc, 0.95),
            .groups = "drop")
ggplot(vpc, aes(time / 24, p50)) +
  geom_ribbon(aes(ymin = p05, ymax = p95), alpha = 0.25) +
  geom_line() +
  facet_wrap(~treatment) +
  scale_y_log10() +
  labs(x = "Time (days)", y = "Dalbavancin concentration (mg/L)",
       caption = "Median and 90% prediction interval of the virtual cohort (with residual error).") +
  theme_bw()

PKNCA validation

NCA of the typical-value profiles at the reference covariates, compared with the Figure 4 Cmax values and the Table 3 terminal half-life (8.77 days = 210.5 h).

nca_events <- data.frame(
  id = 1:2, treatment = c("1500 mg day 1", "1000 mg day 1"),
  time = 0, amt = c(1500, 1000), evid = 1L, dur = 0.5, cmt = "central"
)
nca_obs <- nca_events |>
  select(id, treatment) |>
  tidyr::crossing(time = sort(unique(c(0, seq(0.25, 2, by = 0.25), 4, 8, 12, 24,
                                       seq(48, 150 * 24, by = 24))))) |>
  mutate(evid = 0L, amt = 0, dur = 0, cmt = "central")
nca_data <- bind_rows(nca_events, nca_obs) |>
  cross_join(ref_cov) |>
  arrange(id, time, desc(evid))
nca_sim <- rxSolve(zeroRe(mod), nca_data, keep = "treatment",
                   returnType = "data.frame")
#> ℹ parameter labels from comments will be replaced by 'label()'
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalvp', 'etalvc', 'etalvp2'
#> Warning: multi-subject simulation without without 'omega'

sim_nca <- nca_sim |>
  filter(!is.na(Cc)) |>
  select(id, time, Cc, treatment)
sim_nca <- bind_rows(
  sim_nca,
  sim_nca |> distinct(id, treatment) |> mutate(time = 0, Cc = 0)
) |>
  distinct(id, treatment, time, .keep_all = TRUE) |>
  arrange(id, treatment, time)

conc_obj <- PKNCA::PKNCAconc(sim_nca, Cc ~ time | treatment + id)
dose_obj <- PKNCA::PKNCAdose(nca_events |> select(id, time, amt, treatment),
                             amt ~ time | treatment + id)
intervals <- data.frame(start = 0, end = Inf, cmax = TRUE, tmax = TRUE,
                        aucinf.obs = TRUE, half.life = TRUE, cl.obs = TRUE,
                        vss.obs = TRUE)
nca_res <- PKNCA::pk.nca(PKNCA::PKNCAdata(conc_obj, dose_obj, intervals = intervals))

published_nca <- tibble::tribble(
  ~treatment,       ~cmax, ~half.life, ~cl.obs, ~vss.obs,
  "1500 mg day 1",  413.2, 8.77 * 24,  0.0531,  15.1,
  "1000 mg day 1",  275.4, 8.77 * 24,  0.0531,  15.1
)
cmp_nca <- nlmixr2lib::ncaComparisonTable(
  simulated = nca_res,
  reference = published_nca,
  by = "treatment",
  units = c(cmax = "mg/L", half.life = "h", cl.obs = "L/h", vss.obs = "L"),
  tolerance_pct = 20
)
knitr::kable(
  cmp_nca,
  caption = paste("Simulated (typical value, reference covariates) vs. Carrothers 2020:",
                  "Cmax from Figure 4, t1/2 from Table 3, CL = theta1, Vss = V1 + V2 + V3.",
                  "* differs from reference by >20%.")
)
Simulated (typical value, reference covariates) vs. Carrothers 2020: Cmax from Figure 4, t1/2 from Table 3, CL = theta1, Vss = V1 + V2 + V3. * differs from reference by >20%.
NCA parameter treatment Reference Simulated % diff
Cmax (mg/L) 1500 mg day 1 413 413 -0.0%
Cmax (mg/L) 1000 mg day 1 275 275 +0.0%
t½ (h) 1500 mg day 1 210 210 -0.2%
t½ (h) 1000 mg day 1 210 210 -0.2%
CL/F (L/h) 1500 mg day 1 0.0531 0.0529 -0.3%
CL/F (L/h) 1000 mg day 1 0.0531 0.0529 -0.3%
Vss/F (L) 1500 mg day 1 15.1 15 -0.5%
Vss/F (L) 1000 mg day 1 15.1 15 -0.5%

nca_wide <- as.data.frame(nca_res) |>
  select(treatment, PPTESTCD, PPORRES) |>
  tidyr::pivot_wider(names_from = PPTESTCD, values_from = PPORRES)
# Typical-value solve: NCA CL and Vss of a linear model are identities of the
# parameters (CL = theta1, Vss = V1 + V2 + V3), apart from tail truncation.
stopifnot(
  all(abs(nca_wide$cl.obs / 0.0531 - 1) < 0.02),
  all(abs(nca_wide$vss.obs / 15.1 - 1) < 0.02),
  all(abs(nca_wide$half.life / (8.77 * 24) - 1) < 0.05)
)

ncaComparisonTable() labels CL and Vss as “CL/F” and “Vss/F”; dalbavancin is given intravenously, so F = 1.

Probability of target attainment (Figure 5)

The paper computes the daily average free AUC as fu * AUC(0-120 h) / 5 with fu = 0.07 (Methods) after a 1500 mg single dose, and reports target attainment against the murine-thigh targets fAUC/MIC = 27.1 (stasis), 53.3 (1-log kill), 111.1 (2-log kill) and the superseded stasis target of 265 (Results, Target Attainment).

auc120 <- sim |>
  filter(treatment == "1500 mg day 1", time <= 120) |>
  arrange(id, time) |>
  group_by(id) |>
  summarise(auc = sum(diff(time) * (head(Cc, -1) + tail(Cc, -1)) / 2), .groups = "drop")

pta <- tidyr::crossing(target = c(27.1, 53.3, 111.1, 265),
                       mic = c(0.125, 0.25, 0.5, 1, 2, 4, 8)) |>
  rowwise() |>
  mutate(pta = 100 * mean(0.07 * auc120$auc / 5 / mic >= target)) |>
  ungroup()

published_pta <- tibble::tribble(
  ~target, ~mic, ~published,
  27.1,    2,    "99",
  27.1,    4,    "87.7",
  53.3,    1,    "99",
  111.1,   0.5,  "99",
  265,     0.25, ">99",
  265,     0.5,  "65"
)
knitr::kable(
  published_pta |>
    left_join(pta, by = c("target", "mic")) |>
    dplyr::rename("fAUC/MIC target" = target, "MIC (mg/L)" = mic,
                  "Published PTA (%)" = published, "Simulated PTA (%)" = pta),
  digits = c(1, 3, 0, 1),
  caption = "Target attainment for 1500 mg single dose, Carrothers 2020 Results and Figure 5."
)
Target attainment for 1500 mg single dose, Carrothers 2020 Results and Figure 5.
fAUC/MIC target MIC (mg/L) Published PTA (%) Simulated PTA (%)
27.1 2.00 99 100.0
27.1 4.00 87.7 82.0
53.3 1.00 99 100.0
111.1 0.50 99 100.0
265.0 0.25 >99 99.5
265.0 0.50 65 57.0

ggplot(pta, aes(mic, pta, colour = factor(target))) +
  geom_line() +
  geom_point() +
  scale_x_log10() +
  labs(x = "MIC (mg/L)", y = "Target attainment (%)", colour = "fAUC/MIC target") +
  theme_bw()


pta_at <- function(tg, m) pta$pta[pta$target == tg & pta$mic == m]
# Bands, not points: the PTA is a tail probability of a random 200-subject
# cohort (Monte Carlo SE ~3 points near 65%), and the paper resampled
# empirical-Bayes estimates of its own 703 subjects rather than a synthetic
# cohort. The bands still fail on a 2-fold error in CL, dose or fu.
stopifnot(
  pta_at(27.1, 2) > 90,
  pta_at(27.1, 4) > 65, pta_at(27.1, 4) < 97,
  pta_at(265, 0.5) > 40, pta_at(265, 0.5) < 85,
  pta_at(265, 0.25) > 90
)

Replicates the target-attainment line of Figure 5 and the PTA values quoted in Results. At the four printed values near 99-100% the simulation agrees. At the two mid-range values (stasis target at MIC 4 mg/L, old target at MIC 0.5 mg/L) the simulated PTA is 6-8 points lower (about 82% vs 87.7% and 57% vs 65% with this seed). This means the synthetic cohort’s AUC(0-120) distribution sits somewhat below the paper’s in its central quantiles (about 7-9% in an exploratory 1000-subject draw). The likely causes are (a) the independent, uncorrelated covariate draws, and (b) the paper resampling empirical-Bayes parameters, which shrinkage pulls towards the typical value. The typical-value checks above reproduce the published numbers to within 0.5%. The parameters are not tuned.

Assumptions and deviations

  • V3 exponent numbering. The printed V3 equation uses theta15 (ALB) and theta16 (WT), while Table 3 lists V3-ALB as theta16 and V3-WT as theta17 and has no theta15. The values are mapped by the covariate NAME in Table 3, which is unambiguous. The missing theta15 is consistent with the age-on-V3 effect that the supplement (Table S3, Run 1003) removed.
  • Albumin units. The paper uses g/dL (reference 3.7). The model takes canonical g/L and converts inline (alb_gdL <- ALB * 0.1).
  • Creatinine clearance. The estimating equation is not stated in the paper or supplement, and there is no sign of BSA normalisation. It is encoded as raw mL/min in CRCL with reference 100 mL/min. Table 2’s unit label “mg/mL” is a misprint. The authors caution against simulating below 30 mL/min.
  • Q-to-V pairing. Table 3 names Q2 and Q3 without explicitly stating which peripheral volume each connects to. Q2 is paired with V2 and Q3 with V3. This is the pairing that reproduces both the Figure 4 Cmax labels and the Table 3 half-life; the swapped pairing fails both.
  • Virtual cohort. Covariates are drawn independently from Table 2 summaries, with no correlation structure, because individual data are not published. The paper instead resampled the empirical-Bayes parameters of its own subjects for the PTA. Its PTA values are therefore compared only against bands.
  • Protein binding. fu = 0.07 is applied in the vignette’s PTA only. The model predicts total plasma concentration.
  • Exposure-response. The paper’s logistic regressions (Figure 3) found no exposure-response relationship (all P >= 0.33) and report no coefficients beyond near-zero figure slopes. No PD model is extracted.