Visualizar datos es útil para identificar a relación entre distintas variables pero también para comunicar el análisis de los datos y resultados. El paquete ggplot2 permite generar gráficos de gran calidad en pocos pasos. Cualquier gráfico de ggplot tendrá como mínimo 3 componentes: los datos, un sistema de coordenadas y una geometría (la representación visual de los datos) y se irá construyendo por capas.

Primera capa: el área del gráfico

Cómo siempre será necesario cargar los paquetes que vamos a usar y ya que estamos los datos con los que venimos trabajando:

library(readr)
library(ggplot2)
paises <- read_csv("datos/paises.csv")
## Parsed with column specification:
## cols(
##   pais = col_character(),
##   continente = col_character(),
##   anio = col_double(),
##   esperanza_de_vida = col_double(),
##   poblacion = col_double(),
##   pib_per_capita = col_double()
## )

La función principal de ggplot2 es justamente ggplot() que nos permite iniciar el gráfico y además 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 usamos paises.

El segundo argumento se llama mapping justamente porque mapea o dibuja los ejes del gráfico y siempre va acompañado de la función aes(). La función aes() recibe las propiedades estéticas del gráfico (o aesthetic en inglés) a partir de las variables (o columnas) del data.frame estamos usando. En este caso le indicamos que en el eje x querremos graficar la variable pib_per_capita y en eje y la variable esperanza_de_vida.

Pero esta sola función no es suficiente, solo genera la primera capa: el área del gráfico.

ggplot(data = paises, mapping = aes(x = pib_per_capita, y = esperanza_de_vida))

Segunda capa: geometrías

Necesitamos agregar una nueva capa a nuestro gráfico, los elementos geométricos o geoms que representaran los datos. Para esto sumamos una función geom, por ejemplo si queremos representar los datos con puntos usaremos geom_point()

ggplot(data = paises, mapping = aes(x = pib_per_capita, y = esperanza_de_vida)) +
  geom_point()

¡Nuestro primer gráfico!

Primer desafío

Ahora es tu turno. Modifica el gráfico anterior para visualizar cómo cambia la esperanza de vida a lo largo de los años.

¿Te parece útil este gráfico?

Este gráfico tiene muchísima información porque tiene un punto por cada país para cada año para visualizar la esperanza de vida. Pero por ahora no podemos identificar esos países, necesitamos agregar información al gráfico.

ggplot(data = paises, mapping = aes(x = anio, y = esperanza_de_vida)) +
  geom_point()

Mapear variables a elementos

Una posible solución sería utilizar otras variables de nuestros datos, por ejemplo continente y mapear el color de los puntos de a cuerdo al continente que pertenecen.

ggplot(data = paises, mapping = aes(x = anio, y = esperanza_de_vida)) +
  geom_point(aes(color = continente))

Ahh, ahora está un poco mejor. Por ejemplo ya podemos ver que muchos países de Europa (los puntos celestes) tienen en promedio mayor esperanza de vida a lo largo de los años que muchos países de África (los puntos rojos). Aún no podemos identificar los países individualmente pero podemos sacar algo más de información de nuestros datos.

Algo muy importante a tener en cuenta: los puntos toman un color de acuerdo a una variable de los datos, y para que ggplot2 identifique esa variable (en este caso continente) es necesario incluirla dentro de una función aes().

Otras geometrías

Este gráfico posiblemente no sea muy adecuado si queremos visualizar la evolución de una variable a lo largo del tiempo, necesitamos cambiar la geometría a lineas usando geom_line()

ggplot(data = paises, mapping = aes(x = anio, y = esperanza_de_vida)) +
  geom_line(aes(color = continente))

Por suerte las funciones geom_*() tienen más o menos nombres amigables. Pero el gráfico sigue teniendo problemas, al parecer dibujó una línea por continente. Si estuviéramos dibujando este gráfico con lápiz y papel muy posiblemente hubiéramos identificado los puntos que corresponden a cada país y los hubiéramos “unido con líneas”, necesitamos que ggplot2 haga esto. ¿Cómo le indicamos que puntos corresponde a cada país? Necesitamos que los agrupe por la variable pais (¡qué bueno que tenemos toda esa información en nuestra base de datos!).

ggplot(data = paises, mapping = aes(x = anio, y = esperanza_de_vida)) +
  geom_line(aes(color = continente, group = pais))

Usamos el argumento group = y de nuevo, lo incluimos dentro de la función aes() para indicarle a ggplot2 que busque la variable pais dentro del data.frame que estamos usando.

Y ahora si, conseguimos el gráfico que estamos buscando.

Segundo desafío

Cuando mencionamos que ggplot2 construye gráficos por capas, lo decíamos en serio! Hasta ahora tenemos dos capas: el área del gráfico y una geometría (las líneas).

  1. Sumá una tercera capa para visualizar puntos además de las líneas.
  2. ¿Porqué los puntos ahora no siguen los colores de los continentes?
  3. ¿Qué cambio podrías hacer para que los puntos también tengan color según el continente?

Acá surge una característica importante de las capas: pueden tener apariencia independiente si solo mapeamos el color en la capa de las líneas y no en la capa de los puntos. Al mismo tiempo, si quisiéramos que todas las capas tenga la misma apariencia podemos incluir el argumento color =en la función global ggpplot() o repetirlo en cada capa.

ggplot(paises, aes(anio, esperanza_de_vida)) +
  geom_line(aes(color = continente, group = pais)) +
  geom_point()

Si te preguntabas a donde fueron a parar el data =, el mapping = y los nombres de los argumentos adentro de la función aes(), x = e y =, resulta que estamos aprovechando que tanto ggplot2 como nosotros ahora sabemos en que orden recibe la información cada función. Siempre el primer elemento que le pasemos o indiquemos a la función ggplot() será el data.frame.

Algunos argumentos para cambiar la apariencia de las geometrías son:

El mapeo entre una variable y un parámetro de geometría se hace a través de una escala. La escala de colores es lo que define, por ejemplo, que los puntos donde la variable continente toma el valor "África" van a tener el color rosa (), donde toma el valor "Américas", mostaza (), etc…

Modificar elementos utilizando un valor único

Es posible que en algún momento necesites cambiar la apariencia de los elementos o geometrías independientemente de las variables de tu data.frame. Por ejemplo podrías querer que todos los puntos sean de un único color: rojos. En este caso geom_point(aes(color = "red")) no va a funcionar -ojo que los colores van en inglés-. Lo que ese código dice es que mapee el parámetro geométrico “color” a una variable que contiene el valor "red" para todas las filas. El mapeo se hace a través de la escala, que va a asignarle un valor (rosa ) a los puntos correspondientes al valor "red".

Ahora que no nos interesa mapear el color a una variable, podemos mover ese argumento afuera de la función aes(): geom_point(color = "red").

Relación entre variables

Muchas veces no es suficiente con mirar los datos crudos para identificar la relación entre las variables; es necesario usar alguna transformación estadística que resalte esas relaciones, ya sea ajustando una recta o calculando promedios.

Para alguna transformaciones estadísticas comunes, {ggplot2} tiene geoms ya programados, pero muchas veces es posible que necesitemos manipular los datos antes de poder hacer un gráfico. A veces esa manipulación será compleja y entonces para no repetir el cálculo muchas veces, guardaremos los datos modificados en una nueva variable. Pero también podemos encadenar la manipulación de los datos y el gráfico resultante.

Por ejemplo, calculemos la esperanza de vida media por continente y para cada año usando dplyr y luego grafiquemos la esperanza_de_vida_media a los largo de los anios:

library(dplyr)

paises %>% 
  group_by(continente, anio) %>% 
  summarise(esperanza_de_vida_media = mean(esperanza_de_vida)) %>% 
  ggplot(aes(anio, esperanza_de_vida_media)) +  # Acá se acaban los %>% y comienzan los "+"
  geom_point()
## `summarise()` regrouping output by 'continente' (override with `.groups` argument)

Esto es posible gracias al operador %>% que le pasa el resultado de summarise() a la función ggplot(). Y este resultado no es ni más ni menos que el data.frame que necesitamos para hacer nuestro gráfico. Es importante notar que una vez que comenzamos el gráfico ya no se puede usar el operador %>% y las capas del gráfico se suman como siempre con +.

Este gráfico entonces parece mostrar que la esperanza de vida fue aumentado a lo largo de los años pero sería interesante ver esa relación más explícitamente agregando una nueva capa con geom_smooth().

paises %>% 
  group_by(continente, anio) %>% 
  summarise(esperanza_de_vida_media = mean(esperanza_de_vida)) %>% 
  ggplot(aes(anio, esperanza_de_vida_media)) +  
  geom_point() +
  geom_smooth()
## `summarise()` regrouping output by 'continente' (override with `.groups` argument)
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

Como dice en el mensaje, por defecto geom_smooth() suaviza los datos usando el método loess (regresión lineal local) cuando hay menos de 1000 datos. Seguramente va a ser muy común que quieras ajustar una regresión lineal global. En ese caso, hay que poner method = "lm":

paises %>% 
  group_by(continente, anio) %>% 
  summarise(esperanza_de_vida_media = mean(esperanza_de_vida)) %>% 
  ggplot(aes(anio, esperanza_de_vida_media)) +  
  geom_point() +
  geom_smooth(method = "lm")
## `summarise()` regrouping output by 'continente' (override with `.groups` argument)
## `geom_smooth()` using formula 'y ~ x'

En gris nos muestra el intervalo de confianza al rededor de este suavizado que en este caso es bastante grande porque tenemos pocos datos!

Cómo cualquier geom, podemos modificar el color, el grosor de la línea y casi cualquier cosa que se te ocurra.

Tercer desafío

Modificá el siguiente código para obtener el gráfico que se muestra más abajo.

paises %>% 
  group_by(continente, _____) %>% 
  summarise(esperanza_de_vida_media = mean(esperanza_de_vida)) %>% 
  ggplot(aes(anio, ________________)) +  
  geom_point(aes(color = continente), size = 3, shape = _____) +
  geom_smooth(color = continente) 
## `summarise()` regrouping output by 'continente' (override with `.groups` argument)
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'

