Data

Europe has a well-developed aviation network, making it an interesting subject for analysis. European air traffic is influenced by various factors, such as economic conditions, regulatory policies, seasonal trends, and international travel demand. This dataset allows for a broad analysis of European aviation trends, including: The busiest airports in terms of traffic, The most active countries for flight departures, Seasonal and yearly fluctuations in air traffic, The relationship between arrivals and departures and the map visualization of the busiest air traffic cities in Europe.

df <- read.csv('flights.csv')
dt <- data.table(df)
str(dt)
## Classes 'data.table' and 'data.frame':   688099 obs. of  14 variables:
##  $ YEAR         : int  2016 2016 2016 2016 2016 2016 2016 2016 2016 2016 ...
##  $ MONTH_NUM    : int  1 1 1 1 1 1 1 1 1 1 ...
##  $ MONTH_MON    : chr  "JAN" "JAN" "JAN" "JAN" ...
##  $ FLT_DATE     : chr  "2016-01-01T00:00:00Z" "2016-01-01T00:00:00Z" "2016-01-01T00:00:00Z" "2016-01-01T00:00:00Z" ...
##  $ APT_ICAO     : chr  "EBAW" "EBBR" "EBCI" "EBLG" ...
##  $ APT_NAME     : chr  "Antwerp" "Brussels" "Charleroi" "Liège" ...
##  $ STATE_NAME   : chr  "Belgium" "Belgium" "Belgium" "Belgium" ...
##  $ FLT_DEP_1    : int  4 174 45 6 7 98 18 1 401 3 ...
##  $ FLT_ARR_1    : int  3 171 47 7 7 99 21 1 341 4 ...
##  $ FLT_TOT_1    : int  7 345 92 13 14 197 39 2 742 7 ...
##  $ FLT_DEP_IFR_2: int  NA 174 45 NA NA NA NA NA 401 NA ...
##  $ FLT_ARR_IFR_2: int  NA 161 45 NA NA NA NA NA 306 NA ...
##  $ FLT_TOT_IFR_2: int  NA 335 90 NA NA NA NA NA 707 NA ...
##  $ Pivot.Label  : chr  "Antwerp (EBAW)" "Brussels (EBBR)" "Charleroi (EBCI)" "Liège (EBLG)" ...
##  - attr(*, ".internal.selfref")=<externalptr>
summary(dt)
##       YEAR        MONTH_NUM       MONTH_MON           FLT_DATE        
##  Min.   :2016   Min.   : 1.000   Length:688099      Length:688099     
##  1st Qu.:2017   1st Qu.: 3.000   Class :character   Class :character  
##  Median :2019   Median : 6.000   Mode  :character   Mode  :character  
##  Mean   :2019   Mean   : 6.301                                        
##  3rd Qu.:2020   3rd Qu.: 9.000                                        
##  Max.   :2022   Max.   :12.000                                        
##                                                                       
##    APT_ICAO           APT_NAME          STATE_NAME          FLT_DEP_1     
##  Length:688099      Length:688099      Length:688099      Min.   :  0.00  
##  Class :character   Class :character   Class :character   1st Qu.:  5.00  
##  Mode  :character   Mode  :character   Mode  :character   Median : 17.00  
##                                                           Mean   : 63.24  
##                                                           3rd Qu.: 71.00  
##                                                           Max.   :847.00  
##                                                                           
##    FLT_ARR_1        FLT_TOT_1      FLT_DEP_IFR_2    FLT_ARR_IFR_2   
##  Min.   :  0.00   Min.   :   0.0   Min.   :   0.0   Min.   :  0.0   
##  1st Qu.:  5.00   1st Qu.:  10.0   1st Qu.:  38.0   1st Qu.: 38.0   
##  Median : 17.00   Median :  35.0   Median :  91.0   Median : 91.0   
##  Mean   : 63.28   Mean   : 126.5   Mean   : 143.7   Mean   :143.6   
##  3rd Qu.: 71.00   3rd Qu.: 141.0   3rd Qu.: 195.0   3rd Qu.:195.0   
##  Max.   :813.00   Max.   :1628.0   Max.   :1039.0   Max.   :817.0   
##                                    NA's   :479785   NA's   :479785  
##  FLT_TOT_IFR_2    Pivot.Label       
##  Min.   :   0.0   Length:688099     
##  1st Qu.:  76.0   Class :character  
##  Median : 182.0   Mode  :character  
##  Mean   : 287.3                     
##  3rd Qu.: 390.0                     
##  Max.   :1624.0                     
##  NA's   :479785

