customer segmentation on online-retailer data using RFM analysis.

library(readxl)
data<-read_excel("C:\\Users\\badal\\Desktop\\datset_\\Online_Retail.xlsx")
head(data)
str(data)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   541909 obs. of  8 variables:
 $ InvoiceNo  : chr  "536365" "536365" "536365" "536365" ...
 $ StockCode  : chr  "85123A" "71053" "84406B" "84029G" ...
 $ Description: chr  "WHITE HANGING HEART T-LIGHT HOLDER" "WHITE METAL LANTERN" "CREAM CUPID HEARTS COAT HANGER" "KNITTED UNION FLAG HOT WATER BOTTLE" ...
 $ Quantity   : num  6 6 8 6 6 2 6 6 6 32 ...
 $ InvoiceDate: POSIXct, format: "2010-12-01 08:26:00" "2010-12-01 08:26:00" "2010-12-01 08:26:00" "2010-12-01 08:26:00" ...
 $ UnitPrice  : num  2.55 3.39 2.75 3.39 3.39 7.65 4.25 1.85 1.85 1.69 ...
 $ CustomerID : num  17850 17850 17850 17850 17850 ...
 $ Country    : chr  "United Kingdom" "United Kingdom" "United Kingdom" "United Kingdom" ...
any(is.na(data))
[1] TRUE
summary(data)
  InvoiceNo          StockCode         Description           Quantity          InvoiceDate                    UnitPrice           CustomerID       Country         
 Length:541909      Length:541909      Length:541909      Min.   :-80995.00   Min.   :2010-12-01 08:26:00   Min.   :-11062.06   Min.   :12346    Length:541909     
 Class :character   Class :character   Class :character   1st Qu.:     1.00   1st Qu.:2011-03-28 11:34:00   1st Qu.:     1.25   1st Qu.:13953    Class :character  
 Mode  :character   Mode  :character   Mode  :character   Median :     3.00   Median :2011-07-19 17:17:00   Median :     2.08   Median :15152    Mode  :character  
                                                          Mean   :     9.55   Mean   :2011-07-04 13:34:57   Mean   :     4.61   Mean   :15288                      
                                                          3rd Qu.:    10.00   3rd Qu.:2011-10-19 11:27:00   3rd Qu.:     4.13   3rd Qu.:16791                      
                                                          Max.   : 80995.00   Max.   :2011-12-09 12:50:00   Max.   : 38970.00   Max.   :18287                      
                                                                                                                                NA's   :135080                     
data <- mutate(data, InvoiceDate = as.Date(InvoiceDate,"%m/%d/%Y"))
unknown timezone '%m/%d/%Y'
data$Amount <- data$Quantity*data$UnitPrice
 sapply(data, function(k) sum(is.na(k)))
  InvoiceNo   StockCode Description    Quantity InvoiceDate   UnitPrice  CustomerID     Country      Amount 
          0           0        1454           0           0           0      135080           0           0 
nrow(data)
[1] 541909
data <- data[!is.na(data$CustomerID),]
data <- data[!is.na(data$Description),]
nrow(data)
[1] 406829
data$Day <- format(as.Date(data$InvoiceDate),"%d")
data$Month <- format(as.Date(data$InvoiceDate),"%m")
data$Year <- format(as.Date(data$InvoiceDate),"%Y")

cncldSalesData <- salesData[startsWith(salesData$InvoiceNo,“C”),] salesData <- salesData[!startsWith(salesData$InvoiceNo,“C”),]

cncldata <- data[startsWith(data$InvoiceNo,"C"),]
data <- data[!startsWith(data$InvoiceNo,"C"),]
summary(data)
  InvoiceNo          StockCode         Description           Quantity         InvoiceDate           UnitPrice          CustomerID      Country              Amount         
 Length:397924      Length:397924      Length:397924      Min.   :    1.00   Min.   :2010-12-01   Min.   :   0.000   Min.   :12346   Length:397924      Min.   :     0.00  
 Class :character   Class :character   Class :character   1st Qu.:    2.00   1st Qu.:2011-04-07   1st Qu.:   1.250   1st Qu.:13969   Class :character   1st Qu.:     4.68  
 Mode  :character   Mode  :character   Mode  :character   Median :    6.00   Median :2011-07-31   Median :   1.950   Median :15159   Mode  :character   Median :    11.80  
                                                          Mean   :   13.02   Mean   :2011-07-10   Mean   :   3.116   Mean   :15294                      Mean   :    22.39  
                                                          3rd Qu.:   12.00   3rd Qu.:2011-10-20   3rd Qu.:   3.750   3rd Qu.:16795                      3rd Qu.:    19.80  
                                                          Max.   :80995.00   Max.   :2011-12-09   Max.   :8142.750   Max.   :18287                      Max.   :168469.60  
     Day               Month               Year          
 Length:397924      Length:397924      Length:397924     
 Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character  
                                                         
                                                         
                                                         
library(ggplot2)
package 㤼㸱ggplot2㤼㸲 was built under R version 3.6.1
ggplot(data, aes(y= Amount , fill= Amount))+ geom_boxplot()

