Analyzing Distressed Population and Land Cover Changes in New York Appalachia (2010-2019)

GEO511

Author

Stephen C. Sanders

Published

December 14, 2024

Introduction

The Southern Tier of New York consists of fourteen counties that are considered part of Northern Appalachia, according to the Appalachian Regional Commission. These counties have experienced some of the most drastic population declines in New York State outside of New York City (McMahon, 2024). On top of the decline in population, communities in much of the Southern Tier suffer from relatively high poverty and low income levels compared to the rest of the United States as a whole (ARC, 2024 County-Level Distressed Area Study).

Map of three subregions of the New York Southern Tier region.

Map of three subregions in Appalachian NY (NY Department of State)

Some areas in Appalachia - such as Eastern Kentucky - have undergone extensive land reclamation efforts in an attempt to repair land that had been damaged during the era of intense coal mining operations, converting much of the reclaimed land into infrastructure, industry, and other development despite consistent population decline (KC, Gyawali, Lucas, et al., 2024). Coal mining was not a large industry in this area at any time, and much of the manufacturing was limited to cities such as Jamestown and Binghamton.

While the need of a study concerning land cover for the purposes of land reclamation may not be necessary for the Southern Tier, an analysis of the region could lead to interesting insights about how the population decline may have impacted land cover changes. The purpose of this analysis is to get a general idea of the changes in population/economic variables and land cover in New York’s Southern Tier between 2010 and 2019 and how they may relate to each other. Specifically, are areas with a higher population decline more likely to have had an increase in natural barren land and/or a decrease in developed area?

Materials and Methods

The analysis looked at various population-related variables. Specifically, data concerning total population, total households, median age, median family income, number of people below the poverty level, unemployment among civilian workforce over the age of 16, and households with no vehicles were pulled and/or aggregated at the U.S., county, and census tract levels.

Census data was pulled for years between 2010 and 2019. All data was gathered using the tidycensus package, and a majority of it came from American Community Survey (ACS) 5-year estimates. U.S. data for 2010 had to be pulled from the decennial census or sourced from governmental reports and news releases since ACS data was not available for this year at the country-level. Data for years other than 2010 and 2019 were pulled to allow for additional analysis in the future.

Primary land cover data for 2010 and 2019 comes from the USGS LCMAP project and were downloaded from the CONUS Mosaic website. This data was processed using the terra package.

The methodology used to determine distressed status of each census tract was taken from the Appalachian Regional Commission’s Distressed Areas Classification System. According to the ARC, the key attributes of a distressed census tract are:

  1. A median family income no greater than 67% of the U.S. average.
  2. A poverty rate of 150% of the U.S. average or greater.

The correlation analysis was conducted on the census-tract level data and used the Pearson method. The cor function of the base R stats package was used to calculate correlation coefficients.

The tidyverse collection of packages was primarily used to easily process large amount of data. The tigris package was used directly to pull study counties and census tracts to create a map of the study area. The sf package helped with processing spatial features, including data pulled from the ACS. The maps were created using either leaflet, mapview, or ggplot2. The gt package allowed for the creation of better looking tables. The heatmaply package was used to visualize the correlation coefficients between all variables.

An Overview of the Analysis Workflow

Below is an outline of how the analysis was conducted:

  1. Create a map of study area for reference.
  2. Pull Census data at US, census tract, and county levels for years between 2010 and 2019, then simplify datasets so only variables for the years 2010 and 2019 are included.
    • Certain data at the tract level (e.g., median family income) could not simply be aggregated to include in the county-level dataset, which required pulling that data separately at the county level.
    • U.S. data was pulled for comparison purposes in the distressed population analysis.
    • U.S. data for 2010 had to be sourced from governmental reports since they were not available for direct download using the tidycensus package. The only variable that was pulled using tidycensus in this case was total population.
    • Data for 2015 are also included in the consolidated datasets for the purposes of future work. They do not have any relevance to the analysis at this time.
  3. Download land cover datasets for years 2010 and 2019, then show side-by-side plot of both images. Show map of areas where the land cover type has changed between the years.
  4. Calculate zonal statistics of each of the 8 land cover classifications over the entire study area, then create a table and bar graph showing the changes in each of the classifications between 2010 and 2019.
  5. Determine distressed status of each of the census tracts for each year using criteria from the ARC by comparing median family income and poverty levels to the U.S. values. Determine which census tracts improved (i.e., were distressed in 2010 and no longer distressed in 2019) and which census tracts became distressed. Create maps to show tracts that improved and tracts that became distressed. Also show table breakdown by county of the number of tracts that became distressed and the number of tracts that improved.
  6. Merge base data variables, distressed variable data, and land cover classification data into single datasets at both the census tract and county levels. Calculate percent changes between 2010 and 2019 for each variable in each of the datasets. Create tables for both to show the percent changes by census tract and by county.
  7. Run basic correlation analysis on census tract-level dataset to figure out relationships between variables, and create a heatmap of correlation results. Lastly, create a table of correlation results between the percentage population change and all other percentage change variables.
    • Since there are only 14 counties, a county-level correlation analysis would have a very large margin of error. For this reason, this step was only done on the census tract-level data.

Data Gathering and Processing

Code
# load necessary libraries
library(tidyverse)
library(tidycensus)
library(sf)
library(tigris)
library(basemaps)
library(leaflet)
library(mapview)
library(terra)
library(tidyterra)
library(gt)
library(heatmaply)
library(stringr)
library(RColorBrewer)
library(gridExtra)
library(reshape2)
library(here)
library(knitr)
knitr::opts_chunk$set(echo=TRUE)  # cache the results for quick compiling

# pull in census api token
census_token = Sys.getenv("CENSUS_TOKEN")
census_api_key(census_token)

Map of Study Area

The map below shows the 14 counties in New York’s Southern Tier (Allegany, Broome, Cattaraugus, Chautauqua, Chemung, Chenango, Cortland, Delaware, Otsego, Schoharie, Schuyler, Steuben, Tioga, and Tompkins) and all 277 census tracts.

Code
# increase timeout time to download files and cache tigris files
options(timeout = 500, tigris_use_cache = TRUE)

# set vector of southern tier counties
southern_tier_counties = c('Allegany', 'Broome', 'Cattaraugus', 'Chautauqua', 
                           'Chemung', 'Chenango', 'Cortland', 'Delaware', 'Otsego', 
                           'Schoharie', 'Schuyler', 'Steuben', 'Tioga', 'Tompkins')

# get study geographies and basemap focused on ny state
ny_state <- states(cb = TRUE, year = 2019) %>% filter(NAME == 'New York')
study_counties <- counties(state = 'NY', cb = TRUE, year = 2019) %>%
  filter(NAME %in% southern_tier_counties)
study_tracts <- tracts(state = 'NY', county = southern_tier_counties, cb = TRUE, year = 2019)
base_ny <- basemap_raster(ext = ny_state, map_service = 'carto', map_type = 'light')
Loading basemap 'light' from map service 'carto'...
Code
# create study area map
study_area_map <-
  leaflet() %>%
  addTiles() %>%
  addPolygons(data = st_transform(study_counties, crs = '+proj=longlat +datum=WGS84'),
              fillOpacity = 0, label = ~study_counties$NAME) %>%
  addPolygons(data = st_transform(study_tracts, crs = '+proj=longlat +datum=WGS84'),
              fillOpacity = 0, color = 'black', weight = '0.75')

study_area_map

Download and Process All Required Data

Census data and land cover data are downloaded and processed here. The census data comes primarily from the American Community Survey (ACS) 5-year surveys, and the land cover data comes from the USGS.

ACS Data

Set function to download ACS data based on geography

Code
# create function to download acs data for multiple years
# create function to download acs data for multiple years
get_acs_data <- function(year, geom) {
  if (geom == 'tract') {
    return(
      get_acs(
        geography = geom, 
        variables = c(
          tot_pop = 'B01003_001',
          tot_hhs = 'B08202_001',
          median_age = 'B07002_001',
          median_income = 'B19113A_001',
          tot_below_poverty = 'B17001_002',
          hhs_no_vehicle = 'B08201_002'
        ),
        state = 'NY',
        geometry = TRUE,
        year = year,
        output = 'wide',
        cache_table = TRUE,
        key = census_token
      ) %>%
        mutate(
          year = year,
          county = str_split(str_split(NAME, ', ', simplify = TRUE)[,2], ' ', simplify = TRUE)[,1],
          pct_below_poverty = tot_below_povertyE / tot_popE,
          pct_hhs_no_vehicles = hhs_no_vehicleE / tot_hhsE
        ) %>%
        filter(county %in% southern_tier_counties & !st_is_empty(geometry))
    )
  } else if (geom == 'county') {
    return(
      get_acs(
        geography = geom, 
        variables = c(
          median_age = 'B07002_001',
          median_income = 'B19113A_001'
        ),
        state = 'NY',
        geometry = TRUE,
        year = year,
        output = 'wide',
        cache_table = TRUE,
        key = census_token
      ) %>%
        mutate(
          year = year,
          county = str_split(str_split(NAME, ', ', simplify = TRUE)[,1], ' ', simplify = TRUE)[,1]
        ) %>%
        filter(county %in% southern_tier_counties & !st_is_empty(geometry))
    )
  } else {
    return(
      get_acs(
        geography = 'us',
        variables = c(
          tot_pop = 'B01003_001',
          tot_hhs = 'B08202_001',
          median_age = 'B07002_001',
          median_income = 'B19113A_001',
          tot_below_poverty = 'B17001_002',
          hhs_no_vehicle = 'B08201_002'
        ),
        geometry = TRUE,
        year = year,
        output = 'wide',
        cache_table = TRUE,
        key = census_token
      ) %>%
        mutate(
          year = year
        )
    )
  }
}

US Data

Code
# get certain decennial 2010 census data at state level
# then sum population and add additional data points taken from governmental reports
us_data.2010 <- get_decennial(geography = 'state',
                              variables = c(tot_pop = 'P001001'),
                              year = 2010,
                              output = 'wide',
                              cache_table = TRUE,
                              geometry = TRUE) %>%
  mutate(NAME = 'United States') %>%
  group_by(NAME) %>%
  summarize(tot_popE = sum(tot_pop)) %>%
  mutate(
    tot_hhsE = 116716292,
    median_ageE = 37.2,
    pct_below_povertyE = 15.3,
    median_incomeE = 64400
  ) %>%
  relocate(geometry, .after = last_col()) %>%
  as_tibble()

# pull acs data at national level
us_data.2014_2019 <- map2(2014:2019, rep('us', times = 6), get_acs_data) %>%
  bind_rows() %>%
  as_tibble()

# separate 2014-2019 us data into 2015 and 2019 data frames
us_data.2015 <- us_data.2014_2019 %>% 
  filter(year == 2015)

us_data.2019 <- us_data.2014_2019 %>%
  filter(year == 2019)

# merge us data for 2010, 2015, and 2019 into a single data frame
us_data.2010_2019 <-
  left_join(us_data.2010, us_data.2015, by = 'NAME', keep = FALSE, suffix = c('', '_2015')) %>%
  left_join(us_data.2019, by = 'NAME', keep = TRUE, suffix = c('_2010', '_2019')) %>%
  select(!all_of(grep('M_', names(.), value = TRUE))) %>% # remove margin of error columns
  rename(
    GEOID = GEOID_2010,
    NAME = NAME_2010,
    geometry = geometry_2010,
    year = year_2010,
    pct_below_povertyE_2010 = pct_below_povertyE,
    tot_below_povertyE_2015 = tot_below_povertyE_2010,
    hhs_no_vehicleE_2015 = hhs_no_vehicleE_2010
  ) %>%
  relocate(GEOID, .after = NAME) %>%
  select(-c('GEOID_2019', 'NAME_2019', 'geometry_2015', 'geometry_2019', 'year', 'year_2019')) %>%
  mutate(
    pct_below_poverty_2015 = (tot_below_povertyE_2015 / tot_popE_2015) * 100,
    pct_below_poverty_2019 = (tot_below_povertyE_2019 / tot_popE_2019) * 100,
    pct_hhs_no_vehicle_2015 = (hhs_no_vehicleE_2015 / tot_hhsE_2015) * 100,
    pct_hhs_no_vehicle_2019 = (hhs_no_vehicleE_2019 / tot_hhsE_2019) * 100,
    pct_tot_pop_chg_2010_2019 = ((tot_popE_2019 - tot_popE_2010) / tot_popE_2010) * 100,
    pct_median_age_chg_2015_2019 = ((median_ageE_2015 - median_ageE_2010) / median_ageE_2015) * 100,
    pct_median_income_chg_2010_2019 = ((median_incomeE_2019 - median_incomeE_2010) / median_incomeE_2010) * 100,
    pct_below_poverty_chg_2010_2019 = ((pct_below_poverty_2019 - pct_below_povertyE_2010) / pct_below_povertyE_2010) * 100,
    pct_hhs_no_vehicle_chg_2015_2019 = ((pct_hhs_no_vehicle_2019 - pct_hhs_no_vehicle_2015) / pct_hhs_no_vehicle_2015) * 100
  ) %>%
  lapply(function(i) if(is.numeric(i)) ifelse(is.infinite(i), 0, i) else i) %>%
  as_tibble() %>%
  relocate(geometry, .after = last_col()) %>%
  st_as_sf(crs = "EPSG: 4269")

Census Tract Data

Code
# get census tract data for 2010-2019
# isolate data for 2010, 2015, and 2019 and put into separate dfs
st_tracts <- map2(2010:2019, rep('tract', times = 10), get_acs_data) %>% 
  bind_rows() %>% 
  as_tibble()

# show point plot of median income and pct below poverty level for each year
# colored by county
ggplot(st_tracts, aes(median_incomeE, pct_below_poverty, color = county)) +
  geom_jitter(na.rm = TRUE) +
  facet_wrap(~year) +
  labs(x = 'Median Income', y = '% Below Poverty Level', color = 'County') +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

There is a clear negative relationship between median family income and percent below poverty level, where tracts that have higher median family incomes tend to have a lower percentage of people below poverty. Most of the outliers for each year are in Tompkins County.

Remaining Census Tract Data
Code
# get economic data (unemployed civilians > 16) for 2010-2019
# data for 2018 returned a server error
st_tracts.economic <- map(c(2010, 2011, 2013, 2014, 2015, 2016, 2017, 2019), function(x) {
  return(
    get_acs(
      geography = 'tract',
      variables = c('DP03_0003', 'DP03_0005'),
      state = 'NY',
      geometry = TRUE,
      year = x,
      output = 'wide',
      cache_table = TRUE,
      key = census_token
    ) %>%
      rename(
        'pop_gt_16_in_civilian_labor_force_{x}' := DP03_0003E,
        'pop_gt_16_in_civilian_labor_force_unemployed_{x}' := DP03_0005E
      ) %>%
      mutate(
        year = x,
        county = str_split(NAME, ' ', simplify = TRUE)[,4]
      )
  )
}) %>%
  bind_rows()%>%
  filter(county %in% southern_tier_counties & !st_is_empty(geometry)) %>%
  as_tibble()

# filter data for years 2010, 2015, and 2019, join with that year's economic data,
# and store in separate data frames for each year
st_tracts.2010 <- # health insurance coverage data not available for 2010
  st_tracts %>% 
  filter(year == 2010) %>%
  left_join(
    select(
      filter(st_tracts.economic, year == 2010),
      GEOID,
      pop_gt_16_in_civilian_labor_force_2010, pop_gt_16_in_civilian_labor_force_unemployed_2010
    )
  ) %>%
  relocate(geometry, .after = last_col())

st_tracts.2015 <- 
  st_tracts %>% 
  filter(year == 2015) %>%
  left_join(
    select(
      filter(st_tracts.economic, year == 2015),
      GEOID,
      pop_gt_16_in_civilian_labor_force_2015, pop_gt_16_in_civilian_labor_force_unemployed_2015
    )
  ) %>%
  relocate(geometry, .after = last_col())

st_tracts.2019 <- 
  st_tracts %>% 
  filter(year == 2019) %>%
  left_join(
    select(
      filter(st_tracts.economic, year == 2019),
      GEOID,
      pop_gt_16_in_civilian_labor_force_2019, pop_gt_16_in_civilian_labor_force_unemployed_2019
    )
  ) %>%
  relocate(geometry, .after = last_col())

