codexkriging-spatial-prediction-uncertainty.py
kriging-spatial-prediction-uncertainty.py
"""Three exact samples: ordinary kriging prediction and model error SD.
Run from _media. Tested with Python 3.14, NumPy 2.3.5, Matplotlib 3.10.7.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
sites = np.array([0.0, 1.0, 3.0])
values = np.array([1.0, 3.0, 2.0])
targets = np.unique(np.r_[np.linspace(-1, 4, 701), sites, 2.0])
covariance = 2.0 ** -np.abs(sites[:, None] - sites)
system = np.block(
[[covariance, np.ones((3, 1))], [np.ones((1, 3)), np.zeros((1, 1))]]
)
cross = 2.0 ** -np.abs(sites[:, None] - targets)
solution = np.linalg.solve(system, np.vstack([cross, np.ones(len(targets))]))
weights, multiplier = solution[:3], solution[3]
prediction = values @ weights
variance = 1 - np.sum(weights * cross, axis=0) - multiplier
assert variance.min() > -1e-12
assert np.allclose(weights.sum(axis=0), 1)
assert np.allclose(system @ solution, np.vstack([cross, np.ones(len(targets))]))
at_two = np.flatnonzero(targets == 2)[0]
assert np.isclose(prediction[at_two], 69 / 29)
assert np.isclose(variance[at_two], 18 / 29)
for site, value in zip(sites, values):
idx = np.flatnonzero(targets == site)[0]
assert np.isclose(prediction[idx], value)
assert abs(variance[idx]) < 1e-12
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False, "figure.facecolor": "white"})
fig, (top, bottom) = plt.subplots(2, 1, figsize=(8.6, 6.2), sharex=True,
gridspec_kw={"height_ratios": [1.35, 1]},
layout="constrained")
nearest = values[np.argmin(np.abs(targets[:, None] - sites), axis=1)]
top.plot(targets, nearest, color="#8a8a8a", linestyle="--", linewidth=1.7,
label="Nearest neighbor (ties go left)")
top.plot(targets, prediction, color="#176b9b", linewidth=2.3,
label="Ordinary kriging")
top.scatter(sites, values, s=48, color="black", zorder=5, label="Exact observations")
top.scatter([2], [69 / 29], s=65, marker="D", color="#c65422", zorder=6)
top.annotate(r"$\widehat Z(2)=69/29\approx2.379$", (2, 69 / 29),
xytext=(1.55, 1.25), arrowprops={"arrowstyle": "->", "color": "#c65422"},
color="#a94118")
top.set(ylabel="Value / prediction", ylim=(0.65, 3.6),
title="Three observations, one fixed covariance model")
top.legend(loc="upper left", ncol=1, fontsize=9, frameon=False)
sd = np.sqrt(np.maximum(variance, 0))
bottom.fill_between(targets, 0, sd, color="#176b9b", alpha=0.15)
bottom.plot(targets, sd, color="#176b9b", linewidth=2)
bottom.scatter([2], [np.sqrt(18 / 29)], color="#c65422", s=45, zorder=5)
bottom.annotate(r"$s_{\rm OK}(2)=\sqrt{18/29}\approx0.788$", (2, np.sqrt(18 / 29)),
xytext=(1.2, 0.25), arrowprops={"arrowstyle": "->", "color": "#c65422"},
color="#a94118")
bottom.set(xlabel="Location s", ylabel="Prediction error SD", ylim=(0, 1.22),
xlim=(-1, 4), xticks=[-1, 0, 1, 2, 3, 4])
for ax in (top, bottom):
ax.axvline(2, color="#c65422", alpha=0.35, linewidth=1)
ax.grid(alpha=0.18)
fig.supxlabel(r"Unknown constant mean; $C(h)=2^{-|h|}$; no measurement noise", fontsize=10)
fig.savefig(Path(__file__).stem + ".png", dpi=130, facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()