Let’s start by loading packages and reading the data:

library(ggplot2)
library(dplyr)
flights <- read.csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2022/2022-07-12/flights.csv')
head(flights)
##   YEAR MONTH_NUM MONTH_MON             FLT_DATE APT_ICAO             APT_NAME
## 1 2016         1       JAN 2016-01-01T00:00:00Z     EBAW              Antwerp
## 2 2016         1       JAN 2016-01-01T00:00:00Z     EBBR             Brussels
## 3 2016         1       JAN 2016-01-01T00:00:00Z     EBCI            Charleroi
## 4 2016         1       JAN 2016-01-01T00:00:00Z     EBLG               Liège
## 5 2016         1       JAN 2016-01-01T00:00:00Z     EBOS        Ostend-Bruges
## 6 2016         1       JAN 2016-01-01T00:00:00Z     EDDB Berlin - Brandenburg
##   STATE_NAME FLT_DEP_1 FLT_ARR_1 FLT_TOT_1 FLT_DEP_IFR_2 FLT_ARR_IFR_2
## 1    Belgium         4         3         7            NA            NA
## 2    Belgium       174       171       345           174           161
## 3    Belgium        45        47        92            45            45
## 4    Belgium         6         7        13            NA            NA
## 5    Belgium         7         7        14            NA            NA
## 6    Germany        98        99       197            NA            NA
##   FLT_TOT_IFR_2                 Pivot.Label
## 1            NA              Antwerp (EBAW)
## 2           335             Brussels (EBBR)
## 3            90            Charleroi (EBCI)
## 4            NA               Liège (EBLG)
## 5            NA        Ostend-Bruges (EBOS)
## 6            NA Berlin - Brandenburg (EDDB)
names(flights)
##  [1] "YEAR"          "MONTH_NUM"     "MONTH_MON"     "FLT_DATE"     
##  [5] "APT_ICAO"      "APT_NAME"      "STATE_NAME"    "FLT_DEP_1"    
##  [9] "FLT_ARR_1"     "FLT_TOT_1"     "FLT_DEP_IFR_2" "FLT_ARR_IFR_2"
## [13] "FLT_TOT_IFR_2" "Pivot.Label"

Let’s aggregate up to the country-month level.

flights %>% 
  group_by(STATE_NAME,YEAR,MONTH_NUM) %>% 
  dplyr::summarise(total_flights = sum(FLT_TOT_1,na.rm=T)) -> agg
## `summarise()` regrouping output by 'STATE_NAME', 'YEAR' (override with `.groups` argument)
head(agg)
## # A tibble: 6 x 4
## # Groups:   STATE_NAME, YEAR [1]
##   STATE_NAME  YEAR MONTH_NUM total_flights
##   <fct>      <int>     <int>         <int>
## 1 Albania     2016         1          1666
## 2 Albania     2016         2          1507
## 3 Albania     2016         3          1676
## 4 Albania     2016         4          1680
## 5 Albania     2016         5          1710
## 6 Albania     2016         6          1720

Get our date variable set up…

date_base <- paste0(agg$YEAR,"-",agg$MONTH_NUM,"-01")
agg$date <- as.Date(date_base,format = "%Y-%m-%d")

And plot the total flights over time:

agg %>% 
  ggplot(aes(x=date, y= log(total_flights), col = as.factor(STATE_NAME))) +
  geom_point() +
  geom_line() +
  theme_bw() +
  labs(col = "Year") +
  ylab("Log of Total Flights") +
  xlab("Date") +
  theme(legend.position = "none") +
  scale_x_date(date_breaks = "1 year", date_labels = "%Y") +
  ggtitle("Log of Total Flights by Country Over Time")

Something worth note is how, when placed on the log scale, the flights across country-months show remarkable parallel trends on average (if you squint you can see some with more severe cycles than others), both before and after the pandemic. This implies that countries have roughly stable “shares” of air transport, although this regime partially broke down when the pandemic rolled through and through the early period of the recovery (much more mixing in 2021). We also see the onset of the war in Ukraine (the one observation which drops to zero).

This can be visualized by taking a look at the total flight rankings over time, specifically the rank-variability post-2020.

agg$post_2020 <- agg$date >= "2020-01-01"

agg %>% 
  arrange(total_flights) %>% 
  group_by(YEAR,MONTH_NUM) %>%
  dplyr::mutate(rank = row_number()) -> agg

agg %>% 
  group_by(STATE_NAME,post_2020) %>% 
  dplyr::mutate(vrank = var(rank,na.rm=T)) -> agg