data <- data[data$Amount<20000,]

data is ready to be analyzed. we will check for top 10 Products sold by Month-Year and Year. Top 5 Selling products by sales revenue in 2010 and 2011

Top_2010 <- aggregate(Amount~Description+Year,data,sum)
Top_2011 <- subset(Top_2010, Year == "2011")
Top_2010 <- subset(Top_2010, Year =="2010")
Top_2010 <- head(Top_2010[with(Top_2010,order(-Amount)),])
Top_2011 <- head(Top_2011[with(Top_2011,order(-Amount)),])
Top_2010$Description <- factor(Top_2010$Description ,levels = Top_2010$Description[order(Top_2010$Amount)])
Top_2011$Description <- factor(Top_2011$Description ,levels = Top_2011$Description[order(Top_2010$Amount)])
ggplot(Top_2010,aes(x = Description, y = Amount )) +
  geom_bar(stat = "identity", fill ="darkred") + coord_flip()

ggplot(Top_2011,aes(x=Description,y=Amount)) + 
  geom_bar(stat = "identity", fill = "darkblue") + coord_flip()

How did these Top 5 Selling Products perfrom monthly in 2010 and 2011

Top_monthly_sold_2011 <- subset(data,Description %in% c(as.vector(Top_2011$Description)), 
                                select = c(InvoiceNo,Description,Quantity,InvoiceDate,CustomerID,Amount,Month))
Top_monthly_sold_2010 <- subset(data,Description %in% c(as.vector(Top_2010$Description)), 
                                select = c(InvoiceNo,Description,Quantity,InvoiceDate,CustomerID,Amount,Month))
ggplot(Top_monthly_sold_2010, aes(x=Month, y= Amount )) + facet_wrap(~Description, ncol=2) + 
    geom_bar(stat="identity", fill = "lightblue") +  labs(title = "Sales by month", x = "Month", y = "Sales Revenue 2010")

ggplot(Top_monthly_sold_2011, aes(x=Month, y= Amount )) + facet_wrap(~Description, ncol=2) + 
    geom_bar(stat="identity",fill = "lightblue") +  labs(title = "Sales by month", x = "Month", y = "Sales Revenue 2011")

Top_qnty_2010 <- aggregate(Quantity~Description+Year,data,sum)
Top_qnty_2011 <- subset(Top_qnty_2010,Year=="2011")
Top_qnty_2010 <- subset(Top_qnty_2010,Year=="2010")
Top_qnty_2010 <- head(Top_qnty_2010[with(Top_qnty_2010,order(-Quantity)),])
Top_qnty_2011 <- head(Top_qnty_2011[with(Top_qnty_2011,order(-Quantity)),])
Top_qnty_2010$Description <- factor(Top_qnty_2010$Description,levels=Top_qnty_2010$Description[order(Top_qnty_2010$Quantity)])
Top_qnty_2011$Description <- factor(Top_qnty_2011$Description,levels=Top_qnty_2011$Description[order(Top_qnty_2011$Quantity)])
ggplot(Top_qnty_2010,aes(x=Description,y=Quantity)) + geom_bar(stat = "identity") + coord_flip()

ggplot(Top_qnty_2011,aes(x=Description,y=Quantity)) + geom_bar(stat = "identity") + coord_flip()

Top_monthly_qnty_2011 <- subset(data,Description %in% c(as.vector(Top_qnty_2011$Description)), select = c(InvoiceNo,Description,Quantity,InvoiceDate,CustomerID,Amount,Month))
Top_monthly_qnty_2010 <- subset(data,Description %in% c(as.vector(Top_qnty_2010$Description)), select = c( InvoiceNo,Description,Quantity,InvoiceDate,CustomerID,Amount,Month))
ggplot(Top_monthly_qnty_2011, aes(x=Month, y= Quantity )) + facet_wrap(~Description, ncol=2) + 
    geom_bar(stat="identity") + 
    labs(title = "Sales by month", x = "Month", y = "Quantity") 

ggplot(Top_monthly_qnty_2010, aes(x=Month, y= Quantity )) + facet_wrap(~Description, ncol=2) + 
    geom_bar(stat="identity") + 
    labs(title = "Sales by month", x = "Month", y = "Quantity") 

CountryWiseAggregation2010 <- aggregate(Amount~Country+Year,data,sum)
CountryWiseAggregation2011 <- subset(CountryWiseAggregation2010, Year=="2011")
CountryWiseAggregation2010 <- subset(CountryWiseAggregation2010,Year=="2010")
CountryWiseAggregation2010 <- CountryWiseAggregation2010[with(CountryWiseAggregation2010,order(-Amount)),]
CountryWiseAggregation2011 <- CountryWiseAggregation2011[with(CountryWiseAggregation2011,order(-Amount)),]
CountryWiseAggregation2010$Country <- factor(CountryWiseAggregation2010$Country,levels=CountryWiseAggregation2010$Country[order(CountryWiseAggregation2010$Amount)])
CountryWiseAggregation2011$Country <- factor(CountryWiseAggregation2011$Country,levels=CountryWiseAggregation2011$Country[order(CountryWiseAggregation2011$Amount)])
ggplot(CountryWiseAggregation2010,aes(x=Country,y=Amount)) + geom_bar(stat = "identity") + coord_flip() + labs(title="2010")

