Skip to contents

Model and source

  • Citation: Svensson E. M., Dosne A.-G., Karlsson M. O. (2016). Population Pharmacokinetics of Bedaquiline and Metabolite M2 in Patients With Drug-Resistant Tuberculosis: The Effect of Time-Varying Weight and Albumin. CPT Pharmacometrics Syst Pharmacol 5(12):682-691. doi:10.1002/psp4.12147.
  • DDMORE Foundation Model Repository: DDMODEL00000219.
  • Article: https://doi.org/10.1002/psp4.12147

The packaged model is a three-compartment population PK model for the antimycobacterial bedaquiline (BDQ) coupled to a one-compartment model for its N-desmethyl metabolite M2. Oral absorption goes through two transit compartments. Time-varying body weight (allometric) and serum-albumin (power and free-fraction) effects act on disposition, with additional Black-race and age effects on clearance.

Population

Svensson 2016 pooled n = 335 adult patients with multidrug-resistant tuberculosis (MDR-TB) from two phase II trials: C208 (placebo-controlled, two-stage) and C209 (open-label). Subjects received oral bedaquiline 400 mg once daily for 2 weeks (loading), followed by 200 mg three times weekly for 22 weeks, on top of an individualized second-line MDR-TB background regimen. Body weight and albumin were measured longitudinally throughout the 24-week treatment phase and 96-week follow-up.

The population was a mix of Asian and Black subjects (study-stage dependent) with median age 32 years (range 18-68), median weight 55-57 kg, and median albumin 3.6-3.8 g/dL increasing toward 4.0-4.1 g/dL at end of treatment (Tables 1 and 2 of Svensson 2016). HIV co-infection was permitted if CD4+ counts exceeded 250 cells/microL.

The same information is available programmatically:

ui <- rxode2::rxode(readModelDb("Svensson_2016_bedaquiline"))
#>  parameter labels from comments will be replaced by 'label()'
str(ui$meta$population, max.level = 1)
#> List of 12
#>  $ n_subjects    : int 335
#>  $ n_studies     : int 2
#>  $ age_range     : chr "18-68 years (Table 1)"
#>  $ age_median    : chr "32 years"
#>  $ weight_range  : chr "Stage 1: 37-81 kg (median 55 kg) at start of treatment; stage 2: 30-113 kg (median 57 kg)"
#>  $ weight_median : chr "55-57 kg (study-stage dependent; Table 1)"
#>  $ sex_female_pct: num NA
#>  $ race_ethnicity: Named num [1:3] 38.2 38.2 23.6
#>   ..- attr(*, "names")= chr [1:3] "Black" "Asian" "Other"
#>  $ disease_state : chr "Adult patients with pulmonary multidrug-resistant tuberculosis (MDR-TB) or pre-extensively / extensively drug-r"| __truncated__
#>  $ dose_range    : chr "Oral bedaquiline 400 mg once daily for the first 2 weeks (loading), followed by 200 mg three times weekly for 2"| __truncated__
#>  $ regions       : chr "Multicenter international (C208 and C209). Specific regional breakdown is not reported in the main text."
#>  $ notes         : chr "Pooled analysis of two phase II trials (C208, two-stage placebo-controlled; C209, open-label) reported in Svens"| __truncated__

Source trace

Per-parameter origins are recorded as inline comments next to each ini() entry in inst/modeldb/ddmore/Svensson_2016_bedaquiline.R. The table below summarises the audit trail.

