Downscaling a climate grid with terrain rests on a long-standing
idea: a climate variable often tracks elevation through a roughly linear
relationship, so a high-resolution elevation model can carry the
variable to a finer grid through a regression. This is the
regression-on-elevation step behind high-resolution climate surfaces
such as CHELSA (Karger et al. 2017). topocast generalizes
the predictor from elevation alone to any set of aligned terrain
covariates, fits the relationship locally rather than globally, and
keeps the per-cell cost independent of the neighborhood size.
This vignette describes the method: the local regression, the summed-area tables that make it fast, the numerical choices, and what the output does and does not preserve.
Installation
# install.packages("pak")
pak::pak("gcol33/topocast")A regression in every neighborhood
A global regression of the coarse response
on
predictors
assumes one relationship over the
whole map. Terrain-climate relationships are not that stable: a lapse
rate on a windward slope differs from one in a rain shadow.
topocast instead fits a separate ordinary least squares
regression in a square window around every coarse cell. For the cell at
row
,
column
,
the window of radius
holds the cells within
rows and columns, and the fit solves
The result is a grid of intercepts
and a grid of slopes
for each predictor, all at the coarse resolution. These coefficient
grids are the locally varying relationship between the response and the
terrain, and topocast(..., coefficients = TRUE) returns
them on the fine grid alongside the fitted layer.
The downscaling step resamples each coefficient grid to the fine resolution and evaluates the regression on the fine predictors:
where indexes fine cells and the coefficients are the resampled coarse grids. The fine-scale structure of the output comes entirely from the fine predictors; the coefficients carry the relationship.
Constant cost with summed-area tables
A naive moving window re-sums every cell in the neighborhood for
every center, so its cost grows with the window area. For a radius of 20
that is over 1,600 cells touched per output cell. topocast
avoids this with summed-area tables, also called integral images.
The normal equations for the window fit are , where holds the within-window sums , , and , and holds and . Each of these is a sum of a fixed quantity over the window. For any grid , the summed-area table stores the sum over the rectangle from the top-left corner to each cell:
With built once in a single pass, the sum over any rectangular window is four lookups:
topocast builds one table for the cell count, one for
,
one per predictor for
and
,
and one per predictor pair for
. Every window then
assembles
and
from four lookups each, solves a
system, and stores the coefficients. The total cost is proportional to
the number of cells and the small per-cell solve, with no dependence on
the radius.
The radius below changes the smoothness of the slope grid but not the runtime.
set.seed(1)
g <- rast(nrows = 80, ncols = 80, xmin = 0, xmax = 8000, ymin = 0, ymax = 8000,
crs = "EPSG:32632")
elev <- setValues(g, 1000 + 500 * sin(crds(g)[, 1] / 1500))
prec <- 800 - 0.15 * elev + setValues(g, rnorm(ncell(g), 0, 15))
y <- as.matrix(prec, wide = TRUE)
x <- as.matrix(elev, wide = TRUE)
small <- window_regression(y, x, radius = 3)
large <- window_regression(y, x, radius = 15)
op <- par(mfrow = c(1, 2), mar = c(2, 2, 2, 4))
plot(setValues(elev, as.vector(t(small$slope[[1]]))), main = "slope, radius 3")
plot(setValues(elev, as.vector(t(large$slope[[1]]))), main = "slope, radius 15")
par(op)Numerical and masking choices
Global centering. Before accumulation the response and each predictor are centered on their global mean over the valid cells. Centering keeps the entries of small even when elevation runs into the thousands, which conditions the solve. The intercept is mapped back to the raw response and predictor scale on output, so the returned coefficients are on the original units.
Complete-case masking. A cell contributes to a window only where the response and every predictor are finite. A separate count table tracks how many valid cells fall in each window, and invalid cells are set to zero in the centered copies so they drop out of the sums without distorting them.
Degenerate windows. A cell is returned as
NA in three cases: the window holds fewer valid cells than
the model needs (k + 1, raised by min_cells);
a predictor has within-window variance below min_variance,
so its slope is not identified; or the assembled system is singular.
These guards keep a flat or data-starved neighborhood from producing a
meaningless coefficient.
Reporting fit quality
The same sufficient statistics give the local quality of each fit at no extra window cost. One more summed-area table, of the squared centered response, yields the within-window total sum of squares , and the residual sum of squares follows from the fitted coefficients as , where is the response side of the normal equations. Their ratio is the coefficient of determination,
returned by diagnostics = TRUE as an
r.squared grid on the fine resolution. It maps where the
terrain explains the response and where the downscaled field carries
little more than the coarse level.
The same statistics also give the residual standard deviation,
,
in the response’s own units rather than R-squared’s unitless,
variance-relative scale, and the window’s valid cell count
itself. diagnostics = TRUE returns both, as
residual.sd and n.valid. A low R-squared is
ambiguous on its own: it can mean the terrain-response relationship is
genuinely weak in that neighborhood, or that the window barely cleared
the minimum valid-cell count and the fit is just noisy from too little
data. n.valid tells the two apart. Because the valid-cell
mask is complete-case across every response fit in one call,
n.valid is one shared grid rather than one per
response.
Carrying a time series
A climatology is one field. A time series shares the terrain
relationship across many periods, so refitting the regression each
period would be wasteful and would let the spatial pattern drift between
steps. topocast instead downscales the baseline once and
carries each period’s coarse anomaly onto the fine baseline.
For a period with coarse value , coarse baseline , and fine baseline , the ratio path returns
and the additive path returns
.
The ratio suits non-negative variables such as precipitation; the
additive form suits variables such as temperature where a difference is
the natural anomaly. Both carry the coarse anomaly verbatim, so the
downscaled series tracks the coarse values over time even though the
baseline itself is a regression surface. A zero or missing baseline in
the ratio path returns NA rather than dividing by zero.
What the output preserves
The downscaled field is the regression evaluated on the fine predictors. Two consequences are worth stating plainly.
The output is not mass conserving. Averaging the fine field back to
the coarse grid does not reproduce the coarse input, because the coarse
residual, the part of
the regression did not explain, is discarded. For a smoothed climate
product this is usually acceptable, and the anomaly path
restores fidelity to the coarse values for the time-varying part.
The slopes are applied to the full fine-predictor range. Where the
fine elevation reaches values the coarse window never saw, the
regression extrapolates linearly. The fine output is only as trustworthy
as a linear relationship over that extended range, which is a reason to
keep predictors that stay roughly linear with the response and to
inspect the result where the terrain is extreme. Setting
clamp = TRUE bounds the downscaled field to the observed
range of the coarse response, capping such extrapolation at the values
the variable actually takes.
Reference
Karger, D. N., Conrad, O., Bohner, J., Kawohl, T., Kreft, H., Soria-Auza, R. W., Zimmermann, N. E., Linder, H. P., and Kessler, M. (2017). Climatologies at high resolution for the earth’s land surface areas. Scientific Data 4, 170122. doi:10.1038/sdata.2017.122