Estimating small-molecule inhibitor potency (IC50) from a serial-dilution screen — the core read-out of a biochemical or cell-based assay in drug discovery. Full workflow: assay QC (Z'-factor) → four-parameter logistic (4PL) curve fitting → IC50 with 95% confidence intervals → potency ranking.
This is the analysis I ran in GraphPad Prism during assay development at
Pfizer, reproduced here in Python (scipy) and R (drc) on shareable
synthetic data.
| File | Toolkit | Role |
|---|---|---|
dose_response_ic50_analysis.ipynb |
Python (scipy.optimize, matplotlib) |
Executed here — plots & output render inline |
r/dose_response.R |
R (drc::drm, LL.4) |
Equivalent fit + pairwise IC50 comparison |
- Assay QC — Z'-factor. Confirm the assay window is robust before reading any potency. Z' ≈ 0.82 here (> 0.5 = excellent, screening-grade).
- 4PL curve fitting in log-concentration space (Prism's log(inhibitor) vs. response, variable slope), which stays stable across four orders of magnitude and yields proper asymmetric confidence intervals on IC50.
- Potency table — IC50 (95% CI), Hill slope, and R² per compound, ranked.
- Ground-truth validation — because the data is synthetic we know each true IC50; every fitted IC50's 95% CI contains it, so the method is unbiased.
Result: five compounds cleanly resolved and ranked (IC50 ≈ 11, 49, 134, 813, 2,978 nM), all fits R² > 0.98, Hill slopes near 1.
All data is synthetic, generated with a fixed random seed by
generate_data.py — a target-inhibition assay read out as
raw signal (RFU) with neutral/max-inhibitor plate controls, plus five test
compounds over a 10-point, 3-fold dilution series in triplicate. No real assay
data is used. The generator is committed so the whole analysis is reproducible.
python generate_data.py # writes data/controls.csv and data/doseresponse.csv- Non-linear regression / curve fitting (4PL, Hill equation)
- Concentration-response and IC50 / EC50 determination
- Assay quality control (Z'-factor, signal window)
- Confidence-interval estimation and validation against ground truth
- Equivalent implementation in Python and R — the GraphPad Prism workflow in code
pip install -r requirements.txt
python generate_data.py
jupyter lab # open dose_response_ic50_analysis.ipynb
# R: Rscript r/dose_response.R