Delays in NYC airports

Business Analytics (23/24)

library(tidymodels)
Warning: package 'tidymodels' was built under R version 4.3.3
Warning: package 'dials' was built under R version 4.3.3
Warning: package 'ggplot2' was built under R version 4.3.3
Warning: package 'infer' was built under R version 4.3.3
Warning: package 'modeldata' was built under R version 4.3.3
Warning: package 'parsnip' was built under R version 4.3.3
Warning: package 'recipes' was built under R version 4.3.3
Warning: package 'rsample' was built under R version 4.3.3
Warning: package 'tune' was built under R version 4.3.3
Warning: package 'workflows' was built under R version 4.3.3
Warning: package 'workflowsets' was built under R version 4.3.3
Warning: package 'yardstick' was built under R version 4.3.3
library(nycflights13)
Warning: package 'nycflights13' was built under R version 4.3.3
library(lubridate)
library(themis)
Warning: package 'themis' was built under R version 4.3.3
library(gridExtra)
Warning: package 'gridExtra' was built under R version 4.3.3

In this job, we will work with data from the nycflights13 package, specifically with:

The job has two parts:

Each of the two parts of the exam has the same weight of 50% to final degree.

flights <- nycflights13::flights
airlines_data <- nycflights13::airlines
airports_data <- nycflights13::airports
planes_data <- nycflights13::planes
weather_data <- nycflights13::weather

1 Examining variables

In this section you will examine the impact of some variables departure delay. Please consider that some values of dep_delay are NA so you need to consider it when computing the mean of departure delay. Choose the adequate plot for each variable.

1.1 Planned Departure Hour

For each origin airport, examine the evolution of average departure delay for each hour of planned departure, represented with the variable hour. Comment briefly the obtained result.

unique(flights$origin) 
[1] "EWR" "LGA" "JFK"
EWR_delay_data_hour <- flights %>%
  filter(flights$origin == "EWR" & flights$dep_delay > 0) %>%
  group_by(hour) %>%
  count() %>%
  rename(TotalDelays_EWR = n)
EWR_delay_data_hour  
# A tibble: 19 × 2
# Groups:   hour [19]
    hour TotalDelays_EWR
   <dbl>           <int>
 1     5             226
 2     6            2808
 3     7            2421
 4     8            2826
 5     9            1854
 6    10            2155
 7    11            1678
 8    12            2990
 9    13            4150
10    14            3726
11    15            4804
12    16            3895
13    17            4997
14    18            4021
15    19            3578
16    20            4082
17    21            2452
18    22              35
19    23              13
EWR_delays_mean <- mean(EWR_delay_data_hour$TotalDelays_EWR)

LGA_delay_data_hour <- flights %>%
  filter(flights$origin == "LGA" & flights$dep_delay > 0) %>%
  group_by(hour) %>%
  count() %>%
  rename(TotalDelays_LGA = n)
LGA_delay_data_hour
# A tibble: 18 × 2
# Groups:   hour [18]
    hour TotalDelays_LGA
   <dbl>           <int>
 1     5              95
 2     6            1413
 3     7            1090
 4     8            1422
 5     9            1687
 6    10            1396
 7    11            2536
 8    12            1817
 9    13            2415
10    14            2450
11    15            2706
12    16            2787
13    17            3250
14    18            2932
15    19            2930
16    20            1458
17    21            1207
18    22              99
LGA_delays_mean <- mean(LGA_delay_data_hour$TotalDelays_LGA)

JFK_delay_data_hour <- flights %>%
  filter(flights$origin == "JFK" & flights$dep_delay > 0) %>%
  group_by(hour) %>%
  count() %>%
  rename(TotalDelays_JFK = n)
JFK_delay_data_hour
# A tibble: 19 × 2
# Groups:   hour [19]
    hour TotalDelays_JFK
   <dbl>           <int>
 1     5             168
 2     6            1209
 3     7            1452
 4     8            2542
 5     9            1851
 6    10            1391
 7    11             820
 8    12            1601
 9    13            1618
10    14            3081
11    15            3854
12    16            4017
13    17            3885
14    18            3683
15    19            4331
16    20            3093
17    21            1937
18    22            1050
19    23             448
JFK_delays_mean <- mean(JFK_delay_data_hour$TotalDelays_JFK)

