Joins & relationships
Spatial joins, attribute joins, nearest neighbour and point-in-polygon.
A join answers "which of these goes with which of those", and there are only two ways to answer it: read a key somebody already wrote down, or compute the relationship from the geometry. The first is faster and authoritative. The second works when no key exists, which is most of the time.
The surprise in spatial joins is arithmetic rather than spatial. An attribute join on a unique key is a function — each left row matches at most one right row. A spatial join is a relation, and a relation can be true several times, so a left feature that falls inside two overlapping zones comes back twice. The row count growing is not a fault; it is the join reporting every relationship it found, and the fix is deciding what one output row should mean.
The pages below run from what a join is doing, through the joins themselves — point-in-polygon, polygon overlay, nearest neighbour — to the specific ways one returns nothing, or far too much.
🧭 Start with the concepts
The ideas the how-to guides below assume you already have.
- Spatial Indexes Explained: R-trees and Why Spatial Joins Are Fast
- Spatial Predicates Explained: Intersects, Within, Contains and the Rest
- Spatial Join Cardinality Explained: One-to-One, One-to-Many and What to Do
- Nearest-Neighbour Joins Explained: Distance, Ties and Search Radius
- Attribute Join or Spatial Join? Choosing How to Combine Two Layers
- Spatial Clustering Explained: DBSCAN, K-Means and What They Assume
Show every concept in this topic
⚙️ Then do the task
Step-by-step walkthroughs with code you can copy and adapt.
- How to Filter Spatial Data in Python Using GeoPandas
- How to Join Attribute Data to a GeoDataFrame in Python
- How to Count Points in Polygons with GeoPandas
- How to Find the Nearest Point in GeoPandas
- How to Aggregate Spatial Data by Region in GeoPandas
- How to Select Features by Location in GeoPandas
Show every how-to guide in this topic
- How to Perform a Spatial Join in Python (GeoPandas)
- How to Fuzzy-Match Place Names When Joining Spatial Data in Python
- How to Run Spatial SQL Queries from Python with PostGIS
- How to Merge Near-Duplicate Features in a Spatial Dataset
- How to Enrich Many Files Against One Reference Layer
- How to Calculate Zonal Statistics in Python
- How to Do a Spatial Join in PostGIS with SQL
- How to Use the Spatial Index Directly in GeoPandas (sindex)
- How to Bin Points into Hexagons in Python
- How to Cluster Points by Location with DBSCAN in Python
- How to Calculate Moran's I in Python
- How to Snap Points to a Street Network in Python
- How to Aggregate Movement into Flows Between Zones
- How to Interpolate to Polygons Instead of a Grid
- How to Reverse Geocode Points to Addresses and Areas in Python
- How to Run a Spatial Join in DuckDB
- How to Extract a Time Series per Polygon from a NetCDF Grid
- How to Assign Points to H3 Cells in Python
- How to Join Two Point Datasets on an H3 Index Instead of a Spatial Join
- How to Join a Census Table to Its Boundaries Without Losing Rows
- How to Aggregate Survey Estimates and Their Margins of Error
- How to Compare Census Years Across Changed Boundaries
- How to Count the Population Within Reach of Each Facility
- How to Measure Distance to the Nearest Facility for Every Home
- How to Summarise Catchment Attributes: Area, Slope and Land Cover
- How to aggregate points to units that meet a minimum count
- How to find which species occur in protected areas
- How to Find All Points Within a Radius in Python
- How to Find the Nearest Coordinates With a BallTree in Python
- How to Find the Country of a Coordinate in Python
- How to Measure Airbnb Prices Against Distance to Transit in Python
- How to Calculate Street Trees per Kilometre of Street
🔧 When it breaks
The errors this topic produces, with the cause explained rather than just the command.
- Spatial Join Returns Empty Results in GeoPandas: How to Fix It
- GeoPandas Merge Returns NaN or No Matches: How to Fix It
- GeoPandas "Columns Overlap but No Suffix Specified" Error: How to Fix It
- GeoPandas Spatial Join Returns Duplicate Rows: How to Fix It
- DBSCAN Returns One Giant Cluster or Labels Everything Noise
- Spatial Weights Warn About Islands or Moran's I Returns nan
Show every fix in this topic
- Fixing Census Joins That Fail Because Leading Zeros Were Dropped
- Fixing Census Data That Does Not Match the Boundary File Year
- Election Results Won't Join the Boundaries: Codes, Wards and Renamed Places
- Why Is My Crash Death Count Bigger Than the Official Total?
- Fixing Local Authority Names That Do Not Match the Boundary File
- Fixing Airbnb Listing Coordinates That Are Not Exact
- Neighbourhood Codes That Match No Boundary: 2010 NTAs Against 2020 Files
- Taxi Zone IDs That Do Not Join: Zones 264 and 265 and Repeated Names
- Storm Events Will Not Join to Counties: Forecast Zones Are Not FIPS Codes
- FEMA Declarations That Will Not Join to Counties: Statewide and Tribal Rows
Learning paths through this topic
A curated route rather than a list — the guides in the order that builds on itself.
- Spatial Statistics
- Networks & Routing
- Raster Analysis
- Spatial SQL & PostGIS
- GIS Data Cleaning
- Batch Processing
- Movement & Time
- Interpolation & Surfaces
- Geocoding & Addresses
- DuckDB for Spatial Data
- Gridded & Climate Data
- H3 & Global Grids
- Census & Demographics
- Location & Accessibility
- Hydrology & Watersheds
- Geoprivacy & Anonymisation
- Biodiversity & Species Data
Related topics
Subjects that keep coming up alongside this one, most overlap first.