Road accidents in France and in Haut-Rhin, 2019–2024ΒΆ

Guided tour of the analysis. Every table comes from a SQL query on the DuckDB database built by src/build_db.py; every chart is produced by a function of src/analysis.py.

Data: ONISR national road-accident file (BAAC), INSEE population estimates. Scope: metropolitan France.

InΒ [1]:
import sys; sys.path.insert(0, '../src')
from IPython.display import Image
import pandas as pd; pd.set_option('display.precision', 3)
from analysis import *
from model import fit, fig_odds

0. Can we trust the data?ΒΆ

Share of missing values on the fields we use, then our yearly death counts compared with the official ONISR figures.

InΒ [2]:
data_quality()
Out[2]:
field missing_%
0 Accident: GPS coordinates 0.00
1 Accident: hour 0.00
2 Accident: weather 0.01
3 Place: speed limit (valid 10-130) 2.23
4 Person: severity 0.05
5 Person: age 1.50
6 Person: sex 1.43
7 Person: travel mode 0.53
InΒ [3]:
validation_vs_onisr()
Out[3]:
year killed_ours killed_onisr
0 2019 3244 3244.0
1 2020 2541 2541.0
2 2021 2944 2944.0
3 2022 3267 3267.0
4 2023 3167 3167.0
5 2024 3193 NaN

Our counts match the official figures exactly for 2019–2023 (2024 was not yet published as a final figure when this was written), so the cleaning keeps every accident.

1. TrendΒΆ

Accidents fell during the 2020 lockdowns and have not returned to 2019 levels; deaths have.

InΒ [4]:
tr = yearly_trend(); tr
Out[4]:
year accidents_fr killed_fr accidents_68 killed_68
0 2019 56016 3244 437 25
1 2020 45121 2541 317 19
2 2021 53540 2944 373 29
3 2022 52380 3267 319 30
4 2023 51641 3167 367 36
5 2024 51058 3193 351 27
InΒ [5]:
fig_trend(tr); Image('../figures/01_trend.png')
Out[5]:
No description has been provided for this image

Haut-Rhin counts are small (20–36 deaths a year), so year-to-year swings are mostly noise.

2. WhenΒΆ

Busiest at the evening rush hour; deadliest between 3 and 6 a.m. (about 11% of accidents fatal, vs 4.5–5.5% during the day).

InΒ [6]:
hw = hour_weekday(); fig_when(hw); Image('../figures/02_when.png')
Out[6]:
No description has been provided for this image

3. WhoΒΆ

InΒ [7]:
km = killed_by_mode(); fig_modes(km)
Out[7]:
2019 2024 change_%
mode
Car 1658 1571 -5.0
Motorcycle 615 597 -3.0
Pedestrian 482 454 -6.0
Bicycle 172 192 12.0
E-scooter 11 47 327.0
E-bike 15 32 113.0
InΒ [8]:
Image('../figures/03_modes.png')
Out[8]:
No description has been provided for this image
InΒ [9]:
ag = killed_rate_by_age(); fig_age(ag); Image('../figures/04_age.png')
Out[9]:
No description has been provided for this image

Young men (20–24) and people over 80 have the highest death rates.

4. WhereΒΆ

InΒ [10]:
dep = by_department(); dep.head(10)
Out[10]:
dep pop killed_per_year acc_per_year killed_per_million rank_safest
0 92 1.642e+06 22.667 2394.500 13.804 1
1 94 1.427e+06 21.833 2297.167 15.303 2
2 93 1.683e+06 27.167 2765.833 16.144 3
3 75 2.103e+06 37.667 4905.000 17.914 4
4 95 1.274e+06 27.333 865.167 21.448 5
5 78 1.462e+06 37.000 676.667 25.316 6
6 91 1.316e+06 34.000 1349.833 25.835 7
7 69 1.921e+06 54.500 2119.167 28.370 8
8 59 2.607e+06 86.333 972.333 33.120 9
9 67 1.168e+06 39.333 636.500 33.664 10
InΒ [11]:
dep[dep.dep == '68']
Out[11]:
dep pop killed_per_year acc_per_year killed_per_million rank_safest
10 68 769231.0 27.667 360.667 35.967 11
InΒ [12]:
fig_map(dep); Image('../figures/05_map_departments.png')
Out[12]:
No description has been provided for this image
InΒ [13]:
profile_68_vs_france()
Out[13]:
area Haut-Rhin France (other depts)
in_town 0.533 0.639
at_night 0.241 0.265
on_motorway 0.091 0.098
with_motorcycle 0.189 0.226
with_bike_or_escooter 0.167 0.138
with_pedestrian 0.148 0.164
fatal 0.072 0.056
killed_per_accident 0.077 0.059
InΒ [14]:
fig_haut_rhin(haut_rhin_points()); Image('../figures/06_haut_rhin_map.png')
Out[14]:
No description has been provided for this image

