Skip to contents

Model and source

Chan 2021 pooled pregabalin concentrations from 10 studies (healthy adults, adults with various degrees of renal function, and adult and pediatric patients with focal onset seizures, FOS) into a population PK model, then used the individual predicted average steady-state concentrations (Cav,ss) in an exposure-response (E-R) analysis of the log-transformed 28-day seizure rate. The two analyses were fitted separately, so the paper contributes two model files:

  • Chan_2021_pregabalin – the final population PK model (Table 2).
  • Chan_2021_pregabalin_lsr28 – the final E-R model (Table 3), driven by the Cav,ss the PK model predicts.
ui_pk <- rxode2::rxode(readModelDb("Chan_2021_pregabalin"))
#> ℹ parameter labels from comments will be replaced by 'label()'
#> Warning: some etas defaulted to non-mu referenced, possible parsing error: etalka, etalka_fed
#> as a work-around try putting the mu-referenced expression on a simple line
ui_er <- rxode2::rxode(readModelDb("Chan_2021_pregabalin_lsr28"))
  • Citation: Chan PLS, Marshall SF, McFadyen L, Liu J. Pregabalin Population Pharmacokinetic and Exposure-Response Analyses for Focal Onset Seizures in Children (4-16 years) and Adults, to Support Dose Recommendations in Children. Clin Pharmacol Ther. 2021;110(1):132-140. doi:10.1002/cpt.2132. Covariate equations in Supplementary Information Appendix Equations I; final NONMEM control stream (run8.mod) in the second supplementary file.
  • Description (PK): One-compartment population PK model with first-order absorption and an absorption lag time for oral pregabalin in pooled pediatric (3 months to 16 years) and adult data (Chan 2021): healthy adults, adults with various degrees of renal function, and adult and pediatric patients with focal onset seizures (10 studies). CL/F is proportional to body-surface-area-normalised creatinine clearance (CRCL, mL/min/1.73 m^2) up to an estimated breakpoint of 96.4 and constant above it, with estimated allometric weight exponents on CL/F (0.52) and V/F (0.70) and female-sex multipliers on both. ka is estimated as a multiple of the individual elimination rate constant CL/V (to avoid flip-flop), with fed and unknown-food-status effects on ka and a fed effect on the lag time. Residual error is combined proportional + additive with separate magnitudes for phase I adult, phase III adult, phase I pediatric (A0081074) and phase III pediatric (A0081041, PERIWINKLE) studies. Individual predicted average steady-state concentrations from this model drive the exposure-response model Chan_2021_pregabalin_lsr28.
  • Description (E-R): Exposure-response (Emax) model for the natural log-transformed 28-day seizure rate (LSR28) during the 12-week double-blind treatment phase in pediatric (4-16 years) and adult patients with focal onset seizures taking adjunctive pregabalin (Chan 2021). LSR28 = Intercept - (Intercept - Emax) * Cav,ss / (EC50 + Cav,ss) + Slope_baseline * baseline LSR28 (Appendix Equations II), with a common Emax (-0.924, the asymptotic intercept under maximal drug effect) and EC50 (4.69 ug/mL) and population-specific intercepts and baseline slopes for children and adults (Table 3). The drug effect therefore differs between populations only through the intercept, i.e. the placebo response. Fitted by nonlinear least squares; no between-subject or residual variance was reported, so the model returns the typical prediction only. Exposure enters as the per-patient column CAV, the individual predicted average steady-state concentration from modellib(‘Chan_2021_pregabalin’).
  • Article: https://doi.org/10.1002/cpt.2132 (open access; PMC8359225)
  • Supplementary information: Appendix Equations I (covariate model) and II (E-R models), Tables S1-S3; the final NONMEM control stream (run8.mod) is the second supplementary file.

Population

The PK analysis included 724 adults (median age 38 years, range 17-75; median weight 75.5 kg, 40-180) and 255 pediatric patients aged 3 months to 16 years (median 10 years; median weight 32.9 kg, 6.6-108), with 5,258 concentrations in total (Table 1). About half were female; most were White (84% of adults, 69% of children), with more Asian patients among the children (22% vs 2%). Absolute creatinine clearance ranged 42.2-261 mL/min in adults and 15.5-293 mL/min in children; normalised to body surface area it was comparable across ages (median 149 mL/min/1.73 m^2 in children, 101 in adults).

The E-R analysis used 280 pediatric patients aged 4-16 years from the PERIWINKLE study (A0081041) and 858 adults (including 8 adolescents aged 13-16 years) from three adult phase III studies (Table S2).

