statspai.mcmc¶
mcmc ¶
Bayesian econometrics by MCMC (statspai.mcmc).
NumPy-only samplers for the standard regression models, convergence
diagnostics for any chain, and marginal likelihoods. The PyMC-backed
estimators for causal designs live in :mod:statspai.bayes.
BMAResult
dataclass
¶
Bases: ResultProtocolMixin
Result of :func:bma.
Attributes:
| Name | Type | Description |
|---|---|---|
table |
DataFrame
|
One row per coefficient: |
models |
DataFrame
|
The models averaged over, best first: one 0/1 column per term,
|
params |
Series
|
|
std_errors |
Series
|
|
n_obs |
int
|
|
n_models |
int
|
Number of models retained. |
method, family |
str
|
|
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame(rng.normal(size=(200, 4)), columns=list("abcd"))
>>> df["y"] = 1 + df["a"] - 0.5 * df["c"] + rng.normal(size=200)
>>> out = sp.bma("y ~ a + b + c + d", df)
>>> isinstance(out, sp.BMAResult)
True
>>> bool(out.table.loc["a", "pip"] > 0.99)
True
predict ¶
Model-averaged prediction of the mean.
Each retained model's prediction, on the scale of the outcome, weighted by its posterior probability.
BayesCausalResult ¶
Bases: ResultProtocolMixin
Posterior of a Bayesian tree causal model (:func:sp.bart_causal).
Attributes:
| Name | Type | Description |
|---|---|---|
estimate, se |
float
|
Posterior mean and standard deviation of the estimand (the sample average of the individual effects over the target units). |
ci |
tuple
|
Equal-tailed credible interval at |
draws |
Series
|
Posterior draws of the estimand (all chains, in chain order). |
cate |
DataFrame
|
Per-row posterior mean, sd and interval of |
diagnostics |
dict
|
|
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> n = 200
>>> x = rng.normal(size=n)
>>> z = rng.binomial(1, 1 / (1 + np.exp(-x)))
>>> y = x + 1.0 * z + rng.normal(scale=0.5, size=n)
>>> df = pd.DataFrame({'y': y, 'z': z, 'x': x})
>>> r = sp.bart_causal(df, 'y', 'z', ['x'], method='bcf', draws=200,
... burnin=200, chains=1, seed=1)
>>> bool(r.ci[0] < 1.0 < r.ci[1])
True
BayesR2Result
dataclass
¶
Bases: ResultProtocolMixin
Result of :func:bayes_r2 and :func:loo_r2.
Attributes:
| Name | Type | Description |
|---|---|---|
kind |
str
|
|
draws |
ndarray
|
Posterior draws of R-squared ( |
estimate |
float
|
Median of the draws; for |
mean, sd, lower, upper |
float
|
Mean, standard deviation and central interval of the draws. |
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x": rng.normal(size=100)})
>>> df["y"] = df["x"] + rng.normal(size=100)
>>> fit = sp.bayes_regress("y ~ x", df, draws=1000, burnin=300, seed=1)
>>> bool(0.3 < sp.bayes_r2(fit).estimate < 0.7)
True
PPCResult
dataclass
¶
Bases: ResultProtocolMixin
Result of :func:ppc.
Attributes:
| Name | Type | Description |
|---|---|---|
stat |
str
|
Name of the test statistic. |
observed |
float
|
Its value on the data. |
replicated |
ndarray
|
Its value on each replicated data set. |
p_value |
float
|
Share of replications with a statistic at least as large as the observed one. Values near 0 or 1 mean the model does not reproduce this feature of the data. |
y, y_rep |
ndarray
|
The observed outcome and the replications (one row each). |
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x": rng.normal(size=80)})
>>> df["y"] = 1 + 2 * df["x"] + rng.normal(size=80)
>>> fit = sp.bayes_regress("y ~ x", df, draws=500, burnin=200, seed=1)
>>> out = sp.ppc(fit, stat="max", seed=1)
>>> bool(0.02 < out.p_value < 0.98)
True
plot ¶
Draw the check.
kind='stat': histogram of the replicated statistic with the
observed value marked. kind='density': the distribution of
the observed outcome over those of n_overlay replications.
BayesFactorResult
dataclass
¶
Bases: ResultProtocolMixin
Result of :func:bayes_factor.
Attributes:
| Name | Type | Description |
|---|---|---|
table |
DataFrame
|
One row per model: |
log_bf |
float
|
Log Bayes factor of the first model against the second. |
bf |
float
|
The Bayes factor itself ( |
evidence |
str
|
Kass and Raftery's label for |
Examples:
>>> import statspai as sp
>>> out = sp.bayes_factor(-100.0, -103.0)
>>> round(out.log_bf, 1)
3.0
>>> out.evidence
'strong evidence for model 1'
LOOResult
dataclass
¶
Bases: ResultProtocolMixin
Expected log predictive density of a model, with its pieces.
Returned by :func:loo, :func:waic and :func:kfold.
Attributes:
| Name | Type | Description |
|---|---|---|
kind |
{'loo', 'waic', 'kfold'}
|
|
estimates |
DataFrame
|
Rows |
pointwise |
DataFrame
|
One row per observation: |
elpd, se_elpd, p, se_p, ic, se_ic |
float
|
The entries of |
mcse_elpd |
float
|
Monte Carlo standard error of |
k_threshold |
float
|
Pareto shapes above it mark observations whose leave-one-out density is not reliably estimated. |
n_obs, n_draws |
int
|
|
log_weights |
ndarray or None
|
The smoothed log weights ( |
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> y, mu = rng.normal(size=20), rng.normal(0, 0.2, size=(500, 1))
>>> ll = -0.5 * (y - mu) ** 2
>>> out = sp.loo(ll)
>>> out.kind, out.n_obs
('loo', 20)
PSISResult
dataclass
¶
Bases: ResultProtocolMixin
Result of :func:psis.
Attributes:
| Name | Type | Description |
|---|---|---|
log_weights |
(ndarray, shape(draws, observations))
|
Smoothed log importance weights, each column normalised to sum to one on the natural scale. |
pareto_k |
ndarray
|
Estimated shape of the generalized Pareto tail of the raw ratios. |
n_eff |
ndarray
|
Effective sample size of the smoothed weights,
|
k_threshold |
float
|
See :func: |
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> out = sp.psis(np.random.default_rng(0).normal(size=(500, 2)))
>>> out.pareto_k.shape, out.weights().shape
((2,), (500, 2))
MCMCDiagnostic
dataclass
¶
Bases: ResultProtocolMixin
A convergence diagnostic for each parameter of a chain.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Which diagnostic this is. |
table |
DataFrame
|
One row per parameter. The columns depend on the diagnostic. |
settings |
dict
|
The arguments the diagnostic was run with. |
passed |
bool or None
|
Whether every parameter passes at the diagnostic's own threshold, when the diagnostic defines one. |
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> draws = np.random.default_rng(0).normal(size=(2000, 2))
>>> out = sp.geweke_diag(draws)
>>> list(out.table.columns)
['z', 'p_value']
>>> isinstance(out, sp.MCMCDiagnostic)
True
GPResult
dataclass
¶
Bases: ResultProtocolMixin
Result of :func:gp_regress.
Attributes:
| Name | Type | Description |
|---|---|---|
params |
Series
|
The hyperparameters: one |
fitted |
Series
|
Posterior mean of the regression function at the sample points. |
loglik |
float
|
Log marginal likelihood at the hyperparameters. When the constant is estimated this is the restricted likelihood, which is not comparable with a fit that fixes the mean. |
kernel, formula, n_obs, level |
|
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> x = np.sort(rng.uniform(0, 6, 80))
>>> df = pd.DataFrame({"x": x, "y": np.sin(x) + 0.2 * rng.normal(size=80)})
>>> fit = sp.gp_regress("y ~ x", df)
>>> isinstance(fit, sp.GPResult)
True
>>> fit.predict(pd.DataFrame({"x": [1.0, 2.0]})).shape
(2, 4)
log_marginal_likelihood ¶
Log marginal likelihood at the hyperparameters.
predict ¶
predict(newdata: Optional[DataFrame] = None, noise: bool = False, level: Optional[float] = None) -> DataFrame
Posterior mean and interval of the regression function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
newdata
|
DataFrame
|
Points at which to predict. Default: the sample. |
None
|
noise
|
bool
|
|
False
|
level
|
float
|
Mass of the interval. Default: the level of the fit. |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Columns |
expected_improvement ¶
expected_improvement(newdata: DataFrame, minimize: bool = True, best: Optional[float] = None) -> Series
Expected improvement over the best value seen so far.
The criterion of efficient global optimisation (Jones, Schonlau and Welch 1998): large where the predicted value is good, or where it is uncertain. The next point to evaluate is the one that maximises it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
newdata
|
DataFrame
|
Candidate points. |
required |
minimize
|
bool
|
Whether small or large outcomes are wanted. |
True
|
best
|
float
|
The value to improve on. Default: the smallest (largest) outcome in the sample. |
None
|
Returns:
| Type | Description |
|---|---|
Series
|
|
BayesMixedResult
dataclass
¶
Bases: BayesRegressResult
Posterior of a hierarchical model fitted by :func:bayes_mixed.
Everything of :class:BayesRegressResult, plus:
Attributes:
| Name | Type | Description |
|---|---|---|
random_effects |
DataFrame
|
Posterior mean and standard deviation of every group's random
effects (columns |
re_cov |
DataFrame
|
Posterior mean of the covariance matrix |
n_groups |
int
|
|
Notes
predict returns the fixed part x' beta (on the outcome scale
of the family at a random effect of zero); add random_effects for
a group-specific prediction.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> g = np.repeat(np.arange(30), 6)
>>> df = pd.DataFrame({"id": g, "x": rng.normal(size=180)})
>>> df["y"] = 1 + 0.5 * df["x"] + rng.normal(size=30)[g] + rng.normal(size=180)
>>> fit = sp.bayes_mixed("y ~ x", df, group="id", draws=500, burnin=200, seed=1)
>>> isinstance(fit, sp.BayesMixedResult)
True
>>> list(fit.params.index)
['Intercept', 'x', 'sigma2', 'var(Intercept)']
>>> fit.random_effects.shape
(30, 2)
BayesRegressResult
dataclass
¶
Bases: _PredictiveMethods, ResultProtocolMixin
Posterior of a model fitted by :func:bayes_regress.
Attributes:
| Name | Type | Description |
|---|---|---|
model |
str
|
The likelihood ( |
params |
Series
|
Posterior means. |
std_errors |
Series
|
Posterior standard deviations. |
table |
DataFrame
|
One row per parameter: posterior mean, standard deviation, Monte
Carlo standard error of the mean ( |
draws |
DataFrame
|
The retained draws, chains stacked, one column per parameter. |
chain |
ndarray
|
Chain label of every row of |
acceptance_rate |
float or None
|
Metropolis acceptance rate, when the sampler has such a step. |
prior |
dict
|
The prior actually used. |
n_obs |
int
|
|
level |
float
|
Mass of the credible interval in |
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x": rng.normal(size=200)})
>>> df["y"] = 1 + 0.5 * df["x"] + rng.normal(size=200)
>>> fit = sp.bayes_regress("y ~ x", df, draws=1000, burnin=200, seed=1)
>>> isinstance(fit, sp.BayesRegressResult)
True
>>> list(fit.params.index)
['Intercept', 'x', 'sigma2']
>>> fit.conf_int().shape
(3, 2)
conf_int ¶
Credible intervals.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
level
|
float
|
Posterior mass; defaults to the level the model was fitted with. |
None
|
kind
|
('equal', 'hpd')
|
Equal-tailed (quantiles) or highest posterior density. |
'equal'
|
prob ¶
Posterior probability of a statement about the parameters.
expr is evaluated on the draws with the parameter names as
variables, e.g. fit.prob("x > 0") or
fit.prob("x1 > x2"). Names that are not valid Python
identifiers are available through backticks, as in
DataFrame.eval.
diagnostics ¶
Convergence diagnostics of the retained draws.
Returns a dict with geweke, heidel and raftery
(first chain) and, when several chains were run,
gelman_rubin. A diagnostic that cannot be computed on this
chain (for instance Raftery-Lewis on a short run) carries the
reason instead.
predict ¶
predict(data: Optional[DataFrame] = None, what: str = 'mean', level: Optional[float] = None) -> Union[ndarray, DataFrame]
Posterior of the expected outcome at the rows of data.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
New data; the estimation sample when omitted. |
None
|
what
|
('mean', 'interval', 'linear', 'draws', 'probabilities')
|
|
'mean'
|
level
|
float
|
Mass of the interval for |
None
|
log_marginal_likelihood ¶
Log marginal likelihood log p(y | model).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method
|
('exact', 'chib', 'gelfand-dey', 'laplace')
|
|
'exact'
|
alpha
|
float
|
Tail mass truncated from the weighting density of the Gelfand-Dey estimator. |
0.05
|
Returns:
| Type | Description |
|---|---|
float
|
|
Notes
A marginal likelihood is only meaningful under a proper prior and
depends on it: with a prior variance of c on a coefficient the
marginal likelihood falls like c^{-1/2} as c grows.
marginal_likelihood_details has the numerical standard error
of the simulation-based estimates.
marginal_likelihood_details ¶
Log marginal likelihood with its method and numerical error.
plot ¶
Trace and density plots of the draws.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
params
|
list of str
|
Parameters to show; all by default (at most 12). |
None
|
kind
|
('trace', 'density', 'acf')
|
|
'trace'
|
BARTResult
dataclass
¶
Bases: ResultProtocolMixin
Result of :func:bart.
Attributes:
| Name | Type | Description |
|---|---|---|
fitted |
DataFrame
|
Posterior mean, standard deviation and interval of the regression
function at the sample points (of the success probability for
|
sigma |
Series
|
Draws of the error standard deviation (absent for binary). |
variable_importance |
Series
|
Share of all splitting rules that use each regressor, averaged over draws. A screening device, not an effect size. |
params |
Series
|
|
n_trees, formula, family, n_obs, level |
|
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x": rng.uniform(-2, 2, 200)})
>>> df["y"] = np.sin(2 * df["x"]) + 0.2 * rng.normal(size=200)
>>> fit = sp.bart("y ~ x", df, n_trees=30, draws=200, burnin=100, seed=1)
>>> isinstance(fit, sp.BARTResult)
True
>>> fit.predict(pd.DataFrame({"x": [0.0, 1.0]})).shape
(2, 4)
predict_draws ¶
Posterior draws of the regression function at newdata:
an array with one row per draw.
predict ¶
Posterior mean and interval of the regression function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
newdata
|
DataFrame
|
Default: the sample. |
None
|
level
|
float
|
|
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Columns |
bayes_arima ¶
bayes_arima(y: Union[str, ndarray, Series], data: Optional[DataFrame] = None, order: Tuple[int, int, int] = (1, 0, 0), constant: Optional[bool] = None, mean_prior: Tuple[float, float] = (0.0, 1000000.0), sigma2_prior: Tuple[float, float] = (0.001, 0.001), horizon: int = 0, draws: int = 10000, burnin: int = 2000, thin: int = 1, chains: int = 1, seed: Optional[int] = None, level: float = 0.95, tune: Optional[float] = None) -> BayesRegressResult
Bayesian ARIMA(p, d, q) with the exact Gaussian likelihood.
The series is differenced d times and the result is modelled as a
stationary and invertible ARMA(p, q) process around a constant. The
posterior is sampled by random-walk Metropolis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
(array, Series or str)
|
The series, or a column name of |
required |
data
|
DataFrame
|
|
None
|
order
|
(p, d, q)
|
|
(1, 0, 0)
|
constant
|
bool
|
Include a constant: the mean of the series when |
None
|
mean_prior
|
(mean, variance)
|
Normal prior of the constant. |
(0, 1e6)
|
sigma2_prior
|
(alpha0, delta0)
|
|
(0.001, 0.001)
|
horizon
|
int
|
Number of periods to forecast. The posterior predictive mean and
interval go to |
0
|
draws
|
int
|
As in :func: |
10000
|
burnin
|
int
|
As in :func: |
10000
|
thin
|
int
|
As in :func: |
10000
|
chains
|
int
|
As in :func: |
10000
|
seed
|
int
|
As in :func: |
10000
|
level
|
int
|
As in :func: |
10000
|
tune
|
float
|
Scale of the proposal relative to the curvature at the mode.
Default |
None
|
Returns:
| Type | Description |
|---|---|
BayesRegressResult
|
|
Notes
The prior of the AR coefficients is uniform over the stationary region, and that of the MA coefficients is uniform over the invertible region. Both regions are bounded, so the prior is proper without any tuning constant.
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> y = np.zeros(300)
>>> for t in range(1, 300):
... y[t] = 0.6 * y[t - 1] + rng.normal()
>>> fit = sp.bayes_arima(y, order=(1, 0, 0), draws=1000, burnin=300, seed=1)
>>> list(fit.params.index)
['const', 'ar.L1', 'sigma2']
>>> bool(0.4 < fit.params["ar.L1"] < 0.8)
True
References
ramirezhassan2026introduction
bayes_bootstrap ¶
bayes_bootstrap(formula: Optional[str] = None, data: Optional[DataFrame] = None, statistic: Optional[Callable[[DataFrame, ndarray], Any]] = None, draws: int = 2000, concentration: float = 1.0, seed: Optional[int] = None, level: float = 0.95) -> BayesRegressResult
Bayesian bootstrap of a regression or of any weighted statistic.
Rubin's (1981) analogue of the bootstrap: instead of resampling rows, draw a weight vector from a flat Dirichlet distribution and recompute the statistic with those weights. The draws are a sample from the posterior of the statistic under a nonparametric model that puts all its mass on the observed rows, so no likelihood is assumed. For least squares the posterior standard deviations are close to heteroskedasticity-robust standard errors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
formula
|
str
|
|
None
|
data
|
DataFrame
|
|
None
|
statistic
|
callable
|
Instead of a formula, |
None
|
draws
|
int
|
Number of posterior draws. |
2000
|
concentration
|
float
|
Dirichlet concentration of the weights. 1 is Rubin's Bayesian bootstrap; larger values pull the weights toward equality. |
1
|
seed
|
int
|
|
None
|
level
|
float
|
Mass of the credible intervals. |
0.95
|
Returns:
| Type | Description |
|---|---|
BayesRegressResult
|
With |
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x": rng.normal(size=200)})
>>> df["y"] = 1 + df["x"] + rng.normal(size=200)
>>> fit = sp.bayes_bootstrap("y ~ x", df, draws=500, seed=1)
>>> list(fit.params.index)
['Intercept', 'x']
>>> med = sp.bayes_bootstrap(
... data=df, statistic=lambda d, w: np.average(d["y"], weights=w),
... draws=500, seed=1)
References
rubin1981bayesian
bart_causal ¶
bart_causal(data: DataFrame, y: str, treat: str, covariates: Sequence[str], *, method: str = 'bcf', estimand: str = 'ATE', propensity: Any = 'logit', draws: int = 1000, burnin: int = 1000, thin: int = 1, chains: int = 4, n_trees_control: Optional[int] = None, n_trees_moderate: int = 50, sd_control: float = 2.0, sd_moderate: float = 1.0, numcut: int = 100, min_leaf: int = 5, seed: Optional[int] = None, level: float = 0.95, cut_grid: str = 'auto') -> BayesCausalResult
Bayesian tree models for a binary treatment: BCF or BART.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
DataFrame
|
|
required |
y
|
str
|
Continuous outcome. |
required |
treat
|
str
|
0/1 treatment. |
required |
covariates
|
sequence of str
|
Numeric confounders / moderators. |
required |
method
|
('bcf', 'bart')
|
|
'bcf'
|
estimand
|
('ATE', 'ATT', 'ATC')
|
The sample average of the individual effects over all units, the treated or the controls. |
'ATE'
|
propensity
|
('logit', 'none')
|
The propensity score used as a covariate of the prognostic forest
( |
'logit'
|
draws
|
int
|
Posterior draws kept per chain, burn-in sweeps, thinning, and independent chains (each with its own seed; R-hat needs >= 2). |
1000
|
burnin
|
int
|
Posterior draws kept per chain, burn-in sweeps, thinning, and independent chains (each with its own seed; R-hat needs >= 2). |
1000
|
thin
|
int
|
Posterior draws kept per chain, burn-in sweeps, thinning, and independent chains (each with its own seed; R-hat needs >= 2). |
1000
|
chains
|
int
|
Posterior draws kept per chain, burn-in sweeps, thinning, and independent chains (each with its own seed; R-hat needs >= 2). |
1000
|
n_trees_control
|
int
|
Trees in the prognostic ( |
None, 50
|
n_trees_moderate
|
int
|
Trees in the prognostic ( |
None, 50
|
sd_control
|
float
|
Prior medians of the scale of the prognostic and effect functions,
in standard deviations of |
2.0, 1.0
|
sd_moderate
|
float
|
Prior medians of the scale of the prognostic and effect functions,
in standard deviations of |
2.0, 1.0
|
numcut
|
int
|
Maximum number of cut points per covariate. |
100
|
min_leaf
|
int
|
Smallest number of observations in a leaf. |
5
|
seed
|
int
|
|
None
|
level
|
float
|
|
0.95
|
cut_grid
|
('auto', 'quantile', 'uniform')
|
Candidate split points of a covariate with more than |
'auto'
|
Returns:
| Type | Description |
|---|---|
BayesCausalResult
|
|
Notes
Evidence. The samplers are checked against known truth on simulated
designs (tests/test_bart_causal.py: posterior intervals cover the
true ATE and the BCF effect forest recovers effect heterogeneity), and
against R bcf 2.0.2 / bartCause 1.0.10 by seed replication on a
fixed data set: several seeds on each side, comparing the
distributions of the posterior means. That is a stochastic screen
(CLAUDE.md §5.1, tier S), not parity; the two sides implement the same
model with different samplers.
sp.bcf (the random-forest / gradient-boosting approximation) is
unchanged; this function is the posterior-sampling BCF.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(2)
>>> n = 200
>>> x = rng.normal(size=n)
>>> z = rng.binomial(1, 0.5, size=n)
>>> y = x + z * (1 + (x > 0)) + rng.normal(scale=0.5, size=n)
>>> df = pd.DataFrame({'y': y, 'z': z, 'x': x})
>>> r = sp.bart_causal(df, 'y', 'z', ['x'], method='bart', draws=150,
... burnin=150, chains=1, seed=3)
>>> r.cate.shape[0]
200
References
[@hahn2020bayesian]
bayes_r2 ¶
bayes_r2(fit: Any, kind: str = 'model', level: float = 0.95) -> BayesR2Result
Bayesian R-squared: the share of predictive variance explained.
For each posterior draw, Var(fit) / (Var(fit) + Var(res)) with
Var(fit) the variance over observations of the expected outcome.
Unlike the classical ratio it cannot exceed one when the prior or a
small sample pulls the fit away from least squares, and it comes
with a posterior distribution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fit
|
fitted model
|
A result of |
required |
kind
|
('model', 'residual')
|
|
'model'
|
level
|
float
|
Mass of the reported interval. |
0.95
|
Returns:
| Type | Description |
|---|---|
BayesR2Result
|
|
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x": rng.normal(size=100)})
>>> df["y"] = df["x"] + rng.normal(size=100)
>>> fit = sp.bayes_regress("y ~ x", df, draws=1000, burnin=300, seed=1)
>>> out = sp.bayes_r2(fit)
>>> out.draws.shape
(1000,)
References
gelman2019rsquared
loo_r2 ¶
loo_r2(fit: Any, loo_result: Optional[LOOResult] = None, n_boot: int = 4000, seed: Optional[int] = None, level: float = 0.95) -> BayesR2Result
Leave-one-out R-squared.
1 - Var(y - yhat_loo) / Var(y), with yhat_loo the prediction
of each observation from a posterior that did not see it
(:func:loo_predict). It estimates the share of variance the model
would explain in new data, so, unlike :func:bayes_r2, it goes down
when a regressor adds noise and can be negative.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fit
|
fitted model
|
A result of |
required |
loo_result
|
LOOResult
|
The output of |
None
|
n_boot
|
int
|
Bayesian bootstrap draws (Dirichlet weights on the observations) behind the interval. They describe sampling uncertainty in the two variances, not posterior uncertainty. |
4000
|
seed
|
int
|
|
None
|
level
|
float
|
|
0.95
|
Returns:
| Type | Description |
|---|---|
BayesR2Result
|
|
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x": rng.normal(size=100)})
>>> df["y"] = df["x"] + rng.normal(size=100)
>>> fit = sp.bayes_regress("y ~ x", df, draws=1000, burnin=300, seed=1)
>>> bool(sp.loo_r2(fit, seed=1).estimate < sp.bayes_r2(fit).estimate + 0.05)
True
References
gelman2019rsquared, vehtari2017practical
mad_sd ¶
Median absolute deviation scaled to a standard deviation.
1.4826 * median(|x - median(x)|): equal to the standard deviation
for a normal sample and insensitive to the tails. Next to the median
it is the robust summary of a set of simulation draws used throughout
Gelman, Hill and Vehtari (2020).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
array - like or DataFrame
|
|
required |
axis
|
int or None
|
Axis to reduce; |
0
|
Returns:
| Type | Description |
|---|---|
(float, ndarray or Series)
|
|
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> round(float(sp.mad_sd(np.array([1.0, 2.0, 3.0, 4.0, 100.0]))), 4)
1.4826
References
gelman2020regression
ppc ¶
ppc(fit: Any, stat: Union[str, Callable[[ndarray], float]] = 'mean', draws: Optional[int] = 1000, seed: Optional[int] = None) -> PPCResult
Posterior predictive check of a fitted Bayesian regression.
Simulates replicated data sets from the fitted model, one per posterior draw, and compares a statistic of each with the same statistic of the observed data. A model that cannot produce data looking like the data it was fitted to is wrong in that respect, whatever its coefficients say.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fit
|
fitted model
|
A result of |
required |
stat
|
str or callable
|
|
'mean'
|
draws
|
int
|
Number of replicated data sets (posterior draws used, evenly
spaced). |
1000
|
seed
|
int
|
|
None
|
Returns:
| Type | Description |
|---|---|
PPCResult
|
|
Notes
Choose statistics the model does not fit directly. The mean of a Gaussian regression with an intercept is reproduced by construction and its check says nothing; the minimum, the maximum, the share of zeros or the skewness can fail. The p-value is a description of the discrepancy, not a calibrated test: it uses the data twice and is conservative.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x": rng.normal(size=200)})
>>> df["y"] = rng.negative_binomial(1, 0.2, size=200)
>>> fit = sp.bayes_regress("y ~ x", df, model="poisson", draws=1000,
... burnin=300, seed=1)
>>> bool(sp.ppc(fit, stat="prop_zero", seed=1).p_value < 0.05)
True
References
gabry2019visualization, gelman2020regression
bayes_factor ¶
bayes_factor(*models: Any, names: Optional[Sequence[str]] = None, prior_probs: Optional[Sequence[float]] = None, method: Optional[str] = None) -> BayesFactorResult
Bayes factors and posterior model probabilities.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*models
|
Any
|
Two or more fitted models that have a
|
()
|
names
|
list of str
|
Labels for the models. |
None
|
prior_probs
|
list of float
|
Prior model probabilities; equal by default. |
None
|
method
|
str
|
Passed to each model's |
None
|
Returns:
| Type | Description |
|---|---|
BayesFactorResult
|
|
Notes
Bayes factors require proper priors and are sensitive to them: making the prior of an extra coefficient more diffuse favours the smaller model without bound (Lindley's paradox). They compare models for the same outcome on the same observations; the function refuses fitted models with different numbers of observations.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x": rng.normal(size=100), "z": rng.normal(size=100)})
>>> df["y"] = 1 + df["x"] + rng.normal(size=100)
>>> m1 = sp.bayes_regress("y ~ x", df, model="conjugate", seed=1)
>>> m2 = sp.bayes_regress("y ~ x + z", df, model="conjugate", seed=1)
>>> bool(sp.bayes_factor(m1, m2).log_bf > 0)
True
References
kass1995bayes
savage_dickey ¶
Savage-Dickey density ratio for a point restriction.
The Bayes factor of the restriction param = value against the
fitted (unrestricted) model equals the posterior density at
value divided by the prior density there (Dickey 1971), provided
the prior of the other parameters under the restriction is their
conditional prior given param = value. With the independent
priors of :func:statspai.bayes_regress that holds when the prior
covariance is diagonal. Under model='conjugate' the coefficients'
prior is scaled by sigma2, so conditioning on param = value
also updates sigma2: the ratio is then the Bayes factor against
the restricted model whose prior is
sigma2 ~ InvGamma((alpha0 + 1) / 2, (delta0 + (value - b0)^2 / B0) / 2),
not InvGamma(alpha0 / 2, delta0 / 2).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
BayesRegressResult
|
|
required |
param
|
str
|
A coefficient of the model. |
required |
value
|
float
|
|
0
|
Returns:
| Type | Description |
|---|---|
dict
|
|
Notes
The posterior density is exact for the conjugate model (Student-t), a Rao-Blackwell average of the full conditionals for the normal and probit models, and a kernel estimate from the draws otherwise. A kernel estimate is unreliable far in the tail; the function then reports the density of the normal approximation and says so.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x": rng.normal(size=200), "z": rng.normal(size=200)})
>>> df["y"] = 1 + df["x"] + rng.normal(size=200)
>>> fit = sp.bayes_regress("y ~ x + z", df, model="conjugate", seed=1)
>>> bool(sp.savage_dickey(fit, "z")["bf01"] > 1)
True
References
dickey1971weighted, verdinelli1995computing
kfold ¶
kfold(fit: Any, k: int = 10, folds: Optional[Any] = None, seed: Optional[int] = None, refit: Optional[Callable[[DataFrame], Any]] = None, data: Optional[DataFrame] = None) -> LOOResult
K-fold cross-validation of a Bayesian model by refitting.
Holds out each fold in turn, refits on the rest, and scores every
held-out observation by its log predictive density averaged over the
posterior draws of the fit that did not see it. Slower than
:func:loo and free of its importance-sampling approximation: the
choice when Pareto shapes are high, and the only valid one when
whole groups must be left out together.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fit
|
fitted model
|
A result of |
required |
k
|
int
|
|
10
|
folds
|
array-like of int
|
Fold of each observation, e.g. from :func: |
None
|
seed
|
int
|
Seed of the random fold assignment. |
None
|
refit
|
callable
|
|
None
|
data
|
DataFrame
|
The estimation data, when |
None
|
Returns:
| Type | Description |
|---|---|
LOOResult
|
With |
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x": rng.normal(size=60)})
>>> df["y"] = 1 + 2 * df["x"] + rng.normal(size=60)
>>> fit = sp.bayes_regress("y ~ x", df, draws=500, burnin=200, seed=1)
>>> out = sp.kfold(fit, k=5, seed=1)
>>> out.kind
'kfold'
References
vehtari2017practical
kfold_split ¶
kfold_split(n: int, k: int = 10, seed: Optional[int] = None, groups: Optional[Any] = None, stratify: Optional[Any] = None) -> ndarray
Assign n observations to k folds of near-equal size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
|
required |
k
|
int
|
|
10
|
seed
|
int
|
|
None
|
groups
|
array - like
|
Keep the observations of a group in one fold (clustered data). |
None
|
stratify
|
array - like
|
Spread the levels of this variable evenly over folds. |
None
|
Returns:
| Type | Description |
|---|---|
ndarray of int
|
Fold of each observation, |
Examples:
loo ¶
loo(x: Any, r_eff: Any = None, save_weights: bool = True) -> LOOResult
Leave-one-out cross-validation by Pareto smoothed importance sampling.
Estimates the expected log predictive density for new data,
elpd = sum_i log p(y_i | y_{-i}), from one fit: the posterior
draws are reweighted to stand in for the posterior without
observation i. The Pareto shape of each observation's weights
says whether that reweighting can be trusted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
fitted model or array, shape (draws, observations)
|
A result with a |
required |
r_eff
|
float or array
|
Relative efficiency of the draws by observation. For a fitted model it is computed from the model's chains; for a bare matrix it defaults to 1 (independent draws). |
None
|
save_weights
|
bool
|
Keep the smoothed log weights on the result, for
:func: |
True
|
Returns:
| Type | Description |
|---|---|
LOOResult
|
|
Notes
p is the effective number of parameters, the difference between
the within-sample log predictive density and elpd. A value far
above the number of parameters in the model, or many high Pareto
shapes, points to a misspecified model or a few very influential
observations; :func:kfold is the remedy that always works.
The reliability threshold on the Pareto shape depends on the number
of draws (see :func:k_threshold). The fixed 0.7 of older software
and of Vehtari, Gelman and Gabry (2017) is its limit for long chains.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x": rng.normal(size=80)})
>>> df["y"] = 1 + 2 * df["x"] + rng.normal(size=80)
>>> fit = sp.bayes_regress("y ~ x", df, draws=1000, burnin=300, seed=1)
>>> out = sp.loo(fit)
>>> bool(1.5 < out.p < 5)
True
References
vehtari2017practical, vehtari2024pareto
loo_compare ¶
Compare models on expected log predictive density.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*models
|
LOOResult, or a single dict of them
|
Results of :func: |
()
|
names
|
list of str
|
Labels; |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
One row per model, best first. |
Notes
A difference of less than about 4 is small whatever its standard
error; for larger ones compare elpd_diff with se_diff. The
normal approximation behind se_diff is itself unreliable with
fewer than about 100 observations or when the models are nearly
identical.
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> y, mu = rng.normal(size=30), rng.normal(0, 0.2, size=(500, 1))
>>> a = -0.5 * (y - mu) ** 2
>>> out = sp.loo_compare(sp.loo(a), sp.loo(a - 0.1), names=["a", "b"])
>>> out.index[0]
'a'
References
vehtari2017practical
loo_predict ¶
loo_predict(fit: Any, loo_result: Optional[LOOResult] = None, what: str = 'mean', draws: Optional[Any] = None) -> ndarray
Leave-one-out prediction of each observation from the other ones.
The posterior expectation of E[y_i | x_i] reweighted to the
posterior that did not see observation i. Unlike fitted values,
these do not benefit from having been fitted to the point they
predict, so residuals from them are honest.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fit
|
fitted model
|
A result with |
required |
loo_result
|
LOOResult
|
The output of |
None
|
what
|
('mean', 'linear')
|
Scale of the prediction: the expected outcome or the linear
index. |
'mean'
|
draws
|
(array, shape(draws, observations))
|
Predict this quantity instead, e.g. a matrix of your own posterior functionals. |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
One prediction per observation. |
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x": rng.normal(size=60)})
>>> df["y"] = 1 + 2 * df["x"] + rng.normal(size=60)
>>> fit = sp.bayes_regress("y ~ x", df, draws=500, burnin=200, seed=1)
>>> sp.loo_predict(fit).shape
(60,)
References
vehtari2017practical
psis ¶
psis(log_ratios: Any, r_eff: Any = None) -> PSISResult
Pareto smoothed importance sampling.
Stabilises importance weights by replacing the largest ones with the
expected order statistics of a generalized Pareto distribution fitted
to the upper tail. The fitted shape k is also the diagnostic:
it measures how heavy the tail of the ratios is, and so how many
draws a reliable estimate needs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log_ratios
|
(array, shape(draws) or (draws, observations))
|
Log importance ratios. For leave-one-out cross-validation these are minus the pointwise log-likelihoods. |
required |
r_eff
|
float or array
|
Relative efficiency of the draws (effective sample size divided
by the number of draws) for each column. Default 1, which is
right for independent draws and optimistic for a slowly mixing
chain. It sets the tail length and scales |
None
|
Returns:
| Type | Description |
|---|---|
PSISResult
|
|
Notes
The tail has ceil(min(0.2 S, 3 sqrt(S / r_eff))) draws. Smoothed
weights are truncated at the largest raw weight. A column whose tail
has fewer than five distinct values is left unsmoothed and gets
k = inf.
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> out = sp.psis(rng.normal(size=(1000, 3)))
>>> out.log_weights.shape
(1000, 3)
>>> bool(np.allclose(np.exp(out.log_weights).sum(axis=0), 1.0))
True
References
vehtari2024pareto, zhang2009new
waic ¶
waic(x: Any) -> LOOResult
Widely applicable information criterion.
elpd_waic = sum_i [log mean_s p(y_i | theta_s) - var_s log p(y_i |
theta_s)]: the within-sample log predictive density minus the
posterior variance of each pointwise log-likelihood (Watanabe 2010).
Asymptotically equal to leave-one-out cross-validation; in finite
samples :func:loo is more robust and carries its own diagnostic,
so prefer it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
fitted model or array, shape (draws, observations)
|
|
required |
Returns:
| Type | Description |
|---|---|
LOOResult
|
With |
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> y, mu = rng.normal(size=20), rng.normal(0, 0.2, size=(500, 1))
>>> ll = -0.5 * (y - mu) ** 2
>>> round(sp.waic(ll).ic, 1) == round(-2 * sp.waic(ll).elpd, 1)
True
References
watanabe2010asymptotic, vehtari2017practical
gelman_rubin ¶
gelman_rubin(chains: Any, confidence: float = 0.95, split: bool = False, autoburnin: bool = False) -> MCMCDiagnostic
Gelman and Rubin (1992) potential scale reduction factor.
Compares the variance between several chains started from dispersed
values with the variance within them. A factor near 1 says the chains
have mixed; the conventional alarm is above 1.1 (1.01 for the split
version). Point estimate and upper confidence limit as in R
coda::gelman.diag (with the Brooks and Gelman 1998 degrees of
freedom correction), plus the multivariate factor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chains
|
list of chains, array (chains x draws x parameters), or a fit
|
At least two chains of equal length. A fitted model that ran
several chains is read chain by chain (its |
required |
confidence
|
float
|
Coverage of the upper limit. |
0.95
|
split
|
bool
|
Split every chain in two before computing the factor, which also detects a single chain that drifts. Use this when only one chain is available. |
False
|
autoburnin
|
bool
|
Discard the first half of every chain first. |
False
|
Returns:
| Type | Description |
|---|---|
MCMCDiagnostic
|
|
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(7)
>>> out = sp.gelman_rubin([rng.normal(size=(1000, 2)) for _ in range(3)])
>>> bool((out.table["psrf"] < 1.05).all())
True
References
gelman1992inference, brooks1998general
geweke_diag ¶
geweke_diag(chain: ChainLike, frac1: float = 0.1, frac2: float = 0.5) -> MCMCDiagnostic
Geweke (1992) convergence diagnostic.
Compares the mean of the first frac1 of the chain with the mean of
the last frac2. If the chain is stationary the two means are equal
and the standardised difference is asymptotically standard normal.
Standard errors use the spectral density at zero of each window, so
autocorrelation is accounted for. Same statistic as R
coda::geweke.diag.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chain
|
array-like, DataFrame or fitted Bayesian result
|
|
required |
frac1
|
float
|
Fraction of the chain in the early window. |
0.1
|
frac2
|
float
|
Fraction of the chain in the late window. |
0.5
|
Returns:
| Type | Description |
|---|---|
MCMCDiagnostic
|
|
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> x = np.random.default_rng(4).normal(size=3000)
>>> out = sp.geweke_diag(x)
>>> bool(abs(out.table["z"].iloc[0]) < 4)
True
References
geweke1992evaluating
heidel_diag ¶
heidel_diag(chain: ChainLike, eps: float = 0.1, pvalue: float = 0.05) -> MCMCDiagnostic
Heidelberger and Welch (1983) stationarity and half-width tests.
Stationarity: a Cramer-von Mises test that the chain is a draw from a
stationary process, applied to the whole chain and then, if it
rejects, after discarding the first 10%, 20%, ... up to 50%. The
half-width test then asks whether the part of the chain retained
estimates the mean precisely enough: it passes when the half-width of
the 95% interval for the mean, relative to the mean, is below eps.
Same procedure as R coda::heidel.diag.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chain
|
array-like, DataFrame or fitted Bayesian result
|
|
required |
eps
|
float
|
Target ratio of half-width to mean. |
0.1
|
pvalue
|
float
|
Significance level of the stationarity test. |
0.05
|
Returns:
| Type | Description |
|---|---|
MCMCDiagnostic
|
|
Notes
The half-width test is relative to the mean, so it is uninformative for a parameter whose posterior mean is near zero.
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> x = 5 + np.random.default_rng(6).normal(size=4000)
>>> out = sp.heidel_diag(x)
>>> bool(out.table["stationary"].iloc[0])
True
References
heidelberger1983simulation
hpd_interval ¶
Highest posterior density interval of each parameter.
The shortest interval that holds prob of the draws (Chen and Shao
1999). For a unimodal posterior it is the HPD region; for a symmetric
one it coincides with the equal-tailed interval. Same rule as R
coda::HPDinterval.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chain
|
array-like, DataFrame or fitted Bayesian result
|
|
required |
prob
|
float
|
Posterior mass inside the interval. |
0.95
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
Columns |
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> x = np.random.default_rng(2).exponential(size=20000)
>>> iv = sp.hpd_interval(x, prob=0.9)
>>> bool(iv["lower"].iloc[0] < 0.01)
True
References
chen1999monte
mcmc_ess ¶
Effective sample size of each parameter of an MCMC chain.
The number of independent draws that would estimate the posterior mean
as precisely as the autocorrelated chain does: n var(x) / S(0),
with S(0) the spectral density at zero of an AR model fitted to
the chain (order by AIC). Same definition as R coda::effectiveSize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chain
|
array-like, DataFrame or fitted Bayesian result
|
Draws, one row per iteration and one column per parameter. |
required |
Returns:
| Type | Description |
|---|---|
Series
|
Effective sample size by parameter. |
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> x = np.random.default_rng(1).normal(size=5000)
>>> bool(4000 < sp.mcmc_ess(x).iloc[0] < 6000)
True
References
plummer2006coda
mcmc_summary ¶
mcmc_summary(chain: ChainLike, quantiles: Sequence[float] = (0.025, 0.25, 0.5, 0.75, 0.975), hpd: Optional[float] = None) -> DataFrame
Posterior summary of an MCMC chain.
Mean, standard deviation, the naive standard error of the mean
(sd / sqrt(n), valid only for independent draws), the time-series
standard error (which accounts for autocorrelation), the effective
sample size and quantiles. The first four columns and the quantiles are
those of R summary(coda::mcmc(x)).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chain
|
array-like, DataFrame or fitted Bayesian result
|
Draws, one row per iteration and one column per parameter. |
required |
quantiles
|
sequence of float
|
Posterior quantiles to report. |
(0.025, 0.25, 0.5, 0.75, 0.975)
|
hpd
|
float
|
If given, also report the highest posterior density interval with
this mass (columns |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
One row per parameter. |
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> draws = np.random.default_rng(3).normal(size=(4000, 2))
>>> list(sp.mcmc_summary(draws).columns[:5])
['mean', 'sd', 'naive_se', 'ts_se', 'ess']
References
plummer2006coda
raftery_diag ¶
raftery_diag(chain: ChainLike, q: float = 0.025, r: float = 0.005, s: float = 0.95, converge_eps: float = 0.001) -> MCMCDiagnostic
Raftery and Lewis (1992) run-length diagnostic.
How long a chain must be to estimate the posterior quantile q to
within +/- r with probability s. The chain is reduced to the
indicator of being below its q quantile, thinned until that
indicator behaves as a first-order Markov chain, and the two-state
chain's transition probabilities give the burn-in M and the total
run N (burn-in included). The dependence factor I = N / Nmin
compares the total with the length an independent sample would need;
values above 5 indicate strong autocorrelation. Same output as R
coda::raftery.diag, which prints I to three significant
digits.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chain
|
array-like, DataFrame or fitted Bayesian result
|
|
required |
q
|
float
|
Quantile to be estimated. |
0.025
|
r
|
float
|
Desired margin of error of the estimated cumulative probability. |
0.005
|
s
|
float
|
Probability of attaining that margin. |
0.95
|
converge_eps
|
float
|
Precision required for the estimated time to convergence. |
0.001
|
Returns:
| Type | Description |
|---|---|
MCMCDiagnostic
|
|
Raises:
| Type | Description |
|---|---|
DataInsufficient
|
If the chain is shorter than |
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> x = np.random.default_rng(5).normal(size=5000)
>>> out = sp.raftery_diag(x)
>>> int(out.table["n_min"].iloc[0])
3746
References
raftery1992how
gp_regress ¶
gp_regress(formula: str, data: DataFrame, kernel: str = 'rbf', ard: bool = True, length_scale: Any = None, signal_var: Optional[float] = None, noise_var: Optional[float] = None, mean: Optional[float] = None, optimize_hyper: bool = True, restarts: int = 4, seed: Optional[int] = None, level: float = 0.95, interpolate: bool = False, likelihood: str = 'reml') -> GPResult
Gaussian process regression.
A nonparametric regression with honest uncertainty bands: the regression function has a Gaussian process prior, and its posterior at any point is normal with a closed-form mean and variance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
formula
|
str
|
|
required |
data
|
DataFrame
|
|
required |
kernel
|
('rbf', 'matern52', 'matern32')
|
|
'rbf'
|
ard
|
bool
|
One length scale per regressor. With |
True
|
length_scale
|
optional
|
Starting values, or the values used when |
None
|
signal_var
|
optional
|
Starting values, or the values used when |
None
|
noise_var
|
optional
|
Starting values, or the values used when |
None
|
mean
|
float
|
A known constant mean. By default the constant is estimated with a flat prior and its uncertainty enters the bands. |
None
|
optimize_hyper
|
bool
|
Maximise the marginal likelihood over the hyperparameters. |
True
|
restarts
|
int
|
Additional random starting points; the marginal likelihood can have several local maxima. |
4
|
seed
|
int
|
For the random starting points. |
None
|
level
|
float
|
|
0.95
|
interpolate
|
bool
|
The outcome has no noise (the output of a deterministic computer model or simulator with fixed random numbers): the fit passes through the data and the bands shrink to zero at the sample points. The noise variance is fixed at a negligible value that keeps the kernel matrix invertible instead of being estimated. |
False
|
likelihood
|
('reml', 'ml')
|
How the constant mean is treated when the hyperparameters are
estimated. |
'reml'
|
Returns:
| Type | Description |
|---|---|
GPResult
|
|
Notes
The cost grows with the cube of the sample size; a few thousand observations are the practical limit.
The bands condition on the estimated hyperparameters and do not carry their uncertainty, so they are somewhat too narrow in small samples.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> x = np.sort(rng.uniform(0, 6, 80))
>>> df = pd.DataFrame({"x": x, "y": np.sin(x) + 0.2 * rng.normal(size=80)})
>>> fit = sp.gp_regress("y ~ x", df)
>>> list(fit.params.index)
['length_scale[x]', 'signal_var', 'noise_var', 'mean']
>>> band = fit.predict(pd.DataFrame({"x": [1.5]}))
>>> bool(band["lower"].iloc[0] < np.sin(1.5) < band["upper"].iloc[0])
True
References
rasmussen2005gaussian
bayes_ivreg ¶
bayes_ivreg(formula: str, data: DataFrame, prior_mean: Any = 0.0, prior_var: Any = 1000.0, first_stage_prior_var: Any = 1000.0, sigma_prior: Optional[Tuple[float, Any]] = None, draws: int = 10000, burnin: int = 2000, thin: int = 1, chains: int = 1, seed: Optional[int] = None, level: float = 0.95) -> BayesRegressResult
Bayesian linear instrumental variables by Gibbs sampling.
The joint model of the first stage and the structural equation,
d = z' delta + v, y = beta d + x' gamma + eps,
(v, eps) ~ N(0, Sigma),
with z the exogenous regressors and the instruments. The posterior
of beta carries the uncertainty of the first stage and of the
error covariance. With a strong instrument and weak priors it is
close to normal around 2SLS; with a weak instrument it is wide and
skewed, and it then depends on the priors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
formula
|
str
|
|
required |
data
|
DataFrame
|
|
required |
prior_mean
|
Any
|
Normal prior of the structural coefficients, in the order
exogenous terms then the endogenous regressor (scalar, vector or
matrix as in :func: |
0.0
|
prior_var
|
Any
|
Normal prior of the structural coefficients, in the order
exogenous terms then the endogenous regressor (scalar, vector or
matrix as in :func: |
0.0
|
first_stage_prior_var
|
(float, array or matrix)
|
Prior variance of the first-stage coefficients (mean zero). |
1000
|
sigma_prior
|
(df, scale)
|
|
None
|
draws
|
int
|
As in :func: |
10000
|
burnin
|
int
|
As in :func: |
10000
|
thin
|
int
|
As in :func: |
10000
|
chains
|
int
|
As in :func: |
10000
|
seed
|
int
|
As in :func: |
10000
|
level
|
int
|
As in :func: |
10000
|
Returns:
| Type | Description |
|---|---|
BayesRegressResult
|
|
Notes
A statspai.AssumptionWarning is raised when the first-stage F is
below 10. The posterior is still the correct posterior of this model,
but it is far from normal, its mean may not exist, and it is
sensitive to the priors; report the median and the interval, and how
they move with prior_var.
fit.prob("rho > 0") is the posterior probability of positive
selection; an interval for rho that excludes zero is the Bayesian
counterpart of a Hausman test.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> n = 400
>>> z, v = rng.normal(size=n), rng.normal(size=n)
>>> d = z + v
>>> y = 1 + 0.5 * d + 0.8 * v + 0.6 * rng.normal(size=n)
>>> df = pd.DataFrame({"y": y, "d": d, "z": z})
>>> fit = sp.bayes_ivreg("y ~ (d ~ z)", df, draws=2000, burnin=500, seed=1)
>>> list(fit.params.index)[:3]
['Intercept', 'd', 'rho']
>>> bool(fit.prob("rho > 0") > 0.99)
True
References
rossi2005bayesian, ramirezhassan2026introduction
bayes_mixed ¶
bayes_mixed(formula: str, data: DataFrame, group: str, random: Optional[Sequence[str]] = None, family: str = 'normal', random_intercept: bool = True, prior_mean: Any = 0.0, prior_var: Any = None, sigma2_prior: Tuple[float, float] = (0.001, 0.001), re_prior: Optional[Tuple[float, Any]] = None, draws: int = 10000, burnin: int = 2000, thin: int = 1, chains: int = 1, seed: Optional[int] = None, level: float = 0.95) -> BayesMixedResult
Bayesian hierarchical model for longitudinal / panel data.
y_it = x_it' beta + w_it' b_i + e_it, where the random effects of
group i are b_i ~ N(0, D). The fixed effects have a normal
prior and D an inverse-Wishart prior. The random effects are
assumed independent of the regressors (a random-effects model, not a
fixed-effects one).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
formula
|
str
|
Fixed part, |
required |
data
|
DataFrame
|
|
required |
group
|
str
|
Column identifying the groups (units of the panel). |
required |
random
|
list of str
|
Columns with a random slope. They should also appear in the formula, so that the random slope is a deviation from the fixed one. |
None
|
family
|
('normal', 'logit', 'poisson')
|
|
'normal'
|
random_intercept
|
bool
|
|
True
|
prior_mean
|
Any
|
Normal prior of the fixed effects, as in
:func: |
0.0
|
prior_var
|
Any
|
Normal prior of the fixed effects, as in
:func: |
0.0
|
sigma2_prior
|
(alpha0, delta0)
|
|
(0.001, 0.001)
|
re_prior
|
(df, scale)
|
|
None
|
draws
|
int
|
As in :func: |
10000
|
burnin
|
int
|
As in :func: |
10000
|
thin
|
int
|
As in :func: |
10000
|
chains
|
int
|
As in :func: |
10000
|
seed
|
int
|
As in :func: |
10000
|
level
|
int
|
As in :func: |
10000
|
Returns:
| Type | Description |
|---|---|
BayesMixedResult
|
The reported parameters are the fixed effects, |
Notes
A prior on D with a scale far from the variance of the random
effects is informative, and with few groups even a well-scaled prior
matters. The fit measures the prior's share of each variance
component (model_info['re_prior_share']) and warns above 25
percent; set re_prior and report how the results move.
R MCMCpack::MCMChlogit and MCMChpoisson add an
observation-level normal error to the linear index. The models here
do not; they are the ones sp.melogit / sp.mepoisson and Stata
melogit / mepoisson fit by maximum likelihood.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> g = np.repeat(np.arange(40), 5)
>>> df = pd.DataFrame({"id": g, "x": rng.normal(size=200)})
>>> u = rng.normal(size=40)[g]
>>> df["y"] = 1 + 0.5 * df["x"] + u + rng.normal(size=200)
>>> fit = sp.bayes_mixed("y ~ x", df, group="id", draws=1000,
... burnin=300, seed=1)
>>> bool(fit.prob("x > 0") > 0.99)
True
>>> slopes = sp.bayes_mixed("y ~ x", df, group="id", random=["x"],
... draws=1000, burnin=300, seed=1)
>>> list(slopes.re_cov.columns)
['Intercept', 'x']
References
chib1999mcmc, ramirezhassan2026introduction
bayes_mixture ¶
bayes_mixture(formula: str, data: DataFrame, components: Union[int, str] = 2, alpha: float = 1.0, alpha_prior: Optional[Tuple[float, float]] = None, prior_mean: Any = None, prior_scale: Any = None, sigma2_prior: Optional[Tuple[float, float]] = None, max_components: Optional[int] = None, grid: int = 200, draws: int = 5000, burnin: int = 1000, thin: int = 1, chains: int = 1, seed: Optional[int] = None, level: float = 0.95) -> BayesRegressResult
Bayesian mixture of normal regressions, finite or Dirichlet process.
Each observation belongs to an unobserved component with its own
coefficients and error variance. "y ~ 1" gives a mixture of
normals: a flexible density estimate and a model-based clustering.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
formula
|
str
|
|
required |
data
|
DataFrame
|
|
required |
components
|
int or 'dp'
|
Number of components, or |
2
|
alpha
|
float
|
Finite mixture: the weights are |
1
|
alpha_prior
|
(shape, rate)
|
Dirichlet process only: a Gamma prior for the concentration, which is then sampled and reported. |
None
|
prior_mean
|
array
|
Prior mean |
None
|
prior_scale
|
float or matrix
|
|
None
|
sigma2_prior
|
(a0, d0)
|
|
None
|
max_components
|
int
|
Dirichlet process only: cap on the number of occupied clusters
(memory). Default |
None
|
grid
|
int
|
Points of the density estimate ( |
200
|
draws
|
int
|
As in :func: |
5000
|
burnin
|
int
|
As in :func: |
5000
|
thin
|
int
|
As in :func: |
5000
|
chains
|
int
|
As in :func: |
5000
|
seed
|
int
|
As in :func: |
5000
|
level
|
int
|
As in :func: |
5000
|
Returns:
| Type | Description |
|---|---|
BayesRegressResult
|
|
Notes
The default prior depends on the data through the pooled fit. It is
printed in result.prior; pass the three prior arguments to fix it.
Component labels are not identified. Ordering by the first coefficient is a convention that works when components differ in that coefficient; the similarity matrix, the partition and the density do not depend on labels.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> y = np.r_[rng.normal(-2, 0.5, 100), rng.normal(2, 0.5, 100)]
>>> fit = sp.bayes_mixture("y ~ 1", pd.DataFrame({"y": y}),
... draws=300, burnin=200, seed=1)
>>> list(fit.params.index)[:2]
['weight[1]', 'weight[2]']
>>> int(np.unique(fit.model_info["cluster"]).size)
2
References
neal2000markov, escobar1995bayesian
bayes_mnprobit ¶
bayes_mnprobit(formula: str, data: DataFrame, prior_mean: Any = 0.0, prior_var: Any = 100.0, sigma_prior: Optional[Tuple[float, Any]] = None, draws: int = 10000, burnin: int = 2000, thin: int = 1, chains: int = 1, seed: Optional[int] = None, level: float = 0.95) -> BayesRegressResult
Bayesian multinomial probit.
An unordered outcome with three or more categories. Each category has a latent utility that is linear in the regressors with normal errors, correlated across categories, and the category with the largest utility is observed. Unlike the multinomial logit the model does not impose independence of irrelevant alternatives.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
formula
|
str
|
|
required |
data
|
DataFrame
|
|
required |
prior_mean
|
float or array
|
Normal prior of the stacked coefficients on the unrestricted latent scale. |
0 and 100
|
prior_var
|
float or array
|
Normal prior of the stacked coefficients on the unrestricted latent scale. |
0 and 100
|
sigma_prior
|
(df, scale)
|
Inverse-Wishart prior of the covariance of the utility
differences. Default |
None
|
draws
|
int
|
As in :func: |
10000
|
burnin
|
int
|
As in :func: |
10000
|
thin
|
int
|
As in :func: |
10000
|
chains
|
int
|
As in :func: |
10000
|
seed
|
int
|
As in :func: |
10000
|
level
|
int
|
As in :func: |
10000
|
Returns:
| Type | Description |
|---|---|
BayesRegressResult
|
|
Notes
Utilities are identified up to location and scale. Location is fixed by differencing against the base and scale by dividing each draw by the standard deviation of the first difference, the approach of McCulloch and Rossi (1994). The error covariance is weakly identified in most data: expect wide posteriors for it and a slow chain.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> n = 300
>>> x = rng.normal(size=n)
>>> u = np.column_stack([np.zeros(n), 0.8 * x, -0.5 * x]) + rng.normal(size=(n, 3))
>>> df = pd.DataFrame({"x": x, "choice": np.array(["a", "b", "c"])[u.argmax(1)]})
>>> fit = sp.bayes_mnprobit("choice ~ x", df, draws=500, burnin=200, seed=1)
>>> list(fit.params.index)[:4]
['b:Intercept', 'b:x', 'c:Intercept', 'c:x']
References
mcculloch1994exact, rossi2005bayesian
bayes_mvprobit ¶
bayes_mvprobit(formulas: Sequence[str], data: DataFrame, prior_mean: Any = 0.0, prior_var: Any = 100.0, sigma_prior: Optional[Tuple[float, Any]] = None, draws: int = 10000, burnin: int = 2000, thin: int = 1, chains: int = 1, seed: Optional[int] = None, level: float = 0.95) -> BayesRegressResult
Bayesian multivariate probit.
Several binary outcomes, one probit equation each, with errors that are correlated across equations. The correlations say whether the outcomes move together beyond what the regressors explain.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
formulas
|
list of str
|
One formula per binary outcome, |
required |
data
|
DataFrame
|
|
required |
prior_mean
|
float or array
|
Normal prior of the stacked coefficients on the unrestricted latent scale. |
0 and 100
|
prior_var
|
float or array
|
Normal prior of the stacked coefficients on the unrestricted latent scale. |
0 and 100
|
sigma_prior
|
(df, scale)
|
Inverse-Wishart prior of the unrestricted error covariance.
Default |
None
|
draws
|
int
|
As in :func: |
10000
|
burnin
|
int
|
As in :func: |
10000
|
thin
|
int
|
As in :func: |
10000
|
chains
|
int
|
As in :func: |
10000
|
seed
|
int
|
As in :func: |
10000
|
level
|
int
|
As in :func: |
10000
|
Returns:
| Type | Description |
|---|---|
BayesRegressResult
|
|
Notes
The sampler does not restrict the error covariance to a correlation matrix. Coefficients and covariance are drawn on an arbitrary scale and each draw is normalised afterwards, which is the approach of Rossi, Allenby and McCulloch. The prior on the reported quantities is the one implied by the stated prior.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> n = 300
>>> x = rng.normal(size=n)
>>> e = rng.multivariate_normal([0, 0], [[1, 0.6], [0.6, 1]], size=n)
>>> df = pd.DataFrame({"x": x, "y1": (0.5 * x + e[:, 0] > 0) * 1,
... "y2": (-0.5 * x + e[:, 1] > 0) * 1})
>>> fit = sp.bayes_mvprobit(["y1 ~ x", "y2 ~ x"], df, draws=500, burnin=200, seed=1)
>>> list(fit.params.index)
['y1:Intercept', 'y1:x', 'y2:Intercept', 'y2:x', 'corr(y1,y2)']
References
rossi2005bayesian, albert1993bayesian
bayes_regress ¶
bayes_regress(formula: str, data: DataFrame, model: str = 'normal', prior_mean: Any = None, prior_var: Any = None, sigma2_prior: Optional[Tuple[float, float]] = None, draws: int = 10000, burnin: int = 2000, thin: int = 1, chains: int = 1, seed: Optional[int] = None, level: float = 0.95, tune: Optional[float] = None, quantile: float = 0.5, scale: Optional[float] = None, scale_prior: Tuple[float, float] = (0.001, 0.001), lower: Optional[float] = None, upper: Optional[float] = None, dof: float = 5.0, size_prior: Optional[Tuple[float, float]] = None, cut_prior_var: float = 1.0, inference: str = 'mcmc', prior: Optional[str] = None, offset: Any = None, exposure: Optional[str] = None, trials: Optional[str] = None) -> BayesRegressResult
Bayesian regression by MCMC.
Fits one of ten likelihoods with a normal prior on the coefficients and returns the posterior draws with their summary, convergence diagnostics and marginal likelihood.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
formula
|
str
|
|
required |
data
|
DataFrame
|
|
required |
model
|
str
|
|
'normal'
|
prior_mean
|
float or array
|
Prior mean of the coefficients (scalar or one per coefficient,
in the order of the design matrix). 0 when not given; giving it
selects |
None
|
prior_var
|
(float, array or matrix)
|
Prior variance: a scalar, a vector of variances or a full
covariance matrix. Under |
None
|
sigma2_prior
|
(alpha0, delta0)
|
|
None
|
draws
|
int
|
Draws kept per chain. |
10000
|
burnin
|
int
|
Iterations discarded at the start of each chain. |
2000
|
thin
|
int
|
Keep one draw in |
1
|
chains
|
int
|
Number of independent chains. With more than one, chains start from dispersed values and the Gelman-Rubin factor is reported. |
1
|
seed
|
int
|
|
None
|
level
|
float
|
Mass of the credible intervals in the summary. |
0.95
|
tune
|
float
|
Scale of the Metropolis proposal relative to the posterior
curvature. Default |
None
|
quantile
|
float
|
Quantile for |
0.5
|
scale
|
float
|
Fix the scale of the asymmetric Laplace likelihood instead of
estimating it ( |
None
|
scale_prior
|
(n0, s0)
|
|
(0.001, 0.001)
|
lower
|
float
|
Censoring points for |
None
|
upper
|
float
|
Censoring points for |
None
|
dof
|
float
|
Degrees of freedom for |
5
|
size_prior
|
(shape, rate)
|
Gamma prior on the size |
None
|
cut_prior_var
|
float
|
Prior variance of the log distance between consecutive cutpoints of the ordered probit (mean zero). |
1
|
inference
|
('mcmc', 'vb')
|
|
'mcmc'
|
prior
|
('vague', 'weakly_informative')
|
Omitted, it is .. versionchanged:: 1.40.0
Omitted, it was |
'vague'
|
offset
|
str or array
|
A term added to the linear index with coefficient one (logit,
Poisson and negative binomial models): a column of |
None
|
exposure
|
str
|
Column whose logarithm is the offset of a count model. |
None
|
trials
|
str
|
|
None
|
Returns:
| Type | Description |
|---|---|
BayesRegressResult
|
|
Notes
Convergence is checked on every fit: a ConvergenceWarning is
raised when the smallest effective sample size is below 100 or the
split potential scale reduction factor exceeds 1.05.
result.diagnostics() runs the Geweke, Heidelberger-Welch and
Raftery-Lewis diagnostics.
The result carries the predictive side of the model:
posterior_linpred (linear index), posterior_epred (expected
outcome) and posterior_predict (new outcomes, with observation
noise) return one row per draw, and log_lik the pointwise
log-likelihoods that :func:statspai.loo, :func:statspai.waic and
:func:statspai.kfold consume. See also :func:statspai.ppc,
:func:statspai.bayes_r2 and :func:statspai.loo_r2.
The ordered probit reports the slopes and J - 1 cutpoints and has
no intercept, as sp.oprobit. The prior N(prior_mean, prior_var)
applies to the slopes and to minus the first cutpoint.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x": rng.normal(size=300)})
>>> df["y"] = (0.3 + df["x"] + rng.normal(size=300) > 0).astype(int)
>>> fit = sp.bayes_regress("y ~ x", df, model="probit", draws=2000,
... burnin=500, seed=1,
... prior="weakly_informative")
>>> bool(fit.prob("x > 0") > 0.99)
True
>>> lml = fit.log_marginal_likelihood()
References
albert1993bayesian, cowles1996accelerating, chib1992bayes, chib1995marginal, gelfand1990sampling, gelfand1994bayesian, geweke1999using, kozumi2011gibbs, ramirezhassan2026introduction, gelman2008weakly, gelman2020regression
bayes_shrink ¶
bayes_shrink(formula: str, data: DataFrame, prior: str = 'lasso', lam: Optional[float] = None, lam_prior: Tuple[float, float] = (1.0, 1.0), spike_sd: float = 0.02, slab_sd: float = 1.0, inclusion: float = 0.5, sigma2_prior: Tuple[float, float] = (0.001, 0.001), standardize: bool = True, draws: int = 10000, burnin: int = 2000, thin: int = 1, seed: Optional[int] = None, level: float = 0.95, global_scale: Optional[float] = None, p0: Optional[float] = None, slab_scale: Optional[float] = None, slab_df: float = 4.0, family: str = 'gaussian') -> BayesRegressResult
Linear regression with a shrinkage or a variable-selection prior.
For many candidate regressors, when the aim is prediction or a
summary of which ones matter. For model probabilities over subsets
see :func:statspai.bma.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
formula
|
str
|
|
required |
data
|
DataFrame
|
|
required |
prior
|
('lasso', 'ssvs', 'horseshoe')
|
|
'lasso'
|
lam
|
float
|
Lasso penalty. When omitted it is estimated, with the prior
|
None
|
lam_prior
|
(shape, rate)
|
|
(1, 1)
|
spike_sd
|
float
|
SSVS: standard deviations of the spike and of the slab, in units
of |
0.02
|
slab_sd
|
float
|
SSVS: standard deviations of the spike and of the slab, in units
of |
0.02
|
inclusion
|
float
|
SSVS: prior probability of the slab. |
0.5
|
sigma2_prior
|
(alpha0, delta0)
|
|
(0.001, 0.001)
|
standardize
|
bool
|
Scale every regressor to unit standard deviation for the prior. Without it the prior treats a regressor in dollars and one in thousands of dollars differently. |
True
|
draws
|
int
|
As in :func: |
10000
|
burnin
|
int
|
As in :func: |
10000
|
thin
|
int
|
As in :func: |
10000
|
seed
|
int
|
As in :func: |
10000
|
level
|
int
|
As in :func: |
10000
|
global_scale
|
float
|
Horseshoe: scale of the half-Cauchy prior on |
None
|
p0
|
float
|
Horseshoe: prior guess of the number of coefficients that are
far from zero. Sets |
None
|
slab_scale
|
float
|
Horseshoe: regularize it (Piironen and Vehtari 2017). The local
scales are replaced by |
None
|
slab_df
|
float
|
Degrees of freedom of the slab. |
4
|
family
|
('gaussian', 'logit')
|
|
'gaussian'
|
Returns:
| Type | Description |
|---|---|
BayesRegressResult
|
|
Notes
The Bayesian lasso shrinks but never sets a coefficient exactly to
zero: its posterior mean is not sparse. Credible intervals after
shrinkage are not confidence intervals for the unshrunk effect; for
inference on one coefficient with many controls use
sp.rlasso_effect or sp.dml.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame(rng.normal(size=(150, 6)), columns=list("abcdef"))
>>> df["y"] = 1 + 2 * df["a"] - df["c"] + rng.normal(size=150)
>>> fit = sp.bayes_shrink("y ~ a + b + c + d + e + f", df, prior="ssvs",
... draws=1000, burnin=300, seed=1)
>>> bool(fit.table.loc["a", "pip"] > 0.95)
True
>>> las = sp.bayes_shrink("y ~ a + b + c + d + e + f", df, draws=1000,
... burnin=300, seed=1)
References
park2008bayesian, george1993variable, carvalho2010horseshoe, makalic2016simple, piironen2017sparsity, polson2013bayesian
abc ¶
abc(simulate: Callable[..., Any], observed: Any, prior: Any, method: str = 'rejection', n_sim: int = 100000, tol: Optional[float] = None, quantile: float = 0.01, adjust: str = 'none', scale: Any = 'mad', n_synthetic: int = 100, draws: int = 5000, burnin: int = 1000, names: Optional[Sequence[str]] = None, vectorized: bool = False, seed: Optional[int] = None, level: float = 0.95) -> BayesRegressResult
Approximate Bayesian computation and Bayesian synthetic likelihood.
Posterior inference for a model that can be simulated but has no tractable likelihood. The data enter only through summary statistics.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
simulate
|
callable
|
|
required |
observed
|
array
|
The summary statistics of the data. |
required |
prior
|
list of scipy.stats distributions, or callable
|
One frozen distribution per parameter (independent priors), or
|
required |
method
|
('rejection', 'synthetic')
|
|
'rejection'
|
n_sim
|
int
|
Rejection: number of prior draws. |
100000
|
tol
|
float
|
Rejection: accept a draw when the scaled distance between its
summaries and the observed ones is at most |
None
|
quantile
|
float
|
|
0.01
|
adjust
|
('none', 'linear')
|
Rejection: |
'none'
|
scale
|
('mad', 'sd')
|
Rejection: each summary is divided by its median absolute
deviation (or standard deviation, or the given numbers) over the
simulations before distances are taken. |
'mad'
|
n_synthetic
|
int
|
Synthetic likelihood: simulations per parameter value. |
100
|
draws
|
int
|
Synthetic likelihood: length of the Metropolis-Hastings chain. |
5000
|
burnin
|
int
|
Synthetic likelihood: length of the Metropolis-Hastings chain. |
5000
|
names
|
list of str
|
Parameter names. Default |
None
|
vectorized
|
bool
|
|
False
|
seed
|
int
|
|
None
|
level
|
float
|
|
0.95
|
Returns:
| Type | Description |
|---|---|
BayesRegressResult
|
|
Notes
Rejection ABC targets the posterior given that the summaries fall
within tol of the observed ones, not the posterior given the
data. It equals the posterior given the summaries as tol goes to
zero, and the posterior given the data only if the summaries are
sufficient. Check sensitivity to quantile.
The regression adjustment is exact when the parameters are linear in the summaries with a spread that does not depend on them, and an extrapolation otherwise.
The synthetic likelihood assumes normal summaries. Its posterior
depends on n_synthetic through the noise of the estimated mean
and covariance; a few times the number of summaries is the minimum.
Examples:
>>> import numpy as np
>>> from scipy import stats
>>> import statspai as sp
>>> y = np.random.default_rng(0).normal(1.0, 1.0, size=50)
>>> def simulate(theta, rng):
... return rng.normal(theta[:, 0], 1 / np.sqrt(50))[:, None]
>>> fit = sp.abc(simulate, [y.mean()], [stats.norm(0, 3)], n_sim=20000,
... vectorized=True, seed=1)
>>> bool(abs(fit.params["theta1"] - y.mean()) < 0.1)
True
References
beaumont2002approximate, wood2010statistical
bayes_sur ¶
bayes_sur(formulas: Sequence[str], data: DataFrame, prior_mean: Any = 0.0, prior_var: Any = 1000.0, sigma_prior: Optional[Tuple[float, Any]] = None, draws: int = 10000, burnin: int = 2000, thin: int = 1, chains: int = 1, seed: Optional[int] = None, level: float = 0.95) -> BayesRegressResult
Bayesian seemingly unrelated regressions.
Several linear equations estimated jointly because their errors are correlated. The posterior of each coefficient uses the information in the other equations' errors, as feasible GLS does, and carries the uncertainty of the error covariance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
formulas
|
list of str
|
One formula per equation, |
required |
data
|
DataFrame
|
Rows with a missing value in any equation are dropped from all. |
required |
prior_mean
|
Any
|
Normal prior of the stacked coefficients (equation by equation,
in the order of |
0.0
|
prior_var
|
Any
|
Normal prior of the stacked coefficients (equation by equation,
in the order of |
0.0
|
sigma_prior
|
(df, scale)
|
|
None
|
draws
|
int
|
As in :func: |
10000
|
burnin
|
int
|
As in :func: |
10000
|
thin
|
int
|
As in :func: |
10000
|
chains
|
int
|
As in :func: |
10000
|
seed
|
int
|
As in :func: |
10000
|
level
|
int
|
As in :func: |
10000
|
Returns:
| Type | Description |
|---|---|
BayesRegressResult
|
|
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> n = 200
>>> e = rng.multivariate_normal([0, 0], [[1, 0.7], [0.7, 1]], size=n)
>>> df = pd.DataFrame({"x": rng.normal(size=n), "z": rng.normal(size=n)})
>>> df["y1"] = 1 + 0.5 * df["x"] + e[:, 0]
>>> df["y2"] = -1 + 0.8 * df["z"] + e[:, 1]
>>> fit = sp.bayes_sur(["y1 ~ x", "y2 ~ z"], df, draws=1000, burnin=300, seed=1)
>>> list(fit.params.index)[:4]
['y1:Intercept', 'y1:x', 'y2:Intercept', 'y2:z']
>>> bool(fit.prob("`corr(y1,y2)` > 0") > 0.99)
True
References
zellner1962efficient, rossi2005bayesian
stochvol ¶
stochvol(y: Union[str, ndarray, Series], data: Optional[DataFrame] = None, demean: bool = True, mu_prior: Tuple[float, float] = (0.0, 100.0), phi_prior: Tuple[float, float] = (5.0, 1.5), sigma_prior: float = 1.0, draws: int = 10000, burnin: int = 2000, thin: int = 1, chains: int = 1, seed: Optional[int] = None, level: float = 0.95) -> BayesRegressResult
Stochastic volatility model for a return series.
The log variance h_t of the returns follows a stationary AR(1)
with level mu, persistence phi and volatility of volatility
sigma. Unlike GARCH the variance has its own shock, and the whole
path of volatilities is estimated with its uncertainty.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
(array, Series or str)
|
Returns (not prices), or a column name of |
required |
data
|
DataFrame
|
|
None
|
demean
|
bool
|
Subtract the sample mean first. |
True
|
mu_prior
|
(mean, variance)
|
Normal prior of the level of the log variance. |
(0, 100)
|
phi_prior
|
(a, b)
|
|
(5, 1.5)
|
sigma_prior
|
float
|
|
1
|
draws
|
int
|
As in :func: |
10000
|
burnin
|
int
|
As in :func: |
10000
|
thin
|
int
|
As in :func: |
10000
|
chains
|
int
|
As in :func: |
10000
|
seed
|
int
|
As in :func: |
10000
|
level
|
int
|
As in :func: |
10000
|
Returns:
| Type | Description |
|---|---|
BayesRegressResult
|
|
Notes
The log chi-square error of log y_t^2 is approximated by a
mixture of seven normals; the approximation error is small next to
posterior uncertainty in samples of the usual size. Exact zeros in
y are offset by a small constant before taking logs, and the
result says so.
Examples:
>>> import numpy as np
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> h = np.zeros(300)
>>> for t in range(1, 300):
... h[t] = -1 + 0.95 * (h[t - 1] + 1) + 0.2 * rng.normal()
>>> y = np.exp(h / 2) * rng.normal(size=300)
>>> fit = sp.stochvol(y, draws=500, burnin=300, seed=1)
>>> list(fit.params.index)
['mu', 'phi', 'sigma']
>>> fit.model_info["volatility"].shape
(300, 3)
References
kastner2014ancillarity, ramirezhassan2026introduction
bart ¶
bart(formula: str, data: DataFrame, family: str = 'gaussian', n_trees: int = 200, k: float = 2.0, base: float = 0.95, power: float = 2.0, nu: float = 3.0, q: float = 0.9, numcut: int = 100, max_depth: int = 8, min_leaf: int = 5, draws: int = 1000, burnin: int = 500, thin: int = 1, seed: Optional[int] = None, level: float = 0.95) -> BARTResult
Bayesian additive regression trees.
A flexible regression that finds nonlinearities and interactions on its own and returns posterior bands for the regression function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
formula
|
str
|
|
required |
data
|
DataFrame
|
|
required |
family
|
('gaussian', 'binary')
|
|
'gaussian'
|
n_trees
|
int
|
|
200
|
k
|
float
|
The prior puts the regression function within the range of the
outcome with probability about |
2
|
base
|
float
|
A node at depth |
0.95 and 2
|
power
|
float
|
A node at depth |
0.95 and 2
|
nu
|
float
|
Prior of the error variance: scaled inverse chi-square with
|
3 and 0.9
|
q
|
float
|
Prior of the error variance: scaled inverse chi-square with
|
3 and 0.9
|
numcut
|
int
|
Candidate cutpoints per regressor (midpoints of the observed
values, or quantiles when there are more than |
100
|
max_depth
|
int
|
Hard cap on the depth of a tree. The prior makes deeper trees vanishingly rare. |
8
|
min_leaf
|
int
|
A split is allowed only if both children keep at least this many observations. Without it trees isolate single observations and the error variance is underestimated. |
5
|
draws
|
int
|
|
1000
|
burnin
|
int
|
|
1000
|
thin
|
int
|
|
1000
|
seed
|
int
|
|
None
|
level
|
float
|
|
0.95
|
Returns:
| Type | Description |
|---|---|
BARTResult
|
|
Notes
The sampler uses birth and death moves only. The bands are for the
regression function, not for new observations; add sigma for
those.
BART bands tend to be somewhat narrow where the function changes quickly and at the edge of the data. Treat them as approximate.
Examples:
>>> import numpy as np, pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(0)
>>> df = pd.DataFrame({"x1": rng.uniform(size=300), "x2": rng.uniform(size=300)})
>>> df["y"] = 10 * np.sin(np.pi * df.x1 * df.x2) + rng.normal(size=300)
>>> fit = sp.bart("y ~ x1 + x2", df, n_trees=50, draws=200, burnin=200, seed=1)
>>> truth = 10 * np.sin(np.pi * df.x1 * df.x2)
>>> bool(np.sqrt(np.mean((fit.fitted["mean"] - truth) ** 2)) < 1.0)
True
References
chipman2010bart