Modelo Logit

El modelo logit se utiliza cuando la variable dependiente es una variable cualitativa. En ocasiones a los modelos que abordan esta situación se les conoce como modelos de clasificación.

Tenemos dos submuestras para trabajar, la base de datos funds.train que tiene 2,400 observaciones y 21 variables y la base de datos funds.validation que tiene 600 variables y las mismas 21 variables.

dim(funds.train)
[1] 2400   21
dim(funds.validation)
[1] 600  21

La base de datos funds.train la usaremos para realizar las estimaciones de los coeficientes, mientras que la base de datos funds.validation la usaremos para evaluar la efectividad en la predicción de los modelos generados.

Para realizar correr un modelo logit se utiliza la función glm() que utiliza la siguiente sintaxis:

glm(Y ~ X1 + X2 + ... + Xn, data = base_de_datos, family = "binomial")

donde:

  • Y = variable cualitativa.
  • X1, X2, …. , Xn = Variables independientes.
  • base_de_datos = es la base de datos que se utilizará para hacer la corrida.
  • family = binomial = es el argumento que indica que la corrida sea mediante un modelo logit.

En nuestro ejemplo queremos definir las variables que me permiten identificar si un individuo es donador, la variable target en nuestra base de datos.

Con base en esto, el primer modelo vamos a considerar todas las variables como variables independientes.

logit1.fit <- glm(funds.train$target ~ ., data=funds.train, family="binomial")
summary(logit1.fit)

Call:
glm(formula = funds.train$target ~ ., family = "binomial", data = funds.train)

Deviance Residuals: 
     Min        1Q    Median        3Q       Max  
-1.69941  -1.15796   0.00159   1.14873   1.74170  

Coefficients:
                      Estimate Std. Error z value Pr(>|z|)    
(Intercept)         -1.613e+00  5.730e-01  -2.814  0.00489 ** 
zipconvert2Yes      -1.376e+01  2.663e+02  -0.052  0.95878    
zipconvert3No        1.375e+01  2.663e+02   0.052  0.95882    
zipconvert4Yes      -1.388e+01  2.663e+02  -0.052  0.95842    
zipconvert5Yes      -1.377e+01  2.663e+02  -0.052  0.95876    
homeownerNo          2.810e-02  1.105e-01   0.254  0.79925    
num_child2           3.785e-03  2.447e-01   0.015  0.98766    
num_child3           8.313e-01  4.459e-01   1.864  0.06231 .  
num_child4           3.922e-01  5.701e-01   0.688  0.49148    
num_child5           1.347e+01  5.354e+02   0.025  0.97992    
income2             -7.574e-02  1.761e-01  -0.430  0.66711    
income3             -1.864e-01  1.943e-01  -0.959  0.33745    
income4             -1.220e-01  1.681e-01  -0.726  0.46800    
income5             -3.247e-01  1.812e-01  -1.792  0.07311 .  
income6             -1.673e-01  2.161e-01  -0.774  0.43874    
income7             -2.875e-01  2.220e-01  -1.296  0.19513    
femaleNo             8.421e-02  8.672e-02   0.971  0.33151    
wealth1             -3.628e-01  2.997e-01  -1.211  0.22601    
wealth2             -1.576e-01  3.057e-01  -0.515  0.60631    
wealth3              6.425e-02  2.984e-01   0.215  0.82953    
wealth4              3.319e-02  3.029e-01   0.110  0.91274    
wealth5              1.096e-01  2.887e-01   0.380  0.70417    
wealth6             -3.831e-01  3.118e-01  -1.229  0.21909    
wealth7             -4.402e-01  3.038e-01  -1.449  0.14734    
wealth8             -2.631e-01  2.562e-01  -1.027  0.30445    
wealth9             -2.203e-01  3.063e-01  -0.719  0.47208    
home_value          -2.145e-04  8.054e-05  -2.663  0.00775 ** 
med_fam_inc         -4.679e-04  1.048e-03  -0.446  0.65534    
avg_fam_inc          1.630e-03  1.139e-03   1.430  0.15260    
pct_lt15k            2.234e-03  5.157e-03   0.433  0.66487    
num_prom            -3.804e-03  3.364e-03  -1.131  0.25817    
lifetime_gifts      -5.372e-04  7.374e-04  -0.729  0.46625    
largest_gift        -2.049e-03  4.890e-03  -0.419  0.67513    
last_gift            8.264e-03  8.367e-03   0.988  0.32332    
months_since_donate  5.811e-02  1.139e-02   5.103 3.34e-07 ***
time_lag            -9.803e-03  7.798e-03  -1.257  0.20871    
avg_gift             7.858e-03  1.249e-02   0.629  0.52912    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 3327.1  on 2399  degrees of freedom
Residual deviance: 3232.4  on 2363  degrees of freedom
AIC: 3306.4

Number of Fisher Scoring iterations: 12

A pesar de que existe una gran cantidad de variables que no son significativas, seguiremos con el ejemplo. Para hacer esto vamos a comparar dos vectores o valores, los valores que hay en la base de datos funds.validation y nuestras predicciones.

Lo primero que haremos será generar un vector donde almacenaremos las predicciones realizadas con base en los resultados de nuestro modelo usando la función predict().

