
3.1 Foundations of Spatial Regression¶
Part: 3 — Spatial Regression (Chapter 3.1 of 7)
Dataset: diabetes_atlantic.shp (666 counties, Atlantic seaboard)
Focus: Why OLS fails on spatial data, substantive vs. nuisance dependence, the model taxonomy, and data preparation
Library: spatialstats.regression, spatialstats.weights (PySpatialStats)
1. Overview¶
Part 2 established that Dia_pct is spatially autocorrelated across counties. This chapter asks the natural
next question: what does that mean for a regression model that explains diabetes prevalence from covariates
like obesity and physical inactivity? The short answer — plain OLS is no longer safe, in one of two distinct
ways — sets up everything that follows in Part 3.
| Step | What we do |
|---|---|
| Diagnosis | Show concretely why spatially dependent residuals break OLS's standard-error and, in one case, coefficient guarantees |
| Two mechanisms | Distinguish substantive dependence (real spillovers between counties) from nuisance dependence (an unmodelled spatially structured omitted variable) |
| Taxonomy | Place OLS, SLX, SAR (SLM), SEM, SARAR, SDM, and SDEM on one map, and note which this library implements |
| Data | Prepare the regression dataset used throughout Part 3: Dia_pct on six health/access covariates plus an urban/rural indicator, over 666 Atlantic-seaboard counties |
By the end, Chapter 3.2 fits the OLS baseline on this exact dataset and starts accumulating the diagnostic evidence that Chapters 3.3–3.5 use to choose a model.
2. Python Setup¶
import sys, warnings
from pathlib import Path
import numpy as np
import pandas as pd
import geopandas as gpd
import matplotlib.pyplot as plt
warnings.filterwarnings("ignore", category=FutureWarning)
warnings.filterwarnings("ignore", category=DeprecationWarning)
def find_repo_root(start: Path = Path.cwd()) -> Path:
for p in [start, *start.parents]:
if (p / "pyproject.toml").exists() and (p / "data").is_dir():
return p
raise FileNotFoundError("Run this notebook from inside the PySpatialStats repository.")
ROOT = find_repo_root()
DATA = ROOT / "data"
try:
import spatialstats
except ImportError:
sys.path.insert(0, str(ROOT))
import spatialstats
from spatialstats.weights import Queen
from spatialstats.esda import Moran
from spatialstats.regression import OLS
plt.rcParams.update({
"figure.dpi": 100, "savefig.dpi": 100, "font.size": 10, "axes.titlesize": 11,
"axes.titleweight": "bold", "axes.spines.top": False, "axes.spines.right": False,
"axes.grid": True, "grid.alpha": 0.25, "legend.frameon": False,
})
C = {"blue": "#2b6cb0", "red": "#c0392b", "green": "#2f855a", "orange": "#dd6b20",
"grey": "#718096", "purple": "#6b46c1", "teal": "#2c7a7b"}
print(f"spatialstats : version {spatialstats.__version__}")
print(f"data folder : {DATA.relative_to(ROOT)}/")
spatialstats : version 0.2.0 data folder : data/
3. Why OLS Fails on Spatial Data¶
OLS assumes each observation's error term is drawn independently. Part 2's whole apparatus — Moran's I, LISA — exists because that assumption routinely fails for areal data: neighbouring counties share climate, policy, infrastructure, and demographics, so their unexplained variation is correlated too. Two distinct failure modes follow, depending on where the dependence actually lives:
- If the dependence is in the errors (an omitted spatially structured variable, or measurement error that clusters geographically), OLS coefficients remain unbiased, but the reported standard errors are wrong — usually too small, because the effective sample size is less than $n$ independent observations would give. Confidence intervals are too narrow and p-values overstate significance.
- If the dependence reflects a genuine spillover — one county's outcome directly depends on its neighbours' outcomes, not just on shared covariates — then the true model has $y$ on both sides implicitly (through a term like $\rho W y$), and omitting that term biases every coefficient, not just the standard errors, because $Wy$ is correlated with the included regressors whenever they are themselves spatially patterned.
Chapter 3.2 puts numbers on the first failure mode directly, and Chapter 3.3's Lagrange Multiplier tests are built specifically to tell the two apart.
4. Two Mechanisms: Substantive vs. Nuisance Dependence¶
| Substantive dependence | Nuisance dependence | |
|---|---|---|
| Interpretation | A real causal channel: outcomes in one place influence outcomes in neighbouring places | An artefact of the model: some spatially structured driver is missing from $X$, so its influence shows up in the residuals instead |
| Formal structure | $y = \rho W y + X\beta + \varepsilon$ — the spatial lag (SAR/SLM) model | $y = X\beta + u,\ u = \lambda W u + \varepsilon$ — the spatial error (SEM) model |
| What $\beta$ means | Not a marginal effect — a change in $x_k$ anywhere propagates through $\rho$ to every other county (Chapter 3.4's impacts()) |
Retains its ordinary OLS interpretation; $\lambda \ne 0$ says "something spatially structured is missing", not "your $\beta$'s are wrong" |
| Correct response | Model the spillover explicitly (spatial lag or Durbin model) | Add the missing covariate if you can identify it, or model the nuisance directly (spatial error model) |
This is Part 2, Chapter 2.1's dependence-vs-heterogeneity distinction, made operational: a regression's LM tests (Chapter 3.3) are a formal, data-driven way to ask which of the two — if either — is present, rather than guessing from theory alone.
5. A Taxonomy of Spatial Regression Models¶
$$ \text{OLS} \;\longrightarrow\; \text{SLX} \;\longrightarrow\; \text{SAR (SLM)} \;\longrightarrow\; \text{SEM} \;\longrightarrow\; \text{SARAR} \;\longrightarrow\; \text{SDM} \;\longrightarrow\; \text{SDEM} $$
OLS (Ordinary Least Squares): The basic, familiar regression model. It assumes each area's outcome is independent of its neighbors, except for shared covariates. No special spatial effects are included. Use this as a baseline.
SLX (Spatial Lag of X): Like OLS, but also includes "neighbor averages" of each predictor as extra inputs. For example, it doesn't just use a county’s own obesity rate, but also its neighbors’ average obesity rate. Helps capture the effect of neighboring conditions.
SAR / SLM (Spatial Lag Model): Adds the outcome values of neighboring areas ("Wy") directly into the regression. Here, a change in one county can ripple through to others—outcomes actually influence each other, beyond the covariates.
SEM (Spatial Error Model): Instead of outcomes influencing each other, this model assumes that some unmeasured, spatially-patterned factor is affecting the residuals (“leftover errors”). Neighboring counties’ errors are correlated, but outcomes themselves don’t cause each other.
SARAR: Combines SAR/SLM and SEM in one model, meaning both outcome spillovers and spatially-patterned residuals are present. Not implemented here.
SDM (Spatial Durbin Model): Combines SAR/SLM and SLX—outcomes and predictors can both spill over to neighbors. Good when both direct and indirect neighborhood effects may matter.
SDEM: Combines SEM and SLX—neighboring effects come through the predictors and the residuals, but not the outcome itself.
| Model | Structure | Available in this library |
|---|---|---|
| OLS | $y = X\beta + \varepsilon$ | ✅ OLS |
| SLX | $y = X\beta + WX\theta + \varepsilon$ (lagged covariates only, still OLS-estimable) | ✅ OLS(lag_x=True) / SLX |
| SAR / SLM | $y = \rho Wy + X\beta + \varepsilon$ | ✅ SpatialLag |
| SEM | $y = X\beta + u,\ u = \lambda Wu + \varepsilon$ | ✅ SpatialError |
| SARAR | $y = \rho Wy + X\beta + u,\ u = \lambda Wu + \varepsilon$ (both mechanisms at once) | ❌ not implemented |
| SDM | $y = \rho Wy + X\beta + WX\theta + \varepsilon$ (SAR + lagged covariates) | ✅ SpatialDurbin (SpatialLag(slx=True)) |
| SDEM | $y = X\beta + WX\theta + u,\ u = \lambda Wu + \varepsilon$ (SEM + lagged covariates) | ✅ SpatialError(slx=True) |
Every model but SARAR is available; Chapter 3.4 works through SLM, SEM, and the Durbin variants (SDM, SDEM) in detail, and Chapter 3.5 shows the "common factor" relationship that connects SDM back to SEM.
6. Data Preparation¶
The running example for the rest of Part 3: diagnosed diabetes prevalence (Dia_pct) explained by obesity,
physical inactivity, exercise access, primary-care access, food-environment quality, social vulnerability, and
an urban/rural indicator, across 666 counties along the Atlantic seaboard — a larger, more geographically
extensive dataset than Part 2's 217-county Northeast subset, chosen here so the regression has more power and a
genuinely non-trivial spatial footprint.
counties = gpd.read_file(DATA / "diabetes_atlantic.shp")
print(f"diabetes_atlantic.shp: {counties.shape[0]} counties across {counties['STATE'].nunique()} states/DC")
print(counties["STATE"].value_counts())
diabetes_atlantic.shp: 666 counties across 11 states/DC STATE Georgia 159 Virginia 128 North Carolina 100 Pennsylvania 67 New York 62 West Virginia 55 South Carolina 46 Maryland 24 New Jersey 21 Delaware 3 District of Columbia 1 Name: count, dtype: int64
UrbRural is a string column ("Urban"/"Rural") and must be coded numerically before it can enter a regression.
covariates = ["Obesity", "PhysInact", "Acc_Exer", "PCP_rate", "FoodEnvIdx", "SVI"]
X = counties[covariates].copy()
X["Urban"] = (counties["UrbRural"] == "Urban").astype(int)
y = counties["Dia_pct"]
print(f"y: Dia_pct, range [{y.min():.2f}, {y.max():.2f}], mean {y.mean():.2f}")
X.describe().T.assign(missing=X.isna().sum())
y: Dia_pct, range [5.86, 16.96], mean 9.14
| count | mean | std | min | 25% | 50% | 75% | max | missing | |
|---|---|---|---|---|---|---|---|---|---|
| Obesity | 666.0 | 27.720706 | 4.792474 | 16.8000 | 23.5600 | 27.96000 | 31.260000 | 41.1800 | 0 |
| PhysInact | 666.0 | 21.540345 | 3.784371 | 13.2800 | 18.7450 | 21.17000 | 23.830000 | 35.1600 | 0 |
| Acc_Exer | 666.0 | 64.620270 | 22.826274 | 0.0000 | 50.0000 | 66.40000 | 82.350000 | 100.0000 | 0 |
| PCP_rate | 666.0 | 55.706156 | 38.313176 | 5.0000 | 32.8000 | 47.60000 | 70.550000 | 536.6000 | 0 |
| FoodEnvIdx | 666.0 | 7.541141 | 0.976839 | 2.6000 | 7.0000 | 7.60000 | 8.200000 | 10.0000 | 0 |
| SVI | 666.0 | 0.565418 | 0.275070 | 0.0013 | 0.3521 | 0.56605 | 0.812575 | 0.9987 | 0 |
| Urban | 666.0 | 0.714715 | 0.451890 | 0.0000 | 0.0000 | 1.00000 | 1.000000 | 1.0000 | 0 |
Spatial weights are needed from the first model onward (Chapter 3.2's residual diagnostics already require
w), so we build them once here: Queen contiguity, row-standardised — the same default used throughout Part 2.
w = Queen(counties)
print(w.summary())
Spatial Weights (W) =================== Observations 666 Transform row Min neighbors 1 Mean neighbors 5.423 Max neighbors 11 Islands 0 Components 1 Nonzero weights 3612
No islands on this larger, more contiguous dataset — unlike Part 2's Northeast subset (which had Nantucket as an isolated county), every Atlantic county has at least one Queen neighbour, so none of Part 2's island-handling caveats apply here. This matters for Part 3 specifically: every model below needs $W$ to represent a fully row-stochastic, invertible system (via $(I - \rho W)^{-1}$ for the lag model), which an island would complicate.
global_moran = Moran(y, w, permutations=999, seed=1)
print(f"Moran's I of raw Dia_pct: {global_moran.I:.4f} (p_sim={global_moran['p_sim']:.4f})")
Moran's I of raw Dia_pct: 0.3707 (p_sim=0.0010)
Diabetes prevalence is itself strongly autocorrelated, exactly as in Part 2 — but that alone does not tell us
why a regression of Dia_pct on these covariates would need a spatial model: if the covariates themselves
fully explain the spatial pattern, plain OLS residuals could still be independent. Chapter 3.2 checks this
directly, on the residuals rather than the raw outcome.
7. Summary and Conclusions¶
This chapter set up the conceptual and data foundation for spatial regression.
What we did¶
- Explained the two distinct ways OLS can fail on spatial data: nuisance dependence (wrong standard errors, unbiased coefficients) and substantive dependence (biased coefficients from an omitted spatial-lag term).
- Mapped that distinction onto the spatial error (SEM) and spatial lag (SAR/SLM) models respectively.
- Placed every model in this library's taxonomy — OLS, SLX, SAR, SEM, SDM, SDEM — noting that SARAR alone is not implemented.
- Prepared the Part 3 regression dataset:
Dia_pcton six covariates plus a codedUrbanindicator, across 666 Atlantic-seaboard counties, with a row-standardised, island-free QueenW. - Confirmed the outcome variable is itself strongly spatially autocorrelated (Moran's I = 0.371, $p_\text{sim}=0.001$) — a stronger raw signal than Part 2's 217-county Northeast subset (I = 0.240) — before any covariate has even been considered.
Practical takeaways¶
- Do not assume which mechanism (substantive vs. nuisance) is at work from theory alone — Chapter 3.3's LM tests exist because the two are formally distinguishable from the same OLS residuals.
- A string categorical column silently breaks a regression if not coded numerically first; always check
X.dtypesbefore fitting. - Confirming zero islands in $W$ up front avoids a class of failures (a non-invertible $(I-\rho W)$) that would otherwise surface confusingly deep inside Chapter 3.4's maximum-likelihood fitting.
Next steps¶
Chapter 3.2 (OLS Baseline and Diagnostics) fits the OLS model on exactly this data and runs the classical and spatial diagnostics — condition number, normality, heteroskedasticity, and residual Moran's I — that Chapter 3.3 builds on.
8. Resources¶
Textbooks¶
| Reference | Notes |
|---|---|
| Anselin, L. (1988). Spatial Econometrics: Methods and Models. Kluwer Academic. | The foundational text for this entire Part; origin of the substantive/nuisance distinction |
| LeSage, J. & Pace, R. K. (2009). Introduction to Spatial Econometrics. CRC Press. | Modern standard reference; origin of the impacts framework used in Chapter 3.4 |
| Elhorst, J. P. (2014). Spatial Econometrics: From Cross-Sectional Data to Spatial Panels. Springer. | Clear taxonomy of the SLX/SAR/SEM/SDM/SDEM model family |
Key papers¶
| Paper | Contribution |
|---|---|
| Anselin, L. (1988). Lagrange multiplier test diagnostics for spatial dependence and spatial heterogeneity. Geographical Analysis, 20, 1–17. | Formal test statistics used in Chapter 3.3 |
| LeSage, J. P. & Pace, R. K. (2009). Chapter 2, spatial model taxonomy. In Introduction to Spatial Econometrics. | The OLS→SLX→SAR→SEM→SARAR→SDM→SDEM taxonomy reproduced in Section 5 |
In this library¶
| Object | Role |
|---|---|
spatialstats.regression.OLS |
Baseline model; lag_x=True gives SLX |
spatialstats.regression.SpatialLag, SpatialError |
SAR/SLM and SEM, by maximum likelihood |
spatialstats.regression.SpatialDurbin |
SpatialLag(slx=True), i.e. SDM |
spatialstats.weights.Queen |
Spatial weights, reused directly from Part 2 |
| Chapter 3.2 | lm_tests, moran_residuals, classical diagnostics |
| Chapter 3.4 | Full treatment of SLM, SEM, SDM, and impacts() |