Hasta ahora, y asumiendo que recorriste los episodios anteriores, visualizamos los datos tal cual vienen en la base de datos o con ayuda de {dplyr} para transformarlos. Pero como adelantamos con geom_smooth(), hay ciertas transformaciones comunes que se pueden hacer usando {ggplot2}.

De paso vamos a usar datos incluidos en el paquete datos (muchos paquetes traen bases de datos para probar cosas, ¡este en particular son todas bases de datos en español!) que podés descargar como siempre con install.packages("datos"). Los datos diamantes contiene información sobre ~ 54000 diamantes, incluido el precio, el quilate, el color, la claridad y el corte de cada uno.

library(ggplot2)
library(dplyr)
library(datos)
str(diamantes)
## tibble [53,940 × 10] (S3: tbl_df/tbl/data.frame)
##  $ precio     : int [1:53940] 326 326 327 334 335 336 336 337 337 338 ...
##  $ quilate    : num [1:53940] 0.23 0.21 0.23 0.29 0.31 0.24 0.24 0.26 0.22 0.23 ...
##  $ corte      : Ord.factor w/ 5 levels "Regular"<"Bueno"<..: 5 4 2 4 2 3 3 3 1 3 ...
##  $ color      : Ord.factor w/ 7 levels "D"<"E"<"F"<"G"<..: 2 2 2 6 7 7 6 5 2 5 ...
##  $ claridad   : Ord.factor w/ 8 levels "I1"<"SI2"<"SI1"<..: 2 3 5 4 2 6 7 3 4 5 ...
##  $ profundidad: num [1:53940] 61.5 59.8 56.9 62.4 63.3 62.8 62.3 61.9 65.1 59.4 ...
##  $ tabla      : num [1:53940] 55 61 65 58 58 57 57 55 61 61 ...
##  $ x          : num [1:53940] 3.95 3.89 4.05 4.2 4.34 3.94 3.95 4.07 3.87 4 ...
##  $ y          : num [1:53940] 3.98 3.84 4.07 4.23 4.35 3.96 3.98 4.11 3.78 4.05 ...
##  $ z          : num [1:53940] 2.43 2.31 2.31 2.63 2.75 2.48 2.47 2.53 2.49 2.39 ...

Gráficos de frecuencias

Este es un gráfico de barras construido usando la función geom_bar(). En el eje x muestra el corte de los diamantes y en el eje y la cantidad (count en inglés) de diamantes en cada categoría. Pero diamantes no tiene una variable que se llame count y tampoco la generamos nosotros. ¡Es calculada internamente por {ggplot2}!

ggplot(data = diamantes, aes(x = corte)) +
  geom_bar()

Cómo el gráfico de barras, también podemos graficar histogramas con geom_histogram() y polígonos de frecuencia con geom_density() para visualizar la cantidad de observaciones que caen en cada categoría (si la variable es discreta como el caso del corte de los diamantes) o rango de valores (para variables continuas).

Un ejemplo de variable continua es el precio de los diamantes, veamos como se ve un histograma y de paso le cambiamos el color a las barras, pero ojo, solo al borde.

ggplot(diamantes, aes(precio)) +
  geom_histogram(color = "darkorange")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Primer desafío

¿Notaste el mensaje que devuelve el gráfico?

`stat_bin() using bins = 30. Pick better value with binwidth.`

Esta geometría tiene dos argumentos importantes bins y binwidth. Cambiá el valor de alguno de los dos argumentos y volvé a generar el gráfico, ¿que rol juegan los argumentos?

También podés revisar la documentación.

Además de contar la cantidad de elementos, {ggplot2} puede calcular muchas otras transformaciones sobre los datos. Por ejemplo si quisiéramos el porcentaje o la proporción que le corresponde a cada categoría de corte respecto del total podemos hacerlo mapeando esa transformación prop al eje y con la función stat().

ggplot(data = diamantes, aes(x = corte)) +
  geom_bar(aes(y = stat(prop), group = 1))

Fijate que la variable prop no es una columna de diamantes sino que es el nombre de una variable computada por geom_bar(), por eso hay que rodearla de la función stat(). El nombre de las variables computadas por cada geoms está en su documentación (abajo de todo, antes de los ejemplos).

Ahora podríamos decir que el 40% de los diamantes en la base de datos tienen un corte ideal. Además de la función stat() agregamos group = 1 y esto no fue sin querer. Probá correr el gráfico sin ese argumento.

Al incluir group = 1, {ggplot2} junta todas las observaciones en un único grupo (con el valor 1) y calcula la proporción o el porcentaje que representa cada corte respecto del total de diamantes. Si no incluimos eso, {ggplot2} asumirá que cada categoría de corte es un grupo independiente y el cálculo del porcentaje también lo hará por separado.

Posición

Ya vimos que la estética del color sólo modifica el borde de las barras, si quisieras modificar el relleno necesitamos cambiar el fill y al igual que antes podemos mapear una variable a esta estética.

ggplot(data = diamantes) +
  geom_bar(aes(x = corte, fill = claridad))

Al mapear una variable distinta, la claridad de los diamantes, podemos visualizar información extra. Este “apilamiento” de las barras es la opción de posición por defecto position = "stack", pero podrías usar una de las otras tres opciones: "identity", "dodge" o "fill".

  • position = "identity" colocará cada barra comenzando en cero quedando todas superpuestas. Para ver esa superposición, debemos hacer que las barras sean ligeramente transparentes configurando el alpha a un valor pequeño.