Parameter / equation Value Source
lmat (mean absorption time, h) log(0.66 × 6) ≈ log(3.97) THETA(9); Svensson 2016 Table 3 “MAT, fraction of 6 hours”
logitfr (fraction of MAT in delay) logit(0.466) THETA(10); Table 3 “FR”
lcl (CL/F) log(2.616) THETA(11); Table 3 “CL/F = 2.62 L/h”
lvc (Vc/F) log(198.34) THETA(12); Table 3 “V/F = 198 L/70 kg”
lq (Q1/F) log(3.658) THETA(13); Table 3 “Q1/F = 3.66 L/h”
lvp (Vp1/F) log(8549.06) THETA(14); Table 3 “VP1/F = 8550 L/70 kg”
lq2 (Q2/F) log(7.335) THETA(15); Table 3 “Q2/F = 7.34 L/h”
lvp2 (Vp2/F) log(2690.91) THETA(16); Table 3 “VP2/F = 2690 L/70 kg”
lcl_m2 (CLM2/(F×fm)) log(10.0496) THETA(17); Table 3 “CLM2/(F×fm) = 10.0 L/h”
lvc_m2 (Vm2/(F×fm)) log(2203.71) THETA(18); Table 3 “VM2/(F×fm) = 2200 L/70 kg”
e_wt_cl 0.181 THETA(19); Table 3 “Allometric scaling clearances = 0.18”
e_wt_vc 1.0 (FIX) THETA(20); Table 3 footnote, allometric volumes fixed to 1
e_alb_vc 1.0 (FIX) THETA(21); Table 3 “Time varying effect of protein binding on disposition - 1 Fix”
e_alb_cl 1.640 THETA(22); Table 3 “Individual time varying effect of albumin = 1.64”
e_race_black_cl 0.839 THETA(23); Table 3 “Effect of black race on CL/CM2 = 0.84”
e_age_cl 0.00881 THETA(24); Table 3 “Age effect on CL/CLM2 = 0.0088”
lfdepot log(1) FIX derived; the .mod’s 1.8002 is a unit conversion (mg→nmol/mL) absorbed into output units
Block IIV CL / CLM2 (variances 0.153, 0.135, 0.212) .mod $OMEGA BLOCK(2); Table 3 BSV CL = 41 % CV, BSV CLM2 = 49 % CV, ρ = 75 %
Diagonal IIV V, Q1, VM2, F, MAT (var 0.172, 0.181, 0.150, 0.0803, 1.16) .mod $OMEGA blocks 13-15, 10, 8
propSd (BDQ residual) sqrt(0.0518) ≈ 0.228 .mod $SIGMA block 3; Table 3 “Proportional residual error BDQ = 23.1 % CV”
propSd_m2 (M2 residual) sqrt(0.0367) ≈ 0.191 .mod $SIGMA block 4 diagonal; Table 3 “Proportional residual error M2 = 19.3 % CV”
Two-transit-cmt absorption ODE chain n/a .mod $DES, depot/transit1/transit2/central
3-cmt BDQ disposition ODEs n/a .mod $DES BDQC, BDQPERI1, BDQPERI2
1-cmt M2 disposition ODE n/a .mod $DES M2
Albumin-FU power on vc_obs (output only) n/a .mod $ERROR VE = VB*ALLVE*COVFUIP

On the choice of source. The DDMORE bundle ships an Output_real_*.lst listing labelled as the run on the original real dataset. Inspection shows that listing is an ESTIMATION STEP OMITTED: YES (i.e. evaluation-only) run: it does not minimise; it reports R MATRIX ALGORITHMICALLY SINGULAR and COVARIANCE STEP ABORTED; and its INITIAL ESTIMATE OF THETA block disagrees with the bundled .mod’s $THETA for THETA(3), THETA(12), THETA(14), THETA(16), THETA(18), and THETA(24) by factors of 100 to 1000 — implying the listing was generated by a different (internally rescaled) .mod. The bundled .mod’s $THETA block, by contrast, matches Svensson 2016 Table 3 and Table 2 exactly. The packaged model therefore uses the .mod $THETA (and equivalently the publication’s reported point values) as the authoritative source for parameter values; all values were independently confirmed against the published Tables.

Virtual cohort

Original observed data are not publicly available. The figures below use a virtual population whose covariate distributions approximate the Svensson 2016 demographics. Body weight and albumin are time-varying in the source paper but supplied here as constants per subject (one observation per id) to keep the demonstration self-contained — see Assumptions and deviations for context.

n_subjects <- 200L

cohort <- tibble::tibble(
  id         = seq_len(n_subjects),
  WT         = pmax(35, pmin(120, rnorm(n_subjects, mean = 57,  sd = 12))),
  ALB        = pmax(2.0, pmin(5.5, rnorm(n_subjects, mean = 3.7, sd = 0.5))),
  AGE        = pmax(18, pmin(70, rnorm(n_subjects, mean = 32,  sd = 10))),
  RACE_BLACK = rbinom(n_subjects, size = 1, prob = 0.38)
)
summary(cohort)
#>        id               WT             ALB             AGE       
#>  Min.   :  1.00   Min.   :35.00   Min.   :2.423   Min.   :18.00  
#>  1st Qu.: 50.75   1st Qu.:49.23   1st Qu.:3.494   1st Qu.:24.60  
#>  Median :100.50   Median :57.55   Median :3.793   Median :31.60  
#>  Mean   :100.50   Mean   :57.19   Mean   :3.785   Mean   :31.93  
#>  3rd Qu.:150.25   3rd Qu.:65.13   3rd Qu.:4.087   3rd Qu.:38.55  
#>  Max.   :200.00   Max.   :86.70   Max.   :5.087   Max.   :60.26  
#>    RACE_BLACK   
#>  Min.   :0.000  
#>  1st Qu.:0.000  
#>  Median :0.000  
#>  Mean   :0.355  
#>  3rd Qu.:1.000  
#>  Max.   :1.000

