library(tidyverse)
library(openintro)
library(tidyr)
library(dplyr)
library(data.table)
library(glue)
library(gridExtra)

Overview

This lab provides practice for handling untidy data. It is sometimes easier to manually fix some issues from a dataset, but R is powerful enough to handle this process for us. This invaluable for larger datasets.

Load data

Start by loading the hosted csv. In this case, R assigned V1 and V2 to the column names without headers. This is fine as we will change them to more useful names while tidying.

flight_delays_url <- 'https://raw.githubusercontent.com/Megabuster/Data607/refs/heads/main/data/assignment5/flight_delays.csv'
raw_data <- fread(flight_delays_url)

Tidying the data

There was a blank line in the original data. The function “fread” is not able to handle that and instead inserted a row filled with “NA”s. Remove this line first because it is useless and impacts other mutations.

untidy_flight_df <- raw_data %>% 
  filter_all(all_vars(!is.na(.)))
untidy_flight_df
##         V1      V2 Los Angeles Phoenix San Diego San Francisco Seattle
##     <char>  <char>       <int>   <int>     <int>         <int>   <int>
## 1:  ALASKA on time         497     221       212           503    1841
## 2:         delayed          62      12        20           102     305
## 3: AM WEST on time         694    4840       383           320     201
## 4:         delayed         117     415        65           129      61

A major issue with this data is that it is in a wide form. This is easy to read on a spreadsheet, but does not work well with many R functions. We can start by collecting each city into a single “city” column using the “melt” function.

melt_flight_df <- untidy_flight_df %>% 
  melt(id.vars = c(colnames(untidy_flight_df)[1:2]), variable.name = 'city', value.name = 'total_flights')
melt_flight_df
##          V1      V2          city total_flights
##      <char>  <char>        <fctr>         <int>
##  1:  ALASKA on time   Los Angeles           497
##  2:         delayed   Los Angeles            62
##  3: AM WEST on time   Los Angeles           694
##  4:         delayed   Los Angeles           117
##  5:  ALASKA on time       Phoenix           221
##  6:         delayed       Phoenix            12
##  7: AM WEST on time       Phoenix          4840
##  8:         delayed       Phoenix           415
##  9:  ALASKA on time     San Diego           212
## 10:         delayed     San Diego            20
## 11: AM WEST on time     San Diego           383
## 12:         delayed     San Diego            65
## 13:  ALASKA on time San Francisco           503
## 14:         delayed San Francisco           102
## 15: AM WEST on time San Francisco           320
## 16:         delayed San Francisco           129
## 17:  ALASKA on time       Seattle          1841
## 18:         delayed       Seattle           305
## 19: AM WEST on time       Seattle           201
## 20:         delayed       Seattle            61
##          V1      V2          city total_flights

V1 and V2 also need more meaningful names. Let’s rename them to airline and status respectively.

rename_flight_df <- melt_flight_df %>% rename(
  airline = V1,
  status = V2
)
rename_flight_df
##     airline  status          city total_flights
##      <char>  <char>        <fctr>         <int>
##  1:  ALASKA on time   Los Angeles           497
##  2:         delayed   Los Angeles            62
##  3: AM WEST on time   Los Angeles           694
##  4:         delayed   Los Angeles           117
##  5:  ALASKA on time       Phoenix           221
##  6:         delayed       Phoenix            12
##  7: AM WEST on time       Phoenix          4840
##  8:         delayed       Phoenix           415
##  9:  ALASKA on time     San Diego           212
## 10:         delayed     San Diego            20
## 11: AM WEST on time     San Diego           383
## 12:         delayed     San Diego            65
## 13:  ALASKA on time San Francisco           503
## 14:         delayed San Francisco           102
## 15: AM WEST on time San Francisco           320
## 16:         delayed San Francisco           129
## 17:  ALASKA on time       Seattle          1841
## 18:         delayed       Seattle           305
## 19: AM WEST on time       Seattle           201
## 20:         delayed       Seattle            61
##     airline  status          city total_flights

Fill in missing data

These columns now have meaningful names and the rows are based on each set of total flights. One last issue is that the airline columns is missing every other entry. This is because visually we can assume that if the on time airline was Alaska, the following delayed airline entry is also Alaska. Since R does not innately do this inference, we need to fill in those cells. The initial creation of the data sets the blanks in airline to an empty character instead of “NA”. One way to work with this is to mutate those values to “NA” so that the “fill” function can fill in those missing values with the previous one. The dataframe is finally tidy.

tidy_flight_df <- rename_flight_df %>%
  mutate(airline = if_else(airline == '', NA, airline)) %>%
    fill(airline)
