Continuaremos con nuestra exploración de las comparaciones entre dos variables. Ahora toca el turno de comparar dos variables numéricas con la famosa correlación.

Empecemos con el ejemplo y desde ahí vamos explicando. Una experiencia común a todos los aquí presentes: admisión a la universidad; en este caso a la licenciatura. Para ingresar (al menos a la IBERO) ustedes requieren (al menos antes de la pandemia) de un promedio de preparatoria aprobatorio y de un buen puntaje en el examen de admisión. ¿Qué tan bueno? Pues… hasta que se llenen las sillas disponibles en el salón (supongo).

Esta situación abre varias preguntas interesantes.

¿Qué tanto se relaciona el examen de admisión con el puntaje obtenido en el primer semestre de la carrera? ¿Algunas secciones del examen son mejores para predecir el desempeño académico?

Vamos a contestar con correlaciones. Veamos los datos

library(tidyverse)
library(skimr)
library(corrr)
library(GGally)

library(readr)
exani <- read_csv("exani.csv")

exani <- exani %>% 
  rename(
    r_mate= "razonamientologicomatemático",
    r_verbal= "razonamientoverbal")

head(exani) #Las primeras 5 entradas

skim(exani) # 1,702 alumnos de primer ingreso
── Data Summary ────────────────────────
                           Values
Name                       exani 
Number of rows             1702  
Number of columns          12    
_______________________          
Column type frequency:           
  character                2     
  numeric                  10    
________________________         
Group variables            None  

── Variable type: character ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate   min   max empty n_unique whitespace
1 sexo                  0             1     1     1     0        2          0
2 carrera               0             1     7    43     0       36          0

── Variable type: numeric ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   skim_variable n_missing complete_rate    mean     sd    p0    p25    p50    p75  p100 hist 
 1 X1                    0         1      852.   491.       1  426.   852.  1277.   1702 ▇▇▇▇▇
 2 promedio              0         1       80.3    7.57    60   75     80     85     100 ▁▆▇▅▂
 3 puntaje               0         1     7515.   857.       0 6980   7460   8040    9660 ▁▁▁▇▅
 4 r_mate                0         1       68.1   17.4     15   55     70     80     100 ▁▂▇▆▆
 5 matemáticas           0         1       67.2   15.7     10   55     68.3   79     100 ▁▂▅▇▃
 6 r_verbal              0         1       72.8   14.7     25   65     75     85     100 ▁▃▇▇▅
 7 español               0         1       71.9   14.3     20   63.2   73.7   84.2   100 ▁▂▆▇▅
 8 tics                  0         1       60.2   14.2     20   50     60     70      95 ▁▅▇▅▁
 9 global               13         0.992   68.1   11.3     31   60     68     76      98 ▁▃▇▆▂
10 prom_bac            574         0.663    5.36   1.40     2    4      5      6       9 ▂▃▇▂▁

Primero veamos nuestra variable dependiente: promedio primer semestre (media: 80.3)

exani %>%
  ggplot(aes(x= promedio))+
  geom_histogram(binwidth = 1, fill= "orchid4", color="white")+
  geom_vline(xintercept = mean(exani$promedio), color= "blue")+
  geom_vline(xintercept = 78, color= "black")+
  theme_minimal()

Si fueran coordinadores de carrera… ¿Por qué les preocuparía la línea negra?

El 7.8 es el promedio mínimo para permanecer en la carrera (puntaje de calidad, aunque varía por programa). Todos ellos están en riesgo de salir y reciben una amonestación para que se pongan las pilas. ¿Pudimos haberlo anticipado?

Veamos nustra variable independiente: puntaje global del examen

exani %>%
  ggplot(aes(x= global))+
  geom_histogram(binwidth = 1, fill= "salmon3", color="white")+
  theme_minimal()

Vamos a ver si están relacionados

exani %>% 
  ggplot(aes(x=global, y=promedio)) +
    geom_jitter(color="#69b3a2", alpha=0.7)+
  theme_minimal()

¿Están asociadas?

Vamos a ver cómo se interpreta esto. Un punto refleja la posición que tuvo en global Y en promedio.

Por ello, mientras más márcada es la diagonal que va de abajo izquierda hacia arriba derecha, entonces la relación es más fuerte.

Aquí uno siente como que sí va en la dirección correcta pero hay muchas excepciones. ¿Cuál punto es contra-intutivo?

exani %>% 
  ggplot(aes(x=global, y=promedio)) +
    geom_hex()+
  theme_minimal()

