How to Analyse Road Death Trends with FARS in Python

On this page

Problem statement

US road deaths fell for decades, then rose sharply from 2020, at a time when Americans were driving less. A trend like that raises the obvious questions: who was killed, where, and has it reversed? FARS answers them year by year, but each year is a separate download whose file and column names have drifted, and a national total hides groups moving at different speeds.

This page stacks the FARS person files for 2017–2023, counts deaths by year and by road user (drivers, passengers, pedestrians, cyclists and people on motorcycles), measures the change from 2019 to 2023 for each group, and maps the change by state.

Quick answer

Read the killed people (INJ_SEV 4) from each year's person file and pivot by role:

role = P.PER_TYP.map({1: "driver", 2: "passenger", 5: "pedestrian", 6: "cyclist"}).fillna("other")
T = P.assign(role=role).pivot_table(index="YEAR", columns="role", values="ST_CASE", aggfunc="size")
T["total"] = T.sum(axis=1)
print(T.to_string())
print((T.loc[2023] / T.loc[2019] - 1).map("{:+.1%}".format).to_string())
role  cyclist  driver  other  passenger  pedestrian  total
YEAR                                                      
2017      800   23757    319       6522        6075  37473
2018      868   23045    272       6276        6374  36835
2019      856   22747    353       6127        6272  36355
2020      942   24860    319       6321        6565  39007
2021      971   27555    350       6884        7470  43230
2022     1096   26929    400       6703        7593  42721
2023     1163   25612    384       6499        7367  41025
role
cyclist       +35.9%
driver        +12.6%
other          +8.8%
passenger      +6.1%
pedestrian    +17.5%
total         +12.8%

Deaths fell to 36,355 in 2019, then rose in 2020 and peaked at 43,230 in 2021. In 2023 they were 41,025, still 12.8% above 2019. Every group rose; cyclists most, by 35.9%, then pedestrians by 17.5% and drivers by 12.6%.

Line chart of US road deaths by road user indexed to 2019 equals 100, 2017 to 2023: all groups rise from 2020; total deaths peak at 119 in 2021 and end at 113; drivers follow the total; pedestrians peak in 2022 and end at 117; passengers end at 106; motorcycle deaths keep rising to 126 and cyclist deaths to 136 in 2023. Line chart of US road deaths by road user indexed to 2019 equals 100, 2017 to 2023: all groups rise from 2020; total deaths peak at 119 in 2021 and end at 113; drivers follow the total; pedestrians peak in 2022 and end at 117; passengers end at 106; motorcycle deaths keep rising to 126 and cyclist deaths to 136 in 2023.
Road deaths by road user.

Consistent years, motorcyclists, change by state

1. Read several years consistently

The person file is person.csv or PERSON.CSV, sometimes in a folder inside the zip, and the 2021 and 2022 files begin with a byte-order mark; read only the columns needed and clean their names:

print(f"killed people read: {len(P):,} over {P.YEAR.nunique()} years")
print(P.groupby("YEAR").size().to_string())
killed people read: 276,646 over 7 years
YEAR
2017    37473
2018    36835
2019    36355
2020    39007
2021    43230
2022    42721
2023    41025

276,646 people killed in seven years, read from seven files with three naming conventions.

2. People on motorcycles

Motorcyclists are drivers and passengers whose vehicle body type is a motorcycle, codes 80–89:

moto = P[P.BODY_TYP.between(80, 89)].groupby("YEAR").size()
print(moto.to_string())
print(f"2019 to 2023: {moto[2023] / moto[2019] - 1:+.1%}; share of all deaths in 2023: {moto[2023] / T.loc[2023, 'total']:.1%}")
YEAR
2017    5228
2018    5042
2019    5045
2020    5622
2021    6139
2022    6256
2023    6370
2019 to 2023: +26.3%; share of all deaths in 2023: 15.5%

People killed on motorcycles rose from 5,045 in 2019 to 6,370 in 2023, 26.3% more, and were 15.5% of all road deaths.

Change in US road deaths from 2019 to 2023 by road user. change in deaths, 2019 to 2023cyclists+35.9% (856 to 1,163)on motorcycles+26.3% (5,045 to 6,370)pedestrians+17.5% (6,272 to 7,367)all road users+12.8% (36,355 to 41,025)drivers+12.6%passengers+6.1%020%40%Every group rose; the most exposed road users rose most spatialworkflow.io
Change by road user.

3. Change by state

Compare 2023 with 2019 for each state, with the counts beside the percentages:

S = P[P.YEAR.isin([2019, 2023])].pivot_table(index="STATE", columns="YEAR", values="ST_CASE", aggfunc="size")
S["change"] = S[2023] / S[2019] - 1
S["state"] = NAMES.reindex(S.index).values
print(f"states with more deaths in 2023 than 2019: {(S.change > 0).sum()} of {len(S)}")
print(S.sort_values("change")[["state", 2019, 2023, "change"]].iloc[[0, 1, -2, -1]].round(3).to_string(index=False))
states with more deaths in 2023 than 2019: 44 of 51
               state  2019  2023  change
               Maine   157   135  -0.140
              Hawaii   108    93  -0.139
          Washington   538   809   0.504
District of Columbia    23    44   0.913

44 of the 51 states had more deaths in 2023 than in 2019. Washington State's rose by half, from 538 to 809; the District of Columbia's nearly doubled, from 23 to 44, a change of 21 deaths. Maine and Hawaii fell by 14%.

The peak and the decline

The data used on this page

NHTSA's FARS national CSV files for 2017 to 2023, person level, and the Census Bureau's 2023 state population estimates file for state names.

import zipfile
from pathlib import Path

import pandas as pd
import requests