str(ui_pk$population)
#> List of 14
#>  $ species       : chr "human"
#>  $ n_subjects    : int 979
#>  $ n_studies     : int 10
#>  $ age_range     : chr "3 months to 75 years"
#>  $ age_median    : chr "10 years (children); 38 years (adults)"
#>  $ weight_range  : chr "6.6-180 kg"
#>  $ weight_median : chr "32.9 kg (children); 75.5 kg (adults)"
#>  $ sex_female_pct: num 49.8
#>  $ race_ethnicity: Named num [1:4] 80.2 5.2 6.9 7.7
#>   ..- attr(*, "names")= chr [1:4] "White" "Black" "Asian" "Other"
#>  $ disease_state : chr "Healthy adults, adults with various degrees of renal function, and adult and pediatric patients with focal onset seizures"
#>  $ dose_range    : chr "Oral pregabalin; adults 150-600 mg/day b.i.d. or t.i.d.; children 2.5-10 mg/kg/day (>= 30 kg) or 3.5-14 mg/kg/day (< 30 kg)"
#>  $ renal_function: chr "CLcr 42.2-261 mL/min in adults (excluding the renal study) and 15.5-293 mL/min in children; NCLcr median 149 mL"| __truncated__
#>  $ regions       : chr "Multinational"
#>  $ notes         : chr "724 adults and 255 pediatric patients (162 aged 3 months to < 12 years, 93 aged 12-16 years); 5,258 PK samples "| __truncated__
str(ui_er$population)
#> List of 13
#>  $ species       : chr "human"
#>  $ n_subjects    : int 1138
#>  $ n_studies     : int 4
#>  $ age_range     : chr "4-82 years"
#>  $ age_median    : chr "10 years (children); 38 years (adults)"
#>  $ weight_range  : chr "11-180 kg"
#>  $ weight_median : chr "35.6 kg (children); 74.9 kg (adults)"
#>  $ sex_female_pct: num 48.9
#>  $ race_ethnicity: Named num [1:4] 82.2 4.2 8.3 5.4
#>   ..- attr(*, "names")= chr [1:4] "White" "Black" "Asian" "Other"
#>  $ disease_state : chr "Focal onset seizures, adjunctive pregabalin or placebo"
#>  $ dose_range    : chr "Adults 150-600 mg/day; children 2.5 or 10 mg/kg/day (>= 30 kg) or 3.5 or 14 mg/kg/day (< 30 kg), b.i.d.; placebo arms included"
#>  $ regions       : chr "USA 61.8%, European Union 21.4%, Asia-Pacific 1.5%, other 15.3%"
#>  $ notes         : chr "280 pediatric patients from PERIWINKLE (A0081041) and 858 adult patients (including 8 adolescents aged 13-16 ye"| __truncated__

Source trace

Every ini() value carries an in-file comment naming its source. The table collects them.

Equation / parameter Value Source location
One-compartment, first-order absorption with lag, first-order elimination n/a Methods ‘Population PK model’; control stream ADVAN2 TRANS2
lcl (CL/F at CRCL at or above the breakpoint, 70 kg male) log(4.96) L/h Table 2 ‘CL/F’
lcrcl_hinge (CRCL breakpoint) log(96.4) mL/min/1.73 m^2 Table 2 ‘CLcr breakpoint’
cl = CL * min(CRCL, hinge)/hinge * (WT/70)^0.52 * 0.92^SEXF n/a Appendix Equations I; control stream TVCL lines
lvc (V/F, 70 kg male) log(39.8) L Table 2 ‘V/F’
e_wt_cl, e_wt_vc 0.52, 0.70 Table 2 ‘Body weight on CL/F’, ‘Body weight on V/F’
e_sexf_cl, e_sexf_vc 0.92, 0.83 Table 2 ‘Sex on CL/F’, ‘Sex on V/F’ (male reference)
lka (fasted ka, reference subject) log(10.0) 1/h Table 2 ‘ka fasted’
ka = ka_ref * food factor * (CL/V) / (CL/V)_ref n/a Table 2 footnote f; control stream TVKA = EKEL*FKA
e_fed_ka 0.71/10.0 - 1 Table 2 ‘Food: fed’ (ka)
e_fed_missing_ka 1.22/10.0 - 1 Table 2 ‘Food: unknown’ (ka)
ltlag log(0.32) h Table 2 ‘Tlag’
e_fed_tlag (phase I fed records only) 0.43 Table 2 ‘Food: fed’ (Tlag), footnote h
etalcl, etalvc, etalka, etalka_fed 0.202^2, 0.128^2, 1.17^2, 0.579^2 Table 2 IIV rows
propSdPh1Adult, addSdPh1Adult 0.166, 0.021 ug/mL Table 2 residual rows ‘Phase I adult’
propSdPh3Adult, addSdPh3Adult 0.289, 0.047 ug/mL Table 2 residual rows ‘Phase III adult’
propSdPh1Ped (additive 0) 0.298 Table 2 ‘Phase I pediatric’; control stream $SIGMA 8
propSdPh3Ped, addSdPh3Ped 0.350, 0.68 ug/mL Table 2 residual rows ‘Phase III pediatric’
E-R: LSR28 = Int - (Int - Emax) * Cav/(EC50 + Cav) + Slope * baseline n/a Appendix Equations II, ‘Emax treatment effect response model’ with linear baseline effect
e0, e0_child (intercepts) 0.110, -0.409 Table 3 ‘Children (C) + Adult (A)’, ‘Intercept’
e_lsr28_bl, e_lsr28_bl_child (baseline slopes) 0.945, 1.03 Table 3 ‘Slope baseline’
rmax_lsr28 (common Emax) -0.924 Table 3 ‘Emax’
lec50 (common EC50) log(4.69) ug/mL Table 3 ‘EC50’

How the Table 2 values map onto the NONMEM parameters

