Pyridoxic acid and homovanillic acid, OAT1/3 biomarkers (Ahmad 2021)
Source:vignettes/articles/Ahmad_2021_oatBiomarkers.Rmd
Ahmad_2021_oatBiomarkers.RmdModel and source
- Citation: Ahmad A, Ogungbenro K, Kunze A, Jacobs F, Snoeys J, Rostami-Hodjegan A, Galetin A. Population pharmacokinetic modeling and simulation to support qualification of pyridoxic acid as endogenous biomarker of OAT1/3 renal transporters. CPT Pharmacometrics Syst Pharmacol. 2021;10(5):467-477. doi:10.1002/psp4.12610. Structural equations from Eqs 2-3 of the main text; all parameter values from Table 1; clinical-study design and probenecid model structure from the Supplementary Material.
- Article: https://doi.org/10.1002/psp4.12610
Pyridoxic acid (PDA, the end product of vitamin B6 metabolism) and homovanillic acid (HVA, a dopamine metabolite) are organic anions that the kidney secretes through the basolateral uptake transporters OAT1 and OAT3. Ahmad 2021 built the first population models of their synthesis and elimination, fitting plasma concentrations and urinary amounts simultaneously from a two-phase crossover in which six healthy women received a victim compound alone (control phase) and then with 500 mg probenecid every 6 h (interaction phase).
Both biomarkers use the same structure (Figure 1, Eqs 2-3): a
zero-order synthesis rate ksyn into a one-compartment
plasma pool, renal clearance CLr into a urine compartment,
and nonrenal clearance CLnr. Probenecid inhibits
CLr competitively,
CLr / (1 + C_probenecid / Ki), with its concentration
coming from a one-compartment first-order-absorption PK model fit in a
first stage. PDA and HVA were fit separately, each with its own
Ki against the same probenecid PK, so the package carries
two model files that share the probenecid block:
-
Ahmad_2021_pyridoxicAcid– PDA, the biomarker the paper recommends. -
Ahmad_2021_homovanillicAcid– HVA.
mod_pda <- rxode2::rxode(readModelDb("Ahmad_2021_pyridoxicAcid"))
#> ℹ parameter labels from comments will be replaced by 'label()'
#> Warning: some etas defaulted to non-mu referenced, possible parsing error: etaiov_ksyn_1, etaiov_ksyn_2
#> as a work-around try putting the mu-referenced expression on a simple line
mod_hva <- rxode2::rxode(readModelDb("Ahmad_2021_homovanillicAcid"))
#> ℹ parameter labels from comments will be replaced by 'label()'
#> Warning: some etas defaulted to non-mu referenced, possible parsing error: etaiov_cl_renal_1, etaiov_cl_renal_2
#> as a work-around try putting the mu-referenced expression on a simple line
mod_pda
#> ── rxode2-based free-form 4-cmt ODE model ──────────────────────────────────────
#> ── Initalization: ──
#> Fixed Effects ($theta):
#> lksyn lvc lcl_renal lcl_nonren lki_oat13 lka_prob
#> 4.0707347 2.2343063 2.7212954 1.1724821 3.9982007 -0.3011051
#> lvc_prob lcl_prob propSd addSd propSd_Upda addSd_Upda
#> 2.7080502 -0.1984509 0.1270000 0.2130000 0.3210000 58.0000000
#> propSd_prob addSd_prob
#> 0.1880000 0.0010000
#>
#> Omega ($omega):
#> etalksyn etalcl_renal etalvc_prob etalcl_prob etaiov_ksyn_1
#> etalksyn 0.0974856 0.000000 0.00000 0.000000 0.0000000
#> etalcl_renal 0.0000000 0.070319 0.00000 0.000000 0.0000000
#> etalvc_prob 0.0000000 0.000000 0.14842 0.000000 0.0000000
#> etalcl_prob 0.0000000 0.000000 0.00000 0.051546 0.0000000
#> etaiov_ksyn_1 0.0000000 0.000000 0.00000 0.000000 0.0472723
#> etaiov_ksyn_2 0.0000000 0.000000 0.00000 0.000000 0.0000000
#> etaiov_ksyn_2
#> etalksyn 0.0000000
#> etalcl_renal 0.0000000
#> etalvc_prob 0.0000000
#> etalcl_prob 0.0000000
#> etaiov_ksyn_1 0.0000000
#> etaiov_ksyn_2 0.0472723
#> attr(,"lotriLabels")
#> [1] "Table 1, PDA IIV ksyn = 32% (SE 34%); log(1 + 0.32^2)"
#> [2] "Table 1, PDA IIV CLr = 27% (SE 15%); log(1 + 0.27^2)"
#> [3] "Table 1, Probenecid IIV V = 40% (SE 29%); log(1 + 0.40^2)"
#> [4] "Table 1, Probenecid IIV CL = 23% (SE 23%); log(1 + 0.23^2)"
#> [5] "Table 1, PDA IOV ksyn = 22% (SE 57%); log(1 + 0.22^2)"
#> [6] "Table 1, PDA IOV ksyn = 22%; shared variance, occasion 2"
#> attr(,"lotriFix")
#> etalksyn etalcl_renal etalvc_prob etalcl_prob etaiov_ksyn_1
#> etalksyn FALSE FALSE FALSE FALSE FALSE
#> etalcl_renal FALSE FALSE FALSE FALSE FALSE
#> etalvc_prob FALSE FALSE FALSE FALSE FALSE
#> etalcl_prob FALSE FALSE FALSE FALSE FALSE
#> etaiov_ksyn_1 FALSE FALSE FALSE FALSE FALSE
#> etaiov_ksyn_2 FALSE FALSE FALSE FALSE FALSE
#> etaiov_ksyn_2
#> etalksyn FALSE
#> etalcl_renal FALSE
#> etalvc_prob FALSE
#> etalcl_prob FALSE
#> etaiov_ksyn_1 FALSE
#> etaiov_ksyn_2 TRUE
#>
#> States ($state or $stateDf):
#> Compartment Number Compartment Name
#> 1 1 central
#> 2 2 urine
#> 3 3 depot_prob
#> 4 4 central_prob
#> ── Multiple Endpoint Model ($multipleEndpoint): ──
#> variable cmt dvid*
#> 1 Cc ~ … cmt='Cc' or cmt=5 dvid='Cc' or dvid=1
#> 2 Upda ~ … cmt='Upda' or cmt=6 dvid='Upda' or dvid=2
#> 3 Cc_prob ~ … cmt='Cc_prob' or cmt=7 dvid='Cc_prob' or dvid=3
#> * If dvids are outside this range, all dvids are re-numered sequentially, ie 1,7, 10 becomes 1,2,3 etc
#>
#> ── μ-referencing ($muRefTable): ──
#> theta eta level
#> 1 lksyn etalksyn id
#> 2 lcl_renal etalcl_renal id
#> 3 lvc_prob etalvc_prob id
#> 4 lcl_prob etalcl_prob id
#>
#> ── Model (Normalized Syntax): ──
#> function() {
#> compartmentData <- list(central = list(analyte = "pyridoxic acid",
#> units = "ug", specimen = "plasma", verified = TRUE),
#> urine = list(analyte = "pyridoxic acid", units = "ug",
#> specimen = "urine", verified = TRUE), depot_prob = list(analyte = "probenecid",
#> units = "umol", specimen = "administration site",
#> verified = TRUE), central_prob = list(analyte = "probenecid",
#> units = "umol", specimen = "plasma", verified = TRUE))
#> covariateData <- list(OCC = list(description = "Study occasion (crossover phase): 1 = control phase (no probenecid), 2 = probenecid interaction phase.",
#> units = "(count)", type = "categorical", reference_category = "n/a -- decomposed into occasion indicators that select the per-occasion eta on ksyn",
#> notes = "Carries only the interoccasion variability on the PDA synthesis rate ksyn (Ahmad 2021 Table 1, IOV column; 'IOV between the two interaction phases was estimated only for ksyn'). Two occasions, one per crossover phase, separated by a 21-day washout. Set OCC = 1 throughout for single-occasion simulation; an OCC value other than 1 or 2 switches the IOV term off (typical-value occasion).",
#> source_name = "phase (control / probenecid)"))
#> description <- "Coupled turnover model for the endogenous renal OAT1/OAT3 biomarker pyridoxic acid (PDA) in healthy adults (Ahmad 2021), fit simultaneously to PDA plasma concentrations and urinary amounts with and without the OAT1/3 inhibitor probenecid. PDA is produced at a zero-order synthesis rate ksyn and eliminated by renal clearance CLr (about 82% of total clearance) and nonrenal clearance CLnr into a one-compartment plasma pool; the renally cleared amount accumulates in a urine compartment. Probenecid competitively inhibits CLr through its total-plasma OAT1/3 inhibition constant Ki, driven by probenecid's own one-compartment first-order-absorption PK carried in the same file; synthesis and nonrenal clearance are unaffected. With no probenecid dosed the model sits at its steady-state baseline of about 3.2 ng/mL (typical value). Probenecid doses are given in umol (500 mg = 1752 umol)."
#> population <- list(species = "human", n_subjects = 6L, n_studies = 1L,
#> age_range = "48-58 years", weight_range = "(not reported; body mass index 22-28 kg/m^2)",
#> sex_female_pct = 100, race_ethnicity = "Caucasian (6 of 6)",
#> disease_state = "Healthy volunteers. PDA was monitored as an endogenous biomarker of renal OAT1/OAT3 activity during a transporter drug-drug-interaction study.",
#> dose_range = "Endogenous biomarker (no exogenous PDA dose). Perpetrator: probenecid 500 mg orally at 18:00 and 23:00 on the day before the interaction phase, then 500 mg about every 6 h (07:00, 13:00, 18:00, 23:00) for 7 days.",
#> regions = "Europe (EudraCT 2016-003923-49)", renal_function = "CKD-EPI glomerular filtration rate 3.04-4.59 mL/min/kg (mean 3.92 mL/min/kg)",
#> notes = "Two-phase crossover (Willemin et al. 2021) with a 21-day washout: phase I a single dose of a Janssen victim compound alone (used as the PDA baseline), phase II the same compound co-administered with multiple-dose probenecid. 132 PDA plasma samples (66 per phase) and 108 PDA urine samples (54 per phase; intervals 0-6, 6-12, 12-24, then 24-h collections to 168 h), plus 36 probenecid plasma trough samples. The probenecid model was fit first; its individual empirical Bayes estimates were then fixed while PDA plasma and urine data from both phases were fit simultaneously in NONMEM (FOCE-I). Independently verified against the single-dose 1000 mg probenecid study of Shen et al. 2019 (n = 14).")
#> reference <- "Ahmad A, Ogungbenro K, Kunze A, Jacobs F, Snoeys J, Rostami-Hodjegan A, Galetin A. Population pharmacokinetic modeling and simulation to support qualification of pyridoxic acid as endogenous biomarker of OAT1/3 renal transporters. CPT Pharmacometrics Syst Pharmacol. 2021;10(5):467-477. doi:10.1002/psp4.12610. Structural equations from Eqs 2-3 of the main text; all parameter values from Table 1; clinical-study design and probenecid model structure from the Supplementary Material."
#> units <- list(time = "h", dosing = "umol", concentration = "ug/L")
#> vignette <- "Ahmad_2021_oatBiomarkers"
#> ini({
#> lksyn <- 4.07073469658297
#> label("PDA zero-order synthesis rate ksyn (ug/h)")
#> lvc <- 2.23430625224075
#> label("PDA volume of distribution V (L)")
#> lcl_renal <- 2.72129542785223
#> label("PDA renal clearance CLr (L/h)")
#> lcl_nonren <- 1.17248213723457
#> label("PDA nonrenal clearance CLnr (L/h)")
#> lki_oat13 <- 3.9982007016692
#> label("Probenecid total-plasma OAT1/3 inhibition constant Ki, PDA as probe (umol/L)")
#> lka_prob <- -0.301105092783922
#> label("Probenecid absorption rate constant ka (1/h)")
#> lvc_prob <- 2.70805020110221
#> label("Probenecid apparent volume of distribution V (L)")
#> lcl_prob <- -0.198450938723838
#> label("Probenecid apparent clearance CL (L/h)")
#> propSd <- c(0, 0.127)
#> label("Proportional residual error, PDA plasma (fraction)")
#> addSd <- c(0, 0.213)
#> label("Additive residual error, PDA plasma (ug/L = ng/mL)")
#> propSd_Upda <- c(0, 0.321)
#> label("Proportional residual error, PDA urine amount (fraction)")
#> addSd_Upda <- c(0, 58)
#> label("Additive residual error, PDA urine amount (ug)")
#> propSd_prob <- c(0, 0.188)
#> label("Proportional residual error, probenecid plasma (fraction)")
#> addSd_prob <- fix(0, 0.001)
#> label("Additive residual error, probenecid plasma (umol/L)")
#> etalksyn ~ 0.0974856
#> label("Table 1, PDA IIV ksyn = 32% (SE 34%); log(1 + 0.32^2)")
#> etalcl_renal ~ 0.070319
#> label("Table 1, PDA IIV CLr = 27% (SE 15%); log(1 + 0.27^2)")
#> etalvc_prob ~ 0.14842
#> label("Table 1, Probenecid IIV V = 40% (SE 29%); log(1 + 0.40^2)")
#> etalcl_prob ~ 0.051546
#> label("Table 1, Probenecid IIV CL = 23% (SE 23%); log(1 + 0.23^2)")
#> etaiov_ksyn_1 ~ 0.0472723
#> label("Table 1, PDA IOV ksyn = 22% (SE 57%); log(1 + 0.22^2)")
#> etaiov_ksyn_2 ~ fix(0.0472723)
#> label("Table 1, PDA IOV ksyn = 22%; shared variance, occasion 2")
#> })
#> model({
#> oc1 <- (OCC == 1)
#> oc2 <- (OCC == 2)
#> iov_ksyn <- oc1 * etaiov_ksyn_1 + oc2 * etaiov_ksyn_2
#> ksyn <- exp(lksyn + etalksyn + iov_ksyn)
#> vc <- exp(lvc)
#> cl_renal <- exp(lcl_renal + etalcl_renal)
#> cl_nonren <- exp(lcl_nonren)
#> ki_oat13 <- exp(lki_oat13)
#> ka_prob <- exp(lka_prob)
#> vc_prob <- exp(lvc_prob + etalvc_prob)
#> cl_prob <- exp(lcl_prob + etalcl_prob)
#> Cc_prob <- central_prob/vc_prob
#> cl_renal_eff <- cl_renal/(1 + Cc_prob/ki_oat13)
#> Cc <- central/vc
#> d/dt(central) <- ksyn - (cl_renal_eff + cl_nonren) *
#> Cc
#> d/dt(urine) <- cl_renal_eff * Cc
#> d/dt(depot_prob) <- -ka_prob * depot_prob
#> d/dt(central_prob) <- ka_prob * depot_prob - cl_prob/vc_prob *
#> central_prob
#> central(0) <- ksyn/(cl_renal + cl_nonren) * vc
#> Upda <- urine
#> Cc ~ add(addSd) + prop(propSd)
#> Upda ~ add(addSd_Upda) + prop(propSd_Upda)
#> Cc_prob ~ add(addSd_prob) + prop(propSd_prob)
#> })
#> }Each model declares three endpoints (biomarker plasma
Cc, cumulative urinary amount Upda /
Uhva, probenecid plasma Cc_prob), so rxode2
places their slots after the four ODE states. Observation records below
therefore name the endpoint (cmt = "Cc"): with more than
one declared endpoint, an observation on cmt = "central" is
rejected. Cc already has an endpoint slot from the model
definition, so naming it injects no new compartment and renumbers
nothing, and rxSolve() returns every model variable as a
column regardless.
Population
The model-development data (Supplementary Material, “Clinical study design”) came from six healthy Caucasian women aged 48-58 years (BMI 22-28 kg/m^2, CKD-EPI GFR 3.04-4.59 mL/min/kg) enrolled in EudraCT 2016-003923-49 (Willemin et al. 2021). Phase I was a single dose of a Janssen victim compound alone and served as the biomarker baseline; after a 21-day washout, phase II gave probenecid 500 mg at 18:00 and 23:00 on the preceding day, then 500 mg at 07:00, 13:00, 18:00 and 23:00 for 7 days, with the victim compound co-administered at 07:00 on day 1. Plasma was sampled at 0, 0.25, 1, 2, 4, 6, 8, 12, 24, 72 and 168 h and urine collected over 0-6, 6-12, 12-24 h and then 24-h intervals to 168 h in each phase: 132 plasma and 108 urine samples per biomarker, plus 36 probenecid trough concentrations. The PDA model was verified externally against the single-dose 1000 mg probenecid study of Shen et al. 2019 (14 healthy volunteers).
str(mod_pda$population)
#> List of 12
#> $ species : chr "human"
#> $ n_subjects : int 6
#> $ n_studies : int 1
#> $ age_range : chr "48-58 years"
#> $ weight_range : chr "(not reported; body mass index 22-28 kg/m^2)"
#> $ sex_female_pct: num 100
#> $ race_ethnicity: chr "Caucasian (6 of 6)"
#> $ disease_state : chr "Healthy volunteers. PDA was monitored as an endogenous biomarker of renal OAT1/OAT3 activity during a transport"| __truncated__
#> $ dose_range : chr "Endogenous biomarker (no exogenous PDA dose). Perpetrator: probenecid 500 mg orally at 18:00 and 23:00 on the d"| __truncated__
#> $ regions : chr "Europe (EudraCT 2016-003923-49)"
#> $ renal_function: chr "CKD-EPI glomerular filtration rate 3.04-4.59 mL/min/kg (mean 3.92 mL/min/kg)"
#> $ notes : chr "Two-phase crossover (Willemin et al. 2021) with a 21-day washout: phase I a single dose of a Janssen victim com"| __truncated__Source trace
| Quantity | Value (PDA / HVA) | Source |
|---|---|---|
Plasma turnover
dC/dt = (ksyn - CLr C / (1 + Cprob/Ki) - CLnr C) / V
|
– | Eq. 2 |
Urine dU/dt = CLr C / (1 + Cprob/Ki)
|
– | Eq. 3 |
| Probenecid one-compartment first-order absorption | – | Figure 1; Supplementary Material |
ksyn (ug/h) |
58.6 / 212 | Table 1 |
V (L) |
9.34 / 105 | Table 1 |
CLr (L/h) |
15.2 / 20.4 | Table 1 |
CLnr (L/h) |
3.23 / 1.29 | Table 1 |
Probenecid total Ki (uM) |
54.5 / 137 | Table 1 (note: total Ki) |
Probenecid CL (L/h), V (L),
ka (1/h) |
0.82, 15, 0.74 | Table 1 |
IIV ksyn
|
32% / 27% | Table 1 |
IIV CLr
|
27% / 23% | Table 1 |
| IOV |
ksyn 22% / CLr 5% |
Table 1 |
IIV probenecid CL, V
|
23%, 40% | Table 1 |
| Plasma residual prop / add | 12.7%, 0.213 ng/mL / 17.3%, 0.001 ng/mL (fixed) | Table 1 |
| Urine residual prop / add | 32.1%, 58 ug / 26.9%, 373 ug | Table 1 |
| Probenecid residual prop / add | 18.8%, 0.001 uM (fixed) | Table 1 |
Initial condition C(0) = ksyn / (CLr + CLnr)
|
– | Eq. 2 at steady state (baseline assumption) |
Units: biomarker amounts are in ug and concentrations in ug/L (=
ng/mL), as implied by ksyn in ug/h and the plasma additive
error in ng/mL. Probenecid is in umol and umol/L, as implied by
Ki and the probenecid additive error in uM; published mg
doses are converted with the molecular weight below.
# Probenecid molecular weight, only to convert mg doses into the umol the model
# consumes; the same value carried by the nlmixr2lib compartment register and
# the Ujihira 2025 GCDCA-S model (PubChem CID 4911 agrees to 4 significant
# figures).
MW_PROB <- 285.34
dose_umol <- function(mg) mg * 1000 / MW_PROB
# Probenecid dosing of Willemin et al. (Supplementary Material). The control
# phase starts at t = 0 (07:00); the interaction phase starts 21 days later at
# t = 504 h. Loading doses at 18:00 and 23:00 the day before, then 07:00,
# 13:00, 18:00, 23:00 for 7 days.
T2 <- 504
md_times <- c(T2 - 13, T2 - 8, as.vector(outer(c(0, 6, 11, 16), 24 * (0:6), "+")) + T2)
# One subject's event table for a two-phase crossover. OCC switches at 480 h,
# 24 h before the interaction phase: the biomarker re-equilibrates to the
# occasion-2 parameters within hours (PDA half-life ~0.35 h, HVA ~3.4 h).
design_events <- function(design = c("MD", "SD1000", "SD2000"), tobs, id = 1L) {
design <- match.arg(design)
dose_t <- if (design == "MD") md_times else T2
amt <- dose_umol(c(MD = 500, SD1000 = 1000, SD2000 = 2000)[[design]])
ev <- dplyr::bind_rows(
data.frame(time = dose_t, amt = amt, evid = 1L, cmt = "depot_prob"),
data.frame(time = c(tobs, T2 + tobs), amt = 0, evid = 0L, cmt = "Cc")
)
ev$id <- id
ev$OCC <- ifelse(ev$time >= 480, 2L, 1L)
dplyr::arrange(ev, time, dplyr::desc(evid))
}
cohort_events <- function(design, tobs, n) {
dplyr::bind_rows(lapply(seq_len(n), function(i) design_events(design, tobs, i)))
}
add_phase <- function(sim) {
sim |>
dplyr::mutate(
phase = ifelse(time >= 480, "Probenecid", "Control"),
tphase = ifelse(phase == "Probenecid", time - T2, time)
)
}
auc_trap <- function(t, y) sum(diff(t) * (head(y, -1) + tail(y, -1)) / 2)Steady-state baseline and mass balance
Without probenecid the models must hold at the analytic baseline
Css = ksyn / (CLr + CLnr) and excrete
fe * ksyn per hour into urine, with
fe = CLr / (CLr + CLnr) equal to the 82% (PDA) and 94%
(HVA) renal fractions stated in the Results.
mod_pda0 <- rxode2::zeroRe(mod_pda)
#> Warning: some etas defaulted to non-mu referenced, possible parsing error: etaiov_ksyn_1, etaiov_ksyn_2
#> as a work-around try putting the mu-referenced expression on a simple line
mod_hva0 <- rxode2::zeroRe(mod_hva)
#> Warning: some etas defaulted to non-mu referenced, possible parsing error: etaiov_cl_renal_1, etaiov_cl_renal_2
#> as a work-around try putting the mu-referenced expression on a simple line
ev_base <- data.frame(id = 1L, time = seq(0, 168, by = 1), amt = 0, evid = 0L, cmt = "Cc", OCC = 1L)
bl <- function(m, ksyn, clr, clnr) {
s <- rxode2::rxSolve(m, ev_base, returnType = "data.frame")
css <- ksyn / (clr + clnr)
fe <- clr / (clr + clnr)
urine_col <- intersect(c("Upda", "Uhva"), names(s))
data.frame(
css_analytic = css,
css_min = min(s$Cc), css_max = max(s$Cc),
fe = fe,
urine_168_sim = s[[urine_col]][s$time == 168],
urine_168_analytic = 168 * ksyn * fe
)
}
bl_tab <- dplyr::bind_rows(
PDA = bl(mod_pda0, 58.6, 15.2, 3.23),
HVA = bl(mod_hva0, 212, 20.4, 1.29),
.id = "Biomarker"
)
#> ℹ omega/sigma items treated as zero: 'etalksyn', 'etalcl_renal', 'etalvc_prob', 'etalcl_prob', 'etaiov_ksyn_1', 'etaiov_ksyn_2'
#> ℹ omega/sigma items treated as zero: 'etalksyn', 'etalcl_renal', 'etalvc_prob', 'etalcl_prob', 'etaiov_cl_renal_1', 'etaiov_cl_renal_2'
knitr::kable(bl_tab, digits = 4)| Biomarker | css_analytic | css_min | css_max | fe | urine_168_sim | urine_168_analytic |
|---|---|---|---|---|---|---|
| PDA | 3.1796 | 3.1796 | 3.1796 | 0.8247 | 8119.423 | 8119.423 |
| HVA | 9.7741 | 9.7741 | 9.7741 | 0.9405 | 33497.759 | 33497.759 |
stopifnot(
# Baseline holds flat at the analytic value (numeric ODE path, rtol 1e-6).
all(abs(bl_tab$css_min / bl_tab$css_analytic - 1) < 1e-4),
all(abs(bl_tab$css_max / bl_tab$css_analytic - 1) < 1e-4),
# Renal fractions stated in the Results (82% and 94%).
abs(bl_tab$fe[1] - 0.82) < 0.005,
abs(bl_tab$fe[2] - 0.94) < 0.005,
# Urinary mass balance over 168 h.
all(abs(bl_tab$urine_168_sim / bl_tab$urine_168_analytic - 1) < 1e-3)
)The typical PDA baseline is 3.18 ng/mL and the typical HVA baseline 9.77 ng/mL.
Probenecid PK
The first-stage probenecid model: 500 mg every 6 h accumulates to an
average steady-state concentration of Dose / (CL * tau),
about 356 uM (total), which with the PDA Ki of 54.5 uM
reduces PDA renal clearance about 7.5-fold. The single-dose 1000 mg and
2000 mg profiles correspond to Figure S2B.
tgrid <- seq(0, 168, by = 0.25)
prob_typ <- dplyr::bind_rows(lapply(c("MD", "SD1000", "SD2000"), function(d) {
rxode2::rxSolve(mod_pda0, design_events(d, tgrid), returnType = "data.frame") |>
add_phase() |>
dplyr::filter(phase == "Probenecid") |>
dplyr::mutate(design = d)
}))
#> ℹ omega/sigma items treated as zero: 'etalksyn', 'etalcl_renal', 'etalvc_prob', 'etalcl_prob', 'etaiov_ksyn_1', 'etaiov_ksyn_2'
#> ℹ omega/sigma items treated as zero: 'etalksyn', 'etalcl_renal', 'etalvc_prob', 'etalcl_prob', 'etaiov_ksyn_1', 'etaiov_ksyn_2'
#> ℹ omega/sigma items treated as zero: 'etalksyn', 'etalcl_renal', 'etalvc_prob', 'etalcl_prob', 'etaiov_ksyn_1', 'etaiov_ksyn_2'
ggplot(prob_typ, aes(tphase, Cc_prob, colour = design)) +
geom_line() +
labs(x = "Time after first interaction-phase dose (h)", y = "Probenecid (uM, total)",
colour = "Regimen", title = "Typical probenecid profiles (cf. Figure S2B)") +
theme_bw()
# Steady state on day 7 (144-168 h after the first 07:00 dose).
md_ss <- prob_typ |> dplyr::filter(design == "MD", tphase >= 144, tphase <= 168)
cavg_ss <- auc_trap(md_ss$tphase, md_ss$Cc_prob) / 24
cavg_analytic <- dose_umol(500) / (0.82 * 6)
c(cavg_simulated = cavg_ss, cavg_analytic = cavg_analytic)
#> cavg_simulated cavg_analytic
#> 356.0478 356.1576
# The day-7 dosing is 6/5/5/8 h rather than a strict 6 h, so the 24-h average
# over a whole day (4 doses) must equal Dose/(CL*6 h) once at steady state.
stopifnot(abs(cavg_ss / cavg_analytic - 1) < 0.01)Interaction study (Figure 2)
A virtual cohort of 200 subjects goes through the two-phase crossover
with multiple-dose probenecid; plasma is sampled densely and urine is
summarised on the study’s collection intervals. Each subject’s
between-occasion eta on ksyn (PDA) or CLr
(HVA) switches between the phases.
rxode2::rxSetSeed(2021)
tdense <- sort(unique(c(seq(0, 12, by = 0.25), seq(12, 168, by = 2))))
n_sub <- 200
ev_md <- cohort_events("MD", tdense, n_sub)
sim_pda <- rxode2::rxSolve(mod_pda, ev_md, returnType = "data.frame") |> add_phase()
sim_hva <- rxode2::rxSolve(mod_hva, ev_md, returnType = "data.frame") |> add_phase()
stopifnot(!anyNA(sim_pda$Cc), !anyNA(sim_hva$Cc))
vpc_plasma <- dplyr::bind_rows(PDA = sim_pda, HVA = sim_hva, .id = "Biomarker") |>
dplyr::group_by(Biomarker, phase, tphase) |>
dplyr::summarise(
lo = quantile(Cc, 0.05), med = median(Cc), hi = quantile(Cc, 0.95),
.groups = "drop"
)
ggplot(vpc_plasma, aes(tphase, med)) +
geom_ribbon(aes(ymin = lo, ymax = hi), fill = "grey80") +
geom_line() +
facet_grid(Biomarker ~ phase, scales = "free_y") +
labs(x = "Time after victim-drug dose (h)", y = "Plasma concentration (ng/mL)",
title = "Simulated median and 90% PI (replicates Figure 2a-b and Figure S8a-b)") +
theme_bw()
ubounds <- c(0, 6, 12, 24, 48, 72, 96, 120, 144, 168)
urine_int <- function(sim, ucol) {
sim |>
dplyr::filter(tphase %in% ubounds) |>
dplyr::arrange(id, phase, tphase) |>
dplyr::group_by(id, phase) |>
dplyr::mutate(amount = .data[[ucol]] - dplyr::lag(.data[[ucol]]),
interval = paste0(dplyr::lag(tphase), "-", tphase)) |>
dplyr::filter(!is.na(amount)) |>
dplyr::ungroup()
}
uint <- dplyr::bind_rows(
PDA = urine_int(sim_pda, "Upda"),
HVA = urine_int(sim_hva, "Uhva"),
.id = "Biomarker"
)
# 9 collection intervals per phase per subject.
stopifnot(nrow(uint) == 2 * 2 * n_sub * 9)
uint |>
dplyr::group_by(Biomarker, phase, tphase) |>
dplyr::summarise(lo = quantile(amount, 0.05), med = median(amount),
hi = quantile(amount, 0.95), .groups = "drop") |>
ggplot(aes(tphase, med)) +
geom_errorbar(aes(ymin = lo, ymax = hi), width = 3) +
geom_point() +
facet_grid(Biomarker ~ phase, scales = "free_y") +
labs(x = "End of urine collection interval (h)", y = "Amount excreted in interval (ug)",
title = "Simulated urinary amounts per interval (replicates Figure 2c-d and Figure S8c-d)") +
theme_bw()
AUC ratio and fraction transported (PKNCA)
The paper computed individual plasma AUC0-168 in each
phase and reported interaction ratios of 2.5-5.1 (PDA) and 2.1-3.7
(HVA), with the fraction eliminated by the inhibited transporters
fT = 1 - AUC(control) / AUC(interaction) (Eq. 1) of
0.59-0.81 (PDA) and 0.53-0.73 (HVA) across the six subjects.
nca_one <- function(sim, biomarker) {
conc <- sim |>
dplyr::filter(!is.na(Cc)) |>
dplyr::select(id, phase, time = tphase, Cc) |>
dplyr::mutate(biomarker = biomarker)
conc_obj <- PKNCA::PKNCAconc(conc, Cc ~ time | biomarker + phase + id)
data_obj <- PKNCA::PKNCAdata(
conc_obj,
intervals = data.frame(start = 0, end = 168, auclast = TRUE)
)
as.data.frame(PKNCA::pk.nca(data_obj)) |>
dplyr::filter(PPTESTCD == "auclast") |>
dplyr::select(biomarker, id, phase, PPORRES) |>
tidyr::pivot_wider(names_from = phase, values_from = PPORRES) |>
dplyr::mutate(ratio = Probenecid / Control, fT = 1 - Control / Probenecid)
}
nca <- dplyr::bind_rows(nca_one(sim_pda, "PDA"), nca_one(sim_hva, "HVA"))
#> No dose information provided, calculations requiring dose will return NA.
#> No dose information provided, calculations requiring dose will return NA.
stopifnot(nrow(nca) == 2 * n_sub, !anyNA(nca$ratio))
# Deterministic typical-value ratio on the same design.
typ_ratio <- function(m) {
s <- rxode2::rxSolve(rxode2::zeroRe(m), design_events("MD", tdense), returnType = "data.frame") |>
add_phase()
ctl <- s[s$phase == "Control", ]
int <- s[s$phase == "Probenecid", ]
auc_trap(int$tphase, int$Cc) / auc_trap(ctl$tphase, ctl$Cc)
}
typ <- c(PDA = typ_ratio(mod_pda), HVA = typ_ratio(mod_hva))
#> Warning: some etas defaulted to non-mu referenced, possible parsing error: etaiov_ksyn_1, etaiov_ksyn_2
#> as a work-around try putting the mu-referenced expression on a simple line
#> ℹ omega/sigma items treated as zero: 'etalksyn', 'etalcl_renal', 'etalvc_prob', 'etalcl_prob', 'etaiov_ksyn_1', 'etaiov_ksyn_2'
#> Warning: some etas defaulted to non-mu referenced, possible parsing error: etaiov_cl_renal_1, etaiov_cl_renal_2
#> as a work-around try putting the mu-referenced expression on a simple line
#> ℹ omega/sigma items treated as zero: 'etalksyn', 'etalcl_renal', 'etalvc_prob', 'etalcl_prob', 'etaiov_cl_renal_1', 'etaiov_cl_renal_2'
observed <- data.frame(
biomarker = c("PDA", "HVA"),
obs_ratio = c("2.5-5.1", "2.1-3.7"),
obs_fT = c("0.59-0.81", "0.53-0.73"),
ratio_lo = c(2.5, 2.1), ratio_hi = c(5.1, 3.7)
)
nca_summary <- nca |>
dplyr::group_by(biomarker) |>
dplyr::summarise(
sim_median_ratio = median(ratio),
sim_ratio_5_95 = sprintf("%.2f-%.2f", quantile(ratio, 0.05), quantile(ratio, 0.95)),
sim_median_fT = median(fT),
.groups = "drop"
) |>
dplyr::mutate(typical_ratio = typ[biomarker]) |>
dplyr::left_join(observed, by = "biomarker")
nca_summary |>
dplyr::select(
"Biomarker" = biomarker,
"Typical AUC0-168 ratio" = typical_ratio,
"Simulated median ratio" = sim_median_ratio,
"Simulated 5-95% ratio" = sim_ratio_5_95,
"Observed individual ratios" = obs_ratio,
"Simulated median fT" = sim_median_fT,
"Observed individual fT" = obs_fT
) |>
knitr::kable(digits = 2)| Biomarker | Typical AUC0-168 ratio | Simulated median ratio | Simulated 5-95% ratio | Observed individual ratios | Simulated median fT | Observed individual fT |
|---|---|---|---|---|---|---|
| HVA | 2.90 | 2.87 | 2.32-3.58 | 2.1-3.7 | 0.65 | 0.53-0.73 |
| PDA | 3.43 | 3.41 | 1.84-6.01 | 2.5-5.1 | 0.71 | 0.59-0.81 |
stopifnot(
# Deterministic: the typical subject sits inside the observed range.
all(nca_summary$typical_ratio > nca_summary$ratio_lo),
all(nca_summary$typical_ratio < nca_summary$ratio_hi),
# Cohort centre (robust to which subjects land in the tails).
all(nca_summary$sim_median_ratio > nca_summary$ratio_lo),
all(nca_summary$sim_median_ratio < nca_summary$ratio_hi)
)PDA is the more sensitive probe: the same probenecid exposure raises
its plasma AUC more than HVA’s because its Ki is 2.5-fold
lower, matching the paper’s conclusion.
External verification (Figure 3)
The PDA model was verified against Shen et al. 2019: a single oral
1000 mg probenecid dose with PDA sampled over 24 h. The paper reports a
simulated mean PDA AUC0-tlast ratio of 2.29 (observed
1.95). The deterministic typical-value ratio from this model is the
gate.
t24 <- sort(unique(c(seq(0, 4, by = 0.1), seq(4, 24, by = 0.5))))
s_sd <- rxode2::rxSolve(mod_pda0, design_events("SD1000", t24), returnType = "data.frame") |>
add_phase()
#> ℹ omega/sigma items treated as zero: 'etalksyn', 'etalcl_renal', 'etalvc_prob', 'etalcl_prob', 'etaiov_ksyn_1', 'etaiov_ksyn_2'
ratio_sd <- with(s_sd, auc_trap(tphase[phase == "Probenecid"], Cc[phase == "Probenecid"]) /
auc_trap(tphase[phase == "Control"], Cc[phase == "Control"]))
c(typical_ratio = ratio_sd, paper_simulated_mean = 2.29, paper_observed_mean = 1.95)
#> typical_ratio paper_simulated_mean paper_observed_mean
#> 2.282701 2.290000 1.950000
stopifnot(abs(ratio_sd / 2.29 - 1) < 0.03)
rxode2::rxSetSeed(2019)
sim_sd <- rxode2::rxSolve(mod_pda, cohort_events("SD1000", t24, n_sub), returnType = "data.frame") |>
add_phase()
sim_sd |>
dplyr::group_by(phase, tphase) |>
dplyr::summarise(lo = quantile(Cc, 0.05), med = median(Cc), hi = quantile(Cc, 0.95), .groups = "drop") |>
ggplot(aes(tphase, med)) +
geom_ribbon(aes(ymin = lo, ymax = hi), fill = "grey80") +
geom_line() +
facet_wrap(~phase) +
labs(x = "Time after probenecid 1000 mg (h)", y = "PDA plasma (ng/mL)",
title = "Simulated PDA, single 1000 mg probenecid (replicates Figure 3)") +
theme_bw()
Power calculations (Figure 4)
The paper scaled the probenecid C/Ki ratio by a factor
from 0.025 to 5 to mimic weaker or stronger OAT1/3 inhibitors, simulated
each study design, and computed the power of a paired t-test on
log-transformed plasma AUCs. The paper reports that detecting a weak
inhibitor (I/Ki = 0.1 relative to probenecid) needs 9, 25
and 10 subjects for the multiple-dose (500 mg every 6 h, sampling over
168 h), single 1000 mg and single 2000 mg (sampling over 24 h) designs,
and that a moderate inhibitor (I/Ki = 0.25) in a
single-dose design needs 9 subjects with PDA and 17 with HVA.
Here each scenario is simulated once for 200 subjects on the study
sampling times, and the sample size for 80% power at alpha = 0.05 is
computed analytically from the mean and SD of the individual log AUC
ratios (power.t.test(type = "paired")) rather than by the
paper’s 1000 repeated t-tests per sample size. The inhibitor-potency
factor is applied by dividing Ki by it, which scales
C_probenecid / Ki exactly as in the paper. The paper does
not say whether its simulated AUCs carried residual error, so the sample
size is shown both from the model-predicted concentrations
(Cc) and from the simulated observations with residual
error (sim), taken from the same solve.
t_md <- c(0, 0.25, 1, 2, 4, 6, 8, 12, 24, 72, 168)
t_sd <- c(0, 0.25, 1, 2, 4, 6, 8, 12, 24)
n80_paired <- function(lr) {
ceiling(stats::power.t.test(delta = mean(lr), sd = sd(lr), power = 0.8,
sig.level = 0.05, type = "paired")$n)
}
n_power <- function(m, ki, design, f, seed) {
rxode2::rxSetSeed(seed)
m_f <- m |> rxode2::ini(lki_oat13 = log(ki / f))
tobs <- if (design == "MD") t_md else t_sd
s <- rxode2::rxSolve(m_f, cohort_events(design, tobs, n_sub), returnType = "data.frame") |>
add_phase()
auc <- s |>
dplyr::arrange(id, phase, tphase) |>
dplyr::group_by(id, phase) |>
dplyr::summarise(pred = auc_trap(tphase, Cc), obs = auc_trap(tphase, sim), .groups = "drop") |>
tidyr::pivot_wider(names_from = phase, values_from = c(pred, obs))
lr_pred <- log(auc$pred_Probenecid / auc$pred_Control)
lr_obs <- log(auc$obs_Probenecid / auc$obs_Control)
stopifnot(length(lr_pred) == n_sub, !anyNA(lr_pred), !anyNA(lr_obs))
data.frame(
design = design, f = f, gmr = exp(mean(lr_pred)),
n80_pred = n80_paired(lr_pred), n80_obs = n80_paired(lr_obs)
)
}
pow <- dplyr::bind_rows(
PDA_MD_0.1 = n_power(mod_pda, 54.5, "MD", 0.1, 11),
PDA_SD1000_0.1 = n_power(mod_pda, 54.5, "SD1000", 0.1, 12),
PDA_SD2000_0.1 = n_power(mod_pda, 54.5, "SD2000", 0.1, 13),
PDA_SD1000_0.25 = n_power(mod_pda, 54.5, "SD1000", 0.25, 14),
HVA_SD1000_0.25 = n_power(mod_hva, 137, "SD1000", 0.25, 15),
.id = "scenario"
)
#> ℹ change initial estimate of `lki_oat13` to `6.30078579466324`
#> ℹ change initial estimate of `lki_oat13` to `6.30078579466324`
#> ℹ change initial estimate of `lki_oat13` to `6.30078579466324`
#> ℹ change initial estimate of `lki_oat13` to `5.38449506278909`
#> ℹ change initial estimate of `lki_oat13` to `6.30627528694802`
pow$paper_n <- c(9, 25, 10, 9, 17)
pow$deviation <- pow$scenario == "HVA_SD1000_0.25"
pow |>
dplyr::select(
"Scenario" = scenario,
"Geometric mean AUC ratio" = gmr,
"n, predicted Cc" = n80_pred,
"n, with residual error" = n80_obs,
"Paper n" = paper_n,
"Known deviation" = deviation
) |>
knitr::kable(digits = 2)| Scenario | Geometric mean AUC ratio | n, predicted Cc | n, with residual error | Paper n | Known deviation |
|---|---|---|---|---|---|
| PDA_MD_0.1 | 1.36 | 11 | 13 | 9 | FALSE |
| PDA_SD1000_0.1 | 1.20 | 24 | 27 | 25 | FALSE |
| PDA_SD2000_0.1 | 1.33 | 11 | 13 | 10 | FALSE |
| PDA_SD1000_0.25 | 1.41 | 8 | 9 | 9 | FALSE |
| HVA_SD1000_0.25 | 1.17 | 4 | 9 | 17 | TRUE |
# The required n depends on the SD of the log ratio estimated from 200 draws,
# so it carries ~10% Monte-Carlo noise (n scales with the variance). Measured
# relative differences for the PDA rows were at most 0.22 (predicted Cc) and
# 0.44 (with residual error); the gate is on the predicted-Cc column with a
# 0.5 bound, which still fails on a structural error (a mis-scaled Ki or a mg
# vs umol dose moves n several-fold). The HVA row is a documented deviation.
chk <- pow[!pow$deviation, ]
stopifnot(nrow(chk) == 4, all(abs(chk$n80_pred / chk$paper_n - 1) < 0.5))The PDA sample sizes are reproduced. The HVA figure is not: the model
needs about 4 subjects (9 with residual error) where the paper reports
17. In the HVA model the only variability that differs between the two
phases of a subject is the 5% between-occasion variability on
CLr plus probenecid exposure, so the paired log-ratio is
much tighter than for PDA, whose 22% between-occasion variability on
ksyn dominates. The paper does not describe its HVA power
simulation in more detail (Figure S13), so the source of the difference
could not be identified; the model parameters themselves are as printed
in Table 1.
Hypothetical inhibition of synthesis (Figure 5)
Eq. 4 of the paper explores what would happen if the inhibitor also
reduced ksyn, by a factor (C_probenecid / Ki)'
equal to 0, 0.1 or 10 times the effect on renal clearance. This is a
what-if scenario, not part of the fitted model, so it is built here by
replacing the plasma ODE and supplying the relative factor as a data
column RKSYN.
mod_pda_ksyn <- mod_pda0 |>
rxode2::model(d/dt(central) <- ksyn / (1 + RKSYN * Cc_prob / ki_oat13) - (cl_renal_eff + cl_nonren) * Cc)
#> ℹ add covariate `RKSYN`
#> Warning: some etas defaulted to non-mu referenced, possible parsing error: etaiov_ksyn_1, etaiov_ksyn_2
#> as a work-around try putting the mu-referenced expression on a simple line
stopifnot(identical(mod_pda_ksyn$state, mod_pda$state))
fig5 <- dplyr::bind_rows(lapply(c("MD", "SD1000"), function(d) {
dplyr::bind_rows(lapply(c(0, 0.1, 10), function(r) {
ev <- design_events(d, seq(0, 168, by = 0.5))
ev$RKSYN <- r
rxode2::rxSolve(mod_pda_ksyn, ev, returnType = "data.frame") |>
add_phase() |>
dplyr::filter(phase == "Probenecid") |>
dplyr::mutate(design = d, RKSYN = r)
}))
}))
#> ℹ omega/sigma items treated as zero: 'etalksyn', 'etalcl_renal', 'etalvc_prob', 'etalcl_prob', 'etaiov_ksyn_1', 'etaiov_ksyn_2'
#> ℹ omega/sigma items treated as zero: 'etalksyn', 'etalcl_renal', 'etalvc_prob', 'etalcl_prob', 'etaiov_ksyn_1', 'etaiov_ksyn_2'
#> ℹ omega/sigma items treated as zero: 'etalksyn', 'etalcl_renal', 'etalvc_prob', 'etalcl_prob', 'etaiov_ksyn_1', 'etaiov_ksyn_2'
#> ℹ omega/sigma items treated as zero: 'etalksyn', 'etalcl_renal', 'etalvc_prob', 'etalcl_prob', 'etaiov_ksyn_1', 'etaiov_ksyn_2'
#> ℹ omega/sigma items treated as zero: 'etalksyn', 'etalcl_renal', 'etalvc_prob', 'etalcl_prob', 'etaiov_ksyn_1', 'etaiov_ksyn_2'
#> ℹ omega/sigma items treated as zero: 'etalksyn', 'etalcl_renal', 'etalvc_prob', 'etalcl_prob', 'etaiov_ksyn_1', 'etaiov_ksyn_2'
ggplot(fig5, aes(tphase, Cc, linetype = factor(RKSYN))) +
geom_line() +
facet_wrap(~design, scales = "free_x") +
labs(x = "Time (h)", y = "PDA plasma (ng/mL)", linetype = "(C/Ki)' on ksyn",
title = "Typical PDA with inhibition also on synthesis (replicates Figure 5)") +
theme_bw()
# RKSYN = 0 must reproduce the fitted model exactly.
base_md <- rxode2::rxSolve(mod_pda0, design_events("MD", seq(0, 168, by = 0.5)), returnType = "data.frame") |>
add_phase() |>
dplyr::filter(phase == "Probenecid")
#> ℹ omega/sigma items treated as zero: 'etalksyn', 'etalcl_renal', 'etalvc_prob', 'etalcl_prob', 'etaiov_ksyn_1', 'etaiov_ksyn_2'
f0 <- fig5 |> dplyr::filter(design == "MD", RKSYN == 0)
stopifnot(max(abs(f0$Cc / base_md$Cc - 1)) < 1e-6)Assumptions and deviations
-
IIV and IOV scale. Table 1 reports IIV and IOV as
percentages of exponential random effects; they are read as CV% and
converted with
omega^2 = log(1 + CV^2). -
IOV encoding. The between-occasion variability (PDA
on
ksyn, HVA onCLr) spans the two crossover phases. It is encoded with anOCCcolumn (1 = control, 2 = probenecid) and one eta per occasion, the occasion-2 eta fixed to the occasion-1 variance. SetOCC = 1for single-occasion work. -
Total Ki. The model uses the total-plasma
Kiof Table 1 (54.5 and 137 uM) against total probenecid concentration. The unbound values (3.4 and 8.5 uM, fraction unbound 0.062) are reported only for comparison with in vitro data. -
Probenecid units. The probenecid block runs in umol
and umol/L, implied by
Kiand the additive error in uM; mg doses are converted with a molecular weight of 285.34 g/mol, which is not printed in the paper. The model’sunits$dosingis therefore umol (probenecid is the only dosed compound), whileunits$concentrationis ug/L for the biomarkerCc. -
Baseline. The biomarker starts at its
inhibitor-free steady state
ksyn / (CLr + CLnr); the paper does not state its initial condition, but its control phase was used as the steady-state baseline and it reports no circadian variation. -
Urine.
Upda/Uhvais the cumulative amount excreted since time 0; the study’s collection-interval amounts are differences of it between interval end points, as done above. - Residual-error form. The paper states only “combined proportional and additive”; the nlmixr2 default combined form is used.
-
Probenecid bioavailability. Probenecid was dosed
orally only, so its
CLandVare apparent (CL/F,V/F) and bioavailability is 1 in the model. - Power calculation. The sample sizes are computed analytically from one simulated cohort per scenario rather than from the paper’s repeated t-tests. They reproduce the paper’s PDA sample sizes to within Monte-Carlo noise; the HVA single-dose figure (17 subjects in the paper) is not reproduced, as discussed in that section.
- Data not reproduced. The individual observed data (Figure 2, Figure S1) are not available, so the figures above show simulated prediction intervals only.