Este ejercicio consiste en realizar un análisis exploratorio sobre un dataset de personal de una determinada empresa con 14999 instancias y 10 atributos.

El objetivo es conseguir un modelo adecuado con un resultado aceptable interpretando cada paso del razonamiento necesario para llegar al objetivo.

  • El atributo objetivo es Left. Indica si un empleado se queda en la empresa o se va.

1.Carga de Librerias

Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     

Attaching package: 㤼㸱dplyr㤼㸲

The following objects are masked from 㤼㸱package:stats㤼㸲:

    filter, lag

The following objects are masked from 㤼㸱package:base㤼㸲:

    intersect, setdiff, setequal, union

corrplot 0.84 loaded
Loading required package: lattice
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
randomForest 4.6-14
Type rfNews() to see new features/changes/bug fixes.

Attaching package: 㤼㸱randomForest㤼㸲

The following object is masked from 㤼㸱package:ggplot2㤼㸲:

    margin

The following object is masked from 㤼㸱package:dplyr㤼㸲:

    combine

2.Carga de Datos

3.Ordenamiento de Datos

4.Visualizacion de los datos

raw_data

5.Resumen de los datos

summary(raw_data)
      left           salary     satisfaction_level last_evaluation  number_project  average_montly_hours
 Min.   :0.0000   high  :1237   Min.   :0.0900     Min.   :0.3600   Min.   :2.000   Min.   : 96.0       
 1st Qu.:0.0000   low   :7316   1st Qu.:0.4400     1st Qu.:0.5600   1st Qu.:3.000   1st Qu.:156.0       
 Median :0.0000   medium:6446   Median :0.6400     Median :0.7200   Median :4.000   Median :200.0       
 Mean   :0.2381                 Mean   :0.6128     Mean   :0.7161   Mean   :3.803   Mean   :201.1       
 3rd Qu.:0.0000                 3rd Qu.:0.8200     3rd Qu.:0.8700   3rd Qu.:5.000   3rd Qu.:245.0       
 Max.   :1.0000                 Max.   :1.0000     Max.   :1.0000   Max.   :7.000   Max.   :310.0       
                                                                                                        
 time_spend_company Work_accident    promotion_last_5years         sales     
 Min.   : 2.000     Min.   :0.0000   Min.   :0.00000       sales      :4140  
 1st Qu.: 3.000     1st Qu.:0.0000   1st Qu.:0.00000       technical  :2720  
 Median : 3.000     Median :0.0000   Median :0.00000       support    :2229  
 Mean   : 3.498     Mean   :0.1446   Mean   :0.02127       IT         :1227  
 3rd Qu.: 4.000     3rd Qu.:0.0000   3rd Qu.:0.00000       product_mng: 902  
 Max.   :10.000     Max.   :1.0000   Max.   :1.00000       marketing  : 858  
                                                           (Other)    :2923  

6.Estructura de los datos

str(raw_data)
'data.frame':   14999 obs. of  10 variables:
 $ left                 : int  1 1 1 1 1 1 1 1 1 1 ...
 $ salary               : Factor w/ 3 levels "high","low","medium": 2 3 3 2 2 2 2 2 2 2 ...
 $ satisfaction_level   : num  0.38 0.8 0.11 0.72 0.37 0.41 0.1 0.92 0.89 0.42 ...
 $ last_evaluation      : num  0.53 0.86 0.88 0.87 0.52 0.5 0.77 0.85 1 0.53 ...
 $ number_project       : int  2 5 7 5 2 2 6 5 5 2 ...
 $ average_montly_hours : int  157 262 272 223 159 153 247 259 224 142 ...
 $ time_spend_company   : int  3 6 4 5 3 3 4 5 5 3 ...
 $ Work_accident        : int  0 0 0 0 0 0 0 0 0 0 ...
 $ promotion_last_5years: int  0 0 0 0 0 0 0 0 0 0 ...
 $ sales                : Factor w/ 10 levels "accounting","hr",..: 8 8 8 8 8 8 8 8 8 8 ...

7.Análisis exploratorio

7,1.Revisión de datos nulos

CantidadNulos <- sapply(raw_data, function(x) sum(is.na(x)))
data.frame(CantidadNulos)
  • No se presentan instancias nulas.
  • No se presentan instancias duplicadas.

7,2.Satisfaction Level

par(mfrow = c(1,2))

boxplot(raw_data$satisfaction_level, main = "Satisfaction Level")

hist(raw_data$satisfaction_level, main = "Distribucion Satisfaction Level", freq = F)
lines(density(raw_data$satisfaction_level), col = "red", lwd=2) 

  • Se observa que la mayor parte de las instancias poseen un satisfaction_level entre (0.4 y 1.0).

7,3.Last Evaluation

par(mfrow = c(1,2))

boxplot(raw_data$last_evaluation, main = "Last Evaluation")
hist(raw_data$last_evaluation, main = "Distribucion Last Evaluation", freq = F)
lines(density(raw_data$last_evaluation), col = "red", lwd=2) 

  • Se observa que la mayor parte de las instancias poseen un valor de last_evaluation entre (0.45 y 1.0), con una mediana alrrededor de 0.7.

7,4.Number of Project

par(mfrow = c(1,2))

boxplot(raw_data$number_project, main = "Number of Project")
hist(raw_data$number_project, main = "Distribucion Number of Project")

  • Se observa que la mayor parte de las instancias poseen un valor de number_project entre (2, 5), con una mediana alrrededor de 4.

7,5.Average Montly Hours

par(mfrow = c(1,2))

boxplot(raw_data$average_montly_hours, main = "Average Montly Hours")
hist(raw_data$average_montly_hours, main = "Distribucion Average Montly Hours", freq = F)
lines(density(raw_data$average_montly_hours), col = "red", lwd=2) 

  • Se observa que la mayor parte de las instancias poseen un valor de average_montly_hours entre (125, 275), con una mediana alrrededor de 200.

7,6.Time Spend Company

par(mfrow = c(1,2))

boxplot(raw_data$time_spend_company, main = "Time Spend Company")
hist(raw_data$time_spend_company, main = "Distribucion Time Spend Company")

  • Se observa que la mayor parte de las instancias poseen un valor de time_spend_company entre (2, 6), con una mediana alrrededor de 3, y un valor minimo de 2. Se presentan valores atipicos para valores superiores a 5.

7,7.Salary

colorSalary = rainbow(nlevels((as.factor(raw_data$salary))))
colorYN = rainbow(nlevels((as.factor(raw_data$Work_accident))))
colorSales = rainbow(nlevels((as.factor(raw_data$sales))))

7,8.Salary y Promotion

par(mfrow = c(1,2))

barplot(summary(raw_data$salary), main = "Distribución de 'Salary'", 
        col= colorSalary )
pie(summary(as.factor(raw_data$promotion_last_5years)), labels = c("Si","No"), main = "Distribución de 'Promotion'", col=colorYN)

  • Se observa que la mayor parte de las instancias poseen un valor de salary “low” y “medium”.
  • Se observa que la mayoria de las instancias presentan un valor de Promotion “Si”.

7,9.Work Accident y Distribución de Left

par(mfrow = c(1,2))
pie(summary(as.factor(raw_data$Work_accident)), labels = c("Si","No"), main = "Distribución de 'Work Accident'", col=colorYN)
pie(summary(as.factor(raw_data$left)), labels = c("Si","No"), main = "Distribución de 'Left'", col=colorYN)

  • Se observa que la mayoria de las instancias presentan un valor de Work Accident “Si”.
  • Se observa que aproximadamente un 75% de las instancias presentan un valor de Left“Si”.

7,10.Sales

barplot(summary(raw_data$sales), main = "Distribución de 'Sales'", col = colorSales)
legend("topleft", summary(raw_data$sales), cex = 0.8,  fill = colorSales, legend=levels(raw_data$sales))

8.Estudio de Variables

8,1.Correlación raw_data

corrplot(cor(select(raw_data, -c("salary", "sales"))), type="upper", method="pie")

cor(select(raw_data, -c("salary", "sales")))
                             left satisfaction_level last_evaluation number_project average_montly_hours
left                   1.00000000        -0.38837498     0.006567120    0.023787185          0.071287179
satisfaction_level    -0.38837498         1.00000000     0.105021214   -0.142969586         -0.020048113
last_evaluation        0.00656712         0.10502121     1.000000000    0.349332589          0.339741800
number_project         0.02378719        -0.14296959     0.349332589    1.000000000          0.417210634
average_montly_hours   0.07128718        -0.02004811     0.339741800    0.417210634          1.000000000
time_spend_company     0.14482217        -0.10086607     0.131590722    0.196785891          0.127754910
Work_accident         -0.15462163         0.05869724    -0.007104289   -0.004740548         -0.010142888
promotion_last_5years -0.06178811         0.02560519    -0.008683768   -0.006063958         -0.003544414
                      time_spend_company Work_accident promotion_last_5years
left                         0.144822175  -0.154621634          -0.061788107
satisfaction_level          -0.100866073   0.058697241           0.025605186
last_evaluation              0.131590722  -0.007104289          -0.008683768
number_project               0.196785891  -0.004740548          -0.006063958
average_montly_hours         0.127754910  -0.010142888          -0.003544414
time_spend_company           1.000000000   0.002120418           0.067432925
Work_accident                0.002120418   1.000000000           0.039245435
promotion_last_5years        0.067432925   0.039245435           1.000000000
  • Aparentemente el atributo objetivo left se encuentra relacionado principalmente con los atributos satisfaction_level, time_spend_company, y work_accident.

8,2.Colinealidad raw_data

imcdiag(select(raw_data, -c("left","salary", "sales")), raw_data$left)

Call:
imcdiag(x = select(raw_data, -c("left", "salary", "sales")), 
    y = raw_data$left)


All Individual Multicollinearity Diagnostics Result

                         VIF    TOL       Wi        Fi Leamer   CVIF Klein
satisfaction_level    1.0634 0.9404 158.4444  190.1460 0.9697 1.0801     0
last_evaluation       1.2405 0.8061 600.9249  721.1580 0.8978 1.2600     1
number_project        1.3524 0.7394 880.6199 1056.8143 0.8599 1.3737     1
average_montly_hours  1.2790 0.7819 697.0355  836.4983 0.8842 1.2990     1
time_spend_company    1.0605 0.9429 151.2186  181.4744 0.9710 1.0772     0
Work_accident         1.0053 0.9948  13.1418   15.7712 0.9974 1.0210     0
promotion_last_5years 1.0076 0.9925  18.9554   22.7480 0.9962 1.0234     0

1 --> COLLINEARITY is detected by the test 
0 --> COLLINEARITY is not detected by the test

* all coefficients have significant t-ratios

R-square of y on all x: 0.1927 

* use method argument to check which regressors may be the reason of collinearity
===================================
  • Ss posible que exista colinealidad vinculado con los atributos last_evaluation, number_project, average_montly_hours .

8,3.Estudio de Satisfaction_level

8,3,1.Satisfaction_level vs Last_evaluation

ggplot(raw_data, 
       aes(x = satisfaction_level, y = last_evaluation, color = as.factor(left))) + geom_point()

  • Mediante este scatterplot se ve la relacion entre dos variables continuas: satisfaction_level y last_evaluation.
  • El estudio se realizará particularmente sobre las instancias cuyo valor de left sea 0, es decir sobre aquellos empleados que conserva la empresa. Se distingue para esto la poblacion cuyo satisfaction_level es mayor a 0.50 y cuyo valor de last_evaluation es mayor a 0.45.
  • Cabe considerar que aquellas concentraciones de puntos con valor de left 1 (figura [(0.4, 0.45),(0.48, 0.45),(0.4, 0.58),(0.48, 0.58)] y figura [(0.1, 0.75),(0.15, 0.75),(0.1, 0.95),(0.15, 0.95)]) pueden corresponder a casos de individuos muy capacitados pero poco satisfechos o individuos poco satisfechos y con un bajo desempeño en su ultima evaluacion.

8,3,2.Satisfaction_level vs Number_projects

