Last active
July 20, 2025 20:27
-
-
Save JEFworks/0c76f69ea39ce60deabdc3f100f58f5d to your computer and use it in GitHub Desktop.
Visualizing temporal trends in ICE arrests and encounters across major US cities
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
############# | |
## Dataset on ICE enforcement from Sep. 1, 2023 to Jun. 26, 2025 | |
## Data source: https://deportationdata.org/data/ice.html | |
############# | |
############# | |
## Read in data | |
############# | |
## Arrest data | |
## Download from https://ucla.app.box.com/index.php?rm=box_download_shared_file&shared_name=9d8qnnduhus4bd5mwqt7l95kz34fic2v&file_id=f_1922082018423 | |
arrests <- read_excel('2025-ICLI-00019_2024-ICFO-39357_ERO Admin Arrests_raw.xlsx', skip=6, sheet=1) | |
head(arrests) | |
## Counters, collate across 2 sheets | |
## Download from https://ucla.app.box.com/index.php?rm=box_download_shared_file&shared_name=9d8qnnduhus4bd5mwqt7l95kz34fic2v&file_id=f_1922075106849 | |
encounters1 <- read_excel('~/Downloads/2025-ICLI-00019_2024-ICFO-39357_ERO Encounters_raw.xlsx', skip=6, sheet=1) | |
encounters2 <- read_excel('~/Downloads/2025-ICLI-00019_2024-ICFO-39357_ERO Encounters_raw.xlsx', skip=6, sheet=2) | |
encounters <- rbind(encounters1, encounters2) | |
head(encounters) | |
############# | |
## Clean data | |
############# | |
library(tidyverse) | |
library(dplyr) | |
arrests_df <- data.frame( | |
date = arrests$`Apprehension Date`, | |
city = str_remove(arrests$`Apprehension AOR`, " Area of Responsibility")) | |
encounters_df <- data.frame( | |
date = encounters$`Event Date`, | |
city = str_remove( encounters$`Responsible AOR`, " Area of Responsibility")) | |
# Aggregate results by month | |
arrests_df <- arrests_df %>% | |
filter(year(date) <= 2026) %>% # <-- Remove dates after 2026 (weird error in data) | |
mutate(month = floor_date(date, unit = "month")) | |
# Count methods per month | |
arrests_df_summary <- arrests_df %>% | |
group_by(month, city) %>% | |
summarise(count = n(), .groups = 'drop') | |
head(arrests_df_summary) | |
range(arrests_df_summary$count) | |
encounters_df <- encounters_df %>% | |
filter(year(date) <= 2026) %>% # <-- Remove dates after 2026 (weird error in data) | |
mutate(month = floor_date(date, unit = "month")) | |
# Count methods per month | |
encounters_df_summary <- encounters_df %>% | |
group_by(month, city) %>% | |
summarise(count = n(), .groups = 'drop') | |
head(encounters_df_summary) | |
range(encounters_df_summary$count) | |
# Join the two data sets | |
joined_df <- encounters_df_summary %>% | |
rename(count_encounters = count) %>% | |
full_join(arrests_df_summary %>% rename(count_arrests = count), | |
by = c("month", "city")) | |
head(joined_df) | |
# Cherry pick few cities to highlight | |
topcities <- joined_df %>% filter(year(joined_df$month) == 2025) %>% | |
filter(count_arrests > quantile(count_arrests, 0.7)) %>% | |
summarize(sum(count_arrests), .by=city) | |
topcities | |
topcities2 <- c('Miami', 'Houston', 'Los Angeles', 'Chicago', 'Atlanta', 'New York City') | |
joined_df_sub <- joined_df[joined_df$city %in% topcities2,] | |
############# | |
## Plot | |
############# | |
library(ggplot2) | |
library(gganimate) | |
library(patchwork) | |
head(joined_df_sub) | |
# Pivot longer to have one column for value and one for metric type | |
df_long <- joined_df_sub %>% | |
pivot_longer(cols = c(count_encounters, count_arrests), | |
names_to = "metric", | |
values_to = "count") %>% | |
arrange(city, metric, month) | |
# Clean names for nicer labels | |
df_long$metric <- recode(df_long$metric, | |
count_arrests = "Arrests", | |
count_encounters = "Encounters") | |
df_long$month <- as.Date(df_long$month) | |
# Order | |
df_long$city <- factor(df_long$city, levels=topcities2) | |
# Plot | |
p <- ggplot(df_long, aes(x = month, y = count, group = city, color = city)) + | |
geom_line() + | |
geom_point() + | |
facet_grid(metric ~ city, scales = "free_y") + | |
scale_x_date( | |
date_breaks = "3 months", | |
date_labels = "%b %Y" | |
) + | |
labs( | |
title = "Trends in ICE Activity", | |
subtitle = "Number of ICE arrests and encounters from Sep. 1, 2023 to Jun. 26, 2025 per month | |
highlights recent increased arrests across cities and increased encounters in some cities.", | |
caption = "'Arrest' = An event where an ICE officer apprehended someone, whether or not that person had a criminal record. | |
'Encounter' = An event an ICE officer had with someone, whether or not that encounter resulted in an arrest. | |
Select cities shown. Full data source: https://deportationdata.org/data/ice.html", | |
x = "", | |
y = "Number of People" | |
) + | |
theme_bw( | |
base_size = 16 | |
) + | |
theme( | |
plot.title = element_text(size = 18, lineheight = 1.9, vjust = 3), | |
axis.title = element_text(size = 13), | |
axis.text = element_text(size = 10), | |
plot.subtitle = element_text(size = 13, lineheight = 0.9), | |
plot.caption = element_text(size = 10, vjust = -4), | |
strip.text = element_text(face = "bold", size = 12), | |
legend.position = "none", | |
axis.text.x = element_text(angle = 45, hjust = 1), | |
plot.margin = margin(t = 40, r = 20, b = 40, l = 20) # Top, Right, Bottom, Left | |
) | |
p | |
# Make animated version for fun | |
panim <- p + | |
transition_reveal(month) + | |
enter_fade() + | |
exit_fade() | |
animate(panim, | |
width = 800, | |
height = 400, | |
fps = 20, | |
duration = 8, | |
end_pause = 100) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment