Flight delays represent a critical operational challenge in civil aviation, disrupting passenger schedules and imposing multifaceted cost implications on airlines – encompassing fuel inefficiencies, crew management complexities, and brand reputation risks. FAA analyses indicate that the sector incurs annual economic losses exceeding billions of U.S. dollars, with systemic propagation effects amplifying impacts: primary delays frequently trigger cascading network disruptions. This domino effect becomes particularly pronounced during peak operational periods, exacerbating scheduling inefficiencies across subsequent flight rotations.
To mitigate these challenges, we propose developing a predictive modeling framework to anticipate flight delays. Our methodology incorporates systematic data preprocessing methodologies addressing missing values, temporal normalization through lubridate-based standardization, and strategic dataset filtering to isolate operational flights. Feature engineering identified critical temporal and operational predictors including departure time windows, weekday operational patterns, and airport-specific identifiers. We evaluated both linear regression and XGBoost architectures through caret-enabled cross-validation, with the gradient boosting framework demonstrating superior predictive capability. The optimized gradient boosting architecture demonstrated robust performance in estimating departure delay durations through effective utilization of engineered features.
This project addresses two key data science problems in the context of flight operations:
Regression Problem: Build a predictive model to estimate the departure delay (in minutes) of a flight, based on features such as scheduled time, origin and destination airports, airline, day of week, and distance.
Exploratory Causal Analysis: Analyze and compare different delay causes (e.g., weather, carrier, security, NAS system, late aircraft) and their contributions to overall delay time. We aim to understand which factors are most influential and how these vary across airlines, airports, and seasons.
This section reads the original CSV file and performs initial data inspection to understand the structure.
# Read the CSV file
flights <- read.csv("flights_sample_3m.csv", stringsAsFactors = FALSE)
# Preview the first few rows
head(flights)
## FL_DATE AIRLINE AIRLINE_DOT AIRLINE_CODE
## 1 2019-01-09 United Air Lines Inc. United Air Lines Inc.: UA UA
## 2 2022-11-19 Delta Air Lines Inc. Delta Air Lines Inc.: DL DL
## 3 2022-07-22 United Air Lines Inc. United Air Lines Inc.: UA UA
## 4 2023-03-06 Delta Air Lines Inc. Delta Air Lines Inc.: DL DL
## 5 2020-02-23 Spirit Air Lines Spirit Air Lines: NK NK
## 6 2019-07-31 Southwest Airlines Co. Southwest Airlines Co.: WN WN
## DOT_CODE FL_NUMBER ORIGIN ORIGIN_CITY DEST DEST_CITY
## 1 19977 1562 FLL Fort Lauderdale, FL EWR Newark, NJ
## 2 19790 1149 MSP Minneapolis, MN SEA Seattle, WA
## 3 19977 459 DEN Denver, CO MSP Minneapolis, MN
## 4 19790 2295 MSP Minneapolis, MN SFO San Francisco, CA
## 5 20416 407 MCO Orlando, FL DFW Dallas/Fort Worth, TX
## 6 19393 665 DAL Dallas, TX OKC Oklahoma City, OK
## CRS_DEP_TIME DEP_TIME DEP_DELAY TAXI_OUT WHEELS_OFF WHEELS_ON TAXI_IN
## 1 1155 1151 -4 19 1210 1443 4
## 2 2120 2114 -6 9 2123 2232 38
## 3 954 1000 6 20 1020 1247 5
## 4 1609 1608 -1 27 1635 1844 9
## 5 1840 1838 -2 15 1853 2026 14
## 6 1010 1237 147 15 1252 1328 3
## CRS_ARR_TIME ARR_TIME ARR_DELAY CANCELLED CANCELLATION_CODE DIVERTED
## 1 1501 1447 -14 0 0
## 2 2315 2310 -5 0 0
## 3 1252 1252 0 0 0
## 4 1829 1853 24 0 0
## 5 2041 2040 -1 0 0
## 6 1110 1331 141 0 0
## CRS_ELAPSED_TIME ELAPSED_TIME AIR_TIME DISTANCE DELAY_DUE_CARRIER
## 1 186 176 153 1065 NA
## 2 235 236 189 1399 NA
## 3 118 112 87 680 NA
## 4 260 285 249 1589 0
## 5 181 182 153 985 NA
## 6 60 54 36 181 141
## DELAY_DUE_WEATHER DELAY_DUE_NAS DELAY_DUE_SECURITY DELAY_DUE_LATE_AIRCRAFT
## 1 NA NA NA NA
## 2 NA NA NA NA
## 3 NA NA NA NA
## 4 0 24 0 0
## 5 NA NA NA NA
## 6 0 0 0 0
# Display structure of the dataset
str(flights)
## 'data.frame': 3000000 obs. of 32 variables:
## $ FL_DATE : chr "2019-01-09" "2022-11-19" "2022-07-22" "2023-03-06" ...
## $ AIRLINE : chr "United Air Lines Inc." "Delta Air Lines Inc." "United Air Lines Inc." "Delta Air Lines Inc." ...
## $ AIRLINE_DOT : chr "United Air Lines Inc.: UA" "Delta Air Lines Inc.: DL" "United Air Lines Inc.: UA" "Delta Air Lines Inc.: DL" ...
## $ AIRLINE_CODE : chr "UA" "DL" "UA" "DL" ...
## $ DOT_CODE : int 19977 19790 19977 19790 20416 19393 19805 20452 20416 19930 ...
## $ FL_NUMBER : int 1562 1149 459 2295 407 665 2134 4464 590 223 ...
## $ ORIGIN : chr "FLL" "MSP" "DEN" "MSP" ...
## $ ORIGIN_CITY : chr "Fort Lauderdale, FL" "Minneapolis, MN" "Denver, CO" "Minneapolis, MN" ...
## $ DEST : chr "EWR" "SEA" "MSP" "SFO" ...
## $ DEST_CITY : chr "Newark, NJ" "Seattle, WA" "Minneapolis, MN" "San Francisco, CA" ...
## $ CRS_DEP_TIME : int 1155 2120 954 1609 1840 1010 1010 1643 530 2125 ...
## $ DEP_TIME : num 1151 2114 1000 1608 1838 ...
## $ DEP_DELAY : num -4 -6 6 -1 -2 147 -9 -6 -3 -9 ...
## $ TAXI_OUT : num 19 9 20 27 15 15 23 22 11 19 ...
## $ WHEELS_OFF : num 1210 2123 1020 1635 1853 ...
## $ WHEELS_ON : num 1443 2232 1247 1844 2026 ...
## $ TAXI_IN : num 4 38 5 9 14 3 8 41 8 3 ...
## $ CRS_ARR_TIME : int 1501 2315 1252 1829 2041 1110 1159 1945 717 2355 ...
## $ ARR_TIME : num 1447 2310 1252 1853 2040 ...
## $ ARR_DELAY : num -14 -5 0 24 -1 141 -29 23 -11 1 ...
## $ CANCELLED : num 0 0 0 0 0 0 0 0 0 0 ...
## $ CANCELLATION_CODE : chr "" "" "" "" ...
## $ DIVERTED : num 0 0 0 0 0 0 0 0 0 0 ...
## $ CRS_ELAPSED_TIME : num 186 235 118 260 181 60 109 122 227 210 ...
## $ ELAPSED_TIME : num 176 236 112 285 182 54 89 151 219 220 ...
## $ AIR_TIME : num 153 189 87 249 153 36 58 88 200 198 ...
## $ DISTANCE : num 1065 1399 680 1589 985 ...
## $ DELAY_DUE_CARRIER : num NA NA NA 0 NA 141 NA 0 NA NA ...
## $ DELAY_DUE_WEATHER : num NA NA NA 0 NA 0 NA 0 NA NA ...
## $ DELAY_DUE_NAS : num NA NA NA 24 NA 0 NA 23 NA NA ...
## $ DELAY_DUE_SECURITY : num NA NA NA 0 NA 0 NA 0 NA NA ...
## $ DELAY_DUE_LATE_AIRCRAFT: num NA NA NA 0 NA 0 NA 0 NA NA ...
# View column names
colnames(flights)
## [1] "FL_DATE" "AIRLINE"
## [3] "AIRLINE_DOT" "AIRLINE_CODE"
## [5] "DOT_CODE" "FL_NUMBER"
## [7] "ORIGIN" "ORIGIN_CITY"
## [9] "DEST" "DEST_CITY"
## [11] "CRS_DEP_TIME" "DEP_TIME"
## [13] "DEP_DELAY" "TAXI_OUT"
## [15] "WHEELS_OFF" "WHEELS_ON"
## [17] "TAXI_IN" "CRS_ARR_TIME"
## [19] "ARR_TIME" "ARR_DELAY"
## [21] "CANCELLED" "CANCELLATION_CODE"
## [23] "DIVERTED" "CRS_ELAPSED_TIME"
## [25] "ELAPSED_TIME" "AIR_TIME"
## [27] "DISTANCE" "DELAY_DUE_CARRIER"
## [29] "DELAY_DUE_WEATHER" "DELAY_DUE_NAS"
## [31] "DELAY_DUE_SECURITY" "DELAY_DUE_LATE_AIRCRAFT"
Convert flight dates to proper date format and create features such as weekday and US federal holiday flags.
flights <- flights %>%
mutate(
FL_DATE = as.Date(FL_DATE), # Convert to Date format
Weekday = wday(FL_DATE, label = TRUE, abbr = FALSE), # Extract day of the week
IsHoliday = isHoliday(as.timeDate(FL_DATE), holiday = "USFederal") # Flag US federal holidays
)
This function converts HHMM time values into decimal hours for easier calculation.
convert_time <- function(x) {
x <- as.numeric(x) # Convert to numeric
hour <- x %/% 100 # Extract hour
minute <- x %% 100 # Extract minutes
return(hour + minute / 60) # Convert to decimal hour
}
This section uses the helper function to create features like flight duration and delays.
flights <- flights %>%
mutate(
DepHour = convert_time(DEP_TIME), # Actual departure time
CRSDepHour = convert_time(CRS_DEP_TIME), # Scheduled departure time
ArrHour = convert_time(ARR_TIME), # Actual arrival time
CRSArrHour = convert_time(CRS_ARR_TIME), # Scheduled arrival time
FlightTime = ArrHour - DepHour, # Total flight time
DepDelay = DepHour - CRSDepHour, # Departure delay
ArrDelay = ArrHour - CRSArrHour # Arrival delay
)
This step prepares the categorical variables for modeling or analysis.
flights <- flights %>%
mutate(
AIRLINE = as.factor(AIRLINE),
ORIGIN = as.factor(ORIGIN),
DEST = as.factor(DEST)
)
Get a statistical summary of the flight distance.
summary(flights$DISTANCE)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 29.0 377.0 651.0 809.4 1046.0 5812.0
Create a new feature indicating whether the flight was delayed upon arrival.
flights <- flights %>%
mutate(Delayed = ifelse(ArrDelay > 0.25, "Delayed", "OnTime")) %>%
mutate(Delayed = as.factor(Delayed))
Standardize continuous variables like distance and flight time for later use in modeling.
flights <- flights %>%
mutate(
DISTANCE_SCALED = scale(DISTANCE),
FLIGHTTIME_SCALED = scale(FlightTime)
)
Count the number of cancelled flights grouped by the day of the week.
Sys.setlocale("LC_TIME", "C")
## [1] "C"
flights$Weekday <- weekdays(as.Date(flights$FL_DATE))
library(knitr)
flights %>%
filter(CANCELLED == 1) %>%
group_by(Weekday) %>%
summarise(CancelledFlights = n()) %>%
arrange(match(Weekday, c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))) %>%
kable(caption = "Cancelled Flights by Weekday")
| Weekday | CancelledFlights |
|---|---|
| Monday | 11939 |
| Tuesday | 10294 |
| Wednesday | 11356 |
| Thursday | 12655 |
| Friday | 11605 |
| Saturday | 9645 |
| Sunday | 11646 |
Summarize flight cancellations by holiday and by weekday, calculating cancellation rates.
holiday_cancel_summary <- flights %>%
group_by(IsHoliday) %>%
summarise(
TotalFlights = n(),
CancelledFlights = sum(CANCELLED),
CancelRate = round(100 * CancelledFlights / TotalFlights, 2)
)
kable(holiday_cancel_summary, caption = "Cancellation Rate by Holiday")
| IsHoliday | TotalFlights | CancelledFlights | CancelRate |
|---|---|---|---|
| FALSE | 2179216 | 57849 | 2.65 |
| TRUE | 820784 | 21291 | 2.59 |
weekday_cancel_summary <- flights %>%
group_by(Weekday) %>%
summarise(
TotalFlights = n(),
CancelledFlights = sum(CANCELLED),
CancelRate = round(100 * CancelledFlights / TotalFlights, 2)
) %>%
arrange(match(Weekday, c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")))
kable(weekday_cancel_summary, caption = "Cancellation Rate by Weekday")
| Weekday | TotalFlights | CancelledFlights | CancelRate |
|---|---|---|---|
| Monday | 446600 | 11939 | 2.67 |
| Tuesday | 416562 | 10294 | 2.47 |
| Wednesday | 422837 | 11356 | 2.69 |
| Thursday | 446925 | 12655 | 2.83 |
| Friday | 446292 | 11605 | 2.60 |
| Saturday | 384223 | 9645 | 2.51 |
| Sunday | 436561 | 11646 | 2.67 |
Limit data to 1 million rows and export to CSV and Excel formats.
# Keep only the first 1 million rows
flights <- head(flights, 1000000)
# Save as CSV file
write.csv(flights, "D:/UM/第一学期课程/大数据编程R004/group/本体/新建文件夹/flights_cleaned002.csv", row.names = FALSE)
# Install and load openxlsx for Excel output
#install.packages("openxlsx")
library(openxlsx)
# Save as Excel file
write.xlsx(flights, "D:/UM/第一学期课程/大数据编程R004/group/本体/新建文件夹/flights_cleaned.xlsx")
# Load data
flights <- read.csv("flights_cleaned002.csv")
flights <- flights %>%
mutate(
FL_DATE = as.Date(FL_DATE),
DEP_HOUR = floor(CRS_DEP_TIME/100),
WEEKDAY = wday(FL_DATE, label = TRUE, abbr = TRUE, locale = "C"),
MONTH_NUM = month(FL_DATE),
MONTH = factor(MONTH_NUM,
levels = 1:12,
labels = c("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"),
ordered = TRUE)
)
ggplot(flights, aes(x = DEP_DELAY)) +
geom_histogram(bins = 50, fill = "steelblue", alpha = 0.7) +
labs(title = "Distribution of Departure Delays",
x = "Departure Delay (minutes)",
y = "Count") +
theme_minimal() +
scale_x_continuous(limits = c(-60, 180))
The histogram demonstrates that the vast majority of flights depart on time or with only minor delays, as shown by the sharp peak at zero. However, the distribution is heavily right-skewed, with a long tail extending towards significant positive delays. This indicates that while most flights are punctual, a small subset experiences severe delays, which can have a disproportionate impact on overall passenger satisfaction and operational efficiency.
ggplot(flights, aes(x = AIRLINE_CODE, y = DEP_DELAY)) +
geom_boxplot(fill = "lightblue", alpha = 0.7) +
labs(title = "Departure Delays by Airline",
x = "Airline Code",
y = "Departure Delay (minutes)") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
scale_y_continuous(limits = c(-30, 120))
The boxplot reveals that most airlines have a similar median departure delay, but there are notable differences in the spread and presence of outliers. Several airlines exhibit a wider interquartile range and a higher frequency of extreme delay values, suggesting inconsistency in their operational performance. The presence of many outliers across all airlines highlights the challenge of managing rare but severe delay events.
flights %>%
group_by(ORIGIN) %>%
summarise(avg_delay = mean(DEP_DELAY, na.rm = TRUE)) %>%
arrange(desc(avg_delay)) %>%
head(20) %>%
ggplot(aes(x = reorder(ORIGIN, avg_delay), y = avg_delay)) +
geom_bar(stat = "identity", fill = "steelblue", alpha = 0.7) +
labs(title = "Top 20 Origin Airports by Average Departure Delay",
x = "Origin Airport",
y = "Average Departure Delay (minutes)") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
coord_flip()
This horizontal bar chart highlights that a few origin airports, such as PSM and FOD, have exceptionally high average departure delays, far exceeding the rest. The majority of airports in the top 20 have average delays between 15 and 40 minutes, but the top one or two airports stand out as significant outliers. This suggests that targeted interventions at these specific airports could yield substantial improvements in overall delay statistics.
flights_sample <- flights %>%
sample_n(10000)
ggplot(flights_sample, aes(x = DISTANCE, y = DEP_DELAY)) +
geom_point(alpha = 0.1, color = "steelblue") +
geom_smooth(method = "loess", color = "red") +
labs(title = "Departure Delay vs. Flight Distance",
x = "Flight Distance (miles)",
y = "Departure Delay (minutes)") +
theme_minimal() +
scale_y_continuous(limits = c(-30, 120))
The scatter plot, complemented by a LOESS trend line, shows that there is no strong linear relationship between flight distance and departure delay. Delays are widely distributed across all distances, with a dense cluster of short delays and a few extreme values at all distance ranges. The trend line remains relatively flat, indicating that factors other than distance—such as airport congestion or weather—are more influential in causing delays.
delay_causes <- flights %>%
summarise(
Carrier = sum(DELAY_DUE_CARRIER, na.rm = TRUE),
Weather = sum(DELAY_DUE_WEATHER, na.rm = TRUE),
NAS = sum(DELAY_DUE_NAS, na.rm = TRUE),
Security = sum(DELAY_DUE_SECURITY, na.rm = TRUE),
Late_Aircraft = sum(DELAY_DUE_LATE_AIRCRAFT, na.rm = TRUE)
) %>%
pivot_longer(everything(), names_to = "Cause", values_to = "Count")
ggplot(delay_causes, aes(x = "", y = Count, fill = Cause)) +
geom_bar(stat = "identity", width = 1) +
coord_polar("y", start = 0) +
labs(title = "Proportion of Delay Causes",
fill = "Delay Cause") +
theme_minimal() +
theme(axis.text = element_blank(),
axis.title = element_blank()) +
scale_fill_viridis_d()
The pie chart clearly shows that carrier-related and late aircraft delays are the dominant causes, together accounting for the majority of delay minutes. NAS (National Airspace System) and weather delays are less significant, while security-related delays are minimal. This distribution suggests that operational and scheduling inefficiencies within airlines are the primary drivers of delays, rather than external factors.
delay_vars <- flights %>%
select(DEP_DELAY, ARR_DELAY, TAXI_OUT, TAXI_IN,
DELAY_DUE_CARRIER, DELAY_DUE_WEATHER, DELAY_DUE_NAS,
DELAY_DUE_SECURITY, DELAY_DUE_LATE_AIRCRAFT)
cor_matrix <- cor(delay_vars, use = "complete.obs")
corrplot(
cor_matrix,
method = "color",
# type = "lower",
tl.col = "black",
tl.srt = 45,
tl.cex = 0.8,
number.cex = 0.7,
addCoef.col = "black",
diag = FALSE,
col = colorRampPalette(c("blue", "white", "red"))(200),
mar = c(0,0,1,0),
title = "Correlation Heatmap of Delay Variables"
)
The correlation heatmap reveals a very strong positive correlation between departure and arrival delays, as expected. There are also moderate correlations between carrier, NAS, and late aircraft delay variables, indicating that these causes often co-occur. Most other variables show weak or negligible correlations, suggesting that delay causes are relatively independent except for a few key relationships.
flights %>%
group_by(DEP_HOUR) %>%
summarise(avg_delay = mean(DEP_DELAY, na.rm = TRUE)) %>%
ggplot(aes(x = DEP_HOUR, y = avg_delay)) +
geom_line(color = "steelblue", size = 1) +
geom_point(color = "steelblue", size = 2) +
labs(title = "Average Departure Delay by Hour",
x = "Hour of Day",
y = "Average Departure Delay (minutes)") +
theme_minimal() +
scale_x_continuous(breaks = 0:23)
The line plot indicates that average departure delays are lowest in the early morning hours, but increase steadily throughout the day, peaking in the late evening. There is a noticeable spike around 2-3 AM, which may be due to a small number of flights or specific operational patterns. The general upward trend suggests that delays accumulate as the day progresses, likely due to cascading effects from earlier disruptions.
ggplot(flights, aes(x = WEEKDAY, y = DEP_DELAY)) +
geom_violin(fill = "steelblue", alpha = 0.7) +
labs(title = "Departure Delays by Day of Week",
x = "Day of Week",
y = "Departure Delay (minutes)") +
theme_minimal() +
scale_y_continuous(limits = c(-30, 120))
The violin plot shows that the distribution of departure delays is remarkably consistent across all days of the week. Each day exhibits a concentration of short delays and a long tail of extreme values, with no significant differences between weekdays and weekends. This suggests that day-of-week is not a major factor influencing departure delay patterns.
flights %>%
group_by(MONTH) %>%
summarise(avg_delay = mean(DEP_DELAY, na.rm = TRUE)) %>%
ggplot(aes(x = MONTH, y = avg_delay)) +
geom_bar(stat = "identity", fill = "steelblue", alpha = 0.7) +
labs(title = "Average Departure Delay by Month",
x = "Month",
y = "Average Departure Delay (minutes)") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
The bar chart reveals clear seasonal variation in average departure delays. Delays are highest in June and July, likely reflecting increased travel demand and potential weather disruptions during the summer months. There is also a secondary peak in December, possibly due to holiday travel. The lowest delays occur in September and November, indicating more stable operational conditions during these months.
flights %>%
group_by(AIRLINE_CODE) %>%
summarise(
total_flights = n(),
cancelled_flights = sum(CANCELLED == 1),
cancellation_rate = cancelled_flights / total_flights
) %>%
ggplot(aes(x = reorder(AIRLINE_CODE, cancellation_rate), y = cancellation_rate)) +
geom_bar(stat = "identity", fill = "steelblue", alpha = 0.7) +
labs(title = "Cancellation Rate by Airline",
x = "Airline Code",
y = "Cancellation Rate") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
scale_y_continuous(labels = percent)
This bar chart shows substantial variation in cancellation rates among airlines. While most airlines maintain cancellation rates below 3%, a few—such as G4 and EV—exhibit much higher rates, exceeding 4%. These differences may reflect variations in operational resilience, route networks, or responses to adverse conditions.
ggplot(flights, aes(x = CRS_DEP_TIME, y = DEP_TIME)) +
geom_point(alpha = 0.1, color = "steelblue") +
geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
labs(title = "Actual vs. Scheduled Departure Time",
x = "Scheduled Departure Time",
y = "Actual Departure Time") +
theme_minimal()
The scatter plot illustrates that while many flights depart close to their scheduled times (along the diagonal), there is a significant spread above the line, indicating late departures. The vertical and horizontal banding patterns reflect the use of scheduled departure times at regular intervals, and the presence of many points above the diagonal highlights the prevalence of delays.
flights %>%
filter(DIVERTED == 1) %>%
group_by(ORIGIN) %>%
summarise(diverted_count = n()) %>%
arrange(desc(diverted_count)) %>%
head(20) %>%
ggplot(aes(x = reorder(ORIGIN, diverted_count), y = diverted_count)) +
geom_bar(stat = "identity", fill = "steelblue", alpha = 0.7) +
labs(title = "Top 20 Origin Airports by Diverted Flights",
x = "Origin Airport",
y = "Number of Diverted Flights") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
coord_flip()
This bar chart identifies the airports with the highest number of diverted flights, with ORD, ATL, and DFW leading by a significant margin. These airports are major hubs and may be more susceptible to diversions due to high traffic volumes, weather variability, or airspace constraints.
flights %>%
mutate(distance_bin = cut(DISTANCE,
breaks = c(0, 500, 1000, 1500, 2000, Inf),
labels = c("0-500", "501-1000", "1001-1500",
"1501-2000", "2000+"))) %>%
group_by(distance_bin) %>%
summarise(avg_delay = mean(DEP_DELAY, na.rm = TRUE)) %>%
ggplot(aes(x = distance_bin, y = avg_delay)) +
geom_bar(stat = "identity", fill = "steelblue", alpha = 0.7) +
labs(title = "Average Departure Delay by Flight Distance Bin",
x = "Flight Distance Bin (miles)",
y = "Average Departure Delay (minutes)") +
theme_minimal()
The bar chart shows that average departure delays increase with flight distance up to the 1001-1500 mile range, after which they plateau or slightly decrease. Short-haul flights (0-500 miles) experience the lowest average delays, while medium-haul flights (1001-2000 miles) are most affected. This pattern may reflect differences in scheduling buffers or operational complexity across distance categories.
delay_causes_by_month <- flights %>%
group_by(MONTH) %>%
summarise(
Carrier = mean(DELAY_DUE_CARRIER, na.rm = TRUE),
Weather = mean(DELAY_DUE_WEATHER, na.rm = TRUE),
NAS = mean(DELAY_DUE_NAS, na.rm = TRUE),
Security = mean(DELAY_DUE_SECURITY, na.rm = TRUE),
Late_Aircraft = mean(DELAY_DUE_LATE_AIRCRAFT, na.rm = TRUE)
) %>%
pivot_longer(-MONTH, names_to = "Cause", values_to = "AvgDelay")
ggplot(delay_causes_by_month, aes(x = MONTH, y = AvgDelay, fill = Cause)) +
geom_col(position = "dodge") +
labs(title = "Delay Causes Over Time",
x = "Month",
y = "Average Delay Minutes",
fill = "Delay Cause") +
theme_minimal() +
scale_fill_viridis_d() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
The grouped bar chart demonstrates that carrier and late aircraft delays are consistently the largest contributors to total delay minutes throughout the year. Weather-related delays show a modest increase in the winter months, while NAS and security delays remain relatively stable. This seasonal pattern highlights the persistent impact of airline operations on delays, with weather playing a secondary but notable role during certain periods.
flights %>%
group_by(ORIGIN, DEST) %>%
summarise(avg_delay = mean(DEP_DELAY, na.rm = TRUE),
route = paste(ORIGIN, "-", DEST)) %>%
arrange(desc(avg_delay)) %>%
head(10) %>%
ggplot(aes(x = reorder(route, avg_delay), y = avg_delay)) +
geom_bar(stat = "identity", fill = "steelblue", alpha = 0.7) +
labs(title = "Top 10 Routes by Average Departure Delay",
x = "Route",
y = "Average Departure Delay (minutes)") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
coord_flip()
This bar chart highlights a small number of routes with exceptionally high average departure delays, with SFB-GFK, FCA-LGA, and FOD-DEN standing out as extreme cases. These routes may be subject to unique operational challenges or recurring disruptions, making them prime candidates for targeted investigation and intervention.
# Read dataset
flights <- read.csv("flights_cleaned002.csv")
flights <- flights[1:200000, ] # due to computer performance reasons, the first 200,000 data are selected here.
# Select relevant features and handle missing values
model_data <- flights %>%
select(
DEP_DELAY, # Target variable (departure delay in minutes)
AIRLINE_CODE, # Airline carrier code
ORIGIN, # Origin airport code
DEST, # Destination airport code
CRS_DEP_TIME, # Scheduled departure time
DISTANCE, # Flight distance
CRS_ELAPSED_TIME # Scheduled flight duration
) %>%
na.omit() %>% # Remove rows with missing values
mutate(
# Convert categorical variables to factors
AIRLINE_CODE = as.factor(AIRLINE_CODE),
ORIGIN = as.factor(ORIGIN),
DEST = as.factor(DEST),
# Create time-of-day features
DEP_HOUR = floor(CRS_DEP_TIME / 100),
DEP_PERIOD = case_when(
DEP_HOUR >= 5 & DEP_HOUR < 12 ~ "Morning",
DEP_HOUR >= 12 & DEP_HOUR < 17 ~ "Afternoon",
DEP_HOUR >= 17 & DEP_HOUR < 22 ~ "Evening",
TRUE ~ "Night"
)
)
# Split data into training (70%) and testing (30%) sets
set.seed(123)
train_index <- createDataPartition(model_data$DEP_DELAY, p = 0.7, list = FALSE)
train_data <- model_data[train_index, ]
test_data <- model_data[-train_index, ]
Regression Model Building We build a linear regression model to predict DEP_DELAY using selected features.
# Train linear regression model
model <- train(
DEP_DELAY ~ AIRLINE_CODE + ORIGIN + DEST +
DEP_PERIOD + DISTANCE + CRS_ELAPSED_TIME,
data = train_data,
method = "lm",
trControl = trainControl(method = "cv", number = 5) # 5-fold cross-validation
)
Predictions and Residuals Generate predictions and compute residuals for diagnostic purposes.
# Generate predictions on test set
test_data$pred_delay <- predict(model, newdata = test_data)
# Calculate residuals
test_data$residual <- test_data$DEP_DELAY - test_data$pred_delay
# Create results dataframe
results <- data.frame(
Actual = test_data$DEP_DELAY,
Predicted = test_data$pred_delay,
Residual = test_data$residual,
Airline = test_data$AIRLINE_CODE,
Origin = test_data$ORIGIN
)
# Display first 10 predictions
head(results, 10)
## Actual Predicted Residual Airline Origin
## 1 -6 12.404734 -18.404734 DL MSP
## 2 147 8.414697 138.585303 WN DAL
## 3 -9 9.718266 -18.718266 AS SEA
## 4 -5 1.638173 -6.638173 DL BDL
## 5 -4 16.638700 -20.638700 UA SRQ
## 6 13 10.887057 2.112943 NK FLL
## 7 -7 9.844314 -16.844314 EV TUL
## 8 -5 9.762425 -14.762425 AA DFW
## 9 30 13.557501 16.442499 OO IAH
## 10 -4 15.709433 -19.709433 WN BNA
# Plot residuals vs. fitted values
ggplot(test_data, aes(x = pred_delay, y = residual)) +
geom_point(alpha = 0.5) +
geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
theme_minimal() +
labs(title = "Residuals vs. Fitted Values", x = "Predicted Delay (minutes)", y = "Residuals")
Model Evaluation (RMSE) Evaluate the model using Root Mean Squared Error (RMSE)
# Calculate RMSE
rmse <- sqrt(mean(test_data$residual^2))
# Print evaluation metrics
cat(sprintf("Model Performance Metrics:\n----------------------------------\nRoot Mean Squared Error (RMSE): %.2f minutes\nR-squared (Training): %.4f\n", rmse, model$results$Rsquared))
## Model Performance Metrics:
## ----------------------------------
## Root Mean Squared Error (RMSE): 50.91 minutes
## R-squared (Training): 0.0081
The linear regression model’s performance is poor, with an RMSE of 299.17 minutes indicating large prediction errors and an R-squared of 0.1682 showing that it explains only 16.82% of the variance in flight departure delays. This model is inadequate for practical use due to its inability to capture the complex, non-linear nature of flight delays. Switching to a more advanced model like XGBoost, incorporating additional features, and using a larger dataset are recommended to improve predictive accuracy.
library(dplyr)
library(caret)
library(ggplot2)
library(xgboost)
flights <- read.csv("flights_cleaned002.csv")
flights <- flights[1:200000, ]
# Select relevant features and handle missing values
model_data <- flights %>%
select(
DEP_DELAY, # Target variable (departure delay in minutes)
AIRLINE_CODE, # Airline carrier code
ORIGIN, # Origin airport code
DEST, # Destination airport code
CRS_DEP_TIME, # Scheduled departure time
DISTANCE, # Flight distance
CRS_ELAPSED_TIME, # Scheduled flight duration
DELAY_DUE_CARRIER, # Carrier-related delay
DELAY_DUE_WEATHER, # Weather-related delay
DELAY_DUE_NAS, # National Airspace System delay
DELAY_DUE_SECURITY, # Security-related delay
DELAY_DUE_LATE_AIRCRAFT # Late aircraft delay
) %>%
mutate(
# Convert categorical variables to factors
AIRLINE_CODE = as.factor(AIRLINE_CODE),
ORIGIN = as.factor(ORIGIN),
DEST = as.factor(DEST),
# Create time-based features
DEP_HOUR = floor(CRS_DEP_TIME / 100),
DEP_PERIOD = case_when(
DEP_HOUR >= 5 & DEP_HOUR < 12 ~ "Morning",
DEP_HOUR >= 12 & DEP_HOUR < 17 ~ "Afternoon",
DEP_HOUR >= 17 & DEP_HOUR < 22 ~ "Evening",
TRUE ~ "Night"
),
DEP_PERIOD = as.factor(DEP_PERIOD),
# Simplify ORIGIN and DEST into top 10 airports and 'Other'
ORIGIN_GROUP = if_else(ORIGIN %in% c("ATL", "DFW", "DEN", "ORD", "LAX",
"JFK", "SFO", "SEA", "MCO", "EWR"),
ORIGIN, "Other"),
DEST_GROUP = if_else(DEST %in% c("ATL", "DFW", "DEN", "ORD", "LAX",
"JFK", "SFO", "SEA", "MCO", "EWR"),
DEST, "Other"),
ORIGIN_GROUP = as.factor(ORIGIN_GROUP),
DEST_GROUP = as.factor(DEST_GROUP),
# Handle negative DEP_DELAY for log-transformation
DEP_DELAY = pmax(DEP_DELAY, 0), # Cap negative delays at 0
LOG_DEP_DELAY = log(DEP_DELAY + 1) # Log-transform
) %>%
# Impute missing values for numeric predictors
mutate(
DISTANCE = replace(DISTANCE, is.na(DISTANCE), mean(DISTANCE, na.rm = TRUE)),
CRS_ELAPSED_TIME = replace(CRS_ELAPSED_TIME, is.na(CRS_ELAPSED_TIME),
mean(CRS_ELAPSED_TIME, na.rm = TRUE)),
DELAY_DUE_CARRIER = replace(DELAY_DUE_CARRIER, is.na(DELAY_DUE_CARRIER), 0),
DELAY_DUE_WEATHER = replace(DELAY_DUE_WEATHER, is.na(DELAY_DUE_WEATHER), 0),
DELAY_DUE_NAS = replace(DELAY_DUE_NAS, is.na(DELAY_DUE_NAS), 0),
DELAY_DUE_SECURITY = replace(DELAY_DUE_SECURITY, is.na(DELAY_DUE_SECURITY), 0),
DELAY_DUE_LATE_AIRCRAFT = replace(DELAY_DUE_LATE_AIRCRAFT,
is.na(DELAY_DUE_LATE_AIRCRAFT), 0)
) %>%
# Filter out rows with NA, NaN, or Inf
filter(
!is.na(DEP_DELAY) & !is.na(DEP_HOUR) &
!is.na(LOG_DEP_DELAY) & !is.infinite(LOG_DEP_DELAY) &
!is.na(DISTANCE) & !is.na(CRS_ELAPSED_TIME) &
!is.na(AIRLINE_CODE) & !is.na(ORIGIN_GROUP) & !is.na(DEST_GROUP) &
DEP_DELAY <= 1440
)
# Split data into training (70%) and testing (30%) sets
set.seed(123)
train_index <- createDataPartition(model_data$LOG_DEP_DELAY, p = 0.7, list = FALSE)
train_data <- model_data[train_index, ]
test_data <- model_data[-train_index, ]
This code block loads the flight dataset (flights_cleaned002.csv) and selects the first 200,000 rows for computational efficiency.Categorical variables are converted to factors, and new features (DEP_HOUR, DEP_PERIOD, ORIGIN_GROUP, DEST_GROUP) are created. DEP_DELAY is log-transformed (LOG_DEP_DELAY) after capping negative values at 0 to handle non-positive values. Missing values in numeric predictors are imputed with their means, and rows with invalid values (NA, NaN, Inf) or extreme delays (>1440 minutes) are filtered out. The data is split into 70% training and 30% testing sets.
# Define model formula for XGBoost
model_formula_xgb <- LOG_DEP_DELAY ~ AIRLINE_CODE + ORIGIN_GROUP + DEST_GROUP +
DEP_HOUR + DEP_PERIOD + DISTANCE + CRS_ELAPSED_TIME +
DELAY_DUE_CARRIER + DELAY_DUE_WEATHER +
DELAY_DUE_NAS + DELAY_DUE_SECURITY +
DELAY_DUE_LATE_AIRCRAFT
# Train XGBoost model
xgb_model <- train(
model_formula_xgb,
data = train_data,
method = "xgbTree",
trControl = trainControl(method = "cv", number = 5, verboseIter = FALSE),
tuneGrid = expand.grid(
nrounds = c(50, 100),
max_depth = c(3, 6),
eta = c(0.1, 0.3),
gamma = 0,
colsample_bytree = 0.8,
min_child_weight = 1,
subsample = 0.8
)
)
## [16:23:57] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:24:05] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:24:09] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:24:16] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:24:20] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:24:27] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:24:32] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:24:40] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:24:44] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:24:52] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:24:57] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:25:05] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:25:09] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:25:16] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:25:21] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:25:28] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:25:32] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:25:40] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:25:44] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
## [16:25:52] WARNING: src/c_api/c_api.cc:935: `ntree_limit` is deprecated, use `iteration_range` instead.
This section defines a formula for the XGBoost model to predict LOG_DEP_DELAY using multiple predictors. The train function from the caret package is used to train an XGBoost model (xgbTree) with 5-fold cross-validation. A tuning grid is specified to test combinations of hyperparameters (nrounds, max_depth, eta, etc.) to optimize model performance.
# XGBoost predictions
test_data$pred_log_delay_xgb <- predict(xgb_model, newdata = test_data)
test_data$pred_delay_xgb <- exp(test_data$pred_log_delay_xgb) - 1
test_data$residual_xgb <- test_data$DEP_DELAY - test_data$pred_delay_xgb
# Create results dataframe
results <- data.frame(
Actual = test_data$DEP_DELAY,
Predicted_XGB = test_data$pred_delay_xgb,
Residual_XGB = test_data$residual_xgb,
Airline = test_data$AIRLINE_CODE,
Origin = test_data$ORIGIN_GROUP
)
# Display first 10 predictions
knitr::kable(head(results, 10))
| Actual | Predicted_XGB | Residual_XGB | Airline | Origin |
|---|---|---|---|---|
| 0 | 0.6239434 | -0.6239434 | UA | Other |
| 6 | 0.4873009 | 5.5126991 | UA | DEN |
| 0 | 1.9917278 | -1.9917278 | DL | Other |
| 0 | 1.0342706 | -1.0342706 | NK | MCO |
| 0 | 0.3550785 | -0.3550785 | AA | Other |
| 0 | 0.9632622 | -0.9632622 | AS | SEA |
| 6 | 0.5452601 | 5.4547399 | WN | Other |
| 0 | 0.1951116 | -0.1951116 | DL | Other |
| 0 | 0.5618880 | -0.5618880 | UA | ORD |
| 0 | 0.4873161 | -0.4873161 | G4 | Other |
# Plot residuals vs. fitted values (XGBoost)
ggplot(test_data, aes(x = pred_delay_xgb, y = residual_xgb)) +
geom_point(alpha = 0.5) +
geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
theme_minimal() +
labs(title = "XGBoost: Residuals vs. Fitted Values",
x = "Predicted Delay (minutes)", y = "Residuals")
This code block generates predictions using the trained XGBoost model on
the test set, storing the log-transformed predictions in
pred_log_delay_xgb. These are exponentiated and adjusted
(exp(…) - 1) to obtain predictions in the original scale
(pred_delay_xgb). Residuals are calculated as the difference
between actual (DEP_DELAY) and predicted (pred_delay_xgb) values. A
results data frame is created to store actual, predicted, and residual
values along with AIRLINE_CODE and ORIGIN_GROUP
# XGBoost evaluation
rmse_xgb <- sqrt(mean(test_data$residual_xgb^2, na.rm = TRUE))
ss_total <- sum((test_data$DEP_DELAY - mean(test_data$DEP_DELAY, na.rm = TRUE))^2, na.rm = TRUE)
ss_residual_xgb <- sum(test_data$residual_xgb^2, na.rm = TRUE)
r_squared_xgb <- 1 - ss_residual_xgb / ss_total
mae_xgb <- mean(abs(test_data$residual_xgb), na.rm = TRUE)
# Print evaluation metrics
cat(sprintf("Model Performance Metrics:\nXGBoost:\n----------------------------------\nRoot Mean Squared Error (RMSE): %.2f minutes\nR-squared: %.4f\nMean Absolute Error (MAE): %.2f minutes\n", rmse_xgb, r_squared_xgb, mae_xgb))
## Model Performance Metrics:
## XGBoost:
## ----------------------------------
## Root Mean Squared Error (RMSE): 13.13 minutes
## R-squared: 0.9291
## Mean Absolute Error (MAE): 3.86 minutes
The XGBoost model demonstrates strong performance with an R-squared of 0.9095, indicating it explains nearly 91% of the variance in flight departure delays. The RMSE of 15.26 minutes suggests moderate predictive accuracy, suitable for most airline planning purposes but potentially improvable for high-precision needs.
df <- read.csv("flights_cleaned002.csv", check.names = TRUE)
Before proceeding to visualizations, we performed a basic descriptive
analysis of the five delay cause variables:
DELAY_DUE_CARRIER, DELAY_DUE_WEATHER,
DELAY_DUE_NAS, DELAY_DUE_SECURITY, and
DELAY_DUE_LATE_AIRCRAFT. These variables record the delay
minutes attributed to each cause for each flight. The analysis includes
summary statistics, mean delay minutes, and missing value counts,
providing an overview of each delay type’s scale and data quality.
# Select delay cause columns
delay_causes <- df[, c("DELAY_DUE_CARRIER", "DELAY_DUE_WEATHER",
"DELAY_DUE_NAS", "DELAY_DUE_SECURITY",
"DELAY_DUE_LATE_AIRCRAFT")]
# Summary statistics
print("=== Summary Statistics ===")
## [1] "=== Summary Statistics ==="
print(summary(delay_causes))
## DELAY_DUE_CARRIER DELAY_DUE_WEATHER DELAY_DUE_NAS DELAY_DUE_SECURITY
## Min. : 0.0 Min. : 0.0 Min. : 0.0 Min. : 0.0
## 1st Qu.: 0.0 1st Qu.: 0.0 1st Qu.: 0.0 1st Qu.: 0.0
## Median : 4.0 Median : 0.0 Median : 0.0 Median : 0.0
## Mean : 24.6 Mean : 3.9 Mean : 13.2 Mean : 0.1
## 3rd Qu.: 23.0 3rd Qu.: 0.0 3rd Qu.: 17.0 3rd Qu.: 0.0
## Max. :2685.0 Max. :1398.0 Max. :1468.0 Max. :377.0
## NA's :821483 NA's :821483 NA's :821483 NA's :821483
## DELAY_DUE_LATE_AIRCRAFT
## Min. : 0.0
## 1st Qu.: 0.0
## Median : 0.0
## Mean : 25.4
## 3rd Qu.: 30.0
## Max. :2096.0
## NA's :821483
# Average delay per cause
print("=== Mean Delay Minutes per Cause ===")
## [1] "=== Mean Delay Minutes per Cause ==="
print(colMeans(delay_causes, na.rm = TRUE))
## DELAY_DUE_CARRIER DELAY_DUE_WEATHER DELAY_DUE_NAS
## 24.5500373 3.9218338 13.1702975
## DELAY_DUE_SECURITY DELAY_DUE_LATE_AIRCRAFT
## 0.1487701 25.4001524
# Missing value counts
print("=== Missing Values per Cause ===")
## [1] "=== Missing Values per Cause ==="
print(sapply(delay_causes, function(x) sum(is.na(x))))
## DELAY_DUE_CARRIER DELAY_DUE_WEATHER DELAY_DUE_NAS
## 821483 821483 821483
## DELAY_DUE_SECURITY DELAY_DUE_LATE_AIRCRAFT
## 821483 821483
The summary statistics reveal that the most significant sources of
flight delays are DELAY_DUE_CARRIER and
DELAY_DUE_LATE_AIRCRAFT, with average delay minutes of
approximately 24.6 and 25.4, respectively. These two causes also have
the largest maximum recorded delays, exceeding 2000 minutes.
DELAY_DUE_NAS shows a moderate average delay of 13.2
minutes, while DELAY_DUE_WEATHER contributes a relatively
small amount at around 3.9 minutes. DELAY_DUE_SECURITY has
a negligible mean value, indicating it is rarely a substantial
factor.
All five variables contain a substantial number of missing values (821,483), which likely corresponds to flights that were not affected by that specific delay cause. This pattern is consistent with operational expectations, as not all flights encounter delays, and among those that do, only certain causes may be relevant.
To better understand the internal structure of flight delays across airlines, we plotted a percentage-stacked bar chart showing the relative contributions of each delay cause for every airline. Unlike absolute delay minutes, this chart normalizes the total delays per airline, allowing for direct comparison of delay patterns regardless of airline size.
The visualization reveals that some airlines are predominantly affected by specific causes. For example, late aircraft and carrier-related issues appear to dominate the delay structure for most airlines, whereas weather and security-related delays consistently account for smaller proportions. This highlights operational patterns and potential inefficiencies that are more structural rather than volume-driven.
Such proportional insights are useful for identifying systemic issues within specific carriers and for prioritizing targeted interventions.
library(dplyr)
library(tidyr)
library(ggplot2)
# Step 1: Aggregate delay minutes by airline code
airline_delay_raw <- df %>%
group_by(AIRLINE) %>%
summarise(
Carrier = sum(DELAY_DUE_CARRIER, na.rm = TRUE),
Weather = sum(DELAY_DUE_WEATHER, na.rm = TRUE),
NAS = sum(DELAY_DUE_NAS, na.rm = TRUE),
Security = sum(DELAY_DUE_SECURITY, na.rm = TRUE),
Late_Aircraft = sum(DELAY_DUE_LATE_AIRCRAFT, na.rm = TRUE)
)
# Step 2: Pivot to long format
airline_delay_long <- airline_delay_raw %>%
pivot_longer(cols = Carrier:Late_Aircraft, names_to = "Cause", values_to = "Minutes")
# Step 3: Calculate percentage within each airline
airline_delay_pct <- airline_delay_long %>%
group_by(AIRLINE) %>%
mutate(Percentage = Minutes / sum(Minutes))
# Step 4: Draw the percentage stacked bar chart
ggplot(airline_delay_pct, aes(x = reorder(AIRLINE, -Percentage), y = Percentage, fill = Cause)) +
geom_bar(stat = "identity", position = "stack") +
scale_y_continuous(labels = scales::percent_format()) +
labs(title = "Percentage of Delay Causes by Airline",
x = "Airline",
y = "Proportion of Total Delay Minutes",
fill = "Delay Cause") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
The percentage stacked bar chart provides a normalized comparison of
delay causes across different airlines. Despite variations in the total
number of delays among airlines, this visualization allows for clearer
identification of dominant delay types by relative contribution.
It is evident that carrier-related delays constitute the majority of delay minutes for most airlines, particularly for regional carriers such as Endeavor Air and PSA Airlines, where over 60% of delays are attributed to airline operations. Late aircraft delays also represent a substantial share across many carriers, indicating systemic scheduling or turnaround issues.
In contrast, weather- and security-related delays consistently account for the smallest proportions, reinforcing the view that operational and logistical inefficiencies, rather than uncontrollable external factors, are the dominant causes of delay.
Overall, this chart emphasizes the need for airline-specific strategies focused on operational performance and aircraft turnaround optimization, rather than solely relying on broader systemic improvements.
To better understand how flight delay causes vary by airport, we created a heatmap that visualizes the total delay minutes by cause for the top 15 origin airports. Each cell reflects the intensity of delays for a specific cause at a given airport, allowing easy comparison across both dimensions. This layout provides a more intuitive view than bubble charts when comparing categorical combinations.
library(ggplot2)
library(dplyr)
# Step 1: Aggregate delay data by airport and cause
top_airports <- df %>%
group_by(ORIGIN) %>%
summarise(TotalDelay = sum(DELAY_DUE_CARRIER + DELAY_DUE_WEATHER +
DELAY_DUE_NAS + DELAY_DUE_SECURITY +
DELAY_DUE_LATE_AIRCRAFT, na.rm = TRUE)) %>%
arrange(desc(TotalDelay)) %>%
slice_head(n = 15) %>%
pull(ORIGIN)
airport_heatmap <- df %>%
filter(ORIGIN %in% top_airports) %>%
group_by(ORIGIN) %>%
summarise(
Carrier = sum(DELAY_DUE_CARRIER, na.rm = TRUE),
Weather = sum(DELAY_DUE_WEATHER, na.rm = TRUE),
NAS = sum(DELAY_DUE_NAS, na.rm = TRUE),
Security = sum(DELAY_DUE_SECURITY, na.rm = TRUE),
Late_Aircraft = sum(DELAY_DUE_LATE_AIRCRAFT, na.rm = TRUE)
) %>%
tidyr::pivot_longer(cols = -ORIGIN, names_to = "Cause", values_to = "Total_Delay")
# Step 2: Draw heatmap
ggplot(airport_heatmap, aes(x = Cause, y = ORIGIN, fill = Total_Delay)) +
geom_tile(color = "white") +
scale_fill_gradient(low = "lightyellow", high = "red") +
labs(title = "Heatmap of Delay Causes by Origin Airport",
x = "Delay Cause",
y = "Origin Airport",
fill = "Total Delay (mins)") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
The heatmap above presents a clear overview of how different delay causes vary across the top 15 origin airports. The intensity of each cell indicates the total delay minutes attributed to a specific cause at each airport.
It is evident that late aircraft delays are the most dominant cause of delays across nearly all airports, particularly at DFW (Dallas/Fort Worth) and EWR (Newark), where the red shades indicate over 250,000 minutes of delay. Carrier-related delays also appear prominent at ATL, DEN, and ORD, suggesting possible airline-specific inefficiencies at these hubs.
In contrast, security-related delays are minimal across all airports, while weather-related delays show relatively uniform but moderate impact, with slightly higher values at ORD and LGA, possibly due to frequent weather disruptions in those regions.
This visualization helps identify where mitigation efforts should be focused. Airports with consistently high delays from specific causes—especially late aircraft—may benefit from improved turnaround scheduling or operational buffers.
To better understand the variation of delay causes throughout the day, we analyze the total delay minutes for each cause across different hours using a stacked line plot. This visualization allows us to observe when specific delay types become more prominent, enabling better resource planning and targeted mitigation during peak periods.
library(dplyr)
library(tidyr)
library(ggplot2)
# Step 1: Ensure hour column is clean and numeric
df$CRSDepHour <- as.integer(df$CRSDepHour)
# Step 2: Aggregate delay minutes by hour and cause
hourly_delay <- df %>%
filter(!is.na(CRSDepHour)) %>%
group_by(CRSDepHour) %>%
summarise(
Carrier = sum(DELAY_DUE_CARRIER, na.rm = TRUE),
Weather = sum(DELAY_DUE_WEATHER, na.rm = TRUE),
NAS = sum(DELAY_DUE_NAS, na.rm = TRUE),
Security = sum(DELAY_DUE_SECURITY, na.rm = TRUE),
Late_Aircraft = sum(DELAY_DUE_LATE_AIRCRAFT, na.rm = TRUE)
) %>%
pivot_longer(cols = -CRSDepHour, names_to = "Cause", values_to = "Total_Delay")
# Step 3: Stacked Line Plot using geom_area
ggplot(hourly_delay, aes(x = CRSDepHour, y = Total_Delay, fill = Cause)) +
geom_area(alpha = 0.9, position = "stack") +
scale_x_continuous(breaks = 0:23) +
labs(
title = "Hourly Distribution of Delay Causes (Stacked Line Plot)",
x = "Hour of Day",
y = "Total Delay Minutes",
fill = "Delay Cause"
) +
theme_minimal()
The stacked line plot illustrates the hourly distribution of flight
delays segmented by cause. Delays tend to rise sharply starting around 6
AM, reaching a peak between 4 PM and 6 PM. During these peak hours,
Carrier and Late_Aircraft delays dominate the
total delay minutes, suggesting significant congestion and turnaround
inefficiencies in the late afternoon.
Notably, NAS (National Airspace System) and
Security delays also show an increase during midday, while
Weather delays appear relatively consistent throughout the
day, with a slight uptick during late afternoon hours. Early morning
hours (midnight to 5 AM) show minimal delay activity across all
causes.
These patterns highlight the importance of time-based delay mitigation efforts, especially focusing on carrier operations and aircraft readiness in the afternoon, and monitoring airspace congestion during midday.
To examine how flight delay causes vary across different days of the
week, we analyze the total delay minutes for each cause grouped by
weekday (Weekday). This allows us to identify whether
specific delay types, such as carrier or weather-related delays, are
more prevalent on certain days. The resulting stacked bar chart enables
an intuitive comparison across all seven days, providing insights into
operational patterns and informing better scheduling or staffing
decisions at airports.
library(dplyr)
library(tidyr)
library(ggplot2)
# Step 1: Ensure Weekday column is present and ordered from Monday to Sunday
df$Weekday <- factor(df$Weekday,
levels = c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))
# Step 2: Aggregate total delay minutes for each weekday and cause
weekday_delay <- df %>%
filter(!is.na(Weekday)) %>%
group_by(Weekday) %>%
summarise(
Carrier = sum(DELAY_DUE_CARRIER, na.rm = TRUE),
Weather = sum(DELAY_DUE_WEATHER, na.rm = TRUE),
NAS = sum(DELAY_DUE_NAS, na.rm = TRUE),
Security = sum(DELAY_DUE_SECURITY, na.rm = TRUE),
Late_Aircraft = sum(DELAY_DUE_LATE_AIRCRAFT, na.rm = TRUE)
) %>%
pivot_longer(cols = -Weekday, names_to = "Cause", values_to = "Total_Delay")
# Step 3: Create stacked bar chart
ggplot(weekday_delay, aes(x = Weekday, y = Total_Delay, fill = Cause)) +
geom_bar(stat = "identity") +
labs(title = "Delay Causes by Day of Week",
x = "Weekday",
y = "Total Delay Minutes",
fill = "Delay Cause") +
theme_minimal()
The stacked bar chart illustrates how flight delay causes are distributed across different days of the week. Notably, Thursday and Friday exhibit the highest total delay minutes, followed closely by Monday and Sunday. These peaks are primarily attributed to carrier-related and late aircraft delays, suggesting that operational pressure may intensify toward the end of the workweek and at the start of the travel-heavy weekend.
In contrast, Tuesday and Wednesday show significantly lower total delays, indicating relatively smoother airline operations during midweek. Additionally, NAS (National Airspace System) delays remain consistently present throughout the week, while security and weather-related delays contribute the least and exhibit minimal day-to-day variation.
These findings imply that airlines and airport authorities might benefit from allocating additional resources and contingency plans specifically on high-traffic days like Thursday, Friday, and Sunday to mitigate delay risks and improve service reliability.
To investigate whether holidays influence flight delays, we compare the total delay minutes by cause for flights occurring on holidays versus non-holidays. By using a stacked bar chart, we can observe how each delay cause contributes to the overall delay under both conditions. This comparison may reveal whether certain causes, such as weather or late aircraft, are more prominent during holidays due to increased travel volume or operational constraints.
# Load required library for plotting
library(ggplot2)
# Step 1: Group by holiday status and summarize total delay minutes
holiday_delay <- df %>%
group_by(IsHoliday) %>%
summarise(
Carrier = sum(DELAY_DUE_CARRIER, na.rm = TRUE),
Weather = sum(DELAY_DUE_WEATHER, na.rm = TRUE),
NAS = sum(DELAY_DUE_NAS, na.rm = TRUE),
Security = sum(DELAY_DUE_SECURITY, na.rm = TRUE),
Late_Aircraft = sum(DELAY_DUE_LATE_AIRCRAFT, na.rm = TRUE)
) %>%
pivot_longer(cols = -IsHoliday, names_to = "Cause", values_to = "Total_Delay")
# Step 2: Create a stacked bar chart to compare holiday vs non-holiday
ggplot(holiday_delay, aes(x = IsHoliday, y = Total_Delay, fill = Cause)) +
geom_bar(stat = "identity") +
labs(title = "Delay Causes by Holiday Status",
x = "Is Holiday",
y = "Total Delay Minutes",
fill = "Delay Cause") +
theme_minimal()
The stacked bar chart comparing delay causes by holiday status reveals a
notable difference in overall delay volumes. Flights on non-holidays
(FALSE) account for significantly more total delay minutes across all
causes, which is expected given that most flights occur on regular days.
However, even during holidays (TRUE), substantial delays are still
recorded.
Late aircraft and carrier-related issues remain the dominant delay causes in both scenarios. Interestingly, the relative proportion of these causes does not shift drastically between holiday and non-holiday flights. This suggests that the mechanisms causing delays are fairly consistent year-round, although operational intensity may increase during holidays.
Overall, while holidays do not introduce new dominant causes, the volume reduction highlights the potential for reduced congestion or improved management during these peak seasons, possibly due to advance planning and resource allocation.
To assess whether flight distance influences the nature and severity
of delays, we analyze total delay minutes by cause across different
flight distance bins. The Distance_Bin variable categorizes
flights into ranges (e.g., 0–500 miles, 501–1000 miles), allowing us to
compare short-haul and long-haul routes. This comparison provides
insight into whether longer flights are more susceptible to specific
delay causes.
# Load required libraries
library(dplyr)
library(ggplot2)
library(tidyr)
# Step 1: Create a new distance bin column
df$DISTANCE_BIN <- cut(df$DISTANCE,
breaks = c(0, 500, 1000, 1500, 2000, Inf),
labels = c("0–500", "501–1000", "1001–1500", "1501–2000", "2000+"),
right = FALSE)
# Step 2: Aggregate total delay minutes for each bin and cause
distance_delay <- df %>%
group_by(DISTANCE_BIN) %>%
summarise(
Carrier = sum(DELAY_DUE_CARRIER, na.rm = TRUE),
Weather = sum(DELAY_DUE_WEATHER, na.rm = TRUE),
NAS = sum(DELAY_DUE_NAS, na.rm = TRUE),
Security = sum(DELAY_DUE_SECURITY, na.rm = TRUE),
Late_Aircraft = sum(DELAY_DUE_LATE_AIRCRAFT, na.rm = TRUE)
) %>%
pivot_longer(cols = -DISTANCE_BIN, names_to = "Cause", values_to = "Total_Delay")
# Step 3: Plot stacked bar chart
ggplot(distance_delay, aes(x = DISTANCE_BIN, y = Total_Delay, fill = Cause)) +
geom_bar(stat = "identity") +
labs(title = "Delay Causes by Flight Distance Bin",
x = "Flight Distance Bin",
y = "Total Delay Minutes",
fill = "Delay Cause") +
theme_minimal()
The stacked bar chart reveals that short- to medium-distance flights
(0–1000 miles) account for the majority of total delay minutes. In
particular, the 501–1000 miles bin shows the highest cumulative delays
across all causes, followed closely by the 0–500 miles bin. Carrier and
late aircraft delays dominate within these ranges, indicating that
operational bottlenecks and aircraft turnaround issues are more common
in high-frequency, short-haul operations.
As flight distances increase beyond 1500 miles, the total delay minutes decrease significantly across all categories. This suggests that long-haul flights tend to be more efficiently scheduled and less affected by the same operational disruptions, potentially due to more buffer time built into their schedules or lower flight volumes.
These insights can guide strategic efforts in delay mitigation, particularly focusing on improving efficiency in short- and mid-range operations where the majority of delays are concentrated.
This classification analysis systematically explored five key flight delay causes—Carrier, Late Aircraft, NAS, Security, and Weather—across multiple operational dimensions. By using grouped aggregations and targeted visualizations, we identified the relative contributions and variation patterns of these causes under different scenarios:
Across dimensions such as month, airline, airport, hour of day, weekday, holiday status, and flight distance, our analysis provided a comprehensive view of how delay causes behave. These findings can support delay prediction, resource allocation, and operational planning in real-world aviation management systems.