Juneteenth 2021: Creating a data based movie in R with gganimate of lynchings (CC118)
Data
You can request the data I used in this episode from the CSDE Lynching Database.
The video
Code
This is the R script that I generated in this episode
library(tidyverse)
library(ggtext)
library(gganimate)
library(glue)
library(lubridate)
set.seed(18650619)
state_lookup <- tibble(abb = state.abb,
state = state.name)
lynchings <- read_csv("Weblist_IDs.csv") %>%
rename_all(tolower) %>%
filter(victimsrace == "Black") %>%
inner_join(., state_lookup, by=c("lynchstate" = "abb")) %>%
mutate(county = tolower(lynchcounty),
state = tolower(state)) %>%
select(state, county, name, victimssex, victimsage, methodofdeath, day, month, year) %>%
mutate(county = str_replace_all(county, "\\.", ""),
county = case_when(
state == "arkansas" & county == "probably white" ~ "white",
state == "florida" & county == "dade" ~ "miami-dade",
state == "florida" & county == "desoto" ~ "de soto",
state == "georgia" & county == "campbell" ~ "fulton",
state == "georgia" & county =="dekalb" ~ "de kalb",
state == "georgia" & county == "unidentified southwest georgia county" ~
"decatur",
state == "georgia" & county == "wilkerson" ~ "wilkinson",
state == "louisiana" & county == "arcadia" ~ "bienville",
state == "louisiana" & county == "desoto" ~ "de soto",
state == "louisiana" & county == "la fourche" ~ "lafourche",
state == "mississippi" & county == "desoto" ~ "de soto",
state == "virginia" & county == "alexandria" ~ "richmond",
state == "virginia" & county == "princess anne" ~ "virginia beach",
state == "virginia" & county == "dinwiddlie" ~ "dinwiddie",
state == "virginia" & county == "warwick" ~ "newport news",
TRUE ~ county
)
) %>%
mutate(victimssex = if_else(victimssex == "Unknown", NA_character_, victimssex),
victimssex = tolower(victimssex)) %>%
mutate(victimsage = tolower(victimsage),
victimsage = str_replace(victimsage, '�', ''),
victimsage = str_replace(victimsage, "^1$", "1 year old"),
victimsage = str_replace(victimsage, "^(\\d*)$", "\\1 years old"),
victimsage = str_replace(victimsage, "^(\\d*-\\d*)$", "\\1 years old"),
victimsage = str_replace(victimsage, "^< ?(\\d*)$", "less than \\1 years old"),
victimsage = str_replace(victimsage, "^20s$", "20-30 years old"),
victimsage = str_replace(victimsage, "^about (\\d*)$", "about \\1 years old"),
victimsage = str_replace(victimsage, "^aged$", "elderly"),
victimsage = str_replace(victimsage, "^old man$", "elderly"),
victimsage = str_replace(victimsage, "^old$", "elderly"),
victimsage = str_replace(victimsage, "^boy$", "young"),
victimsage = str_replace(victimsage, "^child$", "young"),
victimsage = str_replace(victimsage, "^youth$", "young"),
victimsage = str_replace(victimsage, "middled", "middle"),
victimsage = str_replace(victimsage, "12 months", "12 months old"),
victimsage = str_replace(victimsage, "late 30s", "35-39 years old"),
victimsage = na_if(victimsage, "unknown")
)%>%
mutate(methodofdeath = str_replace(methodofdeath, "�", " "),
methodofdeath = str_replace(methodofdeath, ";", ","),
methodofdeath = str_replace(methodofdeath, " - ", ", "),
methodofdeath = str_replace(methodofdeath, "-", ", "),
methodofdeath = str_replace(methodofdeath, "\\s+", " "),
methodofdeath = str_replace(methodofdeath, " ,", ","),
methodofdeath = str_replace(methodofdeath, "Hang$", "Hanged"),
methodofdeath = case_when(
is.na(methodofdeath) ~ "Lynched",
methodofdeath == "Not reported" ~ "Lynched",
methodofdeath == "Unknown" ~ "Lynched",
methodofdeath == "Unreported" ~ "Lynched",
methodofdeath == "Unreproted" ~ "Lynched",
TRUE ~ methodofdeath),
methodofdeath = str_to_sentence(methodofdeath)
) %>%
mutate(name = str_replace(name, '�', ''),
name = str_replace(name, '�', ''),
name = str_replace(name, "^\\s+", ""),
name = str_replace(name, "^Unnamed.*", "Unnamed"),
name = str_replace(name, "^Unknown.*", "Unnamed")
) %>%
mutate(day = if_else(day < 1 | day > 31, NA_real_, day),
month = if_else(month < 1 | month > 12, NA_real_, month),
day = if_else(is.na(month), NA_real_, day),
date = case_when(
is.na(month) ~ glue("{year}"),
is.na(day) ~ glue("{month(month, abb=FALSE, label=TRUE)} {year}"),
TRUE ~ glue("{month(month, abb=FALSE, label=TRUE)} {day}, {year}"))
) %>%
mutate(last_lines = glue("<br>{methodofdeath}<br>{date}"),
caption = case_when(is.na(victimssex) ~ glue("**{name}**, {victimsage}{last_lines}"),
is.na(victimsage) ~ glue("**{name}**, {victimssex}{last_lines}"),
TRUE ~ glue("**{name}**, {victimssex} {victimsage}{last_lines}")
)
) %>% select(state, county, caption)
# **Name**, sex and age
# Method of death
# Date
state_county <- map_data("county") %>%
select(region, subregion) %>%
distinct()
anti_join(lynchings, state_county,
by=c("county" = "subregion", "state" = "region"))
county_map <- lynchings %>%
distinct(state) %>%
inner_join(., map_data("county"), by=c("state" = "region"))
state_map <- lynchings %>%
distinct(state) %>%
inner_join(., map_data("state"), by=c("state" = "region"))
lynchings_map_data <- lynchings %>%
mutate(n = 1) %>%
slice_sample(prop=1) %>%
mutate(order = 1:nrow(.)) %>%
mutate(caption = fct_reorder(caption, order)) %>%
right_join(., county_map,
by=c("state" = "state", "county" = "subregion"))
caption_data <- lynchings_map_data %>%
select(caption) %>%
distinct(caption) %>%
drop_na(caption)
static <- lynchings_map_data %>%
drop_na(n) %>%
ggplot(aes(x=long, y=lat, fill=n, group=group)) +
geom_polygon(color="black", size=0.1) +
geom_polygon(data=county_map, aes(x=long, y=lat, group=group),
fill=NA, color="black", size=0.1) +
geom_polygon(data=state_map, aes(x=long, y=lat, group=group),
fill=NA, color="black", size=0.3) +
geom_textbox(data = caption_data,
aes(label=caption), x=-95.5, y=41, hjust =0, vjust=1,
fill = NA, box.color=NA,
width = unit(3, "in"),
inherit.aes = FALSE) +
scale_fill_gradient(name="Number of\nLynchings",
low="#FFFFFF", high = "#FF0000",
limits=c(0, NA)) +
coord_quickmap() +
theme_void() +
theme(
plot.title = element_textbox_simple(face="bold", size=18,
margin = margin(5, 0, 10, 5)),
legend.position = "none"
)
#ggsave("lynchings_map.tiff", height=4.5, width =8)
animation <- static + transition_manual(caption)
animate(animation,
height=4.5, width =8, units = "in", res = 300,
fps = 1, nframes = nrow(caption_data),
renderer = av_renderer("juneteenth_map_movie.mp4")
)