Marginal Posteriors
Understand posterior marginal distributions and use utility functions to evaluate, transform, sample, and summarize them.
What are Marginal Posteriors?
Definition
A marginal posterior distribution \(\pi(\theta_i \mid \mathbf{y})\) is the posterior probability distribution for a single parameter \(\theta_i\), integrating out all other parameters. It answers: "Given the data, what do we believe about this specific parameter?"
After fitting a model with pyINLA, you obtain marginal posteriors for:
- Fixed effects - regression coefficients via
res.marginals_fixed(dict keyed by effect name) - Hyperparameters - precision / range / shape parameters via
res.marginals_hyperpar(dict keyed by R-INLA's descriptive labels, e.g."Precision for the Gaussian observations","Precision for area") - Random effects - group-specific levels via
res.marginals_random(dict-of-dicts: random-effect id → per-level density, keyed by"index.1","index.2", …) - Linear predictor - fitted-value marginals via
res.marginals_linear_predictor, keyed by"Predictor.001","Predictor.002", … Opt-in: this attribute isNoneunless you passcontrol={'compute': {'return_marginals_predictor': True}}topyinla(...).
In pyINLA, each marginal is a 2-column pandas.DataFrame with column names x (parameter values) and y (density values).
Accessing the four marginal types
After fitting, you reach each of the four groups through a named attribute on the result object. Every marginal is a 2-column pandas.DataFrame with columns x (parameter value grid) and y (density).
import numpy as np
import pandas as pd
from pyinla import pyinla
# Synthetic data: y = 1 + 0.5 x + u[group] + noise.
rng = np.random.default_rng(0)
n, K = 200, 20
group = rng.integers(1, K + 1, size=n)
u = rng.normal(0, 0.5, size=K)
x = rng.normal(size=n)
y = 1.0 + 0.5 * x + u[group - 1] + rng.normal(0, 0.2, size=n)
df = pd.DataFrame({"y": y, "x": x, "group": group})
# A small Gaussian model with one fixed effect and one IID random effect.
model = {
"response": "y",
"fixed": ["1", "x"],
"random": [{"id": "group", "model": "iid"}],
}
# Opt in to linear-predictor marginals (otherwise the attribute is None).
res = pyinla(
model=model, family="gaussian", data=df,
control={"compute": {"return_marginals_predictor": True}},
)
# --- 1. Fixed effects --------------------------------------------------
# dict keyed by effect name; each value is a DataFrame with columns x, y.
marg_x = res.marginals_fixed["x"]
# Use pyinla.emarginal for expectations (Simpson quadrature on the grid):
posterior_mean_x = pyinla.emarginal(lambda v: v, marg_x) # E[x]
posterior_var_x = pyinla.emarginal(lambda v: v**2, marg_x) - posterior_mean_x**2
# --- 2. Hyperparameters ------------------------------------------------
# dict keyed by R-INLA's descriptive labels.
marg_prec = res.marginals_hyperpar["Precision for the Gaussian observations"]
# Transform precision -> variance via pyinla.tmarginal (handles the
# Jacobian, re-grids, and returns a properly ordered marginal):
marg_var = pyinla.tmarginal(lambda tau: 1.0 / tau, marg_prec)
posterior_mean_var = pyinla.emarginal(lambda v: v, marg_var)
# Quick summary (mean, sd, quantiles, mode):
pyinla.zmarginal(marg_var)
# --- 3. Random effects -------------------------------------------------
# dict-of-dicts: term -> per-level DataFrame keyed by "index.<k>".
group_marginals = res.marginals_random["group"]
marg_group_5 = group_marginals["index.5"] # density for level 5
mean_group_5 = pyinla.emarginal(lambda v: v, marg_group_5)
# --- 4. Linear predictor -----------------------------------------------
# dict keyed by "Predictor.001", "Predictor.002", ...
marg_pred_1 = res.marginals_linear_predictor["Predictor.001"] # eta_1
ci_pred_1 = pyinla.qmarginal([0.025, 0.975], marg_pred_1) # 95% credible interval
How INLA Computes Marginal Posteriors
INLA computes marginal posteriors through a two-step numerical integration process:
This integral is approximated as a weighted sum over carefully selected hyperparameter configurations:
Two Key Approximations
- Approximation Strategy: How to compute \(\tilde{\pi}(x_i \mid \boldsymbol{\theta}, \mathbf{y})\) for each latent field element
- Integration Strategy: How to select integration points \(\boldsymbol{\theta}^{(k)}\) and weights \(\Delta_k\)
The Hyperparameter Posterior
The posterior for hyperparameters is approximated using the Laplace approximation:
where \(\mathbf{x}^*(\boldsymbol{\theta})\) is the mode of \(\pi(\mathbf{x} \mid \boldsymbol{\theta}, \mathbf{y})\) and \(\tilde{\pi}_G\) is the Gaussian approximation.
Approximation Strategies
The approximation strategy controls how INLA computes the conditional marginal \(\pi(x_i \mid \boldsymbol{\theta}, \mathbf{y})\) for each latent field element. Four strategies are available:
Gaussian
Uses only the mean and variance from the Gaussian approximation to the full conditional. Fast because these are already computed during hyperparameter optimization.
Use when: True posterior is approximately Gaussian, speed is critical.
Simplified Laplace
Mean-skew corrected Gaussian by fitting a Skew-Normal. Uses cubic spline correction that accounts for location bias and skewness.
Use when: General-purpose applications. Best balance of speed and accuracy.
The user-facing default for strategy is "auto", which lets INLA pick among the four based on model size and structure; for most models it resolves to simplified.laplace.
Laplace
Full Laplace approximation evaluated at each value of \(x_i\). Most expensive but provides the best accuracy for non-Gaussian posteriors.
Use when: Maximum accuracy needed, non-Gaussian likelihoods, small datasets.
Adaptive
Uses full Laplace for short latent effects (length ≤ adaptive_max, default 25) and Simplified Laplace for longer effects.
Balances accuracy for small effects with efficiency for large ones.
Use when: Models with both short and long latent effects where accuracy matters for short ones.
Setting the Approximation Strategy
from pyinla import pyinla
# Gaussian approximation (fastest)
res_ga = pyinla(
model=model, family='poisson', data=df,
control={'inla': {'strategy': 'gaussian'}}
)
# Simplified Laplace (default when strategy='auto')
res_sla = pyinla(
model=model, family='poisson', data=df,
control={'inla': {'strategy': 'simplified.laplace'}}
)
# Full Laplace (most accurate)
res_la = pyinla(
model=model, family='poisson', data=df,
control={'inla': {'strategy': 'laplace'}}
)
# Adaptive (hybrid approach)
res_adaptive = pyinla(
model=model, family='poisson', data=df,
control={'inla': {'strategy': 'adaptive', 'adaptive_max': 15}}
)
Comparison
| Strategy | Best For |
|---|---|
gaussian |
Large datasets, quick exploration |
simplified.laplace |
General-purpose applications |
laplace |
Non-Gaussian likelihoods, publication-quality fits |
adaptive |
Mixed short / long latent effects |
VB Correction
The Variational Bayes (VB) correction is a separate mechanism that improves the Gaussian approximation by iteratively correcting the mean (or variance). It is enabled by default in compact mode and works alongside the approximation strategy.
How VB Correction Works
When the Gaussian approximation is used, the mean of the latent field may be biased. VB correction iteratively updates the mean using variational methods to reduce this bias. The correction is applied during the optimization phase before computing marginals.
VB Settings
import copy
import numpy as np
import pandas as pd
from pyinla import pyinla
# Synthetic data: y = 1 + 0.5 x + noise
rng = np.random.default_rng(0)
n = 200
x = rng.normal(size=n)
y = 1.0 + 0.5 * x + rng.normal(0, 0.3, size=n)
df = pd.DataFrame({"y": y, "x": x})
model = {"response": "y", "fixed": ["1", "x"]}
# Default: VB correction enabled with mean strategy
res = pyinla(
model=copy.deepcopy(model), family='gaussian', data=df,
control={
'inla': {
'control_vb': {
'enable': True, # Enable VB correction (default: auto)
'strategy': 'mean', # 'mean' or 'variance'
'iter_max': 25, # Maximum iterations
'hessian_strategy': 'full' # 'full', 'partial', 'diagonal'
}
}
}
)
# Disable VB correction
res_no_vb = pyinla(
model=copy.deepcopy(model), family='gaussian', data=df,
control={'inla': {'control_vb': {'enable': False}}}
)
| Option | Default | Description |
|---|---|---|
enable |
'auto' |
Enable VB correction (auto enables in compact mode) |
strategy |
'mean' |
Correct the mean ('mean') or variance ('variance') |
iter_max |
25 |
Maximum number of VB iterations |
hessian_update |
2 |
Frequency of Hessian updates |
hessian_strategy |
'full' |
Hessian computation: 'full', 'partial', 'diagonal' |
Integration Strategies
The integration strategy controls how INLA integrates over the hyperparameter space \(\boldsymbol{\theta}\) to compute the final marginals. INLA first finds the posterior mode \(\boldsymbol{\theta}^*\) and the Hessian matrix \(\mathbf{H}\), then explores the hyperparameter space using one of the strategies below:
Grid
Regular grid exploration centered at the mode. Points are included if:
First explores along principal axes, then adds combinations satisfying the threshold.
Use when: Few hyperparameters (1-3), want thorough coverage.
CCD
Central Composite Design places integration points strategically using response surface methodology. More efficient than grid for higher dimensions.
Uses eigenvalue decomposition of the Hessian to work in a rescaled, orthogonalized space.
Use when: Multiple hyperparameters, efficiency matters.
The user-facing default for int_strategy is "auto", which lets INLA pick a strategy from model size and dimensionality; for most models it resolves to ccd.
Empirical Bayes
Uses only the posterior mode:
Ignores hyperparameter uncertainty. Single evaluation at the mode.
Use when: Speed is critical, hyperparameters well-determined, quick diagnostics.
User-supplied
You provide the integration design directly via an int_design matrix. Each row gives a hyperparameter configuration plus a weight; INLA evaluates the conditional marginal at each row and combines.
Two modes:
"user"— rows are in the original \(\boldsymbol{\theta}\) parameterization."user.std"— rows are in the rescaled / Hessian-orthogonalized standardized space.
Use when: You want explicit control over the integration grid (sensitivity analysis, custom designs).
Setting the Integration Strategy
from pyinla import pyinla
# CCD integration (auto resolves to this for typical models)
res_ccd = pyinla(
model=model, family='gaussian', data=df,
control={'inla': {'int_strategy': 'ccd'}}
)
# Grid integration (thorough)
res_grid = pyinla(
model=model, family='gaussian', data=df,
control={'inla': {'int_strategy': 'grid'}}
)
# Empirical Bayes (fastest, mode only)
res_eb = pyinla(
model=model, family='gaussian', data=df,
control={'inla': {'int_strategy': 'eb'}}
)
# User-supplied design: each row is (theta_1, ..., theta_p, weight).
# Below is a minimal 3-point design for a single-hyperparameter model.
import numpy as np
design = np.array([
[-1.0, 1.0], # theta = -1 (log-precision), weight = 1
[ 0.0, 2.0], # theta = 0, weight = 2
[ 1.0, 1.0], # theta = 1, weight = 1
])
res_user = pyinla(
model=model, family='gaussian', data=df,
control={'inla': {'int_strategy': 'user', 'int_design': design}}
)
Integration knobs
Beyond the strategy itself, control['inla'] exposes several numeric knobs that fine-tune the grid (effective for grid, mostly ignored under eb and ccd):
| Key | Default | What it controls |
|---|---|---|
dz | 0.75 | Step size in the rescaled (Hessian-orthogonalized) integration grid. |
diff_logdens | 6 | Threshold δ: points are kept only when their log-density is within δ of the mode. |
npoints | 9 | Number of CCD integration points (the central-composite design size). |
cutoff | 1e-4 | Drop integration weights below this fraction of the maximum. |
stencil | 5 | Finite-difference stencil size for derivatives at the mode (5, 7, or 9). |
h | 0.005 | Finite-difference step size for the Hessian. |
interpolator | "auto" | How marginals are interpolated between integration points. |
Combining Strategies
You can combine approximation and integration strategies:
# Fast: Gaussian + Empirical Bayes
control_fast = {
'inla': {
'strategy': 'gaussian',
'int_strategy': 'eb'
}
}
# Accurate: Laplace + Grid
control_accurate = {
'inla': {
'strategy': 'laplace',
'int_strategy': 'grid'
}
}
# Balanced (default): Simplified Laplace + CCD
control_balanced = {
'inla': {
'strategy': 'simplified.laplace',
'int_strategy': 'ccd'
}
}
strategy='gaussian' and int_strategy='eb' for rapid model development. Switch to defaults or more accurate settings for final results.
Marginals for Different Model Components
Latent Field Elements
For latent field elements \(x_i\) (fixed effects, random effects), marginals are computed using the approximation strategy over all integration points:
Hyperparameters
For hyperparameters \(\theta_j\), the marginal is obtained by integrating out other hyperparameters:
This is done numerically from the joint hyperparameter posterior evaluated at the integration points.
Accessing Different Marginals
from pyinla import pyinla
res = pyinla(model=model, family='gaussian', data=df)
# Fixed effects (regression coefficients)
for name, marg in res.marginals_fixed.items():
print(f"{name}: shape {marg.shape}")
# Hyperparameters (precision, variance, etc.)
for name, marg in res.marginals_hyperpar.items():
print(f"{name}: shape {marg.shape}")
# Random effects (if model has them)
if res.marginals_random:
for effect_name, marginals in res.marginals_random.items():
print(f"{effect_name}: {len(marginals)} marginals")
# Linear predictor (fitted values)
if res.marginals_linear_predictor is not None:
print(f"Linear predictor: {len(res.marginals_linear_predictor)} marginals")
Accessing Marginals
import numpy as np
import pandas as pd
import pyinla
# --- Generate a small synthetic dataset --------------------------------
rng = np.random.default_rng(0)
n = 200
x1 = rng.normal(size=n)
x2 = rng.normal(size=n)
y = 1.0 + 2.0 * x1 - 0.5 * x2 + rng.normal(scale=0.3, size=n)
data = pd.DataFrame({"y": y, "x1": x1, "x2": x2})
# --- Fit a Gaussian linear model (dict-style spec) ---------------------
res = pyinla.pyinla(
formula={'response': 'y', 'fixed': ['1', 'x1', 'x2']},
family='gaussian',
data=data,
)
# --- Access marginals --------------------------------------------------
marg_intercept = res.marginals_fixed["(Intercept)"]
marg_x1 = res.marginals_fixed["x1"]
# Hyperparameter keys use INLA's descriptive labels:
marg_precision = res.marginals_hyperpar["Precision for the Gaussian observations"]
# Each marginal is a pandas DataFrame with two columns: 'x' (parameter values) and 'y' (density)
print(marg_x1.shape) # e.g., (43, 2)
print(marg_x1.head()) # First 5 rows of the DataFrame
Marginal Utility Functions
pyINLA provides functions to work with marginal distributions as if they were continuous. Import them directly from pyinla:
import pyinla
# All functions available as pyinla.
pyinla.dmarginal(0.5, marg) # Density at x=0.5
pyinla.pmarginal(0.5, marg) # P(X <= 0.5)
pyinla.qmarginal(0.95, marg) # 95th percentile
Quick Reference
| Function | Purpose | Returns |
|---|---|---|
dmarginal | Evaluate density at point(s) | Density value(s) |
pmarginal | Cumulative probability P(X ≤ x) | Probability |
qmarginal | Quantile function (inverse CDF) | Quantile value |
rmarginal | Random sampling | Array of samples |
emarginal | Expected value of a function E[f(X)] | Scalar |
tmarginal | Transform marginal via function | New marginal |
smarginal | Smooth/interpolate marginal | Refined marginal |
mmarginal | Mode (MAP estimate) | Scalar |
zmarginal | Summary statistics | Dict with mean, sd, quantiles |
hpdmarginal | Highest posterior density interval | HPD bounds |
dmarginal - Density Evaluation
Evaluates the marginal posterior density at one or more points using monotonic spline interpolation.
Mathematical Details
Uses PCHIP (Piecewise Cubic Hermite Interpolating Polynomial) interpolation to create a smooth, monotonicity-preserving approximation. For points outside the support, returns 0.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
x | float or array-like | — | Point(s) at which to evaluate the density. |
marginal | (n, 2) array / dict / DataFrame | — | Marginal with columns / keys x, y. |
log | bool | False | If True, return the log-density (and -inf outside the support). |
Example
import pyinla
import numpy as np
import matplotlib.pyplot as plt
marg = res.marginals_fixed["x1"] # DataFrame with columns ['x', 'y']
# Evaluate at a single point
d = pyinla.dmarginal(0.5, marg)
print(f"Density at 0.5: {d:.4f}")
# Log-density variant (useful for numerical stability and likelihood ratios)
log_d = pyinla.dmarginal(0.5, marg, log=True)
# Evaluate at multiple points for plotting
x_grid = np.linspace(marg["x"].min(), marg["x"].max(), 200)
y_grid = pyinla.dmarginal(x_grid, marg)
plt.figure(figsize=(8, 5))
plt.plot(x_grid, y_grid, 'b-', linewidth=2, label='Interpolated')
plt.scatter(marg["x"], marg["y"], s=20, c='red', alpha=0.5, label='Original points')
plt.xlabel(r'$\theta$')
plt.ylabel(r'$\pi(\theta \mid y)$')
plt.legend()
plt.title('Posterior Marginal Density')
plt.show()
pmarginal - Cumulative Distribution
Computes the cumulative distribution function (CDF): the probability that the parameter is less than or equal to a given value.
Mathematical Details
Integration uses the trapezoidal rule on the discrete marginal, followed by PCHIP interpolation of the cumulative values. This ensures monotonicity of the CDF.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
q | float or array-like | — | Point(s) at which to evaluate the CDF. |
marginal | (n, 2) array / dict / DataFrame | — | Marginal with columns / keys x, y. |
normalize | bool | True | Rescale the cumulative integral to end at exactly 1 before interpolating. |
length | int | 2048 | Number of points on the internal interpolation grid. |
Example
import pyinla
marg = res.marginals_fixed["x1"]
# Probability that coefficient is negative
p_negative = pyinla.pmarginal(0, marg)
print(f"P(x1 < 0) = {p_negative:.4f}")
# Probability that coefficient is in interval [0.2, 0.8]
p_interval = pyinla.pmarginal(0.8, marg) - pyinla.pmarginal(0.2, marg)
print(f"P(0.2 < x1 < 0.8) = {p_interval:.4f}")
# One-sided credible interval
if p_negative < 0.05:
print("Strong evidence that x1 > 0 (95% credible)")
pyinla.pmarginal(0, marg) to get the posterior probability of being negative.
qmarginal - Quantile Function
Computes quantiles (inverse CDF). Given a probability p, returns the value x such that P(X ≤ x) = p.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
p | float or array-like | — | Probability or probabilities in [0, 1]. Output shape matches p. |
marginal | (n, 2) array / dict / DataFrame | — | Marginal with columns / keys x, y. |
length | int | 2048 | Number of points on the internal interpolation grid. |
Example
import pyinla
marg = res.marginals_fixed["x1"]
# Median
median = pyinla.qmarginal(0.5, marg)
print(f"Median: {median:.4f}")
# 95% equal-tailed credible interval
q025 = pyinla.qmarginal(0.025, marg)
q975 = pyinla.qmarginal(0.975, marg)
print(f"95% CI: [{q025:.4f}, {q975:.4f}]")
# Multiple quantiles at once
quantiles = pyinla.qmarginal([0.025, 0.25, 0.5, 0.75, 0.975], marg)
print(f"Quantiles: {quantiles}")
rmarginal - Random Sampling
Generates random samples from the marginal posterior using inverse transform sampling.
Mathematical Details
The inverse transform method generates uniform random numbers and transforms them through the quantile function. This produces samples that follow the target marginal distribution.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
n | int | — | Number of samples to draw. |
marginal | (n, 2) array / dict / DataFrame | — | Marginal with columns / keys x, y. |
rng | np.random.Generator | None | Optional generator for reproducibility. Defaults to a fresh np.random.default_rng(). |
Example
import pyinla
import numpy as np
marg = res.marginals_fixed["x1"]
# Generate samples
samples = pyinla.rmarginal(10000, marg)
# For reproducibility
rng = np.random.default_rng(42)
samples_reproducible = pyinla.rmarginal(10000, marg, rng=rng)
# Monte Carlo estimates
print(f"Sample mean: {samples.mean():.4f}")
print(f"Sample std: {samples.std():.4f}")
print(f"P(X > 0): {(samples > 0).mean():.4f}")
emarginal - Expected Value of Function
Computes the expected value of a function applied to the marginal distribution.
Mathematical Details
Integration uses Simpson's rule on the smoothed marginal, providing \(O(h^4)\) convergence for smooth functions.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
fun | callable (x) -> scalar or vector | — | Function applied to the grid. Vector-valued returns become independent expectations. |
marginal | (n, 2) array / dict / DataFrame | — | Marginal with columns / keys x, y. |
*args, **kwargs | any | — | Extra arguments forwarded to fun at every grid point. |
Works for any fun — does not require monotonicity. Use this rather than tmarginal when you only need a moment.
Example
import pyinla
import numpy as np
marg = res.marginals_fixed["x1"]
# Posterior mean (E[X])
mean = pyinla.emarginal(lambda x: x, marg)
print(f"Mean: {mean:.4f}")
# Posterior variance (E[X^2] - E[X]^2)
ex2 = pyinla.emarginal(lambda x: x**2, marg)
variance = ex2 - mean**2
print(f"Variance: {variance:.4f}")
# Expected value of exp(theta) - useful for log-scale parameters
e_exp = pyinla.emarginal(np.exp, marg)
print(f"E[exp(theta)]: {e_exp:.4f}")
tmarginal - Transform Marginal
Transforms a marginal distribution through a monotonic function, returning a new marginal for the transformed variable.
Change of Variables
For a monotonic transformation \(Y = g(\theta)\), the density of Y is obtained via the change of variables formula. The Jacobian accounts for how the transformation stretches or compresses the density.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
fun | callable (x) -> y | — | Monotonic transformation applied element-wise. |
marginal | (n, 2) array / dict / DataFrame | — | Marginal of X to transform. |
n | int | 2048 | Number of points on the output grid. |
h_diff | float | ~6e-05 | Step size for the numerical derivative of fun (used in the Jacobian). |
method | str | "quantile" | Resampling scheme: "quantile" (default, uses CDF inversion) or "linear" (evenly spaced over the support). |
Example
import pyinla
import numpy as np
# pyinla returns marginals on the user (precision) scale, not log-scale,
# so to operate on log(precision) first transform back:
marg_prec = res.marginals_hyperpar["Precision for the Gaussian observations"]
marg_log_prec = pyinla.tmarginal(np.log, marg_prec)
# Or transform precision to standard deviation directly: sd = 1 / sqrt(precision)
marg_sd = pyinla.tmarginal(lambda x: 1.0 / np.sqrt(x), marg_prec)
# Increase the internal sampling resolution for a smoother result
marg_sd_hi = pyinla.tmarginal(
fun = lambda x: 1.0 / np.sqrt(x),
marginal = marg_prec,
n = 4096,
)
rmarginal to sample, transform the samples, then estimate the density.
smarginal - Smooth/Interpolate
Creates a smoother, higher-resolution version of a marginal distribution using log-density spline interpolation on a non-uniform t-map.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
marginal | (n, 2) array / dict / DataFrame | — | Marginal with columns / keys x, y. |
log | bool | False | If True, return the smoothed log-density (column y holds log-densities). |
extrapolate | float | 0.0 | Symmetric padding (in input units) added on each side of the support. |
keep_type | bool | False | If the input is an (n, 2) ndarray, return the same ndarray form; otherwise output is always {'x','y'} dict. |
factor | int | 15 | Output resolution multiplier. Roughly factor × len(marginal) output points. |
Example
import pyinla
marg = res.marginals_fixed["x1"]
# Original might have ~50 points; smooth via spline interpolation
# factor controls density: ~factor x the number of input points.
marg_smooth = pyinla.smarginal(marg, factor=15)
print(f"Original points: {len(marg)}")
print(f"Smoothed points: {len(marg_smooth['x'])}")
mmarginal - Mode (MAP Estimate)
Returns the mode - the value with highest posterior density (Maximum A Posteriori estimate).
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
marginal | (n, 2) array / dict / DataFrame | — | Marginal with columns / keys x, y. |
Returns a single float. The search is a coarse 1%-99% quantile grid then a 1-D refinement on the bracketing interval.
Example
import pyinla
marg = res.marginals_fixed["x1"]
# Mode (MAP estimate)
mode = pyinla.mmarginal(marg)
# Compare with mean and median
mean = pyinla.emarginal(lambda x: x, marg)
median = pyinla.qmarginal(0.5, marg)
print(f"Mode: {mode:.4f}")
print(f"Mean: {mean:.4f}")
print(f"Median: {median:.4f}")
zmarginal - Summary Statistics
Computes a comprehensive summary: mean, standard deviation, and key quantiles.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
marginal | (n, 2) array / dict / DataFrame | — | Marginal with columns / keys x, y. |
silent | bool | False | If True, suppress the printed table and only return the dict. |
Returns
| Key | Description |
|---|---|
mean | Posterior mean |
sd | Posterior standard deviation |
mode | Posterior mode (MAP) |
quant0.025 | 2.5% quantile |
quant0.25 | 25% quantile (lower quartile) |
quant0.5 | Median |
quant0.75 | 75% quantile (upper quartile) |
quant0.975 | 97.5% quantile |
Example
import pyinla
marg = res.marginals_fixed["x1"]
# Get full summary
summary = pyinla.zmarginal(marg)
print("Posterior Summary for x1:")
print(f" Mean: {summary['mean']:.4f}")
print(f" SD: {summary['sd']:.4f}")
print(f" 95% CI: [{summary['quant0.025']:.4f}, {summary['quant0.975']:.4f}]")
# Summarize all fixed effects
for name, marg in res.marginals_fixed.items():
s = pyinla.zmarginal(marg)
print(f"{name:15s}: {s['mean']:8.4f} ({s['quant0.025']:8.4f}, {s['quant0.975']:8.4f})")
hpdmarginal - HPD Interval
Computes the Highest Posterior Density (HPD) interval - the shortest interval containing a specified probability mass.
where \(k_\alpha\) is chosen such that \(P(\theta \in \text{HPD}_\alpha) = 1 - \alpha\)
HPD vs Equal-Tailed Intervals
For symmetric distributions, HPD and equal-tailed intervals are identical. For skewed distributions:
- HPD: Shortest interval (every point inside has higher density than outside)
- Equal-tailed: Same probability in each tail
HPD is often preferred for reporting because it's the shortest credible interval.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
p | float or array-like | — | Credible mass(es), each in (0, 1). |
marginal | (n, 2) array / dict / DataFrame | — | Marginal with columns / keys x, y. Must be unimodal. |
length | int | 2048 | Number of points on the internal interpolation grid. |
Returns np.ndarray of shape (len(p), 2) with columns [low, high]. For a scalar p, the output is shape (1, 2) — call .ravel() to flatten.
Example
import numpy as np
import pandas as pd
import pyinla
# Synthetic data: y = 1 + 0.5 x1 + noise
rng = np.random.default_rng(0)
n = 200
x1 = rng.normal(size=n)
y = 1.0 + 0.5 * x1 + rng.normal(0, 0.3, size=n)
df = pd.DataFrame({"y": y, "x1": x1})
res = pyinla.pyinla(
model={"response": "y", "fixed": ["1", "x1"]},
family="gaussian", data=df,
)
marg = res.marginals_fixed["x1"]
# 95% HPD interval. hpdmarginal returns shape (len(p), 2); ravel for a flat [low, high].
hpd = pyinla.hpdmarginal(0.95, marg).ravel()
print(f"95% HPD: [{hpd[0]:.4f}, {hpd[1]:.4f}]")
# 95% equal-tailed interval via the 2.5% / 97.5% quantiles.
q = pyinla.qmarginal([0.025, 0.975], marg)
print(f"95% Equal-tailed: [{q[0]:.4f}, {q[1]:.4f}]")
# HPD is ≤ equal-tailed in width; equal for symmetric marginals, shorter for skewed ones.
print(f"HPD width: {hpd[1] - hpd[0]:.4f}")
print(f"Equal-tailed width: {q[1] - q[0]:.4f}")