Inicio del proyecto fiabilidad

Ver video de explicación https://youtu.be/T6YHKmvO_yo carageremos los archivos de tarjeta de credito


card_base <- read.csv("CardBase.csv", stringsAsFactors=FALSE)
customer_base <- read.csv("CustomerBase.csv", stringsAsFactors=FALSE)

transaction_base <- read.csv("TransactionBase.csv", stringsAsFactors=FALSE)

revisemos card base

summary(card_base)
 Card_Number        Card_Family         Credit_Limit      Cust_ID         
 Length:500         Length:500         Min.   :  2000   Length:500        
 Class :character   Class :character   1st Qu.: 35000   Class :character  
 Mode  :character   Mode  :character   Median :139000   Mode  :character  
                                       Mean   :252614                     
                                       3rd Qu.:412500                     
                                       Max.   :899000                     

revisemos la base de clientes

summary(customer_base)
   Cust_ID               Age        Customer_Segment   Customer_Vintage_Group
 Length:5674        Min.   :20.00   Length:5674        Length:5674           
 Class :character   1st Qu.:27.00   Class :character   Class :character      
 Mode  :character   Median :35.00   Mode  :character   Mode  :character      
                    Mean   :35.06                                            
                    3rd Qu.:43.00                                            
                    Max.   :50.00                                            

por ultimo veamos la tabla de transacciones

summary(transaction_base)
 Transaction_ID     Transaction_Date   Credit_Card_ID     Transaction_Value Transaction_Segment
 Length:10000       Length:10000       Length:10000       Min.   :  103     Length:10000       
 Class :character   Class :character   Class :character   1st Qu.:12318     Class :character   
 Mode  :character   Mode  :character   Mode  :character   Median :24721     Mode  :character   
                                                          Mean   :24759                        
                                                          3rd Qu.:37036                        
                                                          Max.   :49995                        

como podemos observar necesitamos relacionar los datasets y prepar la relacion de joins

transaction_base<- transaction_base%>%rename(Card_Number = Credit_Card_ID)

full_card <- inner_join(card_base,customer_base , by= c("Cust_ID") )
full_card <- inner_join(full_card,transaction_base , "Card_Number"  ) 

ya hemos armado nuestra tabla, en este momento procedremos a ver la data y la estructura

head(full_card)
summary(full_card)
 Card_Number        Card_Family         Credit_Limit      Cust_ID               Age        Customer_Segment  
 Length:10000       Length:10000       Min.   :  2000   Length:10000       Min.   :20.00   Length:10000      
 Class :character   Class :character   1st Qu.: 35000   Class :character   1st Qu.:28.00   Class :character  
 Mode  :character   Mode  :character   Median :140000   Mode  :character   Median :36.00   Mode  :character  
                                       Mean   :250840                      Mean   :35.57                     
                                       3rd Qu.:406000                      3rd Qu.:43.00                     
                                       Max.   :899000                      Max.   :50.00                     
 Customer_Vintage_Group Transaction_ID     Transaction_Date   Transaction_Value Transaction_Segment
 Length:10000           Length:10000       Length:10000       Min.   :  103     Length:10000       
 Class :character       Class :character   Class :character   1st Qu.:12318     Class :character   
 Mode  :character       Mode  :character   Mode  :character   Median :24721     Mode  :character   
                                                              Mean   :24759                        
                                                              3rd Qu.:37036                        
                                                              Max.   :49995                        

Convertiremos nuestros campos caracters en factores dentro de la nueva tabla

full_card$Card_Family <- as.factor(full_card$Card_Family)
full_card$Customer_Segment <- as.factor(full_card$Customer_Segment)
full_card$Customer_Vintage_Group <- as.factor(full_card$Customer_Vintage_Group)
full_card$Transaction_Segment <- as.factor(full_card$Transaction_Segment)
full_card$fecha=as.Date(full_card$Transaction_Date,format="%d-%b-%y")
full_card$week_day=weekdays(full_card$fecha)
full_card$week_day <- as.factor(full_card$week_day)

Veamos nuestra estructura nueva

str(full_card) 
'data.frame':   10000 obs. of  13 variables:
 $ Card_Number           : chr  "8638-5407-3631-8196" "8638-5407-3631-8196" "8638-5407-3631-8196" "8638-5407-3631-8196" ...
 $ Card_Family           : Factor w/ 3 levels "Gold","Platinum",..: 3 3 3 3 3 3 3 3 3 3 ...
 $ Credit_Limit          : int  530000 530000 530000 530000 530000 530000 530000 530000 530000 530000 ...
 $ Cust_ID               : chr  "CC67088" "CC67088" "CC67088" "CC67088" ...
 $ Age                   : int  27 27 27 27 27 27 27 27 27 27 ...
 $ Customer_Segment      : Factor w/ 3 levels "Diamond","Gold",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ Customer_Vintage_Group: Factor w/ 3 levels "VG1","VG2","VG3": 1 1 1 1 1 1 1 1 1 1 ...
 $ Transaction_ID        : chr  "CTID85973577" "CTID13215166" "CTID41016745" "CTID82512584" ...
 $ Transaction_Date      : chr  "13-Feb-16" "30-Oct-16" "9-May-16" "31-Aug-16" ...
 $ Transaction_Value     : int  31592 5894 20925 36556 36715 30996 43853 45751 16565 19345 ...
 $ Transaction_Segment   : Factor w/ 15 levels "SEG11","SEG12",..: 11 2 1 1 8 5 13 7 11 11 ...
 $ fecha                 : Date, format: "2016-02-13" "2016-10-30" "2016-05-09" "2016-08-31" ...
 $ week_day              : Factor w/ 7 levels "Friday","Monday",..: 3 4 2 7 1 4 4 1 4 6 ...

Procedremos a seleccionar los campos que deseamos para la clasificacion:

Problema: determinar el segmento de cliente dada la transaccion

La idea de este proyecto es determianr Customer_Segment , procedemos a seleccionar unicament elos campos que nos van a server y a limpiar la data

cards<-full_card%>%dplyr::select( Customer_Segment ,Card_Family,Credit_Limit,Age,Transaction_Segment,Transaction_Value,week_day)
ncolmax<-7
set.seed(123)
cards<-cards[complete.cases(cards),]
cards<-cards[sample(nrow(cards), 2000), ]

se extrae al azar 1000 elementos de toda la colección

str(cards) 
'data.frame':   2000 obs. of  7 variables:
 $ Customer_Segment   : Factor w/ 3 levels "Diamond","Gold",..: 1 1 3 1 2 1 1 3 2 1 ...
 $ Card_Family        : Factor w/ 3 levels "Gold","Platinum",..: 3 3 1 3 3 2 2 1 1 3 ...
 $ Credit_Limit       : int  147000 492000 32000 405000 361000 96000 63000 35000 2000 575000 ...
 $ Age                : int  44 35 34 48 35 34 35 48 39 44 ...
 $ Transaction_Segment: Factor w/ 15 levels "SEG11","SEG12",..: 13 2 11 13 2 4 1 8 5 5 ...
 $ Transaction_Value  : int  13128 48453 16656 41754 42618 39382 8951 25688 15360 17744 ...
 $ week_day           : Factor w/ 7 levels "Friday","Monday",..: 7 6 5 3 4 4 7 6 6 2 ...
head(cards)
summary(cards)
 Customer_Segment   Card_Family   Credit_Limit         Age        Transaction_Segment Transaction_Value      week_day  
 Diamond :934     Gold    :716   Min.   :  2000   Min.   :20.00   SEG19  : 157        Min.   :  109     Friday   :300  
 Gold    :635     Platinum:476   1st Qu.: 34000   1st Qu.:28.00   SEG16  : 148        1st Qu.:12487     Monday   :272  
 Platinum:431     Premium :808   Median :135000   Median :36.00   SEG13  : 146        Median :25274     Saturday :307  
                                 Mean   :251012   Mean   :35.81   SEG12  : 143        Mean   :24822     Sunday   :271  
                                 3rd Qu.:412500   3rd Qu.:44.00   SEG14  : 138        3rd Qu.:36758     Thursday :288  
                                 Max.   :899000   Max.   :50.00   SEG20  : 138        Max.   :49983     Tuesday  :270  
                                                                  (Other):1130                          Wednesday:292  