The clinical regimen is 400 mg once-daily for 2 weeks (loading), then 200 mg three-times-weekly for 22 weeks. To keep the simulation tractable the demonstration runs a 14-day single-cohort window covering the initial loading phase plus one washout day (336 h). Sampling times are dense within the first 24 h and sparser thereafter.

loading_doses <- expand.grid(
  id     = cohort$id,
  TIME   = seq(0, 13 * 24, by = 24)
) |>
  dplyr::mutate(
    EVID = 1L,
    AMT  = 400,
    CMT  = "depot"
  )

sample_grid <- c(
  seq(0, 24, by = 0.5),       # day 1 dense
  seq(25, 14 * 24, by = 6)    # remainder sparser
)

samples <- expand.grid(
  id   = cohort$id,
  TIME = sample_grid
) |>
  dplyr::mutate(
    EVID = 0L,
    AMT  = 0,
    CMT  = "Cc"
  )

events <- dplyr::bind_rows(loading_doses, samples) |>
  dplyr::left_join(cohort, by = "id") |>
  dplyr::arrange(id, TIME, dplyr::desc(EVID)) |>
  dplyr::mutate(DV = NA_real_)

stopifnot(!anyDuplicated(unique(events[, c("id", "TIME", "EVID")])))
nrow(events)
#> [1] 23000

Simulation

mod <- rxode2::rxode(readModelDb("Svensson_2016_bedaquiline"))
#>  parameter labels from comments will be replaced by 'label()'
mod_typical <- rxode2::zeroRe(mod)

sim_typical <- rxode2::rxSolve(
  mod_typical,
  events = events,
  returnType = "data.frame",
  keep = c("WT", "ALB", "AGE", "RACE_BLACK")
)
#>  omega/sigma items treated as zero: 'etalcl', 'etalcl_m2', 'etalvc', 'etalq', 'etalvc_m2', 'etalfdepot', 'etalmat'
#> Warning: multi-subject simulation without without 'omega'
head(sim_typical[, c("id", "time", "Cc", "Cc_m2", "WT", "ALB", "RACE_BLACK")])
#>   id time         Cc        Cc_m2       WT      ALB RACE_BLACK
#> 1  1  0.0 0.00000000 0.000000e+00 49.72523 4.015799          1
#> 2  1  0.5 0.04893919 1.690623e-05 49.72523 4.015799          1
#> 3  1  1.0 0.26161361 1.966523e-04 49.72523 4.015799          1
#> 4  1  1.5 0.59617760 7.317660e-04 49.72523 4.015799          1
#> 5  1  2.0 0.96484356 1.718936e-03 49.72523 4.015799          1
#> 6  1  2.5 1.30179517 3.154027e-03 49.72523 4.015799          1
sim_vpc <- rxode2::rxSolve(
  mod,
  events = events,
  returnType = "data.frame",
  keep = c("WT", "ALB", "AGE", "RACE_BLACK")
)

Replicate published figures

Bedaquiline and M2 trajectory during loading

sim_summary <- sim_vpc |>
  dplyr::filter(time > 0) |>
  dplyr::group_by(time) |>
  dplyr::summarise(
    BDQ_Q05 = quantile(Cc,    0.05, na.rm = TRUE),
    BDQ_Q50 = quantile(Cc,    0.50, na.rm = TRUE),
    BDQ_Q95 = quantile(Cc,    0.95, na.rm = TRUE),
    M2_Q05  = quantile(Cc_m2, 0.05, na.rm = TRUE),
    M2_Q50  = quantile(Cc_m2, 0.50, na.rm = TRUE),
    M2_Q95  = quantile(Cc_m2, 0.95, na.rm = TRUE),
    .groups = "drop"
  ) |>
  tidyr::pivot_longer(-time, names_to = c("analyte", "stat"), names_sep = "_") |>
  tidyr::pivot_wider(names_from = stat, values_from = value)

