Last week, we started talking about spatial autocorrelation and how to measure it. This week, we’ll talk more specifically about what to do if you want to run a regression model that has spatial dependence. First, we’ll start with the simplest model: a spatial error model. We use this model when we believe that our error term is spatially correlated.

#load in data and packages first
library(sf)     
## Warning: package 'sf' was built under R version 4.3.2
## Linking to GEOS 3.11.2, GDAL 3.7.2, PROJ 9.3.0; sf_use_s2() is TRUE
library(dplyr)   
## Warning: package 'dplyr' was built under R version 4.3.3
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(spData) 
## Warning: package 'spData' was built under R version 4.3.2
## To access larger datasets in this package, install the spDataLarge
## package with: `install.packages('spDataLarge',
## repos='https://nowosad.github.io/drat/', type='source')`
library(ggplot2)
## Warning: package 'ggplot2' was built under R version 4.3.3
library(ggthemes)
library(spdep)
## Warning: package 'spdep' was built under R version 4.3.3
library(spatialreg)
## Warning: package 'spatialreg' was built under R version 4.3.3
## Loading required package: Matrix
## Warning: package 'Matrix' was built under R version 4.3.2
## 
## Attaching package: 'spatialreg'
## The following objects are masked from 'package:spdep':
## 
##     get.ClusterOption, get.coresOption, get.mcOption,
##     get.VerboseOption, get.ZeroPolicyOption, set.ClusterOption,
##     set.coresOption, set.mcOption, set.VerboseOption,
##     set.ZeroPolicyOption
library(GWmodel)
## Warning: package 'GWmodel' was built under R version 4.3.3
## Loading required package: robustbase
## Warning: package 'robustbase' was built under R version 4.3.3
## Loading required package: sp
## Warning: package 'sp' was built under R version 4.3.2
## Loading required package: Rcpp
## Welcome to GWmodel version 2.3-2.
library(tidyr)
## Warning: package 'tidyr' was built under R version 4.3.3
## 
## Attaching package: 'tidyr'
## The following objects are masked from 'package:Matrix':
## 
##     expand, pack, unpack
#Let's load in some packages and data
setwd("C:/Users/amode/OneDrive/Documents/DIDA370/chicago_airbnb")
chic <- st_read("airbnb_Chicago 2015.shp")
## Reading layer `airbnb_Chicago 2015' from data source 
##   `C:\Users\amode\OneDrive\Documents\DIDA370\chicago_airbnb\airbnb_Chicago 2015.shp' 
##   using driver `ESRI Shapefile'
## Simple feature collection with 77 features and 20 fields
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: -87.94011 ymin: 41.64454 xmax: -87.52414 ymax: 42.02304
## Geodetic CRS:  WGS 84
#As always, let's plot the data first

ggplot()+
  geom_sf(data = chic, aes(fill=as.numeric(poverty)))+
  scale_fill_steps(
    name = "Inequality",
    low = "lightsteelblue1",
    high = "tomato1",
    n.breaks = 5,
    show.limits = T)+
  theme_void()

RESEARCH QUESTION: Does hardship, crime, or without high school rates effect unemployment rates in Chicago?

#create our variables for the model:
model <- chic %>% 
  select(c(poverty, num_crimes, harship_in, without_hs, geometry)) %>% 
  filter(!poverty %in% "NA") %>% 
  mutate(poverty = as.numeric(poverty))

#Run a basic model

