library(tidyverse)
library(plotly)
library(kableExtra)
library(here)
library(leaflet)
library(lubridate)
library(icons)
library(htmlwidgets)


# get cleaned, merged data for WA
load(file = here("Data", "Clean", params$rdafile))

# Data prep (common)
source(here("Analyses", "Common", "waAnalysesPrep.R"))

# for geocoded LDs
with(homicides, write.csv(cbind(feID,latitude,longitude),
                          here("Data", "Clean",
                               "geocodes2015.csv")))

Introduction

This report tracks the trends in people killed by police in WA since 2015, a period that spans before and after a series of legislative reforms intended to reduce police violence and increase accountability for misconduct. A brief overview of these changes is given in the Legislative History section of this report.

The TOC on the left can be used to navigate through the sections of the report. There are interactive maps with links to media reports, breakdowns by demographics, geography, cause of death, agency and time to provide some insight into the patterns and trends, and a list of all persons killed to acknowledge the lives lost and provide a starting point for those seeking more information on specific cases. Information on the data sources and limitations can be found in the Data sources section. The data and scripts used for this report are available on GitHub, access details can be found in the Public Access section.

MOST RECENT DATA UPDATE (2025-04-12):

Before the 2021 legislative reforms, one out of every 6 persons killed in Washington state was killed during an encounter with law enforcement. (On average, about 250 homicide deaths were recorded each year in WA State from 2015-2020, with about 40 killed during encounters with police. You can view and download the state’s official death data here).

In 2021, the year many reforms passed, the number of people killed by police in Washington dropped by 60%, the largest decline seen in any state. More information on the temporal trends can be found in the section on “Trends over time”.


To find out more about what we do or get involved, please email us at Next Steps Washington


Interactive Map

You can click the numbered circles to reach the individual map pointers for each person killed by police.

  • Hovering over the pointer brings up the name of the person killed and agency of the officer who killed them;

  • Clicking the pointer will bring up a url to a news article on the case (if available).

map <- leaflet(data = homicides, #%>% filter(year==2023), 
               width = "100%") %>% 
  addTiles() %>%
  addMarkers( ~ longitude,
              ~ latitude,
              popup = ~ url_click,
              label = ~ as.character(paste(name, "by", agency)),
              clusterOptions = markerClusterOptions()
              )
map

Breakdowns

Race

Table

See the “Discussion” tab for more information on how race is ascertained and why the number of missing cases is so high.

tab <- homicides %>%
  mutate(race = case_match(race,
    "API" ~ "Asian/Pacific Islander",
    "BAA" ~ "Black/African American",
    "HL" ~ "Hispanic/Latinx",
    "NAA" ~ "Native American/Indigenous",
    "WEA" ~ "White/European American",
    .default = race),
    race = fct_relevel(race,  "Unknown", after = Inf)
  ) %>%
group_by(race) %>%
  summarize(Number = n(),
            Percent = round(100*Number/nrow(homicides), 1)
  ) %>%
  bind_rows(data.frame(race="Total", 
                       Number = sum(.$Number), 
                       Percent = sum(.$Percent))) %>%
  rename(Race = race) 

tab %>%
  kable(caption = "Breakdown by Race") %>%
  kable_styling(bootstrap_options = c("striped","hover")) %>%
  row_spec(row=dim(tab)[1], bold = T) %>%
  add_footnote(label = "Percents may not sum to 100 due to rounding",
               notation = "symbol")
Breakdown by Race
Race Number Percent
Asian/Pacific Islander 25 6.2
Black/African American 45 11.2
Hispanic/Latinx 54 13.4
Native American/Indigenous 15 3.7
White/European American 202 50.2
Unknown 61 15.2
Total 402 99.9
* Percents may not sum to 100 due to rounding

Plots

homicides %>%
  mutate(race = case_match(race,
    "API" ~ "Asian/Pacific Islander",
    "BAA" ~ "Black/African American",
    "HL" ~ "Hispanic/Latinx",
    "NAA" ~ "Native American/Indigenous",
    "WEA" ~ "White/European American",
    .default = race),
    race = fct_relevel(race,  "Unknown", after = Inf)
  ) %>%
  count(race) %>%
  mutate(perc = n / nrow(homicides)) %>%
  
  ggplot(aes(x=race, 
             y = perc, 
             label = n)) +
  geom_bar(stat="identity", fill="blue", alpha=.5) +
  geom_text(aes(y = perc), size = 3, nudge_y = .025) +
  
  theme(axis.text.x = element_text(size=5.5)) +
  
  scale_y_continuous(labels = scales::percent_format(acc=1)) +
  
  labs(title = "Fatalities by Race",
       caption = paste0("WA State since ", 
                        params$from, 
                        "; y-axis=pct, bar label=count")) +
         xlab("Reported Race") +
         ylab("Percent of Total")

homicides %>%
  mutate(raceb = case_when(race == "WEA" ~ "White",
                           race == "Unknown" ~ "Unknown",
                           TRUE ~ "BIPOC"),
         raceb = fct_relevel(raceb, "Unknown", 
                             after = Inf)) %>%
  count(raceb) %>%
  mutate(perc = n / nrow(homicides)) %>%
  ggplot(aes(x=raceb, 
             y = perc, 
             label = n)) +
  geom_text(aes(y = perc), size = 3, nudge_y = .025) +
  geom_bar(stat="identity", fill="blue", alpha=.5) +
  
  scale_y_continuous(labels = scales::percent_format(acc=1)) +
  
  labs(title = "Fatalities by Race",
       caption = paste0("WA State since ", 
                        params$from, 
                        "; y-axis=pct, bar label=count")) +
  xlab("Reported Race") +
  ylab("Percent of Total")


Discussion

Racial disparities in the risk of being killed by police are one of the most important factors driving the public demand for police accountability and reform. For that reason it is important to understand how these numbers can, and cannot be used.

TL;DR While there are many uncertainties in the data that make it difficult/impossible to know the exact numbers, there are still some conclusions we can draw with confidence.


Many case reports are missing data on race

Over 25% of the cases in the original data do not have information that explicitly identifies the race of the person killed. Both the Fatal Encounters project and the Washington Post rely primarily on media reports (and some public records requests) to find these cases, so they are limited by the information provided in those sources, and if race is missing in one dataset it is typically missing in the other. The Fatal Encounters team used a statistical imputation model to try to predict race for the missing cases. A brief description of the methodology is online here. They are able to impute just over half of the missing cases with reasonable confidence, and we include these imputations in the breakdowns we report.

We use a hierarchical assignment of race for the data in this report: Fatal Encounters (FE) with imputation if available, Washington Post (WaPo) otherwise. Discrepancies in race assignment between FE and WaPo are rare, but when they happen we independently research and are either assign race as confirmed, or assign as FE if confirmation is not available. The resulting source distribution is shown below.

tab <- homicides %>%
  mutate(race.source = fct_relevel(race.source, "Race not known", after = Inf)) %>%
  group_by(race.source) %>%
  summarize(Number = n(),
            Percent = round(100*Number/nrow(homicides), 1)
  ) %>%
  bind_rows(data.frame(race.source ="Total", 
                       Number = sum(.$Number), 
                       Percent = sum(.$Percent))) %>%
  rename(`Race Variable Source` = race.source)

tab %>%
  kable(caption = "Breakdown by Source of Information") %>%
  kable_styling(bootstrap_options = c("striped","hover")) %>%
  row_spec(row=dim(tab)[1], bold = T) %>%
  add_footnote(label = "Percents may not sum to 100 due to rounding",
               notation = "symbol")
Breakdown by Source of Information
Race Variable Source Number Percent
FE 274 68.2
FE imputed 43 10.7
WaPo 24 6.0
Race not known 61 15.2
Total 402 100.1
* Percents may not sum to 100 due to rounding

Bottom line: The unknown race cases make it impossible to say exactly how many people killed by police are in each racial group.


We are reporting the raw counts in this report, not per capita rates

Breaking the total count down by race, the largest single group of persons killed by police are identified as White/European-American: 50%

So, does this mean that we can say: “There aren’t any racial disparities in persons killed by police?”.