tidy_flight_df
##     airline  status          city total_flights
##      <char>  <char>        <fctr>         <int>
##  1:  ALASKA on time   Los Angeles           497
##  2:  ALASKA delayed   Los Angeles            62
##  3: AM WEST on time   Los Angeles           694
##  4: AM WEST delayed   Los Angeles           117
##  5:  ALASKA on time       Phoenix           221
##  6:  ALASKA delayed       Phoenix            12
##  7: AM WEST on time       Phoenix          4840
##  8: AM WEST delayed       Phoenix           415
##  9:  ALASKA on time     San Diego           212
## 10:  ALASKA delayed     San Diego            20
## 11: AM WEST on time     San Diego           383
## 12: AM WEST delayed     San Diego            65
## 13:  ALASKA on time San Francisco           503
## 14:  ALASKA delayed San Francisco           102
## 15: AM WEST on time San Francisco           320
## 16: AM WEST delayed San Francisco           129
## 17:  ALASKA on time       Seattle          1841
## 18:  ALASKA delayed       Seattle           305
## 19: AM WEST on time       Seattle           201
## 20: AM WEST delayed       Seattle            61
##     airline  status          city total_flights

Compute airline delays

Now it’s time to analyze the tidied up data. We want to compare the arrival delays for the two airlines. Let’s compute the number of flights where status is delayed and the total flights. Get the percentage of delayed flights using delayed_flights / total_flights for both airlines. Compile the calculated data into a dataframe.

total_alaska_delays <- tidy_flight_df %>%
  filter(airline == 'ALASKA' & status == 'delayed') %>% with(sum(total_flights))
total_alaska_flights <- tidy_flight_df %>% 
  filter(airline == 'ALASKA') %>% with(sum(total_flights))
total_alaska_delay_rate = total_alaska_delays / total_alaska_flights

total_am_west_delays <- tidy_flight_df %>%
  filter(airline == 'AM WEST' & status == 'delayed') %>% with(sum(total_flights))
total_am_west_flights <- tidy_flight_df %>% 
  filter(airline == 'AM WEST') %>% with(sum(total_flights))
total_am_west_delay_rate = total_am_west_delays / total_am_west_flights

delay_rates_df <- data.frame(
  airline = c('ALASKA', 'AM WEST'),
  delay_rate = c(total_alaska_delay_rate, total_am_west_delay_rate)
)
delay_rates_df
##   airline delay_rate
## 1  ALASKA  0.1327152
## 2 AM WEST  0.1089273

Analysis of airline delays

Alaska has a higher overall delay rate of about 13% than AM West with about 11%. A bar plot is a convenient way to show this since we can examine the bars side by side.

delay_rates_df %>% ggplot(aes(x = airline, y = delay_rate)) +
  geom_bar(stat = 'identity') +
  labs(title = 'Delay Rate by Airline')

Airline delays by city

Let’s also compare how both airlines perform in each city. Since we are reusing this code, it’s better to turn it into a function which we will call “calc_flight_rate” which accepts two filter arguments and divides their results. Loop through all combinations of cities and flights to calculate delay rates using this new function.

calc_flight_rate <- function(filter_numer, filter_denom) {
  filtered_flights <- tidy_flight_df %>%
    filter(eval(parse(text=filter_numer))) %>% with(sum(total_flights))
  total_flights <- tidy_flight_df %>%
    filter(eval(parse(text=filter_denom))) %>% with(sum(total_flights))
  total_flight_rate = filtered_flights / total_flights
  return (total_flight_rate)
}

city_delays_col <- c('city', 'airline', 'delay_rate')
city_delays_df <- data.frame(
  matrix(
    nrow = 0,
    ncol = length(city_delays_col)
  )
)

for (city_name in unique(tidy_flight_df$city)) {
  for (airline_name in unique(tidy_flight_df$airline)) {
    filter_arg_delay <- glue("airline == '{airline_name}' & status == 'delayed' & city == '{city_name}'")
    filter_arg_total <- glue("airline == '{airline_name}' & city == '{city_name}'")
    rate <- calc_flight_rate(filter_arg_delay, filter_arg_total)
    city_delays_df <- rbind(city_delays_df, list(city_name,airline_name,rate))
  }
}

colnames(city_delays_df) <- city_delays_col
city_delays_df
##             city airline delay_rate
## 1    Los Angeles  ALASKA 0.11091234
## 2    Los Angeles AM WEST 0.14426634
## 3        Phoenix  ALASKA 0.05150215
## 4        Phoenix AM WEST 0.07897241
## 5      San Diego  ALASKA 0.08620690
## 6      San Diego AM WEST 0.14508929
## 7  San Francisco  ALASKA 0.16859504
## 8  San Francisco AM WEST 0.28730512
## 9        Seattle  ALASKA 0.14212488
## 10       Seattle AM WEST 0.23282443

