Packages Used

Show Packages
library(tidyverse)
library(readxl)
library(robotstxt)
library(rvest)
library(janitor)
library(jsonlite)
library(viridis)
library(maps)

Load/Tidy Data

Show code for loading data
1. Firearm Morality Data

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))
2. Gun Law Strictness Scale

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
3. Join Data
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")

4. Factor Grade

firearm <- data |>
  mutate(gun_strictness_grade = factor(gun_strictness_grade,
                                       levels = c("A", "B", "C", "D", "F"),
                                       ordered = TRUE))

Visualization 1

# 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"))

Visualization 2

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"))

Visualization 3

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"))