No. This raw count can not be used to assess racial disparities, because the word disparity implies the risk is “disproportionate” – that is, higher (or lower) than proportional. Proportional is a comparative term: it compares the proportion of fatalities by race, to the proportion of the population by race. If the two proportions are the same, then we can say there are no disparities. In this case, if 50% of the population is White, then we can say that their risk of being killed by police is proportional to their share of the population.

Bottom line: The raw counts can not be used to identify racial disparities.


So why don’t we calculate standardized per capita rates?

Because the classification of cases by race, in both the Fatal Encounters and WaPo datasets, is not consistent with the classification of race in the US Census population data that are needed to calculate per capita rates.

The calculation of per capita rates takes the ratio of the fatality count (in the numerator) to the population count (in the denominator). To break this down by race we need a consistent measure of race to use for the numerator and denominator for all groups. And we don’t have that.

  • About 5% of people in WA state report two or more races in the US Census. This multiple-race classification does not exist in the datasets on persons killed by police (though the Washington Post is trying to correct that going forward).

  • Hispanic/Latinx is an ethnicity that crosses several racial groups, primarily White, Black and Native American. In the US Census data, race is measured separately from ethnicity, so you can see these overlaps. But in the Fatal Encounters and WaPo datasets, “Hispanic” is coded as a racial group, rather than as a separate ethnicity classification.

For more information on how the Census codes race and ethnicity:

U.S. Decennial Census Measurement of Race and Ethnicity Across the Decades: 1790–2020

Bottom line: The mismatch between racial categories coded in the FE and WaPo data, and the categories used in the US Census makes it impossible to calculate per capita rates accurately for all groups.


What we can say about disparities in WA State

The population of Washington State overwhelmingly identifies as White – 79% in 2020 source: WA State OFM. If we exclude Hispanics (which the Census treats as ethnicity, not race), non-Hispanic Whites comprise 70% of the WA population source: Statistical Atlas of the US. By contrast only 50% of the persons killed by police are identified as non-Hispanic White.

So there is a racial disparity in the risk of being killed by police:

The risk for non-Hispanic Whites is disproportionately low, because their share of fatalities is lower than their population share.

The “risk ratio” is a common way to combine this information into a single number that is easy to understand: the ratio of the fatality share to the population share. When the two shares are the same, the risk ratio equals 1. When it is less than one, this means the share of fatalities is lower than the population share; the risks are disproportionately low. When the risk ratio is greater than one it means the share of fatalities is larger than the population share; the risks are disproportionately high.

This can be summarized as a percentage: 100*(risk ratio-1), which tells you the percent lower (or higher) the risk ratio is than expected if the risk was proportional to the share of population.

In WA State:

  • For non-Hispanic whites this value is 100*( 50% / 70% -1) = -0.29 – their risk of being killed by police is 29% lower than expected.

  • For Blacks/African Americans this value is 100*( 11% / 4% -1) = 2.11 – their risk of being killed by police is 211% higher than expected.

Note that the number of “unknown race” cases after imputation is not enough to change the direction of these disparities. Even if we assume that all of the “unknown race” cases are White, their share of incidents would still be below their share in the population.

So, even though we can’t be certain of the exact value of the risk ratio, we can say with some confidence that there are racial disparities in the risk of being killed by police, and that these disparities indicate that non-Hispanic Whites are less likely than other racial groups to be killed by law enforcement officers.

Bottom line: while the largest single group of persons killed by police is identified as White, the risk ratio for this group, which adjusts for their share of the population, shows they are disproportionately less likely to be killed than other groups.


Perceived vs. self-identified race and implicit bias

The race classification in the FE and WaPo data most likely represent the subject’s race as reported by law enforcement. This may be the officer’s perception, or it may be informed by previous interactions and self-reports, we have no way of knowing. In addition, if our goal was to use these data as evidence for (or against) the effects of systemic implicit bias in the police use of deadly force, we would need to control for other variables that may influence these patterns, like the perceived and actual weapon status of the person killed, the circumstances that precipitated the incidents, the presence or absence of other persons at risk in the incidents, and the detailed population composition of the jurisdiction.

Bottom line: We can’t use these data to answer the question of the officer’s intention, or systemic implicit bias.


Cause of death

The table shows all of the causes that appear in the Fatal Encounters dataset. The plot aggregates these into three categories, as the first two (shot and vehicle-related) account for the large majority of cases. Vehicle-related refers to deaths caused by vehicle crashes or getting run over by a car. Typically these occur in the context of vehicle pursuits/chases, but sometimes they are the result of fatal accidents caused by on-duty officers.

Plot

homicides %>%
  mutate(
    cod_3 = factor(
      case_when(
        cod == "Gunshot" ~ "shot", 
        cod == "Vehicle" ~ "vehicle-related",
        TRUE ~ "other"),
      levels = c("shot", "vehicle-related", "other"))
  ) %>%
  count(cod_3) %>%
  mutate(perc = n / nrow(homicides)) %>%
  ggplot(aes(x=cod_3, 
             y = perc, 
             label = n)) +
  geom_text(aes(y = perc), size = 3, nudge_y = .025) +
  geom_bar(stat="identity", fill="blue", alpha=.5) +
  labs(title = "Fatalities by Cause of Death",
       caption = paste0("WA State since ", 
                        params$from, 
                        "; y-axis=pct, bar label=count")) +
  xlab("Cause of Death") +
  ylab("Percent of Total")

Table

tab <- homicides %>%
  group_by(cod) %>%
  summarize(Number = n(),
            Percent = round(100*Number/nrow(homicides), 1)
  ) %>%
  arrange(desc(Number)) %>%
  bind_rows(data.frame(cod ="Total", 
                       Number = sum(.$Number), 
                       Percent = sum(.$Percent))) 

tab %>%
  kable(caption = "Breakdown by Cause of Death",
        col.names = c("Cause of Death", "Number", "Percent")) %>%
  kable_styling(bootstrap_options = c("striped","hover")) %>%
  row_spec(row=dim(tab)[1], bold = T)  %>%
  add_footnote(label = "Percents may not sum to 100 due to rounding",
               notation = "symbol")
Breakdown by Cause of Death
Cause of Death Number Percent
Gunshot 296 73.6
Vehicle 74 18.4
Taser 12 3.0
Asphyxiated/Restrained 10 2.5
Drowned 5 1.2
Burned/Smoke inhalation 2 0.5
Other 2 0.5
Beaten 1 0.2
Total 402 99.9
* Percents may not sum to 100 due to rounding

Alleged weapon

The source of this information is typically the initial press release or report to the media from the involved law enforcement agency; it has not been independently verified. It should be interpreted as the alleged weapon status of the person who was killed.

The data come from the Post dataset, so this is not available for the additional cases in Fatal Encounters. The additional cases in Fatal Encounters are included in the “Unknown” category in the plot and table below.

Plot

homicides %>%
  count(weapon) %>%
  mutate(perc = n / nrow(homicides)) %>%
  ggplot(aes(reorder(weapon, perc),
             y = perc, 
             label = n)) +
  geom_text(aes(y = perc), size = 3, nudge_y = .025) +
  geom_bar(stat="identity", fill="blue", alpha=.5) +
  labs(title = "Fatalities by Alleged Victim Weapon",
       caption = paste0("WA State since ", 
                        params$from, 
                        "; y-axis=pct, bar label=count")) +
  xlab("Alleged Weapon Used by Victim") +
  ylab("Percent of Total")

Bodycam

This information comes from the Post dataset, so is missing for the additional cases in Fatal Encounters. These additional cases are listed as “Unknown” below.

homicides %>%
  mutate(bodycam = ifelse(bodycam, "Yes", "No"),
         bodycam = tidyr::replace_na(bodycam, "Unknown"),
         bodycam = fct_relevel(bodycam, "Unknown", 
                             after = Inf)) %>%
  group_by(bodycam) %>%
  summarize(Number = n(),
            Percent = round(100*Number/nrow(homicides), 1)
  ) %>%
  bind_rows(data.frame(bodycam ="Total", 
                       Number = sum(.$Number), 
                       Percent = sum(.$Percent))) %>%
  kable(caption = "Breakdown by Bodycam",
        col.names = c("Bodycam", "Number", "Percent")) %>%
  kable_styling(bootstrap_options = c("striped","hover")) %>%
  row_spec(row=4, bold = T)  %>%
  add_footnote(label = "Percents may not sum to 100 due to rounding",
               notation = "symbol")
