Skip to main content

polars-statistics

CI codecov PyPI version License: MIT Python 3.9+

Note: This extension is in early stage development. APIs may change and some features are experimental.

High-performance statistical testing and regression for Polars DataFrames, powered by Rust.

Usable from Python (as a Polars plugin) and from Rust (as an rlib that other Rust crates depend on directly — see Use from Rust).

Features

  • Native Polars Expressions: Full support for group_by, over, and lazy evaluation
  • Statistical Tests: Parametric, non-parametric, distributional, and forecast comparison tests
  • Regression Models: OLS, Ridge, Elastic Net, WLS, Quantile, Isotonic, Huber (M-estimator), PLS, GLMs, ALM (25 distributions)
  • Diagnostics: VIF, leverage, Cook's distance, DFFITS, influence masks, standardized / studentized / externally-studentized residuals, GLM Pearson / deviance / working residuals, Pearson χ², condition number, quasi-separation detection
  • Formula Syntax: R-style formulas with polynomial and interaction effects
  • Hybrid crate: cdylib (Python wheel) and rlib (Rust dependency) from the same source
  • High Performance: Rust-powered with zero-copy data transfer

Installation

pip install polars-statistics

Quick Start

All functions work as Polars expressions, integrating with group_by and over:

import polars as pl
import polars_statistics as ps

df = pl.DataFrame({
    "group": ["A"] * 50 + ["B"] * 50,
    "y": [...],
    "x1": [...],
    "x2": [...],
})

# Run OLS regression per group
result = df.group_by("group").agg(
    ps.ols("y", "x1", "x2").alias("model")
)

# Extract results from struct
result.with_columns(
    pl.col("model").struct.field("r_squared"),
    pl.col("model").struct.field("coefficients"),
)

Statistical Tests

Statistical tests are powered by anofox-statistics, providing full API parity with R's statistical functions and validated against R implementations.

# Parametric tests
ps.ttest_ind("treatment", "control", alternative="two-sided")
ps.ttest_paired("before", "after")

# Non-parametric tests
ps.mann_whitney_u("x", "y")
ps.kruskal_wallis("group1", "group2", "group3")

# Normality tests
ps.shapiro_wilk("x")

# Forecast comparison
ps.diebold_mariano("errors1", "errors2", horizon=1)

# Correlation tests
ps.pearson("x", "y")                    # Pearson correlation with CI
ps.spearman("x", "y")                   # Spearman rank correlation
ps.kendall("x", "y", variant="b")       # Kendall's tau
ps.distance_cor("x", "y")               # Distance correlation (detects nonlinear)
ps.partial_cor("x", "y", ["z1", "z2"])  # Partial correlation

# Categorical tests
ps.binom_test(successes=7, n=10, p0=0.5)  # Exact binomial test
ps.chisq_test("counts", n_rows=2, n_cols=2)  # Chi-square independence
ps.fisher_exact(a=10, b=2, c=3, d=15)   # Fisher's exact test
ps.mcnemar_test(a=45, b=15, c=5, d=35)  # McNemar's test
ps.cohen_kappa("counts", n_categories=3) # Inter-rater agreement
ps.cramers_v("counts", n_rows=3, n_cols=3) # Association strength

All tests return a struct with statistic and p_value fields.

TOST Equivalence Tests

Test for practical equivalence using Two One-Sided Tests (TOST) procedure:

# t-test based equivalence
ps.tost_t_test_two_sample("x", "y", delta=0.5, alpha=0.05)
ps.tost_t_test_paired("before", "after", bounds_type="cohen_d", delta=0.3)

# Correlation equivalence (test if correlation is near zero)
ps.tost_correlation("x", "y", delta=0.3, method="pearson")

# Proportion equivalence
ps.tost_prop_two(successes1=45, n1=100, successes2=48, n2=100, delta=0.1)

# Non-parametric and robust equivalence
ps.tost_wilcoxon_paired("x", "y", delta=0.5)
ps.tost_yuen("x", "y", trim=0.2, delta=0.5)  # Trimmed means
ps.tost_bootstrap("x", "y", n_bootstrap=1000)  # Bootstrap-based