The control stream estimates CL/F and ka as proportionality factors (TVCL = THETA(1) * CLCR below the breakpoint and THETA(1) * THETA(5) above it; KA = CL/V * THETA(3)), but Table 2 prints both in natural units: CL/F 4.96 L/h and ka 10.0 per hour. The printed ka is visibly not THETA(3), whose initial estimate in run8.mod is 77.3, and 10.0 per hour is THETA(3) times the reference elimination rate constant. So Table 2 reports typical values for the reference subject (70 kg male, fasted, CRCL at or above the breakpoint), and 4.96 L/h is the plateau clearance THETA(1) * THETA(5). The model therefore carries these printed values directly and rebuilds the proportionality in model(). Three checks support this reading:

  1. Read the other way (4.96 L/h as THETA(1) per 100 mL/min/1.73 m^2), the plateau is 4.78 L/h and the simulated adult Cav,ss in the Table 4 replication below rises about 4% above the published medians. The chosen reading lands within 1-2%.
  2. The ‘Food: fed’ and ‘Food: unknown’ ka rows (0.71 and 1.22) are fed and unknown-food ka values in per hour, not multipliers. Read as values, the fed ka of 0.71 per hour lowers Cmax by about 27% and delays Tmax to about 3 h after a single dose, matching the food effect in the pregabalin prescribing information (Cmax about 25-30% lower, Tmax about 3 h). Read as multipliers (fed ka 7.1 per hour), food would have almost no effect. The food-effect check below asserts this.
  3. The paper’s statement that the allometric exponent gives “~40% higher CL/F for a typical child weighing 20 kg compared with a typical child weighing 40 kg” (per kg) is reproduced exactly, as 2^(1 - 0.52) = 1.39.

Deterministic checks of the PK model

mod_pk <- readModelDb("Chan_2021_pregabalin")
mod_pk_typ <- rxode2::zeroRe(mod_pk)
#> ℹ parameter labels from comments will be replaced by 'label()'
#> Warning: some etas defaulted to non-mu referenced, possible parsing error: etalka, etalka_fed
#> as a work-around try putting the mu-referenced expression on a simple line
#> Warning: No sigma parameters in the model
#> Warning: some etas defaulted to non-mu referenced, possible parsing error: etalka, etalka_fed
#> as a work-around try putting the mu-referenced expression on a simple line

ref_cov <- function(WT = 70, SEXF = 0, CRCL = 120, FED = 0, FED_MISSING = 0,
                    STUDY_PHASE3 = 0) {
  data.frame(
    WT = WT,
    SEXF = SEXF,
    CRCL = CRCL,
    FED = FED,
    FED_MISSING = FED_MISSING,
    STUDY_PHASE3 = STUDY_PHASE3,
    STUDY_A0081074 = 0,
    STUDY_A0081041 = 0
  )
}

single_dose <- function(cov, dose = 150, id = 1L) {
  obs_t <- sort(unique(c(seq(0, 2, by = 0.02), seq(2, 48, by = 0.1))))
  dplyr::bind_rows(
    data.frame(id = id, time = 0, amt = dose, evid = 1L, cmt = "depot"),
    data.frame(id = id, time = obs_t, amt = NA_real_, evid = 0L, cmt = "central")
  ) |>
    dplyr::bind_cols(cov[rep(1, length(obs_t) + 1), , drop = FALSE])
}
ev_ref <- single_dose(ref_cov())
sim_ref <- rxode2::rxSolve(mod_pk_typ, events = ev_ref,
                           returnType = "data.frame")
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalvc', 'etalka', 'etalka_fed'
cl_ref <- sim_ref$cl[1]
vc_ref <- sim_ref$vc[1]
ka_ref <- sim_ref$ka[1]
thalf_ref <- log(2) * vc_ref / cl_ref

# Typical CL below the breakpoint scales linearly with CRCL
sim_60 <- rxode2::rxSolve(mod_pk_typ, events = single_dose(ref_cov(CRCL = 60)),
                          returnType = "data.frame")
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalvc', 'etalka', 'etalka_fed'

# Per-kg CL ratio at 20 vs 40 kg (same CRCL, sex)
cl_wt <- function(wt) {
  rxode2::rxSolve(mod_pk_typ, events = single_dose(ref_cov(WT = wt)),
                  returnType = "data.frame")$cl[1]
}
perkg_ratio <- (cl_wt(20) / 20) / (cl_wt(40) / 40)
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalvc', 'etalka', 'etalka_fed'
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalvc', 'etalka', 'etalka_fed'

data.frame(
  quantity = c("CL/F reference (L/h)", "V/F reference (L)",
               "ka reference (1/h)", "t1/2 reference (h)",
               "CL/F at CRCL 60 (L/h)", "Per-kg CL/F ratio, 20 vs 40 kg"),
  model = c(cl_ref, vc_ref, ka_ref, thalf_ref, sim_60$cl[1], perkg_ratio),
  expected = c(4.96, 39.8, 10.0, NA, 4.96 * 60 / 96.4, 1.40)
) |>
  knitr::kable(digits = 3, caption = "Typical-value checks (fasted 70 kg male).")
Typical-value checks (fasted 70 kg male).
quantity model expected
CL/F reference (L/h) 4.960 4.960
V/F reference (L) 39.800 39.800
ka reference (1/h) 10.000 10.000
t1/2 reference (h) 5.562 NA
CL/F at CRCL 60 (L/h) 3.087 3.087
Per-kg CL/F ratio, 20 vs 40 kg 1.395 1.400

