Introduction & Datset
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"))
Exploratory Steps
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)
Visualizations
Plane Crash Locations
My first visualization is a interactive Leaflet map which plots the
crash locations via lattitude 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
tecnical reasons. Ultimately less populated and developed areas will
have less recorded traffic which heavily contributes to the difference
in crash numbers between contidents and reigions.
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")
Recorded Plane Crashes by Cause
| 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
Crashes by Cause & Year
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")
Total Recorded Accidents by Classified Cause
| 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

Total Fatalities by Year
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")
Top 15 Years by Recorded Aviation 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

Manufacturers by Crash Cause
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")
Top 10 Aircraft Manufacturers by Recorded 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

Fatality Rates by Occupancy & Decade
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

Fatal/Nonfatal Accidents by Phase
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