Returns struct with estimate, ci_lower, ci_upper, tost_p_value, equivalent.

Regression Models

Regression models are powered by anofox-regression, providing validated implementations against R.

Expression API

# Linear models
ps.ols("y", "x1", "x2")
ps.ridge("y", "x1", "x2", lambda_=1.0)
ps.elastic_net("y", "x1", "x2", lambda_=1.0, alpha=0.5)

# Robust regression
ps.quantile("y", "x1", "x2", tau=0.5)  # Median regression
ps.isotonic("y", "x")                   # Monotonic regression
ps.huber("y", "x1", epsilon=1.35)       # Huber M-estimator (outlier-robust)
ps.pls("y", "x1", "x2", n_components=2) # Partial Least Squares

# GLM models (with optional Ridge regularization)
ps.logistic("y", "x1", "x2", lambda_=0.1)             # Binary classification (BinomialRegressor)
ps.logistic_regression("y", "x1", "x2", penalty="l2", C=1.0)  # sklearn-style logistic
ps.poisson("y", "x1", "x2")                            # Count data

# ALM - 25 distributions, loss/link/extra_parameter exposed
ps.alm("y", "x1", "x2", distribution="laplace", loss="mle")

Diagnostics

# Pre-fit checks
ps.condition_number("x1", "x2")              # Multicollinearity (κ + indices)
ps.vif("x1", "x2", "x3")                     # Variance inflation factor per feature
ps.generalized_vif("x1", "x2", "x3", group_sizes=[1, 2])  # GVIF for grouped predictors
ps.high_vif_predictors("x1", "x2", threshold=10.0)        # Boolean mask
ps.check_binary_separation("y", "x1")        # Quasi-separation detection
ps.check_count_sparsity("y", "x1")           # Sparse-count check

# Per-row OLS residual battery
ps.standardized_residuals("y", "x1", "x2")
ps.studentized_residuals("y", "x1", "x2")
ps.externally_studentized_residuals("y", "x1", "x2")
ps.residual_outliers("y", "x1", "x2", threshold=2.0)       # Boolean mask

# Influence / leverage
ps.leverage("x1", "x2")
ps.cooks_distance("y", "x1", "x2")
ps.dffits("y", "x1", "x2")
ps.influential_cooks("y", "x1", "x2")        # mask, default threshold 4/n
ps.influential_dffits("y", "x1", "x2")       # mask, default 2·√(p/n)
ps.high_leverage_points("x1", "x2")          # mask, default 2·p/n

# GLM residual diagnostics (logistic + Poisson)
ps.logistic_pearson_residuals("y", "x1")
ps.logistic_deviance_residuals("y", "x1")
ps.logistic_working_residuals("y", "x1")
ps.poisson_pearson_residuals("y", "x1")
ps.poisson_deviance_residuals("y", "x1")
ps.poisson_working_residuals("y", "x1")

# GLM goodness-of-fit
ps.pearson_chi_squared_logistic("y", "x1")   # Σ pearson_resid² + df_resid
ps.pearson_chi_squared_poisson("y", "x1")

Formula Syntax

R-style formulas with polynomial and interaction effects:

# Main effects + interaction
ps.ols_formula("y ~ x1 * x2")  # Expands to: x1 + x2 + x1:x2

# Polynomial regression (centered per group)
ps.ols_formula("y ~ poly(x, 2)")

# Explicit transform
ps.ols_formula("y ~ x1 + I(x^2)")

Predictions with Intervals

df.with_columns(
    ps.ols_predict("y", "x1", "x2", interval="prediction", level=0.95)
        .over("group").alias("pred")
).unnest("pred")  # Columns: prediction, lower, upper

Tidy Coefficient Summary

df.group_by("group").agg(
    ps.ols_summary("y", "x1", "x2").alias("coef")
).explode("coef").unnest("coef")
# Columns: term, estimate, std_error, statistic, p_value

*_summary and *_predict are available for the full regression family, including Quantile, Isotonic, and LmDynamic where applicable:

