library(tidyverse)
library(readxl)
library(robotstxt)
library(rvest)
library(janitor)
library(jsonlite)
library(viridis)
library(maps)
Source: Centers for Disease Control and Prevention (CDC)
death_data <- fromJSON("https://data.cdc.gov/resource/489q-934x.json")
death_data <- death_data |>
filter(str_detect(time_period, "3"),
str_detect(cause_of_death, "Firearm"),
rate_type != "Crude") |>
pivot_longer(9:59, names_to = "state", values_to = "rate") |>
mutate(state = str_remove_all(state, "rate_"),
state = str_replace_all(state, "_", " "),
rate = as.numeric(rate)) |>
select(1, 19:20) |>
group_by(state) |>
summarise(firearm_mortality_rate = mean(rate, na.rm = TRUE))
For the 5-point likert scale on gun law strictness, I chose to outsource the state-by-state grading from the Gifford’s Law Center.
Source: Gifford’s Law Center
paths_allowed("https://giffords.org/lawcenter/resources/scorecard2024/")
## [1] TRUE
score_data <- read_html("https://giffords.org/lawcenter/resources/scorecard2024/") |>
html_element("table") |>
html_table() |>
clean_names()
score_data <- score_data |>
mutate(grade = str_remove_all(grade, "[+-]")) |>
filter(grade != "Share") |>
select(2, gun_strictness_grade = grade)
score_data |>
head(5)
## # A tibble: 5 × 2
## state gun_strictness_grade
## <chr> <chr>
## 1 Alabama F
## 2 Alaska F
## 3 Arizona F
## 4 Arkansas F
## 5 California A
score_data <- score_data |> mutate(state = toupper(state))
death_data <- death_data |> mutate(state = toupper(state))
data <- score_data |>
left_join(death_data, by = "state")
firearm <- data |>
mutate(gun_strictness_grade = factor(gun_strictness_grade,
levels = c("A", "B", "C", "D", "F"),
ordered = TRUE))
# Reorder states by mortality so the pattern is easier to see
firearm <- firearm |>
mutate(state = fct_reorder(state, firearm_mortality_rate))
ggplot(firearm, aes(x = gun_strictness_grade, y = state, fill = firearm_mortality_rate)) +
geom_tile(color = "white") +
scale_fill_viridis(name = "Deaths per 100,000",
option = "magma",
direction = -1) +
labs(title = "Firearm Mortality Rates by State and Gun-Law Strictness",
subtitle = "A = most strict, F = least strict",
x = "Gun-law strictness grade",
y = "State") +
theme_minimal(base_size = 12) +
theme(axis.text.y = element_text(size = 8),
panel.grid = element_blank(),
plot.title = element_text(face = "bold"))
grade_summary <- firearm |>
group_by(gun_strictness_grade) |>
summarise(mean_rate = mean(firearm_mortality_rate),
median_rate = median(firearm_mortality_rate),
n_states = n(),
.groups = "drop")
ggplot(grade_summary, aes(x = gun_strictness_grade, y = 1, fill = mean_rate)) +
geom_tile(color = "white", height = 0.8) +
geom_text(aes(label = round(mean_rate, 1)), color = "white", fontface = "bold") +
scale_fill_viridis(name = "Mean deaths\nper 100,000", option = "magma", direction = -1) +
labs(title = "Average Firearm Mortality by Gun-Law Strictness Grade",
subtitle = "States with A grades have substantially lower death rates than F states",
x = "Gun-law strictness grade",
y = NULL) +
scale_y_continuous(expand = c(0, 0)) +
theme_minimal(base_size = 12) +
theme(axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
panel.grid = element_blank(),
plot.title = element_text(face = "bold"))
states_map <- map_data("state")
firearm_map <- firearm |>
mutate(region = tolower(state)) |>
inner_join(states_map, by = "region")
ggplot(firearm_map, aes(x = long, y = lat, group = group, fill = firearm_mortality_rate)) +
geom_polygon(color = "white", size = 0.2) +
coord_map("albers", lat0 = 39, lat1 = 45) +
scale_fill_viridis(name = "Deaths per 100,000", option = "magma", direction = -1) +
labs(title = "Firearm Mortality Rates by State",
subtitle = "Darker shading indicates higher firearm mortality",
x = NULL,
y = NULL) +
theme_void(base_size = 12) +
theme(legend.position = "right",
plot.title = element_text(face = "bold"))