DATA 608: Story-3: Do stricter gun laws reduce firearm gun deaths?

Bikash Bhowmik —- Date: 11-Oct-2025

Column

Column

Instruction
Story - 3 : Do stricter gun laws reduce firearm gun deaths?

The CDC publishes firearm mortality for each State per 100,000 persons https://www.cdc.gov/nchs/pressroom/sosmap/firearm_mortality/firearm.htm. Each State’ firearm control laws can be categorized as very strict to very lax. The purpose of this Story is to answer the question, ” Do stricter firearm control laws help reduce firearm mortality?”

For this assignment you will need to:

Access the firearm mortality data from the CDC using an available API (https://open.cdc.gov/apis.html)

Create a 5 point Likert scale categorizing gun control laws from most lax to strictest and assign each state to the most appropriate Likert bin.

Determine wether stricter gun control laws result in reduced gun violence deaths

Present your story using heat maps

Introduction

In this story, Gun violence continues to pose a serious and multifaceted public health challenge in the United States. Each year, thousands of lives are lost to firearms through homicides, suicides, and accidental shootings—making firearm-related deaths a leading cause of preventable mortality. While many social, cultural, and economic factors contribute to this issue, the role of gun legislation remains one of the most heavily debated aspects of the conversation. For decades, policymakers and researchers have sought to understand whether the strength of state firearm laws influences rates of gun-related deaths.

This objective of this analysis examines how variations in gun control strictness across states relate to firearm mortality rates, using recent data published by the Centers for Disease Control and Prevention (CDC). States are categorized on a five-point scale ranging from least to most restrictive firearm policies. Through the use of heat maps and comparative visualizations, the study aims to highlight potential trends between legislative strictness and mortality outcomes. Ultimately, this data-driven exploration seeks to offer insight into how differences in firearm policy may shape public safety and impact lives across the United States.

Import libraries

library(tidyverse)
library(httr)
library(jsonlite)
library(ggplot2)
library(tidyr)
library(usmap)
library(gridExtra)
library(kableExtra)
library(plotly)
library(dplyr)
library(ggrepel)

Load the data
The dataset in this section comes directly from the CDC’s public API, providing the latest firearm mortality data. The information is automatically converted from JSON into a DataFrame, making it easier to analyze and visualize while ensuring consistency and reproducibility.

Access the firearm mortality data from the CDC using an available API (https://open.cdc.gov/apis.html)

url <- "https://data.cdc.gov/resource/489q-934x.json"
response <- GET(url)
data <- content(response, as = "text")
df <- as.data.frame(fromJSON(data))

# View data
kable(head(df), "simple")
year_and_quarter time_period cause_of_death rate_type unit rate_overall rate_sex_female rate_sex_male rate_alaska rate_alabama rate_arkansas rate_arizona rate_california rate_colorado rate_connecticut rate_district_of_columbia rate_delaware rate_florida rate_georgia rate_hawaii rate_iowa rate_idaho rate_illinois rate_indiana rate_kansas rate_kentucky rate_louisiana rate_massachusetts rate_maryland rate_maine rate_michigan rate_minnesota rate_missouri rate_mississippi rate_montana rate_north_carolina rate_north_dakota rate_nebraska rate_new_hampshire rate_new_jersey rate_new_mexico rate_nevada rate_new_york rate_ohio rate_oklahoma rate_oregon rate_pennsylvania rate_rhode_island rate_south_carolina rate_south_dakota rate_tennessee rate_texas rate_utah rate_virginia rate_vermont rate_washington rate_wisconsin rate_west_virginia rate_wyoming rate_age_1_4 rate_age_5_14 rate_age_15_24 rate_age_25_34 rate_age_35_44 rate_age_45_54 rate_age_55_64 rate_65_74 rate_age_75_84 rate_age_85_plus
2022 Q1 12 months ending with quarter All causes Age-adjusted Deaths per 100,000 873.2 729.4 1038 944.5 1109.8 1097.1 882.5 719.5 808.2 725.9 844.4 868.3 828 973.6 647.1 860.8 892.8 839.4 1011.9 938.4 1153.2 1084.4 717 800.3 910.3 956.2 771.6 986.6 1193.4 925.3 952.8 810.5 839.4 791.9 723.5 1007.9 933.3 694.4 1019.8 1126.4 875.6 893.5 771 1022 871.4 1122.6 918.3 822.2 860.1 800.5 811.1 857.1 1239.9 956.8 NA NA NA NA NA NA NA NA NA NA
2022 Q1 12 months ending with quarter Alzheimer disease Age-adjusted Deaths per 100,000 30.6 35 23.8 28.5 45.5 43.2 29.6 38.4 32.1 21.6 10.7 30.1 19.3 43.4 23.6 30.9 41.5 26.6 29.6 22.9 32.5 42.8 17.5 15.9 28.1 34.1 34.1 33.5 51.8 24.4 36 32.5 29.8 23.3 20.5 25.4 26.3 12.8 34 37.1 40 22.5 28.3 40.1 39.1 37.2 41.2 41 26.1 36.2 46 33.6 35.4 34.2 NA NA NA NA NA NA NA NA NA NA
2022 Q1 12 months ending with quarter COVID-19 Age-adjusted Deaths per 100,000 95 75.2 119.1 121.3 133.6 123.6 113.9 62.4 89.9 50.9 54.8 78.4 106.6 116.4 43.9 78.1 118.9 82.1 112.1 109.7 146.7 108.8 46.7 66.9 70.9 115 68 110.5 140.8 111.8 98.3 80.2 73.8 56.5 62.9 138.3 134.8 63.4 128.1 150.3 77.5 97.4 55.5 122.9 76.3 140.5 126.7 78.4 80.8 32.9 66.6 77.7 154 145.1 NA NA NA NA NA NA NA NA NA NA
2022 Q1 12 months ending with quarter Cancer Age-adjusted Deaths per 100,000 145.9 127.4 170.9 156 159.9 167.9 134.5 131.4 125 134.1 143.8 155.4 141.3 150.4 126.7 152 139.6 149.3 169 152.8 179.6 161.9 136.1 139.2 161.2 159.6 143.6 162.4 184 142.9 152.4 134.2 152.2 145.3 130.3 135.6 140.8 125.3 161.5 176.7 153.7 151.8 139.1 154.3 148.6 165.2 143.5 120.6 149.8 155 148.5 146.7 183.8 153.1 NA NA NA NA NA NA NA NA NA NA
2022 Q1 12 months ending with quarter Chronic liver disease and cirrhosis Age-adjusted Deaths per 100,000 14.4 10.3 18.9 25.5 16.4 17 21 15.4 19.7 12.5 9.1 11.5 13.4 13.7 9.7 14.3 16.1 12.3 15.4 15.4 17.2 12.1 11.1 9.3 17.8 15.1 13.6 13.1 17.3 24.7 15 17.8 15.2 14.5 8.9 41.8 17.6 8.3 14.1 19.3 18 11 16.8 17.5 36.1 17.1 16.8 11.6 11.7 12.6 15.5 12.5 17.9 25 NA NA NA NA NA NA NA NA NA NA
2022 Q1 12 months ending with quarter Chronic lower respiratory diseases Age-adjusted Deaths per 100,000 35.1 33.2 37.8 36.4 51.7 62.3 37 26.3 38.1 23.9 17.7 36.5 32.1 41.1 17.9 40.8 44.1 32.4 52.9 43.8 58.3 39.6 26.8 24.1 43.1 39 30.1 46.7 59.9 39.6 37.3 35.4 41 36 21.6 38.3 41.4 22.5 43 63.5 34.1 30.8 29.4 41.6 41.6 52 36.2 30.6 31.3 32.4 29.5 33.2 59.9 49.5 NA NA NA NA NA NA NA NA NA NA

Data Tidying

In this section, the dataset is filtered to retain only records related to firearm-related injuries and their crude mortality rates. The data are then transformed into a long format to align each state with its corresponding firearm death rate. Additionally, states are classified on a five-point Likert scale to indicate the strictness of their gun control laws, ranging from most lax (1) to most strict (5). These preprocessing steps ensure the dataset is clean, consistent, and ready for accurate visualization and analysis.

# Apply the filter to the data
firearm_data <- df %>% 
    filter(cause_of_death == 'Firearm-related injury' &
         year_and_quarter == '2023 Q1' &
         time_period == '12 months ending with quarter' &
         rate_type == "Age-adjusted")

#Select only the states columns
firearm_df <- firearm_data[1, 9:59]

# Reformat the data
firearm_df <- firearm_df %>%
  gather(key = "state", value = "firearm_mortality_rate", starts_with("rate_")) %>%
  mutate(firearm_mortality_rate = as.numeric(firearm_mortality_rate),
         state = gsub("rate_", "", state)) %>% 
  filter(state != "district_of_columbia") %>% 
  arrange(state)

# View first 5 elements of the data
head(firearm_df)
       state firearm_mortality_rate
1    alabama                   26.4
2     alaska                   21.4
3    arizona                   19.9
4   arkansas                   22.7
5 california                    8.6
6   colorado                   17.1

Next, we will develop a five-point Likert scale to categorize gun control laws across states, ranging from the loosest to the strictest. Each state will then be assigned to the appropriate category based on data from: https://worldpopulationreview.com/state-rankings/strictest-gun-laws-by-state

# Categories for  guns policies per states
likert_values <-c("Very loose", "Very loose","Very loose","Very loose","Very Strict",
                  "Strict","Very Strict","Strict","Moderate","Very loose",
                  "Very Strict","Very loose","Very Strict","Loose","Very loose",
                  "Very loose","Very loose","Very loose","Very loose","Very Strict",
                  "Very Strict","Moderate","Moderate","Very loose","Very loose","Very loose",
                  "Moderate","Moderate","Very loose","Very Strict","Moderate","Very Strict",
                  "Moderate","Very loose","Loose","Very loose","Strict","Strict",
                  "Strict","Very loose","Very loose","Very loose","Very loose",
                  "Very loose","Moderate","Strict","Strict","Very loose","Moderate","Very loose")

# Assuming your data frame is named df_ordered
firearm_df$gun_law <- likert_values

# Change the name of the column

firearm_df$state <- c("Alabama", "Alaska", "Arizona", "Arkansas", "California", 
            "Colorado", "Connecticut", "Delaware", "Florida", "Georgia", 
            "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", 
            "Kentucky", "Louisiana", "Maine", "Maryland", "Massachusetts", 
            "Michigan", "Minnesota", "Mississippi", "Missouri", "Montana", 
            "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico", 
            "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", 
            "Oregon", "Pennsylvania", "Rhode Island", "South Carolina", 
            "South Dakota", "Tennessee", "Texas", "Utah", "Vermont", "Virginia", 
            "Washington", "West Virginia", "Wisconsin", "Wyoming")

# Print the data 
head(firearm_df)
       state firearm_mortality_rate     gun_law
1    Alabama                   26.4  Very loose
2     Alaska                   21.4  Very loose
3    Arizona                   19.9  Very loose
4   Arkansas                   22.7  Very loose
5 California                    8.6 Very Strict
6   Colorado                   17.1      Strict

Correlation between firearm policy and mortality rate

Correlation between firearm policy and mortality rate Let’s calculate the correlation between strict firearm policy and the mortality rate.

# Calculate correlation coefficient
correlation <- cor(firearm_df$firearm_mortality_rate, as.numeric(factor(firearm_df$gun_law, levels = c("Very loose", "Loose", "Moderate", "Strict", "Very Strict"))))

correlation
[1] -0.6671983

The correlation coefficient of -0.6656803 indicates a strong negative correlation between firearm mortality rates and the stringency of gun control laws. This suggests that as gun control laws become stricter, firearm mortality rates tend to decrease.

Create a heat map
A heatmap is an effective visualization for identifying patterns and distributions across geographic regions. In this analysis, the heatmap illustrates the strictness of gun control laws across U.S. states for the fourth quarter of 2022. Each state is colored according to its category on a five-point Likert scale, ranging from Most Lax to Most Strict. This approach allows for easy comparison between states, highlights regional trends, and visually communicates how firearm legislation varies across the country. By combining geographic and categorical data, the heatmap provides an intuitive way to explore the relationship between gun law strictness and firearm-related mortality.

# Define a numeric mapping for gun law categories
gun_law_levels <- c("Very loose", "Loose", "Moderate", "Strict", "Very Strict")
# gun_law_colors <- c("#d73027", "#fc8d59", "#fee08b", "#91bfdb", "#4575b4")  # red→blue
gun_law_colors <- c("#d73027", "#fc8d59", "#fee08b", "#91bfdb", "#1a9850")  # red → green


# Map gun_law to numeric for color scale
firearm_df <- firearm_df %>%
  mutate(
    state_abbr = state.abb[match(state, state.name)],
    state_abbr = ifelse(state == "district_of_columbia", "DC", state_abbr),
    gun_law_num = as.numeric(factor(gun_law, levels = gun_law_levels))
  )

# Plotly choropleth
fig <- plot_ly(
  data = firearm_df,
  type = "choropleth",
  locations = ~state_abbr,
  locationmode = "USA-states",
  z = ~gun_law_num,  # use numeric mapping
  text = ~paste0(
    "<b>", tools::toTitleCase(state), "</b><br>",
    "Gun Law: ", gun_law, "<br>",
    "Firearm Mortality Rate: ", firearm_mortality_rate
  ),
  hoverinfo = "text",
  colorscale = list(
    list(0.0, gun_law_colors[1]),
    list(0.25, gun_law_colors[2]),
    list(0.5, gun_law_colors[3]),
    list(0.75, gun_law_colors[4]),
    list(1.0, gun_law_colors[5])
  ),
  zmin = 1,
  zmax = 5,
  colorbar = list(
    title = "Gun Law Strength",
    tickvals = 1:5,
    ticktext = c("Most Lax","Lax","Moderate","Strict","Most Strict")
  )
) %>%
  layout(
    title = list(
      text = "Gun Laws Strength by US State",
      x = 0.5, xanchor = "center", font = list(size = 20)
    ),
    geo = list(
      scope = "usa",
      projection = list(type = "albers usa"),
      showlakes = TRUE,
      lakecolor = "rgb(255, 255, 255)"
    )
  )

fig

The heatmap uncovers distinct geographic patterns in gun law strictness across the United States. States with more lenient firearm regulations tend to cluster in specific regions, whereas states with stricter laws are concentrated elsewhere. By including firearm mortality rates in the hover text, the heatmap provides immediate insights into potential correlations between legislation and death rates. Overall, this visualization emphasizes the role of geographic context in understanding variations in state-level gun control laws and presents a clear visual narrative for policymakers and the public regarding the landscape of firearm regulation in the U.S.

Firearm Mortality Rate vs. Gun Control Laws

In this analysis, we examine how the strictness of firearm control laws influences firearm mortality rates across U.S. states. By correlating each state’s gun law strictness with its corresponding firearm mortality rate and visualizing the results through a heat map, we uncover a clear pattern: states with stricter firearm regulations tend to experience lower firearm mortality rates.

# Calculate national average
average_rate <- mean(firearm_df$firearm_mortality_rate)

# Reorder gun_law factor
firearm_df$gun_law <- factor(
  firearm_df$gun_law,
  levels = c("Very loose", "Loose", "Moderate", "Strict", "Very Strict")
)

# Build base ggplot
p <- ggplot(firearm_df, aes(x = gun_law, y = firearm_mortality_rate,
                            color = firearm_mortality_rate,
                            text = paste0(
                              "<b>", state, "</b><br>",
                              "Gun Law: ", gun_law, "<br>",
                              "Mortality Rate: ", round(firearm_mortality_rate, 2)
                            ))) +
  geom_jitter(size = 5, width = 0.15, alpha = 0.9) +
  scale_color_gradient(low = "#2ECC71", high = "#E74C3C", name = "Mortality Rate") +
  geom_hline(yintercept = average_rate, linetype = "dashed",
             color = "navyblue", size = 1) +
  annotate("text", x = 5.1, y = average_rate + 0.3,
           label = paste("National Avg:", round(average_rate, 1)),
           color = "navyblue", hjust = 0, size = 4.5, fontface = "bold") +
  labs(
    title = "Firearm Mortality Rate vs Gun Law Strictness (2023 Q1)",
    subtitle = "Hover over points to see details for each state",
    x = "Gun Law Strictness (Least → Most Restrictive)",
    y = "Firearm Mortality Rate per 100,000"
  ) +
  theme_minimal(base_size = 15) +
  theme(
    plot.title = element_text(size = 20, face = "bold", hjust = 0.5),
    plot.subtitle = element_text(size = 14, color = "gray30", hjust = 0.5),
    axis.text = element_text(size = 12, color = "gray20"),
    axis.title = element_text(size = 14, face = "bold"),
    panel.grid.minor = element_blank(),
    panel.grid.major.x = element_blank(),
    legend.position = "right",
    legend.title = element_text(face = "bold")
  )

# Convert to interactive hover plot
fig <- ggplotly(p, tooltip = "text")

fig

The national average for firearm mortality stands at 15.5 per 100,000 persons.

The graph indicates that states with strict gun control laws consistently maintain firearm mortality rates below the national average. In contrast, over 70% of states with lenient gun control laws record firearm mortality rates above the national average. Notably, 16 of the 21 states exceeding the national average in firearm mortality have very loose gun control regulations.

Conclusion

The visualizations indicate a trend in which states with more lenient gun laws generally experience higher firearm-related mortality rates, while states with the strictest laws tend to have the lowest rates. Overall, the chart suggests a negative association between gun law strictness and firearm deaths. A minor exception is observed, states with the most stringent laws show slightly higher mortality rates than some states with moderately strict laws, possibly due to unmeasured factors varying across states. In summary, the analysis supports the conclusion that stricter firearm control laws are associated with a reduction in firearm mortality rates across U.S. states.