A warning about the denominatorΒΆ

Deaths are almost always recorded; minor accidents are not. Haut-Rhin has a higher share of fatal accidents than France, yet fewer deaths per inhabitant: it records fewer non-fatal accidents per inhabitant. The city counts make it visible.

InΒ [15]:
rc = recording_completeness(); fig_completeness(rc); Image('../figures/07_recording_completeness.png')
Out[15]:
No description has been provided for this image
InΒ [16]:
cities().pivot(index='year', columns='city', values='accidents')
Out[16]:
city Colmar Mulhouse Strasbourg
year
2019 48 22 369
2020 20 18 215
2021 31 19 263
2022 24 16 279
2023 19 11 243
2024 24 15 225

Mulhouse records fewer accidents than Colmar, a smaller city, and about 15 times fewer than Strasbourg, which is only about three times its size. That points to under-recording of non-fatal accidents in Mulhouse rather than safer streets, a hypothesis to check with local police or city data.

5. What makes an accident fatal?ΒΆ

Logistic regression on ~300,000 accidents. Odds ratios above 1 increase the odds that someone dies; they are associations, not causes.

InΒ [17]:
ors, stats = fit(); stats
Out[17]:
{'n_accidents': 301290,
 'fatal_rate': 0.055524577649440736,
 'auc_holdout': 0.7616546574231433,
 'pseudo_r2': 0.108045078390962}
InΒ [18]:
ors.round(2)
Out[18]:
odds_ratio ci_low ci_high p_value
E-scooter involved 0.75 0.64 0.87 0.0
Rain, snow, fog or wind 0.84 0.80 0.88 0.0
Night, street lights on 1.09 1.03 1.16 0.0
Weekend 1.17 1.13 1.21 0.0
Dusk or dawn (vs daylight) 1.19 1.12 1.27 0.0
Single vehicle, no collision 1.20 1.15 1.27 0.0
Speed limit 110-130 1.23 1.13 1.35 0.0
Motorcycle involved 1.28 1.23 1.34 0.0
Speed limit 70 (vs <=50) 1.52 1.41 1.65 0.0
Bicycle or e-bike involved 1.52 1.43 1.63 0.0
Night, no street lights 1.75 1.67 1.82 0.0
Outside built-up area 1.91 1.78 2.05 0.0
Speed limit 80-90 2.12 1.97 2.28 0.0
Head-on collision 2.56 2.45 2.69 0.0
Fixed obstacle hit (tree, wall...) 2.62 2.53 2.72 0.0
Heavy vehicle involved 2.99 2.84 3.15 0.0
Pedestrian involved 3.09 2.92 3.26 0.0
InΒ [19]:
fig_odds(ors); Image('../figures/08_odds_ratios.png')
Out[19]:
No description has been provided for this image

ConclusionsΒΆ

  • About 3,000 people die every year on metropolitan French roads; the number is flat since 2019.
  • Two thirds of deaths happen outside built-up areas, which hold only a third of accidents.
  • Deaths of e-scooter riders went from 11 to 47 a year; injured e-scooter users quadrupled.
  • Haut-Rhin is among the safer departments (36 deaths per million, rank 11 of 96).
  • Pedestrians, heavy vehicles, fixed obstacles and head-on collisions multiply the odds of a fatal outcome by 2.5 to 3.