EDA and Modeling Exercise

This notebook uses the Customer Segmentation: AV - Janatahack : Customer Segmentation dataset. The objective of the notebook is to identify the key features that can predict customer segments of an automobile company.

Load libaries

library(tidyverse)
library(skimr)
library(scales)
library(ggpubr)
library(psych)
library(colorspace)
library(PerformanceAnalytics)
library(caret)
library(rpart)
library(rpart.plot)
library(rattle)
library(randomForest)
library(xgboost)
library(nnet)

Import Data

train= readr::read_csv("cust_seg_train.csv")

── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  ID = col_double(),
  Gender = col_character(),
  Ever_Married = col_character(),
  Age = col_double(),
  Graduated = col_character(),
  Profession = col_character(),
  Work_Experience = col_double(),
  Spending_Score = col_character(),
  Family_Size = col_double(),
  Var_1 = col_character(),
  Segmentation = col_character()
)
test= readr::read_csv("cust_seg_test.csv")

── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  ID = col_double(),
  Gender = col_character(),
  Ever_Married = col_character(),
  Age = col_double(),
  Graduated = col_character(),
  Profession = col_character(),
  Work_Experience = col_double(),
  Spending_Score = col_character(),
  Family_Size = col_double(),
  Var_1 = col_character()
)

Summary

skim(train)
── Data Summary ────────────────────────
                           Values
Name                       train 
Number of rows             8068  
Number of columns          11    
_______________________          
Column type frequency:           
  character                7     
  numeric                  4     
________________________         
Group variables            None  

── Variable type: character ──────────────────────────────────────────────────────────────────────────────────────────
  skim_variable  n_missing complete_rate   min   max empty n_unique whitespace
1 Gender                 0         1         4     6     0        2          0
2 Ever_Married         140         0.983     2     3     0        2          0
3 Graduated             78         0.990     2     3     0        2          0
4 Profession           124         0.985     6    13     0        9          0
5 Spending_Score         0         1         3     7     0        3          0
6 Var_1                 76         0.991     5     5     0        7          0
7 Segmentation           0         1         1     1     0        4          0

── Variable type: numeric ────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable   n_missing complete_rate      mean      sd     p0     p25     p50     p75   p100 hist 
1 ID                      0         1     463479.   2595.   458982 461241. 463472. 465744. 467974 ▇▇▇▇▇
2 Age                     0         1         43.5    16.7      18     30      40      53      89 ▇▇▅▃▂
3 Work_Experience       829         0.897      2.64    3.41      0      0       1       4      14 ▇▁▂▁▁
4 Family_Size           335         0.958      2.85    1.53      1      2       3       4       9 ▇▆▁▁▁
skim(test)
── Data Summary ────────────────────────
                           Values
Name                       test  
Number of rows             2627  
Number of columns          10    
_______________________          
Column type frequency:           
  character                6     
  numeric                  4     
________________________         
Group variables            None  

── Variable type: character ──────────────────────────────────────────────────────────────────────────────────────────
  skim_variable  n_missing complete_rate   min   max empty n_unique whitespace
1 Gender                 0         1         4     6     0        2          0
2 Ever_Married          50         0.981     2     3     0        2          0
3 Graduated             24         0.991     2     3     0        2          0
4 Profession            38         0.986     6    13     0        9          0
5 Spending_Score         0         1         3     7     0        3          0
6 Var_1                 32         0.988     5     5     0        7          0

── Variable type: numeric ────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable   n_missing complete_rate      mean      sd     p0     p25    p50    p75   p100 hist 
1 ID                      0         1     463434.   2618.   458989 461162. 463379 465696 467968 ▇▇▇▇▇
2 Age                     0         1         43.6    17.0      18     30      41     53     89 ▇▇▅▃▂
3 Work_Experience       269         0.898      2.55    3.34      0      0       1      4     14 ▇▁▁▁▁
4 Family_Size           113         0.957      2.83    1.55      1      2       2      4      9 ▇▆▁▁▁

Visualization

Missing data

# highlighted bar plot
p1 = train %>% summarise(across(everything(), ~mean(!is.na(.)))) %>% 
  gather() %>%
  mutate(key= fct_reorder(key, value)) %>%
  ggplot(aes(key, value)) +
  geom_col(aes(fill=I(ifelse(value==1,'#adb5bd','#98c1d9'))),width=0.8) +
  geom_text(aes(label= percent(value)),
            nudge_y=0.07, size=3, hjust=1.7) + 
  scale_y_continuous(labels= scales::percent) + 
  theme_minimal() +
  theme(
    panel.grid.major.x = element_blank(),
    panel.grid.minor.x = element_blank(),
    panel.grid.major.y = element_blank(),
    axis.title=element_text(size=10),
    plot.title=element_text(size=12,hjust=0.5),
    plot.title.position = "plot",
    legend.position="none"
  ) +
  theme(axis.text.x=element_blank()) +
  labs(x="Feature", y="% of data present", title="Train data") +
  coord_flip()  

p2 = test %>% summarise(across(everything(), ~mean(!is.na(.)))) %>% 
  gather() %>%
  mutate(key= fct_reorder(key, value)) %>%
  ggplot(aes(key, value)) +
  geom_col(aes(fill=I(ifelse(value==1,'#adb5bd','#98c1d9'))),width=0.8) +
  geom_text(aes(label= percent(value)),
            nudge_y=0.07, size=3, hjust=1.7) + 
  scale_y_continuous(labels= scales::percent) + 
  theme_minimal() +
  theme(
    panel.grid.major.x = element_blank(),
    panel.grid.minor.x = element_blank(),
    panel.grid.major.y = element_blank(),
    axis.title=element_text(size=10),
    plot.title=element_text(size=12,hjust=0.5),
    plot.title.position = "plot",
    legend.position="none"
  ) +
  theme(axis.text.x=element_blank()) +
  labs(x="Feature", y="% of data present", title="Test data") +
  coord_flip() 

ggarrange(p1,p2, ncol=2)

# add labels to train and test sets
train$test_1 = 0 
test$test_1 = 1
# bind test and train data 
data = bind_rows(train, test)
dim(data)
[1] 10695    12
head(data)

Segmentation

# Segmentation
train %>% group_by(Segmentation) %>% tally() %>% mutate(proportion=round(n/sum(n),3))

Var_1 (Anonymised Category for the customer)

# Var_1 (Anonymised Category for the customer)

# table (combined data)
data %>% group_by(Var_1) %>% tally() %>% mutate(proportion=round(n/sum(n),3))

# count plot
v1 = train %>% 
  ggplot(aes(x=Var_1, fill=Segmentation)) +
  geom_bar(stat="count", position="dodge", alpha=0.9) + 
  scale_fill_manual(values=c("#f3722c","#f9c74f","#43aa8b","#577590")) + 
  labs(subtitle="Var_1 (Anonymised Category for the customer)") + 
  theme_light() + 
  theme(axis.title=element_text(size=10),
        legend.title=element_text(size=9),
        legend.position = c(0.13, 0.75))

# heatmap
v2 = train %>% 
  group_by(Segmentation, Var_1) %>% 
  tally() %>% 
  mutate(pct=round(n/sum(n)*100,2)) %>% 
  ggplot(aes(y=fct_rev(Segmentation), x=Var_1, fill=pct)) +
  geom_tile(color="white",size=2) + 
  geom_text(aes(label=paste(pct,"%")), size=3) + 
  scale_fill_distiller(palette = 'Spectral') +
  theme_light() +
  theme(legend.position="top",
        axis.text.y=element_text(size=12),
        axis.ticks=element_blank(),
        axis.title=element_text(size=10),
        legend.title=element_text(size=9),
        axis.title.y = element_text(margin = margin(t = 0, r = 5, b = 0, l = 0)),
        axis.title.x = element_text(margin = margin(t = 5, r = 0, b = 0, l = 0))) +
  labs(y="Segmentation", x="Var_1", 
       fill="Percentage", subtitle="Var_1 and Segmentation") + 
  guides(fill = guide_colorbar(title.position = "top", 
                                title.hjust = .5, 
                                barwidth = unit(20, "lines"), 
                                barheight = unit(.5, "lines")))

# combine plot
ggarrange(v1,v2,ncol=2)

Age

# Age
# histogram with kernel density curve (test and train data)
age1 = ggplot(data, aes(x=Age)) + 
  geom_histogram(aes(y=..density..),
                   binwidth=2,
                   fill="#6c757d", alpha=0.5) +
  geom_density(alpha=.2, color="#00a8e8", size=1) + 
  labs(subtitle="Age Distribution (train and test data combined)") +
  scale_x_continuous(limits = c(0, 100), breaks=seq(0,100,20), oob = scales::oob_keep) +
  theme_light()

# line plot (train data)
age2 = ggplot(data, aes(x=Age)) + 
  geom_density(aes(color=Segmentation),key_glyph = draw_key_point, size=0.8) + 
  scale_x_continuous(limits=c(0,100), breaks=seq(0,100,20)) + 
  scale_y_continuous(limits=c(0,0.045)) + 
  scale_color_manual(values=c("#f3722c","#f9c74f","#43aa8b","#577590"), na.value="#c9ada7") + 
  theme_light() + 
  theme(legend.position = c(0.9, 0.75),
        legend.text = element_text(size=8),
        legend.title = element_text(size=8)) +
  guides(color = guide_legend(override.aes = list(shape=15,size=5))) + 
  labs(subtitle= "Age Distribution (train data)")

ggarrange(age1, age2, ncol=2)

# summary of age by segment (train data)
psych::describeBy(train$Age, train$Segmentation,mat=TRUE)

Work Experience

# Work_Experience

# summary by segment
psych::describeBy(train$Work_Experience, train$Segmentation,mat=TRUE)

# count plot
we1 = data %>% 
  filter(!is.na(Work_Experience)) %>%
  ggplot(aes(x=factor(Work_Experience))) +
  geom_bar(stat="count",fill="#98c1d9") + 
  theme_light() + 
  theme(panel.grid.major.x=element_blank(),
        axis.title=element_text(size=10)) +
  labs(x="Work_Experience (in years)", subtitle="Work_Experience")

