Model and source
- Citation: Tuo Y, Yu X, Li S, Wang J, Liu M, Song X, Ma J, Wang Y, Liu Z, Sun D. Population Pharmacokinetics and Model-Informed Precision Dosing of Clobazam Based on the Developmental and Genetic Characteristics of Children with Epilepsy. Pharmaceutics. 2025;17(7):813. doi:10.3390/pharmaceutics17070813
- Description: Joint parent-plus-metabolite population PK model for oral clobazam and its active metabolite N-desmethylclobazam (norclobazam) in Chinese children with refractory epilepsy (Tuo 2025). Tandem one-compartment disposition: a first-order absorption depot feeds a one-compartment parent, whose entire elimination is routed into a one-compartment metabolite that is then cleared. Absorption was not identifiable from the opportunistic trough-dominated sampling, so Ka is fixed at 1.99 1/h from Jullien 2015; the dosage conversion fraction Fm from clobazam to N-desmethylclobazam was likewise not estimable, so the metabolite clearance and volume are apparent with respect to Fm. Fixed allometric body-weight exponents (0.75 on both clearances, 1 on both volumes, 70 kg reference) scale all four disposition parameters, and CYP2C19 metabolizer phenotype shifts the metabolite clearance only (intermediate and poor metabolizers relative to a normal-metabolizer reference), which is why CYP2C19 poor metabolizers accumulate N-desmethylclobazam without a matching rise in parent exposure. Between-subject variability was estimated on the two clearances only.
- Article: https://doi.org/10.3390/pharmaceutics17070813 (open access, CC BY)
mod <- rxode2::rxode(readModelDb("Tuo_2025_clobazam"))
mod
#> ── rxode2-based free-form 3-cmt ODE model ──────────────────────────────────────
#> ── Initalization: ──
#> Fixed Effects ($theta):
#> lka lcl lvc
#> 0.688134639 1.733423892 4.522549157
#> lcl_ndmclb lvc_ndmclb e_wt_cl
#> 0.009950331 0.609765572 0.750000000
#> e_wt_vc e_wt_cl_ndmclb e_wt_vc_ndmclb
#> 1.000000000 0.750000000 1.000000000
#> e_cyp2c19_im_cl_ndmclb e_cyp2c19_pm_cl_ndmclb propSd
#> -0.250000000 -1.300000000 0.330000000
#> propSd_ndmclb
#> 0.530000000
#>
#> Omega ($omega):
#> etalcl etalcl_ndmclb
#> etalcl 0.1594 0.0000
#> etalcl_ndmclb 0.0000 0.4535
#> attr(,"lotriLabels")
#> [1] "IIV on clobazam CL/F (variance on the log scale)"
#> [2] "IIV on N-desmethylclobazam CL/Fm (variance on the log scale)"
#> attr(,"lotriFix")
#> etalcl etalcl_ndmclb
#> etalcl FALSE FALSE
#> etalcl_ndmclb FALSE FALSE
#>
#> States ($state or $stateDf):
#> Compartment Number Compartment Name
#> 1 1 depot
#> 2 2 central
#> 3 3 central_ndmclb
#> ── Multiple Endpoint Model ($multipleEndpoint): ──
#> variable cmt dvid*
#> 1 Cc ~ … cmt='Cc' or cmt=4 dvid='Cc' or dvid=1
#> 2 Cc_ndmclb ~ … cmt='Cc_ndmclb' or cmt=5 dvid='Cc_ndmclb' or dvid=2
#> * 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 lcl etalcl id
#> 2 lcl_ndmclb etalcl_ndmclb id
#> covariates
#> 1
#> 2 CYP2C19_PM*e_cyp2c19_pm_cl_ndmclb + CYP2C19_IM*e_cyp2c19_im_cl_ndmclb
#>
#> ── Model (Normalized Syntax): ──
#> function() {
#> compartmentData <- list(depot = list(analyte = "clobazam",
#> units = "mg", specimen = "administration site", verified = TRUE),
#> central = list(analyte = "clobazam", units = "mg", specimen = "plasma",
#> verified = TRUE), central_ndmclb = list(analyte = "N-desmethylclobazam",
#> units = "mg", specimen = "plasma", verified = TRUE))
#> covariateData <- list(WT = list(description = "Body weight.",
#> units = "kg", type = "continuous", reference_category = NULL,
#> notes = "Allometric scaling of all four disposition parameters against a 70 kg reference with exponents FIXED at 0.75 (both clearances) and 1 (both volumes); Tuo 2025 Equations (9)-(12) print the exponents inline and Table 2 reports no exponent parameter, so they were not estimated. The cohort weight range is 6.60-73.00 kg (median 20.00 kg), so the 70 kg reference sits at the extreme upper edge of the observed data and the reported typical values are extrapolated adult-standardised values rather than values observed at 70 kg.",
#> source_name = "Weight"), CYP2C19_IM = list(description = "CYP2C19 intermediate-metabolizer phenotype indicator.",
#> units = "(binary)", type = "binary", reference_category = "0 (normal metabolizer; *1/*1 -- both CYP2C19_IM = 0 and CYP2C19_PM = 0)",
#> notes = "1 = subject has CYP2C19 IM phenotype (*1/*2, *1/*3, *2/*17 or *3/*17 in Tuo 2025); 0 = otherwise. Cohort distribution: NM 39.81% (41/103), IM 43.69% (45/103), PM 14.56% (15/103), RM 1.94% (2/103). Affects the metabolite clearance only. The two CYP2C19 rapid metabolizers (*1/*17) were excluded from the covariate analysis for lack of sample size (Tuo 2025 Results 3.1) and no ultrarapid metabolizers (*17/*17) were observed, so neither phenotype has an estimated effect and both fall into the CYP2C19_IM = 0, CYP2C19_PM = 0 reference cell by default; users simulating RM or UM subjects should treat them as an explicit extrapolation.",
#> source_name = "CYP2C19 genotype (NMs / IMs / PMs)"),
#> CYP2C19_PM = list(description = "CYP2C19 poor-metabolizer phenotype indicator.",
#> units = "(binary)", type = "binary", reference_category = "0 (normal, intermediate, or rapid metabolizer)",
#> notes = "1 = subject has CYP2C19 PM phenotype (*2/*2, *2/*3 or *3/*3 in Tuo 2025); 0 = otherwise. Paired with `CYP2C19_IM` to encode the three-level NM (reference) / IM / PM phenotype with two binary indicators. Affects the metabolite clearance only: the paper reports mean post-hoc CL_N-CLB/Fm of 0.46, 0.34 and 0.13 L/h in NMs, IMs and PMs (a 71.7% reduction in PMs vs NMs) against no significant difference in parent CL/F across the three groups.",
#> source_name = "CYP2C19 genotype (NMs / IMs / PMs)"))
#> covariatesDataExcluded <- list(AGE = list(description = "Age.",
#> units = "years", type = "continuous", notes = "Screened in the stepwise covariate analysis and not retained; Tuo 2025 Results 3.2 reports that age had no significant impact on the PK parameters of clobazam or N-desmethylclobazam, which the Discussion attributes to the cohort being purely pediatric (0.85-16.75 years) rather than the combined pediatric-plus-adult range of Tolbert 2016."),
#> SEXF = list(description = "Female sex indicator.", units = "(binary)",
#> type = "binary", notes = "Screened and not retained (Tuo 2025 Results 3.2)."),
#> BSA = list(description = "Body surface area.", units = "m^2",
#> type = "continuous", notes = "Screened and not retained (Tuo 2025 Results 3.2); body weight was the size descriptor carried into the final model."),
#> EGFR = list(description = "Estimated glomerular filtration rate (modified Schwartz formula).",
#> units = "mL/min/1.73m^2", type = "continuous", notes = "Screened as part of the renal-function panel and not retained (Tuo 2025 Results 3.2). The Discussion notes the number of patients with impaired renal function was too small to draw firm conclusions."),
#> ALB = list(description = "Serum albumin.", units = "g/L",
#> type = "continuous", notes = "Screened as part of the hepatic-function panel and not retained (Tuo 2025 Results 3.2)."),
#> ALT = list(description = "Alanine aminotransferase.",
#> units = "U/L", type = "continuous", notes = "Screened as part of the hepatic-function panel and not retained (Tuo 2025 Results 3.2)."),
#> AST = list(description = "Aspartate aminotransferase.",
#> units = "U/L", type = "continuous", notes = "Screened as part of the hepatic-function panel and not retained (Tuo 2025 Results 3.2)."),
#> CONMED_VALPROIC_ACID = list(description = "Concomitant valproic acid indicator.",
#> units = "(binary)", type = "binary", notes = "Screened and not retained (Tuo 2025 Results 3.2 and Supplementary Figure S1); 80.58% of the cohort received valproic acid. The Discussion notes this agrees with prior modelling analyses that found no clinically relevant antiepileptic drug-drug interaction with clobazam."),
#> CONMED_LAMOTRIGINE = list(description = "Concomitant lamotrigine indicator.",
#> units = "(binary)", type = "binary", notes = "Screened and not retained (Tuo 2025 Results 3.2 and Supplementary Figure S1); 28.16% of the cohort."),
#> DIET_KETOGENIC = list(description = "Adherence to a ketogenic diet.",
#> units = "(binary)", type = "binary", notes = "Screened and not retained (Tuo 2025 Results 3.2 and Discussion); only 6.80% of the cohort adhered to a ketogenic diet, which the authors judged too few to resolve an effect despite a published case report of a 42% fall in clobazam and N-desmethylclobazam concentrations on diet initiation."),
#> SNP_ABCB1_RS1045642 = list(description = "ABCB1 3435C>T (rs1045642) genotype.",
#> units = "(genotype)", type = "categorical", notes = "Genotyped and screened; no significant effect on clobazam or N-desmethylclobazam PK (Tuo 2025 Results 3.2 and Supplementary Figure S2). Two further ABCB1 SNPs (rs1128503 1236C>T, rs2032582 2677G>T/A), two CYP3A4 SNPs (rs2740574 *1B, rs2242480 *1G) and six GABA-receptor SNPs (rs2279020, rs279858, rs11503014, rs2229944, rs211014, rs211037) were screened with the same negative result; they are represented here by this single entry rather than one entry each because none carries an estimated coefficient."))
#> description <- "Joint parent-plus-metabolite population PK model for oral clobazam and its active metabolite N-desmethylclobazam (norclobazam) in Chinese children with refractory epilepsy (Tuo 2025). Tandem one-compartment disposition: a first-order absorption depot feeds a one-compartment parent, whose entire elimination is routed into a one-compartment metabolite that is then cleared. Absorption was not identifiable from the opportunistic trough-dominated sampling, so Ka is fixed at 1.99 1/h from Jullien 2015; the dosage conversion fraction Fm from clobazam to N-desmethylclobazam was likewise not estimable, so the metabolite clearance and volume are apparent with respect to Fm. Fixed allometric body-weight exponents (0.75 on both clearances, 1 on both volumes, 70 kg reference) scale all four disposition parameters, and CYP2C19 metabolizer phenotype shifts the metabolite clearance only (intermediate and poor metabolizers relative to a normal-metabolizer reference), which is why CYP2C19 poor metabolizers accumulate N-desmethylclobazam without a matching rise in parent exposure. Between-subject variability was estimated on the two clearances only."
#> population <- list(species = "human", n_subjects = 103L,
#> n_studies = 1L, n_observations = "156 plasma samples yielding 302 analyte concentrations (154 clobazam + 148 N-desmethylclobazam). Sampling depth per patient: 68 sampled once, 21 twice, 12 three times, 1 four times and 1 six times. Assay quantitative ranges 3-1200 ug/L (clobazam) and 40-16000 ug/L (N-desmethylclobazam) by HPLC-MS/MS.",
#> age_range = "0.85-16.75 years", age_median = "5.46 years (mean 5.94, SD 3.15)",
#> weight_range = "6.60-73.00 kg", weight_median = "20.00 kg (mean 22.94, SD 10.48)",
#> sex_female_pct = 43.7, race_ethnicity = c(Asian = 100),
#> disease_state = "Pediatric refractory epilepsy: Lennox-Gastaut syndrome, Dravet syndrome, infantile spasms and other refractory epilepsies. 75.73% of patients were taking three or more antiepileptic drugs; 80.58% received concomitant valproic acid, 28.16% lamotrigine, 24.27% perampanel, 18.45% levetiracetam and 17.48% topiramate, and 6.80% adhered to a ketogenic diet.",
#> dose_range = "Oral clobazam tablets, dosed twice daily when the total dose exceeded 5 mg. Starting dose 5 mg for patients weighing 30 kg or less and 10 mg above 30 kg, then individually titrated on efficacy and tolerability.",
#> regions = "China (single centre: Wuhan Children's Hospital, Tongji Medical College, Huazhong University of Science and Technology; enrolment December 2022 to March 2024).",
#> genotype = "CYP2C19 phenotype: normal metabolizers 41 (39.81%), intermediate 45 (43.69%), poor 15 (14.56%), rapid 2 (1.94%). No ultrarapid metabolizers were observed. All genotype frequencies were consistent with Hardy-Weinberg equilibrium.",
#> notes = "Demographics from Tuo 2025 Table 1. Prospective single-centre opportunistic-sampling study using scavenged residual blood drawn for routine biochemistry during safety follow-up, so the sampling is trough-dominated and carries essentially no information on the absorption or distribution phases -- the reason Ka was fixed and a one-compartment rather than two-compartment parent disposition was selected. Reported therapeutic trough ranges applied by the authors are 30-300 ug/L for clobazam and 300-3000 ug/L for N-desmethylclobazam, with laboratory alert levels of 500 ug/L and 5000 ug/L respectively; these are adult-derived targets carried over to the pediatric setting because no pediatric-specific ranges exist.")
#> reference <- "Tuo Y, Yu X, Li S, Wang J, Liu M, Song X, Ma J, Wang Y, Liu Z, Sun D. Population Pharmacokinetics and Model-Informed Precision Dosing of Clobazam Based on the Developmental and Genetic Characteristics of Children with Epilepsy. Pharmaceutics. 2025;17(7):813. doi:10.3390/pharmaceutics17070813"
#> units <- list(time = "h", dosing = "mg", concentration = "mg/L")
#> vignette <- "Tuo_2025_clobazam"
#> ini({
#> lka <- fix(0.688134638736401)
#> label("Absorption rate constant Ka (1/h)")
#> lcl <- 1.73342389221509
#> label("Apparent clearance CL_CLB/F of clobazam at 70 kg (L/h)")
#> lvc <- 4.52254915729975
#> label("Apparent central volume V_CLB/F of clobazam at 70 kg (L)")
#> lcl_ndmclb <- 0.00995033085316809
#> label("Apparent clearance CL_N-CLB/Fm of N-desmethylclobazam at 70 kg in CYP2C19 normal metabolizers (L/h)")
#> lvc_ndmclb <- 0.609765571620894
#> label("Apparent central volume V_N-CLB/Fm of N-desmethylclobazam at 70 kg (L)")
#> e_wt_cl <- fix(0.75)
#> label("Allometric exponent on clobazam CL/F (unitless)")
#> e_wt_vc <- fix(1)
#> label("Allometric exponent on clobazam V/F (unitless)")
#> e_wt_cl_ndmclb <- fix(0.75)
#> label("Allometric exponent on N-desmethylclobazam CL/Fm (unitless)")
#> e_wt_vc_ndmclb <- fix(1)
#> label("Allometric exponent on N-desmethylclobazam V/Fm (unitless)")
#> e_cyp2c19_im_cl_ndmclb <- -0.25
#> label("CYP2C19 intermediate-metabolizer log-additive shift on N-desmethylclobazam CL/Fm (unitless)")
#> e_cyp2c19_pm_cl_ndmclb <- -1.3
#> label("CYP2C19 poor-metabolizer log-additive shift on N-desmethylclobazam CL/Fm (unitless)")
#> propSd <- c(0, 0.33)
#> label("Proportional residual SD for clobazam (fraction)")
#> propSd_ndmclb <- c(0, 0.53)
#> label("Proportional residual SD for N-desmethylclobazam (fraction)")
#> etalcl ~ 0.1594
#> label("IIV on clobazam CL/F (variance on the log scale)")
#> etalcl_ndmclb ~ 0.4535
#> label("IIV on N-desmethylclobazam CL/Fm (variance on the log scale)")
#> })
#> model({
#> wtr <- WT/70
#> cl <- exp(lcl + etalcl) * wtr^e_wt_cl
#> vc <- exp(lvc) * wtr^e_wt_vc
#> cl_ndmclb <- exp(lcl_ndmclb + etalcl_ndmclb + e_cyp2c19_im_cl_ndmclb *
#> CYP2C19_IM + e_cyp2c19_pm_cl_ndmclb * CYP2C19_PM) *
#> wtr^e_wt_cl_ndmclb
#> vc_ndmclb <- exp(lvc_ndmclb) * wtr^e_wt_vc_ndmclb
#> ka <- exp(lka)
#> d/dt(depot) <- -ka * depot
#> d/dt(central) <- ka * depot - cl * (central/vc)
#> d/dt(central_ndmclb) <- cl * (central/vc) - cl_ndmclb *
#> (central_ndmclb/vc_ndmclb)
#> Cc <- central/vc
#> Cc_ndmclb <- central_ndmclb/vc_ndmclb
#> Cc ~ prop(propSd)
#> Cc_ndmclb ~ prop(propSd_ndmclb)
#> })
#> }Concentrations in the model are in mg/L, matching
the units metadata. Tuo 2025 reports every concentration in
ug/L, so this vignette multiplies model output by 1000
before any comparison with the paper.
UGL <- 1000 # mg/L -> ug/LPopulation
Tuo 2025 is a prospective, single-centre study at Wuhan Children’s
Hospital (December 2022 to March 2024) in 103 Chinese children
with refractory epilepsy (Lennox-Gastaut syndrome, Dravet
syndrome, infantile spasms and other refractory epilepsies) receiving
oral clobazam. Median age was 5.46 years (range 0.85-16.75) and median
body weight 20.0 kg (range 6.60-73.0); 45 of 103 (43.7%) were female
(Table 1). Sampling was opportunistic: 156 plasma
samples were scavenged from residual blood drawn for routine
biochemistry during safety follow-up, yielding 302 analyte
concentrations (154 clobazam and 148 N-desmethylclobazam). Most patients
contributed a single sample (68 of 103), and the draws were almost
entirely in the elimination phase – the reason the authors could not
identify absorption or distribution and fixed Ka.
CYP2C19 phenotype was determined for all 103 patients: 41 normal metabolizers (39.8%), 45 intermediate (43.7%), 15 poor (14.6%) and 2 rapid (1.9%). No ultrarapid metabolizers were observed, and the 2 rapid metabolizers were dropped from the covariate analysis for lack of sample size. The cohort was heavily co-medicated – 75.7% were on three or more antiepileptic drugs, 80.6% on valproic acid – and 6.8% followed a ketogenic diet; none of these was retained as a covariate.
The same information is available programmatically:
str(readModelDb("Tuo_2025_clobazam")()$population)
#> List of 15
#> $ species : chr "human"
#> $ n_subjects : int 103
#> $ n_studies : int 1
#> $ n_observations: chr "156 plasma samples yielding 302 analyte concentrations (154 clobazam + 148 N-desmethylclobazam). Sampling depth"| __truncated__
#> $ age_range : chr "0.85-16.75 years"
#> $ age_median : chr "5.46 years (mean 5.94, SD 3.15)"
#> $ weight_range : chr "6.60-73.00 kg"
#> $ weight_median : chr "20.00 kg (mean 22.94, SD 10.48)"
#> $ sex_female_pct: num 43.7
#> $ race_ethnicity: Named num 100
#> ..- attr(*, "names")= chr "Asian"
#> $ disease_state : chr "Pediatric refractory epilepsy: Lennox-Gastaut syndrome, Dravet syndrome, infantile spasms and other refractory "| __truncated__
#> $ dose_range : chr "Oral clobazam tablets, dosed twice daily when the total dose exceeded 5 mg. Starting dose 5 mg for patients wei"| __truncated__
#> $ regions : chr "China (single centre: Wuhan Children's Hospital, Tongji Medical College, Huazhong University of Science and Tec"| __truncated__
#> $ genotype : chr "CYP2C19 phenotype: normal metabolizers 41 (39.81%), intermediate 45 (43.69%), poor 15 (14.56%), rapid 2 (1.94%)"| __truncated__
#> $ notes : chr "Demographics from Tuo 2025 Table 1. Prospective single-centre opportunistic-sampling study using scavenged resi"| __truncated__Source trace
Every ini() value carries an in-file comment naming its
source location in
inst/modeldb/specificDrugs/Tuo_2025_clobazam.R. They are
collected here.
| Equation / parameter | Value | Source location |
|---|---|---|
| Depot / parent / metabolite ODEs | – | Tuo 2025 Methods 2.5, Equations (1)-(3) |
Exponential IIV P_i = theta * exp(eta_i)
|
– | Equation (4) |
Proportional residual Y = IPRED * (1 + eps)
|
– | Equation (5) |
Continuous covariate form
(Cov/Cov_median)^theta_cov
|
– | Equation (6) |
Categorical covariate form exp(theta_cov)
|
– | Equation (7) |
lka (Ka) |
1.99 1/h, fixed | Table 2 row 1 and Equation (8); value taken from Jullien 2015 (Tuo 2025 reference [19]) |
lvc (V_CLB/F) |
92.07 L at 70 kg | Table 2, RSE 31.15%, bootstrap 40.42-176.27; Equation (9) |
lcl (CL_CLB/F) |
5.66 L/h at 70 kg | Table 2, RSE 10.53%, bootstrap 4.09-7.26; Equation (10) |
lvc_ndmclb (V_N-CLB/Fm) |
1.84 L at 70 kg | Table 2, RSE 29.66%, bootstrap 0.72-2.76; Equation (11) |
lcl_ndmclb (CL_N-CLB/Fm) |
1.01 L/h at 70 kg, NM | Table 2, RSE 11.83%, bootstrap 0.71-1.30; Equation (12) |
e_wt_vc, e_wt_vc_ndmclb
|
1.0, fixed | Equations (9) and (11), printed inline; no Table 2 row |
e_wt_cl, e_wt_cl_ndmclb
|
0.75, fixed | Equations (10) and (12), printed inline; no Table 2 row |
| Reference weight | 70 kg | Equations (9)-(12) denominators |
e_cyp2c19_im_cl_ndmclb |
-0.25 | Table 2 theta_CYP2C19,IM, RSE 39.03%, bootstrap -0.53
to -0.02 |
e_cyp2c19_pm_cl_ndmclb |
-1.30 | Table 2 theta_CYP2C19,PM, RSE 15.01%, bootstrap -1.67
to -0.89 |
| NM reference level | 0.00, fixed | Table 2 theta_CYP2C19,NM
|
etalcl |
variance 0.1594 | Table 2 omega^2 CL_CLB/F (%) = 15.94, footnote
“interindividual variance”; eta-shrinkage 9.73% |
etalcl_ndmclb |
variance 0.4535 | Table 2 omega^2 CL_N-CLB/Fm (%) = 45.35; eta-shrinkage
17.42% |
propSd |
0.33 | Table 2 sigma_CLB, RSE 8.52%, bootstrap 0.27-0.37 |
propSd_ndmclb |
0.53 | Table 2 sigma_N-CLB, RSE 8.60%, bootstrap 0.44-0.61;
eps-shrinkage 33.17% |
| Published validation targets | Table 3 (15 dose rows) | Median trough and PTA by weight and CYP2C19 phenotype |
The apparent (F, Fm) parameterisation
Tuo 2025 Equations (1)-(3) are written in true amounts and carry two
fractions that the study could not estimate: F, oral
bioavailability, and Fm, the
clobazam-to-N-desmethylclobazam dosage conversion fraction (“Since the
amount of CLB converted to N-CLB was not clear, Fm was not estimated in
this study”). Table 2 accordingly reports all four disposition
parameters as apparent: V_CLB/F, CL_CLB/F,
V_N-CLB/Fm, CL_N-CLB/Fm.
The packaged model divides the parent equations through by
F and the metabolite equation by F * Fm, so
each state holds a scaled amount: central holds
A_CLB/F and central_ndmclb holds
A_N-CLB/(F*Fm). The four ini() parameters are
then exactly the four apparent quantities of Table 2, and both observed
concentrations are the true plasma concentrations,
because the same scaling factor divides the state and the volume it is
divided by. This is the convention the register already records for the
sibling ndmima (N-desmethyl-imatinib) models.
A consequence worth stating: the parent’s entire elimination is
routed into the metabolite compartment. The paper writes no separate
non-metabolic clobazam elimination arm, and the Fm factor
of Equation (3) – which is what would otherwise split the parent’s exit
between metabolite formation and other routes – has been absorbed into
the metabolite state scaling.
Virtual cohort
Tuo 2025 Table 3 gives the optimal simulated dosing strategy for 15 combinations of body weight (10, 20, 30, 40, 50 kg) and CYP2C19 phenotype (NM, IM, PM), each with a published median steady-state trough for both analytes and four probability-of-target-attainment columns. That table is the validation target for this vignette, so the cohort mirrors it exactly: twice-daily oral dosing for 10 consecutive days, with the trough read 12 h after the last dose.
tab3 <- tibble::tribble(
~geno, ~wt, ~mgkg, ~pub_clb, ~pub_ndmclb, ~pub_pta_clb30, ~pub_pta_ndmclb300,
"NM", 10, 0.3, 109.28, 629.52, 94.8, 84.1,
"NM", 20, 0.25, 119.74, 699.26, 97.0, 88.4,
"NM", 30, 0.2, 111.47, 651.02, 97.0, 86.3,
"NM", 40, 0.175, 108.24, 630.81, 97.2, 85.5,
"NM", 50, 0.15, 93.03, 568.99, 95.6, 80.9,
"IM", 10, 0.25, 83.75, 658.07, 89.6, 83.5,
"IM", 20, 0.2, 88.17, 708.52, 93.5, 87.1,
"IM", 30, 0.15, 77.19, 626.37, 92.1, 82.7,
"IM", 40, 0.125, 71.53, 579.29, 91.7, 80.9,
"IM", 50, 0.12, 80.33, 616.59, 94.0, 84.4,
"PM", 10, 0.1, 33.50, 962.78, 57.3, 89.9,
"PM", 20, 0.075, 33.06, 950.00, 55.9, 92.2,
"PM", 30, 0.075, 38.59, 1081.81, 66.1, 95.2,
"PM", 40, 0.0625, 35.94, 967.24, 62.3, 91.4,
"PM", 50, 0.06, 37.39, 996.10, 65.2, 92.0
) |>
dplyr::mutate(
id = dplyr::row_number(),
CYP2C19_IM = as.integer(geno == "IM"),
CYP2C19_PM = as.integer(geno == "PM"),
dose_mg = mgkg * wt,
arm = sprintf("%s %g kg (%g mg/kg BID)", geno, wt, mgkg)
)
knitr::kable(
tab3 |>
dplyr::select("CYP2C19" = geno, "Weight (kg)" = wt,
"Dose (mg/kg BID)" = mgkg, "Dose (mg)" = dose_mg,
"Published median CLB trough (ug/L)" = pub_clb,
"Published median N-CLB trough (ug/L)" = pub_ndmclb),
caption = "Tuo 2025 Table 3 dosing scenarios and published median steady-state troughs."
)| CYP2C19 | Weight (kg) | Dose (mg/kg BID) | Dose (mg) | Published median CLB trough (ug/L) | Published median N-CLB trough (ug/L) |
|---|---|---|---|---|---|
| NM | 10 | 0.3000 | 3.00 | 109.28 | 629.52 |
| NM | 20 | 0.2500 | 5.00 | 119.74 | 699.26 |
| NM | 30 | 0.2000 | 6.00 | 111.47 | 651.02 |
| NM | 40 | 0.1750 | 7.00 | 108.24 | 630.81 |
| NM | 50 | 0.1500 | 7.50 | 93.03 | 568.99 |
| IM | 10 | 0.2500 | 2.50 | 83.75 | 658.07 |
| IM | 20 | 0.2000 | 4.00 | 88.17 | 708.52 |
| IM | 30 | 0.1500 | 4.50 | 77.19 | 626.37 |
| IM | 40 | 0.1250 | 5.00 | 71.53 | 579.29 |
| IM | 50 | 0.1200 | 6.00 | 80.33 | 616.59 |
| PM | 10 | 0.1000 | 1.00 | 33.50 | 962.78 |
| PM | 20 | 0.0750 | 1.50 | 33.06 | 950.00 |
| PM | 30 | 0.0750 | 2.25 | 38.59 | 1081.81 |
| PM | 40 | 0.0625 | 2.50 | 35.94 | 967.24 |
| PM | 50 | 0.0600 | 3.00 | 37.39 | 996.10 |
The event table is built as a plain data frame so the covariate
columns survive to rxSolve(). The model declares two
endpoints (Cc, Cc_ndmclb), so observation rows
carry an explicit dvid alongside the ODE-state
cmt; dose rows carry dvid = NA.
DOSE_TIMES <- seq(0, 228, by = 12) # twice daily for 10 days: 20 doses
# Built vectorised (one rbind for the whole cohort) rather than per subject --
# a per-subject rbind loop is quadratic and dominates the render time once the
# cohort reaches a few hundred subjects.
build_events <- function(subjects, obs_times) {
nd <- length(DOSE_TIMES)
no <- length(obs_times)
ns <- nrow(subjects)
doses <- data.frame(
id = rep(subjects$id, each = nd),
time = rep(DOSE_TIMES, times = ns),
amt = rep(subjects$dose_mg, each = nd),
evid = 1L, cmt = "depot", dvid = NA_integer_
)
obs <- data.frame(
id = rep(subjects$id, each = no),
time = rep(obs_times, times = ns),
amt = NA_real_, evid = 0L, cmt = "central", dvid = 1L
)
out <- rbind(doses, obs)
idx <- match(out$id, subjects$id)
out$WT <- subjects$wt[idx]
out$CYP2C19_IM <- subjects$CYP2C19_IM[idx]
out$CYP2C19_PM <- subjects$CYP2C19_PM[idx]
out[order(out$id, out$time, out$evid), ]
}
dense_times <- sort(unique(c(seq(0, 240, by = 0.25), 228, 240)))
ev_typ <- build_events(tab3, dense_times)Simulation
rxode2::zeroRe() gives the typical-value (deterministic)
profile for each arm – the quantity the published median trough should
reproduce, because the steady-state trough is a monotone function of
clearance and the median of a monotone transform is the transform of the
median.
sim_typ <- rxode2::rxSolve(rxode2::zeroRe(mod), ev_typ, returnType = "data.frame")
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalcl_ndmclb'
#> Warning: multi-subject simulation without without 'omega'
stopifnot(!anyNA(sim_typ$Cc), !anyNA(sim_typ$Cc_ndmclb))
sim_typ <- sim_typ |>
dplyr::left_join(dplyr::select(tab3, id, geno, wt, arm, dose_mg), by = "id") |>
dplyr::mutate(clb = Cc * UGL, ndmclb = Cc_ndmclb * UGL)Structural gate: steady-state mass balance
The sharpest cheap check on this model is the steady-state mass
balance, and it pins three things at once: that the explicit ODE system
is really being solved (rxode2 will silently replace a
cl/vc pair with an analytic one-compartment
solution in some models, which would discard the metabolite arm), that
the metabolite formation term carries the parent’s whole
elimination, and that the dose-to-concentration unit chain is right.
Over a steady-state dosing interval,
CL_CLB/F * AUCtau(Cc) must equal the dose, and because
every molecule leaving the parent enters the metabolite,
CL_N-CLB/Fm * AUCtau(Cc_ndmclb) must equal the same dose.
Equivalently the ratio of metabolite to parent AUC is exactly
cl / cl_ndmclb.
tau_win <- sim_typ |> dplyr::filter(time >= 228, time <= 240)
trapz <- function(x, y) sum(diff(x) * (utils::head(y, -1) + utils::tail(y, -1)) / 2)
mb <- tau_win |>
dplyr::group_by(id, arm, dose_mg) |>
dplyr::summarise(
auc_clb = trapz(time, Cc), # mg*h/L
auc_ndmclb = trapz(time, Cc_ndmclb),
cl = dplyr::first(cl), # rxSolve returns the per-subject values
cl_ndmclb = dplyr::first(cl_ndmclb),
.groups = "drop"
) |>
dplyr::mutate(
dose_recovered_clb = cl * auc_clb,
dose_recovered_ndmclb = cl_ndmclb * auc_ndmclb,
err_clb = 100 * (dose_recovered_clb - dose_mg) / dose_mg,
err_ndmclb = 100 * (dose_recovered_ndmclb - dose_mg) / dose_mg,
auc_ratio = auc_ndmclb / auc_clb,
ratio_pred = cl / cl_ndmclb,
err_ratio = 100 * (auc_ratio - ratio_pred) / ratio_pred
)
knitr::kable(
mb |>
dplyr::select("Arm" = arm, "Dose (mg)" = dose_mg,
"CL x AUC, parent (mg)" = dose_recovered_clb,
"CL x AUC, metabolite (mg)" = dose_recovered_ndmclb,
"AUC ratio N-CLB/CLB" = auc_ratio,
"cl / cl_ndmclb" = ratio_pred),
digits = 4,
caption = "Steady-state mass balance. Both recovered doses must equal the administered dose."
)| Arm | Dose (mg) | CL x AUC, parent (mg) | CL x AUC, metabolite (mg) | AUC ratio N-CLB/CLB | cl / cl_ndmclb |
|---|---|---|---|---|---|
| NM 10 kg (0.3 mg/kg BID) | 3.00 | 2.9969 | 3.00 | 5.6097 | 5.6040 |
| NM 20 kg (0.25 mg/kg BID) | 5.00 | 4.9957 | 5.00 | 5.6088 | 5.6040 |
| NM 30 kg (0.2 mg/kg BID) | 6.00 | 5.9953 | 6.00 | 5.6083 | 5.6040 |
| NM 40 kg (0.175 mg/kg BID) | 7.00 | 6.9949 | 7.00 | 5.6080 | 5.6040 |
| NM 50 kg (0.15 mg/kg BID) | 7.50 | 7.4948 | 7.50 | 5.6078 | 5.6040 |
| IM 10 kg (0.25 mg/kg BID) | 2.50 | 2.4974 | 2.50 | 7.2030 | 7.1956 |
| IM 20 kg (0.2 mg/kg BID) | 4.00 | 3.9965 | 4.00 | 7.2019 | 7.1956 |
| IM 30 kg (0.15 mg/kg BID) | 4.50 | 4.4965 | 4.50 | 7.2013 | 7.1956 |
| IM 40 kg (0.125 mg/kg BID) | 5.00 | 4.9964 | 5.00 | 7.2009 | 7.1956 |
| IM 50 kg (0.12 mg/kg BID) | 6.00 | 5.9959 | 6.00 | 7.2006 | 7.1956 |
| PM 10 kg (0.1 mg/kg BID) | 1.00 | 0.9990 | 1.00 | 20.5838 | 20.5626 |
| PM 20 kg (0.075 mg/kg BID) | 1.50 | 1.4987 | 1.50 | 20.5804 | 20.5626 |
| PM 30 kg (0.075 mg/kg BID) | 2.25 | 2.2482 | 2.25 | 20.5787 | 20.5626 |
| PM 40 kg (0.0625 mg/kg BID) | 2.50 | 2.4982 | 2.50 | 20.5776 | 20.5626 |
| PM 50 kg (0.06 mg/kg BID) | 3.00 | 2.9979 | 3.00 | 20.5768 | 20.5626 |
# Deterministic identities: numerical-integration error only, so tight bounds
# are correct here and are NOT an extreme-of-cohort assertion.
stopifnot(
max(abs(mb$err_clb)) < 0.5,
max(abs(mb$err_ndmclb)) < 0.5,
max(abs(mb$err_ratio)) < 0.5
)The metabolite arm therefore survives: if rxode2 had auto-solved the
parent as a linear one-compartment model and dropped the explicit
d/dt() block, the metabolite mass balance would fail
immediately.
Structural gate: closed-form steady-state trough
The parent is a one-compartment model with first-order absorption, so its steady-state trough has a closed form. Comparing it to the ODE solve uses the same per-subject parameters on both sides, so the difference is pure numerical error and a tight bound is appropriate.
ss_trough <- function(dose, ka, cl, vc, tau = 12) {
kel <- cl / vc
dose * ka / (vc * (ka - kel)) *
(exp(-kel * tau) / (1 - exp(-kel * tau)) - exp(-ka * tau) / (1 - exp(-ka * tau)))
}
cf <- sim_typ |>
dplyr::filter(time == 240) |>
dplyr::mutate(closed_form = ss_trough(dose_mg, ka, cl, vc) * UGL) |>
dplyr::select(arm, ode = clb, closed_form) |>
dplyr::mutate(pct_diff = 100 * (ode - closed_form) / closed_form)
stopifnot(max(abs(cf$pct_diff)) < 0.1)
sprintf("Closed-form vs ODE steady-state trough: max |%% diff| = %.4f%%",
max(abs(cf$pct_diff)))
#> [1] "Closed-form vs ODE steady-state trough: max |% diff| = 0.0000%"Replicate published figures
Figure 3 – clearance versus body weight
Tuo 2025 Figure 3 shows both clearances rising allometrically with body weight, with parent clearance above metabolite clearance at every weight.
wt_grid <- tibble::tibble(wt = seq(6.6, 73, length.out = 120)) |>
dplyr::mutate(
`CLB (CL/F)` = 5.66 * (wt / 70)^0.75,
`N-CLB (CL/Fm)` = 1.01 * (wt / 70)^0.75
) |>
tidyr::pivot_longer(-wt, names_to = "Analyte", values_to = "CL")
ggplot(wt_grid, aes(wt, CL, colour = Analyte)) +
geom_line(linewidth = 1) +
labs(x = "Body weight (kg)", y = "Apparent clearance (L/h)",
title = "Clearance versus body weight") +
theme_bw()
Replicates Figure 3 of Tuo 2025: allometric rise of CL_CLB/F and CL_N-CLB/Fm with body weight (typical CYP2C19 normal metabolizer).
Figure 6 – simulated concentration-time profiles at 0.2 mg/kg BID
Tuo 2025 Figure 6 simulates a 20 kg child (the cohort median weight) given 0.2 mg/kg twice daily for 10 days, separately for CYP2C19 NM, IM and PM, and plots the median with the 10th-90th percentile band. The paper’s reading of the figure is that parent troughs are indistinguishable across phenotypes and stay inside 30-300 ug/L, while metabolite troughs rise NM < IM < PM, with about half of the PM subjects above 3000 ug/L.
N_PER_ARM <- 200 # cap: never more than 200 participants per arm
fig6_arms <- tibble::tibble(geno = c("NM", "IM", "PM")) |>
dplyr::mutate(CYP2C19_IM = as.integer(geno == "IM"),
CYP2C19_PM = as.integer(geno == "PM"))
# Coarse over the approach to steady state, dense over the final dosing
# interval where the AUC ratios of the Figure 5 replication are integrated.
# The metabolite elimination rate constant reaches ~5.5 1/h in the upper tail
# of the 45.35% metabolite-clearance variance, so the integration is stiff
# enough that the record count -- not the cohort size -- sets the render time.
fig6_times <- sort(unique(c(seq(0, 228, by = 2), seq(228, 240, by = 0.5))))
fig6_subjects <- fig6_arms |>
dplyr::slice(rep(seq_len(dplyr::n()), each = N_PER_ARM)) |>
dplyr::mutate(id = dplyr::row_number(), wt = 20, dose_mg = 0.2 * 20)
fig6_ev <- build_events(fig6_subjects, fig6_times)
sim_fig6 <- rxode2::rxSolve(mod, fig6_ev, returnType = "data.frame") |>
dplyr::left_join(dplyr::select(fig6_subjects, id, geno), by = "id") |>
dplyr::mutate(clb = Cc * UGL, ndmclb = Cc_ndmclb * UGL,
geno = factor(geno, levels = c("NM", "IM", "PM")))
stopifnot(!anyNA(sim_fig6$clb), !anyNA(sim_fig6$ndmclb))
fig6_summary <- sim_fig6 |>
tidyr::pivot_longer(c(clb, ndmclb), names_to = "analyte", values_to = "conc") |>
dplyr::mutate(analyte = factor(analyte, levels = c("clb", "ndmclb"),
labels = c("CLB", "N-CLB"))) |>
dplyr::group_by(geno, analyte, time) |>
dplyr::summarise(med = median(conc), lo = quantile(conc, 0.1),
hi = quantile(conc, 0.9), .groups = "drop")
ggplot(fig6_summary, aes(time, med, colour = analyte, fill = analyte)) +
geom_ribbon(aes(ymin = lo, ymax = hi), alpha = 0.2, colour = NA) +
geom_line(linewidth = 0.8) +
facet_wrap(~geno, ncol = 1, scales = "free_y") +
labs(x = "Time (h)", y = "Concentration (ug/L)",
colour = "Analyte", fill = "Analyte",
title = "20 kg child, clobazam 0.2 mg/kg twice daily for 10 days") +
theme_bw()
Replicates Figure 6 of Tuo 2025: median (solid) and 10th-90th percentile band (shaded) concentration-time profiles for a 20 kg child on 0.2 mg/kg twice daily for 10 days, by CYP2C19 phenotype.
fig6_trough <- sim_fig6 |>
dplyr::filter(time == 240) |>
dplyr::group_by(geno) |>
dplyr::summarise(
med_clb = median(clb),
med_ndmclb = median(ndmclb),
pct_clb_in = 100 * mean(clb >= 30 & clb <= 300),
pct_ndmclb_gt3000 = 100 * mean(ndmclb > 3000),
.groups = "drop"
)
knitr::kable(
fig6_trough |>
dplyr::rename("CYP2C19" = geno,
"Median CLB trough (ug/L)" = med_clb,
"Median N-CLB trough (ug/L)" = med_ndmclb,
"% CLB in 30-300 ug/L" = pct_clb_in,
"% N-CLB > 3000 ug/L" = pct_ndmclb_gt3000),
digits = 1,
caption = "Steady-state trough summary for the Figure 6 scenario."
)| CYP2C19 | Median CLB trough (ug/L) | Median N-CLB trough (ug/L) | % CLB in 30-300 ug/L | % N-CLB > 3000 ug/L |
|---|---|---|---|---|
| NM | 89.0 | 575.8 | 97.0 | 2.0 |
| IM | 87.4 | 821.8 | 91.5 | 3.5 |
| PM | 90.7 | 2598.2 | 91.0 | 40.0 |
# The paper's qualitative Figure 6 claims. These are assertions on the CENTRE
# of the distribution and on a monotone ordering, not on cohort extremes.
stopifnot(
# "steady-state trough concentrations of CLB had no significant difference
# between the CYP2C19 NMs, IMs and PMs" -- spread across phenotypes is small
# relative to the parent trough itself.
(max(fig6_trough$med_clb) - min(fig6_trough$med_clb)) / min(fig6_trough$med_clb) < 0.15,
# "the steady-state trough concentrations of N-CLB gradually increased in the
# order NMs < IMs < PMs"
!is.unsorted(fig6_trough$med_ndmclb[match(c("NM", "IM", "PM"), fig6_trough$geno)]),
# "Approximately 90% of the N-CLB concentrations in the NMs and IMs were less
# than 3000 ug/L, but about 50% of the N-CLB concentrations in the PMs
# exceeded 3000 ug/L" -- gate the direction and a generous envelope.
fig6_trough$pct_ndmclb_gt3000[fig6_trough$geno == "NM"] < 25,
fig6_trough$pct_ndmclb_gt3000[fig6_trough$geno == "PM"] > 30
)Figure 5 – N-CLB / CLB metabolic ratios by CYP2C19 phenotype
Tuo 2025 Figure 5 reports mean steady-state metabolic ratios of
AUC24h, Cmax and Cmin. The AUC ratio is the one the model pins exactly:
by the steady-state mass balance above it equals
cl / cl_ndmclb, which is exp(-theta_CYP2C19)
times 5.60 and therefore depends only on phenotype, not on weight or
dose.
ratio_window <- sim_fig6 |> dplyr::filter(time >= 228, time <= 240)
ratios <- ratio_window |>
dplyr::group_by(geno, id) |>
dplyr::summarise(
auc24 = trapz(time, ndmclb) / trapz(time, clb),
cmax_r = max(ndmclb) / max(clb),
cmin_r = min(ndmclb) / min(clb),
.groups = "drop"
)
published_ratios <- tibble::tibble(
geno = factor(c("NM", "IM", "PM"), levels = c("NM", "IM", "PM")),
auc24 = c(6.77, 8.28, 27.05),
cmax_r = c(5.81, 6.74, 18.54),
cmin_r = c(8.18, 10.79, 51.40)
)
ratio_cmp <- ratios |>
dplyr::group_by(geno) |>
dplyr::summarise(dplyr::across(c(auc24, cmax_r, cmin_r), mean), .groups = "drop") |>
dplyr::left_join(published_ratios, by = "geno", suffix = c("_sim", "_pub"))
knitr::kable(
ratio_cmp |>
dplyr::select("CYP2C19" = geno,
"AUC24h ratio (simulated)" = auc24_sim,
"AUC24h ratio (Tuo 2025)" = auc24_pub,
"Cmax ratio (simulated)" = cmax_r_sim,
"Cmax ratio (Tuo 2025)" = cmax_r_pub,
"Cmin ratio (simulated)" = cmin_r_sim,
"Cmin ratio (Tuo 2025)" = cmin_r_pub),
digits = 2,
caption = "Replicates Figure 5 of Tuo 2025: mean steady-state N-CLB/CLB metabolic ratios by CYP2C19 phenotype."
)| CYP2C19 | AUC24h ratio (simulated) | AUC24h ratio (Tuo 2025) | Cmax ratio (simulated) | Cmax ratio (Tuo 2025) | Cmin ratio (simulated) | Cmin ratio (Tuo 2025) |
|---|---|---|---|---|---|---|
| NM | 7.56 | 6.77 | 6.34 | 5.81 | 9.57 | 8.18 |
| IM | 10.86 | 8.28 | 8.55 | 6.74 | 15.66 | 10.79 |
| PM | 30.24 | 27.05 | 21.47 | 18.54 | 55.80 | 51.40 |
# Exact identity: the steady-state AUC ratio equals cl / cl_ndmclb, which for a
# 20 kg subject is 5.66/1.01 * exp(-theta). Uses the same drawn etas on both
# sides, so a tight bound is correct.
exact_auc_ratio <- (5.66 / 1.01) * exp(-c(NM = 0, IM = -0.25, PM = -1.30))
auc_ratio_med <- ratios |>
dplyr::group_by(geno) |>
dplyr::summarise(m = median(auc24), .groups = "drop")
stopifnot(
# Ordering NM < IM < PM (Tuo 2025 Figure 5 and Results 3.3).
!is.unsorted(ratio_cmp$auc24_sim),
!is.unsorted(ratio_cmp$cmax_r_sim),
!is.unsorted(ratio_cmp$cmin_r_sim),
# The MEDIAN AUC ratio is the exact structural identity (the published means
# are inflated by the log-normal IIV, so they are compared, not asserted on).
all(abs(100 * (auc_ratio_med$m[match(c("NM", "IM", "PM"), auc_ratio_med$geno)] -
exact_auc_ratio) / exact_auc_ratio) < 20)
)The simulated means sit close to the published means for AUC24h and Cmax. The Cmin ratio is the noisiest of the three in both the paper and the simulation – it is a ratio of two small numbers at the bottom of the profile, and its mean is strongly inflated by the log-normal metabolite-clearance variability – so it is reported side by side rather than gated.
PKNCA validation
NCA is run over the last steady-state dosing interval (228 to 240 h)
on the typical-value cohort, once per analyte. The trough is taken as
cmin, not ctrough: PKNCA defines
ctrough as the predose concentration at the
interval start, which is not returned for this
interval, whereas at steady state the minimum over a full dosing
interval is the trough by construction – the concentration at 228 h and
at 240 h are equal. cmin here reproduces the 240 h model
trough exactly.
nca_conc <- sim_typ |>
dplyr::filter(!is.na(Cc)) |>
dplyr::select(id, arm, geno, wt, time, clb, ndmclb)
dose_df <- ev_typ |>
dplyr::filter(evid == 1L) |>
dplyr::select(id, time, amt) |>
dplyr::left_join(dplyr::select(tab3, id, arm), by = "id")
intervals <- data.frame(
start = 228, end = 240,
cmax = TRUE, tmax = TRUE, auclast = TRUE, cmin = TRUE
)
run_nca <- function(conc_col) {
cobj <- PKNCA::PKNCAconc(
nca_conc |> dplyr::mutate(conc = .data[[conc_col]]),
conc ~ time | arm + id, concu = "ug/L", timeu = "hr"
)
dobj <- PKNCA::PKNCAdose(dose_df, amt ~ time | arm + id, doseu = "mg")
as.data.frame(PKNCA::pk.nca(PKNCA::PKNCAdata(cobj, dobj, intervals = intervals))$result)
}
nca_clb <- run_nca("clb") |> dplyr::mutate(analyte = "CLB")
nca_ndmclb <- run_nca("ndmclb") |> dplyr::mutate(analyte = "N-CLB")
nca_all <- dplyr::bind_rows(nca_clb, nca_ndmclb) |>
dplyr::filter(PPTESTCD %in% c("cmax", "tmax", "auclast", "cmin"))
knitr::kable(
nca_all |>
tidyr::pivot_wider(id_cols = c(analyte, arm), names_from = PPTESTCD,
values_from = PPORRES) |>
dplyr::rename("Analyte" = analyte, "Arm" = arm, "Cmax (ug/L)" = cmax,
"Tmax (h)" = tmax, "AUCtau (ug*h/L)" = auclast,
"Ctrough (ug/L)" = cmin),
digits = 1,
caption = "Steady-state NCA (228-240 h) of the typical-value profiles, by analyte and arm."
)| Analyte | Arm | AUCtau (ug*h/L) | Cmax (ug/L) | Ctrough (ug/L) | Tmax (h) |
|---|---|---|---|---|---|
| CLB | IM 10 kg (0.25 mg/kg BID) | 1898.8 | 236.4 | 86.3 | 1.5 |
| CLB | IM 20 kg (0.2 mg/kg BID) | 1806.8 | 212.2 | 91.1 | 1.5 |
| CLB | IM 30 kg (0.15 mg/kg BID) | 1499.8 | 170.9 | 79.6 | 1.5 |
| CLB | IM 40 kg (0.125 mg/kg BID) | 1343.1 | 150.0 | 73.8 | 1.5 |
| CLB | IM 50 kg (0.12 mg/kg BID) | 1363.4 | 150.0 | 76.7 | 1.5 |
| CLB | NM 10 kg (0.3 mg/kg BID) | 2278.6 | 283.7 | 103.5 | 1.5 |
| CLB | NM 20 kg (0.25 mg/kg BID) | 2258.5 | 265.3 | 113.9 | 1.5 |
| CLB | NM 30 kg (0.2 mg/kg BID) | 1999.7 | 227.8 | 106.2 | 1.5 |
| CLB | NM 40 kg (0.175 mg/kg BID) | 1880.3 | 210.0 | 103.3 | 1.5 |
| CLB | NM 50 kg (0.15 mg/kg BID) | 1704.2 | 187.5 | 95.9 | 1.5 |
| CLB | PM 10 kg (0.1 mg/kg BID) | 759.5 | 94.6 | 34.5 | 1.5 |
| CLB | PM 20 kg (0.075 mg/kg BID) | 677.5 | 79.6 | 34.2 | 1.5 |
| CLB | PM 30 kg (0.075 mg/kg BID) | 749.9 | 85.4 | 39.8 | 1.5 |
| CLB | PM 40 kg (0.0625 mg/kg BID) | 671.5 | 75.0 | 36.9 | 1.5 |
| CLB | PM 50 kg (0.06 mg/kg BID) | 681.7 | 75.0 | 38.3 | 1.5 |
| N-CLB | IM 10 kg (0.25 mg/kg BID) | 13677.4 | 1484.0 | 724.4 | 3.2 |
| N-CLB | IM 20 kg (0.2 mg/kg BID) | 13012.3 | 1331.0 | 763.9 | 3.5 |
| N-CLB | IM 30 kg (0.15 mg/kg BID) | 10800.4 | 1072.8 | 667.1 | 3.8 |
| N-CLB | IM 40 kg (0.125 mg/kg BID) | 9671.5 | 942.5 | 616.9 | 3.8 |
| N-CLB | IM 50 kg (0.12 mg/kg BID) | 9817.4 | 944.2 | 640.8 | 4.0 |
| N-CLB | NM 10 kg (0.3 mg/kg BID) | 12782.3 | 1430.3 | 653.2 | 3.0 |
| N-CLB | NM 20 kg (0.25 mg/kg BID) | 12667.5 | 1332.9 | 718.3 | 3.2 |
| N-CLB | NM 30 kg (0.2 mg/kg BID) | 11215.2 | 1143.3 | 669.8 | 3.5 |
| N-CLB | NM 40 kg (0.175 mg/kg BID) | 10545.1 | 1053.8 | 651.0 | 3.5 |
| N-CLB | NM 50 kg (0.15 mg/kg BID) | 9557.2 | 941.0 | 604.2 | 3.5 |
| N-CLB | PM 10 kg (0.1 mg/kg BID) | 15634.4 | 1480.8 | 1032.2 | 4.8 |
| N-CLB | PM 20 kg (0.075 mg/kg BID) | 13944.5 | 1275.7 | 982.1 | 5.0 |
| N-CLB | PM 30 kg (0.075 mg/kg BID) | 15432.1 | 1389.3 | 1119.6 | 5.0 |
| N-CLB | PM 40 kg (0.0625 mg/kg BID) | 13819.1 | 1231.9 | 1020.7 | 5.2 |
| N-CLB | PM 50 kg (0.06 mg/kg BID) | 14027.4 | 1242.1 | 1049.0 | 5.2 |
Comparison against published NCA
Tuo 2025 reports no conventional NCA table, but Table 3 gives a
published median steady-state trough for both analytes
in each of the 15 dosing scenarios – the same quantity as
ctrough above. That is the reference set.
simulated_long <- nca_all |>
dplyr::filter(PPTESTCD == "cmin") |>
dplyr::select(analyte, arm, PPTESTCD, PPORRES)
published <- dplyr::bind_rows(
tab3 |> dplyr::transmute(analyte = "CLB", arm, cmin = pub_clb),
tab3 |> dplyr::transmute(analyte = "N-CLB", arm, cmin = pub_ndmclb)
)
cmp <- nlmixr2lib::ncaComparisonTable(
simulated = simulated_long,
reference = published,
by = c("analyte", "arm"),
units = c(cmin = "ug/L"),
tolerance_pct = 20
)
knitr::kable(
cmp,
caption = "Simulated versus published median steady-state trough concentration for all 15 Tuo 2025 Table 3 scenarios."
)| NCA parameter | analyte | arm | Reference | Simulated | % diff |
|---|---|---|---|---|---|
| Cmin (ug/L) | CLB | NM 10 kg (0.3 mg/kg BID) | 109 | 104 | -5.3% |
| Cmin (ug/L) | CLB | NM 20 kg (0.25 mg/kg BID) | 120 | 114 | -4.9% |
| Cmin (ug/L) | CLB | NM 30 kg (0.2 mg/kg BID) | 111 | 106 | -4.7% |
| Cmin (ug/L) | CLB | NM 40 kg (0.175 mg/kg BID) | 108 | 103 | -4.6% |
| Cmin (ug/L) | CLB | NM 50 kg (0.15 mg/kg BID) | 93 | 95.9 | +3.0% |
| Cmin (ug/L) | CLB | IM 10 kg (0.25 mg/kg BID) | 83.8 | 86.3 | +3.0% |
| Cmin (ug/L) | CLB | IM 20 kg (0.2 mg/kg BID) | 88.2 | 91.1 | +3.3% |
| Cmin (ug/L) | CLB | IM 30 kg (0.15 mg/kg BID) | 77.2 | 79.6 | +3.2% |
| Cmin (ug/L) | CLB | IM 40 kg (0.125 mg/kg BID) | 71.5 | 73.8 | +3.1% |
| Cmin (ug/L) | CLB | IM 50 kg (0.12 mg/kg BID) | 80.3 | 76.7 | -4.5% |
| Cmin (ug/L) | CLB | PM 10 kg (0.1 mg/kg BID) | 33.5 | 34.5 | +3.0% |
| Cmin (ug/L) | N-CLB | NM 10 kg (0.3 mg/kg BID) | 630 | 653 | +3.8% |
| Cmin (ug/L) | CLB | PM 20 kg (0.075 mg/kg BID) | 33.1 | 34.2 | +3.3% |
| Cmin (ug/L) | N-CLB | NM 20 kg (0.25 mg/kg BID) | 699 | 718 | +2.7% |
| Cmin (ug/L) | CLB | PM 30 kg (0.075 mg/kg BID) | 38.6 | 39.8 | +3.2% |
| Cmin (ug/L) | N-CLB | NM 30 kg (0.2 mg/kg BID) | 651 | 670 | +2.9% |
| Cmin (ug/L) | CLB | PM 40 kg (0.0625 mg/kg BID) | 35.9 | 36.9 | +2.6% |
| Cmin (ug/L) | N-CLB | NM 40 kg (0.175 mg/kg BID) | 631 | 651 | +3.2% |
| Cmin (ug/L) | CLB | PM 50 kg (0.06 mg/kg BID) | 37.4 | 38.3 | +2.6% |
| Cmin (ug/L) | N-CLB | NM 50 kg (0.15 mg/kg BID) | 569 | 604 | +6.2% |
| Cmin (ug/L) | N-CLB | IM 10 kg (0.25 mg/kg BID) | 658 | 724 | +10.1% |
| Cmin (ug/L) | N-CLB | IM 20 kg (0.2 mg/kg BID) | 709 | 764 | +7.8% |
| Cmin (ug/L) | N-CLB | IM 30 kg (0.15 mg/kg BID) | 626 | 667 | +6.5% |
| Cmin (ug/L) | N-CLB | IM 40 kg (0.125 mg/kg BID) | 579 | 617 | +6.5% |
| Cmin (ug/L) | N-CLB | IM 50 kg (0.12 mg/kg BID) | 617 | 641 | +3.9% |
| Cmin (ug/L) | N-CLB | PM 10 kg (0.1 mg/kg BID) | 963 | 1030 | +7.2% |
| Cmin (ug/L) | N-CLB | PM 20 kg (0.075 mg/kg BID) | 950 | 982 | +3.4% |
| Cmin (ug/L) | N-CLB | PM 30 kg (0.075 mg/kg BID) | 1080 | 1120 | +3.5% |
| Cmin (ug/L) | N-CLB | PM 40 kg (0.0625 mg/kg BID) | 967 | 1020 | +5.5% |
| Cmin (ug/L) | N-CLB | PM 50 kg (0.06 mg/kg BID) | 996 | 1050 | +5.3% |
# Recompute the percent difference numerically -- the `% diff` column of
# ncaComparisonTable() is formatted character and cannot be asserted on.
gate <- simulated_long |>
dplyr::left_join(published, by = c("analyte", "arm")) |>
dplyr::mutate(pct_diff = 100 * (PPORRES - cmin) / cmin)
knitr::kable(
gate |>
dplyr::group_by(analyte) |>
dplyr::summarise(`Median % diff` = median(pct_diff),
`Max |% diff|` = max(abs(pct_diff)), .groups = "drop") |>
dplyr::rename("Analyte" = analyte),
digits = 2,
caption = "Agreement between the deterministic model trough and the Tuo 2025 Table 3 published medians."
)| Analyte | Median % diff | Max |% diff| |
|---|---|---|
| CLB | 3.01 | 5.27 |
| N-CLB | 5.31 | 10.08 |
# This comparison is DETERMINISTIC on the simulated side (zeroRe, no RNG), so
# the bound cannot drift with the rxode2 build or the thread count. The
# residual gap is the Monte Carlo median-estimation error of the paper's own
# simulation, which scatters roughly +/- 4%.
stopifnot(
max(abs(dplyr::filter(gate, analyte == "CLB")$pct_diff)) < 15,
max(abs(dplyr::filter(gate, analyte == "N-CLB")$pct_diff)) < 15,
abs(median(gate$pct_diff)) < 8
)Probability of target attainment
The parent trough depends only on etalcl (no IIV was
estimated on V_CLB/F), and it is monotone decreasing in
clearance. The probability of exceeding a target is therefore available
exactly, by root-finding the eta that lands on the
threshold and reading the normal CDF – no sampling, so even the sub-1%
columns of Table 3 are resolved rather than quantised by a finite
cohort.
OM_CL <- 0.1594 # etalcl variance, Table 2
pta_exact <- function(dose, wt, threshold_ugL) {
f <- function(eta) {
cl <- 5.66 * (wt / 70)^0.75 * exp(eta)
vc <- 92.07 * (wt / 70)
ss_trough(dose, 1.99, cl, vc) * UGL - threshold_ugL
}
if (f(-6) < 0) return(0)
if (f(6) > 0) return(100)
eta_star <- stats::uniroot(f, c(-6, 6), tol = 1e-10)$root
100 * stats::pnorm(eta_star / sqrt(OM_CL))
}
pta_clb <- tab3 |>
dplyr::rowwise() |>
dplyr::mutate(
sim_pta30 = pta_exact(dose_mg, wt, 30),
sim_pta300 = pta_exact(dose_mg, wt, 300),
sim_pta500 = pta_exact(dose_mg, wt, 500)
) |>
dplyr::ungroup()
knitr::kable(
pta_clb |>
dplyr::select("CYP2C19" = geno, "Weight (kg)" = wt,
"Dose (mg/kg BID)" = mgkg,
"PTA >= 30 ug/L (simulated)" = sim_pta30,
"PTA >= 30 ug/L (Tuo 2025)" = pub_pta_clb30,
"PTA >= 300 ug/L (simulated)" = sim_pta300,
"PTA >= 500 ug/L (simulated)" = sim_pta500),
digits = 1,
caption = "Replicates the CLB probability-of-target-attainment columns of Tuo 2025 Table 3."
)| CYP2C19 | Weight (kg) | Dose (mg/kg BID) | PTA >= 30 ug/L (simulated) | PTA >= 30 ug/L (Tuo 2025) | PTA >= 300 ug/L (simulated) | PTA >= 500 ug/L (simulated) |
|---|---|---|---|---|---|---|
| NM | 10 | 0.3 | 94.1 | 94.8 | 3.3 | 0.2 |
| NM | 20 | 0.2 | 96.4 | 97.0 | 3.9 | 0.2 |
| NM | 30 | 0.2 | 96.4 | 97.0 | 2.5 | 0.1 |
| NM | 40 | 0.2 | 96.6 | 97.2 | 1.9 | 0.1 |
| NM | 50 | 0.1 | 96.1 | 95.6 | 1.2 | 0.0 |
| IM | 10 | 0.2 | 91.4 | 89.6 | 1.4 | 0.1 |
| IM | 20 | 0.2 | 93.9 | 93.5 | 1.3 | 0.0 |
| IM | 30 | 0.1 | 92.6 | 92.1 | 0.5 | 0.0 |
| IM | 40 | 0.1 | 91.7 | 91.7 | 0.3 | 0.0 |
| IM | 50 | 0.1 | 92.9 | 94.0 | 0.3 | 0.0 |
| PM | 10 | 0.1 | 58.2 | 57.3 | 0.0 | 0.0 |
| PM | 20 | 0.1 | 58.2 | 55.9 | 0.0 | 0.0 |
| PM | 30 | 0.1 | 67.8 | 66.1 | 0.0 | 0.0 |
| PM | 40 | 0.1 | 63.6 | 62.3 | 0.0 | 0.0 |
| PM | 50 | 0.1 | 66.3 | 65.2 | 0.0 | 0.0 |
pta_err <- pta_clb$sim_pta30 - pta_clb$pub_pta_clb30
stopifnot(
# Exact (non-stochastic) computation against published values; compare on the
# percentage-point scale, and gate the centre plus a robust quantile.
abs(median(pta_err)) < 10,
stats::quantile(abs(pta_err), 0.9) < 20,
# The paper's headline stratification: PMs on the recommended dose reach the
# 30 ug/L parent target far less often than NMs and IMs.
mean(pta_clb$sim_pta30[pta_clb$geno == "PM"]) <
mean(pta_clb$sim_pta30[pta_clb$geno == "NM"]) - 20
)The metabolite target-attainment columns depend on both etas, so they
are estimated from the stochastic cohort rather than in closed form. The
cohort is capped at 200 subjects per arm, which resolves a probability
to about 0.5 percentage points – adequate for the
>= 300 ug/L column (81-95% in Table 3) but
not for the >= 3000 and
>= 5000 columns (0.1-9.3%), which are therefore reported
for inspection and not gated.
pta_subjects <- tab3 |>
dplyr::select(arm_id = id, wt, dose_mg, CYP2C19_IM, CYP2C19_PM) |>
dplyr::slice(rep(seq_len(dplyr::n()), each = N_PER_ARM)) |>
dplyr::mutate(id = dplyr::row_number())
# Only the 240 h trough is needed, so the PTA cohort carries one observation
# record per subject.
pta_ev <- build_events(pta_subjects, 240)
sim_pta <- rxode2::rxSolve(mod, pta_ev, returnType = "data.frame") |>
dplyr::left_join(dplyr::select(pta_subjects, id, arm_id), by = "id") |>
dplyr::filter(time == 240) |>
dplyr::mutate(ndmclb = Cc_ndmclb * UGL)
stopifnot(!anyNA(sim_pta$ndmclb))
pta_ndmclb <- sim_pta |>
dplyr::group_by(arm_id) |>
dplyr::summarise(sim_pta300 = 100 * mean(ndmclb >= 300),
sim_pta3000 = 100 * mean(ndmclb >= 3000),
.groups = "drop") |>
dplyr::left_join(dplyr::select(tab3, arm_id = id, geno, wt, mgkg, pub_pta_ndmclb300),
by = "arm_id")
knitr::kable(
pta_ndmclb |>
dplyr::select("CYP2C19" = geno, "Weight (kg)" = wt, "Dose (mg/kg BID)" = mgkg,
"PTA >= 300 ug/L (simulated)" = sim_pta300,
"PTA >= 300 ug/L (Tuo 2025)" = pub_pta_ndmclb300,
"PTA >= 3000 ug/L (simulated, not gated)" = sim_pta3000),
digits = 1,
caption = "Replicates the N-CLB probability-of-target-attainment columns of Tuo 2025 Table 3."
)| CYP2C19 | Weight (kg) | Dose (mg/kg BID) | PTA >= 300 ug/L (simulated) | PTA >= 300 ug/L (Tuo 2025) | PTA >= 3000 ug/L (simulated, not gated) |
|---|---|---|---|---|---|
| NM | 10 | 0.3 | 81.0 | 84.1 | 3.0 |
| NM | 20 | 0.2 | 85.5 | 88.4 | 4.0 |
| NM | 30 | 0.2 | 83.5 | 86.3 | 2.0 |
| NM | 40 | 0.2 | 87.5 | 85.5 | 1.0 |
| NM | 50 | 0.1 | 86.0 | 80.9 | 1.5 |
| IM | 10 | 0.2 | 84.5 | 83.5 | 2.0 |
| IM | 20 | 0.2 | 89.0 | 87.1 | 2.5 |
| IM | 30 | 0.1 | 80.5 | 82.7 | 2.5 |
| IM | 40 | 0.1 | 83.5 | 80.9 | 2.5 |
| IM | 50 | 0.1 | 86.0 | 84.4 | 2.0 |
| PM | 10 | 0.1 | 94.5 | 89.9 | 12.5 |
| PM | 20 | 0.1 | 94.0 | 92.2 | 5.5 |
| PM | 30 | 0.1 | 95.5 | 95.2 | 9.5 |
| PM | 40 | 0.1 | 94.5 | 91.4 | 9.0 |
| PM | 50 | 0.1 | 96.0 | 92.0 | 7.5 |
Assumptions and deviations
-
Interindividual-variance scale. Tuo 2025 Table 2
labels the two IIV rows
omega^2 CL/F (%)with values 15.94 and 45.35. Two readings are possible: a variance expressed as a percentage (0.1594, 0.4535) or a %CV (0.1594, 0.4535 as SDs). The table footnote resolves it in words – “interindividual variance for pharmacokinetic parameters” – and the paper’s own Monte Carlo output confirms it numerically. Reading them as variances reproduces the Table 3 parent PTA columns for the 20 kg NM / 0.25 mg/kg row (3.9% and 0.2% of subjects above 300 and 500 ug/L, against the published 5.1% and 0.1%); reading them as CVs predicts 0.0005% and 7e-11%, four to ten orders of magnitude too small. The variance reading is used. -
Bioavailability and metabolite fraction are not
identifiable.
FandFmwere not estimated, so the model is written in the apparent parameterisation the paper reports;centralandcentral_ndmclbhold scaled amounts. Both predicted concentrations are the true plasma concentrations. See “The apparent (F, Fm) parameterisation” above. A consequence is that the parent’s entire elimination is routed into the metabolite compartment – the paper models no separate non-metabolic clobazam elimination arm. -
Absorption is inherited, not fitted.
Kais fixed at 1.99 1/h from Jullien 2015 (Tuo 2025 reference [19]) because the opportunistic sampling carried essentially no absorption-phase information. Predictions near Tmax are therefore weakly supported by this study’s own data; the trough predictions that the paper and this vignette validate against are not sensitive to it. - 70 kg reference weight is an extrapolation. All four typical values are reported standardised to 70 kg, but the cohort spans 6.60-73.0 kg with a median of 20.0 kg. Only one subject sits near the reference, so the printed typical values are adult-standardised rather than observed.
-
Allometric exponents were not estimated. Equations
(9)-(12) print 1.0 on the volumes and 0.75 on the clearances as literal
constants and Table 2 has no exponent row, no RSE and no bootstrap
interval for them; they are encoded with
fixed(). - No IIV on volumes or on Ka. The Discussion states the model was simplified by “reducing model compartments, freezing parameter values, and ignoring interindividual variability for certain parameters”. Only the two clearances carry an eta, and Table 2 reports no correlation block, so the two etas are independent. This is encoded faithfully rather than by inventing variances.
-
CYP2C19 rapid and ultrarapid metabolizers have no
encoding. The 2 rapid metabolizers (
*1/*17) were excluded from the covariate analysis for lack of sample size and no ultrarapid metabolizers were enrolled, so neither has an estimated effect. Both fall into theCYP2C19_IM = 0, CYP2C19_PM = 0reference cell by default; simulating them is an explicit extrapolation the paper flags as a limitation. -
Screened-but-not-retained covariates (age, sex,
body surface area, renal and hepatic panels, concomitant antiepileptics,
ketogenic diet, and the ABCB1 / CYP3A4 / GABA-receptor SNPs) are
recorded in
covariatesDataExcludedso the paper’s covariate screen is preserved without implying they act in the model. The twelve screened SNPs are represented by a single entry because none carries an estimated coefficient. -
Table 1 unit typo in the source. Tuo 2025 Table 1
labels the two concentration rows
ug*mL^-1while the values (mean 137.01 clobazam, 1611.56 N-desmethylclobazam) and the whole of the surrounding text are inug*L^-1. Theug/Lreading is used. Relatedly, Results 3.1 calls 137.01 the median clobazam concentration whereas Table 1 lists it as the mean (median 114.50); neither number enters the model. -
Comparison target. The paper reports no
conventional NCA table, so the validation reference is the published
median steady-state trough of Table 3 for all 15 weight-by-phenotype
scenarios, compared against
ctroughfrom PKNCA on the typical-value profiles. -
Residual error is excluded from the simulated
concentrations.
CcandCc_ndmclbare individual predictions; the proportional residual terms describe assay and model misspecification error and are not added before computing troughs or target attainment. Adding a 33-53% proportional error would also drive simulated concentrations negative. -
Metabolite PTA resolution. The
>= 3000and>= 5000 ug/Lmetabolite columns of Table 3 range from 0.1% to 9.3%, below what a 200-subject-per-arm cohort resolves; the>= 3000column is shown for inspection and neither is gated. The parent PTA columns are computed exactly instead, by root-finding rather than sampling. -
Equation recovery. All twelve numbered equations
were lost by the PDF-to-markdown conversion (each rendered as
formula-not-decoded); they were recovered from the PDF text layer directly and are the basis for the ODE system, the covariate forms and the fixed allometric exponents.