plot1.1_EWR <- ggplot(EWR_delay_data_hour, aes(factor(hour), TotalDelays_EWR, fill = factor(hour))) +
  geom_col() +
  geom_hline(yintercept = EWR_delays_mean, color="black") +
  labs(x = "Hour", y = "Delays", 
       title = "Hour vs Delays (EWR)") +
  theme_minimal(base_size = 11) +
  theme(legend.position = "none")

plot1.1_LGA <- ggplot(LGA_delay_data_hour, aes(factor(hour), TotalDelays_LGA, fill = factor(hour))) +
  geom_col() +
  geom_hline(yintercept = LGA_delays_mean, color="black") +
  labs(x = "Hour", y = "Delays", 
       title = "Hour vs Delays (LGA)") +
  theme_minimal(base_size = 11) +
  theme(legend.position = "none")

plot1.1_JFK <- ggplot(JFK_delay_data_hour, aes(factor(hour), TotalDelays_JFK, fill = factor(hour))) +
  geom_col() +
  geom_hline(yintercept = JFK_delays_mean, color="black") +
  labs(x = "Hour", y = "Delays", 
       title = "Hour vs Delays (JFK)") +
  theme_minimal(base_size = 11) +
  theme(legend.position = "none")

plot1.1_EWR

plot1.1_JFK

plot1.1_LGA

grid.arrange(plot1.1_EWR, plot1.1_JFK, plot1.1_LGA, ncol =3)

  • We can conclude that the number of delays are higher in hours 16 - 19.
  • There is a trend which tell us that during the beginning of the day and the end of day the delays are lower.
  • We can also observe that Airport LGA and JFK perform better than Airport EWR in terms of the average of delays per hour.

1.2 Number of Flights

For each origin and for each value of time_hour, calculate the average departure delay and the number of flights. With those values, plot for each origin airport the average departure delay as a function of number of flights. Comment briefly the obtained result.

by_EWR_total <- flights %>%
  filter(flights$origin == "EWR" & flights$dep_delay > 0) %>%
  group_by(time_hour) %>%
  summarise(Total_Flights = n(),
            AverageDep_Delay = mean(dep_delay)) 
by_EWR_total
# A tibble: 5,984 × 3
   time_hour           Total_Flights AverageDep_Delay
   <dttm>                      <int>            <dbl>
 1 2013-01-01 05:00:00             1             2   
 2 2013-01-01 06:00:00             6            14.8 
 3 2013-01-01 07:00:00             4            46.5 
 4 2013-01-01 08:00:00             7             8   
 5 2013-01-01 09:00:00             7            30.1 
 6 2013-01-01 10:00:00             8             9.38
 7 2013-01-01 11:00:00             2            13   
 8 2013-01-01 12:00:00            10            18.8 
 9 2013-01-01 13:00:00            18            45.9 
10 2013-01-01 14:00:00            13            47.8 
# ℹ 5,974 more rows
by_LGA_total <- flights %>%
  filter(flights$origin == "LGA" & flights$dep_delay > 0) %>%
  group_by(time_hour) %>%
  summarise(Total_Flights = n(),
            AverageDep_Delay = mean(dep_delay)) 
by_LGA_total
# A tibble: 5,743 × 3
   time_hour           Total_Flights AverageDep_Delay
   <dttm>                      <int>            <dbl>
 1 2013-01-01 05:00:00             1             4   
 2 2013-01-01 06:00:00             3            38.3 
 3 2013-01-01 07:00:00             3             4.67
 4 2013-01-01 08:00:00             2            23.5 
 5 2013-01-01 09:00:00             3            45.3 
 6 2013-01-01 10:00:00             5             5.4 
 7 2013-01-01 11:00:00             4            23   
 8 2013-01-01 12:00:00             3            44   
 9 2013-01-01 13:00:00             6             5.83
10 2013-01-01 14:00:00             5            21.2 
# ℹ 5,733 more rows
by_JFK_total <- flights %>%
  filter(flights$origin == "JFK" & flights$dep_delay > 0) %>%
  group_by(time_hour) %>%
  summarise(Total_Flights = n(),
            AverageDep_Delay = mean(dep_delay)) 
