For this project, I downloaded the crime data for the city of Boston. There is a ton of opportunity for analysis in this dataset, but here I will just be looking at location data. I wanted to see what the crime in the city looks like at a high level.

First, let’s load our libraries. I used leaflet as my main mapping tool for this project.

library(leaflet)
library(dplyr)
library(RColorBrewer)
library(KernSmooth)
library(knitr)

Now, let’s load and prep the data.

mydata = read.csv('Crime_Incident_Reports.csv')

mydata = select(mydata, Crime = INCIDENT_TYPE_DESCRIPTION, Shooting, Year, Lat, Long)

kable(head(mydata))
Crime Shooting Year Lat Long
RESIDENTIAL BURGLARY No 2012 42.34638 -71.10379
AGGRAVATED ASSAULT Yes 2012 42.31684 -71.07458
ROBBERY No 2012 42.34284 -71.09699
COMMERCIAL BURGLARY No 2012 42.31644 -71.06583
ROBBERY No 2012 42.27052 -71.11990
ROBBERY Yes 2012 42.31328 -71.05300

Great. We won’t worry too much about messing with the data. Let’s just get started mapping!

Map 1: Shootings in Boston

First let’s look at where shootings occur.

shootings = filter(mydata, Shooting == 'Yes', !is.na(Lat), !is.na(Long))
shootingLoc = select(shootings, Long, Lat)

shootingHeat = bkde2D(shootingLoc, bandwidth = c(bw.nrd(shootingLoc[,1]), bw.nrd(shootingLoc[,2])))

x= shootingHeat$x1
y = shootingHeat$x2
z = shootingHeat$fhat

contour = contourLines(x,y,z)

shootings2012 = filter(shootings, Year == 2012, !is.na(Lat), !is.na(Long))
shootings2013 = filter(shootings, Year == 2013, !is.na(Lat), !is.na(Long))
shootings2014 = filter(shootings, Year == 2014, !is.na(Lat), !is.na(Long))
shootings2015 = filter(shootings, Year == 2015, !is.na(Lat), !is.na(Long))

shootingsPallette = brewer.pal(4, 'Dark2')

shootingsMap = leaflet(shootings2012) %>% 
  setView(lng=-71.0689, lat=42.32, 12) %>% 
  addProviderTiles('Esri.WorldGrayCanvas', group = 'Light Gray') %>% 
  addProviderTiles('Stamen.Toner', group = 'Black') %>%
  addCircles(data = shootings2012, ~Long, ~Lat, weight = 12, radius=12, 
                   color=shootingsPallette[1], stroke = TRUE, opacity = 0.3, fillOpacity = 0.3, group = '2012') %>% 
  addCircles(data = shootings2013, ~Long, ~Lat, weight = 12, radius=12, 
                   color=shootingsPallette[2], stroke = TRUE, opacity = 0.3, fillOpacity = 0.3, group = '2013') %>% 
  addCircles(data = shootings2014, ~Long, ~Lat, weight = 12, radius=12, 
                   color=shootingsPallette[3], stroke = TRUE, opacity = 0.3, fillOpacity = 0.3, group = '2014') %>% 
  addCircles(data = shootings2015, ~Long, ~Lat, weight = 12, radius=12, 
                   color=shootingsPallette[4], stroke = TRUE, opacity = 0.3, fillOpacity = 0.3, group = '2015') %>% 
  addPolygons(contour[[1]]$x, contour[[1]]$y, fillColor = 'purple', stroke = F, group = 'Heat', fillOpacity = .2) %>%
  addPolygons(contour[[2]]$x, contour[[2]]$y, fillColor = 'purple', stroke = F, group = 'Heat', fillOpacity = .2) %>%
  addPolygons(contour[[3]]$x, contour[[3]]$y, fillColor = 'purple', stroke = F, group = 'Heat', fillOpacity = .2) %>%
  addPolygons(contour[[4]]$x, contour[[4]]$y, fillColor = 'purple', stroke = F, group = 'Heat', fillOpacity = .2) %>%
  addPolygons(contour[[5]]$x, contour[[5]]$y, fillColor = 'purple', stroke = F, group = 'Heat', fillOpacity = .2) %>%
  addPolygons(contour[[6]]$x, contour[[6]]$y, fillColor = 'purple', stroke = F, group = 'Heat', fillOpacity = .2) %>%
  addLayersControl(baseGroups = c('Light Gray', 'Black'), 
                   overlayGroups = c('2012', '2013', '2014', '2015', 'Heat'), 
                   options = layersControlOptions(collapsed = FALSE))