# count plot by segment
we2 = train %>% 
  filter(!is.na(Work_Experience)) %>%
  ggplot(aes(x=factor(Work_Experience), fill=Segmentation)) +
  geom_bar(stat="count", show.legend = F) + 
  theme_light() + 
  facet_wrap(~Segmentation, labeller=label_both) +
  theme(panel.grid.major.x=element_blank(),
        axis.title=element_text(size=10))+
  labs(x="Work_Experience (in years)", subtitle="Work_Experience and Segementation") +
  scale_fill_manual(values=c("#f3722c","#f9c74f","#43aa8b","#577590"))

# combine plot
ggarrange(we1, we2, ncol=2)

Family size

# Family_Size

# summary of Family_Size by segment
psych::describeBy(train$Family_Size, train$Segmentation,mat=TRUE)

# distribution
fs1 = data %>% 
  filter(!is.na(Family_Size)) %>%
  ggplot(aes(x=Family_Size)) + 
  geom_histogram(binwidth = 1, color="white", fill="#98c1d9") + 
  geom_vline(aes(xintercept=mean(Family_Size)),color="#d66853", linetype="dashed", size=1) + 
  geom_text(aes(x=3.9, y=2900, label="Mean = 2.83"), size=3.5, color="#495057") + 
  scale_x_continuous(breaks=seq(1,10,1)) + 
  theme_light() + 
  theme(panel.grid.minor.x=element_blank(),
        axis.title=element_text(size=10)) +
  labs(subtitle="Family_Size")

# bubble plot
fs2 = train %>% 
  filter(!is.na(Family_Size)) %>%
  group_by(Segmentation, Family_Size) %>% tally() %>% rename(Count=n) %>%
  ggplot(aes(y=Segmentation, x=factor(Family_Size))) + 
  geom_point(aes(size=Count, color=Segmentation)) + 
  scale_color_manual(values=c("#f3722c","#f9c74f","#43aa8b","#577590")) + 
  theme(legend.position="bottom",
        axis.title=element_text(size=10),
        legend.title=element_text(size=9)) + 
  labs(x="Family_Size", subtitle="Family_Size and Segmentation") + 
  guides(color=FALSE) + scale_size(range=c(2,12))

ggarrange(fs1,fs2,ncol=2)

Gender

# Gender 
# count plot
ge1 = train %>% 
  ggplot(aes(x=Gender, fill=Segmentation)) +
  geom_bar(stat="count", position="dodge", alpha=0.9) + 
  geom_text(stat="count", aes(label=..count.., group=Segmentation), 
            vjust=1.4, position=position_dodge(width=0.9), size=3.5) +
  scale_fill_manual(values=c("#f3722c","#f9c74f","#43aa8b","#577590")) + 
  labs(subtitle="Gender: Count") + 
  theme_light() + 
  theme(legend.position="bottom",
        axis.title=element_text(size=10),
        legend.title=element_text(size=9),)

# proportion plot
ge2 = train %>% 
  group_by(Segmentation, Gender) %>% tally() %>% mutate(proportion=round(n/sum(n),3)) %>%
  ggplot(aes(y=fct_rev(Segmentation), x=proportion, fill=Gender)) +
  geom_col(width=0.7, alpha=0.9) + 
  geom_text(aes(x = proportion, y = Segmentation, label = paste0(proportion*100, "%")), 
            size = 3, position = position_fill(vjust = 0.5), color="white") +
  theme_light() + 
  theme(legend.position="bottom",
        axis.title=element_text(size=10),
        legend.title=element_text(size=9),) + 
  scale_fill_manual(values=c("#d66853","#364156"), guide=guide_legend(reverse=T)) + 
  labs(fill="Gender", y="Segmentation", x="percentage",subtitle="Gender: Percentage") + 
  scale_x_continuous(labels=scales::percent) 

ggarrange(ge1,ge2, ncol=2) 

Ever married

# Ever_Married 
# count plot
em1 = train %>% filter(Ever_Married!="") %>%
  ggplot(aes(x=Ever_Married, fill=Segmentation)) +
  geom_bar(stat="count", position="dodge", alpha=0.9) + 
  geom_text(stat="count", aes(label=..count.., group=Segmentation), 
            vjust=1.4, position=position_dodge(width=0.9), size=3.5) +
  scale_fill_manual(values=c("#f3722c","#f9c74f","#43aa8b","#577590")) + 
  labs(subtitle="Ever_Married: Count") + 
  theme_light() + 
  theme(legend.position="bottom",
        axis.title=element_text(size=10),
        legend.title=element_text(size=9),)

# proportion plot
em2 = train %>% filter(Ever_Married!="") %>%
  group_by(Segmentation, Ever_Married) %>% tally() %>% mutate(proportion=round(n/sum(n),3)) %>%
  ggplot(aes(y=fct_rev(Segmentation), x=proportion, fill=factor(Ever_Married))) +
  geom_col(width=0.7, alpha=0.9) + 
  geom_text(aes(x = proportion, y = Segmentation, label = paste0(proportion*100, "%")), 
            size = 3, position = position_fill(vjust = 0.5), color="white") +
  theme_light() + 
  theme(legend.position="bottom",
        axis.title=element_text(size=10),
        legend.title=element_text(size=9),) + 
  scale_fill_manual(values=c("#d66853","#364156"), guide=guide_legend(reverse=T)) + 
  labs(fill="Ever_Married", y="Segmentation", x="percentage",subtitle="Ever_Married: Percentage") + 
  scale_x_continuous(labels=scales::percent) 

ggarrange(em1,em2, ncol=2)

Graudated

# Graduated 
# count plot
gr1 = train %>% filter(Graduated!="") %>%
  ggplot(aes(x=Graduated, fill=Segmentation)) +
  geom_bar(stat="count", position="dodge", alpha=0.9) + 
  geom_text(stat="count", aes(label=..count.., group=Segmentation), 
            vjust=1.4, position=position_dodge(width=0.9), size=3.5) +
  scale_fill_manual(values=c("#f3722c","#f9c74f","#43aa8b","#577590")) + 
  labs(subtitle="Graduated: Count") + 
  theme_light() + 
  theme(legend.position="bottom",
        axis.title=element_text(size=10),
        legend.title=element_text(size=9),)

# proportion plot
gr2 = train %>% filter(Graduated!="") %>%
  group_by(Segmentation, Graduated) %>% tally() %>% mutate(proportion=round(n/sum(n),3)) %>%
  ggplot(aes(y=fct_rev(Segmentation), x=proportion, fill=factor(Graduated))) +
  geom_col(width=0.7, alpha=0.9) + 
  geom_text(aes(x = proportion, y = Segmentation, label = paste0(proportion*100, "%")), 
            size = 3, position = position_fill(vjust = 0.5), color="white") +
  theme_light() + 
  theme(legend.position="bottom",
        axis.title=element_text(size=10),
        legend.title=element_text(size=9),) + 
  scale_fill_manual(values=c("#d66853","#364156"), guide=guide_legend(reverse=T)) + 
  labs(fill="Graduated", y="Segmentation", x="percentage",subtitle="Graduated: Percentage") + 
  scale_x_continuous(labels=scales::percent) 

ggarrange(gr1,gr2, ncol=2)

Spending score

# Spending_Score
# count plot
ss1 = train %>% 
  ggplot(aes(x=Spending_Score, fill=Segmentation)) +
  geom_bar(stat="count", position="dodge", alpha=0.9) + 
  geom_text(stat="count", aes(label=..count.., group=Segmentation), 
            vjust=1.4, position=position_dodge(width=0.9), size=3) +
  scale_fill_manual(values=c("#f3722c","#f9c74f","#43aa8b","#577590")) + 
  theme_light() +
  theme(legend.position="bottom") + 
  labs(subtitle="Spending Score: Count")

# proportion plot
ss2 = train %>% 
  group_by(Segmentation, Spending_Score) %>% tally() %>% mutate(proportion=round(n/sum(n),3)) %>%
  ggplot(aes(y=fct_rev(Segmentation), x=proportion, fill=factor(Spending_Score, level=c("High","Average","Low")))) +
  geom_col(width=0.7, alpha=0.9) + 
  geom_text(aes(x = proportion, y = Segmentation, label = paste0(proportion*100, "%")), 
            size = 3, position = position_fill(vjust = 0.5), color="white") +
  theme_light() + 
  theme(legend.position="bottom") + 
  scale_fill_manual(values=c("#d66853","#7d4e57","#364156"), guide=guide_legend(reverse=T)) + 
  labs(fill="Spending_Score", y="Segmentation", x="percentage", subtitle="Spending Score: Percentage") + 
  scale_x_continuous(labels=scales::percent) 

ggarrange(ss1,ss2, ncol=2)

Profession

# Profession 
# level frequency
train %>% group_by(Profession) %>% tally() %>% mutate(proportion=round(n/sum(n),3))

# count
train %>% filter(Profession!="") %>%
  group_by(Profession, Segmentation) %>% 
  tally() %>%
  ggplot(aes(y=Profession, x=n)) + 
  geom_line(aes(group=Profession), color="#736f72") +
  geom_point(aes(color=Segmentation),key_glyph = draw_key_point, alpha=0.8, size=3) + 
  scale_color_manual(values=c("#f3722c","#f9c74f","#43aa8b","#577590")) + 
  guides(color = guide_legend(override.aes = list(shape=15,size=5))) +
  theme_light() + 
  theme(legend.position="top",
        plot.title.position = "plot") +
  labs(x="count", subtitle="Profession: Count")


# proportion
train %>% 
  group_by(Segmentation, Profession) %>% 
  tally() %>% 
  mutate(pct=round(n/sum(n)*100,2)) %>% 
  ggplot(aes(y=fct_rev(Segmentation), x=Profession, fill=pct)) +
  geom_tile(color="white",size=2) + 
  geom_text(aes(label=paste(pct,"%")), size=3) + 
  scale_fill_distiller(palette = 'Spectral') +
  theme_light() +
  theme(legend.position="top",
        axis.text.y=element_text(size=12),
        axis.ticks=element_blank(),
        axis.title=element_text(size=10),
        legend.title=element_text(size=9),
        plot.title.position = "plot",
        axis.title.y = element_text(margin = margin(t = 0, r = 5, b = 0, l = 0)),
        axis.title.x = element_text(margin = margin(t = 5, r = 0, b = 0, l = 0))) +
  labs(y="Segmentation", x="Profession", 
       fill="Percentage", subtitle="Profession: Percentage") + 
  guides(fill = guide_colorbar(title.position = "top", 
                                title.hjust = .5, 
                                barwidth = unit(20, "lines"), 
                                barheight = unit(.5, "lines")))

