
Pharmacokinetic Modelling
FreeAdvanced tools for PK and PD analysis and simulation.
Free · Opens the source repo
What Pharmacokinetic Modelling does
The Pharmacokinetic and Pharmacodynamic Modelling skill provides a suite of scripts designed for analyzing and simulating pharmacokinetic (PK) and pharmacodynamic (PD) data. It is particularly useful for researchers and professionals in pharmaceutical sciences, clinical pharmacology, and related fields who require accurate modeling of drug behavior in the body. The skill supports various analyses, including non-compartmental analysis (NCA), compartmental fitting, population PK analysis, and regimen simulations. Each script is tailored to answer specific questions about drug exposure and effect, allowing users to derive meaningful insights from concentration-time data.
Included scripts cover a range of functionalities: from calculating exposure metrics with nca.py, to fitting structural models with fit_compartmental.py, and simulating dosing regimens with simulate_regimen.py. Users can also check dataset compatibility with NONMEM using check_popk_dataset.py, and assess exposure-response relationships with exposure_response.py. The skill emphasizes the importance of proper model selection and data integrity, ensuring that users can trust their results while avoiding common pitfalls in pharmacometric analysis.
While the skill provides robust analytical tools, it is important to note that it does not make clinical decisions or replace the expertise of qualified professionals. It serves as a modeling aid, reporting findings without concluding on bioequivalence or dosing recommendations. This makes it an essential resource for those looking to enhance their analytical capabilities without stepping outside their professional boundaries.
When to use it
Use this skill when you need to analyze concentration-time data, derive exposure metrics, or simulate dosing regimens in pharmacokinetic studies.
When not to use it
This skill is not suitable for making clinical decisions or substituting for expert pharmacometricians in regulatory contexts.
What you can build with it
Analyzing Drug Concentration Data
Use `nca.py` to compute exposure metrics from concentration-time data and ensure accurate reporting.
Fitting Compartmental Models
Employ `fit_compartmental.py` to identify the best structural model for your data and assess parameter identifiability.
Simulating Dosing Regimens
Utilize `simulate_regimen.py` to predict the pharmacokinetic profile of a drug under various dosing scenarios.
How to install Pharmacokinetic Modelling
View source1. Install with the skills CLI
npx skills add k-dense-ai/scientific-agent-skills/pkpd-modeling --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by k-dense-aiPharmacokinetic and Pharmacodynamic Modelling
When to use
Any question about what the body does to a drug or what the drug does to the body: deriving exposure metrics from concentration-time data, fitting a structural model, building or checking a population analysis, choosing a dose or a regimen, relating exposure to effect, comparing formulations, or scaling to a new population.
The three rules
1. Fix the exposure metric and the analysis population before computing anything. AUC(0-t), AUC(0-inf), AUC(0-tau) at steady state, and Cavg are different quantities and answer different questions. So do AUCinf based on observed versus predicted Clast. Choosing after seeing the numbers is how a negative study becomes positive.
2. Structural model, variability model, and covariate model are three separate decisions. They get conflated constantly — an extra compartment added to absorb what is really unmodelled between-occasion variability, a covariate added to fix what is really a misspecified absorption model. Diagnose which one is wrong before changing any of them.
3. Convergence is not identifiability. A fit that converges with 200% relative standard error on a parameter, or a correlation of 0.99 between two, has told you the data cannot separate them. Every fitting script here reports both and flags them, because the parameter table alone looks fine in exactly this situation.
Scope
This skill computes, diagnoses, and structures. It does not decide that a formulation is
bioequivalent, select a dose for a trial, recommend a dose for a patient, conclude that a drug has
no QT liability, or replace a qualified pharmacometrician, clinical pharmacologist, or the
regulatory review. The scripts report; none of them concludes. tdm_bayes.py in particular is a
modelling aid — any change to a patient's regimen is the treating clinician's decision.
Scripts
cd skills/pkpd-modeling/scripts
| Script | Question answered |
|---|---|
nca.py | What are the exposure metrics, and is the terminal phase good enough to report them? |
fit_compartmental.py | Which structural model do these data support, and are its parameters identifiable? |
simulate_regimen.py | What does this regimen do at steady state, and to what fraction of the population? |
check_popk_dataset.py | Will NONMEM read this dataset the way I think it will? |
exposure_response.py | Is there an exposure-response relationship, and is the plateau in the data? |
bioequivalence.py | Does the 90% CI meet the criterion, and which criterion applies? |
allometry_and_fih.py | What is the starting dose, or the dose in a smaller/younger population? |
ddi_static.py | Does the in vitro data trigger a clinical DDI study under ICH M12? |
tdm_bayes.py | What are this patient's individual parameters from their measured levels? |
All take --format table|tsv|json. Data goes to stdout, provenance and findings to stderr, so
> out.tsv keeps them separate. Exit code is 0 for no findings, 1 when findings were raised,
2 for bad input, so any of them can gate a workflow.
Two private modules carry the shared machinery: _models.py (analytical solutions for linear
mammillary models, plus integrated Michaelis-Menten, TMDD and indirect-response structures) and
_common.py (I/O and reporting). Import them rather than re-deriving a Bateman function.
Workflow
1. Non-compartmental analysis
python3 nca.py -i profile.csv --dose 100 --route extravascular --partial-auc 0-24
Four choices decide the answer and are usually left implicit. This script makes all four explicit:
--auc-method (default linup-logdown), --blq-rule, --lambda-z-points or an explicit
--lambda-z-window, and whether you report auc_inf_obs or auc_inf_pred.
Lambda_z selection uses the standard rule: start from the last three quantifiable points, extend backwards, keep the longer window only if adjusted r-squared improves by more than 0.0001. Plain r-squared can only rise as points are added, so it would always pick the longest window. Points at or before Tmax are never eligible — including Tmax fits the tail of absorption and biases half-life, Vz and AUCinf downward.
On a noiseless simulated one-compartment oral profile with CL/F = 5, V/F = 20, ka = 1.2:
id cmax tmax auc_last lambda_z t_half r2_adj auc_inf_obs pct_auc_extrap cl_f vz_f
1 3.29678 1.5 19.8737 0.25 2.77259 1 19.8739 0.000781037 5.03173 20.1269
The 0.6% overestimate of CL/F is the trapezoidal rule on a sparsely sampled absorption phase, not an error — it is the irreducible bias of NCA on that sampling schedule, and it is why NCA and compartmental estimates of clearance never agree exactly.
The findings are the point. A steady-state profile truncated at tau produces:
finding: subject A: 25.2% of AUCinf is extrapolated (above 20%); AUCinf is driven by the
lambda_z fit, not by data
finding: subject A: lambda_z window spans 0.58 half-lives (below 2.0); the terminal phase may
not have been reached
Both are correct and both are routinely ignored. At steady state the reportable exposure metric is AUC(0-tau), not AUCinf; the script computes AUCinf anyway and tells you not to trust it.
2. Compartmental fitting and model selection
python3 fit_compartmental.py -i profile.csv --dose 500 --route iv-bolus --compare 1cmt,2cmt,3cmt
Parameters are estimated on the log scale, so they cannot go negative and their confidence
intervals come out asymmetric. Weighting defaults to 1/y2 (constant CV), which is the right
default for PK and the wrong one for a homoscedastic PD endpoint.
Fitting simulated two-compartment data (CL 4, V1 12, Q 6, V2 40, 8% proportional error):
model parameters wssr aic bic f_vs_simpler f_p_value compared_with
1cmt 2 3.13201 -19.4956 -18.0795 n/a n/a n/a
2cmt 4 0.0309579 -84.7477 -81.9155 550.936 9.38016e-12 1cmt
3cmt 6 0.0232859 -85.0193 -80.771 1.4826 0.27762 2cmt
AIC picks the three-compartment model. BIC and the F test both reject it. AIC's fixed penalty of 2 per parameter is weak at this sample size, and it selects the overparameterised model more often than practitioners expect. The parameter table settles it:
finding: fit: Q3 has 98% RSE - not estimable from these data at this model size
finding: fit: V3 has 71% RSE - not estimable from these data at this model size
The one-compartment fit meanwhile earns:
finding: fit: residual signs are not random (runs test p = 0.0036) - a structural
misspecification, which no amount of reweighting will fix
That distinction — structural misspecification versus a wrong error model — is the one to get right. A residual-versus-time plot with runs of the same sign means the model shape is wrong. Heteroscedastic residuals with random signs mean the weighting is wrong. Reweighting the first case hides it without fixing it.
3. Population PK
Check the dataset before running anything. This is where the time actually goes.
python3 check_popk_dataset.py -i nmdata.csv --covariates WT,CRCL --time-varying WT
The defects that matter are the silent ones. NM-TRAN does not reject a non-numeric DV — it reads
BLQ as zero and fits it as a genuine zero concentration. A blank covariate becomes 0, so a
missing body weight becomes a 0 kg patient. ADDL without II places no additional doses.
Records sharing a timestamp are applied in file order, so whether a level is pre- or post-dose
depends on which row came first. None of these stop a run.
severity check detail
error non-numeric DV DV contains text... NM-TRAN reads them as 0
error subject with no dose 1 subject(s) have observations but no dose: 2
error TIME not sorted 1 subject(s) have out-of-order TIME: 1
error covariate WT missing 1 record(s) have no value...
warning duplicate TIME within a subject NONMEM applies them in file order...
For the estimation itself, this skill does not reimplement NLME — see
references/population-pk.md for estimation methods, the BLQ M1-M7 methods, covariate model
building, and the diagnostics that decide whether a model is acceptable, and
references/software-ecosystem.md for which tool to reach for.
4. Simulation and regimen selection
python3 simulate_regimen.py --cl 5 --v 40 --dose 500 --interval 12 --n-doses 10 --steady-state
python3 simulate_regimen.py --cl 5 --v 40 --dose 500 --interval 12 --n-doses 10 \
--simulate 2000 --omega-cl 0.35 --omega-v 0.25 --target-trough 4.0
Deterministic simulation answers "what does the typical patient look like", which is almost never the question:
metric p5 p25 median p75 p95 geo_mean
peak 11.861 14.6018 16.6702 19.1476 22.9339 16.6453
trough 0.863226 2.15191 3.64412 5.49655 9.21053 3.27035
target fraction_attaining
trough >= 4 0.444
The typical trough is 3.6 and the target is 4, so 44% of the population attains it. A regimen tuned on the typical patient leaves about half the population on the wrong side of the target. Reported attainment is still optimistic here: this is between-subject variability only, with no residual or between-occasion component.
Linear models are solved analytically and superposed, which is exact. --nonlinear switches to
integrated Michaelis-Menten elimination, where superposition is invalid and multiple-dose
behaviour cannot be inferred from a single dose at all.
5. Exposure-response
python3 exposure_response.py --emax -i er.csv --sigmoid
python3 exposure_response.py --cqtc -i qt.csv --cmax 250
The Emax fit reports fraction_of_emax_reached and flags a fit whose plateau is outside the data.
When the highest observed exposure reaches only a third of the estimated Emax, Emax and EC50 are
extrapolations that are strongly correlated with each other; quoting them as independent estimates
is not supportable, and a "linear" exposure-response is simply the low-concentration limb of the
same curve.
--cqtc evaluates the upper bound of the two-sided 90% confidence interval of predicted
placebo-corrected change-from-baseline QTc against the 10 ms threshold, which is the question ICH
E14 actually asks. A point estimate, or a 95% interval, answers a different one. The bundled model
is an ordinary linear regression for screening; a submission-grade C-QTc analysis needs a mixed
model with random intercept and slope per subject.
Every mode carries the same caveat, because it is the one that gets forgotten: patients are randomised to dose, not to exposure. Exposure-response across quantiles is observational even inside a randomised trial, and can reflect the covariates that drive clearance.
6. Bioequivalence
python3 bioequivalence.py -i be.csv --design 2x2 --metric AUC
python3 bioequivalence.py -i be.csv --design replicate --metric Cmax --scaling both
python3 bioequivalence.py --power --cv 0.30 --gmr 0.95 --target-power 0.80
Three criteria share the word "bioequivalence" and are not interchangeable: average BE (90% CI
inside 80.00-125.00%), EMA's ABEL (limits widened as a function of CVwR, capped at
69.84-143.19%, point estimate still within 80-125%), and FDA's RSABE (a scaled linearised bound
via Hyslop's method, not an interval at all). --scaling refuses to run on a 2x2 design:
error: reference-scaling requires --design replicate. High observed variability in a 2x2 study
does not license widening: without replicated reference administrations there is no estimate of
within-subject reference variability to scale to.
Sample size reproduces the published tables exactly (CV 30%, GMR 0.95, 80% power → N = 40 for a 2x2). Power is computed by integrating over the sampling distribution of the estimated standard deviation rather than treating the standard error as known — the normal approximation overstates power at realistic sample sizes. Note that N is driven far more by the assumed GMR than by CV; assuming 1.00 instead of 0.95 roughly halves the calculated N and is the usual reason a BE study comes in underpowered.
7. Scaling, paediatrics, and first-in-human
python3 allometry_and_fih.py --scale --cl 5 --weight-from 70 --weight-to 6 --pma-weeks 44
python3 allometry_and_fih.py --fih --noael rat=50,dog=10 --safety-factor 10
Scaling by size alone below about 2 years of age overpredicts clearance, in a neonate by several
fold, because clearance is limited by enzyme and renal maturation rather than by size. Supplying
--pma-weeks adds the Anderson-Holford sigmoidal maturation term; omitting it below 20 kg raises
a finding.
parameter reference exponent size_scaled maturation_factor final
CL 5 0.75 0.792063 0.30634 0.242641
V 40 1 3.42857 1 3.42857
Size alone would predict 0.79 L/h; with maturation at 44 weeks post-menstrual age it is 0.24 L/h, a 3.3-fold difference. Volume is not matured — maturation describes eliminating capacity, not distribution space.
--fih uses the body-surface-area conversion from FDA's 2005 maximum-safe-starting-dose guidance
and always emits a finding that a NOAEL-derived MRSD is not sufficient on its own for agonist
immunomodulators: compute MABEL with --mabel and take the lower value.
8. Drug interactions
python3 ddi_static.py --basic --ki 0.5 --imax 2.0 --fu 0.05 --dose 0.4
python3 ddi_static.py --msm --ki 0.5 --imax 2.0 --fu 0.05 --dose 0.4 --fm 0.9 --fg 0.7
ICH M12 basic models with their cut-offs (R1 ≥ 1.02 hepatic, ≥ 11 intestinal; R2 ≥ 1.25 for TDI; R3 ≤ 0.8 for induction; transporter cut-offs by site), plus the mechanistic static model. The basic models are deliberately conservative: a negative is meaningful, a positive is a trigger for further work, not a prediction of clinical magnitude.
The mechanistic static model reports the ceiling alongside the prediction:
note: With fm = 0.9, no inhibitor of this pathway can raise the victim AUC above 10.00-fold. If
the prediction approaches that ceiling, fm is doing more work than the inhibition constants.
fm and Fg dominate the answer far more than the inhibition constants, and are usually the
least well established numbers in the calculation.
9. Therapeutic drug monitoring
python3 tdm_bayes.py --model vancomycin-adult --weight 80 --crcl 75 \
--dose 1500 --interval 12 --level 18.2@11.5 --level 42@2 --target-auc24 500
MAP Bayesian estimation shrinks towards the population when the data are uninformative and follows the data when they are not, which is why it beats both a trough read against population parameters and log-linear regression on two points. A single level raises a finding: it cannot separate clearance from volume, and whichever parameter the sample is uninformative about has simply returned its prior.
The bundled vancomycin parameterisation is explicitly labelled illustrative. Substitute a model validated in your population before the output means anything.
Software ecosystem
Verified against live sources on 2026-07-27; see references/software-ecosystem.md for the full
map and references/source-ledger.md for provenance.
- Pharmpy 2.1.1 (2026-05-19) is the practical Python entry point — model-agnostic, drives
NONMEM/nlmixr2/rxode2, and ships 19
run_*tools includingrun_amd,run_modelsearch,run_covsearch,run_structsearch,run_pdsearch,run_modelrank,run_vpcandrun_qa. Two breaking changes are recent enough to catch you out: 2.0.0 (2026-02-12) changed dataset row indices to start at 1, and 2.1.0 (2026-05-08) renamedadd_placebo_modeltoset_placebo_modeland now requires numpy ≥ 2. - NONMEM 7.6 (user guides dated November 2025) remains the regulatory default. New since 7.5: ADVAN16 (RADAR5 implicit Runge-Kutta for stiff delay differential equations), ADVAN17 (stiff delay differential-algebraic), NUTS Bayesian sampling, and SAEM storage of individual samples.
- nlmixr2 (requires rxode2 ≥ 5.0.0) is the credible open-source NLME alternative;
babelmixr2andmonolix2rxtranslate models between it, NONMEM and Monolix. - PKPy (PeerJ, 2025) is a Python popPK framework but is GitHub-only — not on PyPI, so
uv pip install pkpyfails.chi-drm(1.0.3) is on PyPI for Bayesian PKPD. - Open Systems Pharmacology Suite v12 (PK-Sim/MoBi) is the open-source PBPK platform; Simcyp
and GastroPlus are the commercial ones.
ospsuiteis R-only and needs .NET 8.
Python has no mature NCA or NLME package of regulatory standing. That gap is why this skill ships its own validated NCA and fitting implementations rather than wrapping one.
What this skill exists to prevent
- Lambda_z chosen by plain r-squared, or fitted through Tmax.
- AUCinf reported from a profile where 25% of it was extrapolated.
- AIC allowed to select a compartment whose intercompartmental clearance has 98% RSE.
- Reweighting used to fix non-random residuals, which are a structural problem.
BLQleft in a DV column, where NM-TRAN reads it as a real zero.- A regimen chosen on the typical patient, with no attainment estimate for the population.
- Emax and EC50 quoted as independent estimates when the plateau was never observed.
- Reference-scaled bioequivalence limits applied to a 2x2 study.
- Allometric scaling to a neonate with no maturation term.
- An MRSD from a NOAEL used as the starting dose for an agonist immunomodulator.
References
references/nca-conventions.md— parameter definitions, lambda_z rules, BLQ handling, steady statereferences/structural-models.md— closed-form solutions, parameterisations, NONMEM ADVAN/TRANS mapreferences/population-pk.md— NLME estimation, covariate building, BLQ M1-M7, diagnostics, VPCreferences/pd-and-exposure-response.md— Emax, indirect response, effect compartment, ER analysisreferences/tmdd-and-biologics.md— TMDD approximations, monoclonal antibody PK, immunogenicityreferences/pbpk.md— when PBPK earns its cost, platforms, and what verification requiresreferences/bioequivalence.md— designs, ABE/ABEL/RSABE, ICH M13 series, highly variable drugsreferences/special-populations.md— paediatrics, renal and hepatic impairment, obesity, pregnancyreferences/dataset-standards.md— CDISC PC/PP and ADPC/ADPP, NONMEM data items, common defectsreferences/ddi-and-qt.md— ICH M12 stepwise assessment, static models, ICH E14/S7B C-QTcreferences/antimicrobial-and-tdm.md— PK/PD indices, PTA/CFR, vancomycin AUC-guided dosing, MIPDreferences/software-ecosystem.md— every tool, what it is for, licensing, and verified versionsreferences/regulatory-guidance.md— the guidance ledger with dates, status, and what each requiresreferences/source-ledger.md— provenance and research dates for every claim in this skill
Assets
assets/popk-analysis-plan.md— population analysis plan structure, with the decisions stated up frontassets/nca-reporting-checklist.md— what an NCA report has to state for the numbers to be interpretable
Frequently asked questions about Pharmacokinetic Modelling
Similar skills
Spring Boot Testing
Master testing techniques for Spring Boot 4 applications.
GitHub Issues
Manage GitHub issues efficiently with MCP tools.
Geofeed Tuner
Optimize your IP geolocation feeds in CSV format.
Batch Files
Master Windows batch scripting for automation and task management.
Adobe Illustrator Scripting
Automate your Illustrator workflows with ExtendScript.
Plugin Structure
Create and organize Claude Code plugins effectively.