ggplot(CountryWiseAggregation2011,aes(x=Country,y=Amount)) + geom_bar(stat = "identity") + coord_flip() + labs(title="2011")

CountryProducts2010 <- subset(data,Country %in% c(as.vector(head(CountryWiseAggregation2010$Country))) & Year=="2010" , select = c(InvoiceNo,Description,Quantity,InvoiceDate,CustomerID,Country,Amount,Month,Year))
CountryProducts2011 <- subset(data,Country %in% c(as.vector(head(CountryWiseAggregation2011$Country))) & Year=="2011", select = c(InvoiceNo,Description,Quantity,InvoiceDate,CustomerID,Country,Amount,Month,Year))
TopProdCountryAgg2010 <- aggregate(Quantity~Country+Description+Year,CountryProducts2010,sum)
TopProdCountryAgg2011 <- aggregate(Quantity~Country+Description+Year,CountryProducts2011,sum)
TopProdCountryAgg2010 <- TopProdCountryAgg2010[with(TopProdCountryAgg2010,order(Country,-Quantity)),]
TopProdCountryAgg2011 <- TopProdCountryAgg2011[with(TopProdCountryAgg2011,order(Country,-Quantity)),]
TopProdTopCountry2010 <- TopProdCountryAgg2010[!duplicated(TopProdCountryAgg2010$Country),]
TopProdTopCountry2011 <- TopProdCountryAgg2011[!duplicated(TopProdCountryAgg2011$Country),]
print(TopProdTopCountry2010)
print(TopProdTopCountry2011)

RFM analysis

Here I will be preparing the data for the Recency, Frequency and Monetory analysis for Customer classification.There is nothing much to prepare other than formating dates for the analysis. We will need the following columns for the analysis InvoiceNo, Quantity, InvoiceDate, UnitPrice, CustomerId, Country, and Amount. Here I have included cancelled transactions as they can effect how the customer is classified.

dataRFM <- data[,-c(2,3)]
dataRFM$Amount <- dataRFM$Quantity*dataRFM$UnitPrice
dataRFM <- na.omit(dataRFM)
dataRFM<-aggregate(Amount~InvoiceNo+InvoiceDate+CustomerID,dataRFM,sum)
#install.packages("didrooRFM")
library(didrooRFM)
package 㤼㸱didrooRFM㤼㸲 was built under R version 3.6.1
dataRFM <- dataRFM[,c(1,3,2,4)]
RFM <- findRFM(dataRFM,recencyWeight = 5,frequencyWeight = 5,monetoryWeight = 5)

CustClass <- table(RFM$FinalCustomerClass)
CustClass

Class-1 Class-2 Class-3 Class-4 Class-5 
    790    1395    1249     805      99 
barplot(CustClass)

We can see that most of the customers belong to class 2 and 3. Customers belonging to Class 5 and 4 are our most valuebale Customers. Customer Class by Country

dataRFMCustomer <- data[!duplicated(data$CustomerID),c(7,8)] 
RFMCustContry <- merge(RFM,dataRFMCustomer,by="CustomerID")
CustclassAndCntry <- table(RFMCustContry$Country,RFMCustContry$FinalCustomerClass)
print(CustclassAndCntry)
                      
                       Class-1 Class-2 Class-3 Class-4 Class-5
  Australia                  1       3       2       3       0
  Austria                    1       4       3       1       0
  Bahrain                    1       1       0       0       0
  Belgium                    3       4       9       7       1
  Brazil                     0       1       0       0       0
  Canada                     2       1       1       0       0
  Channel Islands            0       4       3       2       0
  Cyprus                     1       1       3       2       0
  Czech Republic             0       0       1       0       0
  Denmark                    0       3       4       1       0
  EIRE                       0       0       1       0       2
  European Community         0       0       1       0       0
  Finland                    0       4       4       3       1
  France                    14      21      23      24       5
  Germany                    6      33      22      29       4
  Greece                     0       2       2       0       0
  Iceland                    0       0       0       0       1
  Israel                     0       2       1       0       0
  Italy                      0       7       5       2       0
  Japan                      1       4       1       2       0
  Lebanon                    0       1       0       0       0
  Lithuania                  0       0       1       0       0
  Malta                      0       1       0       1       0
  Netherlands                3       1       4       0       1
  Norway                     1       3       2       3       1
  Poland                     1       2       2       1       0
  Portugal                   2       6       5       5       1
  RSA                        0       0       1       0       0
  Saudi Arabia               1       0       0       0       0
  Singapore                  0       0       0       1       0
  Spain                      4       8      11       5       0
  Sweden                     1       2       3       1       1
  Switzerland                0       9       8       3       0
  United Arab Emirates       0       1       1       0       0
  United Kingdom           747    1262    1122     708      81
  Unspecified                0       3       1       0       0
  USA                        0       1       2       1       0
