R incluye mucha funcionalidad para hacer la manipulación de datos más sencilla y es imposible ni siquiera dar una panorámica de todas las herramientas disponibles. Sin embargo, la mayor parte de las cosas que haremos durante los próximos días caerán en tres categorías: agrupar datos, fusionar dato y restructurar datos. Además, las tres categorías resumen bien el modo en el que R opera en análisis más prácticos.

Split-apply-combine

Consideremos la base de dato de impuestos al tabaco. Los datos estaban agrupados por año y estado y teníamos información sobre la carga impositiva al tabaco y la renta de cada estado. Imaginemos que estamos interesados en la carga impositiva media para cada año con el objetivo de ver cómo evoluciona en el tiempo. La estructura del problema es algo que veremos en muchas ocasiones a lo largo de estos días. Queremos dividir los datos por grupos definidos por años, calcular la media para cada uno de esos grupos, y después juntar los grupos de nuevo para tener una base de datos. Veremos por encima tres formas de conseguir ese objetivo.

tobacco <- read.csv("http://koaning.io/theme/data/cigarette.csv")

Empezaremos por funciones que vienen en el paquete base de R porque ilustran bien el proceso que las otras dos aproximaciones intentan simplificar. Esto es lo que queremos hacer, paso por paso

head(tobacco)
mean(tobacco$tax[tobacco$year == 1985])
mean(tobacco$tax[tobacco$year == 1986])
mean(tobacco$tax[tobacco$year == 1987])

Ahora que sabemos hacer bucles, podemos pensar en la siguiente forma de aproximar el problema

for (i in 1985:1990) {
    print(mean(tobacco$tax[tobacco$year == i]))
}

y podríamos añadir más información para saber a qué año se corresponde cada media

for (i in 1985:1990) {
    print(cbind(i, mean(tobacco$tax[tobacco$year == i])))
}

e incluso almacenar la información

output <- data.frame("year"=unique(tobacco$year), "ave"=NA)
for (i in 1:nrow(output)) {
    output$ave[i] <-  mean(tobacco$tax[tobacco$year == output$year[i]])
}

Aunque el proceso es intuitivo, necesitamos poner cierto esfuerzo para interpretar el código. Una forma diferente de atacar el problema es siendo más directo. En lugar de hacer un bucle, indicaremos a R cómo hacer la partición.

split_tobacco <- split(tobacco$tax, tobacco$year)
head(split_tobacco)

y a continuación aplicaremos una media sobre cada uno de los elementos.

mean_tobacco <- lapply(split_tobacco, mean)
head(mean_tobacco)

y por último juntamos los elementos de nuevo usando la función do.call que nos pide que indiquemos cómo hemos de realizar el pegado:

do.call(rbind, mean_tobacco)

Esta es la esencia de la estrategia de dividir (usando split), aplicar (usando lapply) y combinar (usando do.call).

Podemos conseguir lo mismo de otros dos modos usando dos paquetes muy populares en R. Una opción sería usar el paquete plyr que ofrece una interfaz común, clara e intuitiva para operaciones que tienen todas la misma naturaleza.

El uso es muy similar. El primer argumento indica los datos que queremos particionar, el segundo la variable que usaremos para particionar (aunque quizás no es necesario, como veremos enseguida) y el tercero la operación que queremos realizar. Un par de ejemplos serán de utilidad. Empezaremos por instalar el paquete plyr que no viene por defecto:

install.packages("plyr"); library(plyr)

y ahora

group_means <- ddply(tobacco, .(year), function(x) mean(x$tax))
head(group_means)

El nombre de la función ddply nos da mucha información sobre qué es lo que está ocurriendo. La primera letra d nos dice que el input será un data.frame, la segunda letra d que el output es otro data.frame. Todas las funciones en plyr tienen la misma estructura. Por ejemplo, podríamos calcular la media por grupos y devolver el resultado como una lista cambiando la segunda letra de d a l.

head(dlply(tobacco, .(year), function(x) mean(x$tax)))

o podríamos haber hecho los cálculos usando la lista que creamos más arriba cuando demostrábamos split. En este caso, el input es una lista, por lo que la primera letra de la función será l y no necesitamos especificar la variable de agrupación ya que los datos ya están agrupados (lo cual debería ayudarnos a entender que plyr, igual que en el proceso manual, usa una lista como pivote):

ldply(split_tobacco, mean)

El tercer método es similar a plyr pero es un conjunto de funciones que están especializadas en ddply, esto es, funciones que toman y devuelven un data.frame. La sintaxis es un poco diferente a lo que hemos visto hasta ahora pero está convirtiéndose en muy popular, así que es importante no perder este paquete de vista. Empezaremos por instalar y cargar el paquete:

install.packages("dplyr"); library(dplyr)

El paquete funciona mediante verbos encadenados con operadores (%>%) que concatenan operaciones y tienen una enorme similitud a SQL. Por ejemplo, lo que queremos hacer es agrupar los datos por year y después resumirlos para la variable tax. Aquí enfatizo resumir por oposición a mutar en el sentido de que el resultado es un resumen y no una transformación de la columna: cada grupo de observaciones será resumido en un único valor: la media.

tobacco %>%
    group_by(year) %>%
    summarize(ave=mean(tax))

¿Qué pasaría si cambiasemos el verbo summarize por el verbo mutar?

mtobacco <- tobacco %>%
    group_by(year) %>%
    mutate(ave=mean(tax))
head(mtobacco)

