Ordinary vs Universal Kriging: Which to Use

Use ordinary kriging when the mean is constant but unknown across your area; use universal kriging (kriging with drift) when a systematic large-scale trend is present — temperature falling with elevation, a plume from a point source. Decide by testing for a trend: regress the target on coordinates or covariates. If the drift is significant, model it with UniversalKriging(..., drift_terms=['regional_linear']); otherwise OrdinaryKriging is simpler and just as good.

Why This Matters

Choosing the wrong kriging variant quietly corrupts a surface. Fit ordinary kriging to data with a real trend and the variogram never reaches a sill, the weights misbehave, and predictions are biased in exactly the regions you care about. Fit universal kriging to trend-free data and you add parameters that overfit and inflate variance. This decision guide sits within the Ordinary & Universal Kriging workflow and the wider Kriging, Interpolation & Surface Generation Techniques section, and it gives you a test-driven way to pick — not a coin flip.

The Core Distinction

Both estimators predict an unsampled value as a weighted linear combination of neighbours, Z^(s0)=iλiZ(si)\hat{Z}(s_0) = \sum_i \lambda_i Z(s_i). They differ in what they assume about the mean.

Ordinary kriging assumes a constant but unknown mean:

Z(s)=μ+ε(s),E[ε(s)]=0,Z(s) = \mu + \varepsilon(s), \qquad \mathbb{E}[\varepsilon(s)] = 0,

with a single unbiasedness constraint iλi=1\sum_i \lambda_i = 1. Universal kriging replaces the constant mean with a deterministic drift expressed as a sum of basis functions fk(s)f_k(s):

Z(s)=k=0Kakfk(s)+ε(s),Z(s) = \sum_{k=0}^{K} a_k \, f_k(s) + \varepsilon(s),

so the mean varies smoothly across the domain. The fkf_k are typically low-order polynomials of the coordinates (regional linear or quadratic drift) or external covariates (external drift, KED). Universal kriging adds one unbiasedness constraint per drift term, solved jointly with the weights inside the kriging system.

The residual ε(s)\varepsilon(s) must be second-order stationary in both cases. Ordinary kriging assumes that of the raw variable; universal kriging assumes it only of the detrended residual — which is why a trend that breaks ordinary kriging can be absorbed cleanly by universal kriging.

Constant mean versus fitted drift, and the residuals each leaves Left: ten samples along an easting transect climb from about 11 to about 18. A flat amber line marks ordinary kriging's constant mean of 15; an indigo line marks universal kriging's linear drift, which tracks the climb and continues into the shaded zone beyond the last sample while the flat line stays at 15. Right: the same residuals at one shared scale. Ordinary kriging's residuals run about minus four on the left and plus four on the right, a systematic gradient; universal kriging's residuals alternate sign within about one unit. One transect, two mean models — and the residuals each leaves behind Observed values along an easting transect beyond samples 10 15 20 0 500 1000 easting (m) value OK: constant mean μ = 15 extrapolates flat UK: drift a₀ + a₁·easting OK residuals: Z − μ one sign for a long run, then the other — not stationary UK residuals: Z − (a₀ + a₁·easting) alternating, zero-mean — second-order stationary Both strips share one scale: the constant mean leaves a ±4-unit gradient, the fitted drift leaves ±1-unit noise. Ordinary kriging can only explain that gradient as correlation — which is what deforms its variogram.

Comparison Table

Criterion Ordinary kriging Universal kriging (KED)
Mean assumption Constant, unknown Deterministic drift (trend)
Best when No large-scale trend Clear coordinate/covariate gradient
Extra parameters None (just variogram) Drift coefficients per term
Variogram fitted on Raw values Detrended residuals (ideally)
Extrapolation behaviour Reverts to sample mean Follows the drift beyond samples
Overfitting risk Low Higher if drift is spurious
Covariate support No Yes (external drift)
PyKrige class OrdinaryKriging UniversalKriging
Typical domains Homogeneous fields, soil pockets Elevation-driven climate, plumes

A subtle trap: universal kriging should fit its variogram on the residuals after removing the drift, not on the raw data. PyKrige’s automatic fit does not always make that explicit, which is one reason many practitioners prefer the two-stage transparency of regression kriging when covariates are involved.

A Decision Framework

