Spatial Extensions¶
Overview¶
PanelBox extends PanelExperiment with spatial econometric capabilities through the SpatialPanelExperiment mixin. This extension adds methods for:
- Spatial diagnostics -- Moran's I, Local Moran's I (LISA), and LM tests
- Spatial model estimation -- SAR, SEM, SDM, and GNS with automatic model selection
- Spatial effects decomposition -- direct, indirect (spillover), and total effects
- Spatial diagnostic reports -- HTML reports with maps and charts
These methods are automatically available on any PanelExperiment instance -- no additional imports are needed.
Spatial Diagnostics¶
Running the Full Diagnostic Battery¶
import panelbox as pb
import numpy as np
data = pb.load_grunfeld()
exp = pb.PanelExperiment(
data=data,
formula="invest ~ value + capital",
entity_col="firm",
time_col="year"
)
# Create a spatial weights matrix (N x N)
N = data["firm"].nunique()
W = np.random.rand(N, N) # Replace with real spatial weights
np.fill_diagonal(W, 0)
W = W / W.sum(axis=1, keepdims=True) # Row-normalize
# Run diagnostics
diag = exp.run_spatial_diagnostics(W=W, alpha=0.05, verbose=True)
The method on PanelExperiment directly (not via the mixin) accepts:
| Parameter | Type | Default | Description |
|---|---|---|---|
W |
np.ndarray or SpatialWeights |
(required) | N\(\times\)N spatial weight matrix |
alpha |
float |
0.05 |
Significance level |
model_name |
str or None |
None |
OLS model name. If None, fits pooled OLS internally |
verbose |
bool |
True |
Print diagnostic summary |
What is Returned¶
The diagnostics return a dictionary with four components:
diag = exp.run_spatial_diagnostics(W=W)
# Global spatial autocorrelation
morans = diag['morans_i']
print(f"Moran's I: {morans.statistic:.4f} (p={morans.pvalue:.4f})")
# Local Moran's I (LISA clusters)
lisa = diag['morans_i_local']
print(f"LISA clusters:\n{lisa['cluster_type'].value_counts()}")
# LM tests for model specification
lm = diag['lm_tests']
print(f"LM Lag p-value: {lm.get('lm_lag', {}).get('pvalue', 'N/A')}")
print(f"LM Error p-value: {lm.get('lm_error', {}).get('pvalue', 'N/A')}")
# Automatic model recommendation
print(f"Recommended model: {diag['recommendation']}")
| Key | Content | Description |
|---|---|---|
morans_i |
Test result object | Global Moran's I test for spatial autocorrelation |
morans_i_local |
DataFrame | Local Moran's I (LISA) with cluster types |
lm_tests |
dict |
LM Lag, LM Error, Robust LM Lag, Robust LM Error |
recommendation |
str |
Recommended spatial model based on LM decision tree |
Understanding the Model Recommendation¶
The recommendation follows the Anselin & Rey (2014) decision tree:
| LM Lag | LM Error | Robust LM Lag | Robust LM Error | Recommendation |
|---|---|---|---|---|
| Significant | Not significant | -- | -- | SAR (Spatial Lag) |
| Not significant | Significant | -- | -- | SEM (Spatial Error) |
| Significant | Significant | Significant | Not significant | SAR |
| Significant | Significant | Not significant | Significant | SEM |
| Significant | Significant | Both significant | Both significant | SDM or GNS |
| Not significant | Not significant | -- | -- | No spatial dependence |
Estimating Spatial Models¶
Automatic Model Selection¶
After running diagnostics, use estimate_spatial_model() with model_type='auto' to fit the recommended model:
# Run diagnostics first
diag = exp.run_spatial_diagnostics(W=W)
print(f"Recommendation: {diag['recommendation']}")
# Fit the recommended model
results = exp.estimate_spatial_model(model_type='auto', W=W, name='spatial_auto')
Manual Model Selection¶
Specify the spatial model type explicitly:
# Spatial Lag (SAR)
sar = exp.estimate_spatial_model(model_type='sar', W=W, name='sar_model')
# Spatial Error (SEM)
sem = exp.estimate_spatial_model(model_type='sem', W=W, name='sem_model')
# Spatial Durbin (SDM)
sdm = exp.estimate_spatial_model(model_type='sdm', W=W, name='sdm_model')
Supported model types:
| Alias | Model | Description |
|---|---|---|
'sar', 'spatial_lag' |
Spatial Lag (SAR) | \(y = \rho Wy + X\beta + \epsilon\) |
'sem', 'spatial_error' |
Spatial Error (SEM) | \(y = X\beta + u\), \(u = \lambda Wu + \epsilon\) |
'sdm', 'spatial_durbin' |
Spatial Durbin (SDM) | \(y = \rho Wy + X\beta + WX\theta + \epsilon\) |
'gns', 'general_nesting' |
General Nesting Spatial | Encompasses SAR, SEM, and SDM |
'auto' |
Automatic | Uses LM test recommendation |
Using the SpatialPanelExperiment Mixin¶
The mixin provides add_spatial_model() for more explicit control over spatial model fitting:
from panelbox.core.spatial_weights import SpatialWeights
W_obj = SpatialWeights.from_contiguity(gdf, criterion="queen")
# Add spatial models with effects specification
exp.add_spatial_model("SAR-FE", W=W_obj, model_type="sar", effects="fixed")
exp.add_spatial_model("SDM-FE", W=W_obj, model_type="sdm", effects="fixed")
# Compare spatial and non-spatial models together
comp = exp.compare_models()
Effects Decomposition¶
For Spatial Durbin (SDM) and General Nesting Spatial (GNS) models, decompose estimated effects into direct, indirect (spillover), and total components:
# Fit an SDM model
exp.add_spatial_model("SDM", W=W_obj, model_type="sdm", effects="fixed")
# Decompose effects
effects = exp.decompose_spatial_effects("SDM")
print("Direct Effects:")
print(effects["direct"])
print("\nIndirect Effects (Spillovers):")
print(effects["indirect"])
print("\nTotal Effects:")
print(effects["total"])
SDM/GNS Only
Effects decomposition is only available for SDM and GNS models. SAR and SEM models do not support this method because their spillover structure is captured differently.
Comparing Spatial Models¶
Use compare_spatial_models() to compare spatial and non-spatial models together:
# Fit models
exp.fit_model('pooled_ols', name='OLS')
exp.add_spatial_model("SAR", W=W_obj, model_type="sar")
exp.add_spatial_model("SEM", W=W_obj, model_type="sem")
exp.add_spatial_model("SDM", W=W_obj, model_type="sdm")
# Compare all (including non-spatial)
comparison_df = exp.compare_spatial_models(include_non_spatial=True)
print(comparison_df[['Model', 'Type', 'AIC', 'BIC']].to_string())
The comparison table includes spatial-specific columns for \(\rho\) (spatial lag parameter) and \(\lambda\) (spatial error parameter) when applicable.
Spatial Diagnostic Reports¶
Generate an HTML report summarizing spatial diagnostics:
path = exp.spatial_diagnostics_report(
"spatial_diagnostics.html",
include_maps=False # Set True with GeoDataFrame for maps
)
With Maps¶
If you have geometry data in a GeoDataFrame, include choropleth and LISA cluster maps:
import geopandas as gpd
gdf = gpd.read_file("regions.shp")
path = exp.spatial_diagnostics_report(
"spatial_report.html",
include_maps=True,
gdf=gdf
)
The report includes:
- Moran's I scatter plot
- LISA cluster map (if
include_maps=True) - LM test comparison chart
- Model recommendation summary
Complete Example¶
import panelbox as pb
import numpy as np
# 1. Load data and set up experiment
data = pb.load_grunfeld()
exp = pb.PanelExperiment(
data=data,
formula="invest ~ value + capital",
entity_col="firm",
time_col="year"
)
# 2. Create spatial weights (example: contiguity-based)
N = data["firm"].nunique()
W = np.eye(N, k=1) + np.eye(N, k=-1) # Simple neighbor matrix
W = W / W.sum(axis=1, keepdims=True) # Row-normalize
# 3. Fit baseline OLS
exp.fit_model('pooled_ols', name='ols')
# 4. Run spatial diagnostics
diag = exp.run_spatial_diagnostics(W=W, alpha=0.05, verbose=True)
print(f"Moran's I: {diag['morans_i'].statistic:.4f}")
print(f"Recommendation: {diag['recommendation']}")
# 5. Estimate recommended spatial model
spatial_results = exp.estimate_spatial_model(
model_type='auto',
W=W,
name='spatial'
)
# 6. Generate spatial diagnostics report
exp.spatial_diagnostics_report("spatial_diagnostics.html")
# 7. Compare OLS and spatial model
comp = exp.compare_models(model_names=['ols', 'spatial'])
print(comp.summary())
Requirements¶
Spatial extensions require:
- A spatial weights matrix (
W): N\(\times\)N numpy array orSpatialWeightsobject - For maps: a
GeoDataFramewith geometry column (requiresgeopandas) - For
SpatialWeights.from_contiguity():libpysalor polygon geometries
Comparison with Other Software¶
| Task | PanelBox | Stata | R |
|---|---|---|---|
| Moran's I | exp.run_spatial_diagnostics(W) |
estat moran |
spdep::moran.test() |
| LM tests | Included in diagnostics | spatdiag |
spdep::lm.LMtests() |
| SAR estimation | exp.estimate_spatial_model('sar', W) |
spxtregress |
splm::spreml() |
| Auto model selection | model_type='auto' |
Manual | Manual |
| Effects decomposition | exp.decompose_spatial_effects() |
estat impact |
spatialreg::impacts() |
See Also¶
- Experiment Overview -- Pattern overview and quick start
- Workflow -- Fitting and managing models
- Spatial Models -- Spatial model theory and API
- Spatial Visualization -- Choropleth and LISA maps
- Master Reports -- Combined report generation