Breakdown by Bodycam
Bodycam Number Percent
No 237 59.0
Yes 39 9.7
Unknown 126 31.3
Total 402 100.0
* Percents may not sum to 100 due to rounding

County

Washington State has 39 Counties. Since 2015 there have been people killed by police in 28 of them.

Plot

homicides %>%
  group_by(county) %>%
  summarize(n = n(),
            perc = n / nrow(homicides)) %>%
  ggplot(aes(reorder(county, perc),
             y = perc, 
             label = n)) +
  geom_text(aes(y = perc), size = 3, nudge_y = .005) +
  geom_bar(stat="identity", fill="blue", alpha=.5) +
  
  labs(title = "County",
       caption = paste0("WA State since ", 
                        params$from, 
                        "; x-axis=pct, bar label=count")) +
  xlab("County where fatal encounter happened") +
  ylab("Percent of Total") +
  
  scale_y_continuous(labels = scales::percent_format(acc=1)) +
  
  coord_flip()

Table

homicides %>%
  group_by(county) %>%
  summarize(Number = n(),
            Percent = round(100*Number/nrow(homicides), 1)
  ) %>%
  DT::datatable(rownames = F,
                caption = "Breakdown by County")

Agency involved

We use the agency information from Fatal Encounters.

  • The plot only shows agencies with more than two fatalities since 2015. Since there are more than 100 agencies with fatalities, the plot text would become impossible to read if all of them were included. Incidents with more than one agency involved are grouped together and labeled “Multiple Agencies”.

  • The table includes all agencies, all fatalities, and identifies the full list of agencies when there is more than one involved in the incident(s).

Plot

homicides %>%
  mutate(agencyR = ifelse(grepl(",", agency), "Multiple agencies", agency)) %>%
  group_by(agencyR) %>%
  summarize(n = n(),
            perc = n / nrow(homicides)) %>%
  filter(n > 2) %>%
  
  ggplot(aes(reorder(agencyR, perc),
             y = perc, 
             label = n)) +
  geom_text(aes(y = perc), size = 2, nudge_y = .003) +
  geom_bar(stat="identity", fill="blue", alpha=.5) +
  
  labs(title = "Agency responsible (agencies with > 2 fatalities only)",
       caption = paste0("WA State since ", 
                        params$from, 
                        "; x-axis=pct of all incidents, bar label=count")) +
  xlab("") +
  ylab("Percent of Total") +
  
  # this works with the flipped axis id
  theme(axis.text.y = element_text(size=6)) +
  
  scale_y_continuous(labels = scales::percent_format(acc=1)) +
  
  coord_flip()

Table

homicides %>%
  group_by(agency) %>%
  summarize(Number = n(),
            Percent = round(100*Number/nrow(homicides), 1)
  ) %>%
  DT::datatable(rownames = F,
                caption = "Breakdown of Fatalities by Agency of Involved Officer",
                filter = 'top',
                escape = FALSE, 
                extensions = 'Buttons', 
                options = list(
                  dom = 'Bfrtip',
                  buttons = c('copy', 'csv', 'excel', 'pdf', 'print')) )

Legislative Districts

This information is obtained by geocoding the latitude and longitude data on the geocodio platform.

Washington State has 49 legislative districts. Since 2015 there have been people killed by police in 45 of them.

Given the number of districts involved, we only plot those with more than one fatality since 2015 for legibility .

The table includes all districts and all incidents.

Plot

homicides %>%
  group_by(WA_District) %>%
  summarize(n = n(),
            perc = n / nrow(homicides)) %>%
  filter(n > 1) %>%
  
  ggplot(aes(reorder(WA_District, perc),
             y = perc, 
             label = n)) +
  geom_text(aes(y = perc), size = 2, nudge_y = .003) +
  geom_bar(stat="identity", fill="blue", alpha=.5) +
  
  labs(title = "WA Legislative District (LDs with > 1 fatality only)",
       caption = paste0("WA State since ", 
                        params$from, 
                        "; x-axis=pct of all incidents, bar label=count")) +
  xlab("") +
  ylab("Percent of Total") +
  
  # this works with the flipped axis id
  theme(axis.text.y = element_text(size=6)) +
  
  scale_y_continuous(labels = scales::percent_format(acc=1)) +
  
  coord_flip()

Table

homicides %>%
  group_by(WA_District) %>%
  summarize(Number = n(),
            Percent = round(100*Number/nrow(homicides), 1)
  ) %>%
  DT::datatable(rownames = F,
                caption = "Breakdown of Fatalities by WA Legislative District",
                filter = 'top',
                escape = FALSE, 
                extensions = 'Buttons', 
                options = list(
                  dom = 'Bfrtip',
                  buttons = c('copy', 'csv', 'excel', 'pdf', 'print')) )

Online information availability

This information comes from Fatal Encounters. It takes the form of a single url to a news article that is available online. There are often multiple news articles available online, and they may report the conflicting details of the event, as well as conflicting perspectives on the justifiability of the use of lethal force.

The clickable urls are available in this report in the Interactive Map and Say their names sections. They should be treated as a place to start research, not as the definitive description of the event.

tab <- homicides %>%
  mutate(url_info = case_when(url_info == "" ~ "No",
                               is.na(url_info) ~ "No",
                               TRUE ~ "Yes")) %>%
  group_by(url_info) %>%
  summarize(Number = n(),
            Percent = round(100*Number/nrow(homicides), 1)
  ) %>%
  bind_rows(data.frame(url_info ="Total", 
                       Number = sum(.$Number), 
                       Percent = sum(.$Percent))) 

tab %>%
  kable(caption = "URL for news article in Fatal Encounters",
        col.names = c("Availability", "Number", "Percent")) %>%
  kable_styling(bootstrap_options = c("striped","hover")) %>%
  row_spec(row=dim(tab)[1], bold = T) %>%
  add_footnote(label = "Percents may not sum to 100 due to rounding",
               notation = "symbol")
URL for news article in Fatal Encounters
Availability Number Percent
Yes 402 100
Total 402 100
* Percents may not sum to 100 due to rounding

Trend over time

There are two sets of plots here. The first focuses on the monthly totals, and how they have changed over time. The second focuses on the cumulative totals as the year progresses, and how this has changed over time.

# index restricted to first/last complete month
thisyr <- as.character(max(index$year))
prevyrs1 <- paste0(min(index$year), "-", max(index$year)-1)
prevyrs2 <- paste0(min(index$year), "-", last_complete_yr-1)

# left_join to index imposes same restriction
df_by_month <- homicides %>%
  group_by(year, month) %>%
  summarize(count = n()) %>%
  left_join(index, ., by = c("year", "mon"="month")) %>%
  mutate(count = tidyr::replace_na(count, 0),
         mo = match(mon, month.abb),
         mon = factor(mon, levels = month.abb),
         this.yr = factor(ifelse(year==max(year),
                                 thisyr,
                                 prevyrs1),
                          levels = c(prevyrs1, thisyr)),
         last.compl.yr = factor(ifelse(year==last_complete_yr,
                                 as.character(last_complete_yr),
                                 prevyrs2),
                          levels = c(prevyrs2, as.character(last_complete_yr))))

yrs <- as.numeric(lubridate::ymd(
  paste0(seq(min(index$year), max(index$year), 1), 
         "-", start_mo, "-01")))

Monthly totals

Trend

  • The points show the monthly totals, the grey line shows the smoothed average trend in monthly fatalities over time, and the grey shaded ribbon shows the 95% confidence interval around the trend line.

  • We only plot months once the data are complete, so the plot will lag 1-2 months behind the current date.

  • This is an interactive plot: You can hover over points to get the exact values for the number of cases for that month and year.

# we will only plot after current month has finished

