Introducción
La idea de esta serie notebooks es poder introducir algunos conceptos básicos del llamado tidyverse
en R. Vamos a tratar de hacernos amigos de algunos algunos de los verbos que vimos hace un rato y que nos van a hacer la vida más fácil en la manipulación de datos.
Objetivos
- Brindar nociones sobre la lógica general del
tidyverse
para el preprocesamiento de datos
- Introducir algunas funciones básicas para el filtrado, trasformación y merge de datos
- Presentar herramientas para la visualización de datos
En el notebook anterior, introdujimos algunos varios aspectos:
- Empezamos explorar una herramienta para visualización de datos:
ggplot2()
- Mencionamos y utilizamos los cinco verbos de
dplyr
que más son utilizados en el preprocesmaiento de datos: filter()
, mutate()
, group_by()
, summarize()
y arrange()
- Realizamos algunas tareas de preprocesamiento.
La idea es profundizar en estos problemas.
library(tidyverse)
delitos <- read.csv("../data/delitos.csv")
delitos <- delitos %>%
filter(latitud!=0, longitud!=0)
str(delitos)
'data.frame': 237445 obs. of 14 variables:
$ id : int 68400 68401 68402 68492 132437 132469 132480 132566 132583 134576 ...
$ comuna : Factor w/ 15 levels "Comuna 1","Comuna 10",..: 9 13 10 5 15 3 3 13 14 3 ...
$ barrio : Factor w/ 48 levels "AGRONOMIA","ALMAGRO",..: 3 12 20 5 15 46 46 12 40 39 ...
$ latitud : num -34.6 -34.7 -34.6 -34.6 -34.7 ...
$ longitud : num -58.4 -58.5 -58.4 -58.5 -58.5 ...
$ fecha : Factor w/ 731 levels "2016-01-01","2016-01-02",..: 305 305 305 305 305 305 305 305 305 305 ...
$ hora : Factor w/ 1417 levels "00:00:00","00:01:00",..: 61 147 235 174 1238 458 1213 1088 1148 1358 ...
$ uso_arma : Factor w/ 1 level "SIN USO DE ARMA": 1 1 1 1 1 1 1 1 1 1 ...
$ uso_moto : Factor w/ 1 level "SIN MOTO": 1 1 1 1 1 1 1 1 1 1 ...
$ lugar : logi NA NA NA NA NA NA ...
$ origen_dato : logi NA NA NA NA NA NA ...
$ tipo_delito : Factor w/ 7 levels "Homicidio Doloso",..: 1 1 1 2 3 3 6 3 6 3 ...
$ cantidad_vehiculos: int 0 0 0 0 0 0 0 0 0 0 ...
$ cantidad_victimas : int 0 0 0 1 0 0 0 0 0 0 ...
library(lubridate)
delitos <- delitos %>%
mutate(fecha=ymd(fecha))
Trabajando con fechas
Ya tenemos nuestro dataset listo y consistido. En este aparado, la idea es aplicar la potencia que tiene la librería lubridate
para manipular datos de fechas.
Veamos algunas de las tareas que podemos encarar. Tomemos cinco fechas elegidas al azar:
set.seed("99")
muestra_de_fechas <- delitos %>%
sample_n(5) %>%
select(fecha)
muestra_de_fechas
Veamos otra forma de obtener el mismo resultado:
set.seed("99")
muestra_de_fechas <- delitos %>%
sample_n(5) %>%
pull(fecha)
muestra_de_fechas
[1] "2017-11-01" "2016-12-10" "2017-02-17" "2017-09-06" "2017-10-03"
¿En qué se diferencian ambas?
Tomando como input este vector podemos
- Extraer el día de la semana que corresponde a cada fecha:
wday(muestra_de_fechas)
[1] 4 7 6 4 3
wday(muestra_de_fechas, label=TRUE)
[1] mié\\. sáb\\. vie\\. mié\\. mar\\.
7 Levels: dom\\. < lun\\. < mar\\. < mié\\. < jue\\. < ... < sáb\\.
month(muestra_de_fechas)
[1] 11 12 2 9 10
year(muestra_de_fechas)
[1] 2017 2016 2017 2017 2017
Operaciones parecidas podríamos hacer con variables de hora. Pueden consultar la documentación al respecto.
Veamos, ahora, la distribución mensual de delitos:
delitos %>%
select(fecha) %>%
ggplot() +
geom_bar(aes(x = month(fecha, label = TRUE)))

Podemos obtener también un gráfico de barras apiladas, para evaluar cuánto pesa cada tipo de delitp en el total:
delitos %>%
ggplot() +
geom_bar(aes(x = month(fecha, label = TRUE), fill = tipo_delito))

Y uno, 100% apilado:
delitos %>%
ggplot() +
geom_bar(aes(x = month(fecha, label = TRUE), fill = tipo_delito),
position = "fill")

O de barras sin apilar:
delitos %>%
ggplot() +
geom_bar(aes(x = month(fecha, label = TRUE), fill = tipo_delito),
position = "dodge")