ps.quantile_summary("y", "x1", tau=0.5)   # Tidy coefs from quantile fit
ps.quantile_predict("y", "x1", tau=0.5)   # Per-row predictions
ps.isotonic_predict("y", "x")             # Step-function predictions
ps.lm_dynamic_predict("y", "x1")          # Time-averaged predictions

Model Classes

For direct model access outside Polars expressions:

from polars_statistics import OLS, Ridge, Logistic, LogisticRegression, Huber, PLS, ALM

# Fit OLS with inference
model = OLS(compute_inference=True).fit(X, y)
print(model.coefficients, model.r_squared, model.p_values)

# Sklearn-style logistic with L2 penalty
lr = LogisticRegression(penalty="l2", C=1.0).fit(X, y)
lr.predict_proba(X)
lr.decision_function(X)
lr.score(X, y)

# Huber M-estimator (robust to outliers)
hb = Huber(epsilon=1.35).fit(X, y)
print(hb.coefficients, hb.n_outliers, hb.scale)

# Partial Least Squares
pls = PLS(n_components=2).fit(X, y)
print(pls.explained_variance_ratio, pls.transform(X))

# ALM with various distributions
alm = ALM.laplace().fit(X, y)  # Robust to outliers

Available model classes:

  • Linear / robust: OLS, Ridge, ElasticNet, WLS, RLS, BLS, Quantile, Isotonic, Huber, PLS
  • GLMs: Logistic, LogisticRegression (sklearn-style), Poisson, NegativeBinomial, Tweedie, Probit, Cloglog
  • Augmented: ALM (25 distributions), LmDynamic, Aid

Test Model Classes

Statistical tests are also available as model classes with .fit(), .statistic, .p_value, and .summary():

from polars_statistics import TTestInd, ShapiroWilk, KruskalWallis
import numpy as np

# Two-sample t-test
test = TTestInd(alternative="two-sided").fit(x, y)
print(test.statistic, test.p_value)
print(test.summary())

# Normality test
test = ShapiroWilk().fit(x)
print(test.p_value)

# Multi-group comparison
test = KruskalWallis().fit(g1, g2, g3)
print(test.summary())

Available test classes: TTestInd, TTestPaired, BrownForsythe, YuenTest, MannWhitneyU, WilcoxonSignedRank, KruskalWallis, BrunnerMunzel, ShapiroWilk, DAgostino.

Use from Rust

polars-statistics builds as both a Python extension (cdylib) and a Rust library (rlib). Other Rust crates can depend on it directly and call the same statistical and regression code that the Python plugin uses — no Python boundary, no FFI overhead.

Cargo dependency

[dependencies]
polars = { version = "0.52", features = ["lazy", "partition_by"] }
polars-statistics = { version = "0.4", default-features = false }

default-features = false disables the python feature, so pyo3 / numpy are not linked.

Calling the fit functions

Every Polars expression has a public Rust-callable counterpart named <name>_fit that accepts a &[Series] input slice matching the expression's input contract and returns a PolarsResult<Series> (a one-row struct with the model output).

use polars::prelude::*;
use polars_statistics::expressions::wls_fit;

fn main() -> PolarsResult<()> {
    let df = df!(
        "site"   => &["A", "A", "A", "B", "B", "B"],
        "y"      => &[1.0_f64, 3.0, 5.0, 2.0, 5.0, 8.0],
        "weight" => &[1.0_f64, 1.0, 1.0, 1.0, 1.0, 1.0],
        "x1"     => &[0.0_f64, 1.0, 2.0, 0.0, 1.0, 2.0],
    )?;

    for group in df.partition_by(["site"], true)? {
        let y  = group.column("y")?.as_materialized_series().clone();
        let w  = group.column("weight")?.as_materialized_series().clone();
        let x1 = group.column("x1")?.as_materialized_series().clone();
        let with_intercept = Series::new("with_intercept".into(), &[true]);
        let solver         = Series::new("solve_method".into(), &[None::<&str>]);

        let result = wls_fit(&[y, w, with_intercept, solver, x1])?;
        println!("{result:?}");
    }
    Ok(())
}

