How to Calculate Road Death Rates per Vehicle Mile in Python

On this page

Problem statement

Road deaths per resident tell you how many people a state loses; they do not tell you how dangerous its roads are, because people in some states drive two or three times as far as in others. The standard exposure-based rate divides deaths by vehicle miles travelled (VMT), usually per 100 million miles. The Federal Highway Administration publishes VMT for each state, split into rural and urban roads, in its Highway Statistics table VM-2, an Excel sheet with multi-row headers. Joined to FARS, it gives a second ranking of states and a direct comparison of rural and urban roads.

This page reads VM-2 for 2023, joins it to FARS 2023 deaths, computes deaths per 100 million VMT by state, compares the ranking with deaths per resident, and splits deaths and travel between rural and urban roads.

Quick answer

Read the VM-2 sheet without headers, keep the rows with a state code, and divide FARS deaths by total VMT:

deaths = A.groupby("STATE").FATALS.sum()
T = VM2.join(deaths.rename("deaths")).join(POP.set_index("STATE").POPESTIMATE2023.rename("residents")).astype({"deaths": int})
T["per_100m_vmt"] = T.deaths / (T.vmt_total * 1e6) * 1e8               # VM-2 is in millions of miles
T["per_100k_res"] = T.deaths / T.residents * 1e5
print(f"US: {T.deaths.sum() / T.vmt_total.sum() / 1e6 * 1e8:.2f} deaths per 100 million VMT; {T.vmt_total.sum() / 1e6:.2f} trillion miles")
print(T.sort_values("per_100m_vmt", ascending=False)[["state", "deaths", "per_100m_vmt", "per_100k_res"]].head(4).round(2).to_string(index=False))
US: 1.26 deaths per 100 million VMT; 3.25 trillion miles
         state  deaths  per_100m_vmt  per_100k_res
   Mississippi     733          1.79         24.93
       Arizona    1308          1.74         17.60
South Carolina    1047          1.72         19.48
      Kentucky     814          1.66         17.98

Americans drove 3.25 trillion vehicle miles in 2023, and 41,025 died, 1.26 deaths per 100 million miles. Mississippi tops both rankings, at 1.79 per 100 million miles; Arizona, South Carolina and Kentucky follow per mile.

Scatter of the 50 states and DC with deaths per 100,000 residents on the horizontal axis and deaths per 100 million vehicle miles on the vertical, coloured by miles driven per resident: the points rise together from Massachusetts at 4.9 and 0.56 to Mississippi at 24.9 and 1.79; Wyoming, with the most miles per resident, sits far right but at 1.51 per mile; DC, with the fewest miles, sits far left but at 1.26; Oregon and Arizona are high per mile for their per-resident rates. Scatter of the 50 states and DC with deaths per 100,000 residents on the horizontal axis and deaths per 100 million vehicle miles on the vertical, coloured by miles driven per resident: the points rise together from Massachusetts at 4.9 and 0.56 to Mississippi at 24.9 and 1.79; Wyoming, with the most miles per resident, sits far right but at 1.51 per mile; DC, with the fewest miles, sits far left but at 1.26; Oregon and Arizona are high per mile for their per-resident rates.
Two rates, side by side.

Parse the VM-2 sheet, compare rankings, split rural and urban

1. Parse the VM-2 sheet

The sheet has six header rows and footnote rows, with rural columns, urban columns and a grand total, followed by the state FIPS code. Check the parsed totals against the sheet:

print(f"rows parsed: {len(VM2)}; columns used: rural total, urban total, total")
print(f"rural + urban = total for every state: {(VM2.vmt_rural + VM2.vmt_urban - VM2.vmt_total).abs().max():.3f} million miles largest gap")
print(VM2.sort_values("vmt_total", ascending=False).head(3)[["state", "vmt_total"]].round(0).to_string(index=False))
rows parsed: 51; columns used: rural total, urban total, total
rural + urban = total for every state: 0.000 million miles largest gap
     state  vmt_total
California   316612.0
     Texas   300338.0
   Florida   239188.0

Rural and urban travel add up to the total in every row, and California, Texas and Florida drive the most, 317, 300 and 239 billion miles.

2. Compare the two rankings

