Skip to content
Research Atlas

Spatial Analysis & GIS · Lesson 2 of 4

Spatial Autocorrelation

~16 min

The Concept

Before you interpolate anything, you need to check whether there actually is spatial structure in the Bluewater Basin nitrate data, or whether the well values are randomly scattered with no spatial pattern. If nitrate concentrations are spatially random, kriging would be meaningless. If they're spatially clustered, high near high, low near low, kriging will work.

Your supervisor introduces the test for this: Moran's I (a single number that says whether nearby wells look more alike than random chance would explain), a statistic that measures whether nearby wells are more similar than you'd expect by chance. You run it on the fifteen well values and get I = 0.71, with a p-value of 0.003. High positive value, highly significant: the basin's nitrate is spatially clustered, not random.

Moran's I ranges from -1 to +1. Positive values (like our 0.71) mean nearby locations tend to have similar values, spatial clustering. Negative values mean nearby locations tend to have dissimilar values, a checkerboard pattern. Zero means no spatial pattern at all.

The p-value from a permutation test answers: if you randomly shuffled all the well values among the fifteen locations, how often would you get a Moran's I this high or higher? With p=0.003, it would happen by chance only 0.3% of the time, strong evidence that the spatial pattern is real, not coincidental.

The Analogy

Imagine shuffling a deck of cards labeled with everyone's height and dealing them out to random seats in a classroom. If tall kids still ended up sitting mostly next to other tall kids, you would be suspicious the shuffle did not really happen at random. That suspicion is exactly what the p-value here is measuring.

The Math (optional)

Moran's I compares each pair of locations using a spatial weights matrix W, where w_ij encodes how 'connected' locations i and j are (often 1 if they're close, 0 otherwise):

Moran's I

I=nijwijijwij(zizˉ)(zjzˉ)i(zizˉ)2I = \frac{n}{\sum_{i}\sum_{j} w_{ij}} \cdot \frac{\sum_{i}\sum_{j} w_{ij}(z_i - \bar{z})(z_j - \bar{z})}{\sum_i (z_i - \bar{z})^2}

z_i and z_j are nitrate values at wells i and j; z̄ is the mean across all wells; w_ij is 1 if the wells are considered 'neighbours' and 0 otherwise. I ≈ +1 when neighbours have similar values; I ≈ -1 when they're dissimilar.

Try It

Toggle between nitrate, dissolved oxygen, and conductivity. Does the spatial pattern look clustered or random for each variable?

Agricultural zone010203040506070809101112131415
2 mg/L
14 mg/L

Hover any well to see its reading. Notice how nitrate concentrations are highest near the agricultural zone (northeast), while dissolved oxygen is lowest there — a classic signature of nutrient contamination.

Code (optional)

spatial_autocorrelation.pypython
import libpysal
from esda.moran import Moran
import geopandas as gpd

wells_gdf = gpd.read_file("bluewater_wells.geojson")

# Build a spatial weights matrix: each well 'neighbours' its k nearest wells
w = libpysal.weights.KNN.from_dataframe(wells_gdf, k=4)
w.transform = "r"  # row-standardise: each row sums to 1

# Moran's I for nitrate
moran = Moran(wells_gdf["nitrate_mgl"], w, permutations=999)
print(f"Moran's I: {moran.I:.4f}")
print(f"p-value (permutation): {moran.p_sim:.4f}")
print(f"z-score: {moran.z_sim:.4f}")

Line by line

  • KNN.from_dataframe builds the weights matrix by connecting each well to its 4 nearest neighbours, a common choice for sparse monitoring networks.
  • Row-standardising (transform='r') makes each well's neighbours contribute equally regardless of how many they have.
  • permutations=999 means we randomly shuffle the data 999 times to build the null distribution, the p_sim value tells us how often a random shuffle beats our observed I.
  • A z-score above ~2.0 confirms the pattern is statistically significant.

Why Real Researchers Care

Moran's I is routinely used to diagnose spatial autocorrelation before any spatial model is built. It's also used to check residuals after a model is fitted, if the residuals are still spatially correlated, the model hasn't fully captured the spatial structure, and its confidence intervals will be too narrow.

Quick Check

Q1. Moran's I = 0.71 for Bluewater Basin nitrate. What does this mean?

Your Goal

If Moran's I for Bluewater Basin nitrate were 0.02 with p = 0.62, what would that tell you about whether kriging was appropriate, and what would you do instead?

Hint: Low I, high p-value means no detectable spatial pattern. If location doesn't predict the value, distance-weighted interpolation won't work well.

Teach It Back

Explain why you'd bother checking spatial autocorrelation before interpolating, rather than just kriging directly.

Previous lesson