Background

The primary goal of this report is to use publicly-available data to better inform Promise Zone SNAP policy. Drawing on data from the Census Bureau and USDA, this report: 1) gives an overview of current SNAP conditions in the Promise Zone, 2) highlights changes in Promise Zone SNAP access over the last decade, and 3) provides actionable suggestions for improving access.

Three criteria for SNAP access are considered in this report: 1) SNAP enrollment, 2) the number of stores that accept SNAP vouchers, and 3) the types and locations of those stores. Additional metrics were calculated based on those data. These include the ratio of SNAP stores to SNAP enrollees per tract, the average SNAP income cutoff per tract, and the ratio of the median income per tract to the average SNAP cutoff per tract. For an explanation of how these were calculated and the underlying code, see the “Methodology” section at the end of this report.

Summary

SNAP access in the Promise Zone has declined dramatically over the last ten years. Stores accepting SNAP in the Promise Zone are poorly located relative to where SNAP enrollment is highest. Of the stores in the Promise Zone that accept SNAP, half are convenience stores and are therefore poorly-positioned to offer quality food options.

In order to fix these issues, this report recommends: 1) targeted SNAP enrollment outreach to residents in and around Powelton, 2) efforts to increase the number of stores in Mantua and Powelton that accept SNAP vouchers, and 3) efforts to boost the number of large grocery stores and supermarkets in the Promise Zone, ideally in Mantua.

Current SNAP Conditions in the PZ

This section outlines the current state of SNAP enrollment and access in the Promise Zone. The first map indicates the percent SNAP enrollment per tract. The second map and accompaying table indicate the locations and types of currently-active sites accepting SNAP vouchers in the Promise Zone. The final chart illustrates the types of stores accepting SNAP vouchers in the Promise Zone.

phl_demos <- get_acs(geography = "tract", 
                     year = 2020, #Most recent available year
                     variables = c("B11001_001E", #Total number of households
                                   "B22001_002E", #Number of hholds that received foodstamps/SNAP in last 12 months
                                   "B19013_001E", #Median household income in last 12 months
                                   "B25010_001E" #Avg household size
                                   ), 
                     geometry = T, #we want this as a sf
                     state = "PA", 
                     county = "Philadelphia", 
                     output = "wide") |> 
  rename(tot_hh = B11001_001E,
         snap_enroll = B22001_002E,
         med_hh_inc = B19013_001E,
         avg_hh_size = B25010_001E) 

phl_demos <- phl_demos |>
  mutate(perc_snap = snap_enroll / tot_hh * 100) |>
  mutate(avg_snap_thresh = (12*(1396 + (492*(avg_hh_size-1))))) |>
  mutate(med_hh_inc_as_perc_snap_thresh =  med_hh_inc / avg_snap_thresh)|>
              st_transform(crs = st_crs("EPSG:4326"))

#clean historic sites

historic_snap_sites$street_address <-paste(historic_snap_sites$Street.Number, historic_snap_sites$Street.Name, sep=" ")


historic_snap_sites = historic_snap_sites |>
                        mutate(end_year = year(mdy(End.Date)))

historic_snap_sites$end_year[is.na(historic_snap_sites$end_year)] <- "active"

#clean active sites

active_phl_sites <- historic_snap_sites |>
                     filter(end_year == "active")

#I'm using st_join() to join SNAP sites to their tract IDs. This works as long as the sites don't fall on a tract
#border, which is possible.
active_phl_sites = st_as_sf(active_phl_sites, 
                               coords = c("Longitude", "Latitude"), 
                               crs = st_crs("EPSG:4326"))

active_phl_sites = st_join(active_phl_sites, phl_tracts)

active_phl_sites_by_tract <- active_phl_sites |>
                              group_by(GEOID10) |>
                              tally()

active_phl_sites_by_tract$GEOID10 <- as.character(active_phl_sites_by_tract$GEOID10)

active_phl_sites_by_tract = st_transform(active_phl_sites_by_tract, st_crs("EPSG:4326"))

active_phl_sites_by_tract <- st_join(phl_demos, active_phl_sites_by_tract)

active_phl_sites_by_tract$n[is.na(active_phl_sites_by_tract$n)] = 0

active_phl_sites_by_tract <- active_phl_sites_by_tract |>
  mutate(snap_per_hh = n / snap_enroll)

#Define logicals

