Infryne TechWorks logo
INFRYNE TechWorks
◈ GeoAI · Machine Learning · Spatial Science

Spatial Data Leakage in Machine Learning: How to Fix It

Random train/test splits can inflate GeoAI accuracy. Learn how spatial autocorrelation causes leakage and how to validate properly.

Tahira SiddiqueFounder & Head of Spatial Science, AI & ML20 min read
Diagram showing spatial data leakage and evaluation partitions
← Back to Blog
◈ Executive Technical Summary

Standard random train/test splits violate the independent and identically distributed (i.i.d.) assumption whenever geographic observations exhibit spatial autocorrelation. This causes spatial data leakage—rewarding models for memorizing local spatial noise and producing artificially inflated accuracy metrics that collapse in production. For teams building production geospatial intelligence, our Spatial MLOps and GeoAI Consulting implement spatial cross-validation, Moran’s I residual testing, and spatial block partitioning. Audit your project readiness with our interactive GeoAI Feasibility Checklist.

The Lie Your Model Tells

Consider an illustrative production scenario. A team trains a churn model on 200,000 customer records, uses a random 80/20 split, and reports a test AUC of 0.91. Six months after deployment, performance in newly served regions is 0.67 — even though the preprocessing and model code are unchanged.

The hidden difference is geography. Nearby customers from the same cities, neighbourhoods, and service areas appeared in both training and test data. The evaluation therefore measured interpolation among familiar neighbours, while deployment required extrapolation to unfamiliar places.

Core Insight

The model was not tested on the geographic problem it would face after deployment.

This is spatial leakage: dependence across nearby observations makes a nominally held-out test set less independent than it appears. Research on spatially structured data shows that ordinary random cross-validation can produce optimistic performance estimates when the intended prediction domain differs geographically from the training sample. Roberts et al. review the underlying validation problem, while a later spatial k-fold study demonstrates it across real datasets.

Tobler’s First Law — and Why Your Model Does Not Know It

Waldo Tobler’s foundational 1970 paper states: "Everything is related to everything else, but near things are more related than distant things." The original publication captures the intuition behind spatial autocorrelation.

Standard supervised-learning workflows often treat rows as independent and identically distributed. Geographic observations may instead form correlated neighbourhoods, environmental zones, markets, or service regions. A random split preserves those local relationships across both sides of the evaluation boundary.

⬡ Interactive · Reveal the Geographic Structure
WITHOUT GEOGRAPHYWITH GEOGRAPHY

Fig. 1 — A synthetic teaching example. Coordinates reveal clusters that a row-only view conceals.

How Leakage Happens

Imagine a property model evaluated with a random split inside one city. Test properties share school catchments, transport access, flood exposure, and neighbourhood character with training properties a few streets away. The model has not seen the exact test rows, but it has seen much of their local context.

If the deployment target is a new city, that validation design answers the wrong question. The evaluation protocol must reproduce the separation between the available training geography and the intended prediction geography.

◈ Train / Test Split Comparison
Test cells are interleaved with training cells, so nearby observations cross the evaluation boundary.
Train Set Test Set

Measuring Spatial Autocorrelation with Moran’s I & LISA

Moran’s I summarizes spatial correlation by comparing how deviations of neighboring feature values covary compared to the total sample variance. Formally:

I = (N / ∑ w_ij) × [∑_i ∑_j w_ij (y_i - μ) (y_j - μ)] / [∑_i (y_i - μ)²]
Global Moran’s I

Ranges from -1 (checkerboard dispersion) to +1 (perfect clustering). An expected value under spatial randomness is E[I] = -1 / (N - 1) ≈ 0. When evaluated on ML residuals, I > 0 proves the model is leaking location.

Anselin Local Moran’s I (LISA)

Decomposes global autocorrelation into local spatial clusters: High-High (hotspots), Low-Low (coldspots), and spatial outliers (High-Low and Low-High) indicating localized regional shifts.

Interactive · Computed Moran’s I on Synthetic Grid
Spatial Structure:70%
Moran's I0.55
InterpretationStrong Positive
from libpysal.weights import Queen
from esda.moran import Moran, Moran_Local
import geopandas as gpd

gdf = gpd.read_file("observations.geojson")
# Generate spatial weights matrix with row-standardization
w = Queen.from_dataframe(gdf)
w.transform = "r"

# Audit model residuals for spatial clustering
residuals = y_test - model.predict(X_test)
moran = Moran(residuals, w)
print(f"Residual Moran's I: {moran.I:.4f} (p-value: {moran.p_sim:.4f})")
# If p < 0.05, the model's errors are not independent; spatial leakage has occurred.

Buffered Spatial Cross-Validation: The Dead-Zone Guarantee

Even when partitioning data into spatial blocks, points sitting right along fold boundaries still exhibit strong spatial autocorrelation. Buffered Spatial Cross-Validation enforces an exclusion buffer zone around the test fold:

import geopandas as gpd
from shapely.ops import unary_union

def buffered_spatial_train_test_split(gdf, test_polygon, buffer_distance_meters=2000):
    """Drops training observations within buffer_distance of the test boundary."""
    # Isolate test set within geographic target boundary
    test_mask = gdf.geometry.within(test_polygon)
    test_gdf = gdf[test_mask].copy()
    
    # Create exclusion dead-zone buffer around the test set
    test_boundary_union = unary_union(test_gdf.geometry)
    exclusion_zone = test_boundary_union.buffer(buffer_distance_meters)
    
    # Train set strictly excludes test points AND points within the buffer
    train_gdf = gdf[~gdf.geometry.intersects(exclusion_zone)].copy()
    
    print(f"Test samples: {len(test_gdf)} | Train samples: {len(train_gdf)} | Dropped buffer samples: {len(gdf) - len(test_gdf) - len(train_gdf)}")
    return train_gdf, test_gdf

