neighbayes.models.SDM¶
-
class neighbayes.models.SDM(formula=
None, data=None, y=None, X=None, W=None, priors=None, logdet_method=None, robust=False, w_vars=None, logdet_refit=True, logdet_refit_pad_sd=10.0, logdet_aaa_check=True, logdet_probe_check=True)[source]¶ Bayesian Spatial Durbin Model.
Combines a spatial lag of \(y\) with spatial lags of the regressors \(X\):
\[y = \rho Wy + X\beta + WX\theta + \varepsilon, \quad \varepsilon \sim N(0, \sigma^2 I).\]The sampled coefficient vector stacks the local and lagged-regressor blocks as \([\beta, \theta]\). The likelihood includes the spatial Jacobian \(\log|I - \rho W|\).
- Parameters:¶
- formula : str, optional¶
Wilkinson-style formula, e.g.
"y ~ x1 + x2". Requiresdata. Intercept is included by default; suppress with"y ~ x - 1".- data : pandas.DataFrame or geopandas.GeoDataFrame, optional¶
Data source for formula mode.
- y : array-like, optional¶
Dependent variable of shape
(n,). Required in matrix mode.- X : array-like or pandas.DataFrame, optional¶
Design matrix. Required in matrix mode. DataFrame columns are preserved as feature names.
- W : libpysal.graph.Graph or scipy.sparse matrix¶
Spatial weights of shape
(n, n). Accepts alibpysal.graph.Graphor anyscipy.sparsematrix. The legacylibpysal.weights.Wobject is not accepted; passw.sparseorlibpysal.graph.Graph.from_W(w). Should be row-standardized; aUserWarningis raised otherwise.- priors : dict, optional¶
Override default priors. Supported keys:
rho_lower(float, default -1.0): Lower bound of the Uniform prior on \(\rho\).rho_upper(float, default 1.0): Upper bound of the Uniform prior on \(\rho\).beta_mu(float, default 0.0): Normal prior mean for \([\beta, \theta]\).beta_sigma(float, default 1e6): Normal prior std for \([\beta, \theta]\).sigma2_alpha(float, default 2.0): Shape of the InverseGamma prior on \(\sigma^2\).sigma2_beta(float, defaultVar(y)): Scale of the InverseGamma prior on \(\sigma^2\).nu(float, default 4.0): Fixed Student-t degrees of freedom (only used whenrobust=True).
- logdet_method : str, optional¶
How to compute \(\log|I - \rho W|\).
None(default) auto-selects by size:"eigenvalue"forn <= 500; for500 < n <= 60000,"cheb_cholesky"(exact, sparse Cholesky at Chebyshev nodes) whenWis symmetric else"aaa"(AAA rational approximation);"cheb_stochastic"forn > 60000. Explicit opt-ins:"chebyshev"(Barry-Pace) and"slq"(stochastic Lanczos quadrature).- logdet_refit : bool, default True¶
Rebuild the log-determinant interpolant halfway through warmup, on the range the chains have found rather than the interval implied by the prior. A warmup posterior is typically one to two orders of magnitude narrower than the prior, which needs far fewer interpolation nodes and drives the approximation error over the posterior’s support down to the factorization’s roundoff floor. Applies to
"cheb_cholesky","lu_cheb","aaa"and"chol_aaa"; ignored otherwise.The interpolant is only valid on its interval, so the refit window becomes the sampler’s support. The window is padded by
logdet_refit_pad_sdwarmup standard deviations, recorded inidata.attrs["logdet_refit_window"], and a warning is raised if the retained draws ever reach an edge the refit introduced.- logdet_refit_pad_sd : float, default 10.0¶
Padding for the refit window, in warmup posterior standard deviations. At the default the truncated tail is ~1e-23 under normality, and the padding costs a node or two at most.
- logdet_aaa_check : bool, default True¶
For the AAA methods (
"aaa","chol_aaa"), set the number of exact factorizations from where the posterior lies. Warmup starts on a 14-node fit; halfway through, nodes are added only if the warmup posterior lies closer to a singularity of the Jacobian than four times the distance at which the fit’s poles resolve it. The count used is recorded inidata.attrs["logdet_aaa_nodes"]. Off, or on a path without a warmup midpoint, the count is fixed by the prior interval: 14 nodes within|ρ| ≤ 0.9, 18 otherwise.- logdet_probe_check : bool, default True¶
For
"cheb_stochastic", set the number of Hutchinson probes from where the posterior lies. Warmup starts on 50 probes; halfway through, the probes’ own spread prices the bias they leave in the posterior mean of the spatial parameter, and the pool grows, to at most 200, until that bias is expected to stay under 0.0225 posterior sd. The count used is recorded inidata.attrs["logdet_probes"], with a warning if the cap is reached first. Probes cost setup time only; the cost of each draw does not depend on how many there are.- robust : bool, default False¶
If True, replace the Normal error with Student-t. See Robust regression below.
- w_vars : list of str, optional¶
Names of X columns to spatially lag. By default all non-constant columns are lagged. Pass a subset to restrict which variables receive a spatial lag, e.g.
w_vars=["income", "density"]. SDM requires at least one WX column; if filtering eliminates all of them a ValueError is raised.
Notes
Direct, indirect and total effects of \(X\) on \(y\) incorporate both the local and lagged-X blocks via the spatial multiplier \((I - \rho W)^{-1}\) and are reported by
spatial_effects().Robust regression
When
robust=True, the error distribution is changed from Normal to Student-t:\[\varepsilon \sim t_\nu(0, \sigma^2 I)\]where \(\nu\) is a fixed hyperparameter set by
priors={"nu": value}(default 4, LeSage’srval); larger values approach the Normal. Values must exceed 2 so the variance exists.-
__init__(formula=
None, data=None, y=None, X=None, W=None, priors=None, logdet_method=None, robust=False, w_vars=None, logdet_refit=True, logdet_refit_pad_sd=10.0, logdet_aaa_check=True, logdet_probe_check=True)[source]¶
Methods
__init__([formula, data, y, X, W, priors, ...])fit([draws, tune, chains, target_accept, ...])Draw samples from the posterior.
Return fitted values at posterior mean parameters.
Return residuals
y - fitted_values.Run Bayesian LM specification tests and return a summary table.
spatial_diagnostics_decision([alpha, format])Return a model-selection decision from Bayesian LM test results.
spatial_effects([return_posterior_samples])Compute Bayesian inference for direct, indirect, and total impacts.
summary([var_names])Return posterior summary table.
Attributes
Return the ArviZ InferenceData from the most recent fit.
Return the PyMC model object built for the most recent fit.
-
fit(draws=
2000, tune=1000, chains=4, target_accept=None, random_seed=None, progressbar=True, sampler=None, gibbs_backend='auto', thin=1, n_jobs=-1, idata_kwargs=None, **sample_kwargs)[source]¶ Draw samples from the posterior.
Dispatches to this model’s Gibbs sampler (
sampler="gibbs") or NUTS (sampler="nuts"). WhensamplerisNone(default), Gibbs is used if the model has a registered Gibbs sampler, otherwise NUTS.- Parameters:¶
- draws : int¶
Post-warmup draws, warmup steps, and number of chains.
- tune : int¶
Post-warmup draws, warmup steps, and number of chains.
- chains : int¶
Post-warmup draws, warmup steps, and number of chains.
- target_accept : float, optional¶
Target acceptance rate for NUTS. NUTS-only: passing it with the Gibbs sampler raises
TypeError. Defaults to0.9for NUTS.- random_seed : int, optional¶
Seed for reproducibility.
- progressbar : bool, default True¶
Show progress bar(s) during sampling.
- sampler : {"gibbs", "nuts", None}, default None¶
Sampling method.
Noneauto-selects Gibbs when this model has one, else NUTS.- gibbs_backend : {"auto", "jax", "numpy"}, default "auto"¶
Execution backend for the Gibbs sampler.
"auto"uses JAX when installed and supported by the family, else NumPy. Ignored for NUTS.- thin : int, default 1¶
Keep every
thin-th post-warmup Gibbs draw (Gibbs only).- n_jobs : int, default -1¶
Parallel workers for the NumPy Gibbs path (Gibbs only).
- idata_kwargs : dict, optional¶
{"log_likelihood": True}stores the complete Jacobian-corrected pointwise log-likelihood thataz.loo/az.waic/az.compareneed, for Gibbs and NUTS alike. Off by default, as in PyMC: it holds one value per draw, chain, and observation (16 GB at n = 250,000 with 4 × 2,000 draws). For NUTS the dict is also passed topm.sample.- **sample_kwargs¶
For NUTS, forwarded to
pm.sample(nuts_sampler=...); for Gibbs, the family’s declared options (an unsupported key raises).
- Return type:¶
arviz.InferenceData
- property inference_data : arviz.data.inference_data.InferenceData | None[source]¶
Return the ArviZ InferenceData from the most recent fit.
- property pymc_model : pymc.model.core.Model | None[source]¶
Return the PyMC model object built for the most recent fit.
For Gibbs-fitted models the PyMC model is not constructed during sampling; it is built lazily on first access so that downstream consumers (e.g. bridge sampling for marginal likelihoods) can evaluate
logpand the prior under the same model definition used by the NUTS path.
- residuals()[source]¶
Return residuals
y - fitted_values.- Returns:¶
Residual vector
y - fitted_valueson the same scale asfitted_values().- Return type:¶
np.ndarray
- spatial_diagnostics()[source]¶
Run Bayesian LM specification tests and return a summary table.
Looks up the diagnostic suite registered for this model class and calls each test function on this fitted model, collecting the results into a tidy DataFrame. The set of tests depends on the model type — for example, an OLS model runs LM-Lag, LM-Error, LM-SDM-Joint, and LM-SLX-Error-Joint, while an SAR model runs LM-Error, LM-WX, and Robust-LM-WX. Panel models run the
Panel--prefixed analogues (e.g. Panel-LM-Lag).Requires the model to have been fit (
.fit()called) and a spatial weights matrixWto have been supplied at construction time.- Returns:¶
DataFrame indexed by test name with columns:
Column
Description
statistic
Posterior mean of the LM statistic
median
Posterior median of the LM statistic
df
Degrees of freedom for the \(\chi^2\) reference
p_value
Bayesian p-value:
1 - chi2.cdf(mean, df)ci_lower
Lower bound of 95% credible interval (2.5%)
ci_upper
Upper bound of 95% credible interval (97.5%)
The DataFrame has
attrs["model_type"](class name) andattrs["n_draws"](total posterior draws) metadata.- Return type:¶
pandas.DataFrame
- Raises:¶
RuntimeError – If the model has not been fit yet.
ValueError – If no spatial weights matrix
Wwas supplied.
See also
spatial_diagnostics_decisionModel-selection decision based on the test results.
spatial_effectsPosterior inference for direct/indirect/total impacts.
Examples
>>> ols = OLS(formula="price ~ income + crime", data=df, W=w) >>> ols.fit() >>> ols.spatial_diagnostics() statistic median df p_value ci_lower ci_upper LM-Lag 3.21 2.98 1 0.073 0.12 8.54 LM-Error 5.67 5.34 1 0.017 0.34 12.10 LM-SDM-Joint 7.89 7.12 4 0.096 1.23 18.32 LM-SLX-Error-Joint 6.45 5.98 4 0.168 0.89 15.67
-
spatial_diagnostics_decision(alpha=
0.05, format='graphviz')[source]¶ Return a model-selection decision from Bayesian LM test results.
Implements the decision tree from Koley and Bera [2024] (the Bayesian analogue of the classical
stge_kbprocedure in Anselin et al. [1996]). Panel models use thePanel--prefixed test analogues and the panel decision specs, following Elhorst [2014]. The decision logic depends on the current model type and the pattern of significant tests:From OLS (6-test decision tree):
If only LM-Lag is significant → SAR.
If only LM-Error is significant → SEM.
If both are significant → use the Anselin–Florax / Koley–Bera robust pair: Robust-LM-Lag → SAR, Robust-LM-Error → SEM, both → SARAR. If neither robust test is significant, fall back to the lower raw p-value.
If neither naive test is significant → OLS.
From SAR (3-test decision tree):
LM-Error significant → SARAR; LM-WX significant → SDM; Robust-LM-WX significant → SDM.
From SEM (2-test decision tree):
LM-Lag significant → SARAR; LM-WX significant → SDEM.
From SLX (4-test decision tree):
Robust-LM-Lag-SDM significant → SDM; Robust-LM-Error-SDEM significant → SDEM; both → MANSAR; neither → SLX.
From SDM: LM-Error-SDM significant → MANSAR; else SDM.
From SDEM: LM-Lag-SDEM significant → MANSAR; else SDEM.
- Parameters:¶
- alpha : float, default 0.05¶
Significance level for the Bayesian p-values.
- format : {"graphviz", "ascii", "model"}, default "graphviz"¶
Output format.
"model"returns the recommended-model name string."ascii"returns an indented box-drawing rendering of the full decision tree with the chosen path highlighted."graphviz"returns agraphviz.Digraphobject that renders inline in Jupyter; if the optionalgraphvizpackage is not installed aUserWarningis issued and the ASCII rendering is returned instead.
- Returns:¶
Recommended model name when
format="model", an ASCII tree string whenformat="ascii", or agraphviz.Digraphwhenformat="graphviz"(with ASCII fallback on missing dep).- Return type:¶
str or graphviz.Digraph
See also
spatial_diagnosticsCompute the Bayesian LM test statistics.
References
Koley and Bera [2024], Anselin et al. [1996], Elhorst [2014]
-
spatial_effects(return_posterior_samples=
False)[source]¶ Compute Bayesian inference for direct, indirect, and total impacts.
Computes impact measures for each posterior draw, then summarizes the posterior distribution with means, 95% credible intervals, and Bayesian p-values. This is the fully Bayesian analog of the simulation-based approach in LeSage and Pace [2009] and the asymptotic variance formulas in Arbia et al. [2020].
Models without a spatial lag on y do not exhibit global feedback propagation through \((I-\\rho W)^{-1}\). However, models with spatially lagged covariates (SLX, SDEM) can still have non-zero neighbor spillovers captured in the indirect term.
- Parameters:¶
- Returns:¶
If return_posterior_samples is
False(default), returns a DataFrame indexed by feature names with columns for posterior means, credible-interval bounds, and Bayesian p-values.If return_posterior_samples is
True, returns(DataFrame, dict)where the dict has keys"direct","indirect","total", each mapping to a(G, k)array of posterior draws.- Return type:¶