summary(agg)
##                   STATE_NAME        YEAR        MONTH_NUM     total_flights   
##  Albania               :  77   Min.   :2016   Min.   : 1.00   Min.   :     1  
##  Armenia               :  77   1st Qu.:2017   1st Qu.: 3.00   1st Qu.:  2816  
##  Austria               :  77   Median :2019   Median : 6.00   Median :  7688  
##  Belgium               :  77   Mean   :2019   Mean   : 6.27   Mean   : 27342  
##  Bosnia and Herzegovina:  77   3rd Qu.:2020   3rd Qu.: 9.00   3rd Qu.: 30024  
##  Bulgaria              :  77   Max.   :2022   Max.   :12.00   Max.   :215884  
##  (Other)               :2722                                                  
##       date            post_2020            rank           vrank        
##  Min.   :2016-01-01   Mode :logical   Min.   : 1.00   Min.   : 0.0000  
##  1st Qu.:2017-08-01   FALSE:1968      1st Qu.:11.00   1st Qu.: 0.7373  
##  Median :2019-04-01   TRUE :1216      Median :21.00   Median : 1.2301  
##  Mean   :2019-03-13                   Mean   :21.19   Mean   : 2.6713  
##  3rd Qu.:2020-10-08                   3rd Qu.:32.00   3rd Qu.: 2.8613  
##  Max.   :2022-05-01                   Max.   :42.00   Max.   :31.9236  
## 
high_var <- unique(agg[which(agg$vrank >= quantile(agg$vrank,0.9) & agg$post_2020 == T),"STATE_NAME"])

agg[,"high_var"] <- F
agg[which(agg$STATE_NAME %in% unlist(high_var)),"high_var"] <- T

Here is the messy pre-subsetting plot:

agg %>% 
  ggplot(aes(x=date,y=rank,col=STATE_NAME)) +
  geom_point() +
  geom_line() +
  theme_bw() +
  theme(legend.position = "none") +
  ylab("Rank Order of Total Flights") +
  xlab("Date") +
  scale_x_date(date_breaks = "1 year", date_labels = "%Y") +
  ggtitle("Flight Total Rank Orders")

That’s quite cluttered, but we can see a cluster of rank changes post 2020. Here is a little bit more focused view:

agg %>% 
  filter(high_var == T) %>% 
  ggplot(aes(x=date,y=rank,col=STATE_NAME)) +
  geom_point() +
  geom_line() +
  theme_bw() +
  ylab("Rank Order of Total Flights") +
  xlab("Date") +
  scale_x_date(date_breaks = "1 year", date_labels = "%Y") +
  labs(col = "State") +
  ggtitle("10% Most Variable Flight Total Rank Orders Post 2020")

What do we learn? Prior to 2020, many countries had either highly cyclical rankings or near constant rankings. After 2020 and through the recovery, the rankings of a number of states became highly variable, states like Bulgaria increasing their overall rank in the industry while states like Slovania suffered. Other states, like Greece and Cyprus, were less impacted in this way, their rankings seeming do depend on tourist schedules rather than a definite post-covid regime change.