The dataset contains 688,099 observations and 14 variables, representing flight activity at various European airports from 2016 to 2022. The data includes information about flight counts, airport details, and Instrument Flight Rules (IFR) statistics. Missing Values: IFR-related data has 479,785 missing values, indicating that a large portion of flights may not have IFR classifications.

Distribution of flights

Total Flights per Year

Bar chart shows the total number of flights per year from 2016 to 2022. The number of flights increases from 2016 to 2019, reaching a peak in 2019. However, there isn’t a sharp decline in 2020, which doesn’t align with the impact of the COVID-19 pandemic on global air travel.

ggplot(dt, aes(x = YEAR)) + 
  geom_bar() +  
  theme_bw() + 
  labs(title = "Total Flights per Year", x = "Year", y = "Count")

Distribution of Flight Departures

This histogram shows the distribution of flight departures. The majority of airports have a relatively low number of departures by the high frequency of lower values. A few airports have significantly higher departures, creating a right-skewed distribution, meaning a small number of airports handle a disproportionately large share of departures.

ggplot(dt, aes(x = FLT_DEP_1)) + 
  geom_histogram() +  
  theme_bw() + 
  labs(title = "Distribution of Flight Departures", x = "Number of Departures", y = "Frequency")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Distribution of Total Flights per Year

This faceted histogram shows the distribution of total flights for each year. The pattern is similar across years, with most airports operating at lower flight volumes. However, the total number of flights significantly drops in 2020 and 2021 , witha lot of airports operating close to 0.

ggplot(dt, aes(x = FLT_TOT_1)) + 
  geom_histogram() +  
  facet_wrap(~YEAR) +  
  theme_bw() + 
  labs(title = "Distribution of Total Flights per Year", x = "Total Flights", y = "Frequency")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Total Departures by Year

This line graph represents the total number of departures per year over the observed period. The trend shows a steady increase from 2016 to 2019, reaching its peak in 2019. However, there is a sharp decline in 2020, which aligns with the impact of the COVID-19 pandemic.

dt_yearly <- dt[, .(total_departures = sum(FLT_DEP_1, na.rm = TRUE)), by = YEAR]

ggplot(dt_yearly, aes(x = YEAR, y = total_departures)) + 
  geom_point() + 
  geom_line() + 
  theme_bw() +
  labs(title = "Total Departures by Year", x = "Year", y = "Total Departures")

Comparrison

Top 10 Busiest Airports

In This bar chart we can see the top 10 busiest airports based on total flight volume.

top_airports <- dt %>%
  group_by(APT_NAME) %>%
  summarise(total_flights = sum(FLT_TOT_1, na.rm = TRUE)) %>%
  arrange(desc(total_flights)) %>%
  slice(1:10)  

ggplot(top_airports, aes(x = reorder(APT_NAME, total_flights), y = total_flights)) + 
  geom_col(fill = "#756bb1") + 
  theme_bw() +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5)) +
  labs(title = "Top 10 Busiest Airports", x = "Airport", y = "Total Flights")

Total IFR Departures by Country

This bar chart ranks countries based on their total IFR (Instrument Flight Rules) departures. Western European countries have the highest IFR departures, indicatingt their role as major global aviation hubs.

state_departures <- dt %>%
  group_by(STATE_NAME) %>%
  summarise(total_departures = sum(FLT_DEP_1, na.rm = TRUE))

ggplot(state_departures, aes(x = reorder(STATE_NAME, -total_departures), y = total_departures)) +
  geom_bar(stat = "identity", fill = "#dd1c77") +
  theme_bw() +
  labs(title = "Total IFR Departures by Country",
       x = "Country",
       y = "Number of Departures") +
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5))

Number of Flights per Country

This faceted chart shows the number of flights per country over time. The trends vary across countries, with smaller countries exhibiting relatively stable trends.

ggplot(dt, aes(x = fct_infreq(STATE_NAME))) + 
  geom_bar() + 
  theme_bw() + 
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5)) + 
  facet_wrap(~YEAR) + 
  labs(title = "Number of Flights per Country", x = "Country", y = "Flight Count")

IFR Departures by Airport

This bar chart displays the number of IFR departures at different airports. A small number of airports take a majority of IFR departures, while most airports have relatively low IFR activity.

dt_filt <- dt[FLT_DEP_IFR_2 > 0]

