library(nlmixr2lib)
library(rxode2)
#> rxode2 5.1.8 using 2 threads (see ?getRxThreads)
#> no cache: create with `rxCreateCache()`
library(dplyr)
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
library(tidyr)
library(ggplot2)
library(PKNCA)
#>
#> Attaching package: 'PKNCA'
#> The following object is masked from 'package:stats':
#>
#> filterInotersen population PK/PD in hATTR polyneuropathy (Yu 2020)
Yu et al. (2020) developed the first population PK/PD model for an antisense oligonucleotide therapeutic: inotersen, a 20-mer 2’-O-methoxyethyl gapmer that reduces hepatic production of transthyretin (TTR). Plasma inotersen follows a two-compartment model with first-order SC absorption and linear elimination from the central compartment. Serum TTR follows an indirect-response model in which inotersen inhibits the zero-order production of TTR:
with so that TTR starts at its baseline. The PK model was fit first and the individual (post hoc) PK parameters then drove the PD fit; the packaged model carries both layers in one file.
- Citation: Yu RZ, Collins JW, Hall S, Ackermann EJ, Geary RS, Monia BP, Henry SP, Wang Y. Population Pharmacokinetic-Pharmacodynamic Modeling of Inotersen, an Antisense Oligonucleotide for Treatment of Patients with Hereditary Transthyretin Amyloidosis. Nucleic Acid Ther. 2020;30(3):153-163. doi:10.1089/nat.2019.0822
- Article: https://doi.org/10.1089/nat.2019.0822
Population
The analysis pooled three Ionis studies (Yu 2020 Clinical studies section and Table 1): a phase 1 single- and multiple-ascending-dose study in 51 inotersen-treated healthy volunteers (50-400 mg SC), the pivotal phase 2/3 NEURO-TTR study (300 mg SC on days 1, 3 and 5 then once weekly for 65 weeks) and its open-label extension (300 mg SC weekly for up to 5 years). Of 202 subjects, 151 were patients with hereditary transthyretin amyloidosis with polyneuropathy (hATTR-PN); 197 contributed 3,602 PK observations. Median age was 57 years (range 25-81), median body weight 71.8 kg (37.0-140), median lean body mass 51.6 kg (31.3-80.3), 69.8% were male, and 87.6% were Caucasian, 7.43% African American and 3.47% Asian. Median baseline TTR was 22.1 mg/dL. PK samples collected after the onset of antidrug antibodies were excluded.
The same information is available programmatically via
readModelDb("Yu_2020_inotersen")()$population.
Source trace
Every ini() value carries an in-file comment naming its
source row. They are collected here for review.
| Parameter / equation | Value | Source |
|---|---|---|
ka |
0.261 1/h | Table 2 ‘Absorption rate constant, Ka’ |
CL/F (hATTR, LBM 51.6 kg) |
3.4 L/h | Table 2 ‘Clearance, CL’ |
Vc/F (hATTR, LBM 51.6 kg) |
20.7 L | Table 2 ‘Central volume, Vc’ |
Q/F |
0.266 L/h | Table 2 ‘Intercompartmental clearance, Q’ |
Vp/F |
230 L | Table 2 ‘Peripheral volume, Vp’ |
| LBM exponent on CL/F and Q/F | 1 (fixed) | Table 2 ‘Lean body mass * CL/Q’, power centred on median |
| LBM exponent on Vc/F and Vp/F | 1 (fixed) | Table 2 ‘Lean body mass * Vc/Vp’, power centred on median |
| LBM reference | 51.6 kg | Table 1 lean body mass median |
| Healthy effect on CL/F | +0.111, (1 + 0.111 * DIS_HEALTHY)
|
Table 2 ‘Disease * CL’, proportional |
| Healthy effect on Vc/F | -0.284, (1 - 0.284 * DIS_HEALTHY)
|
Table 2 ‘Disease * Vc’, proportional |
| omega^2 CL, Vc, Vp | 0.071, 0.335, 0.677 | Table 2 |
| cov(Vc, CL), cov(CL, Vp) | 0.049, -0.145 | Table 2 (cov(Vc, Vp) not reported; set to 0) |
| PK residual | sigma^2 = 0.168 log-additive;
expSd = sqrt(0.168) = 0.4099
|
Table 2 |
Imax |
0.913 | Table 3 |
IC50 |
9.07 ng/mL | Table 3 |
| Baseline TTR (hATTR) | 20.4 mg/dL | Table 3 ‘Estimated baseline’ |
| Healthy effect on baseline | +0.269, (1 + 0.269 * DIS_HEALTHY)
|
Table 3 and its footnote |
kout |
0.00308 1/h | Table 3 |
| omega^2 IC50, kout; cov | 0.953, 0.288; -0.182 | Table 3 |
| omega^2 baseline | 0.0537 | Table 3 |
| PD residual | proportional 0.13, additive 1.35 mg/dL | Table 3 ‘Residual variability (log space), (SD)’ |
| Indirect-response equation | see above | Population PD modeling section |
Covariates
| Source | Canonical column | Notes |
|---|---|---|
| Lean body mass (kg) | LBM |
Reference 51.6 kg. The paper does not state which LBM formula it used. |
| Disease status | DIS_HEALTHY |
1 = healthy volunteer, 0 = hATTR-PN patient (reference). |
Closed-form checks on the typical PK
The paper derives four numbers from its typical PK estimates: alpha and beta half-lives of 3.91 h and 26.9 days, a steady-state volume of 250.7 L, and a trough accumulation ratio of 6.07 for weekly dosing. These use the same parameters on both sides, so they are checked tightly.
ini_df <- rxode2::rxode2(readModelDb("Yu_2020_inotersen"))$iniDf
#> ℹ parameter labels from comments will be replaced by 'label()'
th <- setNames(ini_df$est, ini_df$name)
cl <- exp(th[["lcl"]])
vc <- exp(th[["lvc"]])
q <- exp(th[["lq"]])
vp <- exp(th[["lvp"]])
kel <- cl / vc
k12 <- q / vc
k21 <- q / vp
s <- kel + k12 + k21
alpha <- (s + sqrt(s^2 - 4 * kel * k21)) / 2
beta <- (s - sqrt(s^2 - 4 * kel * k21)) / 2
checks <- data.frame(
Quantity = c(
"Alpha half-life (h)",
"Beta half-life (days)",
"Vss (L)",
"Trough accumulation ratio, QW"
),
Paper = c(3.91, 26.9, 250.7, 6.07),
Model = c(
log(2) / alpha,
log(2) / beta / 24,
vc + vp,
1 / (1 - exp(-beta * 168))
)
)
knitr::kable(checks, digits = 3)| Quantity | Paper | Model |
|---|---|---|
| Alpha half-life (h) | 3.91 | 3.912 |
| Beta half-life (days) | 26.90 | 26.939 |
| Vss (L) | 250.70 | 250.700 |
| Trough accumulation ratio, QW | 6.07 | 6.067 |
Virtual cohort and dosing regimens
Yu 2020 simulated the 151 hATTR-PN patients of the analysis dataset
under four regimens for 65 weeks (Simulations section). Subject-level
covariates are not published, so the virtual cohort below draws lean
body mass from a truncated log-normal around the pooled-cohort median
and range (Table 1); all subjects are patients
(DIS_HEALTHY = 0). Each regimen uses the same 150 virtual
patients so that differences between arms come from the dosing
alone.
Day 1 of the paper is time 0, so day d is
(d - 1) * 24 h; the last dose of each regimen falls on day
449 (t = 10752 h), which is where Table 4 reports the steady-state
interval.
set.seed(20200301)
rxode2::rxSetSeed(20200301)
n_per_arm <- 150L
cohort <- tibble(
subj = seq_len(n_per_arm),
LBM = pmin(pmax(rlnorm(n_per_arm, log(51.6), 0.16), 31.3), 80.3),
DIS_HEALTHY = 0L
)
t_last <- 448 * 24
regimens <- list(
"300 mg QW" = list(amt = 300, times = seq(0, t_last, by = 168), tau = 168),
"300 mg QOW" = list(amt = 300, times = seq(0, t_last, by = 336), tau = 336),
"Loading + 300 mg QW" = list(
amt = 300,
times = c(0, 48, 96, seq(168, t_last, by = 168)),
tau = 168
),
"150 mg QW" = list(amt = 150, times = seq(0, t_last, by = 168), tau = 168)
)
make_arm <- function(arm_index, cohort, regimens) {
reg <- regimens[[arm_index]]
ids <- cohort |> mutate(id = subj + (arm_index - 1L) * nrow(cohort))
dense <- c(seq(0, 12, by = 0.5), seq(14, reg$tau, by = 2))
obs_t <- sort(unique(c(
dense,
seq(0, t_last + reg$tau, by = 24),
t_last + dense
)))
doses <- ids |>
crossing(time = reg$times) |>
mutate(amt = reg$amt, evid = 1L, cmt = "depot", dvid = NA_integer_)
obs <- ids |>
crossing(time = obs_t) |>
mutate(amt = 0, evid = 0L, cmt = NA_character_, dvid = 1L)
bind_rows(doses, obs) |>
mutate(regimen = names(regimens)[arm_index], tau = reg$tau) |>
arrange(id, time, desc(evid))
}
events <- bind_rows(lapply(
seq_along(regimens),
make_arm,
cohort = cohort,
regimens = regimens
))
stopifnot(!anyDuplicated(events[, c("id", "time", "evid")]))Simulation
The model has two endpoints (Cc and ttr),
so observation rows carry dvid = 1 with no compartment;
rxode2 returns both outputs at every observation time. Residual error is
not added (sigma = NA); between-subject variability is
simulated.
mod <- readModelDb("Yu_2020_inotersen")
sim <- rxode2::rxSolve(
mod,
events = events,
sigma = NA,
useLinCmt = FALSE,
keep = c("regimen", "tau"),
returnType = "data.frame"
)
#> ℹ parameter labels from comments will be replaced by 'label()'
sim <- sim |> mutate(regimen = factor(regimen, levels = names(regimens)))Replicate published figures
Figure 4: plasma trough concentrations
trough <- sim |>
filter(time %in% seq(168, t_last + 336, by = 168)) |>
group_by(regimen, time) |>
summarise(
Q05 = quantile(Cc, 0.05),
Q50 = median(Cc),
Q95 = quantile(Cc, 0.95),
.groups = "drop"
)
ggplot(trough, aes(time / 24 / 7, Q50, colour = regimen, fill = regimen)) +
geom_ribbon(aes(ymin = Q05, ymax = Q95), alpha = 0.15, colour = NA) +
geom_line(linewidth = 0.8) +
labs(
x = "Time (weeks)",
y = "Inotersen plasma trough (ng/mL)",
colour = NULL,
fill = NULL,
caption = "Replicates Figure 4 of Yu 2020 (median and 90% prediction interval)."
) +
theme_bw()
Figure 5: serum TTR
ttr_prof <- sim |>
filter(time %% 24 == 0) |>
group_by(regimen, time) |>
summarise(
Q05 = quantile(ttr, 0.05),
Q50 = median(ttr),
Q95 = quantile(ttr, 0.95),
.groups = "drop"
)
ggplot(ttr_prof, aes(time / 24 / 7, Q50, colour = regimen, fill = regimen)) +
geom_ribbon(aes(ymin = Q05, ymax = Q95), alpha = 0.12, colour = NA) +
geom_line(linewidth = 0.8) +
labs(
x = "Time (weeks)",
y = "Serum TTR (mg/dL)",
colour = NULL,
fill = NULL,
caption = "Replicates Figure 5 of Yu 2020 (median and 90% prediction interval)."
) +
theme_bw()
PKNCA validation against Table 4
Table 4 reports geometric means of AUC over the dosing interval, Cmax
and Ctrough after the first dose (day 1) and after the last dose (day
449). NCA is run on both intervals for each regimen, with Ctrough taken
as the concentration at the end of the interval (PKNCA
ctrough), i.e. just before the next dose. For the loading
regimen the day-1 interval is not compared, because the paper’s day-1
values for that arm mix a 48-hour trough with an AUC equal to the weekly
arms’ and the interval it used is not stated.
# Each interval is re-anchored to its own dose so that it starts at time 0:
# PKNCA evaluates ctrough on time after dose, and the two periods are then
# separate PKNCA groups.
tau_by_reg <- vapply(regimens, `[[`, numeric(1), "tau")
periods <- c("Day 1" = 0, "Day 449" = t_last)
conc <- bind_rows(lapply(names(periods), function(p) {
sim |>
filter(!is.na(Cc)) |>
mutate(time = time - periods[[p]], period = p) |>
filter(time >= 0, time <= tau_by_reg[as.character(regimen)])
})) |>
mutate(regimen = as.character(regimen)) |>
select(id, time, Cc, regimen, period)
doses <- bind_rows(lapply(names(periods), function(p) {
events |>
filter(evid == 1L) |>
mutate(time = time - periods[[p]], period = p) |>
filter(time >= 0, time < tau)
})) |>
select(id, time, amt, regimen, period)
intervals <- expand.grid(
regimen = names(regimens),
period = names(periods),
stringsAsFactors = FALSE
) |>
mutate(start = 0, end = unname(tau_by_reg[regimen])) |>
filter(!(regimen == "Loading + 300 mg QW" & period == "Day 1")) |>
mutate(auclast = TRUE, cmax = TRUE, ctrough = TRUE)
nca <- PKNCA::pk.nca(PKNCA::PKNCAdata(
PKNCA::PKNCAconc(conc, Cc ~ time | regimen + period + id),
PKNCA::PKNCAdose(doses, amt ~ time | regimen + period + id),
intervals = intervals
))
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=301: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=302: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=303: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=304: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=305: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=306: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=307: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=308: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=309: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=310: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=311: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=312: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=313: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=314: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=315: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=316: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=317: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=318: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=319: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=320: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=321: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=322: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=323: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=324: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=325: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=326: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=327: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=328: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=329: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=330: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=331: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=332: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=333: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=334: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=335: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=336: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=337: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=338: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=339: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=340: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=341: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=342: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=343: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=344: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=345: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=346: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=347: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=348: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=349: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=350: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=351: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=352: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=353: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=354: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=355: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=356: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=357: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=358: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=359: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=360: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=361: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=362: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=363: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=364: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=365: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=366: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=367: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=368: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=369: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=370: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=371: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=372: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=373: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=374: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=375: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=376: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=377: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=378: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=379: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=380: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=381: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=382: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=383: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=384: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=385: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=386: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=387: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=388: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=389: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=390: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=391: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=392: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=393: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=394: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=395: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=396: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=397: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=398: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=399: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=400: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=401: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=402: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=403: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=404: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=405: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=406: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=407: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=408: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=409: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=410: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=411: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=412: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=413: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=414: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=415: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=416: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=417: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=418: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=419: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=420: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=421: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=422: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=423: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=424: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=425: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=426: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=427: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=428: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=429: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=430: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=431: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=432: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=433: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=434: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=435: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=436: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=437: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=438: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=439: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=440: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=441: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=442: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=443: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=444: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=445: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=446: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=447: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=448: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=449: No intervals for
#> data
#> Warning: regimen=Loading + 300 mg QW; period=Day 1; id=450: No intervals for
#> data
nca_res <- as.data.frame(nca$result) |>
mutate(group = paste(regimen, period, sep = ", "))
# Table 4 geometric means, converted to ng*h/mL and ng/mL.
reference <- tribble(
~group, ~auclast, ~cmax, ~ctrough,
"300 mg QW, Day 1", 84400, 6350, 5.62,
"300 mg QOW, Day 1", 85500, 6330, 4.28,
"150 mg QW, Day 1", 42300, 3160, 2.81,
"300 mg QW, Day 449", 89900, 6390, 34.3,
"300 mg QOW, Day 449", 90100, 6340, 14.8,
"Loading + 300 mg QW, Day 449", 90000, 6340, 34.3,
"150 mg QW, Day 449", 45000, 3180, 17.2
) |>
as.data.frame()
cmp <- nlmixr2lib::ncaComparisonTable(
simulated = nca_res |> select(group, PPTESTCD, PPORRES),
reference = reference,
by = "group",
units = c(auclast = "ng*h/mL", cmax = "ng/mL", ctrough = "ng/mL")
)
knitr::kable(cmp, caption = "Simulated median vs Yu 2020 Table 4 geometric mean.")| NCA parameter | group | Reference | Simulated | % diff |
|---|---|---|---|---|
| Cmax (ng/mL) | 300 mg QW, Day 1 | 6350 | 6210 | -2.2% |
| Cmax (ng/mL) | 300 mg QOW, Day 1 | 6330 | 6610 | +4.5% |
| Cmax (ng/mL) | 150 mg QW, Day 1 | 3160 | 3360 | +6.2% |
| Cmax (ng/mL) | 300 mg QW, Day 449 | 6390 | 6250 | -2.2% |
| Cmax (ng/mL) | 300 mg QOW, Day 449 | 6340 | 6630 | +4.5% |
| Cmax (ng/mL) | Loading + 300 mg QW, Day 449 | 6340 | 6340 | -0.0% |
| Cmax (ng/mL) | 150 mg QW, Day 449 | 3180 | 3380 | +6.2% |
| AUClast (ng*h/mL) | 300 mg QW, Day 1 | 84400 | 85600 | +1.5% |
| AUClast (ng*h/mL) | 300 mg QOW, Day 1 | 85500 | 84000 | -1.8% |
| AUClast (ng*h/mL) | 150 mg QW, Day 1 | 42300 | 42900 | +1.4% |
| AUClast (ng*h/mL) | 300 mg QW, Day 449 | 89900 | 90900 | +1.1% |
| AUClast (ng*h/mL) | 300 mg QOW, Day 449 | 90100 | 89000 | -1.2% |
| AUClast (ng*h/mL) | Loading + 300 mg QW, Day 449 | 90000 | 89200 | -0.9% |
| AUClast (ng*h/mL) | 150 mg QW, Day 449 | 45000 | 45800 | +1.9% |
| Ctrough (ng/mL) | 300 mg QW, Day 1 | 5.62 | 5.79 | +3.0% |
| Ctrough (ng/mL) | 300 mg QOW, Day 1 | 4.28 | 4.28 | +0.1% |
| Ctrough (ng/mL) | 150 mg QW, Day 1 | 2.81 | 2.92 | +4.1% |
| Ctrough (ng/mL) | 300 mg QW, Day 449 | 34.3 | 36.1 | +5.2% |
| Ctrough (ng/mL) | 300 mg QOW, Day 449 | 14.8 | 16.2 | +9.7% |
| Ctrough (ng/mL) | Loading + 300 mg QW, Day 449 | 34.3 | 35.4 | +3.3% |
| Ctrough (ng/mL) | 150 mg QW, Day 449 | 17.2 | 19.8 | +14.9% |
if (!is.null(attr(cmp, "footnote"))) cat(attr(cmp, "footnote"))
pct <- as.numeric(gsub("[%*+]", "", cmp[["% diff"]]))
stopifnot(!anyNA(pct), length(pct) == 21L)
stopifnot(
abs(median(pct)) < 10,
quantile(abs(pct), 0.9) < 20
)The simulated medians are compared with the paper’s geometric means; for a log-normally distributed exposure the two coincide. The paper also carried parameter uncertainty (500 replicate datasets) that this simulation does not.
TTR reduction against Table 5
Table 5 and the Results text report the mean percent change from baseline in TTR at days 7, 14, 28, 85 and at steady state. Each simulated patient’s change is computed from their own baseline; the steady-state value is taken at day 449.
days <- c(7, 14, 28, 85, 449)
ttr_chg <- sim |>
group_by(id) |>
mutate(bl = ttr[time == 0][1]) |>
ungroup() |>
filter(time %in% ((days - 1) * 24)) |>
mutate(day = time / 24 + 1, pct = 100 * (ttr / bl - 1)) |>
group_by(regimen, day) |>
summarise(simulated = mean(pct), .groups = "drop")
published <- tribble(
~regimen, ~day, ~paper,
"300 mg QW", 7, -20.2,
"300 mg QW", 14, -34.8,
"300 mg QW", 28, -51.2,
"300 mg QW", 85, -67.9,
"300 mg QW", 449, -71.9,
"Loading + 300 mg QW", 7, -32.5,
"Loading + 300 mg QW", 14, -45.1,
"Loading + 300 mg QW", 28, -57.5,
"Loading + 300 mg QW", 85, -69.0,
"Loading + 300 mg QW", 449, -71.9,
"300 mg QOW", 7, -20.2,
"300 mg QOW", 14, -24.7,
"300 mg QOW", 28, -38.0,
"300 mg QOW", 85, -53.8,
"300 mg QOW", 449, -58.8,
"150 mg QW", 7, -15.9,
"150 mg QW", 14, -28.1,
"150 mg QW", 28, -42.6,
"150 mg QW", 85, -58.6,
"150 mg QW", 449, -63.1
)
ttr_cmp <- published |>
left_join(ttr_chg |> mutate(regimen = as.character(regimen)), by = c("regimen", "day")) |>
mutate(difference = simulated - paper)
ttr_cmp |>
dplyr::rename(
Regimen = regimen,
Day = day,
"Paper (% change)" = paper,
"Simulated (% change)" = simulated,
"Difference (points)" = difference
) |>
knitr::kable(digits = 1, caption = "Mean percent change from baseline TTR.")| Regimen | Day | Paper (% change) | Simulated (% change) | Difference (points) |
|---|---|---|---|---|
| 300 mg QW | 7 | -20.2 | -20.6 | -0.4 |
| 300 mg QW | 14 | -34.8 | -35.7 | -0.9 |
| 300 mg QW | 28 | -51.2 | -52.9 | -1.7 |
| 300 mg QW | 85 | -67.9 | -69.8 | -1.9 |
| 300 mg QW | 449 | -71.9 | -73.9 | -2.0 |
| Loading + 300 mg QW | 7 | -32.5 | -31.2 | 1.3 |
| Loading + 300 mg QW | 14 | -45.1 | -44.9 | 0.2 |
| Loading + 300 mg QW | 28 | -57.5 | -58.0 | -0.5 |
| Loading + 300 mg QW | 85 | -69.0 | -69.6 | -0.6 |
| Loading + 300 mg QW | 449 | -71.9 | -72.5 | -0.6 |
| 300 mg QOW | 7 | -20.2 | -20.3 | -0.1 |
| 300 mg QOW | 14 | -24.7 | -26.6 | -1.9 |
| 300 mg QOW | 28 | -38.0 | -41.0 | -3.0 |
| 300 mg QOW | 85 | -53.8 | -57.7 | -3.9 |
| 300 mg QOW | 449 | -58.8 | -62.9 | -4.1 |
| 150 mg QW | 7 | -15.9 | -18.2 | -2.3 |
| 150 mg QW | 14 | -28.1 | -31.2 | -3.1 |
| 150 mg QW | 28 | -42.6 | -46.2 | -3.6 |
| 150 mg QW | 85 | -58.6 | -62.0 | -3.4 |
| 150 mg QW | 449 | -63.1 | -66.7 | -3.6 |
The simulated reductions track the published ones within a few percentage points at every time and regimen. The largest gaps (about 3-4 points more suppression than published) are in the lower-exposure 300 mg QOW and 150 mg QW arms at later times, where the steep part of the concentration-response curve makes the mean sensitive to the tail of the wide IC50 distribution (omega^2 = 0.953) and to the parameter uncertainty the paper included.
Assumptions and deviations
-
Dose effect on clearance omitted. Table 2 reports a
‘Dose effect * CL’ term (exponential, estimate 3.97) that raised
apparent clearance at the 50 mg phase 1 dose, described only as ‘a
simplified exponential model with dose administered being normalized to
300 mg dose’. The natural reading,
CL * exp(3.97 * (1 - DOSE / 300)), would raise CL/F 7.3-fold at 150 mg and cannot be reconciled with the paper’s own Table 4, where every exposure measure at 150 mg is almost exactly half of that at 300 mg. The functional form cannot be recovered from the publication, so the maintainers omitted the term; the paper itself states the nonlinearity is not clinically relevant at 150-300 mg. The model should not be used below 150 mg. The term is recorded in the model’scovariatesDataExcludedmetadata. - Orientation of the disease-status effects. Only the PD effect’s direction is stated (Table 3 footnote: the shift in baseline TTR of healthy volunteers relative to hATTR patients). The PK effects are read on the same orientation, with hATTR patients as the reference. This reproduces the Table 4 hATTR exposures above (the opposite orientation would lower the simulated AUC by about 10%) and the Discussion’s statement that post hoc CL/F in hATTR patients (3.18 L/h) is close to the typical 3.40 L/h.
-
PD residual error. Table 3 reports proportional
(0.13) and additive (1.35) SDs ‘in log space’ for log-transformed TTR.
An additive SD of 1.35 on the log scale is implausible, so the pair is
read as the usual log-transform-both-sides form of a combined error,
W = sqrt(prop^2 + (add / IPRED)^2), and encoded as combined proportional + additive error on linear TTR in mg/dL. - Unreported covariance. Table 2 reports covariances between Vc and CL and between CL and Vp but not between Vc and Vp; it is set to 0. The resulting 3 x 3 matrix is positive definite.
- LBM formula. The paper does not name the formula used for lean body mass. The virtual cohort draws LBM around the pooled-cohort median (51.6 kg, range 31.3-80.3 kg), which includes the 51 healthy volunteers.
- Table 5 day-85 level for the loading regimen. The TTR level printed for the loading regimen at day 85 (7.39 mg/dL) duplicates the 150 mg QW value and is inconsistent with the -69.0% change printed beside it; the comparison above uses the percent changes only.
-
Day convention. Day
din the paper is taken as(d - 1) * 24h after the first dose, so the last dose falls on day 449 as in Table 4. - Parameter uncertainty and study design. The paper’s simulations drew from the parameter variance-covariance matrix and used the observed covariates of the 151 patients; this vignette uses fixed estimates and a synthetic cohort of 150 patients per arm.
- Errata search. No erratum or correction for Yu 2020 was found in Europe PMC as of September 2026.