Correlation

# correlation 
c_data <- data[, c(4,7,9)]
chart.Correlation(c_data , histogram=TRUE, pch=19)

Data preparation

# count of missing data by column
data %>% type.convert() %>% sapply(function(x)sum(is.na(x))) 
             ID          Gender    Ever_Married             Age       Graduated      Profession Work_Experience  Spending_Score 
              0               0             190               0             102             162            1098               0 
    Family_Size           Var_1    Segmentation          test_1 
            448             108            2627               0 
cdf = data

# dummify Spending_Score
cdf$Spending_Score= as.numeric(factor(cdf$Spending_Score, order=TRUE, levels=c("Low","Average","High")))

# inpute missing values
# replace with mean
cdf$Family_Size <- ifelse(is.na(cdf$Family_Size), mean(cdf$Family_Size, na.rm=TRUE), cdf$Family_Size)
cdf$Work_Experience <- ifelse(is.na(cdf$Work_Experience), mean(cdf$Work_Experience, na.rm=TRUE), cdf$Work_Experience)
# replace withunknown
cdf$Profession <- ifelse(is.na(cdf$Profession), "unknown", cdf$Profession)
# replace with most frequent level
cdf$Graduated <- ifelse(is.na(cdf$Graduated), "Yes", cdf$Graduated)
cdf$Ever_Married <- ifelse(is.na(cdf$Ever_Married), "Yes", cdf$Ever_Married)
cdf$Var_1 <- ifelse(is.na(cdf$Var_1), "Cat_6", cdf$Var_1)

sapply(cdf,function(x)sum(is.na(x))) 
             ID          Gender    Ever_Married             Age       Graduated      Profession Work_Experience  Spending_Score 
              0               0               0               0               0               0               0               0 
    Family_Size           Var_1    Segmentation          test_1 
              0               0            2627               0 
# train and test
traindf = cdf %>% filter(test_1==0) %>% select(-test_1, -ID) %>% as.data.frame()
testdf = cdf %>% filter(test_1==1) %>% select(-test_1, -ID,-Segmentation) %>% as.data.frame()

# partition train set based on outcome 
set.seed(3456)
train.index <- createDataPartition(traindf$Segmentation, p = .7, list = FALSE)
xtrain <- traindf[ train.index,]
xtest  <- traindf[-train.index,]

Modeling

Research question: What are the key features for predicting customer segments?

To identify the key features for predicting customer segments, all the features in the dataset are used for modeling.

Decision Tree

set.seed(123)
dt <- train(
  Segmentation ~., data = xtrain, method = "rpart",
  trControl = trainControl("cv", number = 10),
  tuneLength = 10
  )

# plot complexity parameter
plot(dt)

# print best complexity parameter
dt$bestTune %>% unlist()
         cp 
0.001477469 
# plot tree
fancyRpartPlot(dt$finalModel)


# predict
dt.p <- dt %>% predict(xtest)
# accuracy
mean(dt.p == xtest$Segmentation)
[1] 0.5084746
# feature importance
plot(varImp(dt))

Random forest

set.seed(123)
rf <- train(Segmentation ~., data = xtrain, method = "rf",
  trControl = trainControl("cv", number = 10),
  importance = TRUE)

rf$bestTune
rf$finalModel

Call:
 randomForest(x = x, y = y, mtry = param$mtry, importance = TRUE) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 2

        OOB estimate of  error rate: 48.54%
Confusion matrix:
    A   B   C    D class.error
A 705  78 279  319   0.4895004
B 442 135 552  172   0.8962337
C 210  66 900  203   0.3473532
D 317  40  64 1167   0.2651134
# predict
rf.p <- rf %>% predict(xtest)
# accuracy
mean(rf.p == xtest$Segmentation)
[1] 0.5126085
# confusion matrix
confusionMatrix(rf.p, factor(xtest$Segmentation))
Confusion Matrix and Statistics

          Reference
Prediction   A   B   C   D
         A 296 194  73 163
         B  46  59  25  12
         C 119 237 406  26
         D 130  67  87 479

Overall Statistics
                                          
               Accuracy : 0.5126          
                 95% CI : (0.4925, 0.5327)
    No Information Rate : 0.2811          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.3457          
                                          
 Mcnemar's Test P-Value : < 2.2e-16       

Statistics by Class:

                     Class: A Class: B Class: C Class: D
Sensitivity            0.5008  0.10592   0.6870   0.7044
Specificity            0.7648  0.95542   0.7910   0.8367
Pos Pred Value         0.4077  0.41549   0.5152   0.6278
Neg Pred Value         0.8258  0.78129   0.8866   0.8786
Prevalence             0.2443  0.23026   0.2443   0.2811
Detection Rate         0.1224  0.02439   0.1678   0.1980
Detection Prevalence   0.3001  0.05870   0.3258   0.3154
Balanced Accuracy      0.6328  0.53067   0.7390   0.7705
# AUC
#multiclass.roc(response= xtest$Segmentation, predictor = factor(rf.p,ordered=TRUE), plot=FALSE, print.auc=TRUE)

# variable importance
varImpPlot(rf$finalModel, type=2)

#varImp(rf2) 
#importance(rf2$finalModel)

Boosting

set.seed(123)
xgb <- train(Segmentation ~., data = xtrain, method = "xgbTree",trControl = trainControl("cv", number = 10))
xgb$bestTune
# predict
xgb.p = xgb %>% predict(xtest)
# accuracy
mean(xgb.p ==  xtest$Segmentation)
[1] 0.536172
# variable importance (pct)
varImp(xgb)
xgbTree variable importance

  only 20 most important variables shown (out of 22)
plot(varImp(xgb))

Bagging

set.seed(123)
bag <- train(Segmentation ~., data = xtrain, method = "treebag",trControl = trainControl("cv", number = 10))
bag$bestTune
# predict
bag.p = bag %>% predict(xtest)
# accuracy
mean(bag.p ==  xtest$Segmentation)
[1] 0.4745763
# variable importance (pct)
varImp(bag)
treebag variable importance

  only 20 most important variables shown (out of 22)
plot(varImp(bag))

Multinomial logistic regression

## multinorm logistic reg 
# define reference level
xtrain$Segmentation = factor(xtrain$Segmentation, ordered = FALSE)
xtrain$Segmentation <- relevel(xtrain$Segmentation , ref = "A")

# model
multinom_model <- multinom(Segmentation ~ ., data = xtrain)
# weights:  96 (69 variable)
initial  value 7831.176846 
iter  10 value 6893.779420
iter  20 value 6573.644554
iter  30 value 6391.502335
iter  40 value 6308.610680
iter  50 value 6293.992444
iter  60 value 6292.638859
iter  70 value 6292.459550
final  value 6292.454232 
converged
summary(multinom_model)
Call:
multinom(formula = Segmentation ~ ., data = xtrain)

Coefficients:
  (Intercept) GenderMale Ever_MarriedYes         Age GraduatedYes ProfessionDoctor ProfessionEngineer ProfessionEntertainment
B  -1.6186815 -0.1617574       0.3262336  0.01387245    0.3235098       -0.3058758         -0.6295035              -0.7374869
C  -2.8465722 -0.2052905       0.4663696  0.02041253    0.6778097       -0.5185472         -1.6203062              -1.3488591
D   0.4382531  0.2269359      -0.1768833 -0.02807020   -0.5622723        1.0604937          0.7220786               0.6590366
  ProfessionExecutive ProfessionHealthcare ProfessionHomemaker ProfessionLawyer ProfessionMarketing Professionunknown Work_Experience
B          -0.4121788           0.06529989           -0.294444        -1.270369          -1.0150341        -0.6172276     -0.03937352
C          -1.0782623           0.31493832           -1.114422        -1.989324          -0.7130235        -1.7333173     -0.04234758
D           1.3823799           2.81205689            1.485937         2.044152           1.8483612         1.5679038      0.02855183
  Spending_Score Family_Size Var_1Cat_2  Var_1Cat_3 Var_1Cat_4 Var_1Cat_5 Var_1Cat_6  Var_1Cat_7
B      0.3566670   0.1622673  0.4171412  0.04682155 -0.1332478  0.4320620  0.1241479 -0.14628249
C      0.5751489   0.3226334  0.2521779 -0.17074694 -0.9242315  0.2900542  0.3182524 -0.02047237
D     -0.3507156   0.1205068 -0.4020011 -0.44457033 -0.3033641 -0.2418461 -0.1437171 -0.60624816

Std. Errors:
  (Intercept) GenderMale Ever_MarriedYes         Age GraduatedYes ProfessionDoctor ProfessionEngineer ProfessionEntertainment
B   0.3820602 0.08782314       0.1121508 0.003744930   0.09291390        0.1537537          0.1448114               0.1278468
C   0.3996658 0.09056851       0.1209493 0.003909013   0.10216074        0.1583170          0.1795986               0.1412876
D   0.4003354 0.09292432       0.1178644 0.004406274   0.09152707        0.1755550          0.1772991               0.1599361
  ProfessionExecutive ProfessionHealthcare ProfessionHomemaker ProfessionLawyer ProfessionMarketing Professionunknown Work_Experience
B           0.1762950            0.1917768           0.2391510        0.1865849           0.2823286         0.3324021      0.01315727
C           0.1824463            0.1850256           0.2896999        0.1943822           0.2579919         0.4544046      0.01371233
D           0.2172659            0.1809245           0.2342785        0.2308178           0.2188313         0.3052694      0.01293735
  Spending_Score Family_Size Var_1Cat_2 Var_1Cat_3 Var_1Cat_4 Var_1Cat_5 Var_1Cat_6 Var_1Cat_7
B     0.07110530  0.03205418  0.3625032  0.3385279  0.3345384  0.4976097  0.3207676  0.4106196
C     0.07435629  0.03310478  0.3766607  0.3537579  0.3571772  0.5256959  0.3310724  0.4181147
D     0.08566326  0.03162401  0.3690472  0.3399052  0.3334015  0.5001568  0.3208734  0.4086847

Residual Deviance: 12584.91 
AIC: 12722.91 
# predict xtest 
multinorm.p <- predict(multinom_model, newdata = xtest, type="class")