Path("data").mkdir(exist_ok=True)
frames = []
for year in range(2017, 2024):
    f = Path(f"data/FARS{year}NationalCSV.zip")
    if not f.exists():
        f.write_bytes(requests.get(f"https://static.nhtsa.gov/nhtsa/downloads/FARS/{year}/National/FARS{year}NationalCSV.zip", timeout=600).content)
    z = zipfile.ZipFile(f)
    clean = lambda c: c.replace("", "").upper()
    p = pd.read_csv(z.open(next(n for n in z.namelist() if n.lower().endswith("person.csv"))), encoding="latin1", low_memory=False,
                    usecols=lambda c: clean(c) in {"STATE", "ST_CASE", "PER_TYP", "INJ_SEV", "BODY_TYP"}).rename(columns=clean)
    frames.append(p[p.INJ_SEV == 4].assign(YEAR=year))
P = pd.concat(frames, ignore_index=True)
N = Path("data/NST-EST2023-ALLDATA.csv")
if not N.exists():
    N.write_bytes(requests.get("https://www2.census.gov/programs-surveys/popest/datasets/2020-2023/state/totals/NST-EST2023-ALLDATA.csv", timeout=300).content)
NAMES = pd.read_csv(N).query("SUMLEV == 40").set_index("STATE").NAME
print(f"{len(P):,} people killed, {P.YEAR.min()}-{P.YEAR.max()}")
276,646 people killed, 2017-2023

Example 1: the peak and the decline

Find each group's peak year and how far it has come down since:

groups = pd.concat([T[["driver", "passenger", "pedestrian", "cyclist", "total"]], moto.rename("motorcycle")], axis=1)
summary = pd.DataFrame({"peak year": groups.idxmax(), "peak": groups.max(), "2023": groups.loc[2023]})
summary["2023 vs peak"] = (summary["2023"] / summary.peak - 1).map("{:+.1%}".format)
print(summary.to_string())
            peak year   peak   2023 2023 vs peak
driver           2021  27555  25612        -7.1%
passenger        2021   6884   6499        -5.6%
pedestrian       2022   7593   7367        -3.0%
cyclist          2023   1163   1163        +0.0%
total            2021  43230  41025        -5.1%
motorcycle       2023   6370   6370        +0.0%

Driver deaths are 7.1% below their 2021 peak and pedestrian deaths 3.0% below their 2022 peak, but cyclist and motorcycle deaths reached their highest level of the period in 2023.

Choropleth of the percentage change in road deaths by state from 2019 to 2023, contiguous US, on a diverging scale: most states are pale to mid red, rising 0 to 30 percent; Washington rose 50 percent and South Dakota, Nevada, Arizona and Vermont are also dark red; Maine fell 14 percent and Wyoming, Nebraska, Kansas and West Virginia fell slightly; Texas rose 19 and California 12 percent. Choropleth of the percentage change in road deaths by state from 2019 to 2023, contiguous US, on a diverging scale: most states are pale to mid red, rising 0 to 30 percent; Washington rose 50 percent and South Dakota, Nevada, Arizona and Vermont are also dark red; Maine fell 14 percent and Wyoming, Nebraska, Kansas and West Virginia fell slightly; Texas rose 19 and California 12 percent.
Change by state.

A national trend is a sum of trends

Steps to stack several years of FARS person files. stacking seven years of FARS1find the file by nameany case, any folder2clean the column namesbyte-order mark in 2021 and 20223read only what is neededusecols: five columns4keep the killedINJ_SEV 4: 276,646 people5add a year columnthen pivot by roleFARS 2017 to 2023 spatialworkflow.io
Stacking seven years of FARS.

A national trend in road deaths is a sum of several trends with different causes. Driver deaths respond to how much and how fast people drive, to seat-belt use and to impairment; pedestrian and cyclist deaths respond to how many people walk and cycle, to vehicle size and design, and to street design; motorcycle deaths follow riding and helmet use. Splitting by road user, as here, shows which parts moved and whether they moved together.

The years after 2019 show all of them rising at once, then diverging. Total deaths peaked in 2021 and fell in 2022 and 2023, driven by fewer driver deaths, while cyclist deaths kept rising to 2023 and motorcycle deaths did too. A total that is "coming down" can therefore hide groups that are not. Counts are also not rates: travel fell in 2020 and recovered afterwards, so deaths per mile rose more steeply in 2020 than deaths did; the VMT tables give that denominator year by year.

Release versions and body types

Analysing road death trends with FARS: problems and fixes. trend trapsTrapInsteadone national totalby road user: cyclists +35.9%decline read as generalpeaks differ: cyclists stillrisingpercent change for DC23 to 44: show the countscounts read as ratestravel changed: use VMT for ratesFARS 2017 to 2023 spatialworkflow.io
Trend traps.
  • Release versions: the most recent year is often an annual report file, revised in the final release; compare final files where possible.
  • Person types: codes 3 and 4 are occupants of vehicles not in transport and unknown occupants; they are in "other" here.
  • Motorcycle body types: 80–89 covers motorcycles, mopeds and other two- and three-wheeled motor vehicles; FARS's manual lists them.
  • State counts: small states' year-to-year changes are mostly noise; pool years before ranking.
  • Puerto Rico: not in the national files.

FAQ

How have US road deaths changed since 2019?

From FARS: 36,355 deaths in 2019, a peak of 43,230 in 2021, and 41,025 in 2023, still 12.8% above 2019.

Which road users saw the largest increase?

Cyclists: deaths rose 35.9% from 2019 to 2023, against 17.5% for pedestrians and 12.6% for drivers.

How do I combine several years of FARS in Python?

Loop over the yearly zips, find the person or accident file by name regardless of case and folder, clean the column names, and concatenate with a year column.

Are road deaths going down?

Overall, from the 2021 peak; but cyclist and motorcycle deaths were still rising in 2023.