Interpolating Environmental Exposure Surfaces

This guide is part of the Environmental Exposure & Risk Surface Modeling section. It covers the case where you have direct measurements of the exposure — a network of air quality monitors, well samples, noise loggers — and need a value everywhere, including the places nobody measured.

Concept & Epidemiological Alignment

Interpolation answers a narrow question: given measurements at nn locations, what is the most defensible estimate at an unmeasured location? Geostatistical methods answer it by first estimating how quickly the measured quantity decorrelates with distance, then using that model to weight nearby observations. The weighting is not a heuristic — for kriging it is the set of weights that minimises expected squared prediction error under the fitted covariance model, which is why kriging alone returns a prediction variance along with the prediction.

That variance is the reason this family matters to epidemiology. Every other way of producing a surface hands you a number with no stated precision, and a number with no stated precision cannot be carried honestly into a health model. Kriging hands you Z^(s0)\hat{Z}(s_0) and σ2(s0)\sigma^2(s_0) at every location, and the second quantity is what tells you whether the eastern half of the state is being described or guessed.

The method’s assumptions are strong and must be checked against surveillance reality rather than assumed. Second-order stationarity requires that the covariance between two points depends only on the vector between them, not on where they are — a state where one region is dominated by industrial sources and another by wildfire smoke violates it. Isotropy, the default in most fitting code, requires that decorrelation is the same in all directions, which prevailing winds routinely break. Adequate sampling means enough site pairs at short separations to estimate the variogram near the origin, where it matters most; below about thirty sites, the fitted model is largely an assumption in numerical clothing.

Interpolation is the right tool for regionally smooth exposures — fine particulate matter, ozone, temperature, groundwater chemistry. It is the wrong tool for anything whose variation is driven by a source at street scale, because the method’s premise is that nearby places are similar, and a roadside and the garden behind it are not. For those, Land-Use Regression for Air Pollution Exposure is the correct family.

Method Selection

Method Needs Gives a variance Good for Breaks when
Inverse distance weighting A power parameter only No Quick screening, very few sites The power is unjustified; surface has bullseyes at data points
Ordinary kriging Fitted variogram, ≥30 sites Yes Regionally smooth pollutants with a decent network The mean drifts across the region, or anisotropy is ignored
Universal kriging Variogram of residuals plus a trend model Yes A clear large-scale gradient, e.g. coast to interior The trend is overfitted and eats the spatial signal
Regression kriging Covariates on a grid plus residual variogram Yes Covariates explain much of the variation Covariate rasters are unavailable at prediction locations
Nearest monitor Nothing No Auditable coarse screening Residences are far from sites; artificial boundary jumps

The decision is mostly made by two facts you can measure before fitting anything: how many sites you have, and whether a plot of the measured values against easting and northing shows a systematic trend.

Choosing an Interpolation Method From Two Measurable Facts A decision sequence starting from the monitor count. Below thirty usable sites the variogram cannot be estimated reliably and the path leads to inverse distance weighting or nearest-monitor assignment, both labelled as screening only. With thirty or more sites the next question is whether the measured values show a large-scale trend across the region. No trend leads to ordinary kriging. A trend leads to universal kriging when the trend is a smooth function of position, or regression kriging when covariate rasters explain it. Each terminal box notes whether a prediction variance is produced. Count the sites, then look for a trend — the rest follows Usable sites after the completeness rule Fewer than 30 variogram not estimable Distance weighting or nearest monitor no variance — screening only 30 or more sites is there a regional trend? No trend visible mean roughly constant Ordinary kriging fit variogram, predict variance returned Trend present coast to interior, urban core Universal or regression kriging trend removed, residuals fitted Both left-hand outcomes are legitimate — they are simply not surfaces you can attach an error bar to

