The constructor for the spatial_fit class, and the public entry point
for plugging your own model backend into this package. The three built-in
fitters (fit_gwr_model(),
fit_bayesian_spatial_model() and fit_rf_model())
all end by calling it, and so should a custom fit_fn written for
cv_spatial(): wrapping your model in a spatial_fit is
what lets it use the package's folds, metrics, comparison and
area-of-applicability machinery unchanged.
Usage
new_spatial_fit(
subclass,
engine,
formula,
response_var,
predictor_vars,
data_sf,
info = list()
)Arguments
- subclass
Character scalar naming the class to stamp on the object: one of the built-ins
"gwr_fit","bayesian_fit"or"rf_fit", or any name of your own for a custom backend (say"lm_fit"). It is the S3 dispatch key:predict(),fitted(),residuals()andcoef()on the result all dispatch on it, so a customsubclassrequires a matchingpredict.<subclass>()method to be usable withcv_spatial().- engine
The raw model object your backend produced (an
lm, arangerobject, abrmsfit, ...). Nothing inspects it except your own methods.- formula
A formula.
- response_var
Character(1).
- predictor_vars
Character vector.
- data_sf
An sf object used for fitting.
- info
Named list of model-specific extras. Set
fitted_are_oob = TRUEwhen yourfitted()values are held out instead of in-sample, sosummary()labels them correctly.
Details
There are two obligations. Return an object built here from your
fit_fn, and define a predict() method for the subclass
you chose. cv_spatial() scores folds by calling the
predict() generic on the fit, so without a matching
predict.<subclass>() every fold fails. A
fitted.<subclass>() method returning one value per row of the fit's
data_sf, in the same order, is required by
summary() and model_metrics(): both error,
naming the method to define, without one. residuals() and
coef() methods are optional.
The coef() contract
coef() on one of the three built-in backends either returns the
coefficients or signals an error. It never returns NULL. A custom
subclass inherits stats::coef.default(), which returns NULL,
so define a coef.<subclass>() that errors when your backend has no
coefficients; otherwise the hazard described below applies to your own fits.
coef.rf_fit()
always errors, because a forest has no coefficients; coef.gwr_fit()
and coef.bayesian_fit() error when the backend cannot supply them
(a missing package, an engine without the expected component). A
NULL return would be indistinguishable from "this model has no
fixed effects", so lapply(fits, coef) would quietly produce a
shorter answer than the caller expected. Wrap in try() or
tryCatch() when sweeping over a heterogeneous list of fits.
See also
cv_spatial(), which consumes a custom fit_fn;
fit_rf_model() for a worked built-in fitter.
Other model fitting:
fit_bayesian_spatial_model(),
fit_gwr_model(),
fit_rf_model(),
gp_lengthscale_bounds(),
prep_model_data()
Examples
library(sf)
set.seed(1)
n <- 80
site <- st_as_sf(
data.frame(x = 5e5 + runif(n, 0, 1000), y = 5e6 + runif(n, 0, 1000),
elev = rnorm(n)),
coords = c("x", "y"), crs = 32632
)
site$price <- 10 + 0.01 * (st_coordinates(site)[, 1] - 5e5) +
2 * site$elev + rnorm(n)
# A custom backend: an ordinary linear model behind the spatial_fit interface.
lm_fit <- function(train_sf) {
new_spatial_fit(
subclass = "lm_fit",
engine = lm(price ~ elev, st_drop_geometry(train_sf)),
formula = price ~ elev,
response_var = "price",
predictor_vars = "elev",
data_sf = train_sf
)
}
# Required: cv_spatial() scores each fold through the predict() generic,
# which dispatches on the subclass named above.
predict.lm_fit <- function(object, newdata = NULL, ...) {
if (is.null(newdata)) newdata <- object$data_sf
as.numeric(stats::predict(object$engine, st_drop_geometry(newdata)))
}
registerS3method("predict", "lm_fit", predict.lm_fit)
cv <- cv_spatial(site, "price", "elev", fit_fn = lm_fit, k = 3, seed = 1)
#> cv_spatial(): no folds supplied -- using spatial block k-fold CV (k=3).
cv$overall
#> RMSE MAE MAPE SMAPE R2 Adj_R2 n_pred n_MAPE n_SMAPE
#> 1 3.472328 3.009688 21.29953 20.27029 0.2341257 NA 80 80 80
# Always check these two agree before trusting the metrics above.
c(attempted = cv$n_folds_attempted, succeeded = cv$n_folds_succeeded)
#> attempted succeeded
#> 3 3