This project examines the policy question: Do stricter state firearm laws correspond with lower firearm mortality rates?
The analysis combines age-adjusted firearm mortality data from the CDC public API with the 2025 GIFFORDS Annual Gun Law Scorecard to examine state-level patterns.
The analysis shows a descriptive pattern: states in stricter gun law categories generally have lower age-adjusted firearm mortality rates than states in more lax categories.
States are grouped into five strictness categories derived from the GIFFORDS ranking. Individual states are labeled, and the red line connects category medians.
Firearm mortality data are retrieved directly from the CDC public API. The analysis uses age-adjusted firearm mortality rates for the 12 months ending 2025 Q2.
The CDC source contained a 2025 Q3 record at the time of analysis, but state-level values were not populated. Therefore, 2025 Q2 was selected as the most recent period with complete state-level data.
cdc_url <- "https://data.cdc.gov/resource/489q-934x.json?$limit=50000"
response <- httr::GET(cdc_url)
cdc_raw <- jsonlite::fromJSON(
httr::content(
response,
"text",
encoding = "UTF-8"
),
flatten = TRUE
)
cdc_firearm <- cdc_raw %>%
filter(
cause_of_death == "Firearm-related injury",
rate_type == "Age-adjusted",
time_period == "12 months ending with quarter",
year_and_quarter == "2025 Q2"
)
State gun law rankings are retrieved from the 2025 GIFFORDS Annual Gun Law Scorecard, which ranks all 50 states according to gun law strength.
giffords_url <- "https://giffords.org/lawcenter/resources/scorecard/"
webpage <- read_html(giffords_url)
tables <- webpage %>%
html_table(fill = TRUE)
scale_raw <- tables[[1]]
The CDC source stores state mortality values in separate columns. These fields are reshaped into one state-level dataset containing a state name and mortality rate.
state_columns <- paste0(
"rate_",
state.name %>%
str_to_lower() %>%
str_replace_all(" ", "_")
)
gun_data <- cdc_firearm %>%
select(all_of(state_columns)) %>%
pivot_longer(
cols = everything(),
names_to = "State",
values_to = "death_rate"
) %>%
mutate(
State = State %>%
str_remove("^rate_") %>%
str_replace_all("_", " ") %>%
str_to_title(),
death_rate = as.numeric(death_rate)
)
Validation checks confirm that the mortality dataset contains all 50 states and no missing state-level rates.
stopifnot(
nrow(gun_data) == 50,
sum(is.na(gun_data$death_rate)) == 0
)
The GIFFORDS table is cleaned and reduced to the state name and numerical ranking required for the analysis.
names(scale_raw) <- trimws(names(scale_raw))
names(scale_raw)[
str_detect(
names(scale_raw),
"Gun Law Strength"
)
] <- "Gun_Law_Rank"
scale <- scale_raw %>%
filter(
State != "Share",
Grade != "Share",
Gun_Law_Rank != "Share"
) %>%
mutate(
Gun_Law_Rank = as.numeric(Gun_Law_Rank)
) %>%
filter(
!is.na(State),
!is.na(Gun_Law_Rank)
) %>%
select(
State,
Gun_Law_Rank
)
GIFFORDS provides the numerical Gun Law Strength Rank. For this analysis, the ranking is divided into five equal groups to facilitate comparison:
These categories are created for this analysis and are not official GIFFORDS classifications.
scale <- scale %>%
mutate(
strictness_category = case_when(
Gun_Law_Rank >= 1 & Gun_Law_Rank <= 10 ~ "Most Strict",
Gun_Law_Rank >= 11 & Gun_Law_Rank <= 20 ~ "Strict",
Gun_Law_Rank >= 21 & Gun_Law_Rank <= 30 ~ "Moderate",
Gun_Law_Rank >= 31 & Gun_Law_Rank <= 40 ~ "Lax",
Gun_Law_Rank >= 41 & Gun_Law_Rank <= 50 ~ "Most Lax",
TRUE ~ NA_character_
)
)
The CDC and GIFFORDS datasets are then joined using state name.
analysis_data <- gun_data %>%
left_join(
scale,
by = "State"
)
stopifnot(
nrow(analysis_data) == 50,
sum(is.na(analysis_data$Gun_Law_Rank)) == 0
)
The analysis identifies a descriptive relationship between state gun law strength and firearm mortality. States in stricter gun law categories generally have lower age-adjusted firearm mortality rates than states in more lax categories, although variation within and overlap across categories are also evident.
Overall, the results show a state-level association between gun law strength and firearm mortality while highlighting differences among states within the same policy categories.
This analysis is descriptive and does not establish that differences in gun law strictness cause differences in firearm mortality. State mortality patterns may reflect demographic, economic, social, geographic, cultural, and other policy factors not accounted for here.
The workflow includes quality-control checks to confirm that all 50 states are present, mortality values are populated for the selected period, and all states successfully match across the two data sources.
Code is included throughout to document the data acquisition, cleaning, classification, integration, and validation process.
Originally developed as part of my M.S. in Data Science coursework at CUNY SPS, this project was updated with more recent public data, current gun law rankings, additional data-quality checks, and a reproducible workflow.