R Markdown

This is an R Markdown document. Markdown is a simple formatting syntax for authoring HTML, PDF, and MS Word documents. For more details on using R Markdown see http://rmarkdown.rstudio.com.

When you click the Knit button a document will be generated that includes both content as well as the output of any embedded R code chunks within the document. You can embed an R code chunk like this:

summary(cars)
##      speed           dist       
##  Min.   : 4.0   Min.   :  2.00  
##  1st Qu.:12.0   1st Qu.: 26.00  
##  Median :15.0   Median : 36.00  
##  Mean   :15.4   Mean   : 42.98  
##  3rd Qu.:19.0   3rd Qu.: 56.00  
##  Max.   :25.0   Max.   :120.00

Including Plots

You can also embed plots, for example:

title: “Problem Set 4-4: Area-Based Crime Mapping” output: html_document —

Introduction

This module demonstrates area-based crime mapping using choropleth maps. We compare counts and rates across geographic areas and explore both static and interactive mapping.


1. Setup Packages

packages <- c("sf", "tidyverse", "ggspatial", "sfhotspot", "leaflet", "htmltools")

for (p in packages) {
  if (!require(p, character.only = TRUE)) {
    install.packages(p)
    library(p, character.only = TRUE)
  }
}

2. Mexico City (CDMX) Example – Car Jacking Counts

2.1 Load Data

cdmx_car_jacking <- sf::read_sf(
  "https://mpjashby.github.io/crimemappingdata/cdmx_car_jacking.gpkg"
)

cdmx_borough <- sf::read_sf(
  "https://mpjashby.github.io/crimemappingdata/cdmx_alcaldias.gpkg"
)

2.2 View Points on Map

bb <- sf::st_bbox(cdmx_borough)
cdmx_pts_crop <- sf::st_crop(cdmx_car_jacking, bb)
## Warning: attribute variables are assumed to be spatially constant throughout
## all geometries
ggplot() +
  geom_sf(data = cdmx_borough, fill = NA, linewidth = 0.3) +
  geom_sf(data = cdmx_pts_crop, size = 0.3, alpha = 0.6) +
  labs(title = "CDMX Car-Jacking Points") +
  theme_void()


2.3 Count Crimes Per Borough

car_jacking_counts <- sfhotspot::hotspot_count(
  data = cdmx_car_jacking,
  grid = cdmx_borough
)
## Warning in sfhotspot::hotspot_count(data = cdmx_car_jacking, grid = cdmx_borough): `data` has points with the co-ordinates "0, 0".
## ℹ This usually indicates a problem with the data.
## ℹ Check co-ordinates are correct (e.g. by mapping them).
## Warning: 152 point is outside the area covered by the supplied polygons.
counts_table <- sf::st_drop_geometry(car_jacking_counts)

counts_table |> 
  arrange(desc(n)) |> 
  slice(1:10)
## # A tibble: 10 × 3
##    municip nomgeo                  n
##      <int> <chr>               <int>
##  1       5 Gustavo A. Madero     545
##  2       7 Iztapalapa            492
##  3      12 Tlalpan               277
##  4       2 Azcapotzalco          210
##  5      10 Álvaro Obregón        174
##  6       3 Coyoacán              152
##  7      16 Miguel Hidalgo        152
##  8      14 Benito Juárez         140
##  9      17 Venustiano Carranza   113
## 10      13 Xochimilco             98

2.4 Choropleth Map (Counts)

ggplot(car_jacking_counts) +
  geom_sf(aes(fill = n), alpha = 0.7, color = NA) +
  scale_fill_distiller(palette = "Oranges", direction = 1) +
  labs(
    title = "Car-Jacking Incidents by Alcaldía (CDMX, 2019)",
    fill = "Number of Offences"
  ) +
  theme_void()


3. Uttar Pradesh Example – Interactive Mapping

3.1 Load Data

murders <- readr::read_csv(
  "https://mpjashby.github.io/crimemappingdata/uttar_pradesh_murders.csv",
  show_col_types = FALSE
)