by_JFK_total
# A tibble: 6,376 × 3
   time_hour           Total_Flights AverageDep_Delay
   <dttm>                      <int>            <dbl>
 1 2013-01-01 05:00:00             1              2  
 2 2013-01-01 06:00:00             2              7  
 3 2013-01-01 07:00:00             4             24.8
 4 2013-01-01 08:00:00             6             13.8
 5 2013-01-01 09:00:00             9             10.3
 6 2013-01-01 10:00:00             2              1.5
 7 2013-01-01 11:00:00             6             13.8
 8 2013-01-01 12:00:00             7             20  
 9 2013-01-01 13:00:00             5             67.2
10 2013-01-01 14:00:00             6             31.5
# ℹ 6,366 more rows
plot1.2_EWR <- ggplot(by_EWR_total, aes(Total_Flights, AverageDep_Delay)) +
  geom_point() +
  geom_smooth(method = "lm") + ##gam se adapta melhor aos dados
  labs(x = "Total Number of flights", y = "Average Departure Delays",
       title = "Total Number of flights vs Average Departure Delays (EWR)") +
  theme_minimal()+
  theme(legend.position = "none",
        plot.title = element_text(hjust = "0.5"))

plot1.2_LGA <- ggplot(by_LGA_total, aes(Total_Flights, AverageDep_Delay)) +
  geom_point() +
  geom_smooth(method = "lm") + 
  labs(x = "Total Number of flights", y = "Average Departure Delays",
       title = "Total Number of flights vs Average Departure Delays (LGA)") +
  theme_minimal()+
  theme(legend.position = "none",
        plot.title = element_text(hjust = "0.5"))

plot1.2_JFK <- ggplot(by_JFK_total, aes(Total_Flights, AverageDep_Delay)) +
  geom_point() +
  geom_smooth(method = "lm") + 
  labs(x = "Total Number of flights", y = "Average Departure Delays",
       title = "Total Number of flights vs Average Departure Delays (JFK)") +
  theme_minimal()+
  theme(legend.position = "none",
        plot.title = element_text(hjust = "0.5"))

plot1.2_EWR
`geom_smooth()` using formula = 'y ~ x'

plot1.2_JFK
`geom_smooth()` using formula = 'y ~ x'

plot1.2_LGA
`geom_smooth()` using formula = 'y ~ x'

grid.arrange(plot1.2_EWR, plot1.2_JFK, plot1.2_LGA, ncol=3)
`geom_smooth()` using formula = 'y ~ x'
`geom_smooth()` using formula = 'y ~ x'
`geom_smooth()` using formula = 'y ~ x'

  • Based on the above graphics, it is clear that as the number of flights increases, the average departure delay time also increases.

1.3 Weekday

Create a weekday column from time_hour using the wday() function of lubridate with label = TRUE. For each day of the and for each origin, calculate the average departure delay. Comment briefly the obtained result.

flights_weekday <- flights %>%
  mutate(weekday = wday(time_hour, label = TRUE))

average_delays <- flights_weekday %>%
  filter(!is.na(dep_delay)) %>%  
  group_by(origin, weekday) %>%
  summarise(average_delay = mean(dep_delay, na.rm = TRUE), .groups = 'drop')

ggplot(average_delays, aes(x = weekday, y = average_delay, fill = origin)) +
  geom_col(position = "dodge") +
  labs(x = "Weekday", y = "Average Departure Delay (minutes)",
       title = "Average Departure Delays by Weekday and Origin") +
  theme_minimal()

  • All three airports experience the highest average departure delays on Thursdays and Fridays.

  • EWR consistently shows higher departure delays than JFK and LGA (higher total number of flights)

  • Departure delays are generally lower on weekends (Sunday and Saturday) across all airports.

1.4 Month

For each origin airport, examine with an adequate plot the evolution of the average departure delay for each month. Comment briefly the obtained result.

EWR_delay_data <- flights %>%
  filter(flights$origin == "EWR" & flights$dep_delay > 0) %>%
  group_by(month) %>%
  count() %>%
  rename(Month = month, TotalDelays = n)
EWR_delay_data  
# A tibble: 12 × 2
# Groups:   Month [12]
   Month TotalDelays
   <int>       <int>
 1     1        4375
 2     2        3769
 3     3        4834
 4     4        4545
 5     5        4926
 6     6        5008
 7     7        5325
 8     8        4509
 9     9        2950
10    10        3539
11    11        3381
12    12        5550
EWR_delays_mean <- mean(EWR_delay_data$TotalDelays)

