I’m going to preface this with, I’m no Generalized Additive Model or spatial statistics expert … I know enough to be “dangerous” … maybe that’s a bad thing 😉
Today I had a little bit of a thought experiment while thinking about Hotspot mapping. I’ve been wading pretty deeply into the spatio-temporal Generalized Additive Model (GAM) world recently and have been wondering if a GAM framework could be used to identify hotspots. Generally, in spatial statistics hotspots are defined as an area where values are greater than the mean. Typically, this is performed using the Getis-Ord Gi statistic which generally is a modified z-score to account for nearest neighbor distances in a spatial network. The Getis-Ord statistic can also be used to identify cold spots (values less than the mean).
If we have a generalized GAM model like
yi ~ s(yr) + s(x,y) + ti(x,y,yr)
where we have a time (yr), location (x,y), and an interaction term (x,y,yr) between location and time to evaluate changes in space and time. Given this model, the location effects plot shows spatially how spatially the data varies. Couldn’t this be used to identify locations that are (statistically) significantly greater or less than the mean (or some scaled value)? I know when attempting to detect significant changes in a time series from a GAM you need to use the derivative to determine if its significantly different from 0 (...i think) based on Dr Simpson's post. Would something similar have to be done with the s(x,y) term?
Here is some basic R-code with a reproducible example to get the ball rolling. In the code, there is a section that includes a Getis-Ord Gi statistic calculation. The GAM section of the code is relatively short as I'm not sure what is the next best step...as seen in the effects plots there are some negative and positive regions that correspond pretty closely to the Getis-Ord statistics.
## Spatial Data/Analysis
library(raster)
library(sf)
library(spdep)
library(spatstat)
## go-to GAM magic
library(mgcv)
library(gratia)
## Make some data
## https://www.r-bloggers.com/2020/02/spatial-predictions-with-gams-and-rasters/
rbase <- raster(extent(c(0,1,0,1)), nrow = 50, ncol = 50)
rx1 <- rx2 <- rbase
rx1[] <- xFromCell(rbase, 1:ncell(rbase))
rx2[] <- yFromCell(rbase, 1:ncell(rbase))
rtrue <- 6*rx1 - 7*rx1^2- 4*rx2
par(mfrow = c(2,2))
plot(rx1, col = RColorBrewer::brewer.pal(11, "RdBu"), main = "x1")
plot(rx2, col = RColorBrewer::brewer.pal(11, "RdBu"), main = "x2")
plot(rtrue, col = RColorBrewer::brewer.pal(11, "RdBu"), main = "True values")
set.seed(42)
site_means <- data.frame(x = runif(50), y = runif(50)) |>
mutate(site = 1:50,
x1 = extract(rx1, cbind(x,y)),
x2 = extract(rx2, cbind(x,y)),
eta = extract(rtrue, cbind(x,y)),
z = rnorm(50, sd = 1),
yhat = eta + z)
dat <- merge(site_means,expand.grid(site = 1:50, yr = 1:5),"site")|>
mutate(b = rnorm(250, mean = yhat, sd = 0.5))
dat$b<-dat$b+5 #(added 5 to have positive values ... I know I could just do a rlnorm)
## Getis Ord analysis
## a ref https://swampthingecology.org/blog/hot-spot-analysis-geospatial-data-analysis-in-rstats.-part-3/
# turn data into a spatial point data type
# the spstats package needs spatial data to do the nearest neighbor analysis
p.sf <- st_as_sf(dat, coords = c("y", "x"), crs = 4326)
# Find distance range
ptdist=pointDistance(p.sf)
min.dist<-min(ptdist); # Minimum
mean.dist<-mean(ptdist); # Mean
nb<-dnearneigh(st_coordinates(p.sf),min.dist,mean.dist)
nb_lw<-nb2listw(nb,style="B")
## Global GI
globalG.test(p.sf$b,nb_lw,alternative="two.sided")
# local G
nb_lw<-nb2listw(nb)
local_g<-localG(p.sf$b,nb_lw)
# convert to matrix
local_g=as.matrix(local_g)
# column-bind the local_g data
p.sf<-cbind(p.sf,local_g)
# two side ρ-value
p.sf$pval<- 2*pnorm(-abs(p.sf$local_g))
# should identify several significant hotspots (points)
## simple GAM
m1=gam(b~
s(yr,k=5,bs="tp")+
s(x,y,bs="ds")+
ti(x,y,yr,d=c(2,1),bs=c("ds","tp")),
data=dat)
summary(m1)
draw(m1)