La sintaxis de la función predict() que vamos a usar es la siguiente:

predict(objeto_modelo, base_insumo, type="response")

donde:

  • objeto_modelo = el objeto con el que se guardo los resultados de un modelo en específico.
  • base_insumo = es la base de datos donde se sustituiran los valores de las Xs y se calculará la variable dependiente.
  • type=“response” = es la indicación para que se realice el cálculo de las probabilidades una vez sustituidos los valores de las variables independientes.

El resultado de esta función son las probabilidades del modelo logit.

logit1.predict = predict(logit1.fit, funds.validation, type="response")
head(logit1.predict)
        1         2         3         4         5         6 
0.5682940 0.5048864 0.5579361 0.5010086 0.5106020 0.4201963 

Ya que tenemos las probabilidades, es necesario asignar una de las dos opciones, donador o no donador.

Para esto, vamos a crear un vector lleno con la clasificación “No Donor”, y después, con base en las probabilidades, asginar a las observaciones que representa “Donor”.

Para esto usamos la función rep() con la siguiente sintaxis:

rep(valor, cantidad)

donde:

  • valor = es el valor o los caracteres que se van a repetir.
  • cantidad = es la cantidad de veces que el valor se va a generar.
logit1.class = rep("No Donor", length(funds.validation$target))
head(logit1.class)
[1] "No Donor" "No Donor" "No Donor" "No Donor" "No Donor" "No Donor"
tail(logit1.class)
[1] "No Donor" "No Donor" "No Donor" "No Donor" "No Donor" "No Donor"

Una vez que tenemos el vector lleno de la palabra “No Donor” es necesario sustituir las observaciones que tengan una probabilidad mayor a X con la palabra “Donor”. En este caso usamos como probabilidad 50%.

logit1.class[logit1.predict >= 0.5] = "Donor"
head(logit1.predict)
        1         2         3         4         5         6 
0.5682940 0.5048864 0.5579361 0.5010086 0.5106020 0.4201963 
head(logit1.class)
[1] "Donor"    "Donor"    "Donor"    "Donor"    "Donor"    "No Donor"
tail(logit1.predict)
      595       596       597       598       599       600 
0.5298910 0.3571833 0.6377934 0.4474450 0.3923074 0.5479109 
tail(logit1.class)
[1] "Donor"    "No Donor" "Donor"    "No Donor" "No Donor" "Donor"   

Ya tenenemos el vector con las predicciones logit1.class es momento de compararlos con las observaciones en nuestra base de datos de prueba y ver su efectividad.

La primera forma de evaluar es utilizando una confusion matrix.

table(logit1.class, funds.validation$target)
            
logit1.class Donor No Donor
    Donor      130      157
    No Donor   171      142

Otra forma es simplemente encontrar el porcentaje de acierto

mean(logit1.class==funds.validation$target)
[1] 0.4533333

Como función

Como existen varias probabilidades que podemos asignar como parámetro de decisión para decir si es “Donor”, vamos a realizar una función que nos permita encontrar el porcentaje de efectividad a partir de la probabildiad

logit.fun <- function(prob){
logit1.class[logit1.predict >= prob] = "Donor"
print(table(logit1.class, funds.validation$target))
print(mean(logit1.class==funds.validation$target))
}

Creamos la función logit.fun que solo tiene un argumento, la probabilidad que sirve como criterio, al asignar como “Donor” a cada una de las observaciones.

logit.fun(.475)
            
logit1.class Donor No Donor
    Donor      179      194
    No Donor   122      105
[1] 0.4733333

Exportar resultados

Para exportar las predicciones de nuestros resultados como un archivo *csv es necesario realizar una serie de sencillos pasos.

El primero es realizar la predicción de las probabilidades, en este caso los valores que usaremos como variables independientes están en una base de datos llamada “future”.

logit1.csv <- predict(logit1.fit, future, type="response")
head(logit1.csv)
        1         2         3         4         5         6 
0.4449338 0.5114931 0.4594329 0.6381711 0.3222896 0.6578001 

Hay que generar el vector donde se depositaran los resultados de la clasificación con base en las probabilidades calculadas. En esta ocasión asignaremos el nombre de “logit1.csv”. Estos pasos son idénticos a los realizados previamente.

logit1.csv.class = rep("No Donor", length(future$home_value))
head(logit1.csv.class)
[1] "No Donor" "No Donor" "No Donor" "No Donor" "No Donor" "No Donor"

Ya que generamos el vector con solo la palabra “No Donor”, vamos a reemplazar los valores con base en la probabilidad

logit1.csv.class[logit1.csv >= .5] = "Donor"
head(logit1.csv)
        1         2         3         4         5         6 
0.4449338 0.5114931 0.4594329 0.6381711 0.3222896 0.6578001 
head(logit1.csv.class)
[1] "No Donor" "Donor"    "No Donor" "Donor"    "No Donor" "Donor"   

En esta ocasión se decidió clasificar como “Donor” a todas aquellas observaciones que tuvieron una probabilidad mayor a 50%.

Para realizar la exportación es necesario cambiar el vector a un data frame. Lo primero que haremos es convertir el vector con las clasificaciones en una matriz de solo una columna. ¡Muy raro!

