1 Libraries

Here are the libraries used in this analysis.

library(ggplot2)    # graphing
library(dplyr)      # data summarization and manipulation
library(tidyr)      # reshaping data frame
library(data.table) # fast file reading using data.table::fread
library(kableExtra) # beautify table output
library(lubridate)  # date conversion and manipulation

2 Datasets

2.1 Source of Data

This dataset is obtained from one of Kaggle Competition, Corporación Favorita Grocery Sales Forecasting.

They are available as CSV files.

list.files(path='./data/') 
[1] "holidays_events.csv"   "items.csv"             "oil.csv"               "sample_submission.csv" "stores.csv"           
[6] "test.csv"              "train.csv"             "transactions.csv"     

2.2 Loading Data

All data files are stored in data/ folder. date.table::fread() is used for fast reading as the data exceeds 4.8GB.

2.3 Recoding Data

Some variables need to be converted to appropriate type:
- Categorical variable that was repsented as integer in original dataset must be converted to factor
- Date variable was imported as text or factor need to be converted to date type

items.df$perishable  = as.factor(items.df$perishable)
items.df$class       = as.factor(items.df$class)
holidays.df$date     = as.Date(holidays.df$date, format = "%Y-%m-%d")
transactions.df$date = as.Date(transactions.df$date, format = "%Y-%m-%d")
train.df[, date := as.Date(fast_strptime(as.character(date), "%Y-%m-%d"))]

3 Understanding Data

3.1 Stores

This dataset is store metadata. Each row represent a record for a unique store.

3.1.1 Structure and Dimension

Number of columns: 5
Number of rows: 54 (represent total number of stores)

str(stores.df)
Classes ‘data.table’ and 'data.frame':  54 obs. of  5 variables:
 $ store_nbr: int  1 2 3 4 5 6 7 8 9 10 ...
 $ city     : Factor w/ 22 levels "Ambato","Babahoyo",..: 19 19 19 19 22 19 19 19 19 19 ...
 $ state    : Factor w/ 16 levels "Azuay","Bolivar",..: 13 13 13 13 15 13 13 13 13 13 ...
 $ type     : Factor w/ 5 levels "A","B","C","D",..: 4 4 4 4 4 4 4 4 2 3 ...
 $ cluster  : int  13 13 8 9 4 13 8 8 6 15 ...
 - attr(*, ".internal.selfref")=<externalptr> 
summary(stores.df)
   store_nbr                city                               state    type      cluster      
 Min.   : 1.00   Quito        :18   Pichincha                     :19   A: 9   Min.   : 1.000  
 1st Qu.:14.25   Guayaquil    : 8   Guayas                        :11   B: 8   1st Qu.: 4.000  
 Median :27.50   Cuenca       : 3   Azuay                         : 3   C:15   Median : 8.500  
 Mean   :27.50   Santo Domingo: 3   Manabi                        : 3   D:18   Mean   : 8.481  
 3rd Qu.:40.75   Ambato       : 2   Santo Domingo de los Tsachilas: 3   E: 4   3rd Qu.:13.000  
 Max.   :54.00   Latacunga    : 2   Cotopaxi                      : 2          Max.   :17.000  
                 (Other)      :18   (Other)                       :13                          

3.1.2 city

Stores are distribtued across 22 cities.

stores.df %>% count(city) %>%  
  ggplot(aes(x=reorder(city,-n), y=n)) + geom_bar(stat='identity') +  
  labs(title='Stores Distribution by Cities', x='Cities', y='Number of Stores') +
  theme(axis.text.x  = element_text (angle = (90), hjust = 1, vjust = 0.5))

pt1 = prop.table(table(stores.df$city)) %>% sort(decreasing = T) %>% {.*100} %>% head(2)

Majority of stores are located in Quito (33.3%) and Guayaquil (14.8%).

3.1.3 states

Stores are distribtued across 16 states.

stores.df %>% count(state) %>%  
  ggplot(aes(x=reorder(state,-n), y=n)) + geom_bar(stat='identity') +  
  labs(title='Stores Distribution by States', x='States', y='Number of Stores') +
  theme(axis.text.x  = element_text (angle = (90), hjust = 1, vjust = 0.5))

stores.df %>% group_by(state) %>% mutate(cities=paste(unique(city), collapse = " - ")) %>% 
  summarize(n_stores=n(), unique_cities=n_distinct(city),cities=max(cities)) %>% 
  arrange(-n_stores) %>% kable %>%
  kable_styling(bootstrap_options = c("striped", "condensed"))
state n_stores unique_cities cities
Pichincha 19 2 Quito - Cayambe
Guayas 11 4 Guayaquil - Daule - Playas - Libertad
Azuay 3 1 Cuenca
Manabi 3 2 Manta - El Carmen
Santo Domingo de los Tsachilas 3 1 Santo Domingo
Cotopaxi 2 1 Latacunga
El Oro 2 1 Machala
Los Rios 2 2 Babahoyo - Quevedo
Tungurahua 2 1 Ambato
Bolivar 1 1 Guaranda
Chimborazo 1 1 Riobamba
Esmeraldas 1 1 Esmeraldas
Imbabura 1 1 Ibarra
Loja 1 1 Loja
Pastaza 1 1 Puyo
Santa Elena 1 1 Salinas
pt2 = prop.table(table(stores.df$state)) %>% sort(decreasing = T) %>% {.*100} %>% head(2)

Majority of stores are located in state Pichincha (35.2%) and Guayas (20.4%).

3.2 Items

3.2.1 Structure and Dimension

