Skip to main content

polars-stats

polars-stats is a Polars expression plugin that exposes scipy.stats-style probability distributions natively inside Polars expressions:

  • Lazy-native: every method returns a pl.Expr, so a distribution composes inside a LazyFrame query under the optimiser, with no materialisation.

  • Column-valued parameters: any distribution parameter can be a scalar or a Polars expression. A single instance describes a different distribution per row:

    import polars as pl
    import polars_stats as ps
    
    norm = ps.Normal(mu=pl.col("mu"), sigma=pl.col("sigma"))
    norm.cdf(pl.col("x"))
    
  • Polars null and error semantics: a null input gives a null result, and an invalid parameter raises a ComputeError rather than silently returning NaN.

  • Reproducible sampling: every draw is keyed on (seed, row index), so a seeded column repeats across runs, chunkings, thread counts, and both engines.

scipy already does the per-row maths: stats.norm(loc=mu_array, scale=sigma_array).sf(x_array) broadcasts parameter arrays and scores every element against its own distribution, vectorised, with no Python loop. The difference is where the result lands. scipy returns a NumPy array, so a LazyFrame has to collect() first, pushdown stops at that boundary, and realigning the result through later joins and filters is your problem. Here it stays a pl.Expr the planner can see. Why polars-stats has the full comparison.

The math runs in Rust on top of the statrs crate; the Python layer is a thin, typed surface of distribution classes.

Why

Statistical work in Polars today means falling back to .to_pandas() / .to_numpy(), then reaching for one of:

  • scipy.stats, which exits the lazy engine and materialises everything,
  • Python UDFs via map_elements, which are slow and hold the GIL,
  • hand-rolled per-distribution expressions, which are ad hoc and error-prone.

The row-varying, vectorised, lazy-native case is what polars-stats targets.

Quick example

Anomaly scoring, where each row carries its own baseline:

import polars as pl
import polars_stats as ps

readings = pl.LazyFrame(
    {
        "value": [9.8, 101.0, 12.1, 250.0],
        "mu": [10.0, 100.0, 10.0, 100.0],
        "sigma": [0.5, 2.0, 0.5, 2.0],
    }
)

norm = ps.Normal(mu="mu", sigma="sigma")

anomalies = (
    readings.with_columns(upper_tail=norm.sf("value"))
    .filter(pl.col("upper_tail") < 0.01)
    .collect()
)
print(anomalies)
shape: (2, 4)
┌───────┬───────┬───────┬────────────┐
│ value ┆ mu    ┆ sigma ┆ upper_tail │
│ ---   ┆ ---   ┆ ---   ┆ ---        │
│ f64   ┆ f64   ┆ f64   ┆ f64        │
╞═══════╪═══════╪═══════╪════════════╡
│ 12.1  ┆ 10.0  ┆ 0.5   ┆ 0.000013   │
│ 250.0 ┆ 100.0 ┆ 2.0   ┆ 0.0        │
└───────┴───────┴───────┴────────────┘

Each row is scored against its own Normal(mu, sigma), in one vectorised pass, without leaving the lazy engine.

Numerical accuracy

The maths runs on statrs, and make audit sweeps every method against an mpmath oracle at 50 digits, including inputs many decades past where scipy itself saturates. For tail work on Normal, LogNormal and the closed-form distributions (Uniform, Exponential, Bernoulli), use log_cdf / log_sf rather than the linear pair, and isf(q) rather than ppf(1 - q). Beta and Binomial inherit several documented statrs-side limits in this release: there the log methods underflow with the linear ones, and the extreme lower tail of ppf misbehaves. Every known limit is listed with a regime and a magnitude in Numerical accuracy.

Installation

pip install polars-stats

Runtime needs polars>=1.15 and Python >=3.10.

Documentation

Full docs at fbruzzesi.github.io/polars-stats: the API reference with the distribution catalogue and method surface, and the architecture and design notes.

A note on the Rust code

I am not a Rust expert, and a good part of the Rust layer was written with AI assistance.

What I vouch for is the behaviour, which is pinned by an extensive test suite: parity against scipy.stats on every method, property-based invariants, and bit-identity between the constant-parameter fast paths and the general per-row paths.

Treat the Rust idioms with the appropriate skepticism: if you spot something that should be written differently, an issue or PR is very welcome.

Related projects