Para esto utilizaremos la función matrix() en donde utilizaremos el vector de las clasificaciones e indicaremos que solamente tenemos una columna con el argumento ncol=.

value <- matrix(logit1.csv.class, ncol=1)
value
       [,1]      
  [1,] "No Donor"
  [2,] "Donor"   
  [3,] "No Donor"
  [4,] "Donor"   
  [5,] "No Donor"
  [6,] "Donor"   
  [7,] "Donor"   
  [8,] "No Donor"
  [9,] "Donor"   
 [10,] "Donor"   
 [11,] "Donor"   
 [12,] "No Donor"
 [13,] "Donor"   
 [14,] "Donor"   
 [15,] "No Donor"
 [16,] "Donor"   
 [17,] "No Donor"
 [18,] "Donor"   
 [19,] "No Donor"
 [20,] "Donor"   
 [21,] "Donor"   
 [22,] "Donor"   
 [23,] "Donor"   
 [24,] "No Donor"
 [25,] "No Donor"
 [26,] "No Donor"
 [27,] "No Donor"
 [28,] "Donor"   
 [29,] "Donor"   
 [30,] "No Donor"
 [31,] "Donor"   
 [32,] "No Donor"
 [33,] "Donor"   
 [34,] "Donor"   
 [35,] "Donor"   
 [36,] "Donor"   
 [37,] "No Donor"
 [38,] "Donor"   
 [39,] "No Donor"
 [40,] "No Donor"
 [41,] "Donor"   
 [42,] "No Donor"
 [43,] "No Donor"
 [44,] "Donor"   
 [45,] "Donor"   
 [46,] "No Donor"
 [47,] "No Donor"
 [48,] "Donor"   
 [49,] "Donor"   
 [50,] "No Donor"
 [51,] "Donor"   
 [52,] "No Donor"
 [53,] "No Donor"
 [54,] "No Donor"
 [55,] "Donor"   
 [56,] "Donor"   
 [57,] "No Donor"
 [58,] "Donor"   
 [59,] "No Donor"
 [60,] "No Donor"
 [61,] "No Donor"
 [62,] "Donor"   
 [63,] "Donor"   
 [64,] "Donor"   
 [65,] "Donor"   
 [66,] "No Donor"
 [67,] "No Donor"
 [68,] "Donor"   
 [69,] "No Donor"
 [70,] "No Donor"
 [71,] "Donor"   
 [72,] "Donor"   
 [73,] "Donor"   
 [74,] "Donor"   
 [75,] "No Donor"
 [76,] "No Donor"
 [77,] "Donor"   
 [78,] "Donor"   
 [79,] "Donor"   
 [80,] "No Donor"
 [81,] "Donor"   
 [82,] "No Donor"
 [83,] "Donor"   
 [84,] "No Donor"
 [85,] "Donor"   
 [86,] "No Donor"
 [87,] "Donor"   
 [88,] "No Donor"
 [89,] "Donor"   
 [90,] "No Donor"
 [91,] "Donor"   
 [92,] "Donor"   
 [93,] "No Donor"
 [94,] "No Donor"
 [95,] "Donor"   
 [96,] "Donor"   
 [97,] "Donor"   
 [98,] "No Donor"
 [99,] "No Donor"
[100,] "Donor"   
[101,] "No Donor"
[102,] "No Donor"
[103,] "No Donor"
[104,] "Donor"   
[105,] "No Donor"
[106,] "Donor"   
[107,] "No Donor"
[108,] "Donor"   
[109,] "No Donor"
[110,] "Donor"   
[111,] "No Donor"
[112,] "No Donor"
[113,] "No Donor"
[114,] "Donor"   
[115,] "No Donor"
[116,] "Donor"   
[117,] "Donor"   
[118,] "No Donor"
[119,] "No Donor"
[120,] "No Donor"

Ya que realizamos esto, podemos convertir esta matriz en un data frame utilizando la función data.frame() donde el único argumento que utilizaremos es el nombre que le asignamos a la matriz, en este caso “value”.

prueba1 <-data.frame(value)
View(prueba1)

Una vez que ya realizamo esto, simplemente usamos la función write_csv() y con esto podremos realizar la exportación de nuestro data frame. Los argumentos necesarios son el data frame a exportar, seguido del nombre que le asignaremos al archivo.

write_csv(prueba1, "logit1.csv")

Varios modelos en función

