Dataset: Credit card customers

Load packages

library(tidyverse)
library(skimr)
library(Hmisc)
library(ggsci)
library(caret)
library(ggpubr)
library(gridExtra)
library(corrplot)
library(rpart)
library(rpart.plot)
library(rattle)
library(randomForest)
library(caret)
library(xgboost)
library(pscl)
library(pROC)

Import data

df = read.csv("BankChurners.csv", header=TRUE)
head(df)
#delete naive bayes columns
df = df[,2:21]
dim(df)
[1] 10127    20
colnames(df)
 [1] "Attrition_Flag"           "Customer_Age"             "Gender"                   "Dependent_count"         
 [5] "Education_Level"          "Marital_Status"           "Income_Category"          "Card_Category"           
 [9] "Months_on_book"           "Total_Relationship_Count" "Months_Inactive_12_mon"   "Contacts_Count_12_mon"   
[13] "Credit_Limit"             "Total_Revolving_Bal"      "Avg_Open_To_Buy"          "Total_Amt_Chng_Q4_Q1"    
[17] "Total_Trans_Amt"          "Total_Trans_Ct"           "Total_Ct_Chng_Q4_Q1"      "Avg_Utilization_Ratio"   

Target variable

Hmisc::describe(df$Attrition_Flag)
df$Attrition_Flag 
       n  missing distinct 
   10127        0        2 
                                              
Value      Attrited Customer Existing Customer
Frequency               1627              8500
Proportion             0.161             0.839
#convert target variable to numeric 
df$label= ifelse(df$Attrition_Flag =="Attrited Customer","1","0")
df$label = as.factor(df$label)
Hmisc::describe(df$label)
df$label 
       n  missing distinct 
   10127        0        2 
                      
Value          0     1
Frequency   8500  1627
Proportion 0.839 0.161
#drop attrition flag 
df = subset(df, select = -c(Attrition_Flag))
#summary
df = df %>% mutate_at(vars(label, Gender,Education_Level,Income_Category,Marital_Status,Card_Category),list(factor))
skim(df)
── Data Summary ────────────────────────
                           Values
Name                       df    
Number of rows             10127 
Number of columns          20    
_______________________          
Column type frequency:           
  factor                   6     
  numeric                  14    
________________________         
Group variables            None  

── Variable type: factor ──────────────────────────────────────────────────────────────────────────────────────────
  skim_variable   n_missing complete_rate ordered n_unique top_counts                                
1 Gender                  0             1 FALSE          2 F: 5358, M: 4769                          
2 Education_Level         0             1 FALSE          7 Gra: 3128, Hig: 2013, Unk: 1519, Une: 1487
3 Marital_Status          0             1 FALSE          4 Mar: 4687, Sin: 3943, Unk: 749, Div: 748  
4 Income_Category         0             1 FALSE          6 Les: 3561, $40: 1790, $80: 1535, $60: 1402
5 Card_Category           0             1 FALSE          4 Blu: 9436, Sil: 555, Gol: 116, Pla: 20    
6 label                   0             1 FALSE          2 0: 8500, 1: 1627                          

── Variable type: numeric ─────────────────────────────────────────────────────────────────────────────────────────
   skim_variable            n_missing complete_rate     mean       sd    p0      p25      p50       p75      p100
 1 Customer_Age                     0             1   46.3      8.02    26    41       46        52        73    
 2 Dependent_count                  0             1    2.35     1.30     0     1        2         3         5    
 3 Months_on_book                   0             1   35.9      7.99    13    31       36        40        56    
 4 Total_Relationship_Count         0             1    3.81     1.55     1     3        4         5         6    
 5 Months_Inactive_12_mon           0             1    2.34     1.01     0     2        2         3         6    
 6 Contacts_Count_12_mon            0             1    2.46     1.11     0     2        2         3         6    
 7 Credit_Limit                     0             1 8632.    9089.    1438. 2555     4549     11068.    34516    
 8 Total_Revolving_Bal              0             1 1163.     815.       0   359     1276      1784      2517    
 9 Avg_Open_To_Buy                  0             1 7469.    9091.       3  1324.    3474      9859     34516    
10 Total_Amt_Chng_Q4_Q1             0             1    0.760    0.219    0     0.631    0.736     0.859     3.40 
11 Total_Trans_Amt                  0             1 4404.    3397.     510  2156.    3899      4741     18484    
12 Total_Trans_Ct                   0             1   64.9     23.5     10    45       67        81       139    
13 Total_Ct_Chng_Q4_Q1              0             1    0.712    0.238    0     0.582    0.702     0.818     3.71 
14 Avg_Utilization_Ratio            0             1    0.275    0.276    0     0.023    0.176     0.503     0.999
   hist 
 1 ▂▆▇▃▁
 2 ▇▇▇▅▁
 3 ▁▃▇▃▂
 4 ▇▇▆▆▆
 5 ▅▇▇▁▁
 6 ▅▇▇▃▁
 7 ▇▂▁▁▁
 8 ▇▅▇▇▅
 9 ▇▂▁▁▁
10 ▅▇▁▁▁
11 ▇▅▁▁▁
12 ▂▅▇▂▁
13 ▇▆▁▁▁
14 ▇▂▂▂▁

Exploratory data analysis

p1 = df %>% group_by(label,Contacts_Count_12_mon) %>% tally() %>% mutate(prop=n/sum(n)) %>% ggplot(aes(x=Contacts_Count_12_mon, y=prop,fill=label)) + geom_col(position="dodge") + scale_fill_jama() + labs(y="proportion") + theme_light() + theme(legend.position="bottom")
p2 = df %>% group_by(label,Months_Inactive_12_mon) %>% tally() %>% mutate(prop=n/sum(n)) %>% ggplot(aes(x=Months_Inactive_12_mon, y=prop,fill=label)) + geom_col(position="dodge") + scale_fill_jama() + labs(y="proportion") + theme_light() + theme(legend.position="bottom")
grid.arrange(p1,p2,ncol=2,nrow=1)

p3 = df %>% group_by(label,Total_Relationship_Count) %>% tally() %>% mutate(prop=n/sum(n)) %>% ggplot(aes(x=Total_Relationship_Count, y=prop,fill=label)) + geom_col(position="dodge") + scale_fill_jama() + labs(y="proportion") + theme_light() + theme(legend.position="bottom") 
p4 = df %>% group_by(label,Dependent_count) %>% tally() %>% mutate(prop=n/sum(n)) %>% ggplot(aes(x=Dependent_count, y=prop,fill=label)) + geom_col(position="dodge") + scale_fill_jama() + labs(y="proportion") + theme_light() + theme(legend.position="bottom")
grid.arrange(p3,p4,ncol=2,nrow=1)

p5 = df %>% group_by(Gender,label) %>% tally() %>% mutate(prop=n/sum(n)) %>% ggplot(aes(x=label, y=prop,fill=Gender)) + geom_col(position="dodge") + scale_fill_jama() + labs(y="proportion") + theme_light() + theme(legend.position="bottom")
p6 = df %>% group_by(label,Education_Level) %>% tally() %>% mutate(prop=n/sum(n)) %>% ggplot(aes(x=Education_Level, y=prop,fill=label)) + geom_col(position="dodge") + scale_fill_jama() + labs(y="proportion") + theme_light() + theme(legend.position="bottom") + coord_flip()
grid.arrange(p5,p6,ncol=2,nrow=1)

Density plots (inspired by Who’s gonna churn? by Carmine Minichini)

p7 = df %>% ggplot(aes(x=Total_Trans_Ct, fill=label)) + geom_density(alpha=0.6) + scale_fill_jama() + theme_light() 
p8 = df %>% ggplot(aes(x=Total_Ct_Chng_Q4_Q1, fill=label)) + geom_density(alpha=0.6) + scale_fill_jama() + theme_light()
p9 = df %>% ggplot(aes(x=Total_Revolving_Bal, fill=label)) + geom_density(alpha=0.6) + scale_fill_jama() + theme_light()
p10 = df %>% ggplot(aes(x=Avg_Utilization_Ratio, fill=label)) + geom_density(alpha=0.6) + scale_fill_jama() + theme_light()
p11 = df %>% ggplot(aes(x=Total_Trans_Amt, fill=label)) + geom_density(alpha=0.6) + scale_fill_jama() + theme_light()
p12 = df %>% ggplot(aes(x=Credit_Limit, fill=label)) + geom_density(alpha=0.6) + scale_fill_jama() + theme_light()
grid.arrange(p7,p8,ncol=1,nrow=2)