#Defining a logical variable for tracts where the median household income is less than the average snap threshold for the tract
active_phl_sites_by_tract$below_snap_cut = case_when(
                                              active_phl_sites_by_tract$med_hh_inc_as_perc_snap_thresh < 1 ~ "Yes",
                                              active_phl_sites_by_tract$med_hh_inc_as_perc_snap_thresh >= 1 ~ "No"
                                            )

active_phl_sites_by_tract[is.infinite(active_phl_sites_by_tract$snap_per_hh), ] <- NA

#Defining a categorical variable so that a low snap per hh value is anything below half the city-wide median of snap sites per hh per tract
active_phl_sites_by_tract$low_snap_per_hh = case_when(
                                              active_phl_sites_by_tract$snap_per_hh < quantile(na.omit(active_phl_sites_by_tract$snap_per_hh),0.25) ~ "Yes",
                                              active_phl_sites_by_tract$snap_per_hh >= quantile(na.omit(active_phl_sites_by_tract$snap_per_hh),0.25) ~ "No"
                                            )
    
active_phl_sites_by_tract$snap_expand = case_when(
                                         na.omit(active_phl_sites_by_tract$med_hh_inc) > active_phl_sites_by_tract$avg_snap_thresh ~ 0,
                                         na.omit(active_phl_sites_by_tract$med_hh_inc) <= active_phl_sites_by_tract$avg_snap_thresh ~ 
                                           (50 -   active_phl_sites_by_tract$perc_snap))

active_phl_sites_by_tract$snap_expand[active_phl_sites_by_tract$snap_expand == 0] <- NA

active_phl_sites_by_tract$snap_expand_boolean = case_when(
                                         na.omit(active_phl_sites_by_tract$med_hh_inc) > active_phl_sites_by_tract$avg_snap_thresh ~ "No",
                                         na.omit(active_phl_sites_by_tract$med_hh_inc) <= active_phl_sites_by_tract$avg_snap_thresh ~ "Yes")

#Spatial clean

active_pz_sites = active_phl_sites[pz, ]

active_pz_sites_by_tract = active_phl_sites_by_tract[pz, ]

active_pz_largeorsuper = active_pz_sites |>
                            filter(Store.Type %in% c("Large Grocery Store", "Supermarket"))

active_pz_largeorsuper_buffer = st_buffer(active_pz_largeorsuper, units::as_units(0.5, "mile")) |> #dist has to be provided in units of CRS
                                    st_transform(crs = st_crs("EPSG:4326"))

active_phl_largeorsuper = active_phl_sites |>
                            filter(Store.Type %in% c("Large Grocery Store", "Supermarket"))

active_phl_largeorsuper_buffer = st_buffer(active_phl_largeorsuper, units::as_units(0.5, "mile")) |> #dist has to be provided in units of CRS
                                    st_transform(crs = st_crs("EPSG:4326"))

SNAP Enrollment per Tract

Active SNAP Sites per Enrolled Household

Currently-Active SNAP Stores by Type

Street Name Store Type Street Address
26 40th St Grocery Small Grocery Store 804 N 40th St
27 42nd Street Food Market Small Grocery Store 920 N 42nd St
28 44 Food Market Inc Convenience Store 701 N 44th St
75 7-ELEVEN 22479A Convenience Store 3401 Lancaster Ave
105 7-eleven Store 2408-35020a 35020A Convenience Store 3440 Market St
110 8 Brother Associated Inc Medium Grocery Store 1127 N 40th St
177 ALDI 54 Supermarket 4421 Market St
190 Alexander Supermarket Inc Convenience Store 4035 Lancaster Ave
266 Boruco Mini Market Inc Small Grocery Store 4316 Parrish St
317 CHESTNUT BP Convenience Store 4600 Chestnut St
423 D8 Brothers Food Market & Deli Inc Large Grocery Store 4500 Lancaster Ave
425 Dana Mandi 0 Medium Grocery Store 4211 Chestnut St
448 Desh Bangla, LLC Medium Grocery Store 4319 Chestnut St
471 Dollar General 23121 Combination Grocery/Other 801 N 48th St
527 Dollar Warehouse Convenience Store 4007 Market St
635 Family Dollar Store 10581 Combination Grocery/Other 4061 Lancaster Ave
703 Giant Heirloom Market 6551 Supermarket 3401 Chestnut St
708 Girard Food Market I Inc Small Grocery Store 4200 W Girard Ave
711 Girard Neighorhood Food Market Inc Convenience Store 4058 W Girard Ave
727 Grab N Go Market Convenience Store 4026 Lancaster Ave
796 J & L Mini Market II Small Grocery Store 641 N 39th St
815 JAQUEZ GROCERY Convenience Store 856 N Holly St
848 Jose Alexander Cruz Inc Convenience Store 957 N 43rd St
898 L and P Fish Market Inc.  Medium Grocery Store 4064 Lancaster Ave
918 Les No 1 Market Llc Convenience Store 423 N 36th St
1050 New Family Food Market Inc Convenience Store 1043 Belmont Ave
1112 Parrish Supermarket Inc Convenience Store 4529 Parrish St
1173 R D Mini Market Inc Small Grocery Store 340 N 41st St
1193 Rd Family Market Inc Convenience Store 3423 Haverford Ave
1240 RITE AID 3213 Combination Grocery/Other 4641 Chestnut St
1247 Rite Aid 11124 Combination Grocery/Other 4055 Market St
1337 Sanchez Mini Market Convenience Store 4630 Chestnut St
1609 Wawa Food Market 55 Convenience Store 3604 Chestnut St
1620 Wawa Store 8141 8141 Convenience Store 3300 Market St

