library(tidyverse)
library(sf)
library(leaflet)
library(htmlwidgets)
# Load data
nypd_final <- readRDS("data/processed/nypd_analysis_final.rds")
taxi_zones <- readRDS("data/raw/taxi_zones.rds")
# Aggregate by zone with spatial data
zone_summary <- nypd_final %>%
group_by(LocationID, zone, borough, nightlife_category, n_nightlife_venues) %>%
summarise(
total_crimes = n(),
violent_crimes = sum(crime_category == "Violent"),
property_crimes = sum(crime_category == "Property"),
disorder_crimes = sum(crime_category == "Disorder"),
pct_after_midnight = 100 * mean(time_period == "After Midnight (12AM-4AM)"),
.groups = "drop"
) %>%
mutate(
n_nightlife_venues = replace_na(n_nightlife_venues, 0)
) %>%
# Join to spatial data
left_join(taxi_zones %>% select(LocationID, geometry), by = "LocationID") %>%
st_as_sf() %>%
# Transform to WGS84 for Leaflet
st_transform(4326) %>%
# Calculate centroids for bubble placement
mutate(
centroid = st_centroid(geometry),
lon = st_coordinates(centroid)[,1],
lat = st_coordinates(centroid)[,2]
)
# CRITICAL FIX: Force factor levels in correct order
zone_summary <- zone_summary %>%
mutate(
nightlife_category = factor(
as.character(nightlife_category),
levels = c("Low Nightlife", "Medium Nightlife", "High Nightlife"),
ordered = TRUE
)
)
# Create color palette - ORDER MATCHES FACTOR LEVELS
color_pal <- colorFactor(
palette = c("#2C7BB6", "#FDB863", "#D7191C"), # Blue, Orange, Red
levels = c("Low Nightlife", "Medium Nightlife", "High Nightlife")
)
# Create popup text
zone_summary <- zone_summary %>%
mutate(
popup_text = paste0(
"<strong style='font-size:14px;'>", zone, "</strong><br>",
"<strong>Borough:</strong> ", borough, "<br>",
"<strong>Nightlife Category:</strong> ", nightlife_category, "<br>",
"<strong>Nightlife Venues:</strong> ", n_nightlife_venues, "<br>",
"<hr style='margin:5px 0;'>",
"<strong>Total Crimes (8PM-8AM):</strong> ", format(total_crimes, big.mark = ","), "<br>",
"<strong>Violent:</strong> ", format(violent_crimes, big.mark = ","),
" (", round(100*violent_crimes/total_crimes, 1), "%)<br>",
"<strong>Property:</strong> ", format(property_crimes, big.mark = ","),
" (", round(100*property_crimes/total_crimes, 1), "%)<br>",
"<strong>Disorder:</strong> ", format(disorder_crimes, big.mark = ","),
" (", round(100*disorder_crimes/total_crimes, 1), "%)<br>",
"<strong>After-Midnight (12AM-4AM):</strong> ", round(pct_after_midnight, 1), "%"
)
)
# Create interactive map
viz3_map <- leaflet(zone_summary) %>%
# Base map
addProviderTiles(providers$CartoDB.Positron) %>%
# Taxi zone polygons (light background)
addPolygons(
fillColor = ~color_pal(nightlife_category),
fillOpacity = 0.2,
color = "white",
weight = 1,
opacity = 0.5,
highlightOptions = highlightOptions(
weight = 2,
color = "#666",
fillOpacity = 0.4,
bringToFront = TRUE
),
label = ~zone,
group = "Zone Boundaries"
) %>%
# Crime bubble markers
addCircleMarkers(
lng = ~lon,
lat = ~lat,
radius = ~sqrt(total_crimes) / 15,
fillColor = ~color_pal(nightlife_category),
fillOpacity = 0.7,
color = "white",
weight = 2,
popup = ~popup_text,
label = ~paste0(zone, ": ", format(total_crimes, big.mark = ","), " crimes"),
labelOptions = labelOptions(
style = list("font-weight" = "normal", padding = "3px 8px"),
textsize = "12px",
direction = "auto"
),
group = "Crime Bubbles"
) %>%
# Legend - REVERSED for correct Leaflet display
addLegend(
position = "bottomright",
colors = c("#D7191C", "#FDB863", "#2C7BB6"), # REVERSED: Red, Orange, Blue
labels = c("High Nightlife (≥5 venues)",
"Medium Nightlife (2-4 venues)",
"Low Nightlife (0-1 venues)"), # REVERSED: High, Medium, Low
title = "Nightlife Density",
opacity = 0.8
) %>%
# Add title control
addControl(
html = "<div style='background:white; padding:10px; border-radius:5px; box-shadow:0 0 15px rgba(0,0,0,0.2);'>
<h4 style='margin:0; font-weight:bold;'>NYC Nightlife Crime Map (8PM-8AM, 2019-2023)</h4>
<p style='margin:5px 0 0 0; font-size:12px; color:#666;'>
Bubble size = Total crimes | Click bubbles for details
</p></div>",
position = "topright"
) %>%
# Layer controls - MUST HAVE %>% BEFORE THIS
addLayersControl(
overlayGroups = c("Zone Boundaries", "Crime Bubbles"),
options = layersControlOptions(collapsed = FALSE)
) %>%
# Set initial view (NYC center)
setView(lng = -73.935242, lat = 40.730610, zoom = 11)
# Display map
viz3_map