Para este tipo de situaciones la correlación nos da una métrica útil.


exani %>% 
  select(promedio, global) %>% 
  correlate()

Correlation method: 'pearson'
Missing treated using: 'pairwise.complete.obs'

Primero veamos nuestros WARNINGS. Nos avisa que el método por default es el de Pearson. Este es el correcto si queremos asociar dos variables numéricas. en otro momento, probablemente el siguiente semestre, veremos otro tipo de correlaciones, las cuales se asocian al tipo de variable, pero se interpretan igual. Así es que nos mantendremos con esta.

Ahora el valor! Tenemos una correlación de 0.312

¿Bueno, malo, mucho, poco, decepcionante, emocionante?

Voy a dar unas referencias para entender esto.

Las correlaciones tienen dos caracteristicas: direccion y fuerza

  1. La dirección se denota por el signo: positivo o negativo. En este caso es positivo. Una relacion positiva significa que conforme aumenta la var X, tambien aumenta la var Y. A la inversa, valores bajos de X, coinciden con valores bajos de Y. Las direcciones positivas nos dan esta imagen de una diagonal que va de abajo izquierda hacia arriba derecha. Si el signo hubiera sido negativo, entonces la diagonal iria de arriba izquierda hacia abajo derecha. Es negativa porque las vars X y Y van en direcciones opuestas: conforme aumenta X, disminuye Y

  2. La fuerza se denota por la magnitud del coeficiente. Las correlaciones siempre tienen un rango que va del -1 al 1 (cualquier valor fuera de este rango NO es una correlacion). Mientras mas cerca del 1 o el -1, la relacion es más fuerte. Mientras mas cerca del 0, la relacion es mas debil

Entonces, si el valor es positivo (0 a 1), la asociacion es positiva: diagonal de abajo a arriba. Si el valor es negativo (-1 a 0), la asociacion es negativa: diagonal arriba hacia abajo. Cuando la asociación es fuerte, la diagnonal es más nítida; cuando es débil, los puntos hacen una nebulosa difusa (la diagonal se ve más bien horizontal)

Algunos ejemplos:

Así es que una correlación de 0.312 es más bien débil. Veamos la mentada diagonal.

exani %>% 
  ggplot(aes(x=global, y=promedio)) +
    geom_jitter(color="#69b3a2", alpha=0.7)+
  geom_smooth(method='lm')+
  theme_minimal()

Esa diagonal es la que matemáticamente mejor explica la correlación. Nos dice que sí hay una relación positiva. Mientras más horizontal esa línea, menor la asociación. Mientras más inclinada, más fuerte. Esta es una correlación entre débil y moderada.

¿Entonces es un buen examen?

Vamos a examinar otra variable: una que combina el promedio de prepa y el resultado en el examen: puntaje de admisión

exani %>% 
  select(puntaje, promedio) %>% 
  correlate()

Correlation method: 'pearson'
Missing treated using: 'pairwise.complete.obs'

Mucho mejor. Veamos la diagonal

exani %>% 
  ggplot(aes(x=puntaje, y=promedio)) +
    geom_jitter(color="#69b3a2", alpha=0.7)+
  geom_smooth(method='lm')+
  theme_minimal()

Y eso? Tenemos ahí unos datos atípicos. Va de nuevo todo

exani %>%
  filter(puntaje > 5000) %>% 
  select(puntaje, promedio) %>% 
  correlate()

Correlation method: 'pearson'
Missing treated using: 'pairwise.complete.obs'

Aún mejor! Vean la importancia de cuidarse de los outliers

exani %>%
  filter(puntaje > 5000) %>%
  ggplot(aes(x=puntaje, y=promedio)) +
    geom_jitter(color="#69b3a2", alpha=0.7)+
  geom_smooth(method='lm')+
  theme_minimal()

Mejor, no creen?

exani %>% 
    filter(puntaje > 5000) %>%
  ggplot(aes(x=puntaje, y=promedio)) +
    geom_hex()+
  theme_minimal()

Ahora vamos a seguir esta lógica pero en una matriz de correlaciones

Haremos una compración todos contra todos

corr1 <- exani %>% 
  filter(puntaje > 5000) %>%
  select(-c(X1, sexo, carrera)) %>%
  correlate() %>%
  shave()

corr1

Más bonito

corr1 %>% fashion()

Ahora una matriz más informativa. Noten los asteriscos. ¿Qué significan? ¿Cuál es la HO?


