Using Contour Lines to Map Density of NYPD's Stop and Frisk Data
Using elevation contour lines is one of several ways to visualize density from vector point data. I recently used this approach to map “stop and frisks” - the practice of a police officer stopping an individual for reasonable suspicion of criminal activity - in New York City. This practice is often criticized as happening more often in neighborhoods with higher numbers of minority residents, so showing the density of such stops is critical to the story behind this data. If you would like to know more about Stop and Frisk, check out the research conducted by the Center for Constitutional Rights and the New York Civil Liberties Union.
In this post I’ll discuss how I visualized the point density of stop and frisk occurrences using the NYPD’s Stop and Frisk databases. To get an idea of some of the others challenges faced and approaches in mapping this data, check out the Data Without Borders Data Dive NYC wiki page or my precinct github repo.
From points to heat map to contour lines
From the larger Stop and Frisk database, which contains more than 4.2 million records, I selected only stops that occurred in 2008 where the person stopped was either “Black”, “Latino,” or “Black Latino”, and which contained coordinates for the stop.
ogr2ogr -f SQLite /path/to/outfile/sf-race-geo.sqlite PG:"dbname='' host='localhost' port='5432'" -sql "SELECT distinct ogc_fid, lat_lon, year, race, pct FROM public."new_all" where year = '2008' AND xcoord <> '' AND (race = 'B' OR race = 'Q' OR race = 'P')"
This resulted in 432,515 records, which are available as an sqlite file.
For some uses, you can visualize density using only the point data and manipulating the opacity and size of markers. I used this method to create this map, which shows all of the stops from 2008.
However, in this case I wanted to really draw attention to the neighborhoods where stop and frisks happened most frequently. To better achieve this, I created elevation contours based off of vector points to show the density of the stops. Here’s how I did that.
- Using QGIS’ heat map plugin located under the Raster Menu, create a raster heat map from the input point vector blstops2008
- I used a buffer radius of 10 and a decay ratio of .5.
- Load the heat map raster layer in QGIS. It will be a .tif file if you chose GeoTIFF in the step above.
- Verify the heat map worked by opening up the layer properties for your .tif file in QGIS.
- Set color map to Pseudocolor
This is what mine looked like. Success!
Next we convert the raster heat map into vector contours using the gdal_contour option available in QGIS under the raster / extraction menu.
I chose to use an interval value of 150 and selected to include an ELEV attribute column. This is the column we’ll be basing our Carto styling in TileMill.
You can now run the contour from within QGIS, or you can copy the script at the bottom of the prompt and execute it on the command line.
gdal_contour -a ELEV -i 150.0 /path/to/input/blstops2008.tif /path/to/output/bl-contours-2008.shp
Preview the contours in QGIS, selecting categorized styling based on the ELEV column.
Now we have everything we need to style the vector contours in TileMill.
- Import the shapefile into TileMill.
- Style the layer based on the ELEV column. I chose to do this using the spin(color, spin distance) Carto feature to save myself some key strokes. Here’s the full stylesheet.
I added a black background for previewing the map but removed it before exporting. Now the contours are a transparent layer that we can composite with other layers.
Export to MBTiles and upload to MapBox. If you don’t have an account yet, sign up for one.
Use the map layer as a layer itself, or as part of a mash up. Here are the contour lines – which show concentrations of stops of Black, Latino, and Black Latino persons in 2008 – on top of all of the points from all stops in 2008. To add geographical context, I used a MapBox Streets base layer.
Final density map using contour lines
Here’s our final map.