T["rank_vmt"] = T.per_100m_vmt.rank(ascending=False).astype(int)
T["rank_res"] = T.per_100k_res.rank(ascending=False).astype(int)
T["miles_per_resident"] = T.vmt_total * 1e6 / T.residents
print(f"rank correlation of the two rates: {T.per_100m_vmt.corr(T.per_100k_res, method='spearman'):.2f}")
moved = (T.rank_vmt - T.rank_res)
print(T.loc[moved.abs().sort_values(ascending=False).index[:4], ["state", "per_100k_res", "per_100m_vmt", "rank_res", "rank_vmt", "miles_per_resident"]].round(2).to_string(index=False))
rank correlation of the two rates: 0.85
               state  per_100k_res  per_100m_vmt  rank_res  rank_vmt  miles_per_resident
District of Columbia          6.48          1.26        47        26             5126.48
             Indiana         13.17          1.06        23        38            12417.18
             Alabama         19.07          1.35         6        21            14150.74
              Oregon         13.87          1.59        21         6             8696.62

The two rates agree broadly, a rank correlation of 0.85, with large moves for a few. Washington DC goes from 47th per resident to 26th per mile, because its residents drive 5,126 miles a year each; Oregon rises from 21st to 6th per mile, and Alabama and Indiana fall when their long annual mileage is taken into account.

Choropleth of road deaths per 100 million vehicle miles travelled by state in 2023, in quintiles from 0.56 to 1.79, contiguous US: highest in Mississippi, Arizona, South Carolina, Kentucky, Oregon, New Mexico and much of the South; lowest in Massachusetts, the North-east, Minnesota, Wisconsin and Utah. Choropleth of road deaths per 100 million vehicle miles travelled by state in 2023, in quintiles from 0.56 to 1.79, contiguous US: highest in Mississippi, Arizona, South Carolina, Kentucky, Oregon, New Mexico and much of the South; lowest in Massachusetts, the North-east, Minnesota, Wisconsin and Utah.
Deaths per mile driven by state.

3. Split rural and urban roads

FARS classifies each crash as rural or urban, as VM-2 does for travel. Divide each group's deaths by its travel:

dr = A[A.RUR_URBNAME == "Rural"].FATALS.sum(); du = A[A.RUR_URBNAME == "Urban"].FATALS.sum()
other = A.FATALS.sum() - dr - du
vr, vu = VM2.vmt_rural.sum() * 1e6, VM2.vmt_urban.sum() * 1e6
print(f"rural roads: {dr:,} deaths on {vr / 1e9:,.0f} billion miles = {dr / vr * 1e8:.2f} per 100M; "
      f"urban: {du:,} on {vu / 1e9:,.0f} billion = {du / vu * 1e8:.2f}")
print(f"rural share of travel {vr / (vr + vu):.1%}, of deaths {dr / (dr + du):.1%}; deaths not classified rural or urban: {other}")
rural roads: 16,796 deaths on 1,009 billion miles = 1.67 per 100M; urban: 24,036 on 2,238 billion = 1.07
rural share of travel 31.1%, of deaths 41.1%; deaths not classified rural or urban: 193

Rural roads carried 31.1% of travel and 41.1% of deaths: 1.67 deaths per 100 million miles against 1.07 on urban roads, about 56% higher. 193 deaths are in crashes not coded either way.

Miles driven per resident

The data used on this page

NHTSA's FARS 2023 national file; the Federal Highway Administration's Highway Statistics 2023, table VM-2, annual vehicle miles travelled by state and functional system, in millions; and the Census Bureau's 2023 state population estimates.

import zipfile
from pathlib import Path

import pandas as pd
import requests

Path("data").mkdir(exist_ok=True)
SRC = {"FARS2023NationalCSV.zip": "https://static.nhtsa.gov/nhtsa/downloads/FARS/2023/National/FARS2023NationalCSV.zip",
       "NST-EST2023-ALLDATA.csv": "https://www2.census.gov/programs-surveys/popest/datasets/2020-2023/state/totals/NST-EST2023-ALLDATA.csv",
       "vm2_2023.xlsx": "https://www.fhwa.dot.gov/policyinformation/statistics/2023/xls/vm2.xlsx"}
for name, url in SRC.items():
    if not Path("data", name).exists():
        Path("data", name).write_bytes(requests.get(url, timeout=600).content)
A = pd.read_csv(zipfile.ZipFile("data/FARS2023NationalCSV.zip").open("FARS2023NationalCSV/accident.csv"), encoding="latin1", low_memory=False)
POP = pd.read_csv("data/NST-EST2023-ALLDATA.csv").query("SUMLEV == 40")
raw = pd.read_excel("data/vm2_2023.xlsx", sheet_name="A", header=None)
code = pd.to_numeric(raw[18], errors="coerce")
rows = raw[code.between(1, 56)]                                        # state rows carry their FIPS code in column 18; the US total does not count
VM2 = pd.DataFrame({"state": rows[0].str.strip(), "vmt_rural": rows[8].astype(float), "vmt_urban": rows[16].astype(float),
                    "vmt_total": rows[17].astype(float)}).set_index(rows[18].astype(int).rename("STATE"))