logit_models.fun <- function(modelofit){
logit1.cvs <- predict(modelofit, future, type="response")
head(logit1.cvs)
logit1.cvs.class = rep("No Donor", length(future$home_value))
logit1.cvs.class[logit1.cvs >= .5] = "Donor"
head(logit1.cvs)
head(logit1.cvs.class)
mat1 <- matrix(logit1.cvs.class, ncol=1)
prueba1 <-data.frame(mat1)
write_csv(prueba1, "logit1.csv")
}
logit5.cvs <- glm(target ~ zipconvert2 + zipconvert3 + zipconvert4 +zipconvert5 + homeowner  + income+ female + wealth + home_value +med_fam_inc + avg_fam_inc + pct_lt15k + num_prom +lifetime_gifts + largest_gift +last_gift + months_since_donate +time_lag + avg_gift, data=funds.train, family="binomial") 
logit_models.fun(logit5.cvs)
LS0tCnRpdGxlOiAiTW9kZWxvIExvZ2l0IgphdXRob3I6ICJKSkdTIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2NfZmxvYXQ6IHRydWUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpsaWJyYXJ5KHJlYWRyKQpmdW5kcyA8LSByZWFkUkRTKCJ+L0Ryb3Bib3gvVVQgQ3Vyc28vU3RhdGlzdGljYWwgTGVhcm5pbmcvVHJhYmFqbyBGaW5hbC9mdW5kcmFpc2luZy5yZHMiKQpmdXR1cmUgPC0gcmVhZFJEUygifi9Ecm9wYm94L1VUIEN1cnNvL1N0YXRpc3RpY2FsIExlYXJuaW5nL1RyYWJham8gRmluYWwvZnV0dXJlX2Z1bmRyYWlzaW5nLnJkcyIpCmZ1dHVyZSRudW1fY2hpbGQgPSBhcy5mYWN0b3IoZnV0dXJlJG51bV9jaGlsZCkKZnV0dXJlJGluY29tZSA9IGFzLmZhY3RvcihmdXR1cmUkaW5jb21lKQpmdXR1cmUkd2VhbHRoID0gYXMuZmFjdG9yKGZ1dHVyZSR3ZWFsdGgpCmBgYAoKIyB7LnRhYnNldH0KCiMjICoqTW9kZWxvIExvZ2l0KioKCkVsIG1vZGVsbyBsb2dpdCBzZSB1dGlsaXphIGN1YW5kbyBsYSB2YXJpYWJsZSBkZXBlbmRpZW50ZSBlcyB1bmEgdmFyaWFibGUgKipjdWFsaXRhdGl2YSoqLiBFbiBvY2FzaW9uZXMgYSBsb3MgbW9kZWxvcyBxdWUgYWJvcmRhbiBlc3RhIHNpdHVhY2nDs24gc2UgbGVzIGNvbm9jZSBjb21vICoqbW9kZWxvcyBkZSBjbGFzaWZpY2FjacOzbioqLgoKVGVuZW1vcyBkb3Mgc3VibXVlc3RyYXMgcGFyYSB0cmFiYWphciwgbGEgYmFzZSBkZSBkYXRvcyBgZnVuZHMudHJhaW5gIHF1ZSB0aWVuZSAqKjIsNDAwIG9ic2VydmFjaW9uZXMgeSAyMSB2YXJpYWJsZXMqKiB5IGxhIGJhc2UgZGUgZGF0b3MgYGZ1bmRzLnZhbGlkYXRpb25gIHF1ZSB0aWVuZSAqKjYwMCB2YXJpYWJsZXMgeSBsYXMgbWlzbWFzIDIxIHZhcmlhYmxlcyoqLgoKYGBge3J9CmRpbShmdW5kcy50cmFpbikKZGltKGZ1bmRzLnZhbGlkYXRpb24pCmBgYAoKTGEgYmFzZSBkZSBkYXRvcyBgZnVuZHMudHJhaW5gIGxhIHVzYXJlbW9zIHBhcmEgcmVhbGl6YXIgbGFzIGVzdGltYWNpb25lcyBkZSBsb3MgY29lZmljaWVudGVzLCBtaWVudHJhcyBxdWUgbGEgYmFzZSBkZSBkYXRvcyBgZnVuZHMudmFsaWRhdGlvbmAgbGEgdXNhcmVtb3MgcGFyYSBldmFsdWFyIGxhIGVmZWN0aXZpZGFkIGVuIGxhIHByZWRpY2Npw7NuIGRlIGxvcyBtb2RlbG9zIGdlbmVyYWRvcy4KClBhcmEgcmVhbGl6YXIgY29ycmVyIHVuIG1vZGVsbyBsb2dpdCBzZSB1dGlsaXphIGxhIGZ1bmNpw7NuIGBnbG0oKWAgcXVlIHV0aWxpemEgbGEgc2lndWllbnRlIHNpbnRheGlzOgoKYGdsbShZIH4gWDEgKyBYMiArIC4uLiArIFhuLCBkYXRhID0gYmFzZV9kZV9kYXRvcywgZmFtaWx5ID0gImJpbm9taWFsIilgCgpkb25kZToKCisgKipZKiogPSB2YXJpYWJsZSBjdWFsaXRhdGl2YS4KKyAqKlgxLCBYMiwgLi4uLiAsIFhuKiogPSBWYXJpYWJsZXMgaW5kZXBlbmRpZW50ZXMuCisgKipiYXNlX2RlX2RhdG9zKiogPSBlcyBsYSBiYXNlIGRlIGRhdG9zIHF1ZSBzZSB1dGlsaXphcsOhIHBhcmEgaGFjZXIgbGEgY29ycmlkYS4KKyAqKmZhbWlseSA9IGJpbm9taWFsKiogPSBlcyBlbCBhcmd1bWVudG8gcXVlIGluZGljYSBxdWUgbGEgY29ycmlkYSBzZWEgbWVkaWFudGUgdW4gbW9kZWxvIGxvZ2l0LgoKRW4gbnVlc3RybyBlamVtcGxvIHF1ZXJlbW9zIGRlZmluaXIgbGFzIHZhcmlhYmxlcyBxdWUgbWUgcGVybWl0ZW4gaWRlbnRpZmljYXIgc2kgdW4gaW5kaXZpZHVvIGVzIGRvbmFkb3IsIGxhIHZhcmlhYmxlIGB0YXJnZXRgIGVuIG51ZXN0cmEgYmFzZSBkZSBkYXRvcy4KCkNvbiBiYXNlIGVuIGVzdG8sIGVsIHByaW1lciBtb2RlbG8gdmFtb3MgYSBjb25zaWRlcmFyIHRvZGFzIGxhcyB2YXJpYWJsZXMgY29tbyB2YXJpYWJsZXMgaW5kZXBlbmRpZW50ZXMuCgpgYGB7cn0KbG9naXQxLmZpdCA8LSBnbG0oZnVuZHMudHJhaW4kdGFyZ2V0IH4gLiwgZGF0YT1mdW5kcy50cmFpbiwgZmFtaWx5PSJiaW5vbWlhbCIpCnN1bW1hcnkobG9naXQxLmZpdCkKYGBgCgpBIHBlc2FyIGRlIHF1ZSBleGlzdGUgdW5hIGdyYW4gY2FudGlkYWQgZGUgdmFyaWFibGVzIHF1ZSBubyBzb24gc2lnbmlmaWNhdGl2YXMsIHNlZ3VpcmVtb3MgY29uIGVsIGVqZW1wbG8uIFBhcmEgaGFjZXIgZXN0byB2YW1vcyBhIGNvbXBhcmFyIGRvcyB2ZWN0b3JlcyBvIHZhbG9yZXMsIGxvcyB2YWxvcmVzIHF1ZSBoYXkgZW4gbGEgYmFzZSBkZSBkYXRvcyBgZnVuZHMudmFsaWRhdGlvbmAgeSBudWVzdHJhcyBwcmVkaWNjaW9uZXMuCgpMbyBwcmltZXJvIHF1ZSBoYXJlbW9zIHNlcsOhIGdlbmVyYXIgdW4gdmVjdG9yIGRvbmRlIGFsbWFjZW5hcmVtb3MgbGFzIHByZWRpY2Npb25lcyByZWFsaXphZGFzIGNvbiBiYXNlIGVuIGxvcyByZXN1bHRhZG9zIGRlIG51ZXN0cm8gbW9kZWxvIHVzYW5kbyBsYSBmdW5jacOzbiBgcHJlZGljdCgpYC4gCgpMYSBzaW50YXhpcyBkZSBsYSBmdW5jacOzbiBgcHJlZGljdCgpYCBxdWUgdmFtb3MgYSB1c2FyIGVzIGxhIHNpZ3VpZW50ZToKCmBwcmVkaWN0KG9iamV0b19tb2RlbG8sIGJhc2VfaW5zdW1vLCB0eXBlPSJyZXNwb25zZSIpYAoKZG9uZGU6CgorICoqb2JqZXRvX21vZGVsbyoqID0gZWwgb2JqZXRvIGNvbiBlbCBxdWUgc2UgZ3VhcmRvIGxvcyByZXN1bHRhZG9zIGRlIHVuIG1vZGVsbyBlbiBlc3BlY8OtZmljby4KKyAqKmJhc2VfaW5zdW1vKiogPSAgZXMgbGEgYmFzZSBkZSBkYXRvcyBkb25kZSBzZSBzdXN0aXR1aXJhbiBsb3MgdmFsb3JlcyBkZSBsYXMgWHMgeSBzZSBjYWxjdWxhcsOhIGxhIHZhcmlhYmxlIGRlcGVuZGllbnRlLgorICoqdHlwZT0icmVzcG9uc2UiKiogPSBlcyBsYSBpbmRpY2FjacOzbiBwYXJhIHF1ZSBzZSByZWFsaWNlIGVsIGPDoWxjdWxvIGRlIGxhcyBwcm9iYWJpbGlkYWRlcyB1bmEgdmV6IHN1c3RpdHVpZG9zIGxvcyB2YWxvcmVzIGRlIGxhcyB2YXJpYWJsZXMgaW5kZXBlbmRpZW50ZXMuCgpFbCByZXN1bHRhZG8gZGUgZXN0YSBmdW5jacOzbiBzb24gbGFzIHByb2JhYmlsaWRhZGVzIGRlbCBtb2RlbG8gbG9naXQuCgpgYGB7cn0KbG9naXQxLnByZWRpY3QgPSBwcmVkaWN0KGxvZ2l0MS5maXQsIGZ1bmRzLnZhbGlkYXRpb24sIHR5cGU9InJlc3BvbnNlIikKaGVhZChsb2dpdDEucHJlZGljdCkKYGBgCgpZYSBxdWUgdGVuZW1vcyBsYXMgcHJvYmFiaWxpZGFkZXMsIGVzIG5lY2VzYXJpbyBhc2lnbmFyIHVuYSBkZSBsYXMgZG9zIG9wY2lvbmVzLCBkb25hZG9yIG8gbm8gZG9uYWRvci4KClBhcmEgZXN0bywgdmFtb3MgYSBjcmVhciB1biB2ZWN0b3IgbGxlbm8gY29uIGxhIGNsYXNpZmljYWNpw7NuICJObyBEb25vciIsIHkgZGVzcHXDqXMsIGNvbiBiYXNlIGVuIGxhcyBwcm9iYWJpbGlkYWRlcywgYXNnaW5hciBhIGxhcyBvYnNlcnZhY2lvbmVzIHF1ZSByZXByZXNlbnRhICJEb25vciIuCgpQYXJhIGVzdG8gdXNhbW9zIGxhIGZ1bmNpw7NuIGByZXAoKWAgY29uIGxhIHNpZ3VpZW50ZSBzaW50YXhpczoKCmByZXAodmFsb3IsIGNhbnRpZGFkKWAKCmRvbmRlOgoKKyAqKnZhbG9yKiogPSBlcyBlbCB2YWxvciBvIGxvcyBjYXJhY3RlcmVzIHF1ZSBzZSB2YW4gYSByZXBldGlyLgorICoqY2FudGlkYWQqKiA9IGVzIGxhIGNhbnRpZGFkIGRlIHZlY2VzIHF1ZSBlbCB2YWxvciBzZSB2YSBhIGdlbmVyYXIuCgpgYGB7cn0KbG9naXQxLmNsYXNzID0gcmVwKCJObyBEb25vciIsIGxlbmd0aChmdW5kcy52YWxpZGF0aW9uJHRhcmdldCkpCmhlYWQobG9naXQxLmNsYXNzKQp0YWlsKGxvZ2l0MS5jbGFzcykKYGBgCgpVbmEgdmV6IHF1ZSB0ZW5lbW9zIGVsIHZlY3RvciBsbGVubyBkZSBsYSBwYWxhYnJhICJObyBEb25vciIgZXMgbmVjZXNhcmlvIHN1c3RpdHVpciBsYXMgb2JzZXJ2YWNpb25lcyBxdWUgdGVuZ2FuIHVuYSBwcm9iYWJpbGlkYWQgbWF5b3IgYSAqKlgqKiBjb24gbGEgcGFsYWJyYSAiRG9ub3IiLiBFbiBlc3RlIGNhc28gdXNhbW9zIGNvbW8gcHJvYmFiaWxpZGFkIDUwJS4KCmBgYHtyfQpsb2dpdDEuY2xhc3NbbG9naXQxLnByZWRpY3QgPj0gMC41XSA9ICJEb25vciIKaGVhZChsb2dpdDEucHJlZGljdCkKaGVhZChsb2dpdDEuY2xhc3MpCnRhaWwobG9naXQxLnByZWRpY3QpCnRhaWwobG9naXQxLmNsYXNzKQpgYGAKCllhIHRlbmVuZW1vcyBlbCB2ZWN0b3IgY29uIGxhcyBwcmVkaWNjaW9uZXMgYGxvZ2l0MS5jbGFzc2AgZXMgbW9tZW50byBkZSBjb21wYXJhcmxvcyBjb24gbGFzIG9ic2VydmFjaW9uZXMgZW4gbnVlc3RyYSBiYXNlIGRlIGRhdG9zIGRlIHBydWViYSB5IHZlciBzdSBlZmVjdGl2aWRhZC4KCkxhIHByaW1lcmEgZm9ybWEgZGUgZXZhbHVhciBlcyB1dGlsaXphbmRvIHVuYSAqKipjb25mdXNpb24gbWF0cml4KioqLgoKYGBge3J9CnRhYmxlKGxvZ2l0MS5jbGFzcywgZnVuZHMudmFsaWRhdGlvbiR0YXJnZXQpCmBgYAoKT3RyYSBmb3JtYSBlcyBzaW1wbGVtZW50ZSBlbmNvbnRyYXIgZWwgcG9yY2VudGFqZSBkZSBhY2llcnRvCgpgYGB7cn0KbWVhbihsb2dpdDEuY2xhc3M9PWZ1bmRzLnZhbGlkYXRpb24kdGFyZ2V0KQpgYGAKCiMjICoqQ29tbyBmdW5jacOzbioqIAoKQ29tbyBleGlzdGVuIHZhcmlhcyBwcm9iYWJpbGlkYWRlcyBxdWUgcG9kZW1vcyBhc2lnbmFyIGNvbW8gcGFyw6FtZXRybyBkZSBkZWNpc2nDs24gcGFyYSBkZWNpciBzaSBlcyAiRG9ub3IiLCB2YW1vcyBhIHJlYWxpemFyIHVuYSBmdW5jacOzbiBxdWUgbm9zIHBlcm1pdGEgZW5jb250cmFyIGVsIHBvcmNlbnRhamUgZGUgZWZlY3RpdmlkYWQgYSBwYXJ0aXIgZGUgbGEgcHJvYmFiaWxkaWFkCgpgYGB7cn0KbG9naXQuZnVuIDwtIGZ1bmN0aW9uKHByb2Ipewpsb2dpdDEuY2xhc3NbbG9naXQxLnByZWRpY3QgPj0gcHJvYl0gPSAiRG9ub3IiCnByaW50KHRhYmxlKGxvZ2l0MS5jbGFzcywgZnVuZHMudmFsaWRhdGlvbiR0YXJnZXQpKQpwcmludChtZWFuKGxvZ2l0MS5jbGFzcz09ZnVuZHMudmFsaWRhdGlvbiR0YXJnZXQpKQp9CmBgYAoKQ3JlYW1vcyBsYSBmdW5jacOzbiBgbG9naXQuZnVuYCBxdWUgc29sbyB0aWVuZSB1biBhcmd1bWVudG8sIGxhIHByb2JhYmlsaWRhZCBxdWUgc2lydmUgY29tbyBjcml0ZXJpbywgYWwgYXNpZ25hciBjb21vICJEb25vciIgYSBjYWRhIHVuYSBkZSBsYXMgb2JzZXJ2YWNpb25lcy4KCmBgYHtyfQpsb2dpdC5mdW4oLjQ3NSkKYGBgCgojIyAqKkV4cG9ydGFyIHJlc3VsdGFkb3MqKgoKUGFyYSBleHBvcnRhciBsYXMgcHJlZGljY2lvbmVzIGRlIG51ZXN0cm9zIHJlc3VsdGFkb3MgY29tbyB1biBhcmNoaXZvICpjc3YgZXMgbmVjZXNhcmlvIHJlYWxpemFyIHVuYSBzZXJpZSBkZSBzZW5jaWxsb3MgcGFzb3MuCgpFbCBwcmltZXJvIGVzIHJlYWxpemFyIGxhIHByZWRpY2Npw7NuIGRlIGxhcyBwcm9iYWJpbGlkYWRlcywgZW4gZXN0ZSBjYXNvIGxvcyB2YWxvcmVzIHF1ZSB1c2FyZW1vcyBjb21vIHZhcmlhYmxlcyBpbmRlcGVuZGllbnRlcyBlc3TDoW4gZW4gdW5hIGJhc2UgZGUgZGF0b3MgbGxhbWFkYSAqKiJmdXR1cmUiKiouCgpgYGB7cn0KbG9naXQxLmNzdiA8LSBwcmVkaWN0KGxvZ2l0MS5maXQsIGZ1dHVyZSwgdHlwZT0icmVzcG9uc2UiKQpoZWFkKGxvZ2l0MS5jc3YpCmBgYAoKSGF5IHF1ZSBnZW5lcmFyIGVsIHZlY3RvciBkb25kZSBzZSBkZXBvc2l0YXJhbiBsb3MgcmVzdWx0YWRvcyBkZSBsYSBjbGFzaWZpY2FjacOzbiBjb24gYmFzZSBlbiBsYXMgcHJvYmFiaWxpZGFkZXMgY2FsY3VsYWRhcy4gRW4gZXN0YSBvY2FzacOzbiBhc2lnbmFyZW1vcyBlbCBub21icmUgZGUgKioibG9naXQxLmNzdiIqKi4gRXN0b3MgcGFzb3Mgc29uIGlkw6ludGljb3MgYSBsb3MgcmVhbGl6YWRvcyBwcmV2aWFtZW50ZS4KCmBgYHtyfQpsb2dpdDEuY3N2LmNsYXNzID0gcmVwKCJObyBEb25vciIsIGxlbmd0aChmdXR1cmUkaG9tZV92YWx1ZSkpCmhlYWQobG9naXQxLmNzdi5jbGFzcykKYGBgCgpZYSBxdWUgZ2VuZXJhbW9zIGVsIHZlY3RvciBjb24gc29sbyBsYSBwYWxhYnJhICJObyBEb25vciIsIHZhbW9zIGEgcmVlbXBsYXphciBsb3MgdmFsb3JlcyBjb24gYmFzZSBlbiBsYSBwcm9iYWJpbGlkYWQKCmBgYHtyfQpsb2dpdDEuY3N2LmNsYXNzW2xvZ2l0MS5jc3YgPj0gLjVdID0gIkRvbm9yIgpoZWFkKGxvZ2l0MS5jc3YpCmhlYWQobG9naXQxLmNzdi5jbGFzcykKYGBgCgpFbiBlc3RhIG9jYXNpw7NuIHNlIGRlY2lkacOzIGNsYXNpZmljYXIgY29tbyAiRG9ub3IiIGEgdG9kYXMgYXF1ZWxsYXMgb2JzZXJ2YWNpb25lcyBxdWUgdHV2aWVyb24gdW5hIHByb2JhYmlsaWRhZCBtYXlvciBhIDUwJS4KClBhcmEgcmVhbGl6YXIgbGEgZXhwb3J0YWNpw7NuIGVzIG5lY2VzYXJpbyBjYW1iaWFyIGVsIHZlY3RvciBhIHVuIGRhdGEgZnJhbWUuIExvIHByaW1lcm8gcXVlIGhhcmVtb3MgZXMgY29udmVydGlyIGVsIHZlY3RvciBjb24gbGFzIGNsYXNpZmljYWNpb25lcyBlbiB1bmEgbWF0cml6IGRlIHNvbG8gdW5hIGNvbHVtbmEuIMKhTXV5IHJhcm8hCgpQYXJhIGVzdG8gdXRpbGl6YXJlbW9zIGxhIGZ1bmNpw7NuIGBtYXRyaXgoKWAgZW4gZG9uZGUgdXRpbGl6YXJlbW9zIGVsIHZlY3RvciBkZSBsYXMgY2xhc2lmaWNhY2lvbmVzIGUgaW5kaWNhcmVtb3MgcXVlIHNvbGFtZW50ZSB0ZW5lbW9zIHVuYSBjb2x1bW5hIGNvbiBlbCBhcmd1bWVudG8gYG5jb2w9YC4KCgpgYGB7cn0KdmFsdWUgPC0gbWF0cml4KGxvZ2l0MS5jc3YuY2xhc3MsIG5jb2w9MSkKdmFsdWUKYGBgCgpZYSBxdWUgcmVhbGl6YW1vcyBlc3RvLCBwb2RlbW9zIGNvbnZlcnRpciBlc3RhIG1hdHJpeiBlbiB1biBkYXRhIGZyYW1lIHV0aWxpemFuZG8gbGEgZnVuY2nDs24gYGRhdGEuZnJhbWUoKWAgZG9uZGUgZWwgw7puaWNvIGFyZ3VtZW50byBxdWUgdXRpbGl6YXJlbW9zIGVzIGVsIG5vbWJyZSBxdWUgbGUgYXNpZ25hbW9zIGEgbGEgbWF0cml6LCBlbiBlc3RlIGNhc28gKioidmFsdWUiKiouCgpgYGB7cn0KcHJ1ZWJhMSA8LWRhdGEuZnJhbWUodmFsdWUpClZpZXcocHJ1ZWJhMSkKYGBgCgpVbmEgdmV6IHF1ZSB5YSByZWFsaXphbW8gZXN0bywgc2ltcGxlbWVudGUgdXNhbW9zIGxhIGZ1bmNpw7NuIGB3cml0ZV9jc3YoKWAgeSBjb24gZXN0byBwb2RyZW1vcyByZWFsaXphciBsYSBleHBvcnRhY2nDs24gZGUgbnVlc3RybyBkYXRhIGZyYW1lLiBMb3MgYXJndW1lbnRvcyBuZWNlc2FyaW9zIHNvbiBlbCBkYXRhIGZyYW1lIGEgZXhwb3J0YXIsIHNlZ3VpZG8gZGVsIG5vbWJyZSBxdWUgbGUgYXNpZ25hcmVtb3MgYWwgYXJjaGl2by4KCmBgYHtyLH0Kd3JpdGVfY3N2KHBydWViYTEsICJsb2dpdDEuY3N2IikKYGBgCgojIyAqKlZhcmlvcyBtb2RlbG9zIGVuIGZ1bmNpw7NuKioKCmBgYHtyfQpsb2dpdF9tb2RlbHMuZnVuIDwtIGZ1bmN0aW9uKG1vZGVsb2ZpdCl7CmxvZ2l0MS5jc3YgPC0gcHJlZGljdChtb2RlbG9maXQsIGZ1dHVyZSwgdHlwZT0icmVzcG9uc2UiKQpoZWFkKGxvZ2l0MS5jc3YpCmxvZ2l0MS5jc3YuY2xhc3MgPSByZXAoIk5vIERvbm9yIiwgbGVuZ3RoKGZ1dHVyZSRob21lX3ZhbHVlKSkKbG9naXQxLmNzdi5jbGFzc1tsb2dpdDEuY3N2ID49IC41XSA9ICJEb25vciIKaGVhZChsb2dpdDEuY3N2KQpoZWFkKGxvZ2l0MS5jc3YuY2xhc3MpCnZhbHVlIDwtIG1hdHJpeChsb2dpdDEuY3N2LmNsYXNzLCBuY29sPTEpCnBydWViYTEgPC1kYXRhLmZyYW1lKHZhbHVlKQp3cml0ZV9jc3YocHJ1ZWJhMSwgImxvZ2l0MS5jc3YiKQp9CmBgYAoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CmxvZ2l0NS5jdnMgPC0gZ2xtKHRhcmdldCB+IHppcGNvbnZlcnQyICsgemlwY29udmVydDMgKyB6aXBjb252ZXJ0NCAremlwY29udmVydDUgKyBob21lb3duZXIgICsgaW5jb21lKyBmZW1hbGUgKyB3ZWFsdGggKyBob21lX3ZhbHVlICttZWRfZmFtX2luYyArIGF2Z19mYW1faW5jICsgcGN0X2x0MTVrICsgbnVtX3Byb20gK2xpZmV0aW1lX2dpZnRzICsgbGFyZ2VzdF9naWZ0ICtsYXN0X2dpZnQgKyBtb250aHNfc2luY2VfZG9uYXRlICt0aW1lX2xhZyArIGF2Z19naWZ0LCBkYXRhPWZ1bmRzLnRyYWluLCBmYW1pbHk9ImJpbm9taWFsIikgCmBgYAoKYGBge3J9CmxvZ2l0X21vZGVscy5mdW4obG9naXQ1LmN2cykKYGBgCgoKCgoKCgoKCgoK