NUMERICAL AUDIT · 1 NOVEMBER 2026
statsmodels: PHReg silently ignored unknown keywords such as weights=; warning merged
PHReg dropped unsupported keywords without a warning, so PHReg(..., weights=w) returned the unweighted Cox fit; in a simulated IPTW analysis with no true effect it reported HR 2.01 (p = 1.7e-7). The keyword check was merged on 30 October 2026.
Xamit Kadirbekov, Daniyal Kadirbekov · AI-assisted investigation and writing
Checked 1 November 2026: PR #10650 merged on 30 October 2026 (UTC); no release containing the change has been independently tested.
Authors: Xamit Kadirbekov and Daniyal Kadirbekov (GERO). Published 1 November 2026; finding dated 30 October 2026.
Status: Checked 1 November 2026: PR #10650 merged on 30 October 2026 (UTC); no release containing the change has been independently tested.
PHReg dropped unsupported keywords without a warning, so PHReg(..., weights=w) returned the unweighted Cox fit; in a simulated IPTW analysis with no true effect it reported HR 2.01 (p = 1.7e-7). The keyword check was merged on 30 October 2026.
Published proposal
- statsmodels PR #10650 — head commit
b36b6ee071fc659c7e70fdc8eb93357f44ffd2a3, opened 30 October 2026; merged the same day by statsmodels maintainerbashtage, merge commit8b65985e1a93b18aa55cbf64ee4f6ba82e497833.
What the function does
PHReg (statsmodels/duration/hazard_regression.py) fits the Cox proportional-hazards model, the standard tool for estimating how a treatment changes the risk of an event, reported as a hazard ratio (HR). In observational drug studies, sicker patients are often more likely to receive the treatment; a common correction is inverse-probability-of-treatment weighting (IPTW), where weights are passed to the Cox model. R's coxph(weights=) and lifelines' weights_col support this. PHReg does not support case weights. We checked main e82bcf02f2cb7fd915264f6af79a51ca394965e8.
The discrepancy
PHReg.__init__ accepts **kwargs but never checks them, so unsupported keywords are dropped without any warning. Other statsmodels models (OLS, GLM, Logit and others) have warned with ValueWarning: unknown kwargs since GH 7741 / PR 7751; PHReg did not. Two common mistakes follow: weights= silently returns the unweighted fit, and event= (the lifelines name for status=) silently treats every censored observation as an event. tie= instead of ties= and groups= in the constructor were dropped the same way.
import numpy as np
from statsmodels.duration.hazard_regression import PHReg
rng = np.random.default_rng(5); n = 300
x = rng.normal(size=n); t = rng.exponential(np.exp(-0.5 * x))
st = (rng.uniform(size=n) < 0.7).astype(float); w = rng.uniform(0.2, 5, n)
PHReg(t, x[:, None], status=st).fit().params # [0.47262404]
PHReg(t, x[:, None], status=st, weights=w).fit().params # [0.47262404] same, no warning
PHReg(t, x[:, None], event=st).fit().params # [0.46537011] censoring ignored, no warning
Simulated IPTW analysis: 400 patients; severe patients receive the drug with probability 0.8, mild ones with 0.2; severity raises the risk; the drug has no effect (true HR = 1). The IPTW weights are known exactly.
| Calculation | HR | 95% CI | p |
|---|---|---|---|
statsmodels PHReg(time, A, status=E, weights=w) | 2.009 | (1.547, 2.609) | 1.7·10⁻⁷ |
| statsmodels without weights | 2.009 (identical: the weights were dropped) | ||
Reference: weighted Breslow partial likelihood (R coxph formula), own Newton solver | 0.865 | ||
lifelines weights_col="w", robust=True | 0.865 | (0.637, 1.176) | 0.36 |
statsmodels PHReg(time, A, event=E) | 1.543 (164 of 400 censored patients counted as events) |
The conclusion flips from "no effect" to "the drug doubles the risk, p < 10⁻⁶", without a warning.
Reference and why this is not a convention
The reference is an independent Cox implementation (Breslow/Efron, strata, R-style weights). Without weights it agrees with statsmodels to 3·10⁻¹⁵ and with scikit-survival to 1·10⁻¹²; with weights it agrees with lifelines to 1·10⁻⁴, the convergence tolerance of lifelines. The point is not a formula: statsmodels' own policy since GH 7741 is that models must not swallow unknown keywords silently, and PHReg lacked that check. weights is not documented for PHReg, so this is a missing input guard rather than a wrong formula.
Fix and tests
- Call
self._check_kwargs(kwargs)at the start ofPHReg.__init__, as other models do. Supported keywords used byfrom_formula, MICE and mediation (missing,tiesand others) are unaffected. The result is aValueWarning, not an error, so existing code keeps running. - New tests
test_unknown_init_kwargs_warn(weights, event, tie, groups; direct andfrom_formula) andtest_known_init_kwargs_no_warning. With the old code 4 tests fail; with the fixstatsmodels/duration/tests,imputation/tests/test_mice.pyandstats/tests/test_mediation.pypass (144 passed).
Upstream status
PR #10650 was merged on 30 October 2026 at 21:17 UTC, with a short thank-you comment from the maintainer. On 1 November we confirmed the merged state with the GitHub CLI; we have not installed or tested a statsmodels release containing the change, and we do not know which release will ship it.
Limits
The fix adds a warning; it does not add case-weight support to PHReg. The size of the flipped conclusion is specific to the simulated design, chosen with strong confounding; it shows what dropped weights can do, not how often analysts pass weights= to PHReg. The general class of silently swallowed keywords is known (GH 7741, PR 7751, GH 8779, PR 10427); our contribution is the missing check for PHReg, where a bounded GitHub search found no earlier report.
The weights= and event= example was re-run in a separate session on 30 October before submission. No reproduction archive is attached to this page yet.
Attribution and license
AI tools (Claude) helped find the cases, build the reference calculations and draft the patches and tests; the examples were re-run independently before submission. Xamit Kadirbekov and Daniyal Kadirbekov are the report authors and reviewed the submitted diff; upstream software remains credited to its developers. Thank you to the statsmodels maintainers for the quick review and merge. Report: CC BY 4.0. Upstream patches retain their upstream licenses.