Validación cruzada

Definimos tres vectores uno para el indice del 70% de la muestra y 30% para test

ncolmax<-7

train_index <- createDataPartition(y = cards$Customer_Segment,p=0.70,list = F)
training <- cards[train_index,]
testing  <- cards[-train_index,]

Analisis exploratorio


p.Card_Family <- ggplot(training, aes(Customer_Segment, Card_Family))
p.Card_Family <- p.Card_Family + geom_boxplot() + ggtitle("Card_Family")
p.Credit_Limit <- ggplot(training, aes(Customer_Segment, Credit_Limit))
p.Credit_Limit <- p.Credit_Limit + geom_boxplot() + ggtitle("Credit_Limit")
p.Age <- ggplot(training, aes(Customer_Segment, Age))
p.Age <- p.Age + geom_boxplot() + ggtitle("Age")
p.Customer_Segment <- ggplot(training, aes(Customer_Segment, Customer_Segment))
p.Customer_Segment <- p.Customer_Segment + geom_boxplot() + ggtitle("Customer_Segment")
p.Transaction_Value <- ggplot(training, aes(Customer_Segment, Transaction_Value))
p.Transaction_Value <- p.Transaction_Value + geom_boxplot() + ggtitle("Transaction_Value")
p.week_day <- ggplot(training, aes(Customer_Segment, week_day))
p.week_day <- p.week_day + geom_boxplot() + ggtitle("week_day")

gridExtra::grid.arrange(p.Card_Family, p.Credit_Limit, p.Age, p.Customer_Segment,p.Transaction_Value,p.week_day, ncol = 2, nrow = 4)

Dado que nuestra variable a determinar es una dimencion con mas de una categoria implementaremos un modelo de arboles para determinar el segmento

###Arbol de decicion simple

modFit <- caret::train(Customer_Segment ~ ., method = "rpart", data = training)
rattle::fancyRpartPlot(modFit$finalModel)

verificaremos nuestra prediccion y la matriz de confusión

predictions <- predict(modFit, newdata = testing[,c(2:ncolmax,1)])
confusionMatrix(predictions, testing$Customer_Segment)
Confusion Matrix and Statistics

          Reference
Prediction Diamond Gold Platinum
  Diamond      246  120      100
  Gold          34   70       29
  Platinum       0    0        0

Overall Statistics
                                          
               Accuracy : 0.5275          
                 95% CI : (0.4867, 0.5681)
    No Information Rate : 0.4674          
    P-Value [Acc > NIR] : 0.001845        
                                          
                  Kappa : 0.1652          
                                          
 Mcnemar's Test P-Value : < 2.2e-16       

Statistics by Class:

                     Class: Diamond Class: Gold Class: Platinum
Sensitivity                  0.8786      0.3684          0.0000
Specificity                  0.3103      0.8460          1.0000
Pos Pred Value               0.5279      0.5263             NaN
Neg Pred Value               0.7444      0.7425          0.7846
Prevalence                   0.4674      0.3172          0.2154
Detection Rate               0.4107      0.1169          0.0000
Detection Prevalence         0.7780      0.2220          0.0000
Balanced Accuracy            0.5945      0.6072          0.5000

como podemos ver en la siguiente tabla, se detalla un Accuracy de casi el 41%, al verificar el Mean de nuestro prediction vemos que tambien igual

mean(predictions==testing$Customer_Segment)
[1] 0.5275459

Ahora bien verificaremos nuevamente el modelo haciendo uso de RPART para poder generar el scalfolding function Utilizando un metodo de anova calcularemos el arbol de forma manual

modFit_rpat <- rpart(formula = Customer_Segment ~Card_Family+Credit_Limit+Age+Transaction_Segment+Transaction_Value+week_day  
                     , data = training, method = "anova" )

rattle::fancyRpartPlot(modFit_rpat)

Verificaremos el resultado del arbol

modFit_rpat <- rpart(formula = Customer_Segment ~Card_Family+Credit_Limit+Age+Transaction_Segment+Transaction_Value+week_day  
                     , data = training )

Procedmos a calcular nuestras prediccions de tipo clase

predictions_rpat <- predict(modFit_rpat, newdata = testing,type="class")

table(predicho=predictions_rpat, real=testing$Customer_Segment)
          real
predicho   Diamond Gold Platinum
  Diamond      246  126       99
  Gold          21   57       23
  Platinum      13    7        7
error_clas <- mean(predictions_rpat != testing$Customer_Segment)

paste("El error de clasificación es del:", 100 * error_clas, "%.", 
      sum(predictions_rpat == testing$Customer_Segment),
      "clasificaciones correctas de un total de", length(predictions_rpat))
[1] "El error de clasificación es del: 48.2470784641068 %. 310 clasificaciones correctas de un total de 599"

como podemos ver bajo esta variación del arbol la exactitud decae a un 45% Procederemos a ejecutar un bosque de arboles para determinar el mejor modelo para esto utilizaremos la funcion randomForest

modFit.rf <- randomForest::randomForest(Customer_Segment ~ ., data = cards, )
modFit.rf

Call:
 randomForest(formula = Customer_Segment ~ ., data = cards, ) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 2

        OOB estimate of  error rate: 40.1%
Confusion matrix:
         Diamond Gold Platinum class.error
Diamond      770  119       45   0.1755889
Gold         304  300       31   0.5275591
Platinum     231   72      128   0.7030162

evaluaremos el resultado del bosque generado

predictions.rf <- predict(modFit.rf, newdata = testing[,c(1:ncolmax)])
confusionMatrix(predictions.rf, testing$Customer_Segment)
Confusion Matrix and Statistics

          Reference
Prediction Diamond Gold Platinum
  Diamond      280    0        0
  Gold           0  190        0
  Platinum       0    0      129

Overall Statistics
                                     
               Accuracy : 1          
                 95% CI : (0.9939, 1)
    No Information Rate : 0.4674     
    P-Value [Acc > NIR] : < 2.2e-16  
                                     
                  Kappa : 1          
                                     
 Mcnemar's Test P-Value : NA         

Statistics by Class:

                     Class: Diamond Class: Gold Class: Platinum
Sensitivity                  1.0000      1.0000          1.0000
Specificity                  1.0000      1.0000          1.0000
Pos Pred Value               1.0000      1.0000          1.0000
Neg Pred Value               1.0000      1.0000          1.0000
Prevalence                   0.4674      0.3172          0.2154
Detection Rate               0.4674      0.3172          0.2154
Detection Prevalence         0.4674      0.3172          0.2154
Balanced Accuracy            1.0000      1.0000          1.0000

graficaremos el confjunto de errores en la iteracion de los arboles

plot(modFit.rf, main = "Razon del random de arboles")

Como podemos obervar en la tabla y en la grafica el mejor modelo reprecenta una exactitud del 83% la importancia de este valor corresponde a la tabla de variables y su aporte

varImpPlot(modFit.rf, pch = 20, main = "aporte de  Variables para el bosque")

##Kfold para modelo de arbol Ahora procederemos a realizar el kfold sobre todos nuestros posibles modelos

Iniciamos determinado la funcion de calculo de metrica dado un conjunto de features, tomemos encuenta que el error ahora es calculado

get_metric_part <- function(features,dataset,label){
  formulat <- paste0(features, collapse="+")
  
  formulat <- paste0(label,"~",formulat,
                    collapse = "")
  
  
  
  fit<-       rpart(formula=formulat, data = dataset)  
  
  

  test_pred <- predict(fit, newdata = dataset,type="class")
  se <-mean(test_pred!=dataset[,label])
  print( paste0(formulat," ERR=",se))
  return(se*se)
}

