library(readxl)
library(tidyverse)
library(plotly)
library(kableExtra)
library(here)
#library(leaflet)
library(gridExtra)
library(cowplot)

# Data ----

## WA OFM Population data (post-censal estimate series) ----
## "Filter" variable identifies level, 1 is county total

## Since this file is usually made by scripts in the WA-Pop repo
## we check for the file there, and only download if it's not

if(!file.exists("~/GitHub/WA-pop/data-outputs/WA_pop.rda")){
  
  warning("Need to update WA population data each year")
  
  url <- "https://ofm.wa.gov/sites/default/files/public/dataresearch/pop/april1/hseries/ofm_april1_postcensal_estimates_pop_1960-present.xlsx"
  destfile <-  "~/GitHub/WA-pop/ofm_april1_postcensal_estimates_pop_1960-present.xlsx"
  curl::curl_download(url, destfile)
  wa_pop_raw <- read_excel(destfile, sheet = "Population", skip = 3)
  
} else {
  load("~/GitHub/WA-pop/data-outputs/WA_pop.rda")
}

## Extract the data we need for county totals

wa_pop <- wa_pop_raw %>%
  filter(Filter == 1 | Jurisdiction == "State Total") %>%
  select(Jurisdiction, matches("2012|2013|2014|2015|2016|2017|2018|2019|2020|2021|2022")) %>%
  rename_at(vars(starts_with("2")), ~sub(" Postcen.*tion.*", "", .)) %>%
  rename_at(vars(contains("2020")), ~sub(".*$", "2020", .)) %>%
  rename_at(vars(starts_with("2")), ~sub("^", "Y", .)) %>%
  mutate(across(starts_with("Y"), ~as.numeric(.)),
         pct.chg = Y2022/Y2012 - 1)

  
## just the state totals by year
wa_poptotal_long   <- wa_pop_raw %>%
  filter(Jurisdiction == "State Total") %>%
  select(matches("2012|2013|2014|2015|2016|2017|2018|2019|2020|2021|2022")) %>%
  pivot_longer(everything(),
               names_to = "Year",
               values_to = "StatePop") %>%
  mutate(Year = sub("Postcen.*tion.*", "", Year),
         Year = sub("Cen.*tion", "", Year),
         Year = as.numeric(Year),
         StatePop = as.numeric(StatePop))


## OFM NIBRS ----
## Get cleaned OFM NIBRS data for WA (annual updates lag by about 10 mos)

## NOTE THEIR RATES ARE PER 1000 PERSONS 
## We download each time in case the files have been updated.

library(readxl)
#url <- "https://sac.ofm.wa.gov/sites/default/files/public/download/nibrs_12_20.xlsx"
url <- "https://sac.ofm.wa.gov/sites/default/files/public/download/nibrs_12_22.xlsx"
destfile <-  here::here("Data", "OFM NIBRS", "nibrs_12_22.xlsx")
curl::curl_download(url, destfile)
nibrs_wide <- read_excel(destfile) #%>%
  # select(Location:Animal_Cruelty)

nibrs_rates_wide <- nibrs_wide %>%
  mutate_at(vars(Murder:Viol_Of_No_Contact), ~ 100000 * .x/Population) %>%
  mutate_at(vars(Arson:Theft), ~ 100000 * .x/Population) %>%
  mutate_at(vars(Drug_Violations:Animal_Cruelty), ~ 100000 * .x/Population)

# We mimic the version of the data obtained when downloading from the Tableau portal, in
# case we ever need to go back to using that

nibrs_long <- nibrs_wide %>%
  pivot_longer(cols = Population:Animal_Cruelty,
               names_to = "Offence label",
               values_to = "Count") %>%
  rename(County = county) %>%
  mutate(
    IndexYear = as.numeric(IndexYear),
    value = Count, # some are rates, some are counts
    `Offence Type` = case_when(
      `Offence label` == "Population" ~ "Location Population",
      grepl("Prsn|Murd|Mans|Forc|Assa|Kidn|Huma|Viol_Of", `Offence label`) ~ "Crimes Against Persons",
      grepl("Prpr|Arso|Brib|Burg|Counter|Dest|Exto|Robb|Thef", `Offence label`) ~ "Crimes Against Property",
      grepl("Scty|Drug|Gamb|Porn|Pros|Weap|Anim", `Offence label`) ~ "Crimes Against Society",
      grepl("Total|Rate", `Offence label`) ~ "Total Crimes"
    ),
    `Offence label` = recode(
      `Offence label`,
      "Population" = "Location Population",
      "Total" = "Total Crimes",
      "Rate" = "Total Crime Rate",
      "PrsnTotal" = "Person Crime Total",
      "PrsnRate" = "Person Crime Rate",
      "PrprtyRate" = "Property Crime Rate",
      "PrprtyTotal" = "Property Crime Total",
      "SctyTotal" = "Society Crimes Total",
      "SctyRate" = "Society Crime Rate"
    ))