# merge 2010, 2015, & 2019 data into single sf
# compute pct chgs, convert to sf, and change infinite values to NA
st_tracts.2010_2019 <-
  left_join(st_tracts.2010, st_tracts.2015, by = 'GEOID', keep = FALSE, suffix = c('', '_2015')) %>%
  left_join(st_tracts.2019, by = 'GEOID', keep = FALSE, suffix = c('_2010', '_2019')) %>%
  select(-c('NAME_2015', 'geometry_2015', 'year_2015', 'county_2015', 'NAME_2019', 'geometry_2019', 'year_2019', 'county_2019')) %>%
  rename(
    NAME = NAME_2010,
    geometry = geometry_2010,
    county = county_2010,
    year = year_2010
  ) %>%
  mutate(
    across(starts_with('pop_gt_16'), ~ as.numeric(as.character(.))),
    pct_pop_chg_2010_2019 = ((tot_popE_2019 - tot_popE_2010) / tot_popE_2010) * 100,
    pct_poverty_chg_2010_2019 = ((pct_below_poverty_2019 - pct_below_poverty_2010) / pct_below_poverty_2010) * 100,
    pct_hhs_no_vehicles_chg_2010_2019 = ((pct_hhs_no_vehicles_2019 - pct_hhs_no_vehicles_2010) / pct_hhs_no_vehicles_2010) * 100,
    pct_pop_gt_16_in_civilian_labor_force_unemployed_2010 = pop_gt_16_in_civilian_labor_force_unemployed_2010 / pop_gt_16_in_civilian_labor_force_2010,
    pct_pop_gt_16_in_civilian_labor_force_unemployed_2015 = pop_gt_16_in_civilian_labor_force_unemployed_2015 / pop_gt_16_in_civilian_labor_force_2015,
    pct_pop_gt_16_in_civilian_labor_force_unemployed_2019 = pop_gt_16_in_civilian_labor_force_unemployed_2019 / pop_gt_16_in_civilian_labor_force_2019,
    pct_pop_gt_16_in_civilian_labor_force_unemployed_chg_2010_2019 = ((pct_pop_gt_16_in_civilian_labor_force_unemployed_2019 - pct_pop_gt_16_in_civilian_labor_force_unemployed_2010) / pct_pop_gt_16_in_civilian_labor_force_unemployed_2010) * 100
  ) %>%
  lapply(function(i) if(is.numeric(i)) ifelse(is.infinite(i), 0, i) else i) %>%
  as_tibble() %>%
  relocate(geometry, .after = last_col()) %>%
  st_as_sf(crs = "EPSG: 4269")

######################################################################
## REMOVED 36013990000 Census Tract 9900, Chautauqua County, New York
## Had no estimated population AND had empty geometry
######################################################################

County Data

Code
# pull southern tier county data and calculate area in square miles
st_counties <- map2(2010:2019, rep('county', times = 10), get_acs_data) %>%
  bind_rows() %>% 
  as_tibble() %>%
  st_as_sf(crs = "EPSG: 4269") %>%
  mutate(area_sqmi = st_area(.) / 2589988.110336) %>%
  relocate(geometry, .after = last_col())

# separate st_counties for 2010, 2015, and 2019 into separate dataframes
st_counties.2010 <-
  st_counties %>%
  filter(year == 2010) %>%
  as_tibble()

st_counties.2015 <-
  st_counties %>%
  filter(year == 2015) %>%
  as_tibble()

st_counties.2019 <-
  st_counties %>%
  filter(year == 2019) %>%
  as_tibble()

# merge 2010, 2015, & 2019 data into single sf
# compute pct chgs, convert to sf, and change infinite values to NA
# will merge with grouped st_tracts.2010_2019 data below
st_counties_base.2010_2019 <- 
  left_join(st_counties.2010, st_counties.2015, by = 'GEOID', keep = FALSE, suffix = c('', '_2015')) %>%
  left_join(st_counties.2019, by = 'GEOID', keep = FALSE, suffix = c('_2010', '_2019')) %>%
  select(-c('NAME_2015', 'geometry_2015', 'year_2015', 'county_2015', 'NAME_2019', 'geometry_2019', 'year_2019', 'county_2019')) %>%
  rename(
    NAME = NAME_2010,
    geometry = geometry_2010,
    county = county_2010,
    year = year_2010
  ) %>%
  mutate(
    pct_median_age_chg_2010_2019 = ((median_ageE_2019 - median_ageE_2010) / median_ageE_2010) * 100,
    pct_median_income_chg_2010_2019 = ((median_incomeE_2019 - median_incomeE_2010) / median_incomeE_2010) * 100
  ) %>%
  lapply(function(i) if(is.numeric(i)) ifelse(is.infinite(i), 0, i) else i) %>%
  as_tibble() %>%
  relocate(geometry, .after = last_col()) %>%
  st_as_sf(crs = "EPSG: 4269")

# get list of all pct columns
pct_cols <- grep('pct', names(st_tracts.2010_2019), value = TRUE)

# calculate data from st_tracts.2010_2019 by grouping by county, then merge with st_counties_base.2010_2019
# create data frame of all county-level data
st_counties.2010_2019 <- 
  st_tracts.2010_2019 %>%
  select(!all_of(pct_cols)) %>%
  group_by(county) %>%
  reframe(
    tot_pop_2010 = sum(tot_popE_2010),
    tot_pop_2015 = sum(tot_popE_2015),
    tot_pop_2019 = sum(tot_popE_2019),
    tot_hhs_2010 = sum(tot_hhsE_2010),
    tot_hhs_2015 = sum(tot_hhsE_2015),
    tot_hhs_2019 = sum(tot_hhsE_2019),
    tot_below_poverty_2010 = sum(tot_below_povertyE_2010),
    tot_below_poverty_2015 = sum(tot_below_povertyE_2015),
    tot_below_poverty_2019 = sum(tot_below_povertyE_2019),
    hhs_no_vehicles_2010 = sum(hhs_no_vehicleE_2010),
    hhs_no_vehicles_2015 = sum(hhs_no_vehicleE_2015),
    hhs_no_vehicles_2019 = sum(hhs_no_vehicleE_2019),
    pop_gt_16_in_civ_lab_force_2010 = sum(pop_gt_16_in_civilian_labor_force_2010),
    pop_gt_16_in_civ_lab_force_2015 = sum(pop_gt_16_in_civilian_labor_force_2015),
    pop_gt_16_in_civ_lab_force_2019 = sum(pop_gt_16_in_civilian_labor_force_2019),
    pop_gt_16_in_civ_lab_force_unemployed_2010 = pop_gt_16_in_civilian_labor_force_unemployed_2010,
    pop_gt_16_in_civ_lab_force_unemployed_2015 = pop_gt_16_in_civilian_labor_force_unemployed_2015,
    pop_gt_16_in_civ_lab_force_unemployed_2019 = pop_gt_16_in_civilian_labor_force_unemployed_2019,
    pct_below_poverty_2010 = (tot_below_poverty_2010 / tot_pop_2010) * 100,
    pct_below_poverty_2015 = (tot_below_poverty_2015 / tot_pop_2015) * 100,
    pct_below_poverty_2019 = (tot_below_poverty_2019 / tot_pop_2019) * 100,
    pct_hhs_no_vehicles_2010 = (hhs_no_vehicles_2010 / tot_hhs_2010) * 100,
    pct_hhs_no_vehicles_2015 = (hhs_no_vehicles_2015 / tot_hhs_2015) * 100,
    pct_hhs_no_vehicles_2019 = (hhs_no_vehicles_2019 / tot_hhs_2019) * 100,
    pct_pop_gt_16_in_civ_lab_force_unemployed_2010 = (pop_gt_16_in_civ_lab_force_unemployed_2010 / pop_gt_16_in_civ_lab_force_2010) * 100,
    pct_pop_gt_16_in_civ_lab_force_unemployed_2015 = (pop_gt_16_in_civ_lab_force_unemployed_2015 / pop_gt_16_in_civ_lab_force_2015) * 100,
    pct_pop_gt_16_in_civ_lab_force_unemployed_2019 = (pop_gt_16_in_civ_lab_force_unemployed_2019 / pop_gt_16_in_civ_lab_force_2019) * 100,
    pct_pop_chg_2010_2019 = ((tot_pop_2019 - tot_pop_2010) / tot_pop_2010) * 100,
    geometry = st_union(geometry)
  ) %>%
  distinct(county, .keep_all = TRUE) %>%
  left_join(st_drop_geometry(st_counties_base.2010_2019), by = join_by(county == county), suffix = c('', ''), keep = TRUE) %>%
  st_as_sf(crs = st_crs(st_counties_base.2010_2019))

Land Cover Data

Land Cover Set Up

Code
# set urls to land cover images
url_2010 <- 'https://edcintl.cr.usgs.gov/downloads/sciweb1/shared/lcmap/public/full_extent_downloads/version_13/primary-landcover_conus_year_data/LCMAP_CU_2010_V13_LCPRI/LCMAP_CU_2010_V13_LCPRI.tif'
url_2019 <- 'https://edcintl.cr.usgs.gov/downloads/sciweb1/shared/lcmap/public/full_extent_downloads/version_13/primary-landcover_conus_year_data/LCMAP_CU_2019_V13_LCPRI/LCMAP_CU_2019_V13_LCPRI.tif'

# dissolve counties into single study are polygon
st_full_study_area <- st_counties.2010_2019 %>% st_union()

# define crop_raster function - crop land cover rasters to full study area
# return both cropped and masked results
crop_raster <- function(lc_file, study_area) {
  crp <- crop(lc_file, vect(st_transform(study_area, crs = st_crs(lc_file))))
  msk <- mask(crp, vect(st_transform(study_area, crs = st_crs(lc_file))))
  
  return(msk)
}

# define calculate_zonal_stats function - calculate total area of each
# land cover category in square miles over the entire study area
calculate_zonal_stats <- function(lc_file) {
  z <- zonal(
    cellSize(lc_file, unit = 'km'),
    lc_file,
    fun=sum
  )
  
  z <- z %>%
    mutate(area_sqmi = area * 0.386102) %>%
    rename(land_cover_category = names(lc_file)[[1]]) %>%
    subset(select = -area)
}

# set land cover description list
land_cover_type <- c(
  Developed = 1,
  Cropland = 2,
  `Grassland/Shrubland` = 3,
  `Tree Cover` = 4,
  Water = 5,
  Wetlands = 6,
  `Snow and Ice` = 7,
  `Natural Barren` = 8
)

# load three land cover rasters and crop each to study area polygon
# find total area of each land cover categories in entire study area
land_cover_2010 <- rast(url_2010)
st_lc_2010_masked <- crop_raster(land_cover_2010, st_full_study_area)

|---------|---------|---------|---------|
=========================================
                                          
Code
zonal_stats_2010_study_area <- calculate_zonal_stats(st_lc_2010_masked)

|---------|---------|---------|---------|
=========================================
                                          
Code
land_cover_2019 <- rast(url_2019)
st_lc_2019_masked <- crop_raster(land_cover_2019, st_full_study_area)

|---------|---------|---------|---------|
=========================================
                                          
Code
zonal_stats_2019_study_area <- calculate_zonal_stats(st_lc_2019_masked)

|---------|---------|---------|---------|
=========================================
                                          

Land Cover Processing and Change Calculation

This section pertains to land cover over the entire 14-county study area. The interpretation of the land cover images’ pixel values and corresponding land cover classes were taken from p. 7 of the LCMAP Collection 1.3 Data Format Control Book.

Code
# create data base of land cover descriptions and their colors
lc.desc <- data.frame(
  ID = land_cover_type,
  landcover = names(land_cover_type),
  color = c('red', 'yellow', 'lightgreen', 'darkgreen', 'lightblue', 'blue', 'white', 'lightgray'),
  stringsAsFactors = FALSE
)

# define create_lc_plot function - create a ggplot of masked land cover object
create_lc_plot <- function(lc_file, year) {
  # convert file to factors
  lc <- as.factor(lc_file)
  
  # create plot
  plt <-
    ggplot() +
    geom_spatraster(data = lc) +
    scale_fill_manual(values = setNames(lc.desc$color, lc.desc$ID),
                      labels = lc.desc$landcover,
                      breaks = lc.desc$ID,
                      name = 'Landcover Type') +
    ggtitle(paste('Land Cover ', '(', year, ')', sep = '')) +
    theme(legend.position = 'right',
          plot.title = element_text(hjust = 0.5)) +
    guides(fill = guide_legend(ncol = 1, byrow = TRUE))
}

# generate plots for 2010 and 2019 land cover images
lc_2010_plt <- create_lc_plot(st_lc_2010_masked, 2010)
lc_2019_plt <- create_lc_plot(st_lc_2019_masked, 2019)

# display stacked plot of both land cover images
gridExtra::grid.arrange(lc_2010_plt, lc_2019_plt, ncol = 1)

Areas shown in black in the map below are areas where the land cover has changed between 2010 and 2019. The actual change in classification is not depicted.

Code
# compare 2019 and 2010 land cover images to see where changes occurred
chg <- (st_lc_2019_masked == st_lc_2010_masked)

|---------|---------|---------|---------|
=========================================
                                          
Code
# create color palette for map
palette <- colorNumeric(c('black', 'black', 'black', 'white', 'white', 'white'), values(chg),
                        na.color = 'transparent')

# display what areas have changed in land cover
leaflet() %>% addTiles %>%
  addRasterImage(chg, colors = palette, opacity = 0.75, maxBytes = 5000000) %>%
  addPolygons(data = st_transform(st_counties.2010_2019$geometry, crs = '+proj=longlat +datum=WGS84'), 
              color = 'red', opacity = 1, weight = 1, fillOpacity = 0)

|---------|---------|---------|---------|
=========================================
                                          
Code
# land cover changes from 2010 to 2019 in entire study area
land_cover_chg_study_area.2010_2019 <- left_join(
  zonal_stats_2010_study_area, zonal_stats_2019_study_area,
  by = 'land_cover_category', suffix = c('_2010', '_2019')
) %>%
  mutate(
    pct_of_tot_area_2010 = (area_sqmi_2010 / sum(area_sqmi_2010)) * 100,
    pct_of_tot_area_2019 = (area_sqmi_2019 / sum(area_sqmi_2019)) * 100,
    chg_in_area_sqmi = ((area_sqmi_2019 - area_sqmi_2010) / area_sqmi_2010) * 100
  )

# convert land_cover_type vector into a dataframe to merge with land_cover_chg data frame
# so land cover descriptions are included
lc.type <- data.frame(
  land_cover_category = land_cover_type, 
  land_cover_desc = rownames(as.data.frame(land_cover_type))
)

rownames(lc.type) <- 1:nrow(lc.type)

# join lc.type df with land cover chg results df, then remove numbered category column
# and replace all NAs with 0
lc_chg.2010_2019 <- left_join(lc.type, 
                              land_cover_chg_study_area.2010_2019, 
                              by = 'land_cover_category') %>%
  select(-land_cover_category) %>%
  replace(is.na(.), 0)

# create table of land cover change
lc_chg.2010_2019 %>%
  arrange(desc(area_sqmi_2010)) %>%
  gt(rowname_col = 'land_cover_desc') %>%
  tab_header(title = md('**Land Cover Change in NY Southern Tier**')) %>%
  cols_label(
    area_sqmi_2010 = md('**Area (2010)**'),
    area_sqmi_2019  = md('**Area (2019)**'),
    pct_of_tot_area_2010 = md('**% of Area (2010)**'),
    pct_of_tot_area_2019 = md('**% of Area (2019)**'),
    chg_in_area_sqmi = md('**% Change**')
  ) %>%
  fmt_number(columns = c('area_sqmi_2010', 'area_sqmi_2019'), decimals = 1) %>%
  fmt_percent(columns = c('pct_of_tot_area_2010', 'pct_of_tot_area_2019', 
                          'chg_in_area_sqmi'), decimals = 2, scale_values = FALSE)
Land Cover Change in NY Southern Tier
Area (2010) Area (2019) % of Area (2010) % of Area (2019) % Change
Tree Cover 7,387.1 7,386.8 62.36% 62.36% −0.00%
Cropland 3,416.5 3,417.0 28.84% 28.85% 0.02%
Developed 387.2 387.1 3.27% 3.27% −0.02%
Wetlands 378.5 378.4 3.19% 3.19% −0.03%
Water 141.6 141.2 1.20% 1.19% −0.27%
Grassland/Shrubland 109.6 106.5 0.93% 0.90% −2.81%
Natural Barren 25.6 29.0 0.22% 0.24% 13.23%
Snow and Ice 0.0 0.0 0.00% 0.00% 0.00%
Code
# create bar graph of land cover change between 2019 and 2019
ggplot(lc_chg.2010_2019, aes(x = land_cover_desc, y = chg_in_area_sqmi)) +
  geom_col(fill = 'blue') +
  labs(x = 'Land Cover Type', y = '% Change (sq mi)',
       title = 'Change in Area by Land Cover Classification') +
  theme(plot.title = element_text(hjust = 0.5),
        axis.text.x = element_text(angle = 45, hjust = 1))

A majority of the study area is covered by trees and cropland, with much smaller amounts of other land cover classes. The images of land cover change in 2010 and 2019 would not suggest any major land cover changes between that time period. There was an over 13% (about 3.4 square miles) increase in natural barren land and an almost 3% (about 2.9 square miles) decrease in grassland/shrubland. There were very small changes in other land cover types, and no snow and ice cover in either of the two years.

Results

The final analysis involved calculating the number of people who live in distressed areas. Using the criteria defined in the ARC’s Distressed Areas Classification System, the distressed status of each census tract was determined for 2010 and 2019. Using these statuses, it was then possible to see if each census tract improved or became distressed within the same time period. This data was used to figure out how many people lived in distressed areas in each county. The area of each land cover classification was then calculated for each census tract and county.

The above data was combined into separate data frames for census tracts and counties. The percentage change of each study variable was also calculated, except for the percentage change of distressed population at the census-tract level since these values would either be 0% or infinite. A small correlation analysis was conducted to determine relationships between any of the percentage change variables at both the tract level and the correlation coefficients were visualized using a heat map. A separate table was created to see if the percentage change in population was related to any of the other percentage change variables.

Distressed Area Analysis

