binference 0.1.0__py3-none-any.whl

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.
Files changed (43) hide show
  1. binference/__init__.py +85 -0
  2. binference/_kernels.py +252 -0
  3. binference/_quadrature.py +202 -0
  4. binference/_simplex.py +54 -0
  5. binference/_utils.py +139 -0
  6. binference/_version.py +1 -0
  7. binference/basis.py +314 -0
  8. binference/bins.py +645 -0
  9. binference/density.py +323 -0
  10. binference/diagnostics.py +400 -0
  11. binference/estimator.py +197 -0
  12. binference/inference.py +368 -0
  13. binference/io.py +309 -0
  14. binference/likelihoods.py +1684 -0
  15. binference/measure.py +246 -0
  16. binference/model.py +833 -0
  17. binference/plotting.py +538 -0
  18. binference/priors/__init__.py +47 -0
  19. binference/priors/base.py +248 -0
  20. binference/priors/dirichlet.py +640 -0
  21. binference/priors/hyper.py +240 -0
  22. binference/priors/logistic.py +676 -0
  23. binference/priors/rates.py +139 -0
  24. binference/response.py +539 -0
  25. binference/results.py +2271 -0
  26. binference/samplers/__init__.py +19 -0
  27. binference/samplers/_curvature.py +67 -0
  28. binference/samplers/_nuts_core.py +284 -0
  29. binference/samplers/base.py +118 -0
  30. binference/samplers/bootstrap.py +136 -0
  31. binference/samplers/gibbs.py +287 -0
  32. binference/samplers/laplace.py +200 -0
  33. binference/samplers/nuts.py +681 -0
  34. binference/samplers/optimize.py +488 -0
  35. binference/samplers/vb.py +118 -0
  36. binference/selection.py +621 -0
  37. binference/simulate.py +720 -0
  38. binference/validation.py +477 -0
  39. binference/warnings.py +48 -0
  40. binference-0.1.0.dist-info/METADATA +130 -0
  41. binference-0.1.0.dist-info/RECORD +43 -0
  42. binference-0.1.0.dist-info/WHEEL +4 -0
  43. binference-0.1.0.dist-info/licenses/LICENSE +21 -0