p <- ggplot(df_by_month, aes(x = index.date, 
                     y = count))  +
  geom_point(size=1.5, shape=21, stroke=0.25,
             aes(fill = factor(year),
                 text = paste0(mon, ": ", count))) +
  geom_smooth(span = params$smooth_span, col="grey")  +
  
  # year boundaries
  geom_vline(xintercept = yrs, col="white", alpha=.5) +
  
  geom_hline(yintercept = 0, col="grey") +
  
  labs(title = paste("Monthly fatalities:",
                     month.abb[start_mo], start_yr, " - ",
                     month.abb[last_complete_mo], max(index$year)),
       x = "Month/Year",
       y = "Number",
       caption = paste("WA since", params$startyr),
       fill = "Year") +
  
  ylim(c(-2, NA)) + # min needs to be < smoother lower se
  
  scale_x_continuous(
    breaks = df_by_month$index.date[seq(1, nrow(df_by_month), 6)],
    label = df_by_month$mon.yr[seq(1, nrow(df_by_month), 6)]
    )  +
  
  scale_y_continuous(breaks = c(0, 3, 6, 9, 12)) +
  
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5)) +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank())

ggplotly(p, tooltip = "text") %>% 
  style(hoverinfo = "y", traces = 9) # use plotly_json(p) to verify

With key dates

This plot highlights key dates for legislation and legal actions related to police practices and accountability.

  • The \(\color{blue}{\textsf{blue lines}}\) are legislative/regulatory actions (940 passes, 940 WACs, and effective dates of legislation from 2021 forward). Solid lines are reform policies aimed at reducing police violence; dashed lines are partial rollbacks of these reforms.

  • The \(\color{red}{\textsf{red lines}}\) are legal actions (Ellis investigation taken over by AGO, ex-Officer Nelson charged in Jesse Sarey case, Officers Burbank Collins and Rankine charged in Ellis case). Solid lines are actions taken to hold officers accountable; dashed lines are acquittals and exonerations.

  • This is an interactive plot: You can hover over points to get the number of cases for that month, and hovering at the top or bottom of the vertical lines will identify the action.

p <- ggplot(df_by_month, 
            aes(x = index.date, y = count))  +
  geom_point(size=1.5, shape=21, stroke=0.25,
             aes(text = paste0(mon, ": ", count))) +
  geom_smooth(span = params$smooth_span, col="grey")  +
  
  # year boundaries
  geom_vline(xintercept = yrs, col="white", alpha=.5) +


  # reline y-axis in case yr[1] = start_date 
  geom_vline(aes(xintercept = as.numeric(start_date),
                 text = ""),
             col="grey", alpha=.8) +
  
  # key dates for prosecution
  geom_vline(aes(xintercept = as.numeric(date.ellis.ago),
                 text = "Ellis case taken over by AGO"),
             col="red", alpha=.8) +
  geom_vline(aes(xintercept = as.numeric(date.sarey.charged),
                 text = "Sarey case officer charged"),
             col="red", alpha=.8) +
  geom_vline(aes(xintercept = as.numeric(date.ellis.charged),
                 text = "Ellis case officers charged"),
             col="red", alpha=.8) +
    geom_vline(aes(xintercept = as.numeric(date.ellis.acquit),
                 text = "Ellis case officers acquitted"),
             col="red", lty=2, alpha=.8) +
  
  # key dates for legislation
  geom_vline(aes(xintercept = as.numeric(date.940.pass), 
                 text = "940 passes"), 
             col="blue", alpha=.8) +
  geom_vline(aes(xintercept = as.numeric(date.940.WAC), 
                 text = "940 WACs take effect"), 
             col="blue", alpha=.8) +
  geom_vline(aes(xintercept = as.numeric(date.2021.law), 
                 text = "2021 laws"), 
             col="blue", alpha=.8) +
  geom_vline(aes(xintercept = as.numeric(date.2022.law), 
                 text = "2022 laws"), 
             col="blue", lty=2, alpha=.8) +
  geom_vline(aes(xintercept = as.numeric(date.2023.law), 
                 text = "2023 laws"), 
             col="blue", lty=2, alpha=.8) +

  # zero axis
  geom_hline(yintercept = 0, col="grey") +
  
  labs(title = paste("Monthly fatalities with key dates:",
                     month.abb[start_mo], start_yr, " - ",
                     month.abb[last_complete_mo], max(index$year)),
       x = "Month/Year",
       y = "Number",
       caption = paste("WA since", params$startyr),
       fill = "Year") +
  
  ylim(c(-2, NA)) + # min needs to be < smoother lower se
  
  scale_x_continuous(
    breaks = df_by_month$index.date[seq(1, nrow(df_by_month), 6)], 
    label = df_by_month$mon.yr[seq(1, nrow(df_by_month), 6)])  +
  
  scale_y_continuous(breaks = c(0, 3, 6, 9)) +
  
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5)) +
  theme(panel.grid.major = element_blank(),
        panel.grid.minor = element_blank())

ggplotly(p, tooltip = "text") %>% 
  style(hoverinfo = "y", traces = 2)

This year vs. previous years

  • The points show the monthly totals for each year. They are color coded to highlight the current year.

  • The blue line shows the moving average for the monthly fatality rate for the 2015-2020 period, and the grey shaded ribbon shows the 95% confidence band around that average.

  • The black line shows the moving average for the monthly fatality rate for the current year. When this line lies outside the grey ribbon for the blue line, this suggests the difference between this year and previous years is larger than normal variation would predict.

  • We only plot months once the data are complete, so the plot will lag 1-2 months behind the current date.

  • This is an interactive plot: You can hover over points to get the month and year for each point.

# we will only plot after current month has finished

# make sure the factor levels match
this.yr.color = c("lightsteelblue", "black")
names(this.yr.color) <- levels(df_by_month$this.yr)
this.yr.alpha = c(0.5, 1)
names(this.yr.alpha) <- levels(df_by_month$this.yr)

p <- ggplot(data = df_by_month,
            aes(x = mo, y = count)) +
  
  geom_jitter(aes(col = this.yr,
                  alpha = this.yr,
                  text = mon.yr),
              width = 0, height = .1) +
  scale_color_manual(name="Year", 
                     values=this.yr.color) +
  scale_alpha_manual(values=this.yr.alpha) +
  guides(alpha = "none") +
  
  geom_smooth(data = df_by_month %>% filter(year != max(year)),
              color = "lightsteelblue") +
  geom_smooth(data = df_by_month %>% filter(year == max(year)), se=F,
              color = "black") +
  
  scale_x_continuous(breaks = seq(1,12,1), 
                     label = month.abb) +
  
  labs(title = paste0("WA since ", params$startyr, 
                     ":  Monthly totals with moving average"),
       caption = paste("WA since", params$startyr)) +
  xlab("Month") +
  ylab("Number of people killed")


ggplotly(p, tooltip = "text") %>% 
  style(hoverinfo = "y", traces = 3)

Last complete year vs. previous years

  • The points show the monthly totals for each year. They are color coded to highlight the current year.

  • The blue line shows the moving average for the monthly fatality rate for the 2015-2023 period, and the grey shaded ribbon shows the 95% confidence band around that average.

  • The black line shows the moving average for the monthly fatality rate for 2024, the last complete year. When this line lies outside the grey ribbon for the blue line, this suggests the difference between this year and previous years is larger than normal variation would predict.

  • This is an interactive plot: You can hover over points to get the month and year for each point.

# make sure the factor levels match
last.compl.yr.color = c("lightsteelblue", "black")
names(last.compl.yr.color) <- levels(df_by_month$last.compl.yr)
last.compl.yr.alpha = c(0.5, 1)
names(last.compl.yr.alpha) <- levels(df_by_month$last.compl.yr)

p <- ggplot(data = df_by_month %>% filter(year <= last_complete_yr),
            aes(x = mo, y = count)) +
  
  geom_jitter(aes(col = last.compl.yr,
                  alpha = last.compl.yr,
                  text = mon.yr),
              width = 0, height = .1) +
  scale_color_manual(name="Year", 
                     values=last.compl.yr.color) +
  scale_alpha_manual(values=last.compl.yr.alpha) +
  guides(alpha = "none") +
  
  geom_smooth(data = df_by_month %>% filter(year != last_complete_yr),
              color = "lightsteelblue") +
  geom_smooth(data = df_by_month %>% filter(year == last_complete_yr), se=F,
              color = "black") +
  
  scale_x_continuous(breaks = seq(1,12,1), 
                     label = month.abb) +
  
  labs(title = paste0("WA since ", params$startyr, 
                     ":  Monthly totals with moving average"),
       caption = paste("WA since", params$startyr)) +
  xlab("Month") +
  ylab("Number of people killed")


