Tribendimidine pooled population PK (Meister 2019)
Source:vignettes/articles/Meister_2019_tribendimidine.Rmd
Meister_2019_tribendimidine.RmdModel and source
- Citation: Meister I, Assawasuwannakit P, Vanobberghen F, Penny MA, Odermatt P, Sayasone S, Huwyler J, Tarning J, Keiser J. Pooled population pharmacokinetic analysis of tribendimidine for the treatment of Opisthorchis viverrini infections. Antimicrob Agents Chemother. 2019;63(4):e01391-18. doi:10.1128/AAC.01391-18
- Description: Pooled population PK model for the two tribendimidine metabolites dADT (deacetylated amidantel, the anthelminthically active species) and adADT (acetylated dADT) in Opisthorchis viverrini-infected Lao adolescents and adults given single oral doses of 25-600 mg (Meister 2019). Pools the two phase 2a ascending-dose trials of Vanobberghen 2016 (68 patients) with a phase 2b trial (125 patients). A Savic transit-compartment absorption model with a non-integer, log-normally distributed number of transit compartments (5.27 typical) feeds a one-compartment dADT disposition model, from which a fixed 65% of elimination is routed to a one-compartment adADT model (the remaining 35% is assumed renal). Allometric body-weight scaling (fixed 0.75 on clearances, 1 on volumes, reference 52 kg), a linear age effect on dADT clearance, and two absorption covariates: the 200-mg-versus-50-mg tablet formulation and the breaking of the enteric coating of a split 50-mg tablet. Systematic whole-blood and dried-blood-spot matrix conversion factors, each metabolite carrying its own matrix-specific residual error. Fitted on natural-log-transformed molar concentrations, so amounts are nmol and concentrations nmol/L.
- Article: https://doi.org/10.1128/AAC.01391-18
Tribendimidine is an oral anthelmintic marketed in China since 2004 and under development as an alternative to praziquantel for Opisthorchis viverrini infection. After ingestion it is hydrolysed non-enzymatically, without any enzyme involvement, into deacetylated amidantel (dADT) and terephthalaldehyde. dADT is the species that carries the trematocidal activity; part of it is acetylated to adADT and the rest is excreted unchanged in urine. The parent prodrug is never measured, so the model is written entirely in terms of the two metabolites.
Meister 2019 pools the two phase 2a ascending-dose trials that
Vanobberghen_2016_tribendimidine was fitted to (68
patients, 25 to 600 mg) with a new phase 2b trial (125 patients, 400
mg), and refits. This vignette validates the pooled model; the
predecessor has its own vignette.
mod <- rxode2::rxode2(readModelDb("Meister_2019_tribendimidine"))
#> ℹ parameter labels from comments will be replaced by 'label()'
mod
#> ── rxode2-based free-form 3-cmt ODE model ──────────────────────────────────────
#> ── Initalization: ──
#> Fixed Effects ($theta):
#> lmtt lntr lfdepot
#> 1.156881 1.662030 0.000000
#> lcl lvc lcl_adadt
#> 2.760010 4.486387 4.186620
#> lvc_adadt fm e_wt_cl
#> 2.753661 0.650000 0.750000
#> e_wt_vc e_age_cl e_tab200_mtt
#> 1.000000 -0.011900 0.429000
#> e_split50_mtt e_wb_cc e_dbs_cc
#> -0.794000 -0.145000 -0.137000
#> e_wb_cc_adadt e_dbs_cc_adadt expSdPlasma
#> 0.050000 0.070000 0.460477
#> expSdWholeBlood expSdDbs expSdPlasma_adadt
#> 0.564023 0.622523 0.379875
#> expSdWholeBlood_adadt expSdDbs_adadt
#> 0.445891 0.444166
#>
#> Omega ($omega):
#> etalfdepot etalmtt etalntr etalcl etalvc etalcl_adadt
#> etalfdepot 0.132235 0.000000 0.000000 0.000000 0.000000 0.000000
#> etalmtt 0.000000 0.256718 0.000000 0.000000 0.000000 0.000000
#> etalntr 0.000000 0.000000 2.302585 0.000000 0.000000 0.000000
#> etalcl 0.000000 0.000000 0.000000 0.038075 0.000000 0.000000
#> etalvc 0.000000 0.000000 0.000000 0.000000 0.064927 0.000000
#> etalcl_adadt 0.000000 0.000000 0.000000 0.000000 0.000000 0.852541
#> etalvc_adadt 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
#> etalvc_adadt
#> etalfdepot 0.000000
#> etalmtt 0.000000
#> etalntr 0.000000
#> etalcl 0.000000
#> etalvc 0.000000
#> etalcl_adadt 0.000000
#> etalvc_adadt 0.087836
#> attr(,"lotriLabels")
#> [1] "Table 2, F '% CV for BSV' = 37.6 (95% CI 32.1-45.8); log(1 + 0.376^2) = 0.132235"
#> [2] "Table 2, MTT '% CV for BSV' = 54.1 (95% CI 49.9-66.2); log(1 + 0.541^2) = 0.256718"
#> [3] "Table 2, transit-compartment '% CV for BSV' = 300 (95% CI 204-397); log(1 + 3.00^2) = 2.302585"
#> [4] "Table 2, dADT CL/F '% CV for BSV' = 19.7 (95% CI 16.6-22.5); log(1 + 0.197^2) = 0.038075"
#> [5] "Table 2, dADT V/F '% CV for BSV' = 25.9 (95% CI 20.1-28.5); log(1 + 0.259^2) = 0.064927"
#> [6] "Table 2, adADT CL/F '% CV for BSV' = 116 (95% CI 105-172); log(1 + 1.16^2) = 0.852541"
#> [7] "Table 2, adADT V/F '% CV for BSV' = 30.3 (95% CI 25.9-54.3); log(1 + 0.303^2) = 0.087836"
#> attr(,"lotriFix")
#> etalfdepot etalmtt etalntr etalcl etalvc etalcl_adadt etalvc_adadt
#> etalfdepot FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> etalmtt FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> etalntr FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> etalcl FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> etalvc FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> etalcl_adadt FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> etalvc_adadt FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#>
#> States ($state or $stateDf):
#> Compartment Number Compartment Name
#> 1 1 depot
#> 2 2 central
#> 3 3 central_adadt
#> ── Multiple Endpoint Model ($multipleEndpoint): ──
#> variable cmt dvid*
#> 1 Cc ~ … cmt='Cc' or cmt=4 dvid='Cc' or dvid=1
#> 2 Cc_adadt ~ … cmt='Cc_adadt' or cmt=5 dvid='Cc_adadt' 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 covariates
#> 1 lmtt etalmtt id
#> 2 lntr etalntr id
#> 3 lfdepot etalfdepot id
#> 4 lcl etalcl id
#> 5 lvc etalvc id
#> 6 lcl_adadt etalcl_adadt id
#> 7 lvc_adadt etalvc_adadt id
#>
#> ── Model (Normalized Syntax): ──
#> function() {
#> compartmentData <- list(depot = list(analyte = "tribendimidine / dADT in transit (dosed prodrug; never measured)",
#> units = "nmol", specimen = "administration site", verified = TRUE),
#> central = list(analyte = "dADT (deacetylated amidantel)",
#> units = "nmol", specimen = "plasma", verified = TRUE),
#> central_adadt = list(analyte = "adADT (acetylated dADT)",
#> units = "nmol", specimen = "plasma", verified = TRUE))
#> covariateData <- list(AGE = list(description = "Age", units = "years",
#> type = "continuous", reference_category = NULL, notes = "Enters dADT clearance only, as the linear form (1 + slope * (AGE - 45)). Meister 2019 Table 2 footnote d defines the effect as a 'linear covariate relationship between age and CL/F dADT centered on the median age of 45 years', and Table 2 footnote a states the printed estimates are for 'a typical patient at 45 years of age'. Unlike the predecessor model (Vanobberghen_2016_tribendimidine), Meister 2019 retained NO age effect on adADT clearance. Because the form is linear rather than exponential it is only valid over the ages actually studied (15 to 79 years); extrapolating dADT clearance beyond roughly 129 years would make it non-positive.",
#> source_name = "AGE"), WT = list(description = "Body weight",
#> units = "kg", type = "continuous", reference_category = NULL,
#> notes = "Allometric scaling on both clearances (exponent fixed at 0.75) and both volumes (exponent fixed at 1), normalized to 52 kg. NOT a row of Meister 2019 Table 2 because the exponents were fixed a priori rather than estimated, but stated twice in the source: Materials and Methods 'Covariate analysis' ('Total body weight was implemented a priori as an allometric function, centered on the median body weight, on clearance and volume parameters simultaneously using a fixed exponent of 0.75 for clearance and 1 for volume'), and Table 2 footnote a, which fixes the reference patient at 52 kg. The pooled phase 2a median weight is 52 kg and the phase 2b median is 54 kg (Table 1); 52 kg is the value footnote a attaches to the printed estimates and is therefore the centering value used here.",
#> source_name = "WEIGHT"), FORM_TRI_TAB200 = list(description = "Tribendimidine tablet-strength formulation indicator (1 = 200-mg enteric-coated tablets; 0 = 50-mg enteric-coated tablets, the reference formulation)",
#> units = "(binary)", type = "binary", reference_category = "0 (whole 50-mg enteric-coated tablets)",
#> notes = "Multiplicative effect of +42.9% on mean absorption transit time relative to whole 50-mg tablets (Meister 2019 Table 2, 'Formulation on MTT (%)' = 42.9). The Results give the direction explicitly: 'a 42.9% slower mean absorption transit time for the 200-mg formulation than for the 50-mg formulation', which makes the 50-mg tablet the reference level. The authors attribute the delay to the 200-mg tablets floating in the stomach (Discussion, citing the in vitro physicochemical characterisation in reference 16). Unlike Vanobberghen 2016, Meister 2019 retained NO formulation effect on either central volume.",
#> source_name = "FORM"), FORM_TRI_SPLIT50 = list(description = "Split-tablet indicator for tribendimidine 50-mg enteric-coated tablets (1 = the administered 50-mg tablet was broken in half, destroying the enteric coating; 0 = whole tablet)",
#> units = "(binary)", type = "binary", reference_category = "0 (whole tablet)",
#> notes = "Multiplicative effect of -79.4% on mean absorption transit time relative to whole 50-mg tablets (Meister 2019 Table 2, 'Split 50-mg tablets on MTT (%)' = -79.4). The Results give the direction explicitly: 'a 79.4% faster mean absorption transit time for broken tablets than for whole 50-mg tablets'. In the source data only the 25-mg dose level of the second phase 2a trial used split tablets, so this indicator is 1 only when FORM_TRI_TAB200 is 0; the two effects were never observed in combination and the multiplicative composition used here is an extrapolation outside that cell. Registered as the sibling indicator that the FORM_TRI_TAB200 register entry anticipated: Vanobberghen 2016's split-tablet interaction model did not converge, so that earlier extraction pooled split tablets into its reference level.",
#> source_name = "SPLIT"), SAMPLE_WHOLEBLOOD = list(description = "Per-observation sampling-matrix indicator (1 = the concentration was measured in venous whole blood; 0 = venous plasma, the reference matrix)",
#> units = "(binary)", type = "binary", reference_category = "0 (venous plasma)",
#> notes = "Selects both the systematic matrix conversion factor applied to the predicted concentration and the matrix-specific residual error. Meister 2019 Table 2 footnote a fixes the reference matrix: the printed estimates are 'with drug concentrations measured in plasma'. Whole-blood samples were collected in the two phase 2a trials only; the phase 2b trial used dried blood spots exclusively (Table 1). Must be 0 on any record for which SAMPLE_DBS is 1.",
#> source_name = "MATRIX"), SAMPLE_DBS = list(description = "Per-observation sampling-matrix indicator (1 = the concentration was measured in a dried blood spot; 0 = venous plasma, the reference matrix)",
#> units = "(binary)", type = "binary", reference_category = "0 (venous plasma)",
#> notes = "Selects both the systematic matrix conversion factor applied to the predicted concentration and the matrix-specific residual error. Dried blood spots were collected from a fingertip capillary draw onto DMPK-C cards in all three trials and were the only matrix in the phase 2b trial (Meister 2019 Materials and Methods, 'PK sampling and analysis'). Meister 2019 reports that 'the difference between drug concentrations measured in whole blood and dried blood spots was not statistically significant', so this level differs from SAMPLE_WHOLEBLOOD only in its point estimates and its residual magnitude. Must be 0 on any record for which SAMPLE_WHOLEBLOOD is 1.",
#> source_name = "MATRIX"))
#> covariatesDataExcluded <- list(SEXF = list(description = "Female sex indicator",
#> units = "(binary)", type = "binary", notes = "Listed among the covariates tested in Meister 2019 Materials and Methods 'Covariate analysis' ('Other covariates tested included age, sex, formulation, and the effect of breaking the administered tablets') but not retained by the forward-selection / backward-elimination procedure. 54% of the phase 2b participants and 51% of the pooled phase 2a participants were female (Table 1)."),
#> CRCL = list(description = "Creatinine clearance", units = "mL/min",
#> type = "continuous", notes = "Explicitly excluded from the analysis rather than screened and rejected: Meister 2019 Materials and Methods 'Covariate analysis' states 'Creatinine clearance data were not available for all patients and could not be considered in this analysis'. The rural setting of the phase 2b trial did not permit biochemical assessment (Materials and Methods, 'Patients, treatment, and study procedures'). The authors nonetheless attribute the retained age effect on dADT clearance to declining renal function with age (Discussion)."))
#> description <- "Pooled population PK model for the two tribendimidine metabolites dADT (deacetylated amidantel, the anthelminthically active species) and adADT (acetylated dADT) in Opisthorchis viverrini-infected Lao adolescents and adults given single oral doses of 25-600 mg (Meister 2019). Pools the two phase 2a ascending-dose trials of Vanobberghen 2016 (68 patients) with a phase 2b trial (125 patients). A Savic transit-compartment absorption model with a non-integer, log-normally distributed number of transit compartments (5.27 typical) feeds a one-compartment dADT disposition model, from which a fixed 65% of elimination is routed to a one-compartment adADT model (the remaining 35% is assumed renal). Allometric body-weight scaling (fixed 0.75 on clearances, 1 on volumes, reference 52 kg), a linear age effect on dADT clearance, and two absorption covariates: the 200-mg-versus-50-mg tablet formulation and the breaking of the enteric coating of a split 50-mg tablet. Systematic whole-blood and dried-blood-spot matrix conversion factors, each metabolite carrying its own matrix-specific residual error. Fitted on natural-log-transformed molar concentrations, so amounts are nmol and concentrations nmol/L."
#> population <- list(species = "human", n_subjects = 193L,
#> n_studies = 3L, age_range = "median 42 years (range 15-65) in the pooled phase 2a trials; median 48 years (range 15-79) in the phase 2b trial",
#> weight_range = "median 52 kg (range 38-67) in the pooled phase 2a trials; median 54 kg (range 32-85) in the phase 2b trial",
#> sex_female_pct = 53.4, renal_function = "Not assessed. Creatinine clearance was unavailable for part of the pooled data set and was not tested as a covariate.",
#> disease_state = "Adolescents and adults aged 15 years and older with confirmed Opisthorchis viverrini infection, diagnosed by duplicate Kato-Katz thick smears on two stool samples. In the phase 2b trial 90% (n = 113) had a light infection intensity at baseline and the mean egg burden was 145.3 eggs per gram. Across all three trials 151/191 (79%) were cured at 21 days; 93% of the phase 2b participants were cured and the average egg reduction rate exceeded 99%.",
#> dose_range = "Single oral doses of 25, 50, 100, 200, 400 and 600 mg tribendimidine. Phase 2a trial 1 (n = 31) gave 200, 400 and 600 mg as 200-mg enteric-coated tablets (n = 13, 9, 9); phase 2a trial 2 (n = 37) gave 25, 50, 100 and 200 mg as 50-mg enteric-coated tablets (n = 9, 9, 9, 10), the 25-mg dose being a split 50-mg tablet; the phase 2b trial (n = 125) gave 400 mg as 200-mg tablets (n = 123), with 2 patients wrongly dosed at 200 mg and modelled at the dose actually received.",
#> regions = "Champasack district, Lao People's Democratic Republic",
#> studies = "Two phase 2a single-ascending-dose trials (previously reported by Vanobberghen 2016 and Duthaler 2016) pooled with the PK substudy of a noninferiority randomized controlled phase 2b trial of tribendimidine 400 mg versus praziquantel 75 mg/kg conducted February-April 2014; ISRCTN Registry no. ISRCTN96948551.",
#> notes = "Sampling differed by trial (Table 1). The phase 2a trials used dense venous sampling at 0, 1, 2, 3, 4, 4.5, 5, 6, 8, 10 and 24 h for whole blood and plasma, plus dried blood spots at 0, 1, 3, 4.5, 6, 10 h or 0, 2, 4, 5, 8, 24 h. The phase 2b trial used a WinPOPT-optimised sparse scheme of five dried blood spots per patient at 0.32, 2.00, 7.75, 8.00 and 30.0 h (supplemental Tables S1 and S2). Both metabolites were quantified by LC-MS/MS over an analytical range of 1 to 2,000 ng/mL. Concentrations were natural-log transformed and the two metabolites fitted sequentially by the PPP&D method; values below the LLOQ (13% for dADT and 21% for adADT across all matrices) were handled by Beal's M3 method. Parameter precision comes from a 1,000-replicate nonparametric bootstrap stratified by study.")
#> reference <- "Meister I, Assawasuwannakit P, Vanobberghen F, Penny MA, Odermatt P, Sayasone S, Huwyler J, Tarning J, Keiser J. Pooled population pharmacokinetic analysis of tribendimidine for the treatment of Opisthorchis viverrini infections. Antimicrob Agents Chemother. 2019;63(4):e01391-18. doi:10.1128/AAC.01391-18"
#> units <- list(time = "h", dosing = "nmol", concentration = "nmol/L")
#> vignette <- "Meister_2019_tribendimidine"
#> ini({
#> lmtt <- 1.15688119679209
#> label("Mean absorption transit time MTT for a whole 50-mg tablet (h)")
#> lntr <- 1.66203036255327
#> label("Number of theoretical transit compartments (unitless)")
#> lfdepot <- fix(0)
#> label("Relative bioavailability F (fraction)")
#> lcl <- 2.76000994003292
#> label("Apparent dADT clearance CL/F at 52 kg and 45 years (L/h)")
#> lvc <- 4.48638664999812
#> label("Apparent dADT central volume Vc/F at 52 kg (L)")
#> lcl_adadt <- 4.18661983833127
#> label("Apparent adADT clearance CL/F at 52 kg (L/h)")
#> lvc_adadt <- 2.75366071235426
#> label("Apparent adADT central volume Vc/F at 52 kg (L)")
#> fm <- fix(0.65)
#> label("Fraction of dADT elimination converted to adADT (unitless)")
#> e_wt_cl <- fix(0.75)
#> label("Allometric exponent on both clearances (unitless)")
#> e_wt_vc <- fix(1)
#> label("Allometric exponent on both central volumes (unitless)")
#> e_age_cl <- -0.0119
#> label("Linear age effect on dADT CL/F, per year older (fraction)")
#> e_tab200_mtt <- 0.429
#> label("200-mg-tablet effect on MTT, relative to a whole 50-mg tablet (fraction longer)")
#> e_split50_mtt <- -0.794
#> label("Split-50-mg-tablet effect on MTT, relative to a whole 50-mg tablet (fraction shorter)")
#> e_wb_cc <- -0.145
#> label("Whole-blood matrix conversion factor on dADT concentration, relative to plasma (fraction)")
#> e_dbs_cc <- -0.137
#> label("DBS matrix conversion factor on dADT concentration, relative to plasma (fraction)")
#> e_wb_cc_adadt <- 0.05
#> label("Whole-blood matrix conversion factor on adADT concentration, relative to plasma (fraction)")
#> e_dbs_cc_adadt <- 0.07
#> label("DBS matrix conversion factor on adADT concentration, relative to plasma (fraction)")
#> expSdPlasma <- 0.460477
#> label("Log-scale residual SD for dADT in plasma (unitless)")
#> expSdWholeBlood <- 0.564023
#> label("Log-scale residual SD for dADT in whole blood (unitless)")
#> expSdDbs <- 0.622523
#> label("Log-scale residual SD for dADT in dried blood spots (unitless)")
#> expSdPlasma_adadt <- 0.379875
#> label("Log-scale residual SD for adADT in plasma (unitless)")
#> expSdWholeBlood_adadt <- 0.445891
#> label("Log-scale residual SD for adADT in whole blood (unitless)")
#> expSdDbs_adadt <- 0.444166
#> label("Log-scale residual SD for adADT in dried blood spots (unitless)")
#> etalfdepot ~ 0.132235
#> label("Table 2, F '% CV for BSV' = 37.6 (95% CI 32.1-45.8); log(1 + 0.376^2) = 0.132235")
#> etalmtt ~ 0.256718
#> label("Table 2, MTT '% CV for BSV' = 54.1 (95% CI 49.9-66.2); log(1 + 0.541^2) = 0.256718")
#> etalntr ~ 2.302585
#> label("Table 2, transit-compartment '% CV for BSV' = 300 (95% CI 204-397); log(1 + 3.00^2) = 2.302585")
#> etalcl ~ 0.038075
#> label("Table 2, dADT CL/F '% CV for BSV' = 19.7 (95% CI 16.6-22.5); log(1 + 0.197^2) = 0.038075")
#> etalvc ~ 0.064927
#> label("Table 2, dADT V/F '% CV for BSV' = 25.9 (95% CI 20.1-28.5); log(1 + 0.259^2) = 0.064927")
#> etalcl_adadt ~ 0.852541
#> label("Table 2, adADT CL/F '% CV for BSV' = 116 (95% CI 105-172); log(1 + 1.16^2) = 0.852541")
#> etalvc_adadt ~ 0.087836
#> label("Table 2, adADT V/F '% CV for BSV' = 30.3 (95% CI 25.9-54.3); log(1 + 0.303^2) = 0.087836")
#> })
#> model({
#> cl_age <- 1 + e_age_cl * (AGE - 45)
#> mtt_form <- (1 + e_tab200_mtt * FORM_TRI_TAB200) * (1 +
#> e_split50_mtt * FORM_TRI_SPLIT50)
#> matrix_cc <- 1 + e_wb_cc * SAMPLE_WHOLEBLOOD + e_dbs_cc *
#> SAMPLE_DBS
#> matrix_cc_adadt <- 1 + e_wb_cc_adadt * SAMPLE_WHOLEBLOOD +
#> e_dbs_cc_adadt * SAMPLE_DBS
#> mtt <- exp(lmtt + etalmtt) * mtt_form
#> ntr <- exp(lntr + etalntr)
#> fdepot <- exp(lfdepot + etalfdepot)
#> cl <- exp(lcl + etalcl) * (WT/52)^e_wt_cl * cl_age
#> vc <- exp(lvc + etalvc) * (WT/52)^e_wt_vc
#> cl_adadt <- exp(lcl_adadt + etalcl_adadt) * (WT/52)^e_wt_cl
#> vc_adadt <- exp(lvc_adadt + etalvc_adadt) * (WT/52)^e_wt_vc
#> ka <- (ntr + 1)/mtt
#> kel <- cl/vc
#> kel_adadt <- cl_adadt/vc_adadt
#> d/dt(depot) <- transit(ntr, mtt, fdepot) - ka * depot
#> d/dt(central) <- ka * depot - kel * central
#> d/dt(central_adadt) <- fm * kel * central - kel_adadt *
#> central_adadt
#> f(depot) <- 0
#> Cc <- central/vc * matrix_cc
#> Cc_adadt <- central_adadt/vc_adadt * matrix_cc_adadt
#> expSdCc <- expSdPlasma * (1 - SAMPLE_WHOLEBLOOD - SAMPLE_DBS) +
#> expSdWholeBlood * SAMPLE_WHOLEBLOOD + expSdDbs *
#> SAMPLE_DBS
#> expSdCcAdadt <- expSdPlasma_adadt * (1 - SAMPLE_WHOLEBLOOD -
#> SAMPLE_DBS) + expSdWholeBlood_adadt * SAMPLE_WHOLEBLOOD +
#> expSdDbs_adadt * SAMPLE_DBS
#> Cc ~ lnorm(expSdCc)
#> Cc_adadt ~ lnorm(expSdCcAdadt)
#> })
#> }Population
Adolescents and adults aged 15 years and older with confirmed Opisthorchis viverrini infection, diagnosed by duplicate Kato-Katz thick smears on two stool samples. In the phase 2b trial 90% (n = 113) had a light infection intensity at baseline and the mean egg burden was 145.3 eggs per gram. Across all three trials 151/191 (79%) were cured at 21 days; 93% of the phase 2b participants were cured and the average egg reduction rate exceeded 99%.
The pooled analysis set is 193 patients drawn from 3 trials in Champasack, Lao PDR (Meister 2019 Table 1). The two phase 2a trials contributed dense venous plasma and whole-blood sampling plus dried blood spots; the phase 2b trial contributed five dried blood spots per patient at WinPOPT-optimised times of 0.32, 2.00, 7.75, 8.00 and 30.0 h.
pop <- rxode2::rxode(readModelDb("Meister_2019_tribendimidine"))$population
#> ℹ parameter labels from comments will be replaced by 'label()'
tibble::tibble(
Field = c("Age", "Weight", "Female", "Doses", "Region"),
Value = c(pop$age_range, pop$weight_range, paste0(pop$sex_female_pct, "%"),
pop$dose_range, pop$regions)
) |>
knitr::kable()| Field | Value |
|---|---|
| Age | median 42 years (range 15-65) in the pooled phase 2a trials; median 48 years (range 15-79) in the phase 2b trial |
| Weight | median 52 kg (range 38-67) in the pooled phase 2a trials; median 54 kg (range 32-85) in the phase 2b trial |
| Female | 53.4% |
| Doses | Single oral doses of 25, 50, 100, 200, 400 and 600 mg tribendimidine. Phase 2a trial 1 (n = 31) gave 200, 400 and 600 mg as 200-mg enteric-coated tablets (n = 13, 9, 9); phase 2a trial 2 (n = 37) gave 25, 50, 100 and 200 mg as 50-mg enteric-coated tablets (n = 9, 9, 9, 10), the 25-mg dose being a split 50-mg tablet; the phase 2b trial (n = 125) gave 400 mg as 200-mg tablets (n = 123), with 2 patients wrongly dosed at 200 mg and modelled at the dose actually received. |
| Region | Champasack district, Lao People’s Democratic Republic |
Source trace
Every ini() value and every non-obvious
model() equation, with the place in Meister 2019 it came
from.
sourceTrace <- tibble::tribble(
~Quantity, ~Value, ~Source,
"MTT (whole 50-mg tablet)", "3.18 h", "Table 2, 'MTT (h)'",
"Number of transit compartments", "5.27", "Table 2, 'No. of transit compartments'",
"Relative bioavailability F", "1 (fixed)", "Table 2, 'F (%)' = 100 (fixed)",
"dADT CL/F", "15.8 L/h", "Table 2, dADT 'CL/FdADT (liters/h)'",
"dADT V/F", "88.8 L", "Table 2, dADT 'V/FdADT (liters)'",
"adADT CL/F", "65.8 L/h", "Table 2, adADT 'CL/FadADT (liters/h)'",
"adADT V/F", "15.7 L", "Table 2, adADT 'V/FadADT (liters)'",
"Metabolised fraction fm", "0.65 (fixed)", "Methods, 'Structural and stochastic model development'",
"Allometric exponents", "0.75 CL / 1 V (fixed)", "Methods, 'Covariate analysis'",
"Reference weight / age", "52 kg / 45 years", "Table 2 footnote a; footnote d",
"Age on dADT CL/F", "-1.19 %/year", "Table 2, 'Age on CL/FdADT (%)'",
"200-mg tablet on MTT", "+42.9 %", "Table 2, 'Formulation on MTT (%)'",
"Split 50-mg tablet on MTT", "-79.4 %", "Table 2, 'Split 50-mg tablets on MTT (%)'",
"Matrix factors, dADT", "-14.5 % blood, -13.7 % DBS", "Table 2, dADT matrix conversion rows",
"Matrix factors, adADT", "+5.00 % blood, +7.00 % DBS", "Table 2, adADT matrix conversion rows",
"BSV (7 diagonal terms)", "37.6 to 300 % CV", "Table 2, '% CV for BSV'; footnote b",
"RUV (6 terms)", "39.4 to 68.8 % CV", "Table 2, RUV rows; footnote b",
"Transit absorption form", "Savic gamma-PDF, ka = ktr", "Results, 'Pooled population PK modeling'",
"Therapeutic criteria", "Cmax 384 ng/mL; AUC 4520 ng*h/mL", "Results, 'PK-PD analysis and exposure simulations'"
)
knitr::kable(sourceTrace)| Quantity | Value | Source |
|---|---|---|
| MTT (whole 50-mg tablet) | 3.18 h | Table 2, ‘MTT (h)’ |
| Number of transit compartments | 5.27 | Table 2, ‘No. of transit compartments’ |
| Relative bioavailability F | 1 (fixed) | Table 2, ‘F (%)’ = 100 (fixed) |
| dADT CL/F | 15.8 L/h | Table 2, dADT ‘CL/FdADT (liters/h)’ |
| dADT V/F | 88.8 L | Table 2, dADT ‘V/FdADT (liters)’ |
| adADT CL/F | 65.8 L/h | Table 2, adADT ‘CL/FadADT (liters/h)’ |
| adADT V/F | 15.7 L | Table 2, adADT ‘V/FadADT (liters)’ |
| Metabolised fraction fm | 0.65 (fixed) | Methods, ‘Structural and stochastic model development’ |
| Allometric exponents | 0.75 CL / 1 V (fixed) | Methods, ‘Covariate analysis’ |
| Reference weight / age | 52 kg / 45 years | Table 2 footnote a; footnote d |
| Age on dADT CL/F | -1.19 %/year | Table 2, ‘Age on CL/FdADT (%)’ |
| 200-mg tablet on MTT | +42.9 % | Table 2, ‘Formulation on MTT (%)’ |
| Split 50-mg tablet on MTT | -79.4 % | Table 2, ‘Split 50-mg tablets on MTT (%)’ |
| Matrix factors, dADT | -14.5 % blood, -13.7 % DBS | Table 2, dADT matrix conversion rows |
| Matrix factors, adADT | +5.00 % blood, +7.00 % DBS | Table 2, adADT matrix conversion rows |
| BSV (7 diagonal terms) | 37.6 to 300 % CV | Table 2, ‘% CV for BSV’; footnote b |
| RUV (6 terms) | 39.4 to 68.8 % CV | Table 2, RUV rows; footnote b |
| Transit absorption form | Savic gamma-PDF, ka = ktr | Results, ‘Pooled population PK modeling’ |
| Therapeutic criteria | Cmax 384 ng/mL; AUC 4520 ng*h/mL | Results, ‘PK-PD analysis and exposure simulations’ |
Both variance columns are back-transformed with the Table 2 footnote
b definition, %CV = sqrt(exp(variance) - 1), which inverts
to variance = log(1 + CV^2). The residual error is additive
on the natural-log-transformed concentrations the model was fitted to,
which is lnorm() in nlmixr2, so its log-scale SD is
sqrt(log(1 + CV^2)).
Units: the model works in nmol
Meister 2019 fitted natural-log-transformed molar concentrations, exactly as its predecessor did, so amounts are nmol and concentrations nmol/L. Both metabolites are on a molar scale and dADT converts to adADT one-for-one, so no molecular weight appears anywhere inside the model. Two conversions are needed at the edges: milligrams of tribendimidine into nmol for the dose, and nmol/L back into ng/mL to compare against the paper’s tables.
# Molecular weights (g/mol). The two metabolite weights are read from the
# $ERROR LLOQ conversion of the final NONMEM control stream of the predecessor
# publication (Vanobberghen 2016 supplemental File S1); the parent weight is
# not printed in either paper. See Assumptions and deviations.
MW_TRI <- 450.59 # tribendimidine, C28H30N6
MW_DADT <- 173.214 # dADT
MW_ADADT <- 215.251 # adADT
# One mole of tribendimidine enters the absorption chain as one mole of
# dADT-equivalent, which is the stoichiometry the predecessor model encodes.
mgToNmol <- function(mg) mg * 1e6 / MW_TRI
nmolToNg <- function(conc, mw) pmax(conc, 0) * mw / 1000Structural checks on the typical individual
These run with every random effect set to zero, so each is a deterministic algebraic identity that either holds to solver tolerance or does not. Two integrating states are appended to accumulate exposure exactly, rather than approximating it with a trapezoid on a finite grid.
modAuc <- mod |>
rxode2::model(d / dt(auc_dadt) <- central / vc, append = TRUE) |>
rxode2::model(d / dt(auc_adadt) <- central_adadt / vc_adadt, append = TRUE)
typical <- rxode2::zeroRe(modAuc)
#> Warning: No sigma parameters in the model
# zeroRe must actually have zeroed the omega matrix; a non-zero entry here
# would silently make every "typical" number below a single random draw.
stopifnot(all(typical$omega == 0))
typicalCovs <- function(age = 45, wt = 52, form200 = 1, split50 = 0,
wholeblood = 0, dbs = 0) {
data.frame(
id = 1L, AGE = age, WT = wt,
FORM_TRI_TAB200 = form200, FORM_TRI_SPLIT50 = split50,
SAMPLE_WHOLEBLOOD = wholeblood, SAMPLE_DBS = dbs
)
}
# Route A of the multi-output event-table convention: cmt names the ODE state
# and dvid names the endpoint. Dose rows carry dvid = NA so the column stays
# integer. Never name an algebraic observable (Cc) as a compartment.
makeEvents <- function(mg, times, n = 1L) {
rbind(
data.frame(
id = seq_len(n), time = 0, amt = mgToNmol(mg), cmt = "depot",
evid = 1L, dvid = NA_integer_
),
data.frame(
id = rep(seq_len(n), each = length(times)), time = rep(times, n),
amt = NA_real_, cmt = "central", evid = 0L, dvid = 1L
)
) |>
dplyr::arrange(id, time, dplyr::desc(evid))
}
# 480 h is more than 100 dADT half-lives, so the accumulators have converged.
longGrid <- sort(unique(c(seq(0, 24, by = 0.01), seq(24.5, 480, by = 0.5))))
solveTypical <- function(mg = 400, covs = typicalCovs(), model = typical) {
as.data.frame(rxode2::rxSolve(
model, makeEvents(mg, longGrid), covs,
returnType = "data.frame", atol = 1e-12, rtol = 1e-12, maxsteps = 1e6
))
}
typ400 <- solveTypical(400)
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'Dose recovery
Relative bioavailability is fixed to unity, so the entire
administered molar amount must be recovered through dADT clearance. This
is the single sharpest test that the transit chain delivers the whole
dose, that suppressing the ordinary bolus with
f(depot) <- 0 did not silently zero the input, and that
the clearance was transcribed correctly.
Metabolite mass balance
A fixed 65% of everything cleared as dADT is routed to adADT, so the
amount adADT eliminates must be exactly 65% of the amount dADT
eliminates. In terms of exposure that is
CL_adADT * AUC_adADT = fm * CL_dADT * AUC_dADT.
massBalance <- CL_ADADT * aucInfAdadt / (FM * CL_DADT * aucInfDadt)
massBalance
#> [1] 1
stopifnot(abs(massBalance - 1) < 1e-6)A gate that cannot fail proves nothing, so the same identity is
re-measured with fm deliberately mis-set to 0.50 while the
check still divides by 0.65. The ratio must move to 0.50/0.65.
mutated <- rxode2::ini(typical, fm = 0.50)
#> ℹ change initial estimate of `fm` to `0.5`
typMut <- solveTypical(400, model = mutated)
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
mutatedBalance <-
CL_ADADT * typMut$auc_adadt[nrow(typMut)] /
(FM * CL_DADT * typMut$auc_dadt[nrow(typMut)])
mutatedBalance
#> [1] 0.7692308
stopifnot(abs(mutatedBalance - 0.50 / 0.65) < 1e-6)Disposition half-life
The dADT half-life is log(2) * Vc / CL and nothing else.
Meister 2019 Table 3 reports the dADT elimination half-life as a median
per dose group, ranging from 3.34 to 4.56 h.
Covariate arithmetic
Each covariate enters exactly one place, so each effect can be read
back out of the solved model variables. mtt and
cl are returned as columns by rxSolve().
mttOf <- function(form200, split50) {
solveTypical(400, typicalCovs(form200 = form200, split50 = split50))$mtt[1]
}
clOf <- function(age) solveTypical(400, typicalCovs(age = age))$cl[1]
vcOf <- function(wt) solveTypical(400, typicalCovs(wt = wt))$vc[1]
covariateChecks <- tibble::tibble(
Check = c(
"MTT, whole 50-mg tablet (reference)",
"MTT, 200-mg tablet: +42.9%",
"MTT, split 50-mg tablet: -79.4%",
"dADT CL/F at 45 years (reference)",
"dADT CL/F, +10 years: -11.9%",
"dADT Vc/F at 52 kg (reference)",
"dADT Vc/F at 104 kg: allometric exponent 1"
),
Value = c(
mttOf(0, 0), mttOf(1, 0), mttOf(0, 1),
clOf(45), clOf(55), vcOf(52), vcOf(104)
),
Expected = c(
3.18, 3.18 * 1.429, 3.18 * 0.206,
15.8, 15.8 * (1 - 0.0119 * 10), 88.8, 88.8 * 2
)
)
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
knitr::kable(covariateChecks, digits = 4)| Check | Value | Expected |
|---|---|---|
| MTT, whole 50-mg tablet (reference) | 3.1800 | 3.1800 |
| MTT, 200-mg tablet: +42.9% | 4.5442 | 4.5442 |
| MTT, split 50-mg tablet: -79.4% | 0.6551 | 0.6551 |
| dADT CL/F at 45 years (reference) | 15.8000 | 15.8000 |
| dADT CL/F, +10 years: -11.9% | 13.9198 | 13.9198 |
| dADT Vc/F at 52 kg (reference) | 88.8000 | 88.8000 |
| dADT Vc/F at 104 kg: allometric exponent 1 | 177.6000 | 177.6000 |
Sampling-matrix conversion factors
Venous plasma is the reference matrix and must scale the prediction by exactly one; whole blood and dried blood spots apply the Table 2 factors.
matrixRatio <- function(wholeblood, dbs, column) {
s <- solveTypical(400, typicalCovs(wholeblood = wholeblood, dbs = dbs))
ref <- solveTypical(400, typicalCovs())
max(s[[column]][s$time > 1]) / max(ref[[column]][ref$time > 1])
}
matrixChecks <- tibble::tibble(
Matrix = c("Whole blood", "Dried blood spot", "Whole blood", "Dried blood spot"),
Metabolite = c("dADT", "dADT", "adADT", "adADT"),
Ratio = c(
matrixRatio(1, 0, "Cc"), matrixRatio(0, 1, "Cc"),
matrixRatio(1, 0, "Cc_adadt"), matrixRatio(0, 1, "Cc_adadt")
),
Expected = c(1 - 0.145, 1 - 0.137, 1 + 0.05, 1 + 0.07)
)
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
knitr::kable(matrixChecks, digits = 6)| Matrix | Metabolite | Ratio | Expected |
|---|---|---|---|
| Whole blood | dADT | 0.855 | 0.855 |
| Dried blood spot | dADT | 0.863 | 0.863 |
| Whole blood | adADT | 1.050 | 1.050 |
| Dried blood spot | adADT | 1.070 | 1.070 |
Exposure at every published dose level
For a one-compartment model, AUC to infinity is
F * dose / CL and nothing else, so it can be evaluated once
per dose arm on the typical individual with no cohort draw involved at
all. This is the sharpest reproducible test that the clearance, the
allometric reference weight, the age centring, the molar dose conversion
and the metabolite molecular weight are all right together, and
unlike the cohort comparison further down it gives the same answer on
every machine and every rxode2 build.
armCovs <- tibble::tribble(
~mg, ~form200, ~split50, ~age, ~wt, ~published,
25, 0, 1, 42, 52, 509,
50, 0, 0, 42, 52, 1152,
100, 0, 0, 42, 52, 2310,
200, 0, 0, 42, 52, 5382,
400, 1, 0, 48, 54, 9889,
600, 1, 0, 42, 52, 12230
)
typicalAuc <- function(i) {
s <- solveTypical(
armCovs$mg[i],
typicalCovs(
age = armCovs$age[i], wt = armCovs$wt[i],
form200 = armCovs$form200[i], split50 = armCovs$split50[i]
)
)
nmolToNg(s$auc_dadt[nrow(s)], MW_DADT)
}
perArm <-
armCovs |>
dplyr::mutate(
typical = vapply(seq_len(dplyr::n()), typicalAuc, numeric(1)),
pct_diff = 100 * (typical - published) / published,
typical_per_mg = typical / mg,
published_per_mg = published / mg
)
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
#> ℹ omega/sigma items treated as zero: 'etalfdepot', 'etalmtt', 'etalntr', 'etalcl', 'etalvc', 'etalcl_adadt', 'etalvc_adadt'
knitr::kable(
perArm |>
dplyr::select(mg, typical, published, pct_diff, typical_per_mg, published_per_mg) |>
dplyr::rename(
`Dose (mg)` = mg,
`Typical AUC0-inf (h*ng/mL)` = typical,
`Published AUC0-72 (h*ng/mL)` = published,
`% difference` = pct_diff,
`Typical AUC per mg` = typical_per_mg,
`Published AUC per mg` = published_per_mg
),
digits = 2
)| Dose (mg) | Typical AUC0-inf (h*ng/mL) | Published AUC0-72 (h*ng/mL) | % difference | Typical AUC per mg | Published AUC per mg |
|---|---|---|---|---|---|
| 25 | 587.29 | 509 | 15.38 | 23.49 | 20.36 |
| 50 | 1174.57 | 1152 | 1.96 | 23.49 | 23.04 |
| 100 | 2349.15 | 2310 | 1.69 | 23.49 | 23.10 |
| 200 | 4698.30 | 5382 | -12.70 | 23.49 | 26.91 |
| 400 | 9810.69 | 9889 | -0.79 | 24.53 | 24.72 |
| 600 | 14094.89 | 12230 | 15.25 | 23.49 | 20.38 |
The model is strictly linear, so its exposure per milligram is constant apart from the arm-specific age and weight. The published column is not: it ranges from 20.4 to 26.9 h*ng/mL per mg, a 1.32-fold spread across dose levels that a linear model cannot generate and that reflects the small per-arm sample sizes behind Table 3 (nine patients at 25, 50, 100 and 600 mg). That spread is the floor on how well any faithful implementation can match this table, so the agreement is asserted on the centre and bounded by the published column’s own scatter.
stopifnot(
# Centre: a mis-transcribed clearance, dose conversion or molecular weight
# would move every arm together by tens of percent.
abs(stats::median(perArm$pct_diff)) < 8,
# Envelope: no arm may miss by more than the published column's own
# dose-to-dose spread.
max(abs(perArm$pct_diff)) < 25,
# Linearity: exposure per mg varies only through the arm-specific covariates.
max(perArm$typical_per_mg) / min(perArm$typical_per_mg) < 1.10
)Virtual cohort
Six dose arms reproducing the provenance of Meister 2019 Table 3. The 25 mg level of phase 2a trial 2 was a split 50-mg tablet; 50, 100 and 200 mg used whole 50-mg tablets; 400 and 600 mg used 200-mg tablets. Age and weight are drawn around the medians Table 1 reports for the trial each arm came from.
N_PER_ARM <- 60L # well under the 200-per-arm cap
arms <- tibble::tibble(
arm = paste0(c(25, 50, 100, 200, 400, 600), " mg"),
mg = c(25, 50, 100, 200, 400, 600),
form200 = c(0, 0, 0, 0, 1, 1),
split50 = c(1, 0, 0, 0, 0, 0),
# The 400 mg arm is dominated by the phase 2b trial (123 of 132 patients),
# whose medians are 48 years and 54 kg; the rest are phase 2a (42 y, 52 kg).
medAge = c(42, 42, 42, 42, 48, 42),
medWt = c(52, 52, 52, 52, 54, 52)
)
knitr::kable(arms)| arm | mg | form200 | split50 | medAge | medWt |
|---|---|---|---|---|---|
| 25 mg | 25 | 0 | 1 | 42 | 52 |
| 50 mg | 50 | 0 | 0 | 42 | 52 |
| 100 mg | 100 | 0 | 0 | 42 | 52 |
| 200 mg | 200 | 0 | 0 | 42 | 52 |
| 400 mg | 400 | 1 | 0 | 48 | 54 |
| 600 mg | 600 | 1 | 0 | 42 | 52 |
obsTimes <- sort(unique(c(
seq(0, 12, by = 0.25),
seq(13, 24, by = 1),
seq(28, 72, by = 4)
)))
rtruncnorm <- function(n, mean, sd, lower, upper) {
pmin(pmax(stats::rnorm(n, mean, sd), lower), upper)
}
simulateArm <- function(i) {
covs <- data.frame(
id = seq_len(N_PER_ARM),
AGE = rtruncnorm(N_PER_ARM, arms$medAge[i], 12, 15, 79),
WT = rtruncnorm(N_PER_ARM, arms$medWt[i], 8, 32, 85),
FORM_TRI_TAB200 = arms$form200[i],
FORM_TRI_SPLIT50 = arms$split50[i],
SAMPLE_WHOLEBLOOD = 0,
SAMPLE_DBS = 0
)
as.data.frame(rxode2::rxSolve(
mod, makeEvents(arms$mg[i], obsTimes, n = N_PER_ARM), covs,
returnType = "data.frame"
)) |>
dplyr::mutate(
arm = arms$arm[i],
mg = arms$mg[i],
dadt = nmolToNg(Cc, MW_DADT),
adadt = nmolToNg(Cc_adadt, MW_ADADT)
)
}
rxode2::rxSetSeed(20190327)
set.seed(20190327)
sim <-
lapply(seq_len(nrow(arms)), simulateArm) |>
dplyr::bind_rows() |>
dplyr::mutate(arm = factor(arm, levels = arms$arm))
nrow(sim)
#> [1] 26280Cc and Cc_adadt are algebraic observables,
so they are individual predictions and carry no residual error. That is
what we want here, because the paper’s Table 3 secondary parameters were
likewise derived from individual model predictions rather than from
observations.
Replicating the published concentration-time profiles
Meister 2019 Figure 2 shows prediction-corrected visual predictive checks for both metabolites. We plot the 5th, 50th and 95th percentiles of the simulated individual profiles, the same summary that figure overlays on its observed data.
percentiles <-
sim |>
tidyr::pivot_longer(c(dadt, adadt), names_to = "metabolite", values_to = "conc") |>
dplyr::mutate(
metabolite = factor(metabolite, c("dadt", "adadt"), c("dADT", "adADT"))
) |>
dplyr::group_by(arm, metabolite, time) |>
dplyr::summarise(
p05 = stats::quantile(conc, 0.05),
p50 = stats::median(conc),
p95 = stats::quantile(conc, 0.95),
.groups = "drop"
)
ggplot2::ggplot(percentiles, ggplot2::aes(time)) +
ggplot2::geom_ribbon(ggplot2::aes(ymin = p05, ymax = p95, fill = arm), alpha = 0.2) +
ggplot2::geom_line(ggplot2::aes(y = p50, colour = arm)) +
ggplot2::facet_wrap(~metabolite, ncol = 1, scales = "free_y") +
ggplot2::coord_cartesian(xlim = c(0, 48)) +
ggplot2::labs(
title = "Simulated profiles by dose arm (replicates Figure 2 of Meister 2019)",
x = "Time (h)", y = "Concentration (ng/mL)",
colour = "Dose arm", fill = "Dose arm"
) +
ggplot2::theme_bw()
The 25 mg arm peaks much earlier than every other arm, which is the split 50-mg tablet effect: destroying the enteric coating shortens the mean transit time by 79.4% and the drug is released immediately.
Non-compartmental analysis with PKNCA
One PKNCA pass per metabolite. The concentration frame is filtered
only on !is.na() so the time-zero record survives and PKNCA
never has to extrapolate the start of the interval.
runNca <- function(data, concColumn) {
conc <-
data |>
dplyr::mutate(conc = .data[[concColumn]]) |>
dplyr::filter(!is.na(conc)) |>
PKNCA::PKNCAconc(conc ~ time | arm + id)
dose <-
data |>
dplyr::distinct(arm, id, mg) |>
dplyr::mutate(time = 0, dose = mg) |>
PKNCA::PKNCAdose(dose ~ time | arm + id)
intervals <- data.frame(
start = 0, end = 72,
cmax = TRUE, tmax = TRUE, auclast = TRUE, half.life = TRUE
)
PKNCA::pk.nca(PKNCA::PKNCAdata(conc, dose, intervals = intervals)) |>
as.data.frame()
}
ncaDadt <- runNca(sim, "dadt")
ncaAdadt <- runNca(sim, "adadt")
summariseNca <- function(nca) {
nca |>
dplyr::filter(PPTESTCD %in% c("cmax", "tmax", "auclast", "half.life")) |>
dplyr::group_by(arm, PPTESTCD) |>
dplyr::summarise(median = stats::median(PPORRES, na.rm = TRUE), .groups = "drop")
}
simDadt <- summariseNca(ncaDadt)
simAdadt <- summariseNca(ncaAdadt)Formation-rate-limited adADT elimination
CL_adADT / V_adADT is 4.19 per hour, a nominal half-life
of only 10 minutes, yet Table 3 reports the same half-life for both
metabolites at every dose. That is the signature of
formation-rate-limited elimination: adADT cannot disappear faster than
dADT supplies it, so its terminal slope is the dADT slope. This is a
structural consequence of the model and can be asserted subject by
subject.
halfLives <-
dplyr::inner_join(
ncaDadt |>
dplyr::filter(PPTESTCD == "half.life") |>
dplyr::select(arm, id, hl_dadt = PPORRES),
ncaAdadt |>
dplyr::filter(PPTESTCD == "half.life") |>
dplyr::select(arm, id, hl_adadt = PPORRES),
by = c("arm", "id")
) |>
dplyr::filter(!is.na(hl_dadt), !is.na(hl_adadt)) |>
dplyr::mutate(pct_diff = 100 * (hl_adadt - hl_dadt) / hl_dadt)
# Assert on the centre and on a robust quantile, never on the extreme of a
# random cohort.
stopifnot(
abs(stats::median(halfLives$pct_diff)) < 2,
stats::quantile(abs(halfLives$pct_diff), 0.9) < 10
)Comparison against the published secondary PK parameters
Meister 2019 Table 3 reports median secondary PK parameters derived from the final model for each metabolite and dose. Its AUC is labelled 0 to 72 h in the Table 3 footnote, which is the interval used above.
published <- tibble::tribble(
~arm, ~metabolite, ~cmax, ~tmax, ~auclast, ~half.life,
"25 mg", "dADT", 61.4, 1.73, 509, 4.03,
"50 mg", "dADT", 173, 4.86, 1152, 3.34,
"100 mg", "dADT", 315, 3.73, 2310, 3.68,
"200 mg", "dADT", 509, 6.59, 5382, 4.21,
"400 mg", "dADT", 863, 8.56, 9889, 4.22,
"600 mg", "dADT", 1271, 10.0, 12230, 4.56,
"25 mg", "adADT", 23.2, 2.64, 160, 4.03,
"50 mg", "adADT", 67.0, 5.34, 372, 3.34,
"100 mg", "adADT", 111, 4.98, 771, 3.68,
"200 mg", "adADT", 60.8, 6.82, 614, 4.21,
"400 mg", "adADT", 107, 8.79, 1479, 4.41,
"600 mg", "adADT", 253, 10.2, 3748, 4.97
)
toWide <- function(summarised) {
summarised |>
dplyr::select(arm, PPTESTCD, median) |>
tidyr::pivot_wider(names_from = PPTESTCD, values_from = median) |>
dplyr::mutate(arm = as.character(arm))
}
tblDadt <- nlmixr2lib::ncaComparisonTable(
simulated = toWide(simDadt),
reference = published |>
dplyr::filter(metabolite == "dADT") |>
dplyr::select(-metabolite),
by = "arm",
units = c(cmax = "ng/mL", auclast = "h*ng/mL", tmax = "h", half.life = "h")
)
knitr::kable(tblDadt, caption = "dADT: simulated vs Meister 2019 Table 3")| NCA parameter | arm | Reference | Simulated | % diff |
|---|---|---|---|---|
| Cmax (ng/mL) | 25 mg | 61.4 | 94.1 | +53.3%* |
| Cmax (ng/mL) | 50 mg | 173 | 139 | -19.9% |
| Cmax (ng/mL) | 100 mg | 315 | 268 | -14.9% |
| Cmax (ng/mL) | 200 mg | 509 | 535 | +5.0% |
| Cmax (ng/mL) | 400 mg | 863 | 916 | +6.2% |
| Cmax (ng/mL) | 600 mg | 1270 | 1470 | +15.3% |
| Tmax (h) | 25 mg | 1.73 | 1.25 | -27.7%* |
| Tmax (h) | 50 mg | 4.86 | 5 | +2.9% |
| Tmax (h) | 100 mg | 3.73 | 5.62 | +50.8%* |
| Tmax (h) | 200 mg | 6.59 | 5.38 | -18.4% |
| Tmax (h) | 400 mg | 8.56 | 6.5 | -24.1%* |
| Tmax (h) | 600 mg | 10 | 6.38 | -36.2%* |
| AUClast (h*ng/mL) | 25 mg | 509 | 552 | +8.5% |
| AUClast (h*ng/mL) | 50 mg | 1150 | 1190 | +3.0% |
| AUClast (h*ng/mL) | 100 mg | 2310 | 2570 | +11.0% |
| AUClast (h*ng/mL) | 200 mg | 5380 | 4300 | -20.2%* |
| AUClast (h*ng/mL) | 400 mg | 9890 | 9670 | -2.2% |
| AUClast (h*ng/mL) | 600 mg | 12200 | 14100 | +15.5% |
| t½ (h) | 25 mg | 4.03 | 3.48 | -13.7% |
| t½ (h) | 50 mg | 3.34 | 3.76 | +12.5% |
| t½ (h) | 100 mg | 3.68 | 3.8 | +3.3% |
| t½ (h) | 200 mg | 4.21 | 3.53 | -16.0% |
| t½ (h) | 400 mg | 4.22 | 3.97 | -5.9% |
| t½ (h) | 600 mg | 4.56 | 3.75 | -17.7% |
attr(tblDadt, "footnote")
#> [1] "* differs from reference by more than ±20%."
tblAdadt <- nlmixr2lib::ncaComparisonTable(
simulated = toWide(simAdadt),
reference = published |>
dplyr::filter(metabolite == "adADT") |>
dplyr::select(-metabolite),
by = "arm",
units = c(cmax = "ng/mL", auclast = "h*ng/mL", tmax = "h", half.life = "h")
)
knitr::kable(tblAdadt, caption = "adADT: simulated vs Meister 2019 Table 3")| NCA parameter | arm | Reference | Simulated | % diff |
|---|---|---|---|---|
| Cmax (ng/mL) | 25 mg | 23.2 | 21 | -9.4% |
| Cmax (ng/mL) | 50 mg | 67 | 21.1 | -68.6%* |
| Cmax (ng/mL) | 100 mg | 111 | 54 | -51.4%* |
| Cmax (ng/mL) | 200 mg | 60.8 | 113 | +86.0%* |
| Cmax (ng/mL) | 400 mg | 107 | 154 | +43.9%* |
| Cmax (ng/mL) | 600 mg | 253 | 350 | +38.5%* |
| Tmax (h) | 25 mg | 2.64 | 1.75 | -33.7%* |
| Tmax (h) | 50 mg | 5.34 | 5.25 | -1.7% |
| Tmax (h) | 100 mg | 4.98 | 6.12 | +23.0%* |
| Tmax (h) | 200 mg | 6.82 | 5.62 | -17.5% |
| Tmax (h) | 400 mg | 8.79 | 6.5 | -26.1%* |
| Tmax (h) | 600 mg | 10.2 | 6.88 | -32.6%* |
| AUClast (h*ng/mL) | 25 mg | 160 | 139 | -13.0% |
| AUClast (h*ng/mL) | 50 mg | 372 | 191 | -48.7%* |
| AUClast (h*ng/mL) | 100 mg | 771 | 481 | -37.6%* |
| AUClast (h*ng/mL) | 200 mg | 614 | 1050 | +70.7%* |
| AUClast (h*ng/mL) | 400 mg | 1480 | 1620 | +9.5% |
| AUClast (h*ng/mL) | 600 mg | 3750 | 2900 | -22.6%* |
| t½ (h) | 25 mg | 4.03 | 3.48 | -13.7% |
| t½ (h) | 50 mg | 3.34 | 3.76 | +12.5% |
| t½ (h) | 100 mg | 3.68 | 3.81 | +3.4% |
| t½ (h) | 200 mg | 4.21 | 3.54 | -16.0% |
| t½ (h) | 400 mg | 4.41 | 3.97 | -9.9% |
| t½ (h) | 600 mg | 4.97 | 3.75 | -24.5%* |
attr(tblAdadt, "footnote")
#> [1] "* differs from reference by more than ±20%."dADT exposure agreement
AUC is the quantity the structural parameters fix most directly – for a one-compartment model it is dose divided by clearance and nothing else – so it is the sharpest test that the clearance, the allometric reference weight, the age centring and the molar dose conversion are all right together. A mis-transcribed clearance or a wrong molecular weight moves the whole distribution by tens of percent.
aucAgreement <-
dplyr::inner_join(
toWide(simDadt) |> dplyr::select(arm, simulated = auclast),
published |>
dplyr::filter(metabolite == "dADT") |>
dplyr::select(arm, reference = auclast),
by = "arm"
) |>
dplyr::mutate(pct_diff = 100 * (simulated - reference) / reference)
knitr::kable(
aucAgreement |>
dplyr::rename(
`Dose arm` = arm,
`Simulated AUC0-72 (h*ng/mL)` = simulated,
`Published AUC0-72 (h*ng/mL)` = reference,
`% difference` = pct_diff
),
digits = 1
)| Dose arm | Simulated AUC0-72 (h*ng/mL) | Published AUC0-72 (h*ng/mL) | % difference |
|---|---|---|---|
| 25 mg | 552.1 | 509 | 8.5 |
| 50 mg | 1186.9 | 1152 | 3.0 |
| 100 mg | 2565.2 | 2310 | 11.0 |
| 200 mg | 4296.3 | 5382 | -20.2 |
| 400 mg | 9668.7 | 9889 | -2.2 |
| 600 mg | 14123.6 | 12230 | 15.5 |
# This comparison is noisy on BOTH sides -- 60 drawn subjects here against
# nine published patients in four of the six arms -- so it is a sanity bound,
# not the sharp test. The sharp test is the deterministic per-arm table above,
# which involves no cohort draw. Assert only the centre, with headroom: a
# quantile over six per-arm values is not a stable statistic across rxode2
# builds, which resample the cohort.
stopifnot(abs(stats::median(aucAgreement$pct_diff)) < 20)Replicating the weight-stratified exposure simulation
Meister 2019 Figure 4 simulates 1,000 virtual patients at a single oral 400 mg dose for each body weight from 30 to 85 kg in 5 kg steps, at a typical age of 45 years and using the 200-mg formulation, and compares the resulting dADT Cmax and AUC against the therapeutic criteria associated with a 90% probability of cure. The paper’s conclusion is that “400-mg flat dosing achieved the Cmax criterion of 384 ng/ml and the AUC criterion of 4,520 ng*h/ml for all body weights”.
CMAX_CRITERION <- 384 # ng/mL, Results, 'PK-PD analysis and exposure simulations'
AUC_CRITERION <- 4520 # ng*h/mL, same paragraph
N_PER_WEIGHT <- 100L
weights <- seq(30, 85, by = 5)
sweepTimes <- seq(0, 72, by = 0.25)
simulateWeight <- function(wt) {
covs <- data.frame(
id = seq_len(N_PER_WEIGHT), AGE = 45, WT = wt,
FORM_TRI_TAB200 = 1, FORM_TRI_SPLIT50 = 0,
SAMPLE_WHOLEBLOOD = 0, SAMPLE_DBS = 0
)
as.data.frame(rxode2::rxSolve(
mod, makeEvents(400, sweepTimes, n = N_PER_WEIGHT), covs,
returnType = "data.frame"
)) |>
dplyr::mutate(wt = wt, dadt = nmolToNg(Cc, MW_DADT))
}
rxode2::rxSetSeed(20190327)
set.seed(20190327)
exposure <-
lapply(weights, simulateWeight) |>
dplyr::bind_rows() |>
dplyr::group_by(wt, id) |>
dplyr::summarise(
cmax = max(dadt),
auc = sum(diff(time) * (utils::head(dadt, -1) + utils::tail(dadt, -1)) / 2),
.groups = "drop"
)
exposure |>
tidyr::pivot_longer(c(cmax, auc), names_to = "metric", values_to = "value") |>
dplyr::mutate(
metric = factor(metric, c("auc", "cmax"),
c("AUC0-72 (h*ng/mL)", "Cmax (ng/mL)")
),
criterion = ifelse(metric == "Cmax (ng/mL)", CMAX_CRITERION, AUC_CRITERION)
) |>
ggplot2::ggplot(ggplot2::aes(factor(wt), value)) +
ggplot2::geom_boxplot(outlier.size = 0.4) +
ggplot2::geom_hline(ggplot2::aes(yintercept = criterion), linetype = "dashed") +
ggplot2::facet_wrap(~metric, scales = "free_y") +
ggplot2::labs(
title = "400 mg dADT exposure by body weight (replicates Figure 4 of Meister 2019)",
x = "Body weight (kg)", y = NULL
) +
ggplot2::theme_bw()
attainment <-
exposure |>
dplyr::group_by(wt) |>
dplyr::summarise(
medCmax = stats::median(cmax),
medAuc = stats::median(auc),
pctCmax = 100 * mean(cmax > CMAX_CRITERION),
pctAuc = 100 * mean(auc > AUC_CRITERION),
.groups = "drop"
)
knitr::kable(
attainment |>
dplyr::rename(
`Body weight (kg)` = wt,
`Median Cmax (ng/mL)` = medCmax,
`Median AUC0-72 (h*ng/mL)` = medAuc,
`% above Cmax criterion` = pctCmax,
`% above AUC criterion` = pctAuc
),
digits = 1
)| Body weight (kg) | Median Cmax (ng/mL) | Median AUC0-72 (h*ng/mL) | % above Cmax criterion | % above AUC criterion |
|---|---|---|---|---|
| 30 | 1374.9 | 14757.2 | 98 | 100 |
| 35 | 1277.5 | 13662.9 | 98 | 100 |
| 40 | 1245.0 | 12003.3 | 99 | 100 |
| 45 | 1241.7 | 11682.3 | 95 | 98 |
| 50 | 961.5 | 9981.0 | 94 | 94 |
| 55 | 874.7 | 9104.7 | 93 | 94 |
| 60 | 713.3 | 8927.3 | 90 | 95 |
| 65 | 745.2 | 8471.0 | 87 | 92 |
| 70 | 764.8 | 8143.8 | 92 | 91 |
| 75 | 653.0 | 7410.8 | 86 | 88 |
| 80 | 563.0 | 7244.0 | 80 | 84 |
| 85 | 586.6 | 6537.7 | 79 | 79 |
# The paper's claim is about the central tendency of each weight stratum, so
# that is what is asserted: the median of every weight group clears both
# criteria. The narrowest margin is at the heaviest weight and is still large,
# which is why this survives a cohort redraw where an extreme-based assertion
# would not.
stopifnot(
all(attainment$medCmax > CMAX_CRITERION),
all(attainment$medAuc > AUC_CRITERION),
min(attainment$medCmax) / CMAX_CRITERION > 1.15,
min(attainment$medAuc) / AUC_CRITERION > 1.15
)The abstract states that 400 mg “attained therapeutic success in over 90% of adult patients”. At the cohort’s own median weight the per-subject attainment reproduces that.
Assumptions and deviations
The reference level of the formulation covariate is a whole
50-mg tablet, not the 200-mg tablet named in the Table 2
footnote. Meister 2019 Table 2 footnote a describes the typical
patient as “receiving the 200-mg formulation as whole tablets”, which
would make the printed MTT of 3.18 h the 200-mg value. Three independent
lines of evidence say otherwise and the model implements
lmtt as the whole-50-mg-tablet reference:
- The Results state both absorption covariate effects relative to the 50-mg whole tablet – “a 42.9% slower mean absorption transit time for the 200-mg formulation than for the 50-mg formulation” and “a 79.4% faster mean absorption transit time for broken tablets than for whole 50-mg tablets”. A covariate’s reference level is the level the other levels are quoted against.
- The predecessor analysis of the same phase 2a data (Vanobberghen 2016 Table 1) prints a reference MTT of 3.38 h with a +40.1% effect for the 200-mg tablet, using the 50-mg tablet as reference. Meister’s 3.18 h with +42.9% is the same model re-fitted on more data; reading 3.18 h as the 200-mg value would imply a 50-mg reference of 2.23 h, which disagrees with the predecessor.
- Numerically, the reference reading reproduces Table 3 better. Under it the typical 400 mg Cmax and Tmax are about 955 ng/mL and 7.2 h against the published 863 ng/mL and 8.56 h; under the footnote reading they are about 1112 ng/mL and 5.4 h.
Footnote a is read as describing the phase 2b patient who supplied most of the data, not the model’s reference level, and is the one statement in the paper this implementation does not take literally.
The direction of the matrix conversion factors is ambiguous
in the source. Table 2 labels the rows “Whole blood-to-plasma
matrix conversion factor” with dADT values of -14.5% and -13.7%, and the
Results describe the result as “13.7 to 14.5% lower … drug
concentrations in plasma than in blood for dADT”. Taken literally
together those say plasma is lower than blood, i.e. that the factor
multiplies a blood measurement to give plasma. But footnote a fixes
plasma as the reference matrix of the printed estimates, and every other
“Covariate effects” row in the table is the effect of a
non-reference level. The implementation follows the table’s own internal
convention – plasma is the reference with a factor of exactly 1, and
each non-reference matrix multiplies the plasma-scale prediction by
1 + theta – which preserves every printed coefficient
verbatim. A user who prefers the Results sentence’s direction should
invert the two factors. Nothing else in the paper depends on the choice:
Table 3, Figure 4 and both therapeutic criteria are all on the plasma
reference scale, where the factor is 1 either way.
The simulated adADT exposure runs above the published adADT
values. The ncaComparisonTable above flags the
adADT AUC rows. The mass-balance check shows the model routes exactly
65% of dADT elimination into adADT as specified, so the gap is in the
published column rather than in the transcription: Meister 2019 Table
3’s own adADT AUC is not dose-proportional under a model that is
strictly linear, ranging from 3.07 to 7.71 ng*h/mL per mg across the six
dose levels, and the paper itself notes that “overall adADT exposure was
also proportional to the dose for all doses except the 200-mg dose,
where the Cmax and AUC achieved with the 200-mg dose appeared to be
lower than those achieved with the 100-mg dose”, and that the adADT
visual predictive check “presented overpredicted peak concentrations of
the median percentile”. The same offset in the same direction is present
between the predecessor model and its own published adADT table. adADT
is the inactive metabolite and the paper notes its “minor role in the
drug’s activity”; the active dADT columns agree closely.
Molecular weights are not printed in Meister 2019.
The two metabolite weights used to convert nmol/L into ng/mL, 173.214
for dADT and 215.251 for adADT, come from the $ERROR block
LLOQ conversion of the final NONMEM control stream published as
supplemental File S1 of the predecessor Vanobberghen 2016, which
analysed the same assay and the same metabolites. The tribendimidine
weight of 450.59 g/mol used to convert a milligram dose into nmol is not
printed in either paper and is the molecular weight of C28H30N6.
Event tables must dose in nmol, not mg;
mgToNmol() above is the conversion.
One mole of tribendimidine is taken to yield one mole of dADT. This is the stoichiometry the predecessor model encodes, and it is confirmed here independently: with it, the typical 400 mg AUC0-inf is 9,732 ng*h/mL against the published 9,889, whereas any other ratio would miss by that factor.
The exposure-response layer is not implemented as a
model. Meister 2019 fits logistic regressions of O.
viverrini cure against dADT Cmax and AUC, but the continuous
coefficients are never printed. Supplemental Table S3 gives only odds
ratios for exposure categories against an unquantified
reference category, so no absolute probability can be recovered from it;
and its Cmax categories (“<4000”, “4000-<7000”, “7000-<10,000”,
“>=10,000 ng/ml”) are inconsistent with the paper’s own dADT Cmax
range, which reaches only 1,271 ng/ml at the highest dose studied. What
the paper does report in a usable form are the two therapeutic criteria,
384 ng/mL and 4,520 ng*h/mL, which are deterministic thresholds; those
are the basis of the Figure 4 replication above and are recorded in the
vignette rather than invented as a prob_cure output.
Covariate distributions in the virtual cohort are assumed. Table 1 reports medians and ranges but no dispersion, so age and weight are drawn as truncated normals centred on the reported medians with standard deviations of 12 years and 8 kg, truncated to the reported ranges. The structural checks above are all run on the typical individual and are unaffected by this choice.
Creatinine clearance could not be tested. The paper states it was unavailable for part of the pooled data set, so the retained age effect on dADT clearance is the only renal-function proxy in the model. The authors attribute it to declining renal function with age.
The 25 mg arm is the weakest agreement. It is the only arm that used split tablets, it has just nine patients, and the paper reports absorption-phase model misspecification for dADT and warns that “caution should be applied if the model is used for extrapolation during early absorption”. Simulated Cmax runs high there.