stopifnot(
  abs(cl_ref / 4.96 - 1) < 1e-6,
  abs(vc_ref / 39.8 - 1) < 1e-6,
  abs(ka_ref / 10.0 - 1) < 1e-6,
  abs(sim_60$cl[1] / (4.96 * 60 / 96.4) - 1) < 1e-6,
  # 'This translates to ~ 40% higher CL/F for a typical child weighing 20 kg
  # compared with a typical child weighing 40 kg' (Results)
  abs(perkg_ratio - 2^(1 - 0.52)) < 1e-6,
  abs(perkg_ratio - 1.40) < 0.02,
  # 'elimination half-life of ~ 6 hours' (Introduction)
  thalf_ref > 5, thalf_ref < 7
)

The fasted typical subject has a half-life of 5.56 h, consistent with the approximately 6 h the paper quotes.

Food effect

cmax_tmax <- function(sim) {
  i <- which.max(sim$Cc)
  c(cmax = sim$Cc[i], tmax = sim$time[i])
}
fasted <- cmax_tmax(sim_ref)
fed <- cmax_tmax(rxode2::rxSolve(
  mod_pk_typ, events = single_dose(ref_cov(FED = 1, STUDY_PHASE3 = 0)),
  returnType = "data.frame"
))
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalvc', 'etalka', 'etalka_fed'
unknown <- cmax_tmax(rxode2::rxSolve(
  mod_pk_typ, events = single_dose(ref_cov(FED_MISSING = 1, STUDY_PHASE3 = 1)),
  returnType = "data.frame"
))
#> ℹ omega/sigma items treated as zero: 'etalcl', 'etalvc', 'etalka', 'etalka_fed'
food_tab <- rbind(fasted = fasted, fed = fed, unknown = unknown)
knitr::kable(food_tab, digits = 3,
             caption = "Typical single-dose 150 mg Cmax (ug/mL) and Tmax (h) by food status.")
Typical single-dose 150 mg Cmax (ug/mL) and Tmax (h) by food status.
cmax tmax
fasted 3.566 0.76
fed 2.602 3.40
unknown 2.907 2.40

# Fed Cmax falls ~27% and Tmax moves to ~3 h. The alternative reading of the
# Table 2 'Food: fed' row as a ka multiplier (fed ka 7.1/h) would leave the
# Cmax ratio above 0.95 and Tmax below 1 h, and fails both bounds.
stopifnot(
  fed[["cmax"]] / fasted[["cmax"]] > 0.65,
  fed[["cmax"]] / fasted[["cmax"]] < 0.80,
  fed[["tmax"]] > 2.5, fed[["tmax"]] < 4
)

Replicate Table 4: steady-state exposure in children vs adults

Table 4 compares simulated steady-state Cav,ss, Cmax,ss and Cmin,ss in children aged 4-16 years (2.5 or 10 mg/kg/day if at least 30 kg; 3.5 or 14 mg/kg/day if under 30 kg; capped at 150 or 600 mg/day) against adults (150 or 600 mg/day), under fasted conditions, for b.i.d. and t.i.d. regimens. The paper bootstrapped the covariates of its own pediatric and adult FOS patients, which are not public; the virtual cohorts below approximate them from Tables 1 and S2.

rxode2::rxSetSeed(20210701)
set.seed(20210701)

n_per_arm <- 200L

make_adults <- function(n) {
  data.frame(
    WT = pmin(pmax(exp(rnorm(n, log(75.5), 0.2)), 40), 180),
    SEXF = rbinom(n, 1, 0.5),
    CRCL = pmin(pmax(exp(rnorm(n, log(101), 0.22)), 49), 227),
    population = "Adults"
  )
}

make_children <- function(n) {
  # Table S2: median 35.6 kg (range 11-108); Table 4 footnotes: 391 of 1,000
  # simulated children weighed < 30 kg. A log-normal with sdlog 0.6 puts 39%
  # below 30 kg.
  data.frame(
    WT = pmin(pmax(exp(rnorm(n, log(35.6), 0.6)), 11), 108),
    SEXF = rbinom(n, 1, 0.45),
    # Table 1: NCLcr median 149 (range 74-315) mL/min/1.73 m^2
    CRCL = pmin(pmax(exp(rnorm(n, log(149), 0.25)), 74), 315),
    population = "Children"
  )
}

daily_dose <- function(population, WT, level) {
  if (population == "Adults") {
    return(ifelse(level == "150 mg/day", 150, 600))
  }
  per_kg <- if (level == "150 mg/day") {
    ifelse(WT < 30, 3.5, 2.5)
  } else {
    ifelse(WT < 30, 14, 10)
  }
  cap <- ifelse(level == "150 mg/day", 150, 600)
  pmin(per_kg * WT, cap)
}

arms <- tidyr::expand_grid(
  population = c("Adults", "Children"),
  level = c("150 mg/day", "600 mg/day"),
  freq = c("b.i.d.", "t.i.d.")
)

