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.
- Puntos hacia la derecha reflejan puntajes altos en el exani.
- Puntos hacia arriba reflejan puntajes altos en el promedio
- Puntos arriba y a la derecha reflejan puntajes altos en exani 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
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
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:
- Una correlacion de .3 muestra una asociacion más debil que una correlacion de .7; ambas positivas
- Una correlacion de -.2 muestra una asociacion más debil que una correlacion de -.6; ambas negativas
- Una correlacion de .2 muestra una asociacion más debil que una correlacion de -.6
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=