grid.arrange(p9,p10,ncol=1,nrow=2)

grid.arrange(p11,p12,ncol=1,nrow=2)

Feature selection

#check correlation of all numeric variables
df_num = select_if(df,is.numeric)
df_num = data.frame(lapply(df_num, function(x) as.numeric(as.character(x))))
res=cor(df_num)
corrplot(res, type="upper", tl.col="#636363",tl.cex=0.5 )

#drop Months_on_book,Total_Trans_Amt, Total_Amt_Chng_Q4_Q1, Avg_Utilization_Ratio
df1 = df %>% select(-c(Months_on_book,Total_Trans_Amt, Total_Amt_Chng_Q4_Q1, Avg_Utilization_Ratio, Avg_Open_To_Buy))
dim(df1)
#check correlation after dropping variables
df1_num = select_if(df1,is.numeric)
df1_num = data.frame(lapply(df1_num, function(x) as.numeric(as.character(x))))
res2=cor(df1_num)
corrplot(res2, type="lower", tl.col="#636363",tl.cex=0.5 )

Test and train set

trainIndex <- createDataPartition(df1$label, p = .75,list=FALSE)
training <- df1[trainIndex,]
testing <- df1[-trainIndex,]
Hmisc::describe(training$label)
training$label 
       n  missing distinct 
    7596        0        2 
                      
Value          0     1
Frequency   6375  1221
Proportion 0.839 0.161
Hmisc::describe(testing$label)
testing$label 
       n  missing distinct 
    2531        0        2 
                    
Value         0    1
Frequency  2125  406
Proportion 0.84 0.16

Logistic regression

model1= glm(label ~., data=training, family = "binomial")
summary(model1) 

Call:
glm(formula = label ~ ., family = "binomial", data = training)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.8443  -0.4139  -0.2076  -0.0890   3.5346  

Coefficients:
                                Estimate Std. Error z value Pr(>|z|)    
(Intercept)                    5.250e+00  4.985e-01  10.533  < 2e-16 ***
Customer_Age                  -9.093e-03  5.134e-03  -1.771 0.076564 .  
GenderM                       -7.165e-01  1.600e-01  -4.478 7.55e-06 ***
Dependent_count                1.162e-01  3.308e-02   3.512 0.000444 ***
Education_LevelDoctorate       2.583e-01  2.324e-01   1.111 0.266391    
Education_LevelGraduate       -6.983e-02  1.539e-01  -0.454 0.649965    
Education_LevelHigh School     7.434e-02  1.630e-01   0.456 0.648452    
Education_LevelPost-Graduate   4.239e-01  2.226e-01   1.905 0.056843 .  
Education_LevelUneducated      1.176e-01  1.724e-01   0.682 0.495204    
Education_LevelUnknown         9.088e-02  1.708e-01   0.532 0.594734    
Marital_StatusMarried         -3.225e-01  1.682e-01  -1.917 0.055228 .  
Marital_StatusSingle           1.483e-01  1.690e-01   0.878 0.380112    
Marital_StatusUnknown          9.930e-02  2.172e-01   0.457 0.647562    
Income_Category$40K - $60K    -4.664e-01  2.304e-01  -2.024 0.042935 *  
Income_Category$60K - $80K    -3.511e-01  2.073e-01  -1.693 0.090437 .  
Income_Category$80K - $120K    1.470e-01  1.903e-01   0.773 0.439754    
Income_CategoryLess than $40K -3.622e-01  2.493e-01  -1.453 0.146253    
Income_CategoryUnknown        -4.259e-01  2.597e-01  -1.640 0.101059    
Card_CategoryGold              1.389e+00  4.092e-01   3.394 0.000690 ***
Card_CategoryPlatinum          1.483e+00  8.517e-01   1.741 0.081654 .  
Card_CategorySilver            4.279e-01  2.312e-01   1.851 0.064126 .  
Total_Relationship_Count      -5.489e-01  3.058e-02 -17.947  < 2e-16 ***
Months_Inactive_12_mon         4.717e-01  4.106e-02  11.488  < 2e-16 ***
Contacts_Count_12_mon          4.393e-01  3.942e-02  11.145  < 2e-16 ***
Credit_Limit                  -9.382e-06  6.983e-06  -1.344 0.179063    
Total_Revolving_Bal           -9.144e-04  5.151e-05 -17.751  < 2e-16 ***
Total_Trans_Ct                -6.611e-02  2.517e-03 -26.264  < 2e-16 ***
Total_Ct_Chng_Q4_Q1           -2.767e+00  2.004e-01 -13.809  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 6698.1  on 7595  degrees of freedom
Residual deviance: 3871.1  on 7568  degrees of freedom
AIC: 3927.1

Number of Fisher Scoring iterations: 6
pR2(model1)  
fitting null model for pseudo-r2
          llh       llhNull            G2      McFadden          r2ML          r2CU 
-1935.5629265 -3349.0692506  2827.0126481     0.4220594     0.3107638     0.5303478 
anova(model1, test= "Chisq")
Analysis of Deviance Table

Model: binomial, link: logit

Response: label

Terms added sequentially (first to last)

                         Df Deviance Resid. Df Resid. Dev  Pr(>Chi)    
NULL                                      7595     6698.1              
Customer_Age              1     1.71      7594     6696.4 0.1911947    
Gender                    1    12.06      7593     6684.4 0.0005162 ***
Dependent_count           1     1.76      7592     6682.6 0.1849018    
Education_Level           6     8.61      7586     6674.0 0.1969515    
Marital_Status            3     3.54      7583     6670.5 0.3154854    
Income_Category           5     7.35      7578     6663.1 0.1961685    
Card_Category             3     2.66      7575     6660.5 0.4477147    
Total_Relationship_Count  1   192.81      7574     6467.7 < 2.2e-16 ***
Months_Inactive_12_mon    1   170.79      7573     6296.9 < 2.2e-16 ***
Contacts_Count_12_mon     1   362.38      7572     5934.5 < 2.2e-16 ***
Credit_Limit              1     6.66      7571     5927.8 0.0098542 ** 
Total_Revolving_Bal       1   457.22      7570     5470.6 < 2.2e-16 ***
Total_Trans_Ct            1  1345.97      7569     4124.6 < 2.2e-16 ***
Total_Ct_Chng_Q4_Q1       1   253.50      7568     3871.1 < 2.2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
prob=predict(model1,testing,type="response")
prob1=rep(0,2531)
prob1[prob>0.2]=1
cmlr = confusionMatrix(as.factor(prob1), testing$label, positive="1")
cmlr
Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 1870   87
         1  255  319
                                         
               Accuracy : 0.8649         
                 95% CI : (0.8509, 0.878)
    No Information Rate : 0.8396         
    P-Value [Acc > NIR] : 0.0002231      
                                         
                  Kappa : 0.5703         
                                         
 Mcnemar's Test P-Value : < 2.2e-16      
                                         
            Sensitivity : 0.7857         
            Specificity : 0.8800         
         Pos Pred Value : 0.5557         
         Neg Pred Value : 0.9555         
             Prevalence : 0.1604         
         Detection Rate : 0.1260         
   Detection Prevalence : 0.2268         
      Balanced Accuracy : 0.8329         
                                         
       'Positive' Class : 1              
                                         
round(cmlr$byClass["F1"], 4)
   F1 
0.651 
roc_lr2 = roc(testing$label, prob1, plot=TRUE, print.auc=TRUE)
Setting levels: control = 0, case = 1
Setting direction: controls < cases

Decision tree

mt = rpart(label ~., data = training, method = "class")
plotcp(mt)
mt_prune = prune(mt,cp=0.036)
fancyRpartPlot(mt_prune)

printcp(mt_prune)

Classification tree:
rpart(formula = label ~ ., data = training, method = "class")

Variables actually used in tree construction:
[1] Total_Relationship_Count Total_Revolving_Bal      Total_Trans_Ct          

Root node error: 1221/7596 = 0.16074

n= 7596 

        CP nsplit rel error  xerror     xstd
1 0.165029      0   1.00000 1.00000 0.026217
2 0.074529      2   0.66994 0.68223 0.022304
3 0.036000      3   0.59541 0.60852 0.021204
mt_prune$variable.importance
          Total_Trans_Ct      Total_Revolving_Bal Total_Relationship_Count      Total_Ct_Chng_Q4_Q1 
              356.304278               335.977874               141.636180               108.826027 
            Credit_Limit    Contacts_Count_12_mon   Months_Inactive_12_mon             Customer_Age 
               53.946861                 8.022617                 3.047734                 2.168455 