ggplot(sim_summary, aes(time / 24, Q50)) +
  geom_ribbon(aes(ymin = Q05, ymax = Q95), alpha = 0.25) +
  geom_line() +
  facet_wrap(~analyte, scales = "free_y", labeller = ggplot2::as_labeller(c(
    BDQ = "Bedaquiline (Cc)",
    M2  = "Metabolite M2 (Cc_m2)"
  ))) +
  scale_y_log10() +
  labs(
    x = "Time (days after start of treatment)",
    y = "Plasma concentration (mg/L)",
    title = "Loading-phase VPC: bedaquiline and M2",
    caption = paste("Replicates the lower panel of Figure 3 of Svensson 2016 (time after dose 0-24 h",
                    "extended to the loading window 0-14 days) on the simulated cohort.")
  )

Black-race effect on bedaquiline clearance

typical_70kg <- tibble::tibble(
  id         = 1:2,
  WT         = 70,
  ALB        = 4.04,
  AGE        = 32,
  RACE_BLACK = c(0L, 1L)
)

ev_compare <- expand.grid(
  id   = typical_70kg$id,
  TIME = c(0, sample_grid)
) |>
  dplyr::mutate(EVID = 0L, AMT = 0, CMT = "Cc") |>
  dplyr::bind_rows(
    tibble::tibble(id = typical_70kg$id, TIME = 0, EVID = 1L, AMT = 400, CMT = "depot")
  ) |>
  dplyr::left_join(typical_70kg, by = "id") |>
  dplyr::arrange(id, TIME, dplyr::desc(EVID)) |>
  dplyr::mutate(DV = NA_real_)

sim_compare <- rxode2::rxSolve(
  mod_typical,
  events = ev_compare,
  returnType = "data.frame",
  keep = c("RACE_BLACK")
)
#>  omega/sigma items treated as zero: 'etalcl', 'etalcl_m2', 'etalvc', 'etalq', 'etalvc_m2', 'etalfdepot', 'etalmat'
#> Warning: multi-subject simulation without without 'omega'

sim_compare |>
  dplyr::mutate(group = ifelse(RACE_BLACK == 1, "Black race", "non-Black reference")) |>
  ggplot(aes(time, Cc, colour = group)) +
  geom_line(linewidth = 1) +
  scale_y_log10() +
  labs(
    x = "Time after single 400 mg dose (h)",
    y = "Bedaquiline Cc (mg/L)",
    colour = NULL,
    title  = "Effect of Black race on bedaquiline disposition",
    caption = paste("Black patients have ~84 % higher CL (e_race_black_cl = 0.84,",
                    "Svensson 2016 Table 3) and therefore lower exposure for the same dose.")
  )
#> Warning in scale_y_log10(): log-10 transformation introduced
#> infinite values.

PKNCA validation

Single-dose NCA on bedaquiline within the first 24 h after the day-1 dose. The treatment grouping variable here is RACE_BLACK so per-group Cmax / AUC0-24 can be inspected; in a real validation a clinically meaningful stratification (dose group, regimen) would be used.

sim_day1 <- sim_vpc |>
  dplyr::filter(time >= 0, time <= 24) |>
  dplyr::filter(!is.na(Cc)) |>
  dplyr::mutate(treatment = ifelse(RACE_BLACK == 1, "Black", "non-Black")) |>
  dplyr::select(id, time, Cc, treatment)

dose_df_day1 <- events |>
  dplyr::filter(EVID == 1, TIME == 0) |>
  dplyr::mutate(treatment = ifelse(RACE_BLACK == 1, "Black", "non-Black")) |>
  dplyr::select(id, time = TIME, amt = AMT, treatment)

conc_obj <- PKNCA::PKNCAconc(sim_day1,      Cc  ~ time | treatment + id)
dose_obj <- PKNCA::PKNCAdose(dose_df_day1,  amt ~ time | treatment + id)

intervals <- data.frame(
  start    = 0,
  end      = 24,
  cmax     = TRUE,
  tmax     = TRUE,
  auclast  = TRUE
)

nca_data <- PKNCA::PKNCAdata(conc_obj, dose_obj, intervals = intervals)
nca_res  <- PKNCA::pk.nca(nca_data)
nca_summary <- summary(nca_res)
nca_summary
#>  start end treatment   N     auclast        cmax               tmax
#>      0  24     Black  71 20.2 [44.9] 1.68 [59.0] 4.00 [0.500, 24.0]
#>      0  24 non-Black 129 18.8 [56.1] 1.46 [71.5] 5.00 [0.500, 24.0]
#> 
#> Caption: auclast, cmax: geometric mean and geometric coefficient of variation; tmax: median and range; N: number of subjects

