Cabotegravir (Han 2022)
Source:vignettes/articles/Han_2022_cabotegravir.Rmd
Han_2022_cabotegravir.RmdModel and source
- Citation: Han K, Baker M, Lovern M, Paul P, Xiong Y, Patel P, Moore KP, Seal CS, Cutrell AG, D’Amico RD, Benn PD, Landovitz RJ, Marzinke MA, Spreen WR, Ford SL. Population pharmacokinetics of cabotegravir following administration of oral tablet and long-acting intramuscular injection in adult HIV-1-infected and uninfected subjects. Br J Clin Pharmacol. 2022;88(10):4607-4622. doi:10.1111/bcp.15439
- Description: Two-compartment population PK model for cabotegravir after once-daily oral tablets and long-acting (LA) intramuscular injection in adults with and without HIV-1 (Han 2022, 16 pooled phase 1-3 studies). Oral and LA doses are absorbed first-order from separate depots, with oral bioavailability expressed relative to the LA injection (LA absolute bioavailability is the apparent-parameter reference). Baseline body weight scales CL/F and Q/F with one shared estimated exponent and V2/F and V3/F with another, at a 76.6 kg reference. Current smoking raises CL/F by 17.4%. The LA absorption rate constant is 50.9% lower in women, 47.8% higher when the dose is given as two split injections, falls with body mass index (power -0.766 at 25.4 kg/m^2) and rises with needle length (power 0.478 at 1.5 inch); because LA absorption is rate-limiting (flip-flop), a slower LA absorption lowers the first trough but raises the steady-state trough.
- Article: Br J Clin Pharmacol 2022;88(10):4607-4622 (open access, PMC9543358)
Cabotegravir is an integrase strand-transfer inhibitor given as a once-daily oral tablet (for lead-in or bridging) and as a long-acting (LA) intramuscular nanosuspension every one or two months, alone for HIV-1 pre-exposure prophylaxis or with long-acting rilpivirine for treatment. LA absorption is much slower than elimination, so after an injection the decline of plasma concentration is governed by absorption (flip-flop kinetics).
Dose oral tablets into depot_oral and LA injections into
depot_im. INJ_SPLIT and
NEEDLE_LEN_IN describe each injection, so set them on the
LA dose records.
Population
Han 2022 pooled 23 926 plasma concentrations from 1647 adults in 16 phase 1, 2 and 3 studies (Table 1): eight phase 1 oral studies in healthy volunteers, the LA relative-bioavailability study 116815, LATTE (oral induction, 10-60 mg), LATTE-2, FLAIR and ATLAS (HIV-1 treatment, 400 mg every 4 weeks or 600 mg every 8 weeks after an oral lead-in) and HPTN 077 / ECLAIR (pre-exposure prophylaxis, 600-800 mg). 72.2% of the subjects had HIV-1 infection, 25.7% were women, the median age was 36 years (18-74), median weight 76.6 kg (41.2-168.3) and median BMI 25.4 kg/m^2 (15.3-69.5). The cohort was 66.9% White, 23.9% Black, 3.4% Asian and 5.6% other race, and 27.1% were current smokers (Table 2). A total of 12 169 LA injections of 200-800 mg were recorded; 6.0% were given as two split injections and 88.5% used a 1.5-inch needle. ATLAS-2M (647 subjects, 5097 concentrations) was held out for external validation.
The same information is available programmatically via
readModelDb("Han_2022_cabotegravir")()$population.
Source trace
Every value is the ‘Final model’ column of Han 2022 Table 3. The covariate equations are printed in Results 3.2 and their forms are defined in Methods 2.2-2.3.
| Equation / parameter | Value | Source location |
|---|---|---|
lka_oral (KAoral) |
log(1.41) 1/h | Table 3 |
lka_im (KALA) |
log(0.000733) 1/h | Table 3 (0.00073 in the Results 3.2 equation) |
lfdepot_oral (F1, oral relative to LA) |
log(0.756) | Table 3 |
lcl (CL/F) |
log(0.151) L/h | Table 3 |
lvc (V2/F) |
log(5.27) L | Table 3 |
lq (Q/F) |
log(0.507) L/h | Table 3 |
lvp (V3/F) |
log(2.43) L | Table 3 |
e_wt_cl_q |
0.618 | Table 3 ‘BWT on CL/F and Q/F’; reference 76.6 kg from the Results 3.2 equations |
e_wt_vc_vp |
0.702 | Table 3 ‘BWT on V2/F and V3/F’ |
e_smoke_cl |
0.174 | Table 3 ‘Smoke on CL/F 17.4%’; equation ‘(1 + 17.4% if current smoker)’ |
e_sexf_ka_im |
-0.509 | Table 3 ‘Female on KALA 50.9%’; equation ‘(1 - 50.9% if female)’ |
e_bmi_ka_im |
-0.766 | Table 3 ‘BMI exponent on KALA 0.766’, reference 25.4 kg/m^2; sign from Discussion |
e_inj_split_ka_im |
0.478 | Table 3 ‘Split on KALA 47.8%’; equation ‘(1 + 47.8% if split)’ |
e_needle_len_in_ka_im |
0.478 | Table 3 ‘NDL exponent on KALA’, reference 1.5 inch |
etalka_oral |
0.58736 | Table 3 IIV 89.4%, log(1 + 0.894^2) |
etalka_im |
0.28911 | Table 3 IIV 57.9% |
etalcl |
0.052867 | Table 3 IIV 23.3% |
etalvc |
0.040383 | Table 3 IIV 20.3% |
etalfdepot_oral |
0.029827 | Table 3 IIV 17.4% |
addSd |
0.0319 ug/mL | Table 3 ‘Add err’ |
propSd |
0.273 | Table 3 ‘Prop err 27.3%’ |
| Two-compartment, first-order oral and LA absorption | n/a | Figure 1; Methods 2.2 |
Power covariate form, (1 + theta * IND) categorical
form |
n/a | Methods 2.3; Results 3.2 equations |
Typical-value checks against the paper’s half-lives
Results 3.2 gives four half-lives computed from the final model for a
76.6 kg subject. The oral terminal half-life is the slow disposition
phase of the two-compartment model (36.5 h in a non-smoker, 31.2 h in a
current smoker); the LA terminal half-life is log(2) / KALA
because absorption is rate limiting (5.6 weeks in men and 11.5 weeks in
women at BMI 25.4 kg/m^2, unsplit injection, 1.5-inch needle).
mod <- readModelDb("Han_2022_cabotegravir")
p <- rxode2::rxode(mod)$theta
#> ℹ parameter labels from comments will be replaced by 'label()'
beta_half_life <- function(cl, vc, q, vp) {
k10 <- cl / vc
k12 <- q / vc
k21 <- q / vp
s <- k10 + k12 + k21
beta <- (s - sqrt(s^2 - 4 * k10 * k21)) / 2
log(2) / beta
}
cl_ns <- exp(p[["lcl"]])
cl_sm <- exp(p[["lcl"]]) * (1 + p[["e_smoke_cl"]])
ka_im_m <- exp(p[["lka_im"]])
ka_im_f <- exp(p[["lka_im"]]) * (1 + p[["e_sexf_ka_im"]])
hl <- tibble::tribble(
~quantity, ~published, ~model,
"Oral t1/2, non-smoker (h)", 36.5, beta_half_life(cl_ns, exp(p[["lvc"]]), exp(p[["lq"]]), exp(p[["lvp"]])),
"Oral t1/2, current smoker (h)", 31.2, beta_half_life(cl_sm, exp(p[["lvc"]]), exp(p[["lq"]]), exp(p[["lvp"]])),
"LA t1/2, men (weeks)", 5.6, log(2) / ka_im_m / 168,
"LA t1/2, women (weeks)", 11.5, log(2) / ka_im_f / 168,
"KALA ratio, BMI 32.5 vs 20.9", 1 - 0.286, (32.5 / 20.9)^p[["e_bmi_ka_im"]]
) |>
mutate(pct_diff = 100 * (model - published) / published)
knitr::kable(hl, digits = 3, caption = "Half-lives and the BMI contrast from Han 2022 Results 3.2 and Discussion, recomputed from the packaged parameters.")| quantity | published | model | pct_diff |
|---|---|---|---|
| Oral t1/2, non-smoker (h) | 36.500 | 36.464 | -0.098 |
| Oral t1/2, current smoker (h) | 31.200 | 31.238 | 0.122 |
| LA t1/2, men (weeks) | 5.600 | 5.629 | 0.513 |
| LA t1/2, women (weeks) | 11.500 | 11.464 | -0.314 |
| KALA ratio, BMI 32.5 vs 20.9 | 0.714 | 0.713 | -0.131 |
Virtual cohort
The individual data are not public. Han 2022 built each simulated subgroup by resampling baseline weight, sex, BMI and smoking status from the model-building subjects; that table is not published, so the virtual cohorts below draw sex-specific heights and a log-normal BMI around the cohort median of 25.4 kg/m^2 and set weight to BMI x height^2. The resulting weight distribution (median about 77 kg, 5th-95th percentile about 57-108 kg) is close to Table 2. Current smoking is drawn at 27.1%. Every injection uses the 1.5-inch needle, as in the paper’s own simulations, and is unsplit.
set.seed(4607)
rxode2::rxSetSeed(4607)
make_covariates <- function(n, p_female, bmi_floor = NA, id_offset = 0L) {
sexf <- stats::rbinom(n, 1, p_female)
height <- ifelse(sexf == 1, stats::rnorm(n, 1.63, 0.07), stats::rnorm(n, 1.76, 0.07))
bmi <- if (is.na(bmi_floor)) {
exp(stats::rnorm(n, log(25.4), 0.18))
} else {
bmi_floor + abs(stats::rnorm(n, 0, 3.5))
}
tibble(
id = id_offset + seq_len(n),
SEXF = sexf,
BMI = bmi,
WT = bmi * height^2,
SMOKE = stats::rbinom(n, 1, 0.271),
INJ_SPLIT = 0,
NEEDLE_LEN_IN = 1.5
)
}
# Regimen of Methods 2.4: 30 mg oral once daily for 4 weeks, then the 600 mg
# initiation injection 2 h after the last oral dose. A month is taken as
# 4 weeks (672 h).
oral_times <- (0:27) * 24
t_init <- 27 * 24 + 2
month <- 672
# The washout times after the final injection are reported in calendar
# months, so they are converted with 30.44 days per month.
cal_month <- 30.44 * 24
lead_in_doses <- function(cov) {
bind_rows(
tidyr::crossing(cov, tibble(time = oral_times, amt = 30, cmt = "depot_oral")),
tidyr::crossing(cov, tibble(time = t_init, amt = 600, cmt = "depot_im"))
) |>
mutate(evid = 1L)
}Trough after the initiation injection (Ctau-1)
Discussion reports the predicted median (5th-95th percentile) Ctau-1, the trough one month after the 600 mg initiation injection, as 1.43 ug/mL (0.54-2.73) for the model-building population and 0.87 ug/mL (0.32-1.98) for women with BMI >= 30 kg/m^2, the subgroup with the slowest LA absorption.
cov_all <- make_covariates(200, p_female = 0.257) |> mutate(group = "Model-building population")
cov_fbmi <- make_covariates(200, p_female = 1, bmi_floor = 30, id_offset = 200L) |>
mutate(group = "Women, BMI >= 30 kg/m^2")
cov_ctau <- bind_rows(cov_all, cov_fbmi)
obs_ctau <- tidyr::crossing(
cov_ctau,
tibble(time = c(0, t_init + seq(0, month, by = 24)), amt = 0, cmt = "central", evid = 0L)
)
events_ctau <- bind_rows(lead_in_doses(cov_ctau), obs_ctau) |>
arrange(id, time, desc(evid))
stopifnot(!anyDuplicated(unique(events_ctau[, c("id", "time", "evid", "cmt")])))
sim_ctau <- rxode2::rxSolve(
mod,
events = events_ctau,
keep = c("group", "SEXF", "BMI"),
returnType = "data.frame"
)
#> ℹ parameter labels from comments will be replaced by 'label()'The comparison uses PKNCA on the initiation-injection interval; the
last observed concentration of the interval (clast.obs,
sampled exactly one month after the injection) is Ctau-1.
Cc is the individual prediction without residual error, as
in the paper’s simulations.
nca_conc <- sim_ctau |>
dplyr::filter(!is.na(Cc)) |>
dplyr::select(id, time, Cc, group)
nca_dose <- events_ctau |>
dplyr::filter(evid == 1) |>
dplyr::select(id, time, amt, group)
conc_obj <- PKNCA::PKNCAconc(nca_conc, Cc ~ time | group + id)
dose_obj <- PKNCA::PKNCAdose(nca_dose, amt ~ time | group + id)
intervals <- data.frame(start = t_init, end = t_init + month, cmax = TRUE, tmax = TRUE, clast.obs = TRUE)
nca_res <- PKNCA::pk.nca(PKNCA::PKNCAdata(conc_obj, dose_obj, intervals = intervals))
published <- tibble::tribble(
~group, ~clast.obs,
"Model-building population", 1.43,
"Women, BMI >= 30 kg/m^2", 0.87
)
cmp <- nlmixr2lib::ncaComparisonTable(
simulated = nca_res,
reference = published,
by = "group",
params = "clast.obs",
units = c(clast.obs = "ug/mL"),
tolerance_pct = 20
)
knitr::kable(cmp, caption = "Median Ctau-1: simulated versus Han 2022 Discussion. * differs from the reference by more than 20%.")| NCA parameter | group | Reference | Simulated | % diff |
|---|---|---|---|---|
| Clast (ug/mL) | Model-building population | 1.43 | 1.55 | +8.3% |
| Clast (ug/mL) | Women, BMI >= 30 kg/m^2 | 0.87 | 0.907 | +4.3% |
ctau1 <- as.data.frame(nca_res$result) |>
dplyr::filter(PPTESTCD == "clast.obs") |>
group_by(group) |>
summarise(
sim_p05 = quantile(PPORRES, 0.05),
sim_median = median(PPORRES),
sim_p95 = quantile(PPORRES, 0.95),
.groups = "drop"
) |>
left_join(
tibble::tribble(
~group, ~pub_p05, ~pub_median, ~pub_p95,
"Model-building population", 0.54, 1.43, 2.73,
"Women, BMI >= 30 kg/m^2", 0.32, 0.87, 1.98
),
by = "group"
)
ctau1 |>
dplyr::rename(
"Group" = group,
"Sim 5th" = sim_p05, "Sim median" = sim_median, "Sim 95th" = sim_p95,
"Paper 5th" = pub_p05, "Paper median" = pub_median, "Paper 95th" = pub_p95
) |>
knitr::kable(digits = 2, caption = "Ctau-1 (ug/mL), 5th / 50th / 95th percentiles.")| Group | Sim 5th | Sim median | Sim 95th | Paper 5th | Paper median | Paper 95th |
|---|---|---|---|---|---|---|
| Model-building population | 0.64 | 1.55 | 3.35 | 0.54 | 1.43 | 2.73 |
| Women, BMI >= 30 kg/m^2 | 0.40 | 0.91 | 2.09 | 0.32 | 0.87 | 1.98 |
# Centre and robust-quantile checks only (see the repository notes on cohort
# extremes). A mis-transcribed KALA, CL/F, F1 or dose moves the median by far
# more than 25%; the 5th and 95th percentiles of 200 subjects are noisier and
# also absorb the approximate covariate resampling.
stopifnot(
all(abs(ctau1$sim_median / ctau1$pub_median - 1) < 0.25),
all(abs(ctau1$sim_p05 / ctau1$pub_p05 - 1) < 0.4),
all(abs(ctau1$sim_p95 / ctau1$pub_p95 - 1) < 0.4)
)The model-building group mixes men and women; within it the sex contrast the paper reports (Ctau-1 28% lower in women) can be read off directly.
as.data.frame(nca_res$result) |>
dplyr::filter(PPTESTCD == "clast.obs", group == "Model-building population") |>
left_join(cov_all |> select(id, SEXF), by = "id") |>
group_by(Sex = ifelse(SEXF == 1, "Female", "Male")) |>
summarise(N = n(), `Median Ctau-1 (ug/mL)` = median(PPORRES), .groups = "drop") |>
knitr::kable(digits = 2, caption = "Ctau-1 by sex within the model-building cohort (paper: 28% lower in women, Figure 5A).")| Sex | N | Median Ctau-1 (ug/mL) |
|---|---|---|
| Female | 52 | 1.22 |
| Male | 148 | 1.61 |
Figure 6: one year of monthly and two-monthly dosing
Figure 6 shows the predicted profiles for one year of the QM regimen (600 mg initiation injection, then 400 mg every month) and the Q2M regimen (600 mg initiation, 600 mg one month later, then every 2 months) in a population that is 50% female, without residual error. The final injection falls 12 (QM) or 11 (Q2M) months after the initiation injection. Results 3.3 adds that after the final injection concentrations fall below the 0.025 ug/mL quantification limit in about 12-14 months, and below the protein-adjusted IC90 of 0.166 ug/mL in about 7-9 months, in 50% of the population.
cov_qm <- make_covariates(200, p_female = 0.5, id_offset = 1000L) |> mutate(regimen = "QM")
cov_q2m <- make_covariates(200, p_female = 0.5, id_offset = 2000L) |> mutate(regimen = "Q2M")
la_qm <- tibble(time = t_init + (1:12) * month, amt = 400, cmt = "depot_im")
la_q2m <- tibble(time = t_init + c(1, 3, 5, 7, 9, 11) * month, amt = 600, cmt = "depot_im")
last_inj <- c(QM = t_init + 12 * month, Q2M = t_init + 11 * month)
obs_grid <- tibble(time = c(0, seq(t_init, t_init + 30 * month, by = 168)), amt = 0, cmt = "central", evid = 0L)
events_fig6 <- bind_rows(
lead_in_doses(cov_qm),
tidyr::crossing(cov_qm, la_qm) |> mutate(evid = 1L),
lead_in_doses(cov_q2m),
tidyr::crossing(cov_q2m, la_q2m) |> mutate(evid = 1L),
tidyr::crossing(bind_rows(cov_qm, cov_q2m), obs_grid)
) |>
arrange(id, time, desc(evid))
stopifnot(!anyDuplicated(unique(events_fig6[, c("id", "time", "evid", "cmt")])))
sim_fig6 <- rxode2::rxSolve(mod, events = events_fig6, keep = c("regimen"), returnType = "data.frame")
sim_fig6 |>
dplyr::filter(time >= t_init) |>
mutate(month_after_init = (time - t_init) / month) |>
group_by(regimen, month_after_init) |>
summarise(
Q05 = quantile(Cc, 0.05), Q50 = median(Cc), Q95 = quantile(Cc, 0.95),
.groups = "drop"
) |>
ggplot(aes(month_after_init, Q50)) +
geom_ribbon(aes(ymin = Q05, ymax = Q95), alpha = 0.25) +
geom_line() +
geom_hline(yintercept = c(0.025, 0.166), linetype = "dashed") +
facet_wrap(~regimen) +
scale_y_log10() +
labs(
x = "Months after the initiation injection (1 month = 4 weeks)",
y = "Cabotegravir (ug/mL)",
title = "Median and 90% prediction interval, 50% female",
caption = "Replicates Figure 6 of Han 2022. Dashed lines: PA-IC90 0.166 ug/mL and LLOQ 0.025 ug/mL."
)
time_below <- function(df, threshold) {
df <- df[df$time >= df$last_inj, ]
above <- df$time[df$Cc >= threshold]
if (length(above) == 0) return(0)
# first sampled time after the last time at or above the threshold
t_cross <- min(df$time[df$time > max(above)], Inf)
(t_cross - df$last_inj[1]) / cal_month
}
tail_df <- sim_fig6 |>
mutate(last_inj = unname(last_inj[regimen])) |>
group_by(regimen, id) |>
group_modify(~ tibble(
below_ic90 = time_below(.x, 0.166),
below_lloq = time_below(.x, 0.025)
)) |>
ungroup()
tail_sum <- tail_df |>
group_by(regimen) |>
summarise(
median_below_ic90 = median(below_ic90),
median_below_lloq = median(below_lloq),
.groups = "drop"
)
tail_sum |>
dplyr::rename(
"Regimen" = regimen,
"Median months to < 0.166 ug/mL (paper 7-9)" = median_below_ic90,
"Median months to < 0.025 ug/mL (paper 12-14)" = median_below_lloq
) |>
knitr::kable(digits = 1, caption = "Time from the final injection until concentrations fall below the PA-IC90 and the LLOQ (calendar months).")| Regimen | Median months to < 0.166 ug/mL (paper 7-9) | Median months to < 0.025 ug/mL (paper 12-14) |
|---|---|---|
| Q2M | 8.0 | 12.9 |
| QM | 8.7 | 14.0 |
# The paper's approximate ranges with one month of margin each side for the
# weekly sampling grid and the sampling error of a 200-subject median. Doubling
# or halving KALA, the parameter that governs the washout, moves these medians
# by several months.
stopifnot(
all(tail_sum$median_below_ic90 > 6 & tail_sum$median_below_ic90 < 10),
all(tail_sum$median_below_lloq > 11 & tail_sum$median_below_lloq < 15)
)Assumptions and deviations
-
IIV scale. Table 3 prints IIV as a percentage
without saying how it was computed. The variances use the log-normal
conversion
omega^2 = log(1 + CV^2). If the percentages are instead100 x omega, the variances would be 0.799 (KAoral), 0.335 (KALA), 0.0543 (CL/F), 0.0412 (V2/F) and 0.0303 (F1). The difference matters mainly for KAoral, whose 69% shrinkage makes it weakly identified anyway. The Ctau-1 percentiles above do not separate the two readings: the simulated 95th/5th percentile ratio is about 5 in both groups, against 5.1 and 6.2 in the paper, and the cohort covariate approximation moves that ratio as much as the alternative IIV scale would. - IIV correlations not published. Results 3.2 states that off-diagonal covariances among KALA, CL/F and V2/F were estimated, but no values are reported in the paper or the supplement (which holds Figures S1-S4 only). The packaged model therefore has a diagonal IIV matrix. Prediction intervals of quantities that depend on two of these parameters together may be somewhat narrower or wider than the authors’.
-
IIV on F1. The form of the F1 random effect is not
printed; it is encoded as log-normal
(
f(depot_oral) = exp(lfdepot_oral + etalfdepot_oral)). F1 is a bioavailability relative to the LA injection, so values above 1 are not excluded by the model’s definition. -
Sign of the BMI exponent. The PDF prints
0.766without a minus sign in Table 3, its bootstrap CI and the Results 3.2 equation. The Discussion’s statement that KALA is 28.6% lower at BMI 32.5 than at 20.9 kg/m^2 fixes the exponent at -0.766, which the half-life table above confirms. -
Needle length in inches.
NEEDLE_LEN_INis in inches, as the paper records it, and the effect is centred at 1.5 inch. Split injection and needle length are per-injection covariates; supply them on the LA dose record and carry them forward, as the authors did with last observation carried forward. -
Baseline covariates. The final model uses baseline
body weight; supply a constant
WTper subject.SMOKE = 1means current smoker; never, former, ‘not current’ and unknown smokers are the reference group. - LA bioavailability. The LA injection is the bioavailability reference (its absolute bioavailability is folded into every apparent parameter), and oral doses are scaled by F1 = 75.6%.
- Below-quantification data. The authors used the M3 likelihood for BQL concentrations after LA injections. This affects estimation only, not the packaged model.
- Virtual cohort. Covariates were drawn from approximate distributions rather than by resampling the study subjects. A month is taken as 4 weeks in the dosing schedules (the registrational monthly regimen is every 4 weeks), while the washout times after the final injection are expressed in calendar months to match the wording of Results 3.3. Expressed in 4-week months the same simulated washout medians are about 9 percent larger. The ATLAS-2M external-validation data were not available and are not reproduced.