Distance-Band Weights for Irregular County Sizes

This guide, part of Spatial Weights Matrix Construction for Public Health Surveillance, deals with the distance-band weights problem that appears whenever a study area mixes small, dense units with large, sparse ones — which is almost every state-wide or national county analysis.

Problem Context & Constraints

Distance-band weights declare two areas neighbours when their centroids lie within a fixed distance. The rule is simple, symmetric and interpretable, and it is the natural choice when the process under study has a physical range — a vector’s flight distance, a commuting radius, an airborne plume. Its weakness is that one distance means very different things in different parts of a heterogeneous study area.

Take US counties. In the east, county centroids sit twenty to forty kilometres apart; in the interior west, a hundred kilometres or more. A 50 km band gives an eastern county a dozen neighbours and a western county none. A 150 km band connects the west and gives each eastern county sixty neighbours, smoothing its local statistic into a regional average and erasing exactly the local variation a cluster analysis is looking for.

Three constraints shape a defensible choice. Every unit needs at least one neighbour, or local statistics are undefined for it and global statistics silently exclude it. The number of neighbours should not vary by an order of magnitude across the study area, because a local statistic computed over sixty neighbours and one computed over two are not comparable. And the threshold should be justified — by the process’s range, or by the geometry of the units — rather than chosen until the map looks reasonable.

One Distance Means Different Things East and West Two regions drawn at the same scale. In the eastern region small counties are packed closely, and a fifty-kilometre band around one centroid captures twelve neighbouring centroids. In the western region large counties are spread widely, and the same fifty-kilometre band around a centroid captures none, leaving that county an island. Widening the band until the western county has a neighbour would give the eastern county dozens, smoothing its local statistic into a regional average. The same 50 km band: twelve neighbours in one place, none in another Eastern counties 12 neighbours Western counties 0 neighbours: an island widening the band to fix the west smooths the east into a regional average

Prerequisites

  • python 3.11
  • libpysal 4.10, geopandas 0.14.4, numpy 1.26.4, scipy 1.13.1
  • Area polygons in a projected metric coordinate reference system, so distances are metres
  • A representative point per area — a population-weighted centroid is better than a geometric one for irregular counties
  • Where the process has a known range, that range as a candidate threshold

Step-by-Step Solution

Find the minimum threshold that connects every unit, inspect the neighbour-count distribution it produces, and compare against adaptive k-nearest weights before choosing.

# Distance-band weights with guaranteed connectivity and a neighbour-count audit.
# Pinned: libpysal==4.10, geopandas==0.14.4, numpy==1.26.4, scipy==1.13.1
import logging

import numpy as np
import geopandas as gpd
from libpysal.weights import DistanceBand, KNN
from libpysal.weights.util import min_threshold_distance

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("weights.band")


def representative_points(areas: gpd.GeoDataFrame, pop_centroid_col: str | None = None):
    if areas.crs is None or areas.crs.is_geographic:
        raise ValueError("distance weights need a projected CRS in metres")
    areas = areas.sort_values("geoid").reset_index(drop=True)
    pts = areas[pop_centroid_col] if pop_centroid_col else areas.geometry.representative_point()
    return areas, np.c_[pts.x.to_numpy(), pts.y.to_numpy()]


def neighbour_audit(w, label: str) -> dict:
    card = np.array(list(w.cardinalities.values()))
    out = {"label": label, "islands": int((card == 0).sum()),
           "min": int(card.min()), "median": float(np.median(card)),
           "max": int(card.max()), "ratio_max_median": float(card.max() / max(np.median(card), 1))}
    log.info("%s: islands=%d neighbours min=%d median=%.0f max=%d (max/median %.1f)",
             label, out["islands"], out["min"], out["median"], out["max"],
             out["ratio_max_median"])
    return out


def build_band_weights(areas: gpd.GeoDataFrame, process_range_m: float | None = None,
                       pop_centroid_col: str | None = None):
    areas, xy = representative_points(areas, pop_centroid_col)
    d_min = float(min_threshold_distance(xy))       # smallest band with no islands
    threshold = max(d_min, process_range_m or 0.0)
    log.info("minimum connecting threshold %.0f m; chosen %.0f m%s", d_min, threshold,
             " (process range)" if process_range_m and process_range_m >= d_min else "")
    w_band = DistanceBand(xy, threshold=threshold, binary=True, silence_warnings=True)
    w_band.transform = "r"
    band = neighbour_audit(w_band, f"band {threshold/1000:.0f} km")

    # Adaptive comparison: every unit gets exactly k neighbours regardless of size.
    k = max(4, int(round(band["median"])))
    # k-nearest is asymmetric; symmetrize() returns a NEW weights object.
    w_knn = KNN(xy, k=k).symmetrize()
    w_knn.transform = "r"
    knn = neighbour_audit(w_knn, f"knn k={k}")
    return {"band": w_band, "knn": w_knn, "audit": [band, knn], "threshold_m": threshold}

