Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,66 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.0] - 2025-01-17
## [0.5.0] - 2026-05-28

### Added

- **Hybrid crate (Rust + Python)** — `polars-statistics` now builds as both a
`cdylib` (Python plugin) and an `rlib` (Rust dependency). Every Polars
expression has a public `<name>_fit` Rust entry point in
`polars_statistics::expressions`. New `python` Cargo feature gates the
pyo3 / numpy / pymodels surface; downstream Rust crates use
`default-features = false`. (#13)
- **New regression model wrappers**:
- `Huber` — M-estimator robust to outliers; class + `huber()` expression. (#14)
- `LogisticRegression` — sklearn-style API with `predict_proba`,
`decision_function`, `score`, `penalty="l2"`, `C` kwarg. Distinct from
the existing `Logistic` wrapper. (#14)
- `PLS` — Partial Least Squares with `transform()` for the latent space;
class + `pls()` expression. (#19)
- **Penalized IRLS `lambda_` kwarg** on the GLM PyClass models — `PyLogistic`,
`PyPoisson`, `PyNegativeBinomial`, `PyTweedie`, `PyProbit`, `PyCloglog`
(the expression layer already supported it). (#15)
- **ALM expression parity with `PyALM`** — all 25 distributions reachable
from `ps.alm(...)`, plus `loss`, `link`, `role_trim`, and
`extra_parameter` kwargs. (#16)
- **Summary / predict completeness** — added the matching expressions for
families that previously only had a base fit (#18):
- `quantile_summary`, `quantile_predict`
- `isotonic_predict`
- `lm_dynamic_predict`
- **Diagnostics toolkit** (#17 + #27):
- Multicollinearity: `vif`, `generalized_vif`, `high_vif_predictors`
- OLS residual battery: `standardized_residuals`,
`studentized_residuals`, `externally_studentized_residuals`,
`residual_outliers`
- GLM residuals (logistic + Poisson): `*_pearson_residuals`,
`*_deviance_residuals`, `*_working_residuals` for each family
- Influence / leverage: `leverage`, `cooks_distance`, `dffits`,
`influential_cooks`, `influential_dffits`, `high_leverage_points`
- Goodness of fit: `pearson_chi_squared_logistic`,
`pearson_chi_squared_poisson`
- **Documentation**:
- Sweep of the README and mkdocs site to cover every v0.5.0 addition.
- New "Use from Rust" section + `examples/rust_wls.rs` walking through
the rlib path.

### Changed

- Updated `anofox-regression` dependency to v0.5.4 (introduces
`HuberRegressor` and the sklearn-style `LogisticRegression`)
- Updated `anofox-statistics` dependency to v0.4.1
- Python `__version__` caught up from 0.3.0 → 0.5.0 (was lagging two
minor versions behind the wheel metadata)

### Backwards compatibility

Additive. `PyLogistic` is unchanged — the new sklearn-style
`LogisticRegression` is a separate class. The `alm()` expression's input
contract grew but all existing keyword-only callers continue to work
because the new kwargs default to `None` / `"likelihood"`.

## [0.4.0] - 2026-01-17

### Added

Expand Down Expand Up @@ -197,6 +256,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- SIMD-optimized linear algebra via faer
- Automatic parallelization for group operations

[0.5.0]: https://github.com/DataZooDE/polars-statistics/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/DataZooDE/polars-statistics/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/DataZooDE/polars-statistics/compare/v0.2.0...v0.3.0
[0.2.0]: https://github.com/DataZooDE/polars-statistics/compare/v0.1.0...v0.2.0
Expand Down
5 changes: 2 additions & 3 deletions docs/API_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
> **Version:** 0.4.0
> **Last Updated:** 2026-01-17

Complete API reference for polars-statistics. For quick start examples, see the [README](../README.md).
Complete API reference for polars-statistics. For quick start examples, see the [README](https://github.com/DataZooDE/polars-statistics#readme).

---

Expand All @@ -24,7 +24,6 @@ Complete API reference for polars-statistics. For quick start examples, see the
- [TOST Equivalence Tests](#tost-equivalence-tests)
- [Regression Models](#regression-models)
- [Linear Models](#linear-models)
- [Robust Regression](#robust-regression)
- [Diagnostics](#diagnostics)
- [GLM Models](#glm-models)
- [Augmented Linear Model (ALM)](#augmented-linear-model-alm)
Expand Down Expand Up @@ -1978,7 +1977,7 @@ Struct {

## See Also

- [README](../README.md) - Quick start guide
- [README](https://github.com/DataZooDE/polars-statistics#readme) - Quick start guide
- [Polars Documentation](https://docs.pola.rs/)
- [faer](https://github.com/sarah-ek/faer-rs) - Linear algebra backend

Expand Down
2 changes: 1 addition & 1 deletion docs/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,5 @@ result.with_columns(

## See Also

- [README](../../README.md) - Quick start guide
- [README](https://github.com/DataZooDE/polars-statistics#readme) - Quick start guide
- [Polars Documentation](https://docs.pola.rs/)
36 changes: 36 additions & 0 deletions docs/api/classes/glm.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Direct GLM model access outside of Polars expressions.

All GLM classes (`Logistic`, `Poisson`, `NegativeBinomial`, `Tweedie`, `Probit`, `Cloglog`) accept a `lambda_=0.0` kwarg for L2 (ridge) penalty applied inside the IRLS update. The sklearn-style [`LogisticRegression`](#logisticregression) class exposes the same penalty via `C = 1 / lambda_` and an explicit `penalty` choice.

## Common Interface

```python
Expand Down Expand Up @@ -44,6 +46,39 @@ probabilities = model.predict_proba(X_new) # Probability estimates

---

## LogisticRegression

Sklearn-style logistic regression. Distinct from [`Logistic`](#logistic): uses inverse-strength regularization `C = 1 / lambda_` and an explicit `penalty` choice.

```python
from polars_statistics import LogisticRegression

model = LogisticRegression(
penalty: str = "l2", # "l2" or "none"
C: float = 1.0, # Inverse of regularization strength
threshold: float = 0.5,
with_intercept: bool = True,
max_iter: int = 100,
tol: float = 1e-8,
compute_inference: bool = True,
confidence_level: float = 0.95,
)
model.fit(X, y) # y: binary (0/1)

# Methods
classes = model.predict(X_new) # 0/1 predictions
probs = model.predict_proba(X_new) # Probability estimates
scores = model.decision_function(X_new) # Linear scores (log-odds)
accuracy = model.score(X_new, y_new) # Mean accuracy

# Properties
model.coefficients # np.ndarray
model.intercept # float or None
model.n_iter # int — IRLS iterations until convergence
```

---

## Poisson

Poisson regression for count data.
Expand Down Expand Up @@ -135,6 +170,7 @@ model.fit(X, y) # y: binary (0/1)
| Class | Parameters |
|-------|------------|
| `Logistic` | `lambda_`, `with_intercept` |
| `LogisticRegression` | `penalty`, `C`, `threshold`, `with_intercept`, `max_iter`, `tol`, `compute_inference`, `confidence_level` |
| `Poisson` | `lambda_`, `with_intercept` |
| `NegativeBinomial` | `theta`, `estimate_theta`, `lambda_`, `with_intercept` |
| `Tweedie` | `var_power`, `lambda_`, `with_intercept` |
Expand Down
63 changes: 63 additions & 0 deletions docs/api/classes/linear.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,67 @@ model.fit(X, y)

---

## Huber

Huber M-estimator (robust regression).

```python
from polars_statistics import Huber

model = Huber(
epsilon: float = 1.35,
alpha: float = 0.0001,
with_intercept: bool = True,
max_iter: int = 100,
tol: float = 1e-5,
)
model.fit(X, y)

# Properties
model.coefficients # np.ndarray
model.intercept # float or None
model.scale # float — robust scale estimate
model.epsilon # float
model.outliers # np.ndarray[bool] — down-weighted observations
model.n_outliers # int
model.r_squared # float
model.mse # float
model.rmse # float
model.n_observations # int
```

---

## PLS

Partial Least Squares regression.

```python
from polars_statistics import PLS

model = PLS(
n_components: int = 2,
with_intercept: bool = True,
tol: float = 1e-6,
scale: bool = True,
)
model.fit(X, y)

# Methods
predictions = model.predict(X_new)
latent = model.transform(X_new) # Project into latent space

# Properties
model.coefficients # np.ndarray
model.intercept # float or None
model.n_components # int
model.explained_variance_ratio # np.ndarray (one entry per component)
model.r_squared # float
model.n_observations # int
```

---

## Quantile

Quantile regression.
Expand Down Expand Up @@ -179,6 +240,8 @@ model.fitted_values # np.ndarray
| `WLS` | `with_intercept`, `compute_inference` |
| `RLS` | `forgetting_factor`, `with_intercept` |
| `BLS` | `lower_bound`, `upper_bound`, `with_intercept` |
| `Huber` | `epsilon`, `alpha`, `with_intercept`, `max_iter`, `tol` |
| `PLS` | `n_components`, `with_intercept`, `tol`, `scale` |
| `Quantile` | `tau`, `with_intercept` |
| `Isotonic` | `increasing` |

Expand Down
135 changes: 135 additions & 0 deletions docs/api/outputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,141 @@ Struct {

---

## VIF Output

Returned by `vif`.

```
Struct {
terms: List[String], # "x1", "x2", ...
vif: List[Float64],
n_observations: UInt32,
}
```

---

## VIF Mask Output

Returned by `high_vif_predictors`.

```
Struct {
is_high: List[Boolean], # one per input feature column
n_high: UInt32,
n_features: UInt32,
}
```

---

## GVIF Output

Returned by `generalized_vif`.

```
Struct {
gvif: List[Float64], # one value per group
n_groups: UInt32,
}
```

---

## Leverage Output

Returned by `leverage`.

```
Struct {
leverage: List[Float64], # one h_ii per input row
n_observations: UInt32,
}
```

---

## Cook's Distance Output

Returned by `cooks_distance`.

```
Struct {
cooks_d: List[Float64], # one D_i per input row
n_observations: UInt32,
}
```

---

## DFFITS Output

Returned by `dffits`.

```
Struct {
dffits: List[Float64], # one value per input row
n_observations: UInt32,
}
```

---

## Influence Mask Output

Returned by `influential_cooks`, `influential_dffits`, `high_leverage_points`.

```
Struct {
is_influential: List[Boolean],
n_influential: UInt32,
n_observations: UInt32,
}
```

---

## Residual Diagnostics Output

Returned by `standardized_residuals`, `studentized_residuals`, `externally_studentized_residuals`, and all GLM residual functions (`logistic_*_residuals`, `poisson_*_residuals`).

```
Struct {
residuals: List[Float64], # one residual per input row
n_observations: UInt32,
}
```

---

## Outlier Mask Output

Returned by `residual_outliers`.

```
Struct {
is_outlier: List[Boolean],
n_outliers: UInt32,
n_observations: UInt32,
}
```

---

## Chi-Squared Output

Returned by `pearson_chi_squared_logistic` and `pearson_chi_squared_poisson`.

```
Struct {
chi_squared: Float64,
df_resid: UInt32,
n_observations: UInt32,
}
```

---

## GLM Output

Returned by `logistic`, `poisson`, `negative_binomial`, `tweedie`, `probit`, `cloglog`.
Expand Down
Loading
Loading