Spatial Data Prerequisites

  • Geometry: monitor sites as points with a stable site identifier; the prediction target as either a regular grid or the centroids or geometries you will summarise to.
  • Coordinate reference system: one projected metric system for sites and prediction grid, chosen for the study extent. Variogram distances are in the units of this system, so a geographic system silently produces a variogram in degrees.
  • Sample size: at least 30 sites for a defensible variogram, preferably 50 or more; below that, use a published variogram from the literature and say so, or use a non-geostatistical method.
  • Temporal alignment: one averaging window applied identically at every site, with a completeness rule and a record of which sites it removed.
  • Value distribution: strongly right-skewed measurements — many environmental concentrations are — should be log-transformed before fitting, with back-transformation handled explicitly rather than by exponentiating the mean.
  • Duplicate coordinates: two sites at the same location produce a singular kriging system. Deduplicate or jitter deliberately, and log the decision.

Production Implementation

The routine below fits an exponential variogram by weighted least squares, solves the ordinary kriging system at each prediction location using the nearest kk sites, and returns prediction and variance together. It keeps the neighbourhood local, which is what makes a state-wide grid tractable, and it logs the fitted parameters so the surface is reproducible.

# Ordinary kriging of a monitor network onto a prediction grid.
# Pinned: geopandas==0.14.4, numpy==1.26.4, scipy==1.13.1, pyproj==3.6.1
import hashlib
import json
import logging

import numpy as np
import geopandas as gpd
from scipy.optimize import curve_fit
from scipy.spatial import cKDTree

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("exposure.kriging")


def exponential_model(h, nugget, sill, rng):
    """Exponential semivariogram. rng is the practical range / 3."""
    return nugget + (sill - nugget) * (1.0 - np.exp(-h / rng))


def fit_variogram(xy, z, n_lags=15, max_frac=0.5):
    """Empirical semivariogram + weighted least-squares exponential fit."""
    d = np.sqrt(((xy[:, None, :] - xy[None, :, :]) ** 2).sum(-1))
    iu = np.triu_indices_from(d, k=1)
    h, g = d[iu], 0.5 * (z[iu[0]] - z[iu[1]]) ** 2
    cutoff = max_frac * h.max()
    keep = h <= cutoff
    edges = np.linspace(0, cutoff, n_lags + 1)
    idx = np.digitize(h[keep], edges) - 1
    lag_h, lag_g, lag_n = [], [], []
    for b in range(n_lags):
        m = idx == b
        if m.sum() < 30:          # too few pairs to trust this lag
            continue
        lag_h.append(h[keep][m].mean())
        lag_g.append(g[keep][m].mean())
        lag_n.append(int(m.sum()))
    lag_h, lag_g = np.asarray(lag_h), np.asarray(lag_g)
    p0 = [lag_g.min(), lag_g.max(), cutoff / 3.0]
    # Weight each lag by its pair count: short lags decide the near-origin shape.
    popt, _ = curve_fit(exponential_model, lag_h, lag_g, p0=p0,
                        sigma=1.0 / np.sqrt(lag_n), maxfev=20000,
                        bounds=([0, 0, 1.0], [np.inf, np.inf, np.inf]))
    log.info("variogram nugget=%.4f sill=%.4f range=%.0f m lags=%d",
             popt[0], popt[1], popt[2] * 3.0, len(lag_h))
    return tuple(float(v) for v in popt)


