Setup

Here I add my libraries and read in the various data files. I performs some cleaning as well.

library(tidyverse)
library(janitor)
library(sf)
library(mapview)
library(ggplot2)
library(ggmap)

wd <- getwd()

# read in the shapes
zip_path <- file.path(wd, "R-Spatial_II_Lab", "ZIP_CODE_040114", "ZIP_CODE_040114.shp")
zip_codes_sf <- st_read(zip_path)

nyc_food_path <- file.path(wd, "R-Spatial_II_Lab", "nycFoodStore.shp")
nyc_food_store_sf <- st_read(nyc_food_path)

census_path <- file.path(wd, "R-Spatial_II_Lab", "2010 Census Tracts", "geo_export_1dc7b645-647b-4806-b9a0-7b79660f120a.shp")
census_tracts_2010_sf <- st_read(census_path)

# read in the csvs and clean names
health_facility_path <- file.path(wd, "R-Spatial_II_Lab", "NYS_Health_Facility.csv")
health_facility_df <- read_csv(health_facility_path, show_col_types = FALSE, lazy = FALSE) %>% 
  clean_names()

tests_path <- file.path(wd, "R-Spatial_II_Lab", "tests-by-zcta_2020_04_12.csv")
tests_by_zcta_2020_I_df <- read_csv(tests_path, show_col_types = FALSE, lazy = FALSE) %>%
  clean_names()

tests_path <- file.path(wd, "R-Spatial_II_Lab", "tests-by-zcta_2020_04_19.csv")
tests_by_zcta_2020_II_df <- read_csv(tests_path, show_col_types = FALSE, lazy = FALSE) %>%
  clean_names()

tests_path <- file.path(wd, "R-Spatial_II_Lab", "tests-by-zcta_2021_04_23.csv")
tests_by_zcta_2021_df <- read_csv(tests_path, show_col_types = FALSE, lazy = FALSE) %>%
  clean_names()

acs_path <- file.path(wd, "R-Spatial_II_Lab", "ACSDP5Y2018.DP05_data_with_overlays_2020-04-22T132935.csv")
acs_demographics_housing_df <- read_csv(acs_path, skip = 1, show_col_types = FALSE, lazy = FALSE) %>%
  clean_names()

# EDA

# glimpse(tests_by_zcta_2020_I_df)
# glimpse(tests_by_zcta_2020_II_df)
# glimpse(tests_by_zcta_2021_df)
# 
# glimpse(zip_codes_sf)
# 
# plot(st_geometry(zip_codes_sf))

Here I Performed the Bulk of the Joining

# join covid-19 data to zip code area data
# I used the 2021 data as it offered more attributes
# need to convert to chr first to make this work
tests_by_zcta_2021_df <- tests_by_zcta_2021_df %>%
  mutate(modified_zcta = as.character(modified_zcta))

zip_covid_merged_sf <- zip_codes_sf %>%
  left_join(tests_by_zcta_2021_df, by = c("ZIPCODE" = "modified_zcta"))

# Prepare the food
# I used the grepl function to select only records with any of the establishment codes:
# J, A, C, K. I included vehicles (K) but that might not be necessary if people social distance
nyc_food_store_prepared_sf <- nyc_food_store_sf %>%
  filter(grepl("J", Estbl_T) | 
           grepl("A", Estbl_T) | 
           grepl("C", Estbl_T) | 
           grepl("K", Estbl_T))
# also have to transform it to NAD83
nyc_food_store_prepared_sf <- st_transform(nyc_food_store_prepared_sf, 2263)

# testing
stores_per_zipcode <- nyc_food_store_prepared_sf %>%
  group_by(Zip_Cod) %>%
  summarize(store_count = n()) %>%
  ungroup()
print(stores_per_zipcode)

# now to aggregate with the zip codes
# first I create a sf with just summed stores in each zip
zip_food_query_sf <- nyc_food_store_prepared_sf %>%
  st_join(zip_codes_sf, join = st_intersects) %>%
  filter(!is.na(ZIPCODE)) %>%
  group_by(ZIPCODE) %>%
  summarize(store_count = n(), .groups = 'drop')
zip_food_query_df <- as.data.frame(zip_food_query_sf)
zip_food_query_df[3] <- NULL
# join to the zip codes by ZIPCODE and create the finished sf
# also remove some cruft
zip_fstore_agg_sf <- zip_codes_sf %>%
  left_join(zip_food_query_df, by = 'ZIPCODE')

# join covid-19 data to zip code area data
# I used the 2021 data as it offered more attributes
# need to convert to chr first to make this work, also changed zcta to ZIPCODE for the join
tests_2021_df <- tests_by_zcta_2021_df %>%
  mutate(modified_zcta = as.character(modified_zcta)) %>%
  rename(ZIPCODE = modified_zcta)