Choosing between ordinary and universal kriging Start by testing for a trend. No significant trend leads to ordinary kriging. A significant coordinate trend leads to universal kriging with regional linear drift. A trend explained by a covariate raster leads to universal kriging with external drift or to regression kriging. Test for a trend OLS on coords / covariates not significant coordinate trend covariate explains it Ordinary kriging OrdinaryKriging(x, y, z, ...) Universal kriging drift_terms=['regional_linear'] External drift / RK drift_terms=['external_Z'] or regression kriging Then validate: compare cross-validated RMSE of both estimators Keep the drift only if it lowers held-out error and residuals stay stationary

The workflow is: test, choose, validate. Never pick on intuition alone — a mild trend that looks obvious on a map may not improve out-of-sample predictions, and the cross-validation step is what settles it.

Running the Trend Test

Before choosing, quantify the drift. Regress the target on the coordinates; a significant slope or a meaningful R2R^2 signals a trend that ordinary kriging cannot represent.

python
import numpy as np
import statsmodels.api as sm

# x, y: projected coordinates (metres); z: target values
X = sm.add_constant(np.column_stack([x, y]))   # intercept + linear drift
ols = sm.OLS(z, X).fit()

print(ols.summary().tables[1])                 # coefficient significance
print(f"Trend R^2: {ols.rsquared:.3f}")

trend_present = ols.f_pvalue < 0.05 and ols.rsquared > 0.10
print("Use universal kriging" if trend_present else "Ordinary kriging is fine")

A low R2R^2 and non-significant coordinate terms point to ordinary kriging. A clear gradient points to universal kriging. Confirm the detrended residuals look stationary — the constant-mean-of-residuals assumption — using the methods in Testing for Second-Order Stationarity in Python.

Code: Ordinary vs Universal Kriging in PyKrige

The two estimators share almost all their configuration; universal kriging just adds drift_terms.

python
from pykrige.ok import OrdinaryKriging
from pykrige.uk import UniversalKriging

gridx = np.linspace(x.min(), x.max(), 150)
gridy = np.linspace(y.min(), y.max(), 150)

# --- Ordinary kriging: constant unknown mean ---
ok = OrdinaryKriging(
    x, y, z,
    variogram_model="spherical",
    nlags=15, weight=True, verbose=False,
)
z_ok, ss_ok = ok.execute("grid", gridx, gridy)

# --- Universal kriging: linear drift in the coordinates ---
uk = UniversalKriging(
    x, y, z,
    variogram_model="spherical",
    drift_terms=["regional_linear"],   # first-order polynomial trend in x, y
    nlags=15, weight=True, verbose=False,
)
z_uk, ss_uk = uk.execute("grid", gridx, gridy)

print(f"OK surface range: {z_ok.min():.2f} to {z_ok.max():.2f}")
print(f"UK surface range: {z_uk.min():.2f} to {z_uk.max():.2f}")

For kriging with external drift — a covariate raster explaining the trend — pass the covariate instead of a coordinate polynomial:

python
# external_drift is a 2D array on (gridy, gridx); external_drift_x/y are its axes
uk_ked = UniversalKriging(
    x, y, z,
    variogram_model="spherical",
    drift_terms=["external_Z"],
    external_drift=covariate_grid,          # covariate sampled on the grid
    external_drift_x=grid_cov_x,
    external_drift_y=grid_cov_y,
    verbose=False,
)
z_ked, ss_ked = uk_ked.execute("grid", gridx, gridy)

When the trend is driven by covariates rather than raw coordinates, the two-stage regression kriging approach is often clearer and lets you use non-linear trend models.

Validating the Choice

The decisive test is out-of-sample error. Cross-validate both estimators and keep the drift only if it lowers held-out RMSE.

python
from sklearn.model_selection import LeaveOneOut

def loocv(estimator_cls, x, y, z, **kw):
    loo, errs = LeaveOneOut(), []
    idx = np.arange(len(z))
    for i in idx:
        tr = idx != i
        est = estimator_cls(x[tr], y[tr], z[tr], variogram_model="spherical",
                            nlags=12, weight=True, verbose=False, **kw)
        pred, _ = est.execute("points", x[i:i+1], y[i:i+1])
        errs.append(float(pred[0]) - z[i])
    return np.sqrt(np.mean(np.square(errs)))