#

nibrs_rates_long <- nibrs_rates_wide %>%
  pivot_longer(cols = Population:Animal_Cruelty,
               names_to = "Offence label",
               values_to = "Count") %>%
  rename(County = county) %>%
  mutate(
    IndexYear = as.numeric(IndexYear),
    value = Count, # some are rates, some are counts
    `Offence Type` = case_when(
      `Offence label` == "Population" ~ "Location Population",
      grepl("Prsn|Murd|Mans|Forc|Assa|Kidn|Huma|Viol_Of", `Offence label`) ~ "Crimes Against Persons",
      grepl("Prpr|Arso|Brib|Burg|Counter|Dest|Exto|Robb|Thef", `Offence label`) ~ "Crimes Against Property",
      grepl("Scty|Drug|Gamb|Porn|Pros|Weap|Anim", `Offence label`) ~ "Crimes Against Society",
      grepl("Total|Rate", `Offence label`) ~ "Total Crimes"
    ),
    `Offence label` = recode(
      `Offence label`,
      "Population" = "Location Population",
      "Total" = "Total Crimes",
      "Rate" = "Total Crime Rate",
      "PrsnTotal" = "Person Crime Total",
      "PrsnRate" = "Person Crime Rate",
      "PrprtyRate" = "Property Crime Rate",
      "PrprtyTotal" = "Property Crime Total",
      "SctyTotal" = "Society Crimes Total",
      "SctyRate" = "Society Crime Rate"
    ))


ofm.source.url <- "https://sac.ofm.wa.gov/data"

wa.minyr = min(nibrs_long$IndexYear)
wa.maxyr = max(nibrs_long$IndexYear)

wa.breaks = wa.minyr:wa.maxyr
wa.labels = as.character(wa.minyr:wa.maxyr)


# For tables showing participation stats in NIBRS

## Aggregate by agency
## partial variable identifies agencies with non-continuous participation

nibrs_yrs_agency <- nibrs_long %>%
  select(year = IndexYear, County, Location, measure = `Offence label`, pop = Count) %>%
  filter(measure == "Location Population" & !grepl("TOTAL", Location)) %>%
  group_by(County, Location) %>%
  arrange(year) %>%
  summarize(startyr = min(year),
            endyr = max(year),
            start.pop = first(pop),
            end.pop = last(pop),
            avg.pop = round(mean(pop, na.rm=T))) %>%
  arrange(startyr, County, Location) %>%
  mutate(numyrs = endyr - startyr + 1,
         partial = ifelse(numyrs < (wa.maxyr-wa.minyr+1) & endyr != wa.maxyr, 1, 0))


## Aggregate by county
nibrs_yrs_county <- nibrs_yrs_agency %>%
  group_by(County) %>%
  summarize(n.jurisdictions.start = sum(startyr==wa.minyr),
            n.jurisdictions.end = sum(endyr==wa.maxyr),
            last.startyr = max(startyr))


## Population change by location
## Remember: pop here is pop covered by NIBRS
## So county totals are not comparable until full coverage
## full coverage year = 2018

start <- paste0("Y", as.character(wa.minyr))
end <- paste0("Y", as.character(wa.minyr))
wa_pop_changes <- nibrs_wide %>%
  filter(IndexYear == wa.minyr | IndexYear == 2018 | IndexYear == wa.maxyr) %>%
  select(IndexYear, County=county, Location, Population) %>%
  pivot_wider(id_cols = c(County, Location), 
              names_from = IndexYear, 
              names_prefix = "Y",
              values_from = Population) %>%
  mutate(pct.chg.pop.cvr = Y2022/Y2012 -1)

## NIBRS pop coverage
## pct coverage is based on WA pop that year

nibrs_pop_coverage <- nibrs_long %>%
  select(year = IndexYear, County, Location, measure = `Offence label`, pop = Count) %>%
  filter(measure == "Location Population" & !grepl("TOTAL", Location)) %>%
  group_by(year) %>%
  summarize(num.agencies = n(),
            pop.cvr = sum(pop, na.rm=T)) %>%
  left_join(wa_poptotal_long, by=c("year" = "Year")) %>%
  mutate(pop.cvr.pct = pop.cvr/StatePop)

Introduction

The narrative of “rising crime rates” seems to emerge whenever there is a threat of reforms that address police tactics and accountability. Such claims are currently playing a large role in the narrative for rolling back police reforms that WA State enacted in 2021.

Since the data on crime statistics in WA state are publicly available, it is worth comparing these claims to the evidence. In this report we track the statewide trends in crime rates (per 1,000 residents) from 2012-2022 in WA, using the official statewide data published by the Washington Statistical Analysis Center.

  • The statewide data for 2023 data are not available yet from the SAC, they will likely be released in October 2024.