El argumento position
es el que determina si creamos un gráfico apilado, 100% apilado o sin apilar.
Consignas
En todos los casos, realice el gráfico que considere más relevante para responder a la pregunta
- ¿En qué horarios del día hay más delitos habitualmente?
###
- ¿Cuál es el tipo de delito más habitual al mediodía?
###
- ¿Puede notarse alguna diferencia en la distribución horaria del total de delitos entre las comunas?
###
- Genere un gráfico de barras 100% apilado de la distribución de delitos por día de la semana (etiquetada), pero solamente correspondiente a los registros del año 2017:
###
- Seleccione el barrio con mayor cantidad de delitos en cada comuna -no es necesario hacer un gráfico-
###
Imporante…
Aquí hay un punto importante a tener en cuenta en el uso de los group_by
: el orden en el que pasamos las variables importa. En el ejemplo, anterior buscamos dentro cada comuna, el barrio con mayor conteo. Eso se ve en el group_by(comuna, barrio)
.
Ahora bien, supongamos que por error escribimos lo siguiente:
delitos %>%
group_by(barrio, comuna) %>%
summarise(tot=n()) %>%
filter(tot==max(tot)) %>%
arrange(comuna)
La salida ya no se parece a la anterior. Para empezar, tiene 48 filas… Lo que pasó aquí es que buscamos al interior de cada barrio, la comuna con mayor n de delitos, cosa que no tiene demasiado sentido.
Generando mapas buenos, bellos y bonitos
Ahora bien, hasta aquí hemos explorado la dimensión “tiempo” de nuestro dataset. Pero cómo habíamos mencionado en el notebook anterior, también contamos con una dimesión espacial, dado que tenemos los puntos georreferenciados.
Es por ello que vamos a mostrar como realizar algunos mapas interesantes, para lo cual, vamos a uilizar la librería ggmap
, que sigue buena parte de las convenciones y lógica de ggplot
.
library(ggmap)
Google's Terms of Service: https://cloud.google.com/maps-platform/terms/.
Please cite ggmap if you use it! See citation("ggmap") for details.
Una de las ventajas de ggmap
es que podemos generar un mapa base para que nuestros “puntitos” no se vean tan desprovistos, La manera más simple de hacerlo es definir una bounding box que va a constituir una especie de “caja” que deliminan las coordenadas de nuestro mapa base:
bbox <- c(min(delitos$longitud, na.rm = TRUE),
min(delitos$latitud, na.rm = TRUE),
max(delitos$longitud, na.rm = TRUE),
max(delitos$latitud, na.rm = TRUE))
CABA <- get_stamenmap(bbox = bbox,
maptype = "terrain-background")
Source : http://tile.stamen.com/terrain-background/10/345/616.png
Source : http://tile.stamen.com/terrain-background/10/346/616.png
Source : http://tile.stamen.com/terrain-background/10/345/617.png
Source : http://tile.stamen.com/terrain-background/10/346/617.png
Veamos cómo queda:
ggmap(CABA)

Podemos haber usado otro maptype
, el “toner-lite” es útil para visualizaciones por su contraste:
CABA <- get_stamenmap(bbox = bbox,
maptype = "toner-lite")
Source : http://tile.stamen.com/toner-lite/10/345/616.png
Source : http://tile.stamen.com/toner-lite/10/346/616.png
Source : http://tile.stamen.com/toner-lite/10/345/617.png
Source : http://tile.stamen.com/toner-lite/10/346/617.png
ggmap(CABA)

Mapeando datos…
Retomemos nuestro scatter de puntos: pero ahora compliquémosla desde el principio. Hagamos un mapa de todos los delitos, diferenciando por color el tipo de delito:
ggmap(CABA) +
geom_point(data = delitos, aes(x = longitud, y = latitud, color=tipo_delito),
size = 0.1, alpha = 0.1)

Está bonito, pero vemos dos problemas:
- La leyenda es difícil de leer
- La escala de colores no es la mejor, en tanto y en cuanto, no permite discernir claramente diferencias por categoría
El primer problema lo solucionamos fijando a mano los valores de la estética de la leyenda:
ggmap(CABA) +
geom_point(data = delitos,
aes(x = longitud, y = latitud, color = tipo_delito),
size = 0.1, alpha = 0.1) +
guides(color = guide_legend(override.aes = list(size=2, alpha = 1)))

El segundo… ya lo vimos… facetando
ggmap(CABA) +
geom_point(data = delitos,
aes(x = longitud, y = latitud, color = tipo_delito),
size = 0.2, alpha = 0.1) +
facet_wrap(~tipo_delito) +
guides(color = guide_legend(override.aes = list(size=2, alpha = 1))) +
theme(strip.text.x = element_text(size=7.5))

Ahí la cosa está un poco más clara. No obstante hay varias formas de hacer más observables estos patrones. Solo vamos a mostrar una, como para dar la intuición. Vamos a detectar las zonas de mayor concentración de delitos, por tipo de delito. Para ello, vamos a usar una técnica con el estrambótico nombre de two dimentional kernel density estimation.
ggmap(CABA) +
geom_density2d(data = delitos, aes(x = longitud, y = latitud, color = stat(level))) +
scale_color_viridis_c() +
facet_wrap(~tipo_delito) +
theme(strip.text.x = element_text(size=7.5))