The full runnable version is in examples/rust_wls.rs. Run with:

cargo run --example rust_wls --no-default-features

Available fit functions

Every Polars expression has a *_fit Rust entry point in polars_statistics::expressions:

  • Regression: ols_fit, ridge_fit, elastic_net_fit, wls_fit, rls_fit, bls_fit, quantile_fit, isotonic_fit, huber_fit, pls_fit
  • GLMs: logistic_fit, logistic_regression_fit, poisson_fit, negative_binomial_fit, tweedie_fit, probit_fit, cloglog_fit, alm_fit
  • Diagnostics:
    • Pre-fit / multicollinearity: condition_number_fit, vif_fit, generalized_vif_fit, high_vif_predictors_fit, check_binary_separation_fit, check_count_sparsity_fit
    • Residual battery: standardized_residuals_fit, studentized_residuals_fit, externally_studentized_residuals_fit, residual_outliers_fit
    • GLM residuals: logistic_*_residuals_fit, poisson_*_residuals_fit (pearson / deviance / working)
    • Goodness-of-fit: pearson_chi_squared_logistic_fit, pearson_chi_squared_poisson_fit
    • Influence / leverage: leverage_fit, cooks_distance_fit, dffits_fit, influential_cooks_fit, influential_dffits_fit, high_leverage_points_fit
  • Summaries / predictions: ols_summary_fit, ols_predict_fit, …; plus quantile_summary_fit, quantile_predict_fit, isotonic_predict_fit, lm_dynamic_predict_fit
  • Hypothesis tests: ttest_ind_fit, ttest_paired_fit, mann_whitney_fit, wilcoxon_fit, kruskal_wallis_fit, brunner_munzel_fit, brown_forsythe_fit, yuen_fit, shapiro_wilk_fit, dagostino_fit
  • Correlation: pearson_fit, spearman_fit, kendall_fit, distance_cor_fit, partial_cor_fit, semi_partial_cor_fit, icc_fit
  • Categorical: binom_test_fit, prop_test_one_fit, prop_test_two_fit, chisq_test_fit, fisher_exact_fit, mcnemar_test_fit, cohen_kappa_fit, cramers_v_fit, …
  • Forecast comparison / TOST / modern: see expressions::forecast, expressions::tost, expressions::modern.

The input slice layout (which input is y, which are scalars, which are x columns) is documented above each function — same contract that the Polars plugin uses.

Examples