summary(lm1 <- lm(poverty ~ num_crimes + harship_in + without_hs, data = model))
## 
## Call:
## lm(formula = poverty ~ num_crimes + harship_in + without_hs, 
##     data = model)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -9.4884 -3.7719 -0.7289  3.5364 15.2060 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  7.0167604  1.4205383   4.940 4.81e-06 ***
## num_crimes   0.0002010  0.0001082   1.858   0.0672 .  
## harship_in   0.5054880  0.0371429  13.609  < 2e-16 ***
## without_hs  -0.5730250  0.0889068  -6.445 1.09e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 5.371 on 73 degrees of freedom
## Multiple R-squared:  0.7916, Adjusted R-squared:  0.783 
## F-statistic: 92.43 on 3 and 73 DF,  p-value: < 2.2e-16
#Let's test for spatial autocorrelation in the residuals!
#Create the weights first
chic_list <- model %>% 
  poly2nb(st_geometry(model)) %>% 
  nb2listw(zero.policy = TRUE) 

#run the Moran's I test for regression residuals
lm.morantest(lm1, chic_list)
## 
##  Global Moran I for regression residuals
## 
## data:  
## model: lm(formula = poverty ~ num_crimes + harship_in + without_hs,
## data = model)
## weights: chic_list
## 
## Moran I statistic standard deviate = 7.1458, p-value = 4.473e-13
## alternative hypothesis: greater
## sample estimates:
## Observed Moran I      Expectation         Variance 
##      0.462498501     -0.033672615      0.004821243

The p-value in this Moran test is weakly significant, which means there is some evidence of spatial autocorrelation in the residuals

lm_error <- errorsarlm(poverty ~ num_crimes + harship_in + without_hs,
              data = model,
              listw = chic_list,
              zero.policy = TRUE, 
              na.action = na.omit)

summary(lm_error)
## 
## Call:errorsarlm(formula = poverty ~ num_crimes + harship_in + without_hs, 
##     data = model, listw = chic_list, na.action = na.omit, zero.policy = TRUE)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -6.46844 -2.83613 -0.66768  2.38386 14.87910 
## 
## Type: error 
## Coefficients: (asymptotic standard errors) 
##                Estimate  Std. Error z value  Pr(>|z|)
## (Intercept)  5.5442e+00  2.0486e+00  2.7063  0.006803
## num_crimes   7.0210e-05  9.2334e-05  0.7604  0.447021
## harship_in   5.3690e-01  3.9277e-02 13.6695 < 2.2e-16
## without_hs  -5.2320e-01  9.4885e-02 -5.5141 3.505e-08
## 
## Lambda: 0.7366, LR test value: 35.225, p-value: 2.9372e-09
## Asymptotic standard error: 0.084296
##     z-value: 8.7382, p-value: < 2.22e-16
## Wald statistic: 76.357, p-value: < 2.22e-16
## 
## Log likelihood: -219.0248 for error model
## ML residual variance (sigma squared): 14.876, (sigma: 3.857)
## Number of observations: 77 
## Number of parameters estimated: 6 
## AIC: 450.05, (AIC for lm: 483.27)

Since Lamda is insignificant we should not use this model

#This code just allows me to manually extract the residual values
#Normally, R wants an LM object for this test - 
#Running a spatial error model does not output an lm object, which is why we need to do this
#Create a blank vector the length of the variable
seResiduals <- rep(0, length(model$poverty))
#create an index based on the order of the residuals in the lm_error object
resIndex <- lm_error$residuals %>% names() %>% as.integer();
#use the index to fill in the seResiduals object with corresponding residual values
seResiduals[resIndex] <- lm_error$residuals

#perform the Moran Test
chic_list %>%
  moran.test(seResiduals, ., zero.policy = TRUE) 
## 
##  Moran I test under randomisation
## 
## data:  seResiduals  
## weights: .    
## 
## Moran I statistic standard deviate = -0.46932, p-value = 0.6806
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic       Expectation          Variance 
##       -0.04648430       -0.01315789        0.00504237

The linear error model is not significant.

lm_lag <- lagsarlm(poverty ~ num_crimes + harship_in + without_hs,
              data = model,
              listw = chic_list,
              zero.policy = TRUE, 
              na.action = na.omit)