LS0tDQp0aXRsZTogIk9ubGluZSBSZXRhaWxlciINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQpjdXN0b21lciBzZWdtZW50YXRpb24gb24gb25saW5lLXJldGFpbGVyIGRhdGEgdXNpbmcgUkZNIGFuYWx5c2lzLiANCg0KYGBge3J9DQpsaWJyYXJ5KHJlYWR4bCkNCmRhdGE8LXJlYWRfZXhjZWwoIkM6XFxVc2Vyc1xcYmFkYWxcXERlc2t0b3BcXGRhdHNldF9cXE9ubGluZV9SZXRhaWwueGxzeCIpDQpgYGANCg0KYGBge3J9DQpoZWFkKGRhdGEpDQpgYGANCg0KYGBge3J9DQpzdHIoZGF0YSkNCmBgYA0KDQpgYGB7cn0NCmFueShpcy5uYShkYXRhKSkNCmBgYA0KDQpgYGB7cn0NCnN1bW1hcnkoZGF0YSkNCmBgYA0KDQpgYGB7cn0NCiNWaWV3KGRhdGEpDQpsaWJyYXJ5KGRwbHlyKQ0KZGF0YSA8LSBtdXRhdGUoZGF0YSwgSW52b2ljZURhdGUgPSBhcy5EYXRlKEludm9pY2VEYXRlLCIlbS8lZC8lWSIpKQ0KYGBgDQoNCmBgYHtyfQ0KZGF0YSRBbW91bnQgPC0gZGF0YSRRdWFudGl0eSpkYXRhJFVuaXRQcmljZQ0KYGBgDQoNCmBgYHtyfQ0KIHNhcHBseShkYXRhLCBmdW5jdGlvbihrKSBzdW0oaXMubmEoaykpKQ0KYGBgDQoNCmBgYHtyfQ0KbnJvdyhkYXRhKQ0KYGBgDQoNCmBgYHtyfQ0KDQpkYXRhIDwtIGRhdGFbIWlzLm5hKGRhdGEkQ3VzdG9tZXJJRCksXQ0KZGF0YSA8LSBkYXRhWyFpcy5uYShkYXRhJERlc2NyaXB0aW9uKSxdDQpucm93KGRhdGEpDQpgYGANCg0KYGBge3J9DQpkYXRhJERheSA8LSBmb3JtYXQoYXMuRGF0ZShkYXRhJEludm9pY2VEYXRlKSwiJWQiKQ0KZGF0YSRNb250aCA8LSBmb3JtYXQoYXMuRGF0ZShkYXRhJEludm9pY2VEYXRlKSwiJW0iKQ0KZGF0YSRZZWFyIDwtIGZvcm1hdChhcy5EYXRlKGRhdGEkSW52b2ljZURhdGUpLCIlWSIpDQpgYGANCmNuY2xkU2FsZXNEYXRhIDwtIHNhbGVzRGF0YVtzdGFydHNXaXRoKHNhbGVzRGF0YSRJbnZvaWNlTm8sIkMiKSxdDQpzYWxlc0RhdGEgPC0gc2FsZXNEYXRhWyFzdGFydHNXaXRoKHNhbGVzRGF0YSRJbnZvaWNlTm8sIkMiKSxdDQpgYGB7cn0NCmNuY2xkYXRhIDwtIGRhdGFbc3RhcnRzV2l0aChkYXRhJEludm9pY2VObywiQyIpLF0NCmRhdGEgPC0gZGF0YVshc3RhcnRzV2l0aChkYXRhJEludm9pY2VObywiQyIpLF0NCg0KYGBgDQoNCmBgYHtyfQ0Kc3VtbWFyeShkYXRhKQ0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KZ2dwbG90KGRhdGEsIGFlcyh5PSBBbW91bnQgLCBmaWxsPSBBbW91bnQpKSsgZ2VvbV9ib3hwbG90KCkNCmBgYA0KDQpgYGB7cn0NCmRhdGEgPC0gZGF0YVtkYXRhJEFtb3VudDwyMDAwMCxdDQpgYGANCmRhdGEgaXMgcmVhZHkgdG8gYmUgYW5hbHl6ZWQuDQp3ZSB3aWxsIGNoZWNrIGZvciB0b3AgMTAgUHJvZHVjdHMgc29sZCBieSBNb250aC1ZZWFyIGFuZCBZZWFyLg0KVG9wIDUgU2VsbGluZyBwcm9kdWN0cyBieSBzYWxlcyByZXZlbnVlIGluIDIwMTAgYW5kIDIwMTENCg0KDQpgYGB7cn0NClRvcF8yMDEwIDwtIGFnZ3JlZ2F0ZShBbW91bnR+RGVzY3JpcHRpb24rWWVhcixkYXRhLHN1bSkNClRvcF8yMDExIDwtIHN1YnNldChUb3BfMjAxMCwgWWVhciA9PSAiMjAxMSIpDQpUb3BfMjAxMCA8LSBzdWJzZXQoVG9wXzIwMTAsIFllYXIgPT0iMjAxMCIpDQpUb3BfMjAxMCA8LSBoZWFkKFRvcF8yMDEwW3dpdGgoVG9wXzIwMTAsb3JkZXIoLUFtb3VudCkpLF0pDQpUb3BfMjAxMSA8LSBoZWFkKFRvcF8yMDExW3dpdGgoVG9wXzIwMTEsb3JkZXIoLUFtb3VudCkpLF0pDQpUb3BfMjAxMCREZXNjcmlwdGlvbiA8LSBmYWN0b3IoVG9wXzIwMTAkRGVzY3JpcHRpb24gLGxldmVscyA9IFRvcF8yMDEwJERlc2NyaXB0aW9uW29yZGVyKFRvcF8yMDEwJEFtb3VudCldKQ0KVG9wXzIwMTEkRGVzY3JpcHRpb24gPC0gZmFjdG9yKFRvcF8yMDExJERlc2NyaXB0aW9uICxsZXZlbHMgPSBUb3BfMjAxMSREZXNjcmlwdGlvbltvcmRlcihUb3BfMjAxMCRBbW91bnQpXSkNCmdncGxvdChUb3BfMjAxMCxhZXMoeCA9IERlc2NyaXB0aW9uLCB5ID0gQW1vdW50ICkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSJkYXJrcmVkIikgKyBjb29yZF9mbGlwKCkNCg0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KFRvcF8yMDExLGFlcyh4PURlc2NyaXB0aW9uLHk9QW1vdW50KSkgKyANCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAiZGFya2JsdWUiKSArIGNvb3JkX2ZsaXAoKQ0KYGBgDQpIb3cgZGlkIHRoZXNlIFRvcCA1IFNlbGxpbmcgUHJvZHVjdHMgcGVyZnJvbSBtb250aGx5IGluIDIwMTAgYW5kIDIwMTENCg0KYGBge3J9DQoNClRvcF9tb250aGx5X3NvbGRfMjAxMSA8LSBzdWJzZXQoZGF0YSxEZXNjcmlwdGlvbiAlaW4lIGMoYXMudmVjdG9yKFRvcF8yMDExJERlc2NyaXB0aW9uKSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QgPSBjKEludm9pY2VObyxEZXNjcmlwdGlvbixRdWFudGl0eSxJbnZvaWNlRGF0ZSxDdXN0b21lcklELEFtb3VudCxNb250aCkpDQoNCmBgYA0KDQpgYGB7cn0NClRvcF9tb250aGx5X3NvbGRfMjAxMCA8LSBzdWJzZXQoZGF0YSxEZXNjcmlwdGlvbiAlaW4lIGMoYXMudmVjdG9yKFRvcF8yMDEwJERlc2NyaXB0aW9uKSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QgPSBjKEludm9pY2VObyxEZXNjcmlwdGlvbixRdWFudGl0eSxJbnZvaWNlRGF0ZSxDdXN0b21lcklELEFtb3VudCxNb250aCkpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoVG9wX21vbnRobHlfc29sZF8yMDEwLCBhZXMoeD1Nb250aCwgeT0gQW1vdW50ICkpICsgZmFjZXRfd3JhcCh+RGVzY3JpcHRpb24sIG5jb2w9MikgKyANCiAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIGZpbGwgPSAibGlnaHRibHVlIikgKyAgbGFicyh0aXRsZSA9ICJTYWxlcyBieSBtb250aCIsIHggPSAiTW9udGgiLCB5ID0gIlNhbGVzIFJldmVudWUgMjAxMCIpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoVG9wX21vbnRobHlfc29sZF8yMDExLCBhZXMoeD1Nb250aCwgeT0gQW1vdW50ICkpICsgZmFjZXRfd3JhcCh+RGVzY3JpcHRpb24sIG5jb2w9MikgKyANCiAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsZmlsbCA9ICJsaWdodGJsdWUiKSArICBsYWJzKHRpdGxlID0gIlNhbGVzIGJ5IG1vbnRoIiwgeCA9ICJNb250aCIsIHkgPSAiU2FsZXMgUmV2ZW51ZSAyMDExIikNCmBgYA0KDQpgYGB7cn0NClRvcF9xbnR5XzIwMTAgPC0gYWdncmVnYXRlKFF1YW50aXR5fkRlc2NyaXB0aW9uK1llYXIsZGF0YSxzdW0pDQpUb3BfcW50eV8yMDExIDwtIHN1YnNldChUb3BfcW50eV8yMDEwLFllYXI9PSIyMDExIikNClRvcF9xbnR5XzIwMTAgPC0gc3Vic2V0KFRvcF9xbnR5XzIwMTAsWWVhcj09IjIwMTAiKQ0KVG9wX3FudHlfMjAxMCA8LSBoZWFkKFRvcF9xbnR5XzIwMTBbd2l0aChUb3BfcW50eV8yMDEwLG9yZGVyKC1RdWFudGl0eSkpLF0pDQpUb3BfcW50eV8yMDExIDwtIGhlYWQoVG9wX3FudHlfMjAxMVt3aXRoKFRvcF9xbnR5XzIwMTEsb3JkZXIoLVF1YW50aXR5KSksXSkNClRvcF9xbnR5XzIwMTAkRGVzY3JpcHRpb24gPC0gZmFjdG9yKFRvcF9xbnR5XzIwMTAkRGVzY3JpcHRpb24sbGV2ZWxzPVRvcF9xbnR5XzIwMTAkRGVzY3JpcHRpb25bb3JkZXIoVG9wX3FudHlfMjAxMCRRdWFudGl0eSldKQ0KVG9wX3FudHlfMjAxMSREZXNjcmlwdGlvbiA8LSBmYWN0b3IoVG9wX3FudHlfMjAxMSREZXNjcmlwdGlvbixsZXZlbHM9VG9wX3FudHlfMjAxMSREZXNjcmlwdGlvbltvcmRlcihUb3BfcW50eV8yMDExJFF1YW50aXR5KV0pDQoNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChUb3BfcW50eV8yMDEwLGFlcyh4PURlc2NyaXB0aW9uLHk9UXVhbnRpdHkpKSArIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArIGNvb3JkX2ZsaXAoKQ0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KFRvcF9xbnR5XzIwMTEsYWVzKHg9RGVzY3JpcHRpb24seT1RdWFudGl0eSkpICsgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsgY29vcmRfZmxpcCgpDQpgYGANCg0KYGBge3J9DQpUb3BfbW9udGhseV9xbnR5XzIwMTEgPC0gc3Vic2V0KGRhdGEsRGVzY3JpcHRpb24gJWluJSBjKGFzLnZlY3RvcihUb3BfcW50eV8yMDExJERlc2NyaXB0aW9uKSksIHNlbGVjdCA9IGMoSW52b2ljZU5vLERlc2NyaXB0aW9uLFF1YW50aXR5LEludm9pY2VEYXRlLEN1c3RvbWVySUQsQW1vdW50LE1vbnRoKSkNCg0KDQpgYGANCg0KYGBge3J9DQpUb3BfbW9udGhseV9xbnR5XzIwMTAgPC0gc3Vic2V0KGRhdGEsRGVzY3JpcHRpb24gJWluJSBjKGFzLnZlY3RvcihUb3BfcW50eV8yMDEwJERlc2NyaXB0aW9uKSksIHNlbGVjdCA9IGMoIEludm9pY2VObyxEZXNjcmlwdGlvbixRdWFudGl0eSxJbnZvaWNlRGF0ZSxDdXN0b21lcklELEFtb3VudCxNb250aCkpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoVG9wX21vbnRobHlfcW50eV8yMDExLCBhZXMoeD1Nb250aCwgeT0gUXVhbnRpdHkgKSkgKyBmYWNldF93cmFwKH5EZXNjcmlwdGlvbiwgbmNvbD0yKSArIA0KICAgIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKyANCiAgICBsYWJzKHRpdGxlID0gIlNhbGVzIGJ5IG1vbnRoIiwgeCA9ICJNb250aCIsIHkgPSAiUXVhbnRpdHkiKSANCmBgYA0KDQpgYGB7cn0NCmdncGxvdChUb3BfbW9udGhseV9xbnR5XzIwMTAsIGFlcyh4PU1vbnRoLCB5PSBRdWFudGl0eSApKSArIGZhY2V0X3dyYXAofkRlc2NyaXB0aW9uLCBuY29sPTIpICsgDQogICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArIA0KICAgIGxhYnModGl0bGUgPSAiU2FsZXMgYnkgbW9udGgiLCB4ID0gIk1vbnRoIiwgeSA9ICJRdWFudGl0eSIpIA0KYGBgDQoNCmBgYHtyfQ0KQ291bnRyeVdpc2VBZ2dyZWdhdGlvbjIwMTAgPC0gYWdncmVnYXRlKEFtb3VudH5Db3VudHJ5K1llYXIsZGF0YSxzdW0pDQpDb3VudHJ5V2lzZUFnZ3JlZ2F0aW9uMjAxMSA8LSBzdWJzZXQoQ291bnRyeVdpc2VBZ2dyZWdhdGlvbjIwMTAsIFllYXI9PSIyMDExIikNCkNvdW50cnlXaXNlQWdncmVnYXRpb24yMDEwIDwtIHN1YnNldChDb3VudHJ5V2lzZUFnZ3JlZ2F0aW9uMjAxMCxZZWFyPT0iMjAxMCIpDQpDb3VudHJ5V2lzZUFnZ3JlZ2F0aW9uMjAxMCA8LSBDb3VudHJ5V2lzZUFnZ3JlZ2F0aW9uMjAxMFt3aXRoKENvdW50cnlXaXNlQWdncmVnYXRpb24yMDEwLG9yZGVyKC1BbW91bnQpKSxdDQpDb3VudHJ5V2lzZUFnZ3JlZ2F0aW9uMjAxMSA8LSBDb3VudHJ5V2lzZUFnZ3JlZ2F0aW9uMjAxMVt3aXRoKENvdW50cnlXaXNlQWdncmVnYXRpb24yMDExLG9yZGVyKC1BbW91bnQpKSxdDQoNCg0KYGBgDQoNCmBgYHtyfQ0KQ291bnRyeVdpc2VBZ2dyZWdhdGlvbjIwMTAkQ291bnRyeSA8LSBmYWN0b3IoQ291bnRyeVdpc2VBZ2dyZWdhdGlvbjIwMTAkQ291bnRyeSxsZXZlbHM9Q291bnRyeVdpc2VBZ2dyZWdhdGlvbjIwMTAkQ291bnRyeVtvcmRlcihDb3VudHJ5V2lzZUFnZ3JlZ2F0aW9uMjAxMCRBbW91bnQpXSkNCg0KQ291bnRyeVdpc2VBZ2dyZWdhdGlvbjIwMTEkQ291bnRyeSA8LSBmYWN0b3IoQ291bnRyeVdpc2VBZ2dyZWdhdGlvbjIwMTEkQ291bnRyeSxsZXZlbHM9Q291bnRyeVdpc2VBZ2dyZWdhdGlvbjIwMTEkQ291bnRyeVtvcmRlcihDb3VudHJ5V2lzZUFnZ3JlZ2F0aW9uMjAxMSRBbW91bnQpXSkNCg0KYGBgDQoNCmBgYHtyfQ0KDQoNCmdncGxvdChDb3VudHJ5V2lzZUFnZ3JlZ2F0aW9uMjAxMCxhZXMoeD1Db3VudHJ5LHk9QW1vdW50KSkgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyBjb29yZF9mbGlwKCkgKyBsYWJzKHRpdGxlPSIyMDEwIikNCg0KYGBgDQoNCmBgYHtyfQ0KDQoNCmdncGxvdChDb3VudHJ5V2lzZUFnZ3JlZ2F0aW9uMjAxMSxhZXMoeD1Db3VudHJ5LHk9QW1vdW50KSkgKyBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKyBjb29yZF9mbGlwKCkgKyBsYWJzKHRpdGxlPSIyMDExIikNCg0KYGBgDQoNCmBgYHtyfQ0KQ291bnRyeVByb2R1Y3RzMjAxMCA8LSBzdWJzZXQoZGF0YSxDb3VudHJ5ICVpbiUgYyhhcy52ZWN0b3IoaGVhZChDb3VudHJ5V2lzZUFnZ3JlZ2F0aW9uMjAxMCRDb3VudHJ5KSkpICYgWWVhcj09IjIwMTAiICwgc2VsZWN0ID0gYyhJbnZvaWNlTm8sRGVzY3JpcHRpb24sUXVhbnRpdHksSW52b2ljZURhdGUsQ3VzdG9tZXJJRCxDb3VudHJ5LEFtb3VudCxNb250aCxZZWFyKSkNCg0KQ291bnRyeVByb2R1Y3RzMjAxMSA8LSBzdWJzZXQoZGF0YSxDb3VudHJ5ICVpbiUgYyhhcy52ZWN0b3IoaGVhZChDb3VudHJ5V2lzZUFnZ3JlZ2F0aW9uMjAxMSRDb3VudHJ5KSkpICYgWWVhcj09IjIwMTEiLCBzZWxlY3QgPSBjKEludm9pY2VObyxEZXNjcmlwdGlvbixRdWFudGl0eSxJbnZvaWNlRGF0ZSxDdXN0b21lcklELENvdW50cnksQW1vdW50LE1vbnRoLFllYXIpKQ0KDQpUb3BQcm9kQ291bnRyeUFnZzIwMTAgPC0gYWdncmVnYXRlKFF1YW50aXR5fkNvdW50cnkrRGVzY3JpcHRpb24rWWVhcixDb3VudHJ5UHJvZHVjdHMyMDEwLHN1bSkNClRvcFByb2RDb3VudHJ5QWdnMjAxMSA8LSBhZ2dyZWdhdGUoUXVhbnRpdHl+Q291bnRyeStEZXNjcmlwdGlvbitZZWFyLENvdW50cnlQcm9kdWN0czIwMTEsc3VtKQ0KDQoNCg0KVG9wUHJvZENvdW50cnlBZ2cyMDEwIDwtIFRvcFByb2RDb3VudHJ5QWdnMjAxMFt3aXRoKFRvcFByb2RDb3VudHJ5QWdnMjAxMCxvcmRlcihDb3VudHJ5LC1RdWFudGl0eSkpLF0NClRvcFByb2RDb3VudHJ5QWdnMjAxMSA8LSBUb3BQcm9kQ291bnRyeUFnZzIwMTFbd2l0aChUb3BQcm9kQ291bnRyeUFnZzIwMTEsb3JkZXIoQ291bnRyeSwtUXVhbnRpdHkpKSxdDQpUb3BQcm9kVG9wQ291bnRyeTIwMTAgPC0gVG9wUHJvZENvdW50cnlBZ2cyMDEwWyFkdXBsaWNhdGVkKFRvcFByb2RDb3VudHJ5QWdnMjAxMCRDb3VudHJ5KSxdDQpUb3BQcm9kVG9wQ291bnRyeTIwMTEgPC0gVG9wUHJvZENvdW50cnlBZ2cyMDExWyFkdXBsaWNhdGVkKFRvcFByb2RDb3VudHJ5QWdnMjAxMSRDb3VudHJ5KSxdDQpgYGANCg0KYGBge3J9DQpwcmludChUb3BQcm9kVG9wQ291bnRyeTIwMTApDQpgYGANCg0KYGBge3J9DQpwcmludChUb3BQcm9kVG9wQ291bnRyeTIwMTEpDQpgYGANCiNSRk0gYW5hbHlzaXMNCkhlcmUgSSB3aWxsIGJlIHByZXBhcmluZyB0aGUgZGF0YSBmb3IgdGhlIFJlY2VuY3ksIEZyZXF1ZW5jeSBhbmQgTW9uZXRvcnkgYW5hbHlzaXMgZm9yIEN1c3RvbWVyIGNsYXNzaWZpY2F0aW9uLlRoZXJlIGlzIG5vdGhpbmcgbXVjaCB0byBwcmVwYXJlIG90aGVyIHRoYW4gZm9ybWF0aW5nIGRhdGVzIGZvciB0aGUgYW5hbHlzaXMuDQpXZSB3aWxsIG5lZWQgdGhlIGZvbGxvd2luZyBjb2x1bW5zIGZvciB0aGUgYW5hbHlzaXMgSW52b2ljZU5vLCBRdWFudGl0eSwgSW52b2ljZURhdGUsIFVuaXRQcmljZSwgQ3VzdG9tZXJJZCwgQ291bnRyeSwgYW5kIEFtb3VudC4gSGVyZSBJIGhhdmUgaW5jbHVkZWQgY2FuY2VsbGVkIHRyYW5zYWN0aW9ucyBhcyB0aGV5IGNhbiBlZmZlY3QgaG93IHRoZSBjdXN0b21lciBpcyBjbGFzc2lmaWVkLg0KYGBge3J9DQpkYXRhUkZNIDwtIGRhdGFbLC1jKDIsMyldDQpkYXRhUkZNJEFtb3VudCA8LSBkYXRhUkZNJFF1YW50aXR5KmRhdGFSRk0kVW5pdFByaWNlDQpkYXRhUkZNIDwtIG5hLm9taXQoZGF0YVJGTSkNCmBgYA0KDQpgYGB7cn0NCmRhdGFSRk08LWFnZ3JlZ2F0ZShBbW91bnR+SW52b2ljZU5vK0ludm9pY2VEYXRlK0N1c3RvbWVySUQsZGF0YVJGTSxzdW0pDQpgYGANCg0KYGBge3J9DQojaW5zdGFsbC5wYWNrYWdlcygiZGlkcm9vUkZNIikNCmxpYnJhcnkoZGlkcm9vUkZNKQ0KZGF0YVJGTSA8LSBkYXRhUkZNWyxjKDEsMywyLDQpXQ0KUkZNIDwtIGZpbmRSRk0oZGF0YVJGTSxyZWNlbmN5V2VpZ2h0ID0gNSxmcmVxdWVuY3lXZWlnaHQgPSA1LG1vbmV0b3J5V2VpZ2h0ID0gNSkNCmBgYA0KDQpgYGB7cn0NCkN1c3RDbGFzcyA8LSB0YWJsZShSRk0kRmluYWxDdXN0b21lckNsYXNzKQ0KQ3VzdENsYXNzDQpgYGANCg0KYGBge3J9DQpiYXJwbG90KEN1c3RDbGFzcykNCmBgYA0KDQpXZSBjYW4gc2VlIHRoYXQgbW9zdCBvZiB0aGUgY3VzdG9tZXJzIGJlbG9uZyB0byBjbGFzcyAyIGFuZCAzLiBDdXN0b21lcnMgYmVsb25naW5nIHRvIENsYXNzIDUgYW5kIDQgYXJlIG91ciBtb3N0IHZhbHVlYmFsZSBDdXN0b21lcnMuDQpDdXN0b21lciBDbGFzcyBieSBDb3VudHJ5DQpgYGB7cn0NCmRhdGFSRk1DdXN0b21lciA8LSBkYXRhWyFkdXBsaWNhdGVkKGRhdGEkQ3VzdG9tZXJJRCksYyg3LDgpXSANClJGTUN1c3RDb250cnkgPC0gbWVyZ2UoUkZNLGRhdGFSRk1DdXN0b21lcixieT0iQ3VzdG9tZXJJRCIpDQpDdXN0Y2xhc3NBbmRDbnRyeSA8LSB0YWJsZShSRk1DdXN0Q29udHJ5JENvdW50cnksUkZNQ3VzdENvbnRyeSRGaW5hbEN1c3RvbWVyQ2xhc3MpDQpwcmludChDdXN0Y2xhc3NBbmRDbnRyeSkNCmBgYA0KDQo=