The numeric values above are the simulated cohort’s NCA parameters per RACE_BLACK stratum. The publication does not report a paired NCA table — it uses VPCs and a covariate-effect plot (Figure 4) for goodness-of-fit. Instead, the Black-vs-non-Black AUC0-24 ratio is the load-bearing comparison: an effect coefficient of 0.84 on CL implies an AUC ratio (Black / non-Black) of 1 / (1 + 0.84) ≈ 0.54, i.e. Black subjects should have ~46 % lower AUC0-24 at typical 70 kg / 4.04 g/dL / 32 y. The simulated cohort is mixed across covariates, so the observed ratio also reflects the covariate distribution; the deterministic-typical comparison in the prior chunk demonstrates the underlying covariate effect cleanly.

Assumptions and deviations

The packaged model differs from the published Svensson 2016 NONMEM control stream in the following ways. None of these affect the typical-value PK predictions for users who supply ALB and WT as time-varying covariates equivalent to those the publication’s semi-physiological models would predict.

  • Albumin and weight time courses are not embedded in the model. The publication fits a self-limiting logistic albumin model (A0 → Ass with T1/2_return) and a linear weight model (WT0 → WT120 over 120 weeks) simultaneously with the PK and uses individually predicted time-varying ALB and WT inside the disposition equations. The packaged model expects the user to supply ALB and WT as time-varying columns in the dataset (or as constants if a fixed-baseline simulation suffices). The published albumin model parameters (A0 = 3.65 g/dL, Ass = 4.04 g/dL, T1/2_return = 20.4 weeks per Table 2) and weight model parameters (WT0 = 56.6 kg, WT120 = 62.6 kg, linear) are reported in Table 2 for users who wish to derive equivalent trajectories externally.
  • Box-Cox transformations on baseline-albumin / steady-state-albumin / 120-week-weight IIVs are dropped (the relevant IIVs are dropped along with the albumin and weight sub-models above).
  • Between-occasion variability (BOV) on F and MAT is dropped. The published model splits study C208 into two occasions (one per period of rich PK sampling) with BOV variances 0.0382 (F) and 1.16 (MAT). For library-scale simulation the packaged model retains only the BSV component of MAT (variance 1.16, identical magnitude to the BOV) and drops the BOV-on-F entirely. Users running multi-occasion designs should add OCC handling externally.
  • BSV on residual error (BSVRUV1, BSVRUV2 in the .mod) is dropped. The published model includes etas on the residual SD itself (var 0.0539 / 0.0523, correlation 56 %); the packaged model uses fixed proportional SDs equal to the population CV (sqrt(SIGMA) = 22.8 % for BDQ, 19.1 % for M2). This loses inter-individual variability in residual variability but preserves the population-mean residual magnitude.
  • Cross-output residual correlation (BDQ vs M2 residuals, ρ = 43 %) is dropped. nlmixr2 has no idiomatic encoding for cross-output residual correlation in this model class; the packaged residuals are independent.
  • The unit-conversion factor 1.8002 in the .mod’s F1 = 1.8002 * EXP(BOVF + BSVF) is dropped. The .mod expresses concentrations in nmol/mL = µmol/L; the packaged model uses mg/L (= µg/mL). To recover the published nmol/mL output, multiply Cc and Cc_m2 by 1000 / MW, with MW (bedaquiline) = 555.5 g/mol → factor 1.8002, and MW (M2) ≈ 541.5 g/mol → factor 1.847.
  • The .mod’s V_PK vs V_obs distinction is preserved. The albumin-driven free-fraction adjustment (THETA(21) = 1 FIX) acts only on the observation equations (Cc <- central / vc_obs, Cc_m2 <- central_m2 / vc_m2_obs), not on the ODE clearance terms (d/dt(central) <- ... -cl*central/vc -...). This matches the source .mod’s V (in $PK / $DES) vs VE (in $ERROR) bookkeeping. It is a deliberate model-level inconsistency in the publication, retained here for fidelity.
  • Final estimates are taken from the bundled .mod $THETA rather than the bundled Output_real_*.lst. The .lst is an evaluation-only run with a different (internally rescaled) .mod driving its FINAL ESTIMATE block, so its values disagree with the publication for several thetas. The .mod $THETA matches Svensson 2016 Tables 2 and 3 exactly and is the authoritative source.
  • Population race breakdown. Table 1 reports per-stage race counts (e.g., C209 was 38.2 % Asian, 38.2 % Black, ~24 % Other). The pooled percentages above are approximate; consult the source Table for cohort-specific numbers.