ggplotly(p, tooltip = "text")  %>% 
  style(hoverinfo = "y", traces = 3) # use plotly_json(p) to verify

Annual totals

Cumulative by month

  • The lines show the cumulative total fatalities by month as the year progresses, for each year.

  • We only plot months once the data are complete, so the plot will lag 1-2 months behind the current date.

# plotly doesn't handle the multiple legend properly so we don't use it here

# set up color palette for previous years:
n.yrs <- max(index$year) - params$startyr # num yrs excl this yr
paletteFunc <- colorRampPalette(c('blue', 'yellow', 'red'))
my.colors <- paletteFunc(n.yrs)


df <- df_by_month %>%
  group_by(year) %>%
  mutate(cumulative = cumsum(count),
         mon = factor(mon, levels=month.abb))

# Plot prev yrs separately and specify 2 aes (color, ltyp) to get 2 legends

ggplot(data= df %>% filter(year != max(df$year)),
       aes(x = mon, 
           y = cumulative, 
           color = factor(year),
           group = year)) +
  
  geom_line(size = 1.5, alpha = .7) +
  geom_point(aes(text = paste('Total =', cumulative, '<br>', 
                              mon, '<br>', year)),
                 size=1, alpha = 1) +
  scale_color_manual(values = my.colors) +

  geom_line(data = df %>% filter(year == max(df$year)),
            aes(x = mon, 
                y = cumulative,
                linetype = factor(year),
                text = paste('Total =', cumulative, '<br>', 
                             mon, '<br>', year)),
            color = "black", size = 2, alpha = 0.7) +
  scale_linetype_manual(name = "This Yr", values = c("solid")) +
  
  labs(title = paste0("WA since ", params$startyr,
                     ":  Cumulative fatalities by Month and Year"),
       x = "Month", 
       y = "Number of people killed",
       caption = paste("WA since", params$startyr),
       color = "Previous Yrs") +
  
  guides(color = guide_legend(order = 1),
         linetype = guide_legend(order = 2))

#ggplotly(p, tooltip = "text")

By year

Calendar year

graphdf.calyr <- homicides %>%
  filter(year >= params$startyr) %>%
  group_by(Year = year) %>%
  summarize(Num = n())

p <- ggplot(graphdf.calyr,
       aes(x = Year, y = Num, 
           fill = Num,
           text = Num)) +
  
  geom_bar(stat = "identity") + 

  scale_fill_gradient(low = "seashell", high = "firebrick",
                      guide = "none") +
  
  scale_x_continuous(breaks = unique(graphdf.calyr$Year)) +
  
  annotate("text", 
           x = graphdf.calyr$Year[nrow(graphdf.calyr)], 
           y = graphdf.calyr$Num[nrow(graphdf.calyr)] + 1, 
           size = 3, label = "YTD") +
  
  labs(title = "People killed by police in WA by Year",
       x = "Calendar year",
       y = "Number killed")

ggplotly(p, tooltip = "text")

Legislative year

We start with the first complete legislative year.

graphdf.legyr <- homicides %>%
  filter(date > as.Date(params$startlegyr)) %>%
  group_by(leg.year) %>%
  summarize(Num = n())

p <- ggplot(graphdf.legyr,
       aes(x = leg.year, y = Num, 
           fill = Num,
           text = Num)) +
  
  geom_bar(stat = "identity") + 

  scale_fill_gradient(low = "seashell", high = "firebrick",
                      guide = "none") +
  
  scale_x_discrete(labels = function(x) 
    stringr::str_wrap(graphdf.legyr$leg.year, width = 8)) +
  
  theme(axis.text.x = element_text(size = 5)) +
  
  annotate("text", 
           x = graphdf.legyr$leg.year[nrow(graphdf.legyr)], 
           y = graphdf.legyr$Num[nrow(graphdf.legyr)] + 1, 
           size = 3, label = "YTD") +
  
  labs(title = "People killed by police in WA by Legislative Year",
       x = "Legislative year",
       y = "Number killed")

ggplotly(p, tooltip = "text")

By cause of death

Here we plot the cumulative totals each year, broken down to show the two leading causes of death.

  • The bars represent totals for each year, divided by cause of death: gunshot, vehicle pursuit and all other causes. A case is classified as a vehicle pursuit death if the cause of death was vehicular accident during an active or recently terminated vehicle pursuit.

  • Note that the current year only reflects cases for the year to date (April 16 2025), so is not strictly comparable with the previous years.

  • This is an interactive plot: You can hover over bar segments to get the exact values for that cause of death in that year.

Calendar year

graphdf.calyr <-  homicides %>%
  filter(year >= params$startyr) %>%
  mutate(
    cod_3 = factor(
      case_when(
        cod == "Gunshot" ~ "shot", 
        grepl("Active|Terminated", vpursuit) ~ "vehicle pursuit",
        TRUE ~ "other"),
      levels = c("shot", "vehicle pursuit", "other")),
    year = as.character(year)) %>%
  group_by(year, cod_3) %>%
  summarize(n = n()) %>%
  mutate(percent = round(100*n / sum(n), 1))
  
p <- ggplot(graphdf.calyr,
            aes(x = year, y = n,
             fill = cod_3, 
             text = paste0("N=", n, 
                           ", (", round(percent),"%)"))
         ) +
  
  geom_bar(stat="identity", alpha=.5,
           position = position_stack(reverse=T)) +
  
  annotate("text", 
           x = graphdf.calyr$year[nrow(graphdf.calyr)], 
           y = sum(graphdf.calyr$n[(nrow(graphdf.calyr)-2):nrow(graphdf.calyr)]) + 1, 
           size = 3, label = "YTD") +
  
  scale_fill_manual(values = c("cadetblue","darkorange3", "goldenrod")) +
  
  labs(title = paste0("Fatalities by Cause of Death and Year"),
       caption = paste("WA since", params$startyr),
       x = "Year",
       y = "Number",
       fill = "Cause of\nDeath")

ggplotly(p, tooltip = "text")

Legislative year

We start with the first complete legislative year.

graphdf.legyr <-  homicides %>%
  filter(date > as.Date(params$startlegyr)) %>%
  mutate(
    cod_3 = factor(
      case_when(
        cod == "Gunshot" ~ "shot", 
        grepl("Active|Terminated", vpursuit) ~ "vehicle pursuit",
        TRUE ~ "other"),
      levels = c("shot", "vehicle pursuit", "other")),
    year = as.character(year)) %>%
  group_by(leg.year, cod_3) %>%
  summarize(n = n()) %>%
  mutate(percent = round(100*n / sum(n), 1))
  
p <- ggplot(graphdf.legyr,
            aes(x = leg.year, y = n,
                fill = cod_3, 
                text = paste0("N=", n, 
                              ", (", round(percent),"%)"))
            ) +
  
  geom_bar(stat="identity", alpha=.5,
           position = position_stack(reverse=T)) +
  
  annotate("text", 
           x = graphdf.legyr$leg.year[nrow(graphdf.legyr)], 
           y = sum(graphdf.legyr$n[(nrow(graphdf.legyr)-2):nrow(graphdf.legyr)]) + 1, 
           size = 3, label = "YTD") +
  
  scale_fill_manual(values = c("cadetblue","darkorange3", "goldenrod")) +
  
  scale_x_discrete(labels = function(x) 
    stringr::str_wrap(unique(graphdf.legyr$leg.year), width = 8)) +
  
  theme(axis.text.x = element_text(size = 5)) +
  
  labs(title = paste0("Fatalities by Cause of Death and Legislative Year"),
       caption = paste("WA since", params$startyr),
       x = "Legislative Year",
       y = "Number",
       fill = "Cause of\nDeath")

ggplotly(p, tooltip = "text")

WA vs. other states

Here we report the annual totals up through the last complete year – 2024 – with data from the MPV project (since both the Fatal Encounters and WaPo projects stopped updating). This limits the cases to those included in MPV. While we have updated data for WA state using the Fatal Encounters inclusion criteria (all persons who are killed by police – including deaths due to asphyxiation, tasers, vehicle chases, etc), comparable data for other states are not currently available.

Compared to all other states

In this section we compare number shot in WA state to the total in all other states combined.

Population adjusted rate