ggplot(dt_filt, aes(x = fct_reorder(APT_NAME, -FLT_DEP_IFR_2), y = FLT_DEP_IFR_2)) + 
  geom_bar(stat = "identity") + 
  theme_bw() + 
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5)) + 
  labs(title = "IFR Departures by Airport", x = "Airport", y = "Number of IFR Departures")

Yearly IFR Departures by Country

This faceted bar chart shows yearly IFR departures for different countries. We can see clear trend with specific countries consistently dominating each year.

ggplot(dt_filt, aes(x = reorder(STATE_NAME, -FLT_DEP_IFR_2), y = FLT_DEP_IFR_2, fill = as.factor(YEAR))) + 
  geom_bar(stat = "identity", show.legend = FALSE) + 
  theme_bw() + 
  facet_wrap(~ YEAR, scales = "free_y") +  
  labs(title = "Yearly IFR Departures by Country",
       x = "Airport",
       y = "Number of Departures") + 
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5)) # Adjust alignment

Distribution of IFR Departures by Airport

This box plot shows the distribution of IFR departures across different airports. The red dots highlight outliers, which indicate airports with higher or lower than usual flight activity.

ggplot(dt_filt, aes(x = APT_NAME, y = FLT_DEP_IFR_2)) + 
  geom_boxplot(outlier.colour = "red") + 
  theme_bw() + 
  labs(title = "Distribution of IFR Departures by Airport",
       x = "Airport",
       y = "Number of Departures") + 
  theme(axis.text.x = element_text(angle = 90, hjust = 1))

Departures Over Time

This boxplot shows the distribution of flight departures from 2016 to 2022. The number of departures remains relatively stable from 2016 to 2019, followed by a steep drop in 2020.

ggplot(dt, aes(x = factor(YEAR), y = FLT_DEP_1)) + 
  geom_boxplot(outlier.colour = "blue") + 
  theme_bw() +
  labs(title = "Departures Over Time", x = "Year", y = "Number of Departures")

Monthly Departures by Year

This faceted box plot displays monthly departures across different years. Each small panel represents a month within each, bars show the number of flights per month. We can see the seasonal trend each year before 2020, and big sudden decline in April, May, and June of 2020.

ggplot(dt, aes(x = factor(MONTH_NUM), y = FLT_DEP_1)) + 
  geom_boxplot(outlier.colour = "blue") +
  facet_wrap(~YEAR) + 
  theme_bw() +
  labs(title = "Monthly Departures by Year", x = "Month", y = "Number of Departures")

Total Flight Movements by Country

This bar chart represents the total number of flight movements (departures + arrivals) for each country. Because there were a lot of close to 0 values for European cities this graph wasn’t successfull. The total flights that we want to observe here shown as outliers.

dt[, FLT_TOT_1 := FLT_DEP_1 + FLT_ARR_1]

ggplot(dt, aes(x = STATE_NAME, y = FLT_TOT_1)) + 
  geom_boxplot(outlier.colour = "blue") + 
  theme_bw() + 
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5)) + 
  labs(title = "Total Flight Movements by Country", x = "Country", y = "Total Flights")

Correlation Between Departures and Arrivals

The next two graphs are just to confirm the accuracy of the data. A regression plot showing the relationship between departures and arrivals. The near-perfect diagonal alignment confirms a high correlation, meaning most airports have a balanced number of departing and arriving flights.

ggplot(dt, aes(x = FLT_DEP_1, y = FLT_ARR_1)) + 
  geom_point(alpha = 0.3) + 
  geom_smooth(method = "lm") + 
  theme_bw() +
  labs(title = "Correlation Between Departures and Arrivals", x = "Departures", y = "Arrivals")
## `geom_smooth()` using formula = 'y ~ x'

Departures vs Arrivals by Country

This scatter plot compares the number of departures and arrivals for different countries. Each point represents a country with their color-coding.The strong diagonal trend again shows that most countries have close to equal departures and arivals, which is expected, but there are some slight deviations.

ggplot(dt_filt, aes(x = FLT_DEP_IFR_2, y = FLT_ARR_IFR_2, color = STATE_NAME)) + 
  geom_point(alpha = 0.5) + 
  theme_bw() + 
  labs(title = "Departures vs Arrivals by Country",
       x = "IFR Departures",
       y = "IFR Arrivals") + 
  geom_smooth(method = "lm", se = FALSE, color = "black", linetype = "dashed")
## `geom_smooth()` using formula = 'y ~ x'

Visualization