Starting from the minimum connecting threshold, rather than a round number, is what guarantees the connectivity constraint while using the smallest band that achieves it. The neighbour-count audit then shows the cost: if the ratio of maximum to median neighbours is large, the band that connects the sparse areas is over-smoothing the dense ones, and adaptive weights are likely the better choice.

Using a population-weighted centroid rather than a geometric one matters for large irregular counties. A county whose population lives in one town at its eastern edge has a geometric centroid in empty land, and distances measured from it do not describe where the cases are.

Neighbour Counts Under the Two Schemes Two distributions of neighbour counts across the counties of a heterogeneous state. The distance band set at the minimum connecting threshold produces a long right tail: most counties have a handful of neighbours but dense eastern counties have forty or more. The k-nearest scheme gives every county exactly the same number, eliminating the tail. The ratio of the maximum to the median count is the single number that summarises how uneven the band is, and a ratio above about five suggests the adaptive scheme. A long tail of neighbour counts means the band is over-smoothing distance band: long tail k-nearest: all equal 2 40+ k number of neighbours per county

Validation & Edge Cases

A process range below the connecting threshold. If the known range of the process is shorter than the distance needed to connect every unit, the band cannot be both physically meaningful and connected. Use the process range and handle the islands explicitly, following Handling Island Polygons in Spatial Weights, rather than inflating the band.

Asymmetric k-nearest weights. A k-nearest relationship is not symmetric — A may be among B’s nearest while B is not among A’s. Many statistics assume symmetry; symmetrise explicitly and record that the neighbour counts are then at least k rather than exactly k.

Centroids outside their polygon. A crescent-shaped or multipart county can have a geometric centroid that lies outside it, sometimes inside a neighbour. Distances from that point misstate the county’s position. A representative point, which is guaranteed to lie inside, or a population-weighted centroid avoids the problem; check for centroids that fall outside their own polygon and replace them.

Coastal and border units. Counties on a coastline or a state line have neighbours on one side only, so any band gives them fewer neighbours than interior units of the same size. That edge effect is real and structural. Either accept it and report it, or include the neighbouring jurisdiction’s units as unanalysed context so the edge counties see a complete neighbourhood.

Re-deriving the threshold every run. The minimum connecting distance depends on the set of units. Recomputing it nightly means a new or split tract silently changes every unit’s neighbours. Fix the threshold per boundary vintage, store it with the weights, and change it only with the vintage.

Row standardisation hiding the problem. Row-standardised weights make every unit’s weights sum to one, which disguises that one unit averages over sixty neighbours and another over two. Audit the binary cardinalities before standardising.

Inverse-distance weights with a band. Distance-decay weights inside the band reduce the influence of far neighbours but do not fix the count disparity. They are a refinement on top of the band choice, not a substitute for it.

Sensitivity of the conclusion. Run the downstream statistic under both schemes. Clusters that appear under both are robust; clusters that appear only under the wide band are often products of its over-smoothing, the same instability explored in Detecting Hotspot Instability Across Weight Specifications.

INFO weights.band minimum connecting threshold 118400 m; chosen 118400 m
INFO weights.band band 118 km: islands=0 neighbours min=1 median=9 max=58 (max/median 6.4)
INFO weights.band knn k=9: islands=0 neighbours min=9 median=9 max=9 (max/median 1.0)
Choosing the Scheme A two-question decision. If the process has a known physical range and that range connects every unit without an extreme neighbour-count ratio, a distance band at that range is the interpretable choice. If the range is unknown, or the connecting band produces a maximum-to-median neighbour ratio above about five, adaptive k-nearest weights are preferred, with symmetrisation recorded. Either way, the downstream statistic is run under both schemes and clusters that depend on one scheme are reported as unstable. Physical range and neighbour balance decide it known process range? and it connects everyone max/median under 5? neighbour balance distance band at the process range adaptive k-nearest symmetrised, recorded whichever is chosen, rerun the statistic under the other and report unstable clusters

Compliance Notes

Record the representative point used for each area, the minimum connecting threshold, the chosen threshold and its justification, the neighbour-count audit for every scheme considered, the symmetrisation applied to any k-nearest weights, and the sensitivity comparison of downstream results.

A weights matrix is an analytic choice that changes which areas are flagged, so it belongs in the methods of any published cluster finding with enough detail to rebuild it. Storing the matrix itself alongside the analysis — it is small and contains no protected data — lets a reviewer confirm the neighbour structure directly rather than re-deriving it from a description.