Choropleth with spatial weights
A map of one variable with the neighbour network drawn on top — the structure every spatial statistic relies on.

Every spatial statistic starts by deciding who is a neighbour of whom. This chart shows that decision before any test is run: a choropleth of one variable, with a line joining the centres of every pair of neighbouring areas. Here neighbours share a border or a corner (queen contiguity), and each row of the weights matrix is later standardised so that an area's "spatial lag" is the average of its neighbours. Other choices — rook contiguity, the k nearest neighbours, a distance band — give different networks and can change the results, so the network is worth looking at.
Data are André-Michel Guerry's Essay on the Moral Statistics of France
(1833): 85 départements around 1830, as prepared by the Center for Spatial
Data Science and loaded with libpysal (examples.load_example("Guerry"),
fetched once and cached). The variable is the percentage of military
conscripts who could read and write. Weights come from
libpysal.weights.Queen; the map is drawn with GeoPandas.
import geopandas as gpd
import matplotlib.pyplot as plt
import numpy as np
from libpysal import examples, weights
# Data: 85 French départements, c. 1830 (Guerry, 1833)
g = gpd.read_file(examples.load_example("Guerry").get_path("guerry.shp"))
y = g["Litercy"] # % of conscripts who can read and write
# Queen contiguity: neighbours share a border or a corner
w = weights.Queen.from_dataframe(g, use_index=False)
cards = np.array(list(w.cardinalities.values()))
xy = np.column_stack([g.centroid.x, g.centroid.y])
fig, ax = plt.subplots(figsize=(8, 7.4))
g.plot(column=y, cmap="Blues", edgecolor="white", linewidth=0.6, ax=ax, legend=True,
legend_kwds=dict(label="Conscripts who can read and write (%)", shrink=0.6, pad=0.01))
for i, nbrs in w.neighbors.items():
for j in nbrs:
if i < j: # draw each link once
ax.plot(*xy[[i, j]].T, color="#FF9500", lw=0.8, alpha=0.8)
ax.scatter(*xy.T, s=10, color="#1D1D1F", zorder=3)
ax.set_axis_off()
ax.set_title(f"Each département has {w.mean_neighbors:.1f} neighbours on average", pad=26)
ax.text(0, 1.02, f"Queen contiguity · n = {w.n} départements · {int(w.s0 / 2)} links · "
f"{cards.min()} to {cards.max()} neighbours · Guerry (1833)",
transform=ax.transAxes, fontsize=9.5, color="#6E6E73")
plt.show()
Figures use the optional PyStatlas style: add plt.style.use("pystatlas.mplstyle") to match the look exactly.
Data source
- Guerry's moral statistics of France, c. 1830 (85 départements). Guerry, A.-M. (1833). Essai sur la statistique morale de la France. Crochard. Data as compiled in Friendly, M. (2007). A.-M. Guerry's moral statistics of France: Challenges for multivariable spatial analysis. Statistical Science, 22(3), 368–399. https://doi.org/10.1214/07-STS241
Prepared by the Center for Spatial Data Science (University of Chicago) from the R package Guerry (Friendly and Dray), provided “as is”. Fetched once and cached by libpysal (examples.load_example("Guerry")). Variables come from Guerry's tables as translated by Whitt and Reinking (2002).
The examples illustrate statistical methods. Their titles describe what each figure shows in these particular data — often a small, dated or partial sample — not the author's position on the substantive question. For interpretation, context and data quality, rely on the original sources cited here.
How to cite
PyStatlas is created and maintained by Thierry Warin. If it helped your work, please cite it.
Warin, T. (2026). Choropleth with spatial weights. In PyStatlas: A Python Gallery of Statistical Graphics for the Social Sciences. https://warint.github.io/pystatlas/spatial-data/spatial-weights/
@misc{warin2026statlas,
author = {Warin, Thierry},
title = {PyStatlas: A Python Gallery of Statistical Graphics for the Social Sciences},
year = {2026},
howpublished = {\url{https://warint.github.io/pystatlas/}},
note = {Source code: https://github.com/warint/pystatlas}
}More in Spatial data
Moran scatterplot (global autocorrelation)
Each area against the average of its neighbours, with Moran's I, its permutation test and the reference distribution.
spdep::moran.plot() + spdep::moran.mc()LISA cluster map (local autocorrelation)
Where the clusters and spatial outliers are — local Moran's I for each area, mapped with its permutation significance.
spdep::localmoran_perm() / rgeoda::local_moran()Spatial correlogram
Moran's I at increasing neighbour orders, with a permutation envelope — how far spatial clustering reaches.
spdep::sp.correlogram(method = "I")