Dose-Response Modeling¶
4PL/5PL curve fitting, EC50/IC50 estimation, relative potency, and benchmark dose analysis. GPU-accelerated batch fitting for high-throughput screening (HTS).
Validates against R packages: drc, nplr, BMDS.
Dose-response modeling for preclinical pharmacology.
The workhorse of in vitro assay analysis and toxicology studies. Provides 4PL/5PL curve fitting, EC50/IC50 estimation, relative potency, benchmark dose analysis, and GPU-accelerated batch fitting for HTS campaigns.
Validates against: R packages drc, nplr, BMDS.
- class pystatsbio.doseresponse.CurveParams(bottom, top, ec50, hill, asymmetry=None, hormesis=None, model='LL.4')[source]¶
Bases:
objectParameters of a fitted dose-response curve.
For 4PL: bottom + (top - bottom) / (1 + (ec50/x)^hill)
- Parameters:
- class pystatsbio.doseresponse.DoseResponseParams(curve, se, cov, residuals, rss, aic, bic, converged, n_iter, model, dose, response, n_obs, jac)[source]¶
Bases:
objectComputed payload of fitting a single dose-response curve.
The fitted-curve value object lives in
curve(aCurveParams); the envelope reservesResult.paramsfor this payload, so the field is namedcurverather thanparams. The publicDoseResponseSolutionre-exposes it as.paramsto preserve the existing API (fit_result.params.ec50etc.).- Parameters:
- curve: CurveParams¶
- class pystatsbio.doseresponse.DoseResponseSolution(result)[source]¶
Bases:
SolutionReprMixinPublic result of a single dose-response fit — wraps
Result[DoseResponseParams].Exposes every fit output as a read-only property plus the uniform
.backend_name/.timing/.warnings/.infometadata and a Jupyter_repr_html_(viaSolutionReprMixin)..paramsreturns the fittedCurveParams, preserving the public API that callers rely on (fit_result.params.ec50/.to_array()).- Parameters:
result (Result[DoseResponseParams])
- property params: CurveParams¶
The fitted dose-response curve (bottom/top/ec50/hill).
- property cov: ndarray[tuple[Any, ...], dtype[floating]]¶
Parameter covariance matrix (observed information, matching drc).
- class pystatsbio.doseresponse.BatchDoseResponseParams(ec50, hill, top, bottom, converged, rss, n_compounds)[source]¶
Bases:
objectComputed payload of batch-fitting dose-response curves (HTS).
Each array has length n_compounds.
- Parameters:
n_compounds (int)
- class pystatsbio.doseresponse.BatchDoseResponseSolution(result)[source]¶
Bases:
SolutionReprMixinPublic result of batch dose-response fitting — wraps
Result[BatchDoseResponseParams].Exposes every output as a read-only property plus the uniform
.backend_name/.timing/.warnings/.infometadata and a Jupyter_repr_html_(viaSolutionReprMixin).- Parameters:
result (Result[BatchDoseResponseParams])
- class pystatsbio.doseresponse.EC50Params(estimate, se, ci_lower, ci_upper, conf_level, method)[source]¶
Bases:
objectComputed payload of EC50 (or IC50) estimation with confidence interval.
- Parameters:
- class pystatsbio.doseresponse.EC50Solution(result)[source]¶
Bases:
SolutionReprMixinPublic result of EC50 estimation — wraps
Result[EC50Params].Exposes every output as a read-only property plus the uniform
.backend_name/.timing/.warnings/.infometadata and a Jupyter_repr_html_(viaSolutionReprMixin).- Parameters:
result (Result[EC50Params])
- class pystatsbio.doseresponse.RelativePotencyParams(ratio, ci_lower, ci_upper, conf_level, method)[source]¶
Bases:
objectComputed payload of relative potency (ratio of EC50s) with Fieller’s CI.
- class pystatsbio.doseresponse.RelativePotencySolution(result)[source]¶
Bases:
SolutionReprMixinPublic result of relative potency — wraps
Result[RelativePotencyParams].Exposes every output as a read-only property plus the uniform
.backend_name/.timing/.warnings/.infometadata and a Jupyter_repr_html_(viaSolutionReprMixin).- Parameters:
result (Result[RelativePotencyParams])
- class pystatsbio.doseresponse.BMDParams(bmd, bmdl, bmdu, bmr, conf_level, method)[source]¶
Bases:
objectComputed payload of benchmark dose analysis.
- class pystatsbio.doseresponse.BMDSolution(result)[source]¶
Bases:
SolutionReprMixinPublic result of benchmark dose analysis — wraps
Result[BMDParams].Exposes every output as a read-only property plus the uniform
.backend_name/.timing/.warnings/.infometadata and a Jupyter_repr_html_(viaSolutionReprMixin).- Parameters:
result (Result[BMDParams])
- pystatsbio.doseresponse.ll4(dose, bottom, top, ec50, hill)[source]¶
4-parameter log-logistic (LL.4) model.
\[f(x) = c + \frac{d - c}{1 + \exp\bigl(-b \cdot (\ln x - \ln e)\bigr)}\]where
c = bottom,d = top,e = ec50,b = hill.- Parameters:
dose (array) – Dose (concentration) values. May contain zeros.
bottom (float) – Lower asymptote (response at dose → 0 for hill > 0).
top (float) – Upper asymptote (response at dose → ∞ for hill > 0).
ec50 (float) – Dose producing 50 % of the maximal effect.
hill (float) – Hill slope. Positive for increasing, negative for decreasing.
- Returns:
NDArray – Predicted response values.
Validates against (R drc::LL.4())
- Return type:
- pystatsbio.doseresponse.ll5(dose, bottom, top, ec50, hill, asymmetry)[source]¶
5-parameter log-logistic (LL.5) model.
Asymmetric extension of LL.4 with an extra shape parameter
f. Whenasymmetry = 1, this reduces to LL.4.\[f(x) = c + \frac{d - c}{\bigl(1 + \exp(-b \cdot (\ln x - \ln e))\bigr)^f}\]- Parameters:
- Returns:
NDArray
Validates against (R drc::LL.5())
- Return type:
- pystatsbio.doseresponse.weibull1(dose, bottom, top, ec50, hill)[source]¶
Weibull type 1 (W1.4) model.
Asymmetric dose-response, left-skewed.
\[f(x) = c + (d - c) \exp\bigl(-\exp(-b \cdot (\ln x - \ln e))\bigr)\]
- pystatsbio.doseresponse.weibull2(dose, bottom, top, ec50, hill)[source]¶
Weibull type 2 (W2.4) model.
Asymmetric dose-response, right-skewed.
\[f(x) = c + (d - c) \bigl(1 - \exp(-\exp(-b \cdot (\ln x - \ln e)))\bigr)\]
- pystatsbio.doseresponse.brain_cousens(dose, bottom, top, ec50, hill, hormesis)[source]¶
Brain-Cousens hormesis model (BC.5).
Biphasic dose-response with low-dose stimulation. The
hormesisparameter adds a linear term to the numerator that can push the response above the upper asymptote at low doses.\[f(x) = c + \frac{d - c + f \cdot x}{1 + \exp(-b \cdot (\ln x - \ln e))}\]- Parameters:
- Returns:
NDArray
Validates against (R drc::BC.5())
- Return type:
- pystatsbio.doseresponse.fit_drm(dose, response, *, model='LL.4', weights=None, start=None, lower=None, upper=None)[source]¶
Fit a dose-response model to a single curve.
Uses Trust Region Reflective nonlinear least squares (
scipy.optimize.least_squares).- Parameters:
dose (array) – Dose (concentration) values.
response (array) – Response values.
model (str) – Model name:
'LL.4','LL.5','W1.4','W2.4','BC.5'.weights (array or None) – Optional observation weights.
start (dict or None) – Starting values for parameters. If
None, uses self-starting estimates derived from the data.lower (dict or None) – Box constraints on parameters.
upper (dict or None) – Box constraints on parameters.
- Return type:
Notes
Parameter standard errors (
.se) and the covariance (.cov) use the observed-information covariances²·(½H)⁻¹— the inverse of the scaled Hessian of the residual sum of squares,s² = RSS/(n-p)— which is what Rdrc::drm()reports. This differs from the Gauss-Newton approximations²·(JᵀJ)⁻¹used byscipy.optimize.curve_fit/ Rnlsby the second-order curvature term; the two are asymptotically equivalent but can differ by ~10% on a poorly-conditioned coefficient (e.g. the Hill slope).Examples
>>> import numpy as np >>> dose = np.array([0, 0.01, 0.1, 1, 10, 100]) >>> response = np.array([10, 12, 30, 55, 85, 92]) >>> result = fit_drm(dose, response, model='LL.4') >>> round(result.params.ec50, 1) 1.0
Validates against: R drc::drm()
- pystatsbio.doseresponse.fit_drm_batch(dose_matrix, response_matrix, *, model='LL.4', backend='auto', max_iter=100, tol=1e-08)[source]¶
Batch-fit dose-response curves across many compounds.
- Parameters:
dose_matrix (array, shape
(n_compounds, n_doses)) – Dose values for each compound.response_matrix (array, shape
(n_compounds, n_doses)) – Response values for each compound.model (str) – Model name (currently only
'LL.4'for batch fitting).backend (str) – Execution target (device and precision), per the pystatistics convention:
'cpu'(float64),'gpu'(float32, CUDA or Apple Silicon/MPS),'gpu_fp64'(float64, CUDA only), or'auto'(CUDA float32 if present, else CPU). GPU uses batched Levenberg-Marquardt via PyTorch for massive parallelism.max_iter (int) – Maximum LM iterations per compound (default 100).
tol (float) – Convergence tolerance on relative RSS change (default 1e-8).
- Return type:
Notes
GPU backend requires
pip install pystatsbio[gpu](PyTorch). On CPU, curves are fit sequentially usingscipy.optimize. On GPU, all curves are fit simultaneously using batched Jacobian computation and batched normal equations.
- pystatsbio.doseresponse.ec50(fit_result, *, conf_level=0.95, method='delta')[source]¶
Extract the EC50 (ED50) with confidence interval from a fitted model.
The EC50 is the dose producing a response half-way between the fitted lower and upper asymptotes, obtained by solving the fitted curve — matching R
drc::ED(type="relative"). This equals the model’selocation parameter for the symmetric LL.4 and differs for the asymmetric models (LL.5, W1.4, W2.4, BC.5). The confidence interval is a raw-scale symmetric Wald interval (estimate ± t·SE) with the SE from the delta method applied to the solved ED50, matchingdrc::ED(interval="delta").- Parameters:
fit_result (DoseResponseSolution) – A fitted dose-response model.
conf_level (float) – Confidence level (default 0.95).
method (str) –
'delta'(delta method).
- Returns:
EC50Solution
Validates against (R drc::ED())
- Return type:
- pystatsbio.doseresponse.relative_potency(fit1, fit2, *, conf_level=0.95)[source]¶
Relative potency: ratio of EC50s (ED50s) between two curves, Fieller’s CI.
Computes
rho = ED50_2 / ED50_1with a confidence interval based on Fieller’s theorem for the ratio of two independent estimates.Both ED50s are obtained by solving each fitted curve for its half-maximal response (the same quantity
ec50()returns), not by ratioing the rawelocation parameters. For the symmetric LL.4 — and for any two parallel curves, where thee-to-ED50 factor is identical and cancels — the two are the same. They differ for non-parallel asymmetric fits (LL.5/W1.4/W2.4/BC.5), where ratioingewould not be the potency ratio at all.Note this function does not test parallelism; relative potency is only meaningful for curves of the same shape, and assessing that is the caller’s responsibility.
- Parameters:
fit1 (DoseResponseSolution) – First fitted model (reference).
fit2 (DoseResponseSolution) – Second fitted model (test).
conf_level (float) – Confidence level (default 0.95).
- Returns:
RelativePotencySolution
Validates against (R drc::compParm(), drc::EDcomp())
- Return type:
- pystatsbio.doseresponse.bmd(fit_result, *, bmr=0.1, bmr_type='extra', conf_level=0.95, method='delta')[source]¶
Compute benchmark dose (BMD) with BMDL/BMDU.
- Parameters:
fit_result (DoseResponseSolution) – A fitted dose-response model.
bmr (float) – Benchmark response level (default 10 % = 0.10).
bmr_type (str) –
'extra'(extra risk) or'additional'(additional risk).conf_level (float) – Confidence level (default 0.95).
method (str) –
'delta'(delta method).
- Return type:
- Raises:
ValidationError – If inputs are invalid or the BMR target is outside the curve range.
NumericalError – If numerical BMD computation or CI estimation fails.
Notes
For extra risk: the target response is
top - bmr * (top - bottom)(i.e. abmrfraction of the full range from the upper asymptote).For additional risk: the target is
top - bmr * |top - bottom|.Validates against: EPA BMDS software, R BMDL packages