Metadata-Version: 2.4
Name: regdiag
Version: 0.1.0
Summary: R-style regression diagnostic plots (plot(lm)) for statsmodels and scikit-learn
Project-URL: Homepage, https://github.com/SparksFlash/regdiag
Project-URL: Repository, https://github.com/SparksFlash/regdiag
Project-URL: Bug Tracker, https://github.com/SparksFlash/regdiag/issues
Author-email: Sourav <ug2102027@cse.pstu.ac.bd>
License-Expression: MIT
License-File: LICENSE
Keywords: cooks-distance,diagnostics,leverage,linear-regression,qq-plot,regression,residuals,scikit-learn,statsmodels
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Topic :: Scientific/Engineering :: Visualization
Requires-Python: >=3.9
Requires-Dist: matplotlib>=3.6
Requires-Dist: numpy>=1.23
Requires-Dist: scikit-learn>=1.1
Requires-Dist: scipy>=1.9
Provides-Extra: dev
Requires-Dist: build; extra == 'dev'
Requires-Dist: pandas; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Requires-Dist: statsmodels>=0.14; extra == 'dev'
Requires-Dist: twine; extra == 'dev'
Provides-Extra: statsmodels
Requires-Dist: statsmodels>=0.14; extra == 'statsmodels'
Description-Content-Type: text/markdown

# regdiag

[![PyPI version](https://img.shields.io/pypi/v/regdiag.svg)](https://pypi.org/project/regdiag/)
[![Python versions](https://img.shields.io/pypi/pyversions/regdiag.svg)](https://pypi.org/project/regdiag/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![CI](https://github.com/SparksFlash/regdiag/actions/workflows/ci.yml/badge.svg)](https://github.com/SparksFlash/regdiag/actions/workflows/ci.yml)

**R's `plot(lm_model)` for Python — one line, works with both `statsmodels` and `scikit-learn`.**

R has a famous one-liner for regression diagnostics: `plot(lm_model)` produces four
classic diagnostic plots (Residuals vs Fitted, Normal Q-Q, Scale-Location, Residuals
vs Leverage). Python has never had a clean equivalent — the pieces exist scattered
across `statsmodels` (only works on its own `OLS` objects) and a few small,
low-traction packages. `regdiag` fills that gap.

```python
from sklearn.linear_model import LinearRegression
import regdiag

model = LinearRegression().fit(X, y)
regdiag.diagnose(model, X, y)
```

![Regression diagnostics example](assets/diagnostics_demo.png)

## Why regdiag

- **One line, R-style output.** `regdiag.diagnose(model, X, y)` gives you the full
  4-panel figure, same as `plot(lm)`.
- **Works with statsmodels *and* scikit-learn.** Any sklearn estimator exposing
  `.coef_`/`.intercept_` (LinearRegression, Ridge, Lasso, ElasticNet, SGDRegressor,
  BayesianRidge, ...) or a statsmodels `OLS` results object.
- **`statsmodels` is fully optional.** Core dependencies are just numpy, scipy,
  matplotlib, and scikit-learn. VIF and the Breusch-Pagan test are computed with
  plain numpy/scipy — no statsmodels needed at runtime, even though it's the
  library people usually reach for to get those numbers.
- **Honest about black-box models.** Pass a `RandomForestRegressor` or any other
  model that only exposes `.predict()` and `regdiag` switches to a clearly-labeled
  *generic* mode (Residuals vs Fitted, Residual Distribution, Actual vs Predicted).
  It will never compute leverage or Cook's distance for a model where the hat-matrix
  math doesn't apply — those statistics are only well-defined for linear models.

## Install

```bash
pip install regdiag

# with statsmodels support (accept statsmodels OLS objects as input):
pip install "regdiag[statsmodels]"
```

## Quickstart

```python
import numpy as np
from sklearn.linear_model import LinearRegression
import regdiag

rng = np.random.default_rng(0)
X = rng.normal(size=(200, 3))
y = 1.0 + X @ np.array([2.0, -1.5, 0.5]) + rng.normal(scale=1.0, size=200)

model = LinearRegression().fit(X, y)

diag = regdiag.diagnose(model, X, y)   # fits nothing, model must already be fit; shows the 4-panel plot
print(diag.summary())                   # Breusch-Pagan test, VIF table, top Cook's-distance points
```

Also works directly with statsmodels:

```python
import statsmodels.api as sm

X_const = sm.add_constant(X)
results = sm.OLS(y, X_const).fit()
regdiag.diagnose(results, X, y)
```

And degrades gracefully for black-box models instead of lying about statistics:

```python
from sklearn.ensemble import RandomForestRegressor

rf = RandomForestRegressor().fit(X, y)
diag = regdiag.diagnose(rf, X, y)   # 3-panel generic residual analysis
diag.vif()                           # raises NotLinearModelError — VIF isn't defined here
```

## API

```python
regdiag.diagnose(model, X, y, *, feature_names=None, plot=True,
                  figsize=(10, 8), annotate=True, n_annotate=3, save=None)
```
Returns a `RegressionDiagnostics` object:

| Attribute / method | Linear mode | Generic mode |
|---|---|---|
| `.residuals`, `.fitted_values` | ✅ | ✅ |
| `.leverage`, `.cooks_distance`, `.studentized_residuals` | ✅ | `None` |
| `.plot()` | 4-panel | 3-panel |
| `.residuals_vs_fitted()`, `.qq_plot()`, `.scale_location()`, `.residuals_vs_leverage()` | ✅ | raises `NotLinearModelError` |
| `.residual_histogram()`, `.actual_vs_predicted()` | ✅ | ✅ |
| `.breusch_pagan()`, `.vif()` | ✅ | raises `NotLinearModelError` |
| `.summary()` | text report | text report |

## Comparison

| | regdiag | lmdiag | statsmodels (raw) | yellowbrick |
|---|---|---|---|---|
| Works with sklearn linear models | ✅ | ✅ | ❌ | ✅ |
| Works with statsmodels OLS | ✅ | ✅ | ✅ | ❌ |
| statsmodels is optional at runtime | ✅ | ❌ | n/a | ✅ |
| Built-in VIF / Breusch-Pagan | ✅ | ❌ | ✅ (manual) | ❌ |
| Honest black-box fallback (no fake leverage) | ✅ | ❌ | ❌ | partial |
| One-object API (`diagnose(model, X, y)`) | ✅ | functions | manual | partial |

## Statistical notes

- Leverage is computed via QR decomposition of the design matrix (never forms the
  full `n × n` hat matrix).
- Cook's distance uses the classic OLS-based formula. It's applied uniformly to any
  linear-coefficient model (including Ridge/Lasso) for simplicity, but strictly its
  "influence on fitted coefficients" interpretation assumes an OLS estimator —
  interpret with some caution for heavily regularized models.
- Rank-deficient design matrices (collinear columns) and `n ≤ p` datasets raise a
  clear error rather than emitting `NaN`/`inf` silently.
- `statsmodels` WLS/GLS results are not supported in v1 (OLS only).

See `CHANGELOG.md` for release notes and `CONTRIBUTING.md` to get set up for
development.

## License

MIT — see [LICENSE](LICENSE).