build_arm <- function(i) {
  a <- arms[i, ]
  cov <- if (a$population == "Adults") make_adults(n_per_arm) else make_children(n_per_arm)
  cov$id <- (i - 1L) * n_per_arm + seq_len(n_per_arm)
  cov$level <- a$level
  cov$freq <- a$freq
  cov$daily <- daily_dose(a$population, cov$WT, a$level)
  cov
}
subjects <- dplyr::bind_rows(lapply(seq_len(nrow(arms)), build_arm)) |>
  dplyr::mutate(
    treatment = paste(population, level, freq, sep = " | "),
    tau = ifelse(freq == "b.i.d.", 12, 8),
    FED = 0, FED_MISSING = 0, STUDY_PHASE3 = 1,
    STUDY_A0081074 = 0, STUDY_A0081041 = 0
  )
stopifnot(!anyDuplicated(subjects$id))

# Five days of dosing (about 20 adult half-lives) then a dense final interval
t_end <- 120
doses <- subjects |>
  dplyr::rowwise() |>
  dplyr::reframe(
    id = id, treatment = treatment,
    time = seq(0, t_end - tau, by = tau),
    amt = daily / (24 / tau)
  ) |>
  dplyr::mutate(evid = 1L, cmt = "depot")
obs <- subjects |>
  dplyr::rowwise() |>
  dplyr::reframe(
    id = id, treatment = treatment,
    time = seq(t_end - tau, t_end, by = 0.1)
  ) |>
  dplyr::mutate(evid = 0L, cmt = "central", amt = NA_real_)
events <- dplyr::bind_rows(doses, obs) |>
  dplyr::left_join(
    subjects |> dplyr::select(id, WT, SEXF, CRCL, FED, FED_MISSING,
                              STUDY_PHASE3, STUDY_A0081074, STUDY_A0081041),
    by = "id"
  ) |>
  dplyr::arrange(id, time, dplyr::desc(evid))
stopifnot(!anyDuplicated(events[, c("id", "time", "evid")]))

subjects |>
  dplyr::group_by(population) |>
  dplyr::summarise(
    n = dplyr::n(),
    `median WT (kg)` = median(WT),
    `% < 30 kg` = 100 * mean(WT < 30),
    `median CRCL` = median(CRCL),
    `% female` = 100 * mean(SEXF),
    .groups = "drop"
  ) |>
  knitr::kable(digits = 1, caption = "Virtual cohorts (all arms pooled).")
Virtual cohorts (all arms pooled).
population n median WT (kg) % < 30 kg median CRCL % female
Adults 800 75.5 0.0 101.3 52.8
Children 800 35.9 38.8 149.0 39.8
sim_ss <- rxode2::rxSolve(mod_pk, events = events,
                          keep = c("treatment"),
                          returnType = "data.frame")
#> ℹ parameter labels from comments will be replaced by 'label()'
#> Warning: some etas defaulted to non-mu referenced, possible parsing error: etalka, etalka_fed
#> as a work-around try putting the mu-referenced expression on a simple line
stopifnot(all(sim_ss$Cc >= -1e-6 * max(sim_ss$Cc, na.rm = TRUE), na.rm = TRUE))

The exposures below are computed from the individual predictions (Cc, with IIV but without residual error).

# One PKNCA call per population (800 subject-intervals each), with the
# dosing-interval window carried per treatment in the intervals table.
nca_pop <- function(pop) {
  s <- subjects[subjects$population == pop, ]
  conc <- sim_ss |>
    dplyr::filter(treatment %in% s$treatment, !is.na(Cc)) |>
    dplyr::mutate(Cc = pmax(Cc, 0)) |>
    dplyr::select(id, time, Cc, treatment)
  dose <- doses |>
    dplyr::filter(treatment %in% s$treatment) |>
    dplyr::select(id, time, amt, treatment)
  intervals <- s |>
    dplyr::distinct(treatment, tau) |>
    dplyr::transmute(treatment, start = t_end - tau, end = t_end,
                     cmax = TRUE, cmin = TRUE, cav = TRUE)
  conc_obj <- PKNCA::PKNCAconc(conc, Cc ~ time | treatment + id,
                               concu = "ug/mL", timeu = "h")
  dose_obj <- PKNCA::PKNCAdose(dose, amt ~ time | treatment + id, doseu = "mg")
  PKNCA::pk.nca(PKNCA::PKNCAdata(conc_obj, dose_obj, intervals = intervals))
}
nca_adults <- nca_pop("Adults")
nca_children <- nca_pop("Children")

med_tbl <- dplyr::bind_rows(
  as.data.frame(nca_adults$result),
  as.data.frame(nca_children$result)
) |>
  dplyr::filter(PPTESTCD %in% c("cav", "cmax", "cmin")) |>
  dplyr::select(id, PPTESTCD, PPORRES) |>
  dplyr::left_join(
    subjects |> dplyr::select(id, population, level, freq, WT),
    by = "id"
  )
stopifnot(nrow(med_tbl) == 3L * nrow(subjects), !anyNA(med_tbl$PPORRES))

Adult medians against Table 4

