distparams 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- distparams-0.1.0/PKG-INFO +496 -0
- distparams-0.1.0/README.md +475 -0
- distparams-0.1.0/pyproject.toml +78 -0
- distparams-0.1.0/src/distparams/__init__.py +72 -0
- distparams-0.1.0/src/distparams/_converter.py +557 -0
- distparams-0.1.0/src/distparams/_decorator.py +385 -0
- distparams-0.1.0/src/distparams/_distributions/__init__.py +159 -0
- distparams-0.1.0/src/distparams/_distributions/asymmetric_laplace.py +42 -0
- distparams-0.1.0/src/distparams/_distributions/bernoulli.py +39 -0
- distparams-0.1.0/src/distparams/_distributions/beta.py +125 -0
- distparams-0.1.0/src/distparams/_distributions/beta_binomial.py +21 -0
- distparams-0.1.0/src/distparams/_distributions/beta_negative_binomial.py +27 -0
- distparams-0.1.0/src/distparams/_distributions/binomial.py +24 -0
- distparams-0.1.0/src/distparams/_distributions/burr12.py +30 -0
- distparams-0.1.0/src/distparams/_distributions/categorical.py +21 -0
- distparams-0.1.0/src/distparams/_distributions/cauchy.py +24 -0
- distparams-0.1.0/src/distparams/_distributions/chi.py +21 -0
- distparams-0.1.0/src/distparams/_distributions/chi_squared.py +17 -0
- distparams-0.1.0/src/distparams/_distributions/dirichlet.py +17 -0
- distparams-0.1.0/src/distparams/_distributions/dirichlet_multinomial.py +19 -0
- distparams-0.1.0/src/distparams/_distributions/discrete_laplace.py +30 -0
- distparams-0.1.0/src/distparams/_distributions/discrete_uniform.py +22 -0
- distparams-0.1.0/src/distparams/_distributions/discrete_weibull.py +24 -0
- distparams-0.1.0/src/distparams/_distributions/exgaussian.py +50 -0
- distparams-0.1.0/src/distparams/_distributions/exponential.py +22 -0
- distparams-0.1.0/src/distparams/_distributions/f.py +19 -0
- distparams-0.1.0/src/distparams/_distributions/gamma.py +74 -0
- distparams-0.1.0/src/distparams/_distributions/generalized_extreme.py +35 -0
- distparams-0.1.0/src/distparams/_distributions/generalized_normal.py +32 -0
- distparams-0.1.0/src/distparams/_distributions/generalized_pareto.py +29 -0
- distparams-0.1.0/src/distparams/_distributions/generalized_poisson.py +25 -0
- distparams-0.1.0/src/distparams/_distributions/geometric.py +21 -0
- distparams-0.1.0/src/distparams/_distributions/gompertz.py +32 -0
- distparams-0.1.0/src/distparams/_distributions/gumbel.py +23 -0
- distparams-0.1.0/src/distparams/_distributions/half_cauchy.py +21 -0
- distparams-0.1.0/src/distparams/_distributions/half_generalized_normal.py +29 -0
- distparams-0.1.0/src/distparams/_distributions/half_normal.py +23 -0
- distparams-0.1.0/src/distparams/_distributions/half_student_t.py +28 -0
- distparams-0.1.0/src/distparams/_distributions/hypergeometric.py +21 -0
- distparams-0.1.0/src/distparams/_distributions/inverse_gamma.py +25 -0
- distparams-0.1.0/src/distparams/_distributions/johnson_su.py +35 -0
- distparams-0.1.0/src/distparams/_distributions/kumaraswamy.py +19 -0
- distparams-0.1.0/src/distparams/_distributions/laplace.py +23 -0
- distparams-0.1.0/src/distparams/_distributions/levy.py +23 -0
- distparams-0.1.0/src/distparams/_distributions/lkj.py +17 -0
- distparams-0.1.0/src/distparams/_distributions/log_logistic.py +30 -0
- distparams-0.1.0/src/distparams/_distributions/log_normal.py +26 -0
- distparams-0.1.0/src/distparams/_distributions/log_uniform.py +30 -0
- distparams-0.1.0/src/distparams/_distributions/logistic.py +24 -0
- distparams-0.1.0/src/distparams/_distributions/logit_normal.py +26 -0
- distparams-0.1.0/src/distparams/_distributions/lomax.py +29 -0
- distparams-0.1.0/src/distparams/_distributions/matrix_normal.py +21 -0
- distparams-0.1.0/src/distparams/_distributions/maxwell.py +25 -0
- distparams-0.1.0/src/distparams/_distributions/moyal.py +19 -0
- distparams-0.1.0/src/distparams/_distributions/multinomial.py +24 -0
- distparams-0.1.0/src/distparams/_distributions/multivariate_normal.py +19 -0
- distparams-0.1.0/src/distparams/_distributions/negative_binomial.py +44 -0
- distparams-0.1.0/src/distparams/_distributions/normal.py +25 -0
- distparams-0.1.0/src/distparams/_distributions/pareto.py +30 -0
- distparams-0.1.0/src/distparams/_distributions/poisson.py +23 -0
- distparams-0.1.0/src/distparams/_distributions/rayleigh.py +21 -0
- distparams-0.1.0/src/distparams/_distributions/rice.py +28 -0
- distparams-0.1.0/src/distparams/_distributions/skellam.py +17 -0
- distparams-0.1.0/src/distparams/_distributions/skew_normal.py +33 -0
- distparams-0.1.0/src/distparams/_distributions/skew_student_t.py +33 -0
- distparams-0.1.0/src/distparams/_distributions/student_t.py +24 -0
- distparams-0.1.0/src/distparams/_distributions/trapezoid.py +55 -0
- distparams-0.1.0/src/distparams/_distributions/triangular.py +24 -0
- distparams-0.1.0/src/distparams/_distributions/truncated_normal.py +72 -0
- distparams-0.1.0/src/distparams/_distributions/uniform.py +44 -0
- distparams-0.1.0/src/distparams/_distributions/von_mises.py +30 -0
- distparams-0.1.0/src/distparams/_distributions/wald.py +19 -0
- distparams-0.1.0/src/distparams/_distributions/weibull.py +27 -0
- distparams-0.1.0/src/distparams/_distributions/wishart.py +19 -0
- distparams-0.1.0/src/distparams/_distributions/zipf.py +25 -0
- distparams-0.1.0/src/distparams/_ops.py +82 -0
- distparams-0.1.0/src/distparams/_plugins.py +156 -0
- distparams-0.1.0/src/distparams/_registry.py +46 -0
- distparams-0.1.0/src/distparams/_types.py +387 -0
- distparams-0.1.0/src/distparams/_vocabularies.py +193 -0
- distparams-0.1.0/src/distparams/integrations/__init__.py +12 -0
- distparams-0.1.0/src/distparams/integrations/scipy.py +158 -0
- distparams-0.1.0/src/distparams/plugins.py +36 -0
- distparams-0.1.0/src/distparams/py.typed +0 -0
- distparams-0.1.0/src/distparams/random.py +212 -0
- distparams-0.1.0/src/distparams/stats.py +93 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: distparams
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Single source of truth for statistical distribution parameterizations
|
|
5
|
+
Keywords: statistics,distribution,parameterization,bayesian,pymc,scipy
|
|
6
|
+
Author: Will Dean
|
|
7
|
+
Author-email: Will Dean <wd60622@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
17
|
+
Requires-Python: >=3.12, <3.15
|
|
18
|
+
Project-URL: Homepage, https://github.com/williambdean/distparams
|
|
19
|
+
Project-URL: Repository, https://github.com/williambdean/distparams
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# distparams
|
|
23
|
+
|
|
24
|
+
Single source of truth for statistical distribution parameterizations.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install distparams
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from distparams import get_distribution, Support
|
|
36
|
+
|
|
37
|
+
# Get distribution info
|
|
38
|
+
normal = get_distribution("normal")
|
|
39
|
+
sigma = normal.canonical.parameters["sigma"]
|
|
40
|
+
|
|
41
|
+
# Check parameter support
|
|
42
|
+
sigma.support.contains(0.5) # True
|
|
43
|
+
sigma.support.contains(-1) # False
|
|
44
|
+
str(sigma.support) # "(0, ∞)"
|
|
45
|
+
|
|
46
|
+
# Convert between ecosystems
|
|
47
|
+
from distparams import convert
|
|
48
|
+
|
|
49
|
+
convert("gamma", "scipy", "pymc", a=2, scale=3)
|
|
50
|
+
# → {"alpha": 2, "beta": 0.333}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Core concepts
|
|
54
|
+
|
|
55
|
+
| Term | What it means |
|
|
56
|
+
|---|---|
|
|
57
|
+
| **Parameter** | The canonical name for one semantic parameter — normal's `sigma` is PyMC's `sigma`, scipy's `scale`, jStat's `std`: one parameter, many names. Parameters own their `Support` (admissible values) and their bijections. A parameter is a mutable builder while authoring; once registered, its bijections change only via `register_parameter_alias`. |
|
|
58
|
+
| **Bijection** | A name's value relation to a canonical parameter: a one-to-one map with its inverse. The identity bijection is a pure rename — a true *alias* (`loc` ↔ `mu`); a transformed one reparameterizes the value (`tau` → `sigma = tau**-0.5`, via `Bijection.power`/`Bijection.reciprocal`, or an explicit pair when the inverse isn't mechanical, like logit ↔ probability). |
|
|
59
|
+
| **ParameterSet** | A complete set of parameters that determines the distribution — what PPL docs call a "parameterization" (`{mu, sigma}`, `{mu, tau}`). Derived parameterizations live here too: their members are functions of *several* canonical parameters, so they are not bijections at all (gamma's `mu = shape/rate`; beta's `(mu, sigma)` combines the mean and sd relations). Derived sets carry `name`/`description` with the defining formulas, so the registry can explain them. |
|
|
60
|
+
| **NamingConvention** | One ecosystem's parameter names for one distribution. Purely a naming relation — value transforms never live here; a value-transforming name pairs the row with a parameter bijection. |
|
|
61
|
+
| **Vocabulary** | One ecosystem's names for a *parametrization*, written once and applying to every distribution that uses it (keyed `(ecosystem, parametrization)`). Per-distribution conventions override it. |
|
|
62
|
+
| **Distribution** | Canonical parameters + parameter sets + naming overrides. The canonical parametrization name is derived from the parameter declaration order. |
|
|
63
|
+
|
|
64
|
+
Conversion flows *source names → canonical parameters → target names*:
|
|
65
|
+
names resolve through conventions, vocabularies, and bijections; values
|
|
66
|
+
transform through parameter bijections and parameter-set transforms.
|
|
67
|
+
Derived parameterizations are self-describing:
|
|
68
|
+
`get_distribution("beta").canonical.parameter_sets` includes the `(mu, sigma)`
|
|
69
|
+
set with its name and defining formulas.
|
|
70
|
+
|
|
71
|
+
## Coming from scipy?
|
|
72
|
+
|
|
73
|
+
Keep your scipy distributions — lose the parameter-name bookkeeping.
|
|
74
|
+
`distparams.stats` is a drop-in-shaped stand-in for `scipy.stats`: the same
|
|
75
|
+
attribute names (`norm`, `t`, `chi2`, `binom`, ...), but every call accepts
|
|
76
|
+
any parametrization the registry knows and returns a frozen scipy
|
|
77
|
+
distribution:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from distparams.stats import norm
|
|
81
|
+
|
|
82
|
+
norm(mu=0, sigma=1) # ✓ canonical
|
|
83
|
+
norm(loc=0, scale=1) # ✓ plain scipy
|
|
84
|
+
norm(mu=0, scale=1) # ✓ mix freely — names resolve through the registry
|
|
85
|
+
norm(tau=4) # ✓ precision → sigma = 0.5
|
|
86
|
+
# → a frozen scipy distribution: .pdf, .rvs, .mean, ...
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from distparams import stats
|
|
91
|
+
|
|
92
|
+
stats.gamma(mu=4, sigma=2) # ✓ mean/sd parameterization
|
|
93
|
+
stats.binom(n=10, p=0.3) # ✓ discrete, scipy names
|
|
94
|
+
stats.expon(scale=2) # ✓ registry names, scipy spelling
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`dir(distparams.stats)` lists every scipy-style name the registry covers;
|
|
98
|
+
anything else (`stats.normal`, distributions scipy doesn't have) raises an
|
|
99
|
+
`AttributeError` listing what is available. Attributes resolve lazily, so
|
|
100
|
+
importing `distparams` never imports scipy — scipy is only needed when a
|
|
101
|
+
distribution is actually used.
|
|
102
|
+
|
|
103
|
+
Prefer the explicit form? `from_scipy` wraps any `scipy.stats` distribution
|
|
104
|
+
the same way:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from scipy import stats
|
|
108
|
+
from distparams.integrations import from_scipy
|
|
109
|
+
|
|
110
|
+
new_normal = from_scipy(stats.norm)
|
|
111
|
+
|
|
112
|
+
new_normal(mu=0, sigma=1) # ✓ canonical
|
|
113
|
+
new_normal(loc=0, scale=1) # ✓ plain scipy
|
|
114
|
+
new_normal(mean=0, std=1) # ✓ jStat-style names
|
|
115
|
+
new_normal(tau=4) # ✓ precision → sigma = 0.5
|
|
116
|
+
new_normal(0, 1) # ✓ positional
|
|
117
|
+
# → a frozen scipy distribution: .pdf, .rvs, .mean, ...
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Aliases, alternative parameterizations, and even whole derived
|
|
121
|
+
parametrizations come straight from the registry — no mapping code per
|
|
122
|
+
distribution, and anything registered later works too:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
new_gamma = from_scipy(stats.gamma)
|
|
126
|
+
|
|
127
|
+
new_gamma(alpha=2, beta=3) # ✓ shape/rate → a=2, scale=1/3
|
|
128
|
+
new_gamma(2) # ✓ partial — scipy defaults fill the rest
|
|
129
|
+
new_gamma(mu=4, sigma=2) # ✓ mean/sd parameterization
|
|
130
|
+
|
|
131
|
+
new_uniform = from_scipy(stats.uniform)
|
|
132
|
+
new_uniform(lower=1, upper=3) # ✓ even loc/scale via registry transforms
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Unknown names are caught at call time instead of silently misbehaving, and a
|
|
136
|
+
scipy distribution that has no registry entry raises with instructions rather
|
|
137
|
+
than guessing.
|
|
138
|
+
|
|
139
|
+
## Coming from the stdlib random module?
|
|
140
|
+
|
|
141
|
+
`distparams.random` mirrors the `random` module with zero dependencies: the
|
|
142
|
+
variate generators accept every registered parametrization, and everything
|
|
143
|
+
else (`seed`, `choice`, `shuffle`, …) delegates to the RNG untouched:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
import distparams.random
|
|
147
|
+
|
|
148
|
+
distparams.random.gamma(shape=2, scale=3) # canonical names
|
|
149
|
+
distparams.random.gamma(alpha=2, beta=1 / 3) # pymc names (beta is the rate)
|
|
150
|
+
distparams.random.weibull(alpha=2, beta=1.5) # pymc order — alpha is the shape, beta the scale
|
|
151
|
+
|
|
152
|
+
from distparams.random import Random
|
|
153
|
+
|
|
154
|
+
Random(42).gauss(mu=2, variance=2) # variance → sigma
|
|
155
|
+
Random(42).gammavariate(2, 3) # stdlib spelling stays drop-in: beta is the scale
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The stdlib method names (`gammavariate`, `gauss`, `weibullvariate`, …) keep
|
|
159
|
+
stdlib semantics exactly — the same call on `random.Random` draws the same
|
|
160
|
+
number for the same seed, and the `Random` class adds nothing to the
|
|
161
|
+
`random.Random` surface (`dir()` matches stdlib exactly). The registry names
|
|
162
|
+
(`gamma`, `normal`, `weibull`, …) arbitrate the cross-ecosystem traps
|
|
163
|
+
instead: `weibullvariate` swaps alpha/beta relative to PyMC and Stan, and
|
|
164
|
+
`gammavariate`'s `beta` is a scale where pymc's `beta` is a rate.
|
|
165
|
+
|
|
166
|
+
## Interactive Example
|
|
167
|
+
|
|
168
|
+
The registry types are *executable metadata*. In this
|
|
169
|
+
[marimo](https://marimo.dev) notebook, `Support` decides which control each
|
|
170
|
+
parameter gets — sliders for bounded probabilities, floored numbers for
|
|
171
|
+
scales, step-1 counters for trial counts — and `convert()` feeds the values
|
|
172
|
+
straight into scipy:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from distparams import get_distribution
|
|
176
|
+
|
|
177
|
+
params = get_distribution("binomial").params # {"n": Parameter, "p": Parameter}
|
|
178
|
+
params["p"].support # [0, 1]
|
|
179
|
+
params["p"].support.is_bounded # True → render a slider
|
|
180
|
+
|
|
181
|
+
get_distribution("normal").params["sigma"].support
|
|
182
|
+
# (0, ∞) → render a number floored at 0
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Every `Parameter` in the dict also carries its `.description` and
|
|
186
|
+
`.bijections` — the alternative names and forms the registry knows (normal's
|
|
187
|
+
`tau` is the precision: `sigma = tau**-0.5`).
|
|
188
|
+
|
|
189
|
+
[](https://marimo.app/https://github.com/williambdean/distparams/blob/main/examples/scipy_distributions.py)
|
|
190
|
+
|
|
191
|
+
Run it locally (the notebook needs scipy and marimo, which live in the dev
|
|
192
|
+
group — the library itself stays zero-dependency):
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
uv run --group dev marimo edit examples/scipy_distributions.py
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Features
|
|
199
|
+
|
|
200
|
+
- **`@distribution` decorator** — all canonical parameterizations come for free
|
|
201
|
+
- **`distparams.stats`** — scipy.stats-shaped namespace where every call accepts any registered parametrization
|
|
202
|
+
- **`distparams.random`** — the stdlib `random` module accepting every registered parametrization (zero-dependency, seed-exact drop-in)
|
|
203
|
+
- **`from_scipy`** — wrap any scipy distribution into a callable that accepts every registered parametrization
|
|
204
|
+
- **Support** dataclass for parameter domains (positive, unit interval, real, etc.)
|
|
205
|
+
— every parameter's description, support, and aliases are reachable via
|
|
206
|
+
`get_distribution(name).params`
|
|
207
|
+
- **Cross-ecosystem conversion** between PyMC, scipy, Stan, jStat, TFP, NumPyro, R (base `stats`), and the stdlib `random` module
|
|
208
|
+
- **Ecosystem vocabularies** — an ecosystem's parameter names are written once per *parametrization*, not once per distribution
|
|
209
|
+
- **Plugin system** for external packages — add an ecosystem, alias, or whole parameterization without forking
|
|
210
|
+
- **Zero dependencies** — pure Python, ~160 KB of source, ~50 ms import, µs-scale conversions
|
|
211
|
+
|
|
212
|
+
## Why not PreliZ?
|
|
213
|
+
|
|
214
|
+
PreliZ is for prior elicitation — it computes with distributions and brings
|
|
215
|
+
`numba`, `pytensor`, `scipy`, and `matplotlib` with it. distparams is the
|
|
216
|
+
zero-dependency metadata layer underneath: it knows the parameterizations
|
|
217
|
+
(and every ecosystem's names for them) so your library doesn't have to.
|
|
218
|
+
|
|
219
|
+
## Use it in your own package
|
|
220
|
+
|
|
221
|
+
Wrap your constructor once and every ecosystem's parameter names — plus
|
|
222
|
+
alternative parameterizations from the registry — just work:
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
from distparams import distribution
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@distribution("beta")
|
|
229
|
+
def beta(alpha, beta):
|
|
230
|
+
return stats.beta(alpha, beta)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
beta(1, 1) # canonical, positional
|
|
234
|
+
beta(successes=2, failures=3) # → alpha=3, beta=4
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Your function's parameter names are honored exactly — declare them in
|
|
238
|
+
whatever convention your library uses:
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
@distribution("beta")
|
|
242
|
+
def beta(a, b): # scipy-style names
|
|
243
|
+
return stats.beta(a, b)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
beta(alpha=1, beta=2) # → a=1, b=2
|
|
247
|
+
beta(successes=2, failures=3) # → a=3, b=4
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Prefer to keep your dataclasses? The decorator works on them directly — and
|
|
251
|
+
preserves the class (isinstance, classmethods, `dataclasses.fields` all keep
|
|
252
|
+
working):
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
from dataclasses import dataclass
|
|
256
|
+
from distparams import distribution
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@distribution("beta")
|
|
260
|
+
@dataclass
|
|
261
|
+
class Beta:
|
|
262
|
+
alpha: float
|
|
263
|
+
beta: float
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
Beta(a=1, b=2) # → Beta(alpha=1, beta=2)
|
|
267
|
+
Beta(successes=2, failures=3) # → Beta(alpha=3, beta=4)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
PyMC-style `.dist()` classmethods work too — decorate the raw function below
|
|
271
|
+
`@classmethod`, and the bound `cls` is passed through untouched. Context
|
|
272
|
+
parameters ride along: anything the callable declares that the registry
|
|
273
|
+
doesn't know — plus everything in `**kwargs` — is forwarded untouched.
|
|
274
|
+
Unknown kwargs without `**kwargs` stay strict errors.
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
class Normal:
|
|
278
|
+
@classmethod
|
|
279
|
+
@distribution("normal")
|
|
280
|
+
def dist(cls, mu=0, sigma=1, **kwargs): ...
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
Normal.dist(loc=0, scale=1, size=(2, 3)) # ✓ size/dtype-style extras forward
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Note that PyMC's `.dist()` only accepts extras like `size`, `shape`, and
|
|
287
|
+
`dtype` — `observed`, `dims`, and `initval` belong to the constructor in
|
|
288
|
+
model context (PyMC itself raises on them in `.dist()`). Registry-known
|
|
289
|
+
names always resolve: for gamma, `shape` is the shape parameter, not array
|
|
290
|
+
shape.
|
|
291
|
+
|
|
292
|
+
And downstream packages can extend the registry — names, aliases,
|
|
293
|
+
parameterizations — without forking, via `distparams.plugins`. To teach every
|
|
294
|
+
distribution your package's parameter names for a parametrization, register a
|
|
295
|
+
vocabulary once:
|
|
296
|
+
|
|
297
|
+
```python
|
|
298
|
+
from distparams import register_ecosystem_vocabulary
|
|
299
|
+
|
|
300
|
+
register_ecosystem_vocabulary("my_pkg", "mu_sigma", {"center": "mu", "spread": "sigma"})
|
|
301
|
+
# every registered mu_sigma distribution now speaks my_pkg
|
|
302
|
+
|
|
303
|
+
convert("normal", "scipy", "my_pkg", loc=0, scale=1) # {"center": 0, "spread": 1}
|
|
304
|
+
convert("cauchy", "my_pkg", "scipy", center=1, spread=2) # {"loc": 1, "scale": 2}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
A single distribution that names parameters differently keeps a
|
|
308
|
+
per-distribution override via `register_ecosystem_mapping` (it takes
|
|
309
|
+
precedence over the vocabulary row).
|
|
310
|
+
|
|
311
|
+
Rows are **pure renames** — they change what a parameter is called, never its
|
|
312
|
+
value. In the example above `spread` is an sd, so the rename alone is correct.
|
|
313
|
+
If `spread` carried a different quantity — a variance — the row alone would
|
|
314
|
+
silently pass values through (`spread=4` would convert to `scale=4`). Value
|
|
315
|
+
transforms live on the parameter, declared once and inherited by every
|
|
316
|
+
ecosystem that maps a name onto that role:
|
|
317
|
+
|
|
318
|
+
```python
|
|
319
|
+
from distparams import Bijection, register_parameter_alias
|
|
320
|
+
|
|
321
|
+
register_parameter_alias("normal", "sigma", "spread", Bijection.power(0.5))
|
|
322
|
+
|
|
323
|
+
convert("normal", "my_pkg", "scipy", center=0, spread=4) # {"loc": 0, "scale": 2.0}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## The `@distribution` Decorator
|
|
327
|
+
|
|
328
|
+
The flagship feature. Wrap your distribution constructor once and every
|
|
329
|
+
ecosystem's parameter names just work — aliases and alternative
|
|
330
|
+
parameterizations resolve automatically from the registry.
|
|
331
|
+
|
|
332
|
+
All of these are the *same* Normal:
|
|
333
|
+
|
|
334
|
+
```python
|
|
335
|
+
from distparams import distribution
|
|
336
|
+
|
|
337
|
+
# Register the distribution first (see registration below),
|
|
338
|
+
# then define your constructor against the canonical parameters.
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@distribution("normal", canonical=["mu", "sigma"])
|
|
342
|
+
def normal(mu, sigma):
|
|
343
|
+
return stats.norm(loc=mu, scale=sigma)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
normal(mu=0, sigma=1) # ✓ canonical (Gaussian notation)
|
|
347
|
+
normal(mu=0, tau=4) # ✓ tau -> sigma (precision parameterization)
|
|
348
|
+
normal(mu=0, precision=4) # ✓ precision -> sigma (same as tau)
|
|
349
|
+
normal(mu=0, variance=4) # ✓ variance -> sigma
|
|
350
|
+
normal(mu=0, var=4) # ✓ var -> sigma (same as variance)
|
|
351
|
+
normal(loc=0, scale=1) # ✓ scipy notation
|
|
352
|
+
normal(mu=0, std=1) # ✓ your own aliases
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
`Poisson(mu=...)` and `Poisson(lam=...)` are the same parameter — the
|
|
356
|
+
decorator resolves them transparently:
|
|
357
|
+
|
|
358
|
+
```python
|
|
359
|
+
@distribution("poisson", canonical=["rate"])
|
|
360
|
+
def poisson(rate):
|
|
361
|
+
return stats.poisson(mu=rate)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
poisson(mu=5) # ✓ mean parameterization
|
|
365
|
+
poisson(lam=5) # ✓ rate/lambda parameterization
|
|
366
|
+
poisson(rate=5) # ✓ canonical
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
And entire *parameterizations* (not just names) come for free too — a Gamma
|
|
370
|
+
written against `(shape, rate)` happily accepts `(shape, scale)`, `(a, scale)`,
|
|
371
|
+
`(mu, sigma)`, or `(mu, variance)`:
|
|
372
|
+
|
|
373
|
+
```python
|
|
374
|
+
@distribution("gamma", canonical=["shape", "rate"])
|
|
375
|
+
def gamma(shape, rate):
|
|
376
|
+
return stats.gamma(a=shape, scale=1 / rate)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
gamma(shape=2, rate=3) # ✓ canonical
|
|
380
|
+
gamma(shape=2, scale=3) # ✓ scale -> rate = 1/3
|
|
381
|
+
gamma(a=2, scale=3) # ✓ scipy notation
|
|
382
|
+
gamma(mu=2, sigma=2) # ✓ mean/std parameterization
|
|
383
|
+
gamma(mu=2, variance=4) # ✓ mean/variance (same as sigma=2; var= works too)
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Conflicting or unknown parameters are caught at call time instead of
|
|
387
|
+
silently misbehaving:
|
|
388
|
+
|
|
389
|
+
```python
|
|
390
|
+
normal(mu=0, sigma=1, tau=4) # ✗ ValueError: both map to 'sigma'
|
|
391
|
+
normal(mu=0, foo=1) # ✗ ValueError: unknown parameter 'foo'
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
To wire this up, define the distribution once (canonical parameters and
|
|
395
|
+
every ecosystem's naming). The fluent authoring API makes this read like
|
|
396
|
+
the math:
|
|
397
|
+
|
|
398
|
+
```python
|
|
399
|
+
from distparams import Bijection, Distribution, NamingConvention, Parameter, Support, register_distribution
|
|
400
|
+
|
|
401
|
+
# your own distribution's name — "normal" here for familiarity (the built-in
|
|
402
|
+
# is already registered; a fresh name avoids the duplicate-registration error)
|
|
403
|
+
mu = Parameter("mu", "Location parameter", Support.real())
|
|
404
|
+
mu.alias("mean")
|
|
405
|
+
|
|
406
|
+
sigma = Parameter("sigma", "Scale parameter", Support.positive())
|
|
407
|
+
sigma.alias("scale").alias("std")
|
|
408
|
+
sigma.alternative("tau", Bijection.power(-0.5)) # precision → sigma
|
|
409
|
+
|
|
410
|
+
register_distribution(
|
|
411
|
+
Distribution(
|
|
412
|
+
"normal",
|
|
413
|
+
params={"mu": mu, "sigma": sigma}, # canonical name derived: "mu_sigma"
|
|
414
|
+
naming_conventions=[
|
|
415
|
+
NamingConvention("pymc", {"mu": "mu", "sigma": "sigma"}),
|
|
416
|
+
NamingConvention("scipy", {"loc": "mu", "scale": "sigma"}),
|
|
417
|
+
NamingConvention("jstat", {"mean": "mu", "std": "sigma"}),
|
|
418
|
+
],
|
|
419
|
+
)
|
|
420
|
+
)
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Ecosystems that share a parametrization are covered once by the built-in
|
|
424
|
+
vocabularies (`src/distparams/_vocabularies.py`) — per-distribution
|
|
425
|
+
conventions are only needed for genuine exceptions, and the parametrization
|
|
426
|
+
name is derived from the parameter declaration order.
|
|
427
|
+
|
|
428
|
+
## Typing
|
|
429
|
+
|
|
430
|
+
The decorator preserves your function's signature, so canonical calls are
|
|
431
|
+
checked by mypy, pyright, and ty — and IDE autocomplete works on parameter
|
|
432
|
+
names and types:
|
|
433
|
+
|
|
434
|
+
```python
|
|
435
|
+
@distribution("normal")
|
|
436
|
+
def normal(mu: float, sigma: float) -> float: ...
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
normal(mu=0, sigma=1) # ✓ fully type-checked, like a plain function
|
|
440
|
+
normal(0, 1) # ✓ positional works too
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
Alias calls resolve at runtime from the registry, which is beyond what any
|
|
444
|
+
static analyzer can see — a strict checker will flag them even though they
|
|
445
|
+
run correctly:
|
|
446
|
+
|
|
447
|
+
```python
|
|
448
|
+
normal(loc=0, scale=1) # ✓ runs fine, but checkers see unknown names
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
If you want alias resolution to be statically visible, resolve first and
|
|
452
|
+
unpack the result — `resolve_parameters` returns canonical parameters:
|
|
453
|
+
|
|
454
|
+
```python
|
|
455
|
+
params = resolve_parameters("normal", loc=0, scale=1) # {"mu": 0, "sigma": 1}
|
|
456
|
+
normal(**params)
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Rule of thumb: **canonical names are the typed calling convention; aliases
|
|
460
|
+
are a runtime convenience.** Typos in alias names are caught at call time with
|
|
461
|
+
a `ValueError` listing every valid name, so nothing fails silently.
|
|
462
|
+
|
|
463
|
+
## Defaults and alternative parameterizations
|
|
464
|
+
|
|
465
|
+
Your function's own defaults do the heavy lifting — supply what you know,
|
|
466
|
+
let the rest default:
|
|
467
|
+
|
|
468
|
+
```python
|
|
469
|
+
@distribution("normal")
|
|
470
|
+
def normal(mu=0, sigma=1):
|
|
471
|
+
return stats.norm(mu, sigma)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
normal() # mu=0, sigma=1 (pure defaults)
|
|
475
|
+
normal(sigma=2) # mu=0, sigma=2 (mu by default)
|
|
476
|
+
normal(tau=4) # mu=0, sigma=0.5 (precision → sigma)
|
|
477
|
+
normal(precision=4) # mu=0, sigma=0.5 (same as tau)
|
|
478
|
+
normal(variance=4) # mu=0, sigma=2 (variance → sigma)
|
|
479
|
+
normal(var=4) # mu=0, sigma=2 (same as variance)
|
|
480
|
+
normal(mean=0, std=1) # jStat-style names work too
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
One name per parameter: `normal(sigma=2, tau=4)` raises a conflict — both
|
|
484
|
+
map to sigma — rather than guessing which wins. Values are transformed,
|
|
485
|
+
not guessed: `tau=4` means `sigma=0.5`, `tau=2` means `sigma≈0.707`.
|
|
486
|
+
|
|
487
|
+
An unknown parameter names the distribution, lists every valid name grouped
|
|
488
|
+
by role, and suggests the closest match for typos:
|
|
489
|
+
|
|
490
|
+
```python
|
|
491
|
+
normal(precission=4)
|
|
492
|
+
# Unknown parameter 'precission' for distribution 'normal'.
|
|
493
|
+
# Known parameters: (mu or loc or mean) and
|
|
494
|
+
# (sigma or scale or std or tau or precision or variance or var or sd).
|
|
495
|
+
# Did you mean 'precision'?
|
|
496
|
+
```
|