The number of injury crashes in Victoria is in the thousands each year, however, crash counts do not reveal where the highest numbers of fatal and serious outcomes in crash situations are occurring. This visual story explores the increase in the likelihood of recorded injury crashes in Victoria being classified as serious or fatal, when, by road conditions, amongst whom and where.
Analysis is based on full calendar years for the period 2012-25. Severely crashes are defined here as a crash classified in the source data as either a Serious injury accident or a Fatal accident. Percentages are NOT population/ exposure-based measures of crash risk; they are a look at the percentage of recorded injury crashes that are severe.
The information used consists of over 200,000 crash data from several years. The period of years used for this particular project was for the full years from 2012 through 2025 to avoid the distortion of any data from a partial year. Because the primary objective is to gain an understanding of the severity of crash consequences, fatal and serious injury crashes were combined into severe crashes.
I have looked at variables that deal with the time of the crash, its severity, speed zones, lighting conditions, location, and the type of road users involved to explore the story. These variables enabled me to compare the severity of crashes over the course of the day, in different road environments, among the different groups of road users and in the various regions of Victoria.
Data was cleaned prior to creating visualisations. The data was limited to calendar years of the years 2012 to 2025 with the exception of the data from 2026 being an incomplete year. Non-injury crashes were excluded as this study is about the severity of injury crashes.
Fatal and serious injury crashes were combined into a single category (severe), and all other injury crashes remained in their own category. The year, month and hour were extracted from the crash date and time, and where available, numeric values for speed-zone were extracted. Where a value was missing, this was addressed separately for each visualisation, to not lose valuable data from the overall set of data.
data_path <- "C:/Users/aathi/Downloads/victorian_road_crash_data (1).csv"
crashes_raw <- read_csv(
data_path,
show_col_types = FALSE
)
# Clean and prepare the crash data
crashes <- crashes_raw %>%
mutate(
# Create time variables
Year = year(ACCIDENT_DATE),
Month = month(
ACCIDENT_DATE,
label = TRUE,
abbr = TRUE
),
Hour = hour(ACCIDENT_TIME),
# Convert speed zone into numeric values where possible
Speed_numeric = as.numeric(
str_extract(SPEED_ZONE, "\\d+")
),
# Put days of the week in correct order
DAY_OF_WEEK = factor(
DAY_OF_WEEK,
levels = c(
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
)
)
) %>%
# Keep complete calendar years only
filter(
Year >= 2012,
Year <= 2025
) %>%
# Remove non-injury crashes
filter(
SEVERITY != "Non injury accident"
) %>%
# Create simplified severity category
mutate(
Severe = case_when(
SEVERITY %in% c(
"Fatal accident",
"Serious injury accident"
) ~ "Severe",
SEVERITY == "Other injury accident" ~ "Other injury",
TRUE ~ NA_character_
)
) %>%
# Remove records without a usable severity category
filter(
!is.na(Severe)
)
# Statewide severe-crash percentage
statewide_severe_rate <- mean(
crashes$Severe == "Severe",
na.rm = TRUE
) * 100
The volume of the crashes is not the only thing. The percentage of injury serious or fatal crashes is greater in early morning compared to daytime hours and fewer injury crashes are experienced during early morning hours compared with daytime hours.
p1 <- ggplot(
day_hour_summary,
aes(
x = Hour,
y = DAY_OF_WEEK,
fill = Severe_rate,
text = hover
)
) +
geom_tile(colour = "white", linewidth = 0.25) +
scale_fill_viridis_c(
option = "C",
direction = -1,
labels = label_number(accuracy = 1, suffix = "%")
) +
scale_x_continuous(
breaks = seq(0, 23, by = 2),
labels = function(x) sprintf("%02d:00", x)
) +
labs(
x = "Hour of day",
y = NULL,
fill = "Serious/fatal\nshare"
) +
theme_minimal(base_size = 12) +
theme(
panel.grid = element_blank(),
legend.position = "right"
)
plot1 <- ggplotly(
p1,
tooltip = "text",
height = 470
) %>%
layout(
margin = list(l = 85, r = 130, t = 20, b = 70),
xaxis = list(automargin = TRUE),
yaxis = list(automargin = TRUE)
) %>%
config(
responsive = TRUE,
displaylogo = FALSE
)
plot1
Source: Victorian Road Crash Data, Department of Transport and Planning. Complete years 2012–2025.
The percentage of the total number of injury crashes that are classified as serious or fatal tends to increase with increased speed limits. The posted limit is a description of the roadway environment, and not a measurement of the actual travelling speed of the vehicle at the time of the crash.
p2 <- ggplot(
speed_summary_final,
aes(
x = Speed_numeric,
y = Severe_rate,
text = hover
)
) +
geom_line(linewidth = 1, colour = "#3C5488") +
geom_point(
aes(size = Total_crashes),
colour = "#3C5488",
alpha = 0.85
) +
scale_size_continuous(range = c(4, 11), guide = "none") +
scale_x_continuous(
breaks = c(40, 50, 60, 70, 80, 90, 100, 110)
) +
scale_y_continuous(
labels = label_number(accuracy = 1, suffix = "%")
) +
labs(
x = "Posted speed zone (km/h)",
y = "Serious/fatal crash share"
) +
theme_minimal(base_size = 12) +
theme(panel.grid.minor = element_blank())
plot2 <- ggplotly(
p2,
tooltip = "text",
height = 460
) %>%
layout(
margin = list(l = 85, r = 30, t = 20, b = 75),
xaxis = list(automargin = TRUE),
yaxis = list(automargin = TRUE)
) %>%
config(
responsive = TRUE,
displaylogo = FALSE
)
plot2
Source: Victorian Road Crash Data, Department of Transport and Planning. Percentages are calculated within each posted speed-zone category.
There are several crashes that are particularly severe: motorcyclist-involved crash; pedestrian-involved crash; driver age 75 or older. The variations are particularly noticeable on higher speed roads.
p3 <- ggplot(
road_user_speed,
aes(
x = Speed_numeric,
y = Severe_rate,
colour = Group,
group = Group,
text = hover
)
) +
geom_line(linewidth = 0.9, alpha = 0.9) +
geom_point(size = 2.8) +
scale_colour_viridis_d(option = "D", end = 0.9) +
scale_x_continuous(
breaks = c(40, 50, 60, 70, 80, 90, 100, 110)
) +
scale_y_continuous(
labels = label_number(accuracy = 1, suffix = "%")
) +
labs(
x = "Posted speed zone (km/h)",
y = "Serious/fatal crash share",
colour = "Crash involved"
) +
theme_minimal(base_size = 12) +
theme(
panel.grid.minor = element_blank(),
legend.position = "bottom"
)
plot3 <- ggplotly(
p3,
tooltip = "text",
height = 540
) %>%
layout(
margin = list(l = 85, r = 30, t = 20, b = 150),
legend = list(
orientation = "h",
x = 0,
y = -0.30,
xanchor = "left",
yanchor = "top"
),
xaxis = list(automargin = TRUE),
yaxis = list(automargin = TRUE)
) %>%
config(
responsive = TRUE,
displaylogo = FALSE
)
plot3
Source: Victorian Road Crash Data, Department of Transport and Planning. Road-user categories can overlap because a single crash can involve more than one road-user type.
While most recorded injury crashes are in the metro areas, there are a few regional areas with a higher percentage of serious-or-fatal crashes. These are shown by the size of the point and the dashed line represents the number of severe crashes statewide.
p4 <- ggplot(
region_summary_final,
aes(
x = Severe_rate,
y = DTP_REGION,
text = hover
)
) +
geom_vline(
xintercept = statewide_severe_rate,
linetype = "dashed",
colour = "grey45"
) +
geom_segment(
aes(
x = statewide_severe_rate,
xend = Severe_rate,
yend = DTP_REGION
),
colour = "grey75",
linewidth = 1
) +
geom_point(
aes(
size = Total_crashes,
colour = Area_type
),
alpha = 0.9
) +
scale_size_continuous(range = c(5, 16), guide = "none") +
scale_colour_manual(
values = c(
"Metropolitan" = "#0072B2",
"Regional" = "#D55E00"
)
) +
scale_x_continuous(
labels = label_number(accuracy = 1, suffix = "%")
) +
labs(
x = "Serious/fatal crash share",
y = NULL,
colour = NULL
) +
theme_minimal(base_size = 12) +
theme(
panel.grid.major.y = element_blank(),
panel.grid.minor = element_blank(),
legend.position = "bottom"
)
plot4 <- ggplotly(
p4,
tooltip = "text",
height = 500
) %>%
layout(
margin = list(l = 150, r = 30, t = 20, b = 100),
legend = list(
orientation = "h",
x = 0,
y = -0.20
),
xaxis = list(automargin = TRUE),
yaxis = list(automargin = TRUE)
) %>%
config(
responsive = TRUE,
displaylogo = FALSE
)
plot4
Source: Victorian Road Crash Data, Department of Transport and Planning. The statewide reference line is based on all included injury crashes from 2012–2025.
In Victorian LGAs where there were 500 or more recorded injury crashes, areas with a higher proportion of crashes in 80+ km/h areas also have a higher proportion of serious-or-fatal crashes. This correlation is significant (Pearson r ≈ 0.70); it is important to note that this is a correlation – not a cause-and-effect relationship.
p5 <- ggplot(
lga_environment,
aes(
x = High_speed_share,
y = Severe_rate,
size = Total_crashes,
colour = DTP_REGION,
text = hover
)
) +
geom_point(alpha = 0.80) +
geom_smooth(
aes(x = High_speed_share, y = Severe_rate),
inherit.aes = FALSE,
method = "lm",
se = FALSE,
colour = "grey35",
linewidth = 0.8
) +
scale_size_continuous(
range = c(4, 13),
guide = "none"
) +
scale_colour_viridis_d(
option = "D",
end = 0.85
) +
scale_x_continuous(
labels = label_number(accuracy = 1, suffix = "%")
) +
scale_y_continuous(
labels = label_number(accuracy = 1, suffix = "%")
) +
labs(
x = "Crashes in 80+ km/h zones",
y = "Serious/fatal crash share",
colour = "DTP region"
) +
theme_minimal(base_size = 12) +
theme(
legend.position = "right",
panel.grid.minor = element_blank()
)
plot5 <- ggplotly(
p5,
tooltip = "text",
height = 520
) %>%
layout(
autosize = TRUE,
margin = list(
l = 90,
r = 190,
t = 25,
b = 80
),
legend = list(
orientation = "v",
x = 1.02,
y = 1,
xanchor = "left",
yanchor = "top",
font = list(size = 11)
),
xaxis = list(
automargin = TRUE,
title = list(
text = "Crashes in 80+ km/h zones (%)"
)
),
yaxis = list(
automargin = TRUE,
title = list(
text = "Serious/fatal crash share (%)"
)
)
) %>%
config(
responsive = TRUE,
displaylogo = FALSE
)
plot5
Source: Victorian Road Crash Data, Department of Transport and Planning. LGA comparison restricted to LGAs with at least 500 injury crashes and at least 400 crashes with a known numeric speed zone.
The distribution of crash severity is not even across the five views. The impact of these consequences is worse and more likely to be fatal in the early morning, on faster speed road environments, amongst certain group of road users, and in certain geographic locations across Victoria. These patterns are associations and they describe the types of crashes that have been recorded, and should not be used as direct measures of driving risk and are not evidence of causation.
The use of Generative AI (ChatGPT, OpenAI) for troubleshooting R code, polishing written explanations.
The following packages have been used for importing, cleaning and transforming data, analysing it and visualizing it: R, RStudio, ggplot2 and plotly. Interactive visualisations made in R with the packages ffmpeg, ggplot2 and plotly.
R and RStudio were used to import, clean, transform, analyse and
visualise the data. ggplot2 and plotly were
used to construct the interactive visualisations.
Department of Transport and Planning. (2026). Victoria road crash data [Data set]. Transport Victoria Open Data. https://opendata.transport.vic.gov.au/dataset/victoria-road-crash-data
R Core Team. (2026). R: A language and environment for statistical computing [Computer software]. R Foundation for Statistical Computing. https://www.R-project.org/
Wickham, H. (2016). ggplot2: Elegant graphics for data analysis. Springer. https://doi.org/10.1007/978-3-319-24277-4
Sievert, C. (2020). Interactive web-based data visualization with R, plotly, and shiny. Chapman and Hall/CRC.