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, Cauchy, Pareto, Weibull, Bernoulli, Geometric, DiscreteUniform), 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.

Wheels are abi3, so one wheel per platform serves every Python from 3.10 up: manylinux x86_64 and aarch64, macOS x86_64 and arm64, Windows x64, plus an sdist. Alpine (musl) and Windows on ARM have no wheel today even though polars publishes one for both; there pip falls back to the sdist and needs a Rust toolchain. Open an issue if you need either target and it gets added.

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.

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, 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.

Metadata

Release files for polars-stats 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for polars-stats 0.1.0
File Size Uploaded
polars_stats-0.1.0.tar.gz 406.9 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for polars-stats 0.1.0
File
polars_stats-0.1.0-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
polars_stats-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 abi3 Linux glibc 2.17+ x86-64 Details
polars_stats-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl CPython 3.10 abi3 Linux glibc 2.17+ ARM64 Details
polars_stats-0.1.0-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
polars_stats-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 32.5 MB

Release files / polars_stats-0.1.0.tar.gz

Download URL polars_stats-0.1.0.tar.gz
Size 406.9 kB
Tags Source
SHA-256 checksum
How to use checksums
e2f41dd66cf825cacf0fcc84b5c925b2a29c3fa0e3b0a8cea20d3e410f193b2e
BLAKE2b-256 checksum
How to use checksums
ab07a67292059206951fa752d2f320c44e3072cbb118945d9cd015a1f602a11f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release files / polars_stats-0.1.0-cp310-abi3-win_amd64.whl

Download URL polars_stats-0.1.0-cp310-abi3-win_amd64.whl
Size 7.7 MB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
aa02e3af72b1512477c4de92497c099ffc7a6767a67a3a5e0059b5294d9087a4
BLAKE2b-256 checksum
How to use checksums
b686bb80b015f77de7b3155d175d050656ec2c68e43d39b8b38795ac117091bf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release files / polars_stats-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL polars_stats-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 6.6 MB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
f30c39ccd0d0fb7ee783852fdd39cf8061a7e6e5f198f70c8263f27f55e3871e
BLAKE2b-256 checksum
How to use checksums
734a13f7fb460a9db4380eb9eb44bbbb029eaf83bd3e65b51b72b84ca8a6046e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release files / polars_stats-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL polars_stats-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 5.7 MB
Tags CPython 3.10 Linux glibc 2.17+ ARM64 abi3
SHA-256 checksum
How to use checksums
2d7dd4d084f549b396899c16f57bf1d86feac51e7e016d81e5bb0640b95ba1df
BLAKE2b-256 checksum
How to use checksums
b99f4c1dccec8f75108d90b631f4f97eb739ba4a56edc6589398b623fc2c33a2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release files / polars_stats-0.1.0-cp310-abi3-macosx_11_0_arm64.whl

Download URL polars_stats-0.1.0-cp310-abi3-macosx_11_0_arm64.whl
Size 5.6 MB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
a6eb965c1823b4f173d09a6a4807878e0aab9106a76e313d4f4fc0848119c20c
BLAKE2b-256 checksum
How to use checksums
29991314871cbbff61223bc15a26dd95cae474864cf5eb6284a559047d1ea857
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release files / polars_stats-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl

Download URL polars_stats-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl
Size 6.5 MB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
c2a1d5c0e7c8a92cb335f918c6b96b865549f8b60302051a987771f919d9f6fc
BLAKE2b-256 checksum
How to use checksums
df8a8855caeddeafd2ad4e56610b221d5269281699a3a762db1658cf6637a682
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

6 release files

0.0.3

6 release files

0.0.2

6 release files

0.0.1

6 release 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