tree.p = predict(mt_prune, testing, type = "class")
cmt = confusionMatrix(tree.p, testing$label, positive ="1")
cmt
Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 2039  157
         1   86  249
                                          
               Accuracy : 0.904           
                 95% CI : (0.8918, 0.9152)
    No Information Rate : 0.8396          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.6164          
                                          
 Mcnemar's Test P-Value : 7.106e-06       
                                          
            Sensitivity : 0.61330         
            Specificity : 0.95953         
         Pos Pred Value : 0.74328         
         Neg Pred Value : 0.92851         
             Prevalence : 0.16041         
         Detection Rate : 0.09838         
   Detection Prevalence : 0.13236         
      Balanced Accuracy : 0.78641         
                                          
       'Positive' Class : 1               
                                          
round(cmt$byClass["F1"], 4)
    F1 
0.6721 
testing$tp1= tree.p
roc_t= roc(response= testing$label, predictor = factor(testing$tp1, ordered=TRUE), plot=TRUE, print.auc=TRUE)
Setting levels: control = 0, case = 1
Setting direction: controls < cases

*249 out of 486 positive instances were predicted correctly (recall of 0.613) with classification tree.

Random forest

trControl <- trainControl(method = "cv",
    number = 10,
    search = "grid")
set.seed(1234)
rf1 = train(label ~ .,data = training,method="rf",metric ="Accuracy",trControl = trControl)
print(rf1)
plot(rf1)

varImp(rf1)
rf variable importance

  only 20 most important variables shown (out of 27)
rfpred = predict(rf1, testing)
cmrf = confusionMatrix(rfpred, testing$label,positive="1")
cmrf
Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 2076  129
         1   49  277
                                         
               Accuracy : 0.9297         
                 95% CI : (0.919, 0.9393)
    No Information Rate : 0.8396         
    P-Value [Acc > NIR] : < 2.2e-16      
                                         
                  Kappa : 0.7163         
                                         
 Mcnemar's Test P-Value : 3.194e-09      
                                         
            Sensitivity : 0.6823         
            Specificity : 0.9769         
         Pos Pred Value : 0.8497         
         Neg Pred Value : 0.9415         
             Prevalence : 0.1604         
         Detection Rate : 0.1094         
   Detection Prevalence : 0.1288         
      Balanced Accuracy : 0.8296         
                                         
       'Positive' Class : 1              
                                         
round(cmrf$byClass["F1"], 4)
    F1 
0.7568 
testing$rfp= rfpred
roc_rf= roc(response= testing$label, predictor = factor(testing$rfp, ordered=TRUE), plot=TRUE, print.auc=TRUE)
Setting levels: control = 0, case = 1
Setting direction: controls < cases

XGBoost

XGBoost code reference: Who’s gonna churn? by Carmine Minichini

target_column = df1$label
data =  df1 %>% select(-label)
dmy = dummyVars(" ~ .", data = data)
train_data = data.frame(predict(dmy, newdata = data))
data <- cbind(train_data,target_column)
names(data)[33] <- 'label'
trainIndex <- createDataPartition(data$label,p=0.75,list=FALSE)
data_train <- data[trainIndex,]
data_test <-  data[-trainIndex,]
grid_train = data_train
levels(grid_train$label) <- c("X0","X1")
#grid parameters
xgb_grid_1 = expand.grid(
    nrounds = 10,
    eta = seq(2,10,by=1)/10,
    max_depth = c(6, 8, 10),
    gamma = 0,
    subsample = c(0.5, 0.75, 1),
    min_child_weight = c(1,2) ,
    colsample_bytree = c(0.3,0.5)
  )
# pack the training control parameters
  xgb_trcontrol_1 = trainControl(
    method = "cv",
    number = 2,
    search='grid',
    verboseIter = FALSE,
    returnData = TRUE,
    returnResamp = "all", # save losses across all models
    classProbs = TRUE, # set to TRUE for AUC to be computed
    summaryFunction = prSummary, # probability summary(AUC)
    allowParallel = TRUE,
  )
xgb_train_1 = train(
    x = as.matrix(grid_train %>% select(-label)),
    y = factor(grid_train$label),
    trControl = xgb_trcontrol_1,
    tuneGrid = xgb_grid_1,
    method = "xgbTree",
    metric= 'Recall'
  )
best_tune <- xgb_train_1$bestTune
results <- xgb_train_1$results
trained_model <- xgb_train_1
cat(paste("",
            paste('With a recall of:',results[rownames(best_tune),"Recall"]),
            'Best GRIDSEARCH Hyperparameters:',
            '',
            sep='\n\n'))


With a recall of: 0.997489999169304

Best GRIDSEARCH Hyperparameters:
  
rownames(best_tune) <- 'Value'
print(t(best_tune))
                 Value
nrounds          10.00
max_depth         6.00
eta               0.20
gamma             0.00
colsample_bytree  0.30
min_child_weight  1.00
subsample         0.75
#out dataframe
gridresults = results
best_tune = best_tune
train_data = data_train
test_data = data_test
#best hyperparameters from gridsearch
best_tune <- best_tune
#train
data_train <- train_data %>% select(-label)
label_train <- train_data$label
#test
data_test <- test_data %>% select(-label)
label_test <- test_data$label
# as matrix
data_train <- as.matrix(data_train)
data_test <- as.matrix(data_test)
# as numeric
label_train <- as.numeric(label_train)
label_test <- as.numeric(label_test)
#relevel
label_train= ifelse(label_train>1,1,0)
label_test= ifelse(label_test>1,1,0)
#XGB matrix
dtrain <-  xgb.DMatrix(data_train,label=label_train)
dtest <- xgb.DMatrix(data_test,label=label_test)
#XGB model
 model <- xgboost(data= dtrain, 
                   objective = "binary:logistic",
                   max_depth = best_tune$max_depth,
                   nrounds=100,
                   colsample_bytree = best_tune$colsample_bytree,
                   gamma = best_tune$gamma,
                   min_child_weight = best_tune$min_child_weight,
                   eta = best_tune$eta, 
                   subsample = best_tune$subsample,
                   print_every_n = 20,
                   scale_pos_weight=5.22,
                   max_delta_step=1,
                   eval_metric='aucpr',
                   verbose=1,
                   nthread = 4)
[1] train-aucpr:0.452852 
[21]    train-aucpr:0.905627 
[41]    train-aucpr:0.946657 
[61]    train-aucpr:0.963228 
[81]    train-aucpr:0.974350 
[100]   train-aucpr:0.982677 
cv  <-  xgb.cv(data = dtrain, 
                 nround = 50, 
                 print_every_n= 10,
                 verbose = TRUE,
                 metrics = list("aucpr"),
                 nfold = 5, 
                 nthread = 4,
                 objective = "binary:logistic",
                 prediction=F)
[1] train-aucpr:0.820787+0.011257   test-aucpr:0.754658+0.031137 
[11]    train-aucpr:0.938609+0.003525   test-aucpr:0.848662+0.024433 
[21]    train-aucpr:0.963758+0.004984   test-aucpr:0.862755+0.019337 
[31]    train-aucpr:0.978737+0.003634   test-aucpr:0.867039+0.020116 
[41]    train-aucpr:0.988214+0.001604   test-aucpr:0.866213+0.021019 
[50]    train-aucpr:0.993660+0.001419   test-aucpr:0.867678+0.021359 
out <- list(data_train = data_train,
              dtest = dtest,
              label_test = label_test,
              model = model)
data_train <- data_train
pred <- predict(model,dtest)
prediction <- as.numeric(pred > 0.5)
cm <- confusionMatrix(factor(prediction),factor(label_test),positive="1")
cm
Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 1989   72
         1  136  334
                                          
               Accuracy : 0.9178          
                 95% CI : (0.9064, 0.9282)
    No Information Rate : 0.8396          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.7132          
                                          
 Mcnemar's Test P-Value : 1.252e-05       
                                          
            Sensitivity : 0.8227          
            Specificity : 0.9360          
         Pos Pred Value : 0.7106          
         Neg Pred Value : 0.9651          
             Prevalence : 0.1604          
         Detection Rate : 0.1320          
   Detection Prevalence : 0.1857          
      Balanced Accuracy : 0.8793          
                                          
       'Positive' Class : 1               
                                          
round(cm$byClass["F1"], 4)
    F1 
0.7626 
roc.curve = roc(response = label_test,
                  predictor = prediction,
                  levels=c(0, 1),quiet = T) 
