Introduction

The original frequency-class map displays aggravated assaults reported in Nashville during the summer of 2026. Larger circles represent locations with more incidents, while three different colors distinguish incident-frequency categories.

Although the original map effectively illustrates the geographic distribution of reported assaults, it can become difficult to interpret when multiple categories appear simultaneously.

The purpose of this project is to improve the original map by adding interactive frequency filters. These filters allow viewers to explore individual categories and identify locations where multiple incidents were recorded.

Modified Interactive Map

The revised map below allows viewers to turn individual incident-frequency categories on and off using the layer control in the upper-right corner.

Incident Summary

The dataset contains 1,220 mapped aggravated assault records across 625 unique coordinate locations during the period examined.

These totals describe the retrieved records with usable coordinates, rather than necessarily representing every aggravated assault that occurred in Nashville.

Why the Modified Map Is Better

The original frequency-class map displays all three incident categories simultaneously. Although the different colors and circle sizes help distinguish locations, viewers cannot easily isolate particular categories.

The revised map introduces interactive filtering. Viewers can hide locations with only one incident and concentrate on locations where multiple incidents were recorded.

This improvement makes the map easier to explore and allows viewers to examine different geographic patterns without changing the underlying dataset.

The original circle sizes, category colors, and informational popups remain available. The revised map therefore preserves the original information while giving viewers greater control over its presentation.

How the Modified Code Works

The most important modification is the addition of separate map layers and an interactive layer-control menu.

The original map used a single addCircleMarkers() function to display every location. The modified script instead uses three separate addCircleMarkers() functions.

Each function uses filter() to select locations belonging to a particular incident-frequency category. The group argument assigns those locations to an independently controlled map layer.

The addLayersControl() function creates the interactive menu that allows viewers to show or hide individual layers. Setting collapsed = FALSE makes the menu visible when the map first loads.

The script also calculates the total number of mapped incidents and the number of unique coordinate locations. These values provide additional context for interpreting the geographic visualization.

How I Used Artificial Intelligence

I used ChatGPT to evaluate possible improvements to the original frequency-class map and develop the revised R code.

AI suggested three modifications: adding interactive frequency filters, displaying an incident summary, and creating a heat-map layer.

I selected interactive frequency filters because they allow viewers to investigate different incident categories without removing any information from the original map. I also incorporated the suggested incident summary to provide additional context.

I decided not to implement the heat-map suggestion. Although a heat map could help visualize geographic concentrations, it would require additional code and could make the assignment unnecessarily complicated. The frequency filters offered a more manageable way to improve the original visualization.

AI also helped explain how Leaflet’s layer-control function works and how to organize the modified code.

Complete Modified Script

The following code contains the complete modified script used to produce the interactive map.

# STEP 1: Install and load packages

required_packages <- c(
  "jsonlite",
  "tidyverse",
  "leaflet"
)

installed_packages <- rownames(installed.packages())

for (pkg in required_packages) {
  if (!pkg %in% installed_packages) {
    install.packages(pkg)
  }
}

library(jsonlite)
library(tidyverse)
library(leaflet)


# STEP 2: Download crime data

base_url <- paste0(
  "https://services2.arcgis.com/HdTo6HJqh92wn4D8/",
  "arcgis/rest/services/",
  "Metro_Nashville_Police_Department_Incidents_view/",
  "FeatureServer/0/query"
)

query <- paste(
  "Incident_Occurred >= DATE '2026-06-01'",
  "AND Incident_Occurred < DATE '2026-09-01'",
  "AND Offense_NIBRS = '13A'"
)

crime_url <- paste0(
  base_url,
  "?where=",
  URLencode(query, reserved = TRUE),
  "&outFields=*",
  "&returnGeometry=false",
  "&f=json"
)

response <- fromJSON(crime_url)

if (!is.null(response$error)) {
  stop(response$error$message)
}

if (is.null(response$features$attributes)) {
  stop("No crime data were returned by the API.")
}