LS0tDQp0aXRsZTogIlRpZHlUdWVzZGF5IDIwMjIsIFdlZWsgMjgiDQphdXRob3I6ICJDaHJpc3RvcGhlciBTY2h3YXJ6Ig0KZGF0ZTogIjcvMTIvMjAyMiINCnBhZ2VzOg0KICBleHRyYTogdHJ1ZQ0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KTGV0J3Mgc3RhcnQgYnkgbG9hZGluZyBwYWNrYWdlcyBhbmQgcmVhZGluZyB0aGUgZGF0YToNCg0KYGBge3IsIHdhcm5pbmc9RixtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkcGx5cikNCmBgYA0KDQpgYGB7cn0NCmZsaWdodHMgPC0gcmVhZC5jc3YoJ2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9yZm9yZGF0YXNjaWVuY2UvdGlkeXR1ZXNkYXkvbWFzdGVyL2RhdGEvMjAyMi8yMDIyLTA3LTEyL2ZsaWdodHMuY3N2JykNCmhlYWQoZmxpZ2h0cykNCm5hbWVzKGZsaWdodHMpDQpgYGANCg0KTGV0J3MgYWdncmVnYXRlIHVwIHRvIHRoZSBjb3VudHJ5LW1vbnRoIGxldmVsLg0KDQpgYGB7cn0NCmZsaWdodHMgJT4lIA0KICBncm91cF9ieShTVEFURV9OQU1FLFlFQVIsTU9OVEhfTlVNKSAlPiUgDQogIGRwbHlyOjpzdW1tYXJpc2UodG90YWxfZmxpZ2h0cyA9IHN1bShGTFRfVE9UXzEsbmEucm09VCkpIC0+IGFnZw0KDQpoZWFkKGFnZykNCmBgYA0KDQpHZXQgb3VyIGRhdGUgdmFyaWFibGUgc2V0IHVwLi4uDQoNCmBgYHtyfQ0KZGF0ZV9iYXNlIDwtIHBhc3RlMChhZ2ckWUVBUiwiLSIsYWdnJE1PTlRIX05VTSwiLTAxIikNCmFnZyRkYXRlIDwtIGFzLkRhdGUoZGF0ZV9iYXNlLGZvcm1hdCA9ICIlWS0lbS0lZCIpDQpgYGANCg0KQW5kIHBsb3QgdGhlIHRvdGFsIGZsaWdodHMgb3ZlciB0aW1lOg0KDQpgYGB7cn0NCmFnZyAlPiUgDQogIGdncGxvdChhZXMoeD1kYXRlLCB5PSBsb2codG90YWxfZmxpZ2h0cyksIGNvbCA9IGFzLmZhY3RvcihTVEFURV9OQU1FKSkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9saW5lKCkgKw0KICB0aGVtZV9idygpICsNCiAgbGFicyhjb2wgPSAiWWVhciIpICsNCiAgeWxhYigiTG9nIG9mIFRvdGFsIEZsaWdodHMiKSArDQogIHhsYWIoIkRhdGUiKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMSB5ZWFyIiwgZGF0ZV9sYWJlbHMgPSAiJVkiKSArDQogIGdndGl0bGUoIkxvZyBvZiBUb3RhbCBGbGlnaHRzIGJ5IENvdW50cnkgT3ZlciBUaW1lIikNCiAgDQpgYGANCg0KU29tZXRoaW5nIHdvcnRoIG5vdGUgaXMgaG93LCB3aGVuIHBsYWNlZCBvbiB0aGUgbG9nIHNjYWxlLCB0aGUgZmxpZ2h0cyBhY3Jvc3MgY291bnRyeS1tb250aHMgc2hvdyByZW1hcmthYmxlIHBhcmFsbGVsIHRyZW5kcyBvbiBhdmVyYWdlIChpZiB5b3Ugc3F1aW50IHlvdSBjYW4gc2VlIHNvbWUgd2l0aCBtb3JlIHNldmVyZSBjeWNsZXMgdGhhbiBvdGhlcnMpLCBib3RoIGJlZm9yZSBhbmQgYWZ0ZXIgdGhlIHBhbmRlbWljLiAgVGhpcyBpbXBsaWVzIHRoYXQgY291bnRyaWVzIGhhdmUgcm91Z2hseSBzdGFibGUgInNoYXJlcyIgb2YgYWlyIHRyYW5zcG9ydCwgYWx0aG91Z2ggdGhpcyByZWdpbWUgcGFydGlhbGx5IGJyb2tlIGRvd24gd2hlbiB0aGUgcGFuZGVtaWMgcm9sbGVkIHRocm91Z2ggYW5kIHRocm91Z2ggdGhlIGVhcmx5IHBlcmlvZCBvZiB0aGUgcmVjb3ZlcnkgKG11Y2ggbW9yZSBtaXhpbmcgaW4gMjAyMSkuICBXZSBhbHNvIHNlZSB0aGUgb25zZXQgb2YgdGhlIHdhciBpbiBVa3JhaW5lICh0aGUgb25lIG9ic2VydmF0aW9uIHdoaWNoIGRyb3BzIHRvIHplcm8pLg0KDQpUaGlzIGNhbiBiZSB2aXN1YWxpemVkIGJ5IHRha2luZyBhIGxvb2sgYXQgdGhlIHRvdGFsIGZsaWdodCByYW5raW5ncyBvdmVyIHRpbWUsIHNwZWNpZmljYWxseSB0aGUgcmFuay12YXJpYWJpbGl0eSBwb3N0LTIwMjAuDQoNCmBgYHtyfQ0KYWdnJHBvc3RfMjAyMCA8LSBhZ2ckZGF0ZSA+PSAiMjAyMC0wMS0wMSINCg0KYWdnICU+JSANCiAgYXJyYW5nZSh0b3RhbF9mbGlnaHRzKSAlPiUgDQogIGdyb3VwX2J5KFlFQVIsTU9OVEhfTlVNKSAlPiUNCiAgZHBseXI6Om11dGF0ZShyYW5rID0gcm93X251bWJlcigpKSAtPiBhZ2cNCg0KYWdnICU+JSANCiAgZ3JvdXBfYnkoU1RBVEVfTkFNRSxwb3N0XzIwMjApICU+JSANCiAgZHBseXI6Om11dGF0ZSh2cmFuayA9IHZhcihyYW5rLG5hLnJtPVQpKSAtPiBhZ2cNCg0Kc3VtbWFyeShhZ2cpDQoNCmhpZ2hfdmFyIDwtIHVuaXF1ZShhZ2dbd2hpY2goYWdnJHZyYW5rID49IHF1YW50aWxlKGFnZyR2cmFuaywwLjkpICYgYWdnJHBvc3RfMjAyMCA9PSBUKSwiU1RBVEVfTkFNRSJdKQ0KDQphZ2dbLCJoaWdoX3ZhciJdIDwtIEYNCmFnZ1t3aGljaChhZ2ckU1RBVEVfTkFNRSAlaW4lIHVubGlzdChoaWdoX3ZhcikpLCJoaWdoX3ZhciJdIDwtIFQNCg0KYGBgDQoNCkhlcmUgaXMgdGhlIG1lc3N5IHByZS1zdWJzZXR0aW5nIHBsb3Q6DQoNCmBgYHtyfQ0KYWdnICU+JSANCiAgZ2dwbG90KGFlcyh4PWRhdGUseT1yYW5rLGNvbD1TVEFURV9OQU1FKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX2xpbmUoKSArDQogIHRoZW1lX2J3KCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgeWxhYigiUmFuayBPcmRlciBvZiBUb3RhbCBGbGlnaHRzIikgKw0KICB4bGFiKCJEYXRlIikgKw0KICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMSB5ZWFyIiwgZGF0ZV9sYWJlbHMgPSAiJVkiKSArDQogIGdndGl0bGUoIkZsaWdodCBUb3RhbCBSYW5rIE9yZGVycyIpDQpgYGANCg0KVGhhdCdzIHF1aXRlIGNsdXR0ZXJlZCwgYnV0IHdlIGNhbiBzZWUgYSBjbHVzdGVyIG9mIHJhbmsgY2hhbmdlcyBwb3N0IDIwMjAuICBIZXJlIGlzIGEgbGl0dGxlIGJpdCBtb3JlIGZvY3VzZWQgdmlldzoNCg0KYGBge3J9DQphZ2cgJT4lIA0KICBmaWx0ZXIoaGlnaF92YXIgPT0gVCkgJT4lIA0KICBnZ3Bsb3QoYWVzKHg9ZGF0ZSx5PXJhbmssY29sPVNUQVRFX05BTUUpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZSgpICsNCiAgdGhlbWVfYncoKSArDQogIHlsYWIoIlJhbmsgT3JkZXIgb2YgVG90YWwgRmxpZ2h0cyIpICsNCiAgeGxhYigiRGF0ZSIpICsNCiAgc2NhbGVfeF9kYXRlKGRhdGVfYnJlYWtzID0gIjEgeWVhciIsIGRhdGVfbGFiZWxzID0gIiVZIikgKw0KICBsYWJzKGNvbCA9ICJTdGF0ZSIpICsNCiAgZ2d0aXRsZSgiMTAlIE1vc3QgVmFyaWFibGUgRmxpZ2h0IFRvdGFsIFJhbmsgT3JkZXJzIFBvc3QgMjAyMCIpDQpgYGANCg0KV2hhdCBkbyB3ZSBsZWFybj8gIFByaW9yIHRvIDIwMjAsIG1hbnkgY291bnRyaWVzIGhhZCBlaXRoZXIgaGlnaGx5IGN5Y2xpY2FsIHJhbmtpbmdzIG9yIG5lYXIgY29uc3RhbnQgcmFua2luZ3MuICBBZnRlciAyMDIwIGFuZCB0aHJvdWdoIHRoZSByZWNvdmVyeSwgdGhlIHJhbmtpbmdzIG9mIGEgbnVtYmVyIG9mIHN0YXRlcyBiZWNhbWUgaGlnaGx5IHZhcmlhYmxlLCBzdGF0ZXMgbGlrZSBCdWxnYXJpYSBpbmNyZWFzaW5nIHRoZWlyIG92ZXJhbGwgcmFuayBpbiB0aGUgaW5kdXN0cnkgd2hpbGUgc3RhdGVzIGxpa2UgU2xvdmFuaWEgc3VmZmVyZWQuICBPdGhlciBzdGF0ZXMsIGxpa2UgR3JlZWNlIGFuZCBDeXBydXMsIHdlcmUgbGVzcyBpbXBhY3RlZCBpbiB0aGlzIHdheSwgdGhlaXIgcmFua2luZ3Mgc2VlbWluZyBkbyBkZXBlbmQgb24gdG91cmlzdCBzY2hlZHVsZXMgcmF0aGVyIHRoYW4gYSBkZWZpbml0ZSBwb3N0LWNvdmlkIHJlZ2ltZSBjaGFuZ2UuDQo=