Change in SNAP Availability over Time

It is important to understand the history of SNAP access in the Promise Zone. The number of stores in the Promise Zone accepting SNAP vouchers has declined steadily, down 43.3% from its peak of 60 in 2013. Meanwhile, SNAP enrollment as a percentage of the tract population has dropped approximately 7% from its peak of 37% around 2015 to a current level of about 30%. Note, however, that the dropoff in SNAP sites (43%) has been much larger than the dropoff in SNAP enrollment (7%).

#Here it's possible to use area weighted spatial interpolation to get a good estimate of number of households in th Promise Zone, and the subset of that population that's enrolled in SNAP.
phl_demos_twenty = get_acs(geography = "tract", 
                     year = 2020, #Most recent available year
                     variables = c("B11001_001E", #Total number of households
                                   "B22001_002E" #Number of hholds that received foodstamps/SNAP in last 12 months
                                   ), 
                     geometry = T, #we want this as a sf
                     state = "PA", 
                     county = "Philadelphia", 
                     output = "wide") |> 
                  rename(tot_hh = B11001_001E,
                         snap_enroll = B22001_002E) |>
                    st_transform(crs = st_crs("EPSG:4326"))

#vars15 = load_variables(2015, "acs5")
#vars10 = load_variables(2010, "acs5")

phl_demos_fifteen = get_acs(geography = "tract", 
                     year = 2015, #Most recent available year
                     variables = c("B11001_001E", #Total number of households
                                   "B22001_002E" #Number of hholds that received foodstamps/SNAP in last 12 months
                                   ), 
                     geometry = T, #we want this as a sf
                     state = "PA", 
                     county = "Philadelphia", 
                     output = "wide") |> 
                  rename(tot_hh = B11001_001E,
                         snap_enroll = B22001_002E)|>
                    st_transform(crs = st_crs("EPSG:4326"))
  
phl_demos_ten = get_acs(geography = "tract", 
                     year = 2010, #Most recent available year
                     variables = c("B11001_001E", #Total number of households
                                   "B22001_002E" #Number of hholds that received foodstamps/SNAP in last 12 months
                                   ), 
                     geometry = T, #we want this as a sf
                     state = "PA", 
                     county = "Philadelphia", 
                     output = "wide") |> 
                  rename(tot_hh = B11001_001E,
                         snap_enroll = B22001_002E)|>
                    st_transform(crs = st_crs("EPSG:4326"))

pz_demos_twenty = st_interpolate_aw(phl_demos_twenty[, c(3,5)], pz, ext = TRUE) |>
                    mutate(year = 2020)

pz_demos_fifteen = st_interpolate_aw(phl_demos_fifteen[, c(3,5)], pz, ext = TRUE)|>
                    mutate(year = 2015)

pz_demos_ten = st_interpolate_aw(phl_demos_ten[, c(3,5)], pz, ext = TRUE)|>
                    mutate(year = 2010)

pz_historic_demos = rbind(pz_demos_twenty, pz_demos_fifteen, pz_demos_ten)

pz_historic_demos$year = as.character(pz_historic_demos$year)

pz_historic_demos = pz_historic_demos |>
                      mutate(pct_snap_enroll = snap_enroll / tot_hh*100)

