Skip to content

Note

Click here to download the full example code

cokriging

jura: 259 soil samples of heavy metals (mg/kg, coordinates in km) and 100 validation samples withheld from estimation. Cd correlates with Zn, and Zn is also known at the validation points, which suits collocated cokriging.

import sys
from pathlib import Path

HERE = Path(__file__).parent if "__file__" in globals() else Path.cwd()
sys.path.insert(0, str(HERE.parents[1]))
import boitata as bt
import matplotlib.pyplot as plt
import numpy as np
from common import ACCENT, GRAY, INK, save

train = bt.datasets.jura()["prediction"]
test = bt.datasets.jura()["validation"]
xy = train.coords
cd, zn = train["Cd"], train["Zn"]
lag, max_lag = 0.1, 1.5

a linear model of coregionalization with two spherical structures, fitted to the Cd and Zn variograms and their cross-variogram (coregionalization shows the fit), and an ordinary kriging of Cd alone for comparison:

experimentals = [
    [
        bt.experimental_variogram(xy, cd, lag, max_lag),
        bt.experimental_variogram(xy, cd, lag, max_lag, other=zn),
    ],
    [None, bt.experimental_variogram(xy, zn, lag, max_lag)],
]
lmc = bt.Coregionalization.fit(experimentals, ["spherical", "spherical"])
search = bt.Search(radius=1.5, max_samples=24, min_samples=4)
ok = bt.OrdinaryKriging(experimentals[0][0].fit("spherical"), search).fit(train, "Cd")

cokriging takes the samples of both variables with a variable index each. collocated cokriging also uses the secondary value at the target itself, here Zn at each validation point:

ck = bt.Cokriging(lmc, search, means=[cd.mean(), zn.mean()])
ck.fit(np.vstack([xy, xy]), np.r_[cd, zn], [0] * len(cd) + [1] * len(zn))
truth = test["Cd"]
by_ok = ok.predict(test)
by_ck = ck.predict(test, collocated={1: test["Zn"]})


def rmse(e):
    return bt.compare(e, truth)["rmse"]


print(f"validation RMSE: ordinary kriging {rmse(by_ok):.3f}, collocated cokriging {rmse(by_ck):.3f} mg/kg")
print(
    f"variance of estimates: ordinary kriging {by_ok.var():.3f}, cokriging {by_ck.var():.3f}, true {truth.var():.3f}"
)

Out:

validation RMSE: ordinary kriging 0.777, collocated cokriging 0.689 mg/kg
variance of estimates: ordinary kriging 0.211, cokriging 0.531, true 0.477

using Zn at the target lowers the error and removes the smoothing that flattens ordinary kriging:

fig, axes = plt.subplots(1, 2, figsize=(9, 4.2), layout="constrained", sharey=True)
for ax, estimate, title in (
    (axes[0], by_ok, "Ordinary kriging of Cd"),
    (axes[1], by_ck, "Collocated cokriging with Zn"),
):
    ax.scatter(truth, estimate, s=12, color=ACCENT, alpha=0.7, linewidths=0)
    ax.plot([0, 5], [0, 5], color=GRAY, ls="--", lw=1)
    ax.set(xlim=(0, 5), ylim=(0, 5), xlabel="True Cd at validation points (mg/kg)", title=title)
    ax.set_aspect("equal")
    ax.text(0.2, 4.6, f"RMSE {rmse(estimate):.2f} mg/kg", color=INK)
axes[0].set_ylabel("Estimated Cd (mg/kg)")
save(fig, "validation")

Ordinary kriging of Cd, Collocated cokriging with Zn

locally varying anisotropy

anisotropy= takes a LocalAnisotropy as ordinary kriging does: each target searches its own ellipse, shared by Cd and Zn, and every structure and cross term of the model turns with it. here the directions follow the gradient of the ordinary kriging map of Cd, with semi-major ranges half the major. collocated also takes a column of the targets by name:

grid = bt.BlockModel(origin=(0.3, 0.4), size=(0.1, 0.1), count=(47, 54))
guide = grid.with_column("Cd", ok.predict(grid))
lva = bt.LocalAnisotropy.from_grid(guide, "Cd", window=3, ratios=(0.5, 1.0)).smooth(0.5)
by_lva = ck.predict(test, anisotropy=lva, collocated={1: "Zn"})
print(
    f"validation RMSE: collocated cokriging {rmse(by_ck):.3f}, with local anisotropy {rmse(by_lva):.3f} mg/kg"
)

Out:

validation RMSE: collocated cokriging 0.689, with local anisotropy 0.671 mg/kg

Total running time of the script: ( 0 minutes 0.209 seconds)

Download Python source code: example_06_09.py

Download Jupyter notebook: example_06_09.ipynb

Gallery generated by mkdocs-gallery