districts <- sf::read_sf(
  "https://mpjashby.github.io/crimemappingdata/uttar_pradesh_districts.gpkg"
)

3.2 Join Murder Counts to Districts

district_murders <- districts |>
  dplyr::left_join(
    murders,
    by = dplyr::join_by(district_name == district)
  )

glimpse(district_murders)
## Rows: 75
## Columns: 4
## $ state         <chr> "Uttar Pradesh", "Uttar Pradesh", "Uttar Pradesh", "Utta…
## $ district_name <chr> "Agra", "Bareilly", "Etah", "Shahjahanpur", "Pilibhit", …
## $ geom          <POLYGON [°]> POLYGON ((77.6444 27.23444,..., POLYGON ((78.974…
## $ murder        <dbl> 178, 132, 65, 88, 54, 132, 72, 68, 36, 55, 25, 32, 68, 1…

3.3 Interactive Choropleth (Counts)

colours_red <- leaflet::colorNumeric(
  palette = "Reds",
  domain = district_murders$murder
)

leaflet(district_murders) |>
  addProviderTiles("CartoDB.Voyager") |>
  addPolygons(
    fillColor = ~ colours_red(murder),
    fillOpacity = 0.75,
    label = ~ district_name,
    weight = 1,
    color = "black"
  ) |>
  addLegend(
    pal = colours_red,
    values = ~ murder,
    title = "Number of Murders"
  )

4. Rates vs Counts

4.1 Add Population Data & Compute Rate

district_pop <- readr::read_csv(
  "https://mpjashby.github.io/crimemappingdata/uttar_pradesh_population.csv",
  show_col_types = FALSE
)

district_murders_rate <- districts |>
  left_join(murders, by = join_by(district_name == district)) |>
  left_join(district_pop, by = join_by(district_name == district)) |>
  mutate(
    murder_rate = murder / (population / 100000)
  )

glimpse(district_murders_rate)
## Rows: 75
## Columns: 11
## $ state         <chr> "Uttar Pradesh", "Uttar Pradesh", "Uttar Pradesh", "Utta…
## $ district_name <chr> "Agra", "Bareilly", "Etah", "Shahjahanpur", "Pilibhit", …
## $ geom          <POLYGON [°]> POLYGON ((77.6444 27.23444,..., POLYGON ((78.974…
## $ murder        <dbl> 178, 132, 65, 88, 54, 132, 72, 68, 36, 55, 25, 32, 68, 1…
## $ code          <chr> "AG", "BR", "ET", "SJ", "PI", "AH", "PR", "JH", "JL", "J…
## $ headquarters  <chr> "Agra", "Bareilly", "Etah", "Shahjahanpur", "Pilibhit", …
## $ division      <chr> "Agra", "Bareilly", "Aligarh", "Bareilly", "Bareilly", "…
## $ population    <dbl> 4418797, 4448359, 1774480, 3006538, 2031007, 5954391, 32…
## $ area          <dbl> 4041, 2688, 2431, 4388, 3686, 5482, 3717, 5024, 4565, 40…
## $ density_km2   <dbl> 1093, 1655, 730, 685, 551, 1086, 863, 398, 370, 1113, 56…
## $ murder_rate   <dbl> 4.0282457, 2.9673864, 3.6630449, 2.9269545, 2.6587796, 2…

4.2 Interactive Choropleth (Rates)

colours_rate <- leaflet::colorNumeric(
  palette = "Reds",
  domain = district_murders_rate$murder_rate
)

leaflet(district_murders_rate) |>
  addProviderTiles(
    "Esri.WorldImagery",
    options = providerTileOptions(opacity = 0.3)
  ) |>
  addPolygons(
    fillColor = ~ colours_rate(murder_rate),
    fillOpacity = 0.75,
    label = ~ district_name,
    weight = 1,
    color = "black"
  ) |>
  addLegend(
    pal = colours_rate,
    values = ~ murder_rate,
    title = htmltools::HTML("Murders per<br>100,000 Residents")
  )