Banner

6.8 Comparing Global, Spatial and Local Models¶

Part: 6 — Geographically Weighted Models (Chapter 6.8 of 14)

Dataset: diabetes_northeast.shp (217 counties, for full comparability across every model family)

Focus: OLS, SLM, SEM (Part 3) against GWR and MGWR — same data, same weights where applicable, one table

Library: spatialstats.regression, spatialstats.gwmodel (PySpatialStats)


Table of Contents¶

  1. Overview

  2. Python Setup

  3. Fitting Every Model on the Same Data

  4. The Comparison Table

  5. Reading the Comparison Carefully

  6. Summary and Conclusions

  7. Resources


1. Overview¶

Part 3 chose between OLS, the spatial lag model (SLM), and the spatial error model (SEM) — three ways of handling spatial dependence, all still global. This Part's GWR and MGWR handle a different problem entirely — spatial heterogeneity in the relationship itself, not just dependence in the residuals. This chapter puts all five side by side, using the 217-county Northeast dataset (not the 666-county Atlantic data used elsewhere in this Part) specifically so every model can be compared on identical data and Queen weights, with no scale mismatch to explain away.

Step What we do
Fit everything OLS, SLM, SEM (Part 3's own code), GWR, MGWR — same 217 counties, same six covariates
One table AICc, log-likelihood, residual Moran's I, and (where available) local $R^2$ range
Read with caution Why comparing information criteria across such different estimator families needs care, not blind ranking

2. Python Setup¶

In [1]:
import sys, time, 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.regression import OLS, SpatialLag, SpatialError
from spatialstats.gwmodel.linear import GWR, MGWR
from spatialstats.esda import Moran

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"}

counties = gpd.read_file(DATA / "diabetes_northeast.shp")
covariates = ["Obesity", "PhysInact", "Acc_Exer", "PCP_rate", "FoodEnvIdx", "SVI"]
X = counties[covariates]
y = counties["Dia_pct"]
coords = counties[["x", "y"]].to_numpy()
w = Queen(counties)
print(f"spatialstats : version {spatialstats.__version__}")
spatialstats : version 0.2.0

3. Fitting Every Model on the Same Data¶

OLS, SpatialLag, and SpatialError report AIC, not AICc; a small-sample correction $\text{AICc} = \text{AIC} + \frac{2k(k+1)}{n-k-1}$ puts them on the same footing as GWR/MGWR's own aicc_.

In [2]:
def aicc_from_aic(aic, n, k):
    return aic + 2 * k * (k + 1) / (n - k - 1)


n = len(y)
ols = OLS(y, X, w=w)
slm = SpatialLag(y, X, w)
sem = SpatialError(y, X, w)

gwr = GWR(bandwidth=91, fixed=False, kernel="bisquare", n_jobs=2)   # Chapter 6.3's Northeast bandwidth
gwr.fit(X.to_numpy(), y.to_numpy(), coords)

t0 = time.time()
mgwr = MGWR(kernel="bisquare", fixed=False, max_iter=6, tol=1.0, n_jobs=2)   # Chapter 6.6's settings
mgwr.fit(X.to_numpy(), y.to_numpy(), coords)
print(f"MGWR fit time: {time.time() - t0:.0f}s")
MGWR fit time: 26s

4. The Comparison Table¶

In [3]:
rows = []
for name, m in [("OLS", ols), ("SLM", slm), ("SEM", sem)]:
    aicc = aicc_from_aic(m.aic, n, m.n_params)
    resid_I = Moran(m.residuals, w, permutations=0).I
    rows.append({"model": name, "loglik": m.loglik, "aicc": aicc, "resid_moran_I": resid_I,
                "local_r2_min": np.nan, "local_r2_max": np.nan})

gwr_loglik = -0.5 * n * (np.log(2 * np.pi) + np.log(np.sum(gwr.residuals_ ** 2) / n) + 1)
mgwr_loglik = -0.5 * n * (np.log(2 * np.pi) + np.log(np.sum(mgwr.residuals_ ** 2) / n) + 1)
rows.append({"model": "GWR", "loglik": gwr_loglik, "aicc": gwr.aicc_,
            "resid_moran_I": Moran(gwr.residuals_, w, permutations=0).I,
            "local_r2_min": gwr.r2_local_.min(), "local_r2_max": gwr.r2_local_.max()})
rows.append({"model": "MGWR", "loglik": mgwr_loglik, "aicc": mgwr.aicc_,
            "resid_moran_I": Moran(mgwr.residuals_, w, permutations=0).I,
            "local_r2_min": np.nan, "local_r2_max": np.nan})

comparison = pd.DataFrame(rows).set_index("model")
comparison.round(4)
Out[3]:
loglik aicc resid_moran_I local_r2_min local_r2_max
model
OLS -203.6171 421.7701 0.1362 NaN NaN
SLM -202.8790 422.4504 0.1028 NaN NaN
SEM -199.0366 414.7655 -0.0032 NaN NaN
GWR -159.7848 -29.2105 -0.0173 0.537 0.8757
MGWR -105.2750 465.5244 -0.2649 NaN NaN

5. Reading the Comparison Carefully¶

AICc is not directly comparable across this table, and the raw numbers show exactly why blind ranking would mislead: GWR's AICc ($-29.2$) looks dramatically better than every global model's ($414$–$422$), but GWR's likelihood and AICc are computed from a fundamentally different degrees-of-freedom accounting (the effective number of parameters from the hat-matrix trace, not a fixed integer $k$) — the two are not measuring "complexity" on the same scale, and a naive "lowest AICc wins" reading would overstate GWR's case. What is directly comparable and trustworthy:

  • Residual Moran's I: SEM (−0.003) and GWR (−0.017) both essentially eliminate residual autocorrelation; OLS (0.136) and SLM (0.103) do not fully remove it; MGWR's residuals are, surprisingly, negatively autocorrelated (−0.265) — a real, checkable sign that this particular MGWR fit (several covariates pinned to the minimum bandwidth of 8, Chapter 6.6) is overfitting locally, producing a checkerboard-like residual pattern rather than genuinely explaining more structure. More local flexibility is not automatically better.
  • Local $R^2$ range (GWR only, here): 0.537 to 0.876 — a real, substantive finding about where the model fits well that no single global $R^2$ or AICc number could show.
  • SEM beats both SLM and OLS among the global models by both log-likelihood and AICc, consistent with Part 3's original diagnosis on this same variable.

Part 3's spatial lag and error models capture spillover and nuisance dependence in the residuals; GWR and MGWR capture heterogeneity in the relationship itself. These are different problems, and the right choice depends on which one the data actually shows (Chapter 6.1's Chow test motivated heterogeneity specifically for this dataset) — not on which model has the smallest number in a column that is not, in fact, measuring the same thing across every row.