exani %>%
  filter(puntaje > 5000) %>%
  select(-c(X1, sexo, carrera)) %>%
  filter(puntaje > 5000) %>%
  ggpairs()

Vamos a enfocar la pregunta sobre nuestra VD

corr1 %>% focus(promedio)

Y tal vez aún más informativo

corr1 %>%
  focus(promedio) %>%
  mutate(term = reorder(term, promedio)) %>%
  ggplot(aes(term, promedio)) +
    geom_col(fill= "tan4") + 
  coord_flip()+
  theme_minimal()

Y de pilón, un par de gráfias que me gustan más para matriz. Salen de este tutorial.

library(corrplot)
corrplot 0.89 loaded
exani2 <- exani %>%
  filter(puntaje > 5000) %>%
  select(-c(X1, sexo, carrera)) %>% 
  drop_na()
corr2 <- cor(exani2)
corr2 <- cor(exani2)
corrplot(corr2, method = 'number', type = 'lower')

Mi favorito

Pero igual y soy muy cuadrado. Vean como combinan dirección y fuerza pero visualmente

o un combo

LS0tCnRpdGxlOiAiQ29ycmVsYWNpb25lcyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKQ29udGludWFyZW1vcyBjb24gbnVlc3RyYSBleHBsb3JhY2nDs24gZGUgbGFzIGNvbXBhcmFjaW9uZXMgZW50cmUgZG9zIHZhcmlhYmxlcy4gQWhvcmEgdG9jYSBlbCB0dXJubyBkZSBjb21wYXJhciBkb3MgdmFyaWFibGVzIG51bcOpcmljYXMgY29uIGxhIGZhbW9zYSBfX2NvcnJlbGFjacOzbl9fLiAKCkVtcGVjZW1vcyBjb24gZWwgZWplbXBsbyB5IGRlc2RlIGFow60gdmFtb3MgZXhwbGljYW5kby4gVW5hIGV4cGVyaWVuY2lhIGNvbcO6biBhIHRvZG9zIGxvcyBhcXXDrSBwcmVzZW50ZXM6IGFkbWlzacOzbiBhIGxhIHVuaXZlcnNpZGFkOyBlbiBlc3RlIGNhc28gYSBsYSBsaWNlbmNpYXR1cmEuIFBhcmEgaW5ncmVzYXIgKGFsIG1lbm9zIGEgbGEgSUJFUk8pIHVzdGVkZXMgcmVxdWllcmVuIChhbCBtZW5vcyBhbnRlcyBkZSBsYSBwYW5kZW1pYSkgZGUgdW4gcHJvbWVkaW8gZGUgcHJlcGFyYXRvcmlhIGFwcm9iYXRvcmlvIHkgZGUgdW4gYnVlbiBwdW50YWplIGVuIGVsIGV4YW1lbiBkZSBhZG1pc2nDs24uIMK/UXXDqSB0YW4gYnVlbm8/IFB1ZXMuLi4gaGFzdGEgcXVlIHNlIGxsZW5lbiBsYXMgc2lsbGFzIGRpc3BvbmlibGVzIGVuIGVsIHNhbMOzbiAoc3Vwb25nbykuIAoKRXN0YSBzaXR1YWNpw7NuIGFicmUgdmFyaWFzIHByZWd1bnRhcyBpbnRlcmVzYW50ZXMuIAoKPiDCv1F1w6kgdGFudG8gc2UgcmVsYWNpb25hIGVsIGV4YW1lbiBkZSBhZG1pc2nDs24gY29uIGVsIHB1bnRhamUgb2J0ZW5pZG8gZW4gZWwgcHJpbWVyIHNlbWVzdHJlIGRlIGxhIGNhcnJlcmE/IMK/QWxndW5hcyBzZWNjaW9uZXMgZGVsIGV4YW1lbiBzb24gbWVqb3JlcyBwYXJhIHByZWRlY2lyIGVsIGRlc2VtcGXDsW8gYWNhZMOpbWljbz8gCgpWYW1vcyBhIGNvbnRlc3RhciBjb24gY29ycmVsYWNpb25lcy4gVmVhbW9zIGxvcyBkYXRvcwoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2tpbXIpCmxpYnJhcnkoY29ycnIpCmxpYnJhcnkoR0dhbGx5KQoKbGlicmFyeShyZWFkcikKZXhhbmkgPC0gcmVhZF9jc3YoImV4YW5pLmNzdiIpCgpleGFuaSA8LSBleGFuaSAlPiUgCiAgcmVuYW1lKAogICAgcl9tYXRlPSAicmF6b25hbWllbnRvbG9naWNvbWF0ZW3DoXRpY28iLAogICAgcl92ZXJiYWw9ICJyYXpvbmFtaWVudG92ZXJiYWwiKQoKaGVhZChleGFuaSkgI0xhcyBwcmltZXJhcyA1IGVudHJhZGFzCgpza2ltKGV4YW5pKSAjIDEsNzAyIGFsdW1ub3MgZGUgcHJpbWVyIGluZ3Jlc28KYGBgCgpQcmltZXJvIHZlYW1vcyBudWVzdHJhIHZhcmlhYmxlIGRlcGVuZGllbnRlOiBwcm9tZWRpbyBwcmltZXIgc2VtZXN0cmUgKG1lZGlhOiA4MC4zKQoKYGBge3J9CmV4YW5pICU+JQogIGdncGxvdChhZXMoeD0gcHJvbWVkaW8pKSsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEsIGZpbGw9ICJvcmNoaWQ0IiwgY29sb3I9IndoaXRlIikrCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbWVhbihleGFuaSRwcm9tZWRpbyksIGNvbG9yPSAiYmx1ZSIpKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDc4LCBjb2xvcj0gImJsYWNrIikrCiAgdGhlbWVfbWluaW1hbCgpCgpgYGAKClNpIGZ1ZXJhbiBjb29yZGluYWRvcmVzIGRlIGNhcnJlcmEuLi4gwr9Qb3IgcXXDqSBsZXMgcHJlb2N1cGFyw61hIGxhIGzDrW5lYSBuZWdyYT8gCgpFbCA3LjggZXMgZWwgcHJvbWVkaW8gbcOtbmltbyBwYXJhIHBlcm1hbmVjZXIgZW4gbGEgY2FycmVyYSAocHVudGFqZSBkZSBjYWxpZGFkLCBhdW5xdWUgdmFyw61hIHBvciBwcm9ncmFtYSkuIFRvZG9zIGVsbG9zIGVzdMOhbiBlbiByaWVzZ28gZGUgc2FsaXIgeSByZWNpYmVuIHVuYSBhbW9uZXN0YWNpw7NuIHBhcmEgcXVlIHNlIHBvbmdhbiBsYXMgcGlsYXMuIMK/UHVkaW1vcyBoYWJlcmxvIGFudGljaXBhZG8/CgpWZWFtb3MgbnVzdHJhIHZhcmlhYmxlIGluZGVwZW5kaWVudGU6IHB1bnRhamUgZ2xvYmFsIGRlbCBleGFtZW4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmV4YW5pICU+JQogIGdncGxvdChhZXMoeD0gZ2xvYmFsKSkrCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBmaWxsPSAic2FsbW9uMyIsIGNvbG9yPSJ3aGl0ZSIpKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCgpWYW1vcyBhIHZlciBzaSBlc3TDoW4gcmVsYWNpb25hZG9zCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpleGFuaSAlPiUgCiAgZ2dwbG90KGFlcyh4PWdsb2JhbCwgeT1wcm9tZWRpbykpICsKICAgIGdlb21faml0dGVyKGNvbG9yPSIjNjliM2EyIiwgYWxwaGE9MC43KSsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgoKPiDCv0VzdMOhbiBhc29jaWFkYXM/CgpWYW1vcyBhIHZlciBjw7NtbyBzZSBpbnRlcnByZXRhIGVzdG8uIFVuIHB1bnRvIHJlZmxlamEgbGEgcG9zaWNpw7NuIHF1ZSB0dXZvIGVuIGdsb2JhbCBZIGVuIHByb21lZGlvLiAKCiogUHVudG9zIGhhY2lhIGxhIGRlcmVjaGEgcmVmbGVqYW4gcHVudGFqZXMgYWx0b3MgZW4gZWwgZXhhbmkuIAoqIFB1bnRvcyBoYWNpYSBhcnJpYmEgcmVmbGVqYW4gcHVudGFqZXMgYWx0b3MgZW4gZWwgcHJvbWVkaW8KKiBQdW50b3MgYXJyaWJhIHkgYSBsYSBkZXJlY2hhIHJlZmxlamFuIHB1bnRhamVzIGFsdG9zIGVuIGV4YW5pIFkgZW4gcHJvbWVkaW8KClBvciBlbGxvLCBtaWVudHJhcyBtw6FzIG3DoXJjYWRhIGVzIGxhIGRpYWdvbmFsIHF1ZSB2YSBkZSBhYmFqbyBpenF1aWVyZGEgaGFjaWEgYXJyaWJhIGRlcmVjaGEsIGVudG9uY2VzIGxhIHJlbGFjacOzbiBlcyBtw6FzIGZ1ZXJ0ZS4gCgpBcXXDrSB1bm8gc2llbnRlIGNvbW8gcXVlIHPDrSB2YSBlbiBsYSBkaXJlY2Npw7NuIGNvcnJlY3RhIHBlcm8gaGF5IF9fbXVjaGFzX18gZXhjZXBjaW9uZXMuIMK/Q3XDoWwgcHVudG8gZXMgY29udHJhLWludHV0aXZvPwoKYGBge3IgbWVzc2FnZT1UUlVFLCB3YXJuaW5nPUZBTFNFfQpleGFuaSAlPiUgCiAgZ2dwbG90KGFlcyh4PWdsb2JhbCwgeT1wcm9tZWRpbykpICsKICAgIGdlb21faGV4KCkrCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKClBhcmEgZXN0ZSB0aXBvIGRlIHNpdHVhY2lvbmVzIGxhIF9fY29ycmVsYWNpw7NuX18gbm9zIGRhIHVuYSBtw6l0cmljYSDDunRpbC4gCgpgYGB7cn0KCmV4YW5pICU+JSAKICBzZWxlY3QocHJvbWVkaW8sIGdsb2JhbCkgJT4lIAogIGNvcnJlbGF0ZSgpCgpgYGAKCgpQcmltZXJvIHZlYW1vcyBudWVzdHJvcyBXQVJOSU5HUy4gTm9zIGF2aXNhIHF1ZSBlbCBtw6l0b2RvIHBvciBkZWZhdWx0IGVzIGVsIGRlIFBlYXJzb24uIEVzdGUgZXMgZWwgY29ycmVjdG8gc2kgcXVlcmVtb3MgYXNvY2lhciBkb3MgdmFyaWFibGVzIG51bcOpcmljYXMuIGVuIG90cm8gbW9tZW50bywgcHJvYmFibGVtZW50ZSBlbCBzaWd1aWVudGUgc2VtZXN0cmUsIHZlcmVtb3Mgb3RybyB0aXBvIGRlIGNvcnJlbGFjaW9uZXMsIGxhcyBjdWFsZXMgc2UgYXNvY2lhbiBhbCB0aXBvIGRlIHZhcmlhYmxlLCBwZXJvIHNlIGludGVycHJldGFuIGlndWFsLiBBc8OtIGVzIHF1ZSBub3MgbWFudGVuZHJlbW9zIGNvbiBlc3RhLiAKCkFob3JhIGVsIHZhbG9yISBUZW5lbW9zIHVuYSBjb3JyZWxhY2nDs24gZGUgMC4zMTIKCj4gwr9CdWVubywgbWFsbywgbXVjaG8sIHBvY28sIGRlY2VwY2lvbmFudGUsIGVtb2Npb25hbnRlPyAKClZveSBhIGRhciB1bmFzIHJlZmVyZW5jaWFzIHBhcmEgZW50ZW5kZXIgZXN0by4gCgpMYXMgY29ycmVsYWNpb25lcyB0aWVuZW4gZG9zIGNhcmFjdGVyaXN0aWNhczogZGlyZWNjaW9uIHkgZnVlcnphCgogMS4gTGEgX19kaXJlY2Npw7NuX18gc2UgZGVub3RhIHBvciBlbCBzaWdubzogcG9zaXRpdm8gbyBuZWdhdGl2by4gRW4gZXN0ZSBjYXNvIGVzIF9fcG9zaXRpdm9fXy4gVW5hIHJlbGFjaW9uIHBvc2l0aXZhIHNpZ25pZmljYSBxdWUgY29uZm9ybWUgYXVtZW50YSBsYSB2YXIgWCwgdGFtYmllbiBhdW1lbnRhIGxhIHZhciBZLiBBIGxhIGludmVyc2EsIHZhbG9yZXMgYmFqb3MgZGUgWCwgY29pbmNpZGVuIGNvbiB2YWxvcmVzIGJham9zIGRlIFkuIExhcyBkaXJlY2Npb25lcyBwb3NpdGl2YXMgbm9zIGRhbiBlc3RhIGltYWdlbiBkZSB1bmEgZGlhZ29uYWwgcXVlIHZhIGRlIGFiYWpvIGl6cXVpZXJkYSBoYWNpYSBhcnJpYmEgZGVyZWNoYS4gU2kgZWwgc2lnbm8gaHViaWVyYSBzaWRvIF9fbmVnYXRpdm9fXywgZW50b25jZXMgbGEgZGlhZ29uYWwgaXJpYSBkZSBhcnJpYmEgaXpxdWllcmRhIGhhY2lhIGFiYWpvIGRlcmVjaGEuIEVzIG5lZ2F0aXZhIHBvcnF1ZSBsYXMgdmFycyBYIHkgWSB2YW4gZW4gZGlyZWNjaW9uZXMgb3B1ZXN0YXM6IGNvbmZvcm1lIGF1bWVudGEgWCwgZGlzbWludXllIFkKCjIuIExhIF9fZnVlcnphX18gc2UgZGVub3RhIHBvciBsYSBtYWduaXR1ZCBkZWwgY29lZmljaWVudGUuIExhcyBjb3JyZWxhY2lvbmVzIF9fc2llbXByZV9fIHRpZW5lbiB1biByYW5nbyBxdWUgdmEgZGVsIC0xIGFsIDEgKGN1YWxxdWllciB2YWxvciBmdWVyYSBkZSBlc3RlIHJhbmdvIE5PIGVzIHVuYSBjb3JyZWxhY2lvbikuIE1pZW50cmFzIG1hcyBjZXJjYSBkZWwgMSBvIGVsIC0xLCBsYSByZWxhY2lvbiBlcyBtw6FzIF9fZnVlcnRlX18uIE1pZW50cmFzIG1hcyBjZXJjYSBkZWwgMCwgbGEgcmVsYWNpb24gZXMgbWFzIF9fZGViaWxfXyAKCkVudG9uY2VzLCBzaSBlbCB2YWxvciBlcyBwb3NpdGl2byAoMCBhIDEpLCBsYSBhc29jaWFjaW9uIGVzIHBvc2l0aXZhOiBkaWFnb25hbCBkZSBhYmFqbyBhIGFycmliYS4gU2kgZWwgdmFsb3IgZXMgbmVnYXRpdm8gKC0xIGEgMCksIGxhIGFzb2NpYWNpb24gZXMgbmVnYXRpdmE6IGRpYWdvbmFsIGFycmliYSBoYWNpYSBhYmFqby4gQ3VhbmRvIGxhIGFzb2NpYWNpw7NuIGVzIGZ1ZXJ0ZSwgbGEgZGlhZ25vbmFsIGVzIG3DoXMgbsOtdGlkYTsgY3VhbmRvIGVzIGTDqWJpbCwgbG9zIHB1bnRvcyBoYWNlbiB1bmEgbmVidWxvc2EgZGlmdXNhIChsYSBkaWFnb25hbCBzZSB2ZSBtw6FzIGJpZW4gaG9yaXpvbnRhbCkKCkFsZ3Vub3MgZWplbXBsb3M6CgoqIFVuYSBjb3JyZWxhY2lvbiBkZSAuMyBtdWVzdHJhIHVuYSBhc29jaWFjaW9uIG3DoXMgZGViaWwgcXVlIHVuYSBjb3JyZWxhY2lvbiBkZSAuNzsgYW1iYXMgcG9zaXRpdmFzCiogVW5hIGNvcnJlbGFjaW9uIGRlIC0uMiBtdWVzdHJhIHVuYSBhc29jaWFjaW9uIG3DoXMgZGViaWwgcXVlIHVuYSBjb3JyZWxhY2lvbiBkZSAtLjY7IGFtYmFzIG5lZ2F0aXZhcwoqIFVuYSBjb3JyZWxhY2lvbiBkZSAuMiBtdWVzdHJhIHVuYSBhc29jaWFjaW9uIG3DoXMgZGViaWwgcXVlIHVuYSBjb3JyZWxhY2lvbiBkZSAtLjYKCkFzw60gZXMgcXVlIHVuYSBjb3JyZWxhY2nDs24gZGUgMC4zMTIgZXMgbcOhcyBiaWVuIGTDqWJpbC4gVmVhbW9zIGxhIG1lbnRhZGEgZGlhZ29uYWwuIAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZXhhbmkgJT4lIAogIGdncGxvdChhZXMoeD1nbG9iYWwsIHk9cHJvbWVkaW8pKSArCiAgICBnZW9tX2ppdHRlcihjb2xvcj0iIzY5YjNhMiIsIGFscGhhPTAuNykrCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScpKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCgpFc2EgZGlhZ29uYWwgZXMgbGEgcXVlIG1hdGVtw6F0aWNhbWVudGUgbWVqb3IgZXhwbGljYSBsYSBjb3JyZWxhY2nDs24uIE5vcyBkaWNlIHF1ZSBzw60gaGF5IHVuYSByZWxhY2nDs24gcG9zaXRpdmEuIE1pZW50cmFzIG3DoXMgaG9yaXpvbnRhbCBlc2EgbMOtbmVhLCBtZW5vciBsYSBhc29jaWFjacOzbi4gTWllbnRyYXMgbcOhcyBpbmNsaW5hZGEsIG3DoXMgZnVlcnRlLiBFc3RhIGVzIHVuYSBjb3JyZWxhY2nDs24gZW50cmUgZMOpYmlsIHkgbW9kZXJhZGEuIAoKPiDCv0VudG9uY2VzIGVzIHVuIGJ1ZW4gZXhhbWVuPwoKVmFtb3MgYSBleGFtaW5hciBvdHJhIHZhcmlhYmxlOiB1bmEgcXVlIGNvbWJpbmEgZWwgcHJvbWVkaW8gZGUgcHJlcGEgeSBlbCByZXN1bHRhZG8gZW4gZWwgZXhhbWVuOiBfX3B1bnRhamUgZGUgYWRtaXNpw7NuX18gCgpgYGB7cn0KZXhhbmkgJT4lIAogIHNlbGVjdChwdW50YWplLCBwcm9tZWRpbykgJT4lIAogIGNvcnJlbGF0ZSgpCmBgYAoKTXVjaG8gbWVqb3IuIFZlYW1vcyBsYSBkaWFnb25hbAoKYGBge3J9CmV4YW5pICU+JSAKICBnZ3Bsb3QoYWVzKHg9cHVudGFqZSwgeT1wcm9tZWRpbykpICsKICAgIGdlb21faml0dGVyKGNvbG9yPSIjNjliM2EyIiwgYWxwaGE9MC43KSsKICBnZW9tX3Ntb290aChtZXRob2Q9J2xtJykrCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKClkgZXNvPyBUZW5lbW9zIGFow60gdW5vcyBkYXRvcyBhdMOtcGljb3MuIFZhIGRlIG51ZXZvIHRvZG8KCmBgYHtyfQpleGFuaSAlPiUKICBmaWx0ZXIocHVudGFqZSA+IDUwMDApICU+JSAKICBzZWxlY3QocHVudGFqZSwgcHJvbWVkaW8pICU+JSAKICBjb3JyZWxhdGUoKQpgYGAKCkHDum4gbWVqb3IhIFZlYW4gbGEgaW1wb3J0YW5jaWEgZGUgY3VpZGFyc2UgZGUgbG9zIG91dGxpZXJzCgpgYGB7cn0KZXhhbmkgJT4lCiAgZmlsdGVyKHB1bnRhamUgPiA1MDAwKSAlPiUKICBnZ3Bsb3QoYWVzKHg9cHVudGFqZSwgeT1wcm9tZWRpbykpICsKICAgIGdlb21faml0dGVyKGNvbG9yPSIjNjliM2EyIiwgYWxwaGE9MC43KSsKICBnZW9tX3Ntb290aChtZXRob2Q9J2xtJykrCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKTWVqb3IsIG5vIGNyZWVuPyAKCmBgYHtyfQpleGFuaSAlPiUgCiAgICBmaWx0ZXIocHVudGFqZSA+IDUwMDApICU+JQogIGdncGxvdChhZXMoeD1wdW50YWplLCB5PXByb21lZGlvKSkgKwogICAgZ2VvbV9oZXgoKSsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgoKQWhvcmEgdmFtb3MgYSBzZWd1aXIgZXN0YSBsw7NnaWNhIHBlcm8gZW4gdW5hIF9fbWF0cml6IGRlIGNvcnJlbGFjaW9uZXNfXwoKSGFyZW1vcyB1bmEgY29tcHJhY2nDs24gdG9kb3MgY29udHJhIHRvZG9zCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpjb3JyMSA8LSBleGFuaSAlPiUgCiAgZmlsdGVyKHB1bnRhamUgPiA1MDAwKSAlPiUKICBzZWxlY3QoLWMoWDEsIHNleG8sIGNhcnJlcmEpKSAlPiUKICBjb3JyZWxhdGUoKSAlPiUKICBzaGF2ZSgpCgpjb3JyMQpgYGAKCk3DoXMgYm9uaXRvCgpgYGB7cn0KY29ycjEgJT4lIGZhc2hpb24oKQpgYGAKCkFob3JhIHVuYSBtYXRyaXogbcOhcyBpbmZvcm1hdGl2YS4gTm90ZW4gbG9zIGFzdGVyaXNjb3MuIF9fwr9RdcOpIHNpZ25pZmljYW4/X18gwr9DdcOhbCBlcyBsYSBITz8KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgpleGFuaSAlPiUKICBmaWx0ZXIocHVudGFqZSA+IDUwMDApICU+JQogIHNlbGVjdCgtYyhYMSwgc2V4bywgY2FycmVyYSkpICU+JQogIGZpbHRlcihwdW50YWplID4gNTAwMCkgJT4lCiAgZ2dwYWlycygpCmBgYAoKClZhbW9zIGEgZW5mb2NhciBsYSBwcmVndW50YSBzb2JyZSBudWVzdHJhIFZECgpgYGB7cn0KY29ycjEgJT4lIGZvY3VzKHByb21lZGlvKQpgYGAKClkgdGFsIHZleiBhw7puIG3DoXMgaW5mb3JtYXRpdm8gCgpgYGB7cn0KY29ycjEgJT4lCiAgZm9jdXMocHJvbWVkaW8pICU+JQogIG11dGF0ZSh0ZXJtID0gcmVvcmRlcih0ZXJtLCBwcm9tZWRpbykpICU+JQogIGdncGxvdChhZXModGVybSwgcHJvbWVkaW8pKSArCiAgICBnZW9tX2NvbChmaWxsPSAidGFuNCIpICsgCiAgY29vcmRfZmxpcCgpKwogIHRoZW1lX21pbmltYWwoKQpgYGAKClkgZGUgcGlsw7NuLCB1biBwYXIgZGUgZ3LDoWZpYXMgcXVlIG1lIGd1c3RhbiBtw6FzIHBhcmEgbWF0cml6LiBTYWxlbiBkZSBlc3RlIFt0dXRvcmlhbF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2NvcnJwbG90L3ZpZ25ldHRlcy9jb3JycGxvdC1pbnRyby5odG1sKS4gCgpgYGB7ciBlY2hvPVRSVUV9CmxpYnJhcnkoY29ycnBsb3QpCgpleGFuaTIgPC0gZXhhbmkgJT4lCiAgZmlsdGVyKHB1bnRhamUgPiA1MDAwKSAlPiUKICBzZWxlY3QoLWMoWDEsIHNleG8sIGNhcnJlcmEpKSAlPiUgCiAgZHJvcF9uYSgpCiAgCmNvcnIyIDwtIGNvcihleGFuaTIpCgpjb3JycGxvdChjb3JyMiwgbWV0aG9kID0gJ251bWJlcicsIHR5cGUgPSAnbG93ZXInKQpgYGAKCk1pIGZhdm9yaXRvCgpgYGB7ciBlY2hvPVRSVUV9CmNvcnJwbG90KGNvcnIyLCBtZXRob2QgPSAnY29sb3InLCB0eXBlID0gJ2xvd2VyJykKCmBgYAoKUGVybyBpZ3VhbCB5IHNveSBtdXkgY3VhZHJhZG8uIFZlYW4gY29tbyBjb21iaW5hbiBkaXJlY2Npw7NuIHkgZnVlcnphIHBlcm8gdmlzdWFsbWVudGUKCmBgYHtyIGVjaG89VFJVRX0KY29ycnBsb3QoY29ycjIsIG1ldGhvZCA9ICdjaXJjbGUnLCB0eXBlID0gJ2xvd2VyJykKYGBgCgpgYGB7ciBlY2hvPVRSVUV9CmNvcnJwbG90KGNvcnIyLCBtZXRob2QgPSAnc3F1YXJlJywgdHlwZSA9ICdsb3dlcicpCmBgYAoKbyB1biBjb21ibwoKYGBge3IgZWNobz1UUlVFfQpjb3JycGxvdC5taXhlZChjb3JyMiwgbG93ZXIgPSAnY2lyY2xlJywgdXBwZXIgPSAnbnVtYmVyJykKYGBgCgo=