Number of columns: 4
Number of rows: 4100 (each row represent single product)

str(items.df)
Classes ‘data.table’ and 'data.frame':  4100 obs. of  4 variables:
 $ item_nbr  : int  96995 99197 103501 103520 103665 105574 105575 105576 105577 105693 ...
 $ family    : Factor w/ 33 levels "AUTOMOTIVE","BABY CARE",..: 13 13 8 13 6 13 13 13 13 13 ...
 $ class     : Factor w/ 337 levels "1002","1003",..: 65 45 218 18 188 32 32 32 32 23 ...
 $ perishable: Factor w/ 2 levels "0","1": 1 1 1 1 2 1 1 1 1 1 ...
 - attr(*, ".internal.selfref")=<externalptr> 
summary(items.df)
    item_nbr                 family         class      perishable
 Min.   :  96995   GROCERY I    :1334   1016   : 133   0:3114    
 1st Qu.: 818111   BEVERAGES    : 613   1040   : 110   1: 986    
 Median :1306198   CLEANING     : 446   1124   : 100             
 Mean   :1251436   PRODUCE      : 306   1034   :  98             
 3rd Qu.:1904918   DAIRY        : 242   1122   :  81             
 Max.   :2134244   PERSONAL CARE: 153   1072   :  70             
                   (Other)      :1006   (Other):3508             

3.2.2 Family

There are total 33 product familes, with number of items distributed shown below.

items.df %>% count(family) %>%
  ggplot(aes(x=reorder(family,-n), y=n)) + geom_bar(stat='identity') +  
  labs(title='Items Distribution', x='Family', y='Number of Items') +
  theme(axis.text.x  = element_text (angle = (90), hjust = 1, vjust = 0.5))

Top 5 Families of Product and Its Percentage of Items

pt3 = prop.table(table(items.df$family) %>% sort(decreasing = T)) *100 
kable(pt3[1:5],digits = 2, col.names = c('Family','Percentage')) %>% kable_styling(bootstrap_options = c("striped", "condensed"))
Family Percentage
GROCERY I 32.54
BEVERAGES 14.95
CLEANING 10.88
PRODUCE 7.46
DAIRY 5.90

3.3 Holidays

3.3.1 Structure and Dimension

Number of columns: 6
Number of rows: 350 (each row represent a holiday)
Number of unique holidays date: 312

str(holidays.df)
Classes ‘data.table’ and 'data.frame':  350 obs. of  6 variables:
 $ date       : Date, format: "2012-03-02" "2012-04-01" "2012-04-12" "2012-04-14" ...
 $ type       : Factor w/ 6 levels "Additional","Bridge",..: 4 4 4 4 4 4 4 4 4 4 ...
 $ locale     : Factor w/ 3 levels "Local","National",..: 1 3 1 1 1 1 1 3 1 1 ...
 $ locale_name: Factor w/ 24 levels "Ambato","Cayambe",..: 16 3 4 13 20 17 8 11 12 15 ...
 $ description: Factor w/ 103 levels "Batalla de Pichincha",..: 26 55 19 7 9 11 5 56 6 25 ...
 $ transferred: logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 - attr(*, ".internal.selfref")=<externalptr> 
summary(holidays.df)
      date                    type          locale       locale_name                      description  transferred    
 Min.   :2012-03-02   Additional: 51   Local   :152   Ecuador  :174   Carnaval                  : 10   Mode :logical  
 1st Qu.:2013-12-23   Bridge    :  5   National:174   Quito    : 13   Fundacion de Cuenca       :  7   FALSE:338      
 Median :2015-06-08   Event     : 56   Regional: 24   Ambato   : 12   Fundacion de Ibarra       :  7   TRUE :12       
 Mean   :2015-04-24   Holiday   :221                  Guaranda : 12   Cantonizacion de Cayambe  :  6                  
 3rd Qu.:2016-07-03   Transfer  : 12                  Latacunga: 12   Cantonizacion de El Carmen:  6                  
 Max.   :2017-12-26   Work Day  :  5                  Riobamba : 12   Cantonizacion de Guaranda :  6                  
                                                      (Other)  :115   (Other)                   :308                  

3.3.2 Date

The holidays dataset records all holidays from 02 March 2012 till 26 December 2017.

3.3.3 Type and Locale

Notes from data source provider:
- Bridge are extended holidays to make up long weekend, they are replaced by Work Day
- Additional are extra holidays given, typically around Christmas (without replacement Work Day)
- Transfer are replacement for original celeberation that were made a normal day
- Event type is National Holiday but not explained

holidays.df %>% count(type, locale) %>%
  ggplot(aes(x=reorder(locale,-n), y=n, fill=type)) + geom_bar(stat='identity') +  
  labs(title='Holidays Distribution by Locale', x='Type', y='Days of Holidays') +
  theme(axis.text.x  = element_text (angle = (90), hjust = 1, vjust = 0.5))

holidays.df %>% count(locale,type) %>%
  ggplot(aes(x=reorder(type,-n), y=n, fill=locale)) + geom_bar(stat='identity') +  
  labs(title='Holidays Distribution by Type', x='Locale', y='Days of Holidays') +
  theme(axis.text.x  = element_text (angle = (90), hjust = 1, vjust = 0.5))

3.3.4 Transferred

There are total 12 holidays which were transferred from original date. These holidays are marked as Transferred as TRUE.