Code
# get list of columns that are involved in distressed area classification
distressed_cols <- c(
  grep('tot_popE', names(st_tracts.2010_2019), value = TRUE),
  grep('median_income', names(st_tracts.2010_2019), value = TRUE), 
  grep('pct_below_poverty', names(st_tracts.2010_2019), value = TRUE)
)

# determined distressed area classification of each census tract
st_tracts.distressed <-
  st_tracts.2010_2019[,c('GEOID', 'NAME', 'county', distressed_cols)] %>%
  mutate(
    if_Distressed_2010 = (((median_incomeE_2010 / us_data.2010_2019$median_incomeE_2010) <= 0.67) & ((pct_below_poverty_2010 / ((us_data.2010_2019$pct_below_povertyE_2010) / 100)) >= 1.50)),
    if_Distressed_2015 = (((median_incomeE_2015 / us_data.2010_2019$median_incomeE_2015) <= 0.67) & ((pct_below_poverty_2015 / ((us_data.2010_2019$pct_below_poverty_2015) / 100)) >= 1.50)),
    if_Distressed_2019 = (((median_incomeE_2019 / us_data.2010_2019$median_incomeE_2019) <= 0.67) & ((pct_below_poverty_2019 / ((us_data.2010_2019$pct_below_poverty_2019) / 100)) >= 1.50)),
    if_Became_Distressed_2010_2019 = (if_Distressed_2010 == FALSE & if_Distressed_2019 == TRUE),
    if_Distressed_Then_Improved_2010_2019 = (if_Distressed_2010 == TRUE & if_Distressed_2019 == FALSE)
  ) %>%
  relocate(geometry, .after = last_col())

# create spatial table of distressed statuses for each census tract
distressed.tab <- 
  st_tracts.distressed %>%
  select(GEOID, NAME, county, grep('Distressed', colnames(st_tracts.distressed), value = TRUE))

# map which tracts were distressed in 2010
mapview(
  distressed.tab, 
  zcol = 'if_Distressed_2010',
  alpha.regions = 0.5,
  popup = "NAME",
  layer.name = c("Distressed (2010)")
)
Code
# map which tracts were distressed in 2010
mapview(
  distressed.tab, 
  zcol = 'if_Distressed_2019',
  alpha.regions = 0.5,
  popup = "NAME",
  layer.name = c("Distressed (2019)")
)
Code
# create table of number of worsened and improved tracts by county, then display
distressed.tab %>%
  group_by(county) %>%
  summarize(
    tot_tracts = n(),
    across(
      grep('2010', colnames(.), value = TRUE),
      \(x) sum(x, na.rm = TRUE)
    ),
    across(
      grep('2019', colnames(.), value = TRUE),
      \(x) sum(x, na.rm = TRUE)
    )
  ) %>%
  st_drop_geometry() %>%
  relocate(if_Distressed_2019, .after = if_Distressed_2010) %>%
  arrange(desc(tot_tracts)) %>%
  gt() %>%
  cols_label(
    county = md('**County**'),
    tot_tracts = md('**# Tracts**'),
    if_Distressed_2010 = md('**Distressed (2010)**'),
    if_Distressed_2019 = md('**Distressed (2019)**'),
    if_Became_Distressed_2010_2019 = md('**Became Distressed**'),
    if_Distressed_Then_Improved_2010_2019 = md('**Distressed Then Improved**')
  )
County # Tracts Distressed (2010) Distressed (2019) Became Distressed Distressed Then Improved
Broome 55 10 14 5 1
Chautauqua 35 6 9 4 1
Steuben 30 1 3 2 0
Tompkins 23 1 0 0 0
Chemung 22 3 4 1 0
Cattaraugus 21 1 3 2 0
Otsego 17 0 0 0 0
Delaware 14 0 1 1 0
Allegany 13 0 2 2 0
Chenango 12 0 1 1 0
Cortland 12 0 2 2 0
Tioga 10 0 0 0 0
Schoharie 8 0 0 0 0
Schuyler 5 0 0 0 0

Across the study area there were 39 census tracts (14.1%) that were distressed in 2019, an increase from the 22 census tracts (7.9%) that were distressed in 2010. Nine of the fourteen counties had census tracts that became distressed, while only two counties had a tract that improved. In total, 20 census tracts (7.2%) became distressed and only 2 census tracts (0.7%) improved. Cortland County had the largest percentage of census tracts that became distressed (16.7%), followed by Allegany County (15.4%) and Chautauqua County (11.4%).

Most cities in the Southern Tier are at least mostly distressed in terms of number of distressed census tracts. The only cities that did not have distressed areas are Ithaca (Tompkins County) and Oneonta (Otsego County). In contrast, Binghamton, Jamestown, Dunkirk, and Elmira were all significantly distressed.

Otsego, Tioga, Schoharie, and Schuyler Counties had no distressed census tracts in 2010 or 2019. Tompkins County had 1 census tract that was distressed in 2010, but its distressed status could not be determined in 2019.

The map below shows the census tracts that became distressed between 2010 and 2019.

Code
# map which tracts became distressed between 2010 and 2019
mapview(
  distressed.tab, 
  zcol = 'if_Became_Distressed_2010_2019',
  alpha.regions = 0.5,
  popup = "NAME",
  layer.name = c("Became Distressed (2010 -> 2019)")
)

There are a few census tracts that do not appear on the map, although their popup labels work. The table below shows the three census tracts that have an “NA” value in the “if_Became_Distressed_2010_2019” column. These three tracts are missing at least 1 data point that was used to determine distressed status.

Code
# display median income and pct below poverty data data for tracts
# that have NA 'if_Became_Distressed_2010_2019' value
st_tracts.distressed %>%
  filter(is.na(if_Became_Distressed_2010_2019)) %>%
  select(NAME, median_incomeE_2010, median_incomeE_2019, 
         pct_below_poverty_2010, pct_below_poverty_2019) %>%
  st_drop_geometry() %>%
  gt()
NAME median_incomeE_2010 median_incomeE_2019 pct_below_poverty_2010 pct_below_poverty_2019
Census Tract 9402, Cattaraugus County, New York NA NA NaN NaN
Census Tract 9400, Cattaraugus County, New York NA NA 0.5420290 0.2436709
Census Tract 2, Tompkins County, New York 110795 NA 0.6491352 0.6059628

The map below shows the census tracts that were distressed in 2010 and were no longer distressed in 2019.

Code
# map which tracts improved between 2010 and 2019
mapview(
  distressed.tab, 
  zcol = 'if_Distressed_Then_Improved_2010_2019',
  alpha.regions = 0.5,
  popup = "NAME",
  layer.name = c("Improved (2010 -> 2019)")
)

As is the case with census tracts that became distressed, some of the tracts do not show up. The table below shows which tracts have an “NA” value in the ‘if_Distressed_Then_Improved_2010_2019’ column. These 4 census tracts are missing at least 1 data point that was used to determine distressed status.

Code
# display median income and pct below poverty data data for tracts
# that have NA 'if_Distressed_Then_Improved_2010_2019' value
st_tracts.distressed %>%
  filter(is.na(if_Distressed_Then_Improved_2010_2019)) %>%
  select(NAME, median_incomeE_2010, median_incomeE_2019, 
         pct_below_poverty_2010, pct_below_poverty_2019) %>%
  st_drop_geometry() %>%
  gt()
NAME median_incomeE_2010 median_incomeE_2019 pct_below_poverty_2010 pct_below_poverty_2019
Census Tract 9402, Cattaraugus County, New York NA NA NaN NaN
Census Tract 9402, Allegany County, New York NA NA NaN 0.0000000
Census Tract 9400, Cattaraugus County, New York NA NA 0.5420290 0.2436709
Census Tract 1, Tompkins County, New York 35833 NA 0.3029074 0.2594142

Most of the census tracts that became distressed between 2010 and 2019 are located in built-up areas, such as Binghamton, Jamestown, Dunkirk, Cortland, and Hornell. There were additionally a few rural tracts that became distressed, as well as the Allegany Indian Reservation centered around Salamanca in Cattaraugus County.

There were only 2 census tracts that improved from a distressed status: 1 in Broome County in between Binghamton and Johnson City (Census Tract 1) and 1 in eastern Dunkirk (Census Tract 354). In Census Tract 1, the median family income increased by over 64% and the percentage below poverty level increased by less than 4%. In Census Tract 354, the median family income increased by almost 46% and the percentage below poverty level decreased by over 20%.

Distressed Population and Land Cover Change Analysis

Analysis Set Up

Some percentage change values were calculated to be infinite values, which occurred when the value of some variable was 0 in 2010 and had increased by 2019. These values were edited to reflect a change of (an arbitrary value of) 1,000,000% so that they could be used in the correlation analysis.

Code
# define add_land_cover_type_area function
add_land_cover_type_area <- function(summarize_df, df, col, lc_num) {
  summarize_df[1,as.character(col)] = ifelse(
    nrow(filter(df, land_cover_category == lc_num)) == 1,
    filter(df, land_cover_category == lc_num)$area_sqmi,
    0
  )
}

# define gather_data function
gather_data <- function(lc_file, year, geog) {
  # set column names for different land cover categories
  developed_col = as.symbol(paste('developed_area_sqmi_', as.character(year), sep = ''))
  natural_barren_col = as.symbol(paste('natural_barren_area_sqmi_', as.character(year), sep = ''))
  cropland_col = as.symbol(paste('cropland_area_sqmi_', as.character(year), sep = ''))
  tree_cover_col = as.symbol(paste('tree_cover_area_sqmi_', as.character(year), sep = ''))
  grassland_col = as.symbol(paste('grassland_area_sqmi_', as.character(year), sep = ''))
  wetland_col = as.symbol(paste('wetland_area_sqmi_', as.character(year), sep = ''))
  water_col = as.symbol(paste('water_area_sqmi_', as.character(year), sep = ''))
  
  # set distressed column name for the year
  distressed_col_yr = as.symbol(paste('if_Distressed_', as.character(year), sep = ''))
  # set name of tot_pop_distressed column with year appended to end
  tot_pop_distressed_col <- as.symbol(paste('tot_pop_distressed_', as.character(year), sep = ''))
  
  # set columns only used in tracts data gathering
  tot_below_poverty_est_col = as.symbol(paste('tot_below_povertyE_', as.character(year), sep=''))
  hhs_no_vehicles_est_col = as.symbol(paste('hhs_no_vehicleE_', as.character(year), sep = ''))
  
  # set total population column to use in calculations
  tot_pop_est_col = as.symbol(paste('tot_popE_', as.character(year), sep = ''))
  tot_pop_col = as.symbol(paste('tot_pop_', as.character(year), sep = ''))
  tot_hhs_col = as.symbol(paste('tot_hhs_', as.character(year), sep = ''))
  tot_hhs_est_col = as.symbol(paste('tot_hhsE_', as.character(year), sep = ''))
  tot_below_poverty_col = as.symbol(paste('tot_below_poverty_', as.character(year), sep = ''))
  pop_civ_lab_force_col = as.symbol(paste('pop_gt_16_in_civ_lab_force_', as.character(year), sep = ''))
  pop_unemployed_col = as.symbol(paste('pop_gt_16_in_civ_lab_force_unemployed_', as.character(year), sep = ''))
  pop_distressed_col = as.symbol(paste('tot_pop_distressed_', as.character(year), sep = ''))
  hhs_no_vehicles_col = as.symbol(paste('hhs_no_vehicles_', as.character(year), sep = ''))
  area_col = as.symbol(paste('area_sqmi_', as.character(year), sep = ''))
  
  # set names of new columns to be calculated
  pct_below_poverty_col = as.symbol(paste('pct_below_poverty_', as.character(year), sep = ''))
  pct_unemployed_col = as.symbol(paste('pct_unemployed_', as.character(year), sep = ''))
  pct_pop_distressed_col = as.symbol(paste('pct_pop_distressed_', as.character(year), sep = ''))
  pct_hhs_no_vehicles_col = as.symbol(paste('pct_hhs_no_vehicles_', as.character(year), sep = ''))
  pct_developed_col = as.symbol(paste('pct_developed_area_sqmi_', as.character(year), sep = ''))
  pct_natural_barren_col = as.symbol(paste('pct_natural_barren_area_sqmi_', as.character(year), sep = ''))
  pct_cropland_col = as.symbol(paste('pct_cropland_area_sqmi_', as.character(year), sep = ''))
  pct_tree_cover_col = as.symbol(paste('pct_tree_cover_area_sqmi_', as.character(year), sep = ''))
  pct_grassland_col = as.symbol(paste('pct_grassland_area_sqmi_', as.character(year), sep = ''))
  pct_wetland_col = as.symbol(paste('pct_wetland_area_sqmi_', as.character(year), sep = ''))
  pct_water_col = as.symbol(paste('pct_water_area_sqmi_', as.character(year), sep = ''))
  
  # set names of columns to be edited or removed
  median_age_est_col = as.symbol(paste('median_ageE_', as.character(year), sep = ''))
  median_income_est_col = as.symbol(paste('median_incomeE_', as.character(year), sep = ''))
  new_median_age_est_col = as.symbol(paste('median_age_', as.character(year), sep = ''))
  new_median_income_est_col = as.symbol(paste('median_income_', as.character(year), sep = ''))
  median_age_moe_col = as.symbol(paste('median_ageM_', as.character(year), sep = ''))
  median_income_moe_col = as.symbol(paste('median_incomeM_', as.character(year), sep = ''))
  
  # initialize index to use in data_list
  index = 1
  
  # # initialize list to hold dataframe for each county or tract
  if (geog == 'county') {
    data_list = vector('list', length = length(southern_tier_counties))
  } else {
    data_list = vector('list', length = length(st_tracts.2010_2019))
  }
  
  # run this if geog is set to 'county'
  if (geog == 'county') {
    # iterate over each study county and get population, distressed, and zonal stats data,
    # then store resulting data frame in df object
    df <- for (i in southern_tier_counties) {
      # get county geography
      county_sf <- study_counties %>% filter(NAME == i)
      
      # get cropped & masked land cover raster for county
      county_lc_masked <- crop_raster(lc_file, county_sf)
      
      # calculate area of each land cover category in county
      county_zonal_stats <- calculate_zonal_stats(county_lc_masked) %>%
        summarize(
          !!developed_col := filter(., land_cover_category == 1)$area_sqmi,
          !!natural_barren_col := filter(., land_cover_category == 8)$area_sqmi,
          !!cropland_col := filter(., land_cover_category == 2)$area_sqmi,
          !!tree_cover_col := filter(., land_cover_category == 4)$area_sqmi,
          !!grassland_col := filter(., land_cover_category == 3)$area_sqmi,
          !!wetland_col := filter(., land_cover_category == 6)$area_sqmi,
          !!water_col := filter(., land_cover_category == 5)$area_sqmi
        ) %>%
        mutate(county = i,
               geometry = county_sf$geometry) %>%
        st_as_sf(crs = st_crs(st_counties.2010_2019))
      
      # get county stats
      county_pop_data <- 
        st_counties.2010_2019 %>%
        filter(county == i) %>%
        select(county, contains(as.character(year)) & !contains('pct'))
      
      # calculate number of people in each county that lived in tracts that
      # were distressed and improved and in tracts that became distressed
      county_distressed <-
        st_tracts.distressed %>%
        filter(county == i) %>%
        select(GEOID, NAME, county, contains('tot_popE'), contains('Distressed'))
      
      # total number of people living in distress for this year
      pop_distressed <- 
        county_distressed %>%
        filter(!!distressed_col_yr == TRUE) %>%
        summarize(
          !!tot_pop_distressed_col := sum(!!tot_pop_est_col, na.rm = TRUE)
        ) %>%
        mutate(county = i) %>%
        relocate(geometry, .after = last_col())
      
      # get number of people who lived in areas that became distressed by 2019
      pop_became_distressed <-
        county_distressed %>%
        filter(if_Became_Distressed_2010_2019 == TRUE) %>%
        summarize(
          tot_pop_became_distressed_2010_2019 = sum(!!tot_pop_est_col, na.rm = TRUE)
        ) %>%
        mutate(county = i) %>%
        relocate(geometry, .after = last_col())
      
      # get number of people who lived in areas that improved by 2019
      pop_improved <- 
        county_distressed %>%
        filter(if_Distressed_Then_Improved_2010_2019 == TRUE) %>%
        summarize(
          tot_pop_improved_2010_2019 = sum(!!tot_pop_est_col, na.rm = TRUE)
        ) %>%
        mutate(county = i) %>%
        relocate(geometry, .after = last_col())
      
      # join distressed data-related data frames together
      county_distressed.data <-
        st_join(pop_distressed, pop_became_distressed) %>%
        st_join(pop_improved)
      
      # join 3 dataframes, then add to list of county data frames
      d <- st_join(county_pop_data, county_distressed.data, suffix = c('', '.y')) %>%
        st_join(county_zonal_stats, suffix = c('', '.z')) %>%
        select(-c('county.y...17', 'county.y...19', 'county.x', 'county.z', !!median_age_moe_col, !!median_income_moe_col)) %>%
        rename(
          !!new_median_age_est_col := !!median_age_est_col,
          !!new_median_income_est_col := !!median_income_est_col
        ) %>%
        mutate(
          !!pct_below_poverty_col := (!!tot_below_poverty_col / !!tot_pop_col) * 100,
          !!pct_unemployed_col := (!!pop_unemployed_col / !!pop_civ_lab_force_col) * 100,
          !!pct_pop_distressed_col := (!!pop_distressed_col / !!tot_pop_col) * 100,
          !!pct_hhs_no_vehicles_col := (!!hhs_no_vehicles_col / !!tot_hhs_col) * 100,
          !!pct_developed_col := (!!developed_col / !!area_col) * 100,
          !!pct_natural_barren_col := (!!natural_barren_col / !!area_col) * 100,
          !!pct_cropland_col := (!!cropland_col / !!area_col) * 100,
          !!pct_tree_cover_col := (!!tree_cover_col / !!area_col) * 100,
          !!pct_grassland_col := (!!grassland_col / !!area_col) * 100,
          !!pct_wetland_col := (!!wetland_col / !!area_col) * 100,
          !!pct_water_col := (!!water_col / !!area_col) * 100
        )
    
      # add data frame to county data list
      data_list[[index]] <- d
    
      # add 1 to index
      index = index + 1
    }
  } else { # run this if geography is set to 'tract'
      # iterate over each study tract and get population, distressed, and zonal stats data,
      # then store resulting data frame in df object
      df <- for (i in st_tracts.2010_2019$GEOID) {
        # get tract geography
        tract_sf <- st_tracts.2010_2019 %>% filter(GEOID == i)
        
        # get cropped & masked land cover raster for tract
        tract_lc_masked <- crop_raster(lc_file, tract_sf)
        
        # calculate area of each land cover category in tract
        tract_zonal_stats <- calculate_zonal_stats(tract_lc_masked)
        
        # create vector of column names in summarized area table
        col.names <- c(developed_col, natural_barren_col, cropland_col,
                       tree_cover_col, grassland_col, wetland_col, water_col)
        
        # create new single-row data frame with column names called tr.zs
        # to store area stats for census tract
        tr.zs <- as.data.frame(matrix(rep(0, length(col.names)), nrow=1))
        names(tr.zs) <- col.names
        
        # add area for each land cover type to new df
        tr.zs[1, as.character(developed_col)] <- add_land_cover_type_area(tr.zs, 
                                                                          tract_zonal_stats,
                                                                          developed_col, 1)
        tr.zs[1, as.character(cropland_col)] <- add_land_cover_type_area(tr.zs, 
                                                                          tract_zonal_stats,
                                                                          cropland_col, 2)
        tr.zs[1, as.character(grassland_col)] <- add_land_cover_type_area(tr.zs, 
                                                                          tract_zonal_stats,
                                                                          developed_col, 3)
        tr.zs[1, as.character(tree_cover_col)] <- add_land_cover_type_area(tr.zs, 
                                                                          tract_zonal_stats,
                                                                          tree_cover_col, 4)
        tr.zs[1, as.character(water_col)] <- add_land_cover_type_area(tr.zs, 
                                                                          tract_zonal_stats,
                                                                          water_col, 5)
        tr.zs[1, as.character(wetland_col)] <- add_land_cover_type_area(tr.zs, 
                                                                          tract_zonal_stats,
                                                                          wetland_col, 6)
        tr.zs[1, as.character(natural_barren_col)] <- add_land_cover_type_area(tr.zs, 
                                                                          tract_zonal_stats,
                                                                          natural_barren_col, 8)
        
        # add GEOID, total area, and geometry columns to tr.zs then turn into sf
        tr.zs <-
          tr.zs %>%
          mutate(tract = i,
                 !!area_col := !!developed_col + !!natural_barren_col + !!cropland_col + !!tree_cover_col + !!grassland_col + !!wetland_col + !!water_col,
                 geometry = tract_sf$geometry) %>%
          st_as_sf(crs = st_crs(st_tracts.2010_2019))
        
        # get tracts stats
        tract_pop_data <- 
          filter(st_tracts.2010_2019, GEOID == i) %>%
          select(GEOID, NAME, county, contains(as.character(year))) %>%
          rename(
            !!pop_civ_lab_force_col := as.symbol(paste('pop_gt_16_in_civilian_labor_force_', as.character(year), sep = '')),
            !!pop_unemployed_col := as.symbol(paste('pop_gt_16_in_civilian_labor_force_unemployed_', as.character(year), sep = ''))
          )
        
        # get distressed data for tract
        tract_distressed <-
          st_tracts.distressed %>%
          filter(GEOID == i) %>%
          select(GEOID, contains('Became_Distressed'), contains('Improved'))
        
        # join 3 dataframes, then add to list of county data frames
        d <- st_join(tract_pop_data, tract_distressed, suffix = c('', '.y')) %>%
          st_join(tr.zs, suffix = c('', '.z')) %>%
          select(-c(GEOID.y, tract, !!median_age_moe_col, !!median_income_moe_col)) %>%
          rename(
            !!new_median_age_est_col := !!median_age_est_col,
            !!new_median_income_est_col := !!median_income_est_col
          ) %>%
          mutate(
            !!pct_below_poverty_col := (!!tot_below_poverty_est_col / !!tot_pop_est_col) * 100,
            !!pct_unemployed_col := (!!pop_unemployed_col / !!pop_civ_lab_force_col) * 100,
            !!pct_hhs_no_vehicles_col := (!!hhs_no_vehicles_est_col / !!tot_hhs_est_col) * 100,
            !!pct_developed_col := (!!developed_col / !!area_col) * 100,
            !!pct_natural_barren_col := (!!natural_barren_col / !!area_col) * 100,
            !!pct_cropland_col := (!!cropland_col / !!area_col) * 100,
            !!pct_tree_cover_col := (!!tree_cover_col / !!area_col) * 100,
            !!pct_grassland_col := (!!grassland_col / !!area_col) * 100,
            !!pct_wetland_col := (!!wetland_col / !!area_col) * 100,
            !!pct_water_col := (!!water_col / !!area_col) * 100
          )
        
        # add data frame to county data list
        data_list[[index]] <- d
      
        # add 1 to index
        index = index + 1
      }
  }
  
  # bind geography data frames together then return data
  data <- bind_rows(data_list)
  
  return(data)
}