KEY FINDINGS

In one sentence: There is no evidence that crime has increased in WA State after the reform legislation in 2021

Overall, crime rates have displayed typical year to year variation: rates fell by 4% in 2021, and rose by 8% in 2022.

  • The legislation took effect in July 2021.
  • Property crime rates show a slight upward trend, starting in 2020 (the year before the legislative reforms, part of a nationwide trend)
  • The rate of crimes against persons has held steady.
  • The rate of crimes against society fell substantially, largely due to the effects of the Blake decision

Trends at the county and local levels over the past decade show substantial variation, with no consistent patterns for 2022. This, too, suggests that the statewide legislative reforms did not have an identifiable statewide impact on crime rates.


The national crime data reporting system, which these WA data are part of, has been in transition for about a decade, from the old “Summary Reporting System” (SRS) to the new “National Incident Based Reporting System” (NIBRS). The transition was phased in over time, allowing agencies to switch over as their systems were updated. The old SRS was finally retired in 2021. This has implications for the trends the data show over time, and it makes interpreting these trends complicated. Please see the section on Data Sources in this report for more details and this report from the Marshall Project for a summary of the issues.

Note: you can navigate through this document using the floating Table of Contents to the left, and the tabs within sections.

Data Source

The data used in this report are the official WA state crime data, downloaded from the Washington Statistical Analysis Center website. Some details are provided below on where these official data come from; much more information can be obtained by clicking through the links provided.

This report follows the standard approach of presenting the trends in crime rates (per 1,000 persons), not the raw crime counts. This helps to mitigate the impact of the changing coverage of the NIBRS program over this time, and adjust for the changes in population size of each jurisdiction. The population data used in this adjustment is included in the official WA State data set from WSAC.

The NIBRS program

The WA State crime data come from the “National Incident Based Reporting System” (NIBRS). Law enforcement agencies in the US participate in NIBRS on a voluntary basis as part of the Federal Bureau of Investigation’s Uniform Crime Reporting program. NIBRS replaced an earlier crime data reporting program called the “Summary Reporting System”. More information on NIBRS can be found at the Bureau of Justice Statistics website, and some history of the long process of transition between the two systems can be found on Wikipedia. NIBRS collects information on 23 different offense categories made up of 47 offenses and allows all reportable offenses within an incident to be reported.

Here in WA, the Washington Association of Sheriffs and Police Chiefs (WASPC) collects the monthly reported incident-based offense data from participating law enforcement agencies, cleans it, and sends it on to the FBI. They also oversaw the transition from the old SRS system to the new NIBRS system. The NIBRS program in WA State was fully implemented for non-tribal agencies in 2018, but agencies joined the system at different times before that; we present the data on participation by year below.

WASPC publishes an annual report of the NIBRS data, with summary breakdowns for each jurisdiction. This report is in PDF format, so it is not convenient to extract the data for independent analysis.

  • The lag time for this report is about 6 months.

The NIBRS data are made available to the WA public through the Access Washington initiative, and are curated by the Washington State Statistical Analysis Center (WA SAC) in the Office of Financial Management. The data series begins in 2012 – the original target year established by WASPC for full implementation of NIBRS.

  • WA SAC host an online interactive portal for exploring the NIBRS data here.

  • The WA SAC NIBRS data can be downloaded from their site here.

  • The lag time for the release of these data is typically about 10 months.

Information on the crime types included in NIBRS can be found in this document.

Limitations of the NIBRS data

While WA SAC reports the NIBRS data as a continuous series from 2012 to 2022, it is important to recognize that the number of agencies reporting to the system has changed over this time.

In 2012, the target date for full statewide reporting, 197 out of 264 agencies were reporting (75%). The non-reporting agencies included some of the largest agencies in the state (for example, the King and Spokane County Sheriff’s offices and the Olympia Police Department), so the percent of the population covered by NIBRS in 2012 was about 65%.

By 2018, the NIBRS population coverage was close to complete, as seen in the table below.

#nibrs_yrs_agency %>% ggplot(aes(x=startyr)) + geom_bar(fill='blue', alpha=.7)


options(knitr.kable.NA = '')
nibrs_pop_coverage %>%
  mutate(pop.cvr.pct = 100*pop.cvr.pct,
         year = as.character(year)) %>%
  rename(`Year` = year,
         `Agencies Reporting` = num.agencies,
         `Reporting Agencies` = pop.cvr,
         `State Total` = StatePop,
         `% covered` = pop.cvr.pct) %>%
  
  kable(caption = "NIBRS Coverage By Year in WA SAC Data Series",
        digits = c(0,0,0,0,1),
        format.args = list(big.mark = ",")) %>%
  
  kable_styling(bootstrap_options = c("striped","hover")) %>%
  
  #row_spec(row=dim(tab)[1], bold = T) %>%

  add_header_above(c(" " = 2, "Population Coverage" = 3)) #%>%