holidays.df %>% filter(transferred == T) %>% kable %>% kable_styling(bootstrap_options = c("striped", "condensed"))
date type locale locale_name description transferred
2012-10-09 Holiday National Ecuador Independencia de Guayaquil TRUE
2013-10-09 Holiday National Ecuador Independencia de Guayaquil TRUE
2014-10-09 Holiday National Ecuador Independencia de Guayaquil TRUE
2016-05-24 Holiday National Ecuador Batalla de Pichincha TRUE
2016-07-25 Holiday Local Guayaquil Fundacion de Guayaquil TRUE
2016-08-10 Holiday National Ecuador Primer Grito de Independencia TRUE
2017-01-01 Holiday National Ecuador Primer dia del ano TRUE
2017-04-12 Holiday Local Cuenca Fundacion de Cuenca TRUE
2017-05-24 Holiday National Ecuador Batalla de Pichincha TRUE
2017-08-10 Holiday National Ecuador Primer Grito de Independencia TRUE
2017-09-28 Holiday Local Ibarra Fundacion de Ibarra TRUE
2017-12-06 Holiday Local Quito Fundacion de Quito TRUE

The above transferred holidays correpsonse to the original holidays as below.

holidays.df %>% filter(type == 'Transfer') %>% kable %>% kable_styling(bootstrap_options = c("striped", "condensed"))
date type locale locale_name description transferred
2012-10-12 Transfer National Ecuador Traslado Independencia de Guayaquil FALSE
2013-10-11 Transfer National Ecuador Traslado Independencia de Guayaquil FALSE
2014-10-10 Transfer National Ecuador Traslado Independencia de Guayaquil FALSE
2016-05-27 Transfer National Ecuador Traslado Batalla de Pichincha FALSE
2016-07-24 Transfer Local Guayaquil Traslado Fundacion de Guayaquil FALSE
2016-08-12 Transfer National Ecuador Traslado Primer Grito de Independencia FALSE
2017-01-02 Transfer National Ecuador Traslado Primer dia del ano FALSE
2017-04-13 Transfer Local Cuenca Fundacion de Cuenca FALSE
2017-05-26 Transfer National Ecuador Traslado Batalla de Pichincha FALSE
2017-08-11 Transfer National Ecuador Traslado Primer Grito de Independencia FALSE
2017-09-29 Transfer Local Ibarra Fundacion de Ibarra FALSE
2017-12-08 Transfer Local Quito Traslado Fundacion de Quito FALSE

This also means holidays with type Transfer had been normal days and its celebration had been moved to holidays with Transferred equals TRUE

3.4 Transactions

Transaction is the records the count of sales transactions for each date, store_nbr combination. Only included for the training data timeframe.

3.4.1 Structure and Dimension

Number of columns: 3
Number of rows: 83488

Structure

str(transactions.df)
Classes ‘data.table’ and 'data.frame':  83488 obs. of  3 variables:
 $ date        : Date, format: "2013-01-01" "2013-01-02" "2013-01-02" "2013-01-02" ...
 $ store_nbr   : int  25 1 2 3 4 5 6 7 8 9 ...
 $ transactions: int  770 2111 2358 3487 1922 1903 2143 1874 3250 2940 ...
 - attr(*, ".internal.selfref")=<externalptr> 

Sample Rows

head(transactions.df) %>% kable %>% kable_styling(bootstrap_options = c("striped", "condensed"))
date store_nbr transactions
2013-01-01 25 770
2013-01-02 1 2111
2013-01-02 2 2358
2013-01-02 3 3487
2013-01-02 4 1922
2013-01-02 5 1903

Summary

summary(transactions.df)
      date              store_nbr      transactions 
 Min.   :2013-01-01   Min.   : 1.00   Min.   :   5  
 1st Qu.:2014-03-27   1st Qu.:13.00   1st Qu.:1046  
 Median :2015-06-08   Median :27.00   Median :1393  
 Mean   :2015-05-20   Mean   :26.94   Mean   :1695  
 3rd Qu.:2016-07-14   3rd Qu.:40.00   3rd Qu.:2079  
 Max.   :2017-08-15   Max.   :54.00   Max.   :8359  

3.5 Train

3.5.1 Structure and Dimension

Number of columns: 6
Number of rows: 125,497,040

Notes from Datasource Provider:

  • Training data, which includes the target unit_sales by date, store_nbr, and item_nbr and a unique id to label rows.
  • The target unit_sales can be integer (e.g., a bag of chips) or float (e.g., 1.5 kg of cheese).
  • Negative values of unit_sales represent returns of that particular item.
  • The onpromotion column tells whether that item_nbr was on promotion for a specified date and store_nbr.
  • Approximately 16% of the onpromotion values in this file are NaN.
  • NOTE: The training data does not include rows for items that had zero unit_sales for a store/date combination. There is no information as to whether or not the item was in stock for the store on the date, and teams will need to decide the best way to handle that situation. Also, there are a small number of items seen in the training data that aren’t seen in the test data.

Structure

str(train.df)
Classes ‘data.table’ and 'data.frame':  125497040 obs. of  6 variables:
 $ id         : int  0 1 2 3 4 5 6 7 8 9 ...
 $ date       : Date, format: "2013-01-01" "2013-01-01" "2013-01-01" "2013-01-01" ...
 $ store_nbr  : int  25 25 25 25 25 25 25 25 25 25 ...
 $ item_nbr   : int  103665 105574 105575 108079 108701 108786 108797 108952 111397 114790 ...
 $ unit_sales : num  7 1 2 1 1 3 1 1 13 3 ...
 $ onpromotion: logi  NA NA NA NA NA NA ...
 - attr(*, ".internal.selfref")=<externalptr> 

Sample Rows

