ndi: Neighborhood Deprivation Indices

Ian D. Buller (GitHub: @idblr)

2022-09-30

Start with the necessary packages for the vignette.

loadedPackages <- c("dplyr", "ggplot2", "ndi", "tidycensus", "tigris")
invisible(lapply(loadedPackages, library, character.only = TRUE))
options(tigris_use_cache = TRUE)

Set your U.S. Census Bureau access key. Follow this link to obtain one. Specify your access key in the messer() or powell_wiley() functions using the key argument of the get_acs() function from the tidycensus package called within each or by using the census_api_key() function from the tidycensus package before running the messer() or powell_wiley() functions (see an example of the latter below).

tidycensus::census_api_key("...") # INSERT YOUR OWN KEY FROM U.S. CENSUS API

Compute NDI (Messer)

Compute the NDI (Messer) values (2006-2010 5-year ACS) for Georgia, U.S.A., census tracts. This metric is based on Messer et al. (2006) with the following socio-economic status (SES) variables:

Characteristic SES dimension ACS table source Description
OCC Occupation C24030 Percent males in management, science, and arts occupation
CWD Housing B25014 Percent of crowded housing
POV Poverty B17017 Percent of households in poverty
FHH Poverty B25115 Percent of female headed households with dependents
PUB Poverty B19058 Percent of households on public assistance
U30 Poverty B19001 Percent households earning <$30,000 per year
EDU Education B06009 Percent earning less than a high school education
EMP Employment B23001 (2010 only); B23025 (2011 onward) Percent unemployed
messer2010GA <- ndi::messer(state = "GA", year = 2010, round_output = TRUE)

One output from the messer() function is a tibble containing the identification, geographic name, NDI (Messer) values, and raw census characteristics for each tract.

messer2010GA$ndi
## # A tibble: 1,969 × 14
##    GEOID  state county tract     NDI NDIQu…¹   OCC   CWD   POV   FHH   PUB   U30
##    <chr>  <chr> <chr>  <chr>   <dbl> <fct>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
##  1 13001… Geor… Appli… 9501  -0.0075 2-Belo…     0   0     0.1   0.1   0.1   0.3
##  2 13001… Geor… Appli… 9502   0.0458 4-Most…     0   0     0.3   0.1   0.2   0.5
##  3 13001… Geor… Appli… 9503   0.0269 3-Abov…     0   0     0.2   0     0.2   0.4
##  4 13001… Geor… Appli… 9504  -0.0083 2-Belo…     0   0     0.1   0     0.1   0.3
##  5 13001… Geor… Appli… 9505   0.0231 3-Abov…     0   0     0.2   0     0.2   0.4
##  6 13003… Geor… Atkin… 9601   0.0619 4-Most…     0   0.1   0.2   0.2   0.2   0.5
##  7 13003… Geor… Atkin… 9602   0.0593 4-Most…     0   0.1   0.3   0.1   0.2   0.4
##  8 13003… Geor… Atkin… 9603   0.0252 3-Abov…     0   0     0.3   0.1   0.2   0.4
##  9 13005… Geor… Bacon… 9701   0.0061 3-Abov…     0   0     0.2   0     0.2   0.4
## 10 13005… Geor… Bacon… 9702…  0.0121 3-Abov…     0   0     0.2   0.1   0.1   0.5
## # … with 1,959 more rows, 2 more variables: EDU <dbl>, EMP <dbl>, and
## #   abbreviated variable name ¹​NDIQuart

A second output from the messer() function is the results from the principal component analysis used to compute the NDI (Messer) values.

messer2010GA$pca
## Principal Components Analysis
## Call: psych::principal(r = ndi_vars_pca, nfactors = 1, n.obs = nrow(ndi_vars_pca), 
##     covar = FALSE, scores = TRUE, missing = imp)
## Standardized loadings (pattern matrix) based upon correlation matrix
##       PC1   h2   u2 com
## OCC -0.59 0.35 0.65   1
## CWD  0.47 0.22 0.78   1
## POV  0.87 0.76 0.24   1
## FHH  0.67 0.45 0.55   1
## PUB  0.89 0.79 0.21   1
## U30  0.90 0.81 0.19   1
## EDU  0.79 0.62 0.38   1
## EMP  0.46 0.21 0.79   1
## 
##                 PC1
## SS loadings    4.21
## Proportion Var 0.53
## 
## Mean item complexity =  1
## Test of the hypothesis that 1 component is sufficient.
## 
## The root mean square of the residuals (RMSR) is  0.11 
##  with the empirical chi square  1221.09  with prob <  2.3e-246 
## 
## Fit based upon off diagonal values = 0.95

A third output from the messer() function is a tibble containing a breakdown of the missingness of the census characteristics used to compute the NDI (Messer) values.

messer2010GA$missing
## # A tibble: 8 × 4
##   variable total n_missing percent_missing
##   <chr>    <int>     <int> <chr>          
## 1 CWD       1969        14 0.71 %         
## 2 EDU       1969        13 0.66 %         
## 3 EMP       1969        13 0.66 %         
## 4 FHH       1969        14 0.71 %         
## 5 OCC       1969        15 0.76 %         
## 6 POV       1969        14 0.71 %         
## 7 PUB       1969        14 0.71 %         
## 8 U30       1969        14 0.71 %