NIBRS Coverage By Year in WA SAC Data Series
Population Coverage
Year Agencies Reporting Reporting Agencies State Total % covered
2012 197 4,424,469 6,817,770 64.9
2013 209 4,833,361 6,882,400 70.2
2014 215 5,231,712 6,968,170 75.1
2015 223 6,076,465 7,061,410 86.1
2016 229 6,645,947 7,183,700 92.5
2017 225 6,769,937 7,310,300 92.6
2018 234 7,400,602 7,427,570 99.6
2019 232 7,520,805 7,546,410 99.7
2020 233 7,634,791 7,706,310 99.1
2021 234 7,749,936 7,766,975 99.8
2022 236 7,845,853 7,864,400 99.8
  #footnote(general = c("Percents may not sum to 100 due to rounding"))

There is missing data from 2012-2018 from the non participating agencies. This means that:

  • The raw crime counts are not comparable over time when aggregated up into larger groups (like counties, or the statewide totals).

    Solution: Use crime rates per 1,000 persons.

    Crime rates are numerically comparable when aggregated, since the population adjustment for the participating agencies keeps track of the changing coverage.

  • Trends in aggregated crime rates can still be influenced by the missing data, if the rates for the missing agencies are different than the participating agencies.

    Solution: Restrict the focus to data after 2018

    Once coverage reaches the 99% level in 2018, the potential impacts of missing data on trend estimates is minimal.


Population changes

The other reason crime rates (rather than raw counts) are preferable when looking at trends over time is that they adjust for any changes in population size. The WA state population grew by 15% from 2012 to 2022. But the population growth rate was not the same statewide: some counties actually lost population, while other counties grew even faster.

The graph below shows the percent change in population size by county. The state total is highlighted by a black outline. Hovering over the bars in the plot will pop up the population counts and change for each jurisdiction.

p <- ggplot(wa_pop,
       aes(x=pct.chg, y=reorder(Jurisdiction, pct.chg), 
           fill = pct.chg,
           col=factor(ifelse(Jurisdiction=="State Total", 1, 0)),
           text=paste(Jurisdiction, "<br>",
                      "start:", scales::comma(Y2012), "<br>",
                      "end:", scales::comma(Y2022), "<br>",
                      "change:", scales::percent(pct.chg, acc=1)))) +
  
  geom_bar(stat="identity") +
  
  scale_color_manual(values = c("grey", "black")) +
  scale_fill_gradient(
    low = "seashell", 
    high = "firebrick") +
  
  scale_x_continuous(labels = scales::percent) + #_format(acc=1)) +
  
  theme(axis.text.y=element_text(size=rel(0.6)), legend.position = "none") +
  labs(title = paste0("Population Change (%) by County: 2012-", wa.maxyr),
       x="Percent change",
       y="County")

ggplotly(p, tooltip = "text")
  • Data source: Official WA State estimates from the Office of Financial Management. This is the “post-censal estimate” series, available for download here

NIBRS participation data

In the tabs below you can find the NIBRS participation data for WA State broken down by county and agency.

County participation stats

This table shows the NIBRS participation statistics by County:

  • how many law enforcement agencies joined in 2012, the first year of NIBRS data series

  • how many were participating in 2022, the most recent year, and

  • the year the last agency to participate joined.

Tips: See all 39 counties by selecting “50” in the “show entries” box. Sorting the table by the last column will make it easier to see which counties have complete participation for all years.

nibrs_yrs_county %>%
  DT::datatable(rownames = F,
                colnames = c("County", "Agencies in 2012", 
                             paste("Agencies in", wa.maxyr), "Year last agency joined"),
                caption = "NIBRS PARTICIPATION STATISTICS FOR EACH COUNTY",
                filter = 'top',
                escape = FALSE,
                #fillContainer = FALSE, 
                options = list(pageLength = 10))

Agency participation stats

This table shows when each law enforcement agency started participating in NIBRS, the most recent year of participation and the number of years of participation. The program was started in 2012, so the maximum number of years of participation is 11.

nibrs_yrs_agency %>%
  select(-contains("pop")) %>%
  select(-partial) %>%
  filter(!grepl("TOTAL", Location )) %>%
  
  DT::datatable(rownames = F,
                colnames = c("County", "Agency", "First year", "Last year", "Years participated"),
                caption = "NIBRS PARTICIPATION STATISTICS FOR EACH LAW ENFORCEMENT AGENCY",
                filter = 'top',
                escape = FALSE,
                #fillContainer = FALSE, 
                options = list(pageLength = 10))