LGA_delay_data <- flights %>%
  filter(flights$origin == "LGA" & flights$dep_delay > 0) %>%
  group_by(month) %>%
  count() %>%
  rename(Month = month, TotalDelays = n)
LGA_delay_data
# A tibble: 12 × 2
# Groups:   Month [12]
   Month TotalDelays
   <int>       <int>
 1     1        2193
 2     2        2225
 3     3        2842
 4     4        2692
 5     5        2812
 6     6        3318
 7     7        3539
 8     8        3075
 9     9        2216
10    10        2611
11    11        2471
12    12        3696
LGA_delays_mean <- mean(LGA_delay_data$TotalDelays)

JFK_delay_data <- flights %>%
  filter(flights$origin == "JFK" & flights$dep_delay > 0) %>%
  group_by(month) %>%
  count() %>%
  rename(Month = month, TotalDelays = n)
JFK_delay_data
# A tibble: 12 × 2
# Groups:   Month [12]
   Month TotalDelays
   <int>       <int>
 1     1        3094
 2     2        3130
 3     3        3533
 4     4        3306
 5     5        3553
 6     6        4329
 7     7        5045
 8     8        4129
 9     9        2649
10    10        2572
11    11        2387
12    12        4304
JFK_delays_mean <- mean(JFK_delay_data$TotalDelays)

plot1.3_EWR <- ggplot(EWR_delay_data, aes(factor(Month), TotalDelays, color = factor(Month), group = 2)) +
  geom_point() +
  geom_line(color = "red") +
  geom_hline(yintercept = EWR_delays_mean, color="black") +
  #scale_fill_manual(values = c("red", "blue", "green")) we need 12
  labs(x="Month", y= "Delays",
       title = "Month vs Delays (EWR)") +
  theme_minimal(base_size = 11) +
  theme(legend.position = "none") 

plot1.3_LGA <- ggplot(LGA_delay_data, aes(factor(Month), TotalDelays, color = factor(Month), group = 2)) +
  geom_point() +
  geom_line(color = "red") +
  geom_hline(yintercept = LGA_delays_mean, color="black") +
  #scale_fill_manual(values = c("red", "blue", "green")) we need 12
  labs(x="Month", y= "Delays",
       title = "Month vs Delays (LGA)") +
  theme_minimal(base_size = 11) +
  theme(legend.position = "none") 

plot1.3_JFK <- ggplot(JFK_delay_data, aes(factor(Month), TotalDelays, color = factor(Month), group = 2)) +
  geom_point() +
  geom_line(color = "red") +
  geom_hline(yintercept = JFK_delays_mean, color="black") +
  #scale_fill_manual(values = c("red", "blue", "green")) we need 12
  labs(x="Month", y= "Delays",
       title = "Month vs Delays (JFK)") +
  theme_minimal(base_size = 11) +
  theme(legend.position = "none") 

grid.arrange(plot1.3_EWR, plot1.3_LGA, plot1.3_JFK, ncol=3)

  • We can conclude that the number of delays are higher in december and lowest in Setember, Octuber and November.
  • There is a trend which tell us that during the holiday season the delays are higher and they are lower just before the holiday period.
  • We can also observe that Airport LGA and JFK perform better than Airport EWR in terms of the average of delays per month. This observation can be reasoned out as EWR flies out more flights than LGA or JFK.

1.5 Wind

Join the flights and weather datasets, so that we can obtain the weather for each flight.

### Bringing the flights data to similar granularity of the weather data
by_time_hour_airport = flights %>%
  filter(dep_delay > 0) %>%
  group_by(origin, time_hour) %>%
  summarise(totalDelay = mean(dep_delay),totalflightsdelayed = n())
