“Los datos son la nueva ciencia. El Big Data son las respuestas”
Pat Gelsinger
Los gráficos en R
R dispone de múltiples funciones diseñadas para la representación
gráfica de datos. Estas funciones se dividen en dos grandes grupos:
funciones gráficas de alto nivel y de bajo
nivel. La diferencia fundamental es que las funciones de alto
nivel son las que generan gráficos completos, mientras que las de bajo
nivel se limitan a añadir elementos a un gráfico existente (por tanto
creado por una función de alto nivel).
El paquete graphics (que se carga en memoria cada vez que arrancamos
R) contiene un buen número de funciones de alto y bajo nivel para
generar gráficos. Numerosos paquetes -plotrix, scatterplot3D, rgl,
maps, shapes, …, y sobre todo ggplot2- contienen muchísimas más
funciones gráficas que mejoran y complementan las que vienen por defecto
con R.
Funciones gráficas de alto nivel
Estas funciones son las que generan gráficos completos. Entre las más
utilizadas podemos citar plot() (gráficos de nubes de puntos, entre
otros), hist() (histogramas), barplot() (diagramas de barras), boxplot()
(diagramas de caja y bigote), pie() (diagrama de sectores) o pers()
(superficies en 3D). Todas estas funciones disponen de multitud de
argumentos que permiten controlar las etiquetas de los ejes, sus
límites, títulos, tamaño, colores, etc.:
xlim, ylim: controlan, respectivamente, la extensión de
los ejes X e Y. Así xlim=c(0,10) indica que el eje X se
extiende de 0 a 10; ylim=c(-5,5) indica que el eje Y va de -5 a 5. Si no
se incluyen estos valores, R los ajusta por defecto de modo que se
incluyan todos los valores disponibles en el dataframe.
xlab e ylab especifican las etiquetas para los ejes X e
Y respectivamente.
main indica el título del gráfico.
sub permite especificar un subtítulo.
Los ejemplos que se muestran más abajo permiten ver como utilizar
estas opciones.
Dos argumentos importantes que son comunes a la mayoría de gráficos
de alto nivel son los siguientes:
- add=TRUE: fuerza a la función a actuar como si fuese de bajo nivel
(intenta superponer la figura que genera a un gráfico ya existente).
Esta opción no está disponible para todas las funciones.
type Indica el tipo de gráfico a realizar. En concreto:
- type=“p” representa puntos (opción por defecto)
- type=“l” representa líneas
- type=“b” (both, ambos) representa puntos unidos por líneas.
- type=“n” No dibuja nada.
Funciones gráficas de bajo nivel:
Permiten añadir líneas, puntos, etiquetas… a un gráfico ya existente.
Son de gran utilidad para completar un gráfico. Entre estas funciones
cabe destacar:
lines(): Permite añadir lineas (uniendo puntos concretos) a una
gráfica ya existente.
abline(): Añade lineas horizontales, verticales u oblicuas,
indicando pendiente y ordenada.
points(): Permite añadir puntos.
legend(): Permite añadir una leyenda.
text(): Añade texto en las posiciones que se indiquen.
grid(): Añade una malla de fondo.
title(): permite añadir un título o subtítulo.
Argumentos comunes a las funciones gráficas de alto y bajo
nivel
Los siguientes argumentos opcionales son comunes a muchas funciones
gráficas de alto y bajo nivel. Sus valores por defecto pueden obtenerse
ejecutando la función par(). Se puede encontrar el significado y valores
posibles de cada uno de estos argumentos (y muchos más) ejecutando
help(par).
pch: Indica la forma en que se dibujaran los puntos
(círculo, cuadrado, estrella, etc). El listado de valores y formas
disponibles puede verse mediante help(points)
lty: Indica la forma en que se dibujan las líneas
(continua, a trazos, …).
lwd: Ancho de las líneas.
col: Color usado para el gráfico (ya sea para puntos,
líneas…). Puede vers un listado completo de los colores disponibles en R
ejecutando la función colors(). help(colors) explica como obtener aún
más colores. Este documento contiene una muestra de cada color.
font: Fuente a usar en el texto.
las: Cambia el estilo de las etiquetas de los ejes (0
paralelo a los ejes, 1 siempre horizontales, 2, perpendiculares a los
ejes, 3 siempre verticales)
Ejemplos de funciones gráficas de alto nivel
Podemos destacar, entre las más utilizadas:
plot()
Esta función ofrece muchas variantes dependiendo del tipo de objeto
al que se aplique. El caso más simple corresponde a la representación de
dos variables x e y. En tal caso, plot(x,y) representa un diagrama de
dispersión de puntos de y frente a x.
A modo de ejemplo se muestra a continuación un gráfico de la
esperanza de vida (LifeExpectancy, que será nuestra variable y) frente
al índice de felicidad (Happiness, que es la x) en una muestra de 143
países. Los datos se encuentran en el dataframe(HappyPlanetIndex) del
paquete (Lock5Data) (consultar help(HappyPlanetIndex) para ver las
variables en este dataframe, y http://www.happyplanetindex.org/about/ para más
información sobre este estudio):
library(Lock5Data)
data(HappyPlanetIndex)
attach(HappyPlanetIndex)
plot(Happiness,LifeExpectancy,pch=19,col="red")

hist() Esta función permite dibujar histogramas de
frecuencias para variables continuas. Por ejemplo, el histograma de los
niveles de felicidad en los distintos países de la muestra se obtiene
fácilmente como:
hist(Happiness,col="darkolivegreen1")