shootingsMap

I think it’s interesting how concentrated shootings are in the city. Perhaps you’d expect more shootings to occur in ‘high crime’ areas, but we’ll see later that the section of Boston where most of the shootings happen is actually not the area with the highest crime density.

Map 2: Murders and Prostitution

Moving on, let’s take a look at murders and prostitution charges.

prostitution = filter(mydata, Crime == "PROSTITUTION CHARGES", !is.na(Lat), !is.na(Long))
murder = filter(mydata, Crime == "HOMICIDE", !is.na(Lat), !is.na(Long))

murderIcon = makeIcon('icons/gun2.png', 30,30)
prostitutionIcon = makeIcon('icons/prostitution2.png', 25,30)

murderProstMap = leaflet() %>%
  setView(lng=-71.0689, lat=42.32, 12) %>% 
  addProviderTiles('Esri.WorldGrayCanvas', group = 'Light Gray') %>% 
  addProviderTiles('Stamen.Toner', group = 'Black') %>%
  addMarkers(data = prostitution, lng = ~Long, lat = ~Lat, icon = prostitutionIcon, group = 'Prostitution') %>%
  addMarkers(data = murder, lng = ~Long, lat = ~Lat, icon = murderIcon, group = 'Murder') %>%
  addLayersControl(baseGroups = c('Light Gray', 'Black'), 
                   overlayGroups = c('Prostitution', 'Murder'), 
                   options = layersControlOptions(collapsed = FALSE))

murderProstMap

What strikes me most about this map is where all the prostitution charges are happening. Almost all of them are on Blue Hill Ave, and Dorcester Ave. What might explain such a dramaitic distribution? It could be that those streets are ‘the place’ if you’re interested in that sort of thing. I’m a little skeptical that in 2015, prostitutes are just hanging around Dorcester Ave though. We have the internet. Perhaps this deserves some further analysis at a later time. It’s worth noting that this data only contains information that the police know. In this sense, it does not contain records on every crime committed, it only contains records for crimes that the police know about.

The map of murders seems to make a nice subset of the shootings map, which makes sense. Perhaps not every murder was a shooting, but it seems to make sense that the areas of shootings and murders would overlap.

Based on the previous two maps, it would seem like southern Boston has the most crime. However, we’ve only looked at low volume crimes. A murder doesn’t happen every day. But I bet something like aggrivated assault or DUI does. Let’s see which crimes happen the most often in Boston.

count = as.data.frame(table(mydata$Crime))
count = count = count[order(-count$Freq),]
kable(head(count,10))
Var1 Freq
120 VAL 27363
82 OTHER LARCENY 24443
114 SIMPLE ASSAULT 17697
70 MedAssist 17128
75 MVAcc 13832
122 VANDALISM 13339
56 InvPer 12937
64 LARCENY FROM MOTOR VEHICLE 12742
30 DRUG CHARGES 12042
43 FRAUD 8742

As we can see, petty crimes are committed far more often than more serious crimes, such as those that involve a shooting.

Map 3: Heat Map of All Crime

With this new crime-agnostic view, let’s look at our new crime map.

totalCrime = filter(mydata, !is.na(Lat), !is.na(Long))
locations = select(totalCrime, Long, Lat)
sampleLocations = locations[sample(nrow(locations), 5000),]

