Metadata-Version: 2.4
Name: modist
Version: 0.2.0
Summary: Interactive distribution widgets for marimo, in the style of wigglystuff
Keywords: marimo,anywidget,statistics,distribution,visualization
Author: Will Dean
Author-email: Will Dean <wd60622@gmail.com>
License-Expression: MIT
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering :: Visualization
Requires-Dist: anywidget>=0.11.0
Requires-Dist: pytest>=8 ; extra == 'dev'
Requires-Dist: marimo>=0.9 ; extra == 'dev'
Requires-Dist: conjugate-models ; extra == 'dev'
Requires-Dist: jupyter>=1.0 ; extra == 'dev'
Requires-Dist: ipykernel>=7.3.0 ; extra == 'dev'
Requires-Dist: jupytext>=1.19.5 ; extra == 'dev'
Requires-Dist: pymc>=5.10 ; extra == 'pymc'
Requires-Dist: pymc-extras>=0.0.4 ; extra == 'pymc'
Requires-Dist: scipy>=1.12 ; extra == 'scipy'
Requires-Python: >=3.12, <3.15
Project-URL: Documentation, https://github.com/williambdean/modist#readme
Project-URL: Homepage, https://github.com/williambdean/modist
Project-URL: Repository, https://github.com/williambdean/modist
Provides-Extra: dev
Provides-Extra: pymc
Provides-Extra: scipy
Description-Content-Type: text/markdown

# `modist`

Interactive distribution widgets for [marimo](https://marimo.io), in the style
of [`koaning/wigglystuff`](https://github.com/koaning/wigglystuff). Drag the
density curve to shape a distribution, then feed the params straight into a
distribution constructor with a single splat.

![modist widget example](https://raw.githubusercontent.com/williambdean/modist/main/docs/widget-example.png)

## Install

```sh
uv add modist            # or: uv pip install modist  (pip install modist)
```

## Quickstart

```python
import marimo as mo
import modist as md

w = mo.ui.anywidget(md.Normal())
w
```

```python
params = w.value            # {'mu': ..., 'sigma': ...}
```

```python
import pymc as pm
dist = pm.Normal.dist(**params)   # or pm.Beta / pm.Gamma / pm.StudentT
```

## Families

| Widget                       | Params        | Domain            | Drag affordances          |
| ---------------------------- | ------------- | ----------------- | ------------------------- |
| [`Normal`](src/modist/normal.py)  | `mu`, `sigma`      | free             | mean line → `mu`, ±1σ squares → `sigma` |
| [`Beta`](src/modist/beta.py)      | `alpha`, `beta`    | fixed `[0, 1]`    | mean line → translate, q25/q75 squares → concentrate |
| [`Gamma`](src/modist/gamma.py)    | `alpha`, `beta`    | edge pinned at 0  | mean line → translate, q25/q75 squares → reshape |
| [`StudentT`](src/modist/studentt.py) | `mu`, `sigma`, `nu` | free             | mean line → `mu`, q75 square → `sigma`, tails dial → `nu` |

`StudentT`'s third parameter is a **tails dial**: drag it up for fatter tails
(lower `nu`) or down for thinner tails (higher `nu`). Because `nu` has no
natural on-curve landmark, its drag is a separate 1-D slider rather than a
point you move on the density curve.

`alpha`/`beta` follow the [PyMC](https://www.pymc.io) / statistics convention
(`Gamma`'s `beta` is the **rate**, not scipy's `scale`). The lazy `.scipy` and
`.pymc` adapters map to the right parametrization automatically:

```python
n = md.Normal(mu=2.0, sigma=3.0)
n.scipy   # <scipy.stats.norm> via loc=/scale=
n.pymc    # pm.Normal.dist(mu=2.0, sigma=3.0)

g = md.Gamma(alpha=2.0, beta=3.0)
g.scipy   # scipy.stats.gamma(a=2.0, scale=1/3)  -- rate handled for you
```

`w.value` is a plain dict of the synced traits, so `pm.X.dist(**w.value)` works
with no conversion.

## Jupyter

The widgets are anywidget/ipywidgets under the hood, so they run in plain
Jupyter too — no marimo required. Just `display()` the widget and read its
`.params` (or `.scipy`) instead of wrapping it in `mo.ui.anywidget(...)`:

```python
import modist as md
from IPython.display import display

w = md.Normal(mu=0, sigma=1)
display(w)          # drag the curve to reshape it

w.params            # {'mu': ..., 'sigma': ...}
```

A full walkthrough notebook — all five families, live scipy stats, and a
beta-prior combination example — lives at
[`demos/jupyter_example.ipynb`](demos/jupyter_example.ipynb).

From a checkout:

```sh
uv sync --extra dev --extra scipy   # installs jupyter, ipykernel, jupytext
make jupyter                        # opens demos/jupyter_example.ipynb in JupyterLab
```

`make jupyter` registers the repo's `.venv` as a `modist` kernel, so the
notebook uses exactly the installed packages. Requires a local
[JupyterLab](https://jupyter.org/install) (installed alongside jupyter via the
dev extras).

## How it works

Each family is its own anywidget class with a small set of synced parameter
traits (no `x_min`/`x_max`/`n_points`). The view — SVG scaffold, pan/zoom,
draggable hit lines, and per-family math — lives in a self-contained ESM module.

Source JS lives in [`js/`](js/) (`js/base.js` shared scaffold + one family file,
all importing a vendored copy of [jStat](https://jstat.github.io/) for
`pdf`/`cdf`/quantile math). Anywidget delivers `_esm` as a Blob URL, which
cannot resolve relative imports, so [esbuild](https://esbuild.github.io)
bundles each family (jStat inlined) into the committed `src/modist/static/*.js`
files — the same pattern wigglystuff uses for its JS-heavy widgets.

### Rebuilding the JS

```sh
make js          # esbuild js/*.js -> src/modist/static/*.js
make js-watch    # rebuild on every edit (for anywidget hot-reload dev)
```

Requires a local esbuild (`npm install --no-save esbuild`).

## Development

```sh
make venv        # creates .venv with dev deps + esbuild
make test        # pytest
npm run test:js  # Playwright JS integration probes (headless Chromium)
```

## Acknowledgements

- [jStat](https://jstat.github.io/) — JavaScript statistics library (MIT), vendored and bundled for the pdf/cdf/quantile math.
- [wigglystuff](https://github.com/koaning/wigglystuff) — the interaction and architecture model (one class per family, prebuilt ESM per class).