The number of people shot is represented here as a population-adjusted rate per million residents, which makes it possible to compare states with different sized populations. The raw numbers are given on the next tab.

p <- ggplot(state.rates %>% filter(grepl("Washington|Other", state)), 
       aes(x=factor(year), y=rate, 
           group=state, fill=state,
           text =paste("rate: ", round(rate, 1)))) + 
  geom_col() + #coord_flip() +
  facet_wrap(~state) +
  
  theme(axis.text.x = element_text(size = 6),
        axis.text.y = element_text(size = 8)) +
  
  labs(title = "Rate of persons shot by police: WaPo data",
       x="Year", y="Rate per million")

ggplotly(p, tooltip = "text")

Number shot and annual percent change

kable(wapo_tab,
      caption = "Persons shot by police: WaPo data",
      col.names = c("Year", rep(c("All other states", "WA"), 2)),
      digits = c(0,0,0,1,1),
      align = c("l",rep("r", 4))) %>%
  
  kable_styling(bootstrap_options = c("striped","hover"),
                full_width = F) %>%
  add_header_above(c("", "Number" = 2, "Percent Change" = 2 ))
Persons shot by police: WaPo data
Number
Percent Change
Year All other states WA All other states WA
2015 979 16 NA NA
2016 933 26 -4.70% 62.50%
2017 946 38 1.39% 46.15%
2018 969 23 2.43% -39.47%
2019 956 36 -1.34% 56.52%
2020 990 31 3.56% -13.89%
2021 1037 13 4.75% -58.06%
2022 1058 39 2.03% 200.00%
2023 1142 22 7.94% -43.59%
2024 1141 32 -0.09% 45.45%

State rankings 2024

Here we report the WaPo data by state for the most recent complete year: 2024.

Number shot

  • States are ranked by the the number of persons shot in 2024.

  • The bar for WA State is highlighted with a black border.

  • These are interactive plots: You can hover over bars to get the exact values for the number of persons shot in 2024.

p <- ggplot(data = states.all %>% filter(!grepl("United|Other", state)),
            aes(x=shot2024, y=reorder(state, shot2024), 
                fill = shot2024,
                color = factor(ifelse(state=="Washington", 1, 0)),
                text=paste(state, 
                           "total:", shot2024))) +
              
  geom_col() +
    
  scale_color_manual(values = c("grey", "black")) +
  scale_fill_gradient2(
    low = "blue", 
    mid = "white", 
    high = "red", 
    midpoint = median(states.all$shot2024)) +
  #scale_x_continuous(labels = scales::percent) +
  
  theme(axis.text.y=element_text(size=rel(.7)), legend.position = "none") +
  labs(title = paste("State ranking: Number of persons shot by police in", last_complete_yr),
       x="Number shot",
       y="State")

ggplotly(p, tooltip = "text")

Population-adjusted rate

  • States are ranked by the population adjusted rate of persons shot in 2024: the rate per million state residents.

  • The bar for WA State is highlighted with a black border.

  • These are interactive plots: You can hover over bars to get the exact values for the rate of persons shot in 2024.

p <- ggplot(data = states.all %>% filter(!grepl("United|Other", state)),
            aes(x=rateshot2024, y=reorder(state, rateshot2024), 
                fill = rateshot2024,
                color = factor(ifelse(state=="Washington", 1, 0)),
                text=paste(state, 
                           "total:", round(rateshot2024, 1)))
            ) +
              
  geom_col() +
    
  scale_color_manual(values = c("grey", "black")) +
  scale_fill_gradient2(
    low = "blue", 
    mid = "white", 
    high = "red", 
    midpoint = median(states.all$rateshot2024)) +
  #scale_x_continuous(labels = scales::percent) +
  
  theme(axis.text.y=element_text(size=rel(.7)), legend.position = "none") +
  labs(title = paste("State ranking: Population adjusted rate of persons shot by police in", last_complete_yr),
       x="Rate per million residents",
       y="State")

ggplotly(p, tooltip = "text")

Change from previous year

  • States are ranked by the percent change in the number of persons shot from 2023 to 2024.

  • The bar for WA State is highlighted with a black border.

  • These are interactive plots: You can hover over bars to get the exact values for the number of persons shot in 2024 year, and the percent change from the previous year.

df <- states.all %>% 
  filter(!grepl("United|Other", state)) %>%
  select(state, shot2023:shot2024) %>%
  mutate(pct.chg = shot2024/shot2023 -1)

p <- ggplot(data = df,
            aes(x=pct.chg, y=reorder(state, pct.chg), 
                fill = pct.chg,
                color = factor(ifelse(state=="Washington", 1, 0)),
                text=paste0(state, 
                           paste("<br>", "number shot in ", last_complete_yr, ": ", shot2024),
                           paste("<br>", "change from last year: ", scales::percent(pct.chg, acc = 1))))
            ) +
              
  geom_col() +
    
  scale_color_manual(values = c("grey", "black")) +
  scale_fill_gradient2(
    low = "blue", 
    mid = "white", 
    high = "red", 
    midpoint = 0) +
  scale_x_continuous(labels = scales::percent) +
  
  theme(axis.text.y=element_text(size=rel(.7)), legend.position = "none") +
  labs(title = paste("State ranking: Percent change in number of persons shot by police",
                     last_complete_yr-1, "-", last_complete_yr),
       x="Percent change",
       y="State")

ggplotly(p, tooltip = "text")

Say their names

Name known

Of the 402 persons killed by police, 391 of the victim’s names are known at this time. In the table below “WA-LD” refers to the Washington state legislative district.

NOTE: There are 2 tabs above, one for the names we do know, the other for the names we don’t yet know. Selecting a tab will display the associated table.

homicides %>%
  filter(name != "Unknown") %>%
  select(name, date, age=age, race, county, `WA-LD`=WA_District, 
         agency, link = url_click) %>%
  arrange(desc(date)) %>%
  DT::datatable(rownames = F,
                caption = paste("The Names We Know:  as of", scrape_date),
                filter = 'top',
                escape = FALSE, 
                extensions = 'Buttons', 
                options = list(
                  dom = 'Bfrtip',
                  buttons = c('copy', 'csv', 'excel', 'pdf', 'print')) )

Name Unknown

The remaining 11 of the victim’s names are not known at this time. In the table below “WA-LD” refers to the Washington state legislative district.

homicides %>%
  filter(name == "Unknown") %>%
  select(name, date, age=age, race, county, `WA-LD`=WA_District, 
         agency, link = url_click) %>%
  arrange(desc(date)) %>%
  DT::datatable(rownames = F,
                caption = paste("The Names We Don't Know:  as of", scrape_date),
                filter = 'top',
                escape = FALSE, 
                extensions = 'Buttons', 
                options = list(
                  dom = 'Bfrtip',
                  buttons = c('copy', 'csv', 'excel', 'pdf', 'print')) )

Appendices

Legislative history

Police accountability and reform has been an active topic in the Washington state legislature since 2018. Changes have been achieved via both citizen initiative and traditional bills.

One of the first major legislative reforms was Initiative 940. I-940 established de-escalation training requirements, independent investigations of all homicides by police and changed the criminal accountability standard from malice to reasonable officer. It passed with over 60% of the statewide vote in the 2018 November general election, becoming WA state law on December 6, 2018. Certain provisions took effect later: amendments by HB 1064 took effect February 4, 2019, and the first Washington Administrative Code (WAC) requirements took effect January 6, 2020. The WAC is reviewed and updated annually.

In the 2021 legislative session (Jan - April) several bills were passed on a range of issues relating to police reform.

  • HB 1054 Established limits on the use of police tactics like neck restraints, canines, tear gas and pursuits, prohibited the use of military equipment, and required police to be identifiable.

  • HB 1088 Required agencies to forward Brady impeachment information for their officers to the local county prosecutor, request Brady information from prosecutors during the hiring process, and report the hiring of officers with prior potential impeachment disclosure to the prosecutor.

  • HB 1267 Established a statewide Office of Independent Investigations to investigate all incidents of police use of deadly force.

  • HB 1089 Established state audits of all independent investigations of police use of deadly force.

  • HB 1310 Established requirements for deescalation and restrictions on the use of force and deadly force.

  • SB 5051 Provided oversight and decertification authority for the WA Criminal Justice Training Commission.

  • SB 5259 Established a statewide police use of force data collection program.