published_adults <- tibble::tribble(
  ~treatment,                          ~cav, ~cmax, ~cmin,
  "Adults | 150 mg/day | b.i.d.",      1.34, 2.43,  0.60,
  "Adults | 600 mg/day | b.i.d.",      5.37, 9.73,  2.39,
  "Adults | 150 mg/day | t.i.d.",      1.34, 1.97,  0.81,
  "Adults | 600 mg/day | t.i.d.",      5.35, 7.86,  3.23
)
cmp <- nlmixr2lib::ncaComparisonTable(
  simulated = nca_adults,
  reference = published_adults,
  by = "treatment",
  units = c(cav = "ug/mL", cmax = "ug/mL", cmin = "ug/mL"),
  tolerance_pct = 20
)
knitr::kable(cmp, caption = paste(
  "Simulated vs published (Table 4) adult steady-state medians.",
  "* differs from the reference by >20%."
))
Simulated vs published (Table 4) adult steady-state medians. * differs from the reference by >20%.
NCA parameter treatment Reference Simulated % diff
Cmax (ug/mL) Adults | 150 mg/day | b.i.d. 2.43 2.39 -1.5%
Cmax (ug/mL) Adults | 600 mg/day | b.i.d. 9.73 9.8 +0.8%
Cmax (ug/mL) Adults | 150 mg/day | t.i.d. 1.97 2.01 +1.9%
Cmax (ug/mL) Adults | 600 mg/day | t.i.d. 7.86 8.16 +3.8%
Cmin (ug/mL) Adults | 150 mg/day | b.i.d. 0.6 0.594 -1.1%
Cmin (ug/mL) Adults | 600 mg/day | b.i.d. 2.39 2.34 -2.1%
Cmin (ug/mL) Adults | 150 mg/day | t.i.d. 0.81 0.837 +3.3%
Cmin (ug/mL) Adults | 600 mg/day | t.i.d. 3.23 3.09 -4.4%
Cavg (ug/mL) Adults | 150 mg/day | b.i.d. 1.34 1.34 -0.1%
Cavg (ug/mL) Adults | 600 mg/day | b.i.d. 5.37 5.33 -0.7%
Cavg (ug/mL) Adults | 150 mg/day | t.i.d. 1.34 1.37 +2.2%
Cavg (ug/mL) Adults | 600 mg/day | t.i.d. 5.35 5.27 -1.6%
adult_meds <- med_tbl |>
  dplyr::filter(population == "Adults") |>
  dplyr::group_by(level, freq, PPTESTCD) |>
  dplyr::summarise(sim = median(PPORRES), .groups = "drop") |>
  dplyr::mutate(treatment = paste("Adults", level, freq, sep = " | ")) |>
  dplyr::inner_join(
    published_adults |>
      tidyr::pivot_longer(-treatment, names_to = "PPTESTCD", values_to = "pub"),
    by = c("treatment", "PPTESTCD")
  ) |>
  dplyr::mutate(pct_diff = 100 * (sim / pub - 1))
stopifnot(nrow(adult_meds) == 12L)
# Cav,ss is dose / CL/F and is insensitive to absorption; a mis-transcribed
# CL/F, breakpoint or weight exponent moves it by tens of percent. The
# envelope also covers Cmax and Cmin, which depend on the assumed CRCL and
# weight distributions of the virtual adults.
stopifnot(
  all(abs(adult_meds$pct_diff[adult_meds$PPTESTCD == "cav"]) < 10),
  abs(median(adult_meds$pct_diff)) < 10,
  max(abs(adult_meds$pct_diff)) < 25
)

Children-to-adult ratios against Table 4

med_by <- med_tbl |>
  dplyr::mutate(wtgrp = ifelse(population == "Adults", "Adults",
                               ifelse(WT < 30, "< 30 kg", ">= 30 kg"))) |>
  dplyr::group_by(level, freq, PPTESTCD) |>
  dplyr::group_modify(function(d, k) {
    adult <- median(d$PPORRES[d$population == "Adults"])
    ch <- d[d$population == "Children", ]
    data.frame(
      All = median(ch$PPORRES) / adult,
      `< 30 kg` = median(ch$PPORRES[ch$WT < 30]) / adult,
      `>= 30 kg` = median(ch$PPORRES[ch$WT >= 30]) / adult,
      check.names = FALSE
    )
  }) |>
  dplyr::ungroup()

# Table 4 reports one ratio per frequency; the ratios are dose-level
# independent in a linear model, so the two levels are averaged here.
ratio_sim <- med_by |>
  dplyr::group_by(freq, PPTESTCD) |>
  dplyr::summarise(dplyr::across(c(All, `< 30 kg`, `>= 30 kg`), mean),
                   .groups = "drop")
ratio_pub <- tibble::tribble(
  ~freq,    ~PPTESTCD, ~All_pub, ~lt30_pub, ~ge30_pub,
  "b.i.d.", "cav",     0.91,     0.89,      0.92,
  "t.i.d.", "cav",     0.90,     0.88,      0.91,
  "b.i.d.", "cmax",    1.01,     1.05,      0.99,
  "t.i.d.", "cmax",    0.99,     1.02,      0.97,
  "b.i.d.", "cmin",    0.75,     0.68,      0.80,
  "t.i.d.", "cmin",    0.78,     0.72,      0.81
)
ratio_cmp <- dplyr::inner_join(ratio_sim, ratio_pub, by = c("freq", "PPTESTCD"))
stopifnot(nrow(ratio_cmp) == 6L)
ratio_cmp |>
  dplyr::select(PPTESTCD, freq, All, All_pub, `< 30 kg`, lt30_pub,
                `>= 30 kg`, ge30_pub) |>
  dplyr::rename(
    "Parameter" = PPTESTCD,
    "Frequency" = freq,
    "All (sim)" = All,
    "All (Table 4)" = All_pub,
    "< 30 kg (sim)" = `< 30 kg`,
    "< 30 kg (Table 4)" = lt30_pub,
    ">= 30 kg (sim)" = `>= 30 kg`,
    ">= 30 kg (Table 4)" = ge30_pub
  ) |>
  knitr::kable(digits = 2, caption = "Median ratio, children (4-16 years) to adults.")