if (isTRUE(response$exceededTransferLimit)) {
  stop(
    "The API returned incomplete data. ",
    "Pagination is required before continuing."
  )
}

CrimeData <- response$features$attributes


# STEP 3: Clean data

CrimeData <- CrimeData |>
  mutate(
    Incident_Occurred = as.POSIXct(
      Incident_Occurred / 1000,
      origin = "1970-01-01",
      tz = "America/Chicago"
    ),
    Incident_Reported = as.POSIXct(
      Incident_Reported / 1000,
      origin = "1970-01-01",
      tz = "America/Chicago"
    )
  ) |>
  filter(
    !is.na(Longitude),
    !is.na(Latitude)
  )


# STEP 4: Aggregate and categorize incidents

CrimeLocations <- CrimeData |>
  count(
    Latitude,
    Longitude,
    sort = TRUE,
    name = "Incidents"
  ) |>
  mutate(
    FrequencyGroup = case_when(
      Incidents == 1 ~ "One Incident",
      Incidents <= 3 ~ "Two to Three Incidents",
      TRUE ~ "Four or More Incidents"
    ),
    FrequencyGroup = factor(
      FrequencyGroup,
      levels = c(
        "One Incident",
        "Two to Three Incidents",
        "Four or More Incidents"
      )
    ),
    DotColor = case_when(
      FrequencyGroup == "One Incident" ~ "gray",
      FrequencyGroup == "Two to Three Incidents" ~ "gold",
      TRUE ~ "red"
    )
  )


# STEP 5: Create popups

LocationPopup <- ~paste0(
  "<strong>Incidents at This Location:</strong> ",
  Incidents,
  "<br><strong>Frequency Category:</strong> ",
  FrequencyGroup,
  "<br><br><strong>Coordinates:</strong><br>",
  round(Latitude, 6),
  ", ",
  round(Longitude, 6)
)


# STEP 6: Calculate summary statistics

TotalIncidents <- sum(CrimeLocations$Incidents)
TotalLocations <- nrow(CrimeLocations)


# STEP 7: Create the interactive map

FrequencyClassMap <- leaflet(CrimeLocations) |>
  addProviderTiles("Esri.WorldStreetMap") |>

  addCircleMarkers(
    data = CrimeLocations |>
      filter(FrequencyGroup == "One Incident"),
    lng = ~Longitude,
    lat = ~Latitude,
    radius = ~sqrt(Incidents) * 3,
    stroke = TRUE,
    weight = 1,
    color = "black",
    fillColor = "gray",
    fillOpacity = 0.7,
    popup = LocationPopup,
    group = "One Incident"
  ) |>

  addCircleMarkers(
    data = CrimeLocations |>
      filter(
        FrequencyGroup == "Two to Three Incidents"
      ),
    lng = ~Longitude,
    lat = ~Latitude,
    radius = ~sqrt(Incidents) * 3,
    stroke = TRUE,
    weight = 1,
    color = "black",
    fillColor = "gold",
    fillOpacity = 0.7,
    popup = LocationPopup,
    group = "Two to Three Incidents"
  ) |>

  addCircleMarkers(
    data = CrimeLocations |>
      filter(
        FrequencyGroup == "Four or More Incidents"
      ),
    lng = ~Longitude,
    lat = ~Latitude,
    radius = ~sqrt(Incidents) * 3,
    stroke = TRUE,
    weight = 1,
    color = "black",
    fillColor = "red",
    fillOpacity = 0.7,
    popup = LocationPopup,
    group = "Four or More Incidents"
  ) |>

  addLayersControl(
    overlayGroups = c(
      "One Incident",
      "Two to Three Incidents",
      "Four or More Incidents"
    ),
    options = layersControlOptions(
      collapsed = FALSE
    )
  ) |>

  addLegend(
    position = "bottomright",
    colors = c("gray", "gold", "red"),
    labels = c(
      "One Incident",
      "Two to Three Incidents",
      "Four or More Incidents"
    ),
    title = "Incident Frequency"
  )

FrequencyClassMap