Source code for mmm_framework.utils.statistics

"""Statistical utility functions for MMM Framework.

This module provides statistical utilities commonly used in Bayesian
model analysis, such as computing highest density intervals (HDI).
"""

from __future__ import annotations

from typing import TYPE_CHECKING

import numpy as np

if TYPE_CHECKING:
    from numpy.typing import NDArray


[docs] def compute_hdi_bounds( samples: NDArray, hdi_prob: float = 0.94, axis: int = 0, ) -> tuple[NDArray, NDArray]: """Compute highest density interval bounds using percentiles. Computes the central credible interval bounds for a given probability mass. This uses a simple percentile-based approach which is appropriate for approximately symmetric distributions. Parameters ---------- samples : NDArray Sample array from posterior distribution. Shape can be (n_samples,) for 1D or (n_samples, n_observations) for 2D. hdi_prob : float, default=0.94 Probability mass for the HDI. For example, 0.94 gives the central 94% interval. axis : int, default=0 Axis along which to compute percentiles. Typically axis=0 when samples are in the first dimension. Returns ------- tuple[NDArray, NDArray] Tuple of (lower_bound, upper_bound) arrays. Shape depends on input shape and axis parameter. Examples -------- >>> import numpy as np >>> from mmm_framework.utils import compute_hdi_bounds >>> >>> # Generate samples >>> np.random.seed(42) >>> samples = np.random.randn(1000, 10) # 1000 samples, 10 observations >>> >>> # Compute 94% HDI >>> lower, upper = compute_hdi_bounds(samples, hdi_prob=0.94) >>> print(f"Lower bounds shape: {lower.shape}") # (10,) >>> print(f"Upper bounds shape: {upper.shape}") # (10,) Notes ----- This function uses a simple percentile-based approach rather than a true highest density interval algorithm. For symmetric distributions like the Normal distribution, this is equivalent to the HDI. For highly skewed distributions, a proper HDI algorithm may give different (narrower) intervals. The percentiles are computed as: - lower = (1 - hdi_prob) / 2 * 100 - upper = (1 + hdi_prob) / 2 * 100 For hdi_prob=0.94, this gives percentiles 3 and 97. """ hdi_low_pct = (1 - hdi_prob) / 2 * 100 hdi_high_pct = (1 + hdi_prob) / 2 * 100 return ( np.percentile(samples, hdi_low_pct, axis=axis), np.percentile(samples, hdi_high_pct, axis=axis), )