Dapagliflozin whole-body PBPK/PD (Nemitz 2026)
Source:vignettes/articles/Nemitz_2026_dapagliflozin_pbpk.Rmd
Nemitz_2026_dapagliflozin_pbpk.RmdThe model
Nemitz_2026_dapagliflozin_pbpk is a whole-body
physiologically based pharmacokinetic/pharmacodynamic model of
dapagliflozin and its major UGT1A9 metabolite
dapagliflozin-3-O-glucuronide (D3OG), built by Nemitz, Elias and Koenig
(2026) from 28 curated clinical studies and distributed by the authors
as SBML under CC-BY.
mod <- rxode2::rxode(readModelDb("Nemitz_2026_dapagliflozin_pbpk"))
length(mod$state)
#> [1] 31The 31 ODEs are organised as a systemic circulation (venous, arterial, portal and hepatic-venous plasma pools, plus lung and a lumped rest-of-body) linking three submodels:
-
Intestine. Oral drug dissolves from the dose
compartment into the gut lumen at
ka_dis_dap; 84% of what leaves the lumen is absorbed into gut plasma and 16% is excreted in feces. - Liver. Carrier-mediated uptake (OAT3-like, reversible Michaelis-Menten), irreversible UGT1A9 glucuronidation to D3OG, and reversible D3OG export.
- Kidney. Uptake of both species, further UGT1A9 conversion at ten times the hepatic rate per litre of tissue, and first-order urinary excretion of each.
The pharmacodynamic layer links kidney plasma dapagliflozin to
urinary glucose excretion (UGE) by an Emax reduction of the renal
threshold for glucose (RTG). Glucose is filtered at rate
GFR and excreted only while fasting plasma glucose exceeds
RTG.
A note on units and dosing. Following the source
SBML, every plasma and tissue state is a concentration in
mmol/L rather than an amount; only depot,
depot_iv (both mg) and the cumulative excretion pools are
amounts. Doses must therefore be given into depot (oral) or
depot_iv (intravenous) and never directly into a plasma or
tissue compartment. Model time is in minutes.
Population
pop <- mod$population
data.frame(Field = names(pop), Value = vapply(pop, as.character, character(1))) |>
knitr::kable(row.names = FALSE)| Field | Value |
|---|---|
| species | human |
| n_subjects | NA |
| n_studies | 28 |
| age_range | adults; not tabulated per study in the source |
| weight_range | study-specific mean bodyweight where reported (Methods Section 2.5); 75 kg reference individual otherwise |
| sex_female_pct | NA |
| disease_state | Healthy volunteers, patients with type 1 or type 2 diabetes mellitus, and subjects with renal or hepatic impairment. Pediatric and animal studies were excluded (Methods Section 2.1). |
| dose_range | Oral 0.001-500 mg single and multiple dose across the curated studies (Table 1); 10 mg is the standard maintenance dose. A single 80 microgram intravenous dose (Boulton 2013) informs the distribution parameters. |
| regions | not reported; the curated studies are international |
| notes | Deterministic typical-individual model. Methods Section 2.2 states explicitly that ‘all simulations were performed deterministically using the optimized parameter set representing the typical (mean) individual’ and that inter-individual variability was NOT included, so no IIV and no residual-error model are reported and none are invented here. Data were curated into PK-DB (study identifiers in Table 1) from a PubMed and PKPDAI search screening 190 records down to 28 studies. Parameters were fitted sequentially - pharmacokinetic first (Table S2), then pharmacodynamic (Table S3) - by weighted least squares over a subset of single-dose data from healthy subjects, type 2 diabetics and subjects with renal impairment, with 100 multi-start local optimisation runs. The authors note that renal threshold for glucose measurements were available from only one study, fecal excretion from one study, and urinary glucose excretion under fed conditions from none, so the pharmacodynamic layer rests on a narrow evidence base. |
The model is deterministic. Methods Section 2.2
states that all simulations used “the optimized parameter set
representing the typical (mean) individual” and that inter-individual
variability was not included, so the model carries no IIV and no
residual-error estimate, and propSd is fixed at zero. Every
simulation below is therefore a single typical individual per scenario
arm.
Source trace
Every value in ini() traces to one of four places: the
manuscript Methods, the Supplementary Materials (equations in Section
S4, optimized parameters in Tables S2 and S3), or the authors’ archived
SBML model, which the manuscript names as the definitive model
source.
| Quantity | Parameters | Source |
|---|---|---|
| Fractional organ volumes (gut, kidney, liver, lung) |
fvgu, fvki, fvli,
fvlu
|
Methods 2.2 (1.71%, 0.44%, 2.10%, 0.76%) |
| Fractional blood-pool volumes |
fvar, fvve, fvpo,
fvhv
|
Archived SBML
FVar/FVve/FVpo/FVhv
(not tabulated in the manuscript) |
| Fractional blood flows |
fqgu, fqh, fqki,
fqlu
|
Methods 2.2 (18.00%, 21.50%, 19.00%, 100%) |
| Cardiac output, hematocrit, blood fraction |
covbw, fcardio, hct,
fblood
|
Archived SBML COBW, f_cardiac_function,
HCT, Fblood
|
| Molecular weights, conversions |
mr_dap, mr_glc, cf_mg_g,
cf_ml_l
|
Archived SBML Mr_dap, KI__Mr_glc,
KI__cf_*
|
| Partition coefficient, tissue distribution |
kp_dap, ftissue_dap
|
Table S2 (25.517; 0.01 L/min) and Methods 2.2 |
| Dissolution, absorption, absorbed fraction |
ka_dis_dap, dapabs_k,
f_dap_abs
|
Table S2 (0.84842; 0.05946); Figure 1B / Section S2 (84% absorbed, 16% feces) |
| Transport Vmax and Km |
dapim_vmax, dapim_km_dap,
d3gex_vmax, d3gex_km_d3og,
d3gim_vmax, d3gim_km_d3og
|
Section S4 liver model (Km 33 and 115 umol/L); Vmax from the archived SBML, held fixed per Methods 2.3 |
| UGT1A9 metabolism |
dap2d3g_vmax, dap2d3g_km_dap,
f_dap2d3g_ki, f_ugt1a9
|
Table S2 (0.01992; 10.0); Methods 2.3 fixes Km at 479 umol/L |
| Renal excretion |
dapex_k, d3gex_k
|
Table S2 (0.01815; 0.45036 min^-1) |
| Pharmacodynamics |
rtg_base, rtg_m_fpg, rtg_e50,
rtg_gamma, rtg_max_inhib
|
Table S3 (8.00; 1.2533; 6.49e-6; 1.036; 0.70673) |
| GFR, reference FPG |
gfr_healthy, fpg_healthy
|
Archived SBML KI__GFR_healthy (100 mL/min),
KI__fpg_healthy (5 mmol/L) |
| Scenario scalars |
RENALFUNC_REL, HEPFUNC_REL,
FED
|
Methods 2.2 (KDIGO, Child-Turcotte-Pugh and prandial mappings) |
Rate laws and ODEs for the intestine, liver, kidney and
pharmacodynamic submodels are Supplementary Section S4. The
whole-body circulation ODEs are not written out anywhere in the
manuscript or supplement and were taken from the archived SBML
(models/dapagliflozin_body_flat.md, Zenodo 10.5281/zenodo.18011516,
v0.9.8 – the version Methods 2.2 names as used for the analysis).
Simulation helper
# Time is in minutes. Observations are placed on the `venous` ODE state; rxode2
# returns every algebraic observable (Cc, Cc_d3og, RTG, UGE, ...) as a column on
# those rows.
sim_dap <- function(dose_mg, hours = 24, wt = 75, fpg = 5,
renal = 1, hep = 1, fed = 0, by_min = 5, route = "depot") {
tmax <- hours * 60
tt <- sort(unique(c(seq(0, min(tmax, 240), by = 2), seq(0, tmax, by = by_min))))
ev <- rbind(
data.frame(time = 0, amt = dose_mg, evid = 1L, cmt = route),
data.frame(time = tt, amt = NA_real_, evid = 0L, cmt = "venous")
)
ev$WT <- wt
ev$FPG <- fpg
ev$RENALFUNC_REL <- renal
ev$HEPFUNC_REL <- hep
ev$FED <- fed
rxode2::rxSolve(mod, ev, returnType = "data.frame",
atol = 1e-12, rtol = 1e-10) |>
as.data.frame() |>
mutate(hour = time / 60)
}Replicating Figure S19 (Sha 2015): the decisive check
Supplementary Figure S19 is the only published figure that shows the model’s plasma, UGE and RTG predictions together, for a 10 mg single oral dose in healthy volunteers. It is the sharpest available test of the transcription.
sha <- sim_dap(10, hours = 25)
sha_long <- bind_rows(
transmute(sha, hour, panel = "dapagliflozin plasma (umol/L)", value = Cc),
transmute(sha, hour, panel = "UGE (g)", value = UGE),
transmute(sha, hour, panel = "RTG (mmol/L)", value = RTG)
) |>
mutate(panel = factor(panel, levels = c("dapagliflozin plasma (umol/L)",
"UGE (g)", "RTG (mmol/L)")))
ggplot(sha_long, aes(hour, value)) +
geom_line(linewidth = 0.8) +
facet_wrap(~panel, scales = "free_y") +
labs(x = "time (h)", y = NULL,
title = "Replicates Supplementary Figure S19 of Nemitz 2026 (Sha 2015, 10 mg PO)") +
theme_bw()
Read off the published panels: plasma peaks near 0.28 umol/L at about 1 h; UGE rises to a plateau near 15 g; and RTG starts at exactly 8.0 mmol/L, falls to roughly 2.5, and recovers to about 6.3 by 25 h.
sha_chk <- c(
cmax_uM = max(sha$Cc),
tmax_h = sha$hour[which.max(sha$Cc)],
rtg_t0 = sha$RTG[1],
rtg_min = min(sha$RTG),
rtg_25h = tail(sha$RTG, 1),
uge_24h = sha$UGE[which.min(abs(sha$hour - 24))]
)
round(sha_chk, 4)
#> cmax_uM tmax_h rtg_t0 rtg_min rtg_25h uge_24h
#> 0.2829 0.8667 8.0000 2.4638 6.1909 15.9835
stopifnot(
abs(sha_chk[["cmax_uM"]] - 0.28) < 0.02,
abs(sha_chk[["tmax_h"]] - 1.0) < 0.25,
# The RTG intercept is the single most diagnostic number in the paper: it is
# exactly rtg_base, so it identifies which published parameter set is in use.
abs(sha_chk[["rtg_t0"]] - 8.0) < 1e-8,
abs(sha_chk[["rtg_min"]] - 2.5) < 0.15,
abs(sha_chk[["rtg_25h"]] - 6.3) < 0.25,
abs(sha_chk[["uge_24h"]] - 15.5) < 1.5
)That RTG(0) = 8.0 is important beyond this figure – see
the Errata, where it settles a conflict between two published parameter
sets.
Mass balance and routes of elimination
The paper states that dapagliflozin is “primarily metabolized by UGT1A9 to D3G, which accounts for the majority of urinary excretion, while unchanged dapagliflozin contributes less than 2%”, and Supplementary Section S2 states that 16% of the dose is excreted in feces.
mb_sim <- sim_dap(10, hours = 168, by_min = 10)
dose_mmol <- 10 / 408.873
mb_end <- tail(mb_sim, 1)
mass_balance <- tibble::tibble(
Route = c("Urine, dapagliflozin (unchanged)", "Urine, D3OG",
"Feces, dapagliflozin", "Total recovered by 168 h"),
`Amount (mmol)` = c(mb_end$Aurine_dap, mb_end$Aurine_d3og, mb_end$Afeces_dap,
mb_end$Aurine_dap + mb_end$Aurine_d3og + mb_end$Afeces_dap),
`Percent of dose` = 100 * c(mb_end$Aurine_dap, mb_end$Aurine_d3og,
mb_end$Afeces_dap,
mb_end$Aurine_dap + mb_end$Aurine_d3og +
mb_end$Afeces_dap) / dose_mmol
)
knitr::kable(mass_balance, digits = c(0, 5, 1))| Route | Amount (mmol) | Percent of dose |
|---|---|---|
| Urine, dapagliflozin (unchanged) | 0.00039 | 1.6 |
| Urine, D3OG | 0.01428 | 58.4 |
| Feces, dapagliflozin | 0.00391 | 16.0 |
| Total recovered by 168 h | 0.01859 | 76.0 |
stopifnot(
# Fecal excretion is structurally 1 - f_dap_abs and must be exactly 16%.
abs(mb_end$Afeces_dap / dose_mmol - 0.16) < 1e-3,
# "unchanged dapagliflozin contributes less than 2%"
mb_end$Aurine_dap / dose_mmol < 0.02,
# D3OG is the majority urinary species.
mb_end$Aurine_d3og > 10 * mb_end$Aurine_dap
)24% of the dose is still unrecovered at 168 h, held in the slowly-equilibrating rest-of-body tissue reservoir. That is a real property of the published parameter set and is discussed in the Errata.
Dose dependency (Figures 2 and 3)
Section 3.3 reports “a clear dose-dependent rise in exposure metrics (AUC and Cmax), while half-lives remained largely unchanged” and, for the pharmacodynamics, “lower RTG and a nonlinear increase in urinary glucose excretion”.
dose_levels <- c(2.5, 10, 20, 50, 100, 250, 500)
dose_sim <- bind_rows(lapply(dose_levels, function(d) {
sim_dap(d, hours = 24) |> mutate(dose_mg = d)
})) |>
mutate(dose = factor(dose_mg, levels = dose_levels))
bind_rows(
transmute(dose_sim, hour, dose, panel = "dapagliflozin plasma (umol/L)", value = Cc),
transmute(dose_sim, hour, dose, panel = "UGE (g)", value = UGE)
) |>
ggplot(aes(hour, value, colour = dose)) +
geom_line(linewidth = 0.7) +
facet_wrap(~panel, scales = "free_y") +
labs(x = "time (h)", y = NULL, colour = "dose (mg)",
title = "Replicates Figures 2B-2C of Nemitz 2026 (oral dose range)") +
theme_bw()
dose_tab <- dose_sim |>
group_by(dose_mg) |>
summarise(
Cmax_uM = max(Cc),
AUC0_24 = sum(diff(hour) * (head(Cc, -1) + tail(Cc, -1)) / 2),
UGE24_g = UGE[which.min(abs(hour - 24))],
RTGmin = min(RTG),
.groups = "drop"
) |>
mutate(
`Cmax / dose` = Cmax_uM / dose_mg,
`AUC / dose` = AUC0_24 / dose_mg
)
dose_tab |>
rename("Dose (mg)" = dose_mg, "Cmax (umol/L)" = Cmax_uM,
"AUC0-24 (umol*h/L)" = AUC0_24, "UGE 24 h (g)" = UGE24_g,
"RTG min (mmol/L)" = RTGmin) |>
knitr::kable(digits = 4)| Dose (mg) | Cmax (umol/L) | AUC0-24 (umol*h/L) | UGE 24 h (g) | RTG min (mmol/L) | Cmax / dose | AUC / dose |
|---|---|---|---|---|---|---|
| 2.5 | 0.0707 | 0.2104 | 8.1559 | 2.8320 | 0.0283 | 0.0842 |
| 10.0 | 0.2829 | 0.8419 | 15.9835 | 2.4638 | 0.0283 | 0.0842 |
| 20.0 | 0.5659 | 1.6840 | 21.1087 | 2.3999 | 0.0283 | 0.0842 |
| 50.0 | 1.4155 | 4.2123 | 42.0950 | 2.3617 | 0.0283 | 0.0842 |
| 100.0 | 2.8335 | 8.4319 | 53.8979 | 2.3487 | 0.0283 | 0.0843 |
| 250.0 | 7.1023 | 21.1352 | 62.5711 | 2.3397 | 0.0284 | 0.0845 |
| 500.0 | 14.2668 | 42.4541 | 65.7601 | 2.3345 | 0.0285 | 0.0849 |
Three structural claims fall out of that table and are asserted directly.
The PK is linear over the clinical range and only mildly supra-proportional above it. Across 2.5-20 mg, dose-normalised Cmax and AUC are constant to better than 0.1%. Extending the scan to 500 mg – a 200-fold range – they rise monotonically, but by less than 1% in total, as liver-tissue concentrations begin to approach the UGT1A9 Km of 479 umol/L. This is consistent with Section 3.3, which reports “a clear dose-dependent rise in exposure metrics (AUC and Cmax)” without claiming strict proportionality.
The PD is strongly saturating, because RTG can be depressed
by at most rtg_max_inhib. The same 200-fold dose increase
that raises exposure 200-fold raises 24 h urinary glucose excretion only
about 8-fold.
The RTG floor is undershot very slightly at the highest
doses. The nominal floor is
rtg_base * (1 - rtg_max_inhib) = 2.346 mmol/L, which the
Emax term could never cross if it were a conventional Hill function
bounded by 1. It is not: the numerator uses kidney plasma and
the denominator kidney tissue dapagliflozin (Errata 4), so the
ratio can exceed 1 slightly and RTG dips about 0.5% below the floor at
500 mg. The assertion pins both the magnitude and the fact that it
happens at all.
clin_tab <- dose_tab[dose_tab$dose_mg <= 20, ]
rtg_floor <- 8 * (1 - 0.70673)
stopifnot(
# Clinical range (2.5-20 mg): dose-normalised exposure constant to <0.1%.
diff(range(clin_tab$`Cmax / dose`)) / mean(clin_tab$`Cmax / dose`) < 1e-3,
diff(range(clin_tab$`AUC / dose`)) / mean(clin_tab$`AUC / dose`) < 1e-3,
# Full 200-fold scan: monotonically supra-proportional, but by under 1%.
all(diff(dose_tab$`Cmax / dose`) > 0),
all(diff(dose_tab$`AUC / dose`) > 0),
diff(range(dose_tab$`Cmax / dose`)) / mean(dose_tab$`Cmax / dose`) < 0.01,
diff(range(dose_tab$`AUC / dose`)) / mean(dose_tab$`AUC / dose`) < 0.01,
# Saturating PD: across a 200-fold dose increase, 24 h UGE rises only about
# 8-fold -- monotone, but nowhere near dose-proportional.
all(diff(dose_tab$UGE24_g) > 0),
dose_tab$UGE24_g[dose_tab$dose_mg == 500] <
10 * dose_tab$UGE24_g[dose_tab$dose_mg == 2.5],
dose_tab$UGE24_g[dose_tab$dose_mg == 500] >
5 * dose_tab$UGE24_g[dose_tab$dose_mg == 2.5],
# RTG approaches its nominal floor and, because of the asymmetric Emax term,
# undershoots it at the top of the range -- by less than 1%.
all(dose_tab$RTGmin < 8),
min(dose_tab$RTGmin) < rtg_floor,
all(dose_tab$RTGmin > rtg_floor * 0.99)
)Renal impairment (Figure 4)
Methods 2.2 maps KDIGO categories onto the relative-renal-function
scalar. The RENALFUNC_REL column carries it with the
register’s fraction-of-normal orientation.
renal_levels <- c(normal = 1.00, mild = 0.69, moderate = 0.32, severe = 0.19)
renal_sim <- bind_rows(lapply(names(renal_levels), function(k) {
sim_dap(20, hours = 24, renal = renal_levels[[k]]) |>
mutate(renal = factor(k, levels = names(renal_levels)))
}))
bind_rows(
transmute(renal_sim, hour, renal, panel = "dapagliflozin plasma (umol/L)", value = Cc),
transmute(renal_sim, hour, renal, panel = "D3OG plasma (umol/L)", value = Cc_d3og),
transmute(renal_sim, hour, renal, panel = "UGE (g)", value = UGE)
) |>
ggplot(aes(hour, value, colour = renal)) +
geom_line(linewidth = 0.7) +
facet_wrap(~panel, scales = "free_y") +
labs(x = "time (h)", y = NULL, colour = "renal function",
title = "Replicates Figures 4B-4C of Nemitz 2026 (20 mg PO)") +
theme_bw()
renal_tab <- renal_sim |>
group_by(renal) |>
summarise(Cmax_dap = max(Cc), Cmax_d3og = max(Cc_d3og),
UGE24 = UGE[which.min(abs(hour - 24))], .groups = "drop") |>
mutate(
`Parent Cmax (% of normal)` = 100 * Cmax_dap / Cmax_dap[renal == "normal"],
`D3OG Cmax (% of normal)` = 100 * Cmax_d3og / Cmax_d3og[renal == "normal"],
`UGE 24 h (% of normal)` = 100 * UGE24 / UGE24[renal == "normal"]
)
renal_tab |>
select(renal, `Parent Cmax (% of normal)`, `D3OG Cmax (% of normal)`,
`UGE 24 h (% of normal)`) |>
rename("Renal function" = renal) |>
knitr::kable(digits = 1)| Renal function | Parent Cmax (% of normal) | D3OG Cmax (% of normal) | UGE 24 h (% of normal) |
|---|---|---|---|
| normal | 100.0 | 100.0 | 100.0 |
| mild | 105.8 | 111.6 | 82.2 |
| moderate | 114.2 | 134.8 | 50.3 |
| severe | 117.7 | 150.5 | 32.5 |
The Abstract states that “renal impairment reduced UGE by 40-60% despite modest changes in plasma exposure”, and Section 3.4 that “plasma concentrations of dapagliflozin were minimally affected by renal dysfunction, whereas exposure to its main metabolite (D3G) increased with declining renal function”.
uge_pct <- setNames(renal_tab$`UGE 24 h (% of normal)`, renal_tab$renal)
par_pct <- setNames(renal_tab$`Parent Cmax (% of normal)`, renal_tab$renal)
met_pct <- setNames(renal_tab$`D3OG Cmax (% of normal)`, renal_tab$renal)
stopifnot(
# Monotone loss of glucosuria with declining renal function.
all(diff(uge_pct) < 0),
# The paper's headline 40-60% reduction band is spanned by the impaired groups.
100 - uge_pct[["moderate"]] > 40, 100 - uge_pct[["moderate"]] < 60,
100 - uge_pct[["severe"]] > 40,
# Parent exposure only modestly affected; metabolite strongly accumulating.
all(par_pct < 125),
met_pct[["severe"]] > 140,
met_pct[["severe"]] > par_pct[["severe"]]
)Hepatic impairment (Figure 5)
HEPFUNC_REL is the fraction-of-normal liver function, so
the source’s cirrhosis severity is its complement: Child-Turcotte-Pugh
A, B and C map to f_cirrhosis 0.40, 0.70 and 0.80,
i.e. HEPFUNC_REL 0.60, 0.30 and 0.20.
hep_levels <- c(normal = 1.0, `CTP A` = 0.6, `CTP B` = 0.3, `CTP C` = 0.2)
hep_tab <- bind_rows(lapply(names(hep_levels), function(k) {
sim_dap(10, hours = 24, hep = hep_levels[[k]]) |>
summarise(liver = k, Cmax_dap = max(Cc), Cmax_d3og = max(Cc_d3og),
UGE24 = UGE[which.min(abs(hour - 24))])
})) |>
mutate(
liver = factor(liver, levels = names(hep_levels)),
`Parent Cmax (% of normal)` = 100 * Cmax_dap / Cmax_dap[liver == "normal"],
`D3OG Cmax (% of normal)` = 100 * Cmax_d3og / Cmax_d3og[liver == "normal"],
`UGE 24 h (% of normal)` = 100 * UGE24 / UGE24[liver == "normal"]
)
hep_tab |>
select(liver, `Parent Cmax (% of normal)`, `D3OG Cmax (% of normal)`,
`UGE 24 h (% of normal)`) |>
rename("Liver function" = liver) |>
knitr::kable(digits = 1)| Liver function | Parent Cmax (% of normal) | D3OG Cmax (% of normal) | UGE 24 h (% of normal) |
|---|---|---|---|
| normal | 100.0 | 100.0 | 100.0 |
| CTP A | 106.7 | 90.5 | 104.5 |
| CTP B | 112.1 | 82.1 | 108.4 |
| CTP C | 114.0 | 79.0 | 109.8 |
Section 3.5: “with increasing severity, dapagliflozin plasma exposure showed small increases in Cmax and AUC, whereas exposure of the main metabolite D3G showed small decreases”, and “UGE increased slightly with cirrhosis severity”.
hp <- setNames(hep_tab$`Parent Cmax (% of normal)`, hep_tab$liver)
hm <- setNames(hep_tab$`D3OG Cmax (% of normal)`, hep_tab$liver)
hu <- setNames(hep_tab$`UGE 24 h (% of normal)`, hep_tab$liver)
stopifnot(
all(diff(hp) > 0), all(diff(hm) < 0), all(diff(hu) > 0), # directions
all(hp <= 120), all(hm >= 75), all(hu <= 115) # all "small"
)Both effects have the same mechanistic origin: cirrhosis removes functional liver parenchyma and shunts blood past the liver, so less parent is converted to D3OG. Parent rises slightly and metabolite falls slightly; the small rise in UGE follows the small rise in parent exposure.
Food effect (Figure 6)
Fed dosing scales the intestinal absorption rate to 30% of fasted.
food_sim <- bind_rows(
sim_dap(10, hours = 24, fed = 0) |> mutate(state = "fasted"),
sim_dap(10, hours = 24, fed = 1) |> mutate(state = "fed")
)
ggplot(food_sim, aes(hour, Cc, colour = state)) +
geom_line(linewidth = 0.8) +
labs(x = "time (h)", y = "dapagliflozin plasma (umol/L)", colour = NULL,
title = "Replicates Figure 6B of Nemitz 2026 (10 mg PO, fasted vs fed)") +
theme_bw()
trapz <- function(x, y) sum(diff(x) * (head(y, -1) + tail(y, -1)) / 2)
food_tab <- food_sim |>
group_by(state) |>
summarise(Cmax = max(Cc), tmax_h = hour[which.max(Cc)],
AUC0_24 = trapz(hour, Cc), .groups = "drop")
food_tab |>
rename("Prandial state" = state, "Cmax (umol/L)" = Cmax,
"tmax (h)" = tmax_h, "AUC0-24 (umol*h/L)" = AUC0_24) |>
knitr::kable(digits = 4)| Prandial state | Cmax (umol/L) | tmax (h) | AUC0-24 (umol*h/L) |
|---|---|---|---|
| fasted | 0.2829 | 0.8667 | 0.8419 |
| fed | 0.2057 | 1.5333 | 0.8395 |
cmax_change <- 100 * (food_tab$Cmax[food_tab$state == "fed"] /
food_tab$Cmax[food_tab$state == "fasted"] - 1)
auc_change <- 100 * (food_tab$AUC0_24[food_tab$state == "fed"] /
food_tab$AUC0_24[food_tab$state == "fasted"] - 1)
round(c(`Cmax change (%)` = cmax_change, `AUC change (%)` = auc_change), 2)
#> Cmax change (%) AUC change (%)
#> -27.29 -0.27
stopifnot(
# "changes in absorption predominantly affected Cmax, while AUC and half-life
# remained almost unchanged" (Section 3.6)
cmax_change < -20, cmax_change > -45,
abs(auc_change) < 1,
# tmax is delayed in the fed state
food_tab$tmax_h[food_tab$state == "fed"] >
food_tab$tmax_h[food_tab$state == "fasted"]
)The model predicts a 27% fall in Cmax. The paper reports that the clinical studies it curated showed a 30-50% reduction (Section 3.6), so the model sits just below the observed band; the qualitative finding – Cmax down, AUC unchanged, tmax delayed – is reproduced.
Non-compartmental analysis
PKNCA is run on three clinically studied dose levels spanning the range over which the model is linear, to confirm exposure proportionality independently of the trapezoidal summary above.
nca_doses <- c(2.5, 10, 20)
nca_sim <- bind_rows(lapply(seq_along(nca_doses), function(i) {
sim_dap(nca_doses[i], hours = 48, by_min = 10) |>
mutate(id = i, dose_mg = nca_doses[i])
}))
conc_data <- nca_sim |>
filter(!is.na(Cc)) |>
select(id, hour, Cc, dose_mg)
dose_data <- conc_data |>
group_by(id, dose_mg) |>
slice(1) |>
mutate(hour = 0) |>
ungroup() |>
as.data.frame()
o_conc <- PKNCA::PKNCAconc(as.data.frame(conc_data), Cc ~ hour | id / dose_mg)
o_dose <- PKNCA::PKNCAdose(dose_data, dose_mg ~ hour | id)
nca_res <- PKNCA::pk.nca(PKNCA::PKNCAdata(
o_conc, o_dose,
intervals = data.frame(start = 0, end = 24,
cmax = TRUE, tmax = TRUE, auclast = TRUE)
))
nca_tab <- as.data.frame(nca_res$result) |>
select(dose_mg, PPTESTCD, PPORRES) |>
tidyr::pivot_wider(names_from = PPTESTCD, values_from = PPORRES) |>
mutate(`Cmax / dose` = cmax / dose_mg, `AUClast / dose` = auclast / dose_mg)
nca_tab |>
rename("Dose (mg)" = dose_mg, "Cmax (umol/L)" = cmax, "tmax (h)" = tmax,
"AUClast 0-24 (umol*h/L)" = auclast) |>
knitr::kable(digits = 4)| Dose (mg) | AUClast 0-24 (umol*h/L) | Cmax (umol/L) | tmax (h) | Cmax / dose | AUClast / dose |
|---|---|---|---|---|---|
| 2.5 | 0.2104 | 0.0707 | 0.8667 | 0.0283 | 0.0842 |
| 10.0 | 0.8418 | 0.2829 | 0.8667 | 0.0283 | 0.0842 |
| 20.0 | 1.6840 | 0.5659 | 0.8667 | 0.0283 | 0.0842 |
stopifnot(
diff(range(nca_tab$`Cmax / dose`)) / mean(nca_tab$`Cmax / dose`) < 1e-3,
diff(range(nca_tab$`AUClast / dose`)) / mean(nca_tab$`AUClast / dose`) < 1e-3,
# "It is rapidly absorbed, reaching peak plasma concentrations within 2 h"
all(nca_tab$tmax <= 2)
)Half-life
The half-life deserves its own comment, because the model’s terminal phase is much slower than dapagliflozin’s reported clinical half-life.
hl_sim <- sim_dap(10, hours = 168, by_min = 10)
apparent_hl <- function(lo, hi) {
d <- dplyr::filter(hl_sim, hour >= lo, hour <= hi, Cc > 0)
log(2) / -coef(stats::lm(log(Cc) ~ hour, data = d))[[2]]
}
hl_tab <- tibble::tibble(
`Window (h)` = c("2-12", "4-24", "24-72", "72-168"),
`Apparent half-life (h)` = c(apparent_hl(2, 12), apparent_hl(4, 24),
apparent_hl(24, 72), apparent_hl(72, 168))
)
knitr::kable(hl_tab, digits = 2)| Window (h) | Apparent half-life (h) |
|---|---|
| 2-12 | 1.62 |
| 4-24 | 6.33 |
| 24-72 | 179.82 |
| 72-168 | 179.83 |
stopifnot(
# The profile is multi-phasic: the late phase is an order of magnitude slower.
hl_tab$`Apparent half-life (h)`[4] > 10 * hl_tab$`Apparent half-life (h)`[1],
# And it is dose-independent, as Section 3.3 reports.
abs(apparent_hl(72, 168) -
(function() {
s <- sim_dap(100, hours = 168, by_min = 10)
d <- dplyr::filter(s, hour >= 72, hour <= 168, Cc > 0)
log(2) / -coef(stats::lm(log(Cc) ~ hour, data = d))[[2]]
})()) < 0.1
)Over the first half-day the profile decays with an apparent half-life of about 1.6 h, consistent with the published figures in which dapagliflozin “returned to baseline within approximately 10 h” (Section 3.3). The terminal phase, however, is roughly 180 h. See the Errata.
Assumptions and deviations / Errata
1. The published pharmacodynamic parameters and the archived model disagree, and the paper’s own figure settles it. Supplementary Table S3 and the authors’ archived SBML (v0.9.8 and v0.9.9, the only released versions) report different PD parameter sets:
| Parameter | Table S3 (used here) | Archived SBML v0.9.8 |
|---|---|---|
KI__RTG_base |
8.00 | 7.0019 |
KI__RTG_m_fpg |
1.2533 | 1.6532 |
KI__RTG_E50 |
6.49e-6 | 9.3838e-6 |
KI__RTG_max_inhibition |
0.70673 | 0.712547 |
KI__RTG_gamma |
1.036 | 1.03599 |
The two are separate fits: Table S3 states optimisation bounds of [8,
14] for RTG_base and [0.2, 3] for m_fpg,
whereas the archived fitting/parameters.py uses [7, 14] and
[0.05, 2]. In each case RTG_base was driven to its own
lower bound, so that parameter is rail-limited and only weakly
identified. The two sets are close to degenerate over the fitted range –
RTG_base + m_fpg * (FPG - 5) gives 11.13 for both
at the diabetic anchor FPG = 7.5 mmol/L – and differ mainly at healthy
glucose, where the baseline RTG is 8.00 versus 7.00.
This model uses Table S3, because the paper’s own
Supplementary Figure S19 plots RTG in a healthy subject starting at
exactly 8.0 mmol/L, which is RTG_base from Table S3 and not
the archived 7.00. The published figures were therefore generated with
the Table S3 set, and the public archive is stale with respect to the
publication. The rtg_t0 assertion above pins this. The
PK parameters (Table S2) agree exactly with the archive
to full precision, so no such choice arises there.
2. The whole-body circulation ODEs are not in the paper. Supplementary Section S4 writes out only the intestine, liver, kidney and PD submodels. The venous/arterial/portal/hepatic-vein/lung/rest-of-body balance equations, the blood-pool volume corrections, and the cirrhotic shunting terms were taken from the authors’ archived SBML, which Methods 2.2 and Section S4 both name as the definitive model source. No parameter or equation here comes from a training-data default or a generic PBPK template.
3. Two transcription errors in the Supplementary equations, corrected here.
- The kidney model prints
DAPEX = f_renal * D3GEX_k * V_ki * d3g_extandD3GEX = f_renal * DAPEX_k * V_ki * dap_ext– the two rate constants and the two species are transposed. The archived SBML pairs each species with its own rate constant, which is the only physically sensible reading, and that is what is implemented. - The intestine model prints
absorption = F_dap,abs * absorptionandDAPABS = (1 - F_dap,abs) * absorption, which are circular and duplicate the left-hand nameDAPABS. The archived SBML resolves these toDAPABS = F_dap_abs * absorptionandDAPEXC = (1 - F_dap_abs) * absorption.
4. The RTG Emax term is asymmetric, and this is reproduced faithfully. In both the Supplementary equation and the archived SBML, the numerator is driven by kidney plasma dapagliflozin while the denominator uses kidney tissue dapagliflozin. Because both sources agree, this is implemented as published rather than silently symmetrised. It is almost certainly an authoring slip, but it is nearly harmless: transport into kidney tissue was deliberately made fast and non-rate-limiting (Methods 2.3), so the two states track each other closely and the term behaves like a conventional Hill function.
It is not entirely harmless, and the dose scan above
quantifies the one visible consequence. A symmetric Hill term is bounded
by 1, so RTG could never fall below
rtg_base * (1 - rtg_max_inhib) = 2.346 mmol/L. Because the
numerator and denominator use different states, the ratio can exceed 1
slightly whenever kidney plasma runs above kidney tissue, and the
simulated RTG minimum does dip below that floor – to 2.334 mmol/L at 500
mg, an undershoot of about 0.5%. The effect is negligible at clinical
doses and is asserted, not corrected, because both published sources
specify the asymmetric form.
5. A numerical guard was added to the RTG powers.
kidney and kidney_plasma both start at exactly
zero and are raised to the non-integer Hill exponent 1.036. A solver
step that takes either infinitesimally negative makes
(-1e-20)^1.036 NaN, which propagates to every state from
the first step. Without a guard the model fails to solve at all for
moderate and severe cirrhosis. Both bases are therefore clamped at zero,
which is a no-op for physically meaningful (non-negative)
concentrations.
6. ftissue_dap is rail-limited, giving a very
long terminal phase. Table S2 reports
ftissue_dap = 0.01 L/min against a lower optimisation bound
of exactly 0.01 – the optimiser drove it to the floor. Combined with the
large fitted partition coefficient (Kp_dap = 25.5), this
creates a deep, slowly-exchanging rest-of-body reservoir and a terminal
half-life near 180 h, well beyond dapagliflozin’s reported clinical
half-life of about 13 h, with 24% of the dose still unexcreted at 168 h.
Over the 0-24 h window the studies actually observed, the profile decays
with an apparent half-life of about 2 h and matches the published
figures. Users simulating beyond about 48 h, or accumulation over
repeated dosing, should be aware that the terminal phase is not
clinically identified.
7. Table S3 tabulates KI__RTG_gamma with unit
“mM”. A Hill coefficient is dimensionless, and the archived
SBML annotates it as such; the unit column is a typographical error and
does not affect the value.
8. Body surface area is computed but unused. The archived SBML derives BSA from body weight and height, but BSA enters no rate law. Height is therefore not carried as a covariate and BSA is not implemented.
9. The stomach state is inert and omitted. The
archived SBML carries GU__dap_stomach with a zero
derivative and a zero initial condition, so it is identically zero and
unobservable; it is not implemented.
10. Fasting plasma glucose is a covariate rather than a
state. In the archived SBML, plasma glucose is a state with a
zero derivative – exactly a constant input. It is carried here as the
canonical FPG covariate column, which is equivalent and
additionally allows a time-varying glucose profile to be supplied.
Methods 2.3 assumes 5 mmol/L for healthy subjects and 7.5 mmol/L for
both type 1 and type 2 diabetes when a study did not report it.
11. No inter-individual variability or residual
error. Methods 2.2 states these were not estimated, so
propSd is fixed at zero and there are no etas. The model
produces typical-individual trajectories only and cannot generate a VPC
or individual predictions.
12. Evidence base for the PD layer is narrow. The Discussion notes that RTG measurements come from a single study, fecal excretion from a single study, and UGE under fed conditions from none. The food effect on UGE that Figure 6C predicts is therefore an extrapolation with no supporting clinical data.
13. The published model under-predicts observed UGE by roughly a factor of two. This is a property of the source model, not of the transcription, and it is visible in the authors’ own Supplementary Figure S19: against the Sha 2015 data (10 mg PO, n = 54) the simulated UGE plateaus near 15 g by 8 h while the observed values keep rising to about 40 g at 24 h, and the simulated RTG recovers to about 6.3 mmol/L against an observed 4.7. The plasma PK in the same figure matches well over the first two hours. The assertions in the Figure S19 section above therefore pin the model’s predictions – reproducing the published curves, which is what a transcription check can establish – and not agreement with the clinical observations. Anyone using this model for quantitative UGE predictions should calibrate against their own data first; the narrow PD evidence base in item 12 is the likely reason for the gap.