8.3 Spatial modeling of rainfall in Paraná, Brazil
8.3.1 Data
Average rainfall recorded over different year over May-June at 143 stations
data.frame(cbind(parana$coords, Rainfall = parana$data)) |>
ggplot()+
geom_point(aes(east, north, color = Rainfall), size = 2) +
coord_fixed(ratio = 1) +
scale_color_gradient(low = "blue", high = "orange") +
geom_path(data = data.frame(parana$border), aes(east, north)) +
theme_bw()
8.3.2 Model
\[ Y_i \sim N (\mu_i, \sigma^2), i = 1,2, ..., n\]
\[\mu_i = \beta_0 + Z(si) \]
Rainfall as a normal distribution with a mean that follow a zero-mean Gaussian process with a Matèrn covariance function.
8.3.2.1 Mesh construction
coo <- parana$coords # I think they are in degree
summary(dist(coo))## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 1.0 143.5 231.2 244.1 333.6 619.5
mesh <- inla.mesh.2d(
loc = coo, # our coords
offset = c(50, 100), # inner border and outer border
cutoff = 1, # avoid building small and plenty triangle if point are close
max.edge = c(30, 60) # small triangle (30) and big one outside (60)
)
# we have a warning of a deprecated function
plot(mesh)
points(coo, col = "red")
str(mesh, max.level = 1)## List of 8
## $ meta :List of 5
## $ manifold: chr "R2"
## $ n : int 1189
## $ loc : num [1:1189, 1:3] 219 679 819 819 614 ...
## $ graph :List of 5
## $ segm :List of 2
## $ idx :List of 2
## $ crs : NULL
## - attr(*, "class")= chr "inla.mesh"
On this one we did not use boundary:
bnd <- inla.nonconvex.hull(coo)
meshb <- inla.mesh.2d(
boundary = bnd, offset = c(50, 100),
cutoff = 1, max.edge = c(30, 60)
)
plot(meshb)
points(coo, col = "red")
8.3.2.2 SPDE model on the mesh
\[ \alpha = v + d/2\]
d = 2 in spatial 2D case
spde <- inla.spde2.matern(mesh = mesh, alpha = 2, constr = TRUE)indexs <- inla.spde.make.index("s", spde$n.spde)8.3.2.3 Projection matrix:
We need to build a matrix that contains n rows (n = number of locations) and m column (m = vertex of triangle).

A <- inla.spde.make.A(mesh = mesh, loc = coo)
rowSums(A) |> sum()## [1] 143
8.3.2.4 Prediction data
We nee to build a grid: 50 x 50
bb <- bbox(parana$border) # bounding box
x <- seq(bb[1, "min"] - 1, bb[1, "max"] + 1, length.out = 50) # increase a bit the size
y <- seq(bb[2, "min"] - 1, bb[2, "max"] + 1, length.out = 50)
coop <- as.matrix(expand.grid(x, y))Then you keep point only inside the border:
ind <- point.in.polygon(
coop[, 1], coop[, 2],
parana$border[, 1], parana$border[, 2]
)
coop <- coop[which(ind == 1), ]
plot(coop, asp = 1)
And create a projection matrix with it:
Ap <- inla.spde.make.A(mesh = mesh, loc = coop)
dim(Ap) ## [1] 1533 1189
8.3.2.5 Building estimation and prediction “stack”
# stack for estimation stk.e
stk.e <- inla.stack(
tag = "est",
data = list(y = parana$data),
A = list(1, A),
effects = list(data.frame(b0 = rep(1, nrow(coo))), s = indexs) # our models b0 and spatial effect
)
# stack for prediction stk.p
stk.p <- inla.stack(
tag = "pred",
data = list(y = NA), # to be filled
A = list(1, Ap),
effects = list(data.frame(b0 = rep(1, nrow(coop))), s = indexs)
)
# stk.full has stk.e and stk.p
stk.full <- inla.stack(stk.e, stk.p)8.3.2.7 Inla() call
This is not intuitive:
res <- inla(formula,
data = inla.stack.data(stk.full), #it has both data and grid
control.predictor = list(
compute = TRUE,
A = inla.stack.A(stk.full)
)
)8.3.2.8 result!
We will get the index of the rows with prediction and use it to extract the correct mean and quantiles:
index <- inla.stack.index(stk.full, tag = "pred")$data
pred_mean <- res$summary.fitted.values[index, "mean"]
pred_ll <- res$summary.fitted.values[index, "0.025quant"]
pred_ul <- res$summary.fitted.values[index, "0.975quant"]We build a tidy data frame with it:
dpm <- rbind(
data.frame(
east = coop[, 1], north = coop[, 2],
value = pred_mean, variable = "pred_mean"
),
data.frame(
east = coop[, 1], north = coop[, 2],
value = pred_ll, variable = "pred_ll"
),
data.frame(
east = coop[, 1], north = coop[, 2],
value = pred_ul, variable = "pred_ul"
)
)
dpm$variable <- as.factor(dpm$variable)And map it:
ggplot(dpm) + geom_tile(aes(east, north, fill = value)) +
facet_wrap(~variable, nrow = 1) +
coord_fixed(ratio = 1) +
scale_fill_gradient(
name = "Rainfall",
low = "blue", high = "orange"
) +
theme_bw()
8.3.2.9 just the spatial field:
newloc <- cbind(c(219, 678, 818), c(20, 20, 160))
Aproj <- inla.spde.make.A(mesh, loc = newloc)
Aproj %*% res$summary.random$s$mean## 3 x 1 Matrix of class "dgeMatrix"
## [,1]
## [1,] -3.1661566
## [2,] 0.8556036
## [3,] -1.2270720
rang <- apply(mesh$loc[, c(1, 2)], 2, range)
proj <- inla.mesh.projector(mesh,
xlim = rang[, 1], ylim = rang[, 2],
dims = c(300, 300)
)
mean_s <- inla.mesh.project(proj, res$summary.random$s$mean)
sd_s <- inla.mesh.project(proj, res$summary.random$s$sd)
df <- expand.grid(x = proj$x, y = proj$y)
df$mean_s <- as.vector(mean_s)
df$sd_s <- as.vector(sd_s)
library(viridis)
library(cowplot)
gmean <- ggplot(df, aes(x = x, y = y, fill = mean_s)) +
geom_raster() +
scale_fill_viridis(na.value = "transparent") +
coord_fixed(ratio = 1) + theme_bw()
gsd <- ggplot(df, aes(x = x, y = y, fill = sd_s)) +
geom_raster() +
scale_fill_viridis(na.value = "transparent") +
coord_fixed(ratio = 1) + theme_bw()
plot_grid(gmean, gsd)