Let’s visualize the trends with another bar plot. Delay rates for each airline are shown side by side and separated by city. This plot definitively shows that for every city, AM West has a higher delay rate. This looks like a massive contradiction with the earlier data. What is going on with this data?

ggplot(city_delays_df, aes(x = city, y = delay_rate, fill = airline)) +
  geom_bar(stat = 'identity', position = 'dodge') +
    labs(title = 'Delay Rate by Airline and City')

Checking the total flights per city for both airlines adds some insight into this issue. Taking the total flights for each city and airline combination while ignoring the delay status, we get a flights per city table for both airlines. The table implies that AM West’s data are weighted toward Phoenix where it had a delay percentage of 7.9%. Alaska’s data are mostly driven by its performance in Seattle with a delay percentage of 14.2%. Despite Alaska winning the head to head lower delay percentage in every city, it is unfortunately penalized for deriving more of its delay rate from its second worst delay city (only behind San Francisco).

tidy_flight_df %>% group_by(airline, city) %>% summarise(total_flights = sum(total_flights), .groups = 'drop') %>%
  arrange(desc(total_flights))
## # A tibble: 10 × 3
##    airline city          total_flights
##    <chr>   <fct>                 <int>
##  1 AM WEST Phoenix                5255
##  2 ALASKA  Seattle                2146
##  3 AM WEST Los Angeles             811
##  4 ALASKA  San Francisco           605
##  5 ALASKA  Los Angeles             559
##  6 AM WEST San Francisco           449
##  7 AM WEST San Diego               448
##  8 AM WEST Seattle                 262
##  9 ALASKA  Phoenix                 233
## 10 ALASKA  San Diego               232
city_delays_df %>% 
  filter(airline == 'AM WEST' & city == 'Phoenix' | airline == 'ALASKA' & city == 'Seattle')
##      city airline delay_rate
## 1 Phoenix AM WEST 0.07897241
## 2 Seattle  ALASKA 0.14212488

Conclusions

The original input data was very easy to read, but was not tidy for a data frame. Having individual cities as columns was the most egregious issue. It is better to keep data in a tidy long form and adapt as needed.

The city specific analysis was especially eye opening. It was easy to declare AM West the more reliable airline because of a better overall delay rate. However, more granular data showed that Alaska consistently outperformed AM West in every single city.

Further work into solving this conundrum could include accounting for each airline having different amounts of flights in each city.