polars-stats is not the first take on statistics inside Polars expressions. Three projects cover neighbouring ground, and if your need matches their scope they may serve you well:

  • polars-random generates random columns as native Polars expressions (uniform, normal, binomial, integers), with column-valued parameters, per-call seeds, and a global set_random_seed. It registers .random namespaces on Expr, DataFrame, and LazyFrame, which reads very naturally when sampling is the whole job. Its focus is sampling; polars-stats treats sampling as one method of a full distribution object, next to pdf / cdf / sf / ppf, their numerically stable log variants, and closed-form moments.
  • polars_rng exposes one sampling expression per distribution (prng.normal(mu=pl.col("x"), sigma=3)), also as a Rust plugin over the same statrs crate, also with column-valued parameters. Its sampling catalogue is wider than what polars-stats ships today (Poisson, Gamma, Weibull, Laplace, plus categorical and integer draws), so for pure simulation it may be the better fit. The differences are scope and reproducibility: it is sampling only, with no pdf / cdf / ppf or moments, and it draws from a thread-local RNG with no seed argument, where polars-stats keys every draw on (seed, row index) so a seeded column repeats across runs, chunkings, and engines.
  • polars_normal_stats covers the Normal distribution through three focused expressions, normal_cdf / normal_ppf / normal_pdf, each evaluated at a column of points. Its mean and std travel as plugin kwargs, so they are scalars: the common case, handled in three functions and nothing more. polars-stats generalises the same idea to a catalogue of distributions behind one scipy-like class API, passes parameters as plugin inputs so they can be columns, and adds survival functions, log_cdf / log_sf, and reproducible sampling.

License

This project is licensed under the MIT license.

Download files

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

Source Distribution

polars_stats-0.0.1.tar.gz (322.2 kB view details)

Uploaded Source

Built Distributions

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

polars_stats-0.0.1-cp310-abi3-win_amd64.whl (4.8 MB view details)

Uploaded CPython 3.10+Windows x86-64

polars_stats-0.0.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.3 MB view details)

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

polars_stats-0.0.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (3.8 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64

polars_stats-0.0.1-cp310-abi3-macosx_11_0_arm64.whl (3.7 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

polars_stats-0.0.1-cp310-abi3-macosx_10_12_x86_64.whl (4.1 MB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

Details for the file polars_stats-0.0.1.tar.gz.

File metadata

  • Download URL: polars_stats-0.0.1.tar.gz
  • Upload date:
  • Size: 322.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for polars_stats-0.0.1.tar.gz
Algorithm Hash digest
SHA256 84313e225c80c178c7678c984229410b4517dcead6bd84d393278a2c5b88fd54
MD5 2821eed6b9bf97d6dc8f64c0fa3ae0ca
BLAKE2b-256 fdd95844bfcfd37827e27fc9d7dedf9de0b8502fb4a18bd34e0fb999daf2036f

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_stats-0.0.1.tar.gz:

Publisher: publish_to_pypi.yml on FBruzzesi/polars-stats

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_stats-0.0.1-cp310-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for polars_stats-0.0.1-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 1b4f58427648f5b04202ce92d2d38a0244c790a7ab51f064d1e46ccd034b3b5f
MD5 f325aa4334364489c78b61e7b1470df7
BLAKE2b-256 1dd8565f93dcfbe212549ae5739d35a28200f038362fdb007bdd7e7616f50896

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_stats-0.0.1-cp310-abi3-win_amd64.whl:

Publisher: publish_to_pypi.yml on FBruzzesi/polars-stats

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_stats-0.0.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for polars_stats-0.0.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 5d990bf5dc31900a15fba3edd26918603a6c83e2816a95c9fc989691f15be38b
MD5 96cd106451f9736876d90825748d5c12
BLAKE2b-256 616b3c89cc84c7983060937f3f26c49b28b148b252398b9527c5861e6045c977

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_stats-0.0.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: publish_to_pypi.yml on FBruzzesi/polars-stats

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_stats-0.0.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for polars_stats-0.0.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 757396c8581b9238943ac25aae207b0190c014b3cea7fdb6205e789a718b22c8
MD5 36812d448f22b6c257ce807af2a29a00
BLAKE2b-256 4da0f9304c59ca827f9a0590e1e78896e15758fda733f0769436e0b71d2ff30e

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_stats-0.0.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: publish_to_pypi.yml on FBruzzesi/polars-stats

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_stats-0.0.1-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for polars_stats-0.0.1-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 fd727277139e9d2df902790dc789de3ec561442a76960265e01631b6943ff081
MD5 70340a1f8958ab3a74065f75c0865475
BLAKE2b-256 f74213d3d030f8fbd9d350cd6cb0fce2a3bf87354fa8876008177caf188c88e2

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_stats-0.0.1-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: publish_to_pypi.yml on FBruzzesi/polars-stats

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_stats-0.0.1-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for polars_stats-0.0.1-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 5853263c8a033b6b67f8c814f5087df88b8e116f13990ca404b11831353c7c04
MD5 b83ce2f6cb5703043e99e55afcf30470
BLAKE2b-256 456382bcc879998f4941dcd81cc68ce97f110fff371a24f377cd488f1eb9fb1c

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_stats-0.0.1-cp310-abi3-macosx_10_12_x86_64.whl:

Publisher: publish_to_pypi.yml on FBruzzesi/polars-stats

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

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page