stats19 provides functions for downloading and formatting road crash data. Specifically, it enables access to the UK’s official road traffic casualty database, STATS19. (The name comes from the form used by the police to record car crashes and other incidents resulting in casualties on the roads.)
The raw data is provided as a series of .csv
files that
contain integers and which are stored in dozens of .zip
files. Finding, reading-in and formatting the data for research can be a
time consuming process subject to human error. stats19
speeds up these vital but boring and error-prone stages of the research
process with a single function: get_stats19()
. By allowing
public access to properly labelled road crash data,
stats19 aims to make road safety research more
reproducible and accessible.
For transparency and modularity, each stage can be undertaken separately, as documented in the stats19 vignette.
The package has now been peer reviewed and is stable, and has been published in the Journal of Open Source Software (Lovelace et al. 2019). Please tell people about the package, link to it and cite it if you use it in your work.
Install and load the latest version with:
::install_github("ropensci/stats19") remotes
library(stats19)
#> Data provided under OGL v3.0. Cite the source and link to:
#> www.nationalarchives.gov.uk/doc/open-government-licence/version/3/
You can install the released version of stats19 from CRAN with:
install.packages("stats19")
Load the development version of the package from this repository with:
::load_all() devtools
get_stats19()
requires year
and
type
parameters, mirroring the provision of STATS19 data
files, which are categorised by year (from 1979 onward) and type (with
separate tables for crashes, casualties and vehicles, as outlined
below). The following command, for example, gets crash data from 2023
(note: we follow the “crash not accident” campaign of
RoadPeace
in naming crashes, although the DfT refers to the relevant tables as
‘accidents’ data):
= get_stats19(year = 2023, type = "collision")
crashes #> Files identified: dft-road-casualty-statistics-collision-2023.csv
#> https://data.dft.gov.uk/road-accidents-safety-data/dft-road-casualty-statistics-collision-2023.csv
#> Data already exists in data_dir, not downloading
#> Reading in:
#> ~/data/stats19/dft-road-casualty-statistics-collision-2023.csv
#> date and time columns present, creating formatted datetime column
What just happened? For the year
2023 we read-in
crash-level (type = "collision"
) data on all road crashes
recorded by the police across Great Britain. The dataset contains 38
columns (variables) for 104,258 crashes. We were not asked to download
the file (by default you are asked to confirm the file that will be
downloaded). The contents of this dataset, and other datasets provided
by stats19, are outlined below and described in more
detail in the stats19
vignette.
We will see below how the function also works to get the
corresponding casualty and vehicle datasets for 2023. The package also
allows STATS19 files to be downloaded and read-in separately, allowing
more control over what you download, and subsequently read-in, with
read_collisions()
, read_casualties()
and
read_vehicles()
, as described in the vignette.
Data files can be downloaded without reading them in using the
function dl_stats19()
. If there are multiple matches, you
will be asked to choose from a range of options. Providing just the
year, for example, will result in the following options:
dl_stats19(year = 2023, data_dir = tempdir())
Multiple matches. Which do you want to download?
1: dft-road-casualty-statistics-casualty-2023.csv
2: dft-road-casualty-statistics-vehicle-2023.csv
3: dft-road-casualty-statistics-collision-2023.csv
Selection:
Enter an item from the menu, or 0 to exit
STATS19 data consists of 3 main tables:
The contents of each is outlined below.
Crash data was downloaded and read-in using the function
get_stats19()
, as described above.
nrow(crashes)
#> [1] 104258
ncol(crashes)
#> [1] 38
Some of the key variables in this dataset include:
= grepl(pattern = "severity|speed|pedestrian|light_conditions", x = names(crashes))
key_column_names
crashes[key_column_names]#> # A tibble: 104,258 × 6
#> accident_severity speed_limit pedestrian_crossing_hu…¹ pedestrian_crossing_…²
#> <chr> <chr> <chr> <chr>
#> 1 Slight 20 Control by other author… Pedestrian phase at t…
#> 2 Slight 30 None within 50 metres Zebra
#> 3 Slight 30 None within 50 metres No physical crossing …
#> 4 Slight 30 None within 50 metres No physical crossing …
#> 5 Slight 30 None within 50 metres No physical crossing …
#> 6 Slight 30 Control by other author… Pedestrian phase at t…
#> 7 Slight 20 None within 50 metres No physical crossing …
#> 8 Slight 50 None within 50 metres No physical crossing …
#> 9 Slight 20 None within 50 metres Pelican, puffin, touc…
#> 10 Slight 20 Control by school cross… Pelican, puffin, touc…
#> # ℹ 104,248 more rows
#> # ℹ abbreviated names: ¹pedestrian_crossing_human_control,
#> # ²pedestrian_crossing_physical_facilities
#> # ℹ 2 more variables: light_conditions <chr>, enhanced_severity_collision <dbl>
For the full list of columns, run names(crashes)
or see
the vignette.
As with crashes
, casualty data for 2023 can be
downloaded, read-in and formatted as follows:
= get_stats19(year = 2023, type = "casualty", ask = FALSE, format = TRUE)
casualties #> Files identified: dft-road-casualty-statistics-casualty-2023.csv
#> https://data.dft.gov.uk/road-accidents-safety-data/dft-road-casualty-statistics-casualty-2023.csv
#> Data already exists in data_dir, not downloading
#> Warning: The following named parsers don't match the column names:
#> accident_severity, carriageway_hazards, collision_index, collision_reference,
#> collision_year, date, day_of_week, did_police_officer_attend_scene_of_accident,
#> did_police_officer_attend_scene_of_collision, enhanced_collision_severity,
#> first_road_class, first_road_number, junction_control, junction_detail,
#> latitude, legacy_collision_severity, light_conditions,
#> local_authority_district, local_authority_highway,
#> local_authority_ons_district, location_easting_osgr, location_northing_osgr,
#> longitude, lsoa_of_accident_location, lsoa_of_collision_location,
#> number_of_casualties, number_of_vehicles, pedestrian_crossing_human_control,
#> pedestrian_crossing_physical_facilities, police_force, road_surface_conditions,
#> road_type, second_road_class, second_road_number, special_conditions_at_site,
#> speed_limit, time, trunk_road_flag, urban_or_rural_area, weather_conditions,
#> adjusted_serious, adjusted_slight, injury_based, accident_ref_no,
#> effective_date_of_change, previously_published_value, replacement_value,
#> variable, age_band_of_driver, age_of_driver, age_of_vehicle, dir_from_e,
#> dir_from_n, dir_to_e, dir_to_n, driver_distance_banding, driver_home_area_type,
#> driver_imd_decile, engine_capacity_cc, escooter_flag, first_point_of_impact,
#> generic_make_model, hit_object_in_carriageway, hit_object_off_carriageway,
#> journey_purpose_of_driver, junction_location, lsoa_of_driver, propulsion_code,
#> sex_of_driver, skidding_and_overturning, towing_and_articulation,
#> vehicle_direction_from, vehicle_direction_to, vehicle_leaving_carriageway,
#> vehicle_left_hand_drive, vehicle_location_restricted_lane, vehicle_manoeuvre,
#> vehicle_type
#> Warning in asMethod(object): NAs introduced by coercion
nrow(casualties)
#> [1] 132977
ncol(casualties)
#> [1] 21
The results show that there were 132,977 casualties reported by the police in the STATS19 dataset in 2023, and 21 columns (variables). Values for a sample of these columns are shown below:
c(4, 5, 6, 14)]
casualties[#> # A tibble: 132,977 × 4
#> vehicle_reference casualty_reference casualty_class bus_or_coach_passenger
#> <chr> <chr> <chr> <chr>
#> 1 1 1 Pedestrian Not a bus or coach pass…
#> 2 2 1 Driver or rider Not a bus or coach pass…
#> 3 3 2 Passenger Not a bus or coach pass…
#> 4 1 1 Driver or rider Not a bus or coach pass…
#> 5 2 1 Driver or rider Not a bus or coach pass…
#> 6 2 1 Driver or rider Not a bus or coach pass…
#> 7 1 1 Pedestrian Not a bus or coach pass…
#> 8 1 1 Driver or rider Not a bus or coach pass…
#> 9 1 1 Driver or rider Not a bus or coach pass…
#> 10 1 1 Pedestrian Not a bus or coach pass…
#> # ℹ 132,967 more rows
The full list of column names in the casualties
dataset
is:
names(casualties)
#> [1] "accident_index" "accident_year"
#> [3] "accident_reference" "vehicle_reference"
#> [5] "casualty_reference" "casualty_class"
#> [7] "sex_of_casualty" "age_of_casualty"
#> [9] "age_band_of_casualty" "casualty_severity"
#> [11] "pedestrian_location" "pedestrian_movement"
#> [13] "car_passenger" "bus_or_coach_passenger"
#> [15] "pedestrian_road_maintenance_worker" "casualty_type"
#> [17] "casualty_home_area_type" "casualty_imd_decile"
#> [19] "lsoa_of_casualty" "enhanced_casualty_severity"
#> [21] "casualty_distance_banding"
Data for vehicles involved in crashes in 2023 can be downloaded, read-in and formatted as follows:
= get_stats19(year = 2023, type = "vehicle", ask = FALSE, format = TRUE)
vehicles #> Files identified: dft-road-casualty-statistics-vehicle-2023.csv
#> https://data.dft.gov.uk/road-accidents-safety-data/dft-road-casualty-statistics-vehicle-2023.csv
#> Data already exists in data_dir, not downloading
#> Warning: The following named parsers don't match the column names:
#> accident_severity, carriageway_hazards, collision_index, collision_reference,
#> collision_year, date, day_of_week, did_police_officer_attend_scene_of_accident,
#> did_police_officer_attend_scene_of_collision, enhanced_collision_severity,
#> first_road_class, first_road_number, junction_control, junction_detail,
#> latitude, legacy_collision_severity, light_conditions,
#> local_authority_district, local_authority_highway,
#> local_authority_ons_district, location_easting_osgr, location_northing_osgr,
#> longitude, lsoa_of_accident_location, lsoa_of_collision_location,
#> number_of_casualties, number_of_vehicles, pedestrian_crossing_human_control,
#> pedestrian_crossing_physical_facilities, police_force, road_surface_conditions,
#> road_type, second_road_class, second_road_number, special_conditions_at_site,
#> speed_limit, time, trunk_road_flag, urban_or_rural_area, weather_conditions,
#> age_band_of_casualty, age_of_casualty, bus_or_coach_passenger, car_passenger,
#> casualty_class, casualty_distance_banding, casualty_home_area_type,
#> casualty_imd_decile, casualty_reference, casualty_severity, casualty_type,
#> enhanced_casualty_severity, lsoa_of_casualty, pedestrian_location,
#> pedestrian_movement, pedestrian_road_maintenance_worker, sex_of_casualty,
#> adjusted_serious, adjusted_slight, injury_based, accident_ref_no,
#> effective_date_of_change, previously_published_value, replacement_value,
#> variable
#> Warning in asMethod(object): NAs introduced by coercion
#> Warning in asMethod(object): NAs introduced by coercion
nrow(vehicles)
#> [1] 189815
ncol(vehicles)
#> [1] 34
The results show that there were 189,815 vehicles involved in crashes reported by the police in the STATS19 dataset in 2023, with 34 columns (variables). Values for a sample of these columns are shown below:
c(3, 14:16)]
vehicles[#> # A tibble: 189,815 × 4
#> accident_reference vehicle_leaving_carriageway hit_object_off_carriageway
#> <chr> <chr> <chr>
#> 1 010419171 Did not leave carriageway None
#> 2 010419183 Did not leave carriageway None
#> 3 010419183 Did not leave carriageway None
#> 4 010419183 Did not leave carriageway None
#> 5 010419189 Did not leave carriageway None
#> 6 010419189 Did not leave carriageway None
#> 7 010419191 Did not leave carriageway None
#> 8 010419191 Did not leave carriageway None
#> 9 010419192 Did not leave carriageway None
#> 10 010419192 Did not leave carriageway None
#> # ℹ 189,805 more rows
#> # ℹ 1 more variable: first_point_of_impact <chr>
The full list of column names in the vehicles
dataset
is:
names(vehicles)
#> [1] "accident_index" "accident_year"
#> [3] "accident_reference" "vehicle_reference"
#> [5] "vehicle_type" "towing_and_articulation"
#> [7] "vehicle_manoeuvre" "vehicle_direction_from"
#> [9] "vehicle_direction_to" "vehicle_location_restricted_lane"
#> [11] "junction_location" "skidding_and_overturning"
#> [13] "hit_object_in_carriageway" "vehicle_leaving_carriageway"
#> [15] "hit_object_off_carriageway" "first_point_of_impact"
#> [17] "vehicle_left_hand_drive" "journey_purpose_of_driver"
#> [19] "sex_of_driver" "age_of_driver"
#> [21] "age_band_of_driver" "engine_capacity_cc"
#> [23] "propulsion_code" "age_of_vehicle"
#> [25] "generic_make_model" "driver_imd_decile"
#> [27] "driver_home_area_type" "lsoa_of_driver"
#> [29] "escooter_flag" "dir_from_e"
#> [31] "dir_from_n" "dir_to_e"
#> [33] "dir_to_n" "driver_distance_banding"
An important feature of STATS19 data is that the collision table
contains geographic coordinates. These are provided at ~10m resolution
in the UK’s official coordinate reference system (the Ordnance Survey
National Grid, EPSG code 27700). stats19 converts the
non-geographic tables created by format_collisions()
into
the geographic data form of the sf
package
with the function format_sf()
as follows:
= format_sf(crashes)
crashes_sf #> 12 rows removed with no coordinates
The note arises because NA
values are not permitted in
sf
coordinates, and so rows containing no coordinates are
automatically removed. Having the data in a standard geographic form
allows various geographic operations to be performed on it. The
following code chunk, for example, returns all crashes within the
boundary of West Yorkshire (which is contained in the object police_boundaries
,
an sf
data frame containing all police jurisdictions in
England and Wales).
library(sf)
#> Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.3.1; sf_use_s2() is TRUE
library(dplyr)
#>
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#>
#> filter, lag
#> The following objects are masked from 'package:base':
#>
#> intersect, setdiff, setequal, union
= filter(police_boundaries, pfa16nm == "West Yorkshire")
wy #> old-style crs object detected; please recreate object with a recent sf::st_crs()
= crashes_sf[wy, ]
crashes_wy nrow(crashes_sf)
#> [1] 104246
nrow(crashes_wy)
#> [1] 4249
This subsetting has selected the 4,249 crashes which occurred within West Yorkshire in 2023.
The three main tables we have just read-in can be joined by shared key variables. This is demonstrated in the code chunk below, which subsets all casualties that took place in Leeds, and counts the number of casualties by severity for each crash:
= casualties$accident_index %in% crashes_wy$accident_index
sel = casualties[sel, ]
casualties_wy names(casualties_wy)
#> [1] "accident_index" "accident_year"
#> [3] "accident_reference" "vehicle_reference"
#> [5] "casualty_reference" "casualty_class"
#> [7] "sex_of_casualty" "age_of_casualty"
#> [9] "age_band_of_casualty" "casualty_severity"
#> [11] "pedestrian_location" "pedestrian_movement"
#> [13] "car_passenger" "bus_or_coach_passenger"
#> [15] "pedestrian_road_maintenance_worker" "casualty_type"
#> [17] "casualty_home_area_type" "casualty_imd_decile"
#> [19] "lsoa_of_casualty" "enhanced_casualty_severity"
#> [21] "casualty_distance_banding"
= casualties_wy %>%
cas_types select(accident_index, casualty_type) %>%
mutate(n = 1) %>%
group_by(accident_index, casualty_type) %>%
summarise(n = sum(n)) %>%
::spread(casualty_type, n, fill = 0)
tidyr$Total = rowSums(cas_types[-1])
cas_types= left_join(crashes_wy, cas_types, by = "accident_index") cj
What just happened? We found the subset of casualties that took place
in West Yorkshire with reference to the accident_index
variable. Then we used functions from the tidyverse
package dplyr (and spread()
from
tidyr) to create a dataset with a column for each
casualty type. We then joined the updated casualty data onto the
crashes_wy
dataset. The result is a spatial
(sf
) data frame of crashes in Leeds, with columns counting
how many road users of different types were hurt. The original and
joined data look like this:
%>%
crashes_wy select(accident_index, accident_severity) %>%
st_drop_geometry()
#> # A tibble: 4,249 × 2
#> accident_index accident_severity
#> * <chr> <chr>
#> 1 2023121345088 Slight
#> 2 2023122300320 Fatal
#> 3 2023122300338 Slight
#> 4 2023131258653 Slight
#> 5 2023131258655 Slight
#> 6 2023131258661 Serious
#> 7 2023131258672 Slight
#> 8 2023131258685 Slight
#> 9 2023131258728 Slight
#> 10 2023131258735 Slight
#> # ℹ 4,239 more rows
1:2, c("accident_index", "Cyclist")]
cas_types[#> # A tibble: 2 × 2
#> # Groups: accident_index [2]
#> accident_index Cyclist
#> <chr> <dbl>
#> 1 2023121345088 1
#> 2 2023122300320 1
1:2, c(1, 5, 34)] %>% st_drop_geometry()
cj[#> # A tibble: 2 × 3
#> accident_index latitude lsoa_of_accident_location
#> * <chr> <int> <chr>
#> 1 2023121345088 NA E01027735
#> 2 2023122300320 NA E01027735
The join operation added a geometry column to the casualty data, enabling it to be mapped (for more advanced maps, see the vignette):
= cj$Total / 3
cex plot(cj["speed_limit"], cex = cex)
The spatial distribution of crashes in West Yorkshire clearly relates to the region’s geography. Crashes tend to happen on busy Motorway roads (with a high speed limit, of 70 miles per hour, as shown in the map above) and city centres, of Leeds and Bradford in particular. The severity and number of people hurt (proportional to circle width in the map above) in crashes is related to the speed limit.
STATS19 data can be used as the basis of road safety research. The map below, for example, shows the results of an academic paper on the social, spatial and temporal distribution of bike crashes in West Yorkshire, which estimated the number of crashes per billion km cycled based on commuter cycling as a proxy for cycling levels overall (more sophisticated measures of cycling levels are now possible thanks to new data sources) (Lovelace, Roberts, and Kellar 2016):
We can also explore seasonal trends in crashes by aggregating crashes by day of the year:
library(ggplot2)
head(cj$date)
#> [1] "2023-08-22" "2023-04-02" "2023-03-29" "2023-01-01" "2023-01-01"
#> [6] "2023-01-01"
class(cj$date)
#> [1] "Date"
= cj %>%
crashes_dates st_set_geometry(NULL) %>%
group_by(date) %>%
summarise(
walking = sum(Pedestrian),
cycling = sum(Cyclist),
passenger = sum(`Car occupant`)
%>%
) ::gather(mode, casualties, -date)
tidyrggplot(crashes_dates, aes(date, casualties)) +
geom_smooth(aes(colour = mode), method = "loess") +
ylab("Casualties per day")
#> `geom_smooth()` using formula = 'y ~ x'
Different types of crashes also tend to happen at different times of day. This is illustrated in the plot below, which shows the times of day when people who were travelling by different modes were most commonly injured.
library(stringr)
= cj %>%
crash_times st_set_geometry(NULL) %>%
group_by(hour = as.numeric(str_sub(time, 1, 2))) %>%
summarise(
walking = sum(Pedestrian),
cycling = sum(Cyclist),
passenger = sum(`Car occupant`)
%>%
) ::gather(mode, casualties, -hour)
tidyr
ggplot(crash_times, aes(hour, casualties)) +
geom_line(aes(colour = mode))
Note that cycling manifests distinct morning and afternoon peaks (see Lovelace, Roberts, and Kellar 2016 for more on this).
Examples of how the package can been used for policy making include:
Use of methods taught in the stats19-training vignette by road safety analysts at Essex Highways and the Safer Essex Roads Partnership (SERP) to inform the deployment of proactive front-line police enforcement in the region (credit: Will Cubbin).
Mention of road crash data analysis based on the package in an article
on urban SUVs. The question of how vehicle size and type relates to road
safety is an important area of future research. A starting point for
researching this topic can be found in the stats19-vehicles
vignette, representing a possible next step in terms of how the data can
be used.
There is much important research that needs to be done to help make the transport systems in many cities safer. Even if you’re not working with UK data, we hope that the data provided by stats19 data can help safety researchers develop new methods to better understand the reasons why people are needlessly hurt and killed on the roads.
The next step is to gain a deeper understanding of stats19 and the data it provides. Then it’s time to pose interesting research questions, some of which could provide an evidence-base in support policies that save lives. For more on these next steps, see the package’s introductory vignette.
The stats19 package builds on previous work, including:
Got to the following URL: commonslibrary.parliament.uk/constituency-data-traffic-accidents online↩︎