This vignette fits a binomial geostatistical model for loaloa
prevalence using a village-level IID effect and a FEM Matérn spatial
field (shape = 2, ν = 2), matching the Matérn shape used in
geostatsp examples. When INLA is available, the same
model is also fit with geostatsp::glgm for comparison.
data("loaloa", package = "geostatsp")
loaloa <- terra::unwrap(loaloa)
elev_r <- terra::unwrap(elevationLoa)
evi_r <- terra::unwrap(eviLoa)
elev_s_r <- (elev_r - 500) / 100
evi_s_r <- (
evi_r - mean(terra::values(evi_r), na.rm = TRUE)
) / sd(terra::values(evi_r), na.rm = TRUE)
loaloa_for_fit <- geostatsp:::gm.dataSpatial(
formula = y ~ elev + evi,
data = loaloa,
covariates = list(elev = elev_s_r, evi = evi_s_r),
grid = geostatsp::squareRaster(loaloa, cells = 200, buffer = 1e4)
)Use a coarse knot raster over a buffered region around the villages;
pass it directly as knots to matern() (extent
endpoints plus interior cell centers; see
knots_from_spatraster()). (geostatsp /
terra may mask matern, so call it with
::.)
geostatsp Matérn shape = 2 is ν = 2 (SPDE α = 3 in
2D), so we pass shape = 2 to matern() (cubic
B-splines / random_fem_ssq_3). Locations come from the
geometry column (WKT) in the model data frame.
knots_grid <- geostatsp::squareRaster(loaloa, cells = 6, buffer = 300 * 1000)
loaloa_df <- as.data.frame(loaloa_for_fit$data, geom = "WKT")
loaloa_model <- adlaplace::binomial(y, size = N) ~
elev + evi +
adlaplace::iid(villageID, init = 0.2) +
adlaplaceGrf::matern(
geometry,
knots = knots_grid,
shape = 2L,
# range = practical rho = sqrt(8*nu)/kappa (m); sd = field SD
init = c(100 * 1000, 0.2),
lower = c(1 * 1000, 0.001)
)fit <- adlaplace::adlaplace(
loaloa_model,
data = loaloa_df,
num_shards = 20L,
control = list(maxit = 200, trace = 3, REPORT = 1),
config = list(num_threads = 2L),
verbose = FALSE
)
#> N = 6, M = 5 machine precision = 2.22045e-16
#> At X0, 0 variables are exactly at the bounds
#> At iterate 0 f= 11024 |proj g|= 1810.4
#> At iterate 1 f = 10802 |proj g|= 1704.3
#> At iterate 2 f = 10280 |proj g|= 880.56
#> At iterate 3 f = 10047 |proj g|= 323.65
#> At iterate 4 f = 9961.9 |proj g|= 121.73
#> At iterate 5 f = 9914.7 |proj g|= 90.542
#> At iterate 6 f = 9877 |proj g|= 41.35
#> At iterate 7 f = 9869.2 |proj g|= 16.928
#> At iterate 8 f = 9866.4 |proj g|= 11.085
#> At iterate 9 f = 9865 |proj g|= 2.8793
#> At iterate 10 f = 9864.8 |proj g|= 15.332
#> At iterate 11 f = 9864.5 |proj g|= 2.0543
#> At iterate 12 f = 9864.5 |proj g|= 2.3233
#> At iterate 13 f = 9864.3 |proj g|= 1.5686
#> At iterate 14 f = 9864.2 |proj g|= 2.0568
#> At iterate 15 f = 9863.9 |proj g|= 2.3491
#> At iterate 16 f = 9863.9 |proj g|= 3.4063
#> At iterate 17 f = 9863.8 |proj g|= 2.4097
#> At iterate 18 f = 9863.7 |proj g|= 4.3066
#> At iterate 19 f = 9863.7 |proj g|= 2.3779
#> At iterate 20 f = 9863.6 |proj g|= 3.0863
#> At iterate 21 f = 9863.3 |proj g|= 4.1074
#> At iterate 22 f = 9863.1 |proj g|= 7.4599
#> At iterate 23 f = 9863 |proj g|= 2.3925
#> At iterate 24 f = 9862.9 |proj g|= 3.0329
#> At iterate 25 f = 9862.9 |proj g|= 0.86045
#> At iterate 26 f = 9862.9 |proj g|= 0.70823
#> At iterate 27 f = 9862.9 |proj g|= 0.52402
#> At iterate 28 f = 9862.9 |proj g|= 0.027066
#> At iterate 29 f = 9862.9 |proj g|= 0.1095
#> At iterate 30 f = 9862.9 |proj g|= 0.036299
#> At iterate 31 f = 9862.9 |proj g|= 0.022447
#>
#> iterations 31
#> function evaluations 37
#> segments explored during Cauchy searches 31
#> BFGS updates skipped 0
#> active bounds at final generalized Cauchy point 0
#> norm of the final projected gradient 0.0224474
#> final function value 9862.91
#>
#> F = 9862.91
#> final value 9862.910184
#> converged
fit$optim$convergence
#> [1] 0
coef(fit)
#> elev_linear evi_linear intercept
#> -2.588382e-01 8.046617e-01 -2.351296e+00
#> villageID_iid geometry_matern_log_range geometry_matern_log_sd
#> 6.587140e-01 2.615201e+05 1.771369e+00loaFit <- geostatsp::glgm(
y ~ elev_s + evi_s +
f(
villageID,
prior = "pc.prec",
param = c(1, 0.5),
model = "iid"
),
loaloa,
grid = 100,
covariates = list(elev_s = elev_s_r, evi_s = evi_s_r),
family = "binomial",
Ntrials = loaloa$N,
shape = 2,
buffer = 25000,
prior = list(sd = 1, range = 500 * 1000),
control.inla = list(strategy = "gaussian")
)logLik(fit)
#> 'log Lik.' -9862.91 (df=6)
cf <- coef(fit)
ad_pars <- c(
intercept = unname(cf[["intercept"]]),
elev = unname(cf[["elev_linear"]]),
evi = unname(cf[["evi_linear"]]),
village_sd = unname(cf[["villageID_iid"]]),
# matern: practical range rho = sqrt(8*nu)/kappa (m); sd = field SD
range = unname(cf[["geometry_matern_log_range"]]),
sd = unname(cf[["geometry_matern_log_sd"]])
)
ad_pars
#> intercept elev evi village_sd range
#> -2.351296e+00 -2.588382e-01 8.046617e-01 6.587140e-01 2.615201e+05
#> sd
#> 1.771369e+00if (length(loaFit$parameters)) {
glgm_sum <- loaFit$parameters$summary
knitr::kable(glgm_sum[, c("mean", "0.025quant", "0.975quant")], digits = 3)
# Map ad_pars names to rows of loaFit$parameters$summary
row_for <- function(...) {
pats <- c(...)
hits <- unique(unlist(lapply(pats, function(p) {
grep(p, rownames(glgm_sum), ignore.case = TRUE)
})))
if (!length(hits)) {
return(NA_character_)
}
rownames(glgm_sum)[hits[1L]]
}
map <- c(
intercept = row_for("^\\(Intercept\\)$", "^Intercept$"),
elev = row_for("^elev"),
evi = row_for("^evi"),
village_sd = row_for("^sd villageID$", "sd.?villageID", "villageID.*[Ss]d"),
range = row_for("^range/1000$", "^range$"),
sd = row_for("^sd$", "SD for space")
)
glgm_pars <- setNames(
vapply(map, function(nm) {
if (is.na(nm)) {
return(NA_real_)
}
as.numeric(glgm_sum[nm, "mean"])
}, numeric(1)),
names(map)
)
# summary may store range in km as "range/1000"
if (!is.na(map[["range"]]) && identical(map[["range"]], "range/1000")) {
glgm_pars[["range"]] <- glgm_pars[["range"]] * 1000
}
compare <- rbind(
adlaplace = ad_pars[names(map)],
glgm = glgm_pars
)
knitr::kable(compare, digits = 3)
}Posterior mean and SD of the spatial field from both fits (glgm when available):
data("worldMap", package = "mapmisc")
worldMap <- terra::unwrap(worldMap)
worldMap <- terra::crop(worldMap, terra::ext(c(-2, 10, -10, 5) * 1e6))
worldMapT <- terra::project(worldMap, loaloa)
mapmisc::map.new(loaloa)
if (plot_compare) {
terra::plot(
loaFit$raster[["random.mean"]],
add = TRUE, col = my_col$col, breaks = my_col$breaks, legend = FALSE
)
}
terra::plot(worldMapT, add = TRUE)
mapmisc::legendBreaks("right", my_col)Posterior mean and SD of the spatial field (glgm vs adlaplace).
mapmisc::map.new(loaloa)
terra::plot(
my_sim[["mean"]],
add = TRUE, col = my_col$col, breaks = my_col$breaks, legend = FALSE
)
terra::plot(worldMapT, add = TRUE)
mapmisc::legendBreaks("right", my_col)Posterior mean and SD of the spatial field (glgm vs adlaplace).
mapmisc::map.new(loaloa)
if (plot_compare) {
terra::plot(
loaFit$raster[["random.sd"]],
add = TRUE, col = sd_col$col, breaks = sd_col$breaks, legend = FALSE
)
}
terra::plot(worldMapT, add = TRUE)
mapmisc::legendBreaks("right", sd_col)Posterior mean and SD of the spatial field (glgm vs adlaplace).
mapmisc::map.new(loaloa)
terra::plot(
my_sim[["sd"]],
add = TRUE, col = sd_col$col, breaks = sd_col$breaks, legend = FALSE
)
terra::plot(worldMapT, add = TRUE)
mapmisc::legendBreaks("right", sd_col)Posterior mean and SD of the spatial field (glgm vs adlaplace).
Four posterior draws of the adlaplace Matérn field:
sim_layers <- paste0("sim", 1:6)
terra::plot(my_sim[[sim_layers]], nc = 2, col = my_col$col, breaks = my_col$breaks, legend = FALSE)
mapmisc::legendBreaks("right", my_col, bty = "n")