En este caso, lo que estamos pidiendo es una mutación de la base de datos original, de tal forma que cada entrada tiene asociada la media de la variable tax para su año correspondiente.

Una cosa a enfatizar es la generalidad de este tipo de aproximación. Muchas operaciones, no solo el cálculo de medidas resumen, se pueden concebir como parte de split-apply-combine. Pensemos, por ejemplo, en cómo el consumo de tabaco cambiá en relación a los impuestos para cada año. Una aproximación, no la mejor, sería hacer una regresión separada entre consumo e impuestos para cada año. Para ver el efecto, solo queremos ver los coeficientes:

lm_models <- dlply(tobacco, ~ year, function(x) lm(packpc ~ log(tax), data=x))
lm_coefs <- ldply(lm_models, coefficients)
lm_coefs

Hay dos cosas a tener en cuenta aquí. En primer lugar que estamos transformando un data.frame en una lista (dlply) y después la lista resultante en otro data.frame (ldply). Verifica que entiendes por qué hacemos esto. La otra cosa a tener en cuenta es que hemos generado una función anónima para calcular cada regresión. El argumento que necesitamos para especificar qué hacer en cada grupo es una función que toma un único parámetro, en este caso la base de datos correspodiente a ese año. Como es una función concreta que solo necesitamos para una única tarea, no nos molestamos ni siquiera en darle un nombre.

Fusionar dos bases de datos

Por ahora hemos trabajado con un único data.frame que vive en memoria. Sin embargo, en muchas ocasiones (bien por que los datos vienen en estructuras diferentes o por que generamos bases separadas en el transcurso de nuestras operaciones) necesitaremos fusionar bases de datos que contienen una llave común. Pensemos en un ejemplo trivial en el que queremos tomar los coeficientes que acabamos de estimar y los queremos juntar de nuevo en nuestra base de datos original.

merged_tobacco <- merge(tobacco, lm_coefs, by="year")
head(merged_tobacco)

La función merge toma otros argumentos que nos permiten especificar diferentes clases de join, qué hacer con las unidades que no existen en las dos bases de datos (¿las incluimos o las dejamos?), o qué hacer con variables que existen en las dos bases de datos.

Restructurar un base de datos

Una operación que haremos mucho a lo largo de los dos siguientes días será restructurar el formato de nuestra base de datos. Por ejemplo, ahora nuestra base de datos sobre consumo de tabaco está estructurada por pares estado-año.

head(tobacco)

Cada fila representa un año en un determinado estado. Sin embargo, podemos querer cambiar el formato de tal forma que cada fila represente datos para un determinado año. Por supuesto, y como siempre, existen modos de hacer esto usando las librerías base en R, pero creo que el paquete reshape2 nos ofrece una vía más directa e intuitiva. Las operaciones aquí se dividen en dos funciones: una operación derrite (melts) la base de datos de acuerdo con algunas variables que hagan de índice. La segunda funde (casts) en la estructura que nosotros queramos. Por ejemplo, para el problema anterior podemos hacer:

library(reshape2)
molten_tobacco <- melt(tobacco[, c("state", "year", "tax")], id=c("state", "year"))
head(molten_tobacco)
tail(molten_tobacco)
head(dcast(molten_tobacco, state ~ variable + year))

Vayamos paso por paso en lo que ha pasado antes. En primer lugar, hemos tomado únicamente tres variables de la base de datos y hemos pedido a R que la transforme de tal manera que state e year permanezcan como índices en las filas. En este caso, lo único que estamos haciendo es añadir dos nuevas variables variable y value que capturan las variables y valores que no son id. Si hacemos melt sin seleccionar variables lo podemos ver con más claridad

head(melt(tobacco, id=c("state", "year")))
tail(melt(tobacco, id=c("state", "year")))

Aquí vemos con claridad que todas las variables han sido transformadas para que aparezcan en una única columna con su nombre y su valor. La siguiente fase es cast que nos permite, usando una fórmula, indicar qué variables van a permanecer en las filas y cuáles en las columnas. En este caso, la izquierda del símbolo ~ representa las filas y la derecha las columnas.

Así, por ejemplo, en el caso anterior lo que queríamos era pivotar la variable tax a las columnas y añadir a esta el valor de año, mientras que en las filas permanece la variable state