Tract-Level Population and Land Cover Change Data

Code
# get full counties sfs for 2010 and 2019 and fill NA with 0, then join them into a single sf
final_tracts_df.2010 <- gather_data(st_lc_2010_masked, 2010, 'tract')
final_tracts_df.2010[is.na(final_tracts_df.2010)] <- 0
final_tracts_df.2019 <- gather_data(st_lc_2019_masked, 2019, 'tract')
final_tracts_df.2019[is.na(final_tracts_df.2019)] <- 0

final_tracts_df <- 
  st_join(final_tracts_df.2010, final_tracts_df.2019, 
                             suffix = c('', '.y'), largest = TRUE) %>% 
  select(-contains('.y'), -contains('M_'), -contains('chg')) %>%
  rename_with(~ str_replace(., 'E_', '_'), 
              grep('E_', colnames(.), value = TRUE)) %>%
  mutate(
    pct_pop_chg = ((tot_pop_2019 - tot_pop_2010) / tot_pop_2010) * 100,
    pct_hhs_chg = ((tot_hhs_2019 - tot_hhs_2010) / tot_hhs_2010) * 100,
    pct_median_age_chg = ((median_age_2019 - median_age_2010) / median_age_2010) * 100,
    pct_median_income_chg = ((median_income_2019 - median_income_2010) / median_income_2010) * 100,
    pct_below_poverty_chg = ((pct_below_poverty_2019 - pct_below_poverty_2010) / pct_below_poverty_2010) * 100,
    pct_unemployed_chg = ((pct_unemployed_2019 - pct_unemployed_2010) / pct_unemployed_2010)  * 100,
    pct_hhs_no_vehicles_chg = ((pct_hhs_no_vehicles_2019 - pct_hhs_no_vehicles_2010) / pct_hhs_no_vehicles_2010) * 100,
    pct_developed_chg = ((pct_developed_area_sqmi_2019 - pct_developed_area_sqmi_2010) / pct_developed_area_sqmi_2010) * 100,
    pct_natural_barren_chg = ((pct_natural_barren_area_sqmi_2019 - pct_natural_barren_area_sqmi_2010) / pct_natural_barren_area_sqmi_2010) * 100,
    pct_cropland_chg = ((pct_cropland_area_sqmi_2019 - pct_cropland_area_sqmi_2010) / pct_cropland_area_sqmi_2010) * 100,
    pct_tree_cover_chg = ((pct_tree_cover_area_sqmi_2019 - pct_tree_cover_area_sqmi_2010) / pct_tree_cover_area_sqmi_2010) * 100,
    pct_grassland_chg = ((pct_grassland_area_sqmi_2019 - pct_grassland_area_sqmi_2010) / pct_grassland_area_sqmi_2010) * 100,
    pct_wetland_chg = ((pct_wetland_area_sqmi_2019 - pct_wetland_area_sqmi_2010) / pct_wetland_area_sqmi_2010) * 100,
    pct_water_chg = ((pct_water_area_sqmi_2019 - pct_water_area_sqmi_2010) / pct_water_area_sqmi_2010) * 100
  ) %>%
  mutate_if(is.numeric, ~ replace_na(., 0) %>% replace(., is.infinite(.), 1000000)) %>%
  relocate(geometry, .after = last_col())

final_tracts_df[is.na(final_tracts_df)] <- 0

# display county-level table of data
final_tracts_df %>%
  select(NAME, county, grep('chg', colnames(.), value = TRUE), contains('Became'), contains('Improved')) %>%
  relocate(if_Became_Distressed_2010_2019, .after = pct_below_poverty_chg) %>%
  relocate(if_Distressed_Then_Improved_2010_2019, .after = if_Became_Distressed_2010_2019) %>%
  mutate(NAME = str_split(.$NAME, ',', simplify = TRUE)) %>%
  st_drop_geometry() %>%
  gt(groupname_col = 'county') %>%
  tab_options(row_group.as_column = TRUE, 
              container.height = 1500,
              container.overflow.y = TRUE) %>%
  tab_stubhead(label = 'county') %>%
  tab_header(
    title = md('**Changes in Population Characteristics, Economic Indicators, and Land Cover**'),
    subtitle = md('By Southern Tier Census Tract (2010 - 2019)')
  ) %>%
  cols_label(
    pct_pop_chg = md('**% Pop Change**'),
    pct_hhs_chg = md('**% HHs Change**'),
    pct_median_age_chg = md('**% Median Age Change**'),
    pct_median_income_chg = md('**% Median Income Change**'),
    pct_below_poverty_chg = md('**% Pop Below Poverty Change**'),
    if_Became_Distressed_2010_2019 = md('**If Became Distressed**'),
    if_Distressed_Then_Improved_2010_2019 = md('**If Improved**'),
    pct_unemployed_chg = md('**% Unemployment Rate Change**'),
    pct_hhs_no_vehicles_chg = md('**% HHs No Vehicle Change**'),
    pct_developed_chg = md('**% Developed Change**'),
    pct_natural_barren_chg = md('**% Natural Barren Change**'),
    pct_cropland_chg = md('**% Cropland Change**'),
    pct_tree_cover_chg = md('**% Tree Cover Change**'),
    pct_grassland_chg = md('**% Grassland Change**'),
    pct_wetland_chg = md('**% Wetland Change**'),
    pct_water_chg = md('**% Water Change**')
  ) %>%
  fmt_percent(decimals = 1, scale_values = FALSE)