ggplot(diamantes) +
  geom_bar(aes(x = corte, fill = claridad), alpha = 0.2, position = "identity")

  • position = "fill" apila las barras al igual que position = "stack", pero transforma los datos para que cada conjunto de barras apiladas tenga la misma altura. Esto hace que sea más fácil comparar proporciones entre grupos.
ggplot(diamantes) +
  geom_bar(aes(x = corte, fill = claridad), position = "fill")

  • position = "dodge" coloca las barras una al lado de la otra. Esto hace que sea más fácil comparar valores individuales.
ggplot(diamantes) +
  geom_bar(aes(x = corte, fill = claridad), position = "dodge")

Gráficos de líneas suavizas

Cómo vimos antes, los gráficos de líneas suavizadas (smoothers) ajustan un modelo a los datos y luego grafican las predicciones del modelo. Sin entrar en muchos detalles, se puede aplicar distintos modelos y la elección del mismo dependerá de los datos.

ggplot(diamantes, aes(quilate, precio)) +
  geom_point() +
  geom_smooth()
## `geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'

Gráficos de caja

Los diagramas de caja, mejor conocidos como boxplots calculan un resumen robusto de la distribución y luego muestran una caja con formato especial.

La línea central de la caja corresponde a la mediana (el valor que toma el dato central) y los extremos de la caja son los cuartiles 1 y 3, definiendo así el rango intercuartil (IQR). Los extremos están definidos como el valor observado que no esté más lejos de 1.5*IQR de la mediana y los puntos son los las observaciones que se escapan de ese rango, que pueden ser considerados outliers o valores extremos.

ggplot(diamantes, aes(claridad, precio)) +
  geom_boxplot()

Los boxplot dan muchísima información sobre los datos pero al mismo tiempo esconden la cantidad de observaciones que se usaron para generarlos y en particular cual es la frecuencia a lo largo del eje y. Por esta razón también existen geom_violin() y geom_jitter().

Segundo desafío

  1. Volvé a graficar la distribución del precio para cada tipo de claridad pero ahora usando geom_violin() y geom_jitter().
  2. ¿Qué ventajas y desventajas encuentran respecto de geom_boxplot()?
ggplot(diamantes, aes(claridad, precio)) +
  geom_violin() 

ggplot(diamantes, aes(claridad, precio)) +
  geom_jitter()

Cuando nuestra base de datos es muy grande corremos el riesgo de generar de que los elementos del gráfico estén tan juntos que se solapen y no se vean. Esto se conoce como overplotting. La base de datos diamantes tiene 53940 observaciones y al graficar un punto por cada una, aún si están separados por la claridad, quedan superpuestos.

Para resolver este problema se suele modificar la estética de los elementos, cambiando el tamaño o size para que ocupen menos lugar y se vean mejor, cambiando la forma o shape por alguna que no tenga relleno y permita ver los elementos que hay atrás o modificando la transparencia o alpha por la misma razón. En bases de datos tan grandes como la de diamantes, muchas veces hay que utilizar varios de estos recursos.

Graficando en múltiples paneles

Vimos que es posible graficar más de dos variables en un gráfico mapeando una variable al color o por ejemplo el tipo de línea o linetype

ggplot(diamantes, aes(quilate, precio)) +
  geom_point(aes(color = color))

En este caso no solo visualizamos la relación entre el precio y el quilate del diamante, también podemos ver que rol juega el color. También podríamos haber intentando resolver el problema generando un gráfico por cada color filtrando las observaciones correspondientes.

diamantes %>% 
  filter(color == "D") %>% 
  ggplot(aes(quilate, precio)) +
  geom_point(aes(color = color))

Pero sería muchísimo trabajo si tenemos que hacer esto para cada una de las 7 categorías de color. La buena noticia es que {ggplot2} tiene un par de funciones justo para resolver este problema:

ggplot(diamantes, aes(quilate, precio)) +
  geom_point(aes(color = color)) +
  facet_wrap(~color)

Esta nueva capa con facet_wrap() divide al gráfico inicial en 7 paneles o facets, uno por cada color. Esta función requiere saber que variable será la responsable de separar los paneles y para eso se usa la notación de funciones de R: ~color. Esto se lee como generar paneles “en función del color”.

¿Y si quisiéramos generar paneles a partir de 2 variables? Para eso existe facet_grid(). En este gráfico generamos paneles viendo la “relación entre el corte y el color” y por ejemplo en el primer panel arriba a la izquierda podremos observar los diamantes que son al mismo tiempo de color D y corte Regular. En este caso mapear la variable color al color de los diamantes no parece ser necesario ya que cada columna ya nos permite identificar eso, sin embargo en algunos casos ayuda a leer el gráfico más rápido.

ggplot(diamantes, aes(quilate, precio)) +
  geom_point(aes(color = color)) +
  facet_grid(corte~color)

Tercer desafío

Generá boxplots para analizar como se comporta el precio según la claridad para cada tipo de corte como se ve acá.

