Diagnostic Plots
OpenPKPD includes a plots subpackage with publication-quality diagnostic
figures. All plots require matplotlib.
Installation
uv add "OpenPKPD[plots]"
# or
pip install "OpenPKPD[plots]"
Quick example
from openpkpd.plots.diagnostics import compute_diagnostics
from openpkpd.plots.gof import diagnostic_panel
from openpkpd.plots.pk import spaghetti_plot
from openpkpd.plots.eta import eta_histograms
# Run your model first
result = built.fit()
# Build the diagnostics DataFrame (all residuals, predictions, ETAs)
diag_df = compute_diagnostics(built.population_model, result)
# 2×3 GOF panel
fig = diagnostic_panel(diag_df, title="Theophylline FOCE — GOF")
fig.savefig("gof_panel.png", dpi=150)
# Individual concentration-time profiles
fig2 = spaghetti_plot(diag_df, log_y=True)
# ETA histograms with N(0, ω) overlay
fig3 = eta_histograms(diag_df, result.omega_final)
compute_diagnostics()
All plot functions take a diagnostics DataFrame produced by:
from openpkpd.plots.diagnostics import compute_diagnostics
diag_df = compute_diagnostics(population_model, result)
Columns returned
Column |
Description |
|---|---|
|
Subject identifier |
|
Observation time |
|
Observed value |
|
Population prediction (ETA = 0) |
|
Individual prediction (ETA = η̂ᵢ) |
|
|
|
|
|
Weighted residual |
|
Individual weighted residual |
|
Conditional weighted residual (via C_i Cholesky) |
|
Subject-level empirical Bayes estimates |
Any covariates |
Passed through from the dataset |
Only rows with EVID=0 and MDV=0 are included.
GOF plots (plots/gof.py)
diagnostic_panel()
Combined 2×3 panel of all six GOF plots:
from openpkpd.plots.gof import diagnostic_panel
fig = diagnostic_panel(diag_df, title="Model 1 — GOF", figsize=(14, 10))
fig.savefig("gof.png", dpi=150)
Individual GOF functions
from openpkpd.plots.gof import (
dv_vs_ipred, dv_vs_pred,
cwres_vs_time, cwres_vs_pred,
cwres_qq, abs_iwres_vs_ipred,
)
fig = dv_vs_ipred(diag_df, log_scale=True)
fig = cwres_vs_time(diag_df)
fig = cwres_qq(diag_df)
All six functions accept an optional ax argument and return a
matplotlib.figure.Figure.
PK plots (plots/pk.py)
from openpkpd.plots.pk import concentration_time, spaghetti_plot, mean_profile
# Individual profiles (one line per subject)
fig = spaghetti_plot(diag_df, log_y=True, mean_overlay=True)
# Single subject
fig = concentration_time(diag_df, individual=3, log_y=True)
# Population mean ± SD band
fig = mean_profile(diag_df, log_y=False, sd_band=True)
PD plots (plots/pd.py)
from openpkpd.plots.pd import (
effect_time, emax_curve, hysteresis_loop, pd_individual
)
# Effect vs time
fig = effect_time(diag_df, effect_col="DV")
# E vs C with Emax overlay
fig = emax_curve(diag_df, conc_col="IPRED", effect_col="DV",
emax=result.theta_final[4],
ec50=result.theta_final[5])
# Clockwise/counter-clockwise hysteresis loop
fig = hysteresis_loop(diag_df, conc_col="IPRED", effect_col="DV",
color_by_time=True)
# Per-subject dual PK + PD panels
fig = pd_individual(diag_df, conc_col="IPRED", effect_col="DV", n_cols=3)
ETA plots (plots/eta.py)
from openpkpd.plots.eta import eta_histograms, eta_pairs, eta_vs_covariate
# Histograms with N(0, ω_kk) overlay
fig = eta_histograms(diag_df, result.omega_final, overlay_normal=True)
# Pairwise scatter matrix
fig = eta_pairs(diag_df)
# ETA vs continuous covariate
fig = eta_vs_covariate(diag_df, covariate="WT", eta_col="ETA1")
# ETA vs categorical covariate (box plots)
fig = eta_vs_covariate(diag_df, covariate="SEX", eta_col="ETA1",
categorical=True)
Model performance plots (plots/model_perf.py)
from openpkpd.plots.model_perf import ofv_history, vpc
# OFV convergence plot
fig = ofv_history(result, log_scale=False)
# Visual predictive check (Monte Carlo simulation)
fig = vpc(diag_df, built.population_model, result, n_sim=200,
percentiles=(5, 50, 95))
NCA plots (plots/nca.py)
from openpkpd.plots.nca import (
nca_distributions,
nca_boxplot,
nca_profile_plot,
dose_proportionality_plot,
)
Function |
Input |
Description |
|---|---|---|
|
NCA results DataFrame |
Histogram grid for C0 (when available), Cmax, AUC_last, AUC_inf, t½, CL/F, Vz/F |
|
NCA results DataFrame |
Side-by-side boxplots, optional |
|
Raw C-t data + |
C-t profile annotated with Cmax, Tmax, AUC fill, and λz regression |
|
NCA results DataFrame with dose column |
Log-log exposure vs dose with slope-1 reference and power model |
See the NCA guide for detailed usage examples.
Saving figures
Every plot function returns a matplotlib.figure.Figure. Save with:
fig.savefig("plot.png", dpi=150, bbox_inches="tight")
fig.savefig("plot.pdf") # Vector PDF for publication
Style
All plots use a consistent IBM colorblind-friendly palette and minimal grid style. To override:
import matplotlib.pyplot as plt
with plt.rc_context({"figure.figsize": (8, 6)}):
fig = diagnostic_panel(diag_df)