pz_historic_demos$active_snap_sites = c(34, 56, 45)

2010 (Total Sites: 45) — — | — — 2015 (Total Sites: 56) — — | — — 2020 (Total Sites: 34)

Opportunities for Improvement

The data available from the American Communities Survey and the USDA can inform more targeted policy to expand SNAP access.

First, ACS data can be used to identify where SNAP enrollment is lower than expected. Comparing the median income per tract to the predicted SNAP cutoff per tract indicates whether SNAP enrollment in that tract could be higher. For tracts where the median income is lower than the SNAP cutoff, at least 50% of the population should be eligible for SNAP. If SNAP enrollment is lower than 50%, then SNAP enrollment can likely be expanded in that tract.

Second, the ratio of SNAP-accepting stores per tract to SNAP-enrolled households per tract indicates whether SNAP supply meets demand. In the map below, tracts in yellow are those where the ratio of SNAP stores to SNAP-enrolled households is in the bottom quartile city-wide, suggesting that supply may be insufficient.

(Both these measures are imperfect. They are meant to give a sense of what tracts most need higher enrollment or better access, and what the approximate magnitude of that need is, but they cannot project an exact target enrollment percentage or ratio of SNAP stores to enrolled households.)

Finally, the locations of the only three large grocery stores or supermarkets are indicated in black, with purple buffers to indicate a walkable range (half a mile). Areas not covered by these buffers would likey benefit from increased access to large, high-quality grocery stores.

Addendum: SEPTA Routes

These maps below compare the most recently-available SEPTA bus and rail routes to the locations of active SNAP-accepting large grocery stores and supermarkets in and around the Promise Zone.

Further Questions

This report raises a few questions worth investigating further in order to better serve the Promise Zone community.

First, what explains the decline in SNAP sites from 2011 to the present? Was this a city-wide trend or did it only happen in the Promise Zone? Did these stores go out of business entirely or simply stop accepting SNAP? If the latter is true, how can they be convinced to accept SNAP again?

Second, how the quality of SNAP food offerings in the Promise Zone be assessed? Is it worth conducting a field survey of the 34 active SNAP-accepting stores in the Promise Zone?

Methodology

This report combines data from the Census Bureau’s 2015-20 American Communities Survey and from the USDA’s Food Retail Locator. These are imperfect data sources; the ACS relies on 5-year geographic estimates, and its most current year is 2020, while the USDA offers client-level data through 2022. However, when treated in combination and with caution, they can meaningfully inform Promise Zone policy.

This report refers to multiple new variables calculated based from ACS and USDA data:

First, the average SNAP income threshold per tract was calculated by taking the average household size per tract and plugging it into the formula for the annual per household SNAP income threshold, c = 12(1396 + (492(n-1))), where n is the number of members in the household.

phl_demos <- phl_demos |>
  mutate(perc_snap = snap_enroll / tot_hh * 100) |>
  mutate(avg_snap_thresh = (12*(1396 + (492*(avg_hh_size-1))))) 

Second, to identify tracts where there may be an opportunity for greater SNAP enrollment, the ratio of per tract median annual household income to per tract average SNAP income threshold is calculated. Then, for all tracts where this ratio is less than or equal to one, the difference of 50% and the tract’s percent SNAP enrollment is calculated (given that the median represents 50% of all incomes in the tract). All tracts where this difference is greater than zero indicate the approximate potential to expand SNAP enrollment per tract. They were identified with “Yes” or “No” values on the map, but can be moused over to see a better approximation of the magnitude of potential SNAP expansion.

phl_demos <- phl_demos |>
  mutate(med_hh_inc_as_perc_snap_thresh =  med_hh_inc / avg_snap_thresh)
  

active_phl_sites_by_tract$snap_expand = case_when(
                                         active_phl_sites_by_tract$med_hh_inc > active_phl_sites_by_tract$avg_snap_thresh ~ 0,
                                         active_phl_sites_by_tract$med_hh_inc <= active_phl_sites_by_tract$avg_snap_thresh ~ 
                                           (50 -   active_phl_sites_by_tract$perc_snap)
                                           )
                                           
active_phl_sites_by_tract$snap_expand_boolean = case_when(
                                         active_phl_sites_by_tract$med_hh_inc > active_phl_sites_by_tract$avg_snap_thresh ~ "No",
                                         active_phl_sites_by_tract$med_hh_inc <= active_phl_sites_by_tract$avg_snap_thresh ~ "Yes")