rmse_ok = loocv(OrdinaryKriging, x, y, z)
rmse_uk = loocv(UniversalKriging, x, y, z, drift_terms=["regional_linear"])
print(f"OK RMSE={rmse_ok:.4f} | UK RMSE={rmse_uk:.4f}")

If universal kriging does not beat ordinary kriging in cross-validation, the trend was not worth modelling — prefer the simpler estimator.

Interpreting the Comparison

  • UK wins clearly: a genuine drift exists and is well captured. Confirm the detrended residual variogram reaches a sill.
  • OK and UK tie: the trend is weak; choose ordinary kriging for simplicity and stability.
  • UK worse than OK: the drift is spurious or overfitted, or the residuals are non-stationary. Revisit the trend test and residual diagnostics.
  • Variogram never plateaus under OK: a hallmark of an unmodelled trend — move to universal kriging.
Variogram shape as a diagnostic for the mean model An annotated variogram plot with lag distance from 0 to 1000 metres on the horizontal axis and semivariance from 0 to 3 on the vertical axis. The orange curve, computed on raw values while a drift is present, keeps climbing to 2.95 and never plateaus. The green curve, computed on detrended residuals, rises from a nugget of 0.25 and flattens at a sill of 1.6 once the lag passes the 420 metre range. A panel on the right maps three curve shapes to their verdicts: a sill on raw values means ordinary kriging, a curve with no plateau means universal kriging, and a residual curve that still climbs means the drift order is too low. The variogram tells you which mean model you should have used 0 1 2 3 0 250 500 750 1000 lag distance h (m) semivariance γ(h) raw values, drift left in γ climbs past the sill and never plateaus residual γ flattens — sill 1.6 range 420 m detrended residuals what universal kriging models Reading the curve γ on raw values reaches a sill constant mean holds → OrdinaryKriging γ climbs, no plateau in sight unmodelled drift → UniversalKriging residual γ still climbs drift order too low → regional_quadratic Fit the variogram on the residuals, not the raw values, whenever a drift is in the model.

Troubleshooting

Symptom Likely cause Fix
OK variogram keeps rising, no sill Unmodelled large-scale trend Switch to universal kriging or detrend first
UK adds parameters but RMSE worsens Spurious/overfitted drift Drop drift terms; use ordinary kriging
external_Z drift errors in PyKrige Covariate grid axes not aligned with external_drift_x/y Match the drift array shape and axes to the covariate grid
UK residuals still show a gradient Drift order too low Add regional_quadratic or a covariate drift term
Both estimators biased at edges Extrapolation beyond sample hull Clip the grid to the convex hull of samples
Trend test insignificant but map looks trended Trend is short-range, not global Treat as autocorrelation; ordinary kriging is appropriate

Next Steps

For the mechanics of running each estimator end to end, see Step-by-Step Ordinary Kriging with PyKrige. When the trend comes from covariates and you want a flexible, inspectable model, move to Regression Kriging.

Frequently Asked Questions

When should I use universal kriging instead of ordinary kriging?

Use universal kriging when the variable has a systematic large-scale trend — a spatial drift — that a constant mean cannot represent, such as temperature falling with elevation or a pollutant gradient from a source. Ordinary kriging assumes the mean is constant (though unknown) across the domain; a real trend violates that assumption, biases the variogram, and inflates prediction error. If a regression of the target on coordinates or covariates is significant, universal kriging (or regression kriging) is the better choice.

How do I test whether my data has a trend?

Fit an ordinary least squares regression of the target on the spatial coordinates (and any candidate covariates) and check the coefficients’ significance and the R-squared. A significant coordinate term or a clear directional gradient in a scatter of value against easting/northing indicates drift. You can also inspect whether the variogram keeps rising without reaching a sill, which is a symptom of an unmodelled trend.

What are drift_terms in PyKrige UniversalKriging?

drift_terms tells PyKrige how to model the trend. ‘regional_linear’ fits a linear function of the x and y coordinates; ‘point_logarithmic’ adds logarithmic point drift; ‘external_Z’ uses an external covariate raster (kriging with external drift); and ‘specified’/‘functional’ accept custom drift arrays or functions. You pass the corresponding data — for example external_drift and its grid — alongside the drift_terms list.


Related

← Back to Ordinary & Universal Kriging