Spatial Analysis of Crime Data with sf
Build an R analysis that maps where crimes happen across a city. You'll load real, publicly available crime incident data with coordinates, match each incident to the neighborhood it happened in using a spatial join, and calculate incident density per square kilometer and per 1,000 residents. The result is a set of choropleth maps (maps where each neighborhood is colored by its value), with the top hotspots labeled, plus a short report on what the data shows. You'll learn how to work with spatial data in R using the sf package: reading shapefiles and GeoJSON, coordinate reference systems, spatial joins, area calculations, and mapping with ggplot2's geom_sf. A good choice of data is the City of Chicago's open data portal, which publishes both crime incidents and neighborhood (community area) boundaries.
- Estimate
- ~13h
- Steps
- 7
- Completed by
- 0
- Proposed by
- codeseed.app
sf · ggplot2 · dplyr
Project roadmap
- 01
Get the data
~1.5hDownload one year of crime incidents with latitude and longitude, and the matching neighborhood boundaries, from a city's open data portal (Chicago's works well), plus a population figure per neighborhood. Put the files in data/raw/ and write down the source URL and download date of each in data/README.md. Done when: the three files and data/README.md are in the repo.
- 02
Load and clean the data with sf
~2hRead the boundaries with sf::st_read. Read the incidents with readr, drop rows with missing or clearly wrong coordinates (for example 0,0), and convert them to an sf object with st_as_sf, using crs = 4326 (latitude/longitude). Print how many rows were kept and dropped. Done when: both datasets load as sf objects and the cleaning numbers are printed.
- 03
Match coordinate systems
~1.5hLatitude/longitude can't be used to measure areas in km², so transform both layers with st_transform to a projected coordinate system in meters suited to the city (for Chicago, EPSG:26971 or UTM zone 16N). Check with st_crs that both layers now use the same system. Done when: both layers share the same projected CRS.
- 04
Count incidents per neighborhood
~2hUse st_join to attach each incident to the neighborhood polygon that contains it, then count incidents per neighborhood with dplyr, including neighborhoods with zero incidents. Report how many incidents fell outside every boundary. Done when: a table has one row per neighborhood with its incident count.
- 05
Compute density measures
~1.5hCalculate each neighborhood's area in km² with st_area, then two measures: incidents per km² and incidents per 1,000 residents, joined from the population data. Explain in a comment why the two measures can rank neighborhoods differently. Done when: both measures are computed for every neighborhood.
- 06
Build choropleth maps
~2.5hPlot each measure as a map with ggplot2's geom_sf, using a sequential color scale such as viridis, a clear title and a legend with units. Label the 10 highest-density neighborhoods with their names using geom_sf_text or geom_sf_label, and save the maps as PNG files in output/. Done when: both maps are saved in output/ with the top 10 neighborhoods labeled.
- 07
Write a short report
~2hCreate a Quarto or R Markdown report that runs the whole analysis from the raw files and includes both maps, a table of the top 10 neighborhoods for each measure, and a few paragraphs on what the maps show and their limits, such as reporting differences and population estimates. Done when: rendering the report from a clean session produces an HTML file with the maps, tables and text.
Ready to build this?
Get a GitHub repo and start building. Your AI reviewer checks each step as you go.
Tech stack