Changes in Population Characteristics, Economic Indicators, and Land Cover
By Southern Tier Census Tract (2010 - 2019)
county NAME % Pop Change % HHs Change % Median Age Change % Median Income Change % Pop Below Poverty Change If Became Distressed If Improved % Unemployment Rate Change % HHs No Vehicle Change % Developed Change % Natural Barren Change % Cropland Change % Tree Cover Change % Grassland Change % Wetland Change % Water Change
Cortland Census Tract 9703 −7.5% −4.4% 15.8% −2.0% −52.2% FALSE FALSE −66.6% −66.0% −0.6% 85.7% 1.0% 0.0% 50.0% 0.0% 0.0%
Census Tract 9706 −3.9% −13.0% −13.0% 10.9% 68.2% FALSE FALSE −53.1% −30.7% −0.2% −0.0% 16.7% 0.0% 50.0% 0.0% 0.0%
Census Tract 9701 −16.7% −3.1% 13.9% 32.4% 15.3% FALSE FALSE −50.8% 57.1% 1.2% 17.5% 0.0% −0.4% 20.5% −0.1% 4.6%
Census Tract 9702 −1.4% 1.9% 19.9% 53.8% 3.6% FALSE FALSE −63.5% 12.4% 0.9% 18.8% −0.1% 0.2% −5.8% 0.0% 0.1%
Census Tract 9704 −5.3% −1.7% 3.8% 8.9% −19.6% FALSE FALSE 34.3% 20.7% 0.1% 2.8% −0.3% 0.5% −16.8% 0.1% 9.7%
Census Tract 9705 7.4% 8.2% −2.4% −8.3% 26.2% TRUE FALSE −28.8% −43.1% −0.4% 28.6% 0.5% 2.3% −27.3% −0.0% −0.0%
Census Tract 9707 3.7% −1.7% −18.3% 18.6% 56.1% FALSE FALSE −34.9% −26.2% 0.0% 100.0% −2.3% 0.0% 0.0% 0.0% 0.0%
Census Tract 9708 −17.3% 5.3% 1.5% 171.4% 1,000,000.0% FALSE FALSE 217.1% −100.0% −0.3% 66.7% 0.0% 0.0% 0.0% 0.0% 0.0%
Census Tract 9709 −6.6% −3.0% −12.2% −8.8% −8.9% TRUE FALSE −25.2% −30.0% −0.5% 68.7% −0.5% 1.6% −13.6% 0.0% 0.0%
Census Tract 9710 −2.0% −4.0% −5.7% 29.2% −20.1% FALSE FALSE 3.4% 122.5% 0.3% 32.7% −0.2% 0.2% −18.0% −0.2% 18.7%
Census Tract 9711 −1.5% 4.0% 12.5% 29.9% 10.8% FALSE FALSE −9.8% −35.2% 0.8% 248.6% −0.1% 0.0% −2.6% −0.0% −0.9%
Census Tract 9712 7.1% 7.6% 0.2% 31.8% 84.0% FALSE FALSE 38.9% 44.2% 2.8% 32.8% −0.2% −0.2% 10.7% 0.0% −4.8%
Delaware Census Tract 9703 4.7% 5.7% 8.4% 4.6% 15.1% FALSE FALSE −40.9% −81.4% 0.2% 20.0% 0.0% 0.4% −27.1% −0.1% 1.4%
Census Tract 9709 171.4% 0.0% 2.6% 0.0% 0.0% FALSE FALSE −37.3% 0.0% −0.9% 25.0% −0.0% 0.3% 50.0% 0.0% 0.0%
Census Tract 9701 −8.9% 0.3% 4.3% 10.8% 12.7% FALSE FALSE −54.4% −10.6% −0.4% 37.6% −0.1% 0.0% −6.4% −0.1% −0.1%
Census Tract 9714 −7.3% −6.0% 4.3% 22.4% −12.6% FALSE FALSE 7.7% 56.4% −0.6% 8.5% 0.4% −0.1% 11.3% −0.2% 0.4%
Census Tract 9702 −0.3% 0.2% 8.0% 18.9% 25.3% FALSE FALSE −17.6% 96.8% 0.1% 83.3% −0.0% 0.1% −9.1% 0.0% −1.6%
Census Tract 9704 −7.4% −9.8% 9.0% 24.1% 56.5% FALSE FALSE −37.3% 24.0% −0.3% 72.6% −0.2% 0.4% −30.8% 0.5% −0.9%
Census Tract 9705 −14.1% −11.7% 23.0% 23.2% −3.0% FALSE FALSE −29.7% 48.7% −0.5% 1.7% −0.1% 0.2% −13.6% 0.1% −0.8%
Census Tract 9706 −7.6% −1.9% 2.2% 2.8% 33.4% TRUE FALSE −54.3% −8.0% −0.2% 11.6% −0.0% −0.1% 10.7% −0.0% −0.9%
Census Tract 9707 0.4% −1.8% 6.5% 23.7% 9.2% FALSE FALSE −30.9% −36.1% −0.2% 5.4% −0.0% 0.1% −9.4% −0.2% −0.8%
Census Tract 9708 −27.2% −26.6% 3.1% 38.0% 31.2% FALSE FALSE −46.2% 3.8% −0.7% 41.3% −0.1% 0.1% −7.3% −0.0% −0.0%
Census Tract 9710 0.9% 7.6% 16.7% 28.7% 59.1% FALSE FALSE −15.9% 56.9% −0.2% 38.8% −0.2% 0.1% −11.8% 0.0% 0.7%
Census Tract 9711 −13.0% −4.0% 7.7% 10.5% 89.8% FALSE FALSE −62.6% 39.4% −0.3% 4.0% 0.0% 0.0% −10.7% 0.0% −4.9%
Census Tract 9712 −8.5% 4.4% 26.9% 32.9% −3.9% FALSE FALSE −5.4% 160.2% −0.5% 14.6% 0.2% 0.0% −13.4% −0.2% −12.0%
Census Tract 9713 −12.7% −17.0% 8.8% 62.0% 42.6% FALSE FALSE −87.9% 25.3% −0.3% 3.6% 0.4% −0.1% 6.1% −0.4% −0.1%
Broome Census Tract 2 −7.1% −4.9% −3.4% 34.6% 11.7% TRUE FALSE −24.7% 58.5% −0.2% 75.0% −0.0% −0.0% −0.0% 0.0% 0.0%
Census Tract 18 −10.6% −19.0% 4.9% 8.0% −29.6% FALSE FALSE −74.1% −26.2% −0.1% 20.0% 0.0% 0.1% 0.0% 0.0% 0.0%
Census Tract 122.01 −2.6% 1.1% −4.8% 7.9% 7.8% FALSE FALSE −34.0% −100.0% −0.6% 4.2% −0.1% −0.3% 76.9% −0.2% 1.8%
Census Tract 122.02 −7.2% −7.7% −4.4% 20.5% 84.9% FALSE FALSE 11.0% −54.9% −0.3% −7.9% −0.1% 0.2% −10.3% −0.1% 1.5%
Census Tract 125 −4.9% 4.0% 4.5% 60.0% −17.0% FALSE FALSE −12.7% 88.5% −0.4% 46.0% −0.0% −0.1% −16.0% −0.3% 0.1%
Census Tract 131 −1.4% 1.6% 6.9% 14.3% 13.4% FALSE FALSE −22.0% 64.0% −0.4% 70.0% 4.5% 0.0% 0.0% 0.0% 0.0%
Census Tract 134 0.4% −5.9% −16.0% −0.6% −4.2% FALSE FALSE 13.1% 0.9% 0.3% 1,150.0% 5.0% −11.9% −0.0% 0.0% 0.0%
Census Tract 138 −13.2% −6.3% 5.8% 5.7% −53.7% FALSE FALSE −10.0% 3.6% 0.1% −20.0% 0.0% 0.0% 0.0% 0.0% 0.0%
Census Tract 143.02 −3.5% 20.7% 0.0% 14.6% 322.1% FALSE FALSE 37.6% 1,000,000.0% −1.1% 162.5% 1.8% −0.1% 60.0% −3.4% 7.5%
Census Tract 145 −3.9% −2.9% 10.7% −6.0% 85.1% FALSE FALSE −39.0% −29.4% 0.2% 200.0% −0.1% −0.1% 14.3% −0.2% −0.2%
Census Tract 146 5.1% −0.1% 6.6% 14.8% 113.0% FALSE FALSE −63.2% −11.1% 1.0% 26.7% 0.0% −0.0% −10.9% −0.2% −22.0%
Census Tract 1 17.6% 0.8% 6.8% 64.2% 3.8% FALSE TRUE 469.0% 40.4% 0.0% −0.0% 0.0% 0.0% 0.0% 0.0% 0.0%
Census Tract 3 −22.3% −14.5% −37.3% 87.0% 30.3% FALSE FALSE −28.6% −18.8% −2.4% 2,466.7% −0.8% −0.5% 1,000,000.0% −0.0% −17.5%
Census Tract 4 −21.4% −25.5% 7.6% 77.1% 56.5% TRUE FALSE 24.5% 50.8% −2.1% 800.0% 100.0% 12.5% −0.0% −0.0% −1.2%
Census Tract 5 11.0% −2.4% −20.1% −2.4% 26.6% FALSE FALSE −8.0% 1.5% −0.3% 107.1% 25.0% 0.0% 0.0% 0.0% −6.0%
Census Tract 6 −4.5% −4.4% 4.6% 35.8% −11.7% FALSE FALSE −1.5% 35.2% −1.0% 14.2% 50.0% 0.0% 0.0% −0.0% −0.0%
Census Tract 7 −8.7% −10.1% −3.8% 16.4% 173.7% TRUE FALSE 8.8% −63.1% 0.1% 60.0% −1.2% −0.1% −50.0% 0.0% 0.0%
Census Tract 9 12.4% 8.8% −1.4% 4.9% −27.3% FALSE FALSE −26.0% 25.6% −0.1% 25.0% 0.0% 0.0% 0.0% 0.0% −0.2%
Census Tract 12 1.7% −9.8% −41.8% 13.0% −7.0% FALSE FALSE −8.5% −4.9% 0.0% 6.2% −100.0% 0.0% 0.0% 0.0% 0.0%
Census Tract 13 5.5% 0.7% 3.5% −16.5% 1.4% FALSE FALSE 75.4% 16.2% −0.0% 0.0% 0.0% 0.0% 0.0% 0.0% 0.0%
Census Tract 14 1.2% −3.4% −0.3% 47.0% 62.9% FALSE FALSE 197.6% −13.8% −0.1% 100.0% 0.0% −0.0% 0.0% −1.2% −0.0%
Census Tract 15 −11.2% −4.3% −6.2% 67.3% −63.4% FALSE FALSE 12.3% 15.5% −0.0% 300.0% −5.3% 0.0% 0.0% 0.0% 0.0%
Census Tract 16 −15.6% −21.5% 6.9% 27.3% 2.3% FALSE FALSE −64.8% −39.5% −0.2% 0.0% 2.2% 0.1% 50.0% 0.0% 0.0%
Census Tract 17 −4.2% 2.8% −11.8% −18.5% 33.4% TRUE FALSE 76.1% −8.3% −0.0% 25.0% 0.0% 0.0% 0.0% 0.0% 0.0%
Census Tract 102 −4.4% 1.3% 17.0% 33.1% −23.0% FALSE FALSE −37.3% 102.3% −0.2% 16.0% −0.1% −0.0% 7.0% −0.0% −1.0%
Census Tract 119.01 −4.6% 1.3% 2.3% 6.8% −6.6% FALSE FALSE 109.5% 10.5% 0.0% 22.6% −0.1% 0.2% −20.5% −0.1% 0.2%
Census Tract 119.02 −3.5% 5.5% 20.6% 35.2% −5.9% FALSE FALSE −53.1% 205.5% 0.4% 1.1% −0.1% −0.2% 36.0% 0.0% 0.6%
Census Tract 119.03 −3.0% 1.7% −0.5% 9.4% −57.2% FALSE FALSE −37.7% 72.8% 0.9% 15.0% −0.1% −0.1% 12.7% 1.4% −8.6%
Census Tract 120 3.9% 9.8% 5.6% 22.6% −36.8% FALSE FALSE −31.6% 23.2% 0.6% 13.5% −0.1% −0.0% −3.7% 0.0% 0.0%
Census Tract 121.01 −6.0% −12.1% 3.4% 17.4% 204.3% FALSE FALSE −40.0% 24.2% 0.2% −2.3% 0.3% −0.1% −0.4% 0.4% 0.4%
Census Tract 121.02 −3.7% −6.6% 5.6% 19.0% −21.6% FALSE FALSE −38.7% 60.6% 0.1% 22.7% −0.9% 0.4% −15.7% −0.2% −1.1%
Census Tract 121.03 −5.8% −10.6% 6.3% 12.8% 360.8% FALSE FALSE −19.9% −26.5% 0.1% 78.8% −0.1% −0.1% 11.9% −1.5% 6.2%
Census Tract 123 −4.1% 18.0% 20.1% 28.0% −24.7% FALSE FALSE −20.5% 1.8% 0.2% 25.9% −0.2% 0.1% −8.0% 0.0% −0.7%
Census Tract 124 −9.7% −0.2% 24.9% 33.4% −16.4% FALSE FALSE −54.0% −51.1% −0.4% 38.6% −0.1% 0.2% −25.6% −0.2% −0.1%
Census Tract 126 −3.8% −2.2% 6.4% 14.9% 23.7% FALSE FALSE 78.2% 250.3% −0.1% 7.0% 0.0% −0.0% −2.8% 0.0% −0.2%
Census Tract 127.01 −6.3% −3.2% 6.0% 2.5% −36.7% FALSE FALSE 28.2% −12.0% 1.2% 80.2% −0.7% −0.8% 24.7% 0.0% 0.4%
Census Tract 127.02 −3.5% 3.8% 11.6% 11.0% −53.0% FALSE FALSE −43.2% −31.4% −0.1% 15.1% 0.0% 0.0% −12.1% 0.0% 0.0%
Census Tract 128 −4.2% 26.5% 3.7% 22.1% −31.9% FALSE FALSE −19.5% −8.1% −0.0% 8.8% 0.1% −0.2% 13.8% 0.0% −0.4%
Census Tract 129 49.2% 58.3% 45.6% 12.2% −84.0% FALSE FALSE −38.1% 187.8% 2.4% −22.6% −0.4% 0.0% 6.8% 1.3% −3.3%
Census Tract 130 −4.8% −12.1% 6.8% 14.3% 161.4% FALSE FALSE −47.6% −84.8% −1.0% 25.8% 6.9% −1.2% 129.4% −0.2% 0.0%
Census Tract 132.01 7.5% 3.5% −4.5% 31.3% 184.4% FALSE FALSE 32.1% −70.1% −0.1% 1,000,000.0% 0.0% 0.0% 0.0% 0.0% 0.0%
Census Tract 132.02 −12.2% −3.7% 13.5% 21.8% −26.6% FALSE FALSE 135.1% 8.8% −0.5% 1,000,000.0% 1.2% −0.0% −0.0% −0.0% 0.0%
Census Tract 133.01 4.1% −5.3% −1.9% 8.8% 17.9% FALSE FALSE −4.2% −55.8% 1.6% 123.5% −6.9% −0.4% −8.7% 0.0% 0.0%
Census Tract 133.03 −6.5% 7.4% 14.4% −4.9% 17.6% FALSE FALSE −72.6% 81.5% −0.0% −4.8% −0.0% 0.0% −4.1% −0.1% 7.0%
Census Tract 133.04 −18.2% −4.8% 15.1% 57.9% −16.9% FALSE FALSE −7.2% 166.2% −0.0% 180.0% −0.1% −0.2% 8.9% 0.0% −4.5%
Census Tract 135 −4.7% −36.6% −12.0% 21.3% 102.9% TRUE FALSE −35.7% 7.4% −0.3% 1,000,000.0% 1,000,000.0% 0.0% 0.0% 0.0% 0.0%
Census Tract 136 −3.0% −13.0% 13.2% 54.0% 26.1% FALSE FALSE −10.8% 2.8% −0.2% 1,000,000.0% 0.0% 0.0% 1,000,000.0% 0.0% 0.0%
Census Tract 137 −10.2% 2.9% 11.1% 44.8% −23.8% FALSE FALSE 1.7% 51.7% −0.4% 52.6% 0.0% 0.8% −4.6% 0.0% 0.0%
Census Tract 139 18.2% 4.4% −8.0% 25.5% −1.9% FALSE FALSE −28.0% −24.6% −0.7% 100.0% 100.0% 0.0% 0.0% 0.0% 0.0%
Census Tract 140 −9.1% −16.8% −14.0% 23.5% −23.1% FALSE FALSE −45.9% 54.0% −0.4% 1,000,000.0% 0.0% 0.0% 0.0% 0.0% 0.0%
Census Tract 141 −0.1% −8.9% −9.4% 21.0% 54.8% FALSE FALSE 31.2% −4.7% −0.3% 200.0% 1.4% 0.0% 100.0% 0.0% 0.0%
Census Tract 142 −15.3% −8.6% 0.2% 19.6% 70.9% FALSE FALSE −67.0% −54.7% 0.2% 9.6% −1.0% −0.6% 100.0% 0.0% 0.0%
Census Tract 143.01 3.7% −1.4% −4.6% −1.0% 89.6% FALSE FALSE −59.3% 82.7% −0.3% 3.4% 0.6% −0.1% 8.6% −0.4% −1.0%
Census Tract 144 7.3% 5.5% −8.1% −3.9% 353.7% FALSE FALSE −25.3% −47.6% 0.1% 34.8% 0.0% −0.3% 6.3% 0.0% 0.0%
Census Tract 11 13.7% −10.6% −24.9% −49.0% 31.4% FALSE FALSE 169.2% 17.4% −0.5% 29.4% 0.0% 0.0% 1,000,000.0% −0.0% −0.7%
Cattaraugus Census Tract 9402 0.0% 0.0% 0.0% 0.0% 0.0% FALSE FALSE 0.0% 0.0% 17.9% 1,000,000.0% −6.0% 0.1% 0.0% 0.0% −0.0%
Census Tract 9603 −0.1% 0.0% 10.8% 38.8% −11.6% FALSE FALSE −60.4% −39.7% 0.9% 6.6% −0.0% −0.1% 8.4% 0.0% −2.5%
Census Tract 9612 −8.0% −5.1% −0.2% 13.5% 2.1% FALSE FALSE −14.2% 50.5% 0.3% 16.0% 0.7% −0.1% 4.8% −2.3% −4.7%
Census Tract 9400 −8.4% 2.0% 17.1% 0.0% −55.0% FALSE FALSE 52.2% −100.0% 1.5% −17.5% 1.6% 0.1% 27.7% 0.2% −2.0%
Census Tract 9403 −10.5% −14.5% −8.0% 2.2% 26.4% TRUE FALSE −32.9% 29.7% −0.5% 0.1% 1.4% −0.1% 15.5% −0.1% −0.0%
Census Tract 9601 −7.0% −6.0% 7.4% 19.5% −2.4% FALSE FALSE −67.9% 368.6% 4.2% 32.8% 0.1% −0.4% 1.2% 0.0% −0.7%
Census Tract 9602 −12.7% −10.8% 22.2% 15.8% −3.3% FALSE FALSE 12.0% 98.2% −0.2% 51.9% 0.1% −0.5% 26.2% −0.2% −0.9%
Census Tract 9604 −6.2% −3.9% −0.7% 32.3% 21.4% FALSE FALSE −48.8% 158.6% −0.0% 3.7% 0.0% 0.0% −11.0% −0.1% −1.2%
Census Tract 9605 −5.9% 7.6% −2.0% 1.9% −6.8% TRUE FALSE −1.5% 28.4% 2.3% 24.6% −0.0% −0.3% 11.1% 0.0% 4.1%
Census Tract 9606 2.7% 0.8% 0.2% 24.8% −8.2% FALSE FALSE −35.1% 2.4% −0.3% 46.7% 0.0% −0.3% 10.0% −0.1% 8.7%
Census Tract 9607.02 −14.8% −14.0% 8.1% 25.8% 28.3% FALSE FALSE −36.5% −44.9% −0.4% 11.2% 0.1% −0.3% 19.5% −0.2% 16.3%
Census Tract 9608 −2.8% −2.4% 4.8% 17.4% −6.6% FALSE FALSE −10.4% −19.8% −0.3% 2.4% −0.0% −0.0% 5.2% 0.1% −7.4%
Census Tract 9610 4.5% 4.1% 5.5% −0.5% 93.8% FALSE FALSE 84.8% 203.4% −0.4% 30.5% 0.3% −0.1% −2.9% 0.2% 13.0%
Census Tract 9611 9.4% 11.5% 3.5% 23.2% −5.0% FALSE FALSE −59.0% −53.2% −0.6% 54.7% −0.6% −0.0% 0.5% 1.5% 3.2%
Census Tract 9613 −9.1% 13.4% 48.9% 11.1% −9.1% FALSE FALSE −55.0% −29.9% −0.5% 12.5% −0.1% −0.1% 8.2% −0.0% 0.2%
Census Tract 9614 −2.8% 0.3% −1.7% 29.9% −6.5% FALSE FALSE −50.1% 116.4% 0.7% 22.5% −0.0% −0.4% 12.8% 0.1% 0.5%
Census Tract 9615 −5.1% −11.9% −8.9% 3.8% 114.8% FALSE FALSE −52.9% 12.0% −0.8% 150.0% 3.8% −0.2% −24.2% 0.0% 0.0%
Census Tract 9616 −11.3% −6.3% 6.1% 34.5% −0.2% FALSE FALSE −47.0% 81.9% −0.8% 244.4% 2.5% −1.7% −10.0% 0.0% 3.5%
Census Tract 9617 −6.7% −1.0% −4.6% 9.5% 32.2% FALSE FALSE −42.0% 20.6% −3.8% 587.5% 85.4% 0.0% −68.0% −0.0% −0.0%
Census Tract 9618 −1.7% −10.9% 3.6% 37.2% −28.3% FALSE FALSE −26.6% 35.4% −0.1% 57.9% −0.2% −0.4% 4.5% 0.0% 0.0%
Census Tract 9622 11.5% 4.8% 2.9% 23.9% 16.3% FALSE FALSE −52.5% 17.6% 0.3% 32.0% −0.1% −0.2% 15.1% −0.6% 9.9%
Chautauqua Census Tract 306 −8.9% −6.5% 1.9% 20.1% −27.5% FALSE FALSE −49.1% −10.8% −0.5% 24.3% 0.9% 0.1% 33.3% 0.0% 0.0%
Census Tract 354 −2.9% −7.6% −3.1% 45.9% −20.1% FALSE TRUE −0.9% −19.8% −0.0% −3.2% 0.0% 1.1% 0.0% 0.0% 0.0%
Census Tract 359.02 −18.1% −9.6% 1.5% −46.4% 118.4% FALSE FALSE −32.3% −32.7% 0.4% 0.0% −6.3% 0.0% 0.0% 0.0% 0.0%
Census Tract 367 −5.2% −3.6% −2.4% 45.4% −42.6% FALSE FALSE −56.9% 21.0% 0.9% 78.6% −0.0% 0.0% −1.2% −0.0% −1.2%
Census Tract 370 −3.8% 7.4% 10.1% 46.8% 69.4% FALSE FALSE −38.6% −100.0% −0.4% −5.6% 0.1% −0.1% 1.3% 0.0% 12.8%
Census Tract 301 −3.6% 0.5% 24.9% 39.6% 5.2% FALSE FALSE −54.5% 9.2% −0.5% 7.5% 5.0% 0.5% 200.0% 0.0% 0.0%
Census Tract 302 4.8% −3.4% −8.9% 19.7% 117.6% TRUE FALSE −53.1% 32.1% 0.0% 0.0% 0.0% −0.6% 25.0% 0.0% 0.0%
Census Tract 303 −7.6% −3.9% −10.5% −9.0% 43.1% FALSE FALSE −70.7% −18.7% −1.5% 13.1% 0.2% 0.2% 40.0% 0.0% 0.0%
Census Tract 304 −4.5% −7.8% −1.0% 0.3% 2.6% FALSE FALSE −44.7% −54.5% −0.3% 4.0% 9.3% 0.0% −0.0% −0.0% −0.0%
Census Tract 305 9.8% 2.7% −12.7% −10.7% 3.0% FALSE FALSE −41.7% 9.4% −1.7% 42.9% 150.0% 0.0% 0.0% 0.0% 0.0%
Census Tract 307 −17.4% −3.8% 17.1% 27.4% −3.9% FALSE FALSE −65.5% −12.9% −0.4% 40.0% 0.3% 0.2% 0.0% −0.0% 0.0%
Census Tract 308 −12.1% −18.1% 4.0% −0.4% 178.3% TRUE FALSE 166.1% 83.2% −0.0% 100.0% −0.5% −0.7% 28.6% 0.0% 0.0%
Census Tract 351 −12.4% −8.4% 0.9% 23.1% −13.7% FALSE FALSE −25.7% −7.8% −0.4% −1.4% 0.1% −0.0% 2.2% 0.0% 3.3%
Census Tract 353 −3.6% −1.0% 6.9% 14.5% −34.9% FALSE FALSE −38.3% 96.4% −0.9% −17.6% 0.3% 0.0% −6.5% −0.0% 2.0%
Census Tract 355 −9.5% −2.5% 20.3% 13.4% −17.0% TRUE FALSE 112.3% 31.6% −1.2% −8.2% 30.1% 3.2% 58.3% −0.0% 27.3%
Census Tract 356 −5.7% −7.6% 2.1% −4.1% 87.8% TRUE FALSE −66.7% 53.7% −0.6% 9.7% 11.3% −1.6% 22.7% 0.0% −0.5%
Census Tract 357 −5.6% −9.5% 25.4% 25.4% −13.0% FALSE FALSE 116.7% −47.1% −0.1% −25.0% 1.5% −0.6% −0.0% −0.0% 0.0%
Census Tract 358 −9.1% −20.4% −10.0% 30.3% −29.5% FALSE FALSE 58.3% −61.5% −0.4% 62.5% 0.4% 1.0% −28.9% 0.0% 0.0%
Census Tract 359.01 −0.4% −7.3% 15.3% 19.6% 77.6% FALSE FALSE 171.1% 91.3% 0.0% 40.0% −0.1% 0.5% −18.6% −0.0% 0.0%
Census Tract 360 −2.6% 7.5% 6.3% 29.9% −13.2% FALSE FALSE −35.0% 92.3% 0.4% 2.7% 0.0% 0.0% −5.6% 0.0% 0.3%
Census Tract 361 −11.1% −5.8% 14.9% 31.3% 17.8% FALSE FALSE −25.9% 36.6% 1.1% 259.4% 0.3% −0.5% 11.1% −0.1% 6.8%
Census Tract 363 −5.6% −7.8% −6.0% 0.0% 58.5% FALSE FALSE 39.1% −22.8% −0.3% −30.2% 0.6% −0.3% −0.9% −0.1% −0.6%
Census Tract 364.01 −7.8% −11.7% 0.5% 19.7% 45.8% FALSE FALSE −9.5% 135.0% −0.4% 26.6% 0.2% 0.0% −1.8% −0.0% −0.0%
Census Tract 364.02 4.9% 13.2% 1.9% 30.3% −6.7% FALSE FALSE −54.8% 56.5% 0.2% 70.6% −0.1% −0.1% 1.3% −0.0% 0.4%
Census Tract 365 −6.1% −8.3% 5.9% 51.7% −1.1% FALSE FALSE −3.0% 179.2% 0.2% 64.2% 0.0% −0.1% 0.0% −0.1% −0.2%
Census Tract 366 6.3% 10.5% 2.3% 21.9% −3.7% FALSE FALSE 17.0% 71.3% −0.4% 84.8% 0.2% −0.3% 3.7% 0.0% −0.0%
Census Tract 368 2.8% −2.4% 1.1% 44.1% 64.0% FALSE FALSE −24.5% 28.9% 0.0% 5.5% 0.3% −0.2% 1.5% −0.1% 0.1%
Census Tract 369.01 −8.4% −5.9% 16.7% 51.7% −65.7% FALSE FALSE −73.6% 49.2% 1.1% 48.9% 0.0% −0.5% 6.5% −0.1% 4.8%
Census Tract 369.02 −12.7% 13.8% −0.6% 19.2% 176.6% FALSE FALSE −24.7% −39.2% 0.1% 29.5% 0.1% 0.2% −7.1% 0.0% −0.0%
Census Tract 371 0.1% 1.5% 7.8% 40.1% −27.9% FALSE FALSE −67.7% −4.4% −0.1% 123.7% −0.2% −0.7% −11.1% −0.1% −0.0%
Census Tract 372 8.4% 7.1% 15.7% 18.4% −51.9% FALSE FALSE −13.1% 0.9% 0.2% 44.1% 0.3% −2.1% 46.2% 0.0% 0.0%
Census Tract 373 −12.0% −11.1% −2.8% 27.7% −8.8% FALSE FALSE −44.1% 80.6% −0.9% 37.4% 0.1% −0.2% 8.4% −0.0% 5.6%
Census Tract 374 −0.7% −4.6% −2.8% 20.6% −26.3% FALSE FALSE −49.3% 217.2% 0.0% 27.2% 0.0% −0.1% 2.8% 0.0% 1.2%
Census Tract 375 −6.4% 4.5% 24.3% 22.1% 130.7% FALSE FALSE 37.2% −6.6% −0.4% 54.0% −0.1% −0.1% 6.8% 0.0% 0.4%
Census Tract 376 2.9% 8.0% 10.2% 40.4% −33.5% FALSE FALSE −11.8% 66.7% 0.4% −21.4% 0.1% 0.0% −4.6% −0.1% −3.8%
Chemung Census Tract 1 −4.9% −17.7% 10.8% 19.6% 10.9% FALSE FALSE −3.1% 44.0% −1.1% 61.5% 9.3% −0.9% 57.1% 0.0% −3.5%
Census Tract 6 −18.9% −24.8% −5.8% 1.5% −15.3% FALSE FALSE 12.7% 12.7% −0.5% 600.0% 33.3% 0.0% 0.0% −0.0% −0.0%
Census Tract 104 1.5% 3.1% 1.6% 20.2% 319.5% FALSE FALSE −27.8% 218.0% −0.5% 1,000,000.0% 8.3% −2.5% 150.0% 0.0% 0.0%
Census Tract 109 −4.4% −0.6% 9.5% 3.4% −41.6% FALSE FALSE 106.5% −62.1% −0.4% −20.0% 1.6% 0.3% −66.0% 0.3% −0.5%
Census Tract 2 −2.0% −16.8% −12.1% 17.7% −20.0% FALSE FALSE −39.3% −3.8% −0.2% 100.0% 33.3% 0.0% 100.0% 0.0% 0.0%
Census Tract 3 21.1% 0.0% 10.1% 0.0% 0.0% FALSE FALSE 0.0% 0.0% −3.4% 250.0% −0.0% 0.0% 0.0% 0.0% 0.0%
Census Tract 5 −12.2% −4.1% −11.8% 38.1% 35.8% FALSE FALSE −53.4% −10.9% 0.0% 0.0% 0.0% 0.0% 0.0% 0.0% 0.0%
Census Tract 7 −16.6% −13.6% −3.8% 242.1% 3.0% FALSE FALSE −36.6% 3.1% −0.7% 38.5% 32.2% −6.7% 150.0% 0.0% −0.9%
Census Tract 9 −12.1% −10.8% 1.9% 23.3% −7.6% FALSE FALSE −29.8% 39.3% −0.3% 25.0% 7.8% −75.0% 100.0% −0.0% −2.2%
Census Tract 10 −0.9% −7.4% −11.8% 7.7% 39.3% TRUE FALSE −65.6% 36.3% −0.1% 50.0% 7.1% 0.0% 0.0% 0.0% −1.2%
Census Tract 11 −5.6% −10.2% −4.5% 42.9% −14.4% FALSE FALSE −40.2% −29.8% −0.2% −0.0% 10.8% −0.0% 0.0% −0.0% −0.0%
Census Tract 101 6.6% 8.8% 4.3% 29.2% −39.9% FALSE FALSE −36.3% −4.1% −0.4% 6.6% 0.2% 0.0% −13.4% −0.3% 1.7%
Census Tract 102 5.4% −2.5% 1.3% 35.1% −4.3% FALSE FALSE −28.9% 9.5% 0.4% 57.1% 0.0% −0.1% 19.2% 0.0% −5.5%
Census Tract 103 0.6% 2.4% −0.2% 21.4% −24.7% FALSE FALSE −5.9% −79.6% −0.1% 8.0% 0.2% −0.1% 28.6% 0.0% −0.8%
Census Tract 105 −2.6% −1.1% −13.6% 4.1% 5.2% FALSE FALSE −57.6% −3.2% 2.1% −5.2% −14.4% 2.1% −35.9% −0.8% −50.0%
Census Tract 106 −8.6% 0.2% 24.3% 69.1% −66.2% FALSE FALSE −50.8% −48.8% −0.2% 14.3% 0.2% 0.0% 66.7% 0.0% 0.0%
Census Tract 107 1.4% 0.7% 7.3% 17.4% −23.4% FALSE FALSE −18.0% 144.4% 0.3% 1.1% −0.2% −0.0% −20.7% −0.1% 8.5%
Census Tract 108 −0.9% −1.7% 13.3% 30.2% −18.6% FALSE FALSE −9.1% 44.3% −0.6% 64.3% 4.6% 3.8% 160.0% −1.6% −100.0%
Census Tract 110 −15.9% −9.3% 19.9% 21.7% −31.7% FALSE FALSE 2.0% −13.2% −0.8% 2.0% 0.3% −0.1% 34.0% −0.0% −0.1%
Census Tract 111 −7.9% −1.1% 7.8% 49.7% −3.9% FALSE FALSE −68.3% −15.2% −0.8% 77.8% 1.9% −0.8% 21.4% 0.0% 1.5%
Census Tract 112 −13.4% −1.7% 14.8% 14.7% 144.9% FALSE FALSE 1.6% −24.4% −1.0% −8.4% 0.3% −0.0% −6.4% 0.6% −5.5%
Census Tract 4 14.3% 0.1% 2.8% −21.6% 129.2% FALSE FALSE 1,074.5% 82.2% −0.4% 23.5% 1.7% 0.0% 0.0% 0.0% 0.0%
Chenango Census Tract 9704 −11.9% −12.6% 13.2% −2.5% 57.1% TRUE FALSE −31.7% 50.2% −0.0% 0.0% 0.3% 0.0% −0.0% 0.0% 0.0%
Census Tract 9701 −2.2% 5.6% 4.2% 3.3% 36.0% FALSE FALSE −48.5% 21.6% 0.8% 18.7% −0.1% 0.8% −9.4% −0.1% 5.1%
Census Tract 9702 −8.6% 5.3% 12.4% 0.3% 7.6% FALSE FALSE −9.8% 124.1% 1.0% 65.0% −0.0% 0.1% −5.3% −0.0% 1.5%
Census Tract 9703 −4.5% 7.2% −16.1% 45.0% −9.4% FALSE FALSE −27.8% 63.7% 0.1% −5.0% −0.2% 0.4% −9.1% 0.0% 0.0%
Census Tract 9705 −5.1% 2.0% 6.6% 16.1% −18.0% FALSE FALSE 10.2% 133.1% 0.1% 30.1% −0.1% 0.4% −10.9% −0.0% 1.8%
Census Tract 9706.01 −4.5% 3.7% −1.2% 44.3% −38.9% FALSE FALSE −61.3% 26.4% 0.3% 9.1% −0.2% 0.1% −6.2% −0.1% 5.0%
Census Tract 9706.02 −10.0% 21.3% 2.2% 21.0% −3.9% FALSE FALSE −68.8% 88.4% 0.4% 25.8% −0.1% 0.1% −11.0% −0.0% 0.7%
Census Tract 9707 −0.8% 23.0% 12.3% 10.0% 96.7% FALSE FALSE 41.6% 241.9% −0.1% 194.1% 0.0% −0.1% 8.8% 0.2% −0.2%
Census Tract 9708.01 −6.2% 3.6% 7.3% 15.9% −44.3% FALSE FALSE −36.0% 54.5% 0.2% 13.5% −0.1% 0.0% −2.9% −0.2% 0.4%
Census Tract 9708.02 −4.4% 0.7% 0.2% 35.2% 2.8% FALSE FALSE −15.7% −10.4% 0.4% 35.6% −0.1% −0.0% −2.2% 0.0% 0.2%
Census Tract 9709 −8.1% −8.0% 11.4% 25.1% −8.8% FALSE FALSE −17.3% −38.2% 0.2% 6.4% −0.1% 0.2% −16.3% −0.1% 0.3%
Census Tract 9710 −7.4% 5.0% 17.2% 37.7% −12.1% FALSE FALSE 22.5% 66.6% 0.5% 5.3% −0.1% 0.1% −8.0% −0.2% 0.4%
Otsego Census Tract 5905 −4.8% −6.0% 0.7% 45.8% −14.6% FALSE FALSE −44.5% 56.3% 0.2% 9.3% −0.2% 0.7% −36.6% 0.3% −1.5%
Census Tract 5909 2.4% −2.0% 14.2% 12.3% −28.9% FALSE FALSE −9.4% −31.8% −1.1% 12.5% 1.6% 0.4% 266.7% 0.0% −2.6%
Census Tract 5910 4.6% −12.0% −11.5% 8.8% −14.0% FALSE FALSE −5.1% −44.6% −0.6% 1,900.0% 21.4% −6.9% 100.0% −0.0% 0.0%
Census Tract 5913 18.9% −10.0% −1.0% 0.0% 0.0% FALSE FALSE 34.1% 0.0% −3.2% 400.0% −33.3% 2.3% 0.0% 0.0% 0.0%
Census Tract 5901 0.2% −0.9% 6.3% 16.7% 0.3% FALSE FALSE 10.8% −10.9% 0.4% 44.5% −0.1% 0.1% −3.2% −0.1% 0.7%
Census Tract 5902.01 −13.8% −3.7% −0.7% 21.0% −13.0% FALSE FALSE −35.0% 31.0% 0.2% 63.8% −0.2% 0.4% −18.7% −0.1% 4.5%
Census Tract 5902.02 −4.5% −4.0% 3.2% 44.5% −53.2% FALSE FALSE 22.2% 29.6% −0.2% 51.5% −0.2% 0.6% −10.8% 0.0% −0.2%
Census Tract 5903 −5.6% −2.6% −0.4% 30.5% 29.0% FALSE FALSE −43.1% −3.8% 0.1% 89.8% −0.1% 1.0% −12.5% 0.0% −0.3%
Census Tract 5904 −8.6% −9.5% 4.9% 16.6% −1.5% FALSE FALSE −31.9% 15.1% 0.2% 61.6% −0.1% 0.4% −11.5% −0.0% −1.6%
Census Tract 5906 −9.2% −15.7% −0.2% 13.0% 11.1% FALSE FALSE −42.3% −22.9% 0.1% 166.7% −0.2% 0.1% −2.2% 0.1% −4.6%
Census Tract 5907 −7.9% −6.1% 13.9% 26.4% −5.7% FALSE FALSE −33.8% −66.9% −0.3% 59.1% 0.0% 0.1% −5.9% 0.0% −1.2%
Census Tract 5908 −3.9% −10.2% 18.5% 25.0% 27.2% FALSE FALSE −22.4% 0.6% −0.5% 35.6% 0.2% −0.0% −9.6% 0.1% −1.6%
Census Tract 5911 −12.8% −8.3% 32.0% 27.3% −0.1% FALSE FALSE −63.2% 38.7% −1.3% 56.3% −2.8% 2.2% 19.4% 0.0% 1.6%
Census Tract 5912 −17.0% 1.0% 0.0% 102.2% −42.5% FALSE FALSE −72.6% −63.6% −0.5% 375.0% 0.6% −0.6% −40.0% 0.0% 0.0%
Census Tract 5914 −2.3% −7.0% 5.5% 9.9% −3.0% FALSE FALSE −57.6% 51.0% −0.3% 50.0% −0.0% 0.1% −14.0% 0.1% −1.7%
Census Tract 5915 −0.2% 0.5% 7.6% 29.5% 2.9% FALSE FALSE 16.7% 35.4% 0.1% 64.2% −0.1% 0.2% −13.5% −0.0% 0.1%
Census Tract 5916 −6.4% −5.5% 18.8% 4.2% 84.9% FALSE FALSE −55.9% 65.4% −0.1% 8.4% −0.1% 0.2% −23.2% 0.0% −0.7%
Tioga Census Tract 204.02 −16.1% −10.0% 6.3% 23.9% −60.4% FALSE FALSE −90.1% 11.1% −0.1% 120.0% −0.1% −0.1% 20.0% 0.0% −0.2%
Census Tract 207.03 −7.9% −13.1% −6.2% 6.1% 7.2% FALSE FALSE 37.8% −24.7% −0.2% 40.5% −4.9% −12.3% −18.7% 0.0% 233.3%
Census Tract 201 −8.4% −5.2% 7.1% 41.4% −31.8% FALSE FALSE −14.1% 20.4% 0.6% 112.0% −0.0% 0.0% −2.5% −0.0% −4.7%
Census Tract 202 −5.0% −7.2% 6.5% 23.6% −22.4% FALSE FALSE 117.1% −37.2% −0.2% 13.9% −0.0% −0.0% 4.6% −0.5% 10.5%
Census Tract 203 −5.1% −0.2% 10.0% 37.0% 94.6% FALSE FALSE 9.1% −1.7% 0.7% 8.4% −0.1% −0.1% 13.8% −0.1% 0.1%
Census Tract 204.01 −1.4% 2.3% 9.5% 35.7% −1.2% FALSE FALSE −28.9% −4.6% 0.3% 21.3% −0.0% −0.1% 0.9% 0.1% 0.3%
Census Tract 205 −1.8% 17.9% 5.1% 6.5% 76.6% FALSE FALSE −23.5% 27.7% −0.9% 0.0% 5.6% −0.3% 16.7% 0.0% −0.3%
Census Tract 206 −0.4% 8.0% 12.1% 33.9% 19.9% FALSE FALSE −4.2% 62.4% 0.3% 12.4% −0.2% −0.0% 1.5% 0.2% 0.1%
Census Tract 207.01 −6.3% −7.2% −1.0% 19.0% −14.1% FALSE FALSE −4.2% 69.3% 0.8% 41.7% 0.3% −0.3% 6.5% −0.2% 0.1%
Census Tract 207.02 −2.3% −6.3% −0.2% 40.5% 17.3% FALSE FALSE −56.4% −53.3% 0.3% 14.8% 0.1% −0.1% −11.0% −0.0% −1.2%
Tompkins Census Tract 7 −2.2% −0.2% −1.4% 22.7% 14.1% FALSE FALSE 64.8% 30.2% 0.1% −37.5% 0.0% 0.0% 6.7% 0.0% 0.6%
Census Tract 12 −34.0% −48.6% 0.0% 0.0% 1,000,000.0% FALSE FALSE 289.3% 0.0% 0.6% 20.7% −9.8% −1.1% 100.0% −0.0% 1,000,000.0%
Census Tract 13 1.6% 12.1% 0.7% 1.1% −26.4% FALSE FALSE 4.5% 116.5% −0.8% 44.8% 0.5% 0.1% −5.5% 0.0% −4.3%
Census Tract 16 3.0% 13.2% 21.8% 12.0% −37.6% FALSE FALSE −35.2% −13.0% 0.4% 48.3% −0.0% 0.5% −9.4% −0.2% 0.0%
Census Tract 14 −4.8% −0.1% −11.4% 13.2% 10.7% FALSE FALSE −10.9% −20.8% −0.0% 11.0% −0.0% 0.0% −3.4% −0.2% −0.0%
Census Tract 15 7.1% 11.3% 16.4% −14.1% 11.1% FALSE FALSE −74.5% 36.2% 0.8% 5.0% 0.2% −1.0% −0.8% 0.0% −0.2%
Census Tract 17 4.6% 7.0% 8.5% 30.7% −42.8% FALSE FALSE −83.8% −15.0% 0.9% 94.7% −0.0% 0.1% −14.2% −0.1% −2.6%
Census Tract 18 −0.8% 3.4% 22.4% 19.3% 166.1% FALSE FALSE −35.3% −51.6% 0.7% 10.2% 0.1% −0.1% 0.9% −0.3% 30.7%
Census Tract 19 5.8% 4.0% −8.0% 16.5% 9.0% FALSE FALSE −45.8% −69.6% 0.4% 2.0% 0.0% 0.1% −6.3% −0.0% 2.4%
Census Tract 20 15.1% 4.4% −5.9% 38.9% 71.9% FALSE FALSE −57.4% 56.6% 1.0% 16.8% −0.1% 0.2% −15.8% −0.0% −4.2%
Census Tract 21 −11.3% −7.7% 8.8% 17.5% 53.4% FALSE FALSE 44.9% −10.8% 0.1% 14.2% −0.0% −0.1% −0.2% −0.0% 13.5%
Census Tract 23 4.9% −0.7% −4.1% 27.4% 177.3% FALSE FALSE −40.2% 109.8% 0.2% 4.7% 0.0% 0.4% −6.5% −0.1% 0.1%
Census Tract 22 −2.0% 12.1% 6.8% 6.9% −29.7% FALSE FALSE −64.3% −1.6% −0.2% 76.5% −0.0% 0.3% −14.7% −0.0% 18.7%
Census Tract 1 13.1% 8.5% −5.1% −100.0% −14.4% FALSE FALSE 112.4% −13.3% −0.2% 20.0% 0.0% 0.0% 0.0% 0.0% 0.0%
Census Tract 2 −7.1% −6.0% −2.3% −100.0% −6.7% FALSE FALSE −29.7% 41.2% −0.2% 12.5% 0.0% 0.0% 0.0% 0.0% 0.0%
Census Tract 3 65.1% 28.2% −2.0% −15.5% 130.1% FALSE FALSE 69.9% 13.5% 0.0% 12.5% −0.3% 0.0% 7.1% 0.0% 0.0%
Census Tract 4 −12.1% −31.7% 0.0% 15.9% −20.5% FALSE FALSE −7.3% 4.8% −0.1% −100.0% 0.1% 0.1% −4.8% 0.0% 0.0%
Census Tract 5 −1.4% 2.9% 9.0% 3.6% −18.6% FALSE FALSE 15.3% 78.7% 0.1% 50.0% −0.5% −0.8% 41.2% 0.0% −9.1%
Census Tract 6 −8.2% −12.1% 0.3% 40.4% −43.0% FALSE FALSE 76.0% 53.2% 0.3% 57.1% −1.0% 0.5% −5.4% 0.0% −0.0%
Census Tract 8 18.3% −4.7% 0.7% 86.0% −12.1% FALSE FALSE −68.8% −3.8% −0.1% 1,000,000.0% −0.0% 0.0% 1,000,000.0% −0.0% −0.0%
Census Tract 9 15.9% 8.6% 9.6% 14.1% −26.2% FALSE FALSE 319.1% −51.5% 0.5% 650.0% 0.5% −0.3% −9.6% 0.0% 0.0%
Census Tract 10 −1.0% 0.2% 2.6% 23.2% 110.0% FALSE FALSE 35.0% 8.0% 0.5% −14.6% −0.1% 1.3% −13.2% −0.2% 7.4%
Census Tract 11 35.4% 10.2% −5.8% 11.9% −19.5% FALSE FALSE 126.5% −37.4% 0.5% 41.0% 0.4% −0.2% −1.1% 0.2% −6.3%
Schoharie Census Tract 7402 1.6% −7.9% 5.5% 2.9% −25.8% FALSE FALSE −50.0% −3.0% −0.2% 13.9% −0.2% −0.1% −0.8% 0.0% −0.6%
Census Tract 7403 −25.8% −100.0% −1.5% 0.0% 0.0% FALSE FALSE −52.5% 0.0% 0.8% 0.0% −3.0% 0.0% 1,000,000.0% 0.0% 0.0%
Census Tract 7401 −6.3% −10.1% 6.2% 14.7% 9.7% FALSE FALSE −40.5% 27.6% 0.2% 7.8% −0.0% 0.1% −9.6% 0.0% −0.4%
Census Tract 7404 3.6% 6.6% 4.8% −3.8% 111.6% FALSE FALSE −27.3% 22.1% −0.1% 47.7% 0.0% 0.1% 2.3% 0.1% −17.7%
Census Tract 7405 −4.3% −6.8% 2.4% 52.3% 15.7% FALSE FALSE −36.5% 13.9% 0.1% 23.6% 0.0% 0.4% −19.6% −0.1% 1.1%
Census Tract 7406 −16.1% −10.9% 7.6% 17.0% −8.2% FALSE FALSE −15.3% 26.8% −0.4% 35.2% 0.3% −0.1% 1.5% −0.0% −2.2%
Census Tract 7407 −6.2% 3.8% 21.2% 7.0% 0.7% FALSE FALSE −64.5% 0.8% 0.2% 26.8% 0.0% −0.2% 76.4% 0.0% −8.4%
Census Tract 7408 0.0% 4.4% 9.2% 11.3% 3.9% FALSE FALSE 3.0% 113.7% −0.0% 25.1% 0.5% −0.5% 64.9% −0.1% −1.8%
Schuyler Census Tract 9501 −0.7% 8.8% 11.3% 32.0% 360.5% FALSE FALSE −18.2% −47.9% 1.1% 36.5% 0.6% 0.1% −21.8% −0.0% 0.0%
Census Tract 9502 −7.4% −7.1% 0.9% 8.2% 533.7% FALSE FALSE 183.7% −58.3% −0.2% 95.7% 0.4% −0.1% −17.4% 0.0% 0.4%
Census Tract 9503 −13.7% −3.4% 25.8% 11.9% 33.8% FALSE FALSE −32.2% −25.3% 0.3% 15.7% 0.0% −0.0% 1.7% 0.2% −0.2%
Census Tract 9504 −1.1% −11.2% −0.2% 32.0% 121.7% FALSE FALSE 85.6% −40.6% −0.4% 52.0% 0.2% 0.1% −18.1% −0.1% −4.1%
Census Tract 9505 0.7% −2.2% 8.6% 42.4% 17.0% FALSE FALSE −44.0% 41.2% 0.5% 37.8% 1.0% −0.5% 6.8% −0.3% −0.1%
Steuben Census Tract 9607 −6.1% −9.8% −8.4% −24.8% 5.5% FALSE FALSE −30.5% −11.2% −0.7% 0.0% 5.1% −0.7% 37.5% −0.0% −0.0%
Census Tract 9612 −5.0% 0.5% 10.4% 18.9% −29.6% FALSE FALSE −7.1% 42.4% 0.4% 5.3% −0.4% −0.7% −60.0% 0.0% 0.0%
Census Tract 9616 23.1% 18.7% −1.0% 30.0% −43.3% FALSE FALSE −70.8% 0.0% −0.4% 7.3% 0.2% 0.0% −9.0% 0.1% −14.3%
Census Tract 9622 0.7% −9.1% −9.3% 37.1% 14.1% FALSE FALSE −15.6% 59.1% 0.2% 31.4% 0.2% −0.3% 65.5% 0.1% −3.4%
Census Tract 9626 −9.6% 4.1% 19.3% 32.8% −69.4% FALSE FALSE −9.5% −16.8% −0.1% −0.0% 0.6% 0.0% 8.3% 0.0% 0.0%
Census Tract 9601 −15.8% −12.5% −1.0% 16.5% 3.2% FALSE FALSE 38.9% 547.5% 0.1% 3.9% 0.2% 0.2% −17.7% 0.0% −0.0%
Census Tract 9602 6.8% 1.1% 3.1% 18.5% −26.2% FALSE FALSE −42.0% 18.2% 0.8% 83.3% 0.0% 0.2% −12.3% 0.0% −6.3%
Census Tract 9603 −4.1% −2.5% 3.8% 13.2% 57.4% FALSE FALSE 3.3% −17.4% 0.1% 11.9% 0.1% −0.1% −2.3% 0.0% −29.4%
Census Tract 9604 −4.6% 3.0% 27.3% 25.5% −12.9% FALSE FALSE −21.2% 25.8% −0.1% 5.5% 0.0% 0.3% −15.2% −0.1% 0.2%
Census Tract 9605 5.2% 1.1% −0.7% 14.0% 44.0% FALSE FALSE −23.4% 18.7% 0.4% 31.3% 0.0% −0.0% −2.6% −0.0% 0.2%
Census Tract 9606 −2.0% 0.4% 11.1% 16.7% 53.9% FALSE FALSE 10.1% −53.7% −0.4% 7.8% 0.1% 0.1% −17.5% 0.0% −1.2%
Census Tract 9608 1.8% −10.3% −11.8% 38.1% 7.9% TRUE FALSE −63.0% 90.9% −0.1% 23.8% 0.5% −0.8% 16.7% 0.0% 0.0%
Census Tract 9609 −9.2% −12.3% 5.1% −2.3% 88.6% TRUE FALSE 11.6% 2.4% −0.3% 44.4% 20.0% −3.4% −50.0% 0.0% 0.0%
Census Tract 9610 −2.7% 2.9% 18.2% 27.4% 3.0% FALSE FALSE 35.3% −47.8% 0.2% 21.1% −0.0% −0.0% −4.2% −0.0% −6.7%
Census Tract 9611 0.0% −6.9% 7.2% 1.4% 76.9% FALSE FALSE −38.9% 17.5% 0.7% 1.2% 0.3% −0.3% −6.1% 0.2% −4.2%
Census Tract 9613 −2.1% 4.3% 4.5% 3.0% 48.2% FALSE FALSE −13.5% 189.8% 0.1% 9.1% 0.2% −0.2% 15.7% 0.0% −1.3%
Census Tract 9614 −4.2% −4.8% 16.8% 8.3% 58.4% FALSE FALSE −42.7% 38.9% −0.2% 20.2% 0.2% −0.0% −1.4% −0.1% −0.1%
Census Tract 9615 −13.3% −3.4% 16.7% 27.0% −47.4% FALSE FALSE −54.9% −27.9% 0.3% 0.9% 0.1% −0.1% 1.2% 0.0% −0.0%
Census Tract 9617 −5.0% 10.1% 25.2% 54.9% 1.8% FALSE FALSE −50.8% 1.8% −0.6% 1.1% 0.5% −0.1% −1.7% 0.0% −4.2%
Census Tract 9618 −5.3% −4.4% 10.0% 21.9% 18.6% FALSE FALSE −22.1% 49.4% 0.1% 34.4% 0.1% −0.2% 33.7% −0.0% −1.9%
Census Tract 9619 −4.5% 4.9% 6.2% 42.0% 20.7% FALSE FALSE −41.2% 10.1% −0.0% 9.1% −0.1% −0.0% 4.5% −0.0% −1.5%
Census Tract 9620 1.5% −4.8% −3.5% 18.2% 16.3% FALSE FALSE −29.5% 50.8% 0.7% 50.8% −0.0% −0.0% 0.4% −0.0% 1.4%
Census Tract 9621 −5.4% −7.1% 10.0% 26.9% −12.3% FALSE FALSE −28.1% −7.6% 0.6% 24.3% −0.0% 0.1% −17.8% −0.2% −7.7%
Census Tract 9623 5.0% 3.8% 0.0% 61.7% −27.6% FALSE FALSE 44.1% 3.8% 0.5% 3.8% −0.4% −0.2% 7.4% −0.3% 3.0%
Census Tract 9624 −1.9% −6.4% −0.3% 25.0% −30.0% FALSE FALSE −84.8% −0.0% −0.5% 9.1% 9.0% −0.0% 0.0% −0.0% −0.0%
Census Tract 9625 5.2% 1.3% −19.9% 20.5% 12.1% FALSE FALSE 287.5% 171.4% −1.7% 69.2% 12.1% −0.0% 20.0% −0.0% −0.0%
Census Tract 9627 −6.2% −3.5% 11.5% 104.6% −5.3% FALSE FALSE 2.9% −31.1% −1.1% −0.0% 13.2% 1.6% 700.0% 0.0% 0.0%
Census Tract 9628 −4.0% −1.0% 9.5% 16.7% −66.7% FALSE FALSE −18.4% 1.0% 0.9% −5.2% −0.0% 0.0% −5.4% 0.1% −2.0%
Census Tract 9629 5.2% 8.0% −2.4% 33.8% −32.8% FALSE FALSE −5.2% 208.7% −0.1% 94.2% −0.3% −0.2% 78.6% 0.0% −0.8%
Census Tract 9630 −14.9% −15.3% 1.4% 33.6% −12.1% FALSE FALSE 22.6% 23.8% 1.7% 6.1% 0.1% −0.2% 10.4% −0.1% −0.8%
Allegany Census Tract 9510 −1.6% −8.7% −1.4% 0.6% 18.9% TRUE FALSE −82.2% −8.9% −0.4% 4.8% 0.0% 0.4% −17.4% −0.6% −1.8%
Census Tract 9511 1.8% 13.0% 6.9% 11.7% −12.7% FALSE FALSE 34.5% 0.8% −1.4% 41.8% 0.1% 0.2% −19.3% 0.0% −4.5%
Census Tract 9512 −8.6% −8.6% 9.6% 10.0% 24.7% FALSE FALSE −21.8% 50.5% −0.7% 44.1% 0.2% 0.1% −14.5% −0.2% 0.0%
Census Tract 9513 −14.2% −9.8% 16.5% 24.8% −20.8% FALSE FALSE −61.4% 35.1% −1.1% −0.5% −0.2% 0.2% −9.1% 0.0% −1.8%
Census Tract 9402 1,000,000.0% 1,000,000.0% 0.0% 0.0% 0.0% FALSE FALSE 0.0% 0.0% −0.0% 0.0% −2.9% 0.1% −0.0% 0.0% −0.0%
Census Tract 9501 −12.5% −10.0% 10.3% 18.7% 6.4% FALSE FALSE 5.2% −32.1% 0.8% 6.1% −0.1% 0.1% −2.5% 0.0% −12.6%
Census Tract 9503 2.8% 2.9% 13.4% 15.0% 0.5% FALSE FALSE −49.0% −33.0% 2.0% 41.6% −0.1% −0.1% 9.5% −0.1% −1.3%
Census Tract 9504 9.4% −4.5% −10.0% 14.5% 15.1% FALSE FALSE −30.8% 85.1% −0.2% 25.6% −0.1% −0.0% 5.3% 0.0% −0.1%
Census Tract 9505 −6.7% −3.4% 2.0% 8.5% 30.4% FALSE FALSE 38.5% −22.7% −0.4% 9.3% −0.2% 0.1% −9.3% 3.6% 1.1%
Census Tract 9506 −1.2% 1.2% 15.3% 16.4% −12.6% TRUE FALSE −35.0% −32.9% −0.4% −3.2% 0.0% 0.4% −33.6% −0.4% −10.2%
Census Tract 9507 −17.8% −14.6% 21.6% 36.1% −14.4% FALSE FALSE −46.8% 75.9% −0.5% 21.1% −0.2% 0.2% −13.9% −0.3% −2.4%
Census Tract 9508 −2.1% −20.9% 1.0% 25.0% 9.1% FALSE FALSE −77.7% −0.5% −0.3% 3.8% −0.2% 0.1% −2.5% 0.0% −4.5%
Census Tract 9509 −9.9% −0.1% 17.5% 25.2% −7.3% FALSE FALSE −66.6% 122.2% 0.0% 32.7% −0.2% 0.2% −5.2% 0.1% 2.0%