summary(lm_lag)
## 
## Call:lagsarlm(formula = poverty ~ num_crimes + harship_in + without_hs, 
##     data = model, listw = chic_list, na.action = na.omit, zero.policy = TRUE)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -10.2137  -3.3077  -1.0158   3.8907  17.1961 
## 
## Type: lag 
## Coefficients: (asymptotic standard errors) 
##                Estimate  Std. Error z value  Pr(>|z|)
## (Intercept)  3.92318523  1.86512855  2.1034   0.03543
## num_crimes   0.00016931  0.00010147  1.6686   0.09520
## harship_in   0.44728541  0.04608668  9.7053 < 2.2e-16
## without_hs  -0.49649799  0.09083357 -5.4660 4.603e-08
## 
## Rho: 0.21044, LR test value: 5.0977, p-value: 0.023957
## Asymptotic standard error: 0.094539
##     z-value: 2.226, p-value: 0.026015
## Wald statistic: 4.955, p-value: 0.026015
## 
## Log likelihood: -234.0885 for lag model
## ML residual variance (sigma squared): 25.357, (sigma: 5.0355)
## Number of observations: 77 
## Number of parameters estimated: 6 
## AIC: 480.18, (AIC for lm: 483.27)
## LM test for residual autocorrelation
## test value: 26.263, p-value: 2.9796e-07

The model shows a lot of potential due to its p-value of .01

seResiduals <- rep(0, length(model$poverty))
resIndex <- lm_lag$residuals %>% names() %>% as.integer();
seResiduals[resIndex] <- lm_lag$residuals

#perform the Moran Test
chic_list %>%
  moran.test(seResiduals, ., zero.policy = TRUE) 
## 
##  Moran I test under randomisation
## 
## data:  seResiduals  
## weights: .    
## 
## Moran I statistic standard deviate = 4.4918, p-value = 3.532e-06
## alternative hypothesis: greater
## sample estimates:
## Moran I statistic       Expectation          Variance 
##       0.307990182      -0.013157895       0.005111864

The p-value here is not significant, so there is no evidence of spatial autocorrelation in the residuals.

LM <- lm.LMtests(lm1, chic_list, test = "all")
## Please update scripts to use lm.RStests in place of lm.LMtests
LM
## 
##  Rao's score (a.k.a Lagrange multiplier) diagnostics for spatial
##  dependence
## 
## data:  
## model: lm(formula = poverty ~ num_crimes + harship_in + without_hs,
## data = model)
## test weights: listw
## 
## RSerr = 38.646, df = 1, p-value = 5.081e-10
## 
## 
##  Rao's score (a.k.a Lagrange multiplier) diagnostics for spatial
##  dependence
## 
## data:  
## model: lm(formula = poverty ~ num_crimes + harship_in + without_hs,
## data = model)
## test weights: listw
## 
## RSlag = 5.8264, df = 1, p-value = 0.01579
## 
## 
##  Rao's score (a.k.a Lagrange multiplier) diagnostics for spatial
##  dependence
## 
## data:  
## model: lm(formula = poverty ~ num_crimes + harship_in + without_hs,
## data = model)
## test weights: listw
## 
## adjRSerr = 35.293, df = 1, p-value = 2.836e-09
## 
## 
##  Rao's score (a.k.a Lagrange multiplier) diagnostics for spatial
##  dependence
## 
## data:  
## model: lm(formula = poverty ~ num_crimes + harship_in + without_hs,
## data = model)
## test weights: listw
## 
## adjRSlag = 2.4737, df = 1, p-value = 0.1158
## 
## 
##  Rao's score (a.k.a Lagrange multiplier) diagnostics for spatial
##  dependence
## 
## data:  
## model: lm(formula = poverty ~ num_crimes + harship_in + without_hs,
## data = model)
## test weights: listw
## 
## SARMA = 41.12, df = 2, p-value = 1.178e-09

the R Slag and the adjRSlag test are the only one that are significant. this reinforces the idea that the lag model is the best fit for this data and varibales.