En el mismo paquete Lock5Data podemos encontrar el dataframe
SalaryGender que contiene una muestra de 100 profesores universitarios
de EEUU, 50 hombres y 50 mujeres; para cada uno se tiene el salario
anual (en miles de dólares), la edad y la variable PhD que vale 1 si el
profesor es doctor y 0 si no lo es. Podemos ver la distribución de
salarios entre hombres y mujeres mediante un histograma combinado
utilizando la función histStack() del paquete plotrix:
data(SalaryGender)
attach(SalaryGender)
Gender=factor(Gender,levels=c(0,1),labels=c("Female","Male"))
library(plotrix)
histStack(Salary,Gender,legend.pos="topright")

barplot() Se utiliza para dibujar diagramas de barras. El
siguiente ejemplo muestra el número de países en cada una de las 7
regiones en que se dividió el planeta para el estudio de los niveles de
felicidad:
barplot(table(Region),xlab="Region",main="Happiness level by region", col=rainbow(10))

El paquete plotrix contiene la función barp() que permite dar
“volumen” a la barras:
barp(table(Region),col="lightblue",cylindrical=TRUE,shadow=TRUE)

Es posible construir diagramas de barras por categorías; podemos, por
ejemplo representar la frecuencia de doctores por sexo utilizando los
datos del dataframe SalaryGender:
PhD=factor(PhD,levels=c(0,1),c("PhD","non PhD"))
barplot(table(Gender,PhD),beside=TRUE,legend.text=TRUE,col=c("pink","cyan"))

pie() Aporta la misma información que el diagrama de barras,
pero en forma de diagrama de sectores:
pie(table(Region))

El paquete plotrix permite elaborar diagramas de sectores en 3D
mediante la función pie3D:
pie3D(table(Region))

boxplot() Lleva a cabo la representación de gráficos de
“caja y bigote”. El siguiente ejemplo muestra el reparto de los niveles
de felicidad entre las distintas regiones del globo:
boxplot(Happiness~Region,col="gold",xlab="Region",ylab="Happiness level",
main="Average happiness level by region")

persp() Esta función realiza representaciones
tridimensionales (superficies). El dataframe volcano que se distribuye
junto con la instalación básica de R (ver help(volcano)) contiene
información topográfica del volcán Maunga Whau en Auckland, Nueva
Zelanda, definida sobre una malla de 870×610 metros, con un nodo cada 10
metros. Podemos trazar el perfil topográfico de este volcán
mediante:
persp(x = 10*(1:nrow(volcano)), y=10*(1:ncol(volcano)), z=3*volcano,
theta = 135, phi = 30, col = "green3", scale = FALSE,
ltheta = -120, shade = 0.75, border = NA, box = FALSE, main="Volcán Maunga Whau, Auckland, NZ")