Median ratio, children (4-16 years) to adults.
Parameter Frequency All (sim) All (Table 4) < 30 kg (sim) < 30 kg (Table 4) >= 30 kg (sim) >= 30 kg (Table 4)
cav b.i.d. 0.92 0.91 0.90 0.89 0.92 0.92
cmax b.i.d. 0.98 1.01 1.03 1.05 0.96 0.99
cmin b.i.d. 0.79 0.75 0.67 0.68 0.86 0.80
cav t.i.d. 0.89 0.90 0.85 0.88 0.90 0.91
cmax t.i.d. 0.94 0.99 0.97 1.02 0.92 0.97
cmin t.i.d. 0.79 0.78 0.70 0.72 0.84 0.81

# The pediatric dosing rule (40% more per kg below 30 kg) should bring both
# weight groups to within ~10-30% of adult exposure; a wrong weight exponent
# or a mis-applied dose rule separates the < 30 kg and >= 30 kg ratios.
stopifnot(
  abs(median(ratio_cmp$All - ratio_cmp$All_pub)) < 0.1,
  max(abs(ratio_cmp$All - ratio_cmp$All_pub)) < 0.2,
  max(abs(ratio_cmp$`< 30 kg` - ratio_cmp$lt30_pub)) < 0.25,
  max(abs(ratio_cmp$`>= 30 kg` - ratio_cmp$ge30_pub)) < 0.25
)
sim_ss |>
  dplyr::filter(!is.na(Cc)) |>
  dplyr::mutate(tad = time - min(time), .by = treatment) |>
  tidyr::separate(treatment, c("population", "level", "freq"), sep = " \\| ") |>
  dplyr::group_by(population, level, freq, tad) |>
  dplyr::summarise(
    Q05 = quantile(Cc, 0.05), Q50 = median(Cc), Q95 = quantile(Cc, 0.95),
    .groups = "drop"
  ) |>
  ggplot(aes(tad, Q50, colour = population, fill = population)) +
  geom_ribbon(aes(ymin = Q05, ymax = Q95), alpha = 0.15, colour = NA) +
  geom_line() +
  facet_grid(level ~ freq, scales = "free") +
  labs(x = "Time after dose at steady state (h)",
       y = "Pregabalin concentration (ug/mL)",
       colour = NULL, fill = NULL,
       caption = paste("Median and 90% interval over the final dosing interval;",
                       "the setting of Table 4 and Figure S3."))

Exposure-response model

The E-R model predicts the log-transformed 28-day seizure rate during the double-blind phase (LSR28) from the patient’s Cav,ss and observed baseline LSR28. Table 3 footnote a gives the placebo response at the median baseline (3.00 in children, 2.40 in adults) as a check.

mod_er <- readModelDb("Chan_2021_pregabalin_lsr28")
er_pred <- function(CAV, LSR28_BL, CHILD) {
  d <- data.frame(id = seq_along(CAV), time = 0, evid = 0L,
                  CAV = CAV, LSR28_BL = LSR28_BL, CHILD = CHILD)
  out <- rxode2::rxSolve(mod_er, events = d, returnType = "data.frame")
  if (is.null(out$id)) out$id <- 1L
  out$lsr28[match(d$id, out$id)]
}

placebo <- er_pred(CAV = c(0, 0), LSR28_BL = c(3.00, 2.40), CHILD = c(1, 0))
stopifnot(
  # Table 3 'Placebo response' C: 2.68, A: 2.38 (computed as intercept +
  # slope * median baseline; printed to 2 decimals)
  abs(placebo[1] - 2.68) < 0.005,
  abs(placebo[2] - 2.38) < 0.005
)
knitr::kable(
  data.frame(population = c("Children", "Adults"),
             `median baseline LSR28` = c(3.00, 2.40),
             `model placebo response` = placebo,
             `Table 3` = c(2.68, 2.38), check.names = FALSE),
  digits = 3, caption = "Placebo response at the median baseline (Table 3 footnote a)."
)
Placebo response at the median baseline (Table 3 footnote a).
population median baseline LSR28 model placebo response Table 3
Children 3.0 2.681 2.68
Adults 2.4 2.378 2.38

The paper also reports observed changes from baseline in LSR28 (Results; Figure 2): group means of -0.0296 (adult placebo), -0.313 (pediatric placebo), -0.545 (adults, 600 mg/day b.i.d.) and -0.551 (children, 10 mg/kg/day). The typical predictions at the median baseline and at the median Cav,ss of each regimen (Table 4: 5.37 ug/mL in adults; 0.91 times that in children) should be close to those means.