Definimos nuestra funcion de segmentar n-k capas

fit_nk_part <- function(k,dataset,label){
  features <- colnames(dataset)
  features <- setdiff(features , label)
  features_subset<- combn(features,k,simplify = FALSE)
  
   metric <- lapply(features_subset,
                    get_metric_part,
                   dataset=dataset,
                   label=label)
   
   index<-
    which.min(metric %>% unlist() )
   
   print( ".... Mejor Modelo (Respuesta) ....")
   
   
  return(features_subset[[index]])

  
} 

Verificamos el posible resultado. el sistema debe de retornar la menor error el listado que se detalla corresponde a todos los posibles modelso que ha iterado

fit_nk_part(5,training,"Customer_Segment")
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment+Transaction_Value ERR=0.443254817987152"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment+week_day ERR=0.443254817987152"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Value+week_day ERR=0.471092077087794"
[1] "Customer_Segment~Card_Family+Credit_Limit+Transaction_Segment+Transaction_Value+week_day ERR=0.483226266952177"
[1] "Customer_Segment~Card_Family+Age+Transaction_Segment+Transaction_Value+week_day ERR=0.494646680942184"
[1] "Customer_Segment~Credit_Limit+Age+Transaction_Segment+Transaction_Value+week_day ERR=0.443254817987152"
[1] ".... Mejor Modelo (Respuesta) ...."
[1] "Card_Family"         "Credit_Limit"        "Age"                 "Transaction_Segment" "Transaction_Value"  

Como podemos ver ya tenemos la respuesta de la evaluacion de cada modelo

Quitaremos los logs de las funciones

get_metric_part <- function(features,dataset,label){
  formulat <- paste0(features, collapse="+")
  
  formulat <- paste0(label,"~",formulat,
                    collapse = "")
  
  
  
  fit<-       rpart(formula=formulat, data = dataset)  
  
  

  test_pred <- predict(fit, newdata = dataset,type="class")
  se <-mean(test_pred!=dataset[,label])
  
  return(se*se)
}

Definimos nuestra funcion de segmentar n-k capas

fit_nk_part <- function(k,dataset,label){
  features <- colnames(dataset)
  features <- setdiff(features , label)
  features_subset<- combn(features,k,simplify = FALSE)
  
   metric <- lapply(features_subset,
                    get_metric_part,
                   dataset=dataset,
                   label=label)
   
   index<-
    which.min(metric %>% unlist() )
   
  
   
   
  return(features_subset[[index]])

  
} 

calcularemos la lista de los mejores modelos a testear con un maximo de 6 posibles mejores modelos para predecir

n_features <- ncol(training)-1

list_models<-
  lapply(1:n_features,
         fit_nk_part,
         dataset=training,
         label = 'Customer_Segment')
list_models
[[1]]
[1] "Credit_Limit"

[[2]]
[1] "Credit_Limit" "Age"         

[[3]]
[1] "Credit_Limit"        "Age"                 "Transaction_Segment"

[[4]]
[1] "Card_Family"         "Credit_Limit"        "Age"                 "Transaction_Segment"

[[5]]
[1] "Card_Family"         "Credit_Limit"        "Age"                 "Transaction_Segment" "Transaction_Value"  

[[6]]
[1] "Card_Family"         "Credit_Limit"        "Age"                 "Transaction_Segment" "Transaction_Value"  
[6] "week_day"           

acontinuacion definiremos la fuccion que escanea y calcula el train y test para cada modelo a testear

loocv <- function(index, dataset, label, features){
  train <- dataset[-index,]
  test <- dataset[index,]
  formulat <- paste0(label, "~", paste0(features, collapse="+"), collapse = "")
  
  fit<-        rpart(formula=formulat, data = train )  
  
  
  test_pred <- predict(fit, test,type="class")
  se <-mean(test_pred!=test[,label])
  
  
  resprint<- paste(formulat, " Err:", se, " correctos:", sum(test_pred == test$Customer_Segment),
      " de", length(test_pred))
  
  print(resprint)
  return(se*se)
}

Definimos la funcion fold de neustros modelos en los que hacemos test de cada segmento que nos pueda calcular carret

folds <- function(k, dataset, label, features){
  se_folds_all <- lapply(createFolds(dataset[,label], k = k, list = TRUE, returnTrain = FALSE), 
                         loocv, 
                         label=label, 
                         dataset=dataset,
                         features=features)
  return(mean(se_folds_all %>% unlist()))
}

Aplicaremos un Lapply a la lista de modelos para nuestra funcion de fold en k segmentos de 4

test <- lapply(list_models,
               folds,
               k=6,
               dataset=cards,
               label="Customer_Segment"
)
[1] "Customer_Segment~Credit_Limit  Err: 0.486486486486487  correctos: 171  de 333"
[1] "Customer_Segment~Credit_Limit  Err: 0.477477477477477  correctos: 174  de 333"
[1] "Customer_Segment~Credit_Limit  Err: 0.471471471471471  correctos: 176  de 333"
[1] "Customer_Segment~Credit_Limit  Err: 0.523952095808383  correctos: 159  de 334"
[1] "Customer_Segment~Credit_Limit  Err: 0.514970059880239  correctos: 162  de 334"
[1] "Customer_Segment~Credit_Limit  Err: 0.510510510510511  correctos: 163  de 333"
[1] "Customer_Segment~Credit_Limit+Age  Err: 0.525525525525526  correctos: 158  de 333"
[1] "Customer_Segment~Credit_Limit+Age  Err: 0.483483483483483  correctos: 172  de 333"
[1] "Customer_Segment~Credit_Limit+Age  Err: 0.513513513513513  correctos: 162  de 333"
[1] "Customer_Segment~Credit_Limit+Age  Err: 0.473053892215569  correctos: 176  de 334"
[1] "Customer_Segment~Credit_Limit+Age  Err: 0.491017964071856  correctos: 170  de 334"
[1] "Customer_Segment~Credit_Limit+Age  Err: 0.516516516516517  correctos: 161  de 333"
[1] "Customer_Segment~Credit_Limit+Age+Transaction_Segment  Err: 0.539156626506024  correctos: 153  de 332"
[1] "Customer_Segment~Credit_Limit+Age+Transaction_Segment  Err: 0.493975903614458  correctos: 168  de 332"
[1] "Customer_Segment~Credit_Limit+Age+Transaction_Segment  Err: 0.497005988023952  correctos: 168  de 334"
[1] "Customer_Segment~Credit_Limit+Age+Transaction_Segment  Err: 0.523952095808383  correctos: 159  de 334"
[1] "Customer_Segment~Credit_Limit+Age+Transaction_Segment  Err: 0.467065868263473  correctos: 178  de 334"
[1] "Customer_Segment~Credit_Limit+Age+Transaction_Segment  Err: 0.502994011976048  correctos: 166  de 334"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment  Err: 0.507507507507508  correctos: 164  de 333"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment  Err: 0.48948948948949  correctos: 170  de 333"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment  Err: 0.476047904191617  correctos: 175  de 334"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment  Err: 0.502994011976048  correctos: 166  de 334"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment  Err: 0.452095808383234  correctos: 183  de 334"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment  Err: 0.521084337349398  correctos: 159  de 332"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment+Transaction_Value  Err: 0.5  correctos: 167  de 334"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment+Transaction_Value  Err: 0.495495495495495  correctos: 168  de 333"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment+Transaction_Value  Err: 0.487951807228916  correctos: 170  de 332"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment+Transaction_Value  Err: 0.505988023952096  correctos: 165  de 334"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment+Transaction_Value  Err: 0.504504504504504  correctos: 165  de 333"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment+Transaction_Value  Err: 0.508982035928144  correctos: 164  de 334"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment+Transaction_Value+week_day  Err: 0.495495495495495  correctos: 168  de 333"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment+Transaction_Value+week_day  Err: 0.451807228915663  correctos: 182  de 332"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment+Transaction_Value+week_day  Err: 0.531531531531532  correctos: 156  de 333"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment+Transaction_Value+week_day  Err: 0.538922155688623  correctos: 154  de 334"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment+Transaction_Value+week_day  Err: 0.467065868263473  correctos: 178  de 334"
[1] "Customer_Segment~Card_Family+Credit_Limit+Age+Transaction_Segment+Transaction_Value+week_day  Err: 0.494011976047904  correctos: 169  de 334"
print(paste("index:",which.min(test)))
[1] "index: 4"
list_models[which.min(test)]
[[1]]
[1] "Card_Family"         "Credit_Limit"        "Age"                 "Transaction_Segment"