head(dcast(molten_tobacco, state ~ variable + year))
LS0tCnRpdGxlOiAiTWFuaXB1bGFjacOzbiBkZSBkYXRvcyIKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJUIgJWQsICVZJylgIgotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFLCBjYWNoZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGV2YWwgPSBGQUxTRSkgCmBgYAoKYFJgIGluY2x1eWUgbXVjaGEgZnVuY2lvbmFsaWRhZCBwYXJhIGhhY2VyIGxhIG1hbmlwdWxhY2nDs24gZGUgZGF0b3MgbcOhcyBzZW5jaWxsYQp5IGVzIGltcG9zaWJsZSBuaSBzaXF1aWVyYSBkYXIgdW5hIHBhbm9yw6FtaWNhIGRlIHRvZGFzIGxhcyBoZXJyYW1pZW50YXMKZGlzcG9uaWJsZXMuIFNpbiBlbWJhcmdvLCBsYSBtYXlvciBwYXJ0ZSBkZSBsYXMgY29zYXMgcXVlIGhhcmVtb3MgZHVyYW50ZSBsb3MKcHLDs3hpbW9zIGTDrWFzIGNhZXLDoW4gZW4gdHJlcyBjYXRlZ29yw61hczogYWdydXBhciBkYXRvcywgZnVzaW9uYXIgZGF0byB5CnJlc3RydWN0dXJhciBkYXRvcy4gQWRlbcOhcywgbGFzIHRyZXMgY2F0ZWdvcsOtYXMgcmVzdW1lbiBiaWVuIGVsIG1vZG8gZW4gZWwgcXVlCmBSYCBvcGVyYSBlbiBhbsOhbGlzaXMgbcOhcyBwcsOhY3RpY29zLgoKIyMgU3BsaXQtYXBwbHktY29tYmluZQoKQ29uc2lkZXJlbW9zIGxhIGJhc2UgZGUgZGF0byBkZSBpbXB1ZXN0b3MgYWwgdGFiYWNvLiBMb3MgZGF0b3MgZXN0YWJhbiBhZ3J1cGFkb3MKcG9yIGHDsW8geSBlc3RhZG8geSB0ZW7DrWFtb3MgaW5mb3JtYWNpw7NuIHNvYnJlIGxhIGNhcmdhIGltcG9zaXRpdmEgYWwgdGFiYWNvIHkgbGEKcmVudGEgZGUgY2FkYSBlc3RhZG8uIEltYWdpbmVtb3MgcXVlIGVzdGFtb3MgaW50ZXJlc2Fkb3MgZW4gbGEgY2FyZ2EgaW1wb3NpdGl2YQptZWRpYSBwYXJhIGNhZGEgYcOxbyBjb24gZWwgb2JqZXRpdm8gZGUgdmVyIGPDs21vIGV2b2x1Y2lvbmEgZW4gZWwgdGllbXBvLiBMYQplc3RydWN0dXJhIGRlbCBwcm9ibGVtYSBlcyBhbGdvIHF1ZSB2ZXJlbW9zIGVuIG11Y2hhcyBvY2FzaW9uZXMgYSBsbyBsYXJnbyBkZQplc3RvcyBkw61hcy4gUXVlcmVtb3MgZGl2aWRpciBsb3MgZGF0b3MgcG9yIGdydXBvcyBkZWZpbmlkb3MgcG9yIGHDsW9zLCBjYWxjdWxhcgpsYSBtZWRpYSBwYXJhIGNhZGEgdW5vIGRlIGVzb3MgZ3J1cG9zLCB5IGRlc3B1w6lzIGp1bnRhciBsb3MgZ3J1cG9zIGRlIG51ZXZvIHBhcmEKdGVuZXIgdW5hIGJhc2UgZGUgZGF0b3MuIFZlcmVtb3MgcG9yIGVuY2ltYSB0cmVzIGZvcm1hcyBkZSBjb25zZWd1aXIgZXNlCm9iamV0aXZvLiAKCmBgYHtyfQp0b2JhY2NvIDwtIHJlYWQuY3N2KCJodHRwOi8va29hbmluZy5pby90aGVtZS9kYXRhL2NpZ2FyZXR0ZS5jc3YiKQpgYGAKCkVtcGV6YXJlbW9zIHBvciBmdW5jaW9uZXMgcXVlIHZpZW5lbiBlbiBlbCBwYXF1ZXRlIGJhc2UgZGUgYFJgIHBvcnF1ZSBpbHVzdHJhbgpiaWVuIGVsIHByb2Nlc28gcXVlIGxhcyBvdHJhcyBkb3MgYXByb3hpbWFjaW9uZXMgaW50ZW50YW4gc2ltcGxpZmljYXIuIEVzdG8gZXMKbG8gcXVlIHF1ZXJlbW9zIGhhY2VyLCBwYXNvIHBvciBwYXNvCgpgYGB7cn0KaGVhZCh0b2JhY2NvKQptZWFuKHRvYmFjY28kdGF4W3RvYmFjY28keWVhciA9PSAxOTg1XSkKbWVhbih0b2JhY2NvJHRheFt0b2JhY2NvJHllYXIgPT0gMTk4Nl0pCm1lYW4odG9iYWNjbyR0YXhbdG9iYWNjbyR5ZWFyID09IDE5ODddKQpgYGAKCkFob3JhIHF1ZSBzYWJlbW9zIGhhY2VyIGJ1Y2xlcywgcG9kZW1vcyBwZW5zYXIgZW4gbGEgc2lndWllbnRlIGZvcm1hIGRlCmFwcm94aW1hciBlbCBwcm9ibGVtYQoKYGBge3J9CmZvciAoaSBpbiAxOTg1OjE5OTApIHsKICAgIHByaW50KG1lYW4odG9iYWNjbyR0YXhbdG9iYWNjbyR5ZWFyID09IGldKSkKfQpgYGAKCnkgcG9kcsOtYW1vcyBhw7FhZGlyIG3DoXMgaW5mb3JtYWNpw7NuIHBhcmEgc2FiZXIgYSBxdcOpIGHDsW8gc2UgY29ycmVzcG9uZGUgY2FkYSBtZWRpYQpgYGB7cn0KZm9yIChpIGluIDE5ODU6MTk5MCkgewogICAgcHJpbnQoY2JpbmQoaSwgbWVhbih0b2JhY2NvJHRheFt0b2JhY2NvJHllYXIgPT0gaV0pKSkKfQpgYGAKCmUgaW5jbHVzbyBhbG1hY2VuYXIgbGEgaW5mb3JtYWNpw7NuCmBgYHtyfQpvdXRwdXQgPC0gZGF0YS5mcmFtZSgieWVhciI9dW5pcXVlKHRvYmFjY28keWVhciksICJhdmUiPU5BKQpmb3IgKGkgaW4gMTpucm93KG91dHB1dCkpIHsKICAgIG91dHB1dCRhdmVbaV0gPC0gIG1lYW4odG9iYWNjbyR0YXhbdG9iYWNjbyR5ZWFyID09IG91dHB1dCR5ZWFyW2ldXSkKfQpgYGAKCkF1bnF1ZSBlbCBwcm9jZXNvIGVzIGludHVpdGl2bywgbmVjZXNpdGFtb3MgcG9uZXIgY2llcnRvIGVzZnVlcnpvIHBhcmEKaW50ZXJwcmV0YXIgZWwgY8OzZGlnby4gVW5hIGZvcm1hIGRpZmVyZW50ZSBkZSBhdGFjYXIgZWwgcHJvYmxlbWEgZXMgc2llbmRvIG3DoXMKZGlyZWN0by4gRW4gbHVnYXIgZGUgaGFjZXIgdW4gYnVjbGUsIGluZGljYXJlbW9zIGEgYFJgIGPDs21vIGhhY2VyIGxhIHBhcnRpY2nDs24uIAoKYGBge3J9CnNwbGl0X3RvYmFjY28gPC0gc3BsaXQodG9iYWNjbyR0YXgsIHRvYmFjY28keWVhcikKaGVhZChzcGxpdF90b2JhY2NvKQpgYGAKCnkgYSBjb250aW51YWNpw7NuIGFwbGljYXJlbW9zIHVuYSBtZWRpYSBzb2JyZSBjYWRhIHVubyBkZSBsb3MgZWxlbWVudG9zLiAKCmBgYHtyfQptZWFuX3RvYmFjY28gPC0gbGFwcGx5KHNwbGl0X3RvYmFjY28sIG1lYW4pCmhlYWQobWVhbl90b2JhY2NvKQpgYGAKCnkgcG9yIMO6bHRpbW8ganVudGFtb3MgbG9zIGVsZW1lbnRvcyBkZSBudWV2byB1c2FuZG8gbGEgZnVuY2nDs24gYGRvLmNhbGxgIHF1ZSBub3MKcGlkZSBxdWUgaW5kaXF1ZW1vcyBfY8OzbW9fIGhlbW9zIGRlIHJlYWxpemFyIGVsIHBlZ2FkbzoKCmBgYHtyfQpkby5jYWxsKHJiaW5kLCBtZWFuX3RvYmFjY28pCmBgYAoKRXN0YSBlcyBsYSBlc2VuY2lhIGRlIGxhIGVzdHJhdGVnaWEgZGUgZGl2aWRpciAodXNhbmRvIGBzcGxpdGApLCBhcGxpY2FyICh1c2FuZG8KYGxhcHBseWApIHkgY29tYmluYXIgKHVzYW5kbyBgZG8uY2FsbGApLgoKUG9kZW1vcyBjb25zZWd1aXIgbG8gbWlzbW8gZGUgb3Ryb3MgZG9zIG1vZG9zIHVzYW5kbyBkb3MgcGFxdWV0ZXMgbXV5IHBvcHVsYXJlcwplbiBgUmAuIFVuYSBvcGNpw7NuIHNlcsOtYSB1c2FyIGVsIHBhcXVldGUgYHBseXJgIHF1ZSBvZnJlY2UgdW5hIGludGVyZmF6IGNvbcO6biwKY2xhcmEgZSBpbnR1aXRpdmEgcGFyYSBvcGVyYWNpb25lcyBxdWUgdGllbmVuIHRvZGFzIGxhIG1pc21hIG5hdHVyYWxlemEuIAogCkVsIHVzbyBlcyBtdXkgc2ltaWxhci4gRWwgcHJpbWVyIGFyZ3VtZW50byBpbmRpY2EgbG9zIGRhdG9zIHF1ZSBxdWVyZW1vcwpwYXJ0aWNpb25hciwgZWwgc2VndW5kbyBsYSB2YXJpYWJsZSBxdWUgdXNhcmVtb3MgcGFyYSBwYXJ0aWNpb25hciAoYXVucXVlIHF1aXrDoXMKbm8gZXMgbmVjZXNhcmlvLCBjb21vIHZlcmVtb3MgZW5zZWd1aWRhKSB5IGVsIHRlcmNlcm8gbGEgb3BlcmFjacOzbiBxdWUgcXVlcmVtb3MKcmVhbGl6YXIuIFVuIHBhciBkZSBlamVtcGxvcyBzZXLDoW4gZGUgdXRpbGlkYWQuIEVtcGV6YXJlbW9zIHBvciBpbnN0YWxhciBlbApwYXF1ZXRlIGBwbHlyYCBxdWUgbm8gdmllbmUgcG9yIGRlZmVjdG86IAoKYGBge3J9Cmluc3RhbGwucGFja2FnZXMoInBseXIiKTsgbGlicmFyeShwbHlyKQpgYGAKCnkgYWhvcmEKYGBge3J9Cmdyb3VwX21lYW5zIDwtIGRkcGx5KHRvYmFjY28sIC4oeWVhciksIGZ1bmN0aW9uKHgpIG1lYW4oeCR0YXgpKQpoZWFkKGdyb3VwX21lYW5zKQpgYGAKCkVsIG5vbWJyZSBkZSBsYSBmdW5jacOzbiBgZGRwbHlgIG5vcyBkYSBtdWNoYSBpbmZvcm1hY2nDs24gc29icmUgcXXDqSBlcyBsbyBxdWUKZXN0w6Egb2N1cnJpZW5kby4gTGEgcHJpbWVyYSBsZXRyYSBgZGAgbm9zIGRpY2UgcXVlIGVsIGlucHV0IHNlcsOhIHVuCmBkYXRhLmZyYW1lYCwgbGEgc2VndW5kYSBsZXRyYSBgZGAgcXVlIGVsIG91dHB1dCBlcyBvdHJvIGBkYXRhLmZyYW1lYC4gVG9kYXMgbGFzCmZ1bmNpb25lcyBlbiBgcGx5cmAgdGllbmVuIGxhIG1pc21hIGVzdHJ1Y3R1cmEuIFBvciBlamVtcGxvLCBwb2Ryw61hbW9zIGNhbGN1bGFyCmxhIG1lZGlhIHBvciBncnVwb3MgeSBkZXZvbHZlciBlbCByZXN1bHRhZG8gY29tbyB1bmEgbGlzdGEgY2FtYmlhbmRvIGxhIHNlZ3VuZGEKbGV0cmEgZGUgYGRgIGEgYGxgLgoKYGBge3J9CmhlYWQoZGxwbHkodG9iYWNjbywgLih5ZWFyKSwgZnVuY3Rpb24oeCkgbWVhbih4JHRheCkpKQpgYGAKCm8gcG9kcsOtYW1vcyBoYWJlciBoZWNobyBsb3MgY8OhbGN1bG9zIHVzYW5kbyBsYSBsaXN0YSBxdWUgY3JlYW1vcyBtw6FzIGFycmliYQpjdWFuZG8gZGVtb3N0csOhYmFtb3MgYHNwbGl0YC4gRW4gZXN0ZSBjYXNvLCBlbCBpbnB1dCBlcyB1bmEgbGlzdGEsIHBvciBsbyBxdWUgbGEKcHJpbWVyYSBsZXRyYSBkZSBsYSBmdW5jacOzbiBzZXLDoSBgbGAgeSBubyBuZWNlc2l0YW1vcyBlc3BlY2lmaWNhciBsYSB2YXJpYWJsZQpkZSBhZ3J1cGFjacOzbiB5YSBxdWUgbG9zIGRhdG9zIHlhIGVzdMOhbiBhZ3J1cGFkb3MgKGxvIGN1YWwgZGViZXLDrWEgYXl1ZGFybm9zIGEKZW50ZW5kZXIgcXVlIGBwbHlyYCwgaWd1YWwgcXVlIGVuIGVsIHByb2Nlc28gbWFudWFsLCB1c2EgdW5hIGxpc3RhIGNvbW8gcGl2b3RlKToKCmBgYHtyfQpsZHBseShzcGxpdF90b2JhY2NvLCBtZWFuKQpgYGAKCkVsIHRlcmNlciBtw6l0b2RvIGVzIHNpbWlsYXIgYSBgcGx5cmAgcGVybyBlcyB1biBjb25qdW50byBkZSBmdW5jaW9uZXMgcXVlIGVzdMOhbgplc3BlY2lhbGl6YWRhcyBlbiBgZGRwbHlgLCBlc3RvIGVzLCBmdW5jaW9uZXMgcXVlIHRvbWFuIHkgZGV2dWVsdmVuIHVuCmBkYXRhLmZyYW1lYC4gTGEgc2ludGF4aXMgZXMgdW4gcG9jbyBkaWZlcmVudGUgYSBsbyBxdWUgaGVtb3MgdmlzdG8gaGFzdGEgYWhvcmEKcGVybyBlc3TDoSBjb252aXJ0acOpbmRvc2UgZW4gbXV5IHBvcHVsYXIsIGFzw60gcXVlIGVzIGltcG9ydGFudGUgbm8gcGVyZGVyIGVzdGUKcGFxdWV0ZSBkZSB2aXN0YS4gRW1wZXphcmVtb3MgcG9yIGluc3RhbGFyIHkgY2FyZ2FyIGVsIHBhcXVldGU6IAoKYGBge3J9Cmluc3RhbGwucGFja2FnZXMoImRwbHlyIik7IGxpYnJhcnkoZHBseXIpCmBgYAoKRWwgcGFxdWV0ZSBmdW5jaW9uYSBtZWRpYW50ZSBfdmVyYm9zXyBlbmNhZGVuYWRvcyBjb24gb3BlcmFkb3JlcyAoYCU+JWApIHF1ZSBjb25jYXRlbmFuCm9wZXJhY2lvbmVzIHkgdGllbmVuIHVuYSBlbm9ybWUgc2ltaWxpdHVkIGEgU1FMLiBQb3IgZWplbXBsbywgbG8gcXVlIHF1ZXJlbW9zCmhhY2VyIGVzIF9hZ3J1cGFyXyBsb3MgZGF0b3MgcG9yIGB5ZWFyYCB5IGRlc3B1w6lzIF9yZXN1bWlybG9zXyBwYXJhIGxhIHZhcmlhYmxlCmB0YXhgLiBBcXXDrSBlbmZhdGl6byBfcmVzdW1pcl8gcG9yIG9wb3NpY2nDs24gYSBfbXV0YXJfIGVuIGVsIHNlbnRpZG8gZGUgcXVlIGVsCnJlc3VsdGFkbyBlcyB1biBfcmVzdW1lbl8geSBubyB1bmEgdHJhbnNmb3JtYWNpw7NuIGRlIGxhIGNvbHVtbmE6IGNhZGEgZ3J1cG8gZGUKb2JzZXJ2YWNpb25lcyBzZXLDoSByZXN1bWlkbyBlbiB1biDDum5pY28gdmFsb3I6IGxhIG1lZGlhLiAKCmBgYHtyfQp0b2JhY2NvICU+JQogICAgZ3JvdXBfYnkoeWVhcikgJT4lCiAgICBzdW1tYXJpemUoYXZlPW1lYW4odGF4KSkKYGBgCgrCv1F1w6kgcGFzYXLDrWEgc2kgY2FtYmlhc2Vtb3MgZWwgdmVyYm8gYHN1bW1hcml6ZWAgcG9yIGVsIHZlcmJvIG11dGFyPwoKYGBge3J9Cm10b2JhY2NvIDwtIHRvYmFjY28gJT4lCiAgICBncm91cF9ieSh5ZWFyKSAlPiUKICAgIG11dGF0ZShhdmU9bWVhbih0YXgpKQpoZWFkKG10b2JhY2NvKQpgYGAKCkVuIGVzdGUgY2FzbywgbG8gcXVlIGVzdGFtb3MgcGlkaWVuZG8gZXMgdW5hIF9tdXRhY2nDs25fIGRlIGxhIGJhc2UgZGUgZGF0b3MKb3JpZ2luYWwsIGRlIHRhbCBmb3JtYSBxdWUgY2FkYSBlbnRyYWRhIHRpZW5lIGFzb2NpYWRhIGxhIG1lZGlhIGRlIGxhIHZhcmlhYmxlCmB0YXhgIHBhcmEgc3UgYcOxbyBjb3JyZXNwb25kaWVudGUuIAoKVW5hIGNvc2EgYSBlbmZhdGl6YXIgZXMgbGEgZ2VuZXJhbGlkYWQgZGUgZXN0ZSB0aXBvIGRlIGFwcm94aW1hY2nDs24uIE11Y2hhcwpvcGVyYWNpb25lcywgbm8gc29sbyBlbCBjw6FsY3VsbyBkZSBtZWRpZGFzIHJlc3VtZW4sIHNlIHB1ZWRlbiBjb25jZWJpciBjb21vCnBhcnRlIGRlIGBzcGxpdC1hcHBseS1jb21iaW5lYC4gUGVuc2Vtb3MsIHBvciBlamVtcGxvLCBlbiBjw7NtbyBlbCBjb25zdW1vIGRlCnRhYmFjbyBjYW1iacOhIGVuIHJlbGFjacOzbiBhIGxvcyBpbXB1ZXN0b3MgcGFyYSBjYWRhIGHDsW8uIFVuYSBhcHJveGltYWNpw7NuLCBubyBsYQptZWpvciwgc2Vyw61hIGhhY2VyIHVuYSByZWdyZXNpw7NuIHNlcGFyYWRhIGVudHJlIGNvbnN1bW8gZSBpbXB1ZXN0b3MgcGFyYSBjYWRhCmHDsW8uIFBhcmEgdmVyIGVsIGVmZWN0bywgc29sbyBxdWVyZW1vcyB2ZXIgbG9zIGNvZWZpY2llbnRlczogCgpgYGB7cn0KbG1fbW9kZWxzIDwtIGRscGx5KHRvYmFjY28sIH4geWVhciwgZnVuY3Rpb24oeCkgbG0ocGFja3BjIH4gbG9nKHRheCksIGRhdGE9eCkpCmxtX2NvZWZzIDwtIGxkcGx5KGxtX21vZGVscywgY29lZmZpY2llbnRzKQpsbV9jb2VmcwpgYGAKCkhheSBkb3MgY29zYXMgYSB0ZW5lciBlbiBjdWVudGEgYXF1w60uIEVuIHByaW1lciBsdWdhciBxdWUgZXN0YW1vcyB0cmFuc2Zvcm1hbmRvCnVuIGBkYXRhLmZyYW1lYCBlbiB1bmEgbGlzdGEgKGBkbHBseWApIHkgZGVzcHXDqXMgbGEgbGlzdGEgcmVzdWx0YW50ZSBlbiBvdHJvCmBkYXRhLmZyYW1lYCAoYGxkcGx5YCkuIFZlcmlmaWNhIHF1ZSBlbnRpZW5kZXMgcG9yIHF1w6kgaGFjZW1vcyBlc3RvLiBMYSBvdHJhCmNvc2EgYSB0ZW5lciBlbiBjdWVudGEgZXMgcXVlIGhlbW9zIGdlbmVyYWRvIHVuYSBmdW5jacOzbiBhbsOzbmltYSBwYXJhIGNhbGN1bGFyCmNhZGEgcmVncmVzacOzbi4gRWwgYXJndW1lbnRvIHF1ZSBuZWNlc2l0YW1vcyBwYXJhIGVzcGVjaWZpY2FyIHF1w6kgaGFjZXIgZW4gY2FkYQpncnVwbyBlcyB1bmEgZnVuY2nDs24gcXVlIHRvbWEgdW4gw7puaWNvIHBhcsOhbWV0cm8sIGVuIGVzdGUgY2FzbyBsYSBiYXNlIGRlIGRhdG9zCmNvcnJlc3BvZGllbnRlIGEgZXNlIGHDsW8uIENvbW8gZXMgdW5hIGZ1bmNpw7NuIGNvbmNyZXRhIHF1ZSBzb2xvIG5lY2VzaXRhbW9zIHBhcmEKdW5hIMO6bmljYSB0YXJlYSwgbm8gbm9zIG1vbGVzdGFtb3Mgbmkgc2lxdWllcmEgZW4gZGFybGUgdW4gbm9tYnJlLgoKIyMjIEZ1c2lvbmFyIGRvcyBiYXNlcyBkZSBkYXRvcwoKUG9yIGFob3JhIGhlbW9zIHRyYWJhamFkbyBjb24gdW4gw7puaWNvIGBkYXRhLmZyYW1lYCBxdWUgdml2ZSBlbiBtZW1vcmlhLiBTaW4KZW1iYXJnbywgZW4gbXVjaGFzIG9jYXNpb25lcyAoYmllbiBwb3IgcXVlIGxvcyBkYXRvcyB2aWVuZW4gZW4gZXN0cnVjdHVyYXMKZGlmZXJlbnRlcyBvIHBvciBxdWUgZ2VuZXJhbW9zIGJhc2VzIHNlcGFyYWRhcyBlbiBlbCB0cmFuc2N1cnNvIGRlIG51ZXN0cmFzCm9wZXJhY2lvbmVzKSBuZWNlc2l0YXJlbW9zIF9mdXNpb25hcl8gYmFzZXMgZGUgZGF0b3MgcXVlIGNvbnRpZW5lbiB1bmEgbGxhdmUKY29tw7puLiBQZW5zZW1vcyBlbiB1biBlamVtcGxvIHRyaXZpYWwgZW4gZWwgcXVlIHF1ZXJlbW9zIHRvbWFyIGxvcyBjb2VmaWNpZW50ZXMKcXVlIGFjYWJhbW9zIGRlIGVzdGltYXIgeSBsb3MgcXVlcmVtb3MganVudGFyIGRlIG51ZXZvIGVuIG51ZXN0cmEgYmFzZSBkZSBkYXRvcwpvcmlnaW5hbC4gCgpgYGB7cn0KbWVyZ2VkX3RvYmFjY28gPC0gbWVyZ2UodG9iYWNjbywgbG1fY29lZnMsIGJ5PSJ5ZWFyIikKaGVhZChtZXJnZWRfdG9iYWNjbykKYGBgCgpMYSBmdW5jacOzbiBgbWVyZ2VgIHRvbWEgb3Ryb3MgYXJndW1lbnRvcyBxdWUgbm9zIHBlcm1pdGVuIGVzcGVjaWZpY2FyIGRpZmVyZW50ZXMKY2xhc2VzIGRlIGBqb2luYCwgcXXDqSBoYWNlciBjb24gbGFzIHVuaWRhZGVzIHF1ZSBubyBleGlzdGVuIGVuIGxhcyBkb3MgYmFzZXMgZGUKZGF0b3MgKMK/bGFzIGluY2x1aW1vcyBvIGxhcyBkZWphbW9zPyksIG8gcXXDqSBoYWNlciBjb24gdmFyaWFibGVzIHF1ZSBleGlzdGVuIGVuCmxhcyBkb3MgYmFzZXMgZGUgZGF0b3MuIAoKIyMjIFJlc3RydWN0dXJhciB1biBiYXNlIGRlIGRhdG9zCgpVbmEgb3BlcmFjacOzbiBxdWUgaGFyZW1vcyBtdWNobyBhIGxvIGxhcmdvIGRlIGxvcyBkb3Mgc2lndWllbnRlcyBkw61hcyBzZXLDoQpyZXN0cnVjdHVyYXIgZWwgZm9ybWF0byBkZSBudWVzdHJhIGJhc2UgZGUgZGF0b3MuIFBvciBlamVtcGxvLCBhaG9yYSBudWVzdHJhCmJhc2UgZGUgZGF0b3Mgc29icmUgY29uc3VtbyBkZSB0YWJhY28gZXN0w6EgZXN0cnVjdHVyYWRhIHBvciBwYXJlcyBlc3RhZG8tYcOxby4gCgpgYGB7cn0KaGVhZCh0b2JhY2NvKQpgYGAKCkNhZGEgZmlsYSByZXByZXNlbnRhIHVuIGHDsW8gZW4gdW4gZGV0ZXJtaW5hZG8gZXN0YWRvLiBTaW4gZW1iYXJnbywgcG9kZW1vcwpxdWVyZXIgY2FtYmlhciBlbCBmb3JtYXRvIGRlIHRhbCBmb3JtYSBxdWUgY2FkYSBmaWxhIHJlcHJlc2VudGUgZGF0b3MgcGFyYSB1bgpkZXRlcm1pbmFkbyBhw7FvLiBQb3Igc3VwdWVzdG8sIHkgY29tbyBzaWVtcHJlLCBleGlzdGVuIG1vZG9zIGRlIGhhY2VyIGVzdG8KdXNhbmRvIGxhcyBsaWJyZXLDrWFzIGJhc2UgZW4gYFJgLCBwZXJvIGNyZW8gcXVlIGVsIHBhcXVldGUgYHJlc2hhcGUyYCBub3Mgb2ZyZWNlCnVuYSB2w61hIG3DoXMgZGlyZWN0YSBlIGludHVpdGl2YS4gTGFzIG9wZXJhY2lvbmVzIGFxdcOtIHNlIGRpdmlkZW4gZW4gZG9zCmZ1bmNpb25lczogdW5hIG9wZXJhY2nDs24gX2RlcnJpdGVfIChfbWVsdHNfKSBsYSBiYXNlIGRlIGRhdG9zIGRlIGFjdWVyZG8gY29uCmFsZ3VuYXMgdmFyaWFibGVzIHF1ZSBoYWdhbiBkZSDDrW5kaWNlLiBMYSBzZWd1bmRhIF9mdW5kZV8gKF9jYXN0c18pIGVuIGxhCmVzdHJ1Y3R1cmEgcXVlIG5vc290cm9zIHF1ZXJhbW9zLiBQb3IgZWplbXBsbywgcGFyYSBlbCBwcm9ibGVtYSBhbnRlcmlvciBwb2RlbW9zIGhhY2VyOgoKYGBge3J9CmxpYnJhcnkocmVzaGFwZTIpCm1vbHRlbl90b2JhY2NvIDwtIG1lbHQodG9iYWNjb1ssIGMoInN0YXRlIiwgInllYXIiLCAidGF4IildLCBpZD1jKCJzdGF0ZSIsICJ5ZWFyIikpCmhlYWQobW9sdGVuX3RvYmFjY28pCnRhaWwobW9sdGVuX3RvYmFjY28pCmhlYWQoZGNhc3QobW9sdGVuX3RvYmFjY28sIHN0YXRlIH4gdmFyaWFibGUgKyB5ZWFyKSkKYGBgCgpWYXlhbW9zIHBhc28gcG9yIHBhc28gZW4gbG8gcXVlIGhhIHBhc2FkbyBhbnRlcy4gRW4gcHJpbWVyIGx1Z2FyLCBoZW1vcyB0b21hZG8Kw7puaWNhbWVudGUgdHJlcyB2YXJpYWJsZXMgZGUgbGEgYmFzZSBkZSBkYXRvcyB5IGhlbW9zIHBlZGlkbyBhIGBSYCBxdWUgbGEKdHJhbnNmb3JtZSBkZSB0YWwgbWFuZXJhIHF1ZSBgc3RhdGVgIGUgYHllYXJgIHBlcm1hbmV6Y2FuIGNvbW8gw61uZGljZXMgZW4gbGFzCmZpbGFzLiBFbiBlc3RlIGNhc28sIGxvIMO6bmljbyBxdWUgZXN0YW1vcyBoYWNpZW5kbyBlcyBhw7FhZGlyIGRvcyBudWV2YXMKdmFyaWFibGVzIGB2YXJpYWJsZWAgeSBgdmFsdWVgIHF1ZSBjYXB0dXJhbiBsYXMgdmFyaWFibGVzIHkgdmFsb3JlcyBxdWUgbm8gc29uCmBpZGAuIFNpIGhhY2Vtb3MgYG1lbHRgIHNpbiBzZWxlY2Npb25hciB2YXJpYWJsZXMgbG8gcG9kZW1vcyB2ZXIgY29uIG3DoXMKY2xhcmlkYWQKCmBgYHtyfQpoZWFkKG1lbHQodG9iYWNjbywgaWQ9Yygic3RhdGUiLCAieWVhciIpKSkKdGFpbChtZWx0KHRvYmFjY28sIGlkPWMoInN0YXRlIiwgInllYXIiKSkpCmBgYAoKQXF1w60gdmVtb3MgY29uIGNsYXJpZGFkIHF1ZSB0b2RhcyBsYXMgdmFyaWFibGVzIGhhbiBzaWRvIHRyYW5zZm9ybWFkYXMgcGFyYSBxdWUKYXBhcmV6Y2FuIGVuIHVuYSDDum5pY2EgY29sdW1uYSBjb24gc3Ugbm9tYnJlIHkgc3UgdmFsb3IuIExhIHNpZ3VpZW50ZSBmYXNlIGVzCmBjYXN0YCBxdWUgbm9zIHBlcm1pdGUsIHVzYW5kbyB1bmEgZsOzcm11bGEsIGluZGljYXIgcXXDqSB2YXJpYWJsZXMgdmFuIGEKcGVybWFuZWNlciBlbiBsYXMgZmlsYXMgeSBjdcOhbGVzIGVuIGxhcyBjb2x1bW5hcy4gRW4gZXN0ZSBjYXNvLCBsYSBpenF1aWVyZGEgZGVsCnPDrW1ib2xvIGB+YCByZXByZXNlbnRhIGxhcyBmaWxhcyB5IGxhIGRlcmVjaGEgbGFzIGNvbHVtbmFzLgoKQXPDrSwgcG9yIGVqZW1wbG8sIGVuIGVsIGNhc28gYW50ZXJpb3IgbG8gcXVlIHF1ZXLDrWFtb3MgZXJhIHBpdm90YXIgbGEgdmFyaWFibGUKYHRheGAgYSBsYXMgY29sdW1uYXMgeSBhw7FhZGlyIGEgZXN0YSBlbCB2YWxvciBkZSBhw7FvLCBtaWVudHJhcyBxdWUgZW4gbGFzIGZpbGFzCnBlcm1hbmVjZSBsYSB2YXJpYWJsZSBgc3RhdGVgCgpgYGB7cn0KaGVhZChkY2FzdChtb2x0ZW5fdG9iYWNjbywgc3RhdGUgfiB2YXJpYWJsZSArIHllYXIpKQpgYGAK