Interpolating Soil Moisture with Kriging
A soil probe gives you a number at one point. A field has thousands of points you didn’t probe. Kriging is the answer to “what’s the value everywhere else, and how sure am I.” It’s named after Danie Krige, a South African mining engineer who developed it in the 1950s to estimate ore grade between drill holes. Today it’s used just as often for soil moisture, pH, and pollutant concentration: anything measured at scattered locations, where a full map matters more than the individual readings.
This is also the first article on this site where Python doesn’t run live. Read on for why, and what it means for how this page is built.
Prefer to skip straight to a result? The standalone tool runs the same computation: upload your own file, pick the column, and it predicts the surface for you.
1. Upload your file
A point file (.geojson, or a shapefile as a single .zip or as .shp/.dbf/.shx selected together) with a numeric column: soil moisture, pH, anything measured at scattered locations.
2. Why fit a variogram, instead of just averaging nearby points?
The simplest way to fill in the gaps is Inverse Distance Weighting (IDW): average the nearby points, weighting closer ones more, with the weight falling off by some fixed power of distance (usually 2). It’s fast and easy to reason about, and this site’s Buffer/Proximity tool family uses distance the same simple way. It has a real limitation, though: the decay rate is assumed, not measured. Whether moisture actually changes fast or slowly with distance in this field, at this time, is never asked.
Kriging asks it, in three steps this article builds up one at a time:
- The empirical variogram: for every pair of sample points, compute the squared difference in their values and the distance between them, then average those squared differences within distance bins. This directly measures how different two points tend to be as a function of how far apart they are. Pairs close together should differ less than pairs far apart, if there’s any spatial structure to find at all.
- Fitting a model: a smooth curve (spherical, exponential, or Gaussian; this article uses spherical) is fit to those binned points by nonlinear least squares. The fitted curve has three numbers worth knowing:
- Nugget: the curve’s value at distance zero. Two points right on top of each other should be identical, so a nugget above zero means measurement noise or small-scale variability the model can’t (and shouldn’t try to) explain away.
- Partial sill: how much more the curve rises as distance grows. Nugget plus partial sill gives the total sill: the variance you’d expect between two points so far apart they’re basically unrelated.
- Range: the distance at which the curve flattens out and reaches that total sill. Beyond the range, two points tell you nothing about each other; within it, they do, more so the closer they are.
- Kriging itself: using that fitted model as the actual weighting rule instead of an assumed power law, it predicts a value at every point on a grid, as a weighted combination of the sample points where the weights come from the model. Solving for those weights also produces a prediction variance at every grid point: a real “how sure am I,” which IDW has no equivalent for at all.
4. Compute: fit the model in R — runnable here; Python — reference only
This code doesn’t run on this page. It’s real, working pykrige, the desktop/server equivalent of the R cell to the left. See why Python can’t run here.
import numpy as np
from pykrige.ok import OrdinaryKriging
# Reference implementation only -- not executable in this browser
# tool. Constructing OrdinaryKriging() already does BOTH R steps
# above in one call: it computes the empirical variogram internally,
# then fits the model to it.
ok = OrdinaryKriging(
lon, lat, values,
variogram_model="spherical",
verbose=False,
enable_plotting=False,
)
print(ok.variogram_model_parameters) # [sill, range, nugget]
5. The variogram, and predicting the surface
The empirical bins below are the direct measurement from step 1 above: half the average squared difference within each distance bin (that halving is why the axis says semivariance, not just variance). The line is the spherical model fit to them, with its own nugget, sill, and range marked directly on the chart.
Once the model above looks right, predict the surface itself:
Also reference only. Continues the same pykrige object fitted in the step above: .execute() is what actually predicts the grid.
import numpy as np
# Reference implementation only -- not executable in this browser
# tool. `ok` is the OrdinaryKriging object fitted in the step above;
# .execute() is the equivalent of this step's krige() call.
grid_lon = np.linspace(lon.min(), lon.max(), 25)
grid_lat = np.linspace(lat.min(), lat.max(), 25)
z, variance = ok.execute("grid", grid_lon, grid_lat)
6. Why Python is reference-only here
R’s geostatistics ecosystem (gstat, built on sp/sf) has been the standard tool for kriging since long before either language ran in a browser. Python’s closest equivalent, pykrige, has no WebAssembly build anywhere. That’s confirmed directly against this project, not assumed from general knowledge: there is no pykrige wheel published for Pyodide, and none of the usual pure-Python fallbacks (scikit-gstat, hand-rolled numpy) reach feature parity with gstat’s fitting routines without becoming a second, untested implementation of the exact algorithm this article is trying to teach correctly.
This is a different situation from Viewshed, this site’s other R-only article. There, the Python library (GDAL) exists in principle but isn’t in this specific browser runtime’s package index. Here, there is no WebAssembly build of pykrige to load in the first place. That’s a harder, more permanent gap than a narrower one.
The snippet above is real, working pykrige code for a desktop or server Python environment; it just has nowhere to run inside this page.
7. Where to next
- Just needed the map? The standalone Kriging Interpolator: upload your own file, pick the column, choose a model, done.
- Want the simpler distance-only version first? See Buffer/Proximity, which uses no fitted model, just a fixed distance.
First tool in the GeoStatistics family; more are planned.