County-Level Distressed Population and Land Cover Change Data

Code
# get full counties sfs for 2010 and 2019 and fill NA with 0, then join them into a single sf
final_counties_df.2010 <- gather_data(st_lc_2010_masked, 2010, 'county')
final_counties_df.2010[is.na(final_counties_df.2010)] <- 0
final_counties_df.2019 <- gather_data(st_lc_2019_masked, 2019, 'county')
final_counties_df.2019[is.na(final_counties_df.2019)] <- 0

final_counties_df <- 
  st_join(final_counties_df.2010, final_counties_df.2019, 
                             suffix = c('', '.y'), largest = TRUE) %>% 
  select(-c(county.y, tot_pop_became_distressed_2010_2019, tot_pop_improved_2010_2019)) %>%
  rename(
    tot_pop_became_distressed_2010_2019 = tot_pop_became_distressed_2010_2019.y, 
    tot_pop_improved_2010_2019 = tot_pop_improved_2010_2019.y
  ) %>%
  mutate(
    pct_pop_chg = ((tot_pop_2019 - tot_pop_2010) / tot_pop_2010) * 100,
    pct_hhs_chg = ((tot_hhs_2019 - tot_hhs_2010) / tot_hhs_2010) * 100,
    pct_median_age_chg = ((median_age_2019 - median_age_2010) / median_age_2010) * 100,
    pct_median_income_chg = ((median_income_2019 - median_income_2010) / median_income_2010) * 100,
    pct_below_poverty_chg = ((pct_below_poverty_2019 - pct_below_poverty_2010) / pct_below_poverty_2010) * 100,
    pct_unemployed_chg = ((pct_unemployed_2019 - pct_unemployed_2010) / pct_unemployed_2010)  * 100,
    pct_pop_distressed_chg = ((pct_pop_distressed_2019 - pct_pop_distressed_2010) / pct_pop_distressed_2010) * 100,
    pct_hhs_no_vehicles_chg = ((pct_hhs_no_vehicles_2019 - pct_hhs_no_vehicles_2010) / pct_hhs_no_vehicles_2010) * 100,
    pct_developed_chg = ((pct_developed_area_sqmi_2019 - pct_developed_area_sqmi_2010) / pct_developed_area_sqmi_2010) * 100,
    pct_natural_barren_chg = ((pct_natural_barren_area_sqmi_2019 - pct_natural_barren_area_sqmi_2010) / pct_natural_barren_area_sqmi_2010) * 100,
    pct_cropland_chg = ((pct_cropland_area_sqmi_2019 - pct_cropland_area_sqmi_2010) / pct_cropland_area_sqmi_2010) * 100,
    pct_tree_cover_chg = ((pct_tree_cover_area_sqmi_2019 - pct_tree_cover_area_sqmi_2010) / pct_tree_cover_area_sqmi_2010) * 100,
    pct_grassland_chg = ((pct_grassland_area_sqmi_2019 - pct_grassland_area_sqmi_2010) / pct_grassland_area_sqmi_2010) * 100,
    pct_wetland_chg = ((pct_wetland_area_sqmi_2019 - pct_wetland_area_sqmi_2010) / pct_wetland_area_sqmi_2010) * 100,
    pct_water_chg = ((pct_water_area_sqmi_2019 - pct_water_area_sqmi_2010) / pct_water_area_sqmi_2010) * 100
  ) %>%
  mutate_if(is.numeric, ~ replace_na(., 0) %>% replace(., is.infinite(.), 1000000))