We can visualize the NDI (Messer) values geographically by linking them to spatial information from the tigris package and plotting with the ggplot2 package suite.

# Obtain the 2010 counties from the "tigris" package
county2010GA <- tigris::counties(state = "GA", year = 2010, cb = TRUE)
# Remove first 9 characters from GEOID for compatibility with tigris information
county2010GA$GEOID <- substring(county2010GA$GEO_ID, 10) 

# Obtain the 2010 census tracts from the "tigris" package
tract2010GA <- tigris::tracts(state = "GA", year = 2010, cb = TRUE)
# Remove first 9 characters from GEOID for compatibility with tigris information
tract2010GA$GEOID <- substring(tract2010GA$GEO_ID, 10) 

# Join the NDI (Messer) values to the census tract geometry
GA2010messer <- dplyr::left_join(tract2010GA, messer2010GA$ndi, by = "GEOID")

# Visualize the NDI (Messer) values (2006-2010 5-year ACS) for Georgia, U.S.A., census tracts 
## Continuous Index
ggplot2::ggplot() + 
  ggplot2::geom_sf(data = GA2010messer, 
                   ggplot2::aes(fill = NDI),
                   size = 0.05,
                   color = "white") +
   ggplot2::geom_sf(data = county2010GA,
                   fill = "transparent", 
                   color = "white",
                   size = 0.2) +
  ggplot2::theme_minimal() +
  ggplot2::scale_fill_viridis_c() +
  ggplot2::labs(fill = "Index (Continuous)",
                caption = "Source: U.S. Census ACS 2006-2010 estimates") +
  ggplot2::ggtitle("Neighborhood Deprivation Index (Messer)",
                   subtitle = "GA census tracts as the referent")

## Categorical Index
### Rename "9-NDI not avail" level as NA for plotting
GA2010messer$NDIQuartNA <- factor(replace(as.character(GA2010messer$NDIQuart), 
                                            GA2010messer$NDIQuart == "9-NDI not avail", NA),
                                  c(levels(GA2010messer$NDIQuart)[-5], NA))

ggplot2::ggplot() + 
  ggplot2::geom_sf(data = GA2010messer, 
                   ggplot2::aes(fill = NDIQuartNA),
                   size = 0.05,
                   color = "white") +
   ggplot2::geom_sf(data = county2010GA,
                   fill = "transparent", 
                   color = "white",
                   size = 0.2) +
  ggplot2::theme_minimal() + 
  ggplot2::scale_fill_viridis_d(guide = ggplot2::guide_legend(reverse = TRUE),
                                na.value = "grey80") +
  ggplot2::labs(fill = "Index (Categorical)",
                caption = "Source: U.S. Census ACS 2006-2010 estimates") +
  ggplot2::ggtitle("Neighborhood Deprivation Index (Messer) Quartiles",
                   subtitle = "GA census tracts as the referent")

The results above are at the tract level. The NDI (Messer) values can also be calculated at the county level.

messer2010GA_county <- ndi::messer(geo = "county", state = "GA", year = 2010)

# Join the NDI (Messer) values to the county geometry
GA2010messer_county <- dplyr::left_join(county2010GA, messer2010GA_county$ndi, by = "GEOID")

# Visualize the NDI (Messer) values (2006-2010 5-year ACS) for Georgia, U.S.A., counties
## Continuous Index
ggplot2::ggplot() + 
  ggplot2::geom_sf(data = GA2010messer_county, 
                   ggplot2::aes(fill = NDI),
                   size = 0.20,
                   color = "white") +
  ggplot2::theme_minimal() + 
  ggplot2::scale_fill_viridis_c() +
  ggplot2::labs(fill = "Index (Continuous)",
                caption = "Source: U.S. Census ACS 2006-2010 estimates") +
  ggplot2::ggtitle("Neighborhood Deprivation Index (Messer)",
                   subtitle = "GA counties as the referent")

## Categorical Index

### Rename "9-NDI not avail" level as NA for plotting
GA2010messer_county$NDIQuartNA <- factor(replace(as.character(GA2010messer_county$NDIQuart), 
                                            GA2010messer_county$NDIQuart == "9-NDI not avail", NA),
                                         c(levels(GA2010messer_county$NDIQuart)[-5], NA))

ggplot2::ggplot() + 
  ggplot2::geom_sf(data = GA2010messer_county, 
                   ggplot2::aes(fill = NDIQuartNA),
                   size = 0.20,
                   color = "white") +
  ggplot2::theme_minimal() + 
  ggplot2::scale_fill_viridis_d(guide = ggplot2::guide_legend(reverse = TRUE),
                                na.value = "grey80") +
  ggplot2::labs(fill = "Index (Categorical)",
                caption = "Source: U.S. Census ACS 2006-2010 estimates") +
  ggplot2::ggtitle("Neighborhood Deprivation Index (Messer) Quartiles",
                   subtitle = "GA counties as the referent")