cav_adult_600 <- 5.37
cav_child_10 <- 0.91 * 5.37
cfb <- er_pred(
  CAV = c(0, 0, cav_adult_600, cav_child_10),
  LSR28_BL = c(2.40, 3.00, 2.40, 3.00),
  CHILD = c(0, 1, 0, 1)
) - c(2.40, 3.00, 2.40, 3.00)
cfb_tab <- data.frame(
  group = c("Adults, placebo", "Children, placebo",
            "Adults, 600 mg/day", "Children, 10 mg/kg/day"),
  model = cfb,
  observed_mean = c(-0.0296, -0.313, -0.545, -0.551)
)
knitr::kable(cfb_tab, digits = 3,
             caption = "Typical change from baseline in LSR28 vs observed group means.")
Typical change from baseline in LSR28 vs observed group means.
group model observed_mean
Adults, placebo -0.022 -0.030
Children, placebo -0.319 -0.313
Adults, 600 mg/day -0.574 -0.545
Children, 10 mg/kg/day -0.582 -0.551
# Typical predictions at the median covariates against observed group means:
# the drug-arm differences are about 0.03, the placebo differences about 0.01.
stopifnot(max(abs(cfb_tab$model - cfb_tab$observed_mean)) < 0.1)
# Replicates Figure 1 and the typical-patient lines of Figure S4: predicted
# LSR28 against Cav,ss at the median baseline of each population.
grid <- seq(0, 12, by = 0.1)
er_curve <- dplyr::bind_rows(
  data.frame(population = "Children (baseline 3.00)", CAV = grid,
             lsr28 = er_pred(grid, rep(3.00, length(grid)), rep(1, length(grid)))),
  data.frame(population = "Adults (baseline 2.40)", CAV = grid,
             lsr28 = er_pred(grid, rep(2.40, length(grid)), rep(0, length(grid))))
)
ggplot(er_curve, aes(CAV, lsr28, colour = population)) +
  geom_line(linewidth = 1) +
  labs(x = "Cav,ss (ug/mL)", y = "Predicted LSR28 (double-blind phase)",
       colour = NULL,
       caption = "Replicates Figure 1 / Figure S4 typical-patient lines of Chan 2021.")

Assumptions and deviations

  • Reporting convention of Table 2. Table 2 prints CL/F (4.96 L/h), ka (10.0 per hour) and the fed and unknown-food ka rows (0.71 and 1.22) as reference-subject typical values in natural units, while the NONMEM parameters are proportionality factors (footnotes c, f, g). The maintainers took 4.96 L/h as the plateau CL/F reached at or above the CRCL breakpoint and rebuilt the proportionality in model(). The section “How the Table 2 values map onto the NONMEM parameters” gives the evidence.
  • ka scales with the individual elimination rate constant. As in the control stream, ka is a multiple of each subject’s own CL/V, including its CL and V random effects and covariates. Table 2’s ka of 10.0 per hour applies to the reference subject only; a child with a higher CL/V absorbs faster.
  • IIV scale. Table 2’s IIV percentages are taken as sqrt(omega^2), the same convention as its residual SD rows, which match sqrt($SIGMA) of the supplied control stream. On that scale, the control stream’s initial estimates also sit closest to the base-model IIVs quoted in the Results. IIV on Tlag was 0 by design and is omitted.
  • Fed-state ka random effect. The control stream replaces (does not add to) the ka random effect with a separate one for fed records in phase I studies. The model declares both etas and selects one with FED * (1 - STUDY_PHASE3). This breaks mu-referencing for etalka and etalka_fed (a load-time warning), which matters only for re-estimation. The paper did not use fed conditions in its simulations; neither does this vignette beyond the food-effect check.
  • Phase flag for the pediatric PK study. The control stream sets its phase I flag from a FLAGPHASE data column that is not published. The pediatric phase I study A0081074 is assumed to be phase I (STUDY_PHASE3 = 0). This affects only fed records in that study, and its residual error is set by its own study indicator either way.
  • Residual error form. $ERROR uses F * exp(eps_prop) + eps_add, encoded as the combined add() + prop() form. The phase I pediatric additive SD was held at 0 in the control stream (it has no Table 2 row).
  • Race and age were screened (Table S1) but not retained. They appear in covariatesDataExcluded.
  • Virtual cohorts. The Table 4 simulations bootstrapped the paper’s own patients. Here adults and children are drawn from log-normal weight and CRCL distributions matched to the medians and ranges of Tables 1 and S2 and to the 39% of simulated children under 30 kg. Exposures come from individual predictions without residual error, at 200 subjects per arm rather than 1,000.
  • E-R model variability. The E-R model was fitted by nonlinear least squares, and Table 3 reports estimates and standard errors only. With no between-subject or residual variance, the model returns typical predictions. The adult-only fit in the first row of Table 3 was a development step and is not encoded.
  • Pediatric indicator in the E-R model. CHILD = 1 identifies the pediatric population (PERIWINKLE, 4-16 years). The 8 adolescents from adult study 1008-034 were analysed with the adult parameters (Table 3 footnote b), so they take CHILD = 0.
  • No erratum or correction notice for Chan 2021 was found in Europe PMC as of 2026-09-28.