ggplot(raw_data, 
       aes(x = number_project, y = satisfaction_level, color = as.factor(left))) + geom_boxplot()

  • Se observa que aquellas instancias con una cantidad de proyectos superiores a 4 y con niveles de conformidad entre 0.10 y 0.70, presentan la mayoria de las salidas de la empresa.

  • Se observa que aquellas instancias con una cantidad de proyectos menores a 4, presentan una mayoria con niveles de satisfaccion entre 0.50 y 0.80.

8,3,3.Satisfaction_level vs Number_projects

ggplot(raw_data, 
       aes(x = average_montly_hours, y = satisfaction_level, color = as.factor(left))) + geom_boxplot()

  • Se observa que la mayoria de los indiviuos que dejan la empresa mantenian una media de horas mensuales superiores a las 200 horas, posiblemente se relacione con el alto numero de proyectos en los que participaban.

8,3,4.Satisfaction_level vs Time_Spend_in_Company(años)

ggplot(raw_data, 
       aes(x = time_spend_company, y = satisfaction_level, color = as.factor(left))) + geom_boxplot()

  • La mayoria de los empleados que abandonaron la empresa, estuvieron menos de 5 años en la compañia, y mantuvieron un nivel de satisfaccion con una mediana de alrrededor de 0.45.

9.Modelos con Raw Data

9,1.Conjunto de datos de Entrenamiento y Prueba


data_train_1 <- sample_frac(raw_data, 0.7)
prop.table(table(data_train_1$left))

        0         1 
0.7590247 0.2409753 
data_test_1 <- setdiff(raw_data, data_train_1)
prop.table(table(data_test_1$left))

        0         1 
0.8801029 0.1198971 
data_train_1$left <- factor(data_train_1$left)
data_test_1$left <- factor(data_test_1$left)
  • Separacion del raw_data original en: 70% para entrenamiento, 30% para tests.

9,2.Modelo de Arbol de Decision

tree_1 <- rpart(formula = left ~ ., data = data_train_1)
rpart.plot(tree_1)


prediccion <- predict(tree_1, newdata = data_test_1, type = "class")
confusionMatrix(prediccion, data_test_1[["left"]])
Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 2708   29
         1   30  344
                                          
               Accuracy : 0.981           
                 95% CI : (0.9756, 0.9855)
    No Information Rate : 0.8801          
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.9102          
                                          
 Mcnemar's Test P-Value : 1               
                                          
            Sensitivity : 0.9890          
            Specificity : 0.9223          
         Pos Pred Value : 0.9894          
         Neg Pred Value : 0.9198          
             Prevalence : 0.8801          
         Detection Rate : 0.8705          
   Detection Prevalence : 0.8798          
      Balanced Accuracy : 0.9556          
                                          
       'Positive' Class : 0               
                                          

9,3.Regresión Logistica

glm.model <- glm(formula = left ~ ., data = data_train_1, family = binomial(logit))
summary(glm.model)

Call:
glm(formula = left ~ ., family = binomial(logit), data = data_train_1)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.3066  -0.6690  -0.4022  -0.1078   3.0220  

Coefficients:
                        Estimate Std. Error z value Pr(>|z|)    
(Intercept)           -1.3546828  0.2280553  -5.940 2.85e-09 ***
salarylow              1.8941971  0.1511511  12.532  < 2e-16 ***
salarymedium           1.3888745  0.1520547   9.134  < 2e-16 ***
satisfaction_level    -4.1580605  0.1168443 -35.586  < 2e-16 ***
last_evaluation        0.6592715  0.1781232   3.701 0.000215 ***
number_project        -0.3017375  0.0254097 -11.875  < 2e-16 ***
average_montly_hours   0.0043428  0.0006187   7.020 2.22e-12 ***
time_spend_company     0.2880462  0.0187221  15.385  < 2e-16 ***
Work_accident         -1.5825269  0.1083561 -14.605  < 2e-16 ***
promotion_last_5years -1.6161117  0.3120835  -5.178 2.24e-07 ***
saleshr                0.1587844  0.1548761   1.025 0.305252    
salesIT               -0.2104590  0.1436538  -1.465 0.142909    
salesmanagement       -0.4990663  0.1932009  -2.583 0.009790 ** 
salesmarketing        -0.1203607  0.1571524  -0.766 0.443745    
salesproduct_mng      -0.2351966  0.1533708  -1.534 0.125149    
salesRandD            -0.7177087  0.1727671  -4.154 3.26e-05 ***
salessales            -0.1145991  0.1207182  -0.949 0.342463    
salessupport          -0.0620536  0.1287843  -0.482 0.629919    
salestechnical        -0.0922945  0.1262020  -0.731 0.464581    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 11595.1  on 10498  degrees of freedom
Residual deviance:  9038.5  on 10480  degrees of freedom
AIC: 9076.5

Number of Fisher Scoring iterations: 5
lgm.predict <- round(predict(glm.model, data_test_1, type = "response"))
lgm.predict <- factor(lgm.predict)
confusionMatrix(lgm.predict, data_test_1$left) 
Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 2542  231
         1  196  142
                                          
               Accuracy : 0.8627          
                 95% CI : (0.8502, 0.8747)
    No Information Rate : 0.8801          
    P-Value [Acc > NIR] : 0.99844         
                                          
                  Kappa : 0.3222          
                                          
 Mcnemar's Test P-Value : 0.09989         
                                          
            Sensitivity : 0.9284          
            Specificity : 0.3807          
         Pos Pred Value : 0.9167          
         Neg Pred Value : 0.4201          
             Prevalence : 0.8801          
         Detection Rate : 0.8171          
   Detection Prevalence : 0.8914          
      Balanced Accuracy : 0.6546          
                                          
       'Positive' Class : 0               
                                          

9,4.Random Forest

rf.model <- randomForest(left~., data = data_train_1)
rf.prediction <- predict(rf.model, data_test_1, type = "class")
confusionMatrix(rf.prediction, data_test_1$left)
Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 2733   29
         1    5  344
                                          
               Accuracy : 0.9891          
                 95% CI : (0.9848, 0.9924)
    No Information Rate : 0.8801          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.9467          
                                          
 Mcnemar's Test P-Value : 7.998e-05       
                                          
            Sensitivity : 0.9982          
            Specificity : 0.9223          
         Pos Pred Value : 0.9895          
         Neg Pred Value : 0.9857          
             Prevalence : 0.8801          
         Detection Rate : 0.8785          
   Detection Prevalence : 0.8878          
      Balanced Accuracy : 0.9602          
                                          
       'Positive' Class : 0               
                                          

9,5.LDA

lda.model <- train(left ~., data = data_train_1, method = "lda")
lda.predict <- predict(lda.model, data_test_1)
confusionMatrix(lda.predict, data_test_1$left)
Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 2517  239
         1  221  134
                                          
               Accuracy : 0.8521          
                 95% CI : (0.8392, 0.8644)
    No Information Rate : 0.8801          
    P-Value [Acc > NIR] : 1.000           
                                          
                  Kappa : 0.2845          
                                          
 Mcnemar's Test P-Value : 0.428           
                                          
            Sensitivity : 0.9193          
            Specificity : 0.3592          
         Pos Pred Value : 0.9133          
         Neg Pred Value : 0.3775          
             Prevalence : 0.8801          
         Detection Rate : 0.8091          
   Detection Prevalence : 0.8859          
      Balanced Accuracy : 0.6393          
                                          
       'Positive' Class : 0               
                                          

A fin de poder evaluar los modelos se utilizan los siguientes valores resultado:

  • Matriz de Confusion: Matriz que permite comparar los valores obtenidos durante la prueba y entrenamiento en las predicciones, los valores que se muestran corresponden a: verdaderos positivo (es la cantidad de positivos que fueron clasificados correctamente como positivos por el modelo), verdaderos negativos (es la cantidad de negativos que fueron clasificados correctamente como negativos por el modelo), falsos negativos (es la cantidad de positivos que fueron clasificados incorrectamente como negativos) y falsos positivos (es la cantidad de negativos que fueron clasificados incorrectamente como positivos).

9,6.Iteración sobre los modelos

results_tree <- matrix(nrow=10,ncol=1)
for (i in 1:10){
  
   data_train_1 <- sample_frac(raw_data, 0.7)
   prop.table(table(data_train_1$left))
  
   data_test_1 <- setdiff(raw_data, data_train_1)
   prop.table(table(data_test_1$left))
  
   data_train_1$left <- factor(data_train_1$left)
   data_test_1$left <- factor(data_test_1$left)
   
   tree_1 <- rpart(formula = left ~ ., data = data_train_1)
   prediccion <- predict(tree_1, newdata = data_test_1, type = "class")
   res_tree <- confusionMatrix(prediccion, data_test_1[["left"]])
   results_tree[i,] <- res_tree$overall["Accuracy"]
}
  • Se obtiene la media de 10 observaciones del modelo para una posterior comparación del Accuracy del Modelo.
results_log <- matrix(nrow=10,ncol=1)
for (i in 1:10){
  
   data_train_1 <- sample_frac(raw_data, 0.7)
   prop.table(table(data_train_1$left))
  
   data_test_1 <- setdiff(raw_data, data_train_1)
   prop.table(table(data_test_1$left))
  
   data_train_1$left <- factor(data_train_1$left)
   data_test_1$left <- factor(data_test_1$left)
   
   glm.model <- glm(formula = left ~ ., data = data_train_1, family = "binomial")
   lgm.predict <- round(predict(glm.model, data_test_1, type = "response"))
   lgm.predict <- factor(lgm.predict)
   res_lgm = confusionMatrix(lgm.predict, data_test_1$left) 
   results_log[i,] <- res_lgm$overall["Accuracy"]
}
  • Se obtiene la media de 10 observaciones del modelo para una posterior comparación del Accuracy del Modelo.
results_rf <- matrix(nrow=10,ncol=1)
for (i in 1:10){
  
   data_train_1 <- sample_frac(raw_data, 0.7)
   prop.table(table(data_train_1$left))
  
   data_test_1 <- setdiff(raw_data, data_train_1)
   prop.table(table(data_test_1$left))
  
   data_train_1$left <- factor(data_train_1$left)
   data_test_1$left <- factor(data_test_1$left)
   
   rf.model <- randomForest(left~., data = data_train_1)
   rf.prediction <- predict(rf.model, data_test_1, type = "class")
   res_random = confusionMatrix(rf.prediction, data_test_1$left)
   results_rf[i,] <- res_random$overall["Accuracy"]
}
  • Se obtiene la media de 10 observaciones del modelo para una posterior comparación del Accuracy del Modelo.
results_lda <- matrix(nrow=10,ncol=1)
for (i in 1:10){
  
   data_train_1 <- sample_frac(raw_data, 0.7)
   prop.table(table(data_train_1$left))
  
   data_test_1 <- setdiff(raw_data, data_train_1)
   prop.table(table(data_test_1$left))
  
   data_train_1$left <- factor(data_train_1$left)
   data_test_1$left <- factor(data_test_1$left)
   
   lda.model <- train(left ~., data = data_train_1, method = "lda")
   lda.predict <- predict(lda.model, data_test_1)
   res_lda = confusionMatrix(lda.predict, data_test_1$left)
   
   results_lda[i,] <- res_lda$overall["Accuracy"]
}
Arbol de Decision Regresion Logistica Random Forest LDA
0.9889021 0.9701466 0.9910586 0.971527

10.Limpieza de datos

data_set <- raw_data
data_set <- dummy_cols(data_set, select_columns = c("sales"))
data_set$sales = NULL

data_set <- dummy_cols(data_set, select_columns = c("salary"))
data_set$salary = NULL
  • Dada la naturaleza de los atributos sales y salary (factores char), se procede a realizar un proceso de encoding para su posterior manipulacion. Se elimina la columna original a fin de evitar problemas de colinealidad.
data_set <- data_set %>% filter(satisfaction_level > 0.48) %>% filter(last_evaluation > 0.50)
  • En la observacion de satisfaction_level vs last_evaluation se resolvio trabajar sobre la mayor concentracion de empleados que permanecen en la organización.
