16 Interactive maps

While maps of data are useful, their ability to show incident-level information is quite limited. They tend to show broad trends - where crime happened in a city - rather than provide information about specific crime incidents. While broad trends are important, there are significant drawbacks about being unable to get important information about an incident without having to check the data. An interactive map bridges this gap by showing trends while allowing you to zoom into individual incidents and see information about each incident.

For this lesson we will continue to use the officer shooting data so let’s load that.

load("data/officer_shootings_geocoded.rda")

16.1 Why do interactive graphs matter?

16.1.1 Understanding your data

The most important thing to learn from this course is that understanding your data is crucial to good research. Making interactive maps is a very useful way to better understand your data as you can immediately see geographic patterns and quickly look at characteristics of those incidents to understand them.

In this lesson we will make a map of each officer-involved shooting that lets you click on the shooting and see some information about it. If we see a cluster of shootings, we can click on each shooting to see if they are similar. Though it is possible to find these patterns just looking at the data, it is easier to be able to see a geographic pattern and immediately look at information about each incident.

16.1.2 Police departments use them

Interactive maps are popular in large police departments such as Philadelphia and New York City. They allow easy understanding of geographic patterns in the data and, importantly, allow such access to people who do not have the technical skills necessary to create the maps. If nothing else, learning interactive maps will help you with a future job.

16.2 Making the interactive map

As usual, let’s take a look at the top 6 rows of the data.

head(officer_shootings_geocoded)
#>   shooting_number                                 location      dates
#> 1           19-04      4900 Hazel Avenue, Philadelphia, PA 2019-03-06
#> 2           19-06      1300 Kater Street, Philadelphia, PA 2019-03-28
#> 4           19 11     2100 Taney Terrace, Philadelphia, PA 2019-04-25
#> 5           19-13   1800 N. Broad Street, Philadelphia, PA 2019-05-11
#> 6           19 14          3400 G Street, Philadelphia, PA 2019-05-20
#> 7           18-01 2800 Kensington Avenue, Philadelphia, PA 2018-01-13
#>         lon      lat
#> 1 -75.22087 39.95046
#> 2 -75.16355 39.94289
#> 4 -75.19104 39.92646
#> 5 -75.15754 39.98030
#> 6 -75.11482 39.99991
#> 7 -75.12253 39.99151

This data is fairly sparse about information regarding the shooting. All it has is the date , shooting number, and address (which isn’t that useful as location is already covered by the map). The level of detail about the crime may be sparse, but we can still create a map where you can click an incident dot on the map and a popup will tell you when it happened.

We will use the package leaflet for our interactive map. leaflet produces maps similar to Google Maps with circles (or any icon we choose) for each value we add to the map. It allows you to zoom in, scroll around, and provides context to each incident that isn’t available on a static map.

install.packages("leaflet")
library(leaflet)

To make a leaflet map we need to run the function leaflet() and add a tile to the map. A tile is simply the background of the map. This website provides a large number of potential tiles to use, though many are not relevant to our purposes of crime mapping.

We will use a standard tile from Open Street Maps. This tile gives street names and highlights important features such has parks and large stores which provides useful contexts for looking at the data. The attribution parameter isn’t strictly necessary but it is good form to say where your tile is from.