LS0tDQp0aXRsZTogIkFzc2lnbm1lbnQgNSAtIFRpZHlpbmcgYW5kIFRyYW5zZm9ybWluZyBEYXRhIg0KYXV0aG9yOiAiTGF3cmVuY2UgWXUiDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpvdXRwdXQ6IG9wZW5pbnRybzo6bGFiX3JlcG9ydA0KLS0tDQoNCmBgYHtyIGxvYWQtcGFja2FnZXMsIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkob3BlbmludHJvKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGRhdGEudGFibGUpDQpsaWJyYXJ5KGdsdWUpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmBgYA0KDQojIyMgT3ZlcnZpZXcNClRoaXMgbGFiIHByb3ZpZGVzIHByYWN0aWNlIGZvciBoYW5kbGluZyB1bnRpZHkgZGF0YS4gSXQgaXMgc29tZXRpbWVzIGVhc2llciB0byBtYW51YWxseSBmaXggc29tZSBpc3N1ZXMgZnJvbSBhIGRhdGFzZXQsIGJ1dCBSIGlzIHBvd2VyZnVsIGVub3VnaCB0byBoYW5kbGUgdGhpcyBwcm9jZXNzIGZvciB1cy4gVGhpcyBpbnZhbHVhYmxlIGZvciBsYXJnZXIgZGF0YXNldHMuDQoNCiMjIyBMb2FkIGRhdGENCg0KU3RhcnQgYnkgbG9hZGluZyB0aGUgaG9zdGVkIGNzdi4gSW4gdGhpcyBjYXNlLCBSIGFzc2lnbmVkIFYxIGFuZCBWMiB0byB0aGUgY29sdW1uIG5hbWVzIHdpdGhvdXQgaGVhZGVycy4gVGhpcyBpcyBmaW5lIGFzIHdlIHdpbGwgY2hhbmdlIHRoZW0gdG8gbW9yZSB1c2VmdWwgbmFtZXMgd2hpbGUgdGlkeWluZy4gDQpgYGB7ciBjb2RlLWNodW5rLWxhYmVsfQ0KZmxpZ2h0X2RlbGF5c191cmwgPC0gJ2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9NZWdhYnVzdGVyL0RhdGE2MDcvcmVmcy9oZWFkcy9tYWluL2RhdGEvYXNzaWdubWVudDUvZmxpZ2h0X2RlbGF5cy5jc3YnDQpyYXdfZGF0YSA8LSBmcmVhZChmbGlnaHRfZGVsYXlzX3VybCkNCmBgYA0KIyMjIFRpZHlpbmcgdGhlIGRhdGENCg0KVGhlcmUgd2FzIGEgYmxhbmsgbGluZSBpbiB0aGUgb3JpZ2luYWwgZGF0YS4gVGhlIGZ1bmN0aW9uICJmcmVhZCIgaXMgbm90IGFibGUgdG8gaGFuZGxlIHRoYXQgYW5kIGluc3RlYWQgaW5zZXJ0ZWQgYSByb3cgZmlsbGVkIHdpdGggIk5BInMuIFJlbW92ZSB0aGlzIGxpbmUgZmlyc3QgYmVjYXVzZSBpdCBpcyB1c2VsZXNzIGFuZCBpbXBhY3RzIG90aGVyIG11dGF0aW9ucy4NCmBgYHtyIHJlbW92ZS1ibGFuay1saW5lfQ0KdW50aWR5X2ZsaWdodF9kZiA8LSByYXdfZGF0YSAlPiUgDQogIGZpbHRlcl9hbGwoYWxsX3ZhcnMoIWlzLm5hKC4pKSkNCnVudGlkeV9mbGlnaHRfZGYNCmBgYA0KDQpBIG1ham9yIGlzc3VlIHdpdGggdGhpcyBkYXRhIGlzIHRoYXQgaXQgaXMgaW4gYSB3aWRlIGZvcm0uIFRoaXMgaXMgZWFzeSB0byByZWFkIG9uIGEgc3ByZWFkc2hlZXQsIGJ1dCBkb2VzIG5vdCB3b3JrIHdlbGwgd2l0aCBtYW55IFIgZnVuY3Rpb25zLiBXZSBjYW4gc3RhcnQgYnkgY29sbGVjdGluZyBlYWNoIGNpdHkgaW50byBhIHNpbmdsZSAiY2l0eSIgY29sdW1uIHVzaW5nIHRoZSAibWVsdCIgZnVuY3Rpb24uIA0KYGBge3Igd2lkZS10by1sb25nfQ0KbWVsdF9mbGlnaHRfZGYgPC0gdW50aWR5X2ZsaWdodF9kZiAlPiUgDQogIG1lbHQoaWQudmFycyA9IGMoY29sbmFtZXModW50aWR5X2ZsaWdodF9kZilbMToyXSksIHZhcmlhYmxlLm5hbWUgPSAnY2l0eScsIHZhbHVlLm5hbWUgPSAndG90YWxfZmxpZ2h0cycpDQptZWx0X2ZsaWdodF9kZg0KYGBgDQoNClYxIGFuZCBWMiBhbHNvIG5lZWQgbW9yZSBtZWFuaW5nZnVsIG5hbWVzLiBMZXQncyByZW5hbWUgdGhlbSB0byBhaXJsaW5lIGFuZCBzdGF0dXMgcmVzcGVjdGl2ZWx5LiANCmBgYHtyIHJlbmFtZS1jb2x1bW5zfQ0KcmVuYW1lX2ZsaWdodF9kZiA8LSBtZWx0X2ZsaWdodF9kZiAlPiUgcmVuYW1lKA0KICBhaXJsaW5lID0gVjEsDQogIHN0YXR1cyA9IFYyDQopDQpyZW5hbWVfZmxpZ2h0X2RmDQpgYGANCg0KIyMjIEZpbGwgaW4gbWlzc2luZyBkYXRhDQoNClRoZXNlIGNvbHVtbnMgbm93IGhhdmUgbWVhbmluZ2Z1bCBuYW1lcyBhbmQgdGhlIHJvd3MgYXJlIGJhc2VkIG9uIGVhY2ggc2V0IG9mIHRvdGFsIGZsaWdodHMuIE9uZSBsYXN0IGlzc3VlIGlzIHRoYXQgdGhlIGFpcmxpbmUgY29sdW1ucyBpcyBtaXNzaW5nIGV2ZXJ5IG90aGVyIGVudHJ5LiBUaGlzIGlzIGJlY2F1c2UgdmlzdWFsbHkgd2UgY2FuIGFzc3VtZSB0aGF0IGlmIHRoZSBvbiB0aW1lIGFpcmxpbmUgd2FzIEFsYXNrYSwgdGhlIGZvbGxvd2luZyBkZWxheWVkIGFpcmxpbmUgZW50cnkgaXMgYWxzbyBBbGFza2EuIFNpbmNlIFIgZG9lcyBub3QgaW5uYXRlbHkgZG8gdGhpcyBpbmZlcmVuY2UsIHdlIG5lZWQgdG8gZmlsbCBpbiB0aG9zZSBjZWxscy4gVGhlIGluaXRpYWwgY3JlYXRpb24gb2YgdGhlIGRhdGEgc2V0cyB0aGUgYmxhbmtzIGluIGFpcmxpbmUgdG8gYW4gZW1wdHkgY2hhcmFjdGVyIGluc3RlYWQgb2YgIk5BIi4gT25lIHdheSB0byB3b3JrIHdpdGggdGhpcyBpcyB0byBtdXRhdGUgdGhvc2UgdmFsdWVzIHRvICJOQSIgc28gdGhhdCB0aGUgImZpbGwiIGZ1bmN0aW9uIGNhbiBmaWxsIGluIHRob3NlIG1pc3NpbmcgdmFsdWVzIHdpdGggdGhlIHByZXZpb3VzIG9uZS4gVGhlIGRhdGFmcmFtZSBpcyBmaW5hbGx5IHRpZHkuDQpgYGB7ciBmaWxsLWluLW1pc3NpbmctY2VsbHN9DQp0aWR5X2ZsaWdodF9kZiA8LSByZW5hbWVfZmxpZ2h0X2RmICU+JQ0KICBtdXRhdGUoYWlybGluZSA9IGlmX2Vsc2UoYWlybGluZSA9PSAnJywgTkEsIGFpcmxpbmUpKSAlPiUNCiAgICBmaWxsKGFpcmxpbmUpDQp0aWR5X2ZsaWdodF9kZg0KYGBgDQoNCiMjIyBDb21wdXRlIGFpcmxpbmUgZGVsYXlzDQoNCk5vdyBpdCdzIHRpbWUgdG8gYW5hbHl6ZSB0aGUgdGlkaWVkIHVwIGRhdGEuIFdlIHdhbnQgdG8gY29tcGFyZSB0aGUgYXJyaXZhbCBkZWxheXMgZm9yIHRoZSB0d28gYWlybGluZXMuIExldCdzIGNvbXB1dGUgdGhlIG51bWJlciBvZiBmbGlnaHRzIHdoZXJlIHN0YXR1cyBpcyBkZWxheWVkIGFuZCB0aGUgdG90YWwgZmxpZ2h0cy4gR2V0IHRoZSBwZXJjZW50YWdlIG9mIGRlbGF5ZWQgZmxpZ2h0cyB1c2luZyBkZWxheWVkX2ZsaWdodHMgLyB0b3RhbF9mbGlnaHRzIGZvciBib3RoIGFpcmxpbmVzLiBDb21waWxlIHRoZSBjYWxjdWxhdGVkIGRhdGEgaW50byBhIGRhdGFmcmFtZS4NCmBgYHtyIGNvbXBhcmUtYWlybGluZS1kZWxheXN9DQp0b3RhbF9hbGFza2FfZGVsYXlzIDwtIHRpZHlfZmxpZ2h0X2RmICU+JQ0KICBmaWx0ZXIoYWlybGluZSA9PSAnQUxBU0tBJyAmIHN0YXR1cyA9PSAnZGVsYXllZCcpICU+JSB3aXRoKHN1bSh0b3RhbF9mbGlnaHRzKSkNCnRvdGFsX2FsYXNrYV9mbGlnaHRzIDwtIHRpZHlfZmxpZ2h0X2RmICU+JSANCiAgZmlsdGVyKGFpcmxpbmUgPT0gJ0FMQVNLQScpICU+JSB3aXRoKHN1bSh0b3RhbF9mbGlnaHRzKSkNCnRvdGFsX2FsYXNrYV9kZWxheV9yYXRlID0gdG90YWxfYWxhc2thX2RlbGF5cyAvIHRvdGFsX2FsYXNrYV9mbGlnaHRzDQoNCnRvdGFsX2FtX3dlc3RfZGVsYXlzIDwtIHRpZHlfZmxpZ2h0X2RmICU+JQ0KICBmaWx0ZXIoYWlybGluZSA9PSAnQU0gV0VTVCcgJiBzdGF0dXMgPT0gJ2RlbGF5ZWQnKSAlPiUgd2l0aChzdW0odG90YWxfZmxpZ2h0cykpDQp0b3RhbF9hbV93ZXN0X2ZsaWdodHMgPC0gdGlkeV9mbGlnaHRfZGYgJT4lIA0KICBmaWx0ZXIoYWlybGluZSA9PSAnQU0gV0VTVCcpICU+JSB3aXRoKHN1bSh0b3RhbF9mbGlnaHRzKSkNCnRvdGFsX2FtX3dlc3RfZGVsYXlfcmF0ZSA9IHRvdGFsX2FtX3dlc3RfZGVsYXlzIC8gdG90YWxfYW1fd2VzdF9mbGlnaHRzDQoNCmRlbGF5X3JhdGVzX2RmIDwtIGRhdGEuZnJhbWUoDQogIGFpcmxpbmUgPSBjKCdBTEFTS0EnLCAnQU0gV0VTVCcpLA0KICBkZWxheV9yYXRlID0gYyh0b3RhbF9hbGFza2FfZGVsYXlfcmF0ZSwgdG90YWxfYW1fd2VzdF9kZWxheV9yYXRlKQ0KKQ0KZGVsYXlfcmF0ZXNfZGYNCmBgYA0KDQojIyMgQW5hbHlzaXMgb2YgYWlybGluZSBkZWxheXMNCg0KQWxhc2thIGhhcyBhIGhpZ2hlciBvdmVyYWxsIGRlbGF5IHJhdGUgb2YgYWJvdXQgMTMlIHRoYW4gQU0gV2VzdCB3aXRoIGFib3V0IDExJS4gQSBiYXIgcGxvdCBpcyBhIGNvbnZlbmllbnQgd2F5IHRvIHNob3cgdGhpcyBzaW5jZSB3ZSBjYW4gZXhhbWluZSB0aGUgYmFycyBzaWRlIGJ5IHNpZGUuIA0KYGBge3IgcGxvdC1kZWxheXN9DQpkZWxheV9yYXRlc19kZiAlPiUgZ2dwbG90KGFlcyh4ID0gYWlybGluZSwgeSA9IGRlbGF5X3JhdGUpKSArDQogIGdlb21fYmFyKHN0YXQgPSAnaWRlbnRpdHknKSArDQogIGxhYnModGl0bGUgPSAnRGVsYXkgUmF0ZSBieSBBaXJsaW5lJykNCmBgYA0KDQojIyMgQWlybGluZSBkZWxheXMgYnkgY2l0eQ0KDQpMZXQncyBhbHNvIGNvbXBhcmUgaG93IGJvdGggYWlybGluZXMgcGVyZm9ybSBpbiBlYWNoIGNpdHkuIFNpbmNlIHdlIGFyZSByZXVzaW5nIHRoaXMgY29kZSwgaXQncyBiZXR0ZXIgdG8gdHVybiBpdCBpbnRvIGEgZnVuY3Rpb24gd2hpY2ggd2Ugd2lsbCBjYWxsICJjYWxjX2ZsaWdodF9yYXRlIiB3aGljaCBhY2NlcHRzIHR3byBmaWx0ZXIgYXJndW1lbnRzIGFuZCBkaXZpZGVzIHRoZWlyIHJlc3VsdHMuIExvb3AgdGhyb3VnaCBhbGwgY29tYmluYXRpb25zIG9mIGNpdGllcyBhbmQgZmxpZ2h0cyB0byBjYWxjdWxhdGUgZGVsYXkgcmF0ZXMgdXNpbmcgdGhpcyBuZXcgZnVuY3Rpb24uDQoNCmBgYHtyIGRlbGF5cy1ieS1jaXR5fQ0KY2FsY19mbGlnaHRfcmF0ZSA8LSBmdW5jdGlvbihmaWx0ZXJfbnVtZXIsIGZpbHRlcl9kZW5vbSkgew0KICBmaWx0ZXJlZF9mbGlnaHRzIDwtIHRpZHlfZmxpZ2h0X2RmICU+JQ0KICAgIGZpbHRlcihldmFsKHBhcnNlKHRleHQ9ZmlsdGVyX251bWVyKSkpICU+JSB3aXRoKHN1bSh0b3RhbF9mbGlnaHRzKSkNCiAgdG90YWxfZmxpZ2h0cyA8LSB0aWR5X2ZsaWdodF9kZiAlPiUNCiAgICBmaWx0ZXIoZXZhbChwYXJzZSh0ZXh0PWZpbHRlcl9kZW5vbSkpKSAlPiUgd2l0aChzdW0odG90YWxfZmxpZ2h0cykpDQogIHRvdGFsX2ZsaWdodF9yYXRlID0gZmlsdGVyZWRfZmxpZ2h0cyAvIHRvdGFsX2ZsaWdodHMNCiAgcmV0dXJuICh0b3RhbF9mbGlnaHRfcmF0ZSkNCn0NCg0KY2l0eV9kZWxheXNfY29sIDwtIGMoJ2NpdHknLCAnYWlybGluZScsICdkZWxheV9yYXRlJykNCmNpdHlfZGVsYXlzX2RmIDwtIGRhdGEuZnJhbWUoDQogIG1hdHJpeCgNCiAgICBucm93ID0gMCwNCiAgICBuY29sID0gbGVuZ3RoKGNpdHlfZGVsYXlzX2NvbCkNCiAgKQ0KKQ0KDQpmb3IgKGNpdHlfbmFtZSBpbiB1bmlxdWUodGlkeV9mbGlnaHRfZGYkY2l0eSkpIHsNCiAgZm9yIChhaXJsaW5lX25hbWUgaW4gdW5pcXVlKHRpZHlfZmxpZ2h0X2RmJGFpcmxpbmUpKSB7DQogICAgZmlsdGVyX2FyZ19kZWxheSA8LSBnbHVlKCJhaXJsaW5lID09ICd7YWlybGluZV9uYW1lfScgJiBzdGF0dXMgPT0gJ2RlbGF5ZWQnICYgY2l0eSA9PSAne2NpdHlfbmFtZX0nIikNCiAgICBmaWx0ZXJfYXJnX3RvdGFsIDwtIGdsdWUoImFpcmxpbmUgPT0gJ3thaXJsaW5lX25hbWV9JyAmIGNpdHkgPT0gJ3tjaXR5X25hbWV9JyIpDQogICAgcmF0ZSA8LSBjYWxjX2ZsaWdodF9yYXRlKGZpbHRlcl9hcmdfZGVsYXksIGZpbHRlcl9hcmdfdG90YWwpDQogICAgY2l0eV9kZWxheXNfZGYgPC0gcmJpbmQoY2l0eV9kZWxheXNfZGYsIGxpc3QoY2l0eV9uYW1lLGFpcmxpbmVfbmFtZSxyYXRlKSkNCiAgfQ0KfQ0KDQpjb2xuYW1lcyhjaXR5X2RlbGF5c19kZikgPC0gY2l0eV9kZWxheXNfY29sDQpjaXR5X2RlbGF5c19kZg0KYGBgDQpMZXQncyB2aXN1YWxpemUgdGhlIHRyZW5kcyB3aXRoIGFub3RoZXIgYmFyIHBsb3QuIERlbGF5IHJhdGVzIGZvciBlYWNoIGFpcmxpbmUgYXJlIHNob3duIHNpZGUgYnkgc2lkZSBhbmQgc2VwYXJhdGVkIGJ5IGNpdHkuIFRoaXMgcGxvdCBkZWZpbml0aXZlbHkgc2hvd3MgdGhhdCBmb3IgZXZlcnkgY2l0eSwgQU0gV2VzdCBoYXMgYSBoaWdoZXIgZGVsYXkgcmF0ZS4gVGhpcyBsb29rcyBsaWtlIGEgbWFzc2l2ZSBjb250cmFkaWN0aW9uIHdpdGggdGhlIGVhcmxpZXIgZGF0YS4gV2hhdCBpcyBnb2luZyBvbiB3aXRoIHRoaXMgZGF0YT8gDQpgYGB7ciBjaXR5LWRlbGF5LXBsb3R9DQpnZ3Bsb3QoY2l0eV9kZWxheXNfZGYsIGFlcyh4ID0gY2l0eSwgeSA9IGRlbGF5X3JhdGUsIGZpbGwgPSBhaXJsaW5lKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JywgcG9zaXRpb24gPSAnZG9kZ2UnKSArDQogICAgbGFicyh0aXRsZSA9ICdEZWxheSBSYXRlIGJ5IEFpcmxpbmUgYW5kIENpdHknKQ0KYGBgDQoNCkNoZWNraW5nIHRoZSB0b3RhbCBmbGlnaHRzIHBlciBjaXR5IGZvciBib3RoIGFpcmxpbmVzIGFkZHMgc29tZSBpbnNpZ2h0IGludG8gdGhpcyBpc3N1ZS4gVGFraW5nIHRoZSB0b3RhbCBmbGlnaHRzIGZvciBlYWNoIGNpdHkgYW5kIGFpcmxpbmUgY29tYmluYXRpb24gd2hpbGUgaWdub3JpbmcgdGhlIGRlbGF5IHN0YXR1cywgd2UgZ2V0IGEgZmxpZ2h0cyBwZXIgY2l0eSB0YWJsZSBmb3IgYm90aCBhaXJsaW5lcy4gVGhlIHRhYmxlIGltcGxpZXMgdGhhdCBBTSBXZXN0J3MgZGF0YSBhcmUgd2VpZ2h0ZWQgdG93YXJkIFBob2VuaXggd2hlcmUgaXQgaGFkIGEgZGVsYXkgcGVyY2VudGFnZSBvZiA3LjklLiBBbGFza2EncyBkYXRhIGFyZSBtb3N0bHkgZHJpdmVuIGJ5IGl0cyBwZXJmb3JtYW5jZSBpbiBTZWF0dGxlIHdpdGggYSBkZWxheSBwZXJjZW50YWdlIG9mIDE0LjIlLiBEZXNwaXRlIEFsYXNrYSB3aW5uaW5nIHRoZSBoZWFkIHRvIGhlYWQgbG93ZXIgZGVsYXkgcGVyY2VudGFnZSBpbiBldmVyeSBjaXR5LCBpdCBpcyB1bmZvcnR1bmF0ZWx5IHBlbmFsaXplZCBmb3IgZGVyaXZpbmcgbW9yZSBvZiBpdHMgZGVsYXkgcmF0ZSBmcm9tIGl0cyBzZWNvbmQgd29yc3QgZGVsYXkgY2l0eSAob25seSBiZWhpbmQgU2FuIEZyYW5jaXNjbykuDQpgYGB7ciB0YWJsZS1kZXNjfQ0KdGlkeV9mbGlnaHRfZGYgJT4lIGdyb3VwX2J5KGFpcmxpbmUsIGNpdHkpICU+JSBzdW1tYXJpc2UodG90YWxfZmxpZ2h0cyA9IHN1bSh0b3RhbF9mbGlnaHRzKSwgLmdyb3VwcyA9ICdkcm9wJykgJT4lDQogIGFycmFuZ2UoZGVzYyh0b3RhbF9mbGlnaHRzKSkNCmNpdHlfZGVsYXlzX2RmICU+JSANCiAgZmlsdGVyKGFpcmxpbmUgPT0gJ0FNIFdFU1QnICYgY2l0eSA9PSAnUGhvZW5peCcgfCBhaXJsaW5lID09ICdBTEFTS0EnICYgY2l0eSA9PSAnU2VhdHRsZScpDQpgYGANCiMjIyBDb25jbHVzaW9ucw0KDQpUaGUgb3JpZ2luYWwgaW5wdXQgZGF0YSB3YXMgdmVyeSBlYXN5IHRvIHJlYWQsIGJ1dCB3YXMgbm90IHRpZHkgZm9yIGEgZGF0YSBmcmFtZS4gSGF2aW5nIGluZGl2aWR1YWwgY2l0aWVzIGFzIGNvbHVtbnMgd2FzIHRoZSBtb3N0IGVncmVnaW91cyBpc3N1ZS4gSXQgaXMgYmV0dGVyIHRvIGtlZXAgZGF0YSBpbiBhIHRpZHkgbG9uZyBmb3JtIGFuZCBhZGFwdCBhcyBuZWVkZWQuIA0KDQpUaGUgY2l0eSBzcGVjaWZpYyBhbmFseXNpcyB3YXMgZXNwZWNpYWxseSBleWUgb3BlbmluZy4gSXQgd2FzIGVhc3kgdG8gZGVjbGFyZSBBTSBXZXN0IHRoZSBtb3JlIHJlbGlhYmxlIGFpcmxpbmUgYmVjYXVzZSBvZiBhIGJldHRlciBvdmVyYWxsIGRlbGF5IHJhdGUuIEhvd2V2ZXIsIG1vcmUgZ3JhbnVsYXIgZGF0YSBzaG93ZWQgdGhhdCBBbGFza2EgY29uc2lzdGVudGx5IG91dHBlcmZvcm1lZCBBTSBXZXN0IGluIGV2ZXJ5IHNpbmdsZSBjaXR5LiANCg0KRnVydGhlciB3b3JrIGludG8gc29sdmluZyB0aGlzIGNvbnVuZHJ1bSBjb3VsZCBpbmNsdWRlIGFjY291bnRpbmcgZm9yIGVhY2ggYWlybGluZSBoYXZpbmcgZGlmZmVyZW50IGFtb3VudHMgb2YgZmxpZ2h0cyBpbiBlYWNoIGNpdHkuIA0KDQo=