Seleccionamos al que menor error tenga listado de indices con error:

test
[[1]]
[1] 0.24788

[[2]]
[1] 0.2508829

[[3]]
[1] 0.2545661

[[4]]
[1] 0.242118

[[5]]
[1] 0.2505374

[[6]]
[1] 0.2474678

list_models
[[1]]
[1] "Credit_Limit"

[[2]]
[1] "Credit_Limit" "Age"         

[[3]]
[1] "Credit_Limit"        "Age"                 "Transaction_Segment"

[[4]]
[1] "Card_Family"         "Credit_Limit"        "Age"                 "Transaction_Segment"

[[5]]
[1] "Card_Family"         "Credit_Limit"        "Age"                 "Transaction_Segment" "Transaction_Value"  

[[6]]
[1] "Card_Family"         "Credit_Limit"        "Age"                 "Transaction_Segment" "Transaction_Value"  
[6] "week_day"           
modFit.rf <- randomForest::randomForest(Customer_Segment ~ Card_Family+Credit_Limit+Age+Transaction_Segment, data = cards )
modFit.rf

Call:
 randomForest(formula = Customer_Segment ~ Card_Family + Credit_Limit +      Age + Transaction_Segment, data = cards) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 2

        OOB estimate of  error rate: 24.35%
Confusion matrix:
         Diamond Gold Platinum class.error
Diamond      795  102       37   0.1488223
Gold         157  451       27   0.2897638
Platinum     111   53      267   0.3805104

evaluaremos el resultado del bosque generado

predictions.rf <- predict(modFit.rf, newdata = testing[,c(1:ncolmax)])
confusionMatrix(predictions.rf, testing$Customer_Segment)
Confusion Matrix and Statistics

          Reference
Prediction Diamond Gold Platinum
  Diamond      279    0        2
  Gold           1  190        0
  Platinum       0    0      127

Overall Statistics
                                         
               Accuracy : 0.995          
                 95% CI : (0.9854, 0.999)
    No Information Rate : 0.4674         
    P-Value [Acc > NIR] : < 2.2e-16      
                                         
                  Kappa : 0.9921         
                                         
 Mcnemar's Test P-Value : NA             

Statistics by Class:

                     Class: Diamond Class: Gold Class: Platinum
