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(), 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 pinguinos.

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 largo_pico_mm y en el eje y la variable alto_pico_mm.

Todo esto sólo generará la primera capa: el área del gráfico.

ggplot(data = pinguinos, mapping = aes(x = largo_pico_mm, y = alto_pico_mm)) 

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 capaz para sumar una segunda.

ggplot(data = pinguinos, mapping = aes(x = largo_pico_mm, y = alto_pico_mm)) +
  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 la especie de pingüino.

ggplot(data = pinguinos, mapping = aes(x = largo_pico_mm, y = alto_pico_mm)) +
  geom_point(aes(color = especie))

De nuevo, utilizamos la función aes() para asignar una variable de nuestros datos a un elemento del gráfico. ¡Y tada! ¡Cada especie de pingüinos 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 = pinguinos, mapping = aes(x = largo_pico_mm, y = alto_pico_mm)) +
  geom_point(aes(color = especie)) +
  geom_smooth(aes(color = especie), 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 hacerlo más llamativo.

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 3 colores para las 3 especies, usaremos "darkorange", "purple" y "cyan4" siguiendo las hermosas visualizaciones hechas por Allison Horst.

ggplot(data = pinguinos, mapping = aes(x = largo_pico_mm, y = alto_pico_mm)) +
  geom_point(aes(color = especie)) +
  geom_smooth(aes(color = especie), method = "lm") +
  scale_color_manual(values = c("darkorange","purple","cyan4")) 

¡Va quedando! Ahora, vamos a añadir algunos elementos de texto con una nueva capa ggplot: labs().

ggplot(data = pinguinos, mapping = aes(x = largo_pico_mm, y = alto_pico_mm)) +
  geom_point(aes(color = especie)) +
  geom_smooth(aes(color = especie), method = "lm") +
  scale_color_manual(values = c("darkorange","purple","cyan4")) +
  labs(title = "Dimensiones del pico de los pingüinos",
       subtitle = "Pingüinos Adelia, Barbijo y Papúaen la Estación Palmer LTER",
       x = "Largo del pico (mm)",
       y = "Alto del pico (mm)",
       color = "Especie",
       shape = "Especie") 

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 = pinguinos, mapping = aes(x = largo_pico_mm, y = alto_pico_mm)) +
  geom_point(aes(color = especie)) +
  geom_smooth(aes(color = especie), method = "lm") +
  scale_color_manual(values = c("darkorange","purple","cyan4")) +
  labs(title = "Dimensiones del pico de los pingüinos",
       subtitle = "Pingüinos Adelia, Barbijo y Papúaen la Estación Palmer LTER",
       x = "Largo del pico (mm)",
       y = "Alto del pico (mm)",
       color = "Especie",
       shape = "Especie") +
  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!

