Skip to content

API reference

Technical description of the machinery. The generated docstrings live on two pages, Continuous and Discrete; the input types, dtypes, and error contracts are in Parameters and contracts.

For worked examples, see the tutorial and the How-to guides.

Catalogue

Distribution Kind Parameters scipy equivalent
Beta(a, b) continuous a > 0, b > 0 beta(a, b)
Cauchy(loc, scale) continuous scale > 0 cauchy(loc=loc, scale=scale); its undefined moments are null here, nan there
Exponential(rate) continuous rate > 0 expon(scale=1 / rate)
LogNormal(mu, sigma) continuous sigma > 0 lognorm(s=sigma, scale=exp(mu))
Normal(mu, sigma) continuous sigma > 0 norm(loc=mu, scale=sigma)
Pareto(scale, shape) continuous scale > 0, shape > 0 pareto(b=shape, scale=scale); the divergent moments are +inf in both
Uniform(min, max) continuous max > min uniform(loc=min, scale=max - min)
Weibull(shape, scale) continuous shape > 0, scale > 0 weibull_min(c=shape, scale=scale)
Bernoulli(p) discrete 0 <= p <= 1 bernoulli(p)
Binomial(n, p) discrete n >= 0, 0 <= p <= 1 binom(n, p)
DiscreteUniform(min, max) discrete min <= max, both inclusive randint(low=min, high=max + 1); max is inclusive
Geometric(p) discrete 0 < p <= 1 geom(p)

Each class names its parameters after the distribution's conventional parameters. Normal and LogNormal default to mu=0.0, sigma=1.0; the others have no defaults. Parameter values are validated at evaluation, not at construction.

Method surface

Every distribution exposes the same surface, defined on the base classes. Value-keyed methods take a scalar, a column name (str), or a pl.Expr; argument-free statistics take none. All return a pl.Expr.

Method Continuous Discrete Meaning
pdf(x) yes no probability density
log_pdf(x) yes no log density
pmf(x) no yes probability mass
log_pmf(x) no yes log mass
cdf(x) yes yes P(X <= x)
sf(x) yes yes survival, P(X > x), accurate in the upper tail
ppf(q) yes yes inverse cdf, q in [0, 1]
isf(q) yes yes inverse survival, the x with sf(x) = q
log_cdf(x) yes yes log cdf
log_sf(x) yes yes log survival
mean() yes yes E[X]
variance() / std() yes yes variance and its square root
median() yes yes ppf(0.5), or a closed form when available
entropy() yes yes differential / Shannon entropy, in nats
sample(seed=None) yes yes one variate per row
samples(size, seed=None) yes yes a width-size Array per row

Every value-keyed method runs in Rust (a native statrs binding or a hand-written stable form); median is ppf(0.5) unless a closed form exists, and std is sqrt(variance) unless a form with a wider representable range does. The exceptions are Beta and Binomial, whose log_cdf / log_sf are log(cdf) / log(sf) for now (see Accuracy).

Argument-free statistics return one value per row of parameters: with column-valued parameters, mean() yields the mean of a different distribution on every row. A moment the distribution does not have is null, one whose integral diverges is +inf, and both still raise on an invalid parameter; see Parameters and contracts.

Type guards

Two module-level guards say which kind a distribution is, for code that accepts either and has to pick between pmf and pdf. Both narrow the argument's type, so the branch body needs no cast.

is_discrete

is_discrete(obj: object) -> TypeIs[DiscreteDistribution]

Whether obj is a discrete distribution.

Narrowing guard: on the true branch a type checker sees obj as a DiscreteDistribution, so pmf / log_pmf are reachable without a cast, and on the false branch it drops DiscreteDistribution from the type.

Parameters:

Name Type Description Default
obj object

Any object.

required

Returns:

Type Description
TypeIs[DiscreteDistribution]

True if obj is an instance of DiscreteDistribution.

Examples:

>>> import polars_stats as ps
>>> ps.is_discrete(ps.Binomial(10, 0.5))
True
>>> ps.is_discrete(ps.Normal())
False

Dispatching on the kind picks the density method that exists:

>>> import polars as pl
>>> def density(dist: ps.DiscreteDistribution | ps.ContinuousDistribution, value: str):
...     return dist.pmf(value) if ps.is_discrete(dist) else dist.pdf(value)
>>> pl.DataFrame({"x": [0.0, 1.0]}).select(density(ps.Binomial(10, 0.5), "x"))
shape: (2, 1)
┌──────────┐
│ x        │
│ ---      │
│ f64      │
╞══════════╡
│ 0.000977 │
│ 0.009766 │
└──────────┘
Source code in polars_stats/distributions/_base.py
def is_discrete(obj: object, /) -> TypeIs[DiscreteDistribution]:
    """Whether ``obj`` is a discrete distribution.

    Narrowing guard: on the true branch a type checker sees ``obj`` as a
    ``DiscreteDistribution``, so ``pmf`` / ``log_pmf`` are reachable without a cast, and on the
    false branch it drops ``DiscreteDistribution`` from the type.

    Arguments:
        obj: Any object.

    Returns:
        ``True`` if ``obj`` is an instance of ``DiscreteDistribution``.

    Examples:
        >>> import polars_stats as ps
        >>> ps.is_discrete(ps.Binomial(10, 0.5))
        True
        >>> ps.is_discrete(ps.Normal())
        False

        Dispatching on the kind picks the density method that exists:

        >>> import polars as pl
        >>> def density(dist: ps.DiscreteDistribution | ps.ContinuousDistribution, value: str):
        ...     return dist.pmf(value) if ps.is_discrete(dist) else dist.pdf(value)
        >>> pl.DataFrame({"x": [0.0, 1.0]}).select(density(ps.Binomial(10, 0.5), "x"))
        shape: (2, 1)
        ┌──────────┐
        │ x        │
        │ ---      │
        │ f64      │
        ╞══════════╡
        │ 0.000977 │
        │ 0.009766 │
        └──────────┘
    """
    return isinstance(obj, DiscreteDistribution)

is_continuous

is_continuous(obj: object) -> TypeIs[ContinuousDistribution]

Whether obj is a continuous distribution.

The counterpart of is_discrete: the two kinds are disjoint, so exactly one of the guards holds for any distribution in this package.

Parameters:

Name Type Description Default
obj object

Any object.

required

Returns:

Type Description
TypeIs[ContinuousDistribution]

True if obj is an instance of ContinuousDistribution.

Examples:

>>> import polars_stats as ps
>>> ps.is_continuous(ps.Normal())
True
>>> ps.is_continuous(ps.Binomial(10, 0.5))
False

The guard narrows, so pdf type checks inside the branch:

>>> import polars as pl
>>> dist = ps.Normal(mu=0.0, sigma=1.0)
>>> if ps.is_continuous(dist):
...     print(round(pl.DataFrame({"x": [0.0]}).select(dist.pdf("x")).item(), 6))
0.398942
Source code in polars_stats/distributions/_base.py
def is_continuous(obj: object, /) -> TypeIs[ContinuousDistribution]:
    """Whether ``obj`` is a continuous distribution.

    The counterpart of [`is_discrete`][polars_stats.is_discrete]: the two kinds are disjoint, so
    exactly one of the guards holds for any distribution in this package.

    Arguments:
        obj: Any object.

    Returns:
        ``True`` if ``obj`` is an instance of ``ContinuousDistribution``.

    Examples:
        >>> import polars_stats as ps
        >>> ps.is_continuous(ps.Normal())
        True
        >>> ps.is_continuous(ps.Binomial(10, 0.5))
        False

        The guard narrows, so ``pdf`` type checks inside the branch:

        >>> import polars as pl
        >>> dist = ps.Normal(mu=0.0, sigma=1.0)
        >>> if ps.is_continuous(dist):
        ...     print(round(pl.DataFrame({"x": [0.0]}).select(dist.pdf("x")).item(), 6))
        0.398942
    """
    return isinstance(obj, ContinuousDistribution)

Compatibility

Dimension Values
Python 3.10 to 3.14 (per requires-python), single abi3 wheel
Polars >=1.15 (the pyo3-polars ABI floor)
OS wheels for Linux x86_64/aarch64, macOS arm64/x86_64, Windows x86_64
Runtime dependencies polars only