final_counties_df[is.na(final_counties_df)] <- 0

# display county-level table of data
final_counties_df %>%
  select(county, grep('chg', colnames(.), value = TRUE)) %>%
  st_drop_geometry() %>%
  gt(rowname_col = 'county') %>%
  tab_header(
    title = md('**Changes in Population Characteristics, Economic Indicators, and Land Cover**'),
    subtitle = md('By Southern Tier County (2010 - 2019)')
  ) %>%
  cols_label(
    pct_pop_chg = md('**% Pop Change**'),
    pct_hhs_chg = md('**% HHs Change**'),
    pct_median_age_chg = md('**% Median Age Change**'),
    pct_median_income_chg = md('**% Median Income Change**'),
    pct_below_poverty_chg = md('**% Pop Below Poverty Change**'),
    pct_unemployed_chg = md('**% Unemployment Rate Change**'),
    pct_pop_distressed_chg = md('**% Pop Distressed Change**'),
    pct_hhs_no_vehicles_chg = md('**% HHs No Vehicle Change**'),
    pct_developed_chg = md('**% Developed Change**'),
    pct_natural_barren_chg = md('**% Natural Barren Change**'),
    pct_cropland_chg = md('**% Cropland Change**'),
    pct_tree_cover_chg = md('**% Tree Cover Change**'),
    pct_grassland_chg = md('**% Grassland Change**'),
    pct_wetland_chg = md('**% Wetland Change**'),
    pct_water_chg = md('**% Water Change**')
  ) %>%
  fmt_percent(decimals = 1, scale_values = FALSE)