`summarise()` has grouped output by 'origin'. You can override using the
`.groups` argument.
merged_fli_weather <- inner_join(by_time_hour_airport, weather_data, by =c("origin","time_hour")) 
merged_fli_weather
# A tibble: 18,005 × 17
# Groups:   origin [3]
   origin time_hour           totalDelay totalflightsdelayed  year month   day
   <chr>  <dttm>                   <dbl>               <int> <int> <int> <int>
 1 EWR    2013-01-01 05:00:00       2                      1  2013     1     1
 2 EWR    2013-01-01 06:00:00      14.8                    6  2013     1     1
 3 EWR    2013-01-01 07:00:00      46.5                    4  2013     1     1
 4 EWR    2013-01-01 08:00:00       8                      7  2013     1     1
 5 EWR    2013-01-01 09:00:00      30.1                    7  2013     1     1
 6 EWR    2013-01-01 10:00:00       9.38                   8  2013     1     1
 7 EWR    2013-01-01 11:00:00      13                      2  2013     1     1
 8 EWR    2013-01-01 13:00:00      45.9                   18  2013     1     1
 9 EWR    2013-01-01 14:00:00      47.8                   13  2013     1     1
10 EWR    2013-01-01 15:00:00      18.6                   15  2013     1     1
# ℹ 17,995 more rows
# ℹ 10 more variables: hour <int>, temp <dbl>, dewp <dbl>, humid <dbl>,
#   wind_dir <dbl>, wind_speed <dbl>, wind_gust <dbl>, precip <dbl>,
#   pressure <dbl>, visib <dbl>

Create a windy variable equal to one if wind_speed is larger than 25 and zero otherwise. For each origin airport, use boxplots to check if wind speed has impact on the average departure delay for each value of time_hour.

windy_fli_weather <- merged_fli_weather %>%
  mutate(windy = ifelse(wind_speed > 25, 1, 0))

average_delays <- windy_fli_weather %>%
  filter(!is.na(totalDelay)) %>%  
  group_by(origin, time_hour, windy) %>%
  summarise(average_delay = mean(totalDelay, na.rm = TRUE), .groups = 'drop')

ggplot(average_delays, aes(x = as.factor(windy), y = average_delay, fill = origin)) +
  geom_boxplot() +
  facet_wrap(~ origin) +
  labs(title = "Effect of Wind Conditions on Departure Delays by Origin Airport",
       x = "Windy (1 = Wind Speed > 25, 0 = Wind Speed <= 25)",
       y = "Departure Delay (minutes)",
       fill = "Origin Airport") +
  scale_fill_manual(values = c("EWR" = "green", "JFK" = "blue", "LGA" = "purple")) +
  theme_minimal()

2 Predictive Modelling

Let’s define a predictive model of delays for the EWR airport to predict if a flight will arrive late, that is, with a delay equal or larger than 15 minutes.

2.1 Target Variable

Filter the flights departing from EWR, excluding observations with NA in dep_delay. Define the target variable as a binary factor, being the positive case that the flight arrives late.

cleaning_flights <- nycflights13::flights %>%
  filter(origin == "EWR", !is.na(dep_delay)) %>% 
  mutate(
    dep_delay = ifelse(dep_delay > 15, "late", "on time"), 
    dep_delay = as.factor(dep_delay),  
    time_hour = as.Date(time_hour, format = "%Y-%m-%d %H:%M:%S")  
  ) %>%
  na.omit() %>%
  mutate(across(where(is.character), as.factor))

2.2 Examining Target Variable

Examine the target variable, and check if its prediction is a balanced or unbalanced problem.

target_distribution <- cleaning_flights %>%
  count(dep_delay) %>%
  mutate(proportion = n / sum(n))

target_distribution
# A tibble: 2 × 3
  dep_delay     n proportion
  <fct>     <int>      <dbl>
1 late      28718      0.245
2 on time   88409      0.755

The analysis shows that predicting flight delays at EWR is an unbalanced problem, with 24.52% of flights being late and 75.48% on time.

2.3 Model Elements

Define model elements:

  • Split the dataset into train and test set. Keep as train test 90% of observations.
  • Define five folds of the training set for cross validation.
  • Define as metrics sensitivity, specificy and accuracy.
set.seed(123123)

data_split <- initial_split(cleaning_flights, prop = 0.9, strata = dep_delay)
train_data <- training(data_split)
test_data <- testing(data_split)

folds <- vfold_cv(train_data, v = 5, strata = dep_delay)

metrics <- metric_set(accuracy, sensitivity, specificity)

2.4 Preprocessing

For data preprocessing, keep only some variables using the formula in recipe():

dep_delay ~ month + hour + time_hour + carrier, training(split)

where split is the name of the split obtained in the previous section.

Include the following steps in preprocessing:

  • Add a weekday variable using time_hour, and after that remove the time_hour variable.
  • Define month and hour as factors.
  • Transform all factors into dummies.
  • Add a downsampling step.
