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.

One Bandwidth Versus One per Covariate On the left, standard GWR applies one medium bandwidth circle to all three covariates: temperature, housing age and insurance coverage. On the right, multiscale GWR gives temperature a very wide bandwidth reflecting a regional effect, housing age a narrow bandwidth reflecting neighbourhood variation, and insurance coverage a bandwidth covering the whole study area, meaning its effect is effectively constant. Each relationship at its own scale GWR: one bandwidth all covariates bw = 120 neighbours MGWR: one per covariate housing age bw 45 temperature bw 610 insurance bw = all a bandwidth near the full sample means the effect is effectively global

Prerequisites

  • python 3.11
  • numpy 1.26.4, pandas 2.2.2, geopandas 0.14.4
  • mgwr 2.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.

Reading the Bandwidths Horizontal bars show each term's bandwidth as a share of the sample, from zero to one. Housing age has a share of about zero point zero five, meaning strong local variation to be mapped. The intercept has about zero point two, meaning regional variation in baseline risk. Temperature has about zero point six, a broad regional gradient. Insurance coverage reaches the full sample, meaning a global effect to report as one coefficient. Bandwidth as a share of the sample housing age 0.05 map it intercept 0.20 regional temperature 0.60 broad gradient insurance 1.00 global 0 1 short bandwidths are the ones worth mapping

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
Is MGWR Worth It Here? Three boxes compare model fits. Ordinary least squares has an AICc of two thousand four hundred and residual Moran's I of zero point three one. GWR improves to an AICc of two thousand two hundred and ten with residual Moran's I of zero point one two. MGWR improves further to an AICc of two thousand and eighty-five with residual Moran's I of zero point zero four. Both improvements are large enough to justify the more complex model. Fit and residual clustering, three models OLS AICc 2400 residual I 0.31 GWR AICc 2210 residual I 0.12 MGWR AICc 2085 residual I 0.04 report all three; the added complexity has to earn its place

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.