Searching for Food Deserts in Los Angeles County
By: Josh Mancuso
Tags: Python, Data Science, Data Visualization, Pandas, Food

img source: robrogers.com

For a recent data science project, I collaborated with several other Lambda School students to search for food deserts in L.A. County. A general definition for what qualifies as a food desert is an area that does not have access, within one mile, to a grocery store/market providing fresh, healthy food options, such as fruits, vegetables, meats, etc. We wanted to test the theory that lower income neighborhoods are more likely to live in a food desert.

Step 1: Source the data

There were 3 main types of data we needed to find for this project: data for grocery stores/markets in L.A. County, geographic data for each zip code in L.A. County, and finally, income data for each zip code.

For the grocery stores, we sourced data from the SNAP retailers database for all of CA and then filtered for L.A. County. Unfortunately, this data was overly inclusive, as it contained many convenience stores, liquor stores, and other random establishments that should not qualify as providing access to healthy and fresh food. Using regex, we tried to eliminate as many of these non-qualifying establishments as possible. Finally, we used the latitude and longitude for each market and generated a Point object using the Shapely library.

Next, we were able to find geographic information for each zip code in L.A. County. However, due to the curvature of the earth, we needed to convert this data into a 2-dimensional representation, or projection, so that we can display it graphically. We read the data into a geopandas dataframe, and then called the to_crs() method, and pass in ‘epsg=4326’ which converts the geographic data from GIS coordinates to latitude/longitude, and also formats the zip code polygon objects to display properly in a 2-d visualization.

The final data we sourced was median household income for each zip code in L.A. County, which we store in a pandas dataframe to be merged with food desert and grocery density later in the project.

.

Step 2: Perform a Grid Search over all of L.A. County

We wanted to do an exhaustive grid search of the entirety of L.A. County in order to identify the locations of any food deserts, as well as compile some summary statistics for each zip code such as average number of grocery stores within 1 mile in order to calculate a grocery density metric for each zip code. Then later we would compare this data with median income for each zip code and examine the results.

First, for each zip code, we created a rectangular shape around the geographic bounds of the zip code, then starting in the northwestern corner, iterated in quarter mile steps horizontally (eastward), then started again at the western edge but stepped down one quarter mile south, and iterated across horizontally again, repeating this process until the entire area had been exhausted. At each step, we generated a Shapely Point object and appended to a list for that zip code. We added this list of Points inside each zip code to a new column in the geographic dataframe. For all of L.A. county, this resulted in just under 58,000 points.

Next, for each zip code, we iterated over each point in the list of points. For each point, we used the buffer() method to draw a circle with radius of one mile around each point and save that circle as a Shapely Polygon object. Because each shapely Polygon object contains the values for all coordinates that comprise the exterior of the shape of the object, we can call the contains() method for that polygon and pass in a coordinate. This method returns a Boolean True or False whether or not that given coordinate is located in the interior of that Polygon object.

In order to test for food deserts, we can check the coordinates of all of our markets to see if they exist inside the boundaries of that circle. However, the dataframe contains several thousand markets, which would be computationally quite expensive to perform this check for every market for every single generated Point in L.A. County. Instead of iterating thru every market in our dataframe, we can speed up the process significantly by first filtering for markets whose coordinates are within a square boundary of our circle, which we can access using the bounds property of the circle (a Shapely Polygon object). Now our list of markets to test is significantly reduced. For each market inside the square, if the circle object contains the market coordinates, we increase the count of number of groceries inside the circle.

Finally, in order to create a grocery density statistic as well as a master list of all points that were food deserts (no markets found inside the one mile radius circle for that point), we made a function to iterate over each zip code, and then each point in its list of points. We initialized a counter for each zip code that would record the number of food deserts found among the list of points. We also initialized two empty lists, one to record the locations of any food deserts found, and one to record the number of groceries inside the circle for each point in the list.

After iterating through each zip code, we unpacked the results to create a master list of all food deserts found, as well as to compute summary statistics by zip code such as grocery density (average number of nearby groceries found, as well as the total number of food deserts for each zip code).

Step 3: Analyze the results and create visualizations:

After unpacking the results of the exhaustive grid search, we first wanted to create a visualization of where the food deserts were found. As you can see, the majority of them are located in the northern half of and western portions L.A. County. These are areas that are generally either national parks, mountainous regions, or even actual deserts, so these food deserts existing there makes perfect sense.

Next, we plotted the grocery store density by zip code. As you can see, the density was highest in the heart of the city.

Lastly, we wanted to examine the grocery density relative to income, so we binned incomes into 4 ranges, and plotted the average grocery density for each bin.

Interestingly, our results seemed to indicate that lower income areas actually had a higher grocery store density than higher income areas.

Conclusions/Takeaways:

After consideration of why our results were paradoxical to our expectations, we learned a couple of good data science lessons. First, the results of any data science project will generally ultimately hinge on the quality of the underlying data. In our case, the underlying data for our list of markets and grocery stores was overly broad. Even after our best efforts at manually filtering out disqualifying establishments using regex, there were still several hundred to perhaps thousands of ‘markets’ that probably should not have qualified. Unfortunately, we did not have enough man hours to go through each establishment one by one and make a determination of whether or not healthy/fresh food was available.

Also, due to the unique topography of Los Angeles, many wealthy areas are located in the sprawling hills/mountainside and beachfront areas. Most of these wealthier households own cars and have no issue driving more than one mile to a grocery store. Conversely, lower income households are often in more dense urban areas. Members of these households often don’t own a vehicle, but can get around by walking, biking, or using public transportation. Since the overall density of these area is higher, and because our dataframe of markets included many corner stores, bodegas, or ethnic stores/retail establishments, the grocery density in these areas ended up higher than for the high income zip codes.

Overall, I thought this was a great and enjoyable project. I gained some experience w some new python libraries, including GeoPandas, Shapely, pyproj, made some neat geographic data visualizations, and learned some valuable data science lessons along the way.

If interested in checking out some of our code, here is a link to the notebook for this blog, and here is a link to our group repo