I wanted to show the map by flight volume, but when i merged the cities lat and long data there were a lot of missing values

Global Airports by Total Flights

A world map visualizing total flights handled by different cities globally. The color gradient represents flight volume, with darker shades indicating airports with higher traffic. This map isn’t particularly useful, as cities in Europe are not clearly visible, and there are limited observations outside of Europe.

# Download city coordinates data
cities_url <- "https://simplemaps.com/static/data/world-cities/basic/simplemaps_worldcities_basicv1.75.zip"
temp <- tempfile()
download.file(cities_url, temp)
unzip(temp, files = "worldcities.csv")

cities <- fread("worldcities.csv")
unique_cities <- unique(dt$APT_NAME)
european_cities <- cities[city %in% unique_cities, .(APT_NAME = city, lat, lng)]
european_cities[, .N, by = APT_NAME][N > 1]
##        APT_NAME     N
##          <char> <int>
##  1:    Santiago    11
##  2:   Barcelona     3
##  3:      Vienna     4
##  4:     Hamburg     2
##  5:    Valencia     7
##  6:  Birmingham     3
##  7:      Athens     6
##  8:        Riga     2
##  9:      Dublin     4
## 10:      Lisbon     3
## 11:      Naples     2
## 12:     Sevilla     2
## 13:    Zaragoza     4
## 14:   Stuttgart     2
## 15:   Rotterdam     2
## 16:     Glasgow     3
## 17:     Bristol     7
## 18:      Bremen     2
## 19:  Manchester    10
## 20:   Newcastle     4
## 21:  Valladolid     3
## 22: Santa Maria     9
## 23: Southampton     6
## 24:      Venice     2
## 25:      Bergen     5
## 26:       Porto     2
## 27:     Granada     4
## 28:      Geneva     4
## 29:   Groningen     2
## 30:    Aberdeen     7
## 31:   Santander     2
## 32:      Burgos     6
## 33:      Flores     2
## 34: Farnborough     2
## 35:        Faro     2
## 36:   Waterford     7
## 37:    La Palma     4
## 38:      Weston     4
## 39:     Hanover     9
## 40:     Montijo     2
## 41:       Malta     2
## 42:     Shannon     2
## 43:     Madeira     2
##        APT_NAME     N
european_cities <- european_cities[!duplicated(APT_NAME)]

merged_data <- merge(dt, european_cities, by = "APT_NAME", all.x = TRUE)
sum(is.na(merged_data$lat)) ## A lot of missing values
## [1] 411872
city_flights <- merged_data %>%
  group_by(APT_NAME, lat, lng) %>%
  summarise(total_flights = sum(FLT_TOT_1, na.rm = TRUE)) %>%
  ungroup()
## `summarise()` has grouped output by 'APT_NAME', 'lat'. You can override using
## the `.groups` argument.
world_map <- ne_countries(scale = "medium", returnclass = "sf")

ggplot() +
  geom_sf(data = world_map, fill = "gray90", color = "black") + 
  geom_point(data = city_flights, aes(x = lng, y = lat, color = total_flights), 
             alpha = 0.7, size = 3) + 
  theme_minimal() +
  labs(title = "Global Airports by Total Flights",
       subtitle = "Color represents the number of flights", color = "Total Flights")
## Warning: Removed 204 rows containing missing values or values outside the scale range
## (`geom_point()`).

European Airports by Total Flights

Here we keep only European cities and limit the map to the European region. Unfortunately, this was not very helpful due to the absence of latitude and longitude values for certain cities.

europe_cities <- city_flights %>%
  filter(lat >= 35, lat <= 72, lng >= -25, lng <= 40)  

ggplot() +
  geom_sf(data = world_map, fill = "gray90", color = "black") + 
  geom_point(data = europe_cities, aes(x = lng, y = lat, color = total_flights), 
             alpha = 0.7, size = 3) +  
  theme_bw() +
  labs(title = "European Airports by Total Flights",
       subtitle = "Color represents the number of flights", color = "Total Flights") +
  coord_sf(xlim = c(-25, 40), ylim = c(35, 72), expand = FALSE)  # Limit map to Europe

Conclusions

In this analysis of European flight data, we observed clear seasonal trends in air traffic. The total departures by year highlighted dominant countries in air travel. In a lot of graphs that we observed we can see clear decline in 2020 flights due to the impact of the COVID-19 pandemic. By refining the scope to European cities, we obtained more meaningful insights into air traffic patterns.