LS0tCnRpdGxlOiAiVmlzdWFsaXphY2nDs24gZGUgZGF0b3MgY29uIHtnZ3Bsb3QyfSBJSSIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IGZhbHNlCiAgICBoaWdobGlnaHQ6IHRhbmdvCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKYGBgCgpIYXN0YSBhaG9yYSwgeSBhc3VtaWVuZG8gcXVlIHJlY29ycmlzdGUgbG9zIGVwaXNvZGlvcyBhbnRlcmlvcmVzLCB2aXN1YWxpemFtb3MgbG9zIGRhdG9zIHRhbCBjdWFsIHZpZW5lbiBlbiBsYSBiYXNlIGRlIGRhdG9zIG8gY29uIGF5dWRhIGRlIHtkcGx5cn0gcGFyYSB0cmFuc2Zvcm1hcmxvcy4gUGVybyBjb21vIGFkZWxhbnRhbW9zIGNvbiBgZ2VvbV9zbW9vdGgoKWAsIGhheSBjaWVydGFzIHRyYW5zZm9ybWFjaW9uZXMgY29tdW5lcyBxdWUgc2UgcHVlZGVuIGhhY2VyIHVzYW5kbyB7Z2dwbG90Mn0uCgpEZSBwYXNvIHZhbW9zIGEgdXNhciBkYXRvcyBpbmNsdWlkb3MgZW4gZWwgcGFxdWV0ZSBgZGF0b3NgIChtdWNob3MgcGFxdWV0ZXMgdHJhZW4gYmFzZXMgZGUgZGF0b3MgcGFyYSBwcm9iYXIgY29zYXMsIMKhZXN0ZSBlbiBwYXJ0aWN1bGFyIHNvbiB0b2RhcyBiYXNlcyBkZSBkYXRvcyBlbiBlc3Bhw7FvbCEpIHF1ZSBwb2TDqXMgZGVzY2FyZ2FyIGNvbW8gc2llbXByZSBjb24gYGluc3RhbGwucGFja2FnZXMoImRhdG9zIilgLiBMb3MgZGF0b3MgYGRpYW1hbnRlc2AgY29udGllbmUgaW5mb3JtYWNpw7NuIHNvYnJlIH4gNTQwMDAgZGlhbWFudGVzLCBpbmNsdWlkbyBlbCBgcHJlY2lvYCwgZWwgYHF1aWxhdGVgLCBlbCBgY29sb3JgLCBsYSBgY2xhcmlkYWRgIHkgZWwgYGNvcnRlYCBkZSBjYWRhIHVuby4KCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShkcGx5cikKbGlicmFyeShkYXRvcykKc3RyKGRpYW1hbnRlcykKYGBgCgojIyBHcsOhZmljb3MgZGUgZnJlY3VlbmNpYXMKCkVzdGUgZXMgdW4gZ3LDoWZpY28gZGUgYmFycmFzIGNvbnN0cnVpZG8gdXNhbmRvIGxhIGZ1bmNpw7NuIGBnZW9tX2JhcigpYC4gRW4gZWwgZWplIHggbXVlc3RyYSBlbCBgY29ydGVgIGRlIGxvcyBkaWFtYW50ZXMgeSBlbiBlbCBlamUgeSBsYSBjYW50aWRhZCAoKmNvdW50KiBlbiBpbmdsw6lzKSBkZSBkaWFtYW50ZXMgZW4gY2FkYSBjYXRlZ29yw61hLiBQZXJvIGBkaWFtYW50ZXNgIG5vIHRpZW5lIHVuYSB2YXJpYWJsZSBxdWUgc2UgbGxhbWUgYGNvdW50YCB5IHRhbXBvY28gbGEgZ2VuZXJhbW9zIG5vc290cm9zLiDCoUVzIGNhbGN1bGFkYSBpbnRlcm5hbWVudGUgcG9yIHtnZ3Bsb3QyfSEKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGRpYW1hbnRlcywgYWVzKHggPSBjb3J0ZSkpICsKICBnZW9tX2JhcigpCmBgYAoKQ8OzbW8gZWwgZ3LDoWZpY28gZGUgYmFycmFzLCB0YW1iacOpbiBwb2RlbW9zIGdyYWZpY2FyIGhpc3RvZ3JhbWFzIGNvbiBgZ2VvbV9oaXN0b2dyYW0oKWAgeSBwb2zDrWdvbm9zIGRlIGZyZWN1ZW5jaWEgY29uIGBnZW9tX2RlbnNpdHkoKWAgcGFyYSB2aXN1YWxpemFyIGxhIGNhbnRpZGFkIGRlIG9ic2VydmFjaW9uZXMgcXVlIGNhZW4gZW4gY2FkYSBjYXRlZ29yw61hIChzaSBsYSB2YXJpYWJsZSBlcyBkaXNjcmV0YSBjb21vIGVsIGNhc28gZGVsIGNvcnRlIGRlIGxvcyBkaWFtYW50ZXMpIG8gcmFuZ28gZGUgdmFsb3JlcyAocGFyYSB2YXJpYWJsZXMgY29udGludWFzKS4gCgpVbiBlamVtcGxvIGRlIHZhcmlhYmxlIGNvbnRpbnVhIGVzIGVsIGBwcmVjaW9gIGRlIGxvcyBkaWFtYW50ZXMsIHZlYW1vcyBjb21vIHNlIHZlIHVuIGhpc3RvZ3JhbWEgeSBkZSBwYXNvIGxlIGNhbWJpYW1vcyBlbCBjb2xvciBhIGxhcyBiYXJyYXMsIHBlcm8gb2pvLCBzb2xvIGFsIGJvcmRlLgoKYGBge3J9CmdncGxvdChkaWFtYW50ZXMsIGFlcyhwcmVjaW8pKSArCiAgZ2VvbV9oaXN0b2dyYW0oY29sb3IgPSAiZGFya29yYW5nZSIpCmBgYAoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqUHJpbWVyIGRlc2Fmw61vKioKCsK/Tm90YXN0ZSBlbCBtZW5zYWplIHF1ZSBkZXZ1ZWx2ZSBlbCBncsOhZmljbz8gCgpgYHN0YXRfYmluKClgIHVzaW5nIGBiaW5zID0gMzBgLiBQaWNrIGJldHRlciB2YWx1ZSB3aXRoIGBiaW53aWR0aGAuYAoKRXN0YSBnZW9tZXRyw61hIHRpZW5lIGRvcyBhcmd1bWVudG9zIGltcG9ydGFudGVzIGBiaW5zYCB5IGBiaW53aWR0aGAuIENhbWJpw6EgZWwgdmFsb3IgZGUgYWxndW5vIGRlIGxvcyBkb3MgYXJndW1lbnRvcyB5IHZvbHbDqSBhIGdlbmVyYXIgZWwgZ3LDoWZpY28sIMK/cXVlIHJvbCBqdWVnYW4gbG9zIGFyZ3VtZW50b3M/CgpUYW1iacOpbiBwb2TDqXMgcmV2aXNhciBsYSBkb2N1bWVudGFjacOzbi4KOjo6CgpBZGVtw6FzIGRlIGNvbnRhciBsYSBjYW50aWRhZCBkZSBlbGVtZW50b3MsIHtnZ3Bsb3QyfSBwdWVkZSBjYWxjdWxhciBtdWNoYXMgb3RyYXMgdHJhbnNmb3JtYWNpb25lcyBzb2JyZSBsb3MgZGF0b3MuIFBvciBlamVtcGxvIHNpIHF1aXNpw6lyYW1vcyBlbCBwb3JjZW50YWplIG8gbGEgcHJvcG9yY2nDs24gcXVlIGxlIGNvcnJlc3BvbmRlIGEgY2FkYSBjYXRlZ29yw61hIGRlIGBjb3J0ZWAgcmVzcGVjdG8gZGVsIHRvdGFsIHBvZGVtb3MgaGFjZXJsbyAqbWFwZWFuZG8qIGVzYSB0cmFuc2Zvcm1hY2nDs24gYHByb3BgIGFsIGVqZSB5IGNvbiBsYSBmdW5jacOzbiBgc3RhdCgpYC4KCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGRpYW1hbnRlcywgYWVzKHggPSBjb3J0ZSkpICsKICBnZW9tX2JhcihhZXMoeSA9IHN0YXQocHJvcCksIGdyb3VwID0gMSkpCmBgYAoKRmlqYXRlIHF1ZSBsYSB2YXJpYWJsZSBgcHJvcGAgbm8gZXMgdW5hIGNvbHVtbmEgZGUgYGRpYW1hbnRlc2Agc2lubyBxdWUgZXMgZWwgbm9tYnJlIGRlIHVuYSB2YXJpYWJsZSBjb21wdXRhZGEgcG9yIGBnZW9tX2JhcigpYCwgcG9yIGVzbyBoYXkgcXVlIHJvZGVhcmxhIGRlIGxhIGZ1bmNpw7NuIGBzdGF0KClgLiBFbCBub21icmUgZGUgbGFzIHZhcmlhYmxlcyBjb21wdXRhZGFzIHBvciBjYWRhIGdlb21zIGVzdMOhIGVuIHN1IGRvY3VtZW50YWNpw7NuIChhYmFqbyBkZSB0b2RvLCBhbnRlcyBkZSBsb3MgZWplbXBsb3MpLgoKQWhvcmEgcG9kcsOtYW1vcyBkZWNpciBxdWUgZWwgNDAlIGRlIGxvcyBkaWFtYW50ZXMgZW4gbGEgYmFzZSBkZSBkYXRvcyB0aWVuZW4gdW4gY29ydGUgKmlkZWFsKi4gQWRlbcOhcyBkZSBsYSBmdW5jacOzbiAgYHN0YXQoKWAgYWdyZWdhbW9zIGBncm91cCA9IDFgIHkgZXN0byBubyBmdWUgc2luIHF1ZXJlci4gUHJvYsOhIGNvcnJlciBlbCBncsOhZmljbyBzaW4gZXNlIGFyZ3VtZW50by4KCkFsIGluY2x1aXIgYGdyb3VwID0gMWAsIHtnZ3Bsb3QyfSBqdW50YSB0b2RhcyBsYXMgb2JzZXJ2YWNpb25lcyBlbiB1biDDum5pY28gZ3J1cG8gKGNvbiBlbCB2YWxvciBgMWApIHkgY2FsY3VsYSBsYSBwcm9wb3JjacOzbiBvIGVsIHBvcmNlbnRhamUgcXVlIHJlcHJlc2VudGEgY2FkYSBjb3J0ZSByZXNwZWN0byBkZWwgdG90YWwgZGUgZGlhbWFudGVzLiBTaSBubyBpbmNsdWltb3MgZXNvLCB7Z2dwbG90Mn0gYXN1bWlyw6EgcXVlIGNhZGEgY2F0ZWdvcsOtYSBkZSBjb3J0ZSBlcyB1biBncnVwbyBpbmRlcGVuZGllbnRlIHkgZWwgY8OhbGN1bG8gZGVsIHBvcmNlbnRhamUgdGFtYmnDqW4gbG8gaGFyw6EgcG9yIHNlcGFyYWRvLiAKCgojIyMgUG9zaWNpw7NuCgpZYSB2aW1vcyBxdWUgbGEgZXN0w6l0aWNhIGRlbCBgY29sb3JgIHPDs2xvIG1vZGlmaWNhIGVsIGJvcmRlIGRlIGxhcyBiYXJyYXMsIHNpIHF1aXNpZXJhcyBtb2RpZmljYXIgZWwgcmVsbGVubyBuZWNlc2l0YW1vcyBjYW1iaWFyIGVsIGBmaWxsYCB5IGFsIGlndWFsIHF1ZSBhbnRlcyBwb2RlbW9zICptYXBlYXIqIHVuYSB2YXJpYWJsZSBhIGVzdGEgZXN0w6l0aWNhLgoKYGBge3J9CmdncGxvdChkYXRhID0gZGlhbWFudGVzKSArCiAgZ2VvbV9iYXIoYWVzKHggPSBjb3J0ZSwgZmlsbCA9IGNsYXJpZGFkKSkKYGBgCgpBbCAqbWFwZWFyKiB1bmEgdmFyaWFibGUgZGlzdGludGEsIGxhIGNsYXJpZGFkIGRlIGxvcyBkaWFtYW50ZXMsIHBvZGVtb3MgdmlzdWFsaXphciBpbmZvcm1hY2nDs24gZXh0cmEuIEVzdGUgImFwaWxhbWllbnRvIiBkZSBsYXMgYmFycmFzIGVzIGxhIG9wY2nDs24gZGUgcG9zaWNpw7NuIHBvciBkZWZlY3RvIGBwb3NpdGlvbiA9ICJzdGFjayJgLCBwZXJvIHBvZHLDrWFzIHVzYXIgdW5hIGRlIGxhcyBvdHJhcyB0cmVzIG9wY2lvbmVzOiBgImlkZW50aXR5ImAsIGAiZG9kZ2UiYCBvIGAiZmlsbCJgLgoKKiBgcG9zaXRpb24gPSAiaWRlbnRpdHkiYCBjb2xvY2Fyw6EgY2FkYSBiYXJyYSBjb21lbnphbmRvIGVuIGNlcm8gcXVlZGFuZG8gdG9kYXMgc3VwZXJwdWVzdGFzLiBQYXJhIHZlciBlc2Egc3VwZXJwb3NpY2nDs24sIGRlYmVtb3MgaGFjZXIgcXVlIGxhcyBiYXJyYXMgc2VhbiBsaWdlcmFtZW50ZSB0cmFuc3BhcmVudGVzIGNvbmZpZ3VyYW5kbyBlbCBgYWxwaGFgIGEgdW4gdmFsb3IgcGVxdWXDsW8uCgpgYGB7cn0KZ2dwbG90KGRpYW1hbnRlcykgKwogIGdlb21fYmFyKGFlcyh4ID0gY29ydGUsIGZpbGwgPSBjbGFyaWRhZCksIGFscGhhID0gMC4yLCBwb3NpdGlvbiA9ICJpZGVudGl0eSIpCmBgYAoKKiBgcG9zaXRpb24gPSAiZmlsbCJgIGFwaWxhIGxhcyBiYXJyYXMgYWwgaWd1YWwgcXVlIGBwb3NpdGlvbiA9ICJzdGFjayJgLCBwZXJvIHRyYW5zZm9ybWEgbG9zIGRhdG9zIHBhcmEgcXVlIGNhZGEgY29uanVudG8gZGUgYmFycmFzIGFwaWxhZGFzIHRlbmdhIGxhIG1pc21hIGFsdHVyYS4gRXN0byBoYWNlIHF1ZSBzZWEgbcOhcyBmw6FjaWwgY29tcGFyYXIgcHJvcG9yY2lvbmVzIGVudHJlIGdydXBvcy4KCmBgYHtyfQpnZ3Bsb3QoZGlhbWFudGVzKSArCiAgZ2VvbV9iYXIoYWVzKHggPSBjb3J0ZSwgZmlsbCA9IGNsYXJpZGFkKSwgcG9zaXRpb24gPSAiZmlsbCIpCmBgYAoKKiBgcG9zaXRpb24gPSAiZG9kZ2UiYCBjb2xvY2EgbGFzIGJhcnJhcyB1bmEgYWwgbGFkbyBkZSBsYSBvdHJhLiBFc3RvIGhhY2UgcXVlIHNlYSBtw6FzIGbDoWNpbCBjb21wYXJhciB2YWxvcmVzIGluZGl2aWR1YWxlcy4KCmBgYHtyfQpnZ3Bsb3QoZGlhbWFudGVzKSArCiAgZ2VvbV9iYXIoYWVzKHggPSBjb3J0ZSwgZmlsbCA9IGNsYXJpZGFkKSwgcG9zaXRpb24gPSAiZG9kZ2UiKQpgYGAKCiMjIEdyw6FmaWNvcyBkZSBsw61uZWFzIHN1YXZpemFzCgpDw7NtbyB2aW1vcyBhbnRlcywgbG9zIGdyw6FmaWNvcyBkZSBsw61uZWFzIHN1YXZpemFkYXMgKHNtb290aGVycykgYWp1c3RhbiB1biBtb2RlbG8gYSBsb3MgZGF0b3MgeSBsdWVnbyBncmFmaWNhbiBsYXMgcHJlZGljY2lvbmVzIGRlbCBtb2RlbG8uIFNpbiBlbnRyYXIgZW4gbXVjaG9zIGRldGFsbGVzLCBzZSBwdWVkZSBhcGxpY2FyIGRpc3RpbnRvcyBtb2RlbG9zIHkgbGEgZWxlY2Npw7NuIGRlbCBtaXNtbyBkZXBlbmRlcsOhIGRlIGxvcyBkYXRvcy4gCgpgYGB7cn0KZ2dwbG90KGRpYW1hbnRlcywgYWVzKHF1aWxhdGUsIHByZWNpbykpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKCkKYGBgCgojIyBHcsOhZmljb3MgZGUgY2FqYQoKTG9zIGRpYWdyYW1hcyBkZSBjYWphLCBtZWpvciBjb25vY2lkb3MgY29tbyBib3hwbG90cyBjYWxjdWxhbiB1biByZXN1bWVuIHJvYnVzdG8gZGUgbGEgZGlzdHJpYnVjacOzbiB5IGx1ZWdvIG11ZXN0cmFuIHVuYSBjYWphIGNvbiBmb3JtYXRvIGVzcGVjaWFsLiAKCkxhIGzDrW5lYSBjZW50cmFsIGRlIGxhIGNhamEgY29ycmVzcG9uZGUgYSBsYSAqKm1lZGlhbmEqKiAoZWwgdmFsb3IgcXVlIHRvbWEgZWwgZGF0byBjZW50cmFsKSB5IGxvcyBleHRyZW1vcyBkZSBsYSBjYWphIHNvbiBsb3MgKipjdWFydGlsZXMgMSB5IDMqKiwgZGVmaW5pZW5kbyBhc8OtIGVsICoqcmFuZ28gaW50ZXJjdWFydGlsKiogKElRUikuIExvcyBleHRyZW1vcyBlc3TDoW4gZGVmaW5pZG9zIGNvbW8gZWwgdmFsb3Igb2JzZXJ2YWRvIHF1ZSBubyBlc3TDqSBtw6FzIGxlam9zIGRlICoqMS41XCpJUVIqKiBkZSBsYSBtZWRpYW5hIHkgbG9zIHB1bnRvcyBzb24gbG9zIGxhcyBvYnNlcnZhY2lvbmVzIHF1ZSBzZSBlc2NhcGFuIGRlIGVzZSByYW5nbywgcXVlIHB1ZWRlbiBzZXIgY29uc2lkZXJhZG9zICoqb3V0bGllcnMqKiBvICoqdmFsb3JlcyBleHRyZW1vcyoqLgoKYGBge3J9CmdncGxvdChkaWFtYW50ZXMsIGFlcyhjbGFyaWRhZCwgcHJlY2lvKSkgKwogIGdlb21fYm94cGxvdCgpCmBgYAoKTG9zIGJveHBsb3QgZGFuIG11Y2jDrXNpbWEgaW5mb3JtYWNpw7NuIHNvYnJlIGxvcyBkYXRvcyBwZXJvIGFsIG1pc21vIHRpZW1wbyAqZXNjb25kZW4qIGxhIGNhbnRpZGFkIGRlIG9ic2VydmFjaW9uZXMgcXVlIHNlIHVzYXJvbiBwYXJhIGdlbmVyYXJsb3MgeSBlbiBwYXJ0aWN1bGFyIGN1YWwgZXMgbGEgZnJlY3VlbmNpYSBhIGxvIGxhcmdvIGRlbCBlamUgeS4gUG9yIGVzdGEgcmF6w7NuIHRhbWJpw6luIGV4aXN0ZW4gYGdlb21fdmlvbGluKClgIHkgYGdlb21faml0dGVyKClgLgoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqU2VndW5kbyBkZXNhZsOtbyoqCgoxLiBWb2x2w6kgYSBncmFmaWNhciBsYSBkaXN0cmlidWNpw7NuIGRlbCBwcmVjaW8gcGFyYSBjYWRhIHRpcG8gZGUgY2xhcmlkYWQgcGVybyBhaG9yYSB1c2FuZG8gYGdlb21fdmlvbGluKClgIHkgYGdlb21faml0dGVyKClgLgoyLiDCv1F1w6kgdmVudGFqYXMgeSBkZXN2ZW50YWphcyBlbmN1ZW50cmFuIHJlc3BlY3RvIGRlIGBnZW9tX2JveHBsb3QoKWA/Cjo6OgoKYGBge3J9CmdncGxvdChkaWFtYW50ZXMsIGFlcyhjbGFyaWRhZCwgcHJlY2lvKSkgKwogIGdlb21fdmlvbGluKCkgCmBgYAoKYGBge3J9CmdncGxvdChkaWFtYW50ZXMsIGFlcyhjbGFyaWRhZCwgcHJlY2lvKSkgKwogIGdlb21faml0dGVyKCkKYGBgCgo6Ojogey5hbGVydCAuYWxlcnQtc3VjY2Vzc30KQ3VhbmRvIG51ZXN0cmEgYmFzZSBkZSBkYXRvcyBlcyBtdXkgZ3JhbmRlIGNvcnJlbW9zIGVsIHJpZXNnbyBkZSBnZW5lcmFyIGRlIHF1ZSBsb3MgZWxlbWVudG9zIGRlbCBncsOhZmljbyBlc3TDqW4gdGFuIGp1bnRvcyBxdWUgc2Ugc29sYXBlbiB5IG5vIHNlIHZlYW4uIEVzdG8gc2UgY29ub2NlIGNvbW8gKipvdmVycGxvdHRpbmcqKi4gTGEgYmFzZSBkZSBkYXRvcyBgZGlhbWFudGVzYCB0aWVuZSA1Mzk0MCBvYnNlcnZhY2lvbmVzIHkgYWwgZ3JhZmljYXIgdW4gcHVudG8gcG9yIGNhZGEgdW5hLCBhw7puIHNpIGVzdMOhbiBzZXBhcmFkb3MgcG9yIGxhIGNsYXJpZGFkLCBxdWVkYW4gc3VwZXJwdWVzdG9zLiAKClBhcmEgcmVzb2x2ZXIgZXN0ZSBwcm9ibGVtYSBzZSBzdWVsZSBtb2RpZmljYXIgbGEgZXN0w6l0aWNhIGRlIGxvcyBlbGVtZW50b3MsIGNhbWJpYW5kbyBlbCB0YW1hw7FvIG8gIGBzaXplYCBwYXJhIHF1ZSBvY3VwZW4gbWVub3MgbHVnYXIgeSBzZSB2ZWFuIG1lam9yLCBjYW1iaWFuZG8gbGEgZm9ybWEgIG8gYHNoYXBlYCBwb3IgYWxndW5hIHF1ZSBubyB0ZW5nYSByZWxsZW5vIHkgcGVybWl0YSB2ZXIgbG9zIGVsZW1lbnRvcyBxdWUgaGF5IGF0csOhcyBvIG1vZGlmaWNhbmRvIGxhIHRyYW5zcGFyZW5jaWEgbyBgYWxwaGFgIHBvciBsYSBtaXNtYSByYXrDs24uIEVuIGJhc2VzIGRlIGRhdG9zIHRhbiBncmFuZGVzIGNvbW8gbGEgZGUgZGlhbWFudGVzLCBtdWNoYXMgdmVjZXMgaGF5IHF1ZSB1dGlsaXphciB2YXJpb3MgZGUgZXN0b3MgcmVjdXJzb3MuIAo6OjoKCiMjIEdyYWZpY2FuZG8gZW4gbcO6bHRpcGxlcyBwYW5lbGVzCgpWaW1vcyBxdWUgZXMgcG9zaWJsZSBncmFmaWNhciBtw6FzIGRlIGRvcyB2YXJpYWJsZXMgZW4gdW4gZ3LDoWZpY28gKm1hcGVhbmRvKiB1bmEgdmFyaWFibGUgYWwgYGNvbG9yYCBvIHBvciBlamVtcGxvIGVsIHRpcG8gZGUgbMOtbmVhIG8gYGxpbmV0eXBlYAoKYGBge3J9CmdncGxvdChkaWFtYW50ZXMsIGFlcyhxdWlsYXRlLCBwcmVjaW8pKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjb2xvcikpCmBgYAoKRW4gZXN0ZSBjYXNvIG5vIHNvbG8gdmlzdWFsaXphbW9zIGxhIHJlbGFjacOzbiBlbnRyZSBlbCBwcmVjaW8geSBlbCBxdWlsYXRlIGRlbCBkaWFtYW50ZSwgdGFtYmnDqW4gcG9kZW1vcyB2ZXIgcXVlIHJvbCBqdWVnYSBlbCBjb2xvci4gVGFtYmnDqW4gcG9kcsOtYW1vcyBoYWJlciBpbnRlbnRhbmRvIHJlc29sdmVyIGVsIHByb2JsZW1hIGdlbmVyYW5kbyB1biBncsOhZmljbyBwb3IgY2FkYSBjb2xvciBmaWx0cmFuZG8gbGFzIG9ic2VydmFjaW9uZXMgY29ycmVzcG9uZGllbnRlcy4KCmBgYHtyfQpkaWFtYW50ZXMgJT4lIAogIGZpbHRlcihjb2xvciA9PSAiRCIpICU+JSAKICBnZ3Bsb3QoYWVzKHF1aWxhdGUsIHByZWNpbykpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGNvbG9yKSkKCmBgYAoKUGVybyBzZXLDrWEgbXVjaMOtc2ltbyB0cmFiYWpvIHNpIHRlbmVtb3MgcXVlIGhhY2VyIGVzdG8gcGFyYSBjYWRhIHVuYSBkZSBsYXMgNyBjYXRlZ29yw61hcyBkZSBjb2xvci4gTGEgYnVlbmEgbm90aWNpYSBlcyBxdWUge2dncGxvdDJ9IHRpZW5lIHVuIHBhciBkZSBmdW5jaW9uZXMganVzdG8gcGFyYSByZXNvbHZlciBlc3RlIHByb2JsZW1hOgoKYGBge3J9CmdncGxvdChkaWFtYW50ZXMsIGFlcyhxdWlsYXRlLCBwcmVjaW8pKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjb2xvcikpICsKICBmYWNldF93cmFwKH5jb2xvcikKYGBgCgpFc3RhIG51ZXZhIGNhcGEgY29uIGBmYWNldF93cmFwKClgIGRpdmlkZSBhbCBncsOhZmljbyBpbmljaWFsIGVuIDcgcGFuZWxlcyBvICpmYWNldHMqLCB1bm8gcG9yIGNhZGEgY29sb3IuIEVzdGEgZnVuY2nDs24gcmVxdWllcmUgc2FiZXIgcXVlIHZhcmlhYmxlIHNlcsOhIGxhIHJlc3BvbnNhYmxlIGRlIHNlcGFyYXIgbG9zIHBhbmVsZXMgeSBwYXJhIGVzbyBzZSB1c2EgbGEgKipub3RhY2nDs24gZGUgZnVuY2lvbmVzKiogZGUgUjogYH5jb2xvcmAuIEVzdG8gc2UgbGVlIGNvbW8gZ2VuZXJhciBwYW5lbGVzICoiZW4gZnVuY2nDs24gZGVsIGNvbG9yIiouCgrCv1kgc2kgcXVpc2nDqXJhbW9zIGdlbmVyYXIgcGFuZWxlcyBhIHBhcnRpciBkZSAyIHZhcmlhYmxlcz8gUGFyYSBlc28gZXhpc3RlIGBmYWNldF9ncmlkKClgLiBFbiBlc3RlIGdyw6FmaWNvIGdlbmVyYW1vcyBwYW5lbGVzIHZpZW5kbyBsYSAqInJlbGFjacOzbiBlbnRyZSBlbCBjb3J0ZSB5IGVsIGNvbG9yIiogeSBwb3IgZWplbXBsbyBlbiBlbCBwcmltZXIgcGFuZWwgYXJyaWJhIGEgbGEgaXpxdWllcmRhIHBvZHJlbW9zIG9ic2VydmFyIGxvcyBkaWFtYW50ZXMgcXVlIHNvbiBhbCBtaXNtbyB0aWVtcG8gZGUgY29sb3IgRCB5IGNvcnRlIFJlZ3VsYXIuIEVuIGVzdGUgY2FzbyAqbWFwZWFyKiBsYSB2YXJpYWJsZSBjb2xvciBhbCBjb2xvciBkZSBsb3MgZGlhbWFudGVzIG5vIHBhcmVjZSBzZXIgbmVjZXNhcmlvIHlhIHF1ZSBjYWRhIGNvbHVtbmEgeWEgbm9zIHBlcm1pdGUgaWRlbnRpZmljYXIgZXNvLCBzaW4gZW1iYXJnbyBlbiBhbGd1bm9zIGNhc29zIGF5dWRhIGEgbGVlciBlbCBncsOhZmljbyBtw6FzIHLDoXBpZG8uIAoKYGBge3J9CmdncGxvdChkaWFtYW50ZXMsIGFlcyhxdWlsYXRlLCBwcmVjaW8pKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjb2xvcikpICsKICBmYWNldF9ncmlkKGNvcnRlfmNvbG9yKQpgYGAKCjo6OiB7LmFsZXJ0IC5hbGVydC1pbmZvfQoqKlRlcmNlciBkZXNhZsOtbyoqCgpHZW5lcsOhIGJveHBsb3RzIHBhcmEgYW5hbGl6YXIgY29tbyBzZSBjb21wb3J0YSBlbCBgcHJlY2lvYCBzZWfDum4gbGEgYGNsYXJpZGFkYCBwYXJhIGNhZGEgdGlwbyBkZSBgY29ydGVgIGNvbW8gc2UgdmUgYWPDoS4KCmBgYHtyIGVjaG89RkFMU0V9CmdncGxvdChkaWFtYW50ZXMsIGFlcyhjbGFyaWRhZCwgcHJlY2lvLCBmaWxsID0gY2xhcmlkYWQpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGZhY2V0X3dyYXAofmNvcnRlKQpgYGAKOjo6CgoKCgo8ZGl2IGNsYXNzPSJidG4tZ3JvdXAiIHJvbGU9Imdyb3VwIiBhcmlhLWxhYmVsPSJOYXZlZ2FjacOzbiI+CiAgPGEgaHJlZj0gIjA2LWdyYWZpY29zLUkuaHRtbCIgY2xhc3MgPSAiYnRuIGJ0bi1wcmltYXJ5Ij5BbnRlcmlvcjwvYT4KICA8YSBocmVmPSAiMDgtZHBseXItdGlkeXItSUkuaHRtbCIgY2xhc3MgPSAiYnRuIGJ0bi1wcmltYXJ5Ij5TaWd1aWVudGU8L2E+CjwvZGl2Pg==