LS0tCnRpdGxlOiAiVmlzdWFsaXphY2nDs24gZGUgZGF0b3MgY29uIHtnZ3Bsb3QyfSBJIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogZmFsc2UKICAgIGhpZ2hsaWdodDogdGFuZ28gICAgCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKYGBgCgpWaXN1YWxpemFyIGRhdG9zIGVzIMO6dGlsIHBhcmEgaWRlbnRpZmljYXIgYSByZWxhY2nDs24gZW50cmUgZGlzdGludGFzIHZhcmlhYmxlcyBwZXJvIHRhbWJpw6luIHBhcmEgY29tdW5pY2FyIGVsIGFuw6FsaXNpcyBkZSBsb3MgZGF0b3MgeSByZXN1bHRhZG9zLiBFbCBwYXF1ZXRlIGBnZ3Bsb3QyYCBwZXJtaXRlIGdlbmVyYXIgZ3LDoWZpY29zIGRlIGdyYW4gY2FsaWRhZCBlbiBwb2NvcyBwYXNvcy4gQ3VhbHF1aWVyIGdyw6FmaWNvIGRlIGdncGxvdCB0ZW5kcsOhIGNvbW8gbcOtbmltbyAzIGNvbXBvbmVudGVzOiBsb3MgKipkYXRvcyoqLCB1biAqKnNpc3RlbWEgZGUgY29vcmRlbmFkYXMqKiB5IHVuYSAqKmdlb21ldHLDrWEqKiAobGEgcmVwcmVzZW50YWNpw7NuIHZpc3VhbCBkZSBsb3MgZGF0b3MpIHkgc2UgaXLDoSBjb25zdHJ1eWVuZG8gcG9yIGNhcGFzLiAKCiMjIyBQcmltZXJhIGNhcGE6IGVsIMOhcmVhIGRlbCBncsOhZmljbwoKQ8OzbW8gc2llbXByZSBzZXLDoSBuZWNlc2FyaW8gY2FyZ2FyIGxvcyBwYXF1ZXRlcyBxdWUgdmFtb3MgYSB1c2FyIHkgeWEgcXVlIGVzdGFtb3MgbG9zIGRhdG9zIGNvbiBsb3MgcXVlIHZlbmltb3MgdHJhYmFqYW5kbzoKCmBgYHtyfQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGdncGxvdDIpCnBhaXNlcyA8LSByZWFkX2NzdigiZGF0b3MvcGFpc2VzLmNzdiIpCmBgYAoKTGEgZnVuY2nDs24gcHJpbmNpcGFsIGRlIGdncGxvdDIgZXMganVzdGFtZW50ZSBgZ2dwbG90KClgIHF1ZSBub3MgcGVybWl0ZSAqaW5pY2lhciogZWwgZ3LDoWZpY28geSBhZGVtw6FzIGRlZmluaXIgbGFzIGNhcmFjdGVyw61zdGljYXMgKmdsb2JhbGVzKi4gRWwgcHJpbWVyIGFyZ3VtZW50byBkZSBlc3RhIGZ1bmNpw7NuIHNlcsOhbiBsb3MgZGF0b3MgcXVlIHF1ZXJlbW9zIHZpc3VhbGl6YXIsIHNpZW1wcmUgZW4gdW4gZGF0YS5mcmFtZS4gRW4gZXN0ZSBjYXNvIHVzYW1vcyBgcGFpc2VzYC4gCgpFbCBzZWd1bmRvIGFyZ3VtZW50byBzZSBsbGFtYSBtYXBwaW5nIGp1c3RhbWVudGUgcG9ycXVlICptYXBlYSogbyAqZGlidWphKiBsb3MgZWplcyBkZWwgZ3LDoWZpY28geSAqKnNpZW1wcmUqKiB2YSBhY29tcGHDsWFkbyBkZSBsYSBmdW5jacOzbiBgYWVzKClgLiBMYSBmdW5jacOzbiBgYWVzKClgIHJlY2liZSBsYXMgcHJvcGllZGFkZXMgZXN0w6l0aWNhcyBkZWwgZ3LDoWZpY28gKG8gKmFlc3RoZXRpYyogZW4gaW5nbMOpcykgYSBwYXJ0aXIgZGUgbGFzIHZhcmlhYmxlcyAobyBjb2x1bW5hcykgZGVsIGRhdGEuZnJhbWUgZXN0YW1vcyB1c2FuZG8uIEVuIGVzdGUgY2FzbyBsZSBpbmRpY2Ftb3MgcXVlIGVuIGVsIGVqZSAqKngqKiBxdWVycmVtb3MgZ3JhZmljYXIgbGEgdmFyaWFibGUgYHBpYl9wZXJfY2FwaXRhYCB5IGVuIGVqZSAqKnkqKiBsYSB2YXJpYWJsZSBgZXNwZXJhbnphX2RlX3ZpZGFgLgoKUGVybyBlc3RhIHNvbGEgZnVuY2nDs24gbm8gZXMgc3VmaWNpZW50ZSwgc29sbyBnZW5lcmEgbGEgcHJpbWVyYSBjYXBhOiBlbCDDoXJlYSBkZWwgZ3LDoWZpY28uCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBwYWlzZXMsIG1hcHBpbmcgPSBhZXMoeCA9IHBpYl9wZXJfY2FwaXRhLCB5ID0gZXNwZXJhbnphX2RlX3ZpZGEpKQpgYGAKCiMjIyBTZWd1bmRhIGNhcGE6IGdlb21ldHLDrWFzCk5lY2VzaXRhbW9zIGFncmVnYXIgdW5hIG51ZXZhIGNhcGEgYSBudWVzdHJvIGdyw6FmaWNvLCBsb3MgZWxlbWVudG9zIGdlb23DqXRyaWNvcyBvICpnZW9tcyogcXVlIHJlcHJlc2VudGFyYW4gbG9zIGRhdG9zLiBQYXJhIGVzdG8gKnN1bWFtb3MqIHVuYSBmdW5jacOzbiBnZW9tLCBwb3IgZWplbXBsbyBzaSBxdWVyZW1vcyByZXByZXNlbnRhciBsb3MgZGF0b3MgY29uIHB1bnRvcyB1c2FyZW1vcyBgZ2VvbV9wb2ludCgpYAoKYGBge3J9CmdncGxvdChkYXRhID0gcGFpc2VzLCBtYXBwaW5nID0gYWVzKHggPSBwaWJfcGVyX2NhcGl0YSwgeSA9IGVzcGVyYW56YV9kZV92aWRhKSkgKwogIGdlb21fcG9pbnQoKQpgYGAKCsKhTnVlc3RybyBwcmltZXIgZ3LDoWZpY28hIAoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqUHJpbWVyIGRlc2Fmw61vKioKCkFob3JhIGVzIHR1IHR1cm5vLiBNb2RpZmljYSBlbCBncsOhZmljbyBhbnRlcmlvciBwYXJhIHZpc3VhbGl6YXIgY8OzbW8gY2FtYmlhIGxhIGVzcGVyYW56YSBkZSB2aWRhIGEgbG8gbGFyZ28gZGUgbG9zIGHDsW9zLiAKCsK/VGUgcGFyZWNlIMO6dGlsIGVzdGUgZ3LDoWZpY28/Cjo6OgoKRXN0ZSBncsOhZmljbyB0aWVuZSBtdWNow61zaW1hIGluZm9ybWFjacOzbiBwb3JxdWUgdGllbmUgdW4gcHVudG8gcG9yIGNhZGEgcGHDrXMgcGFyYSBjYWRhIGHDsW8gcGFyYSB2aXN1YWxpemFyIGxhIGVzcGVyYW56YSBkZSB2aWRhLiBQZXJvIHBvciBhaG9yYSBubyBwb2RlbW9zIGlkZW50aWZpY2FyIGVzb3MgcGHDrXNlcywgbmVjZXNpdGFtb3MgYWdyZWdhciBpbmZvcm1hY2nDs24gYWwgZ3LDoWZpY28uCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBwYWlzZXMsIG1hcHBpbmcgPSBhZXMoeCA9IGFuaW8sIHkgPSBlc3BlcmFuemFfZGVfdmlkYSkpICsKICBnZW9tX3BvaW50KCkKYGBgCgojIyMgTWFwZWFyIHZhcmlhYmxlcyBhIGVsZW1lbnRvcwoKVW5hIHBvc2libGUgc29sdWNpw7NuIHNlcsOtYSB1dGlsaXphciBvdHJhcyB2YXJpYWJsZXMgZGUgbnVlc3Ryb3MgZGF0b3MsIHBvciBlamVtcGxvIGBjb250aW5lbnRlYCB5ICptYXBlYXIqIGVsIGNvbG9yIGRlIGxvcyBwdW50b3MgZGUgYSBjdWVyZG8gYWwgY29udGluZW50ZSBxdWUgcGVydGVuZWNlbi4KCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IHBhaXNlcywgbWFwcGluZyA9IGFlcyh4ID0gYW5pbywgeSA9IGVzcGVyYW56YV9kZV92aWRhKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gY29udGluZW50ZSkpCmBgYAoKQWhoLCBhaG9yYSBlc3TDoSB1biBwb2NvIG1lam9yLiBQb3IgZWplbXBsbyB5YSBwb2RlbW9zIHZlciBxdWUgbXVjaG9zIHBhw61zZXMgZGUgRXVyb3BhIChsb3MgcHVudG9zIGNlbGVzdGVzKSB0aWVuZW4gZW4gcHJvbWVkaW8gbWF5b3IgZXNwZXJhbnphIGRlIHZpZGEgYSBsbyBsYXJnbyBkZSBsb3MgYcOxb3MgcXVlIG11Y2hvcyBwYcOtc2VzIGRlIMOBZnJpY2EgKGxvcyBwdW50b3Mgcm9qb3MpLiBBw7puIG5vIHBvZGVtb3MgaWRlbnRpZmljYXIgbG9zIHBhw61zZXMgaW5kaXZpZHVhbG1lbnRlIHBlcm8gcG9kZW1vcyBzYWNhciBhbGdvIG3DoXMgZGUgaW5mb3JtYWNpw7NuIGRlIG51ZXN0cm9zIGRhdG9zLiAKCgpBbGdvIG11eSBpbXBvcnRhbnRlIGEgdGVuZXIgZW4gY3VlbnRhOiAqKmxvcyBwdW50b3MgdG9tYW4gdW4gY29sb3IgZGUgYWN1ZXJkbyBhIHVuYSB2YXJpYWJsZSBkZSBsb3MgZGF0b3MqKiwgeSBwYXJhIHF1ZSBnZ3Bsb3QyIGlkZW50aWZpcXVlIGVzYSB2YXJpYWJsZSAoZW4gZXN0ZSBjYXNvIGBjb250aW5lbnRlYCkgZXMgbmVjZXNhcmlvIGluY2x1aXJsYSBkZW50cm8gZGUgdW5hIGZ1bmNpw7NuIGBhZXMoKWAuCgojIyMgT3RyYXMgZ2VvbWV0csOtYXMKCkVzdGUgZ3LDoWZpY28gcG9zaWJsZW1lbnRlIG5vIHNlYSBtdXkgYWRlY3VhZG8gc2kgcXVlcmVtb3MgdmlzdWFsaXphciBsYSAqZXZvbHVjacOzbiogZGUgdW5hIHZhcmlhYmxlIGEgbG8gbGFyZ28gZGVsIHRpZW1wbywgbmVjZXNpdGFtb3MgY2FtYmlhciBsYSBnZW9tZXRyw61hIGEgbGluZWFzIHVzYW5kbyBgZ2VvbV9saW5lKClgCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBwYWlzZXMsIG1hcHBpbmcgPSBhZXMoeCA9IGFuaW8sIHkgPSBlc3BlcmFuemFfZGVfdmlkYSkpICsKICBnZW9tX2xpbmUoYWVzKGNvbG9yID0gY29udGluZW50ZSkpCmBgYAoKUG9yIHN1ZXJ0ZSBsYXMgZnVuY2lvbmVzIGBnZW9tXyooKWAgdGllbmVuIG3DoXMgbyBtZW5vcyBub21icmVzIGFtaWdhYmxlcy4gUGVybyBlbCBncsOhZmljbyBzaWd1ZSB0ZW5pZW5kbyBwcm9ibGVtYXMsIGFsIHBhcmVjZXIgZGlidWrDsyB1bmEgbMOtbmVhIHBvciBjb250aW5lbnRlLiBTaSBlc3R1dmnDqXJhbW9zIGRpYnVqYW5kbyBlc3RlIGdyw6FmaWNvIGNvbiBsw6FwaXogeSBwYXBlbCBtdXkgcG9zaWJsZW1lbnRlIGh1YmnDqXJhbW9zIGlkZW50aWZpY2FkbyBsb3MgcHVudG9zIHF1ZSBjb3JyZXNwb25kZW4gYSBjYWRhIHBhw61zIHkgbG9zIGh1YmnDqXJhbW9zICJ1bmlkbyBjb24gbMOtbmVhcyIsIG5lY2VzaXRhbW9zIHF1ZSBnZ3Bsb3QyIGhhZ2EgZXN0by4gwr9Dw7NtbyBsZSBpbmRpY2Ftb3MgcXVlIHB1bnRvcyBjb3JyZXNwb25kZSBhIGNhZGEgcGHDrXM/IE5lY2VzaXRhbW9zIHF1ZSBsb3MgKmFncnVwZSogcG9yIGxhIHZhcmlhYmxlIGBwYWlzYCAowqFxdcOpIGJ1ZW5vIHF1ZSB0ZW5lbW9zIHRvZGEgZXNhIGluZm9ybWFjacOzbiBlbiBudWVzdHJhIGJhc2UgZGUgZGF0b3MhKS4KCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IHBhaXNlcywgbWFwcGluZyA9IGFlcyh4ID0gYW5pbywgeSA9IGVzcGVyYW56YV9kZV92aWRhKSkgKwogIGdlb21fbGluZShhZXMoY29sb3IgPSBjb250aW5lbnRlLCBncm91cCA9IHBhaXMpKQpgYGAKClVzYW1vcyBlbCBhcmd1bWVudG8gYGdyb3VwID1gIHkgZGUgbnVldm8sIGxvIGluY2x1aW1vcyBkZW50cm8gZGUgbGEgZnVuY2nDs24gYGFlcygpYCBwYXJhIGluZGljYXJsZSBhIGdncGxvdDIgcXVlIGJ1c3F1ZSBsYSB2YXJpYWJsZSBgcGFpc2AgZGVudHJvIGRlbCBkYXRhLmZyYW1lIHF1ZSBlc3RhbW9zIHVzYW5kby4gCgpZIGFob3JhIHNpLCBjb25zZWd1aW1vcyBlbCBncsOhZmljbyBxdWUgZXN0YW1vcyBidXNjYW5kby4gCgo6Ojogey5hbGVydCAuYWxlcnQtaW5mb30KKipTZWd1bmRvIGRlc2Fmw61vKioKCkN1YW5kbyBtZW5jaW9uYW1vcyBxdWUgZ2dwbG90MiBjb25zdHJ1eWUgZ3LDoWZpY29zIHBvciBjYXBhcywgbG8gZGVjw61hbW9zIGVuIHNlcmlvISBIYXN0YSBhaG9yYSB0ZW5lbW9zIGRvcyBjYXBhczogZWwgw6FyZWEgZGVsIGdyw6FmaWNvIHkgdW5hIGdlb21ldHLDrWEgKGxhcyBsw61uZWFzKS4gCgoxLiBTdW3DoSB1bmEgdGVyY2VyYSBjYXBhIHBhcmEgdmlzdWFsaXphciBwdW50b3MgYWRlbcOhcyBkZSBsYXMgbMOtbmVhcy4KMi4gwr9Qb3JxdcOpIGxvcyBwdW50b3MgYWhvcmEgbm8gc2lndWVuIGxvcyBjb2xvcmVzIGRlIGxvcyBjb250aW5lbnRlcz8KMy4gwr9RdcOpIGNhbWJpbyBwb2Ryw61hcyBoYWNlciBwYXJhIHF1ZSBsb3MgcHVudG9zIHRhbWJpw6luIHRlbmdhbiBjb2xvciBzZWfDum4gZWwgY29udGluZW50ZT8KOjo6CgpBY8OhIHN1cmdlIHVuYSBjYXJhY3RlcsOtc3RpY2EgaW1wb3J0YW50ZSBkZSBsYXMgY2FwYXM6IHB1ZWRlbiB0ZW5lciBhcGFyaWVuY2lhIGluZGVwZW5kaWVudGUgc2kgc29sbyAqbWFwZWFtb3MqIGVsIGNvbG9yIGVuIGxhIGNhcGEgZGUgbGFzIGzDrW5lYXMgeSBubyBlbiBsYSBjYXBhIGRlIGxvcyBwdW50b3MuIEFsIG1pc21vIHRpZW1wbywgc2kgcXVpc2nDqXJhbW9zIHF1ZSB0b2RhcyBsYXMgY2FwYXMgdGVuZ2EgbGEgbWlzbWEgYXBhcmllbmNpYSBwb2RlbW9zIGluY2x1aXIgZWwgYXJndW1lbnRvIGBjb2xvciA9IGBlbiBsYSBmdW5jacOzbiBnbG9iYWwgYGdncHBsb3QoKWAgbyByZXBldGlybG8gZW4gY2FkYSBjYXBhLgoKYGBge3J9CmdncGxvdChwYWlzZXMsIGFlcyhhbmlvLCBlc3BlcmFuemFfZGVfdmlkYSkpICsKICBnZW9tX2xpbmUoYWVzKGNvbG9yID0gY29udGluZW50ZSwgZ3JvdXAgPSBwYWlzKSkgKwogIGdlb21fcG9pbnQoKQpgYGAKCjo6OiB7LmFsZXJ0IC5hbGVydC1zdWNjZXNzfQoKU2kgdGUgcHJlZ3VudGFiYXMgYSBkb25kZSBmdWVyb24gYSBwYXJhciBlbCBgZGF0YSA9IGAsIGVsIGBtYXBwaW5nID0gYCB5IGxvcyBub21icmVzIGRlIGxvcyBhcmd1bWVudG9zIGFkZW50cm8gZGUgbGEgZnVuY2nDs24gYGFlcygpYCwgYHggPSBgIGUgYHkgPSBgLCByZXN1bHRhIHF1ZSBlc3RhbW9zIGFwcm92ZWNoYW5kbyBxdWUgdGFudG8gZ2dwbG90MiBjb21vIG5vc290cm9zIGFob3JhIHNhYmVtb3MgZW4gcXVlIG9yZGVuIHJlY2liZSBsYSBpbmZvcm1hY2nDs24gY2FkYSBmdW5jacOzbi4gU2llbXByZSBlbCBwcmltZXIgZWxlbWVudG8gcXVlIGxlICpwYXNlbW9zKiBvIGluZGlxdWVtb3MgYSBsYSBmdW5jacOzbiBgZ2dwbG90KClgIHNlcsOhIGVsIGRhdGEuZnJhbWUuCjo6OgoKQWxndW5vcyBhcmd1bWVudG9zIHBhcmEgY2FtYmlhciBsYSBhcGFyaWVuY2lhIGRlIGxhcyBnZW9tZXRyw61hcyBzb246CgoqIGBjb2xvcmAgbyBgY29sb3VyYCBtb2RpZmljYSBlbCBjb2xvciBkZSBsw61uZWFzIHkgcHVudG9zCiogYGZpbGxgbW9kaWZpY2EgZWwgY29sb3IgZGVsIMOhcmVhIGRlIHVuIGVsZW1lbnRvLCBwb3IgZWplbXBsbyBlbCByZWxsZW5vIGRlIHVuIHB1bnRvCiogYGxpbmV0eXBlYCBtb2RpZmljYSBlbCB0aXBvIGRlIGzDrW5lYSAocHVudGVhZGEsIGNvbnRpbnVhLCBjb24gZ3Vpb25lcywgZXRjLikKKiBgcGNoYCBtb2RpZmljYSBlbCB0YW1hw7FvIGRlbCBwdW50bwoqIGBzaXplYCBtb2RpZmljYSBlbCB0YW1hw7FvIGRlIGxvcyBlbGVtZW50b3MgKHBvciBlamVtcGxvIGVsIHRhbWHDsW8gZGUgcHVudG9zIG8gZWwgZ3Jvc29yIGRlIGzDrW5lYXMpCiogYGFscGhhYCBtb2RpZmljYSBsYSB0cmFuc3BhcmVuY2lhIGRlIGxvcyBlbGVtZW50b3MgKDEgPSBvcGFjbywgMCA9IHRyYW5zcGFyZW50ZSkKKiBgc2hhcGVgIG1vZGlmaWNhIGVsIHRpcG8gZGUgcHVudG8gKGPDrXJjdWxvcywgY3VhZHJhZG9zLCB0cmnDoW5ndWxvcywgZXRjLikKCkVsICptYXBlbyogZW50cmUgdW5hIHZhcmlhYmxlIHkgdW4gcGFyw6FtZXRybyBkZSBnZW9tZXRyw61hIHNlIGhhY2UgYSB0cmF2w6lzIGRlIHVuYSAqKmVzY2FsYSoqLiBMYSBlc2NhbGEgZGUgY29sb3JlcyBlcyBsbyBxdWUgZGVmaW5lLCBwb3IgZWplbXBsbywgcXVlIGxvcyBwdW50b3MgZG9uZGUgbGEgdmFyaWFibGUgYGNvbnRpbmVudGVgIHRvbWEgZWwgdmFsb3IgYCLDgWZyaWNhImAgdmFuIGEgdGVuZXIgZWwgY29sb3Igcm9zYSAoPHNwYW4gc3R5bGU9ImNvbG9yOiNGNzdENzUiPiYjOTY3OTs8L3NwYW4+KSwgZG9uZGUgdG9tYSBlbCB2YWxvciBgIkFtw6lyaWNhcyJgLCBtb3N0YXphICg8c3BhbiBzdHlsZT0iY29sb3I6I0I3Qjk0MCI+JiM5Njc5Ozwvc3Bhbj4pLCBldGMuLi4KCjo6OiB7LmFsZXJ0IC5hbGVydC1zdWNjZXNzfQoqKk1vZGlmaWNhciBlbGVtZW50b3MgdXRpbGl6YW5kbyB1biB2YWxvciDDum5pY28qKgoKRXMgcG9zaWJsZSBxdWUgZW4gYWxnw7puIG1vbWVudG8gbmVjZXNpdGVzIGNhbWJpYXIgbGEgYXBhcmllbmNpYSBkZSBsb3MgZWxlbWVudG9zIG8gZ2VvbWV0csOtYXMgaW5kZXBlbmRpZW50ZW1lbnRlIGRlIGxhcyB2YXJpYWJsZXMgZGUgdHUgZGF0YS5mcmFtZS4gUG9yIGVqZW1wbG8gcG9kcsOtYXMgcXVlcmVyIHF1ZSB0b2RvcyBsb3MgcHVudG9zIHNlYW4gZGUgdW4gw7puaWNvIGNvbG9yOiByb2pvcy4gRW4gZXN0ZSBjYXNvIGBnZW9tX3BvaW50KGFlcyhjb2xvciA9ICJyZWQiKSlgIG5vIHZhIGEgZnVuY2lvbmFyIC1vam8gcXVlIGxvcyBjb2xvcmVzIHZhbiBlbiBpbmdsw6lzLS4gTG8gcXVlIGVzZSBjw7NkaWdvIGRpY2UgZXMgcXVlIG1hcGVlIGVsIHBhcsOhbWV0cm8gZ2VvbcOpdHJpY28gImNvbG9yIiBhIHVuYSB2YXJpYWJsZSBxdWUgY29udGllbmUgZWwgdmFsb3IgYCJyZWQiYCBwYXJhIHRvZGFzIGxhcyBmaWxhcy4gRWwgbWFwZW8gc2UgaGFjZSBhIHRyYXbDqXMgZGUgbGEgZXNjYWxhLCBxdWUgdmEgYSBhc2lnbmFybGUgdW4gdmFsb3IgKHJvc2EgPHNwYW4gc3R5bGU9ImNvbG9yOiNGNzdENzUiPiYjOTY3OTs8L3NwYW4+KSBhIGxvcyBwdW50b3MgY29ycmVzcG9uZGllbnRlcyBhbCB2YWxvciBgInJlZCJgLgoKQWhvcmEgcXVlIG5vIG5vcyBpbnRlcmVzYSAqbWFwZWFyKiBlbCBjb2xvciBhIHVuYSB2YXJpYWJsZSwgcG9kZW1vcyBtb3ZlciBlc2UgYXJndW1lbnRvICoqYWZ1ZXJhKiogZGUgbGEgZnVuY2nDs24gYGFlcygpYDogYGdlb21fcG9pbnQoY29sb3IgPSAicmVkIilgLiAKOjo6IAoKIyMjIFJlbGFjacOzbiBlbnRyZSB2YXJpYWJsZXMKCk11Y2hhcyB2ZWNlcyBubyBlcyBzdWZpY2llbnRlIGNvbiBtaXJhciBsb3MgZGF0b3MgY3J1ZG9zIHBhcmEgaWRlbnRpZmljYXIgbGEgcmVsYWNpw7NuIGVudHJlIGxhcyB2YXJpYWJsZXM7IGVzIG5lY2VzYXJpbyB1c2FyIGFsZ3VuYSB0cmFuc2Zvcm1hY2nDs24gZXN0YWTDrXN0aWNhIHF1ZSByZXNhbHRlIGVzYXMgcmVsYWNpb25lcywgeWEgc2VhIGFqdXN0YW5kbyB1bmEgcmVjdGEgbyBjYWxjdWxhbmRvIHByb21lZGlvcy4gCgpQYXJhIGFsZ3VuYSB0cmFuc2Zvcm1hY2lvbmVzIGVzdGFkw61zdGljYXMgY29tdW5lcywge2dncGxvdDJ9IHRpZW5lIGdlb21zIHlhIHByb2dyYW1hZG9zLCBwZXJvIG11Y2hhcyB2ZWNlcyBlcyBwb3NpYmxlIHF1ZSBuZWNlc2l0ZW1vcyBtYW5pcHVsYXIgbG9zIGRhdG9zIGFudGVzIGRlIHBvZGVyIGhhY2VyIHVuIGdyw6FmaWNvLiBBIHZlY2VzIGVzYSBtYW5pcHVsYWNpw7NuIHNlcsOhIGNvbXBsZWphIHkgZW50b25jZXMgcGFyYSBubyByZXBldGlyIGVsIGPDoWxjdWxvIG11Y2hhcyB2ZWNlcywgZ3VhcmRhcmVtb3MgbG9zIGRhdG9zIG1vZGlmaWNhZG9zIGVuIHVuYSBudWV2YSB2YXJpYWJsZS4gUGVybyB0YW1iacOpbiBwb2RlbW9zICplbmNhZGVuYXIqIGxhIG1hbmlwdWxhY2nDs24gZGUgbG9zIGRhdG9zIHkgZWwgZ3LDoWZpY28gcmVzdWx0YW50ZS4KClBvciBlamVtcGxvLCBjYWxjdWxlbW9zIGxhIGVzcGVyYW56YSBkZSB2aWRhIG1lZGlhIHBvciBjb250aW5lbnRlIHkgcGFyYSBjYWRhIGHDsW8gdXNhbmRvIFtgZHBseXJgXSgwNS1kcGx5ci1JLmh0bWwpIHkgbHVlZ28gZ3JhZmlxdWVtb3MgbGEgYGVzcGVyYW56YV9kZV92aWRhX21lZGlhYCBhIGxvcyBsYXJnbyBkZSBsb3MgYGFuaW9zYDoKCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQoKcGFpc2VzICU+JSAKICBncm91cF9ieShjb250aW5lbnRlLCBhbmlvKSAlPiUgCiAgc3VtbWFyaXNlKGVzcGVyYW56YV9kZV92aWRhX21lZGlhID0gbWVhbihlc3BlcmFuemFfZGVfdmlkYSkpICU+JSAKICBnZ3Bsb3QoYWVzKGFuaW8sIGVzcGVyYW56YV9kZV92aWRhX21lZGlhKSkgKyAgIyBBY8OhIHNlIGFjYWJhbiBsb3MgJT4lIHkgY29taWVuemFuIGxvcyAiKyIKICBnZW9tX3BvaW50KCkKYGBgCgpFc3RvIGVzIHBvc2libGUgZ3JhY2lhcyBhbCBvcGVyYWRvciBgJT4lYCBxdWUgbGUgKnBhc2EqIGVsIHJlc3VsdGFkbyBkZSBgc3VtbWFyaXNlKClgIGEgbGEgZnVuY2nDs24gYGdncGxvdCgpYC4gWSBlc3RlIHJlc3VsdGFkbyBubyBlcyBuaSBtw6FzIG5pIG1lbm9zIHF1ZSBlbCBkYXRhLmZyYW1lIHF1ZSBuZWNlc2l0YW1vcyBwYXJhIGhhY2VyIG51ZXN0cm8gZ3LDoWZpY28uIEVzIGltcG9ydGFudGUgbm90YXIgcXVlIHVuYSB2ZXogcXVlIGNvbWVuemFtb3MgZWwgZ3LDoWZpY28geWEgKipubyoqIHNlIHB1ZWRlIHVzYXIgZWwgb3BlcmFkb3IgYCU+JWAgeSBsYXMgY2FwYXMgZGVsIGdyw6FmaWNvIHNlICpzdW1hbiogY29tbyBzaWVtcHJlIGNvbiBgK2AuCgpFc3RlIGdyw6FmaWNvIGVudG9uY2VzIHBhcmVjZSBtb3N0cmFyIHF1ZSBsYSBlc3BlcmFuemEgZGUgdmlkYSBmdWUgYXVtZW50YWRvIGEgbG8gbGFyZ28gZGUgbG9zIGHDsW9zIHBlcm8gc2Vyw61hIGludGVyZXNhbnRlIHZlciBlc2EgcmVsYWNpw7NuIG3DoXMgZXhwbMOtY2l0YW1lbnRlIGFncmVnYW5kbyB1bmEgbnVldmEgY2FwYSBjb24gYGdlb21fc21vb3RoKClgLgoKYGBge3J9CnBhaXNlcyAlPiUgCiAgZ3JvdXBfYnkoY29udGluZW50ZSwgYW5pbykgJT4lIAogIHN1bW1hcmlzZShlc3BlcmFuemFfZGVfdmlkYV9tZWRpYSA9IG1lYW4oZXNwZXJhbnphX2RlX3ZpZGEpKSAlPiUgCiAgZ2dwbG90KGFlcyhhbmlvLCBlc3BlcmFuemFfZGVfdmlkYV9tZWRpYSkpICsgIAogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9zbW9vdGgoKQpgYGAKCkNvbW8gZGljZSBlbiBlbCBtZW5zYWplLCBwb3IgZGVmZWN0byBgZ2VvbV9zbW9vdGgoKWAgc3Vhdml6YSBsb3MgZGF0b3MgdXNhbmRvIGVsIG3DqXRvZG8gKmxvZXNzKiAocmVncmVzacOzbiBsaW5lYWwgbG9jYWwpIGN1YW5kbyBoYXkgbWVub3MgZGUgMTAwMCBkYXRvcy4gU2VndXJhbWVudGUgdmEgYSBzZXIgbXV5IGNvbcO6biBxdWUgcXVpZXJhcyBhanVzdGFyIHVuYSByZWdyZXNpw7NuIGxpbmVhbCBnbG9iYWwuIEVuIGVzZSBjYXNvLCBoYXkgcXVlIHBvbmVyIGBtZXRob2QgPSAibG0iYDoKCmBgYHtyfQpwYWlzZXMgJT4lIAogIGdyb3VwX2J5KGNvbnRpbmVudGUsIGFuaW8pICU+JSAKICBzdW1tYXJpc2UoZXNwZXJhbnphX2RlX3ZpZGFfbWVkaWEgPSBtZWFuKGVzcGVyYW56YV9kZV92aWRhKSkgJT4lIAogIGdncGxvdChhZXMoYW5pbywgZXNwZXJhbnphX2RlX3ZpZGFfbWVkaWEpKSArICAKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpCmBgYAoKRW4gZ3JpcyBub3MgbXVlc3RyYSBlbCBpbnRlcnZhbG8gZGUgY29uZmlhbnphIGFsIHJlZGVkb3IgZGUgZXN0ZSBzdWF2aXphZG8gcXVlIGVuIGVzdGUgY2FzbyBlcyBiYXN0YW50ZSBncmFuZGUgcG9ycXVlIHRlbmVtb3MgcG9jb3MgZGF0b3MhIAoKQ8OzbW8gY3VhbHF1aWVyIGdlb20sIHBvZGVtb3MgbW9kaWZpY2FyIGVsIGNvbG9yLCBlbCBncm9zb3IgZGUgbGEgbMOtbmVhIHkgY2FzaSBjdWFscXVpZXIgY29zYSBxdWUgc2UgdGUgb2N1cnJhLgoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqVGVyY2VyIGRlc2Fmw61vKioKCk1vZGlmaWPDoSBlbCBzaWd1aWVudGUgY8OzZGlnbyBwYXJhIG9idGVuZXIgZWwgZ3LDoWZpY28gcXVlIHNlIG11ZXN0cmEgbcOhcyBhYmFqby4KCmBgYHtyIGV2YWw9RkFMU0V9CnBhaXNlcyAlPiUgCiAgZ3JvdXBfYnkoY29udGluZW50ZSwgX19fX18pICU+JSAKICBzdW1tYXJpc2UoZXNwZXJhbnphX2RlX3ZpZGFfbWVkaWEgPSBtZWFuKGVzcGVyYW56YV9kZV92aWRhKSkgJT4lIAogIGdncGxvdChhZXMoYW5pbywgX19fX19fX19fX19fX19fXykpICsgIAogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gY29udGluZW50ZSksIHNpemUgPSAzLCBzaGFwZSA9IF9fX19fKSArCiAgZ2VvbV9zbW9vdGgoY29sb3IgPSBjb250aW5lbnRlKSAKYGBgCgpgYGB7ciBlY2hvPUZBTFNFfQpwYWlzZXMgJT4lIAogIGdyb3VwX2J5KGNvbnRpbmVudGUsIGFuaW8pICU+JSAKICBzdW1tYXJpc2UoZXNwZXJhbnphX2RlX3ZpZGFfbWVkaWEgPSBtZWFuKGVzcGVyYW56YV9kZV92aWRhKSkgJT4lIAogIGdncGxvdChhZXMoYW5pbywgZXNwZXJhbnphX2RlX3ZpZGFfbWVkaWEpKSArICAKICBnZW9tX3Ntb290aChhZXMoY29sb3IgPSBjb250aW5lbnRlKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gY29udGluZW50ZSksIHNoYXBlID0gOCwgc2l6ZSA9IDMpIApgYGAKOjo6IAoKCgoKPGRpdiBjbGFzcz0iYnRuLWdyb3VwIiByb2xlPSJncm91cCIgYXJpYS1sYWJlbD0iTmF2ZWdhY2nDs24iPgogIDxhIGhyZWY9ICIwNS1kcGx5ci1JLmh0bWwiIGNsYXNzID0gImJ0biBidG4tcHJpbWFyeSI+QW50ZXJpb3I8L2E+CiAgPGEgaHJlZj0gIjA3LWdyYWZpY29zLUlJLmh0bWwiIGNsYXNzID0gImJ0biBidG4tcHJpbWFyeSI+U2lndWllbnRlPC9hPgo8L2Rpdj4=