# confusion matrix
confusionMatrix(multinorm.p, factor(xtest$Segmentation))
Confusion Matrix and Statistics

          Reference
Prediction   A   B   C   D
         A 272 174  70 152
         B  72  83  48  30
         C 130 242 398  38
         D 117  58  75 460

Overall Statistics
                                          
               Accuracy : 0.5014          
                 95% CI : (0.4813, 0.5216)
    No Information Rate : 0.2811          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.3319          
                                          
 Mcnemar's Test P-Value : < 2.2e-16       

Statistics by Class:

                     Class: A Class: B Class: C Class: D
Sensitivity            0.4602  0.14901   0.6734   0.6765
Specificity            0.7834  0.91944   0.7757   0.8562
Pos Pred Value         0.4072  0.35622   0.4926   0.6479
Neg Pred Value         0.8178  0.78317   0.8802   0.8713
Prevalence             0.2443  0.23026   0.2443   0.2811
Detection Rate         0.1124  0.03431   0.1645   0.1902
Detection Prevalence   0.2761  0.09632   0.3340   0.2935
Balanced Accuracy      0.6218  0.53423   0.7246   0.7664
# ROC AUC
#multiclass.roc(xtest$Segmentation, factor(multinorm.p,ordered = T))
# get z values
zvalues <- summary(multinom_model)$coefficients / summary(multinom_model)$standard.errors

# then p values
pnorm(abs(zvalues), lower.tail=FALSE)*2
   (Intercept) GenderMale Ever_MarriedYes          Age GraduatedYes ProfessionDoctor ProfessionEngineer ProfessionEntertainment
B 2.268107e-05 0.06549656    0.0036272048 2.119511e-04 4.980110e-04     4.665839e-02       1.379769e-05            7.997036e-09
C 1.060779e-12 0.02340930    0.0001153003 1.770834e-07 3.250816e-11     1.055226e-03       1.849888e-19            1.336331e-21
D 2.736416e-01 0.01459973    0.1334240094 1.884047e-10 8.085750e-10     1.533463e-09       4.647971e-05            3.778460e-05
  ProfessionExecutive ProfessionHealthcare ProfessionHomemaker ProfessionLawyer ProfessionMarketing Professionunknown Work_Experience
B        1.938695e-02         7.334805e-01        2.182460e-01     9.859979e-12        3.241138e-04      6.332962e-02     0.002766766
C        3.420572e-09         8.873027e-02        1.196626e-04     1.395038e-24        5.714237e-03      1.364699e-04     0.002013157
D        1.983440e-10         1.782935e-54        2.259075e-10     8.284192e-19        3.001382e-17      2.804512e-07     0.027318918
  Spending_Score  Family_Size Var_1Cat_2 Var_1Cat_3  Var_1Cat_4 Var_1Cat_5 Var_1Cat_6 Var_1Cat_7
