24  Regionalization

Code
import contextily as ctx
import geopandas as gpd
import matplotlib.pyplot as plt
from geosnap import DataStore
from geosnap import analyze as gaz
from geosnap import io as gio
from geosnap import visualize as gvz

%load_ext watermark
%watermark -a  'eli knaap' -iv -d -u
OMP: Info #276: omp_set_nested routine deprecated, please use omp_set_max_active_levels instead.
Author: eli knaap

Last updated: 2025-11-25

matplotlib: 3.10.8
geosnap   : 0.15.3
geopandas : 1.1.1
contextily: 1.6.2

As with geodemographic clustering, when carrying out a regionalization exercise, we are searching for groups of observations (census tracts in this case) which are similar in socioeconomic and demographic composition. If the goal of geodemographics is to identify neighborhood types, that could exist anywhere in the region, the goal of regionalization is to identify specific neighborhoods that exist at a distinct place in the region

Following that concept, we can use constrained clustering to develop an empirical version of geographically-bounded neighborhoods, where the neighborhoods are defined by internal social homogeneity. This is similar to the historic and well-defined neighborhood zones in places like Chicago and Pittsburgh. If your goal were to try and delineate neighborhoods in a bespoke region as the LA Times tried to do, regionalization would be a data-driven way to do so.

Chicago Neighborhoods

Chicago Neighborhoods

https://en.wikivoyage.org/wiki/File:Chicago_neighborhoods_map.png

Pittsburgh Neighborhoods

Pittsburgh Neighborhoods

https://www.visitpittsburgh.com/neighborhoods

Code
datasets = DataStore()
la = gio.get_acs(datasets, county_fips="06037", years=2021, level="tract")
la = la.to_crs(la.estimate_utm_crs())

la[["median_household_income", "geometry"]].explore(
    "median_household_income",
    scheme="quantiles",
    k=8,
    cmap="magma",
    tiles="CartoDB Positron",
)

columns = [
    "median_household_income",
    "median_home_value",
    "p_edu_college_greater",
    "p_edu_hs_less",
    "p_nonhisp_white_persons",
    "p_nonhisp_black_persons",
    "p_hispanic_persons",
    "p_asian_persons",
]
la.dropna(subset=columns).plot()
/Users/knaaptime/miniforge3/envs/urban_analysis/lib/python3.12/site-packages/geosnap/io/util.py:273: UserWarning: Unable to find local adjustment year for 2021. Attempting from online data
  warn(
/Users/knaaptime/miniforge3/envs/urban_analysis/lib/python3.12/site-packages/geosnap/io/constructors.py:218: UserWarning: Currency columns unavailable at this resolution; not adjusting for inflation
  warn(

24.1 Cross-Sectional Regionalization

Similar to Chapter 23, we begin by loading Census data (this time from the Los Angeles metropolitan region), then generate a typology or set of regions by passing a collection of variables to the regionalize function from geosnap. In this instance, we will create 25 regions using spatially-constrained hierarchical clustering (using Ward’s linkage). Since the regionalization technique enforces a spatial constraint, we also need to pass a libpysal Graph or spatial weights object; alternatively, we can pass the name of a contiguity Graph, like “rook” or “queen”.

Code
la_ward_reg, la_ward_model = gaz.regionalize(
    gdf=la,
    method="ward_spatial",
    n_clusters=25,
    columns=columns,
    return_model=True,
    spatial_weights="queen",
)
la_ward_reg[columns + ["geometry", "ward_spatial"]].explore(
    "ward_spatial", categorical=True, cmap="Accent", tiles="CartoDB Positron"
)
/Users/knaaptime/miniforge3/envs/urban_analysis/lib/python3.12/site-packages/geosnap/analyze/geodemo.py:406: FutureWarning: `use_index` defaults to False but will default to True in future. Set True/False directly to control this behavior and silence this warning
  w0 = W.from_dataframe(df, **weights_kwargs)
/Users/knaaptime/miniforge3/envs/urban_analysis/lib/python3.12/site-packages/libpysal/weights/contiguity.py:347: UserWarning: The weights matrix is not fully connected: 
 There are 4 disconnected components.
 There are 2 islands with ids: 568, 1944.
  W.__init__(self, neighbors, ids=ids, **kw)
Make this Notebook Trusted to load map: File -> Trust Notebook