heatMapPrep = bkde2D(locations, bandwidth = c(bw.nrd(locations[,1]), bw.nrd(locations[,2])))

x=heatMapPrep$x1
y = heatMapPrep$x2
z = heatMapPrep$fhat

contour = contourLines(x,y,z)

heatPallete = brewer.pal(11, 'Spectral')

crimeHeatMap = leaflet(totalCrime) %>%
setView(lng=-71.0689, lat=42.32, 12) %>% 
addProviderTiles('Stamen.Toner') %>%
addCircles(data = sampleLocations, ~Long, ~Lat, weight = 12, radius = 12, opacity = 0.3, fillOpacity = 0.3, color = 'grey') %>%
addPolygons(contour[[1]]$x, contour[[1]]$y, fillColor = heatPallete[10], stroke = F, fillOpacity = .35) %>%
addPolygons(contour[[2]]$x, contour[[2]]$y, fillColor = heatPallete[10], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[3]]$x, contour[[3]]$y, fillColor = heatPallete[10], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[4]]$x, contour[[4]]$y, fillColor = heatPallete[9], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[5]]$x, contour[[5]]$y, fillColor = heatPallete[9], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[6]]$x, contour[[6]]$y, fillColor = heatPallete[9], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[7]]$x, contour[[7]]$y, fillColor = heatPallete[8], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[8]]$x, contour[[8]]$y, fillColor = heatPallete[8], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[9]]$x, contour[[9]]$y, fillColor = heatPallete[8], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[10]]$x, contour[[10]]$y, fillColor = heatPallete[7], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[11]]$x, contour[[11]]$y, fillColor = heatPallete[7], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[12]]$x, contour[[12]]$y, fillColor = heatPallete[7], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[13]]$x, contour[[13]]$y, fillColor = heatPallete[7], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[14]]$x, contour[[14]]$y, fillColor = heatPallete[6], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[15]]$x, contour[[15]]$y, fillColor = heatPallete[6], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[16]]$x, contour[[16]]$y, fillColor = heatPallete[6], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[17]]$x, contour[[17]]$y, fillColor = heatPallete[6], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[18]]$x, contour[[18]]$y, fillColor = heatPallete[5], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[19]]$x, contour[[19]]$y, fillColor = heatPallete[5], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[20]]$x, contour[[20]]$y, fillColor = heatPallete[5], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[21]]$x, contour[[21]]$y, fillColor = heatPallete[5], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[22]]$x, contour[[22]]$y, fillColor = heatPallete[4], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[23]]$x, contour[[23]]$y, fillColor = heatPallete[4], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[24]]$x, contour[[24]]$y, fillColor = heatPallete[4], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[25]]$x, contour[[25]]$y, fillColor = heatPallete[4], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[26]]$x, contour[[26]]$y, fillColor = heatPallete[3], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[27]]$x, contour[[27]]$y, fillColor = heatPallete[3], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[28]]$x, contour[[28]]$y, fillColor = heatPallete[3], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[29]]$x, contour[[29]]$y, fillColor = heatPallete[3], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[30]]$x, contour[[30]]$y, fillColor = heatPallete[2], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[31]]$x, contour[[31]]$y, fillColor = heatPallete[2], stroke = F, fillOpacity = .35) %>%
  addPolygons(contour[[32]]$x, contour[[32]]$y, fillColor = heatPallete[2], stroke = F, fillOpacity = .3) %>%
  addPolygons(contour[[33]]$x, contour[[33]]$y, fillColor = heatPallete[2], stroke = F, fillOpacity = .3) %>%
  addPolygons(contour[[34]]$x, contour[[34]]$y, fillColor = heatPallete[1], stroke = F, fillOpacity = .3)

crimeHeatMap

Note that the grey marks representing crimes are a sample of all crime, so that the graphic remains responsive. We can now see that downtown has the highest crime density, and there is a bit of a second peak in the southern boston area that we identified earlier.