def ordinary_kriging(sites: gpd.GeoDataFrame, value_col: str,
                     grid: gpd.GeoDataFrame, k: int = 24):
    """Predict value_col at every grid point with a local ordinary kriging system.

    Returns (prediction, variance) arrays aligned to `grid`. Both frames must
    already share one projected CRS in metres.
    """
    if sites.crs is None or grid.crs is None or sites.crs != grid.crs:
        raise ValueError("sites and grid must share one projected CRS")
    if sites.crs.is_geographic:
        raise ValueError("project to metres before kriging; degrees are not distances")

    sites = sites.sort_values("site_id").reset_index(drop=True)  # deterministic
    xy = np.c_[sites.geometry.x.values, sites.geometry.y.values]
    z = sites[value_col].to_numpy(float)
    nugget, sill, rng = fit_variogram(xy, z)

    tree = cKDTree(xy)
    gxy = np.c_[grid.geometry.x.values, grid.geometry.y.values]
    k = min(k, len(sites))
    dists, nbr = tree.query(gxy, k=k)
    pred = np.empty(len(gxy))
    var = np.empty(len(gxy))

    for i in range(len(gxy)):
        idx = nbr[i]
        h = np.sqrt(((xy[idx][:, None, :] - xy[idx][None, :, :]) ** 2).sum(-1))
        A = np.ones((k + 1, k + 1))
        A[:k, :k] = exponential_model(h, nugget, sill, rng)
        np.fill_diagonal(A[:k, :k], 0.0)
        A[k, k] = 0.0
        b = np.ones(k + 1)
        b[:k] = exponential_model(dists[i], nugget, sill, rng)
        try:
            w = np.linalg.solve(A, b)
        except np.linalg.LinAlgError:          # duplicate coordinates
            w = np.linalg.lstsq(A, b, rcond=None)[0]
        pred[i] = w[:k] @ z[idx]
        var[i] = max(w[:k] @ b[:k] + w[k], 0.0)

    cfg = {"model": "exponential", "nugget": nugget, "sill": sill,
           "range_m": rng * 3.0, "k": k, "crs": sites.crs.to_string()}
    log.info("kriged %d locations cfg_sha256=%s", len(gxy),
             hashlib.sha256(json.dumps(cfg, sort_keys=True).encode()).hexdigest()[:16])
    return pred, var

Three details in that code are the difference between a working surface and a plausible fiction. The variogram fit is weighted by pair count, so the short lags — which control the near-origin behaviour and therefore almost all of the prediction weight — dominate the fit instead of being outvoted by the noisy long lags. The kriging system is solved on the nearest kk sites rather than globally, which keeps the cost linear in grid size and also limits the damage from a distant outlier. And the variance is floored at zero, because a numerically negative kriging variance signals a non-positive-definite system rather than a physically meaningful result.

What Each Variogram Parameter Does to the Surface A semivariogram plot with separation distance on the horizontal axis and semivariance on the vertical axis. Empirical lag points rise from a nonzero intercept and flatten out. A fitted exponential curve passes through them. The intercept is labelled nugget and annotated as measurement error plus variation below the shortest site spacing, which controls how much the surface smooths through the data. The plateau is labelled sill and equals the total variance. The distance where the curve reaches ninety-five per cent of the sill is labelled range, annotated as the distance beyond which a monitor contributes almost nothing, which is what determines how far the network can be trusted. Three numbers decide how far your monitors reach nugget sill range high nugget: surface smooths through the data and the map stops showing local contrast short range: monitors stop informing each other and variance climbs in gaps separation distance semivariance

Parameter Selection & Tuning

The variogram model family matters less than the near-origin behaviour. Exponential and spherical models fit most environmental data adequately; a Gaussian model implies an unrealistically smooth field and frequently produces an unstable kriging system. Fit two families and compare their cross-validated error rather than arguing about which is theoretically preferable.

The lag binning is the parameter most people leave at a default and should not. Bins must be narrow enough to resolve the rise near the origin and wide enough that each holds enough pairs — thirty is a common minimum — for a stable mean. The maximum lag should stop at roughly half the study extent, because beyond that the pair count collapses and the empirical points are noise.

The neighbourhood size kk trades bias against cost. Values between 16 and 32 are typical; below about 12 the prediction becomes jumpy at cell boundaries, and above 40 the gain is negligible while the solve cost grows cubically. Check the choice by re-running cross-validation at two settings — if the error is insensitive, take the cheaper one.

Anisotropy should be tested, not assumed away. Compute directional variograms in four sectors; if the range differs by more than roughly a factor of two between the longest and shortest directions, an isotropic model is misallocating weight, and the surface will be smeared perpendicular to the true gradient.

The nugget should not be forced to zero to make the surface pass exactly through the observations. An exact interpolator is only appropriate when the measurements have no error, which is never true of environmental monitoring. Forcing a zero nugget produces the characteristic bullseye artefact around each site.

Edge Cases & Failure Modes

Fewer than thirty sites. The fitted variogram is dominated by sampling noise and the resulting surface has an unearned appearance of precision. Use a published variogram for the pollutant and region, declare that you did, and inflate the reported uncertainty accordingly.

