Block-kriging adequacy diagnostics for a set of cells
Source:R/kriging-adequacy.R
kriging_adequacy.Rdsummarize_by_cell() aggregates by plain means inside cell
boundaries. A block-kriging aggregator would instead weight observations
by their spatial correlation with the cell, and would return an estimate
and a variance for every cell, thin or empty. Whether that is worth
having on a given layer is a question with a measurable answer, and this
function measures it, changing no cell value: for every cell it reports
the block-kriging estimate and variance implied by a fitted variogram,
that variance as a share of the variance the cell's mean would have with
no data at all, and, where the cell has points,
whether it exceeds the design-based variance of the plain mean,
\(s^2/n\); and it scores the variogram itself by blocked
cross-validation.
Usage
kriging_adequacy(
assigned_points_sf,
response_var,
cells_sf,
id_col = "poly_id",
sac = NULL,
folds = NULL,
k = 5L,
seed = 123L,
nmax = 50L,
max_neighbours = 2000L,
max_box_ratio = 1000,
quiet = TRUE
)Arguments
- assigned_points_sf
Points with a cell identifier column, as
assign_features_to_polygons()returns.- response_var
The response column.
- cells_sf
The cell polygons, with the matching ID column. A layer with no CRS is taken to be in the points' CRS (and points with none in the cells'), with a warning.
- id_col
Preferred name of the ID column, found as
summarize_by_cell()finds it: on the points the first ofid_col,"poly_id","polygon_id"and"cell_id"; on the cells the first of that column,"poly_id","polygon_id","id","cell_id"and"grid_id". IDs are matched as text, whole numbers written out in full, so a double1e5matches an integer100000; a point whose ID matches no cell is counted in no cell, with a warning. Default"poly_id".- sac
Optional
sac_rangecarrying a variogram model.- folds
Optional
make_folds()result onassigned_points_sffor the cross-validation statistic; built here withblock_kfoldwhenNULL. As incv_*(), folds whose recorded rows sit at other locations inassigned_points_sf(built on another layer, such as the points beforeassign_features_to_polygons()dropped some) are refused.- k, seed
Folds and seed for that construction.
- nmax
The number of neighbours each kriging system uses (
gstat'snmax): the locations nearest the cell's centre, or, for a cell those leave some of its own locations out of, all of its own plus this many outside it (see "The kriging neighbourhood"). The cross-validation kriges each held-out point from itsnmaxnearest training locations. Default 50.- max_neighbours
The largest kriging system a cell is given when its neighbourhood has to grow to hold all its own locations; a cell that would need more is left out (
kr_columnsNA) with a warning. Never belownmax. Default 2000.- max_box_ratio
A cell whose bounding box is more than this many times its area is left out (
kr_columnsNA) with a warning, because gstat's discretisation of it costs memory in proportion. Default 1000.- quiet
Suppress progress messages. Default
TRUE.
Value
An sf object of class "kriging_adequacy", one row
per cell with the cell geometry and: the ID column, n (points in
the cell), mean (the plain mean), se (its naive standard
error), kr_pred, kr_var, kr_ratio,
kr_exceeds_design, kr_shift and kr_n_used (how many
locations the cell was kriged from; NA for a cell left out).
Attributes:
variogram (the model frame), sill, nugget,
range, range_identified, rejected_reason (why the
range was refused, from sac; NA when it was identified),
cv (a list:
zscore_var, zscore_mean, rmse, n_pred,
k, method), nmax, n_points (the points
used), n_locations (the distinct locations among them, which
the kriging used) and cells_left_out (counts of cells left out,
named shape for max_box_ratio and size for
max_neighbours).
Reading the columns
kr_ratioThe block-kriging variance over the cell's prior variance, in \([0, 1]\). The prior variance is the variance the cell's mean would have with no data at all, \(\bar C(B,B)\): the covariance averaged over pairs of points in the cell, on the discretisation gstat block-kriges with, and without the nugget, which averages out over a block (gstat leaves it out of the block variance too). Each cell has its own: a cell's mean varies less than a single point does, and far less once the cell is wider than the range, so the point sill is not the scale. It is the coverage score, and it needs no hand-set threshold in metres or point counts: as it approaches 1 the estimate carries almost no information from the data about the cell and is reverting to the estimated mean. Ordinary kriging adds the variance of that estimated mean, so a cell the data do not reach comes out at or above its prior variance and reads 1. A cell at 0.05 is well determined; a cell at 0.8 is mostly prior.
NAwhen the model is a pure nugget, where a cell mean has no prior variance to be a share of.kr_exceeds_designTRUEwhere the kriging variance is larger than \(s^2/n\) from the cell's own points: kriging is not earning its keep there, and that is said per cell instead of globally.NAfor cells with fewer than two points, where \(s^2\) does not exist.kr_shiftThe kriged estimate minus the plain mean, in units of the plain mean's standard error (
NAwhere that is not defined). How much the aggregator would move the value, against the precision the value has.
The cross-validation statistic
The kriging variance is only as good as the variogram. With blocked
folds, each held-out point is kriged from the training folds and the
standardised error \((z - \hat z) / \sqrt{kv}\) is recorded; the
variance of those over all held-out points should be about 1. Its
departure from 1 measures how badly the kriging variance is understated
(or overstated): 1.4 means the variances are roughly 40 percent too
small. Measured on simulated exponential fields (n = 300 on a 1000-unit
extent, sill 1, nugget 0.2, five blocked folds, eight draws per
configuration): 0.93–1.07 with the true variogram, 0.85–1.01 with the
variogram estimated from the same points. With the nugget understated
tenfold it moved only to 0.95–1.24. That is a property of the folds and
not a weakness of the statistic: under blocked folds every held-out point
is far from the training data, where the kriging variance is close to the
sill whatever the nugget, so the blocked statistic checks the sill and
range. To check the nugget, pass random folds
(make_folds(method = "random_kfold")) as folds: the
held-out points are then close to their neighbours, where the nugget
decides the variance. The statistic is computed fold by fold with
gstat::krige() on the splits make_folds() built, each
held-out point kriged from that split's own training set, so the folds
carry the same separation the package uses everywhere else:
"buffered_loo" and "nndm" keep the points they exclude
around each held-out one out of its kriging, and print() names the
scheme that ran. A vector of fold labels is run as k-fold, each fold
kriged from all the others.
What it said about block kriging as an aggregator
On the same simulated fields, with 16, 36 and 64 square cells: under uniform sampling the kriged and plain cell means differed by more than one standard error in 11–24 percent of cells and by more than two in 0–7 percent, the kriging variance exceeded \(s^2/n\) in 10–26 percent of populated cells, and at most one cell was empty. Under clustered sampling (eight clusters of 60-unit spread) the two aggregators parted: shifts above one standard error in 34–63 percent of cells and above two in 9–25 percent, the kriging variance below \(s^2/n\) in only 13–52 percent of populated cells, and 3–27 of the cells empty, each with a kriged estimate and variance where the plain mean has nothing. So a block-kriging aggregator earns its place on clustered layers and rarely on uniform ones, and this function says which kind a layer is.
What this needs
A variogram model. sac is a estimate_sac_range()
result carrying one (attr(, "variogram_model")); when NULL
it is estimated here from the response. The model families are the ones
the package interprets elsewhere: exponential, spherical and Gaussian
components with a nugget. Anything else is refused by name. A
model whose range was not identified (an NA estimate with the
model attached) is used with a warning that says why it was refused, and
the reason is kept as attr(, "rejected_reason"): a range past the
fitted lags means the sill was never reached and the ratios rest on an
extrapolation; a fit that did not converge stopped wherever the optimiser
halted; a variogram that falls with distance, or a range below the
shortest lag, describes the data poorly at some lags.
The model has to be of the response itself. A variogram of residuals
(estimate_sac_range(predictor_vars = ...), attr(,
"detrended") TRUE) leaves out the variance the predictors
explain, while the response is kriged here without them, so
kr_var, kr_ratio and the cross-validation statistic come out
too small (the statistic at 4.3–5.2 against 0.67–1.53 with a spatially
structured covariate); it is used with a warning. The points are put in
the CRS the variogram was fitted in (attr(sac, "crs")), because
its range is a length in that CRS's units. Requires gstat.
The kriging neighbourhood
gstat kriges a cell from the nmax locations nearest its
centre. A cell holding more locations than that would be estimated from
its middle alone, which describes the middle rather than the cell (in one
simulated case a cell of 1,500 points came out 0.43 off at
nmax = 50, against 0.07 from all of them and 0.02 for its plain
mean). So wherever the nmax locations nearest a cell's centre
leave out any of the cell's own locations, the cell is kriged from all of
its own locations plus the nmax nearest outside it;
kr_n_used says how many locations each cell was kriged from. The
cost of a kriging system grows with the cube of its size (about 1 s at
2,000 locations and 17 s at 5,000), so a cell that would need more than
max_neighbours is left out with a warning, its kr_ columns
NA.
gstat also discretises each cell into 500 points on a regular grid
laid over the cell's whole bounding box, keeping those inside, so the
memory a cell takes grows with the ratio of that box to its area: about
54 MB more for a thin diagonal strip at a ratio of 708, and 592 MB at
7,072. A cell whose bounding box exceeds its area more than
max_box_ratio times (a sliver, parts far apart, a cell that is
mostly hole) is left out the same way. attr(, "cells_left_out")
counts both kinds, and print() says how many cells have no
estimate and why.
Repeat measurements at one location
Two observations at the same coordinates (visits to a station, records
geocoded to one address) make a kriging system singular, because
gstat gives them the full sill, nugget included, as their
covariance, as if they were one observation. The kriging and its
cross-validation therefore use one observation per location, the mean of
its replicates, with a warning; n and mean still count every
point. How much of the nugget \(c_0\) a mean of \(m\) replicates
keeps is read off the replicates: their pooled within-location variance
\(s_w^2\), capped at \(c_0\), is the part that differs from visit to
visit and averages down, and the rest is micro-scale variation the visits
share, so the mean carries error variance \(c_0 - s_w^2 + s_w^2/m\).
That goes to gstat as a known measurement error (its
weights) on the model with the nugget set to zero. For replicates
that differ only by measurement error this is exactly the kriging of every
observation, and for identical replicates it is the kriging of one; a
location seen once is kriged as before. In the cross-validation a
location is held out whole, under the fold of its first row, and its
error variance is part of its standardised error. A cell or held-out
location gstat still cannot krige is reported NA with a
warning, and print() says how many.
References
Cressie, N. (1993). Statistics for Spatial Data, revised edition. Wiley. (Block kriging, chapter 3; cross-validation of the kriging variance, section 2.6.4.)
Examples
if (requireNamespace("gstat", quietly = TRUE)) {
library(sf)
set.seed(1)
n <- 200
x <- 5e5 + runif(n, 0, 1000); y <- 5e6 + runif(n, 0, 1000)
d <- as.matrix(dist(cbind(x, y)))
z <- as.numeric(t(chol(0.8 * exp(-d / 100) + diag(0.2, n))) %*% rnorm(n))
pts <- st_as_sf(data.frame(x = x, y = y, z = z), coords = c("x", "y"), crs = 32632)
bnd <- st_sf(geometry = st_as_sfc(st_bbox(pts)))
cells <- create_grid_polygons(bnd, target_cells = 16, type = "square")
asg <- assign_features_to_polygons(pts, cells)
ka <- kriging_adequacy(asg, "z", cells, k = 4)
print(ka) # the report; print() because only a block's
# last value shows on its own
attr(ka, "cv")$zscore_var # about 1 when the variogram is right
}
#> Registered S3 method overwritten by 'stars':
#> method from
#> st_interpolate_aw.stars sf
#> Block-kriging adequacy over 20 cells (200 points, nmax 50)
#> variogram: Nug(0.168, 0) + Exp(1.23, 138); sill 1.39, nugget 0.168 (12%), range 414.6
#> kriging variance / no-data variance of the cell mean (1 = nothing from the data): median 0.066, range 0.034-0.309; 0 cell(s) above 0.5
#> kriging variance exceeds s^2/n in 0 of the 16 cell(s) with two or more points
#> kriged minus plain mean: |shift| > 1 SE in 2 of 16 cell(s), > 2 SE in 0
#> empty cells: 1 (kriged estimate and variance available for each)
#> blocked CV (block_kfold, 4 folds, 200 points): var of standardised error 0.95 (1 = kriging variance correct; above 1 = understated), mean -0.12, RMSE 0.972
#> [1] 0.947184