Other important bills passed in 2021 include SB 5066 which established a duty for officers to intervene when their colleagues use excessive force, and HB 1140 which requires that juveniles be provided access to attorneys.

The year these reforms passed the number of people killed by police in Washington dropped by 60%, the largest decline seen in any state. More information on the temporal trends can be found in the section on “Trend over time”.

Some changes have been made to the 2021 reforms in subsequent sessions:

  • In 2022 HB 2037 modified the policy on use of force during Terry Stops allowing force to be used to prevent a person from fleeing.

  • In 2023 SB 5352 modified the policy on vehicular pursuits, reducing the standard of evidence from probable cause to reasonable suspicion, adding some offenses to the list of pursuit-eligible offenses, and relaxing the supervisory authorization requirements.

  • In 2024 Initiative 2113 further modified the statewide pursuit policy, eliminating the specific list of offenses that restricted pursuits to violent felonies, and allowing them for any suspected criminal offense.


Data sources

Where the data come from

This report is updated about once each week, pulling, cleaning and merging data from three online sources: Fatal Encounters (https://fatalencounters.org/), the Washington Post (https://www.washingtonpost.com/graphics/investigations/police-shootings-database/) and Mapping Police Violence (https://mappingpoliceviolence.us/).

  • Fatal Encounters includes all deaths during encounters with police. This open-source project was active through December 31, 2021, includes records going back to 2000, and produced the largest compilation of persons killed during encounters with police that is publicly available. It covers deaths by all causes during a police encounter, including suicides and vehicular pursuits. Updating has been taken over by the Mapping Police Violence Project since 2022, but with somewhat narrower coverage by cause of death.

  • Washington Post (WaPo) only includes fatal shootings by on-duty police. It was actively updating from January 1, 2015 through December 31, 2024.

  • Mapping Police Violence (MPV) covers fatal shootings and includes additional incidents where police kill someone through use of a chokehold, baton, taser or other means, killings by off-duty police, and some pursuit-related deaths. It is the only project that is still actively updating, with data going back to January 1,2013.

  • For WA State only: We update fatalities if they are known to us before they appear in the national datasets, and we supplement the Fatal Encounters/Mapping Police Violence data with police vehicular pursuit-related fatalities found through web searches – replicating the alerts used by Fatal Encounters in the past, and including any additional incidents brought to our attention.


Note: Our project is in the process of updating our code and reports to reflect the end of WaPo updating. The MPV data are already used for verifying and updating the incidents in Washington covered by this report. But modifications to the report text and code are still in process.


For all of our data sources, it is worth noting that:

  • None include deaths in custody after booking.

  • All may be missing incidents.

A comparison of the cases found in each dataset is presented in the next section of this report.

There has typically been a 1-2 week lag in the actively updating datasets, but it can sometimes be longer. The date shown in the “most recent data update” tab above is an indication of the current lag. As of this report, the last data updates were:

  • December 31 2021 for Fatal Encounters (no longer updating)
  • December 31 2024 for the Washington Post (no longer updating)
  • March 31 2025 for Mapping Police Violence

For Washington State, the most recent known fatality included in this report was on April 12 2025.

This report is restricted to the cases that can be classified as homicides by police and vehicular homicides during police pursuits, it excludes cases where the cause of death during the police encounter is ruled a suicide and other medical emergencies that result in death while in custody (see the next section for details).


Dataset features

All of the data sources we use – the Fatal Encounters project, the Washington Post and Mapping Police Violence – are open-source, “unofficial” datasets. Their data are not drawn from mandatory government reporting on deaths caused by law enforcement officers because there is no official national data program designed for this purpose. The datasets are based instead on cases known/discovered by the two project teams, primarily through online searches. You can find more details about the methodology used by each project on their websites: Fatal Encounters, Washington Post, and Mapping Police Violence.

Open-source vs. Official

In theory, deaths caused by police should also be captured by several official data collection programs:

Studies have repeatedly shown, however, that these official programs consistently fail to identify about half of the people killed by police. The most recent study to have replicated this finding was conducted by the Institute for Health Metrics and Evaluation in 2021, and published in the Lancet. You can access the study here, and less technical summaries can be found in the news media. Other studies show similar rates of undercounting in the Arrest Related Death and Supplementary Homicide Reporting systems, one example is here.

The one exception to this is the National Violent Deaths Reporting system, where a study from 2015 found that it captured almost all of the people shot and killed by police in the 27 states that were participating in the program at the time. But there has been no update since the state coverage reached all 50 states in 2018, and there has been no assessment of its coverage of deaths due to causes other than gunshot. Perhaps most importantly, the study simply showed that this dataset had comparable coverage to the open-source datasets, not that it was superior.

Research has also shown that the methodology used by the open-source data sets is effective:

“Findings indicate that the open-source methodology alone identifies the majority of law enforcement homicides, but agency surveys aid in identifying deaths by other causes (e.g., accidents, suicides, and natural causes).” BJS 2019


At this point, it is widely acknowleged that the open-source datasets have the most complete coverage of these cases. That is why we use them here.


The other key benefit to the Fatal Encounters / Mapping Police Violence data in particular is that every case includes a link to documentation that verifies the case and serves as a starting place for additional research. This allows us to identify the law enforcement agency(ies) involved, provides more context and sometimes reveals conflicting narratives as to the events. All of this helps to remind us that each case is a person, not just a statistic.

That said, it is important to keep in mind that these open-source datasets, too, are not perfect. They will miss cases that have not generated a digital signature that can be found using their search methodologies. So it would be accurate to say that our open-source data provide a lower bound estimate of the number of people killed by police; the true value is likely higher, but it can not be lower.

Dataset differences

[Note: This section has not yet been updated to reflect the cessation of updating for the WaPo project data]

Fatal Encounters includes more cases than the Washington Post, because the Post dataset is restricted to fatal shootings.


Among the cases missing from the Washington Post data are Manny Ellis (in WA) and George Floyd (at the national level), because their deaths were caused by asphyxiation, not gunshots.


The Washington Post also excludes deaths that occur during police vehicular pursuits. As can be seen in this report, police pursuits are the second leading cause of death in police-civilian encounters, so their inclusion in Fatal Encounters is important. You can find more details on pursuit fatalities here in WA State in this report.

The total number of cases in the Fatal Encounters dataset for WA State since 2015 is 440. These are classified as follows:

tab <- merged_data %>%
  group_by(circumstances) %>%
  summarize(Number = n(),
            Percent = round(100*Number/nrow(merged_data), 1)
  ) %>%
  arrange(desc(Number)) %>%
  bind_rows(data.frame(circumstances ="Total", 
                       Number = sum(.$Number), 
                       Percent = sum(.$Percent))) 

tab %>%
  kable(caption = "All Fatal Encounters dataset cases, broken down by type",
        col.names = c("Type", "Number", "Percent")) %>%
  kable_styling(bootstrap_options = c("striped","hover")) %>%
  row_spec(dim(tab)[1], bold = T)  %>%
  add_footnote(label = "Percents may not sum to 100 due to rounding",
               notation = "symbol")
All Fatal Encounters dataset cases, broken down by type
Type Number Percent
Deadly force 279 63.4
Active pursuit 46 10.5
Suicide 33 7.5
Involved pursuit 22 5.0
Less lethal force 22 5.0
Attempted stop 10 2.3
Unintended 10 2.3
Mix of pursuit-related cases 7 1.6
Vehicle accident 6 1.4
NA 2 0.5
Drowned 1 0.2
Medical emergency 1 0.2
Terminated pursuit 1 0.2
Total 440 100.1
* Percents may not sum to 100 due to rounding

Exclusions

Cases officially reported as suicides and “Medical Emergencies” are excluded from the analyses in this report.

The Fatal Encounters data (and our extension of their data for WA State) includes some cases classified as “suicides” that occur during an encounter with police. These are not cases of “suicide by cop”, where the victim appears to have provoked the police in order to be killed. They are cases where the victim shoots or otherwise kills themself. We have excluded these from the analyses in this report, because the report focuses on persons killed by police.

However, we acknowledge that these cases warrant further assessment. In many of these cases, officers did shoot at the suspect, but missed or failed to kill them. In addition the classification as “suicide” is not definitive, it is typically based on the narrative provided by law enforcement and some level of investigation by the local coroner or medical examiner. Finally, even if these are suicides, it is possible that the outcome might have been different if the case had been handled differently, for example, by calling in a crisis intervention team and deescalating effectively.

The excluded case counts and fraction of total cases by year is shown in the table below.

tab <- merged_data %>%
  group_by(year, homicide) %>%
  summarize(Number = n()) %>%
  pivot_wider(id_cols = year,
              names_from = homicide,
              names_prefix = "N",
              values_from = Number) %>%
  replace_na(list(N0=0)) %>%
  mutate(Percent = round(100*N0/(N0 + N1), 1),
         year = as.character(year)) %>%
  select(year, Number=N0, Percent) %>%
  bind_rows(data.frame(year ="Total", 
                       Number = sum(.$Number), 
                       Percent = round(100*sum(.$Number)/nrow(merged_data), 1)))

tab %>%
  kable(caption = "Cases labeled 'Suicide' in Fatal Encounters dataset by year",
        col.names = c("Type", "Number", "Percent of all cases")) %>%
  kable_styling(bootstrap_options = c("striped","hover"),
                full_width = F) %>%
  row_spec(dim(tab)[1], bold = T)  %>%
  add_footnote(label = "These cases were excluded from analyses in this report",
               notation = "symbol")
Cases labeled ‘Suicide’ in Fatal Encounters dataset by year
Type Number Percent of all cases
2015 1 4.8
2016 4 11.1
2017 2 3.8
2018 5 12.2
2019 6 10.9
2020 6 10.7
2021 9 31.0
2022 2 3.8
2023 1 2.9
2024 2 3.9
2025 0 0.0
Total 38 8.6
* These cases were excluded from analyses in this report

There are also a couple of cases where the cause of death is coded as “Medical emergency”. We exclude these cases from this report as well, as they do not meet the definition of “homicide.” But we note that in all of these cases, there was substantial interaction with the police in the period preceding the death, and the person died in police custody. The cases in WA both involved a lengthy vehicular pursuit, and in the national level dataset, many of these cases involved the victim being restrained and injected with Ketamine by emergency responders.


Homicide case comparison

Restricting the Fatal Encounters dataset to the homicide cases, it still has 31% more cases than found in the Post dataset (through 2024, when the Post stopped updating).

tab <- homicides %>%
  filter(year < 2025) %>%
  mutate(in.wapo = ifelse(is.na(wapoID), 
                          "In FE only", 
                          "In both FE and WaPo")) %>%
  group_by(in.wapo) %>%
  summarize(Number = n(),
            Percent = round(100*Number/nrow(homicides), 1)
  ) %>%
  bind_rows(data.frame(in.wapo ="Total", 
                       Number = sum(.$Number), 
                       Percent = sum(.$Percent))) 

tab %>%
  kable(caption = "Cases found in each dataset",
        col.names = c("Case is:", "Number", "Percent")) %>%
  kable_styling(bootstrap_options = c("striped","hover")) %>%
  row_spec(row=dim(tab)[1], bold = T) %>%
  add_footnote(label = "Percents may not sum to 100 due to rounding",
               notation = "symbol")
Cases found in each dataset
Case is: Number Percent
In FE only 114 28.4
In both FE and WaPo 276 68.7
Total 390 97.1
* Percents may not sum to 100 due to rounding

Missing cases in WaPo

Again through the end of 2024, when the Post stopped updating.

By cause of death
tab <- homicides %>%
    filter(year < 2025) %>%
  filter(is.na(wapoID)) %>%
  group_by(cod) %>%
  summarize(Number = n()
  ) %>%
  arrange(desc(Number)) %>%
  bind_rows(data.frame(cod ="Total", 
                       Number = sum(.$Number))) 

pct.shot.missing <- tab$Number[tab$cod=="Gunshot"]/tab$Number[tab$cod=="Total"]

Among the cases missing from the Post dataset, 13 (11%) are identified as fatal shootings in the Fatal Encounters dataset.

tab %>%
  kable(caption = "Cause of death for cases missing from WaPo dataset",
        col.names = c("Cause:", "Number")) %>%
  kable_styling(bootstrap_options = c("striped","hover")) %>%
  row_spec(row=c(which(tab$cod=="Gunshot"),
                 dim(tab)[1]), bold = T)
Cause of death for cases missing from WaPo dataset
Cause: Number
Vehicle 69
Gunshot 13
Taser 12
Asphyxiated/Restrained 10
Drowned 5
Burned/Smoke inhalation 2
Other 2
Beaten 1
Total 114

By name, for fatal shootings

Below is the list of cases identified as gunshot fatalities in Fatal Encounters that are missing from the Washington Post dataset for 2015-2024.

homicides %>%
  filter(year < 2025) %>%
  filter(is.na(wapoID) & cod == "Gunshot") %>%
  select(name, date, city, county, agency, url_click) %>%
  DT::datatable(rownames = F,
                caption = "Gunshot cases missing from WaPo dataset",
                escape = FALSE)

What is a homicide?

The deadly force incidents in this report are homicides. A homicide is simply defined as the killing of one person by another. In the context of this report it refers to any encounter with law enforcement officers that results in a fatality. Homicides normally result in a criminal investigation or inquest, but the word does not imply a crime has been committed.

  • The word homicide means only that the death was caused in some way by the officer.

  • It does not not mean the officer’s actions that led to the death were justified, or that they were unjustified.

The law then defines different types of homicides, in order to distinguish levels of intention and criminal culpability. In the U.S., these types and definitions are broadly similar across states, but do vary somewhat in the the how many different types are specified, and what names are used for each type. The classifications for WA State can be found in 9A.32 RCW

The definitions below are taken from a useful online summary that uses simple language, based on California State laws.

Homicide
Homicide is the killing of one person by another. This is a broad term that includes both legal and illegal killings. For example, a soldier may kill another soldier in battle, but that is not a crime. The situation in which the killing happened determines whether it is a crime.

  • Murder is the illegal and intentional killing of another person. Under California Penal Code Section 187, for example, murder is defined as one person killing another person with malice aforethought. Malice is defined as the knowledge and intention or desire to do evil. Malice aforethought is found when one person kills another person with the intention to do so.

    In California, for example, a defendant may be charged with first-degree, second-degree, capital or felony murder.

    • First-degree murder is the most serious and includes capital murder – first-degree murder with “special circumstances” that make the crime even more egregious. These cases can be punishable by life in prison without the possibility of parole, or death.

    • Second-degree murder is murder without premeditation, but with intent that is typically rooted in pre-existing circumstances. The penalty for second-degree murder may be up to 15 years to life in prison in California.

    • Felony murder is a subset of first-degree murder and is charged when a person is killed during the commission of a felony, such as a robbery or rape.

  • Manslaughter is the illegal killing of another person without premeditation, and in some cases without the intent to kill. These cases are treated as less severe crimes than murder. Manslaughter can also be categorized as voluntary or involuntary.

    • Voluntary manslaughter occurs when a person kills another without premeditation, typically in the heat of passion. The provocation must be such that a reasonable person under the same circumstances would have acted the same way. Penalties for voluntary manslaughter include up to 11 years in prison in California.

    • Involuntary manslaughter is when a person is killed by actions that involve a wanton disregard for life by another. Involuntary manslaughter is committed without premeditation and without the true intent to kill, but the death of another person still occurs as a result. Penalties for involuntary manslaughter include up to four years in prison in California.

    • Vehicular manslaughter occurs when a person dies in a car accident due to another driver’s gross negligence or even simple negligence, in certain circumstances.


Public Access

This report follows the principles of transparent, reproducible science. All of the scripts, data and software are open source and freely available to the public, online.


The scripts needed to scrape the data and reproduce this report are posted on our public GitHub Repository.


The software used to produce this report comes from the open-source R project. You can view the code in the report itself by clicking the Code buttons in the right margin, or you can download all of the scripts from the repository.

  • If you find a bug in our code, please let us know by posting it as an issue in the repository.

  • If you have a question about the methodology, or the data, or the topic in general, please start a thread on the repository discussions page (note: you’ll need a GitHub account – they’re free, just follow the instructions at the link).

  • If you want to get involved, please email us at Next Steps Washington


: : : :