Hierarchical H3 Hexagons & Positional Embeddings

Passing raw (latitude, longitude) into decision trees forces orthogonal axis splits that overfit bounding rectangles. Production GeoAI systems encode spatial context using discrete Uber H3 hexagons or multi-frequency sinusoidal embeddings:

import h3
import numpy as np

def add_hierarchical_spatial_features(df, lat_col="lat", lon_col="lon"):
    # Multi-resolution hexagonal spatial tokens
    df["h3_res6"] = df.apply(lambda r: h3.geo_to_h3(r[lat_col], r[lon_col], resolution=6), axis=1) # ~36 km²
    df["h3_res8"] = df.apply(lambda r: h3.geo_to_h3(r[lat_col], r[lon_col], resolution=8), axis=1) # ~0.7 km²
    
    # Spherical multi-frequency harmonic coordinate embedding (Sphere2Vec pattern)
    lat_rad = np.radians(df[lat_col].values)
    lon_rad = np.radians(df[lon_col].values)
    
    for freq in [1, 2, 4, 8, 16]:
        df[f"sin_lat_{freq}"] = np.sin(freq * lat_rad)
        df[f"cos_lat_{freq}"] = np.cos(freq * lat_rad)
        df[f"sin_lon_{freq}"] = np.sin(freq * lon_rad)
        df[f"cos_lon_{freq}"] = np.cos(freq * lon_rad)
        
    return df

Five-Step Action Plan

StepFocusImplementation
01Audit Spatial DependenceCompute Moran’s I on both raw target and baseline model residuals.
02Map Every SplitVisually verify whether nearby points cross the training and test boundaries.
03Rebuild EvaluationUse spatial blocks, geographic buffering, or leave-region-out cross-validation.
04Ablate Location FeaturesRetain coordinates only when they improve spatial CV, not merely random CV.
05Monitor Production DriftTrack model calibration and error distributions by geographic subregion.

Is Your Model Geographically Honest?

Audit Spatial Dependence & Leakage

Infryne TechWorks audits spatial dependence, evaluation leakage, prediction support, and geographic drift in production ML systems.

Frequently Asked Questions

What is spatial data leakage and why does it happen in machine learning?

Spatial data leakage occurs when standard random cross-validation distributes spatially autocorrelated observations across both training and test sets. Because Tobler's First Law states near things are more related than distant things, training samples near test samples allow the model to memorize local spatial noise rather than learning genuine causal patterns, generating inflated validation metrics that collapse upon deployment.

How do you test model residuals for spatial autocorrelation using Moran’s I?

After fitting a model, compute the prediction residuals (y_true - y_pred) and calculate Global Moran's I using spatial weights matrices (via PySAL/esda). If Moran's I is significantly positive (p < 0.05), the model residuals are spatially clustered, indicating the model violates the independent and identically distributed (i.i.d.) assumption and is leaking location.

What is the difference between Spatial Block Cross-Validation and Buffered Cross-Validation?

Spatial Block Cross-Validation groups data into contiguous geographic blocks (using GroupKFold). Buffered Cross-Validation enforces an additional exclusion zone around the test fold, dropping training points within the range of autocorrelation to guarantee complete statistical independence between folds.

How should raw coordinates (latitude/longitude) be encoded in machine learning models?

Directly feeding raw coordinates into decision trees forces unnatural axis-aligned cuts that overfit geography. Best practices include converting coordinates to sinusoidal multi-frequency positional embeddings (Sphere2Vec), aggregating points into discrete hierarchical spatial bins (Uber H3 hexagons), or computing spatial lag features.

Primary Sources & Datasets

Continue Reading

More engineering research from Infryne TechWorks

Remote Sensing
Wildfire Burn Scar Mapping with Sentinel-2 & Landsat

Wildfire burn scar mapping using Sentinel-2 and Landsat 8/9 — NBR, BAIS2, RBR spectral indices, NASA FIRMS active fire integration, and the Infryne TechWorks satellite analytics platform.

18 min read
Urban Heat
Heat Is Not Distributed Fairly: Mapping Thermal Inequality in Karachi

A 10-year Landsat archive study overlaying land surface temperature with census wards, income proxies, and tree canopy to map spatial heat burden in Karachi.

24 min read
Remote Sensing
From Raw Satellite Pixels to a Working NDVI Alert Pipeline

A production walkthrough of Sentinel-2 L2A, cloud masking, median composites, spatial validation, CNN inference, and field-ready crop stress alerts.

22 min read
Frontend GIS
MapLibre GL JS: Performance Guide for Large Datasets

Render 100,000+ geometries at 60 FPS in MapLibre GL JS without freezing the UI. Master vector tiles, PMTiles, WebGL clustering, and feature-state memory tuning.

15 min read
Cloud Engineering
Scaling Geospatial Pipelines: From 10 Files to 10,000 in AWS Batch

A production field guide to AWS Batch array jobs, container memory failures, IAM roles, S3 request smoothing, and scaling imagery processing to 10,000 tiles.

9 min read
GIS Engineering
Cloud-Native Web Mapping Architecture: A Practical Guide

How to design scalable web maps with PostGIS, vector tiles, PMTiles, and MapLibre. Architecture patterns, trade-offs, and costs.

18 min read
GeoAI
Scaling Spatial Pipelines: Why Your Location Queries Are Crashing

A field guide to PostGIS storefront architecture, ST_DWithin indexing, and the decisions that pull spatial systems back from the edge.

8 min read