ggplot(data_set, 
       aes(x = satisfaction_level, y = last_evaluation, color = as.factor(left))) + geom_point()

10,1.Matriz de correlación

corrplot(cor(data_set), type="upper", method="pie")

  • Aparentemente la variable left se encuentra principalmente relacionada con satisfaction_level, last_evaluation, number_project, average_montly_hours y time_spend_company.
imcdiag(data_set, data_set$left)

Call:
imcdiag(x = data_set, y = data_set$left)


All Individual Multicollinearity Diagnostics Result

                         VIF    TOL       Wi       Fi Leamer CVIF Klein
left                  1.4331 0.6978 211.5604 222.7179 0.8353    0     0
satisfaction_level    1.0209 0.9795  10.2101  10.7486 0.9897    0     0
last_evaluation       1.1213 0.8918  59.2678  62.3935 0.9443    0     0
number_project        1.0763 0.9291  37.2578  39.2228 0.9639    0     0
average_montly_hours  1.0875 0.9195  42.7347  44.9885 0.9589    0     0
time_spend_company    1.1922 0.8388  93.8901  98.8419 0.9158    0     0
Work_accident         1.0143 0.9859   6.9662   7.3336 0.9929    0     0
promotion_last_5years 1.0349 0.9663  17.0316  17.9298 0.9830    0     0
sales_accounting         Inf 0.0000      Inf      Inf 0.0000  NaN     0
sales_hr                 Inf 0.0000      Inf      Inf 0.0000  NaN     0
sales_IT                 Inf 0.0000      Inf      Inf 0.0000  NaN     0
sales_management         Inf 0.0000      Inf      Inf 0.0000  NaN     0
sales_marketing          Inf 0.0000      Inf      Inf 0.0000  NaN     0
sales_product_mng        Inf 0.0000      Inf      Inf 0.0000  NaN     0
sales_RandD              Inf 0.0000      Inf      Inf 0.0000  NaN     0
sales_sales              Inf 0.0000      Inf      Inf 0.0000  NaN     0
sales_support            Inf 0.0000      Inf      Inf 0.0000  NaN     0
sales_technical          Inf 0.0000      Inf      Inf 0.0000  NaN     0
salary_high              Inf 0.0000      Inf      Inf 0.0000  NaN     0
salary_low               Inf 0.0000      Inf      Inf 0.0000  NaN     0
salary_medium            Inf 0.0000      Inf      Inf 0.0000  NaN     0

1 --> COLLINEARITY is detected by the test 
0 --> COLLINEARITY is not detected by the test

satisfaction_level , last_evaluation , number_project , average_montly_hours , time_spend_company , Work_accident , promotion_last_5years , sales_accounting , sales_hr , sales_IT , sales_management , sales_marketing , sales_product_mng , sales_RandD , sales_sales , sales_support , sales_technical , salary_high , coefficient(s) are non-significant may be due to multicollinearity

R-square of y on all x: 1 

* use method argument to check which regressors may be the reason of collinearity
===================================
  • Se detectan variables con un alto valor de VIF, aquellas variables procedentes del proceso de encoding, aparentemente no aportan mucha relevancia al modelo y se decide excluirlas del mismo.

models <- regsubsets(left~., data = data_set, nvmax = 5)
2  linear dependencies found
Reordering variables and trying again:
summary(models)
Subset selection object
Call: regsubsets.formula(left ~ ., data = data_set, nvmax = 5)
20 Variables  (and intercept)
                      Forced in Forced out
satisfaction_level        FALSE      FALSE
last_evaluation           FALSE      FALSE
number_project            FALSE      FALSE
average_montly_hours      FALSE      FALSE
time_spend_company        FALSE      FALSE
Work_accident             FALSE      FALSE
promotion_last_5years     FALSE      FALSE
sales_accounting          FALSE      FALSE
sales_hr                  FALSE      FALSE
sales_IT                  FALSE      FALSE
sales_management          FALSE      FALSE
sales_marketing           FALSE      FALSE
sales_product_mng         FALSE      FALSE
sales_RandD               FALSE      FALSE
sales_sales               FALSE      FALSE
sales_support             FALSE      FALSE
salary_high               FALSE      FALSE
salary_low                FALSE      FALSE
sales_technical           FALSE      FALSE
salary_medium             FALSE      FALSE
1 subsets of each size up to 6
Selection Algorithm: exhaustive
         satisfaction_level last_evaluation number_project average_montly_hours time_spend_company
1  ( 1 ) " "                " "             " "            " "                  "*"               
2  ( 1 ) " "                "*"             " "            " "                  "*"               
3  ( 1 ) " "                "*"             " "            "*"                  "*"               
4  ( 1 ) " "                "*"             "*"            "*"                  "*"               
5  ( 1 ) " "                "*"             "*"            "*"                  "*"               
6  ( 1 ) " "                "*"             "*"            "*"                  "*"               
         Work_accident promotion_last_5years sales_accounting sales_hr sales_IT sales_management
1  ( 1 ) " "           " "                   " "              " "      " "      " "             
2  ( 1 ) " "           " "                   " "              " "      " "      " "             
3  ( 1 ) " "           " "                   " "              " "      " "      " "             
4  ( 1 ) " "           " "                   " "              " "      " "      " "             
5  ( 1 ) " "           " "                   " "              " "      " "      " "             
6  ( 1 ) "*"           " "                   " "              " "      " "      " "             
         sales_marketing sales_product_mng sales_RandD sales_sales sales_support sales_technical
1  ( 1 ) " "             " "               " "         " "         " "           " "            
2  ( 1 ) " "             " "               " "         " "         " "           " "            
3  ( 1 ) " "             " "               " "         " "         " "           " "            
4  ( 1 ) " "             " "               " "         " "         " "           " "            
5  ( 1 ) " "             " "               " "         " "         " "           " "            
6  ( 1 ) " "             " "               " "         " "         " "           " "            
         salary_high salary_low salary_medium
1  ( 1 ) " "         " "        " "          
2  ( 1 ) " "         " "        " "          
3  ( 1 ) " "         " "        " "          
4  ( 1 ) " "         " "        " "          
5  ( 1 ) " "         "*"        " "          
6  ( 1 ) " "         "*"        " "          
plot(models)

  • Se lleva a cabo una seleccion de variables donde se supone que las variables mas relevantes para el modelo son: satisfaction_level. last_evaluation, number_project, average_montly_hours, time_spend_company, Work_accident.
data_set_1 <- select(data_set, c("left", "satisfaction_level", "last_evaluation", "number_project", "average_montly_hours", "time_spend_company", "Work_accident"))

dim(data_set_1)/dim(raw_data)
[1] 0.6527102 0.7000000
  • Tras la limpieza de los datos, se recorto el dataset original en un 35%.

10,2.Matriz de correlación

corrplot(cor(data_set_1), type="upper", method="pie")

imcdiag(data_set_1, data_set_1$left)

Call:
imcdiag(x = data_set_1, y = data_set_1$left)


All Individual Multicollinearity Diagnostics Result

                        VIF    TOL       Wi       Fi Leamer CVIF Klein
left                 1.4014 0.7135 654.5625 785.5553 0.8447    0     0
satisfaction_level   1.0195 0.9808  31.8390  38.2107 0.9904    0     0
last_evaluation      1.1180 0.8945 192.4022 230.9063 0.9458    0     0
number_project       1.0749 0.9303 122.0834 146.5151 0.9645    0     0
average_montly_hours 1.0860 0.9208 140.3014 168.3789 0.9596    0     0
time_spend_company   1.1487 0.8705 242.4752 291.0000 0.9330    0     0
Work_accident        1.0121 0.9880  19.7275  23.6755 0.9940    0     0

1 --> COLLINEARITY is detected by the test 
0 --> COLLINEARITY is not detected by the test

satisfaction_level , last_evaluation , number_project , average_montly_hours , time_spend_company , Work_accident , coefficient(s) are non-significant may be due to multicollinearity

R-square of y on all x: 1 

* use method argument to check which regressors may be the reason of collinearity
===================================
  • No se observan rastros de colinealidad, dado que el valor de VIF es cercado a 1.
results_tree <- matrix(nrow=10,ncol=1)
for (i in 1:10){
  
   data_train_2 <- sample_frac(data_set_1, 0.7)
   prop.table(table(data_train_2$left))
  
   data_test_2 <- setdiff(data_set_1, data_train_2)
   prop.table(table(data_test_2$left))
  
   data_train_2$left <- factor(data_train_2$left)
   data_test_2$left <- factor(data_test_2$left)
   
   tree_1 <- rpart(formula = left ~ ., data = data_train_2)
   prediccion <- predict(tree_1, newdata = data_test_2, type = "class")
   res_tree <- confusionMatrix(prediccion, data_test_2[["left"]])
   
   results_tree[i,] <- res_tree$overall["Accuracy"]
}
results_log <- matrix(nrow=10,ncol=1)
for (i in 1:10){
  
   data_train_2 <- sample_frac(data_set_1, 0.7)
   prop.table(table(data_train_2$left))
  
   data_test_2 <- setdiff(data_set_1, data_train_2)
   prop.table(table(data_test_2$left))
  
   data_train_2$left <- factor(data_train_2$left)
   data_test_2$left <- factor(data_test_2$left)
   
   glm.model <- glm(formula = left ~ ., data = data_train_2, family = "binomial")
   lgm.predict <- round(predict(glm.model, data_test_2, type = "response"))
   lgm.predict <- factor(lgm.predict)
   res_lgm = confusionMatrix(lgm.predict, data_test_2$left) 
   results_log[i,] <- res_lgm$overall["Accuracy"]
}
results_rf <- matrix(nrow=10,ncol=1)
for (i in 1:10){
  
   data_train_2 <- sample_frac(data_set_1, 0.7)
   prop.table(table(data_train_2$left))
  
   data_test_2 <- setdiff(data_set_1, data_train_2)
   prop.table(table(data_test_2$left))
  
   data_train_2$left <- factor(data_train_2$left)
   data_test_2$left <- factor(data_test_2$left)
   
   rf.model <- randomForest(left~., data = data_train_2)
   rf.prediction <- predict(rf.model, data_test_2, type = "class")
   res_random = confusionMatrix(rf.prediction, data_test_2$left)
   results_rf[i,] <- res_random$overall["Accuracy"]
}
results_lda <- matrix(nrow=10,ncol=1)
for (i in 1:10){
  
   data_train_2 <- sample_frac(data_set_1, 0.7)
   prop.table(table(data_train_2$left))
  
   data_test_2 <- setdiff(data_set_1, data_train_2)
   prop.table(table(data_test_2$left))
  
   data_train_2$left <- factor(data_train_2$left)
   data_test_2$left <- factor(data_test_2$left)
   
   lda.model <- train(left ~., data = data_train_2, method = "lda")
   lda.predict <- predict(lda.model, data_test_2)
   res_lda = confusionMatrix(lda.predict, data_test_2$left)
   
   results_lda[i,] <- res_lda$overall["Accuracy"]
}

10,3.Accuracy

Arbol de Decision Regresion Logistica Random Forest LDA
0.9889021 0.9701466 0.9910586 0.971527

Tras la limpieza de datos y seleccion de variables se obtuvieron valores aceptables de accuracy. Sin embargo deben evaluarse los modelos en forma individual para seleccionar el modelo mas adecuado.