head(train.df) %>% kable %>% kable_styling(bootstrap_options = c("striped", "condensed"))
id date store_nbr item_nbr unit_sales onpromotion
0 2013-01-01 25 103665 7 NA
1 2013-01-01 25 105574 1 NA
2 2013-01-01 25 105575 2 NA
3 2013-01-01 25 108079 1 NA
4 2013-01-01 25 108701 1 NA
5 2013-01-01 25 108786 3 NA

Summary

summary(train.df)
LS0tDQp0aXRsZTogIkZhdm9yaXRhIg0KYXV0aG9yOiAiV1FEIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIHRoZW1lOiBjZXJ1bGVhbg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19kZXB0aDogMw0KICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICBodG1sX2RvY3VtZW50OiBkZWZhdWx0DQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobz1UUlVFLCBmaWcuaGVpZ2h0PTMuNSwgZmlnLndpZHRoPTkpDQpgYGANCg0KIyBMaWJyYXJpZXMNCg0KSGVyZSBhcmUgdGhlIGxpYnJhcmllcyB1c2VkIGluIHRoaXMgYW5hbHlzaXMuIA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkoZ2dwbG90MikgICAgIyBncmFwaGluZw0KbGlicmFyeShkcGx5cikgICAgICAjIGRhdGEgc3VtbWFyaXphdGlvbiBhbmQgbWFuaXB1bGF0aW9uDQpsaWJyYXJ5KHRpZHlyKSAgICAgICMgcmVzaGFwaW5nIGRhdGEgZnJhbWUNCmxpYnJhcnkoZGF0YS50YWJsZSkgIyBmYXN0IGZpbGUgcmVhZGluZyB1c2luZyBkYXRhLnRhYmxlOjpmcmVhZA0KbGlicmFyeShrYWJsZUV4dHJhKSAjIGJlYXV0aWZ5IHRhYmxlIG91dHB1dA0KbGlicmFyeShsdWJyaWRhdGUpICAjIGRhdGUgY29udmVyc2lvbiBhbmQgbWFuaXB1bGF0aW9uDQpgYGANCg0KIyBEYXRhc2V0cw0KDQojIyBTb3VyY2Ugb2YgRGF0YQ0KVGhpcyBkYXRhc2V0IGlzIG9idGFpbmVkIGZyb20gb25lIG9mIEthZ2dsZSBDb21wZXRpdGlvbiwgW0NvcnBvcmFjacOzbiBGYXZvcml0YSBHcm9jZXJ5IFNhbGVzIEZvcmVjYXN0aW5nXShodHRwczovL3d3dy5rYWdnbGUuY29tL2NhcHRjYWxjdWxhdG9yL2EtdmVyeS1leHRlbnNpdmUtZmF2b3JpdGEtZXhwbG9yYXRvcnktYW5hbHlzaXMvZGF0YSkuDQoNClRoZXkgYXJlIGF2YWlsYWJsZSBhcyBDU1YgZmlsZXMuDQpgYGB7cn0NCmxpc3QuZmlsZXMocGF0aD0nLi9kYXRhLycpIA0KYGBgDQoNCiMjIExvYWRpbmcgRGF0YQ0KQWxsIGRhdGEgZmlsZXMgYXJlIHN0b3JlZCBpbiAqKmRhdGEvKiogZm9sZGVyLiAqKmBkYXRlLnRhYmxlOjpmcmVhZCgpYCoqIGlzIHVzZWQgZm9yIGZhc3QgcmVhZGluZyBhcyB0aGUgZGF0YSBleGNlZWRzIDQuOEdCLg0KYGBge3IgcmVzdWx0cz0naGlkZScsIGVjaG89RkFMU0V9DQppdGVtcy5kZiAgICAgICAgPSBmcmVhZCgnLi9kYXRhL2l0ZW1zLmNzdicsc3RyaW5nc0FzRmFjdG9ycyA9IFQpDQpvaWwuZGYgICAgICAgICAgPSBmcmVhZCgnLi9kYXRhL29pbC5jc3YnLCBzdHJpbmdzQXNGYWN0b3JzID0gVCkNCnN0b3Jlcy5kZiAgICAgICA9IGZyZWFkKCcuL2RhdGEvc3RvcmVzLmNzdicsIHN0cmluZ3NBc0ZhY3RvcnMgPSBUKQ0KdHJhbnNhY3Rpb25zLmRmID0gZnJlYWQoJy4vZGF0YS90cmFuc2FjdGlvbnMuY3N2Jywgc3RyaW5nc0FzRmFjdG9ycyA9IFQpDQpob2xpZGF5cy5kZiAgICAgPSBmcmVhZCgnLi9kYXRhL2hvbGlkYXlzX2V2ZW50cy5jc3YnLCBzdHJpbmdzQXNGYWN0b3JzID0gVCkNCnRyYWluLmRmICAgICAgICA9IGZyZWFkKCcuL2RhdGEvdHJhaW4uY3N2Jywgc3RyaW5nc0FzRmFjdG9ycyA9IFQsIGNvbENsYXNzZXMgPSBjKGRhdGU9J0RhdGUnKSkNCmBgYA0KDQojIyBSZWNvZGluZyBEYXRhDQpTb21lIHZhcmlhYmxlcyBuZWVkIHRvIGJlIGNvbnZlcnRlZCB0byBhcHByb3ByaWF0ZSB0eXBlOiAgDQotICoqQ2F0ZWdvcmljYWwqKiB2YXJpYWJsZSB0aGF0IHdhcyByZXBzZW50ZWQgYXMgaW50ZWdlciBpbiBvcmlnaW5hbCBkYXRhc2V0IG11c3QgYmUgY29udmVydGVkIHRvIGZhY3RvciAgDQotICoqRGF0ZSoqIHZhcmlhYmxlIHdhcyBpbXBvcnRlZCBhcyB0ZXh0IG9yIGZhY3RvciBuZWVkIHRvIGJlIGNvbnZlcnRlZCB0byBkYXRlIHR5cGUNCmBgYHtyfQ0KaXRlbXMuZGYkcGVyaXNoYWJsZSAgPSBhcy5mYWN0b3IoaXRlbXMuZGYkcGVyaXNoYWJsZSkNCml0ZW1zLmRmJGNsYXNzICAgICAgID0gYXMuZmFjdG9yKGl0ZW1zLmRmJGNsYXNzKQ0KaG9saWRheXMuZGYkZGF0ZSAgICAgPSBhcy5EYXRlKGhvbGlkYXlzLmRmJGRhdGUsIGZvcm1hdCA9ICIlWS0lbS0lZCIpDQp0cmFuc2FjdGlvbnMuZGYkZGF0ZSA9IGFzLkRhdGUodHJhbnNhY3Rpb25zLmRmJGRhdGUsIGZvcm1hdCA9ICIlWS0lbS0lZCIpDQp0cmFpbi5kZlssIGRhdGUgOj0gYXMuRGF0ZShmYXN0X3N0cnB0aW1lKGFzLmNoYXJhY3RlcihkYXRlKSwgIiVZLSVtLSVkIikpXQ0KYGBgDQoNCiMgVW5kZXJzdGFuZGluZyBEYXRhDQoNCiMjIFN0b3Jlcw0KDQpUaGlzIGRhdGFzZXQgaXMgKipzdG9yZSBtZXRhZGF0YSoqLiBFYWNoIHJvdyByZXByZXNlbnQgYSByZWNvcmQgZm9yIGEgKip1bmlxdWUgc3RvcmUqKi4NCg0KIyMjIFN0cnVjdHVyZSBhbmQgRGltZW5zaW9uDQoNCk51bWJlciBvZiBjb2x1bW5zOiAqKmByIG5jb2woc3RvcmVzLmRmKWAqKiAgDQpOdW1iZXIgb2Ygcm93czogKipgciBucm93KHN0b3Jlcy5kZilgKiogKHJlcHJlc2VudCB0b3RhbCBudW1iZXIgb2Ygc3RvcmVzKQ0KYGBge3J9DQpzdHIoc3RvcmVzLmRmKQ0KYGBgDQpgYGB7cn0NCnN1bW1hcnkoc3RvcmVzLmRmKQ0KYGBgDQojIyMgY2l0eQ0KDQpTdG9yZXMgYXJlIGRpc3RyaWJ0dWVkIGFjcm9zcyAqKmByIHVuaXF1ZShzdG9yZXMuZGYkY2l0eSkgJT4lIGxlbmd0aGAgY2l0aWVzKiouDQoNCmBgYHtyIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTN9DQpzdG9yZXMuZGYgJT4lIGNvdW50KGNpdHkpICU+JSAgDQogIGdncGxvdChhZXMoeD1yZW9yZGVyKGNpdHksLW4pLCB5PW4pKSArIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JykgKyAgDQogIGxhYnModGl0bGU9J1N0b3JlcyBEaXN0cmlidXRpb24gYnkgQ2l0aWVzJywgeD0nQ2l0aWVzJywgeT0nTnVtYmVyIG9mIFN0b3JlcycpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggID0gZWxlbWVudF90ZXh0IChhbmdsZSA9ICg5MCksIGhqdXN0ID0gMSwgdmp1c3QgPSAwLjUpKQ0KYGBgDQpgYGB7cn0NCnB0MSA9IHByb3AudGFibGUodGFibGUoc3RvcmVzLmRmJGNpdHkpKSAlPiUgc29ydChkZWNyZWFzaW5nID0gVCkgJT4lIHsuKjEwMH0gJT4lIGhlYWQoMikNCmBgYA0KDQpNYWpvcml0eSBvZiBzdG9yZXMgYXJlIGxvY2F0ZWQgaW4gKipgciBuYW1lcyhwdDFbMV0pYCAoYHIgZm9ybWF0KHB0MVsxXSxkaWdpdHM9MiwgbnNtYWxsPTEpYCUpKiogYW5kICoqYHIgbmFtZXMocHQxWzJdKWAgKGByIGZvcm1hdChwdDFbMl0sZGlnaXRzPTIsIG5zbWFsbD0xKWAlKSoqLg0KDQojIyMgc3RhdGVzDQoNClN0b3JlcyBhcmUgZGlzdHJpYnR1ZWQgYWNyb3NzICoqYHIgdW5pcXVlKHN0b3Jlcy5kZiRzdGF0ZSkgJT4lIGxlbmd0aGAgc3RhdGVzKiouDQoNCmBgYHtyIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTl9DQpzdG9yZXMuZGYgJT4lIGNvdW50KHN0YXRlKSAlPiUgIA0KICBnZ3Bsb3QoYWVzKHg9cmVvcmRlcihzdGF0ZSwtbiksIHk9bikpICsgZ2VvbV9iYXIoc3RhdD0naWRlbnRpdHknKSArICANCiAgbGFicyh0aXRsZT0nU3RvcmVzIERpc3RyaWJ1dGlvbiBieSBTdGF0ZXMnLCB4PSdTdGF0ZXMnLCB5PSdOdW1iZXIgb2YgU3RvcmVzJykgKw0KICB0aGVtZShheGlzLnRleHQueCAgPSBlbGVtZW50X3RleHQgKGFuZ2xlID0gKDkwKSwgaGp1c3QgPSAxLCB2anVzdCA9IDAuNSkpDQpgYGANCmBgYHtyfQ0Kc3RvcmVzLmRmICU+JSBncm91cF9ieShzdGF0ZSkgJT4lIG11dGF0ZShjaXRpZXM9cGFzdGUodW5pcXVlKGNpdHkpLCBjb2xsYXBzZSA9ICIgLSAiKSkgJT4lIA0KICBzdW1tYXJpemUobl9zdG9yZXM9bigpLCB1bmlxdWVfY2l0aWVzPW5fZGlzdGluY3QoY2l0eSksY2l0aWVzPW1heChjaXRpZXMpKSAlPiUgDQogIGFycmFuZ2UoLW5fc3RvcmVzKSAlPiUga2FibGUgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImNvbmRlbnNlZCIpKQ0KYGBgDQpgYGB7cn0NCnB0MiA9IHByb3AudGFibGUodGFibGUoc3RvcmVzLmRmJHN0YXRlKSkgJT4lIHNvcnQoZGVjcmVhc2luZyA9IFQpICU+JSB7LioxMDB9ICU+JSBoZWFkKDIpDQpgYGANCg0KTWFqb3JpdHkgb2Ygc3RvcmVzIGFyZSBsb2NhdGVkIGluIHN0YXRlICoqYHIgbmFtZXMocHQyWzFdKWAgKGByIGZvcm1hdChwdDJbMV0sZGlnaXRzPTIsIG5zbWFsbD0xKWAlKSoqIGFuZCAqKmByIG5hbWVzKHB0MlsyXSlgIChgciBmb3JtYXQocHQyWzJdLGRpZ2l0cz0yLCBuc21hbGw9MSlgJSkqKi4NCg0KDQojIyBJdGVtcw0KDQojIyMgU3RydWN0dXJlIGFuZCBEaW1lbnNpb24NCg0KTnVtYmVyIG9mIGNvbHVtbnM6ICoqYHIgbmNvbChpdGVtcy5kZilgKiogIA0KTnVtYmVyIG9mIHJvd3M6ICoqYHIgbnJvdyhpdGVtcy5kZilgKiogKGVhY2ggcm93IHJlcHJlc2VudCBzaW5nbGUgcHJvZHVjdCkNCg0KYGBge3J9DQpzdHIoaXRlbXMuZGYpDQpgYGANCg0KYGBge3J9DQpzdW1tYXJ5KGl0ZW1zLmRmKQ0KYGBgDQojIyMgRmFtaWx5DQpUaGVyZSBhcmUgdG90YWwgKipgciBsZW5ndGgodW5pcXVlKGl0ZW1zLmRmJGZhbWlseSkpYCoqIHByb2R1Y3QgZmFtaWxlcywgd2l0aCBudW1iZXIgb2YgaXRlbXMgZGlzdHJpYnV0ZWQgc2hvd24gYmVsb3cuDQpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9OX0NCml0ZW1zLmRmICU+JSBjb3VudChmYW1pbHkpICU+JQ0KICBnZ3Bsb3QoYWVzKHg9cmVvcmRlcihmYW1pbHksLW4pLCB5PW4pKSArIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JykgKyAgDQogIGxhYnModGl0bGU9J0l0ZW1zIERpc3RyaWJ1dGlvbicsIHg9J0ZhbWlseScsIHk9J051bWJlciBvZiBJdGVtcycpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggID0gZWxlbWVudF90ZXh0IChhbmdsZSA9ICg5MCksIGhqdXN0ID0gMSwgdmp1c3QgPSAwLjUpKQ0KYGBgDQoqKlRvcCA1IEZhbWlsaWVzIG9mIFByb2R1Y3QgYW5kIEl0cyBQZXJjZW50YWdlIG9mIEl0ZW1zKioNCg0KYGBge3J9DQpwdDMgPSBwcm9wLnRhYmxlKHRhYmxlKGl0ZW1zLmRmJGZhbWlseSkgJT4lIHNvcnQoZGVjcmVhc2luZyA9IFQpKSAqMTAwIA0Ka2FibGUocHQzWzE6NV0sZGlnaXRzID0gMiwgY29sLm5hbWVzID0gYygnRmFtaWx5JywnUGVyY2VudGFnZScpKSAlPiUga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiY29uZGVuc2VkIikpDQpgYGANCg0KIyMgSG9saWRheXMNCg0KIyMjIFN0cnVjdHVyZSBhbmQgRGltZW5zaW9uDQoNCk51bWJlciBvZiBjb2x1bW5zOiAqKmByIG5jb2woaG9saWRheXMuZGYpYCoqICANCk51bWJlciBvZiByb3dzOiAqKmByIG5yb3coaG9saWRheXMuZGYpYCAoZWFjaCByb3cgcmVwcmVzZW50IGEgaG9saWRheSkqKiAgDQpOdW1iZXIgb2YgdW5pcXVlIGhvbGlkYXlzIGRhdGU6ICoqYHIgbGVuZ3RoKHVuaXF1ZShob2xpZGF5cy5kZiRkYXRlKSlgKioNCg0KYGBge3J9DQpzdHIoaG9saWRheXMuZGYpDQpgYGANCg0KYGBge3J9DQpzdW1tYXJ5KGhvbGlkYXlzLmRmKQ0KYGBgDQojIyMgRGF0ZQ0KDQpUaGUgaG9saWRheXMgZGF0YXNldCByZWNvcmRzIGFsbCBob2xpZGF5cyBmcm9tICoqYHIgZm9ybWF0KG1pbihob2xpZGF5cy5kZiRkYXRlKSwiJWQgJUIgJVkiKWAqKiB0aWxsICoqYHIgZm9ybWF0KG1heChob2xpZGF5cy5kZiRkYXRlKSwiJWQgJUIgJVkiKWAqKi4NCg0KIyMjIFR5cGUgYW5kIExvY2FsZQ0KDQpOb3RlcyBmcm9tIGRhdGEgc291cmNlIHByb3ZpZGVyOiAgDQotICoqQnJpZGdlKiogYXJlIGV4dGVuZGVkIGhvbGlkYXlzIHRvIG1ha2UgdXAgbG9uZyB3ZWVrZW5kLCB0aGV5IGFyZSByZXBsYWNlZCBieSAqKldvcmsgRGF5KiogIA0KLSAqKkFkZGl0aW9uYWwqKiBhcmUgZXh0cmEgaG9saWRheXMgZ2l2ZW4sIHR5cGljYWxseSBhcm91bmQgQ2hyaXN0bWFzICh3aXRob3V0IHJlcGxhY2VtZW50IFdvcmsgRGF5KSAgDQotICoqVHJhbnNmZXIqKiBhcmUgcmVwbGFjZW1lbnQgZm9yIG9yaWdpbmFsIGNlbGViZXJhdGlvbiB0aGF0IHdlcmUgbWFkZSBhIG5vcm1hbCBkYXkgIA0KLSAqKkV2ZW50KiogdHlwZSBpcyAqKk5hdGlvbmFsIEhvbGlkYXkqKiBidXQgbm90IGV4cGxhaW5lZA0KDQpgYGB7ciBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD0zfQ0KaG9saWRheXMuZGYgJT4lIGNvdW50KHR5cGUsIGxvY2FsZSkgJT4lDQogIGdncGxvdChhZXMoeD1yZW9yZGVyKGxvY2FsZSwtbiksIHk9biwgZmlsbD10eXBlKSkgKyBnZW9tX2JhcihzdGF0PSdpZGVudGl0eScpICsgIA0KICBsYWJzKHRpdGxlPSdIb2xpZGF5cyBEaXN0cmlidXRpb24gYnkgTG9jYWxlJywgeD0nVHlwZScsIHk9J0RheXMgb2YgSG9saWRheXMnKSArDQogIHRoZW1lKGF4aXMudGV4dC54ICA9IGVsZW1lbnRfdGV4dCAoYW5nbGUgPSAoOTApLCBoanVzdCA9IDEsIHZqdXN0ID0gMC41KSkNCmBgYA0KDQpgYGB7ciBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD0zfQ0KaG9saWRheXMuZGYgJT4lIGNvdW50KGxvY2FsZSx0eXBlKSAlPiUNCiAgZ2dwbG90KGFlcyh4PXJlb3JkZXIodHlwZSwtbiksIHk9biwgZmlsbD1sb2NhbGUpKSArIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JykgKyAgDQogIGxhYnModGl0bGU9J0hvbGlkYXlzIERpc3RyaWJ1dGlvbiBieSBUeXBlJywgeD0nTG9jYWxlJywgeT0nRGF5cyBvZiBIb2xpZGF5cycpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggID0gZWxlbWVudF90ZXh0IChhbmdsZSA9ICg5MCksIGhqdXN0ID0gMSwgdmp1c3QgPSAwLjUpKQ0KYGBgDQojIyMgVHJhbnNmZXJyZWQNCg0KVGhlcmUgYXJlIHRvdGFsICoqYHIgc3VtKGhvbGlkYXlzLmRmJHRyYW5zZmVycmVkID09IFQpYCoqIGhvbGlkYXlzIHdoaWNoIHdlcmUgdHJhbnNmZXJyZWQgZnJvbSBvcmlnaW5hbCBkYXRlLiBUaGVzZSBob2xpZGF5cyBhcmUgbWFya2VkIGFzICoqVHJhbnNmZXJyZWQgYXMgVFJVRSoqLg0KDQpgYGB7cn0NCmhvbGlkYXlzLmRmICU+JSBmaWx0ZXIodHJhbnNmZXJyZWQgPT0gVCkgJT4lIGthYmxlICU+JSBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJjb25kZW5zZWQiKSkNCmBgYA0KVGhlIGFib3ZlIHRyYW5zZmVycmVkIGhvbGlkYXlzIGNvcnJlcHNvbnNlIHRvICoqdGhlIG9yaWdpbmFsIGhvbGlkYXlzKiogYXMgYmVsb3cuDQpgYGB7cn0NCmhvbGlkYXlzLmRmICU+JSBmaWx0ZXIodHlwZSA9PSAnVHJhbnNmZXInKSAlPiUga2FibGUgJT4lIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImNvbmRlbnNlZCIpKQ0KYGBgDQpUaGlzIGFsc28gbWVhbnMgaG9saWRheXMgd2l0aCB0eXBlICoqVHJhbnNmZXIqKiBoYWQgYmVlbiBub3JtYWwgZGF5cyBhbmQgaXRzIGNlbGVicmF0aW9uIGhhZCBiZWVuIG1vdmVkIHRvIGhvbGlkYXlzIHdpdGggKipUcmFuc2ZlcnJlZCBlcXVhbHMgVFJVRSoqDQoNCg0KIyMgVHJhbnNhY3Rpb25zDQoNClRyYW5zYWN0aW9uIGlzIHRoZSByZWNvcmRzIHRoZSBjb3VudCBvZiBzYWxlcyB0cmFuc2FjdGlvbnMgZm9yIGVhY2ggZGF0ZSwgc3RvcmVfbmJyIGNvbWJpbmF0aW9uLiBPbmx5IGluY2x1ZGVkIGZvciB0aGUgdHJhaW5pbmcgZGF0YSB0aW1lZnJhbWUuDQoNCiMjIyBTdHJ1Y3R1cmUgYW5kIERpbWVuc2lvbg0KDQpOdW1iZXIgb2YgY29sdW1uczogKipgciBuY29sKHRyYW5zYWN0aW9ucy5kZilgKiogIA0KTnVtYmVyIG9mIHJvd3M6ICoqYHIgbnJvdyh0cmFuc2FjdGlvbnMuZGYpYCoqIA0KDQoqKlN0cnVjdHVyZSoqDQpgYGB7cn0NCnN0cih0cmFuc2FjdGlvbnMuZGYpDQpgYGANCioqU2FtcGxlIFJvd3MqKg0KYGBge3J9DQpoZWFkKHRyYW5zYWN0aW9ucy5kZikgJT4lIGthYmxlICU+JSBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJjb25kZW5zZWQiKSkNCmBgYA0KKipTdW1tYXJ5KioNCmBgYHtyfQ0Kc3VtbWFyeSh0cmFuc2FjdGlvbnMuZGYpDQpgYGANCg0KDQoNCiMjIFRyYWluDQoNCiMjIyBTdHJ1Y3R1cmUgYW5kIERpbWVuc2lvbg0KDQpOdW1iZXIgb2YgY29sdW1uczogKipgciBuY29sKHRyYWluLmRmKWAqKiAgDQpOdW1iZXIgb2Ygcm93czogKipgciBmb3JtYXQobnJvdyh0cmFpbi5kZiksIGJpZy5tYXJrPSIsIilgKiogDQoNCioqTm90ZXMgZnJvbSBEYXRhc291cmNlIFByb3ZpZGVyOiAgKiogIA0KDQotIFRyYWluaW5nIGRhdGEsIHdoaWNoIGluY2x1ZGVzIHRoZSB0YXJnZXQgdW5pdF9zYWxlcyBieSBkYXRlLCBzdG9yZV9uYnIsIGFuZCBpdGVtX25iciBhbmQgYSB1bmlxdWUgaWQgdG8gbGFiZWwgcm93cy4gIA0KLSBUaGUgdGFyZ2V0IHVuaXRfc2FsZXMgY2FuIGJlIGludGVnZXIgKGUuZy4sIGEgYmFnIG9mIGNoaXBzKSBvciBmbG9hdCAoZS5nLiwgMS41IGtnIG9mIGNoZWVzZSkuICANCi0gTmVnYXRpdmUgdmFsdWVzIG9mIHVuaXRfc2FsZXMgcmVwcmVzZW50IHJldHVybnMgb2YgdGhhdCBwYXJ0aWN1bGFyIGl0ZW0uICANCi0gVGhlIG9ucHJvbW90aW9uIGNvbHVtbiB0ZWxscyB3aGV0aGVyIHRoYXQgaXRlbV9uYnIgd2FzIG9uIHByb21vdGlvbiBmb3IgYSBzcGVjaWZpZWQgZGF0ZSBhbmQgc3RvcmVfbmJyLiAgDQotIEFwcHJveGltYXRlbHkgMTYlIG9mIHRoZSBvbnByb21vdGlvbiB2YWx1ZXMgaW4gdGhpcyBmaWxlIGFyZSBOYU4uICANCi0gTk9URTogVGhlIHRyYWluaW5nIGRhdGEgZG9lcyBub3QgaW5jbHVkZSByb3dzIGZvciBpdGVtcyB0aGF0IGhhZCB6ZXJvIHVuaXRfc2FsZXMgZm9yIGEgc3RvcmUvZGF0ZSBjb21iaW5hdGlvbi4gVGhlcmUgaXMgbm8gaW5mb3JtYXRpb24gYXMgdG8gd2hldGhlciBvciBub3QgdGhlIGl0ZW0gd2FzIGluIHN0b2NrIGZvciB0aGUgc3RvcmUgb24gdGhlIGRhdGUsIGFuZCB0ZWFtcyB3aWxsIG5lZWQgdG8gZGVjaWRlIHRoZSBiZXN0IHdheSB0byBoYW5kbGUgdGhhdCBzaXR1YXRpb24uIEFsc28sIHRoZXJlIGFyZSBhIHNtYWxsIG51bWJlciBvZiBpdGVtcyBzZWVuIGluIHRoZSB0cmFpbmluZyBkYXRhIHRoYXQgYXJlbid0IHNlZW4gaW4gdGhlIHRlc3QgZGF0YS4NCg0KKipTdHJ1Y3R1cmUqKg0KDQpgYGB7cn0NCnN0cih0cmFpbi5kZikNCmBgYA0KDQoqKlNhbXBsZSBSb3dzKioNCg0KYGBge3J9DQpoZWFkKHRyYWluLmRmKSAlPiUga2FibGUgJT4lIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImNvbmRlbnNlZCIpKQ0KYGBgDQoNCioqU3VtbWFyeSoqDQoNCmBgYHtyfQ0Kc3VtbWFyeSh0cmFpbi5kZikNCmBgYA0KDQoNCg0KDQoNCg0K