La visualización de datos es útil no sólo para explorar los datos e
identificar la relación entre diferentes variables, sino también para
comunicar el resultado del análisis. El paquete ggplot2
nos permite generar gráficos de alta calidad con unas pocas líneas de
código. Cualquier gráfico de ggplot tendrá al menos 3 componentes: los
datos, un sistema de coordenadas y una
geometría (la representación visual de los datos) y se
construirá por capas.
¡Empecemos a hacer gráficos!
Primera capa: el área del gráfico
La función principal de ggplot2 se llama también
ggplot()
(pero sin el 2!), y nos permite iniciar el gráfico
y definir las características globales. El primer argumento de esta
función serán los datos que queremos visualizar, siempre en un
data.frame. En este caso utilizamos cultivares
.
El segundo argumento se llama mapping
porque es donde
definimos cómo se “mapean” las columnas del data.frame o las variables
de los datos a las propiedades visuales de las geometrías. Este mapeo
está definido por la función aes()
. En este caso indicamos
que en el eje x queremos graficar la variable
Rendimiento_Ajustado
y en el eje y la variable
Aceite_porcentaje
.
Todo esto sólo generará la primera capa: el área del gráfico.
ggplot(data = cultivares, mapping = aes(x = Rendimiento_Ajustado, y = Aceite_porcentaje))
Segunda capa: geometrías
Necesitamos añadir una nueva capa a nuestro gráfico, los elementos
geométricos o “geoms” que representarán los datos. Para ello añadimos
una función geom, por ejemplo si queremos representar los datos con
puntos utilizaremos geom_point()
. Para hacer esto
necesitaremos agregar un +
al final de la primera capa para
sumar una segunda.
ggplot(data = cultivares, mapping = aes(x = Rendimiento_Ajustado, y = Aceite_porcentaje)) +
geom_point()
Ya tenemos nuestro primer gráfico!
Tal vez observaste que los puntos están agrupados de una manera
particular. Quizá alguna otra variable explique este comportamiento.
Para incluir información de otras variables en nuestro gráfico
podemos aprovechar las características estéticas de las geometrías. En
este caso, podemos “pintar” los puntos según el tipo de ensayo.
ggplot(data = cultivares, mapping = aes(x = Rendimiento_Ajustado, y = Aceite_porcentaje)) +
geom_point(aes(color = `Tipo Ensayo`))
De nuevo, utilizamos la función aes()
para asignar una
variable de nuestros datos a un elemento del gráfico. ¡Y tada! ¡Cada
tipo de ensayo tiene características diferentes!
Añadiendo geometrías
Muchas veces no basta con mirar los datos en bruto para identificar
la relación entre las variables; es necesario utilizar alguna
transformación estadística para resaltar esas relaciones, ya sea
ajustando un modelo o calculando alguna estadística.
Para ello, ggplot2 dispone de geoms que calculan transformaciones
estadísticas comunes. Vamos a probar con geom_smoth()
para
ajustar un modelo lineal a cada especie.
ggplot(data = cultivares, mapping = aes(x = Rendimiento_Ajustado, y = Aceite_porcentaje)) +
geom_point(aes(color = `Tipo Ensayo`)) +
geom_smooth(aes(color = `Tipo Ensayo`), method = "lm")
Por defecto geom_smooth()
ajusta los datos utilizando el
método loess (regresión lineal local) cuando hay menos de 1000 datos
disponibles. Pero es muy común que se quiera ajustar una regresión
lineal global. En ese caso, tenemos que agregar el argumento
method = "lm"
.
Hablemos del aspecto del gráfico
Por ahora utilizamos el aspecto por defecto de ggplot. Podríamos
cambiar el aspecto del gráfico para adaptarlo al estilo de la
institución donde trabajamos, de la revista donde lo vamos a publicar o
simplemente para darle un estilo propio.
Empecemos por el color. Para cambiar el aspecto estético de un
elemento del gráfico, añadimos una nueva capa con la función
scale_*
. En este caso utilizaremos
scale_color_manual()
para elegir los colores de los puntos
manualmente. También podríamos utilizar paletas de colores previamente
definidas como las familias Viridis o Color Brewer.
Necesitaremos 4 colores para los cuatro tipos de ensayo, usaremos
"darkorange"
, "purple"
, "cyan4"
y "lightblue"
.
ggplot(data = cultivares, mapping = aes(x = Rendimiento_Ajustado, y = Aceite_porcentaje)) +
geom_point(aes(color = `Tipo Ensayo`)) +
geom_smooth(aes(color = `Tipo Ensayo`), method = "lm") +
scale_color_manual(values = c("darkorange","purple","cyan4", "lightblue"))
¡Va quedando! Ahora, vamos a añadir algunos elementos de texto con
una nueva capa ggplot: labs()
.
ggplot(data = cultivares, mapping = aes(x = Rendimiento_Ajustado, y = Aceite_porcentaje)) +
geom_point(aes(color = `Tipo Ensayo`)) +
geom_smooth(aes(color = `Tipo Ensayo`), method = "lm") +
scale_color_manual(values = c("darkorange","purple","cyan4", "lightblue")) +
labs(title = "Rendimiento y procentaje de aceite por tipo de ensayo",
subtitle = "Cultivares ACA de la Red Nacional de Ensayos de Girasol",
x = "Porcentaje de Aceite (%)",
y = "Rendimiento (kg/ha)",
color = "Tipo de Ensayo",
shape = "Tipo de Ensayo")
Ahora las etiquetas de los ejes son más legibles y tenemos un título
y un subtítulo que explican de qué trata el gráfico.
Podríamos seguir cambiando esto infinitamente pero terminaremos con
el aspecto general del gráfico.
El aspecto general de un gráfico está definido por su tema. ggplot2
tiene muchos temas disponibles y para todos los gustos. Pero también hay
otros paquetes que amplían las posibilidades, por ejemplo ggthemes. Por
defecto ggplot2 utiliza theme_grey()
, probemos con
theme_minimal()
:
ggplot(data = cultivares, mapping = aes(x = Rendimiento_Ajustado, y = Aceite_porcentaje)) +
geom_point(aes(color = `Tipo Ensayo`)) +
geom_smooth(aes(color = `Tipo Ensayo`), method = "lm") +
scale_color_manual(values = c("darkorange","purple","cyan4", "lightblue")) +
labs(title = "Rendimiento y procentaje de aceite por tipo de ensayo",
subtitle = "Cultivares ACA de la Red Nacional de Ensayos de Girasol",
x = "Porcentaje de Aceite (%)",
y = "Rendimiento (kg/ha)",
color = "Tipo de Ensayo",
shape = "Tipo de Ensayo") +
theme_minimal()
Ahora es tu turno. Elige un tema que te guste y pruébalo. Además, si
se te ocurre un título mejor, ¡modifícalo!
LS0tDQp0aXRsZTogIlZpc3VhbGl6YW5kbyBkYXRvcyINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogZmFsc2UNCiAgICBoaWdobGlnaHQ6IHRhbmdvDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoDQoJZWNobyA9IFRSVUUsDQoJbWVzc2FnZSA9IEZBTFNFLA0KCXdhcm5pbmcgPSBGQUxTRQ0KKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCmN1bHRpdmFyZXMgPC0gcmVhZF9jc3YoImRhdG9zL1JOR19zbWFsbC5jc3YiKQ0KYGBgDQoNCkxhIHZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zIGVzIMO6dGlsIG5vIHPDs2xvIHBhcmEgZXhwbG9yYXIgbG9zIGRhdG9zIGUgaWRlbnRpZmljYXIgbGEgcmVsYWNpw7NuIGVudHJlIGRpZmVyZW50ZXMgdmFyaWFibGVzLCBzaW5vIHRhbWJpw6luIHBhcmEgY29tdW5pY2FyIGVsIHJlc3VsdGFkbyBkZWwgYW7DoWxpc2lzLiBFbCBwYXF1ZXRlICoqZ2dwbG90MioqIG5vcyBwZXJtaXRlIGdlbmVyYXIgZ3LDoWZpY29zIGRlIGFsdGEgY2FsaWRhZCBjb24gdW5hcyBwb2NhcyBsw61uZWFzIGRlIGPDs2RpZ28uIEN1YWxxdWllciBncsOhZmljbyBkZSBnZ3Bsb3QgdGVuZHLDoSBhbCBtZW5vcyAzIGNvbXBvbmVudGVzOiBsb3MgKipkYXRvcyoqLCB1biAqKnNpc3RlbWEgZGUgY29vcmRlbmFkYXMqKiB5IHVuYSAqKmdlb21ldHLDrWEqKiAobGEgcmVwcmVzZW50YWNpw7NuIHZpc3VhbCBkZSBsb3MgZGF0b3MpIHkgc2UgY29uc3RydWlyw6EgcG9yIGNhcGFzLg0KDQrCoUVtcGVjZW1vcyBhIGhhY2VyIGdyw6FmaWNvcyENCg0KIyMgUHJpbWVyYSBjYXBhOiBlbCDDoXJlYSBkZWwgZ3LDoWZpY28NCg0KTGEgZnVuY2nDs24gcHJpbmNpcGFsIGRlIGdncGxvdDIgc2UgbGxhbWEgdGFtYmnDqW4gYGdncGxvdCgpYCAocGVybyBzaW4gZWwgMiEpLCB5IG5vcyBwZXJtaXRlIGluaWNpYXIgZWwgZ3LDoWZpY28geSBkZWZpbmlyIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGdsb2JhbGVzLiBFbCBwcmltZXIgYXJndW1lbnRvIGRlIGVzdGEgZnVuY2nDs24gc2Vyw6FuIGxvcyBkYXRvcyBxdWUgcXVlcmVtb3MgdmlzdWFsaXphciwgc2llbXByZSBlbiB1biBkYXRhLmZyYW1lLiBFbiBlc3RlIGNhc28gdXRpbGl6YW1vcyBgY3VsdGl2YXJlc2AuDQoNCkVsIHNlZ3VuZG8gYXJndW1lbnRvIHNlIGxsYW1hIGBtYXBwaW5nYCBwb3JxdWUgZXMgZG9uZGUgZGVmaW5pbW9zIGPDs21vIHNlICJtYXBlYW4iIGxhcyBjb2x1bW5hcyBkZWwgZGF0YS5mcmFtZSBvIGxhcyB2YXJpYWJsZXMgZGUgbG9zIGRhdG9zIGEgbGFzIHByb3BpZWRhZGVzIHZpc3VhbGVzIGRlIGxhcyBnZW9tZXRyw61hcy4gRXN0ZSBtYXBlbyBlc3TDoSBkZWZpbmlkbyBwb3IgbGEgZnVuY2nDs24gYGFlcygpYC4gRW4gZXN0ZSBjYXNvIGluZGljYW1vcyBxdWUgZW4gZWwgZWplIHggcXVlcmVtb3MgZ3JhZmljYXIgbGEgdmFyaWFibGUgYFJlbmRpbWllbnRvX0FqdXN0YWRvYCB5IGVuIGVsIGVqZSB5IGxhIHZhcmlhYmxlIGBBY2VpdGVfcG9yY2VudGFqZWAuDQoNClRvZG8gZXN0byBzw7NsbyBnZW5lcmFyw6EgbGEgcHJpbWVyYSBjYXBhOiBlbCDDoXJlYSBkZWwgZ3LDoWZpY28uDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBjdWx0aXZhcmVzLCBtYXBwaW5nID0gYWVzKHggPSBSZW5kaW1pZW50b19BanVzdGFkbywgeSA9IEFjZWl0ZV9wb3JjZW50YWplKSkgDQpgYGANCg0KIyMgU2VndW5kYSBjYXBhOiBnZW9tZXRyw61hcw0KDQpOZWNlc2l0YW1vcyBhw7FhZGlyIHVuYSBudWV2YSBjYXBhIGEgbnVlc3RybyBncsOhZmljbywgbG9zIGVsZW1lbnRvcyBnZW9tw6l0cmljb3MgbyAiZ2VvbXMiIHF1ZSByZXByZXNlbnRhcsOhbiBsb3MgZGF0b3MuIFBhcmEgZWxsbyBhw7FhZGltb3MgdW5hIGZ1bmNpw7NuIGdlb20sIHBvciBlamVtcGxvIHNpIHF1ZXJlbW9zIHJlcHJlc2VudGFyIGxvcyBkYXRvcyBjb24gcHVudG9zIHV0aWxpemFyZW1vcyBgZ2VvbV9wb2ludCgpYC4gUGFyYSBoYWNlciBlc3RvIG5lY2VzaXRhcmVtb3MgYWdyZWdhciB1biBgK2AgYWwgZmluYWwgZGUgbGEgcHJpbWVyYSBjYXBhIHBhcmEgc3VtYXIgdW5hIHNlZ3VuZGEuIA0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gY3VsdGl2YXJlcywgbWFwcGluZyA9IGFlcyh4ID0gUmVuZGltaWVudG9fQWp1c3RhZG8sIHkgPSBBY2VpdGVfcG9yY2VudGFqZSkpICArDQogIGdlb21fcG9pbnQoKQ0KYGBgDQoNCllhIHRlbmVtb3MgbnVlc3RybyBwcmltZXIgZ3LDoWZpY28hIA0KDQpUYWwgdmV6IG9ic2VydmFzdGUgcXVlIGxvcyBwdW50b3MgZXN0w6FuIGFncnVwYWRvcyBkZSB1bmEgbWFuZXJhIHBhcnRpY3VsYXIuIFF1aXrDoSBhbGd1bmEgb3RyYSB2YXJpYWJsZSBleHBsaXF1ZSBlc3RlIGNvbXBvcnRhbWllbnRvLiANCg0KUGFyYSBpbmNsdWlyIGluZm9ybWFjacOzbiBkZSBvdHJhcyB2YXJpYWJsZXMgZW4gbnVlc3RybyBncsOhZmljbyBwb2RlbW9zIGFwcm92ZWNoYXIgbGFzIGNhcmFjdGVyw61zdGljYXMgZXN0w6l0aWNhcyBkZSBsYXMgZ2VvbWV0csOtYXMuIEVuIGVzdGUgY2FzbywgcG9kZW1vcyAicGludGFyIiBsb3MgcHVudG9zIHNlZ8O6biBlbCB0aXBvIGRlIGVuc2F5by4gDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBjdWx0aXZhcmVzLCBtYXBwaW5nID0gYWVzKHggPSBSZW5kaW1pZW50b19BanVzdGFkbywgeSA9IEFjZWl0ZV9wb3JjZW50YWplKSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGBUaXBvIEVuc2F5b2ApKQ0KYGBgDQoNCkRlIG51ZXZvLCB1dGlsaXphbW9zIGxhIGZ1bmNpw7NuIGBhZXMoKWAgcGFyYSBhc2lnbmFyIHVuYSB2YXJpYWJsZSBkZSBudWVzdHJvcyBkYXRvcyBhIHVuIGVsZW1lbnRvIGRlbCBncsOhZmljby4gwqFZIHRhZGEhIMKhQ2FkYSB0aXBvIGRlIGVuc2F5byB0aWVuZSBjYXJhY3RlcsOtc3RpY2FzIGRpZmVyZW50ZXMhDQoNCiMjIEHDsWFkaWVuZG8gZ2VvbWV0csOtYXMNCg0KTXVjaGFzIHZlY2VzIG5vIGJhc3RhIGNvbiBtaXJhciBsb3MgZGF0b3MgZW4gYnJ1dG8gcGFyYSBpZGVudGlmaWNhciBsYSByZWxhY2nDs24gZW50cmUgbGFzIHZhcmlhYmxlczsgZXMgbmVjZXNhcmlvIHV0aWxpemFyIGFsZ3VuYSB0cmFuc2Zvcm1hY2nDs24gZXN0YWTDrXN0aWNhIHBhcmEgcmVzYWx0YXIgZXNhcyByZWxhY2lvbmVzLCB5YSBzZWEgYWp1c3RhbmRvIHVuIG1vZGVsbyBvIGNhbGN1bGFuZG8gYWxndW5hIGVzdGFkw61zdGljYS4gDQoNClBhcmEgZWxsbywgZ2dwbG90MiBkaXNwb25lIGRlIGdlb21zIHF1ZSBjYWxjdWxhbiB0cmFuc2Zvcm1hY2lvbmVzIGVzdGFkw61zdGljYXMgY29tdW5lcy4gVmFtb3MgYSBwcm9iYXIgY29uIGBnZW9tX3Ntb3RoKClgIHBhcmEgYWp1c3RhciB1biBtb2RlbG8gbGluZWFsIGEgY2FkYSBlc3BlY2llLiANCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGN1bHRpdmFyZXMsIG1hcHBpbmcgPSBhZXMoeCA9IFJlbmRpbWllbnRvX0FqdXN0YWRvLCB5ID0gQWNlaXRlX3BvcmNlbnRhamUpKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gYFRpcG8gRW5zYXlvYCkpICsNCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG9yID0gYFRpcG8gRW5zYXlvYCksIG1ldGhvZCA9ICJsbSIpDQpgYGANCg0KUG9yIGRlZmVjdG8gYGdlb21fc21vb3RoKClgIGFqdXN0YSBsb3MgZGF0b3MgdXRpbGl6YW5kbyBlbCBtw6l0b2RvIGxvZXNzIChyZWdyZXNpw7NuIGxpbmVhbCBsb2NhbCkgY3VhbmRvIGhheSBtZW5vcyBkZSAxMDAwIGRhdG9zIGRpc3BvbmlibGVzLiBQZXJvIGVzIG11eSBjb23Dum4gcXVlIHNlIHF1aWVyYSBhanVzdGFyIHVuYSByZWdyZXNpw7NuIGxpbmVhbCBnbG9iYWwuIEVuIGVzZSBjYXNvLCB0ZW5lbW9zIHF1ZSBhZ3JlZ2FyIGVsIGFyZ3VtZW50byBgbWV0aG9kID0gImxtImAuDQoNCiMjIEhhYmxlbW9zIGRlbCBhc3BlY3RvIGRlbCBncsOhZmljbyAgDQogIA0KUG9yIGFob3JhIHV0aWxpemFtb3MgZWwgYXNwZWN0byBwb3IgZGVmZWN0byBkZSBnZ3Bsb3QuIFBvZHLDrWFtb3MgY2FtYmlhciBlbCBhc3BlY3RvIGRlbCBncsOhZmljbyBwYXJhIGFkYXB0YXJsbyBhbCBlc3RpbG8gZGUgbGEgaW5zdGl0dWNpw7NuIGRvbmRlIHRyYWJhamFtb3MsIGRlIGxhIHJldmlzdGEgZG9uZGUgbG8gdmFtb3MgYSBwdWJsaWNhciBvIHNpbXBsZW1lbnRlIHBhcmEgZGFybGUgdW4gZXN0aWxvIHByb3Bpby4gDQogIA0KRW1wZWNlbW9zIHBvciBlbCBjb2xvci4gUGFyYSBjYW1iaWFyIGVsIGFzcGVjdG8gZXN0w6l0aWNvIGRlIHVuIGVsZW1lbnRvIGRlbCBncsOhZmljbywgYcOxYWRpbW9zIHVuYSBudWV2YSBjYXBhIGNvbiBsYSBmdW5jacOzbiBgc2NhbGVfKmAuIEVuIGVzdGUgY2FzbyB1dGlsaXphcmVtb3MgYHNjYWxlX2NvbG9yX21hbnVhbCgpYCBwYXJhIGVsZWdpciBsb3MgY29sb3JlcyBkZSBsb3MgcHVudG9zIG1hbnVhbG1lbnRlLiBUYW1iacOpbiBwb2Ryw61hbW9zIHV0aWxpemFyIHBhbGV0YXMgZGUgY29sb3JlcyBwcmV2aWFtZW50ZSBkZWZpbmlkYXMgY29tbyBsYXMgZmFtaWxpYXMgVmlyaWRpcyBvIENvbG9yIEJyZXdlci4gDQogIA0KTmVjZXNpdGFyZW1vcyA0IGNvbG9yZXMgcGFyYSBsb3MgY3VhdHJvIHRpcG9zIGRlIGVuc2F5bywgdXNhcmVtb3MgYCJkYXJrb3JhbmdlImAsIGAicHVycGxlImAgLCBgImN5YW40ImAgeSBgImxpZ2h0Ymx1ZSJgLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gY3VsdGl2YXJlcywgbWFwcGluZyA9IGFlcyh4ID0gUmVuZGltaWVudG9fQWp1c3RhZG8sIHkgPSBBY2VpdGVfcG9yY2VudGFqZSkpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBgVGlwbyBFbnNheW9gKSkgKw0KICBnZW9tX3Ntb290aChhZXMoY29sb3IgPSBgVGlwbyBFbnNheW9gKSwgbWV0aG9kID0gImxtIikgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiZGFya29yYW5nZSIsInB1cnBsZSIsImN5YW40IiwgImxpZ2h0Ymx1ZSIpKSANCmBgYA0KDQrCoVZhIHF1ZWRhbmRvISBBaG9yYSwgdmFtb3MgYSBhw7FhZGlyIGFsZ3Vub3MgZWxlbWVudG9zIGRlIHRleHRvIGNvbiB1bmEgbnVldmEgY2FwYSBnZ3Bsb3Q6IGBsYWJzKClgLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gY3VsdGl2YXJlcywgbWFwcGluZyA9IGFlcyh4ID0gUmVuZGltaWVudG9fQWp1c3RhZG8sIHkgPSBBY2VpdGVfcG9yY2VudGFqZSkpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBgVGlwbyBFbnNheW9gKSkgKw0KICBnZW9tX3Ntb290aChhZXMoY29sb3IgPSBgVGlwbyBFbnNheW9gKSwgbWV0aG9kID0gImxtIikgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiZGFya29yYW5nZSIsInB1cnBsZSIsImN5YW40IiwgImxpZ2h0Ymx1ZSIpKSAgKw0KICBsYWJzKHRpdGxlID0gIlJlbmRpbWllbnRvIHkgcHJvY2VudGFqZSBkZSBhY2VpdGUgcG9yIHRpcG8gZGUgZW5zYXlvIiwNCiAgICAgICBzdWJ0aXRsZSA9ICJDdWx0aXZhcmVzIEFDQSBkZSBsYSBSZWQgTmFjaW9uYWwgZGUgRW5zYXlvcyBkZSBHaXJhc29sIiwNCiAgICAgICB4ID0gIlBvcmNlbnRhamUgZGUgQWNlaXRlICglKSIsDQogICAgICAgeSA9ICJSZW5kaW1pZW50byAoa2cvaGEpIiwNCiAgICAgICBjb2xvciA9ICJUaXBvIGRlIEVuc2F5byIsDQogICAgICAgc2hhcGUgPSAiVGlwbyBkZSBFbnNheW8iKSANCmBgYA0KDQpBaG9yYSBsYXMgZXRpcXVldGFzIGRlIGxvcyBlamVzIHNvbiBtw6FzIGxlZ2libGVzIHkgdGVuZW1vcyB1biB0w610dWxvIHkgdW4gc3VidMOtdHVsbyBxdWUgZXhwbGljYW4gZGUgcXXDqSB0cmF0YSBlbCBncsOhZmljby4gDQoNClBvZHLDrWFtb3Mgc2VndWlyIGNhbWJpYW5kbyBlc3RvIGluZmluaXRhbWVudGUgcGVybyB0ZXJtaW5hcmVtb3MgY29uIGVsIGFzcGVjdG8gZ2VuZXJhbCBkZWwgZ3LDoWZpY28uIA0KDQpFbCBhc3BlY3RvIGdlbmVyYWwgZGUgdW4gZ3LDoWZpY28gZXN0w6EgZGVmaW5pZG8gcG9yIHN1IHRlbWEuIGdncGxvdDIgdGllbmUgbXVjaG9zIHRlbWFzIGRpc3BvbmlibGVzIHkgcGFyYSB0b2RvcyBsb3MgZ3VzdG9zLiBQZXJvIHRhbWJpw6luIGhheSBvdHJvcyBwYXF1ZXRlcyBxdWUgYW1wbMOtYW4gbGFzIHBvc2liaWxpZGFkZXMsIHBvciBlamVtcGxvIGdndGhlbWVzLiBQb3IgZGVmZWN0byBnZ3Bsb3QyIHV0aWxpemEgYHRoZW1lX2dyZXkoKWAsIHByb2JlbW9zIGNvbiBgdGhlbWVfbWluaW1hbCgpYDoNCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGN1bHRpdmFyZXMsIG1hcHBpbmcgPSBhZXMoeCA9IFJlbmRpbWllbnRvX0FqdXN0YWRvLCB5ID0gQWNlaXRlX3BvcmNlbnRhamUpKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gYFRpcG8gRW5zYXlvYCkpICsNCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG9yID0gYFRpcG8gRW5zYXlvYCksIG1ldGhvZCA9ICJsbSIpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImRhcmtvcmFuZ2UiLCJwdXJwbGUiLCJjeWFuNCIsICJsaWdodGJsdWUiKSkgICsNCiAgbGFicyh0aXRsZSA9ICJSZW5kaW1pZW50byB5IHByb2NlbnRhamUgZGUgYWNlaXRlIHBvciB0aXBvIGRlIGVuc2F5byIsDQogICAgICAgc3VidGl0bGUgPSAiQ3VsdGl2YXJlcyBBQ0EgZGUgbGEgUmVkIE5hY2lvbmFsIGRlIEVuc2F5b3MgZGUgR2lyYXNvbCIsDQogICAgICAgeCA9ICJQb3JjZW50YWplIGRlIEFjZWl0ZSAoJSkiLA0KICAgICAgIHkgPSAiUmVuZGltaWVudG8gKGtnL2hhKSIsDQogICAgICAgY29sb3IgPSAiVGlwbyBkZSBFbnNheW8iLA0KICAgICAgIHNoYXBlID0gIlRpcG8gZGUgRW5zYXlvIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQo+IEFob3JhIGVzIHR1IHR1cm5vLiBFbGlnZSB1biB0ZW1hIHF1ZSB0ZSBndXN0ZSB5IHBydcOpYmFsby4gQWRlbcOhcywgc2kgc2UgdGUgb2N1cnJlIHVuIHTDrXR1bG8gbWVqb3IsIMKhbW9kaWbDrWNhbG8hDQo=