Each example includes a complete, runnable script that shows the expected input DataFrame shape — so you can see exactly what columns and data types each method requires (addresses issue #36).

Runnable examples

Example Description
examples/01_ols_regression.py OLS regression basics: fitting, coefficients, predictions, and R-style formulas
examples/02_grouped_regression.py Running regression per group with group_by and over
examples/03_glm_models.py Generalized Linear Models: logistic (binary), Poisson (counts)
examples/04_statistical_tests.py T-tests, Mann-Whitney U, Shapiro-Wilk, and other hypothesis tests
examples/05_demand_classification.py AID (Automatic Identification of Demand) for demand pattern classification
examples/rust_wls.rs Rust API example: Weighted Least Squares via wls_fit

Cookbook (docs/examples/)

Page Description
docs/examples/ab-testing.md A/B testing
docs/examples/advanced-correlation.md Advanced correlation
docs/examples/categorical-analysis.md Categorical analysis
docs/examples/equivalence-testing.md Equivalence testing (TOST)
docs/examples/forecast-comparison.md Forecast comparison
docs/examples/glm-models.md GLM models
docs/examples/group-analysis.md Group analysis
docs/examples/hypothesis-testing.md Hypothesis testing
docs/examples/regression-workflow.md Regression workflow
docs/examples/regularized-regression.md Regularized regression
docs/examples/special-models.md Special models

Documentation

For the legacy monolithic reference, see docs/API_REFERENCE.md.

Performance

Built on high-performance Rust libraries:

  • faer: Fast linear algebra with SIMD
  • Zero-copy: Direct memory sharing between Python and Rust
  • Automatic parallelization: For group_by operations

Development

git clone https://github.com/DataZooDE/polars-statistics.git
cd polars-statistics
python -m venv .venv && source .venv/bin/activate
pip install maturin numpy polars pytest
maturin develop --release
pytest

License

MIT License - see LICENSE for details.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

polars_statistics-0.6.0.tar.gz (2.6 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

polars_statistics-0.6.0-cp39-abi3-win_amd64.whl (8.0 MB view details)

Uploaded CPython 3.9+Windows x86-64

polars_statistics-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (7.2 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

polars_statistics-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (6.4 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

polars_statistics-0.6.0-cp39-abi3-macosx_11_0_arm64.whl (6.3 MB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

polars_statistics-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl (6.8 MB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

File details

Details for the file polars_statistics-0.6.0.tar.gz.

File metadata

  • Download URL: polars_statistics-0.6.0.tar.gz
  • Upload date:
  • Size: 2.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for polars_statistics-0.6.0.tar.gz
Algorithm Hash digest
SHA256 e6cc3442dd1515deaf332eae9ef95cffdbf4d61447830e4c3a7cae4905660300
MD5 fdb615aa8ab73bc091c53ad6fddc2e4b
BLAKE2b-256 89b6ef5c8fb1810db98ab0501ecb325751b161c5706beb242a60d45635999f6c

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_statistics-0.6.0.tar.gz:

Publisher: publish.yml on DataZooDE/polars-statistics

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polars_statistics-0.6.0-cp39-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for polars_statistics-0.6.0-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 478185540dfcfdf545d7004fe19f5b3c7c03fbd57bdc0a8f51345c7c4a28117f
MD5 92943fb3c84878a6da242f3f83bd4d3a
BLAKE2b-256 0002197b901661c33a5db04461d31975bced2485301d233f353725c2250a3b17

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_statistics-0.6.0-cp39-abi3-win_amd64.whl:

Publisher: publish.yml on DataZooDE/polars-statistics

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polars_statistics-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for polars_statistics-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 f111a097f211dbdbf2e5740a1d0728ccb02cbb65669f7eb08713d804c3383072
MD5 0ea334c1b8e6a9a0c821df9219c353dd
BLAKE2b-256 3dafd1df383cda80ac8f738ced31bbdfc2ef19bd7f3c34571021d64a61f448f9

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_statistics-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: publish.yml on DataZooDE/polars-statistics

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polars_statistics-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for polars_statistics-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 8fcfa19cadf6ef172ab531e808cdc12a21430980e9cbeaa045b91536ed48fc48
MD5 4a0d457c65118e6ee6c1d2a8ff0453a2
BLAKE2b-256 d60ff1ac7f35d8218d494691602bee7ea944ef7058941582f1bbd39dcf4952aa

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_statistics-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: publish.yml on DataZooDE/polars-statistics

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polars_statistics-0.6.0-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for polars_statistics-0.6.0-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 a9777a626f9725a84a0f486a7993000564f71f01b537040d14be9bb2dc490532
MD5 560d7f2242719b91d3eaf40d48da4470
BLAKE2b-256 16991bd260cd4e02069c84f2374fd210a09090109e9ae314769f2ebc8d6e2c3e

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_statistics-0.6.0-cp39-abi3-macosx_11_0_arm64.whl:

Publisher: publish.yml on DataZooDE/polars-statistics

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polars_statistics-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for polars_statistics-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 52781bf31597239a65d4f29a6d613f11af79d326184c2596553e8520ecffed41
MD5 8d1e0feddc9a9a9dcd6b143299ddb0f6
BLAKE2b-256 e019fd23404b900e25e9c254c4ede2aef153d3bec06fb83e4afec8965b7c1099

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_statistics-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl:

Publisher: publish.yml on DataZooDE/polars-statistics

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.6.0 This release

6 files

0.5.0

6 files

0.4.0

8 files

0.3.0

8 files

0.2.0

8 files

0.1.0

8 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page