Mapping NIBRS Progress: State-by-State Adoption Rates and Historical Agency Enrollment
Using {ggplot2} and {geofacet}, this graphic showcases a creative hack to apply continuous color scales in geom_density() by converting it to geom_line(), alongside composing geographically arranged layouts for state-by-state insights into NIBRS adoption trends.
#TidyTuesday
Colour Scales
{geofacet}
Author
Aditya Dahiya
Published
February 19, 2025
About the Data
The data explored this week comes from the FBI Crime Data API, specifically focusing on agency-level data across all 50 states in the USA. This dataset, part of the FBI’s Uniform Crime Reporting (UCR) Program, includes information from over 18,000 law enforcement agencies, ranging from federal and state agencies to local, university, and tribal entities.
The data is dynamically updated through the FBI’s Crime Data Explorer (CDE), which was launched in 2017 to provide a more accessible and interactive platform for analyzing crime statistics.
Key variables in the dataset include agency type, geographic location (latitude, longitude, county, and state), and participation in the National Incident-Based Reporting System (NIBRS). Questions such as how agency types vary, their geographic distribution, and trends in NIBRS adoption can be explored using this dataset.
The data is available for analysis in both R and Python, with options to access it via the TidyTuesday GitHub repository. Thanks to Ford Johnson for curating this dataset.
Figure 1: This graphic explores the adoption of the FBI’s National Incident-Based Reporting System (NIBRS) across the United States, combining geographic and temporal insights. The faceted map, arranged by state, highlights the percentage of law enforcement agencies participating in NIBRS, revealing regional trends in adoption. Paired with density plots, it also visualizes the yearly enrollment of agencies into NIBRS, showcasing when states saw the most significant shifts toward modernized crime reporting, offering a deeper understanding of its geographic spread and historical progression across the U.S. law enforcement landscape.
How I made this graphic?
Loading required libraries
Code
# Data Import and Wrangling Toolslibrary(tidyverse) # All things tidy# Final plot toolslibrary(scales) # Nice Scales for ggplot2library(fontawesome) # Icons display in ggplot2library(ggtext) # Markdown text support for ggplot2library(showtext) # Display fonts in ggplot2library(colorspace) # Lighten and Darken colourslibrary(geofacet) # Geographic facets in R# Option 2: Read directly from GitHubagencies <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2025/2025-02-18/agencies.csv')
Visualization Parameters
Code
# Font for titlesfont_add_google("Kavivanar",family ="title_font") # Font for the captionfont_add_google("Saira Extra Condensed",family ="caption_font") # Font for plot textfont_add_google("Mulish",family ="body_font") showtext_auto()# A base Colourbg_col <-"grey92"seecolor::print_color(bg_col)# Colour for highlighted texttext_hil <-"grey30"seecolor::print_color(text_hil)# Colour for the texttext_col <-"grey20"seecolor::print_color(text_col)# Define Base Text Sizebts <-90# Caption stuff for the plotsysfonts::font_add(family ="Font Awesome 6 Brands",regular = here::here("docs", "Font Awesome 6 Brands-Regular-400.otf"))github <-""github_username <-"aditya-dahiya"xtwitter <-""xtwitter_username <-"@adityadahiyaias"social_caption_1 <- glue::glue("<span style='font-family:\"Font Awesome 6 Brands\";'>{github};</span> <span style='color: {text_hil}'>{github_username} </span>")social_caption_2 <- glue::glue("<span style='font-family:\"Font Awesome 6 Brands\";'>{xtwitter};</span> <span style='color: {text_hil}'>{xtwitter_username}</span>")plot_caption <-paste0("**Data:** Ford Johnson through FBI's Crime Data Explorer", " | **Code:** ", social_caption_1, " | **Graphics:** ", social_caption_2 )rm(github, github_username, xtwitter, xtwitter_username, social_caption_1, social_caption_2)# Add text to plot-------------------------------------------------plot_title <-"Mapping NIBRS Progress"plot_subtitle <-str_wrap("State-by-State Adoption Rates and Historical Agency Enrollment in FBI's National Incident-Based Reporting System.", 62)str_view(plot_subtitle)
Exploratory Data Analysis and Wrangling
Code
library(summarytools)agencies |>dfSummary() |>view()names(agencies)range(agencies$nibrs_start_date, na.rm = T)agencies |>group_by(state_abbr) |>summarise(is_nibrs =mean(is_nibrs, na.rm = T) )# Custom labeling function for displaying years in 2 or 4 digitscustom_date_labels <-function(x) { year_labels <-format(x, "%y") # Two-digit year year_labels[x ==as.Date("1985-01-01") | x ==as.Date("2000-01-01")] <-format(x[x ==as.Date("1985-01-01") | x ==as.Date("2000-01-01")], "%Y") # Four-digit for 1985 and 2000return(year_labels)}# Since geom_density() and stat_density() do not support clour along x-axis,# lets compute density manually, and then colour it using geom_line() or# geom_point()# Compute density for each state separatelydf1 <- agencies |># Remove missing valuesfilter(!is.na(nibrs_start_date)) |>mutate(numeric_date =as.numeric(nibrs_start_date) ) |># Convert date to numericgroup_by(state_abbr) |>summarise(density_obj =list(density( numeric_date, n =512 ) ), .groups ="drop" ) |># Compute density per statemutate(x =map( density_obj, ~as.Date(.x$x, origin ="1970-01-01") ), # Convert back to datesy =map(density_obj, ~ .x$y) # Extract density values ) |>select(state_abbr, x, y) |>unnest(cols =c(x, y)) # Expand lists into columns# Compute percentage agencies in each state reporting data to FBIdf2 <- agencies |>group_by(state_abbr) |>summarise(yes =mean(is_nibrs, na.rm =TRUE),no =1- yes ) |>pivot_longer(cols =c(yes, no),names_to ="fill_var",values_to ="value" ) |>mutate(fill_var =str_to_title(fill_var))
# Saving a thumbnaillibrary(magick)# Saving a thumbnail for the webpageimage_read(here::here("data_vizs", "tidy_fbi_crime_data.png")) |>image_resize(geometry ="x400") |>image_write( here::here("data_vizs", "thumbnails", "tidy_fbi_crime_data.png" ) )
Session Info
Code
# Data Import and Wrangling Toolslibrary(tidyverse) # All things tidy# Final plot toolslibrary(scales) # Nice Scales for ggplot2library(fontawesome) # Icons display in ggplot2library(ggtext) # Markdown text support for ggplot2library(showtext) # Display fonts in ggplot2library(colorspace) # Lighten and Darken colourslibrary(geofacet) # Geographic facets in Rsessioninfo::session_info()$packages |>as_tibble() |>select(package, version = loadedversion, date, source) |>arrange(package) |> janitor::clean_names(case ="title" ) |> gt::gt() |> gt::opt_interactive(use_search =TRUE ) |> gtExtras::gt_theme_espn()
Table 1: R Packages and their versions used in the creation of this page and graphics