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 pillar, 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.

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.

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