6. Summary and Conclusions¶

This chapter put every model family from Parts 3 and 6 side by side, on identical data, and read the result carefully rather than mechanically.

What we did¶

  1. Fit OLS, SLM, SEM, GWR, and MGWR on the same 217-county dataset and six covariates.
  2. Built one comparison table: log-likelihood, AICc (with a small-sample correction for the Part 3 models), residual Moran's I, and local $R^2$ range where available.
  3. Found SEM and GWR both essentially eliminate residual autocorrelation; OLS and SLM do not.
  4. Found MGWR's residuals are negatively autocorrelated — a real, checkable sign of local overfitting from several covariates' minimum-bandwidth fits (Chapter 6.6), not evidence MGWR is simply "better" than GWR.
  5. Explained explicitly why GWR/MGWR's AICc is not on the same scale as the global models' — a naive "lowest-AICc-wins" comparison across this table would be actively misleading.

Practical takeaways¶

  • Residual Moran's I is the most directly comparable diagnostic across every model family in this table; lean on it more than a raw AICc comparison between fundamentally different estimator classes.
  • A more locally flexible model (MGWR here) is not automatically better — check its residuals for the opposite failure mode (overfitting-induced negative autocorrelation), not just for remaining positive autocorrelation.
  • Spatial lag/error models and geographically weighted models solve different problems (dependence vs. heterogeneity); use the diagnostics from Part 3, Chapter 3.3 and this Part's Chapter 6.1 evidence together to decide which one the data actually needs, rather than picking by information criterion alone.

Next steps¶

Chapter 6.9 (Machine-Learning and Ensemble GW Models) moves beyond linear local coefficients entirely, trading interpretability for potentially stronger predictive accuracy.


7. Resources¶

Textbooks¶

Reference Notes
Fotheringham, A. S., Brunsdon, C. & Charlton, M. (2002). Geographically Weighted Regression. Wiley. Chapter 5 discusses comparing GWR against global alternatives
Burnham, K. P. & Anderson, D. R. (2002). Model Selection and Multimodel Inference (2nd ed.). Springer. AICc theory and its small-sample correction, used in Section 3

Key papers¶

Paper Contribution
Fotheringham, A. S., Charlton, M. & Brunsdon, C. (1996). op. cit. (Chapter 6.3) Effective-parameter accounting for GWR's AICc

In this library¶

Object Role
spatialstats.regression.OLS, SpatialLag, SpatialError Part 3's global models, refit here for direct comparison
spatialstats.gwmodel.linear.GWR, MGWR This Part's local models
spatialstats.esda.Moran The single most trustworthy comparison metric across every row of this chapter's table