leaflet() %>% 
  addTiles('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
           attribution = '&copy; <a href="http://openstreetmap.org">
                OpenStreetMap</a> contributors')

When you run the above code it shows a world map (copied several times). Zoom into it and it’ll start showing relevant features of wherever you’re looking.

Note the %>% between the leaflet() function and the addTiles() function. This is called a “pipe” in R and is used like the + in ggplot() to combine multiple functions together. This is used heavily in what is called the “tidyverse”, a series of packages that are prominent in modern R and useful for data analysis. We won’t be covering them in this book but for more information on them you can check the tidyverse website. For this lesson you need to know that each piece of the leaflet function must end with %>% for the next line to work.

To add the points to the graph we use the function addMarkers() which has two parameters, lng and lat. For both parameters we put the column in which the longitude and latitude are, respectively.

leaflet() %>% 
  addTiles('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
           attribution = '&copy; <a href="http://openstreetmap.org">
                OpenStreetMap</a> contributors') %>%
  addMarkers(lng = officer_shootings_geocoded$lon, 
             lat = officer_shootings_geocoded$lat)

It now adds an icon indicating where every shooting in our data is. You can zoom in and scroll around to see more about where the shootings happen. These icons are a bit large, covering nearly all of the city and making it hard to see where shootings happen. To change the icons to circles we can change the function addMarkers() to addCircleMarkers(), keeping the rest of the code the same,

leaflet() %>% 
  addTiles('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
           attribution = '&copy; <a href="http://openstreetmap.org">
                OpenStreetMap</a> contributors') %>%
  addCircleMarkers(lng = officer_shootings_geocoded$lon, 
                   lat = officer_shootings_geocoded$lat)

This makes the icon into circles but they are still large and cover most of the map. To adjust the size of our icons we use the radius parameter in addMarkers() or addCircleMarkers(). The larger the radius, the larger the icons.

leaflet() %>% 
  addTiles('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
           attribution = '&copy; <a href="http://openstreetmap.org">
                OpenStreetMap</a> contributors') %>%
  addCircleMarkers(lng = officer_shootings_geocoded$lon, 
                   lat = officer_shootings_geocoded$lat,
                   radius = 5)

Setting the radius option to 5 shrinks the size of the icon a lot. In your own maps you’ll have to fiddle with this option to get it to look the way you want. Let’s move on to adding information about each icon when clicked upon.

16.3 Adding popup information

The parameter popup in the addMarkers() or addCircleMarkers() functions lets you input a character value (if not already a character value it will convert it to one) and that will be shown as a popup when you click on the icon. Let’s start simple here by inputting the dates column in our data and then build it up to a more complicated popup.

leaflet() %>% 
  addTiles('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
           attribution = '&copy; <a href="http://openstreetmap.org">
                OpenStreetMap</a> contributors') %>%
  addCircleMarkers(lng = officer_shootings_geocoded$lon, 
                   lat = officer_shootings_geocoded$lat,
                   radius = 5,
                   popup = officer_shootings_geocoded$dates)

Try clicking around and you’ll see that the data of the incident you clicked on appears over the dot. Though fairly clear in this case, we usually want to have a title indicating what the value in the popup means. We can do this by using the paste() function to combine text explaining the value with the value itself. Let’s add the words “Date of Shooting:” before the date.

leaflet() %>% 
  addTiles('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
           attribution = '&copy; <a href="http://openstreetmap.org">
                OpenStreetMap</a> contributors') %>%
  addCircleMarkers(lng = officer_shootings_geocoded$lon, 
                   lat = officer_shootings_geocoded$lat,
                   radius = 5,
                   popup = paste("Date of Shooting:", officer_shootings_geocoded$dates))

We don’t have many other columns but we can add the location and shooting number to the popup by adding them to the paste() function we’re using.

leaflet() %>% 
  addTiles('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
           attribution = '&copy; <a href="http://openstreetmap.org">
                OpenStreetMap</a> contributors') %>%
  addCircleMarkers(lng = officer_shootings_geocoded$lon, 
                   lat = officer_shootings_geocoded$lat,
                   radius = 5,
                   popup = paste("Shooting Number:", officer_shootings_geocoded$shooting_number,
                                 "Date:", officer_shootings_geocoded$dates,
                                 "Location:", officer_shootings_geocoded$location))

Just adding the location text makes it try to print out everything on one line which is hard to read. If we add the text <br> where we want a line break it will make one. <br> is the HTML tag for line-break which is why it works making a new line in this case.

leaflet() %>% 
  addTiles('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
           attribution = '&copy; <a href="http://openstreetmap.org">
                OpenStreetMap</a> contributors') %>%
  addCircleMarkers(lng = officer_shootings_geocoded$lon, 
                   lat = officer_shootings_geocoded$lat,
                   radius = 5,
                   popup = paste("Shooting Number:", officer_shootings_geocoded$shooting_number,
                                 "<br>",
                                 "Date:", officer_shootings_geocoded$dates,
                                 "<br>",
                                 "Location:", officer_shootings_geocoded$location))

16.4 Dealing with too many markers

Even though we shrunk the size of the circles, it is still rather hard to see any trends as there are so many incidents and relatively large circles. One solution is to keep shrinking the size of the circles, but this quickly becomes a bad solution when using more frequent data such as a crime data set (Philadelphia data alone has about 200k crimes reported per year). The other solution is to cluster the data into groups where the dots only show if you zoom down.

If we add the code clusterOptions = markerClusterOptions() to our addCircleMarkers() it will cluster for us.

leaflet() %>% 
  addTiles('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
           attribution = '&copy; <a href="http://openstreetmap.org">
                OpenStreetMap</a> contributors') %>%
  addCircleMarkers(lng = officer_shootings_geocoded$lon, 
                   lat = officer_shootings_geocoded$lat,
                   radius = 5,
                   popup = paste("Shooting Number:", officer_shootings_geocoded$shooting_number,
                                 "<br>",
                                 "Date:", officer_shootings_geocoded$dates,
                                 "<br>",
                                 "Location:", officer_shootings_geocoded$location),
                   clusterOptions = markerClusterOptions())

Incidents close to each other are grouped together in fairly arbitrary groupings and we can see how large each grouping is by moving our cursor over the circle. Click on a circle or zoom in and and it will show smaller groupings at lower levels of aggregation. Keep clicking or zooming in and it will eventually show each incident as its own circle.

This method is very useful for dealing with huge amounts of data as it avoids overflowing the map with too many icons at one time. A downside, however, is that the clusters are created arbitrarily meaning that important context, such as neighborhood, can be lost.

16.5 Interactive choropleth maps

In Chapter 15 we worked on choropleth maps which are maps with shaded regions, such as states colored by which political party won them in an election. Here we will make interactive choropleth maps where you can click on a shaded region and see information about that region. We’ll make the same map as before - Census tracts with the number of officer-involved shootings.

Let’s load the tract-level officer-involved shooting data we made earlier.

load("data/philly_tracts_shootings.rda")

We’ll begin the leaflet map similar to before but use the function addPolygons() and our input here is the geometry column of philly_tracts_shootings.

leaflet()  %>% 
  addTiles('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
           attribution = '&copy; <a href="http://openstreetmap.org">
                OpenStreetMap</a> contributors') %>%
  addPolygons(data = philly_tracts_shootings$geometry)
#> Warning: sf layer is not long-lat data
#> Warning: sf layer has inconsistent datum (+proj=lcc +lat_1=40.96666666666667 +lat_2=39.93333333333333 +lat_0=39.33333333333334 +lon_0=-77.75 +x_0=600000 +y_0=0 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=us-ft +no_defs).
#> Need '+proj=longlat +datum=WGS84'