Fitting Multiscale GWR for Mixed-Scale Covariates
This guide, part of Geographically Weighted Regression for Disease-Rate Modeling, covers the extension that fixes standard GWR’s most restrictive assumption: that every relationship varies over the same spatial scale.
Problem Context & Constraints
Geographically weighted regression estimates a separate set of coefficients at every location, weighting nearby observations more heavily through a kernel with a single bandwidth. That one bandwidth is a compromise. In a model of asthma emergency visits by tract, the effect of summer temperature varies over hundreds of kilometres — it is a regional, climate-driven relationship. The effect of housing age varies over a few kilometres, neighbourhood by neighbourhood. The effect of health insurance coverage may barely vary at all, being set by statewide policy.
Standard GWR picks one bandwidth for all three. If it picks a short one, the temperature coefficient becomes noisy, swinging between tracts for no real reason. If it picks a long one, the housing-age effect is smoothed into a regional blur and the neighbourhoods where it matters most disappear. Either way, one of the covariates is estimated at the wrong scale.
Multiscale GWR (MGWR) gives each covariate its own bandwidth, chosen by a back-fitting algorithm. The fitted bandwidths themselves become results: a covariate whose bandwidth approaches the full study area has a relationship that is effectively global, and one with a short bandwidth has genuine local variation. The costs are computational — MGWR fits are much slower than GWR — and inferential: with many local coefficients per covariate, the multiple-testing problem is sharper, and MGWR supplies an adjusted critical value for each covariate.
Prerequisites
python3.11numpy1.26.4,pandas2.2.2,geopandas0.14.4mgwr2.2.1 (the PySAL implementation of GWR and MGWR)- An outcome suitable for Gaussian MGWR — typically a log-transformed smoothed rate, since the Poisson form of MGWR is not implemented in
mgwr - Covariates on a common areal geography, checked for collinearity
- A prior standard GWR fit for comparison, as in Fitting GWR for Disease Rate Covariates in Python
Step-by-Step Solution
Standardise the outcome and covariates, initialise MGWR from a GWR bandwidth, run the multiscale bandwidth search, and extract per-covariate bandwidths, adjusted critical values and significant local estimates.
# Multiscale GWR with per-covariate bandwidths.
# Pinned: numpy==1.26.4, pandas==2.2.2, geopandas==0.14.4, mgwr==2.2.1
import logging
import numpy as np
import pandas as pd
import geopandas as gpd
from mgwr.gwr import GWR, MGWR
from mgwr.sel_bw import Sel_BW
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("mgwr")
COVARIATES = ["summer_temp", "pct_pre1960_housing", "pct_uninsured"]
def zscore(a: np.ndarray) -> np.ndarray:
return (a - a.mean(axis=0)) / a.std(axis=0)
def fit_mgwr(gdf: gpd.GeoDataFrame, outcome: str = "log_rate") -> tuple:
coords = np.column_stack([gdf.geometry.centroid.x, gdf.geometry.centroid.y])
y = zscore(gdf[[outcome]].to_numpy(dtype=float))
X = zscore(gdf[COVARIATES].to_numpy(dtype=float))
# GWR first: its bandwidth initialises the back-fitting and is the comparison.
gwr_bw = Sel_BW(coords, y, X, fixed=False, kernel="bisquare").search(criterion="AICc")
gwr = GWR(coords, y, X, gwr_bw, fixed=False, kernel="bisquare").fit()
log.info("GWR bandwidth=%d AICc=%.1f", gwr_bw, gwr.aicc)
sel = Sel_BW(coords, y, X, multi=True, fixed=False, kernel="bisquare")
bws = sel.search(criterion="AICc", multi_bw_min=[30], multi_bw_max=[len(gdf)],
init_multi=gwr_bw, tol_multi=1e-5, max_iter_multi=200)
mgwr = MGWR(coords, y, X, sel, fixed=False, kernel="bisquare").fit()
log.info("MGWR bandwidths=%s AICc=%.1f", list(map(int, bws)), mgwr.aicc)
return gwr, mgwr, bws
def summarise(mgwr, bws, n: int) -> pd.DataFrame:
names = ["intercept"] + COVARIATES
crit = mgwr.critical_tval() # per-covariate adjusted critical t
tvals = mgwr.params / mgwr.bse
rows = []
for j, name in enumerate(names):
sig = np.abs(tvals[:, j]) > crit[j]
rows.append({
"term": name,
"bandwidth": int(bws[j]),
"share_of_sample": round(bws[j] / n, 2),
"enp": round(float(mgwr.ENP_j[j]), 1),
"adj_crit_t": round(float(crit[j]), 2),
"median_coef": round(float(np.median(mgwr.params[:, j])), 3),
"pct_sig": round(100 * sig.mean(), 1),
})
return pd.DataFrame(rows)
Standardising both the outcome and the covariates is not optional for MGWR. The back-fitting algorithm updates one covariate’s surface at a time, and bandwidth comparisons across covariates only mean something when the covariates are on the same scale. Coefficients are then in standard-deviation units, which also makes them comparable with each other.
The share_of_sample column is the most useful single output. A bandwidth above about 0.8 of the sample means the relationship is essentially global and can be reported as a single number; one below 0.1 means strong local variation worth mapping. The effective number of parameters per term (enp) tells the same story in model-complexity terms. The percentage of locations where a term is significant, pct_sig, completes the picture: a short bandwidth with almost no significant local estimates means the variation is real in the model but too uncertain to act on.
Validation & Edge Cases
Compare with GWR and OLS. Report the AICc of ordinary least squares, GWR and MGWR together. MGWR should improve on GWR; if it does not, the covariates share a common scale and standard GWR is adequate. Also check that MGWR residuals show less spatial autocorrelation than OLS residuals using global Moran’s I.
Convergence. The back-fitting algorithm iterates until the change in the fit falls below tol_multi. If it hits max_iter_multi, the bandwidths are not settled. Increase iterations, check for collinearity, and inspect whether one bandwidth oscillates between two values — a sign that two covariates are competing to explain the same pattern.
Local collinearity. Covariates that are only mildly correlated globally can be strongly correlated within a small neighbourhood, producing unstable local coefficients with opposite signs. Compute local condition numbers from a GWR fit at the shortest MGWR bandwidth; values above 30 flag areas where individual coefficient maps should not be interpreted.
Minimum bandwidth. Letting a bandwidth fall below about 30 neighbours produces coefficients fitted to handfuls of areas. The code sets multi_bw_min=[30]; justify a different floor from the geography.
Adjusted significance. Use the per-covariate critical t-values from critical_tval(), not 1.96. They account for the effective number of tests implied by each bandwidth, so a short-bandwidth covariate gets a stricter threshold than a global one.
Computation time. MGWR scales poorly with sample size. A few thousand tracts can take hours. For larger problems, fit on a representative subset to explore, parallelise with the pool argument where available, or move up a geographic level.
Bandwidths that sit on a bound. A bandwidth that settles exactly at the minimum or maximum allowed is telling you the search wanted to go further. At the maximum, the covariate is global and nothing is lost. At the minimum, the covariate may be picking up a pattern at a finer scale than the geography supports, or absorbing residual spatial structure that belongs to an omitted variable. Refit with that covariate removed and see whether the intercept’s bandwidth shortens to take its place; if it does, the covariate was standing in for unexplained local variation rather than describing a real local relationship.
Bandwidth uncertainty. The fitted bandwidths are point estimates. A bandwidth of 47 and one of 70 may be statistically indistinguishable, and the difference between them should not be interpreted. Where the scale itself is a finding worth reporting, bootstrap the bandwidth search on resampled residuals or report the AICc profile around the chosen value, so readers can see how sharply the data prefer it.
Rates on a log scale. Since the Gaussian model is fitted to log smoothed rates, coefficients are multiplicative effects on the rate. Tracts with zero cases need a smoothed rate or an offset before logging; document which.
INFO mgwr GWR bandwidth=118 AICc=2210.4
INFO mgwr MGWR bandwidths=[204, 611, 47, 1015] AICc=2084.9
Compliance Notes
Record the outcome transformation, the covariate list and sources, the kernel and bandwidth bounds, the convergence tolerance and iterations used, per-covariate bandwidths and adjusted critical values, and the OLS, GWR and MGWR comparison.
Local coefficient maps invite causal reading — “poverty drives asthma more in this neighbourhood” — that an ecological regression cannot support. Present MGWR results as describing where associations are stronger or weaker, note that the covariates are area-level, and map coefficients only where they pass the adjusted significance threshold and local collinearity checks.
Related Topics
- Geographically Weighted Regression for Disease-Rate Modeling — the parent guide.
- Choosing Adaptive vs Fixed GWR Bandwidth — the kernel choice that applies to every MGWR term.
- Diagnosing Spatial Autocorrelation in Regression Residuals — the residual check used to compare models.