LS0tDQp0aXRsZTogIkFuYWxpc2lzIGV4cGxvcmF0b3JpbyBkZSBkYXRhU2V0ICdSUiBISCAtIExlZnQnIg0KYXV0aG9yOiAiQWx2YXJleiBJZ25hY2lvIE5pY29sYXMiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgZmlnOmhlaWdodDogNA0KICAgIGZpZzp3aWR0aDogNg0KICAgIHRoZW1lOiByZWFkYWJsZQ0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgcGRmX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQpmaWc6aGVpZ2h0OiA0DQotLS0NCg0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQpFc3RlIGVqZXJjaWNpbyBjb25zaXN0ZSBlbiByZWFsaXphciB1biBhbsOhbGlzaXMgZXhwbG9yYXRvcmlvIHNvYnJlIHVuIGRhdGFzZXQgZGUgcGVyc29uYWwgZGUgdW5hIGRldGVybWluYWRhIGVtcHJlc2EgY29uIDE0OTk5IGluc3RhbmNpYXMgeSAxMCBhdHJpYnV0b3MuIA0KDQpFbCBvYmpldGl2byBlcyBjb25zZWd1aXIgdW4gbW9kZWxvIGFkZWN1YWRvIGNvbiB1biByZXN1bHRhZG8gYWNlcHRhYmxlIGludGVycHJldGFuZG8gY2FkYSBwYXNvIGRlbCByYXpvbmFtaWVudG8gbmVjZXNhcmlvIHBhcmEgbGxlZ2FyIGFsIG9iamV0aXZvLg0KDQoqIEVsIGF0cmlidXRvIG9iamV0aXZvIGVzIExlZnQuIEluZGljYSBzaSB1biBlbXBsZWFkbyBzZSBxdWVkYSBlbiBsYSBlbXByZXNhIG8gc2UgdmEuDQoNCiMgMS5DYXJnYSBkZSBMaWJyZXJpYXMNCg0KYGBge3IgQ2FyZ2EgZGUgbGlicmVyw61hcywgZWNobz1GQUxTRX0NCmxpYnJhcnkoImRwbHlyIikNCmxpYnJhcnkoImNvcnJwbG90IikNCmxpYnJhcnkoImZhc3REdW1taWVzIikNCmxpYnJhcnkoImdncGxvdDIiKQ0KbGlicmFyeSgicnBhcnQiKQ0KbGlicmFyeSgicnBhcnQucGxvdCIpDQpsaWJyYXJ5KCJjYXJldCIpDQpsaWJyYXJ5KCJjYVRvb2xzIikNCmxpYnJhcnkoInJhbmRvbUZvcmVzdCIpDQpsaWJyYXJ5KCJjbGFzcyIpDQpsaWJyYXJ5KGxlYXBzKQ0KbGlicmFyeShtY3Rlc3QpIA0KYGBgDQoNCiMgMi5DYXJnYSBkZSBEYXRvcw0KDQpgYGB7ciBDYXJnYSBkZSBEYXRvcyAsIGVjaG89RkFMU0V9DQpyYXdfZGF0YSA8LSByZWFkLmNzdigicmVjdXJzb3NfaHVtYW5vcy5jc3YiKQ0KYGBgDQoNCiMgMy5PcmRlbmFtaWVudG8gZGUgRGF0b3MNCg0KYGBge3IgT3JkZW5hbWllbnRvIGRlIGNvbHVtbmFzIGRlIHJhd19kYXRhLCBlY2hvID0gRkFMU0V9DQpyYXdfZGF0YSA8LSByYXdfZGF0YVtjKCJsZWZ0Iiwic2FsYXJ5Iiwic2F0aXNmYWN0aW9uX2xldmVsIiwibGFzdF9ldmFsdWF0aW9uIiwibnVtYmVyX3Byb2plY3QiLCJhdmVyYWdlX21vbnRseV9ob3VycyIsInRpbWVfc3BlbmRfY29tcGFueSIsIldvcmtfYWNjaWRlbnQiLCJwcm9tb3Rpb25fbGFzdF81eWVhcnMiLCJzYWxlcyIpXQ0KYGBgDQoNCiMgNC5WaXN1YWxpemFjaW9uIGRlIGxvcyBkYXRvcw0KDQpgYGB7ciBWaXN1YWxpemFjacOzbiBEYXRhc2V0fQ0KcmF3X2RhdGENCmBgYA0KDQojIDUuUmVzdW1lbiBkZSBsb3MgZGF0b3MNCmBgYHtyIFJlc3VtZW4gZGVsIERhdGFzZXR9DQpzdW1tYXJ5KHJhd19kYXRhKQ0KYGBgDQoNCiMgNi5Fc3RydWN0dXJhIGRlIGxvcyBkYXRvcw0KYGBge3IgRXN0cnVjdHVyYSBkZWwgRGF0YXNldH0NCnN0cihyYXdfZGF0YSkNCmBgYA0KDQojIDcuQW7DoWxpc2lzIGV4cGxvcmF0b3JpbyANCg0KIyMgNywxLlJldmlzacOzbiBkZSBkYXRvcyBudWxvcw0KDQpgYGB7ciBSZXZpc2nDs24gZGUgZGF0b3MgbnVsb3N9DQpDYW50aWRhZE51bG9zIDwtIHNhcHBseShyYXdfZGF0YSwgZnVuY3Rpb24oeCkgc3VtKGlzLm5hKHgpKSkNCmRhdGEuZnJhbWUoQ2FudGlkYWROdWxvcykNCmBgYA0KKiBObyBzZSBwcmVzZW50YW4gaW5zdGFuY2lhcyBudWxhcy4gICAgDQoqIE5vIHNlIHByZXNlbnRhbiBpbnN0YW5jaWFzIGR1cGxpY2FkYXMuICAgIA0KDQojIyA3LDIuU2F0aXNmYWN0aW9uIExldmVsDQoNCmBgYHtyIFNhdGlzZmFjdGlvbiBMZXZlbH0NCnBhcihtZnJvdyA9IGMoMSwyKSkNCg0KYm94cGxvdChyYXdfZGF0YSRzYXRpc2ZhY3Rpb25fbGV2ZWwsIG1haW4gPSAiU2F0aXNmYWN0aW9uIExldmVsIikNCg0KaGlzdChyYXdfZGF0YSRzYXRpc2ZhY3Rpb25fbGV2ZWwsIG1haW4gPSAiRGlzdHJpYnVjaW9uIFNhdGlzZmFjdGlvbiBMZXZlbCIsIGZyZXEgPSBGKQ0KbGluZXMoZGVuc2l0eShyYXdfZGF0YSRzYXRpc2ZhY3Rpb25fbGV2ZWwpLCBjb2wgPSAicmVkIiwgbHdkPTIpIA0KDQpgYGANCiogU2Ugb2JzZXJ2YSBxdWUgbGEgbWF5b3IgcGFydGUgZGUgbGFzIGluc3RhbmNpYXMgcG9zZWVuIHVuIHNhdGlzZmFjdGlvbl9sZXZlbCBlbnRyZSAoMC40IHkgMS4wKS4gICAgIA0KDQojIyAgNywzLkxhc3QgRXZhbHVhdGlvbg0KDQpgYGB7ciBMYXN0IEV2YWx1YXRpb259DQpwYXIobWZyb3cgPSBjKDEsMikpDQoNCmJveHBsb3QocmF3X2RhdGEkbGFzdF9ldmFsdWF0aW9uLCBtYWluID0gIkxhc3QgRXZhbHVhdGlvbiIpDQpoaXN0KHJhd19kYXRhJGxhc3RfZXZhbHVhdGlvbiwgbWFpbiA9ICJEaXN0cmlidWNpb24gTGFzdCBFdmFsdWF0aW9uIiwgZnJlcSA9IEYpDQpsaW5lcyhkZW5zaXR5KHJhd19kYXRhJGxhc3RfZXZhbHVhdGlvbiksIGNvbCA9ICJyZWQiLCBsd2Q9MikgDQoNCmBgYA0KDQoqIFNlIG9ic2VydmEgcXVlIGxhIG1heW9yIHBhcnRlIGRlIGxhcyBpbnN0YW5jaWFzIHBvc2VlbiB1biB2YWxvciBkZSBsYXN0X2V2YWx1YXRpb24gZW50cmUgKDAuNDUgeSAxLjApLCBjb24gdW5hIG1lZGlhbmEgYWxycmVkZWRvciBkZSAwLjcuICAgICAgICANCg0KIyMgNyw0Lk51bWJlciBvZiBQcm9qZWN0DQoNCmBgYHtyIE51bWJlciBvZiBQcm9qZWN0fQ0KcGFyKG1mcm93ID0gYygxLDIpKQ0KDQpib3hwbG90KHJhd19kYXRhJG51bWJlcl9wcm9qZWN0LCBtYWluID0gIk51bWJlciBvZiBQcm9qZWN0IikNCmhpc3QocmF3X2RhdGEkbnVtYmVyX3Byb2plY3QsIG1haW4gPSAiRGlzdHJpYnVjaW9uIE51bWJlciBvZiBQcm9qZWN0IikNCmBgYA0KDQoqIFNlIG9ic2VydmEgcXVlIGxhIG1heW9yIHBhcnRlIGRlIGxhcyBpbnN0YW5jaWFzIHBvc2VlbiB1biB2YWxvciBkZSBudW1iZXJfcHJvamVjdCBlbnRyZSAoMiwgNSksIGNvbiB1bmEgbWVkaWFuYSBhbHJyZWRlZG9yIGRlIDQuICAgIA0KDQojIyA3LDUuQXZlcmFnZSBNb250bHkgSG91cnMNCg0KYGBge3IgQXZlcmFnZSBNb250bHkgSG91cnN9DQpwYXIobWZyb3cgPSBjKDEsMikpDQoNCmJveHBsb3QocmF3X2RhdGEkYXZlcmFnZV9tb250bHlfaG91cnMsIG1haW4gPSAiQXZlcmFnZSBNb250bHkgSG91cnMiKQ0KaGlzdChyYXdfZGF0YSRhdmVyYWdlX21vbnRseV9ob3VycywgbWFpbiA9ICJEaXN0cmlidWNpb24gQXZlcmFnZSBNb250bHkgSG91cnMiLCBmcmVxID0gRikNCmxpbmVzKGRlbnNpdHkocmF3X2RhdGEkYXZlcmFnZV9tb250bHlfaG91cnMpLCBjb2wgPSAicmVkIiwgbHdkPTIpIA0KDQpgYGANCg0KKiBTZSBvYnNlcnZhIHF1ZSBsYSBtYXlvciBwYXJ0ZSBkZSBsYXMgaW5zdGFuY2lhcyBwb3NlZW4gdW4gdmFsb3IgZGUgYXZlcmFnZV9tb250bHlfaG91cnMgZW50cmUgKDEyNSwgMjc1KSwgY29uIHVuYSBtZWRpYW5hIGFscnJlZGVkb3IgZGUgMjAwLg0KDQojIyA3LDYuVGltZSBTcGVuZCBDb21wYW55DQoNCmBgYHtyIFRpbWUgU3BlbmQgQ29tcGFueX0NCnBhcihtZnJvdyA9IGMoMSwyKSkNCg0KYm94cGxvdChyYXdfZGF0YSR0aW1lX3NwZW5kX2NvbXBhbnksIG1haW4gPSAiVGltZSBTcGVuZCBDb21wYW55IikNCmhpc3QocmF3X2RhdGEkdGltZV9zcGVuZF9jb21wYW55LCBtYWluID0gIkRpc3RyaWJ1Y2lvbiBUaW1lIFNwZW5kIENvbXBhbnkiKQ0KYGBgDQoNCiogU2Ugb2JzZXJ2YSBxdWUgbGEgbWF5b3IgcGFydGUgZGUgbGFzIGluc3RhbmNpYXMgcG9zZWVuIHVuIHZhbG9yIGRlIHRpbWVfc3BlbmRfY29tcGFueSBlbnRyZSAoMiwgNiksIGNvbiB1bmEgbWVkaWFuYSBhbHJyZWRlZG9yIGRlIDMsIHkgdW4gdmFsb3IgbWluaW1vIGRlIDIuIFNlIHByZXNlbnRhbiB2YWxvcmVzIGF0aXBpY29zIHBhcmEgdmFsb3JlcyBzdXBlcmlvcmVzIGEgNS4NCg0KIyMgNyw3LlNhbGFyeQ0KDQpgYGB7ciBDb2xvciBwYXJhIEhpc3RvZ3JhbWEgU2FsYXJ5fQ0KY29sb3JTYWxhcnkgPSByYWluYm93KG5sZXZlbHMoKGFzLmZhY3RvcihyYXdfZGF0YSRzYWxhcnkpKSkpDQpjb2xvcllOID0gcmFpbmJvdyhubGV2ZWxzKChhcy5mYWN0b3IocmF3X2RhdGEkV29ya19hY2NpZGVudCkpKSkNCmNvbG9yU2FsZXMgPSByYWluYm93KG5sZXZlbHMoKGFzLmZhY3RvcihyYXdfZGF0YSRzYWxlcykpKSkNCmBgYA0KDQoNCiMjIDcsOC5TYWxhcnkgeSBQcm9tb3Rpb24NCg0KYGBge3IgU2FsYXJ5IHkgUHJvbW90aW9ufQ0KcGFyKG1mcm93ID0gYygxLDIpKQ0KDQpiYXJwbG90KHN1bW1hcnkocmF3X2RhdGEkc2FsYXJ5KSwgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlICdTYWxhcnknIiwgDQogICAgICAgIGNvbD0gY29sb3JTYWxhcnkgKQ0KcGllKHN1bW1hcnkoYXMuZmFjdG9yKHJhd19kYXRhJHByb21vdGlvbl9sYXN0XzV5ZWFycykpLCBsYWJlbHMgPSBjKCJTaSIsIk5vIiksIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSAnUHJvbW90aW9uJyIsIGNvbD1jb2xvcllOKQ0KYGBgDQoNCiogU2Ugb2JzZXJ2YSBxdWUgbGEgbWF5b3IgcGFydGUgZGUgbGFzIGluc3RhbmNpYXMgcG9zZWVuIHVuIHZhbG9yIGRlIHNhbGFyeSAibG93IiB5ICJtZWRpdW0iLiAgICAgDQoqIFNlIG9ic2VydmEgcXVlIGxhIG1heW9yaWEgZGUgbGFzIGluc3RhbmNpYXMgcHJlc2VudGFuIHVuIHZhbG9yIGRlIFByb21vdGlvbiAiU2kiLiAgICANCg0KIyMgNyw5LldvcmsgQWNjaWRlbnQgeSBEaXN0cmlidWNpw7NuIGRlIExlZnQNCg0KYGBge3IgV29yayBBY2NpZGVudCB5IERpc3RyaWJ1Y2nDs24gZGUgTGVmdH0NCnBhcihtZnJvdyA9IGMoMSwyKSkNCnBpZShzdW1tYXJ5KGFzLmZhY3RvcihyYXdfZGF0YSRXb3JrX2FjY2lkZW50KSksIGxhYmVscyA9IGMoIlNpIiwiTm8iKSwgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlICdXb3JrIEFjY2lkZW50JyIsIGNvbD1jb2xvcllOKQ0KcGllKHN1bW1hcnkoYXMuZmFjdG9yKHJhd19kYXRhJGxlZnQpKSwgbGFiZWxzID0gYygiU2kiLCJObyIpLCBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgJ0xlZnQnIiwgY29sPWNvbG9yWU4pDQpgYGANCg0KKiBTZSBvYnNlcnZhIHF1ZSBsYSBtYXlvcmlhIGRlIGxhcyBpbnN0YW5jaWFzIHByZXNlbnRhbiB1biB2YWxvciBkZSBXb3JrIEFjY2lkZW50ICJTaSIuDQoqIFNlIG9ic2VydmEgcXVlIGFwcm94aW1hZGFtZW50ZSB1biA3NSUgZGUgbGFzIGluc3RhbmNpYXMgcHJlc2VudGFuIHVuIHZhbG9yIGRlIExlZnQiU2kiLiAgICANCg0KIyMgNywxMC5TYWxlcw0KDQpgYGB7ciBTYWxlc30NCmJhcnBsb3Qoc3VtbWFyeShyYXdfZGF0YSRzYWxlcyksIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSAnU2FsZXMnIiwgY29sID0gY29sb3JTYWxlcykNCmxlZ2VuZCgidG9wbGVmdCIsIHN1bW1hcnkocmF3X2RhdGEkc2FsZXMpLCBjZXggPSAwLjgsICBmaWxsID0gY29sb3JTYWxlcywgbGVnZW5kPWxldmVscyhyYXdfZGF0YSRzYWxlcykpDQpgYGANCg0KDQojIDguRXN0dWRpbyBkZSBWYXJpYWJsZXMNCg0KIyMgOCwxLkNvcnJlbGFjacOzbiByYXdfZGF0YQ0KDQpgYGB7ciBDb3JyZWxhY2nDs24gcmF3X2RhdGF9DQpjb3JycGxvdChjb3Ioc2VsZWN0KHJhd19kYXRhLCAtYygic2FsYXJ5IiwgInNhbGVzIikpKSwgdHlwZT0idXBwZXIiLCBtZXRob2Q9InBpZSIpDQpjb3Ioc2VsZWN0KHJhd19kYXRhLCAtYygic2FsYXJ5IiwgInNhbGVzIikpKQ0KYGBgDQoNCiogQXBhcmVudGVtZW50ZSBlbCBhdHJpYnV0byBvYmpldGl2byAqKmxlZnQqKiAgc2UgZW5jdWVudHJhIHJlbGFjaW9uYWRvIHByaW5jaXBhbG1lbnRlIGNvbiBsb3MgYXRyaWJ1dG9zICoqc2F0aXNmYWN0aW9uX2xldmVsLCB0aW1lX3NwZW5kX2NvbXBhbnksIHkgd29ya19hY2NpZGVudCoqLiAgICANCg0KIyMgOCwyLkNvbGluZWFsaWRhZCByYXdfZGF0YQ0KDQpgYGB7ciBDb2xpbmVhbGlkYWQgcmF3X2RhdGF9DQppbWNkaWFnKHNlbGVjdChyYXdfZGF0YSwgLWMoImxlZnQiLCJzYWxhcnkiLCAic2FsZXMiKSksIHJhd19kYXRhJGxlZnQpDQpgYGANCg0KKiBTcyBwb3NpYmxlIHF1ZSBleGlzdGEgY29saW5lYWxpZGFkIHZpbmN1bGFkbyBjb24gbG9zIGF0cmlidXRvcyAqKmxhc3RfZXZhbHVhdGlvbiwgbnVtYmVyX3Byb2plY3QsIGF2ZXJhZ2VfbW9udGx5X2hvdXJzICoqLiAgIA0KDQojIyA4LDMuRXN0dWRpbyBkZSBTYXRpc2ZhY3Rpb25fbGV2ZWwNCg0KIyMjIDgsMywxLlNhdGlzZmFjdGlvbl9sZXZlbCB2cyBMYXN0X2V2YWx1YXRpb24NCg0KYGBge3IgU2F0aXNmYWN0aW9uX2xldmVsIHZzIExhc3RfZXZhbHVhdGlvbn0NCmdncGxvdChyYXdfZGF0YSwgDQogICAgICAgYWVzKHggPSBzYXRpc2ZhY3Rpb25fbGV2ZWwsIHkgPSBsYXN0X2V2YWx1YXRpb24sIGNvbG9yID0gYXMuZmFjdG9yKGxlZnQpKSkgKyBnZW9tX3BvaW50KCkNCmBgYA0KDQoqIE1lZGlhbnRlIGVzdGUgc2NhdHRlcnBsb3Qgc2UgdmUgbGEgcmVsYWNpb24gZW50cmUgZG9zIHZhcmlhYmxlcyBjb250aW51YXM6ICoqc2F0aXNmYWN0aW9uX2xldmVsIHkgbGFzdF9ldmFsdWF0aW9uKiouICAgIA0KKiBFbCBlc3R1ZGlvIHNlIHJlYWxpemFyw6EgcGFydGljdWxhcm1lbnRlIHNvYnJlIGxhcyBpbnN0YW5jaWFzIGN1eW8gdmFsb3IgZGUgbGVmdCBzZWEgMCwgZXMgZGVjaXIgc29icmUgYXF1ZWxsb3MgZW1wbGVhZG9zIHF1ZSBjb25zZXJ2YSBsYSBlbXByZXNhLiBTZSBkaXN0aW5ndWUgcGFyYSBlc3RvIGxhIHBvYmxhY2lvbiBjdXlvIHNhdGlzZmFjdGlvbl9sZXZlbCAqKmVzIG1heW9yIGEgMC41MCoqIHkgY3V5byB2YWxvciBkZSBsYXN0X2V2YWx1YXRpb24gKiplcyBtYXlvciBhIDAuNDUqKi4gICAgDQoqIENhYmUgY29uc2lkZXJhciBxdWUgYXF1ZWxsYXMgY29uY2VudHJhY2lvbmVzIGRlIHB1bnRvcyBjb24gdmFsb3IgZGUgbGVmdCAxIChmaWd1cmEgWygwLjQsIDAuNDUpLCgwLjQ4LCAwLjQ1KSwoMC40LCAwLjU4KSwoMC40OCwgMC41OCldIHkgZmlndXJhIFsoMC4xLCAwLjc1KSwoMC4xNSwgMC43NSksKDAuMSwgMC45NSksKDAuMTUsIDAuOTUpXSkgcHVlZGVuIGNvcnJlc3BvbmRlciBhIGNhc29zIGRlIGluZGl2aWR1b3MgbXV5IGNhcGFjaXRhZG9zIHBlcm8gcG9jbyBzYXRpc2ZlY2hvcyBvIGluZGl2aWR1b3MgcG9jbyBzYXRpc2ZlY2hvcyB5IGNvbiB1biBiYWpvIGRlc2VtcGXDsW8gZW4gc3UgdWx0aW1hIGV2YWx1YWNpb24uICAgICAgDQoNCg0KIyMjIDgsMywyLlNhdGlzZmFjdGlvbl9sZXZlbCB2cyBOdW1iZXJfcHJvamVjdHMNCg0KYGBge3IgU2F0aXNmYWN0aW9uX2xldmVsIHZzIE51bWJlcl9wcm9qZWN0c30NCmdncGxvdChyYXdfZGF0YSwgDQogICAgICAgYWVzKHggPSBudW1iZXJfcHJvamVjdCwgeSA9IHNhdGlzZmFjdGlvbl9sZXZlbCwgY29sb3IgPSBhcy5mYWN0b3IobGVmdCkpKSArIGdlb21fYm94cGxvdCgpDQpgYGANCg0KKiBTZSBvYnNlcnZhIHF1ZSBhcXVlbGxhcyBpbnN0YW5jaWFzIGNvbiB1bmEgY2FudGlkYWQgZGUgcHJveWVjdG9zIHN1cGVyaW9yZXMgYSA0ICB5IGNvbiBuaXZlbGVzIGRlIGNvbmZvcm1pZGFkIGVudHJlIDAuMTAgeSAwLjcwLCBwcmVzZW50YW4gbGEgbWF5b3JpYSBkZSBsYXMgc2FsaWRhcyBkZSBsYSBlbXByZXNhLiAgICANCg0KKiBTZSBvYnNlcnZhIHF1ZSBhcXVlbGxhcyBpbnN0YW5jaWFzIGNvbiB1bmEgY2FudGlkYWQgZGUgcHJveWVjdG9zIG1lbm9yZXMgYSA0LCBwcmVzZW50YW4gdW5hIG1heW9yaWEgY29uIG5pdmVsZXMgZGUgc2F0aXNmYWNjaW9uIGVudHJlIDAuNTAgeSAwLjgwLiAgIA0KDQojIyMgOCwzLDMuU2F0aXNmYWN0aW9uX2xldmVsIHZzIE51bWJlcl9wcm9qZWN0cw0KDQpgYGB7ciBTYXRpc2ZhY3Rpb25fbGV2ZWwgdnMgQXZlcmFnZSBNb250aGx5IEhvdXJzfQ0KZ2dwbG90KHJhd19kYXRhLCANCiAgICAgICBhZXMoeCA9IGF2ZXJhZ2VfbW9udGx5X2hvdXJzLCB5ID0gc2F0aXNmYWN0aW9uX2xldmVsLCBjb2xvciA9IGFzLmZhY3RvcihsZWZ0KSkpICsgZ2VvbV9ib3hwbG90KCkNCmBgYA0KKiBTZSBvYnNlcnZhIHF1ZSBsYSBtYXlvcmlhIGRlIGxvcyBpbmRpdml1b3MgcXVlIGRlamFuIGxhIGVtcHJlc2EgbWFudGVuaWFuIHVuYSBtZWRpYSBkZSBob3JhcyBtZW5zdWFsZXMgc3VwZXJpb3JlcyBhIGxhcyAyMDAgaG9yYXMsIHBvc2libGVtZW50ZSBzZSByZWxhY2lvbmUgY29uIGVsIGFsdG8gbnVtZXJvIGRlIHByb3llY3RvcyBlbiBsb3MgcXVlIHBhcnRpY2lwYWJhbi4gICANCg0KDQojIyMgOCwzLDQuU2F0aXNmYWN0aW9uX2xldmVsIHZzIFRpbWVfU3BlbmRfaW5fQ29tcGFueShhw7FvcykNCmBgYHtyfQ0KZ2dwbG90KHJhd19kYXRhLCANCiAgICAgICBhZXMoeCA9IHRpbWVfc3BlbmRfY29tcGFueSwgeSA9IHNhdGlzZmFjdGlvbl9sZXZlbCwgY29sb3IgPSBhcy5mYWN0b3IobGVmdCkpKSArIGdlb21fYm94cGxvdCgpDQpgYGANCg0KKiBMYSBtYXlvcmlhIGRlIGxvcyBlbXBsZWFkb3MgcXVlIGFiYW5kb25hcm9uIGxhIGVtcHJlc2EsIGVzdHV2aWVyb24gbWVub3MgZGUgNSBhw7FvcyBlbiBsYSBjb21wYcOxaWEsIHkgbWFudHV2aWVyb24gdW4gbml2ZWwgZGUgc2F0aXNmYWNjaW9uIGNvbiB1bmEgbWVkaWFuYSBkZSBhbHJyZWRlZG9yIGRlIDAuNDUuDQoNCiMgOS5Nb2RlbG9zIGNvbiBSYXcgRGF0YQ0KDQojIyA5LDEuQ29uanVudG8gZGUgZGF0b3MgZGUgRW50cmVuYW1pZW50byB5IFBydWViYQ0KDQpgYGB7ciBEYXRvcyBkZSBFbnRyZW5hbWllbnRvIHkgZGUgVGVzdH0NCg0KZGF0YV90cmFpbl8xIDwtIHNhbXBsZV9mcmFjKHJhd19kYXRhLCAwLjcpDQpwcm9wLnRhYmxlKHRhYmxlKGRhdGFfdHJhaW5fMSRsZWZ0KSkNCg0KZGF0YV90ZXN0XzEgPC0gc2V0ZGlmZihyYXdfZGF0YSwgZGF0YV90cmFpbl8xKQ0KcHJvcC50YWJsZSh0YWJsZShkYXRhX3Rlc3RfMSRsZWZ0KSkNCg0KZGF0YV90cmFpbl8xJGxlZnQgPC0gZmFjdG9yKGRhdGFfdHJhaW5fMSRsZWZ0KQ0KZGF0YV90ZXN0XzEkbGVmdCA8LSBmYWN0b3IoZGF0YV90ZXN0XzEkbGVmdCkNCmBgYA0KDQoqIFNlcGFyYWNpb24gZGVsIHJhd19kYXRhIG9yaWdpbmFsIGVuOiAqKjcwJSBwYXJhIGVudHJlbmFtaWVudG8sIDMwJSBwYXJhIHRlc3RzKiouICAgDQoNCiMjIDksMi5Nb2RlbG8gZGUgQXJib2wgZGUgRGVjaXNpb24NCg0KYGBge3IgQXJib2wgZGUgZGVjaXNpb259DQp0cmVlXzEgPC0gcnBhcnQoZm9ybXVsYSA9IGxlZnQgfiAuLCBkYXRhID0gZGF0YV90cmFpbl8xKQ0KcnBhcnQucGxvdCh0cmVlXzEpDQoNCnByZWRpY2Npb24gPC0gcHJlZGljdCh0cmVlXzEsIG5ld2RhdGEgPSBkYXRhX3Rlc3RfMSwgdHlwZSA9ICJjbGFzcyIpDQpjb25mdXNpb25NYXRyaXgocHJlZGljY2lvbiwgZGF0YV90ZXN0XzFbWyJsZWZ0Il1dKQ0KYGBgDQoNCiMjIDksMy5SZWdyZXNpw7NuIExvZ2lzdGljYQ0KDQpgYGB7cn0NCmdsbS5tb2RlbCA8LSBnbG0oZm9ybXVsYSA9IGxlZnQgfiAuLCBkYXRhID0gZGF0YV90cmFpbl8xLCBmYW1pbHkgPSBiaW5vbWlhbChsb2dpdCkpDQpzdW1tYXJ5KGdsbS5tb2RlbCkNCmBgYA0KDQpgYGB7cn0NCmxnbS5wcmVkaWN0IDwtIHJvdW5kKHByZWRpY3QoZ2xtLm1vZGVsLCBkYXRhX3Rlc3RfMSwgdHlwZSA9ICJyZXNwb25zZSIpKQ0KbGdtLnByZWRpY3QgPC0gZmFjdG9yKGxnbS5wcmVkaWN0KQ0KY29uZnVzaW9uTWF0cml4KGxnbS5wcmVkaWN0LCBkYXRhX3Rlc3RfMSRsZWZ0KSANCmBgYA0KDQojIyA5LDQuUmFuZG9tIEZvcmVzdA0KDQpgYGB7cn0NCnJmLm1vZGVsIDwtIHJhbmRvbUZvcmVzdChsZWZ0fi4sIGRhdGEgPSBkYXRhX3RyYWluXzEpDQpgYGANCg0KYGBge3J9DQpyZi5wcmVkaWN0aW9uIDwtIHByZWRpY3QocmYubW9kZWwsIGRhdGFfdGVzdF8xLCB0eXBlID0gImNsYXNzIikNCmNvbmZ1c2lvbk1hdHJpeChyZi5wcmVkaWN0aW9uLCBkYXRhX3Rlc3RfMSRsZWZ0KQ0KYGBgDQoNCiMjIDksNS5MREENCg0KYGBge3J9DQpsZGEubW9kZWwgPC0gdHJhaW4obGVmdCB+LiwgZGF0YSA9IGRhdGFfdHJhaW5fMSwgbWV0aG9kID0gImxkYSIpDQpsZGEucHJlZGljdCA8LSBwcmVkaWN0KGxkYS5tb2RlbCwgZGF0YV90ZXN0XzEpDQpjb25mdXNpb25NYXRyaXgobGRhLnByZWRpY3QsIGRhdGFfdGVzdF8xJGxlZnQpDQpgYGANCg0KQSBmaW4gZGUgcG9kZXIgZXZhbHVhciBsb3MgbW9kZWxvcyBzZSB1dGlsaXphbiBsb3Mgc2lndWllbnRlcyB2YWxvcmVzIHJlc3VsdGFkbzogICAgIA0KDQoqIE1hdHJpeiBkZSBDb25mdXNpb246IE1hdHJpeiBxdWUgcGVybWl0ZSBjb21wYXJhciBsb3MgdmFsb3JlcyBvYnRlbmlkb3MgZHVyYW50ZSBsYSBwcnVlYmEgeSBlbnRyZW5hbWllbnRvIGVuIGxhcyBwcmVkaWNjaW9uZXMsIGxvcyB2YWxvcmVzIHF1ZSBzZSBtdWVzdHJhbiBjb3JyZXNwb25kZW4gYTogKip2ZXJkYWRlcm9zIHBvc2l0aXZvKiogKGVzIGxhIGNhbnRpZGFkIGRlIHBvc2l0aXZvcyBxdWUgZnVlcm9uIGNsYXNpZmljYWRvcyBjb3JyZWN0YW1lbnRlIGNvbW8gcG9zaXRpdm9zIHBvciBlbCBtb2RlbG8pLCAqKnZlcmRhZGVyb3MgbmVnYXRpdm9zKiogKGVzIGxhIGNhbnRpZGFkIGRlIG5lZ2F0aXZvcyBxdWUgZnVlcm9uIGNsYXNpZmljYWRvcyBjb3JyZWN0YW1lbnRlIGNvbW8gbmVnYXRpdm9zIHBvciBlbCBtb2RlbG8pLCAqKmZhbHNvcyBuZWdhdGl2b3MqKiAoZXMgbGEgY2FudGlkYWQgZGUgcG9zaXRpdm9zIHF1ZSBmdWVyb24gY2xhc2lmaWNhZG9zIGluY29ycmVjdGFtZW50ZSBjb21vIG5lZ2F0aXZvcykgeQ0KKipmYWxzb3MgcG9zaXRpdm9zKiogKGVzIGxhIGNhbnRpZGFkIGRlIG5lZ2F0aXZvcyBxdWUgZnVlcm9uIGNsYXNpZmljYWRvcyBpbmNvcnJlY3RhbWVudGUgY29tbyBwb3NpdGl2b3MpLiAgICAgDQoNCg0KIyMgOSw2Lkl0ZXJhY2nDs24gc29icmUgbG9zIG1vZGVsb3MNCg0KYGBge3IgSXRlcmFjaW9uIHNvYnJlIG1vZGVsbyB0cmVlfQ0KcmVzdWx0c190cmVlIDwtIG1hdHJpeChucm93PTEwLG5jb2w9MSkNCmZvciAoaSBpbiAxOjEwKXsNCiAgDQogICBkYXRhX3RyYWluXzEgPC0gc2FtcGxlX2ZyYWMocmF3X2RhdGEsIDAuNykNCiAgIHByb3AudGFibGUodGFibGUoZGF0YV90cmFpbl8xJGxlZnQpKQ0KICANCiAgIGRhdGFfdGVzdF8xIDwtIHNldGRpZmYocmF3X2RhdGEsIGRhdGFfdHJhaW5fMSkNCiAgIHByb3AudGFibGUodGFibGUoZGF0YV90ZXN0XzEkbGVmdCkpDQogIA0KICAgZGF0YV90cmFpbl8xJGxlZnQgPC0gZmFjdG9yKGRhdGFfdHJhaW5fMSRsZWZ0KQ0KICAgZGF0YV90ZXN0XzEkbGVmdCA8LSBmYWN0b3IoZGF0YV90ZXN0XzEkbGVmdCkNCiAgIA0KICAgdHJlZV8xIDwtIHJwYXJ0KGZvcm11bGEgPSBsZWZ0IH4gLiwgZGF0YSA9IGRhdGFfdHJhaW5fMSkNCiAgIHByZWRpY2Npb24gPC0gcHJlZGljdCh0cmVlXzEsIG5ld2RhdGEgPSBkYXRhX3Rlc3RfMSwgdHlwZSA9ICJjbGFzcyIpDQogICByZXNfdHJlZSA8LSBjb25mdXNpb25NYXRyaXgocHJlZGljY2lvbiwgZGF0YV90ZXN0XzFbWyJsZWZ0Il1dKQ0KICAgcmVzdWx0c190cmVlW2ksXSA8LSByZXNfdHJlZSRvdmVyYWxsWyJBY2N1cmFjeSJdDQp9DQpgYGANCg0KKiBTZSBvYnRpZW5lIGxhIG1lZGlhIGRlIDEwIG9ic2VydmFjaW9uZXMgZGVsIG1vZGVsbyBwYXJhIHVuYSBwb3N0ZXJpb3IgY29tcGFyYWNpw7NuIGRlbCBBY2N1cmFjeSBkZWwgTW9kZWxvLiAgICANCg0KYGBge3IgSXRlcmFjaW9uIHNvYnJlIG1vZGVsbyBMb2dpc3RpY299DQpyZXN1bHRzX2xvZyA8LSBtYXRyaXgobnJvdz0xMCxuY29sPTEpDQpmb3IgKGkgaW4gMToxMCl7DQogIA0KICAgZGF0YV90cmFpbl8xIDwtIHNhbXBsZV9mcmFjKHJhd19kYXRhLCAwLjcpDQogICBwcm9wLnRhYmxlKHRhYmxlKGRhdGFfdHJhaW5fMSRsZWZ0KSkNCiAgDQogICBkYXRhX3Rlc3RfMSA8LSBzZXRkaWZmKHJhd19kYXRhLCBkYXRhX3RyYWluXzEpDQogICBwcm9wLnRhYmxlKHRhYmxlKGRhdGFfdGVzdF8xJGxlZnQpKQ0KICANCiAgIGRhdGFfdHJhaW5fMSRsZWZ0IDwtIGZhY3RvcihkYXRhX3RyYWluXzEkbGVmdCkNCiAgIGRhdGFfdGVzdF8xJGxlZnQgPC0gZmFjdG9yKGRhdGFfdGVzdF8xJGxlZnQpDQogICANCiAgIGdsbS5tb2RlbCA8LSBnbG0oZm9ybXVsYSA9IGxlZnQgfiAuLCBkYXRhID0gZGF0YV90cmFpbl8xLCBmYW1pbHkgPSAiYmlub21pYWwiKQ0KICAgbGdtLnByZWRpY3QgPC0gcm91bmQocHJlZGljdChnbG0ubW9kZWwsIGRhdGFfdGVzdF8xLCB0eXBlID0gInJlc3BvbnNlIikpDQogICBsZ20ucHJlZGljdCA8LSBmYWN0b3IobGdtLnByZWRpY3QpDQogICByZXNfbGdtID0gY29uZnVzaW9uTWF0cml4KGxnbS5wcmVkaWN0LCBkYXRhX3Rlc3RfMSRsZWZ0KSANCiAgIHJlc3VsdHNfbG9nW2ksXSA8LSByZXNfbGdtJG92ZXJhbGxbIkFjY3VyYWN5Il0NCn0NCmBgYA0KDQoqIFNlIG9idGllbmUgbGEgbWVkaWEgZGUgMTAgb2JzZXJ2YWNpb25lcyBkZWwgbW9kZWxvIHBhcmEgdW5hIHBvc3RlcmlvciBjb21wYXJhY2nDs24gZGVsIEFjY3VyYWN5IGRlbCBNb2RlbG8uICAgIA0KDQpgYGB7ciBJdGVyYWNpb24gc29icmUgbW9kZWxvIFJhbmRvbSBGb3Jlc3R9DQpyZXN1bHRzX3JmIDwtIG1hdHJpeChucm93PTEwLG5jb2w9MSkNCmZvciAoaSBpbiAxOjEwKXsNCiAgDQogICBkYXRhX3RyYWluXzEgPC0gc2FtcGxlX2ZyYWMocmF3X2RhdGEsIDAuNykNCiAgIHByb3AudGFibGUodGFibGUoZGF0YV90cmFpbl8xJGxlZnQpKQ0KICANCiAgIGRhdGFfdGVzdF8xIDwtIHNldGRpZmYocmF3X2RhdGEsIGRhdGFfdHJhaW5fMSkNCiAgIHByb3AudGFibGUodGFibGUoZGF0YV90ZXN0XzEkbGVmdCkpDQogIA0KICAgZGF0YV90cmFpbl8xJGxlZnQgPC0gZmFjdG9yKGRhdGFfdHJhaW5fMSRsZWZ0KQ0KICAgZGF0YV90ZXN0XzEkbGVmdCA8LSBmYWN0b3IoZGF0YV90ZXN0XzEkbGVmdCkNCiAgIA0KICAgcmYubW9kZWwgPC0gcmFuZG9tRm9yZXN0KGxlZnR+LiwgZGF0YSA9IGRhdGFfdHJhaW5fMSkNCiAgIHJmLnByZWRpY3Rpb24gPC0gcHJlZGljdChyZi5tb2RlbCwgZGF0YV90ZXN0XzEsIHR5cGUgPSAiY2xhc3MiKQ0KICAgcmVzX3JhbmRvbSA9IGNvbmZ1c2lvbk1hdHJpeChyZi5wcmVkaWN0aW9uLCBkYXRhX3Rlc3RfMSRsZWZ0KQ0KICAgcmVzdWx0c19yZltpLF0gPC0gcmVzX3JhbmRvbSRvdmVyYWxsWyJBY2N1cmFjeSJdDQp9DQpgYGANCg0KKiBTZSBvYnRpZW5lIGxhIG1lZGlhIGRlIDEwIG9ic2VydmFjaW9uZXMgZGVsIG1vZGVsbyBwYXJhIHVuYSBwb3N0ZXJpb3IgY29tcGFyYWNpw7NuIGRlbCBBY2N1cmFjeSBkZWwgTW9kZWxvLiAgICANCg0KYGBge3IgSXRlcmFjaW9uIHNvYnJlIG1vZGVsbyBMREF9DQpyZXN1bHRzX2xkYSA8LSBtYXRyaXgobnJvdz0xMCxuY29sPTEpDQpmb3IgKGkgaW4gMToxMCl7DQogIA0KICAgZGF0YV90cmFpbl8xIDwtIHNhbXBsZV9mcmFjKHJhd19kYXRhLCAwLjcpDQogICBwcm9wLnRhYmxlKHRhYmxlKGRhdGFfdHJhaW5fMSRsZWZ0KSkNCiAgDQogICBkYXRhX3Rlc3RfMSA8LSBzZXRkaWZmKHJhd19kYXRhLCBkYXRhX3RyYWluXzEpDQogICBwcm9wLnRhYmxlKHRhYmxlKGRhdGFfdGVzdF8xJGxlZnQpKQ0KICANCiAgIGRhdGFfdHJhaW5fMSRsZWZ0IDwtIGZhY3RvcihkYXRhX3RyYWluXzEkbGVmdCkNCiAgIGRhdGFfdGVzdF8xJGxlZnQgPC0gZmFjdG9yKGRhdGFfdGVzdF8xJGxlZnQpDQogICANCiAgIGxkYS5tb2RlbCA8LSB0cmFpbihsZWZ0IH4uLCBkYXRhID0gZGF0YV90cmFpbl8xLCBtZXRob2QgPSAibGRhIikNCiAgIGxkYS5wcmVkaWN0IDwtIHByZWRpY3QobGRhLm1vZGVsLCBkYXRhX3Rlc3RfMSkNCiAgIHJlc19sZGEgPSBjb25mdXNpb25NYXRyaXgobGRhLnByZWRpY3QsIGRhdGFfdGVzdF8xJGxlZnQpDQogICANCiAgIHJlc3VsdHNfbGRhW2ksXSA8LSByZXNfbGRhJG92ZXJhbGxbIkFjY3VyYWN5Il0NCn0NCmBgYA0KDQp8IEFyYm9sIGRlIERlY2lzaW9uIHwgUmVncmVzaW9uIExvZ2lzdGljYSB8IFJhbmRvbSBGb3Jlc3QgfCBMREEgfA0KfDotOnw6LTp8Oi06fDotOnwNCnxgciBtZWFuKHJlc3VsdHNfdHJlZSlgfGByIG1lYW4ocmVzdWx0c19sb2cpYHxgciBtZWFuKHJlc3VsdHNfcmYpYHxgciBtZWFuKHJlc3VsdHNfbGRhKWB8DQoNCg0KIyAxMC5MaW1waWV6YSBkZSBkYXRvcw0KDQpgYGB7ciBFbmNvZGluZ30NCmRhdGFfc2V0IDwtIHJhd19kYXRhDQpkYXRhX3NldCA8LSBkdW1teV9jb2xzKGRhdGFfc2V0LCBzZWxlY3RfY29sdW1ucyA9IGMoInNhbGVzIikpDQpkYXRhX3NldCRzYWxlcyA9IE5VTEwNCg0KZGF0YV9zZXQgPC0gZHVtbXlfY29scyhkYXRhX3NldCwgc2VsZWN0X2NvbHVtbnMgPSBjKCJzYWxhcnkiKSkNCmRhdGFfc2V0JHNhbGFyeSA9IE5VTEwNCmBgYA0KDQoqIERhZGEgbGEgbmF0dXJhbGV6YSBkZSBsb3MgYXRyaWJ1dG9zIHNhbGVzIHkgc2FsYXJ5IChmYWN0b3JlcyBjaGFyKSwgc2UgcHJvY2VkZSBhIHJlYWxpemFyIHVuIHByb2Nlc28gZGUgZW5jb2RpbmcgcGFyYSBzdSBwb3N0ZXJpb3IgbWFuaXB1bGFjaW9uLiBTZSBlbGltaW5hIGxhIGNvbHVtbmEgb3JpZ2luYWwgYSBmaW4gZGUgZXZpdGFyIHByb2JsZW1hcyBkZSBjb2xpbmVhbGlkYWQuICAgIA0KDQpgYGB7cn0NCmRhdGFfc2V0IDwtIGRhdGFfc2V0ICU+JSBmaWx0ZXIoc2F0aXNmYWN0aW9uX2xldmVsID4gMC40OCkgJT4lIGZpbHRlcihsYXN0X2V2YWx1YXRpb24gPiAwLjUwKQ0KYGBgDQoNCiogRW4gbGEgb2JzZXJ2YWNpb24gZGUgKipzYXRpc2ZhY3Rpb25fbGV2ZWwgdnMgbGFzdF9ldmFsdWF0aW9uKiogc2UgcmVzb2x2aW8gdHJhYmFqYXIgc29icmUgbGEgbWF5b3IgY29uY2VudHJhY2lvbiBkZSBlbXBsZWFkb3MgcXVlIHBlcm1hbmVjZW4gZW4gbGEgb3JnYW5pemFjacOzbi4gICAgDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGFfc2V0LCANCiAgICAgICBhZXMoeCA9IHNhdGlzZmFjdGlvbl9sZXZlbCwgeSA9IGxhc3RfZXZhbHVhdGlvbiwgY29sb3IgPSBhcy5mYWN0b3IobGVmdCkpKSArIGdlb21fcG9pbnQoKQ0KYGBgDQoNCiMjIDEwLDEuTWF0cml6IGRlIGNvcnJlbGFjacOzbg0KDQpgYGB7ciBDb3JyZWxhY2nDs259DQpjb3JycGxvdChjb3IoZGF0YV9zZXQpLCB0eXBlPSJ1cHBlciIsIG1ldGhvZD0icGllIikNCmBgYA0KDQoqIEFwYXJlbnRlbWVudGUgbGEgdmFyaWFibGUgbGVmdCBzZSBlbmN1ZW50cmEgcHJpbmNpcGFsbWVudGUgcmVsYWNpb25hZGEgY29uICoqc2F0aXNmYWN0aW9uX2xldmVsLCBsYXN0X2V2YWx1YXRpb24sIG51bWJlcl9wcm9qZWN0LCBhdmVyYWdlX21vbnRseV9ob3VycyB5IHRpbWVfc3BlbmRfY29tcGFueSoqLg0KDQpgYGB7cn0NCmltY2RpYWcoZGF0YV9zZXQsIGRhdGFfc2V0JGxlZnQpDQpgYGANCiogU2UgZGV0ZWN0YW4gdmFyaWFibGVzIGNvbiB1biBhbHRvIHZhbG9yIGRlIFZJRiwgYXF1ZWxsYXMgdmFyaWFibGVzIHByb2NlZGVudGVzIGRlbCBwcm9jZXNvIGRlIGVuY29kaW5nLCBhcGFyZW50ZW1lbnRlIG5vIGFwb3J0YW4gbXVjaGEgcmVsZXZhbmNpYSBhbCBtb2RlbG8geSBzZSBkZWNpZGUgZXhjbHVpcmxhcyBkZWwgbWlzbW8uICAgIA0KDQpgYGB7cn0NCg0KbW9kZWxzIDwtIHJlZ3N1YnNldHMobGVmdH4uLCBkYXRhID0gZGF0YV9zZXQsIG52bWF4ID0gNSkNCnN1bW1hcnkobW9kZWxzKQ0KcGxvdChtb2RlbHMpDQoNCmBgYA0KDQoqIFNlIGxsZXZhIGEgY2FibyB1bmEgc2VsZWNjaW9uIGRlIHZhcmlhYmxlcyBkb25kZSBzZSBzdXBvbmUgcXVlIGxhcyB2YXJpYWJsZXMgbWFzIHJlbGV2YW50ZXMgcGFyYSBlbCBtb2RlbG8gc29uOiAqKnNhdGlzZmFjdGlvbl9sZXZlbC4gbGFzdF9ldmFsdWF0aW9uLCBudW1iZXJfcHJvamVjdCwgYXZlcmFnZV9tb250bHlfaG91cnMsIHRpbWVfc3BlbmRfY29tcGFueSwgV29ya19hY2NpZGVudCoqLiAgICANCg0KYGBge3J9DQpkYXRhX3NldF8xIDwtIHNlbGVjdChkYXRhX3NldCwgYygibGVmdCIsICJzYXRpc2ZhY3Rpb25fbGV2ZWwiLCAibGFzdF9ldmFsdWF0aW9uIiwgIm51bWJlcl9wcm9qZWN0IiwgImF2ZXJhZ2VfbW9udGx5X2hvdXJzIiwgInRpbWVfc3BlbmRfY29tcGFueSIsICJXb3JrX2FjY2lkZW50IikpDQoNCmRpbShkYXRhX3NldF8xKS9kaW0ocmF3X2RhdGEpDQpgYGANCg0KKiBUcmFzIGxhIGxpbXBpZXphIGRlIGxvcyBkYXRvcywgc2UgcmVjb3J0byBlbCBkYXRhc2V0IG9yaWdpbmFsIGVuIHVuICoqMzUlKiouICAgIA0KDQoNCiMjIDEwLDIuTWF0cml6IGRlIGNvcnJlbGFjacOzbg0KDQpgYGB7ciBDb3JyZWxhY2nDs24gM30NCmNvcnJwbG90KGNvcihkYXRhX3NldF8xKSwgdHlwZT0idXBwZXIiLCBtZXRob2Q9InBpZSIpDQpgYGANCg0KYGBge3J9DQppbWNkaWFnKGRhdGFfc2V0XzEsIGRhdGFfc2V0XzEkbGVmdCkNCmBgYA0KKiBObyBzZSBvYnNlcnZhbiByYXN0cm9zIGRlIGNvbGluZWFsaWRhZCwgZGFkbyBxdWUgZWwgdmFsb3IgZGUgVklGIGVzIGNlcmNhZG8gYSAxLg0KDQpgYGB7ciBJdGVyYWNpb24gc29icmUgbW9kZWxvIHRyZWUgMn0NCnJlc3VsdHNfdHJlZSA8LSBtYXRyaXgobnJvdz0xMCxuY29sPTEpDQpmb3IgKGkgaW4gMToxMCl7DQogIA0KICAgZGF0YV90cmFpbl8yIDwtIHNhbXBsZV9mcmFjKGRhdGFfc2V0XzEsIDAuNykNCiAgIHByb3AudGFibGUodGFibGUoZGF0YV90cmFpbl8yJGxlZnQpKQ0KICANCiAgIGRhdGFfdGVzdF8yIDwtIHNldGRpZmYoZGF0YV9zZXRfMSwgZGF0YV90cmFpbl8yKQ0KICAgcHJvcC50YWJsZSh0YWJsZShkYXRhX3Rlc3RfMiRsZWZ0KSkNCiAgDQogICBkYXRhX3RyYWluXzIkbGVmdCA8LSBmYWN0b3IoZGF0YV90cmFpbl8yJGxlZnQpDQogICBkYXRhX3Rlc3RfMiRsZWZ0IDwtIGZhY3RvcihkYXRhX3Rlc3RfMiRsZWZ0KQ0KICAgDQogICB0cmVlXzEgPC0gcnBhcnQoZm9ybXVsYSA9IGxlZnQgfiAuLCBkYXRhID0gZGF0YV90cmFpbl8yKQ0KICAgcHJlZGljY2lvbiA8LSBwcmVkaWN0KHRyZWVfMSwgbmV3ZGF0YSA9IGRhdGFfdGVzdF8yLCB0eXBlID0gImNsYXNzIikNCiAgIHJlc190cmVlIDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkaWNjaW9uLCBkYXRhX3Rlc3RfMltbImxlZnQiXV0pDQogICANCiAgIHJlc3VsdHNfdHJlZVtpLF0gPC0gcmVzX3RyZWUkb3ZlcmFsbFsiQWNjdXJhY3kiXQ0KfQ0KYGBgDQoNCmBgYHtyIEl0ZXJhY2lvbiBzb2JyZSBtb2RlbG8gTG9naXN0aWNvIDJ9DQpyZXN1bHRzX2xvZyA8LSBtYXRyaXgobnJvdz0xMCxuY29sPTEpDQpmb3IgKGkgaW4gMToxMCl7DQogIA0KICAgZGF0YV90cmFpbl8yIDwtIHNhbXBsZV9mcmFjKGRhdGFfc2V0XzEsIDAuNykNCiAgIHByb3AudGFibGUodGFibGUoZGF0YV90cmFpbl8yJGxlZnQpKQ0KICANCiAgIGRhdGFfdGVzdF8yIDwtIHNldGRpZmYoZGF0YV9zZXRfMSwgZGF0YV90cmFpbl8yKQ0KICAgcHJvcC50YWJsZSh0YWJsZShkYXRhX3Rlc3RfMiRsZWZ0KSkNCiAgDQogICBkYXRhX3RyYWluXzIkbGVmdCA8LSBmYWN0b3IoZGF0YV90cmFpbl8yJGxlZnQpDQogICBkYXRhX3Rlc3RfMiRsZWZ0IDwtIGZhY3RvcihkYXRhX3Rlc3RfMiRsZWZ0KQ0KICAgDQogICBnbG0ubW9kZWwgPC0gZ2xtKGZvcm11bGEgPSBsZWZ0IH4gLiwgZGF0YSA9IGRhdGFfdHJhaW5fMiwgZmFtaWx5ID0gImJpbm9taWFsIikNCiAgIGxnbS5wcmVkaWN0IDwtIHJvdW5kKHByZWRpY3QoZ2xtLm1vZGVsLCBkYXRhX3Rlc3RfMiwgdHlwZSA9ICJyZXNwb25zZSIpKQ0KICAgbGdtLnByZWRpY3QgPC0gZmFjdG9yKGxnbS5wcmVkaWN0KQ0KICAgcmVzX2xnbSA9IGNvbmZ1c2lvbk1hdHJpeChsZ20ucHJlZGljdCwgZGF0YV90ZXN0XzIkbGVmdCkgDQogICByZXN1bHRzX2xvZ1tpLF0gPC0gcmVzX2xnbSRvdmVyYWxsWyJBY2N1cmFjeSJdDQp9DQpgYGANCg0KYGBge3IgSXRlcmFjaW9uIHNvYnJlIG1vZGVsbyBSYW5kb20gRm9yZXN0IDJ9DQpyZXN1bHRzX3JmIDwtIG1hdHJpeChucm93PTEwLG5jb2w9MSkNCmZvciAoaSBpbiAxOjEwKXsNCiAgDQogICBkYXRhX3RyYWluXzIgPC0gc2FtcGxlX2ZyYWMoZGF0YV9zZXRfMSwgMC43KQ0KICAgcHJvcC50YWJsZSh0YWJsZShkYXRhX3RyYWluXzIkbGVmdCkpDQogIA0KICAgZGF0YV90ZXN0XzIgPC0gc2V0ZGlmZihkYXRhX3NldF8xLCBkYXRhX3RyYWluXzIpDQogICBwcm9wLnRhYmxlKHRhYmxlKGRhdGFfdGVzdF8yJGxlZnQpKQ0KICANCiAgIGRhdGFfdHJhaW5fMiRsZWZ0IDwtIGZhY3RvcihkYXRhX3RyYWluXzIkbGVmdCkNCiAgIGRhdGFfdGVzdF8yJGxlZnQgPC0gZmFjdG9yKGRhdGFfdGVzdF8yJGxlZnQpDQogICANCiAgIHJmLm1vZGVsIDwtIHJhbmRvbUZvcmVzdChsZWZ0fi4sIGRhdGEgPSBkYXRhX3RyYWluXzIpDQogICByZi5wcmVkaWN0aW9uIDwtIHByZWRpY3QocmYubW9kZWwsIGRhdGFfdGVzdF8yLCB0eXBlID0gImNsYXNzIikNCiAgIHJlc19yYW5kb20gPSBjb25mdXNpb25NYXRyaXgocmYucHJlZGljdGlvbiwgZGF0YV90ZXN0XzIkbGVmdCkNCiAgIHJlc3VsdHNfcmZbaSxdIDwtIHJlc19yYW5kb20kb3ZlcmFsbFsiQWNjdXJhY3kiXQ0KfQ0KYGBgDQoNCg0KYGBge3IgSXRlcmFjaW9uIHNvYnJlIG1vZGVsbyBMREEgMn0NCnJlc3VsdHNfbGRhIDwtIG1hdHJpeChucm93PTEwLG5jb2w9MSkNCmZvciAoaSBpbiAxOjEwKXsNCiAgDQogICBkYXRhX3RyYWluXzIgPC0gc2FtcGxlX2ZyYWMoZGF0YV9zZXRfMSwgMC43KQ0KICAgcHJvcC50YWJsZSh0YWJsZShkYXRhX3RyYWluXzIkbGVmdCkpDQogIA0KICAgZGF0YV90ZXN0XzIgPC0gc2V0ZGlmZihkYXRhX3NldF8xLCBkYXRhX3RyYWluXzIpDQogICBwcm9wLnRhYmxlKHRhYmxlKGRhdGFfdGVzdF8yJGxlZnQpKQ0KICANCiAgIGRhdGFfdHJhaW5fMiRsZWZ0IDwtIGZhY3RvcihkYXRhX3RyYWluXzIkbGVmdCkNCiAgIGRhdGFfdGVzdF8yJGxlZnQgPC0gZmFjdG9yKGRhdGFfdGVzdF8yJGxlZnQpDQogICANCiAgIGxkYS5tb2RlbCA8LSB0cmFpbihsZWZ0IH4uLCBkYXRhID0gZGF0YV90cmFpbl8yLCBtZXRob2QgPSAibGRhIikNCiAgIGxkYS5wcmVkaWN0IDwtIHByZWRpY3QobGRhLm1vZGVsLCBkYXRhX3Rlc3RfMikNCiAgIHJlc19sZGEgPSBjb25mdXNpb25NYXRyaXgobGRhLnByZWRpY3QsIGRhdGFfdGVzdF8yJGxlZnQpDQogICANCiAgIHJlc3VsdHNfbGRhW2ksXSA8LSByZXNfbGRhJG92ZXJhbGxbIkFjY3VyYWN5Il0NCn0NCmBgYA0KDQojIyAxMCwzLkFjY3VyYWN5ICAgDQoNCnwgQXJib2wgZGUgRGVjaXNpb24gfCBSZWdyZXNpb24gTG9naXN0aWNhIHwgUmFuZG9tIEZvcmVzdCB8IExEQSB8DQp8Oi06fDotOnw6LTp8Oi06fA0KfGByIG1lYW4ocmVzdWx0c190cmVlKWB8YHIgbWVhbihyZXN1bHRzX2xvZylgfGByIG1lYW4ocmVzdWx0c19yZilgfGByIG1lYW4ocmVzdWx0c19sZGEpYHwNCg0KKipUcmFzIGxhIGxpbXBpZXphIGRlIGRhdG9zIHkgc2VsZWNjaW9uIGRlIHZhcmlhYmxlcyBzZSBvYnR1dmllcm9uIHZhbG9yZXMgYWNlcHRhYmxlcyBkZSBhY2N1cmFjeSoqLiBTaW4gZW1iYXJnbyBkZWJlbiBldmFsdWFyc2UgbG9zIG1vZGVsb3MgZW4gZm9ybWEgaW5kaXZpZHVhbCBwYXJhIHNlbGVjY2lvbmFyIGVsIG1vZGVsbyBtYXMgYWRlY3VhZG8uICAgIA==