Areal Spatial Modelling (R)¶

spdep for autocorrelation, CARBayes for BYM, sf maps throughout¶

The R counterpart to areal_python.ipynb. Where the Python notebook builds the CAR sampler from scratch, this notebook uses the field-standard spatial stack: spdep for the spatial weights and Moran's I, CARBayes for the Bayesian BYM disease-mapping model, and sf + ggplot2 to draw the choropleth maps inline. Same data: the North Carolina SIDS counties.

In [1]:
options(repr.plot.width=8.2, repr.plot.height=3.4)
.libPaths(c("C:/Users/user/R/win-library/4.6", .libPaths()))
suppressMessages({library(sf); library(spdep); library(CARBayes); library(ggplot2)})
nc <- st_read(system.file("shape/nc.shp", package="sf"), quiet=TRUE)
nc$expected <- nc$BIR74 * sum(nc$SID74)/sum(nc$BIR74)          # expected SIDS given births
nc$SMR <- nc$SID74 / nc$expected
nc$nonwhite <- scale(nc$NWBIR74 / nc$BIR74)[,1]
cat(nrow(nc), "NC counties;", sum(nc$SID74), "SIDS deaths;", "raw SMR range", round(min(nc$SMR),1), "-", round(max(nc$SMR),1), "\n")
100 NC counties; 667 SIDS deaths; raw SMR range 0 - 4.7 

1. Raw SMR map and spatial weights¶

The standardised mortality ratio (observed / expected SIDS) mapped across counties — noisy in small counties. poly2nb builds the neighbour graph from the polygons and nb2listw the weights.

In [2]:
nb <- poly2nb(nc); lw <- nb2listw(nb, style="W")
cat("avg neighbours:", round(mean(card(nb)),1), "\n")
ggplot(nc) + geom_sf(aes(fill=SMR), color="white", linewidth=0.15) +
  scale_fill_distiller(palette="OrRd", direction=1) +
  labs(title="Raw SMR (observed / expected SIDS)", fill="SMR") + theme_void()
avg neighbours: 4.9 
No description has been provided for this image

2. Moran's I — spatial autocorrelation¶

moran.test gives the analytic test and moran.mc a permutation version; moran.plot draws the scatterplot of each county against its neighbours' mean.

In [3]:
mt <- moran.mc(nc$SMR, lw, nsim=999)
cat(sprintf("Moran's I = %.3f, permutation p = %.4f  -> SIDS rates are spatially autocorrelated\n", mt$statistic, mt$p.value))
# permutation null distribution: reshuffle the values over the map and recompute I
hist(mt$res, breaks=30, col="grey80", border="white", xlim=range(c(mt$res, mt$statistic)),
     xlab="Moran's I", main=sprintf("Permutation null (999 reshuffles),  p = %.3f", mt$p.value))
abline(v=mt$statistic, col="red", lwd=3); legend("topright", "observed I", col="red", lwd=3, bty="n")
moran.plot(nc$SMR, lw, xlab="county SMR", ylab="mean of neighbours' SMR", main="Moran scatterplot")
Moran's I = 0.231, permutation p = 0.0010  -> SIDS rates are spatially autocorrelated
No description has been provided for this image
No description has been provided for this image

3. BYM disease mapping with CARBayes¶

S.CARbym fits the Besag–York–Mollié Poisson model — a spatially structured plus an unstructured random effect — with our nonwhite-births covariate and the births-based offset. The fitted relative risk is the smoothed map.

In [4]:
W <- nb2mat(nb, style="B")
dat <- data.frame(SID74=nc$SID74, expected=nc$expected, nonwhite=nc$nonwhite)   # plain data.frame for the model
set.seed(1)
m <- S.CARbym(SID74 ~ offset(log(expected)) + nonwhite, family="poisson", data=dat,
              W=W, burnin=5000, n.sample=20000, thin=10, verbose=FALSE)
bet <- m$summary.results["nonwhite", c("Mean","2.5%","97.5%")]
cat(sprintf("covariate (nonwhite births): beta = %.2f  95%% CrI [%.2f, %.2f]\n", bet[1], bet[2], bet[3]))
nc$RR <- m$fitted.values / nc$expected
cat(sprintf("shrinkage: raw SMR sd %.2f -> BYM relative-risk sd %.2f\n", sd(nc$SMR), sd(nc$RR)))
p1<-ggplot(nc)+geom_sf(aes(fill=SMR),color="white",linewidth=0.15)+scale_fill_distiller(palette="OrRd",direction=1,limits=c(0,max(nc$SMR)))+labs(title="Raw SMR (noisy)",fill="")+theme_void()
p2<-ggplot(nc)+geom_sf(aes(fill=RR),color="white",linewidth=0.15)+scale_fill_distiller(palette="OrRd",direction=1,limits=c(0,max(nc$SMR)))+labs(title="BYM smoothed relative risk",fill="")+theme_void()
suppressMessages(library(patchwork)); print(p1 + p2)
covariate (nonwhite births): beta = 0.40  95% CrI [0.28, 0.54]
shrinkage: raw SMR sd 0.78 -> BYM relative-risk sd 0.44
No description has been provided for this image

The exceedance probability $P(\text{RR}>1.5)$ from the posterior samples flags the counties that are elevated with confidence — the decision map.

In [5]:
risk.samples <- m$samples$fitted / matrix(nc$expected, nrow=nrow(m$samples$fitted), ncol=nrow(nc), byrow=TRUE)
nc$exceed <- colMeans(risk.samples > 1.5)
ggplot(nc) + geom_sf(aes(fill=exceed), color="white", linewidth=0.15) +
  scale_fill_distiller(palette="Reds", direction=1, limits=c(0,1)) +
  labs(title="P(relative risk > 1.5) — exceedance", fill="P") + theme_void()
cat(sprintf("%d counties have P(RR>1.5) > 0.9 (elevated with high confidence)\n", sum(nc$exceed>0.9)))
7 counties have P(RR>1.5) > 0.9 (elevated with high confidence)
No description has been provided for this image

4. Summary¶

The standard R spatial stack reproduces the from-scratch results. spdep confirms significant spatial autocorrelation in the SIDS rates (Moran's I permutation test), CARBayes's S.CARbym fits the Besag–York–Mollié model — recovering the positive nonwhite-births covariate effect and shrinking the noisy raw SMR into a smooth relative-risk map — and sf/ggplot2 render every step as a choropleth, with an exceedance-probability map flagging the confidently-elevated counties.

spdep, CARBayes and sf are the field-standard areal-data tools and the package mirror of the Bayesian sampler in areal_python.ipynb; they are the same tools used for the Scotland lip-cancer BYM in Bayesian Hierarchical Spatial Poisson Model. This areal foundation underpins the spatial arc and maps directly onto regional disability/mortality surveillance. Next, spatial econometrics reuses this neighbour graph in a simultaneous-autoregressive model of spillovers.