zip_fstore_covid_sf <- left_join(zip_fstore_agg_sf, tests_2021_df, by = "ZIPCODE")

# aggregate the health facilities to zip codes
# I handle this similarly to the stores from above but because we start with df its a little more complicated
# I reuse some code from lab 1, this pipe gets only facilities in proper counties and filters for some anomalies
health_facility_sf <- health_facility_df %>%
  filter(!is.na(facility_longitude) & !is.na(facility_latitude)) %>%
  filter(facility_county %in% c("Bronx", "Kings", "New York", "Queens", "Richmond")) %>%
  filter(!((facility_longitude == 0) & (facility_latitude == 0))) %>%
  filter(facility_id != 5764) %>%
  st_as_sf(coords = c("facility_longitude", "facility_latitude"), crs = 2263)
# check some stuff
# str(health_facility_sf)
# st_crs(health_facility_sf)
# I want to get the number and quantity of abbreviations
# abbr_counts <- health_facility_sf %>%
#   count(short_description, name = "count") %>%
#   arrange(desc(count))
# print(abbr_counts)
# Based on this I can select what I want to keep by filtering out certain short_description entries
filtered_health_facility_sf <- health_facility_sf %>%
  filter(!short_description %in% c("DTC", "DTC-EC", "CHHA", "LTHHCP"))
# check using the previous code
abbr_counts_check <- filtered_health_facility_sf %>%
  count(short_description, name = "count") %>%
  arrange(desc(count))
print(abbr_counts_check)

# now we can do another aggregation via the same methodology
health_facility_query_sf <- zip_fstore_covid_sf %>%
  st_join(filtered_health_facility_sf, join = st_within) %>%
  group_by(ZIPCODE) %>%
  summarize(health_facility_count = n(),
            geometry = st_union(geometry))
health_facility_query_df <- as.data.frame(health_facility_query_sf)
health_facility_query_df[3] <- NULL

zip_covid_fstore_hfacility_agg_sf <- zip_fstore_covid_sf %>%
  left_join(health_facility_query_df, by = 'ZIPCODE')

# now to join ACS to census
# complicated to join so we need to clean first
# I look for the strings of county names, converting where needed, and generate a new column; boro
acs_demographics_housing_clean_df <- acs_demographics_housing_df %>%
  mutate(tract_number = str_extract(geographic_area_name, "\\d+")) %>%
  mutate(
    boro = case_when(
      str_detect(`geographic_area_name`, "Bronx") ~ "Bronx",
      str_detect(`geographic_area_name`, "Queens") ~ "Queens",
      str_detect(`geographic_area_name`, "Kings") ~ "Brooklyn",
      str_detect(`geographic_area_name`, "New York County") ~ "Manhattan",
      str_detect(`geographic_area_name`, "Richmond") ~ "Staten Island"
    )
  ) %>%
  select(id, tract_number, boro, everything())

# now we can join
acs_demographics_housing_sf <- census_tracts_2010_sf %>%
  left_join(acs_demographics_housing_clean_df, by = c("boro_name" = "boro", "ctlabel" = "tract_number"))

Final Part

Here I did the last few joins to create the final shapefile.

# almost there, just need to aggregate the census and zip code stuff, another big one
acs_demographics_housing_sf <- st_transform(acs_demographics_housing_sf, st_crs(zip_covid_fstore_hfacility_agg_sf))

joined_sf <- st_join(zip_covid_fstore_hfacility_agg_sf, acs_demographics_housing_sf, join = st_intersects)

# list_of_columns <- names(joined_sf)
# print(list_of_columns)

zip_fstores_hfacilities_demographics_sf <- joined_sf %>%
  group_by(ZIPCODE) %>%
  mutate(across(c(40:395), ~if(is.numeric(.x)) sum(.x, na.rm = TRUE) else NA_real_, .names = "{.col}_sum")) %>%
  ungroup()

# Now to remove columns, first the unsummed columns and old columns from the census shape
zip_fstores_hfacilities_demographics_sf <- zip_fstores_hfacilities_demographics_sf %>%
  select(1:26, 396:ncol(zip_fstores_hfacilities_demographics_sf))
# here I want to remove any variable (column) namse that include percent_estimate or margins of error as they are unneccessary
zip_fstores_hfacilities_demographics_sf <- zip_fstores_hfacilities_demographics_sf %>%
  select(-matches("percent_estimate|margin"))



# I use this to determine what columns to keep or remove
list_of_columns <- names(zip_fstores_hfacilities_demographics_sf)
print(list_of_columns)