A gap wider than the range. Inside such a gap, kriging returns the global mean with a variance near the sill — mathematically honest, cartographically misleading, because the colour ramp shows a smooth field. Mask or flag every prediction whose variance exceeds a stated fraction of the sill rather than shipping it as if it were informed.

Skewed data and naive back-transformation. Kriging the log of a concentration and exponentiating the prediction returns a median, not a mean, and understates the mean by a factor that depends on the variance. If your health model needs a mean, apply the lognormal correction explicitly and note it.

Preferential monitor siting. Regulatory networks are sited where problems are expected, which means the sample is not representative of the region. Kriging assumes nothing about representativeness for the prediction itself, but it does mean that the study-area average computed from the surface is biased. Report area-weighted summaries from the grid, not the mean of the monitors.

Temporal gaps masquerading as spatial structure. A site that reported for eight months of a year, kept by a lenient completeness rule, contributes a value from a different season than its neighbours. This appears as a spurious short-range nugget. Enforce the completeness rule before fitting, not after.

Memory on a fine grid. A 30 m grid over a state is hundreds of millions of cells. Predict in tiles with a halo wider than the range, write each tile to a cloud-optimized GeoTIFF, and never assemble the whole array in memory — the patterns for this are covered in Cloud-Native Geospatial Storage for Surveillance.

The variance band is where most of those failure modes become visible at once. A map of prediction variance beside the prediction itself is the single most useful diagnostic an interpolated surface can ship with:

The Variance Map Shows What the Prediction Map Hides Two panels of the same state. The prediction panel shows a smooth concentration surface with no visible sign of where monitors are sparse. The variance panel shows low variance in rings around each monitor and a broad region of high variance in the north-east where no monitor lies within the fitted range. A mask threshold drawn on the variance scale identifies the cells whose prediction is little better than the regional mean and should be flagged or withheld. Ship the variance band, or nobody can see where the surface is guessing Prediction smooth everywhere Prediction variance no monitor within range low variance rings at each monitor Mask or flag cells whose variance exceeds the agreed share of the sill

Compliance & Audit Controls

A kriged surface is a derived product with a long downstream life, so its provenance must travel with it. Record the source monitor dataset and vintage, the averaging window and completeness rule, the site count after the rule, the variogram family and fitted nugget, sill and range, the neighbourhood size, the projected coordinate reference system, and the cross-validated error and bias. Hash that configuration and write the digest into the raster metadata so a given file maps to exactly one run.

Determinism requires a stable site sort before fitting and a fixed optimiser seed where the fit is stochastic. The surface itself is not protected data — it contains no personal information — but the moment it is joined to residences it enters the protected environment, and the join is a separately logged step. Publish the surface and its variance band openly where policy allows: an exposure surface that external analysts can re-run is the cheapest form of peer review available.

Production Implementation Checklist

Frequently Asked Questions

Is inverse distance weighting ever acceptable? Yes, for screening and for networks too small to fit a variogram, provided you do not attach an error bar to it. Its weakness is that the power parameter is chosen rather than estimated, so the smoothness of the surface is an author’s decision instead of a property of the data, and it gives you no way to say where the surface is uninformed.

How do I krige a whole time series without refitting every day? Fit the variogram on a pooled or seasonal basis rather than daily, because a single day’s network rarely has enough pairs. Check the pooled model against a handful of individual days, and refit when the cross-validated error drifts rather than on a fixed schedule.

Why does my kriged surface have rings around the monitors? Almost always a nugget forced to zero, which makes kriging an exact interpolator and forces the surface to pass through each measurement. Fit the nugget instead. A secondary cause is a neighbourhood so small that the set of contributing sites changes abruptly between adjacent cells.

Should I krige the pollutant or its logarithm? Krige whichever is closer to symmetric, which for most concentration data is the logarithm. Then decide deliberately what the health model needs: exponentiating gives a median, and recovering a mean requires the lognormal correction using the local kriging variance.

What do I do about the prediction variance in the health model? At minimum, report it and restrict sensitivity analyses to well-informed areas. Better, carry it forward — either by multiple imputation from the predictive distribution or by a measurement-error model. The methods are set out in Exposure Measurement Error & Uncertainty.