distsfactory
A Python package for constructing probability distributions from partial specifications — moments, quantiles, mode, and support.
Part of the DistributionsFactories family (alongside DistributionsFactories.jl for Julia and distsfactory-r). The Julia package is the parameterization master; this Python port mirrors its behaviour and is cross-validated against it.
Design
- Built on
scipy.stats— returns frozen scipy distribution objects (or scipy-compatible wrappers) that work with the rest of the scientific Python ecosystem. - Specify what you know (mean, variance, quantiles, mode, support) and get back a ready-to-use distribution.
- Accepts distribution names as strings or
scipy.statsdistribution objects directly.
Quick start
from distsfactory import make_dist
# Construct from moments — returns a frozen scipy.stats distribution
d = make_dist("gamma", mean=5, var=3)
type(d) # scipy.stats._distn_infrastructure.rv_continuous_frozen
d.pdf(2) # density
d.cdf(0.95) # CDF
d.ppf(0.5) # quantile (percent point function)
d.rvs(100) # random samples
d.mean() # 5.0
d.var() # 3.0
# scipy.stats objects work too
import scipy.stats as st
d = make_dist(st.gamma, mean=5, var=3)
# Truncated Normal on [-1, 4] with mean 1 and standard deviation 0.8
d = make_dist("normal", mean=1.0, std=0.8, support=(-1, 4))
type(d) # scipy.stats._distn_infrastructure.rv_continuous_frozen
# (a scipy.stats.truncnorm)
d.mean(), d.std() # (1.0, 0.8) — moments after truncation
d.kwds # {'a': -2.408, 'b': 3.666,
# 'loc': 0.9822, 'scale': 0.8232}
# — parent Normal(μ, σ) solved so the truncated
# distribution hits the requested (mean, std)
Whenever scipy provides a native truncated form (truncnorm, truncexpon, …) we return that. For families where scipy doesn't (Truncated{Laplace}, Truncated{Gamma}, …) the return is a distsfactory._support._TruncatedDist wrapper with the same pdf/cdf/ppf/mean/var/rvs surface.
Supported distributions
27 families across continuous (real, positive, unit) and discrete supports. Each family supports a subset of specification types — mean+var is universal; quantile- and mode-based forms are implemented where they exist in the Julia package.
Continuous on (-∞, ∞)
| Distribution | Support | Free params | Methods |
|---|---|---|---|
| Normal | (-∞, ∞) |
2 | mean+var, q1+q3, mode+var, mode+iqr |
| Student's T | (-∞, ∞) |
1 (+2 via partial_dist) |
mean+var (μ=0); arbitrary via partial_dist("tdist", df=ν) |
| Cauchy | (-∞, ∞) |
2 | two quantiles (moments undefined) |
| Laplace | (-∞, ∞) |
2 | mean+var, two quantiles, mode+iqr |
| Logistic | (-∞, ∞) |
2 | mean+var, two quantiles, mode+iqr, mean+quantile |
| Gumbel | (-∞, ∞) |
2 | mean+var, two quantiles |
| Uniform | (-∞, ∞) |
2 | mean+var |
| Symmetric Triangular | (-∞, ∞) |
2 | mean+var |
| Triangular (asymmetric) | (-∞, ∞) |
3 | mean+var+mode |
Continuous on [0, ∞)
| Distribution | Free params | Methods |
|---|---|---|
| Gamma | 2 | mean+var, mean+mode, mode+var, mode+iqr, mode+quantile, two quantiles, mean+quantile |
| Erlang | 2 | mean+var (k rounded — see #4) |
| Exponential | 1 | mean, var, single quantile, mean+var |
| Chi-squared | 1 | mean+var, mean alone, var alone |
| Chi | 2 | mean+var |
| Rayleigh | 1 | mean, var, mode, single quantile, mean+var |
| Log-normal | 2 | mean+var, two quantiles, mean+quantile |
| Weibull | 2 | mean+var, two quantiles |
| Frechet | 2 | mean+var |
| F | 2 | mean+var |
| Inverse Gamma | 2 | mean+var |
| Pareto | 2 | mean+var, two quantiles |
| Folded Normal | 2 | mean+var (2D Newton) |
Continuous on [0, 1]
| Distribution | Free params | Methods |
|---|---|---|
| Beta | 2 | mean+var, mean+mode, two quantiles, mean+quantile |
Discrete
| Distribution | Support | Methods |
|---|---|---|
| Binomial | {0, …, n} |
mean+var |
| Discrete Uniform | {a, …, b} |
mean+var |
| Discrete Symmetric Triangular | {μ-n, …, μ+n} |
mean+var |
| Discrete Triangular | {a, …, b} (mode at c) |
mean+var+mode (approximate) |
| Poisson | {0, 1, 2, …} |
mean, var, mean+var |
| Negative Binomial | {0, 1, 2, …} |
mean+var |
| Geometric | {0, 1, 2, …} |
mean, var, single quantile, mean+var |
Specification styles
# Moment-based
make_dist("gamma", mean=5, var=3)
make_dist("gamma", mean=5, std=2) # std -> var
make_dist("gamma", mean=5, cv=0.5) # coefficient of variation
make_dist("gamma", mean=4, scv=0.5) # squared CV
make_dist("gamma", mean=5, second_moment=28) # E[X²] -> var
make_dist("exponential", mean=3) # 1-parameter family
# Quantile-based
make_dist("exponential", median=2.0)
make_dist("logistic", q1=2, q3=8)
make_dist("normal", q1=-1, q3=1)
make_dist("gamma", quantiles=[(0.1, 1.0), (0.9, 10.0)])
make_dist("beta", mean=0.4, median=0.38)
make_dist("normal", median=5, iqr=2)
# Mode-based
make_dist("rayleigh", mode=2)
make_dist("gamma", mean=5, mode=3)
make_dist("beta", mean=0.4, mode=0.35)
make_dist("gamma", mode=3, iqr=4)
make_dist("normal", mode=3, var=4)
make_dist("logistic", mode=5, iqr=4)
# 3-parameter triangular (mean + var + mode)
make_dist("triangular", mean=5, var=2, mode=4)
make_dist("discrete_triangular", mean=5, var=2, mode=5)
Support — affine transforms and truncation
The support= keyword places a distribution on an arbitrary support. The package chooses between an affine transform (when the requested support has the same shape as the natural one) and truncation (when it's strictly contained).
import math
# Affine shift — Gamma on [3, ∞)
make_dist("gamma", mean=8, var=3, support=(3, math.inf))
# Affine flip — Gamma on (-∞, 10]
make_dist("gamma", mean=5, var=3, support=(-math.inf, 10))
# Affine scale — Beta on [2, 7]
make_dist("beta", mean=3.5, var=0.5, support=(2, 7))
# Truncation — Normal on [-0.5, 0.5] with moment matching (2D Newton)
make_dist("normal", mean=0.1, var=0.05, support=(-0.5, 0.5))
# Truncation — Gamma/Beta with actual moment matching (generic 2D Newton)
make_dist("gamma", mean=3, var=1, support=(0, 10))
make_dist("beta", mean=0.5, var=0.02, support=(0.2, 0.8))
# Discrete shift — Binomial on {10, …, 15}
make_dist("binomial", mean=12, var=1.2, support=range(10, 16))
# Truncated Poisson on {2, …, 10} (var is determined by mean)
make_dist("poisson", mean=2.5, var=0.59, support=range(2, 11))
For location-scale Student-t, use partial_dist together with support=:
from distsfactory import partial_dist, make_dist
spec = partial_dist("tdist", df=5)
# Half-truncated location-scale Student-t
d = make_dist(spec, mean=2.0, var=1.0, support=(0.0, math.inf))
(Two-sided Truncated{TDist} is not implemented yet — same gap as in the Julia package; see #2.)
Partial specifications — partial_dist
The Python analog of Julia's @dist macro. Pin some scipy parameters and leave the rest to be solved from moment constraints.
from distsfactory import partial_dist, make_dist
# Pin Gamma's shape, solve scale from mean
spec = partial_dist("gamma", a=3.0)
d = make_dist(spec, mean=5.0)
d.kwds # {'a': 3.0, 'scale': 1.6666…}
# Pin Gamma's shape, solve scale from variance
d = make_dist(spec, var=3.0)
# Pin Logistic's location, solve scale from variance
spec = partial_dist("logistic", loc=2.0)
d = make_dist(spec, var=22.3)
# Pin Beta's α, solve β from mean
spec = partial_dist("beta", a=2.0)
d = make_dist(spec, mean=0.4)
d.kwds # {'a': 2.0, 'b': 3.0}
# Full instance — Student-t with df=7, solve loc/scale from (mean, var)
spec = partial_dist("tdist", df=7)
d = make_dist(spec, mean=5.0, var=2.0)
partial_dist uses the canonical (Julia-compatible) parameter set for each family. For example, gamma exposes (a, scale) — not (a, loc, scale) — because Julia's Gamma has only (α, θ). To shift the support, use support=.
Feasibility checks
from distsfactory import dist_exists
dist_exists("beta", mean=0.5, var=0.1) # True
dist_exists("beta", mean=0.5, var=0.3) # False (var too large)
dist_exists("exponential", mean=2.5, var=6.25) # True (var == mean²)
dist_exists("exponential", mean=2.5, var=1.5) # False
dist_exists("tdist", mean=1, var=2) # False (TDist requires mean=0)
When make_dist fails for the same input, it raises ValueError carrying the same reason string.
Discovery
from distsfactory import available_distributions
import math
# All distributions feasible for these moments
available_distributions(mean=5, var=3)
# ['normal', 'laplace', 'logistic', 'gumbel', 'uniform', 'sym_triangular',
# 'gamma', 'erlang', 'lognormal', 'weibull', 'frechet', 'inverse_gamma',
# 'pareto', 'folded_normal']
# Filter by natural support: tuple, range, or category string
available_distributions(support="positive")
available_distributions(support=(0, math.inf), mean=5, var=3)
available_distributions(support=(0, 1), mean=0.5, var=0.05) # -> ['beta']
available_distributions(support="integer_nonneg")
Testing
The test suite is self-contained — pytest covers the package end-to-end with no Julia install required.
One additional file, tests/test_cross_julia.py, reads a checked-in JSON oracle (tests/data/cross_oracle.json) of reference values generated by the Julia package. It catches any cross-language numerical drift. The oracle is produced by scripts/build_cross_oracle.jl in the Julia repo, which by default writes the oracle into both the Python and R sibling-package test directories in a single run. Regenerate after material changes with:
cd ../DistributionsFactories.jl
julia --project=. scripts/build_cross_oracle.jl
Installation
pip install distsfactory
Requires Python ≥ 3.10. Pulls in scipy >= 1.11 and numpy >= 1.24.
Development install:
pip install -e ".[dev]"
Releases are cut from a git tag; see PUBLISHING.md.
Known residuals (parity with Julia)
The Julia package is the parameterization master. Inherited issues are tracked locally with a link to the upstream Julia issue:
- #1 — Gamma
from_mean_modeaccepts negative mode (bug; mirrors Julia #7) - #2 — two-sided
Truncated{TDist}not implemented (mirrors Julia #1) - #3 —
parse_specsilently dropsstdwhen bothvarandstdpassed (mirrors Julia #10) - #4 — Erlang is approximate but undocumented (mirrors Julia #11)
- #5 — Frechet single-point start at
CV²==1(mirrors Julia #12)
Authors
Ron Ashri, Sarat Moka, Yoni Nazarathy
Metadata
Release files for distsfactory 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| distsfactory-0.2.0.tar.gz | 59.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| distsfactory-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 102.5 kB
Release files / distsfactory-0.2.0.tar.gz
| Download URL | distsfactory-0.2.0.tar.gz |
|---|---|
| Size | 59.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
896304fb7a3c024818fd1f0ab978e2ff9a6bd0b69d24df80b4829381127259e8
|
|
BLAKE2b-256 checksum How to use checksums |
cb0825137d82af5d6b3d1c9ea3a0013294790d424992e9898a6000c284da3001
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 May 21, 2026.
Transparency logRelease files / distsfactory-0.2.0-py3-none-any.whl
| Download URL | distsfactory-0.2.0-py3-none-any.whl |
|---|---|
| Size | 43.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
31057665c80af26f6ccbfeef475a419288555f1f32c38ebe5092d8e69ca7f5f9
|
|
BLAKE2b-256 checksum How to use checksums |
be9ac07fbc4f5231c85809b6080e8b2d847bb003963e49f1f6026f6ccd922d7e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
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 May 21, 2026.
Transparency log