ordered_levels <- c("≥ +50%", "+10% to +49%", "-9% to +9%", "-49% to -10%", "≥ -50%", "New activity")

palette_colors <- c(
  "≥ +50%"        = "#1a9850",   # dark green
  "+10% to +49%"  = "#a6dba0",   # light green
  "-9% to +9%"    = "#f7f7f7",   # beige
  "-49% to -10%"  = "#ef8a62",   # light red
  "≥ -50%"        = "#b2182b",   # dark red
  "New activity"  = "#4575b4"    # blue
)

pal <- colorFactor(
  palette = palette_colors,
  levels = ordered_levels,
  na.color = "transparent"
)

pal_novmar <- colorFactor(
  palette = palette_colors,
  levels = ordered_levels,
  na.color = "transparent"
)

January vs November: Wildire impacts

The map below plots the percent change in boardings on LA Metro buses between mid November and early January, relative to the January baseline. Red indicates a significant decline in boardings during the wildfires. Green indicates a significant increase. In some cases, block groups went from zero boardings in November to a non-zero number in January, these are in their own category “new activity” in blue, since a percent change off zero is infinite.

The map reveals dramatic drops in boardings in Altadena, reflecting cancelled service and evacuations. However, boardings increased in two block groups in the neighborhood. Boardings also declined heavily in areas impacted by the Palisades fire. Multivariate modelling is needed to estimate the localized ridership effects of air pollution, cancelled school, fare-free transit, and the opening of shelters.

# Map 1: Jan vs Nov
leaflet(cbg_geo) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addPolygons(
    fillColor = ~ifelse(novjanboards_cat == "No activity", NA, pal(novjanboards_cat)),
    fillOpacity = ~ifelse(novjanboards_cat == "No activity", 0, 0.7),
    color = "#333333", weight = 0.2, opacity = 0.4,
    label = ~paste0("GEOID: ", GEOID, "<br>Change: ", novjanboards_cat),
    highlightOptions = highlightOptions(weight = 2, color = "black", bringToFront = TRUE)
  ) %>%
  addLegend("bottomright", pal = pal, values = ~novjanboards_cat,
            title = "Jan → Nov % Change", opacity = 1)
## Warning: sf layer has inconsistent datum (+proj=longlat +datum=NAD83 +no_defs).
## Need '+proj=longlat +datum=WGS84'

Overal, far more block groups saw a decline in ridership compared to gain.

library(ggplot2)
## Warning: package 'ggplot2' was built under R version 4.4.2
# First: ensure proper factor level order
cbg_geo$novjanboards_cat <- factor(
  cbg_geo$novjanboards_cat,
  levels = c("≥ +50%", "+10% to +49%", "-9% to +9%", "-49% to -10%", "≥ -50%", "New activity")
)

# Set matching colors (same as Leaflet)
palette_colors <- c(
  "≥ +50%"        = "#1a9850",
  "+10% to +49%"  = "#a6dba0",
  "-9% to +9%"    = "#f7f7f7",
  "-49% to -10%"  = "#ef8a62",
  "≥ -50%"        = "#b2182b",
  "New activity"  = "#4575b4"
)

# Create barplot
ggplot(
  cbg_geo %>% filter(!is.na(novjanboards_cat)),
  aes(x = novjanboards_cat, fill = novjanboards_cat)
) +
  geom_bar() +
  scale_fill_manual(values = palette_colors) +
  labs(
    title = "Number of Block Groups by % Change in Boardings (Jan vs Nov)",
    x = "Category",
    y = "Number of Block Groups",
    fill = NULL
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

March versus November: Uneven recovery

We repeat this analysis but now compare March 2025 to the same baseline period in November. By then, the number of blocks that lost ridership is not much higher than the number that gained ridership back:

# Ensure correct factor level order for novmarboards_cat
cbg_geo$novmarboards_cat <- factor(
  cbg_geo$novmarboards_cat,
  levels = c("≥ +50%", "+10% to +49%", "-9% to +9%", "-49% to -10%", "≥ -50%", "New activity")
)

# Use the same palette
palette_colors <- c(
  "≥ +50%"        = "#1a9850",
  "+10% to +49%"  = "#a6dba0",
  "-9% to +9%"    = "#f7f7f7",
  "-49% to -10%"  = "#ef8a62",
  "≥ -50%"        = "#b2182b",
  "New activity"  = "#4575b4"
)

# Plot it
ggplot(
  cbg_geo %>% filter(!is.na(novmarboards_cat)),
  aes(x = novmarboards_cat, fill = novmarboards_cat)
) +
  geom_bar() +
  scale_fill_manual(values = palette_colors) +
  labs(
    title = "Number of Block Groups by % Change in Boardings (Nov vs Mar)",
    x = "Category",
    y = "Number of Block Groups",
    fill = NULL
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

By March, ridership returned across most of the city, including parts of Altadena. Ridership remained very depressed in block groups along the edge of the Palisades fire.

# Map 2: Nov vs Mar
leaflet(cbg_geo) %>%
  addProviderTiles("CartoDB.Positron") %>%
  addPolygons(
    fillColor = ~ifelse(novmarboards_cat == "No activity", NA, pal_novmar(novmarboards_cat)),
    fillOpacity = ~ifelse(novmarboards_cat == "No activity", 0, 0.7),
    color = "#333333", weight = 0.2, opacity = 0.4,
    label = ~paste0("GEOID: ", GEOID, "<br>Change: ", novmarboards_cat),
    highlightOptions = highlightOptions(weight = 2, color = "black", bringToFront = TRUE)
  ) %>%
  addLegend("bottomright", pal = pal_novmar, values = ~novmarboards_cat,
            title = "Nov → Mar % Change", opacity = 1)
## Warning: sf layer has inconsistent datum (+proj=longlat +datum=NAD83 +no_defs).
## Need '+proj=longlat +datum=WGS84'

Note that the echo = FALSE parameter was added to the code chunk to prevent printing of the R code that generated the plot.