print(f"{len(A):,} crashes; VM-2 rows: {len(VM2)}, the 50 states and DC")
37,769 crashes; VM-2 rows: 51, the 50 states and DC

Example 1: miles driven per resident

The gap between the two rates is the miles each resident drives:

m = T.miles_per_resident
print(f"miles per resident: {m.min():,.0f} ({T.state[m.idxmin()]}) to {m.max():,.0f} ({T.state[m.idxmax()]}); median {m.median():,.0f}")
print(f"correlation of miles per resident with deaths per resident: {m.corr(T.per_100k_res):.2f}; with deaths per mile: {m.corr(T.per_100m_vmt):.2f}")
miles per resident: 5,126 (District of Columbia) to 16,298 (Wyoming); median 10,119
correlation of miles per resident with deaths per resident: 0.81; with deaths per mile: 0.37

Annual miles per resident run from 5,126 in DC to 16,298 in Wyoming. They correlate at 0.81 with deaths per resident and only 0.37 with deaths per mile: much of the per-resident ranking is a ranking of how far people drive.

Travel and deaths on rural and urban roads in the US, 2023. rural and urban roads, 2023rural roadsurban roadsshare of travel31.1%68.9%share of deaths41.1%58.9%deaths per 100M miles1.671.07193 deaths are in crashes coded neither rural nor urban spatialworkflow.io
Rural and urban roads.

A denominator that matches the question

Two denominators for road death rates and the questions they answer. two rates, two questionsPer 100,000 residentshow many people a state losespublic health comparisonUS 12.2; Mississippi 24.9tracks miles driven: r = 0.81Per 100 million vehicle mileshow often travel ends in a deathroad safety comparisonUS 1.26; Mississippi 1.79ignores walking and cyclingRank correlation between them: 0.85 spatialworkflow.io
Two rates, two questions.

A rate needs a denominator that matches the question. Deaths per resident is a public health measure: it says how many people a population loses, wherever they were driving. Deaths per 100 million VMT is a road safety measure: it says how often travel on a state's roads ends in a death. The two diverge where residents drive very different distances, which is mostly a matter of geography: rural states with long distances and little public transport accumulate many miles per person.

Neither rate is "the" risk. VMT counts vehicle miles, not person miles, so it ignores occupancy and the exposure of pedestrians and cyclists, who are a growing share of deaths. It is an estimate built from traffic counts, with its own uncertainty. And the rural and urban split depends on census urban area boundaries, which change; rural roads carry a minority of travel and a far larger share of deaths, a pattern that holds across years even as the exact figures move.

Units, footnotes and person miles

Road death rates per vehicle mile: problems and fixes. VMT rate trapsTrapInsteadUS total row keptkeep state FIPS 1-56: else 6.49trillionVMT read as milesVM-2 is in millions: multiply by1e6unclassified crashes droppedreport them: 193 deathsone rate called the riskshow both: DC 47th or 26thFARS 2023 and FHWA VM-2 2023 spatialworkflow.io
VMT rate traps.
  • Units: VM-2 is in millions of vehicle miles; forgetting the factor gives rates a million times too high.
  • Unclassified crashes: a few FARS crashes are not coded rural or urban; report them rather than dropping them silently.
  • Footnotes: the sheet's footnotes describe state-specific estimation methods; read them before comparing small differences.
  • Other years: the FHWA URL changes with the year, and the table layout has changed over time; check columns on each new file.
  • Person miles: the National Household Travel Survey gives person miles by mode, the denominator for pedestrian and cyclist risk.

FAQ

How do I calculate a road death rate per vehicle mile?

Divide deaths by vehicle miles travelled and multiply by 100 million: deaths / vmt * 1e8, with VMT from the FHWA's Highway Statistics table VM-2.

Which US state has the most dangerous roads per mile driven?

In 2023, by deaths per 100 million VMT computed on this page, Mississippi had the highest rate.

Why do rankings by deaths per person and per mile differ?

Because residents of some states drive much further than others; a state with long rural distances can be high per person and average per mile.

Are rural roads more dangerous than urban roads?

Per mile travelled, yes: in 2023 rural roads carried less than a third of US travel and about two fifths of the deaths.