binference/plotting.py ADDED
@@ -0,0 +1,538 @@
1
+ """Plotting helpers (require matplotlib: ``pip install "binference[plot]"``).
2
+
3
+ Colours follow the active matplotlib style. The population (medians, bands, bars,
4
+ histograms) takes the first colour of ``axes.prop_cycle``, or ``color``; what the objects'
5
+ own data say (the stack, an object's own likelihood) takes the second, or
6
+ ``sample_color``; reference lines, error bars and the truth (dashed) take ``text.color``.
7
+ 2-D medians ramp from a pale tint of the population colour to the colour, and relative
8
+ uncertainties likewise in ``text.color``; masked bins show the axes background.
9
+ ``SAMPLE_COLOR`` is the coral the binference docs give single objects, for ``sample_color``.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from collections.abc import Sequence
15
+ from typing import Any
16
+
17
+ import numpy as np
18
+
19
+ from ._utils import warn
20
+ from .warnings import InputWarning
21
+
22
+ __all__ = [
23
+ "SAMPLE_COLOR",
24
+ "plot_corner",
25
+ "plot_individual",
26
+ "plot_population",
27
+ "plot_sbc",
28
+ "plot_trace",
29
+ ]
30
+
31
+ #: The coral of single objects and samples in the binference docs (their brand ``sample``
32
+ #: colour), to pass as ``sample_color``; ``docs/_scripts/brand.py --check`` keeps the two equal.
33
+ SAMPLE_COLOR = "#EC5F3E"
34
+
35
+
36
+ def _style_color(name: str, value: Any, slot: int):
37
+ """``value`` as RGBA (a list of them for per-bar colours), or colour ``slot`` of the
38
+ style's cycle when None.
39
+
40
+ Resolved now, not left as "C0": a figure drawn after leaving a style context would
41
+ otherwise colour its lines from another cycle than its bars and bands.
42
+ """
43
+ from matplotlib.colors import is_color_like, to_rgba
44
+
45
+ if value is None:
46
+ value = f"C{slot}"
47
+ if is_color_like(value):
48
+ return to_rgba(value)
49
+ if isinstance(value, list | tuple) and value and all(is_color_like(c) for c in value):
50
+ return [to_rgba(c) for c in value]
51
+ raise ValueError(
52
+ f"{name} must be a matplotlib colour, a list of them, or None, got {value!r}."
53
+ )
54
+
55
+
56
+ def _ramp(ax, color):
57
+ """A sequential colormap from a pale tint of ``color`` (over the axes background, so
58
+ zero differs from a masked bin) to ``color``."""
59
+ from matplotlib.colors import LinearSegmentedColormap, to_rgba
60
+
61
+ ground, ink = np.array(to_rgba(ax.get_facecolor())), np.array(to_rgba(color))
62
+ return LinearSegmentedColormap.from_list("ramp", [0.85 * ground + 0.15 * ink, ink])
63
+
64
+
65
+ def _tidy(ax) -> None:
66
+ """Recessive axes: no top/right spines."""
67
+ ax.spines["top"].set_visible(False)
68
+ ax.spines["right"].set_visible(False)
69
+
70
+
71
+ def _plt():
72
+ try:
73
+ import matplotlib.pyplot as plt
74
+ except ImportError as err: # pragma: no cover - depends on environment
75
+ raise ImportError("Plotting needs matplotlib: pip install 'binference[plot]'.") from err
76
+ return plt
77
+
78
+
79
+ #: axis labels of what can be plotted (``which``)
80
+ _YLABEL = {
81
+ "density": "density",
82
+ "fractions": "fraction per bin",
83
+ "rates": "rate",
84
+ "selected": "selected fraction per bin",
85
+ }
86
+
87
+
88
+ def _overlay(result: Any, values: Any, which: str, total: float) -> np.ndarray:
89
+ """Overlay values (fractions or rates) on the scale of the plotted posterior.
90
+
91
+ Overlays are normalised and multiplied by ``total``, the posterior mean total
92
+ when rates are plotted or the model is a rate model (1 otherwise), so only
93
+ their shape is compared; the selected population of an overlay is taken under
94
+ the fit's selection.
95
+ """
96
+ v = np.asarray(values, dtype=float)
97
+ v = v / v.sum()
98
+ if which == "selected":
99
+ return result._selected_of(v[None, :]).mean(axis=0)
100
+ if which == "fractions":
101
+ return v
102
+ v = v * total
103
+ return v / result.bins.volumes if which == "density" else v
104
+
105
+
106
+ def plot_population(
107
+ result: Any,
108
+ ax: Any = None,
109
+ *,
110
+ which: str = "density",
111
+ levels: Sequence[float] = (0.68, 0.95),
112
+ truth: Any = None,
113
+ stack: Any = None,
114
+ color: Any = None,
115
+ sample_color: Any = None,
116
+ label: str | None = None,
117
+ alpha: float = 0.25,
118
+ step: bool = True,
119
+ ):
120
+ """Plot a population posterior: bands for 1-D bins, images for 2-D grids, bars for categories.
121
+
122
+ Overlays (``truth``, ``stack``) contribute only their shape: for rates (and rate
123
+ models) they are scaled to the posterior mean total; for the selected population
124
+ they are reweighted by the fit's selection (bins without a basis). 2-D grids show
125
+ the median and the relative uncertainty as images (an array of two axes) and take
126
+ no overlays.
127
+
128
+ Parameters
129
+ ----------
130
+ result : PopulationResult or PointEstimate
131
+ ax : matplotlib Axes, optional
132
+ Axes to draw on; ``None`` makes a new figure.
133
+ which : {"density", "fractions", "rates", "selected"}
134
+ Plot densities per unit volume (n(z)-style), fractions per bin, rates, or the
135
+ selected population per bin (the singular spellings are accepted too).
136
+ levels : sequence of float
137
+ Central credible levels shown as nested bands.
138
+ truth : array_like (K,), optional
139
+ True fractions to overlay.
140
+ stack : array_like (K,), optional
141
+ A naive stacked estimate to overlay (see :meth:`Likelihoods.stack
142
+ <binference.Likelihoods.stack>`).
143
+ color : matplotlib colour, optional
144
+ Colour of the posterior; ``None`` takes the style's first colour.
145
+ sample_color : matplotlib colour, optional
146
+ Colour of the ``stack`` overlay; ``None`` takes the style's second colour.
147
+ label : str, optional
148
+ Legend label of the median (default "posterior median").
149
+ alpha : float
150
+ Opacity of each credible band.
151
+ step : bool
152
+ Draw 1-D bins as steps (True) or as lines through the bin centres.
153
+ """
154
+ from .results import _central, _which_name
155
+
156
+ plt = _plt()
157
+ color = _style_color("color", color, 0)
158
+ sample_color = _style_color("sample_color", sample_color, 1)
159
+ ink = plt.rcParams["text.color"]
160
+ bins = result.bins
161
+ which = _which_name(which)
162
+ if bins.ndim > 2 and not bins.is_categorical:
163
+ raise ValueError(
164
+ "plot supports 1-D and 2-D grids and categories; for more axes use "
165
+ "result.density() or result.summary() (audit O-9)."
166
+ )
167
+ X = result._which(which)
168
+ med = np.median(X, axis=0)
169
+ # overlays of rates (and of densities of rate models) are scaled to the mean total
170
+ scaled = which == "rates" or result.kind == "rates"
171
+ total = float(np.mean(result.total)) if scaled else 1.0
172
+ if bins.ndim == 2 and not bins.is_categorical:
173
+ if truth is not None or stack is not None or label is not None:
174
+ warn("2-D plots show no truth, stack or label; they are ignored.", InputWarning)
175
+ return _plot_2d(result, X, plt, ax, color)
176
+ if ax is None:
177
+ _, ax = plt.subplots()
178
+ ylabel = _YLABEL[which]
179
+ if bins.is_categorical:
180
+ x = np.arange(bins.K)
181
+ lo, hi = _central(X, levels[0])
182
+ ax.bar(x, med, color=color, alpha=0.6, label=label or "posterior median")
183
+ if X.shape[0] > 1:
184
+ ax.errorbar(x, med, yerr=[med - lo, hi - med], fmt="none", ecolor=ink, capsize=3)
185
+ if truth is not None:
186
+ ax.plot(x, _overlay(result, truth, which, total), "D", color=ink, ms=6, label="truth")
187
+ if stack is not None:
188
+ ax.plot(
189
+ x,
190
+ _overlay(result, stack, which, total),
191
+ "s",
192
+ color=sample_color,
193
+ ms=6,
194
+ label="stacked",
195
+ )
196
+ ax.set_xticks(x, [str(lab) for lab in bins.labels], rotation=45, ha="right")
197
+ else:
198
+ edges = bins.edges[0]
199
+ centers = bins.centers
200
+ band_handles = []
201
+ for n_layers, lev in enumerate(sorted(levels, reverse=True), start=1):
202
+ if X.shape[0] < 2:
203
+ break
204
+ # the region inside this band is covered by n_layers translucent fills
205
+ band_handles.append(
206
+ _band_patch(color, 1.0 - (1.0 - alpha) ** n_layers, f"{lev:.0%} interval")
207
+ )
208
+ lo, hi = _central(X, lev)
209
+ if step:
210
+ ax.fill_between(
211
+ edges,
212
+ _step_y(bins, lo),
213
+ _step_y(bins, hi),
214
+ step="post",
215
+ color=color,
216
+ alpha=alpha,
217
+ lw=0,
218
+ )
219
+ else:
220
+ ax.fill_between(centers, lo, hi, color=color, alpha=alpha, lw=0)
221
+ lab = label or "posterior median"
222
+ if step:
223
+ ax.step(edges, _step_y(bins, med), where="post", color=color, lw=1.5, label=lab)
224
+ else:
225
+ ax.plot(centers, med, color=color, lw=1.5, label=lab)
226
+ if truth is not None:
227
+ t = _overlay(result, truth, which, total)
228
+ ax.step(
229
+ edges, _step_y(bins, t), where="post", color=ink, lw=1.5, ls="--", label="truth"
230
+ )
231
+ if stack is not None:
232
+ s = _overlay(result, stack, which, total)
233
+ ax.step(
234
+ edges,
235
+ _step_y(bins, s),
236
+ where="post",
237
+ color=sample_color,
238
+ lw=1.5,
239
+ ls=":",
240
+ label="stacked",
241
+ )
242
+ ax.set_xlim(edges[0], edges[-1])
243
+ ax.set_xlabel("bin coordinate")
244
+ handles, labels = ax.get_legend_handles_labels()
245
+ # median first, then bands (innermost first), then reference lines
246
+ handles = handles[:1] + band_handles[::-1] + handles[1:]
247
+ labels = labels[:1] + [h.get_label() for h in band_handles[::-1]] + labels[1:]
248
+ ax.set_ylabel(ylabel)
249
+ ax.set_ylim(bottom=0)
250
+ _tidy(ax)
251
+ ax.legend(handles, labels, frameon=False)
252
+ return ax
253
+ ax.set_ylabel(ylabel)
254
+ ax.set_ylim(bottom=0)
255
+ _tidy(ax)
256
+ ax.legend(frameon=False)
257
+ return ax
258
+
259
+
260
+ def _step_y(bins, y):
261
+ # per-bin values on the full 1-D grid (NaN, a gap, in masked bins), padded for step="post"
262
+ full = bins.unflatten(np.asarray(y, dtype=float))
263
+ return np.append(full, full[-1])
264
+
265
+
266
+ def _band_patch(color: str, alpha: float, label: str):
267
+ from matplotlib.patches import Patch
268
+
269
+ return Patch(facecolor=color, alpha=alpha, lw=0, label=label)
270
+
271
+
272
+ def _plot_2d(result, X, plt, ax, color):
273
+ bins = result.bins
274
+ med = bins.unflatten(np.median(X, axis=0))
275
+ sd = bins.unflatten(X.std(axis=0)) if X.shape[0] > 1 else None
276
+ ex, ey = bins.edges
277
+ if ax is None:
278
+ n = 2 if sd is not None else 1
279
+ # side by side when each panel, with its colour bar, keeps at least 4 in, else stacked
280
+ shape = (1, n) if plt.rcParams["figure.figsize"][0] / n >= 4 else (n, 1)
281
+ fig, ax = plt.subplots(*shape, figsize=_figsize(plt, *shape, 0.8), squeeze=False)
282
+ ax = ax.ravel()
283
+ else:
284
+ fig = None
285
+ axes = np.atleast_1d(ax)
286
+ im = axes[0].pcolormesh(ex, ey, med.T, shading="flat", cmap=_ramp(axes[0], color))
287
+ plt.colorbar(im, ax=axes[0], label="posterior median")
288
+ axes[0].set_xlabel("axis 0")
289
+ axes[0].set_ylabel("axis 1")
290
+ if sd is not None and len(axes) > 1:
291
+ with np.errstate(divide="ignore", invalid="ignore"):
292
+ rel = np.where(med > 0, sd / med, np.nan)
293
+ grey = _ramp(axes[1], plt.rcParams["text.color"])
294
+ im2 = axes[1].pcolormesh(ex, ey, rel.T, shading="flat", cmap=grey)
295
+ plt.colorbar(im2, ax=axes[1], label="relative uncertainty (sd / median)")
296
+ axes[1].set_xlabel("axis 0")
297
+ if fig is not None:
298
+ _tight(fig)
299
+ return axes
300
+
301
+
302
+ def _figsize(plt, nrow: int, ncol: int, aspect: float) -> tuple:
303
+ """The style's figure width, and a height of ``nrow`` panels of height ``aspect`` x
304
+ their width, each with room for two lines of text (labels, titles)."""
305
+ width = plt.rcParams["figure.figsize"][0]
306
+ text = 2 * plt.rcParams["font.size"] / 72
307
+ return width, nrow * (aspect * width / ncol + text)
308
+
309
+
310
+ def _subplots(plt, fig, nrow: int, ncol: int, aspect: float, **kwargs: Any):
311
+ # a new figure of nrow x ncol axes, or those axes added to fig
312
+ if fig is None:
313
+ figsize = _figsize(plt, nrow, ncol, aspect)
314
+ return plt.subplots(nrow, ncol, figsize=figsize, squeeze=False, **kwargs)
315
+ return fig, np.asarray(fig.subplots(nrow, ncol, squeeze=False, **kwargs))
316
+
317
+
318
+ def _tight(fig) -> None:
319
+ # tidy a figure the style left without a layout engine (a docs-style figure has one)
320
+ if fig.get_layout_engine() is None:
321
+ fig.tight_layout()
322
+
323
+
324
+ def _chosen_bins(result: Any, which: str, idx: Any, max_bins: int):
325
+ """The draws of ``which``, the bins ``idx`` (by default the ``max_bins`` largest by
326
+ mean, in order) and the quantity's symbol."""
327
+ from .results import _SYMBOL, _which_name
328
+
329
+ X = result._which(which)
330
+ if idx is None:
331
+ idx = np.sort(np.argsort(-X.mean(axis=0))[:max_bins])
332
+ return X, list(idx), _SYMBOL[_which_name(which)]
333
+
334
+
335
+ def plot_corner(
336
+ result: Any,
337
+ idx: Sequence[int] | None = None,
338
+ *,
339
+ max_bins: int = 4,
340
+ n_hist: int = 30,
341
+ color: Any = None,
342
+ fig: Any = None,
343
+ which: str = "fractions",
344
+ ):
345
+ """Corner plot of the fractions (or ``which="rates"``, ...) in selected bins.
346
+
347
+ Parameters
348
+ ----------
349
+ result : PopulationResult
350
+ idx : sequence of int, optional
351
+ Bins to show; ``None`` chooses the ``max_bins`` with the largest mean.
352
+ max_bins : int, optional
353
+ Number of bins shown when ``idx`` is ``None``.
354
+ n_hist : int, optional
355
+ Histogram bins per panel.
356
+ color : matplotlib colour, optional
357
+ Colour of the histograms; ``None`` takes the style's first colour.
358
+ fig : matplotlib Figure, optional
359
+ Figure to draw on; ``None`` makes a new one.
360
+ which : {"fractions", "rates", "density", "selected"}, optional
361
+ Quantity shown: fractions, rates, densities or the selected population.
362
+ """
363
+ plt = _plt()
364
+ color = _style_color("color", color, 0)
365
+ X, idx, name = _chosen_bins(result, which, idx, max_bins)
366
+ n = len(idx)
367
+ fig, axes = _subplots(plt, fig, n, n, 1.0)
368
+ for i, ki in enumerate(idx):
369
+ for j, kj in enumerate(idx):
370
+ a = axes[i, j]
371
+ if j > i:
372
+ a.set_visible(False)
373
+ continue
374
+ if i == j:
375
+ a.hist(X[:, ki], bins=n_hist, color=color, histtype="step")
376
+ a.set_yticks([])
377
+ else:
378
+ a.hist2d(X[:, kj], X[:, ki], bins=n_hist, cmap=_ramp(a, color))
379
+ if i == n - 1:
380
+ a.set_xlabel(f"{name}[{kj}]")
381
+ if j == 0 and i > 0:
382
+ a.set_ylabel(f"{name}[{ki}]")
383
+ _tight(fig)
384
+ return fig
385
+
386
+
387
+ def plot_trace(
388
+ result: Any,
389
+ idx: Sequence[int] | None = None,
390
+ *,
391
+ max_bins: int = 4,
392
+ fig: Any = None,
393
+ which: str = "fractions",
394
+ ):
395
+ """Trace plots of the fractions (or ``which="rates"``, ...) in selected bins, one
396
+ line per chain.
397
+
398
+ Parameters
399
+ ----------
400
+ result : PopulationResult
401
+ idx : sequence of int, optional
402
+ Bins to show; ``None`` chooses the ``max_bins`` with the largest mean.
403
+ max_bins : int, optional
404
+ Number of bins shown when ``idx`` is ``None``.
405
+ fig : matplotlib Figure, optional
406
+ Figure to draw on; ``None`` makes a new one.
407
+ which : {"fractions", "rates", "density", "selected"}, optional
408
+ Quantity shown: fractions, rates, densities or the selected population.
409
+ """
410
+ X, idx, name = _chosen_bins(result, which, idx, max_bins)
411
+ C = X.reshape(result.n_chains, -1, result.K)
412
+ fig, axes = _subplots(_plt(), fig, len(idx), 1, 0.4, sharex=True)
413
+ for a, k in zip(axes[:, 0], idx, strict=True):
414
+ for c in range(C.shape[0]):
415
+ a.plot(C[c, :, k], lw=0.6, alpha=0.8)
416
+ a.set_ylabel(f"{name}[{k}]")
417
+ axes[-1, 0].set_xlabel("iteration")
418
+ _tight(fig)
419
+ return fig
420
+
421
+
422
+ def plot_individual(
423
+ result: Any,
424
+ i: int,
425
+ ax: Any = None,
426
+ n_draws: int = 200,
427
+ *,
428
+ color: Any = None,
429
+ sample_color: Any = None,
430
+ ):
431
+ """Compare object ``i``'s own likelihood (flat-prior posterior) with its shrunk posterior.
432
+
433
+ Parameters
434
+ ----------
435
+ result : PopulationResult or PointEstimate
436
+ i : int
437
+ Index of the object.
438
+ ax : matplotlib Axes, optional
439
+ Axes to draw on; ``None`` makes a new figure.
440
+ n_draws : int, optional
441
+ Population draws averaged for the shrunk posterior.
442
+ color : matplotlib colour, optional
443
+ Colour of the shrunk posterior; ``None`` takes the style's first colour.
444
+ sample_color : matplotlib colour, optional
445
+ Colour of the object's own likelihood; ``None`` takes the style's second colour.
446
+ """
447
+ plt = _plt()
448
+ color = _style_color("color", color, 0)
449
+ sample_color = _style_color("sample_color", sample_color, 1)
450
+ model = result._require_model()
451
+ bins = result.bins
452
+ own = model.likelihoods.subset([i]).toarray()[0] * bins.volumes
453
+ own = own / own.sum()
454
+ shrunk = result.assignment_probabilities(idx=[i], n_draws=n_draws)[0]
455
+ if ax is None:
456
+ _, ax = plt.subplots()
457
+ if bins.is_categorical or bins.ndim > 1:
458
+ x = np.arange(bins.K)
459
+ ax.bar(x - 0.2, own, width=0.4, color=sample_color, label="own likelihood (flat prior)")
460
+ ax.bar(x + 0.2, shrunk, width=0.4, color=color, label="hierarchical posterior")
461
+ ax.set_xlabel("bin")
462
+ else:
463
+ edges = bins.edges[0]
464
+ vol = bins.volumes
465
+ ax.step(
466
+ edges,
467
+ _step_y(bins, own / vol),
468
+ where="post",
469
+ color=sample_color,
470
+ lw=1.5,
471
+ ls="--",
472
+ label="own likelihood (flat prior)",
473
+ )
474
+ ax.step(
475
+ edges,
476
+ _step_y(bins, shrunk / vol),
477
+ where="post",
478
+ color=color,
479
+ lw=1.5,
480
+ label="hierarchical posterior",
481
+ )
482
+ ax.set_xlabel("bin coordinate")
483
+ ax.set_ylabel("posterior density")
484
+ _tidy(ax)
485
+ ax.legend(frameon=False)
486
+ return ax
487
+
488
+
489
+ def plot_sbc(
490
+ sbc_result: Any,
491
+ n_bins: int | None = None,
492
+ max_panels: int = 12,
493
+ fig: Any = None,
494
+ *,
495
+ color: Any = None,
496
+ ):
497
+ """Rank histograms from :func:`binference.validation.sbc` with a 99% uniformity band.
498
+
499
+ Parameters
500
+ ----------
501
+ sbc_result : SBCResult
502
+ n_bins : int, optional
503
+ Histogram bins; ``None`` uses the default of :meth:`SBCResult.histogram
504
+ <binference.validation.SBCResult.histogram>`.
505
+ max_panels : int, optional
506
+ Most quantities shown.
507
+ fig : matplotlib Figure, optional
508
+ Figure to draw on; ``None`` makes a new one.
509
+ color : matplotlib colour, optional
510
+ Colour of the histograms; ``None`` takes the style's first colour.
511
+ """
512
+ from scipy import stats
513
+
514
+ plt = _plt()
515
+ color = _style_color("color", color, 0)
516
+ ink = plt.rcParams["text.color"]
517
+ H = sbc_result.histogram(n_bins)
518
+ names = sbc_result.names
519
+ q = min(len(names), max_panels)
520
+ ncol = min(4, q)
521
+ nrow = int(np.ceil(q / ncol))
522
+ fig, axes = _subplots(plt, fig, nrow, ncol, 1.2)
523
+ n_sims = sbc_result.ranks.shape[0]
524
+ nb = H.shape[1]
525
+ # per bin: bins can hold different numbers of the possible ranks (audit G-1)
526
+ p = sbc_result.expected(n_bins) / n_sims
527
+ lo, hi = stats.binom.interval(0.99, n_sims, p)
528
+ for j in range(nrow * ncol):
529
+ a = axes.flat[j]
530
+ if j >= q:
531
+ a.set_visible(False)
532
+ continue
533
+ a.stairs(hi, np.arange(nb + 1) - 0.5, baseline=lo, fill=True, color=ink, alpha=0.15, lw=0)
534
+ a.bar(np.arange(nb), H[j], width=1.0, color=color, alpha=0.8)
535
+ a.set_title(names[j])
536
+ a.set_yticks([])
537
+ _tight(fig)
538
+ return fig
@@ -0,0 +1,47 @@
1
+ """Priors on population fractions (the simplex) and rates.
2
+
3
+ Conjugate simplex priors (Gibbs, EM and VB all closed-form):
4
+
5
+ * :class:`Dirichlet` (the default, ``alpha=1``)
6
+ * :class:`GeneralizedDirichlet`
7
+ * :class:`DirichletTree`
8
+ * :class:`PolyaTree`
9
+
10
+ Smooth logistic-normal priors (sampled with NUTS, hyperparameters inferred):
11
+
12
+ * :class:`LogisticNormal`
13
+ * :class:`LogisticRandomWalk`
14
+ * :class:`LogisticGP`
15
+
16
+ Rates (Poisson process, conjugate):
17
+
18
+ * :class:`GammaRates`
19
+
20
+ Hyperpriors for scale parameters: :class:`HalfNormal`, :class:`LogNormal`,
21
+ :class:`InverseGamma`, :class:`Gamma`, :class:`Fixed`.
22
+ """
23
+
24
+ from .base import BoundPrior, PopulationPrior
25
+ from .dirichlet import Dirichlet, DirichletTree, GeneralizedDirichlet, PolyaTree
26
+ from .hyper import Fixed, Gamma, HalfNormal, Hyperprior, InverseGamma, LogNormal
27
+ from .logistic import LogisticGP, LogisticNormal, LogisticRandomWalk
28
+ from .rates import GammaRates
29
+
30
+ __all__ = [
31
+ "BoundPrior",
32
+ "Dirichlet",
33
+ "DirichletTree",
34
+ "Fixed",
35
+ "Gamma",
36
+ "GammaRates",
37
+ "GeneralizedDirichlet",
38
+ "HalfNormal",
39
+ "Hyperprior",
40
+ "InverseGamma",
41
+ "LogNormal",
42
+ "LogisticGP",
43
+ "LogisticNormal",
44
+ "LogisticRandomWalk",
45
+ "PolyaTree",
46
+ "PopulationPrior",
47
+ ]