Sensitivity                  0.9964      1.0000          0.9845
Specificity                  0.9937      0.9976          1.0000
Pos Pred Value               0.9929      0.9948          1.0000
Neg Pred Value               0.9969      1.0000          0.9958
Prevalence                   0.4674      0.3172          0.2154
Detection Rate               0.4658      0.3172          0.2120
Detection Prevalence         0.4691      0.3189          0.2120
Balanced Accuracy            0.9951      0.9988          0.9922
LS0tDQp0aXRsZTogIkZpYWJpbGlkYWQgcHJvYmxlbWEgZGUgY2xhc2lmaWNhY2lvbiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCkluaWNpbyBkZWwgcHJveWVjdG8gZmlhYmlsaWRhZCANCg0KYGBge3IgZWNobz1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoZmFjdG9leHRyYSkNCmxpYnJhcnkoTUFTUykNCmxpYnJhcnkoY2xhc3MpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KElTTFIpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkocHN5Y2gpDQpsaWJyYXJ5KEdHYWxseSkNCmxpYnJhcnkocnBhcnQpDQpsaWJyYXJ5KHJwYXJ0LnBsb3QpDQpsaWJyYXJ5KHJhdHRsZSkNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KYGBgDQogDQpWZXIgdmlkZW8gZGUgZXhwbGljYWNp824gW2h0dHBzOi8veW91dHUuYmUvVDZZSEttdk9feW9dKGh0dHBzOi8veW91dHUuYmUvVDZZSEttdk9feW8pDQpjYXJhZ2VyZW1vcyBsb3MgYXJjaGl2b3MgZGUgdGFyamV0YSBkZSBjcmVkaXRvDQpgYGB7cn0NCg0KY2FyZF9iYXNlIDwtIHJlYWQuY3N2KCJDYXJkQmFzZS5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQ0KY3VzdG9tZXJfYmFzZSA8LSByZWFkLmNzdigiQ3VzdG9tZXJCYXNlLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpDQoNCnRyYW5zYWN0aW9uX2Jhc2UgPC0gcmVhZC5jc3YoIlRyYW5zYWN0aW9uQmFzZS5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQ0KYGBgDQpyZXZpc2Vtb3MgY2FyZCBiYXNlDQpgYGB7cn0NCnN1bW1hcnkoY2FyZF9iYXNlKQ0KYGBgDQpyZXZpc2Vtb3MgbGEgYmFzZSBkZSBjbGllbnRlcw0KYGBge3J9DQpzdW1tYXJ5KGN1c3RvbWVyX2Jhc2UpDQpgYGANCg0KDQpwb3IgdWx0aW1vIHZlYW1vcyBsYSB0YWJsYSBkZSB0cmFuc2FjY2lvbmVzDQpgYGB7cn0NCnN1bW1hcnkodHJhbnNhY3Rpb25fYmFzZSkNCmBgYA0KY29tbyBwb2RlbW9zIG9ic2VydmFyIG5lY2VzaXRhbW9zIHJlbGFjaW9uYXIgbG9zIGRhdGFzZXRzIHkgcHJlcGFyIGxhIHJlbGFjaW9uIGRlIGpvaW5zIA0KYGBge3J9DQp0cmFuc2FjdGlvbl9iYXNlPC0gdHJhbnNhY3Rpb25fYmFzZSU+JXJlbmFtZShDYXJkX051bWJlciA9IENyZWRpdF9DYXJkX0lEKQ0KDQpmdWxsX2NhcmQgPC0gaW5uZXJfam9pbihjYXJkX2Jhc2UsY3VzdG9tZXJfYmFzZSAsIGJ5PSBjKCJDdXN0X0lEIikgKQ0KZnVsbF9jYXJkIDwtIGlubmVyX2pvaW4oZnVsbF9jYXJkLHRyYW5zYWN0aW9uX2Jhc2UgLCAiQ2FyZF9OdW1iZXIiICApIA0KYGBgDQp5YSBoZW1vcyBhcm1hZG8gbnVlc3RyYSB0YWJsYSwgZW4gZXN0ZSBtb21lbnRvIHByb2NlZHJlbW9zIGEgdmVyIGxhIGRhdGEgeSBsYSBlc3RydWN0dXJhDQpgYGB7cn0NCmhlYWQoZnVsbF9jYXJkKQ0KYGBgDQpgYGB7cn0NCnN1bW1hcnkoZnVsbF9jYXJkKQ0KYGBgDQpDb252ZXJ0aXJlbW9zIG51ZXN0cm9zIGNhbXBvcyBjYXJhY3RlcnMgZW4gZmFjdG9yZXMgZGVudHJvIGRlIGxhIG51ZXZhIHRhYmxhIA0KYGBge3J9DQpmdWxsX2NhcmQkQ2FyZF9GYW1pbHkgPC0gYXMuZmFjdG9yKGZ1bGxfY2FyZCRDYXJkX0ZhbWlseSkNCmZ1bGxfY2FyZCRDdXN0b21lcl9TZWdtZW50IDwtIGFzLmZhY3RvcihmdWxsX2NhcmQkQ3VzdG9tZXJfU2VnbWVudCkNCmZ1bGxfY2FyZCRDdXN0b21lcl9WaW50YWdlX0dyb3VwIDwtIGFzLmZhY3RvcihmdWxsX2NhcmQkQ3VzdG9tZXJfVmludGFnZV9Hcm91cCkNCmZ1bGxfY2FyZCRUcmFuc2FjdGlvbl9TZWdtZW50IDwtIGFzLmZhY3RvcihmdWxsX2NhcmQkVHJhbnNhY3Rpb25fU2VnbWVudCkNCmZ1bGxfY2FyZCRmZWNoYT1hcy5EYXRlKGZ1bGxfY2FyZCRUcmFuc2FjdGlvbl9EYXRlLGZvcm1hdD0iJWQtJWItJXkiKQ0KZnVsbF9jYXJkJHdlZWtfZGF5PXdlZWtkYXlzKGZ1bGxfY2FyZCRmZWNoYSkNCmZ1bGxfY2FyZCR3ZWVrX2RheSA8LSBhcy5mYWN0b3IoZnVsbF9jYXJkJHdlZWtfZGF5KQ0KYGBgDQpWZWFtb3MgbnVlc3RyYSBlc3RydWN0dXJhIG51ZXZhIA0KDQpgYGB7cn0NCnN0cihmdWxsX2NhcmQpIA0KDQoNCmBgYA0KDQpQcm9jZWRyZW1vcyBhIHNlbGVjY2lvbmFyIGxvcyBjYW1wb3MgcXVlIGRlc2VhbW9zIHBhcmEgbGEgY2xhc2lmaWNhY2lvbjoNCg0KIyBQcm9ibGVtYTogZGV0ZXJtaW5hciBlbCBzZWdtZW50byBkZSBjbGllbnRlIGRhZGEgbGEgdHJhbnNhY2Npb24NCkxhIGlkZWEgZGUgZXN0ZSBwcm95ZWN0byBlcyBkZXRlcm1pYW5yIEN1c3RvbWVyX1NlZ21lbnQgLCBwcm9jZWRlbW9zIGEgc2VsZWNjaW9uYXIgdW5pY2FtZW50IGVsb3MgY2FtcG9zIHF1ZSBub3MgdmFuIGEgc2VydmVyDQp5IGEgbGltcGlhciBsYSBkYXRhDQoNCmBgYHtyfQ0KY2FyZHM8LWZ1bGxfY2FyZCU+JWRwbHlyOjpzZWxlY3QoIEN1c3RvbWVyX1NlZ21lbnQgLENhcmRfRmFtaWx5LENyZWRpdF9MaW1pdCxBZ2UsVHJhbnNhY3Rpb25fU2VnbWVudCxUcmFuc2FjdGlvbl9WYWx1ZSx3ZWVrX2RheSkNCm5jb2xtYXg8LTcNCnNldC5zZWVkKDEyMykNCmNhcmRzPC1jYXJkc1tjb21wbGV0ZS5jYXNlcyhjYXJkcyksXQ0KY2FyZHM8LWNhcmRzW3NhbXBsZShucm93KGNhcmRzKSwgMjAwMCksIF0NCg0KYGBgDQpzZSBleHRyYWUgYWwgYXphciAxMDAwIGVsZW1lbnRvcyBkZSB0b2RhIGxhIGNvbGVjY2nzbg0KYGBge3J9DQpzdHIoY2FyZHMpIA0KYGBgDQoNCg0KYGBge3J9DQpoZWFkKGNhcmRzKQ0KYGBgDQpgYGB7cn0NCnN1bW1hcnkoY2FyZHMpDQpgYGANCg0KDQoNCiMjIFZhbGlkYWNp824gY3J1emFkYQ0KRGVmaW5pbW9zIHRyZXMgdmVjdG9yZXMgdW5vIHBhcmEgZWwgaW5kaWNlIGRlbCA3MCUgZGUgbGEgbXVlc3RyYSB5IDMwJSBwYXJhIHRlc3QNCmBgYHtyfQ0KbmNvbG1heDwtNw0KDQp0cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHkgPSBjYXJkcyRDdXN0b21lcl9TZWdtZW50LHA9MC43MCxsaXN0ID0gRikNCnRyYWluaW5nIDwtIGNhcmRzW3RyYWluX2luZGV4LF0NCnRlc3RpbmcgIDwtIGNhcmRzWy10cmFpbl9pbmRleCxdDQpgYGANCg0KIyMgQW5hbGlzaXMgZXhwbG9yYXRvcmlvDQpgYGB7cn0NCg0KcC5DYXJkX0ZhbWlseSA8LSBnZ3Bsb3QodHJhaW5pbmcsIGFlcyhDdXN0b21lcl9TZWdtZW50LCBDYXJkX0ZhbWlseSkpDQpwLkNhcmRfRmFtaWx5IDwtIHAuQ2FyZF9GYW1pbHkgKyBnZW9tX2JveHBsb3QoKSArIGdndGl0bGUoIkNhcmRfRmFtaWx5IikNCnAuQ3JlZGl0X0xpbWl0IDwtIGdncGxvdCh0cmFpbmluZywgYWVzKEN1c3RvbWVyX1NlZ21lbnQsIENyZWRpdF9MaW1pdCkpDQpwLkNyZWRpdF9MaW1pdCA8LSBwLkNyZWRpdF9MaW1pdCArIGdlb21fYm94cGxvdCgpICsgZ2d0aXRsZSgiQ3JlZGl0X0xpbWl0IikNCnAuQWdlIDwtIGdncGxvdCh0cmFpbmluZywgYWVzKEN1c3RvbWVyX1NlZ21lbnQsIEFnZSkpDQpwLkFnZSA8LSBwLkFnZSArIGdlb21fYm94cGxvdCgpICsgZ2d0aXRsZSgiQWdlIikNCnAuQ3VzdG9tZXJfU2VnbWVudCA8LSBnZ3Bsb3QodHJhaW5pbmcsIGFlcyhDdXN0b21lcl9TZWdtZW50LCBDdXN0b21lcl9TZWdtZW50KSkNCnAuQ3VzdG9tZXJfU2VnbWVudCA8LSBwLkN1c3RvbWVyX1NlZ21lbnQgKyBnZW9tX2JveHBsb3QoKSArIGdndGl0bGUoIkN1c3RvbWVyX1NlZ21lbnQiKQ0KcC5UcmFuc2FjdGlvbl9WYWx1ZSA8LSBnZ3Bsb3QodHJhaW5pbmcsIGFlcyhDdXN0b21lcl9TZWdtZW50LCBUcmFuc2FjdGlvbl9WYWx1ZSkpDQpwLlRyYW5zYWN0aW9uX1ZhbHVlIDwtIHAuVHJhbnNhY3Rpb25fVmFsdWUgKyBnZW9tX2JveHBsb3QoKSArIGdndGl0bGUoIlRyYW5zYWN0aW9uX1ZhbHVlIikNCnAud2Vla19kYXkgPC0gZ2dwbG90KHRyYWluaW5nLCBhZXMoQ3VzdG9tZXJfU2VnbWVudCwgd2Vla19kYXkpKQ0KcC53ZWVrX2RheSA8LSBwLndlZWtfZGF5ICsgZ2VvbV9ib3hwbG90KCkgKyBnZ3RpdGxlKCJ3ZWVrX2RheSIpDQoNCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHAuQ2FyZF9GYW1pbHksIHAuQ3JlZGl0X0xpbWl0LCBwLkFnZSwgcC5DdXN0b21lcl9TZWdtZW50LHAuVHJhbnNhY3Rpb25fVmFsdWUscC53ZWVrX2RheSwgbmNvbCA9IDIsIG5yb3cgPSA0KQ0KYGBgDQoNCkRhZG8gcXVlIG51ZXN0cmEgdmFyaWFibGUgYSBkZXRlcm1pbmFyIGVzIHVuYSBkaW1lbmNpb24gY29uIG1hcyBkZSB1bmEgY2F0ZWdvcmlhIGltcGxlbWVudGFyZW1vcyB1biBtb2RlbG8gZGUgYXJib2xlcyBwYXJhIGRldGVybWluYXIgZWwgc2VnbWVudG8NCg0KIyMjQXJib2wgZGUgZGVjaWNpb24gc2ltcGxlDQpgYGB7cn0NCm1vZEZpdCA8LSBjYXJldDo6dHJhaW4oQ3VzdG9tZXJfU2VnbWVudCB+IC4sIG1ldGhvZCA9ICJycGFydCIsIGRhdGEgPSB0cmFpbmluZykNCnJhdHRsZTo6ZmFuY3lScGFydFBsb3QobW9kRml0JGZpbmFsTW9kZWwpDQpgYGANCg0KdmVyaWZpY2FyZW1vcyBudWVzdHJhIHByZWRpY2Npb24geSBsYSBtYXRyaXogZGUgY29uZnVzafNuDQpgYGB7cn0NCnByZWRpY3Rpb25zIDwtIHByZWRpY3QobW9kRml0LCBuZXdkYXRhID0gdGVzdGluZ1ssYygyOm5jb2xtYXgsMSldKQ0KY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zLCB0ZXN0aW5nJEN1c3RvbWVyX1NlZ21lbnQpDQpgYGANCg0KY29tbyBwb2RlbW9zIHZlciBlbiBsYSBzaWd1aWVudGUgdGFibGEsIHNlIGRldGFsbGEgdW4gQWNjdXJhY3kgZGUgY2FzaSBlbCA0MSUsIGFsIHZlcmlmaWNhciBlbCBNZWFuIGRlIG51ZXN0cm8gcHJlZGljdGlvbg0KdmVtb3MgcXVlIHRhbWJpZW4gaWd1YWwNCmBgYHtyfQ0KbWVhbihwcmVkaWN0aW9ucz09dGVzdGluZyRDdXN0b21lcl9TZWdtZW50KQ0KYGBgDQoNCkFob3JhIGJpZW4gdmVyaWZpY2FyZW1vcyBudWV2YW1lbnRlIGVsIG1vZGVsbyBoYWNpZW5kbyB1c28gZGUgUlBBUlQgcGFyYSBwb2RlciBnZW5lcmFyIGVsIHNjYWxmb2xkaW5nIGZ1bmN0aW9uDQpVdGlsaXphbmRvIHVuIG1ldG9kbyBkZSBhbm92YSBjYWxjdWxhcmVtb3MgZWwgYXJib2wgZGUgZm9ybWEgbWFudWFsDQpgYGB7cn0NCm1vZEZpdF9ycGF0IDwtIHJwYXJ0KGZvcm11bGEgPSBDdXN0b21lcl9TZWdtZW50IH5DYXJkX0ZhbWlseStDcmVkaXRfTGltaXQrQWdlK1RyYW5zYWN0aW9uX1NlZ21lbnQrVHJhbnNhY3Rpb25fVmFsdWUrd2Vla19kYXkgIA0KICAgICAgICAgICAgICAgICAgICAgLCBkYXRhID0gdHJhaW5pbmcsIG1ldGhvZCA9ICJhbm92YSIgKQ0KDQpyYXR0bGU6OmZhbmN5UnBhcnRQbG90KG1vZEZpdF9ycGF0KQ0KYGBgDQpWZXJpZmljYXJlbW9zIGVsIHJlc3VsdGFkbyBkZWwgYXJib2wNCmBgYHtyfQ0KbW9kRml0X3JwYXQgPC0gcnBhcnQoZm9ybXVsYSA9IEN1c3RvbWVyX1NlZ21lbnQgfkNhcmRfRmFtaWx5K0NyZWRpdF9MaW1pdCtBZ2UrVHJhbnNhY3Rpb25fU2VnbWVudCtUcmFuc2FjdGlvbl9WYWx1ZSt3ZWVrX2RheSAgDQogICAgICAgICAgICAgICAgICAgICAsIGRhdGEgPSB0cmFpbmluZyApDQoNCg0KYGBgDQoNCg0KUHJvY2VkbW9zIGEgY2FsY3VsYXIgbnVlc3RyYXMgcHJlZGljY2lvbnMgZGUgdGlwbyBjbGFzZSANCmBgYHtyfQ0KcHJlZGljdGlvbnNfcnBhdCA8LSBwcmVkaWN0KG1vZEZpdF9ycGF0LCBuZXdkYXRhID0gdGVzdGluZyx0eXBlPSJjbGFzcyIpDQoNCnRhYmxlKHByZWRpY2hvPXByZWRpY3Rpb25zX3JwYXQsIHJlYWw9dGVzdGluZyRDdXN0b21lcl9TZWdtZW50KQ0KYGBgDQoNCg0KYGBge3J9DQplcnJvcl9jbGFzIDwtIG1lYW4ocHJlZGljdGlvbnNfcnBhdCAhPSB0ZXN0aW5nJEN1c3RvbWVyX1NlZ21lbnQpDQoNCnBhc3RlKCJFbCBlcnJvciBkZSBjbGFzaWZpY2FjafNuIGVzIGRlbDoiLCAxMDAgKiBlcnJvcl9jbGFzLCAiJS4iLCANCiAgICAgIHN1bShwcmVkaWN0aW9uc19ycGF0ID09IHRlc3RpbmckQ3VzdG9tZXJfU2VnbWVudCksDQogICAgICAiY2xhc2lmaWNhY2lvbmVzIGNvcnJlY3RhcyBkZSB1biB0b3RhbCBkZSIsIGxlbmd0aChwcmVkaWN0aW9uc19ycGF0KSkNCmBgYA0KDQpjb21vIHBvZGVtb3MgdmVyIGJham8gZXN0YSB2YXJpYWNp824gZGVsIGFyYm9sIGxhIGV4YWN0aXR1ZCBkZWNhZSBhIHVuIDQ1JQ0KUHJvY2VkZXJlbW9zIGEgZWplY3V0YXIgdW4gYm9zcXVlIGRlIGFyYm9sZXMgcGFyYSBkZXRlcm1pbmFyIGVsIG1lam9yIG1vZGVsbw0KcGFyYSBlc3RvIHV0aWxpemFyZW1vcyBsYSBmdW5jaW9uIHJhbmRvbUZvcmVzdA0KYGBge3J9DQptb2RGaXQucmYgPC0gcmFuZG9tRm9yZXN0OjpyYW5kb21Gb3Jlc3QoQ3VzdG9tZXJfU2VnbWVudCB+IC4sIGRhdGEgPSBjYXJkcywgKQ0KbW9kRml0LnJmDQpgYGANCmV2YWx1YXJlbW9zIGVsIHJlc3VsdGFkbyBkZWwgYm9zcXVlIGdlbmVyYWRvDQpgYGB7cn0NCnByZWRpY3Rpb25zLnJmIDwtIHByZWRpY3QobW9kRml0LnJmLCBuZXdkYXRhID0gdGVzdGluZ1ssYygxOm5jb2xtYXgpXSkNCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9ucy5yZiwgdGVzdGluZyRDdXN0b21lcl9TZWdtZW50KQ0KDQpgYGANCmdyYWZpY2FyZW1vcyBlbCBjb25manVudG8gZGUgZXJyb3JlcyBlbiBsYSBpdGVyYWNpb24gZGUgbG9zIGFyYm9sZXMNCmBgYHtyfQ0KcGxvdChtb2RGaXQucmYsIG1haW4gPSAiUmF6b24gZGVsIHJhbmRvbSBkZSBhcmJvbGVzIikNCmBgYA0KDQoNCg0KQ29tbyBwb2RlbW9zIG9iZXJ2YXIgZW4gbGEgdGFibGEgeSBlbiBsYSBncmFmaWNhIGVsIG1lam9yIG1vZGVsbyByZXByZWNlbnRhIHVuYSBleGFjdGl0dWQgZGVsIDgzJQ0KbGEgaW1wb3J0YW5jaWEgZGUgZXN0ZSB2YWxvciBjb3JyZXNwb25kZSBhIGxhIHRhYmxhIGRlIHZhcmlhYmxlcyB5IHN1IGFwb3J0ZQ0KYGBge3J9DQp2YXJJbXBQbG90KG1vZEZpdC5yZiwgcGNoID0gMjAsIG1haW4gPSAiYXBvcnRlIGRlICBWYXJpYWJsZXMgcGFyYSBlbCBib3NxdWUiKQ0KYGBgDQoNCiMjS2ZvbGQgcGFyYSBtb2RlbG8gZGUgYXJib2wNCkFob3JhIHByb2NlZGVyZW1vcyBhIHJlYWxpemFyIGVsIGtmb2xkIHNvYnJlIHRvZG9zIG51ZXN0cm9zIHBvc2libGVzIG1vZGVsb3MNCg0KSW5pY2lhbW9zIGRldGVybWluYWRvIGxhIGZ1bmNpb24gZGUgY2FsY3VsbyBkZSBtZXRyaWNhIGRhZG8gdW4gY29uanVudG8gZGUgZmVhdHVyZXMsIHRvbWVtb3MgZW5jdWVudGEgcXVlIGVsIGVycm9yIGFob3JhIGVzIGNhbGN1bGFkbyANCmBgYHtyfQ0KZ2V0X21ldHJpY19wYXJ0IDwtIGZ1bmN0aW9uKGZlYXR1cmVzLGRhdGFzZXQsbGFiZWwpew0KICBmb3JtdWxhdCA8LSBwYXN0ZTAoZmVhdHVyZXMsIGNvbGxhcHNlPSIrIikNCiAgDQogIGZvcm11bGF0IDwtIHBhc3RlMChsYWJlbCwifiIsZm9ybXVsYXQsDQogICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIiIpDQogIA0KICANCiAgDQogIGZpdDwtICAgICAgIHJwYXJ0KGZvcm11bGE9Zm9ybXVsYXQsIGRhdGEgPSBkYXRhc2V0KSAgDQogIA0KICANCg0KICB0ZXN0X3ByZWQgPC0gcHJlZGljdChmaXQsIG5ld2RhdGEgPSBkYXRhc2V0LHR5cGU9ImNsYXNzIikNCiAgc2UgPC1tZWFuKHRlc3RfcHJlZCE9ZGF0YXNldFssbGFiZWxdKQ0KICBwcmludCggcGFzdGUwKGZvcm11bGF0LCIgRVJSPSIsc2UpKQ0KICByZXR1cm4oc2Uqc2UpDQp9DQpgYGANCg0KRGVmaW5pbW9zIG51ZXN0cmEgZnVuY2lvbiBkZSBzZWdtZW50YXIgbi1rIGNhcGFzDQoNCmBgYHtyfQ0KZml0X25rX3BhcnQgPC0gZnVuY3Rpb24oayxkYXRhc2V0LGxhYmVsKXsNCiAgZmVhdHVyZXMgPC0gY29sbmFtZXMoZGF0YXNldCkNCiAgZmVhdHVyZXMgPC0gc2V0ZGlmZihmZWF0dXJlcyAsIGxhYmVsKQ0KICBmZWF0dXJlc19zdWJzZXQ8LSBjb21ibihmZWF0dXJlcyxrLHNpbXBsaWZ5ID0gRkFMU0UpDQogIA0KICAgbWV0cmljIDwtIGxhcHBseShmZWF0dXJlc19zdWJzZXQsDQogICAgICAgICAgICAgICAgICAgIGdldF9tZXRyaWNfcGFydCwNCiAgICAgICAgICAgICAgICAgICBkYXRhc2V0PWRhdGFzZXQsDQogICAgICAgICAgICAgICAgICAgbGFiZWw9bGFiZWwpDQogICANCiAgIGluZGV4PC0NCiAgICB3aGljaC5taW4obWV0cmljICU+JSB1bmxpc3QoKSApDQogICANCiAgIHByaW50KCAiLi4uLiBNZWpvciBNb2RlbG8gKFJlc3B1ZXN0YSkgLi4uLiIpDQogICANCiAgIA0KICByZXR1cm4oZmVhdHVyZXNfc3Vic2V0W1tpbmRleF1dKQ0KDQogIA0KfSANCmBgYA0KDQpWZXJpZmljYW1vcyBlbCBwb3NpYmxlIHJlc3VsdGFkby4gZWwgc2lzdGVtYSBkZWJlIGRlIHJldG9ybmFyIGxhIG1lbm9yIGVycm9yDQplbCBsaXN0YWRvIHF1ZSBzZSBkZXRhbGxhIGNvcnJlc3BvbmRlIGEgdG9kb3MgbG9zIHBvc2libGVzIG1vZGVsc28gcXVlIGhhIGl0ZXJhZG8gDQpgYGB7cn0NCmZpdF9ua19wYXJ0KDUsdHJhaW5pbmcsIkN1c3RvbWVyX1NlZ21lbnQiKQ0KYGBgDQoNCkNvbW8gcG9kZW1vcyB2ZXIgeWEgdGVuZW1vcyBsYSByZXNwdWVzdGEgZGUgbGEgZXZhbHVhY2lvbiBkZSBjYWRhIG1vZGVsbw0KDQpRdWl0YXJlbW9zICBsb3MgbG9ncyBkZSBsYXMgZnVuY2lvbmVzDQoNCmBgYHtyfQ0KZ2V0X21ldHJpY19wYXJ0IDwtIGZ1bmN0aW9uKGZlYXR1cmVzLGRhdGFzZXQsbGFiZWwpew0KICBmb3JtdWxhdCA8LSBwYXN0ZTAoZmVhdHVyZXMsIGNvbGxhcHNlPSIrIikNCiAgDQogIGZvcm11bGF0IDwtIHBhc3RlMChsYWJlbCwifiIsZm9ybXVsYXQsDQogICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIiIpDQogIA0KICANCiAgDQogIGZpdDwtICAgICAgIHJwYXJ0KGZvcm11bGE9Zm9ybXVsYXQsIGRhdGEgPSBkYXRhc2V0KSAgDQogIA0KICANCg0KICB0ZXN0X3ByZWQgPC0gcHJlZGljdChmaXQsIG5ld2RhdGEgPSBkYXRhc2V0LHR5cGU9ImNsYXNzIikNCiAgc2UgPC1tZWFuKHRlc3RfcHJlZCE9ZGF0YXNldFssbGFiZWxdKQ0KICANCiAgcmV0dXJuKHNlKnNlKQ0KfQ0KYGBgDQoNCkRlZmluaW1vcyBudWVzdHJhIGZ1bmNpb24gZGUgc2VnbWVudGFyIG4tayBjYXBhcw0KDQpgYGB7cn0NCmZpdF9ua19wYXJ0IDwtIGZ1bmN0aW9uKGssZGF0YXNldCxsYWJlbCl7DQogIGZlYXR1cmVzIDwtIGNvbG5hbWVzKGRhdGFzZXQpDQogIGZlYXR1cmVzIDwtIHNldGRpZmYoZmVhdHVyZXMgLCBsYWJlbCkNCiAgZmVhdHVyZXNfc3Vic2V0PC0gY29tYm4oZmVhdHVyZXMsayxzaW1wbGlmeSA9IEZBTFNFKQ0KICANCiAgIG1ldHJpYyA8LSBsYXBwbHkoZmVhdHVyZXNfc3Vic2V0LA0KICAgICAgICAgICAgICAgICAgICBnZXRfbWV0cmljX3BhcnQsDQogICAgICAgICAgICAgICAgICAgZGF0YXNldD1kYXRhc2V0LA0KICAgICAgICAgICAgICAgICAgIGxhYmVsPWxhYmVsKQ0KICAgDQogICBpbmRleDwtDQogICAgd2hpY2gubWluKG1ldHJpYyAlPiUgdW5saXN0KCkgKQ0KICAgDQogIA0KICAgDQogICANCiAgcmV0dXJuKGZlYXR1cmVzX3N1YnNldFtbaW5kZXhdXSkNCg0KICANCn0gDQpgYGANCmNhbGN1bGFyZW1vcyBsYSBsaXN0YSBkZSBsb3MgbWVqb3JlcyBtb2RlbG9zIGEgdGVzdGVhciBjb24gdW4gbWF4aW1vIGRlIDYgcG9zaWJsZXMgbWVqb3JlcyBtb2RlbG9zIHBhcmEgcHJlZGVjaXINCg0KYGBge3J9DQpuX2ZlYXR1cmVzIDwtIG5jb2wodHJhaW5pbmcpLTENCg0KbGlzdF9tb2RlbHM8LQ0KICBsYXBwbHkoMTpuX2ZlYXR1cmVzLA0KICAgICAgICAgZml0X25rX3BhcnQsDQogICAgICAgICBkYXRhc2V0PXRyYWluaW5nLA0KICAgICAgICAgbGFiZWwgPSAnQ3VzdG9tZXJfU2VnbWVudCcpDQpsaXN0X21vZGVscw0KDQpgYGANCg0KYWNvbnRpbnVhY2lvbiBkZWZpbmlyZW1vcyBsYSBmdWNjaW9uIHF1ZSBlc2NhbmVhIHkgY2FsY3VsYSBlbCB0cmFpbiB5IHRlc3QgcGFyYSBjYWRhIG1vZGVsbyBhIHRlc3RlYXINCg0KYGBge3J9DQpsb29jdiA8LSBmdW5jdGlvbihpbmRleCwgZGF0YXNldCwgbGFiZWwsIGZlYXR1cmVzKXsNCiAgdHJhaW4gPC0gZGF0YXNldFstaW5kZXgsXQ0KICB0ZXN0IDwtIGRhdGFzZXRbaW5kZXgsXQ0KICBmb3JtdWxhdCA8LSBwYXN0ZTAobGFiZWwsICJ+IiwgcGFzdGUwKGZlYXR1cmVzLCBjb2xsYXBzZT0iKyIpLCBjb2xsYXBzZSA9ICIiKQ0KICANCiAgZml0PC0gICAgICAgIHJwYXJ0KGZvcm11bGE9Zm9ybXVsYXQsIGRhdGEgPSB0cmFpbiApICANCiAgDQogIA0KICB0ZXN0X3ByZWQgPC0gcHJlZGljdChmaXQsIHRlc3QsdHlwZT0iY2xhc3MiKQ0KICBzZSA8LW1lYW4odGVzdF9wcmVkIT10ZXN0WyxsYWJlbF0pDQogIA0KICANCiAgcmVzcHJpbnQ8LSBwYXN0ZShmb3JtdWxhdCwgIiBFcnI6Iiwgc2UsICIgY29ycmVjdG9zOiIsIHN1bSh0ZXN0X3ByZWQgPT0gdGVzdCRDdXN0b21lcl9TZWdtZW50KSwNCiAgICAgICIgZGUiLCBsZW5ndGgodGVzdF9wcmVkKSkNCiAgDQogIHByaW50KHJlc3ByaW50KQ0KICByZXR1cm4oc2Uqc2UpDQp9DQoNCmBgYA0KDQpEZWZpbmltb3MgbGEgZnVuY2lvbiBmb2xkIGRlIG5ldXN0cm9zIG1vZGVsb3MgZW4gbG9zIHF1ZSBoYWNlbW9zIHRlc3QgZGUgY2FkYSBzZWdtZW50byBxdWUgbm9zIHB1ZWRhIGNhbGN1bGFyIGNhcnJldA0KDQpgYGB7cn0NCmZvbGRzIDwtIGZ1bmN0aW9uKGssIGRhdGFzZXQsIGxhYmVsLCBmZWF0dXJlcyl7DQogIHNlX2ZvbGRzX2FsbCA8LSBsYXBwbHkoY3JlYXRlRm9sZHMoZGF0YXNldFssbGFiZWxdLCBrID0gaywgbGlzdCA9IFRSVUUsIHJldHVyblRyYWluID0gRkFMU0UpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBsb29jdiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWw9bGFiZWwsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFzZXQ9ZGF0YXNldCwNCiAgICAgICAgICAgICAgICAgICAgICAgICBmZWF0dXJlcz1mZWF0dXJlcykNCiAgcmV0dXJuKG1lYW4oc2VfZm9sZHNfYWxsICU+JSB1bmxpc3QoKSkpDQp9DQpgYGANCkFwbGljYXJlbW9zIHVuIExhcHBseSBhIGxhIGxpc3RhIGRlIG1vZGVsb3MgcGFyYSBudWVzdHJhIGZ1bmNpb24gZGUgZm9sZCBlbiBrIHNlZ21lbnRvcyBkZSA0IA0KDQpgYGB7cn0NCnRlc3QgPC0gbGFwcGx5KGxpc3RfbW9kZWxzLA0KICAgICAgICAgICAgICAgZm9sZHMsDQogICAgICAgICAgICAgICBrPTYsDQogICAgICAgICAgICAgICBkYXRhc2V0PWNhcmRzLA0KICAgICAgICAgICAgICAgbGFiZWw9IkN1c3RvbWVyX1NlZ21lbnQiDQopDQpwcmludChwYXN0ZSgiaW5kZXg6Iix3aGljaC5taW4odGVzdCkpKQ0KbGlzdF9tb2RlbHNbd2hpY2gubWluKHRlc3QpXQ0KYGBgDQpTZWxlY2Npb25hbW9zIGFsIHF1ZSBtZW5vciBlcnJvciB0ZW5nYQ0KbGlzdGFkbyBkZSBpbmRpY2VzIGNvbiBlcnJvcjoNCg0KYGBge3J9DQp0ZXN0DQoNCmBgYA0KDQpgYGB7cn0NCg0KbGlzdF9tb2RlbHMNCmBgYA0KDQoNCg0KDQpgYGB7cn0NCm1vZEZpdC5yZiA8LSByYW5kb21Gb3Jlc3Q6OnJhbmRvbUZvcmVzdChDdXN0b21lcl9TZWdtZW50IH4gQ2FyZF9GYW1pbHkrQ3JlZGl0X0xpbWl0K0FnZStUcmFuc2FjdGlvbl9TZWdtZW50LCBkYXRhID0gY2FyZHMgKQ0KbW9kRml0LnJmDQpgYGANCmV2YWx1YXJlbW9zIGVsIHJlc3VsdGFkbyBkZWwgYm9zcXVlIGdlbmVyYWRvDQpgYGB7cn0NCnByZWRpY3Rpb25zLnJmIDwtIHByZWRpY3QobW9kRml0LnJmLCBuZXdkYXRhID0gdGVzdGluZ1ssYygxOm5jb2xtYXgpXSkNCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9ucy5yZiwgdGVzdGluZyRDdXN0b21lcl9TZWdtZW50KQ0KDQpgYGA=