flight_recipe <- recipe(dep_delay ~ month + hour + time_hour + carrier, data = train_data) %>%
  step_mutate(weekday = wday(time_hour, label = TRUE)) %>%
  step_mutate(month = as.factor(month),
              hour = as.factor(hour)) %>%
  step_rm(time_hour) %>%
  step_dummy(all_nominal_predictors()) %>%
  step_downsample(dep_delay)

prepared_recipe <- prep(flight_recipe, train_data)

2.5 Model definition

Let’s define two simple models:

  • A logistic regression with glmnet with penalty = 1 and mixture = 0.
  • A decision tree with rpart.
lr_mod <-
  logistic_reg(penalty = 1, mixture = 0) %>%
  set_engine("glmnet")
lr_wf <- 
  workflow() %>%
  add_recipe(flight_recipe) %>%
  add_model(lr_mod)
lr_wf
══ Workflow ════════════════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: logistic_reg()

── Preprocessor ────────────────────────────────────────────────────────────────
5 Recipe Steps

• step_mutate()
• step_mutate()
• step_rm()
• step_dummy()
• step_downsample()

── Model ───────────────────────────────────────────────────────────────────────
Logistic Regression Model Specification (classification)

Main Arguments:
  penalty = 1
  mixture = 0

Computational engine: glmnet 
dt_mod <- decision_tree() %>%
  set_engine("rpart") %>%
  set_mode("classification")
dt_wf <- 
  workflow() %>%
  add_recipe(flight_recipe) %>%
  add_model(dt_mod)

2.6 Cross Validation

Test the two models with cross validation.

lr_cv <- lr_wf %>%
  fit_resamples(folds, metrics = metrics)
Warning: package 'glmnet' was built under R version 4.3.3
dt_cv <- dt_wf %>%
  fit_resamples(folds, metrics = metrics)

lr_cv %>%
  collect_metrics()
# A tibble: 3 × 6
  .metric     .estimator  mean     n std_err .config             
  <chr>       <chr>      <dbl> <int>   <dbl> <chr>               
1 accuracy    binary     0.644     5 0.00163 Preprocessor1_Model1
2 sensitivity binary     0.692     5 0.00146 Preprocessor1_Model1
3 specificity binary     0.628     5 0.00249 Preprocessor1_Model1
dt_cv %>%
  collect_metrics()
# A tibble: 3 × 6
  .metric     .estimator  mean     n std_err .config             
  <chr>       <chr>      <dbl> <int>   <dbl> <chr>               
1 accuracy    binary     0.544     5 0.00505 Preprocessor1_Model1
2 sensitivity binary     0.801     5 0.00417 Preprocessor1_Model1
3 specificity binary     0.460     5 0.00724 Preprocessor1_Model1
  • I chose the decision tree model due to its higher sensitivity, which ensures better identification of delayed flights, aligning with the primary objective of minimizing missed detections of departure delays.

2.7 Select and Fit Model

Select which model is best and train it with the whole train test. Briefly comment why have you chosen the model.

best_model <- dt_wf %>% 
  fit(train_data) 

2.8 Test the Final Model

Test the final model on the test set, obtaining the confusion matrix and the performance metrics. Comment briefly the results.

best_model %>%
  predict(test_data) %>%
  bind_cols(test_data %>% select(dep_delay)) %>%
  metrics(truth = dep_delay, estimate = .pred_class)
# A tibble: 3 × 3
  .metric     .estimator .estimate
  <chr>       <chr>          <dbl>
1 accuracy    binary         0.554
2 sensitivity binary         0.810
3 specificity binary         0.471
best_model %>%
  predict(test_data, type = "prob")
# A tibble: 11,713 × 2
   .pred_late `.pred_on time`
        <dbl>           <dbl>
 1      0.242           0.758
 2      0.242           0.758
 3      0.242           0.758
 4      0.272           0.728
 5      0.242           0.758
 6      0.280           0.720
 7      0.280           0.720
 8      0.272           0.728
 9      0.316           0.684
10      0.637           0.363
# ℹ 11,703 more rows
  • Sensitivity is expected to be high, indicating that the model is effective in identifying delayed flights. Conclusion - These results confirm the model’s alignment with the primary objective of detecting delayed flights effectively.