Aquí se ve de forma más nítida que la distribución espacial de cada tipo de delito es bien diferenciada.
Veamos, ahora, la distribución del total de delitos por día y hora de la semana:
delitos <- delitos %>%
mutate(hora_base = hour(hms(hora)))
ggmap(CABA) +
geom_density2d(data = delitos, aes(x = longitud, y = latitud, color = stat(level))) +
scale_color_viridis_c() +
facet_wrap(~hora_base, nrow=4) +
labs(title = "Concentración espacial de delitos",
subtitle = "según hora del día")

NA
delitos <- delitos %>%
mutate(dia=wday(ymd(fecha), label=TRUE))
ggmap(CABA) +
geom_density2d(data = delitos, aes(x = longitud, y = latitud, color = stat(level))) +
scale_color_viridis_c() +
facet_wrap(~dia, ncol=3) +
labs(title = "Concentración espacial de delitos",
subtitle = "según día de la semana")

Consignas
Repetir los últimos mapas, pero generando información solamente sobre los hurtos de automotores.
###
###
LS0tDQp0aXRsZTogIkludHJvZHVjY2lvbiBhbCBgdGlkeXZlcnNlYCBjb24gZGF0b3MgZ2VvZ3JhZmljb3MuIElJIg0KYXV0aG9yOiAiRHIuIEdlcm1hbiBSb3NhdGkiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCmZpZ193aWR0aDogMTUNCmZpZ19oZWlnaHQ6IDIwIA0KLS0tDQoNCg0KYGBge3IgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPVRSVUUsIGhpZ2hsaWdodD1UUlVFLCBlY2hvPVRSVUUpDQpgYGANCg0KIyMgSW50cm9kdWNjacOzbg0KTGEgaWRlYSBkZSBlc3RhIHNlcmllIG5vdGVib29rcyBlcyBwb2RlciBpbnRyb2R1Y2lyIGFsZ3Vub3MgY29uY2VwdG9zIGLDoXNpY29zIGRlbCBsbGFtYWRvIGB0aWR5dmVyc2VgIGVuIFIuIFZhbW9zIGEgdHJhdGFyIGRlIGhhY2Vybm9zIGFtaWdvcyBkZSBhbGd1bm9zIGFsZ3Vub3MgZGUgbG9zIHZlcmJvcyBxdWUgdmltb3MgaGFjZSB1biByYXRvIHkgcXVlIG5vcyB2YW4gYSBoYWNlciBsYSB2aWRhIG3DoXMgZsOhY2lsIGVuIGxhIG1hbmlwdWxhY2nDs24gZGUgZGF0b3MuDQoNCiMjIyBPYmpldGl2b3MNCiogQnJpbmRhciBub2Npb25lcyBzb2JyZSBsYSBsw7NnaWNhIGdlbmVyYWwgZGVsIGB0aWR5dmVyc2VgIHBhcmEgZWwgcHJlcHJvY2VzYW1pZW50byBkZSBkYXRvcw0KKiBJbnRyb2R1Y2lyIGFsZ3VuYXMgZnVuY2lvbmVzIGLDoXNpY2FzIHBhcmEgZWwgZmlsdHJhZG8sIHRyYXNmb3JtYWNpw7NuIHkgbWVyZ2UgZGUgZGF0b3MNCiogUHJlc2VudGFyIGhlcnJhbWllbnRhcyBwYXJhIGxhIHZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zDQoNCkVuIGVsIG5vdGVib29rIGFudGVyaW9yLCBpbnRyb2R1amltb3MgYWxndW5vcyB2YXJpb3MgYXNwZWN0b3M6DQoNCjEuIEVtcGV6YW1vcyBleHBsb3JhciB1bmEgaGVycmFtaWVudGEgcGFyYSB2aXN1YWxpemFjacOzbiBkZSBkYXRvczogYGdncGxvdDIoKWANCjIuIE1lbmNpb25hbW9zIHkgdXRpbGl6YW1vcyBsb3MgY2luY28gdmVyYm9zIGRlIGBkcGx5cmAgcXVlIG3DoXMgc29uIHV0aWxpemFkb3MgZW4gZWwgcHJlcHJvY2VzbWFpZW50byBkZSBkYXRvczogYGZpbHRlcigpYCwgYG11dGF0ZSgpYCwgYGdyb3VwX2J5KClgLCBgc3VtbWFyaXplKClgIHkgYGFycmFuZ2UoKWANCjMuIFJlYWxpemFtb3MgYWxndW5hcyB0YXJlYXMgZGUgcHJlcHJvY2VzYW1pZW50by4NCg0KTGEgaWRlYSBlcyBwcm9mdW5kaXphciBlbiBlc3RvcyBwcm9ibGVtYXMuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KDQoNCmBgYHtyfQ0KZGVsaXRvcyA8LSByZWFkLmNzdigiLi4vZGF0YS9kZWxpdG9zLmNzdiIpDQpkZWxpdG9zIDwtIGRlbGl0b3MgJT4lDQogICAgICAgICAgICAgICAgZmlsdGVyKGxhdGl0dWQhPTAsIGxvbmdpdHVkIT0wKQ0Kc3RyKGRlbGl0b3MpDQpgYGANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkobHVicmlkYXRlKQ0KZGVsaXRvcyA8LSBkZWxpdG9zICU+JSANCiAgICAgICAgICAgICAgICBtdXRhdGUoZmVjaGE9eW1kKGZlY2hhKSkNCmBgYA0KDQoNCg0KIyMgVHJhYmFqYW5kbyBjb24gZmVjaGFzDQoNCllhIHRlbmVtb3MgbnVlc3RybyBkYXRhc2V0IGxpc3RvIHkgY29uc2lzdGlkby4gRW4gZXN0ZSBhcGFyYWRvLCBsYSBpZGVhIGVzIGFwbGljYXIgbGEgcG90ZW5jaWEgcXVlIHRpZW5lIGxhIGxpYnJlcsOtYSBgbHVicmlkYXRlYCBwYXJhIG1hbmlwdWxhciBkYXRvcyBkZSBmZWNoYXMuDQoNClZlYW1vcyBhbGd1bmFzIGRlIGxhcyB0YXJlYXMgcXVlIHBvZGVtb3MgZW5jYXJhci4gVG9tZW1vcyBjaW5jbyBmZWNoYXMgZWxlZ2lkYXMgYWwgYXphcjoNCg0KDQpgYGB7cn0NCnNldC5zZWVkKCI5OSIpDQptdWVzdHJhX2RlX2ZlY2hhcyA8LSBkZWxpdG9zICU+JSANCiAgICBzYW1wbGVfbig1KSAlPiUgDQogICAgc2VsZWN0KGZlY2hhKQ0KDQptdWVzdHJhX2RlX2ZlY2hhcw0KDQpgYGANCg0KDQoNClZlYW1vcyBvdHJhIGZvcm1hIGRlIG9idGVuZXIgZWwgbWlzbW8gcmVzdWx0YWRvOg0KDQpgYGB7cn0NCnNldC5zZWVkKCI5OSIpDQptdWVzdHJhX2RlX2ZlY2hhcyA8LSBkZWxpdG9zICU+JSANCiAgICBzYW1wbGVfbig1KSAlPiUgDQogICAgcHVsbChmZWNoYSkNCg0KbXVlc3RyYV9kZV9mZWNoYXMNCmBgYA0KDQoNCg0KKsK/RW4gcXXDqSBzZSBkaWZlcmVuY2lhbiBhbWJhcz8qDQoNCg0KVG9tYW5kbyBjb21vIGlucHV0IGVzdGUgdmVjdG9yIHBvZGVtb3MgDQoNCiogRXh0cmFlciBlbCBkw61hIGRlIGxhIHNlbWFuYSBxdWUgY29ycmVzcG9uZGUgYSBjYWRhIGZlY2hhOg0KDQpgYGB7cn0NCndkYXkobXVlc3RyYV9kZV9mZWNoYXMpDQpgYGANCg0KYGBge3J9DQp3ZGF5KG11ZXN0cmFfZGVfZmVjaGFzLCBsYWJlbD1UUlVFKQ0KYGBgDQoNCg0KDQoqIEVsIG1lcw0KDQpgYGB7cn0NCm1vbnRoKG11ZXN0cmFfZGVfZmVjaGFzKQ0KYGBgDQoNCg0KDQoqIEVsIGHDsW8NCg0KYGBge3J9DQp5ZWFyKG11ZXN0cmFfZGVfZmVjaGFzKQ0KYGBgDQoNCg0KDQpPcGVyYWNpb25lcyBwYXJlY2lkYXMgcG9kcsOtYW1vcyBoYWNlciBjb24gdmFyaWFibGVzIGRlIGhvcmEuIFB1ZWRlbiBjb25zdWx0YXIgbGEgW2RvY3VtZW50YWNpw7NuXShtb250aChtdWVzdHJhX2RlX2ZlY2hhcykNCikgYWwgcmVzcGVjdG8uDQoNCg0KDQpWZWFtb3MsIGFob3JhLCBsYSBkaXN0cmlidWNpw7NuIG1lbnN1YWwgZGUgZGVsaXRvczoNCg0KYGBge3J9DQpkZWxpdG9zICU+JQ0KICAgICAgICBzZWxlY3QoZmVjaGEpICU+JQ0KICAgICAgICBnZ3Bsb3QoKSArDQogICAgICAgICAgICAgICAgZ2VvbV9iYXIoYWVzKHggPSBtb250aChmZWNoYSwgbGFiZWwgPSBUUlVFKSkpDQpgYGANCg0KDQpQb2RlbW9zIG9idGVuZXIgdGFtYmnDqW4gdW4gZ3LDoWZpY28gZGUgYmFycmFzIGFwaWxhZGFzLCBwYXJhIGV2YWx1YXIgY3XDoW50byBwZXNhIGNhZGEgdGlwbyBkZSBkZWxpdHAgZW4gZWwgdG90YWw6DQoNCmBgYHtyfQ0KZGVsaXRvcyAlPiUgDQogICAgZ2dwbG90KCkgKw0KICAgICAgICBnZW9tX2JhcihhZXMoeCA9IG1vbnRoKGZlY2hhLCBsYWJlbCA9IFRSVUUpLCBmaWxsID0gdGlwb19kZWxpdG8pKQ0KDQpgYGANCg0KWSB1bm8sIDEwMCUgYXBpbGFkbzoNCg0KDQpgYGB7cn0NCmRlbGl0b3MgJT4lIA0KICAgIGdncGxvdCgpICsNCiAgICAgICAgZ2VvbV9iYXIoYWVzKHggPSBtb250aChmZWNoYSwgbGFiZWwgPSBUUlVFKSwgZmlsbCA9IHRpcG9fZGVsaXRvKSwNCiAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSAiZmlsbCIpDQpgYGANCg0KDQpPIGRlIGJhcnJhcyBzaW4gYXBpbGFyOg0KDQpgYGB7cn0NCmRlbGl0b3MgJT4lIA0KICAgIGdncGxvdCgpICsNCiAgICAgICAgZ2VvbV9iYXIoYWVzKHggPSBtb250aChmZWNoYSwgbGFiZWwgPSBUUlVFKSwgZmlsbCA9IHRpcG9fZGVsaXRvKSwNCiAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSAiZG9kZ2UiKQ0KYGBgDQoNCg0KRWwgYXJndW1lbnRvIGBwb3NpdGlvbmAgZXMgZWwgcXVlIGRldGVybWluYSBzaSBjcmVhbW9zIHVuIGdyw6FmaWNvIGFwaWxhZG8sIDEwMCUgYXBpbGFkbyBvIHNpbiBhcGlsYXIuDQoNCg0KDQojIyMgQ29uc2lnbmFzDQoNCkVuIHRvZG9zIGxvcyBjYXNvcywgcmVhbGljZSBlbCBncsOhZmljbyBxdWUgY29uc2lkZXJlIG3DoXMgcmVsZXZhbnRlIHBhcmEgcmVzcG9uZGVyIGEgbGEgcHJlZ3VudGENCg0KMS4gwr9FbiBxdcOpIGhvcmFyaW9zIGRlbCBkw61hIGhheSBtw6FzIGRlbGl0b3MgaGFiaXR1YWxtZW50ZT8gDQoNCmBgYHtyIGVjaG89VFJVRX0NCiMjIw0KYGBgDQoNCjIuIMK/Q3XDoWwgZXMgZWwgdGlwbyBkZSBkZWxpdG8gbcOhcyBoYWJpdHVhbCBhbCBtZWRpb2TDrWE/IA0KDQpgYGB7ciBlY2hvPVRSVUV9DQojIyMNCmBgYA0KDQoNCjMuIMK/UHVlZGUgbm90YXJzZSBhbGd1bmEgZGlmZXJlbmNpYSBlbiBsYSBkaXN0cmlidWNpw7NuIGhvcmFyaWEgZGVsIHRvdGFsIGRlIGRlbGl0b3MgZW50cmUgbGFzIGNvbXVuYXM/DQoNCmBgYHtyIGVjaG89VFJVRX0NCiMjIw0KYGBgDQoNCg0KNC4gR2VuZXJlIHVuIGdyw6FmaWNvIGRlIGJhcnJhcyAxMDAlIGFwaWxhZG8gZGUgbGEgZGlzdHJpYnVjacOzbiBkZSBkZWxpdG9zIHBvciBkw61hIGRlIGxhIHNlbWFuYSAoZXRpcXVldGFkYSksIHBlcm8gc29sYW1lbnRlIGNvcnJlc3BvbmRpZW50ZSBhIGxvcyByZWdpc3Ryb3MgZGVsIGHDsW8gMjAxNzoNCg0KDQpgYGB7cn0NCiMjIw0KYGBgDQoNCg0KDQo1LiBTZWxlY2Npb25lIGVsIGJhcnJpbyBjb24gbWF5b3IgY2FudGlkYWQgZGUgZGVsaXRvcyBlbiBjYWRhIGNvbXVuYSAtbm8gZXMgbmVjZXNhcmlvIGhhY2VyIHVuIGdyw6FmaWNvLQ0KDQpgYGB7cn0NCiMjIw0KYGBgDQoNCg0KIyMjIyBJbXBvcmFudGUuLi4NCg0KQXF1w60gaGF5IHVuIHB1bnRvIGltcG9ydGFudGUgYSB0ZW5lciBlbiBjdWVudGEgZW4gZWwgdXNvIGRlIGxvcyBgZ3JvdXBfYnlgOiBlbCBvcmRlbiBlbiBlbCBxdWUgcGFzYW1vcyBsYXMgdmFyaWFibGVzIGltcG9ydGEuIEVuIGVsIGVqZW1wbG8sIGFudGVyaW9yIGJ1c2NhbW9zIGRlbnRybyBjYWRhIGNvbXVuYSwgZWwgYmFycmlvIGNvbiBtYXlvciBjb250ZW8uIEVzbyBzZSB2ZSBlbiBlbCBgZ3JvdXBfYnkoY29tdW5hLCBiYXJyaW8pYC4NCg0KQWhvcmEgYmllbiwgc3Vwb25nYW1vcyBxdWUgcG9yIGVycm9yIGVzY3JpYmltb3MgbG8gc2lndWllbnRlOg0KDQpgYGB7cn0NCmRlbGl0b3MgJT4lDQogICAgICAgIGdyb3VwX2J5KGJhcnJpbywgY29tdW5hKSAlPiUNCiAgICAgICAgc3VtbWFyaXNlKHRvdD1uKCkpICU+JQ0KICAgICAgICBmaWx0ZXIodG90PT1tYXgodG90KSkgJT4lDQogICAgICAgIGFycmFuZ2UoY29tdW5hKQ0KYGBgDQoNCkxhIHNhbGlkYSB5YSBubyBzZSBwYXJlY2UgYSBsYSBhbnRlcmlvci4gUGFyYSBlbXBlemFyLCB0aWVuZSA0OCBmaWxhcy4uLiBMbyBxdWUgcGFzw7MgYXF1w60gZXMgcXVlIGJ1c2NhbW9zIGFsIGludGVyaW9yIGRlIGNhZGEgYmFycmlvLCBsYSBjb211bmEgY29uIG1heW9yIG4gZGUgZGVsaXRvcywgY29zYSBxdWUgbm8gdGllbmUgZGVtYXNpYWRvIHNlbnRpZG8uDQoNCg0KDQojIyBHZW5lcmFuZG8gbWFwYXMgYnVlbm9zLCBiZWxsb3MgeSBib25pdG9zDQoNCkFob3JhIGJpZW4sIGhhc3RhIGFxdcOtIGhlbW9zIGV4cGxvcmFkbyBsYSBkaW1lbnNpw7NuICJ0aWVtcG8iIGRlIG51ZXN0cm8gZGF0YXNldC4gUGVybyBjw7NtbyBoYWLDrWFtb3MgbWVuY2lvbmFkbyBlbiBlbCBub3RlYm9vayBhbnRlcmlvciwgdGFtYmnDqW4gY29udGFtb3MgY29uIHVuYSBkaW1lc2nDs24gZXNwYWNpYWwsIGRhZG8gcXVlIHRlbmVtb3MgbG9zIHB1bnRvcyBnZW9ycmVmZXJlbmNpYWRvcy4NCg0KRXMgcG9yIGVsbG8gcXVlIHZhbW9zIGEgbW9zdHJhciBjb21vIHJlYWxpemFyIGFsZ3Vub3MgbWFwYXMgaW50ZXJlc2FudGVzLCBwYXJhIGxvIGN1YWwsIHZhbW9zIGEgdWlsaXphciBsYSBsaWJyZXLDrWEgYGdnbWFwYCwgcXVlIHNpZ3VlIGJ1ZW5hIHBhcnRlIGRlIGxhcyBjb252ZW5jaW9uZXMgeSBsw7NnaWNhIGRlIGBnZ3Bsb3RgLg0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dtYXApDQpgYGANCg0KVW5hIGRlIGxhcyB2ZW50YWphcyBkZSBgZ2dtYXBgIGVzIHF1ZSBwb2RlbW9zIGdlbmVyYXIgdW4gbWFwYSBiYXNlIHBhcmEgcXVlIG51ZXN0cm9zICJwdW50aXRvcyIgbm8gc2UgdmVhbiB0YW4gZGVzcHJvdmlzdG9zLCBMYSBtYW5lcmEgbcOhcyBzaW1wbGUgZGUgaGFjZXJsbyBlcyBkZWZpbmlyIHVuYSAqYm91bmRpbmcgYm94KiBxdWUgdmEgYSBjb25zdGl0dWlyIHVuYSBlc3BlY2llIGRlICJjYWphIiBxdWUgZGVsaW1pbmFuIGxhcyBjb29yZGVuYWRhcyBkZSBudWVzdHJvIG1hcGEgYmFzZToNCg0KYGBge3J9DQpiYm94IDwtIGMobWluKGRlbGl0b3MkbG9uZ2l0dWQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgbWluKGRlbGl0b3MkbGF0aXR1ZCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICBtYXgoZGVsaXRvcyRsb25naXR1ZCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICBtYXgoZGVsaXRvcyRsYXRpdHVkLCBuYS5ybSA9IFRSVUUpKQ0KDQpDQUJBIDwtIGdldF9zdGFtZW5tYXAoYmJveCA9IGJib3gsIA0KICAgICAgICAgICAgICAgICAgICAgIG1hcHR5cGUgPSAidGVycmFpbi1iYWNrZ3JvdW5kIikNCmBgYA0KDQpWZWFtb3MgY8OzbW8gcXVlZGE6DQoNCmBgYHtyfQ0KZ2dtYXAoQ0FCQSkNCmBgYA0KDQoNClBvZGVtb3MgaGFiZXIgdXNhZG8gb3RybyBgbWFwdHlwZWAsIGVsICJ0b25lci1saXRlIiBlcyDDunRpbCBwYXJhIHZpc3VhbGl6YWNpb25lcyBwb3Igc3UgY29udHJhc3RlOg0KDQpgYGB7cn0NCkNBQkEgPC0gZ2V0X3N0YW1lbm1hcChiYm94ID0gYmJveCwgDQogICAgICAgICAgICAgICAgICAgICAgbWFwdHlwZSA9ICJ0b25lci1saXRlIikNCg0KZ2dtYXAoQ0FCQSkNCg0KYGBgDQoNCg0KIyMjIE1hcGVhbmRvIGRhdG9zLi4uDQpSZXRvbWVtb3MgbnVlc3RybyBzY2F0dGVyIGRlIHB1bnRvczogcGVybyBhaG9yYSBjb21wbGlxdcOpbW9zbGEgZGVzZGUgZWwgcHJpbmNpcGlvLiBIYWdhbW9zIHVuIG1hcGEgZGUgdG9kb3MgbG9zIGRlbGl0b3MsIGRpZmVyZW5jaWFuZG8gcG9yIGNvbG9yIGVsIHRpcG8gZGUgZGVsaXRvOg0KDQpgYGB7cn0NCmdnbWFwKENBQkEpICsNCiAgICBnZW9tX3BvaW50KGRhdGEgPSBkZWxpdG9zLCBhZXMoeCA9IGxvbmdpdHVkLCB5ID0gbGF0aXR1ZCwgY29sb3I9dGlwb19kZWxpdG8pLA0KICAgICAgICAgICAgICAgc2l6ZSA9IDAuMSwgYWxwaGEgPSAwLjEpDQpgYGANCg0KRXN0w6EgYm9uaXRvLCBwZXJvIHZlbW9zIGRvcyBwcm9ibGVtYXM6DQoNCjEuIExhIGxleWVuZGEgZXMgZGlmw61jaWwgZGUgbGVlcg0KMi4gTGEgZXNjYWxhIGRlIGNvbG9yZXMgbm8gZXMgbGEgbWVqb3IsIGVuIHRhbnRvIHkgZW4gY3VhbnRvLCBubyBwZXJtaXRlIGRpc2Nlcm5pciBjbGFyYW1lbnRlIGRpZmVyZW5jaWFzIHBvciBjYXRlZ29yw61hDQoNCkVsIHByaW1lciBwcm9ibGVtYSBsbyBzb2x1Y2lvbmFtb3MgZmlqYW5kbyBhIG1hbm8gbG9zIHZhbG9yZXMgZGUgbGEgZXN0w6l0aWNhIGRlIGxhIGxleWVuZGE6DQoNCmBgYHtyfQ0KZ2dtYXAoQ0FCQSkgKw0KICAgIGdlb21fcG9pbnQoZGF0YSA9IGRlbGl0b3MsIA0KICAgICAgICAgICAgICAgYWVzKHggPSBsb25naXR1ZCwgeSA9IGxhdGl0dWQsIGNvbG9yID0gdGlwb19kZWxpdG8pLA0KICAgICAgICAgICAgICAgc2l6ZSA9IDAuMSwgYWxwaGEgPSAwLjEpICsNCiAgICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTIsIGFscGhhID0gMSkpKSANCmBgYA0KDQoNCg0KRWwgc2VndW5kby4uLiB5YSBsbyB2aW1vcy4uLiBmYWNldGFuZG8NCg0KDQpgYGB7ciBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9MTJ9DQpnZ21hcChDQUJBKSArDQogICAgZ2VvbV9wb2ludChkYXRhID0gZGVsaXRvcywgDQogICAgICAgICAgICAgICBhZXMoeCA9IGxvbmdpdHVkLCB5ID0gbGF0aXR1ZCwgY29sb3IgPSB0aXBvX2RlbGl0byksDQogICAgICAgICAgICAgICBzaXplID0gMC4yLCBhbHBoYSA9IDAuMSkgKw0KICAgICAgICBmYWNldF93cmFwKH50aXBvX2RlbGl0bykgKw0KICAgICAgICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTIsIGFscGhhID0gMSkpKSArDQogICAgICAgIHRoZW1lKHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplPTcuNSkpDQoNCmBgYA0KDQpBaMOtIGxhIGNvc2EgZXN0w6EgdW4gcG9jbyBtw6FzIGNsYXJhLiBObyBvYnN0YW50ZSBoYXkgdmFyaWFzIGZvcm1hcyBkZSBoYWNlciBtw6FzIG9ic2VydmFibGVzIGVzdG9zIHBhdHJvbmVzLiBTb2xvIHZhbW9zIGEgbW9zdHJhciB1bmEsIGNvbW8gcGFyYSBkYXIgbGEgaW50dWljacOzbi4gVmFtb3MgYSBkZXRlY3RhciBsYXMgem9uYXMgZGUgbWF5b3IgY29uY2VudHJhY2nDs24gZGUgZGVsaXRvcywgcG9yIHRpcG8gZGUgZGVsaXRvLiBQYXJhIGVsbG8sIHZhbW9zIGEgdXNhciB1bmEgdMOpY25pY2EgY29uIGVsIGVzdHJhbWLDs3RpY28gbm9tYnJlIGRlIFsqKnR3byBkaW1lbnRpb25hbCBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0aW9uKipdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL011bHRpdmFyaWF0ZV9rZXJuZWxfZGVuc2l0eV9lc3RpbWF0aW9uKS4NCg0KDQpgYGB7ciBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9MTJ9DQpnZ21hcChDQUJBKSArDQogICAgZ2VvbV9kZW5zaXR5MmQoZGF0YSA9IGRlbGl0b3MsIGFlcyh4ID0gbG9uZ2l0dWQsIHkgPSBsYXRpdHVkLCBjb2xvciA9IHN0YXQobGV2ZWwpKSkgKw0KICAgICAgICBzY2FsZV9jb2xvcl92aXJpZGlzX2MoKSArIA0KICAgICAgICBmYWNldF93cmFwKH50aXBvX2RlbGl0bykgKw0KICAgICAgICB0aGVtZShzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZT03LjUpKQ0KDQpgYGANCg0KQXF1w60gc2UgdmUgZGUgZm9ybWEgbcOhcyBuw610aWRhIHF1ZSBsYSBkaXN0cmlidWNpw7NuIGVzcGFjaWFsIGRlIGNhZGEgdGlwbyBkZSBkZWxpdG8gZXMgYmllbiBkaWZlcmVuY2lhZGEuDQoNClZlYW1vcywgYWhvcmEsIGxhIGRpc3RyaWJ1Y2nDs24gZGVsIHRvdGFsIGRlIGRlbGl0b3MgcG9yIGTDrWEgeSBob3JhIGRlIGxhIHNlbWFuYToNCg0KDQpgYGB7ciBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9MTJ9DQpkZWxpdG9zIDwtIGRlbGl0b3MgJT4lIA0KICAgIG11dGF0ZShob3JhX2Jhc2UgPSBob3VyKGhtcyhob3JhKSkpDQoNCmdnbWFwKENBQkEpICsNCiAgICBnZW9tX2RlbnNpdHkyZChkYXRhID0gZGVsaXRvcywgYWVzKHggPSBsb25naXR1ZCwgeSA9IGxhdGl0dWQsIGNvbG9yID0gc3RhdChsZXZlbCkpKSArDQogICAgICAgIHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpICsgDQogICAgICAgIGZhY2V0X3dyYXAofmhvcmFfYmFzZSwgbnJvdz00KSArDQogICAgICAgICBsYWJzKHRpdGxlID0gIkNvbmNlbnRyYWNpw7NuIGVzcGFjaWFsIGRlIGRlbGl0b3MiLA0KICAgICAgICAgc3VidGl0bGUgPSAic2Vnw7puIGhvcmEgZGVsIGTDrWEiKQ0KICAgICAgICANCmBgYA0KDQoNCg0KYGBge3IgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTEyfQ0KDQpkZWxpdG9zIDwtIGRlbGl0b3MgJT4lDQogICAgICAgIG11dGF0ZShkaWE9d2RheSh5bWQoZmVjaGEpLCBsYWJlbD1UUlVFKSkNCg0KZ2dtYXAoQ0FCQSkgKw0KICAgIGdlb21fZGVuc2l0eTJkKGRhdGEgPSBkZWxpdG9zLCBhZXMoeCA9IGxvbmdpdHVkLCB5ID0gbGF0aXR1ZCwgY29sb3IgPSBzdGF0KGxldmVsKSkpICsNCiAgICAgICAgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgKyANCiAgICAgICAgZmFjZXRfd3JhcCh+ZGlhLCBuY29sPTMpICsgDQogICAgICAgIGxhYnModGl0bGUgPSAiQ29uY2VudHJhY2nDs24gZXNwYWNpYWwgZGUgZGVsaXRvcyIsDQogICAgICAgICBzdWJ0aXRsZSA9ICJzZWfDum4gZMOtYSBkZSBsYSBzZW1hbmEiKQ0KDQpgYGANCg0KDQojIyMgQ29uc2lnbmFzDQpSZXBldGlyIGxvcyDDumx0aW1vcyBtYXBhcywgcGVybyBnZW5lcmFuZG8gaW5mb3JtYWNpw7NuIHNvbGFtZW50ZSBzb2JyZSBsb3MgaHVydG9zIGRlIGF1dG9tb3RvcmVzLg0KDQoqIERlbnNpZGFkIHBvciBkw61hDQoNCmBgYHtyIGZpZy5oZWlnaHQ9MTIsIGZpZy53aWR0aD0xMn0NCiMjIw0KYGBgDQoNCg0KKiBEZW5zaWRhZCBwb3IgaG9yYQ0KDQpgYGB7ciBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9MTJ9DQojIyMNCmBgYA0KDQo=