LS0tDQp0aXRsZTogIlZpc3VhbGl6YW5kbyBkYXRvcyINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogZmFsc2UNCiAgICBoaWdobGlnaHQ6IHRhbmdvDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoDQoJZWNobyA9IFRSVUUsDQoJbWVzc2FnZSA9IEZBTFNFLA0KCXdhcm5pbmcgPSBGQUxTRQ0KKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCnBpbmd1aW5vcyA8LSByZWFkX2NzdigiZGF0b3MvcGluZ3Vpbm9zLmNzdiIpDQpgYGANCg0KTGEgdmlzdWFsaXphY2nDs24gZGUgZGF0b3MgZXMgw7p0aWwgbm8gc8OzbG8gcGFyYSBleHBsb3JhciBsb3MgZGF0b3MgZSBpZGVudGlmaWNhciBsYSByZWxhY2nDs24gZW50cmUgZGlmZXJlbnRlcyB2YXJpYWJsZXMsIHNpbm8gdGFtYmnDqW4gcGFyYSBjb211bmljYXIgZWwgcmVzdWx0YWRvIGRlbCBhbsOhbGlzaXMuIEVsIHBhcXVldGUgKipnZ3Bsb3QyKiogbm9zIHBlcm1pdGUgZ2VuZXJhciBncsOhZmljb3MgZGUgYWx0YSBjYWxpZGFkIGNvbiB1bmFzIHBvY2FzIGzDrW5lYXMgZGUgY8OzZGlnby4gQ3VhbHF1aWVyIGdyw6FmaWNvIGRlIGdncGxvdCB0ZW5kcsOhIGFsIG1lbm9zIDMgY29tcG9uZW50ZXM6IGxvcyAqKmRhdG9zKiosIHVuICoqc2lzdGVtYSBkZSBjb29yZGVuYWRhcyoqIHkgdW5hICoqZ2VvbWV0csOtYSoqIChsYSByZXByZXNlbnRhY2nDs24gdmlzdWFsIGRlIGxvcyBkYXRvcykgeSBzZSBjb25zdHJ1aXLDoSBwb3IgY2FwYXMuDQoNCsKhRW1wZWNlbW9zIGEgaGFjZXIgZ3LDoWZpY29zIQ0KDQojIyBQcmltZXJhIGNhcGE6IGVsIMOhcmVhIGRlbCBncsOhZmljbw0KDQpMYSBmdW5jacOzbiBwcmluY2lwYWwgZGUgZ2dwbG90MiBzZSBsbGFtYSB0YW1iacOpbiBgZ2dwbG90KClgLCB5IG5vcyBwZXJtaXRlIGluaWNpYXIgZWwgZ3LDoWZpY28geSBkZWZpbmlyIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGdsb2JhbGVzLiBFbCBwcmltZXIgYXJndW1lbnRvIGRlIGVzdGEgZnVuY2nDs24gc2Vyw6FuIGxvcyBkYXRvcyBxdWUgcXVlcmVtb3MgdmlzdWFsaXphciwgc2llbXByZSBlbiB1biBkYXRhLmZyYW1lLiBFbiBlc3RlIGNhc28gdXRpbGl6YW1vcyBgcGluZ3Vpbm9zYC4NCg0KRWwgc2VndW5kbyBhcmd1bWVudG8gc2UgbGxhbWEgYG1hcHBpbmdgIHBvcnF1ZSBlcyBkb25kZSBkZWZpbmltb3MgY8OzbW8gc2UgIm1hcGVhbiIgbGFzIGNvbHVtbmFzIGRlbCBkYXRhLmZyYW1lIG8gbGFzIHZhcmlhYmxlcyBkZSBsb3MgZGF0b3MgYSBsYXMgcHJvcGllZGFkZXMgdmlzdWFsZXMgZGUgbGFzIGdlb21ldHLDrWFzLiBFc3RlIG1hcGVvIGVzdMOhIGRlZmluaWRvIHBvciBsYSBmdW5jacOzbiBgYWVzKClgLiBFbiBlc3RlIGNhc28gaW5kaWNhbW9zIHF1ZSBlbiBlbCBlamUgeCBxdWVyZW1vcyBncmFmaWNhciBsYSB2YXJpYWJsZSBgbGFyZ29fcGljb19tbWAgeSBlbiBlbCBlamUgeSBsYSB2YXJpYWJsZSBgYWx0b19waWNvX21tYC4NCg0KVG9kbyBlc3RvIHPDs2xvIGdlbmVyYXLDoSBsYSBwcmltZXJhIGNhcGE6IGVsIMOhcmVhIGRlbCBncsOhZmljby4NCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IHBpbmd1aW5vcywgbWFwcGluZyA9IGFlcyh4ID0gbGFyZ29fcGljb19tbSwgeSA9IGFsdG9fcGljb19tbSkpIA0KYGBgDQoNCiMjIFNlZ3VuZGEgY2FwYTogZ2VvbWV0csOtYXMNCg0KTmVjZXNpdGFtb3MgYcOxYWRpciB1bmEgbnVldmEgY2FwYSBhIG51ZXN0cm8gZ3LDoWZpY28sIGxvcyBlbGVtZW50b3MgZ2VvbcOpdHJpY29zIG8gImdlb21zIiBxdWUgcmVwcmVzZW50YXLDoW4gbG9zIGRhdG9zLiBQYXJhIGVsbG8gYcOxYWRpbW9zIHVuYSBmdW5jacOzbiBnZW9tLCBwb3IgZWplbXBsbyBzaSBxdWVyZW1vcyByZXByZXNlbnRhciBsb3MgZGF0b3MgY29uIHB1bnRvcyB1dGlsaXphcmVtb3MgYGdlb21fcG9pbnQoKWAuIFBhcmEgaGFjZXIgZXN0byBuZWNlc2l0YXJlbW9zIGFncmVnYXIgdW4gYCtgIGFsIGZpbmFsIGRlIGxhIHByaW1lcmEgY2FwYXogcGFyYSBzdW1hciB1bmEgc2VndW5kYS4gDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBwaW5ndWlub3MsIG1hcHBpbmcgPSBhZXMoeCA9IGxhcmdvX3BpY29fbW0sIHkgPSBhbHRvX3BpY29fbW0pKSArDQogIGdlb21fcG9pbnQoKQ0KYGBgDQoNCllhIHRlbmVtb3MgbnVlc3RybyBwcmltZXIgZ3LDoWZpY28hIA0KDQpUYWwgdmV6IG9ic2VydmFzdGUgcXVlIGxvcyBwdW50b3MgZXN0w6FuIGFncnVwYWRvcyBkZSB1bmEgbWFuZXJhIHBhcnRpY3VsYXIuIFF1aXrDoSBhbGd1bmEgb3RyYSB2YXJpYWJsZSBleHBsaXF1ZSBlc3RlIGNvbXBvcnRhbWllbnRvLiANCg0KUGFyYSBpbmNsdWlyIGluZm9ybWFjacOzbiBkZSBvdHJhcyB2YXJpYWJsZXMgZW4gbnVlc3RybyBncsOhZmljbyBwb2RlbW9zIGFwcm92ZWNoYXIgbGFzIGNhcmFjdGVyw61zdGljYXMgZXN0w6l0aWNhcyBkZSBsYXMgZ2VvbWV0csOtYXMuIEVuIGVzdGUgY2FzbywgcG9kZW1vcyAicGludGFyIiBsb3MgcHVudG9zIHNlZ8O6biBsYSBlc3BlY2llIGRlIHBpbmfDvGluby4gDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBwaW5ndWlub3MsIG1hcHBpbmcgPSBhZXMoeCA9IGxhcmdvX3BpY29fbW0sIHkgPSBhbHRvX3BpY29fbW0pKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gZXNwZWNpZSkpDQpgYGANCg0KRGUgbnVldm8sIHV0aWxpemFtb3MgbGEgZnVuY2nDs24gYGFlcygpYCBwYXJhIGFzaWduYXIgdW5hIHZhcmlhYmxlIGRlIG51ZXN0cm9zIGRhdG9zIGEgdW4gZWxlbWVudG8gZGVsIGdyw6FmaWNvLiDCoVkgdGFkYSEgwqFDYWRhIGVzcGVjaWUgZGUgcGluZ8O8aW5vcyB0aWVuZSBjYXJhY3RlcsOtc3RpY2FzIGRpZmVyZW50ZXMhDQoNCiMjIEHDsWFkaWVuZG8gZ2VvbWV0csOtYXMNCg0KTXVjaGFzIHZlY2VzIG5vIGJhc3RhIGNvbiBtaXJhciBsb3MgZGF0b3MgZW4gYnJ1dG8gcGFyYSBpZGVudGlmaWNhciBsYSByZWxhY2nDs24gZW50cmUgbGFzIHZhcmlhYmxlczsgZXMgbmVjZXNhcmlvIHV0aWxpemFyIGFsZ3VuYSB0cmFuc2Zvcm1hY2nDs24gZXN0YWTDrXN0aWNhIHBhcmEgcmVzYWx0YXIgZXNhcyByZWxhY2lvbmVzLCB5YSBzZWEgYWp1c3RhbmRvIHVuIG1vZGVsbyBvIGNhbGN1bGFuZG8gYWxndW5hIGVzdGFkw61zdGljYS4gDQoNClBhcmEgZWxsbywgZ2dwbG90MiBkaXNwb25lIGRlIGdlb21zIHF1ZSBjYWxjdWxhbiB0cmFuc2Zvcm1hY2lvbmVzIGVzdGFkw61zdGljYXMgY29tdW5lcy4gVmFtb3MgYSBwcm9iYXIgY29uIGBnZW9tX3Ntb3RoKClgIHBhcmEgYWp1c3RhciB1biBtb2RlbG8gbGluZWFsIGEgY2FkYSBlc3BlY2llLiANCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IHBpbmd1aW5vcywgbWFwcGluZyA9IGFlcyh4ID0gbGFyZ29fcGljb19tbSwgeSA9IGFsdG9fcGljb19tbSkpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBlc3BlY2llKSkgKw0KICBnZW9tX3Ntb290aChhZXMoY29sb3IgPSBlc3BlY2llKSwgbWV0aG9kID0gImxtIikNCmBgYA0KDQpQb3IgZGVmZWN0byBgZ2VvbV9zbW9vdGgoKWAgYWp1c3RhIGxvcyBkYXRvcyB1dGlsaXphbmRvIGVsIG3DqXRvZG8gbG9lc3MgKHJlZ3Jlc2nDs24gbGluZWFsIGxvY2FsKSBjdWFuZG8gaGF5IG1lbm9zIGRlIDEwMDAgZGF0b3MgZGlzcG9uaWJsZXMuIFBlcm8gZXMgbXV5IGNvbcO6biBxdWUgc2UgcXVpZXJhIGFqdXN0YXIgdW5hIHJlZ3Jlc2nDs24gbGluZWFsIGdsb2JhbC4gRW4gZXNlIGNhc28sIHRlbmVtb3MgcXVlIGFncmVnYXIgZWwgYXJndW1lbnRvIGBtZXRob2QgPSAibG0iYC4NCg0KIyMgSGFibGVtb3MgZGVsIGFzcGVjdG8gZGVsIGdyw6FmaWNvICANCiAgDQpQb3IgYWhvcmEgdXRpbGl6YW1vcyBlbCBhc3BlY3RvIHBvciBkZWZlY3RvIGRlIGdncGxvdC4gUG9kcsOtYW1vcyBjYW1iaWFyIGVsIGFzcGVjdG8gZGVsIGdyw6FmaWNvIHBhcmEgYWRhcHRhcmxvIGFsIGVzdGlsbyBkZSBsYSBpbnN0aXR1Y2nDs24gZG9uZGUgdHJhYmFqYW1vcywgZGUgbGEgcmV2aXN0YSBkb25kZSBsbyB2YW1vcyBhIHB1YmxpY2FyIG8gc2ltcGxlbWVudGUgcGFyYSBoYWNlcmxvIG3DoXMgbGxhbWF0aXZvLiANCiAgDQpFbXBlY2Vtb3MgcG9yIGVsIGNvbG9yLiBQYXJhIGNhbWJpYXIgZWwgYXNwZWN0byBlc3TDqXRpY28gZGUgdW4gZWxlbWVudG8gZGVsIGdyw6FmaWNvLCBhw7FhZGltb3MgdW5hIG51ZXZhIGNhcGEgY29uIGxhIGZ1bmNpw7NuIGBzY2FsZV8qYC4gRW4gZXN0ZSBjYXNvIHV0aWxpemFyZW1vcyBgc2NhbGVfY29sb3JfbWFudWFsKClgIHBhcmEgZWxlZ2lyIGxvcyBjb2xvcmVzIGRlIGxvcyBwdW50b3MgbWFudWFsbWVudGUuIFRhbWJpw6luIHBvZHLDrWFtb3MgdXRpbGl6YXIgcGFsZXRhcyBkZSBjb2xvcmVzIHByZXZpYW1lbnRlIGRlZmluaWRhcyBjb21vIGxhcyBmYW1pbGlhcyBWaXJpZGlzIG8gQ29sb3IgQnJld2VyLiANCiAgDQpOZWNlc2l0YXJlbW9zIDMgY29sb3JlcyBwYXJhIGxhcyAzIGVzcGVjaWVzLCB1c2FyZW1vcyBgImRhcmtvcmFuZ2UiYCwgYCJwdXJwbGUiYCB5IGAiY3lhbjQiYCBzaWd1aWVuZG8gbGFzIGhlcm1vc2FzIHZpc3VhbGl6YWNpb25lcyBoZWNoYXMgcG9yIEFsbGlzb24gSG9yc3QuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBwaW5ndWlub3MsIG1hcHBpbmcgPSBhZXMoeCA9IGxhcmdvX3BpY29fbW0sIHkgPSBhbHRvX3BpY29fbW0pKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gZXNwZWNpZSkpICsNCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG9yID0gZXNwZWNpZSksIG1ldGhvZCA9ICJsbSIpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImRhcmtvcmFuZ2UiLCJwdXJwbGUiLCJjeWFuNCIpKSANCmBgYA0KDQrCoVZhIHF1ZWRhbmRvISBBaG9yYSwgdmFtb3MgYSBhw7FhZGlyIGFsZ3Vub3MgZWxlbWVudG9zIGRlIHRleHRvIGNvbiB1bmEgbnVldmEgY2FwYSBnZ3Bsb3Q6IGBsYWJzKClgLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gcGluZ3Vpbm9zLCBtYXBwaW5nID0gYWVzKHggPSBsYXJnb19waWNvX21tLCB5ID0gYWx0b19waWNvX21tKSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGVzcGVjaWUpKSArDQogIGdlb21fc21vb3RoKGFlcyhjb2xvciA9IGVzcGVjaWUpLCBtZXRob2QgPSAibG0iKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJkYXJrb3JhbmdlIiwicHVycGxlIiwiY3lhbjQiKSkgKw0KICBsYWJzKHRpdGxlID0gIkRpbWVuc2lvbmVzIGRlbCBwaWNvIGRlIGxvcyBwaW5nw7xpbm9zIiwNCiAgICAgICBzdWJ0aXRsZSA9ICJQaW5nw7xpbm9zIEFkZWxpYSwgQmFyYmlqbyB5IFBhcMO6YWVuIGxhIEVzdGFjacOzbiBQYWxtZXIgTFRFUiIsDQogICAgICAgeCA9ICJMYXJnbyBkZWwgcGljbyAobW0pIiwNCiAgICAgICB5ID0gIkFsdG8gZGVsIHBpY28gKG1tKSIsDQogICAgICAgY29sb3IgPSAiRXNwZWNpZSIsDQogICAgICAgc2hhcGUgPSAiRXNwZWNpZSIpIA0KYGBgDQoNCkFob3JhIGxhcyBldGlxdWV0YXMgZGUgbG9zIGVqZXMgc29uIG3DoXMgbGVnaWJsZXMgeSB0ZW5lbW9zIHVuIHTDrXR1bG8geSB1biBzdWJ0w610dWxvIHF1ZSBleHBsaWNhbiBkZSBxdcOpIHRyYXRhIGVsIGdyw6FmaWNvLiANCg0KUG9kcsOtYW1vcyBzZWd1aXIgY2FtYmlhbmRvIGVzdG8gaW5maW5pdGFtZW50ZSBwZXJvIHRlcm1pbmFyZW1vcyBjb24gZWwgYXNwZWN0byBnZW5lcmFsIGRlbCBncsOhZmljby4gDQoNCkVsIGFzcGVjdG8gZ2VuZXJhbCBkZSB1biBncsOhZmljbyBlc3TDoSBkZWZpbmlkbyBwb3Igc3UgdGVtYS4gZ2dwbG90MiB0aWVuZSBtdWNob3MgdGVtYXMgZGlzcG9uaWJsZXMgeSBwYXJhIHRvZG9zIGxvcyBndXN0b3MuIFBlcm8gdGFtYmnDqW4gaGF5IG90cm9zIHBhcXVldGVzIHF1ZSBhbXBsw61hbiBsYXMgcG9zaWJpbGlkYWRlcywgcG9yIGVqZW1wbG8gZ2d0aGVtZXMuIFBvciBkZWZlY3RvIGdncGxvdDIgdXRpbGl6YSBgdGhlbWVfZ3JleSgpYCwgcHJvYmVtb3MgY29uIGB0aGVtZV9taW5pbWFsKClgOg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gcGluZ3Vpbm9zLCBtYXBwaW5nID0gYWVzKHggPSBsYXJnb19waWNvX21tLCB5ID0gYWx0b19waWNvX21tKSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGVzcGVjaWUpKSArDQogIGdlb21fc21vb3RoKGFlcyhjb2xvciA9IGVzcGVjaWUpLCBtZXRob2QgPSAibG0iKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJkYXJrb3JhbmdlIiwicHVycGxlIiwiY3lhbjQiKSkgKw0KICBsYWJzKHRpdGxlID0gIkRpbWVuc2lvbmVzIGRlbCBwaWNvIGRlIGxvcyBwaW5nw7xpbm9zIiwNCiAgICAgICBzdWJ0aXRsZSA9ICJQaW5nw7xpbm9zIEFkZWxpYSwgQmFyYmlqbyB5IFBhcMO6YWVuIGxhIEVzdGFjacOzbiBQYWxtZXIgTFRFUiIsDQogICAgICAgeCA9ICJMYXJnbyBkZWwgcGljbyAobW0pIiwNCiAgICAgICB5ID0gIkFsdG8gZGVsIHBpY28gKG1tKSIsDQogICAgICAgY29sb3IgPSAiRXNwZWNpZSIsDQogICAgICAgc2hhcGUgPSAiRXNwZWNpZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KPiBBaG9yYSBlcyB0dSB0dXJuby4gRWxpZ2UgdW4gdGVtYSBxdWUgdGUgZ3VzdGUgeSBwcnXDqWJhbG8uIEFkZW3DoXMsIHNpIHNlIHRlIG9jdXJyZSB1biB0w610dWxvIG1lam9yLCDCoW1vZGlmw61jYWxvIQ0K