B   5.274762e-07 4.142697e-07  0.2498458  0.8899960 0.690406589  0.3852439  0.6987311  0.7216547
C   1.033708e-14 1.922077e-22  0.5031705  0.6293329 0.009664721  0.5811177  0.3364127  0.9609484
D   4.237754e-05 1.386236e-04  0.2760239  0.1908989 0.362871866  0.6287119  0.6542301  0.1379647
LS0tCnRpdGxlOiAiQXV0b21vYmlsZSBjdXN0b21lcnMiCmRhdGU6ICIyMDIxLzAzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBFREEgYW5kIE1vZGVsaW5nIEV4ZXJjaXNlIAoKVGhpcyBub3RlYm9vayB1c2VzIHRoZSBbQ3VzdG9tZXIgU2VnbWVudGF0aW9uOiBBViAtIEphbmF0YWhhY2sgOiBDdXN0b21lciBTZWdtZW50YXRpb25dKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vYWJpc2hla3N1ZGFyc2hhbi9jdXN0b21lci1zZWdtZW50YXRpb24pIGRhdGFzZXQuIFRoZSBvYmplY3RpdmUgb2YgdGhlIG5vdGVib29rIGlzIHRvIGlkZW50aWZ5IHRoZSBrZXkgZmVhdHVyZXMgdGhhdCBjYW4gcHJlZGljdCBjdXN0b21lciBzZWdtZW50cyBvZiBhbiBhdXRvbW9iaWxlIGNvbXBhbnkuIAoKCiMjIyBMb2FkIGxpYmFyaWVzCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShza2ltcikKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoZ2dwdWJyKQpsaWJyYXJ5KHBzeWNoKQpsaWJyYXJ5KGNvbG9yc3BhY2UpCmxpYnJhcnkoUGVyZm9ybWFuY2VBbmFseXRpY3MpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkocnBhcnQpCmxpYnJhcnkocnBhcnQucGxvdCkKbGlicmFyeShyYXR0bGUpCmxpYnJhcnkocmFuZG9tRm9yZXN0KQpsaWJyYXJ5KHhnYm9vc3QpCmxpYnJhcnkobm5ldCkKYGBgCgojIyMgSW1wb3J0IERhdGEKYGBge3J9CnRyYWluPSByZWFkcjo6cmVhZF9jc3YoImN1c3Rfc2VnX3RyYWluLmNzdiIpCnRlc3Q9IHJlYWRyOjpyZWFkX2NzdigiY3VzdF9zZWdfdGVzdC5jc3YiKQpgYGAKCiMjIyBTdW1tYXJ5CmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpza2ltKHRyYWluKQpza2ltKHRlc3QpCmBgYAoKIyMgVmlzdWFsaXphdGlvbgoKIyMjIyBNaXNzaW5nIGRhdGEKYGBge3IsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTEuNn0KIyBoaWdobGlnaHRlZCBiYXIgcGxvdApwMSA9IHRyYWluICU+JSBzdW1tYXJpc2UoYWNyb3NzKGV2ZXJ5dGhpbmcoKSwgfm1lYW4oIWlzLm5hKC4pKSkpICU+JSAKICBnYXRoZXIoKSAlPiUKICBtdXRhdGUoa2V5PSBmY3RfcmVvcmRlcihrZXksIHZhbHVlKSkgJT4lCiAgZ2dwbG90KGFlcyhrZXksIHZhbHVlKSkgKwogIGdlb21fY29sKGFlcyhmaWxsPUkoaWZlbHNlKHZhbHVlPT0xLCcjYWRiNWJkJywnIzk4YzFkOScpKSksd2lkdGg9MC44KSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD0gcGVyY2VudCh2YWx1ZSkpLAogICAgICAgICAgICBudWRnZV95PTAuMDcsIHNpemU9MywgaGp1c3Q9MS43KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHM9IHNjYWxlczo6cGVyY2VudCkgKyAKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKAogICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICBwbG90LnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEyLGhqdXN0PTAuNSksCiAgICBwbG90LnRpdGxlLnBvc2l0aW9uID0gInBsb3QiLAogICAgbGVnZW5kLnBvc2l0aW9uPSJub25lIgogICkgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSkgKwogIGxhYnMoeD0iRmVhdHVyZSIsIHk9IiUgb2YgZGF0YSBwcmVzZW50IiwgdGl0bGU9IlRyYWluIGRhdGEiKSArCiAgY29vcmRfZmxpcCgpICAKCnAyID0gdGVzdCAlPiUgc3VtbWFyaXNlKGFjcm9zcyhldmVyeXRoaW5nKCksIH5tZWFuKCFpcy5uYSguKSkpKSAlPiUgCiAgZ2F0aGVyKCkgJT4lCiAgbXV0YXRlKGtleT0gZmN0X3Jlb3JkZXIoa2V5LCB2YWx1ZSkpICU+JQogIGdncGxvdChhZXMoa2V5LCB2YWx1ZSkpICsKICBnZW9tX2NvbChhZXMoZmlsbD1JKGlmZWxzZSh2YWx1ZT09MSwnI2FkYjViZCcsJyM5OGMxZDknKSkpLHdpZHRoPTAuOCkgKwogIGdlb21fdGV4dChhZXMobGFiZWw9IHBlcmNlbnQodmFsdWUpKSwKICAgICAgICAgICAgbnVkZ2VfeT0wLjA3LCBzaXplPTMsIGhqdXN0PTEuNykgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzPSBzY2FsZXM6OnBlcmNlbnQpICsgCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSgKICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgcGxvdC50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMixoanVzdD0wLjUpLAogICAgcGxvdC50aXRsZS5wb3NpdGlvbiA9ICJwbG90IiwKICAgIGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIKICApICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCkpICsKICBsYWJzKHg9IkZlYXR1cmUiLCB5PSIlIG9mIGRhdGEgcHJlc2VudCIsIHRpdGxlPSJUZXN0IGRhdGEiKSArCiAgY29vcmRfZmxpcCgpIAoKZ2dhcnJhbmdlKHAxLHAyLCBuY29sPTIpCmBgYAoKCmBgYHtyfQojIGFkZCBsYWJlbHMgdG8gdHJhaW4gYW5kIHRlc3Qgc2V0cwp0cmFpbiR0ZXN0XzEgPSAwIAp0ZXN0JHRlc3RfMSA9IDEKIyBiaW5kIHRlc3QgYW5kIHRyYWluIGRhdGEgCmRhdGEgPSBiaW5kX3Jvd3ModHJhaW4sIHRlc3QpCmRpbShkYXRhKQpoZWFkKGRhdGEpCmBgYAoKIyMjIyBTZWdtZW50YXRpb24KCmBgYHtyfQojIFNlZ21lbnRhdGlvbgp0cmFpbiAlPiUgZ3JvdXBfYnkoU2VnbWVudGF0aW9uKSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlKHByb3BvcnRpb249cm91bmQobi9zdW0obiksMykpCmBgYAoKIyMjIyBWYXJfMSAoQW5vbnltaXNlZCBDYXRlZ29yeSBmb3IgdGhlIGN1c3RvbWVyKQoKYGBge3IsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTJ9CiMgVmFyXzEgKEFub255bWlzZWQgQ2F0ZWdvcnkgZm9yIHRoZSBjdXN0b21lcikKCiMgdGFibGUgKGNvbWJpbmVkIGRhdGEpCmRhdGEgJT4lIGdyb3VwX2J5KFZhcl8xKSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlKHByb3BvcnRpb249cm91bmQobi9zdW0obiksMykpCgojIGNvdW50IHBsb3QKdjEgPSB0cmFpbiAlPiUgCiAgZ2dwbG90KGFlcyh4PVZhcl8xLCBmaWxsPVNlZ21lbnRhdGlvbikpICsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsIHBvc2l0aW9uPSJkb2RnZSIsIGFscGhhPTAuOSkgKyAKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygiI2YzNzIyYyIsIiNmOWM3NGYiLCIjNDNhYThiIiwiIzU3NzU5MCIpKSArIAogIGxhYnMoc3VidGl0bGU9IlZhcl8xIChBbm9ueW1pc2VkIENhdGVnb3J5IGZvciB0aGUgY3VzdG9tZXIpIikgKyAKICB0aGVtZV9saWdodCgpICsgCiAgdGhlbWUoYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgbGVnZW5kLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTkpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC4xMywgMC43NSkpCgojIGhlYXRtYXAKdjIgPSB0cmFpbiAlPiUgCiAgZ3JvdXBfYnkoU2VnbWVudGF0aW9uLCBWYXJfMSkgJT4lIAogIHRhbGx5KCkgJT4lIAogIG11dGF0ZShwY3Q9cm91bmQobi9zdW0obikqMTAwLDIpKSAlPiUgCiAgZ2dwbG90KGFlcyh5PWZjdF9yZXYoU2VnbWVudGF0aW9uKSwgeD1WYXJfMSwgZmlsbD1wY3QpKSArCiAgZ2VvbV90aWxlKGNvbG9yPSJ3aGl0ZSIsc2l6ZT0yKSArIAogIGdlb21fdGV4dChhZXMobGFiZWw9cGFzdGUocGN0LCIlIikpLCBzaXplPTMpICsgCiAgc2NhbGVfZmlsbF9kaXN0aWxsZXIocGFsZXR0ZSA9ICdTcGVjdHJhbCcpICsKICB0aGVtZV9saWdodCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249InRvcCIsCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgICAgIGF4aXMudGlja3M9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgIGxlZ2VuZC50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT05KSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQobWFyZ2luID0gbWFyZ2luKHQgPSAwLCByID0gNSwgYiA9IDAsIGwgPSAwKSksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KG1hcmdpbiA9IG1hcmdpbih0ID0gNSwgciA9IDAsIGIgPSAwLCBsID0gMCkpKSArCiAgbGFicyh5PSJTZWdtZW50YXRpb24iLCB4PSJWYXJfMSIsIAogICAgICAgZmlsbD0iUGVyY2VudGFnZSIsIHN1YnRpdGxlPSJWYXJfMSBhbmQgU2VnbWVudGF0aW9uIikgKyAKICBndWlkZXMoZmlsbCA9IGd1aWRlX2NvbG9yYmFyKHRpdGxlLnBvc2l0aW9uID0gInRvcCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlLmhqdXN0ID0gLjUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhcndpZHRoID0gdW5pdCgyMCwgImxpbmVzIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhcmhlaWdodCA9IHVuaXQoLjUsICJsaW5lcyIpKSkKCiMgY29tYmluZSBwbG90CmdnYXJyYW5nZSh2MSx2MixuY29sPTIpCmBgYAoKIyMjIyBBZ2UKCmBgYHtyLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD0yfQojIEFnZQojIGhpc3RvZ3JhbSB3aXRoIGtlcm5lbCBkZW5zaXR5IGN1cnZlICh0ZXN0IGFuZCB0cmFpbiBkYXRhKQphZ2UxID0gZ2dwbG90KGRhdGEsIGFlcyh4PUFnZSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHk9Li5kZW5zaXR5Li4pLAogICAgICAgICAgICAgICAgICAgYmlud2lkdGg9MiwKICAgICAgICAgICAgICAgICAgIGZpbGw9IiM2Yzc1N2QiLCBhbHBoYT0wLjUpICsKICBnZW9tX2RlbnNpdHkoYWxwaGE9LjIsIGNvbG9yPSIjMDBhOGU4Iiwgc2l6ZT0xKSArIAogIGxhYnMoc3VidGl0bGU9IkFnZSBEaXN0cmlidXRpb24gKHRyYWluIGFuZCB0ZXN0IGRhdGEgY29tYmluZWQpIikgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDEwMCksIGJyZWFrcz1zZXEoMCwxMDAsMjApLCBvb2IgPSBzY2FsZXM6Om9vYl9rZWVwKSArCiAgdGhlbWVfbGlnaHQoKQoKIyBsaW5lIHBsb3QgKHRyYWluIGRhdGEpCmFnZTIgPSBnZ3Bsb3QoZGF0YSwgYWVzKHg9QWdlKSkgKyAKICBnZW9tX2RlbnNpdHkoYWVzKGNvbG9yPVNlZ21lbnRhdGlvbiksa2V5X2dseXBoID0gZHJhd19rZXlfcG9pbnQsIHNpemU9MC44KSArIAogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHM9YygwLDEwMCksIGJyZWFrcz1zZXEoMCwxMDAsMjApKSArIAogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHM9YygwLDAuMDQ1KSkgKyAKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoIiNmMzcyMmMiLCIjZjljNzRmIiwiIzQzYWE4YiIsIiM1Nzc1OTAiKSwgbmEudmFsdWU9IiNjOWFkYTciKSArIAogIHRoZW1lX2xpZ2h0KCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOSwgMC43NSksCiAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT04KSkgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNoYXBlPTE1LHNpemU9NSkpKSArIAogIGxhYnMoc3VidGl0bGU9ICJBZ2UgRGlzdHJpYnV0aW9uICh0cmFpbiBkYXRhKSIpCgpnZ2FycmFuZ2UoYWdlMSwgYWdlMiwgbmNvbD0yKQpgYGAKCmBgYHtyfQojIHN1bW1hcnkgb2YgYWdlIGJ5IHNlZ21lbnQgKHRyYWluIGRhdGEpCnBzeWNoOjpkZXNjcmliZUJ5KHRyYWluJEFnZSwgdHJhaW4kU2VnbWVudGF0aW9uLG1hdD1UUlVFKQpgYGAKCiMjIyMgV29yayBFeHBlcmllbmNlCgpgYGB7ciwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9Mn0KIyBXb3JrX0V4cGVyaWVuY2UKCiMgc3VtbWFyeSBieSBzZWdtZW50CnBzeWNoOjpkZXNjcmliZUJ5KHRyYWluJFdvcmtfRXhwZXJpZW5jZSwgdHJhaW4kU2VnbWVudGF0aW9uLG1hdD1UUlVFKQoKIyBjb3VudCBwbG90CndlMSA9IGRhdGEgJT4lIAogIGZpbHRlcighaXMubmEoV29ya19FeHBlcmllbmNlKSkgJT4lCiAgZ2dwbG90KGFlcyh4PWZhY3RvcihXb3JrX0V4cGVyaWVuY2UpKSkgKwogIGdlb21fYmFyKHN0YXQ9ImNvdW50IixmaWxsPSIjOThjMWQ5IikgKyAKICB0aGVtZV9saWdodCgpICsgCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSkgKwogIGxhYnMoeD0iV29ya19FeHBlcmllbmNlIChpbiB5ZWFycykiLCBzdWJ0aXRsZT0iV29ya19FeHBlcmllbmNlIikKCiMgY291bnQgcGxvdCBieSBzZWdtZW50CndlMiA9IHRyYWluICU+JSAKICBmaWx0ZXIoIWlzLm5hKFdvcmtfRXhwZXJpZW5jZSkpICU+JQogIGdncGxvdChhZXMoeD1mYWN0b3IoV29ya19FeHBlcmllbmNlKSwgZmlsbD1TZWdtZW50YXRpb24pKSArCiAgZ2VvbV9iYXIoc3RhdD0iY291bnQiLCBzaG93LmxlZ2VuZCA9IEYpICsgCiAgdGhlbWVfbGlnaHQoKSArIAogIGZhY2V0X3dyYXAoflNlZ21lbnRhdGlvbiwgbGFiZWxsZXI9bGFiZWxfYm90aCkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCkpKwogIGxhYnMoeD0iV29ya19FeHBlcmllbmNlIChpbiB5ZWFycykiLCBzdWJ0aXRsZT0iV29ya19FeHBlcmllbmNlIGFuZCBTZWdlbWVudGF0aW9uIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjZjM3MjJjIiwiI2Y5Yzc0ZiIsIiM0M2FhOGIiLCIjNTc3NTkwIikpCgojIGNvbWJpbmUgcGxvdApnZ2FycmFuZ2Uod2UxLCB3ZTIsIG5jb2w9MikKCmBgYAoKIyMjIyBGYW1pbHkgc2l6ZQoKYGBge3IsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTIsIHdhcm5pbmc9RkFMU0V9CiMgRmFtaWx5X1NpemUKCiMgc3VtbWFyeSBvZiBGYW1pbHlfU2l6ZSBieSBzZWdtZW50CnBzeWNoOjpkZXNjcmliZUJ5KHRyYWluJEZhbWlseV9TaXplLCB0cmFpbiRTZWdtZW50YXRpb24sbWF0PVRSVUUpCgojIGRpc3RyaWJ1dGlvbgpmczEgPSBkYXRhICU+JSAKICBmaWx0ZXIoIWlzLm5hKEZhbWlseV9TaXplKSkgJT4lCiAgZ2dwbG90KGFlcyh4PUZhbWlseV9TaXplKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEsIGNvbG9yPSJ3aGl0ZSIsIGZpbGw9IiM5OGMxZDkiKSArIAogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQ9bWVhbihGYW1pbHlfU2l6ZSkpLGNvbG9yPSIjZDY2ODUzIiwgbGluZXR5cGU9ImRhc2hlZCIsIHNpemU9MSkgKyAKICBnZW9tX3RleHQoYWVzKHg9My45LCB5PTI5MDAsIGxhYmVsPSJNZWFuID0gMi44MyIpLCBzaXplPTMuNSwgY29sb3I9IiM0OTUwNTciKSArIAogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9c2VxKDEsMTAsMSkpICsgCiAgdGhlbWVfbGlnaHQoKSArIAogIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCkpICsKICBsYWJzKHN1YnRpdGxlPSJGYW1pbHlfU2l6ZSIpCgojIGJ1YmJsZSBwbG90CmZzMiA9IHRyYWluICU+JSAKICBmaWx0ZXIoIWlzLm5hKEZhbWlseV9TaXplKSkgJT4lCiAgZ3JvdXBfYnkoU2VnbWVudGF0aW9uLCBGYW1pbHlfU2l6ZSkgJT4lIHRhbGx5KCkgJT4lIHJlbmFtZShDb3VudD1uKSAlPiUKICBnZ3Bsb3QoYWVzKHk9U2VnbWVudGF0aW9uLCB4PWZhY3RvcihGYW1pbHlfU2l6ZSkpKSArIAogIGdlb21fcG9pbnQoYWVzKHNpemU9Q291bnQsIGNvbG9yPVNlZ21lbnRhdGlvbikpICsgCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCIjZjM3MjJjIiwiI2Y5Yzc0ZiIsIiM0M2FhOGIiLCIjNTc3NTkwIikpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgIGxlZ2VuZC50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT05KSkgKyAKICBsYWJzKHg9IkZhbWlseV9TaXplIiwgc3VidGl0bGU9IkZhbWlseV9TaXplIGFuZCBTZWdtZW50YXRpb24iKSArIAogIGd1aWRlcyhjb2xvcj1GQUxTRSkgKyBzY2FsZV9zaXplKHJhbmdlPWMoMiwxMikpCgpnZ2FycmFuZ2UoZnMxLGZzMixuY29sPTIpCmBgYAoKCiMjIyMgR2VuZGVyCgpgYGB7ciwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9Mi4yfQojIEdlbmRlciAKIyBjb3VudCBwbG90CmdlMSA9IHRyYWluICU+JSAKICBnZ3Bsb3QoYWVzKHg9R2VuZGVyLCBmaWxsPVNlZ21lbnRhdGlvbikpICsKICBnZW9tX2JhcihzdGF0PSJjb3VudCIsIHBvc2l0aW9uPSJkb2RnZSIsIGFscGhhPTAuOSkgKyAKICBnZW9tX3RleHQoc3RhdD0iY291bnQiLCBhZXMobGFiZWw9Li5jb3VudC4uLCBncm91cD1TZWdtZW50YXRpb24pLCAKICAgICAgICAgICAgdmp1c3Q9MS40LCBwb3NpdGlvbj1wb3NpdGlvbl9kb2RnZSh3aWR0aD0wLjkpLCBzaXplPTMuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjZjM3MjJjIiwiI2Y5Yzc0ZiIsIiM0M2FhOGIiLCIjNTc3NTkwIikpICsgCiAgbGFicyhzdWJ0aXRsZT0iR2VuZGVyOiBDb3VudCIpICsgCiAgdGhlbWVfbGlnaHQoKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIiwKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwKICAgICAgICBsZWdlbmQudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9OSksKQoKIyBwcm9wb3J0aW9uIHBsb3QKZ2UyID0gdHJhaW4gJT4lIAogIGdyb3VwX2J5KFNlZ21lbnRhdGlvbiwgR2VuZGVyKSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlKHByb3BvcnRpb249cm91bmQobi9zdW0obiksMykpICU+JQogIGdncGxvdChhZXMoeT1mY3RfcmV2KFNlZ21lbnRhdGlvbiksIHg9cHJvcG9ydGlvbiwgZmlsbD1HZW5kZXIpKSArCiAgZ2VvbV9jb2wod2lkdGg9MC43LCBhbHBoYT0wLjkpICsgCiAgZ2VvbV90ZXh0KGFlcyh4ID0gcHJvcG9ydGlvbiwgeSA9IFNlZ21lbnRhdGlvbiwgbGFiZWwgPSBwYXN0ZTAocHJvcG9ydGlvbioxMDAsICIlIikpLCAKICAgICAgICAgICAgc2l6ZSA9IDMsIHBvc2l0aW9uID0gcG9zaXRpb25fZmlsbCh2anVzdCA9IDAuNSksIGNvbG9yPSJ3aGl0ZSIpICsKICB0aGVtZV9saWdodCgpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgIGxlZ2VuZC50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT05KSwpICsgCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiNkNjY4NTMiLCIjMzY0MTU2IiksIGd1aWRlPWd1aWRlX2xlZ2VuZChyZXZlcnNlPVQpKSArIAogIGxhYnMoZmlsbD0iR2VuZGVyIiwgeT0iU2VnbWVudGF0aW9uIiwgeD0icGVyY2VudGFnZSIsc3VidGl0bGU9IkdlbmRlcjogUGVyY2VudGFnZSIpICsgCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscz1zY2FsZXM6OnBlcmNlbnQpIAoKZ2dhcnJhbmdlKGdlMSxnZTIsIG5jb2w9MikgCmBgYAoKCiMjIyMgRXZlciBtYXJyaWVkIAoKYGBge3IsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTIuMn0KIyBFdmVyX01hcnJpZWQgCiMgY291bnQgcGxvdAplbTEgPSB0cmFpbiAlPiUgZmlsdGVyKEV2ZXJfTWFycmllZCE9IiIpICU+JQogIGdncGxvdChhZXMoeD1FdmVyX01hcnJpZWQsIGZpbGw9U2VnbWVudGF0aW9uKSkgKwogIGdlb21fYmFyKHN0YXQ9ImNvdW50IiwgcG9zaXRpb249ImRvZGdlIiwgYWxwaGE9MC45KSArIAogIGdlb21fdGV4dChzdGF0PSJjb3VudCIsIGFlcyhsYWJlbD0uLmNvdW50Li4sIGdyb3VwPVNlZ21lbnRhdGlvbiksIAogICAgICAgICAgICB2anVzdD0xLjQsIHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKHdpZHRoPTAuOSksIHNpemU9My41KSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiNmMzcyMmMiLCIjZjljNzRmIiwiIzQzYWE4YiIsIiM1Nzc1OTAiKSkgKyAKICBsYWJzKHN1YnRpdGxlPSJFdmVyX01hcnJpZWQ6IENvdW50IikgKyAKICB0aGVtZV9saWdodCgpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgIGxlZ2VuZC50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT05KSwpCgojIHByb3BvcnRpb24gcGxvdAplbTIgPSB0cmFpbiAlPiUgZmlsdGVyKEV2ZXJfTWFycmllZCE9IiIpICU+JQogIGdyb3VwX2J5KFNlZ21lbnRhdGlvbiwgRXZlcl9NYXJyaWVkKSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlKHByb3BvcnRpb249cm91bmQobi9zdW0obiksMykpICU+JQogIGdncGxvdChhZXMoeT1mY3RfcmV2KFNlZ21lbnRhdGlvbiksIHg9cHJvcG9ydGlvbiwgZmlsbD1mYWN0b3IoRXZlcl9NYXJyaWVkKSkpICsKICBnZW9tX2NvbCh3aWR0aD0wLjcsIGFscGhhPTAuOSkgKyAKICBnZW9tX3RleHQoYWVzKHggPSBwcm9wb3J0aW9uLCB5ID0gU2VnbWVudGF0aW9uLCBsYWJlbCA9IHBhc3RlMChwcm9wb3J0aW9uKjEwMCwgIiUiKSksIAogICAgICAgICAgICBzaXplID0gMywgcG9zaXRpb24gPSBwb3NpdGlvbl9maWxsKHZqdXN0ID0gMC41KSwgY29sb3I9IndoaXRlIikgKwogIHRoZW1lX2xpZ2h0KCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIsCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgbGVnZW5kLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTkpLCkgKyAKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygiI2Q2Njg1MyIsIiMzNjQxNTYiKSwgZ3VpZGU9Z3VpZGVfbGVnZW5kKHJldmVyc2U9VCkpICsgCiAgbGFicyhmaWxsPSJFdmVyX01hcnJpZWQiLCB5PSJTZWdtZW50YXRpb24iLCB4PSJwZXJjZW50YWdlIixzdWJ0aXRsZT0iRXZlcl9NYXJyaWVkOiBQZXJjZW50YWdlIikgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzPXNjYWxlczo6cGVyY2VudCkgCgpnZ2FycmFuZ2UoZW0xLGVtMiwgbmNvbD0yKQpgYGAKCgojIyMjIEdyYXVkYXRlZAoKYGBge3IsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTIuMn0KIyBHcmFkdWF0ZWQgCiMgY291bnQgcGxvdApncjEgPSB0cmFpbiAlPiUgZmlsdGVyKEdyYWR1YXRlZCE9IiIpICU+JQogIGdncGxvdChhZXMoeD1HcmFkdWF0ZWQsIGZpbGw9U2VnbWVudGF0aW9uKSkgKwogIGdlb21fYmFyKHN0YXQ9ImNvdW50IiwgcG9zaXRpb249ImRvZGdlIiwgYWxwaGE9MC45KSArIAogIGdlb21fdGV4dChzdGF0PSJjb3VudCIsIGFlcyhsYWJlbD0uLmNvdW50Li4sIGdyb3VwPVNlZ21lbnRhdGlvbiksIAogICAgICAgICAgICB2anVzdD0xLjQsIHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKHdpZHRoPTAuOSksIHNpemU9My41KSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiNmMzcyMmMiLCIjZjljNzRmIiwiIzQzYWE4YiIsIiM1Nzc1OTAiKSkgKyAKICBsYWJzKHN1YnRpdGxlPSJHcmFkdWF0ZWQ6IENvdW50IikgKyAKICB0aGVtZV9saWdodCgpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgIGxlZ2VuZC50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT05KSwpCgojIHByb3BvcnRpb24gcGxvdApncjIgPSB0cmFpbiAlPiUgZmlsdGVyKEdyYWR1YXRlZCE9IiIpICU+JQogIGdyb3VwX2J5KFNlZ21lbnRhdGlvbiwgR3JhZHVhdGVkKSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlKHByb3BvcnRpb249cm91bmQobi9zdW0obiksMykpICU+JQogIGdncGxvdChhZXMoeT1mY3RfcmV2KFNlZ21lbnRhdGlvbiksIHg9cHJvcG9ydGlvbiwgZmlsbD1mYWN0b3IoR3JhZHVhdGVkKSkpICsKICBnZW9tX2NvbCh3aWR0aD0wLjcsIGFscGhhPTAuOSkgKyAKICBnZW9tX3RleHQoYWVzKHggPSBwcm9wb3J0aW9uLCB5ID0gU2VnbWVudGF0aW9uLCBsYWJlbCA9IHBhc3RlMChwcm9wb3J0aW9uKjEwMCwgIiUiKSksIAogICAgICAgICAgICBzaXplID0gMywgcG9zaXRpb24gPSBwb3NpdGlvbl9maWxsKHZqdXN0ID0gMC41KSwgY29sb3I9IndoaXRlIikgKwogIHRoZW1lX2xpZ2h0KCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIsCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgbGVnZW5kLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTkpLCkgKyAKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygiI2Q2Njg1MyIsIiMzNjQxNTYiKSwgZ3VpZGU9Z3VpZGVfbGVnZW5kKHJldmVyc2U9VCkpICsgCiAgbGFicyhmaWxsPSJHcmFkdWF0ZWQiLCB5PSJTZWdtZW50YXRpb24iLCB4PSJwZXJjZW50YWdlIixzdWJ0aXRsZT0iR3JhZHVhdGVkOiBQZXJjZW50YWdlIikgKyAKICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzPXNjYWxlczo6cGVyY2VudCkgCgpnZ2FycmFuZ2UoZ3IxLGdyMiwgbmNvbD0yKQpgYGAKCiMjIyMgU3BlbmRpbmcgc2NvcmUKCmBgYHtyLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD0yLjJ9CiMgU3BlbmRpbmdfU2NvcmUKIyBjb3VudCBwbG90CnNzMSA9IHRyYWluICU+JSAKICBnZ3Bsb3QoYWVzKHg9U3BlbmRpbmdfU2NvcmUsIGZpbGw9U2VnbWVudGF0aW9uKSkgKwogIGdlb21fYmFyKHN0YXQ9ImNvdW50IiwgcG9zaXRpb249ImRvZGdlIiwgYWxwaGE9MC45KSArIAogIGdlb21fdGV4dChzdGF0PSJjb3VudCIsIGFlcyhsYWJlbD0uLmNvdW50Li4sIGdyb3VwPVNlZ21lbnRhdGlvbiksIAogICAgICAgICAgICB2anVzdD0xLjQsIHBvc2l0aW9uPXBvc2l0aW9uX2RvZGdlKHdpZHRoPTAuOSksIHNpemU9MykgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjZjM3MjJjIiwiI2Y5Yzc0ZiIsIiM0M2FhOGIiLCIjNTc3NTkwIikpICsgCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKSArIAogIGxhYnMoc3VidGl0bGU9IlNwZW5kaW5nIFNjb3JlOiBDb3VudCIpCgojIHByb3BvcnRpb24gcGxvdApzczIgPSB0cmFpbiAlPiUgCiAgZ3JvdXBfYnkoU2VnbWVudGF0aW9uLCBTcGVuZGluZ19TY29yZSkgJT4lIHRhbGx5KCkgJT4lIG11dGF0ZShwcm9wb3J0aW9uPXJvdW5kKG4vc3VtKG4pLDMpKSAlPiUKICBnZ3Bsb3QoYWVzKHk9ZmN0X3JldihTZWdtZW50YXRpb24pLCB4PXByb3BvcnRpb24sIGZpbGw9ZmFjdG9yKFNwZW5kaW5nX1Njb3JlLCBsZXZlbD1jKCJIaWdoIiwiQXZlcmFnZSIsIkxvdyIpKSkpICsKICBnZW9tX2NvbCh3aWR0aD0wLjcsIGFscGhhPTAuOSkgKyAKICBnZW9tX3RleHQoYWVzKHggPSBwcm9wb3J0aW9uLCB5ID0gU2VnbWVudGF0aW9uLCBsYWJlbCA9IHBhc3RlMChwcm9wb3J0aW9uKjEwMCwgIiUiKSksIAogICAgICAgICAgICBzaXplID0gMywgcG9zaXRpb24gPSBwb3NpdGlvbl9maWxsKHZqdXN0ID0gMC41KSwgY29sb3I9IndoaXRlIikgKwogIHRoZW1lX2xpZ2h0KCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpICsgCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiNkNjY4NTMiLCIjN2Q0ZTU3IiwiIzM2NDE1NiIpLCBndWlkZT1ndWlkZV9sZWdlbmQocmV2ZXJzZT1UKSkgKyAKICBsYWJzKGZpbGw9IlNwZW5kaW5nX1Njb3JlIiwgeT0iU2VnbWVudGF0aW9uIiwgeD0icGVyY2VudGFnZSIsIHN1YnRpdGxlPSJTcGVuZGluZyBTY29yZTogUGVyY2VudGFnZSIpICsgCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscz1zY2FsZXM6OnBlcmNlbnQpIAoKZ2dhcnJhbmdlKHNzMSxzczIsIG5jb2w9MikKYGBgCgojIyMjIFByb2Zlc3Npb24KCmBgYHtyfQojIFByb2Zlc3Npb24gCiMgbGV2ZWwgZnJlcXVlbmN5CnRyYWluICU+JSBncm91cF9ieShQcm9mZXNzaW9uKSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlKHByb3BvcnRpb249cm91bmQobi9zdW0obiksMykpCgojIGNvdW50CnRyYWluICU+JSBmaWx0ZXIoUHJvZmVzc2lvbiE9IiIpICU+JQogIGdyb3VwX2J5KFByb2Zlc3Npb24sIFNlZ21lbnRhdGlvbikgJT4lIAogIHRhbGx5KCkgJT4lCiAgZ2dwbG90KGFlcyh5PVByb2Zlc3Npb24sIHg9bikpICsgCiAgZ2VvbV9saW5lKGFlcyhncm91cD1Qcm9mZXNzaW9uKSwgY29sb3I9IiM3MzZmNzIiKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3I9U2VnbWVudGF0aW9uKSxrZXlfZ2x5cGggPSBkcmF3X2tleV9wb2ludCwgYWxwaGE9MC44LCBzaXplPTMpICsgCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCIjZjM3MjJjIiwiI2Y5Yzc0ZiIsIiM0M2FhOGIiLCIjNTc3NTkwIikpICsgCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2hhcGU9MTUsc2l6ZT01KSkpICsKICB0aGVtZV9saWdodCgpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJ0b3AiLAogICAgICAgIHBsb3QudGl0bGUucG9zaXRpb24gPSAicGxvdCIpICsKICBsYWJzKHg9ImNvdW50Iiwgc3VidGl0bGU9IlByb2Zlc3Npb246IENvdW50IikKCiMgcHJvcG9ydGlvbgp0cmFpbiAlPiUgCiAgZ3JvdXBfYnkoU2VnbWVudGF0aW9uLCBQcm9mZXNzaW9uKSAlPiUgCiAgdGFsbHkoKSAlPiUgCiAgbXV0YXRlKHBjdD1yb3VuZChuL3N1bShuKSoxMDAsMikpICU+JSAKICBnZ3Bsb3QoYWVzKHk9ZmN0X3JldihTZWdtZW50YXRpb24pLCB4PVByb2Zlc3Npb24sIGZpbGw9cGN0KSkgKwogIGdlb21fdGlsZShjb2xvcj0id2hpdGUiLHNpemU9MikgKyAKICBnZW9tX3RleHQoYWVzKGxhYmVsPXBhc3RlKHBjdCwiJSIpKSwgc2l6ZT0zKSArIAogIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGUgPSAnU3BlY3RyYWwnKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJ0b3AiLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgICAgICBheGlzLnRpY2tzPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwKICAgICAgICBsZWdlbmQudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9OSksCiAgICAgICAgcGxvdC50aXRsZS5wb3NpdGlvbiA9ICJwbG90IiwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQobWFyZ2luID0gbWFyZ2luKHQgPSAwLCByID0gNSwgYiA9IDAsIGwgPSAwKSksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KG1hcmdpbiA9IG1hcmdpbih0ID0gNSwgciA9IDAsIGIgPSAwLCBsID0gMCkpKSArCiAgbGFicyh5PSJTZWdtZW50YXRpb24iLCB4PSJQcm9mZXNzaW9uIiwgCiAgICAgICBmaWxsPSJQZXJjZW50YWdlIiwgc3VidGl0bGU9IlByb2Zlc3Npb246IFBlcmNlbnRhZ2UiKSArIAogIGd1aWRlcyhmaWxsID0gZ3VpZGVfY29sb3JiYXIodGl0bGUucG9zaXRpb24gPSAidG9wIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGl0bGUuaGp1c3QgPSAuNSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFyd2lkdGggPSB1bml0KDIwLCAibGluZXMiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFyaGVpZ2h0ID0gdW5pdCguNSwgImxpbmVzIikpKQpgYGAKCiMjIyMgQ29ycmVsYXRpb24gCgpgYGB7cn0KIyBjb3JyZWxhdGlvbiAKY19kYXRhIDwtIGRhdGFbLCBjKDQsNyw5KV0KY2hhcnQuQ29ycmVsYXRpb24oY19kYXRhICwgaGlzdG9ncmFtPVRSVUUsIHBjaD0xOSkKYGBgCgojIyMgRGF0YSBwcmVwYXJhdGlvbgoKYGBge3J9CiMgY291bnQgb2YgbWlzc2luZyBkYXRhIGJ5IGNvbHVtbgpkYXRhICU+JSB0eXBlLmNvbnZlcnQoKSAlPiUgc2FwcGx5KGZ1bmN0aW9uKHgpc3VtKGlzLm5hKHgpKSkgCmBgYAoKCmBgYHtyfQpjZGYgPSBkYXRhCgojIGR1bW1pZnkgU3BlbmRpbmdfU2NvcmUKY2RmJFNwZW5kaW5nX1Njb3JlPSBhcy5udW1lcmljKGZhY3RvcihjZGYkU3BlbmRpbmdfU2NvcmUsIG9yZGVyPVRSVUUsIGxldmVscz1jKCJMb3ciLCJBdmVyYWdlIiwiSGlnaCIpKSkKCiMgaW5wdXRlIG1pc3NpbmcgdmFsdWVzCiMgcmVwbGFjZSB3aXRoIG1lYW4KY2RmJEZhbWlseV9TaXplIDwtIGlmZWxzZShpcy5uYShjZGYkRmFtaWx5X1NpemUpLCBtZWFuKGNkZiRGYW1pbHlfU2l6ZSwgbmEucm09VFJVRSksIGNkZiRGYW1pbHlfU2l6ZSkKY2RmJFdvcmtfRXhwZXJpZW5jZSA8LSBpZmVsc2UoaXMubmEoY2RmJFdvcmtfRXhwZXJpZW5jZSksIG1lYW4oY2RmJFdvcmtfRXhwZXJpZW5jZSwgbmEucm09VFJVRSksIGNkZiRXb3JrX0V4cGVyaWVuY2UpCiMgcmVwbGFjZSB3aXRodW5rbm93bgpjZGYkUHJvZmVzc2lvbiA8LSBpZmVsc2UoaXMubmEoY2RmJFByb2Zlc3Npb24pLCAidW5rbm93biIsIGNkZiRQcm9mZXNzaW9uKQojIHJlcGxhY2Ugd2l0aCBtb3N0IGZyZXF1ZW50IGxldmVsCmNkZiRHcmFkdWF0ZWQgPC0gaWZlbHNlKGlzLm5hKGNkZiRHcmFkdWF0ZWQpLCAiWWVzIiwgY2RmJEdyYWR1YXRlZCkKY2RmJEV2ZXJfTWFycmllZCA8LSBpZmVsc2UoaXMubmEoY2RmJEV2ZXJfTWFycmllZCksICJZZXMiLCBjZGYkRXZlcl9NYXJyaWVkKQpjZGYkVmFyXzEgPC0gaWZlbHNlKGlzLm5hKGNkZiRWYXJfMSksICJDYXRfNiIsIGNkZiRWYXJfMSkKCnNhcHBseShjZGYsZnVuY3Rpb24oeClzdW0oaXMubmEoeCkpKSAKYGBgCgoKYGBge3J9CiMgdHJhaW4gYW5kIHRlc3QKdHJhaW5kZiA9IGNkZiAlPiUgZmlsdGVyKHRlc3RfMT09MCkgJT4lIHNlbGVjdCgtdGVzdF8xLCAtSUQpICU+JSBhcy5kYXRhLmZyYW1lKCkKdGVzdGRmID0gY2RmICU+JSBmaWx0ZXIodGVzdF8xPT0xKSAlPiUgc2VsZWN0KC10ZXN0XzEsIC1JRCwtU2VnbWVudGF0aW9uKSAlPiUgYXMuZGF0YS5mcmFtZSgpCgojIHBhcnRpdGlvbiB0cmFpbiBzZXQgYmFzZWQgb24gb3V0Y29tZSAKc2V0LnNlZWQoMzQ1NikKdHJhaW4uaW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih0cmFpbmRmJFNlZ21lbnRhdGlvbiwgcCA9IC43LCBsaXN0ID0gRkFMU0UpCnh0cmFpbiA8LSB0cmFpbmRmWyB0cmFpbi5pbmRleCxdCnh0ZXN0ICA8LSB0cmFpbmRmWy10cmFpbi5pbmRleCxdCmBgYAoKCiMjIyBNb2RlbGluZyAgCgpSZXNlYXJjaCBxdWVzdGlvbjogV2hhdCBhcmUgdGhlIGtleSBmZWF0dXJlcyBmb3IgcHJlZGljdGluZyBjdXN0b21lciBzZWdtZW50cz8gCgpUbyBpZGVudGlmeSB0aGUga2V5IGZlYXR1cmVzIGZvciBwcmVkaWN0aW5nIGN1c3RvbWVyIHNlZ21lbnRzLCBhbGwgdGhlIGZlYXR1cmVzIGluIHRoZSBkYXRhc2V0IGFyZSB1c2VkIGZvciBtb2RlbGluZy4gIAoKIyMjIyBEZWNpc2lvbiBUcmVlCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQpkdCA8LSB0cmFpbigKICBTZWdtZW50YXRpb24gfi4sIGRhdGEgPSB4dHJhaW4sIG1ldGhvZCA9ICJycGFydCIsCiAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKCJjdiIsIG51bWJlciA9IDEwKSwKICB0dW5lTGVuZ3RoID0gMTAKICApCgojIHBsb3QgY29tcGxleGl0eSBwYXJhbWV0ZXIKcGxvdChkdCkKIyBwcmludCBiZXN0IGNvbXBsZXhpdHkgcGFyYW1ldGVyCmR0JGJlc3RUdW5lICU+JSB1bmxpc3QoKQoKIyBwbG90IHRyZWUKZmFuY3lScGFydFBsb3QoZHQkZmluYWxNb2RlbCkKCiMgcHJlZGljdApkdC5wIDwtIGR0ICU+JSBwcmVkaWN0KHh0ZXN0KQojIGFjY3VyYWN5Cm1lYW4oZHQucCA9PSB4dGVzdCRTZWdtZW50YXRpb24pCgojIGZlYXR1cmUgaW1wb3J0YW5jZQpwbG90KHZhckltcChkdCkpCmBgYAoKCiMjIyMgUmFuZG9tIGZvcmVzdAoKYGBge3J9CnNldC5zZWVkKDEyMykKcmYgPC0gdHJhaW4oU2VnbWVudGF0aW9uIH4uLCBkYXRhID0geHRyYWluLCBtZXRob2QgPSAicmYiLAogIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbCgiY3YiLCBudW1iZXIgPSAxMCksCiAgaW1wb3J0YW5jZSA9IFRSVUUpCgpyZiRiZXN0VHVuZQpyZiRmaW5hbE1vZGVsCmBgYAoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CiMgcHJlZGljdApyZi5wIDwtIHJmICU+JSBwcmVkaWN0KHh0ZXN0KQojIGFjY3VyYWN5Cm1lYW4ocmYucCA9PSB4dGVzdCRTZWdtZW50YXRpb24pCgojIHZhcmlhYmxlIGltcG9ydGFuY2UKdmFySW1wUGxvdChyZiRmaW5hbE1vZGVsLCB0eXBlPTIpCiN2YXJJbXAocmYyKSAKI2ltcG9ydGFuY2UocmYyJGZpbmFsTW9kZWwpCmBgYAoKIyMjIyBCb29zdGluZwoKYGBge3J9CnNldC5zZWVkKDEyMykKeGdiIDwtIHRyYWluKFNlZ21lbnRhdGlvbiB+LiwgZGF0YSA9IHh0cmFpbiwgbWV0aG9kID0gInhnYlRyZWUiLHRyQ29udHJvbCA9IHRyYWluQ29udHJvbCgiY3YiLCBudW1iZXIgPSAxMCkpCnhnYiRiZXN0VHVuZQpgYGAKCmBgYHtyfQojIHByZWRpY3QKeGdiLnAgPSB4Z2IgJT4lIHByZWRpY3QoeHRlc3QpCiMgYWNjdXJhY3kKbWVhbih4Z2IucCA9PSAgeHRlc3QkU2VnbWVudGF0aW9uKQoKIyB2YXJpYWJsZSBpbXBvcnRhbmNlIChwY3QpCnZhckltcCh4Z2IpCmBgYAoKYGBge3J9CnBsb3QodmFySW1wKHhnYikpCmBgYAoKCiMjIyBCYWdnaW5nIApgYGB7cn0Kc2V0LnNlZWQoMTIzKQpiYWcgPC0gdHJhaW4oU2VnbWVudGF0aW9uIH4uLCBkYXRhID0geHRyYWluLCBtZXRob2QgPSAidHJlZWJhZyIsdHJDb250cm9sID0gdHJhaW5Db250cm9sKCJjdiIsIG51bWJlciA9IDEwKSkKYmFnJGJlc3RUdW5lCmBgYAoKYGBge3J9CiMgcHJlZGljdApiYWcucCA9IGJhZyAlPiUgcHJlZGljdCh4dGVzdCkKIyBhY2N1cmFjeQptZWFuKGJhZy5wID09ICB4dGVzdCRTZWdtZW50YXRpb24pCgojIHZhcmlhYmxlIGltcG9ydGFuY2UgKHBjdCkKdmFySW1wKGJhZykKcGxvdCh2YXJJbXAoYmFnKSkKYGBgCgojIyMgTXVsdGlub21pYWwgbG9naXN0aWMgcmVncmVzc2lvbgoKYGBge3J9CiMjIG11bHRpbm9ybSBsb2dpc3RpYyByZWcgCiMgZGVmaW5lIHJlZmVyZW5jZSBsZXZlbAp4dHJhaW4kU2VnbWVudGF0aW9uID0gZmFjdG9yKHh0cmFpbiRTZWdtZW50YXRpb24sIG9yZGVyZWQgPSBGQUxTRSkKeHRyYWluJFNlZ21lbnRhdGlvbiA8LSByZWxldmVsKHh0cmFpbiRTZWdtZW50YXRpb24gLCByZWYgPSAiQSIpCgojIG1vZGVsCm11bHRpbm9tX21vZGVsIDwtIG11bHRpbm9tKFNlZ21lbnRhdGlvbiB+IC4sIGRhdGEgPSB4dHJhaW4pCnN1bW1hcnkobXVsdGlub21fbW9kZWwpCmBgYAoKYGBge3J9CiMgcHJlZGljdCB4dGVzdCAKbXVsdGlub3JtLnAgPC0gcHJlZGljdChtdWx0aW5vbV9tb2RlbCwgbmV3ZGF0YSA9IHh0ZXN0LCB0eXBlPSJjbGFzcyIpCgojIGNvbmZ1c2lvbiBtYXRyaXgKY29uZnVzaW9uTWF0cml4KG11bHRpbm9ybS5wLCBmYWN0b3IoeHRlc3QkU2VnbWVudGF0aW9uKSkKCiMgUk9DIEFVQwojbXVsdGljbGFzcy5yb2MoeHRlc3QkU2VnbWVudGF0aW9uLCBmYWN0b3IobXVsdGlub3JtLnAsb3JkZXJlZCA9IFQpKQpgYGAKCmBgYHtyfQojIGdldCB6IHZhbHVlcwp6dmFsdWVzIDwtIHN1bW1hcnkobXVsdGlub21fbW9kZWwpJGNvZWZmaWNpZW50cyAvIHN1bW1hcnkobXVsdGlub21fbW9kZWwpJHN0YW5kYXJkLmVycm9ycwoKIyB0aGVuIHAgdmFsdWVzCnBub3JtKGFicyh6dmFsdWVzKSwgbG93ZXIudGFpbD1GQUxTRSkqMgpgYGAKCgoKCgoKCgoKCgoKCgoKCgoKCgoK