This report displays my visualizations based around aviation accident data. The dataset contains 27,598 accident records spanning from 1918 through August of 2026. It includes columns on crash locations, fatalities, cause of crash, time, flight phase, and operator. I use these records to display several visuals that show characteristics about the history of recorded aviation crashes since the dawn of the airplane. While this dataset may not be 100% complete with every aviation crash, it allows us examine the commonalities between crashes. Because this dataset only contains Crash data and not flight records without an incident, we can not determine overall saftey patterns but several atterns still developed amoung the flight crashes.
setwd('/Users/brad/Documents/MacFiles/R Studio/R_Data_Files')
library(dplyr)
library(ggplot2)
library(lubridate)
library(leaflet)
library(scales)
library(plotly)
library(RColorBrewer)
df = read.csv("planecrashdata.csv", na.strings=c("", "NA"))
ColsToDrop = c("registration", "survivors", "crash_site",
"schedule", "msn", "yom", "flight_type",
"other_fatalities", "circumstances",
"image_url", "hex_code", "external_id")
df = df[, !names(df) %in% ColsToDrop]
df$crash_cause[is.na(df$crash_cause)] = "Unknown"
df$Date = ymd(substring(df$timestamp, 1, 10))
df$year = year(df$Date)
df$month = month(df$Date, label=TRUE, abbr=TRUE)
df$decade = floor(df$year/10)*10
head(df[,c("timestamp", "Date", "year", "month", "decade")])
summary(df$year)
summary(df$year)
summary(df$total_fatalities)
summary(df$crew_on_board)
summary(df$passengers_on_board)
colSums(is.na(df))
count(df, crash_cause, sort=TRUE)
count(df, flight_phase, sort=TRUE)
My first visualization is an interactive Leaflet map which plots the crash locations via latitude and longitude on a global map. 18,475 Of the 27,598 accidents in the dataset had coordinates available for mapping, with the highest concentration in the US and Europe. The color difference represents the determined cause of the crash. We can see that from the crashes with available geolocation data, the most common cause was Human Actions/cause. Followed by unknown (“NA” in the dataset) then technical reasons. Differences in population, economies, and historical records likely contribute to the differences in recorded crashes between continents and regions.
map_df = df %>%
filter(!is.na(latitude), !is.na(longitude),
latitude >= -90, latitude <= 90,
longitude >= -180, longitude <= 180)
map_df$cause_group = map_df$crash_cause
#count(map_df, cause_group, sort=TRUE)
knitr::kable(count(map_df, cause_group, sort=TRUE),
col.names=c("Crash Cause", "Number of Accidents"),
caption="Recorded Plane Crashes by Cause")
| Crash Cause | Number of Accidents |
|---|---|
| Human factor | 6618 |
| Unknown | 5810 |
| Technical failure | 3894 |
| Weather | 1079 |
| Terrorism act, Hijacking, Sabotage | 671 |
| Other causes | 403 |
cause_colors = colorFactor(
palette=c("red", "orange", "blue", "purple", "green", "gray"),
domain=c("Human factor", "Technical failure", "Weather",
"Terrorism act, Hijacking, Sabotage", "Other causes", "Unknown"))
map_df$popup = paste("Aircraft:", map_df$aircraft,
"<br>Date:", map_df$Date,
"<br>Location:", map_df$crash_location,
"<br>Cause:", map_df$cause_group,
"<br>Fatalities:", map_df$total_fatalities)
plt1 = leaflet() %>%
addTiles() %>%
setView(lng=0, lat=20, zoom=2) %>%
addCircleMarkers(
lng=map_df$longitude,
lat=map_df$latitude,
radius=5,
color=cause_colors(map_df$cause_group),
fillColor=cause_colors(map_df$cause_group),
opacity=0.7,
fillOpacity=0.6,
stroke=FALSE,
popup=map_df$popup,
clusterOptions=markerClusterOptions()
) %>%
addLegend(
position="bottomright",
colors=c("red", "orange", "blue", "purple", "green", "gray"),
labels=c("Human factor", "Technical failure", "Weather",
"Terrorism act, Hijacking, Sabotage", "Other causes", "Unknown"),
opacity=1,
title="Crash Cause"
)
plt1
This visualization looks at the number of fatalities on a year-to-year basis. I printed the 15 years with the highest fatalities recorded, with the top 3 all occurring between 1943-45 during the midst of WW2. A trend of decreasing fatalities can also be observed. There were approximately 310 million worldwide passengers in 1970, which shot up to 4.7 billion in 2024, without close to a corresponding increase in fatalities. So, it speaks to the improvements in technology and safety among other reasons that the fatality numbers are significantly less in the modern era when comparing those two time periods.
fatality_counts = df %>%
group_by(year) %>%
summarise(fatalities=sum(total_fatalities, na.rm=TRUE), .groups="keep") %>%
data.frame()
#summary(fatality_counts$fatalities)
plt3 = ggplot(fatality_counts, aes(x=year, y=fatalities)) +
geom_line(color="red", linewidth=1.3) +
geom_point(data=subset(fatality_counts, year %% 10 == 0),
size=2.5, shape=21, fill="white", color="red") +
scale_x_continuous(breaks=seq(1920, 2030, 10)) +
labs(title="Total Aviation Fatalities by Year",
x="Year",
y="Total Fatalities") +
theme_minimal()
top_fatal_years = fatality_counts %>%
arrange(desc(fatalities)) %>%
head(15)
knitr::kable(top_fatal_years,
col.names=c("Year", "Total Fatalities"),
caption="Top 15 Years by Recorded Aviation Fatalities")
| Year | Total Fatalities |
|---|---|
| 1945 | 4555 |
| 1944 | 4416 |
| 1943 | 3876 |
| 1972 | 3305 |
| 1985 | 2928 |
| 1973 | 2868 |
| 1996 | 2758 |
| 1974 | 2668 |
| 1942 | 2647 |
| 1969 | 2568 |
| 1989 | 2470 |
| 1977 | 2424 |
| 1962 | 2385 |
| 1968 | 2375 |
| 1979 | 2330 |
plt3_top = plt3 +
geom_point(data=top_fatal_years, aes(x=year, y=fatalities),
color="black", fill="gold", shape=21, size=3.5)
plt3_labeled = plt3_top +
geom_text(data=top_fatal_years, aes(x=year, y=fatalities, label=year),
color="black", size=3, vjust=-1)
plt3_labeled
The first question I thought of when looking at this data was whether certain brands of aircraft (inspired by the negative press around Boeing) had more recorded crashes historically. After examining the aircraft types and finding over 1,000 different entries, I simplified my data to compare aircraft manufacturers instead. The results surprised me, as despite the number of aircraft and flights dramatically increasing over time, the manufacturers with the most recorded crashes were not necessarily the most dominant brands of the past 50 years. Instead, many of the manufacturers with the most crashes were companies that produced large numbers of aircraft around the time of the Second World War, including Douglas, De Havilland, and Beechcraft. This is interesting when compared to my earlier graphs, which showed some of the highest annual crash and fatality totals during WW2. Additionally, a large percentage of crashes involving these manufacturers were attributed to human factors or unknown causes. Their high numbers of recorded crashes could also be related to the widespread use and long operating histories of their aircraft, rather than the aircraft necessarily being more dangerous. Ultimately this graph is useful for also showing which brands historically have had more crashes, but it doesn’t mean that brands with more recorded crashes are less safe as we do not know the scale of flights
manufacturer_df = df %>%
filter(!is.na(aircraft))
manufacturer_df$manufacturer = sub(" .*", "", manufacturer_df$aircraft)
manufacturer_counts = count(manufacturer_df, manufacturer, sort=TRUE)
top_manufacturers = head(manufacturer_counts, 10)
knitr::kable(top_manufacturers,
col.names=c("Aircraft Manufacturer", "Number of Accidents"),
caption="Top 10 Aircraft Manufacturers by Recorded Accidents")
| Aircraft Manufacturer | Number of Accidents |
|---|---|
| Douglas | 3737 |
| De | 2213 |
| Cessna | 1804 |
| Beechcraft | 1488 |
| Lockheed | 1432 |
| Avro | 1349 |
| Boeing | 1332 |
| Piper | 1085 |
| PZL-Mielec | 840 |
| Antonov | 815 |
manufacturer_causes = manufacturer_df %>%
filter(manufacturer %in% top_manufacturers$manufacturer) %>%
group_by(manufacturer, crash_cause) %>%
summarise(n=n(), .groups="keep") %>%
data.frame()
plt4 = ggplot(manufacturer_causes, aes(x=reorder(manufacturer, n, sum), y=n, fill=crash_cause)) +
geom_bar(stat="identity") +
coord_flip() +
labs(title="Top 10 Aircraft Manufacturers by Crash Cause",
x="Aircraft Manufacturer",
y="Number of Crashes",
fill="Crash Cause") +
scale_fill_brewer(palette="Set2") +
theme_minimal()
plt4
This visualization shows the fatality rate of crew members and passengers based on the number of people on the plane and across decades. I wanted to see if the number of people aboard the plane a noticeable impact on the fatality rate. I also only counted crashes with both a crew member and a passenger when determining the percentage of people who died in recorded crashes for a even comparison. The percentages varied from decade to decade and occupancy group to occupancy group but generally appeared to decline over time. I also noticed that smaller aircraft, particularly those with 1–10 occupants, often had higher fatality rates than larger aircraft. There were also gaps noticeable in the earlier decades for some of the larger occupancy groups. This could be related to larger aircraft not existing, being less available, and/or gaps in recording data.
fatality_df = df %>%
filter(!is.na(crew_on_board), !is.na(crew_fatalities),
!is.na(passengers_on_board), !is.na(passengers_fatalities),
crew_on_board > 0, passengers_on_board > 0,
crew_fatalities >= 0, passengers_fatalities >= 0,
crew_fatalities <= crew_on_board,
passengers_fatalities <= passengers_on_board)
fatality_df$occupancy = fatality_df$crew_on_board + fatality_df$passengers_on_board
fatality_df$occupancy_group = ifelse(fatality_df$occupancy <= 10, "1-10",
ifelse(fatality_df$occupancy <= 50, "11-50",
ifelse(fatality_df$occupancy <= 100, "51-100", "101+")))
fatality_df$occupancy_group = factor(fatality_df$occupancy_group,
levels=c("1-10", "11-50", "51-100", "101+"))
occupancy_counts = fatality_df %>%
group_by(decade, occupancy_group) %>%
summarise(crew_on_board=sum(crew_on_board),
crew_fatalities=sum(crew_fatalities),
passengers_on_board=sum(passengers_on_board),
passengers_fatalities=sum(passengers_fatalities),
.groups="keep") %>%
data.frame()
crew_rates = data.frame(decade=occupancy_counts$decade,
occupancy_group=occupancy_counts$occupancy_group,
type="Crew",
rate=100*occupancy_counts$crew_fatalities/occupancy_counts$crew_on_board)
passenger_rates = data.frame(decade=occupancy_counts$decade,
occupancy_group=occupancy_counts$occupancy_group,
type="Passengers",
rate=100*occupancy_counts$passengers_fatalities/occupancy_counts$passengers_on_board)
rate_df = rbind(crew_rates, passenger_rates)
rate_df$panel = paste(rate_df$type, rate_df$occupancy_group)
rate_df$panel = factor(rate_df$panel,
levels=c("Crew 1-10", "Crew 11-50", "Crew 51-100", "Crew 101+",
"Passengers 1-10", "Passengers 11-50",
"Passengers 51-100", "Passengers 101+"))
plt5 = ggplot(rate_df, aes(x=factor(decade), y=rate, fill=type)) +
geom_bar(stat="identity") +
facet_wrap(~panel, ncol=4, nrow=2) +
scale_x_discrete(breaks=as.character(seq(1920, 2020, 20))) +
scale_fill_manual(values=c("Crew"="orange", "Passengers"="forestgreen")) +
labs(title="Crew and Passenger Fatality Rates by Occupancy and Decade",
x="Decade",
y="Fatality Rate (%)") +
theme_light() +
theme(plot.title=element_text(hjust=0.5),
axis.text.x=element_text(angle=45, hjust=1, size=9),
strip.text=element_text(size=10),
legend.position="none")
plt5
This visualization examines how the number of crashes from different causes has changed over time. Since the second world war, Human factors have been the most known cause, followed by technical failures. A notable observation from this graph can be seen in the spike observed from 1939 until 1945, which lines up with WW2. This is especially interesting when looking at crashes attributed to terrorism, hijacking, or sabotage, which increased massively during this period. 10 years prior, it had the least attributed casualties. Another interesting observation is how the number of recorded crashes has generally decreased in more recent decades, despite an exponential increase in aviation activity. Crashes attributed to unknown causes were not represented in this visualization since they could not be associated with a specific cause.
cause_df = df %>%
filter(crash_cause != "Unknown")
cause_df$crash_cause[cause_df$crash_cause == "Terrorism act, Hijacking, Sabotage"] = "Terror/Hij/Sab"
cause_counts = cause_df %>%
group_by(year, crash_cause) %>%
summarise(n=n(), .groups="keep") %>%
data.frame()
knitr::kable(count(cause_df, crash_cause),
col.names=c("Crash Cause", "Number of Accidents"),
caption="Total Recorded Accidents by Classified Cause")
| Crash Cause | Number of Accidents |
|---|---|
| Human factor | 9582 |
| Other causes | 603 |
| Technical failure | 5975 |
| Terror/Hij/Sab | 1131 |
| Weather | 1444 |
plt2 = ggplot(cause_counts, aes(x=year, y=n, color=crash_cause)) +
geom_line(linewidth=1.3) +
geom_point(data=subset(cause_counts, year %% 10 == 0),
size=2.5, shape=21, fill="white") +
scale_x_continuous(breaks=seq(1920, 2030, 10)) +
labs(title="Plane Crashes by Cause and Year",
x="Year",
y="Number of Crashes",
color="Crash Cause") +
scale_color_brewer(palette="Set2") +
theme_minimal()
plt2
In my last visualization I wanted to explore how the phase of flight that the crash occurred impacted whether the accident resulted in a fatality or not. I found that crashes occurring during ground operations had the lowest percentage involving fatalities(16.5%). This was almost three times higher for Landing/Approach (47.5%) , while Crashes occurring while the Plane was in the flight phase involved at least one fatality 70.8% of the time. Given the higher speed/altitude and distance from assistance this makes sense, but the disparity was surprising. It is worth noting this is also the longest phase of the flight, which likely plays a large role in explaining those numbers.
phase_df = df
phase_df$phase_group = ifelse(is.na(phase_df$flight_phase), "Unknown",
ifelse(phase_df$flight_phase %in% c("Takeoff (climb)", "Take off", "Initial climb"), "Takeoff/Climb",
ifelse(phase_df$flight_phase %in% c("Landing (descent or approach)", "Landing", "Approach"), "Landing/Approach",
ifelse(phase_df$flight_phase %in% c("Flight", "En route", "Manoeuvring",
"Manoeuvring (airshow, firefighting, ag.ops.)"), "Flight",
ifelse(phase_df$flight_phase %in% c("Taxiing", "Taxi", "Parking", "Standing", "Pushback / towing"), "Ground",
"Unknown")))))
phase_df = phase_df %>%
filter(!is.na(total_fatalities))
phase_df$fatal_status = ifelse(phase_df$total_fatalities > 0, "Fatal", "Nonfatal")
phase_counts = phase_df %>%
group_by(phase_group, fatal_status) %>%
summarise(n=length(fatal_status), .groups="keep") %>%
data.frame()
phase_counts$fatal_status = factor(phase_counts$fatal_status,
levels=c("Fatal", "Nonfatal"))
plt6 = plot_ly(textposition="inside", labels=~fatal_status, values=~n,
marker=list(colors=c("firebrick", "steelblue"))) %>%
add_pie(data=phase_counts[phase_counts$phase_group == "Takeoff/Climb",],
name="Takeoff/Climb", title="Takeoff/Climb", domain=list(row=0, column=0)) %>%
add_pie(data=phase_counts[phase_counts$phase_group == "Flight",],
name="Flight", title="Flight", domain=list(row=0, column=1)) %>%
add_pie(data=phase_counts[phase_counts$phase_group == "Landing/Approach",],
name="Landing/Approach", title="Landing/Approach", domain=list(row=0, column=2)) %>%
add_pie(data=phase_counts[phase_counts$phase_group == "Ground",],
name="Ground", title="Ground", domain=list(row=1, column=0)) %>%
add_pie(data=phase_counts[phase_counts$phase_group == "Unknown",],
name="Unknown", title="Unknown", domain=list(row=1, column=1)) %>%
layout(title="Fatal vs Nonfatal Accidents by Flight Phase", showlegend=TRUE,
grid=list(rows=2, columns=3))
plt6
In conclusion, this dataset and its visualizations showed many patterns relating to aviation crashes throughout history. The crash and fatality numbers were heavily influenced by the records during WW2. Human factors were consistently the leading known cause of plane crashes, while accidents occurring during the flight phase had the highest percentage involving fatalities. Both recorded crashes and fatalities have generally decreased as time has moved on, despite the significant increase in the number of flights and passengers. This gives an interesting look into how the characteristics of recorded aviation crashes have changed reflecting improvements in aviation technology, safety and global stability.
I found this open data on Huggingface Citation: @misc{planecrashdata2026, author = {Mauro Pellonara}, title = {PlaneCrashData: Global Aviation Safety and Accident Database (1918-Present)}, year = {2026}, publisher = {Hugging Face}, howpublished = {} }