Funciones de localización e identificación de puntos: locator() e
identify()
Una vez que en la ventana de gráficos tenemos algo representado,
existen dos funciones que permiten trabajar interactivamente con el
gráfico:
La función locator(): al situar el cursor sobre la ventana de
gráficos, cada vez que pulsemos el botón izquierdo del ratón, se
almacenan en memoria las coordenadas del punto que marquemos. Al pulsar
la tecla < ESC >, R nos muestra dichas coordenadas en la consola
(si hemos ejecutado simplemente locator()) o las guarda en el objeto al
que hayamos asignado la salida dicha función. Por ejemplo, si hemos
ejecutado posiciones=locator(), al pulsar < ESC > las coordenadas
de los puntos que hayamos marcado en el gráfico se guardan en la
variable posiciones.
La función identify() permite identificar con el ratón a qué
posiciones dentro del conjunto de datos corresponden los puntos que
señalemos en un gráfico. Si, por ejemplo, ejecutamos
plot(Happiness,LifeExpectancy) para mostrar la nube de puntos de la
esperanza de vida frente al índice de felicidad, y a continuación
ejecutamos identify(Happiness,LifeExpectancy) y picamos en algunos
puntos del gráfico con el ratón, al pulsar < ESC >, R nos devuelve
en la consola (y representa en el gráfico) los números de orden dentro
del dataframe a los que corresponden los puntos que hemos marcado.
LS0tDQp0aXRsZTogIkdyw6FmaWNvcyBlbiBSOiBpbnRyb2R1Y2Npw7NuIg0Kc3VidGl0bGU6ICJEb2N1bWVudG8gTWFya2Rvd24iDQphdXRob3I6ICJOZWlsIEZyYW56IENhYmFuaWxsYXMgSnVyYWRvIg0KZGF0ZTogIjIwMjIvMDQvMzAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY29kZV9kb3dubG9hZDogVFJVRQ0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgd29yZF9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCi0tLQ0KDQo+DQrigJxMb3MgZGF0b3Mgc29uIGxhIG51ZXZhIGNpZW5jaWEuIEVsIEJpZyBEYXRhIHNvbiBsYXMgcmVzcHVlc3Rhc+KAnQ0KPg0KKipQYXQgR2Vsc2luZ2VyKioNCj4NCg0KIyMgTG9zIGdyw6FmaWNvcyBlbiBSDQoNClIgZGlzcG9uZSBkZSBtw7psdGlwbGVzIGZ1bmNpb25lcyBkaXNlw7FhZGFzIHBhcmEgbGEgcmVwcmVzZW50YWNpw7NuIGdyw6FmaWNhIGRlIGRhdG9zLiBFc3RhcyBmdW5jaW9uZXMgc2UgZGl2aWRlbiBlbiBkb3MgZ3JhbmRlcyBncnVwb3M6IGZ1bmNpb25lcyBncsOhZmljYXMgZGUgKiphbHRvIG5pdmVsKiogeSBkZSAqKmJham8gbml2ZWwqKi4gTGEgZGlmZXJlbmNpYSBmdW5kYW1lbnRhbCBlcyBxdWUgbGFzIGZ1bmNpb25lcyBkZSBhbHRvIG5pdmVsIHNvbiBsYXMgcXVlIGdlbmVyYW4gZ3LDoWZpY29zIGNvbXBsZXRvcywgbWllbnRyYXMgcXVlIGxhcyBkZSBiYWpvIG5pdmVsIHNlIGxpbWl0YW4gYSBhw7FhZGlyIGVsZW1lbnRvcyBhIHVuIGdyw6FmaWNvIGV4aXN0ZW50ZSAocG9yIHRhbnRvIGNyZWFkbyBwb3IgdW5hIGZ1bmNpw7NuIGRlIGFsdG8gbml2ZWwpLg0KDQpFbCBwYXF1ZXRlIGdyYXBoaWNzIChxdWUgc2UgY2FyZ2EgZW4gbWVtb3JpYSBjYWRhIHZleiBxdWUgYXJyYW5jYW1vcyBSKSBjb250aWVuZSB1biBidWVuIG7Dum1lcm8gZGUgZnVuY2lvbmVzIGRlIGFsdG8geSBiYWpvIG5pdmVsIHBhcmEgZ2VuZXJhciBncsOhZmljb3MuIE51bWVyb3NvcyBwYXF1ZXRlcyAqLXBsb3RyaXgsIHNjYXR0ZXJwbG90M0QsIHJnbCwgbWFwcywgc2hhcGVzLCDigKYsICp5IHNvYnJlIHRvZG8gZ2dwbG90Mi0gY29udGllbmVuIG11Y2jDrXNpbWFzIG3DoXMgZnVuY2lvbmVzIGdyw6FmaWNhcyBxdWUgbWVqb3JhbiB5IGNvbXBsZW1lbnRhbiBsYXMgcXVlIHZpZW5lbiBwb3IgZGVmZWN0byBjb24gUi4NCg0KPGNlbnRlcj4hW10oZ3JhZmljby1iYXJyYS5naWYpe3dpZHRoPTMwMH08L2NlbnRlcj4NCg0KDQojIyBGdW5jaW9uZXMgZ3LDoWZpY2FzIGRlIGFsdG8gbml2ZWwNCg0KRXN0YXMgZnVuY2lvbmVzIHNvbiBsYXMgcXVlIGdlbmVyYW4gZ3LDoWZpY29zIGNvbXBsZXRvcy4gRW50cmUgbGFzIG3DoXMgdXRpbGl6YWRhcyBwb2RlbW9zIGNpdGFyIHBsb3QoKSAoZ3LDoWZpY29zIGRlIG51YmVzIGRlIHB1bnRvcywgZW50cmUgb3Ryb3MpLCBoaXN0KCkgKGhpc3RvZ3JhbWFzKSwgYmFycGxvdCgpIChkaWFncmFtYXMgZGUgYmFycmFzKSwgYm94cGxvdCgpIChkaWFncmFtYXMgZGUgY2FqYSB5IGJpZ290ZSksIHBpZSgpIChkaWFncmFtYSBkZSBzZWN0b3JlcykgbyBwZXJzKCkgKHN1cGVyZmljaWVzIGVuIDNEKS4gVG9kYXMgZXN0YXMgZnVuY2lvbmVzIGRpc3BvbmVuIGRlIG11bHRpdHVkIGRlIGFyZ3VtZW50b3MgcXVlIHBlcm1pdGVuIGNvbnRyb2xhciBsYXMgZXRpcXVldGFzIGRlIGxvcyBlamVzLCBzdXMgbMOtbWl0ZXMsIHTDrXR1bG9zLCB0YW1hw7FvLCBjb2xvcmVzLCBldGMuOg0KDQotICAgKnhsaW0sIHlsaW0qOiBjb250cm9sYW4sIHJlc3BlY3RpdmFtZW50ZSwgbGEgZXh0ZW5zacOzbiBkZSBsb3MgZWplcyBYIGUgWS4gQXPDrSAqeGxpbT1jKDAsMTApKiBpbmRpY2EgcXVlIGVsIGVqZSBYIHNlIGV4dGllbmRlIGRlIDAgYSAxMDsgeWxpbT1jKC01LDUpIGluZGljYSBxdWUgZWwgZWplIFkgdmEgZGUgLTUgYSA1LiBTaSBubyBzZSBpbmNsdXllbiBlc3RvcyB2YWxvcmVzLCBSIGxvcyBhanVzdGEgcG9yIGRlZmVjdG8gZGUgbW9kbyBxdWUgc2UgaW5jbHV5YW4gdG9kb3MgbG9zIHZhbG9yZXMgZGlzcG9uaWJsZXMgZW4gZWwgZGF0YWZyYW1lLg0KDQotICAgKnhsYWIgZSB5bGFiKiBlc3BlY2lmaWNhbiBsYXMgZXRpcXVldGFzIHBhcmEgbG9zIGVqZXMgWCBlIFkgcmVzcGVjdGl2YW1lbnRlLg0KDQotICAgKm1haW4qIGluZGljYSBlbCB0w610dWxvIGRlbCBncsOhZmljby4NCg0KLSAgICpzdWIqIHBlcm1pdGUgZXNwZWNpZmljYXIgdW4gc3VidMOtdHVsby4NCg0KTG9zIGVqZW1wbG9zIHF1ZSBzZSBtdWVzdHJhbiBtw6FzIGFiYWpvIHBlcm1pdGVuIHZlciBjb21vIHV0aWxpemFyIGVzdGFzIG9wY2lvbmVzLg0KDQoNCg0KRG9zIGFyZ3VtZW50b3MgaW1wb3J0YW50ZXMgcXVlIHNvbiBjb211bmVzIGEgbGEgbWF5b3LDrWEgZGUgZ3LDoWZpY29zIGRlIGFsdG8gbml2ZWwgc29uIGxvcyBzaWd1aWVudGVzOg0KDQotICAgYWRkPVRSVUU6IGZ1ZXJ6YSBhIGxhIGZ1bmNpw7NuIGEgYWN0dWFyIGNvbW8gc2kgZnVlc2UgZGUgYmFqbyBuaXZlbCAoaW50ZW50YSBzdXBlcnBvbmVyIGxhIGZpZ3VyYSBxdWUgZ2VuZXJhIGEgdW4gZ3LDoWZpY28geWEgZXhpc3RlbnRlKS4gRXN0YSBvcGNpw7NuIG5vIGVzdMOhIGRpc3BvbmlibGUgcGFyYSB0b2RhcyBsYXMgZnVuY2lvbmVzLg0KDQp0eXBlIEluZGljYSBlbCB0aXBvIGRlIGdyw6FmaWNvIGEgcmVhbGl6YXIuIEVuIGNvbmNyZXRvOg0KDQotICAgdHlwZT0icCIgcmVwcmVzZW50YSBwdW50b3MgKG9wY2nDs24gcG9yIGRlZmVjdG8pDQotICAgdHlwZT0ibCIgcmVwcmVzZW50YSBsw61uZWFzDQotICAgdHlwZT0iYiIgKGJvdGgsIGFtYm9zKSByZXByZXNlbnRhIHB1bnRvcyB1bmlkb3MgcG9yIGzDrW5lYXMuDQotICAgdHlwZT0ibiIgTm8gZGlidWphIG5hZGEuDQoNCg0KIyMgRnVuY2lvbmVzIGdyw6FmaWNhcyBkZSBiYWpvIG5pdmVsOg0KDQpQZXJtaXRlbiBhw7FhZGlyIGzDrW5lYXMsIHB1bnRvcywgZXRpcXVldGFz4oCmIGEgdW4gZ3LDoWZpY28geWEgZXhpc3RlbnRlLiBTb24gZGUgZ3JhbiB1dGlsaWRhZCBwYXJhIGNvbXBsZXRhciB1biBncsOhZmljby4gRW50cmUgZXN0YXMgZnVuY2lvbmVzIGNhYmUgZGVzdGFjYXI6DQoNCi0gICBsaW5lcygpOiBQZXJtaXRlIGHDsWFkaXIgbGluZWFzICh1bmllbmRvIHB1bnRvcyBjb25jcmV0b3MpIGEgdW5hIGdyw6FmaWNhIHlhIGV4aXN0ZW50ZS4NCg0KLSAgIGFibGluZSgpOiBBw7FhZGUgbGluZWFzIGhvcml6b250YWxlcywgdmVydGljYWxlcyB1IG9ibGljdWFzLCBpbmRpY2FuZG8gcGVuZGllbnRlIHkgb3JkZW5hZGEuDQoNCi0gICBwb2ludHMoKTogUGVybWl0ZSBhw7FhZGlyIHB1bnRvcy4NCg0KLSAgIGxlZ2VuZCgpOiBQZXJtaXRlIGHDsWFkaXIgdW5hIGxleWVuZGEuDQoNCi0gICB0ZXh0KCk6IEHDsWFkZSB0ZXh0byBlbiBsYXMgcG9zaWNpb25lcyBxdWUgc2UgaW5kaXF1ZW4uDQoNCi0gICBncmlkKCk6IEHDsWFkZSB1bmEgbWFsbGEgZGUgZm9uZG8uDQoNCi0gICB0aXRsZSgpOiBwZXJtaXRlIGHDsWFkaXIgdW4gdMOtdHVsbyBvIHN1YnTDrXR1bG8uDQoNCg0KIyMgQXJndW1lbnRvcyBjb211bmVzIGEgbGFzIGZ1bmNpb25lcyBncsOhZmljYXMgZGUgYWx0byB5IGJham8gbml2ZWwNCg0KTG9zIHNpZ3VpZW50ZXMgYXJndW1lbnRvcyBvcGNpb25hbGVzIHNvbiBjb211bmVzIGEgbXVjaGFzIGZ1bmNpb25lcyBncsOhZmljYXMgZGUgYWx0byB5IGJham8gbml2ZWwuIFN1cyB2YWxvcmVzIHBvciBkZWZlY3RvIHB1ZWRlbiBvYnRlbmVyc2UgZWplY3V0YW5kbyBsYSBmdW5jacOzbiBwYXIoKS4gU2UgcHVlZGUgZW5jb250cmFyIGVsIHNpZ25pZmljYWRvIHkgdmFsb3JlcyBwb3NpYmxlcyBkZSBjYWRhIHVubyBkZSBlc3RvcyBhcmd1bWVudG9zICh5IG11Y2hvcyBtw6FzKSBlamVjdXRhbmRvIGhlbHAocGFyKS4NCg0KLSAgICpwY2g6KiBJbmRpY2EgbGEgZm9ybWEgZW4gcXVlIHNlIGRpYnVqYXJhbiBsb3MgcHVudG9zIChjw61yY3VsbywgY3VhZHJhZG8sIGVzdHJlbGxhLCBldGMpLiBFbCBsaXN0YWRvIGRlIHZhbG9yZXMgeSBmb3JtYXMgZGlzcG9uaWJsZXMgcHVlZGUgdmVyc2UgbWVkaWFudGUgaGVscChwb2ludHMpDQoNCi0gICAqbHR5OiogSW5kaWNhIGxhIGZvcm1hIGVuIHF1ZSBzZSBkaWJ1amFuIGxhcyBsw61uZWFzIChjb250aW51YSwgYSB0cmF6b3MsIOKApikuDQoNCi0gICAqbHdkOiogQW5jaG8gZGUgbGFzIGzDrW5lYXMuDQoNCi0gICAqY29sOiogQ29sb3IgdXNhZG8gcGFyYSBlbCBncsOhZmljbyAoeWEgc2VhIHBhcmEgcHVudG9zLCBsw61uZWFz4oCmKS4gUHVlZGUgdmVycyB1biBsaXN0YWRvIGNvbXBsZXRvIGRlIGxvcyBjb2xvcmVzIGRpc3BvbmlibGVzIGVuIFIgZWplY3V0YW5kbyBsYSBmdW5jacOzbiBjb2xvcnMoKS4gaGVscChjb2xvcnMpIGV4cGxpY2EgY29tbyBvYnRlbmVyIGHDum4gbcOhcyBjb2xvcmVzLiBFc3RlIGRvY3VtZW50byBjb250aWVuZSB1bmEgbXVlc3RyYSBkZSBjYWRhIGNvbG9yLg0KDQotICAgKmZvbnQ6KiBGdWVudGUgYSB1c2FyIGVuIGVsIHRleHRvLg0KDQotICAgKmxhczoqIENhbWJpYSBlbCBlc3RpbG8gZGUgbGFzIGV0aXF1ZXRhcyBkZSBsb3MgZWplcyAoMCBwYXJhbGVsbyBhIGxvcyBlamVzLCAxIHNpZW1wcmUgaG9yaXpvbnRhbGVzLCAyLCBwZXJwZW5kaWN1bGFyZXMgYSBsb3MgZWplcywgMyBzaWVtcHJlIHZlcnRpY2FsZXMpDQoNCg0KIyMgRWplbXBsb3MgZGUgZnVuY2lvbmVzIGdyw6FmaWNhcyBkZSBhbHRvIG5pdmVsDQoNClBvZGVtb3MgZGVzdGFjYXIsIGVudHJlIGxhcyBtw6FzIHV0aWxpemFkYXM6DQoNCipwbG90KCkqDQoNCkVzdGEgZnVuY2nDs24gb2ZyZWNlIG11Y2hhcyB2YXJpYW50ZXMgZGVwZW5kaWVuZG8gZGVsIHRpcG8gZGUgb2JqZXRvIGFsIHF1ZSBzZSBhcGxpcXVlLiBFbCBjYXNvIG3DoXMgc2ltcGxlIGNvcnJlc3BvbmRlIGEgbGEgcmVwcmVzZW50YWNpw7NuIGRlIGRvcyB2YXJpYWJsZXMgeCBlIHkuIEVuIHRhbCBjYXNvLCBwbG90KHgseSkgcmVwcmVzZW50YSB1biBkaWFncmFtYSBkZSBkaXNwZXJzacOzbiBkZSBwdW50b3MgZGUgeSBmcmVudGUgYSB4Lg0KDQpBIG1vZG8gZGUgZWplbXBsbyBzZSBtdWVzdHJhIGEgY29udGludWFjacOzbiB1biBncsOhZmljbyBkZSBsYSBlc3BlcmFuemEgZGUgdmlkYSAoTGlmZUV4cGVjdGFuY3ksIHF1ZSBzZXLDoSBudWVzdHJhIHZhcmlhYmxlIHkpIGZyZW50ZSBhbCDDrW5kaWNlIGRlIGZlbGljaWRhZCAoSGFwcGluZXNzLCBxdWUgZXMgbGEgeCkgZW4gdW5hIG11ZXN0cmEgZGUgMTQzIHBhw61zZXMuIExvcyBkYXRvcyBzZSBlbmN1ZW50cmFuIGVuIGVsIGRhdGFmcmFtZShIYXBweVBsYW5ldEluZGV4KSBkZWwgcGFxdWV0ZSAoTG9jazVEYXRhKSAoY29uc3VsdGFyIGhlbHAoSGFwcHlQbGFuZXRJbmRleCkgcGFyYSB2ZXIgbGFzIHZhcmlhYmxlcyBlbiBlc3RlIGRhdGFmcmFtZSwgeSBodHRwOi8vd3d3LmhhcHB5cGxhbmV0aW5kZXgub3JnL2Fib3V0LyBwYXJhIG3DoXMgaW5mb3JtYWNpw7NuIHNvYnJlIGVzdGUgZXN0dWRpbyk6DQoNCmBgYHtyfQ0KbGlicmFyeShMb2NrNURhdGEpDQpkYXRhKEhhcHB5UGxhbmV0SW5kZXgpDQphdHRhY2goSGFwcHlQbGFuZXRJbmRleCkNCnBsb3QoSGFwcGluZXNzLExpZmVFeHBlY3RhbmN5LHBjaD0xOSxjb2w9InJlZCIpDQpgYGANCg0KKmhpc3QoKSoNCkVzdGEgZnVuY2nDs24gcGVybWl0ZSBkaWJ1amFyIGhpc3RvZ3JhbWFzIGRlIGZyZWN1ZW5jaWFzIHBhcmEgdmFyaWFibGVzIGNvbnRpbnVhcy4gUG9yIGVqZW1wbG8sIGVsIGhpc3RvZ3JhbWEgZGUgbG9zIG5pdmVsZXMgZGUgZmVsaWNpZGFkIGVuIGxvcyBkaXN0aW50b3MgcGHDrXNlcyBkZSBsYSBtdWVzdHJhIHNlIG9idGllbmUgZsOhY2lsbWVudGUgY29tbzoNCg0KYGBge3J9DQpoaXN0KEhhcHBpbmVzcyxjb2w9ImRhcmtvbGl2ZWdyZWVuMSIpDQpgYGANCg0KRW4gZWwgbWlzbW8gcGFxdWV0ZSBMb2NrNURhdGEgcG9kZW1vcyBlbmNvbnRyYXIgZWwgZGF0YWZyYW1lIFNhbGFyeUdlbmRlciBxdWUgY29udGllbmUgdW5hIG11ZXN0cmEgZGUgMTAwIHByb2Zlc29yZXMgdW5pdmVyc2l0YXJpb3MgZGUgRUVVVSwgNTAgaG9tYnJlcyB5IDUwIG11amVyZXM7IHBhcmEgY2FkYSB1bm8gc2UgdGllbmUgZWwgc2FsYXJpbyBhbnVhbCAoZW4gbWlsZXMgZGUgZMOzbGFyZXMpLCBsYSBlZGFkIHkgbGEgdmFyaWFibGUgUGhEIHF1ZSB2YWxlIDEgc2kgZWwgcHJvZmVzb3IgZXMgZG9jdG9yIHkgMCBzaSBubyBsbyBlcy4gUG9kZW1vcyB2ZXIgbGEgZGlzdHJpYnVjacOzbiBkZSBzYWxhcmlvcyBlbnRyZSBob21icmVzIHkgbXVqZXJlcyBtZWRpYW50ZSB1biBoaXN0b2dyYW1hIGNvbWJpbmFkbyB1dGlsaXphbmRvIGxhIGZ1bmNpw7NuIGhpc3RTdGFjaygpIGRlbCBwYXF1ZXRlIHBsb3RyaXg6DQoNCmBgYHtyfQ0KDQpkYXRhKFNhbGFyeUdlbmRlcikNCmF0dGFjaChTYWxhcnlHZW5kZXIpDQpHZW5kZXI9ZmFjdG9yKEdlbmRlcixsZXZlbHM9YygwLDEpLGxhYmVscz1jKCJGZW1hbGUiLCJNYWxlIikpDQpsaWJyYXJ5KHBsb3RyaXgpDQpoaXN0U3RhY2soU2FsYXJ5LEdlbmRlcixsZWdlbmQucG9zPSJ0b3ByaWdodCIpDQpgYGANCg0KDQoqYmFycGxvdCgpKg0KU2UgdXRpbGl6YSBwYXJhIGRpYnVqYXIgZGlhZ3JhbWFzIGRlIGJhcnJhcy4gRWwgc2lndWllbnRlIGVqZW1wbG8gbXVlc3RyYSBlbCBuw7ptZXJvIGRlIHBhw61zZXMgZW4gY2FkYSB1bmEgZGUgbGFzIDcgcmVnaW9uZXMgZW4gcXVlIHNlIGRpdmlkacOzIGVsIHBsYW5ldGEgcGFyYSBlbCBlc3R1ZGlvIGRlIGxvcyBuaXZlbGVzIGRlIGZlbGljaWRhZDoNCg0KYGBge3J9DQpiYXJwbG90KHRhYmxlKFJlZ2lvbikseGxhYj0iUmVnaW9uIixtYWluPSJIYXBwaW5lc3MgbGV2ZWwgYnkgcmVnaW9uIiwgY29sPXJhaW5ib3coMTApKQ0KDQpgYGANCg0KRWwgcGFxdWV0ZSBwbG90cml4IGNvbnRpZW5lIGxhIGZ1bmNpw7NuIGJhcnAoKSBxdWUgcGVybWl0ZSBkYXIg4oCcdm9sdW1lbuKAnSBhIGxhIGJhcnJhczoNCg0KYGBge3J9DQoNCmJhcnAodGFibGUoUmVnaW9uKSxjb2w9ImxpZ2h0Ymx1ZSIsY3lsaW5kcmljYWw9VFJVRSxzaGFkb3c9VFJVRSkNCmBgYA0KDQoNCkVzIHBvc2libGUgY29uc3RydWlyIGRpYWdyYW1hcyBkZSBiYXJyYXMgcG9yIGNhdGVnb3LDrWFzOyBwb2RlbW9zLCBwb3IgZWplbXBsbyByZXByZXNlbnRhciBsYSBmcmVjdWVuY2lhIGRlIGRvY3RvcmVzIHBvciBzZXhvIHV0aWxpemFuZG8gbG9zIGRhdG9zIGRlbCBkYXRhZnJhbWUgU2FsYXJ5R2VuZGVyOg0KDQpgYGB7cn0NCg0KUGhEPWZhY3RvcihQaEQsbGV2ZWxzPWMoMCwxKSxjKCJQaEQiLCJub24gUGhEIikpDQpiYXJwbG90KHRhYmxlKEdlbmRlcixQaEQpLGJlc2lkZT1UUlVFLGxlZ2VuZC50ZXh0PVRSVUUsY29sPWMoInBpbmsiLCJjeWFuIikpDQpgYGANCg0KKnBpZSgpKg0KQXBvcnRhIGxhIG1pc21hIGluZm9ybWFjacOzbiBxdWUgZWwgZGlhZ3JhbWEgZGUgYmFycmFzLCBwZXJvIGVuIGZvcm1hIGRlIGRpYWdyYW1hIGRlIHNlY3RvcmVzOg0KDQpgYGB7cn0NCnBpZSh0YWJsZShSZWdpb24pKQ0KYGBgDQoNCkVsIHBhcXVldGUgcGxvdHJpeCBwZXJtaXRlIGVsYWJvcmFyIGRpYWdyYW1hcyBkZSBzZWN0b3JlcyBlbiAzRCBtZWRpYW50ZSBsYSBmdW5jacOzbiBwaWUzRDoNCg0KYGBge3J9DQoNCnBpZTNEKHRhYmxlKFJlZ2lvbikpDQoNCmBgYA0KDQoqYm94cGxvdCgpKg0KTGxldmEgYSBjYWJvIGxhIHJlcHJlc2VudGFjacOzbiBkZSBncsOhZmljb3MgZGUg4oCcY2FqYSB5IGJpZ290ZeKAnS4gRWwgc2lndWllbnRlIGVqZW1wbG8gbXVlc3RyYSBlbCByZXBhcnRvIGRlIGxvcyBuaXZlbGVzIGRlIGZlbGljaWRhZCBlbnRyZSBsYXMgZGlzdGludGFzIHJlZ2lvbmVzIGRlbCBnbG9ibzoNCg0KYGBge3J9DQpib3hwbG90KEhhcHBpbmVzc35SZWdpb24sY29sPSJnb2xkIix4bGFiPSJSZWdpb24iLHlsYWI9IkhhcHBpbmVzcyBsZXZlbCIsDQogICAgICAgIG1haW49IkF2ZXJhZ2UgaGFwcGluZXNzIGxldmVsIGJ5IHJlZ2lvbiIpDQpgYGANCg0KKnBlcnNwKCkqDQpFc3RhIGZ1bmNpw7NuIHJlYWxpemEgcmVwcmVzZW50YWNpb25lcyB0cmlkaW1lbnNpb25hbGVzIChzdXBlcmZpY2llcykuIEVsIGRhdGFmcmFtZSB2b2xjYW5vIHF1ZSBzZSBkaXN0cmlidXllIGp1bnRvIGNvbiBsYSBpbnN0YWxhY2nDs24gYsOhc2ljYSBkZSBSICh2ZXIgaGVscCh2b2xjYW5vKSkgY29udGllbmUgaW5mb3JtYWNpw7NuIHRvcG9ncsOhZmljYSBkZWwgdm9sY8OhbiBNYXVuZ2EgV2hhdSBlbiBBdWNrbGFuZCwgTnVldmEgWmVsYW5kYSwgZGVmaW5pZGEgc29icmUgdW5hIG1hbGxhIGRlIDg3MMOXNjEwIG1ldHJvcywgY29uIHVuIG5vZG8gY2FkYSAxMCBtZXRyb3MuIFBvZGVtb3MgdHJhemFyIGVsIHBlcmZpbCB0b3BvZ3LDoWZpY28gZGUgZXN0ZSB2b2xjw6FuIG1lZGlhbnRlOg0KDQpgYGB7cn0NCg0KcGVyc3AoeCA9IDEwKigxOm5yb3codm9sY2FubykpLCB5PTEwKigxOm5jb2wodm9sY2FubykpLCB6PTMqdm9sY2FubywgDQogICAgICB0aGV0YSA9IDEzNSwgcGhpID0gMzAsIGNvbCA9ICJncmVlbjMiLCBzY2FsZSA9IEZBTFNFLA0KICAgICAgbHRoZXRhID0gLTEyMCwgc2hhZGUgPSAwLjc1LCBib3JkZXIgPSBOQSwgYm94ID0gRkFMU0UsIG1haW49IlZvbGPDoW4gTWF1bmdhIFdoYXUsIEF1Y2tsYW5kLCBOWiIpDQpgYGANCg0KDQojIyBGdW5jaW9uZXMgZGUgbG9jYWxpemFjacOzbiBlIGlkZW50aWZpY2FjacOzbiBkZSBwdW50b3M6IGxvY2F0b3IoKSBlIGlkZW50aWZ5KCkNCg0KVW5hIHZleiBxdWUgZW4gbGEgdmVudGFuYSBkZSBncsOhZmljb3MgdGVuZW1vcyBhbGdvIHJlcHJlc2VudGFkbywgZXhpc3RlbiBkb3MgZnVuY2lvbmVzIHF1ZSBwZXJtaXRlbiB0cmFiYWphciBpbnRlcmFjdGl2YW1lbnRlIGNvbiBlbCBncsOhZmljbzoNCg0KTGEgZnVuY2nDs24gbG9jYXRvcigpOiBhbCBzaXR1YXIgZWwgY3Vyc29yIHNvYnJlIGxhIHZlbnRhbmEgZGUgZ3LDoWZpY29zLCBjYWRhIHZleiBxdWUgcHVsc2Vtb3MgZWwgYm90w7NuIGl6cXVpZXJkbyBkZWwgcmF0w7NuLCBzZSBhbG1hY2VuYW4gZW4gbWVtb3JpYSBsYXMgY29vcmRlbmFkYXMgZGVsIHB1bnRvIHF1ZSBtYXJxdWVtb3MuIEFsIHB1bHNhciBsYSB0ZWNsYSA8IEVTQyA+LCBSIG5vcyBtdWVzdHJhIGRpY2hhcyBjb29yZGVuYWRhcyBlbiBsYSBjb25zb2xhIChzaSBoZW1vcyBlamVjdXRhZG8gc2ltcGxlbWVudGUgbG9jYXRvcigpKSBvIGxhcyBndWFyZGEgZW4gZWwgb2JqZXRvIGFsIHF1ZSBoYXlhbW9zIGFzaWduYWRvIGxhIHNhbGlkYSBkaWNoYSBmdW5jacOzbi4gUG9yIGVqZW1wbG8sIHNpIGhlbW9zIGVqZWN1dGFkbyBwb3NpY2lvbmVzPWxvY2F0b3IoKSwgYWwgcHVsc2FyIDwgRVNDID4gbGFzIGNvb3JkZW5hZGFzIGRlIGxvcyBwdW50b3MgcXVlIGhheWFtb3MgbWFyY2FkbyBlbiBlbCBncsOhZmljbyBzZSBndWFyZGFuIGVuIGxhIHZhcmlhYmxlIHBvc2ljaW9uZXMuDQoNCkxhIGZ1bmNpw7NuIGlkZW50aWZ5KCkgcGVybWl0ZSBpZGVudGlmaWNhciBjb24gZWwgcmF0w7NuIGEgcXXDqSBwb3NpY2lvbmVzIGRlbnRybyBkZWwgY29uanVudG8gZGUgZGF0b3MgY29ycmVzcG9uZGVuIGxvcyBwdW50b3MgcXVlIHNlw7FhbGVtb3MgZW4gdW4gZ3LDoWZpY28uIFNpLCBwb3IgZWplbXBsbywgZWplY3V0YW1vcyBwbG90KEhhcHBpbmVzcyxMaWZlRXhwZWN0YW5jeSkgcGFyYSBtb3N0cmFyIGxhIG51YmUgZGUgcHVudG9zIGRlIGxhIGVzcGVyYW56YSBkZSB2aWRhIGZyZW50ZSBhbCDDrW5kaWNlIGRlIGZlbGljaWRhZCwgeSBhIGNvbnRpbnVhY2nDs24gZWplY3V0YW1vcyBpZGVudGlmeShIYXBwaW5lc3MsTGlmZUV4cGVjdGFuY3kpIHkgcGljYW1vcyBlbiBhbGd1bm9zIHB1bnRvcyBkZWwgZ3LDoWZpY28gY29uIGVsIHJhdMOzbiwgYWwgcHVsc2FyIDwgRVNDID4sIFIgbm9zIGRldnVlbHZlIGVuIGxhIGNvbnNvbGEgKHkgcmVwcmVzZW50YSBlbiBlbCBncsOhZmljbykgbG9zIG7Dum1lcm9zIGRlIG9yZGVuIGRlbnRybyBkZWwgZGF0YWZyYW1lIGEgbG9zIHF1ZSBjb3JyZXNwb25kZW4gbG9zIHB1bnRvcyBxdWUgaGVtb3MgbWFyY2Fkby4NCg0KDQojIyBNw6FzIGluZm9ybWFjacOzbg0KDQotICAgUXVpY2stUjogU2VjY2nDs24gZGUgZ3LDoWZpY29zIGRlIFF1aWNrLVIuDQotICAgUiBHcmFwaGljYWwgTWFudWFsOiBnYWxlcsOtYSBkZSBncsOhZmljb3MgZW4gUiBjb24gc3UgY8OzZGlnbyBjb3JyZXNwb25kaWVudGUuDQotICAgUiBHcmFwaCBHYWxsZXJ5OiBnYWxlcsOtYSBkZSBncsOhZmljb3MgcXVlIHNlIHB1ZWRlbiBoYWNlciBjb24gUiB5IGVsIGPDs2RpZ28gcGFyYSBvYnRlbmVybG9zLg0KLSAgIFIgR3JhcGhpY3M6IEltw6FnZW5lcyBkZSB0b2RvcyBsb3MgZ3LDoWZpY29zIGRlbCBsaWJybyDigJxSIEdyYXBoaWNz4oCdIGRlIFAuIE11cnJlbGwsIGNvbiBlbCBjw7NkaWdvIHBhcmEgZ2VuZXJhcmxvcy4NCi0gICBUaGUgY29tcGxldGUgZ2dwbG90IHR1dG9yaWFsDQotICAgZ2dwbG90IFR1dG9yaWFsOiBJbnRyb2R1Y2Npw7NuIGEgZ2dwbG90IChncmFtbWFyIG9mIGdyYXBoaWNzKSwgbGlicmVyw61hIHF1ZSBwZXJtaXRlIHJlYWxpemFyIGdyw6FmaWNvcyBkZSBhbHRhIGNhbGlkYWQuDQotICAgZ2dwbG90IHF1aWNrIHJlZmVyZW5jZTogZ3XDrWEgZGUgcmVmZXJlbmNpYSBkZSBnZ3Bsb3QuDQotICAgVHJlbGxpcyBncmFwaGljcw0KLSAgIEVqZW1wbG9zIGRlIGdyw6FmaWNvcyBUcmVsbGlzDQotICAgR3JhZmljb3MgdGlwbyDigJxjb21pY+KAnSB4a2NkDQotICAgUmVjdXJzb3MgcGFyYSBnZ3Bsb3QNCg==