plot(roc.curve,print.auc=TRUE)

*334 out of 486 positive instances are predicted correctly with XGB (0.823)

importance_matrix <- xgb.importance(colnames(data_train), model = model)
  xgb.plot.importance(importance_matrix,
                      top_n=10,
                      main='Features Importance',
                      measure = 'Frequency')

Summary of exercise

Recall AUC F1 Score
Logistic regression 0.786 0.833 0.651
Decision tree 0.613 0.786 0.672
Random Forest 0.682 0.83 0.757
XGBoost 0.823 0.879 0.763
LS0tCnRpdGxlOiAiQ3VzdG9tZXIgQ2h1cm4gUHJlZGljdGlvbiBFeGVyY2lzZSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMjIyBEYXRhc2V0OiBbQ3JlZGl0IGNhcmQgY3VzdG9tZXJzXShodHRwczovL3d3dy5rYWdnbGUuY29tL3Nha3NoaWdveWFsNy9jcmVkaXQtY2FyZC1jdXN0b21lcnMvdGFza3M/dGFza0lkPTI3MjkpCgoKIyMjIyBMb2FkIHBhY2thZ2VzCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2tpbXIpCmxpYnJhcnkoSG1pc2MpCmxpYnJhcnkoZ2dzY2kpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkoZ2dwdWJyKQpsaWJyYXJ5KGdyaWRFeHRyYSkKbGlicmFyeShjb3JycGxvdCkKbGlicmFyeShycGFydCkKbGlicmFyeShycGFydC5wbG90KQpsaWJyYXJ5KHJhdHRsZSkKbGlicmFyeShyYW5kb21Gb3Jlc3QpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkoeGdib29zdCkKbGlicmFyeShwc2NsKQpsaWJyYXJ5KHBST0MpCmBgYAoKIyMjIyBJbXBvcnQgZGF0YQpgYGB7cn0KZGYgPSByZWFkLmNzdigiQmFua0NodXJuZXJzLmNzdiIsIGhlYWRlcj1UUlVFKQpoZWFkKGRmKQojZGVsZXRlIG5haXZlIGJheWVzIGNvbHVtbnMKZGYgPSBkZlssMjoyMV0KYGBgCgpgYGB7cn0KZGltKGRmKQpjb2xuYW1lcyhkZikKYGBgCgojIyMjIFRhcmdldCB2YXJpYWJsZQpgYGB7cn0KSG1pc2M6OmRlc2NyaWJlKGRmJEF0dHJpdGlvbl9GbGFnKQojY29udmVydCB0YXJnZXQgdmFyaWFibGUgdG8gbnVtZXJpYyAKZGYkbGFiZWw9IGlmZWxzZShkZiRBdHRyaXRpb25fRmxhZyA9PSJBdHRyaXRlZCBDdXN0b21lciIsIjEiLCIwIikKZGYkbGFiZWwgPSBhcy5mYWN0b3IoZGYkbGFiZWwpCkhtaXNjOjpkZXNjcmliZShkZiRsYWJlbCkKI2Ryb3AgYXR0cml0aW9uIGZsYWcgCmRmID0gc3Vic2V0KGRmLCBzZWxlY3QgPSAtYyhBdHRyaXRpb25fRmxhZykpCmBgYAoKKiBGb3IgdGhlIHB1cnBvc2Ugb2YgdGhpcyBleGVyY2lzZSwgQXR0cml0ZWQgQ3VzdG9tZXIgYW5kIEV4aXN0aW5nIEN1c3RvbWVyIGNsYXNzZXMgYXJlIHJlbGV2ZWxlZCB0byAxIGFuZCAwIHJlc3BlY3RpdmVseS4gCiogT3V0IG9mIDEwMTI3IG9icywgMTYyNyAoMTYuMSUpIGFyZSBhdHRyaXRlZCBjdXN0b21lcnMgc3VnZ2VzdGluZyBhbiBpbWJhbGFuY2VkIGRhdGFzZXQuCgpgYGB7cn0KI3N1bW1hcnkKZGYgPSBkZiAlPiUgbXV0YXRlX2F0KHZhcnMobGFiZWwsIEdlbmRlcixFZHVjYXRpb25fTGV2ZWwsSW5jb21lX0NhdGVnb3J5LE1hcml0YWxfU3RhdHVzLENhcmRfQ2F0ZWdvcnkpLGxpc3QoZmFjdG9yKSkKc2tpbShkZikKYGBgCgojIyMjIEV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMKYGBge3J9CnAxID0gZGYgJT4lIGdyb3VwX2J5KGxhYmVsLENvbnRhY3RzX0NvdW50XzEyX21vbikgJT4lIHRhbGx5KCkgJT4lIG11dGF0ZShwcm9wPW4vc3VtKG4pKSAlPiUgZ2dwbG90KGFlcyh4PUNvbnRhY3RzX0NvdW50XzEyX21vbiwgeT1wcm9wLGZpbGw9bGFiZWwpKSArIGdlb21fY29sKHBvc2l0aW9uPSJkb2RnZSIpICsgc2NhbGVfZmlsbF9qYW1hKCkgKyBsYWJzKHk9InByb3BvcnRpb24iKSArIHRoZW1lX2xpZ2h0KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpCnAyID0gZGYgJT4lIGdyb3VwX2J5KGxhYmVsLE1vbnRoc19JbmFjdGl2ZV8xMl9tb24pICU+JSB0YWxseSgpICU+JSBtdXRhdGUocHJvcD1uL3N1bShuKSkgJT4lIGdncGxvdChhZXMoeD1Nb250aHNfSW5hY3RpdmVfMTJfbW9uLCB5PXByb3AsZmlsbD1sYWJlbCkpICsgZ2VvbV9jb2wocG9zaXRpb249ImRvZGdlIikgKyBzY2FsZV9maWxsX2phbWEoKSArIGxhYnMoeT0icHJvcG9ydGlvbiIpICsgdGhlbWVfbGlnaHQoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIikKZ3JpZC5hcnJhbmdlKHAxLHAyLG5jb2w9Mixucm93PTEpCmBgYAoKKiBDb250YWN0c19Db3VudF8xMl9tb246IGN1c3RvbWVycyB3aXRoIDMgb3IgbW9yZSBjb250YWN0cyBjb3VudCBpbiB0aGUgcGFzdCAxMiBtb250aHMgaGF2ZSBhIGhpZ2hlciBwcm9wb3J0aW9uIG9mIGF0dHJpdGlvbgoqIE1vbnRoc19JbmFjdGl2ZV8xMl9tb246IGN1c3RvbWVycyB3aXRoIDMgb3IgbW9yZSBpbmFjdGl2ZSBtb250aHMgaW4gdGhlIHBhc3QgMTIgbW9udGhzIGhhdmUgYSBoaWdoZXIgcHJvcG9ydGlvbiBvZiBhdHRyaXRpb24gCgpgYGB7cn0KcDMgPSBkZiAlPiUgZ3JvdXBfYnkobGFiZWwsVG90YWxfUmVsYXRpb25zaGlwX0NvdW50KSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlKHByb3A9bi9zdW0obikpICU+JSBnZ3Bsb3QoYWVzKHg9VG90YWxfUmVsYXRpb25zaGlwX0NvdW50LCB5PXByb3AsZmlsbD1sYWJlbCkpICsgZ2VvbV9jb2wocG9zaXRpb249ImRvZGdlIikgKyBzY2FsZV9maWxsX2phbWEoKSArIGxhYnMoeT0icHJvcG9ydGlvbiIpICsgdGhlbWVfbGlnaHQoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIikgCnA0ID0gZGYgJT4lIGdyb3VwX2J5KGxhYmVsLERlcGVuZGVudF9jb3VudCkgJT4lIHRhbGx5KCkgJT4lIG11dGF0ZShwcm9wPW4vc3VtKG4pKSAlPiUgZ2dwbG90KGFlcyh4PURlcGVuZGVudF9jb3VudCwgeT1wcm9wLGZpbGw9bGFiZWwpKSArIGdlb21fY29sKHBvc2l0aW9uPSJkb2RnZSIpICsgc2NhbGVfZmlsbF9qYW1hKCkgKyBsYWJzKHk9InByb3BvcnRpb24iKSArIHRoZW1lX2xpZ2h0KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpCmdyaWQuYXJyYW5nZShwMyxwNCxuY29sPTIsbnJvdz0xKQpgYGAKCiogVG90YWxfUmVsYXRpb25zaGlwX0NvdW50OiBjdXN0b21lcnMgd2l0aCAzIG9yIGxlc3MgdG90YWwgcmVsYXRpb25zaGlwIGNvdW50IGhhcyBhIGhpZ2hlciBwcm9wb3J0aW9uIG9mIGF0dHJpdGlvbgoqIERlcGVuZGVudF9jb3VudDogY3VzdG9tZXJzIHdpdGggMyBvciBtb3JlIGRlcGVuZGVudHMgaGF2ZSBhIGhpZ2hlciBwcm9wb3J0aW9uIG9mIGF0dHJpdGlvbiAKCmBgYHtyfQpwNSA9IGRmICU+JSBncm91cF9ieShHZW5kZXIsbGFiZWwpICU+JSB0YWxseSgpICU+JSBtdXRhdGUocHJvcD1uL3N1bShuKSkgJT4lIGdncGxvdChhZXMoeD1sYWJlbCwgeT1wcm9wLGZpbGw9R2VuZGVyKSkgKyBnZW9tX2NvbChwb3NpdGlvbj0iZG9kZ2UiKSArIHNjYWxlX2ZpbGxfamFtYSgpICsgbGFicyh5PSJwcm9wb3J0aW9uIikgKyB0aGVtZV9saWdodCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKQpwNiA9IGRmICU+JSBncm91cF9ieShsYWJlbCxFZHVjYXRpb25fTGV2ZWwpICU+JSB0YWxseSgpICU+JSBtdXRhdGUocHJvcD1uL3N1bShuKSkgJT4lIGdncGxvdChhZXMoeD1FZHVjYXRpb25fTGV2ZWwsIHk9cHJvcCxmaWxsPWxhYmVsKSkgKyBnZW9tX2NvbChwb3NpdGlvbj0iZG9kZ2UiKSArIHNjYWxlX2ZpbGxfamFtYSgpICsgbGFicyh5PSJwcm9wb3J0aW9uIikgKyB0aGVtZV9saWdodCgpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKSArIGNvb3JkX2ZsaXAoKQpncmlkLmFycmFuZ2UocDUscDYsbmNvbD0yLG5yb3c9MSkKYGBgCgoqIEdlbmRlcjogRmVtYWxlIGhhcyBhIGhpZ2hlciBwcm9wb3J0aW9uIG9mIGF0dHJpdGlvbiBjb21wYXJlZCB0byBtYWxlcy4gCiogRWR1Y2F0aW9uX0xldmVsOiBUaGUgYXR0cml0aW9uIGNsYXNzIGhhcyBhIGhpZ2hlciBwcm9wb3J0aW9uIG9mIGRvY3RvcmF0ZSwgcG9zdC1ncmFkdWF0ZSBhbmQgdW5rbm93biBlZHVjYXRpb24gbGV2ZWwgY29tcGFyZWQgdG8gdGhlIGV4aXN0aW5nIGN1c3RvbWVyIGNsYXNzLgoKRGVuc2l0eSBwbG90cyAoaW5zcGlyZWQgYnkgW1dobydzIGdvbm5hIGNodXJuP10oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS92aXJvc2t5L3doby1zLWdvbm5hLWNodXJuKSBieSBDYXJtaW5lIE1pbmljaGluaSkKYGBge3J9CnA3ID0gZGYgJT4lIGdncGxvdChhZXMoeD1Ub3RhbF9UcmFuc19DdCwgZmlsbD1sYWJlbCkpICsgZ2VvbV9kZW5zaXR5KGFscGhhPTAuNikgKyBzY2FsZV9maWxsX2phbWEoKSArIHRoZW1lX2xpZ2h0KCkgCnA4ID0gZGYgJT4lIGdncGxvdChhZXMoeD1Ub3RhbF9DdF9DaG5nX1E0X1ExLCBmaWxsPWxhYmVsKSkgKyBnZW9tX2RlbnNpdHkoYWxwaGE9MC42KSArIHNjYWxlX2ZpbGxfamFtYSgpICsgdGhlbWVfbGlnaHQoKQpwOSA9IGRmICU+JSBnZ3Bsb3QoYWVzKHg9VG90YWxfUmV2b2x2aW5nX0JhbCwgZmlsbD1sYWJlbCkpICsgZ2VvbV9kZW5zaXR5KGFscGhhPTAuNikgKyBzY2FsZV9maWxsX2phbWEoKSArIHRoZW1lX2xpZ2h0KCkKcDEwID0gZGYgJT4lIGdncGxvdChhZXMoeD1BdmdfVXRpbGl6YXRpb25fUmF0aW8sIGZpbGw9bGFiZWwpKSArIGdlb21fZGVuc2l0eShhbHBoYT0wLjYpICsgc2NhbGVfZmlsbF9qYW1hKCkgKyB0aGVtZV9saWdodCgpCnAxMSA9IGRmICU+JSBnZ3Bsb3QoYWVzKHg9VG90YWxfVHJhbnNfQW10LCBmaWxsPWxhYmVsKSkgKyBnZW9tX2RlbnNpdHkoYWxwaGE9MC42KSArIHNjYWxlX2ZpbGxfamFtYSgpICsgdGhlbWVfbGlnaHQoKQpwMTIgPSBkZiAlPiUgZ2dwbG90KGFlcyh4PUNyZWRpdF9MaW1pdCwgZmlsbD1sYWJlbCkpICsgZ2VvbV9kZW5zaXR5KGFscGhhPTAuNikgKyBzY2FsZV9maWxsX2phbWEoKSArIHRoZW1lX2xpZ2h0KCkKZ3JpZC5hcnJhbmdlKHA3LHA4LG5jb2w9MSxucm93PTIpCmdyaWQuYXJyYW5nZShwOSxwMTAsbmNvbD0xLG5yb3c9MikKZ3JpZC5hcnJhbmdlKHAxMSxwMTIsbmNvbD0xLG5yb3c9MikKYGBgCgoqIFRoZSBhdHRyaXRpb24gY2xhc3MgaGFzIGEgbG93ZXIgdG90YWwgdHJhbnNhY3Rpb24gY291bnQsIHRvdGFsIGNvdW50IGNoYW5nZSwgdG90YWwgcmV2b2x2aW5nIGJhbGFuY2UsIGF2ZXJhZ2UgdXRpbGl6YXRpb24gcmF0aW8gYW5kIHRvdGFsIHRyYW5zYWN0aW9ucyBhbW91bnQgY29tcGFyZWQgdG8gdGhlIGV4aXN0aW5nIGN1c3RvbWVycyBjbGFzcywgYXMgZXhwZWN0ZWQuCgojIyMjIEZlYXR1cmUgc2VsZWN0aW9uCmBgYHtyfQojY2hlY2sgY29ycmVsYXRpb24gb2YgYWxsIG51bWVyaWMgdmFyaWFibGVzCmRmX251bSA9IHNlbGVjdF9pZihkZixpcy5udW1lcmljKQpkZl9udW0gPSBkYXRhLmZyYW1lKGxhcHBseShkZl9udW0sIGZ1bmN0aW9uKHgpIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHgpKSkpCnJlcz1jb3IoZGZfbnVtKQpjb3JycGxvdChyZXMsIHR5cGU9InVwcGVyIiwgdGwuY29sPSIjNjM2MzYzIix0bC5jZXg9MC41ICkKYGBgCiogQXZnX09wZW5fVG9fQnV5IGlzIGhpZ2hseSBjb3JyZWxhdGVkIHRvIENyZWRpdF9MaW1pdAoqIFRvdGFsX1RyYW5zIGFtb3VudCBpcyBoaWdobHkgY29ycmVsYXRlZCB0byBUb3RhbF9UcmFuc19BbXQKKiBUb3RhbF9BbXRfQ2huZ19RNF9RMSBpcyBjb3JyZWxhdGVkIHRvIFRvdGFsX0N0X0NuZ19RNF9RMQoqIEF2Z19VdGlsaXphdGlvbl9SYXRpbyBpcyBjb3JyZWxhdGVkIHRvIEF2Z19PcGVuX1RvX0JVeSwgVG90YWxfUmV2b2x2aW5nX0JhbCBhbmQgQ3JlZGl0X0xpbWl0CgpgYGB7cn0KI2Ryb3AgTW9udGhzX29uX2Jvb2ssVG90YWxfVHJhbnNfQW10LCBUb3RhbF9BbXRfQ2huZ19RNF9RMSwgQXZnX1V0aWxpemF0aW9uX1JhdGlvCmRmMSA9IGRmICU+JSBzZWxlY3QoLWMoTW9udGhzX29uX2Jvb2ssVG90YWxfVHJhbnNfQW10LCBUb3RhbF9BbXRfQ2huZ19RNF9RMSwgQXZnX1V0aWxpemF0aW9uX1JhdGlvLCBBdmdfT3Blbl9Ub19CdXkpKQpkaW0oZGYxKQpgYGAKCmBgYHtyfQojY2hlY2sgY29ycmVsYXRpb24gYWZ0ZXIgZHJvcHBpbmcgdmFyaWFibGVzCmRmMV9udW0gPSBzZWxlY3RfaWYoZGYxLGlzLm51bWVyaWMpCmRmMV9udW0gPSBkYXRhLmZyYW1lKGxhcHBseShkZjFfbnVtLCBmdW5jdGlvbih4KSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih4KSkpKQpyZXMyPWNvcihkZjFfbnVtKQpjb3JycGxvdChyZXMyLCB0eXBlPSJsb3dlciIsIHRsLmNvbD0iIzYzNjM2MyIsdGwuY2V4PTAuNSApCmBgYAoKIyMjIyBUZXN0IGFuZCB0cmFpbiBzZXQKYGBge3J9CnRyYWluSW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihkZjEkbGFiZWwsIHAgPSAuNzUsbGlzdD1GQUxTRSkKdHJhaW5pbmcgPC0gZGYxW3RyYWluSW5kZXgsXQp0ZXN0aW5nIDwtIGRmMVstdHJhaW5JbmRleCxdCmBgYAoKYGBge3J9CkhtaXNjOjpkZXNjcmliZSh0cmFpbmluZyRsYWJlbCkKSG1pc2M6OmRlc2NyaWJlKHRlc3RpbmckbGFiZWwpCmBgYAoKCiMjIyMgTG9naXN0aWMgcmVncmVzc2lvbgoKYGBge3J9Cm1vZGVsMT0gZ2xtKGxhYmVsIH4uLCBkYXRhPXRyYWluaW5nLCBmYW1pbHkgPSAiYmlub21pYWwiKQpzdW1tYXJ5KG1vZGVsMSkgCnBSMihtb2RlbDEpICAKYW5vdmEobW9kZWwxLCB0ZXN0PSAiQ2hpc3EiKQpgYGAKCiogOCB2YXJpYWJsZXMgKEdlbmRlciwgVG90YWxfUmVsYXRpb25zaGlwX0NvdW50LCBNb250aHNfSW5hY3RpdmVfMTJfbW9uLCBDb250YWN0c19Db3VudF8xMl9tb24sIENyZWRpdF9MaW1pdCwgVG90YWxfUmV2b2x2aW5nX0JhbCwgVG90YWxfVHJhbnNfQ3QgYW5kIFRvdGFsX0N0X0NobmdfUTRfUTEpIGFyZSBzaWduaWZpY2FudCB2YXJpYWJsZXMgaW4gcHJlZGljdGluZyBjdXN0b21lciBjaHVybiBmb3IgY3JlZGl0IGNhcmQgc2VydmljZXMuIAogICsgQW5hbHlzaXMgb2YgZGV2aWFuY2UgdGFibGUgc2hvd3MgdGhhdCBUb3RhbF9UcmFuc19DdCBoYXMgbW9zdCBzaWduaWZpY2FudCB2YXJpYWJsZS4gCiogTG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBzdWdnZXN0cyB0aGF0OiAKICArIFRoZSBsb2cgb2RkcyBvZiBhIG1hbGUgY3VzdG9tZXIgY2h1cm5pbmcgaXMgMC43IGxvd2VyIHRoYW4gZmVtYWxlLiAKICArIEZvciBvbmUgdW5pdCBpbmNyZWFzZSBpbiBkZXBlbmRlbnQgY291bnQsIHRoZSBsb2cgb2RkcyBvZiB0aGUgY3VzdG9tZXIgY2h1cm5pbmcgaW5jcmVhc2VzIGJ5IDAuMS4KICArIFZlcnN1cyBpbmNvbWUgY2F0ZWdvcnkgMTIwSyssIHRoZSA0MEstNjBLIGNhdGVnb3J5IGRlY3JlYXNlcyB0aGUgbG9nIG9kZHMgb2YgdGhlIGN1c3RvbWVyIGNodXJuaW5nIGJ5IDAuNS4KICArIFZlcnN1cyB0aGUgYmx1ZSBjYXJkIGNhdGVnb3J5LCBnb2xkIGNhdGVnb3J5IGluY3JlYXNlcyB0aGUgbG9nIG9kZHMgb2YgdGhlIGN1c3RvbWVyIGNodXJuaW5nIGJ5IDEuNAogICsgRm9yIG9uZSB1bml0IGluY3JlYXNlIGluIHRvdGFsIHJlbGF0aW9uc2hpcCBjb3VudCwgdGhlIGxvZyBvZGRzIG9mIHRoZSBjdXN0b21lciBjaHVybmluZyBkZWNyZWFzZXMgYnkgMC41LiAKICArIEZvciBvbmUgdW5pdCBpbmNyZWFzZSBpbiBtb250aHMgaW5hY3RpdmUgaW4gdGhlIGxhc3QgMTIgbW9udGhzLCB0aGUgbG9nIG9kZHMgb2YgdGhlIGN1c3RvbWVyIGNodXJuaW5nIGluY3JlYXNlcyBieSAwLjUuCiAgKyBGb3Igb25lIHVuaXQgaW5jcmVhc2UgaW4gdG90YWwgcmV2b2x2aW5nIGJhbGFuY2UsIHRoZSBsb2cgb2RkcyBvZiB0aGUgY3VzdG9tZXIgY2h1cm5pbmcgZGVjcmVhc2VzIGJ5IDAuMDAwOS4gCiAgKyBGb3Igb25lIHVuaXQgaW5jcmVhc2UgaW4gdG90YWwgdHJhbnNhY3Rpb24gY291bnQsIHRoZSBsb2cgb2RkcyBvZiB0aGUgY3VzdG9tZXIgY2h1cm5pbmcgZGVjcmVhc2VzIGJ5IDAuMDcuCiAgKyBGb3Igb25lIHVuaXQgaW5jcmVhc2UgQ2hhbmdlIGluIFRyYW5zYWN0aW9uIENvdW50IChRNCBvdmVyIFExKSAsIHRoZSBsb2cgb2RkcyBvZiB0aGUgY3VzdG9tZXIgY2h1cm5pbmcgZGVjcmVhc2VzIGJ5IDIuOC4KCgpgYGB7cn0KcHJvYj1wcmVkaWN0KG1vZGVsMSx0ZXN0aW5nLHR5cGU9InJlc3BvbnNlIikKcHJvYjE9cmVwKDAsMjUzMSkKcHJvYjFbcHJvYj4wLjJdPTEKY21sciA9IGNvbmZ1c2lvbk1hdHJpeChhcy5mYWN0b3IocHJvYjEpLCB0ZXN0aW5nJGxhYmVsLCBwb3NpdGl2ZT0iMSIpCmNtbHIKcm91bmQoY21sciRieUNsYXNzWyJGMSJdLCA0KQpyb2NfbHIyID0gcm9jKHRlc3RpbmckbGFiZWwsIHByb2IxLCBwbG90PVRSVUUsIHByaW50LmF1Yz1UUlVFKQpgYGAKKiAzMTkgb3V0IG9mIDQwNiBwb3NpdGl2ZSBpbnN0YW5jZXMgd2VyZSBwcmVkaWN0ZWQgY29ycmVjdGx5IChyZWNhbGwgb2YgMC43ODYpIHdpdGggbG9naXN0aWMgcmVncmVzc2lvbiAocHJvYmFiaWxpdHkgdmFsdWUgMC4yKQoKIyMjIyBEZWNpc2lvbiB0cmVlCmBgYHtyfQptdCA9IHJwYXJ0KGxhYmVsIH4uLCBkYXRhID0gdHJhaW5pbmcsIG1ldGhvZCA9ICJjbGFzcyIpCnBsb3RjcChtdCkKYGBgCgpgYGB7cn0KbXRfcHJ1bmUgPSBwcnVuZShtdCxjcD0wLjAzNikKZmFuY3lScGFydFBsb3QobXRfcHJ1bmUpCnByaW50Y3AobXRfcHJ1bmUpCm10X3BydW5lJHZhcmlhYmxlLmltcG9ydGFuY2UKYGBgCgpgYGB7cn0KdHJlZS5wID0gcHJlZGljdChtdF9wcnVuZSwgdGVzdGluZywgdHlwZSA9ICJjbGFzcyIpCmNtdCA9IGNvbmZ1c2lvbk1hdHJpeCh0cmVlLnAsIHRlc3RpbmckbGFiZWwsIHBvc2l0aXZlID0iMSIpCmNtdApyb3VuZChjbXQkYnlDbGFzc1siRjEiXSwgNCkKdGVzdGluZyR0cDE9IHRyZWUucApyb2NfdD0gcm9jKHJlc3BvbnNlPSB0ZXN0aW5nJGxhYmVsLCBwcmVkaWN0b3IgPSBmYWN0b3IodGVzdGluZyR0cDEsIG9yZGVyZWQ9VFJVRSksIHBsb3Q9VFJVRSwgcHJpbnQuYXVjPVRSVUUpCmBgYAoKKjI0OSBvdXQgb2YgNDg2IHBvc2l0aXZlIGluc3RhbmNlcyB3ZXJlIHByZWRpY3RlZCBjb3JyZWN0bHkgKHJlY2FsbCBvZiAwLjYxMykgd2l0aCBjbGFzc2lmaWNhdGlvbiB0cmVlLiAKCiMjIyMgUmFuZG9tIGZvcmVzdCAKYGBge3J9CnRyQ29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwKICAgIG51bWJlciA9IDEwLAogICAgc2VhcmNoID0gImdyaWQiKQpgYGAKCmBgYHtyfQpzZXQuc2VlZCgxMjM0KQpyZjEgPSB0cmFpbihsYWJlbCB+IC4sZGF0YSA9IHRyYWluaW5nLG1ldGhvZD0icmYiLG1ldHJpYyA9IkFjY3VyYWN5Iix0ckNvbnRyb2wgPSB0ckNvbnRyb2wpCnByaW50KHJmMSkKYGBgCgpgYGB7cn0KcGxvdChyZjEpCnZhckltcChyZjEpCmBgYApgYGB7cn0KcmZwcmVkID0gcHJlZGljdChyZjEsIHRlc3RpbmcpCmNtcmYgPSBjb25mdXNpb25NYXRyaXgocmZwcmVkLCB0ZXN0aW5nJGxhYmVsLHBvc2l0aXZlPSIxIikKY21yZgpyb3VuZChjbXJmJGJ5Q2xhc3NbIkYxIl0sIDQpCnRlc3RpbmckcmZwPSByZnByZWQKcm9jX3JmPSByb2MocmVzcG9uc2U9IHRlc3RpbmckbGFiZWwsIHByZWRpY3RvciA9IGZhY3Rvcih0ZXN0aW5nJHJmcCwgb3JkZXJlZD1UUlVFKSwgcGxvdD1UUlVFLCBwcmludC5hdWM9VFJVRSkKYGBgCiogMjc3IG91dCBvZiA0ODYgcG9zaXRpdmUgaW5zdGFuY2VzIHdlcmUgcHJlZGljdGVkIGNvcnJlY3RseSAocmVjYWxsIG9mIDAuNjgyKSB3aXRoIHJhbmRvbSBmb3Jlc3QuIAoKCiMjIyMgWEdCb29zdApYR0Jvb3N0IGNvZGUgcmVmZXJlbmNlOiBbV2hvJ3MgZ29ubmEgY2h1cm4/XShodHRwczovL3d3dy5rYWdnbGUuY29tL3Zpcm9za3kvd2hvLXMtZ29ubmEtY2h1cm4pIGJ5IENhcm1pbmUgTWluaWNoaW5pICAgCgpgYGB7cn0KdGFyZ2V0X2NvbHVtbiA9IGRmMSRsYWJlbApkYXRhID0gIGRmMSAlPiUgc2VsZWN0KC1sYWJlbCkKZG15ID0gZHVtbXlWYXJzKCIgfiAuIiwgZGF0YSA9IGRhdGEpCnRyYWluX2RhdGEgPSBkYXRhLmZyYW1lKHByZWRpY3QoZG15LCBuZXdkYXRhID0gZGF0YSkpCmRhdGEgPC0gY2JpbmQodHJhaW5fZGF0YSx0YXJnZXRfY29sdW1uKQpuYW1lcyhkYXRhKVszM10gPC0gJ2xhYmVsJwpgYGAKCmBgYHtyfQp0cmFpbkluZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0YSRsYWJlbCxwPTAuNzUsbGlzdD1GQUxTRSkKZGF0YV90cmFpbiA8LSBkYXRhW3RyYWluSW5kZXgsXQpkYXRhX3Rlc3QgPC0gIGRhdGFbLXRyYWluSW5kZXgsXQpgYGAKCmBgYHtyfQpncmlkX3RyYWluID0gZGF0YV90cmFpbgpsZXZlbHMoZ3JpZF90cmFpbiRsYWJlbCkgPC0gYygiWDAiLCJYMSIpCiNncmlkIHBhcmFtZXRlcnMKeGdiX2dyaWRfMSA9IGV4cGFuZC5ncmlkKAogICAgbnJvdW5kcyA9IDEwLAogICAgZXRhID0gc2VxKDIsMTAsYnk9MSkvMTAsCiAgICBtYXhfZGVwdGggPSBjKDYsIDgsIDEwKSwKICAgIGdhbW1hID0gMCwKICAgIHN1YnNhbXBsZSA9IGMoMC41LCAwLjc1LCAxKSwKICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsMikgLAogICAgY29sc2FtcGxlX2J5dHJlZSA9IGMoMC4zLDAuNSkKICApCiMgcGFjayB0aGUgdHJhaW5pbmcgY29udHJvbCBwYXJhbWV0ZXJzCiAgeGdiX3RyY29udHJvbF8xID0gdHJhaW5Db250cm9sKAogICAgbWV0aG9kID0gImN2IiwKICAgIG51bWJlciA9IDIsCiAgICBzZWFyY2g9J2dyaWQnLAogICAgdmVyYm9zZUl0ZXIgPSBGQUxTRSwKICAgIHJldHVybkRhdGEgPSBUUlVFLAogICAgcmV0dXJuUmVzYW1wID0gImFsbCIsICMgc2F2ZSBsb3NzZXMgYWNyb3NzIGFsbCBtb2RlbHMKICAgIGNsYXNzUHJvYnMgPSBUUlVFLCAjIHNldCB0byBUUlVFIGZvciBBVUMgdG8gYmUgY29tcHV0ZWQKICAgIHN1bW1hcnlGdW5jdGlvbiA9IHByU3VtbWFyeSwgIyBwcm9iYWJpbGl0eSBzdW1tYXJ5KEFVQykKICAgIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogICkKYGBgCgpgYGB7cn0KeGdiX3RyYWluXzEgPSB0cmFpbigKICAgIHggPSBhcy5tYXRyaXgoZ3JpZF90cmFpbiAlPiUgc2VsZWN0KC1sYWJlbCkpLAogICAgeSA9IGZhY3RvcihncmlkX3RyYWluJGxhYmVsKSwKICAgIHRyQ29udHJvbCA9IHhnYl90cmNvbnRyb2xfMSwKICAgIHR1bmVHcmlkID0geGdiX2dyaWRfMSwKICAgIG1ldGhvZCA9ICJ4Z2JUcmVlIiwKICAgIG1ldHJpYz0gJ1JlY2FsbCcKICApCmBgYAoKYGBge3J9CmJlc3RfdHVuZSA8LSB4Z2JfdHJhaW5fMSRiZXN0VHVuZQpyZXN1bHRzIDwtIHhnYl90cmFpbl8xJHJlc3VsdHMKdHJhaW5lZF9tb2RlbCA8LSB4Z2JfdHJhaW5fMQpjYXQocGFzdGUoIiIsCiAgICAgICAgICAgIHBhc3RlKCdXaXRoIGEgcmVjYWxsIG9mOicscmVzdWx0c1tyb3duYW1lcyhiZXN0X3R1bmUpLCJSZWNhbGwiXSksCiAgICAgICAgICAgICdCZXN0IEdSSURTRUFSQ0ggSHlwZXJwYXJhbWV0ZXJzOicsCiAgICAgICAgICAgICcnLAogICAgICAgICAgICBzZXA9J1xuXG4nKSkKICAKcm93bmFtZXMoYmVzdF90dW5lKSA8LSAnVmFsdWUnCnByaW50KHQoYmVzdF90dW5lKSkKYGBgCmBgYHtyfQojb3V0IGRhdGFmcmFtZQpncmlkcmVzdWx0cyA9IHJlc3VsdHMKYmVzdF90dW5lID0gYmVzdF90dW5lCnRyYWluX2RhdGEgPSBkYXRhX3RyYWluCnRlc3RfZGF0YSA9IGRhdGFfdGVzdApgYGAKCgpgYGB7cn0KI2Jlc3QgaHlwZXJwYXJhbWV0ZXJzIGZyb20gZ3JpZHNlYXJjaApiZXN0X3R1bmUgPC0gYmVzdF90dW5lCiN0cmFpbgpkYXRhX3RyYWluIDwtIHRyYWluX2RhdGEgJT4lIHNlbGVjdCgtbGFiZWwpCmxhYmVsX3RyYWluIDwtIHRyYWluX2RhdGEkbGFiZWwKI3Rlc3QKZGF0YV90ZXN0IDwtIHRlc3RfZGF0YSAlPiUgc2VsZWN0KC1sYWJlbCkKbGFiZWxfdGVzdCA8LSB0ZXN0X2RhdGEkbGFiZWwKIyBhcyBtYXRyaXgKZGF0YV90cmFpbiA8LSBhcy5tYXRyaXgoZGF0YV90cmFpbikKZGF0YV90ZXN0IDwtIGFzLm1hdHJpeChkYXRhX3Rlc3QpCiMgYXMgbnVtZXJpYwpsYWJlbF90cmFpbiA8LSBhcy5udW1lcmljKGxhYmVsX3RyYWluKQpsYWJlbF90ZXN0IDwtIGFzLm51bWVyaWMobGFiZWxfdGVzdCkKI3JlbGV2ZWwKbGFiZWxfdHJhaW49IGlmZWxzZShsYWJlbF90cmFpbj4xLDEsMCkKbGFiZWxfdGVzdD0gaWZlbHNlKGxhYmVsX3Rlc3Q+MSwxLDApCgpgYGAKCgpgYGB7cn0KI1hHQiBtYXRyaXgKZHRyYWluIDwtICB4Z2IuRE1hdHJpeChkYXRhX3RyYWluLGxhYmVsPWxhYmVsX3RyYWluKQpkdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhX3Rlc3QsbGFiZWw9bGFiZWxfdGVzdCkKYGBgCgpgYGB7cn0KI1hHQiBtb2RlbAogbW9kZWwgPC0geGdib29zdChkYXRhPSBkdHJhaW4sIAogICAgICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gImJpbmFyeTpsb2dpc3RpYyIsCiAgICAgICAgICAgICAgICAgICBtYXhfZGVwdGggPSBiZXN0X3R1bmUkbWF4X2RlcHRoLAogICAgICAgICAgICAgICAgICAgbnJvdW5kcz0xMDAsCiAgICAgICAgICAgICAgICAgICBjb2xzYW1wbGVfYnl0cmVlID0gYmVzdF90dW5lJGNvbHNhbXBsZV9ieXRyZWUsCiAgICAgICAgICAgICAgICAgICBnYW1tYSA9IGJlc3RfdHVuZSRnYW1tYSwKICAgICAgICAgICAgICAgICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBiZXN0X3R1bmUkbWluX2NoaWxkX3dlaWdodCwKICAgICAgICAgICAgICAgICAgIGV0YSA9IGJlc3RfdHVuZSRldGEsIAogICAgICAgICAgICAgICAgICAgc3Vic2FtcGxlID0gYmVzdF90dW5lJHN1YnNhbXBsZSwKICAgICAgICAgICAgICAgICAgIHByaW50X2V2ZXJ5X24gPSAyMCwKICAgICAgICAgICAgICAgICAgIHNjYWxlX3Bvc193ZWlnaHQ9NS4yMiwKICAgICAgICAgICAgICAgICAgIG1heF9kZWx0YV9zdGVwPTEsCiAgICAgICAgICAgICAgICAgICBldmFsX21ldHJpYz0nYXVjcHInLAogICAgICAgICAgICAgICAgICAgdmVyYm9zZT0xLAogICAgICAgICAgICAgICAgICAgbnRocmVhZCA9IDQpCgpjdiAgPC0gIHhnYi5jdihkYXRhID0gZHRyYWluLCAKICAgICAgICAgICAgICAgICBucm91bmQgPSA1MCwgCiAgICAgICAgICAgICAgICAgcHJpbnRfZXZlcnlfbj0gMTAsCiAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgbWV0cmljcyA9IGxpc3QoImF1Y3ByIiksCiAgICAgICAgICAgICAgICAgbmZvbGQgPSA1LCAKICAgICAgICAgICAgICAgICBudGhyZWFkID0gNCwKICAgICAgICAgICAgICAgICBvYmplY3RpdmUgPSAiYmluYXJ5OmxvZ2lzdGljIiwKICAgICAgICAgICAgICAgICBwcmVkaWN0aW9uPUYpCgpvdXQgPC0gbGlzdChkYXRhX3RyYWluID0gZGF0YV90cmFpbiwKICAgICAgICAgICAgICBkdGVzdCA9IGR0ZXN0LAogICAgICAgICAgICAgIGxhYmVsX3Rlc3QgPSBsYWJlbF90ZXN0LAogICAgICAgICAgICAgIG1vZGVsID0gbW9kZWwpCmBgYAoKYGBge3J9CmRhdGFfdHJhaW4gPC0gZGF0YV90cmFpbgpwcmVkIDwtIHByZWRpY3QobW9kZWwsZHRlc3QpCnByZWRpY3Rpb24gPC0gYXMubnVtZXJpYyhwcmVkID4gMC41KQpjbSA8LSBjb25mdXNpb25NYXRyaXgoZmFjdG9yKHByZWRpY3Rpb24pLGZhY3RvcihsYWJlbF90ZXN0KSxwb3NpdGl2ZT0iMSIpCmNtCnJvdW5kKGNtJGJ5Q2xhc3NbIkYxIl0sIDQpCnJvYy5jdXJ2ZSA9IHJvYyhyZXNwb25zZSA9IGxhYmVsX3Rlc3QsCiAgICAgICAgICAgICAgICAgIHByZWRpY3RvciA9IHByZWRpY3Rpb24sCiAgICAgICAgICAgICAgICAgIGxldmVscz1jKDAsIDEpLHF1aWV0ID0gVCkgCnBsb3Qocm9jLmN1cnZlLHByaW50LmF1Yz1UUlVFKQpgYGAKICozMzQgb3V0IG9mIDQ4NiBwb3NpdGl2ZSBpbnN0YW5jZXMgYXJlIHByZWRpY3RlZCBjb3JyZWN0bHkgd2l0aCBYR0IgKDAuODIzKQogCmBgYHtyfQppbXBvcnRhbmNlX21hdHJpeCA8LSB4Z2IuaW1wb3J0YW5jZShjb2xuYW1lcyhkYXRhX3RyYWluKSwgbW9kZWwgPSBtb2RlbCkKICB4Z2IucGxvdC5pbXBvcnRhbmNlKGltcG9ydGFuY2VfbWF0cml4LAogICAgICAgICAgICAgICAgICAgICAgdG9wX249MTAsCiAgICAgICAgICAgICAgICAgICAgICBtYWluPSdGZWF0dXJlcyBJbXBvcnRhbmNlJywKICAgICAgICAgICAgICAgICAgICAgIG1lYXN1cmUgPSAnRnJlcXVlbmN5JykKYGBgCgojIyMjIFN1bW1hcnkgb2YgZXhlcmNpc2UgCgp8ICAgICAgICAgICAgICAgICAgICAgfCBSZWNhbGwgfCBBVUMgICB8IEYxIFNjb3JlIHwKfC0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLXwtLS0tLS0tfC0tLS0tLS0tLS18CnwgTG9naXN0aWMgcmVncmVzc2lvbiB8IDAuNzg2ICB8IDAuODMzIHwgMC42NTEgICAgfAp8IERlY2lzaW9uIHRyZWUgICAgICAgfCAwLjYxMyAgfCAwLjc4NiB8IDAuNjcyICAgIHwKfCBSYW5kb20gRm9yZXN0ICAgICAgIHwgMC42ODIgIHwgMC44MyAgfCAwLjc1NyAgICB8CnwgWEdCb29zdCAgICAgICAgICAgICB8IDAuODIzICB8IDAuODc5IHwgMC43NjMgICAgfAoKIAoK