Changes in Population Characteristics, Economic Indicators, and Land Cover
By Southern Tier County (2010 - 2019)
% Pop Change % HHs Change % Median Age Change % Median Income Change % Pop Below Poverty Change % Unemployment Rate Change % Pop Distressed Change % HHs No Vehicle Change % Developed Change % Natural Barren Change % Cropland Change % Tree Cover Change % Grassland Change % Wetland Change % Water Change
Allegany −4.9% −5.5% 5.3% 18.9% 1.2% −80.7% 1,000,000.0% 17.2% −0.3% 9.8% −0.1% 0.1% −9.8% −0.1% −1.6%
Broome −3.8% −2.8% 0.0% 20.7% 12.4% −19.7% 57.4% 4.6% −0.0% 20.8% −0.1% −0.0% −5.7% 0.0% −0.2%
Cattaraugus −4.5% −2.7% 4.2% 21.7% 4.2% 0.0% 331.7% 32.2% −0.1% 9.0% 0.0% −0.2% 9.2% −0.1% −0.0%
Chautauqua −5.0% −3.6% 5.7% 22.8% 6.7% −47.6% 61.0% 9.6% −0.3% 33.1% 0.0% −0.3% 1.5% −0.1% −0.0%
Chemung −4.3% −4.0% 2.0% 28.8% −8.0% −10.3% 20.5% −0.4% −0.2% 8.4% 0.2% −0.1% 2.2% −0.0% −0.5%
Chenango −6.1% 3.5% 6.1% 19.9% −0.4% −37.0% 1,000,000.0% 39.0% 0.4% 21.5% −0.1% 0.1% −7.3% −0.0% 1.1%
Cortland −3.2% −0.9% 2.2% 23.3% 15.5% −72.1% 1,000,000.0% −22.9% 0.5% 22.7% −0.1% −0.1% 5.4% −0.1% 0.8%
Delaware −6.7% −5.7% 7.3% 20.6% 21.6% −36.5% 1,000,000.0% 21.2% −0.4% 5.2% −0.0% 0.1% −8.8% −0.0% −0.7%
Otsego −4.2% −6.1% 6.2% 21.8% −1.4% −54.8% 0.0% 6.9% −0.2% 43.2% −0.1% 0.3% −10.4% 0.0% −0.3%
Schoharie −4.8% −3.3% 9.6% 14.3% 7.8% −43.6% 0.0% 8.3% −0.0% 22.2% 0.1% −0.1% 6.9% −0.0% −3.2%
Schuyler −3.5% −2.1% 9.1% 30.3% 88.2% −12.8% 0.0% −23.6% 0.3% 40.8% 0.5% −0.1% −12.8% −0.1% −0.2%
Steuben −2.3% −1.7% 5.1% 25.0% 0.8% −43.7% 268.3% 15.3% 0.1% 10.4% 0.1% −0.0% −2.2% −0.0% −0.6%
Tioga −5.2% −1.8% 6.4% 31.2% 5.8% −91.2% 0.0% −0.8% 0.2% 18.1% −0.0% −0.1% 1.1% −0.1% 0.1%
Tompkins 2.0% 3.0% 5.0% 15.9% 0.3% 56.5% −100.0% 9.1% 0.3% 11.6% 0.0% 0.1% −6.6% −0.1% 0.0%

All counties except for Tompkins County had a population decline and an increase in the proportion of distressed people. Tompkins County was the only county where the civilian unemployment rate increased. All counties got older with the exception of Broome County. Additionally, median family income increased by at least 14% in all counties.

There were marginal changes in developed land, cropland, tree cover, wetland, and water areas in all counties. There were more significant changes in natural barren and (to a lesser extent) grassland areas in all counties. Grassland decreased by over 10% in Schuyler County (-12.8%) and Otsego County (-10.4%). All counties had much more natural barren land by 2019. However, the actual amount of land that became barren is relatively small given the very small amount of total natural barren land in the study area.

Analysis Correlations

This correlation analysis used the Pearson method.

Code
# filter for only pct chg columns
tracts.pct_chg <- final_tracts_df %>% 
  select(NAME, grep('chg', colnames(.), value = TRUE)) %>%
  st_drop_geometry()

# calculate correlation coefficients between all variables
tract_correlations <- cor(select(tracts.pct_chg, -c(NAME)))

# display heatmap of correlations
heatmaply(
  tract_correlations, colors = colorRampPalette(c('blue', 'white', 'orange'))(100), 
  dendrogram = 'none', limits = c(-1, 1), branches.lwd = 0.5
)
Code
# create data frame of correlation coefficients between 
# pct distressed pop chg and other variables
tract_cor.df <- melt(tract_correlations) %>% 
  filter(Var1 == 'pct_pop_chg', Var2 != 'pct_pop_chg') %>%
  select(-Var1) %>%
  rename(
    Variable = Var2,
    Coefficient = value
  ) %>%
  arrange(desc(Coefficient))

# display table of correlation with pct distressed pop chg in descending order of correlation
tract_cor.df %>%
  gt() %>%
  tab_header(title = md('**Tract-Level Correlation with Percent Population Change**')) %>%
  cols_align(align = 'center') %>%
  cols_label(
    Variable = md('**Variable**'),
    Coefficient = md('**Coefficient**')
  ) %>%
  fmt_number(columns = Coefficient, decimals = 3)
Tract-Level Correlation with Percent Population Change
Variable Coefficient
pct_hhs_chg 1.000
pct_wetland_chg 0.005
pct_tree_cover_chg 0.005
pct_unemployed_chg 0.003
pct_developed_chg −0.001
pct_cropland_chg −0.004
pct_hhs_no_vehicles_chg −0.004
pct_water_chg −0.004
pct_below_poverty_chg −0.005
pct_grassland_chg −0.008
pct_natural_barren_chg −0.010
pct_median_age_chg −0.025
pct_median_income_chg −0.047

The most significant correlation is between population change and household change, which has a direct relationship. Other strong correlations are between poverty rate change and water area change which have a strong positive correlation, as well as the change in percentage of households with no vehicle and wetland area change which have a strong negative correlation.

The change in population variable is not strongly correlated with any of the other percentage change variables, with the exception of the percentage change in households.

Conclusions

This study was severely limited in the variables that were analyzed, since the study was centered around an analysis of the distressed population based on the two-variable criteria outlined by the Appalachian Regional Commission. A much deeper statistical analysis of the Southern Tier could be conducted to evaluate additional trends and gain better insights into the area, like the study by Ludke et al. (2012) which considered many other demographic variables such as employment by worker class and occupation, educational attainment, dependency ratio, marital status, and more granular poverty level variables (e.g., individual poverty, childhood poverty, senior adult poverty, and female household poverty). This study was also limited in its methodology related to the land cover change analysis. Methods employed by KC et al. (2024) in their analysis of eastern Kentucky would allow for better identification of land cover changes and how it changed, including the use of a random tree classifier, hot spot analysis, and the creation of a map of the areas that changed and what the land became.

The ACS 5-year data sets are generally viewed as the most accurate given the larger sample sizes. There could still be questions regarding the accuracy of certain estimates in many of the census tracts included in this study, especially in rural census tracts with smaller populations and larger margins of error. Despite this possible limitation, this study is still able to provide some valuable insights into how the New York Southern Tier region has changed between 2010 and 2019. Land cover did not change too drastically, with the largest changes being the additional 3.4 square miles of natural barren land and the 2.9 square miles of land that are no longer grasslands in the entire 14-county study area. There were no significant relationships between the change in population and changes in land cover and other population variables, suggesting that one could not accurately predict how a census tract’s population and land cover change based on the rate of population change. While median family income increased in all counties, every county except Tompkins County became older, less populated, and more distressed. Built-up areas in the Southern Tier (especially Binghamton, Jamestown, Dunkirk, Elmira, and Salamanca) are considerably more distressed than rural areas, with Ithaca and Oneonta being the only cities that do not have a significant risk.

The distressed population for each individual census tract could be calculated by determining the distressed status of the block groups within each census tract, and then summing up the total population that lives within distressed block groups in each census tract. Also, a separate study that evaluates the 14-county Southern Tier region in the post-COVID era (2020 - present) could offer valuable information about how the demographics of the Southern Tier have changed further since the onset of the pandemic, and if COVID has increased the number of distressed people.

References

Works Cited

Appalachian Regional Commission. (2023). County Economic Status and Distressed Areas by State, FY 2024. https://www.arc.gov/about-the-appalachian-region/county-economic-status-and-distressed-areas-by-state-fy-2024

Galili, T., O’Callaghan, A., Sidi, J., & Sievert, C. (2017). heatmaply: an R package for creating interactive cluster heatmaps for online publishing. Bioinformatics, 34(9), 1600-1602. https://doi.org/10.1093/bioinformatics/btx657

Hijmans, R. (2024). terra: Spatial Data Analysis. R package version 1.8-6. https://github.com/rspatial/terra

K C, S., Gyawali, B. R., Lucas, S., Antonious, G. F., Chiluwal, A., & Zourarakis, D. (2024). Assessing Land-Cover Change Trends, Patterns, and Transitions in Coalfield Counties of Eastern Kentucky, USA. Land, 13(9), 1541. https://doi.org/10.3390/land13091541

Ludke, R. L., Obermiller, P. J., & Rademacher, E. W. (2012). Demographic Change in Appalachia: A Tentative Analysis. Journal of Appalachian Studies, 18(1/2), 48–92. http://www.jstor.org/stable/23337708

McMahon, E.J. (2024). Eight in 10 New York towns and cities have lost population since 2020. Empire Center. https://www.empirecenter.org/publications/eight-in-10-new-york-towns-and-cities-have-lost-population-since-2020

Walker, K. & Herman, M. (2024). tidycensus: Load US Census Boundary and Attribute Data as ‘tidyverse’ and ‘sf’-Ready Data Frames. R package version 1.6.6. https://walker-data.com/tidycensus

Data Sources and Methodology

2010 U.S. Data

American Community Survey (ACS) Data

Land Cover Data

Pengra, B.W., Stehman, S.V., Horton, J.A., Auch, R.F., Kambly, S., Knuppe, M., Sorenson, D., Robison, C., and Taylor, J.L. (2023). LCMAP CONUS Reference Data Product 1984-2021 land cover, land use and change process attributes: U.S. Geological Survey data release, https://doi.org/10.5066/P933Z1TK

Distressed Area Classification System

Appalachian Regional Commission. (2024). Distressed Areas Classification System. https://www.arc.gov/distressed-areas-classification-system/