A lo largo de esta clase, trabajaremos con el paquete tidyverse. El mismo agrupa una serie de paquetes que tienen una misma lógica en su diseño y por ende funcionan en armonía.
Entre ellos, usaremos principalmente dplyr y tidyr para realizar transformaciones sobre nuestro set de datos. En una futura clase utilizaremos ggplot para realizar gráficos.

A continuación cargamos la librería a nuestro ambiente. Para ello debe estar previamente instalada en nuestra pc.

library(tidyverse)

Para mostrar el funcionamiento básico de tidyverse utilizaremos a modo de ejemplo datos del Informe de Mercado de Trabajo del INDEC.

INDICADOR <- c("Tasa de Actividad", "Tasa de Empleo", "Tasa de Desocupación",
               "Tasa de Actividad", "Tasa de Empleo", "Tasa de Desocupación",
               "Tasa de Actividad", "Tasa de Empleo", "Tasa de Desocupación")

FECHA <-     c("2018.3T", "2018.3T", "2018.3T",
               "2018.4T", "2018.4T", "2018.4T",
               "2019.1T", "2019.1T", "2019.1T")

TASA <-      c(46.7, 42.5, 9,
               46.5, 42.2, 9.1, 
               47,   42.3, 10.1)

Datos <- data.frame(INDICADOR, FECHA, TASA)
Datos

Dplyr

El caracter principal para utilizar este paquete es %>% , pipe (de tubería).

Los %>% toman el set de datos a su izquierda, y los transforman mediante los comandos a su derecha, en los cuales los elementos de la izquierda están implícitos. En otros términos:

\(f(x,y)\) es equivalente a \(x\) %>% \(f(.,y)\)

Veamos las principales funciones que pueden utilizarse con la lógica de este paquete:

glimpse

Permite ver la estructura de la tabla. Nos muestra:

  • número de filas
  • número de columnas
  • nombre de las columnas
  • tipo de dato de cada columna
  • las primeras observaciones de la tabla
glimpse(Datos)

filter

Permite filtrar la tabla de acuerdo al cumplimiento de condiciones lógicas.

Datos %>% 
  filter(TASA > 10 , INDICADOR == "Tasa de Desocupación")

Nótese que en este caso al separar con una , las condiciones se exige el cumplimiento de ambas. En caso de desear que se cumpla alguna de las condiciones debe utilizarse el caracter |.

Datos %>% 
  filter(TASA > 10 | INDICADOR == "Tasa de Desocupación")

rename

Permite renombrar una columna de la tabla. Funciona de la siguiente manera:

Data %>% rename(nuevo_nombre = viejo_nombre)

Datos %>% 
  rename(Periodo = FECHA)

Nótese que, a diferencia del ejemplo de la función filter donde utilizábamos == para comprobar una condición lógica, en este caso se utiliza sólo un = ya que lo estamos haciendo es asignar un nombre.

mutate

Permite agregar una variable a la tabla (especificando el nombre que tomará ésta), que puede ser el resultado de operaciones sobre otras variables de la misma tabla.

En caso de especificar el nombre de una columna existente, el resultado de la operación realizada “sobre-escribirá” la información de la columna con dicho nombre.

Datos <- Datos %>% 
  mutate(PROPORCION = TASA / 100)

Datos

case_when

Permite definir una variable, de forma tal que tome un valor particular para cada condición establecida. En caso de no cumplir con ninguna de las condiciones establecidas, la variable tomará valor NA.
La sintaxis de la función es:
case_when(condicion lógica1 ~ valor asignado1)

Datos <- Datos %>% 
  mutate(CODIGO = case_when(INDICADOR == "Tasa de Actividad"    ~ "ACT",
                            INDICADOR == "Tasa de Empleo"       ~ "EMP",
                            INDICADOR == "Tasa de Desocupación" ~ "DES"))

Datos

Si querémos asignar un valor a todo lo que no cumple ningúna de las condiciones anteriores, podemos poner TRUE ~ valor

select

Permite especificar la serie de columnas que se desea conservar de un DataFrame. También pueden especificarse las columnas que se desean descartar (agregándoles un - adelante). Muy útil para agilizar el trabajo en bases de datos de gran tamaño.

Datos2 <- Datos %>% 
  select(CODIGO, FECHA, PROPORCION)
Datos2

Datos <- Datos %>% 
  select(-c(PROPORCION, CODIGO))
Datos

arrange

Permite ordenar la tabla según los valores de determinada/s variable/s. Es útil cuando luego deben hacerse otras operaciones que requieran del ordenamiento de la tabla, o para mostrar resultados de forma ordenada.

Datos <- Datos %>% 
  arrange(INDICADOR, FECHA)

Datos

summarise

Crea una nueva tabla que resuma la información original. Para ello, definimos las variables de resumen y las formas de agregación.

Datos %>% 
  filter(INDICADOR == "Tasa de Desocupación") %>% 
  summarise(INDICE_MAX = max(TASA),
            INDICE_MIN = min(TASA),
            INDICE_PROM = mean(TASA))

group_by

Esta función permite realizar operaciones de forma agrupada. Lo que hace la función es “separar” a la tabla según los valores de la variable indicada y realizar las operaciones que se especifican a continuación, de manera independiente para cada una de las “subtablas”. En nuestro ejemplo, podría ser útil para calcular el promedio de las tasas por INDICADOR.

Datos %>% 
  group_by(INDICADOR) %>%
  summarise(INDICE_PROM = mean(TASA))

Joins

Otra implementación muy importante del paquete dplyr son las funciones para unir tablas (joins).

left_join

Veamos un ejemplo de la función left_join (una de las más utilizadas en la práctica).
Para ello crearemos previamente un Dataframe que contenga las cantidades de población total y población económicamente activa para cada uno de los períodos del Dataframe Datos.

Poblaciones <- data.frame(FECHA = c("2018.3T", "2018.4T", "2019.1T"),
                          POBLACION_miles = c(27842, 27914, 28261),
                          PEA_miles = c(12990, 12979, 13285))

Poblaciones

Unimos nuestras dos tablas. La siguiente forma de realizarlo es equivalente a:
Datos_join <- left_join(Datos, Poblaciones, by = "FECHA")

Datos_join <- Datos %>% 
  left_join(Poblaciones, by = "FECHA")

Datos_join

Finalmente, podemos calcular la cantidad de personas desocupadas en cada uno de los períodos con los que contamos.

Datos_join %>% 
  filter(INDICADOR == "Tasa de Desocupación") %>% 
  group_by(FECHA) %>% 
  summarise(DESOCUP_miles = round(TASA/100 * PEA_miles, 0))

Tidyr

El paquete tidyr está pensado para facilitar el emprolijamiento de los datos.

Gather es una función que nos permite pasar los datos de forma horizontal a una forma vertical.

spread es una función que nos permite pasar los datos de forma vertical a una forma horizontal.

# Utilizamos un conjunto de datos que viene con la librería datasets
library(datasets)

head(iris)
iris <- iris %>% 
  mutate(id = 1:nrow(.)) %>%  # le agrego un ID
  select(id, everything())    # lo acomodo para que el id este primero. 

head(iris)

Gather y Spread

iris_vertical <- iris %>% gather(., # el . llama a lo que esta atras del %>% 
                                 key   = Variables,
                                 value = Valores,
                                 2:5) #le indico qué columnas juntar
head(iris_vertical)

Podemos deshacer el gather con un Spread

iris_horizontal <- iris_vertical %>%
  spread(. ,
         key   = Variables, # la llave es la variable que va a dar los nombres de columna
         value = Valores) # los valores con que se llenan las celdas

head(iris_horizontal)

Lubridate

El paquete lubridate está pensado para trabajar con los datos tipo fecha (date) o fecha-hora (datetime) para cambiarles el formato, realizar operaciones y extraer información

library(lubridate)

Attaching package: ‘lubridate’

The following object is masked from ‘package:base’:

    date

Cambio de formato

Existe una gran cantidad de funciones para realizar esto. La idea general es poder llevar los objetos datetime a un formato común compuesto de los elementos: año, mes, día, hora, minuto y segundo (también se puede setear el huso horario)

fecha  <- "04/12/92 17:35:16"
fecha
[1] "04/12/92 17:35:16"

Con la función dmy_hms podemos convertir este string a una fecha: estamos indicando que el formato de la fecha es día(d), mes(m), año(y), hora(h), minuto(m) y segundo(s).

fecha  <- dmy_hms(fecha)
fecha
[1] "1992-12-04 17:35:16 UTC"

Muchas funciones de lubridate operan con esta misma lógica.

Otra función para realizar un cambio de formato es parse_date_time. Permite construir objetos datetime a partir de datos más complejos, como por ejemplo cuando aparece el nombre del mes y el año.

En el parámetro x pasamos el dato de la fecha y en el parámetro orders especificamos el orden en el cual se encuentra la información de la fecha.

fecha2  <- "Dec-92"
fecha2 <- parse_date_time(fecha2, orders = 'my')
fecha2
[1] "1992-12-01 UTC"

Extracción de información

Existen muchas funciones muy sencillas para extraer información de un objeto datetime. Algunas son:

year(fecha)  # Obtener el año
[1] 1992
month(fecha) # Obtener el mes
[1] 12
day(fecha)   # Obtener el día
[1] 4
wday(fecha, label = TRUE) # Obtener el nombre del día
[1] vie
Levels: dom < lun < mar < mié < jue < vie < sáb
hour(fecha) # Obtener la hora
[1] 17

Operaciones

Podemos sumar o restarle cualquier período de tiempo a un objeto datetime

# Sumo dos días 
fecha + days(2)
[1] "1992-12-06 17:35:16 UTC"
# Resto 1 semana y dos horas
fecha - (weeks(1) + hours(2))
[1] "1992-11-27 15:35:16 UTC"
LS0tCnRpdGxlOiBUaWR5dmVyc2UgICAgCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCmRhdGU6ICIiCnN1YnRpdGxlOiBFeHBsaWNhY2nDs24KLS0tCgoKQSBsbyBsYXJnbyBkZSBlc3RhIGNsYXNlLCB0cmFiYWphcmVtb3MgY29uIGVsIHBhcXVldGUgW19fdGlkeXZlcnNlX19dKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvKS4gRWwgbWlzbW8gYWdydXBhIHVuYSBzZXJpZSBkZSBwYXF1ZXRlcyBxdWUgdGllbmVuIHVuYSBtaXNtYSBsw7NnaWNhIGVuIHN1IGRpc2XDsW8geSBwb3IgZW5kZSBmdW5jaW9uYW4gZW4gYXJtb27DrWEuICAgICAKRW50cmUgZWxsb3MsIHVzYXJlbW9zIHByaW5jaXBhbG1lbnRlIF9fZHBseXJfXyB5IF9fdGlkeXJfXyBwYXJhIHJlYWxpemFyIHRyYW5zZm9ybWFjaW9uZXMgc29icmUgbnVlc3RybyBzZXQgZGUgZGF0b3MuIEVuIHVuYSBmdXR1cmEgY2xhc2UgdXRpbGl6YXJlbW9zIF9fZ2dwbG90X18gcGFyYSByZWFsaXphciBncsOhZmljb3MuICAgCiAgICAgIApBIGNvbnRpbnVhY2nDs24gY2FyZ2Ftb3MgbGEgbGlicmVyw61hIGEgbnVlc3RybyBhbWJpZW50ZS4gUGFyYSBlbGxvIGRlYmUgZXN0YXIgcHJldmlhbWVudGUgaW5zdGFsYWRhIGVuIG51ZXN0cmEgcGMuCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKUGFyYSBtb3N0cmFyIGVsIGZ1bmNpb25hbWllbnRvIGLDoXNpY28gZGUgdGlkeXZlcnNlIHV0aWxpemFyZW1vcyBhIG1vZG8gZGUgZWplbXBsbyBkYXRvcyBkZWwgW0luZm9ybWUgZGUgTWVyY2FkbyBkZSBUcmFiYWpvIGRlbCBJTkRFQ10oaHR0cHM6Ly93d3cuaW5kZWMuZ29iLmFyL3VwbG9hZHMvaW5mb3JtZXNkZXByZW5zYS9tZXJjYWRvX3RyYWJham9fZXBoXzF0cmltMTlCNDg5QUNDREY5LnBkZikuICAgIApgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CklORElDQURPUiA8LSBjKCJUYXNhIGRlIEFjdGl2aWRhZCIsICJUYXNhIGRlIEVtcGxlbyIsICJUYXNhIGRlIERlc29jdXBhY2nDs24iLAogICAgICAgICAgICAgICAiVGFzYSBkZSBBY3RpdmlkYWQiLCAiVGFzYSBkZSBFbXBsZW8iLCAiVGFzYSBkZSBEZXNvY3VwYWNpw7NuIiwKICAgICAgICAgICAgICAgIlRhc2EgZGUgQWN0aXZpZGFkIiwgIlRhc2EgZGUgRW1wbGVvIiwgIlRhc2EgZGUgRGVzb2N1cGFjacOzbiIpCgpGRUNIQSA8LSAgICAgYygiMjAxOC4zVCIsICIyMDE4LjNUIiwgIjIwMTguM1QiLAogICAgICAgICAgICAgICAiMjAxOC40VCIsICIyMDE4LjRUIiwgIjIwMTguNFQiLAogICAgICAgICAgICAgICAiMjAxOS4xVCIsICIyMDE5LjFUIiwgIjIwMTkuMVQiKQoKVEFTQSA8LSAgICAgIGMoNDYuNywgNDIuNSwgOSwKICAgICAgICAgICAgICAgNDYuNSwgNDIuMiwgOS4xLCAKICAgICAgICAgICAgICAgNDcsICAgNDIuMywgMTAuMSkKCkRhdG9zIDwtIGRhdGEuZnJhbWUoSU5ESUNBRE9SLCBGRUNIQSwgVEFTQSkKRGF0b3MKYGBgCgojIyMgRHBseXIKCkVsIGNhcmFjdGVyIHByaW5jaXBhbCBwYXJhIHV0aWxpemFyIGVzdGUgcGFxdWV0ZSBlcyBgYGAlPiVgYGAgLCBfcGlwZV8gKGRlIHR1YmVyw61hKS4gICAKCkxvcyBgYGAlPiVgYGAgdG9tYW4gZWwgc2V0IGRlIGRhdG9zIGEgc3UgaXpxdWllcmRhLCB5IGxvcyB0cmFuc2Zvcm1hbiBtZWRpYW50ZSBsb3MgY29tYW5kb3MgYSBzdSBkZXJlY2hhLCBlbiBsb3MgY3VhbGVzIGxvcyBlbGVtZW50b3MgZGUgbGEgaXpxdWllcmRhIGVzdMOhbiBpbXBsw61jaXRvcy4gRW4gb3Ryb3MgdMOpcm1pbm9zOiAgCgokZih4LHkpJCBlcyBlcXVpdmFsZW50ZSBhICR4JCAlPiUgJGYoLix5KSQgCgpWZWFtb3MgbGFzIHByaW5jaXBhbGVzIGZ1bmNpb25lcyBxdWUgcHVlZGVuIHV0aWxpemFyc2UgY29uIGxhIGzDs2dpY2EgZGUgZXN0ZSBwYXF1ZXRlOgoKIyMjIyBnbGltcHNlCgpQZXJtaXRlIHZlciBsYSBlc3RydWN0dXJhIGRlIGxhIHRhYmxhLiBOb3MgbXVlc3RyYTogCgoqIG7Dum1lcm8gZGUgZmlsYXMKKiBuw7ptZXJvIGRlIGNvbHVtbmFzCiogbm9tYnJlIGRlIGxhcyBjb2x1bW5hcwoqIHRpcG8gZGUgZGF0byBkZSBjYWRhIGNvbHVtbmEKKiBsYXMgcHJpbWVyYXMgb2JzZXJ2YWNpb25lcyBkZSBsYSB0YWJsYQoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZ2xpbXBzZShEYXRvcykKYGBgCgohW10oaW1nL2dsaW1wc2UucG5nKQoKCiMjIyMgZmlsdGVyCgpQZXJtaXRlIGZpbHRyYXIgbGEgdGFibGEgZGUgYWN1ZXJkbyBhbCBjdW1wbGltaWVudG8gZGUgY29uZGljaW9uZXMgbMOzZ2ljYXMuCiAKYGBge3J9CkRhdG9zICU+JSAKICBmaWx0ZXIoVEFTQSA+IDEwICwgSU5ESUNBRE9SID09ICJUYXNhIGRlIERlc29jdXBhY2nDs24iKQpgYGAKCk7Ds3Rlc2UgcXVlIGVuIGVzdGUgY2FzbyBhbCBzZXBhcmFyIGNvbiB1bmEgIF9fLF9fIGxhcyBjb25kaWNpb25lcyBzZSBleGlnZSBlbCBjdW1wbGltaWVudG8gZGUgYW1iYXMuIEVuIGNhc28gZGUgZGVzZWFyIHF1ZSBzZSBjdW1wbGEgYWxndW5hIGRlIGxhcyBjb25kaWNpb25lcyBkZWJlIHV0aWxpemFyc2UgZWwgY2FyYWN0ZXIgX198X18uCgpgYGB7cn0KRGF0b3MgJT4lIAogIGZpbHRlcihUQVNBID4gMTAgfCBJTkRJQ0FET1IgPT0gIlRhc2EgZGUgRGVzb2N1cGFjacOzbiIpCmBgYAoKIyMjIyByZW5hbWUKClBlcm1pdGUgcmVub21icmFyIHVuYSBjb2x1bW5hIGRlIGxhIHRhYmxhLiBGdW5jaW9uYSBkZSBsYSBzaWd1aWVudGUgbWFuZXJhOiAgCiAgCiBgYGBEYXRhICU+JSByZW5hbWUobnVldm9fbm9tYnJlID0gdmllam9fbm9tYnJlKWBgYCAKCmBgYHtyfQpEYXRvcyAlPiUgCiAgcmVuYW1lKFBlcmlvZG8gPSBGRUNIQSkKYGBgCgpOw7N0ZXNlIHF1ZSwgYSBkaWZlcmVuY2lhIGRlbCBlamVtcGxvIGRlIGxhIGZ1bmNpw7NuIF9fZmlsdGVyX18gZG9uZGUgdXRpbGl6w6FiYW1vcyBfXz09X18gcGFyYSBjb21wcm9iYXIgdW5hIGNvbmRpY2nDs24gbMOzZ2ljYSwgZW4gZXN0ZSBjYXNvIHNlIHV0aWxpemEgc8OzbG8gdW4gX189X18geWEgcXVlIGxvIGVzdGFtb3MgaGFjaWVuZG8gZXMgX2FzaWduYXJfIHVuIG5vbWJyZS4KCiMjIyMgbXV0YXRlCgpQZXJtaXRlIGFncmVnYXIgdW5hIHZhcmlhYmxlIGEgbGEgdGFibGEgKGVzcGVjaWZpY2FuZG8gZWwgbm9tYnJlIHF1ZSB0b21hcsOhIMOpc3RhKSwgcXVlIHB1ZWRlIHNlciBlbCByZXN1bHRhZG8gZGUgb3BlcmFjaW9uZXMgc29icmUgb3RyYXMgdmFyaWFibGVzIGRlIGxhIG1pc21hIHRhYmxhLiAgICAgICAKCkVuIGNhc28gZGUgZXNwZWNpZmljYXIgZWwgbm9tYnJlIGRlIHVuYSBjb2x1bW5hIGV4aXN0ZW50ZSwgZWwgcmVzdWx0YWRvIGRlIGxhIG9wZXJhY2nDs24gcmVhbGl6YWRhICJzb2JyZS1lc2NyaWJpcsOhIiBsYSBpbmZvcm1hY2nDs24gZGUgbGEgY29sdW1uYSBjb24gZGljaG8gbm9tYnJlLgoKYGBge3J9CkRhdG9zIDwtIERhdG9zICU+JSAKICBtdXRhdGUoUFJPUE9SQ0lPTiA9IFRBU0EgLyAxMDApCgpEYXRvcwpgYGAKCiMjIyMgY2FzZV93aGVuCgpQZXJtaXRlIGRlZmluaXIgdW5hIHZhcmlhYmxlLCBkZSBmb3JtYSB0YWwgcXVlIHRvbWUgdW4gdmFsb3IgcGFydGljdWxhciBwYXJhIGNhZGEgY29uZGljacOzbiBlc3RhYmxlY2lkYS4gRW4gY2FzbyBkZSBubyBjdW1wbGlyIGNvbiBuaW5ndW5hIGRlIGxhcyBjb25kaWNpb25lcyBlc3RhYmxlY2lkYXMsIGxhIHZhcmlhYmxlIHRvbWFyw6EgdmFsb3IgX19OQV9fLiAgIApMYSBzaW50YXhpcyBkZSBsYSBmdW5jacOzbiBlczogICAKYGNhc2Vfd2hlbihjb25kaWNpb24gbMOzZ2ljYTEgfiB2YWxvciBhc2lnbmFkbzEpYAoKYGBge3J9CkRhdG9zIDwtIERhdG9zICU+JSAKICBtdXRhdGUoQ09ESUdPID0gY2FzZV93aGVuKElORElDQURPUiA9PSAiVGFzYSBkZSBBY3RpdmlkYWQiICAgIH4gIkFDVCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBJTkRJQ0FET1IgPT0gIlRhc2EgZGUgRW1wbGVvIiAgICAgICB+ICJFTVAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgSU5ESUNBRE9SID09ICJUYXNhIGRlIERlc29jdXBhY2nDs24iIH4gIkRFUyIpKQoKRGF0b3MKYGBgCgpTaSBxdWVyw6ltb3MgYXNpZ25hciB1biB2YWxvciBhIHRvZG8gbG8gcXVlIG5vIGN1bXBsZSBuaW5nw7puYSBkZSBsYXMgY29uZGljaW9uZXMgYW50ZXJpb3JlcywgcG9kZW1vcyBwb25lciBgVFJVRSB+IHZhbG9yYAoKIyMjIyBzZWxlY3QKClBlcm1pdGUgZXNwZWNpZmljYXIgbGEgc2VyaWUgZGUgY29sdW1uYXMgcXVlIHNlIGRlc2VhIGNvbnNlcnZhciBkZSB1biBEYXRhRnJhbWUuIFRhbWJpw6luIHB1ZWRlbiBlc3BlY2lmaWNhcnNlIGxhcyBjb2x1bW5hcyBxdWUgc2UgZGVzZWFuIGRlc2NhcnRhciAoYWdyZWfDoW5kb2xlcyB1biBfLV8gYWRlbGFudGUpLiBNdXkgw7p0aWwgcGFyYSBhZ2lsaXphciBlbCB0cmFiYWpvIGVuIGJhc2VzIGRlIGRhdG9zIGRlIGdyYW4gdGFtYcOxby4KCmBgYHtyfQpEYXRvczIgPC0gRGF0b3MgJT4lIAogIHNlbGVjdChDT0RJR08sIEZFQ0hBLCBQUk9QT1JDSU9OKQpEYXRvczIKCkRhdG9zIDwtIERhdG9zICU+JSAKICBzZWxlY3QoLWMoUFJPUE9SQ0lPTiwgQ09ESUdPKSkKRGF0b3MKYGBgCgojIyMjIGFycmFuZ2UKClBlcm1pdGUgb3JkZW5hciBsYSB0YWJsYSBzZWfDum4gbG9zIHZhbG9yZXMgZGUgZGV0ZXJtaW5hZGEvcyB2YXJpYWJsZS9zLiBFcyDDunRpbCBjdWFuZG8gbHVlZ28gZGViZW4gaGFjZXJzZSBvdHJhcyBvcGVyYWNpb25lcyBxdWUgcmVxdWllcmFuIGRlbCBvcmRlbmFtaWVudG8gZGUgbGEgdGFibGEsIG8gcGFyYSBtb3N0cmFyIHJlc3VsdGFkb3MgZGUgZm9ybWEgb3JkZW5hZGEuCgpgYGB7cn0KRGF0b3MgPC0gRGF0b3MgJT4lIAogIGFycmFuZ2UoSU5ESUNBRE9SLCBGRUNIQSkKCkRhdG9zCmBgYAoKIyMjIyBzdW1tYXJpc2UKCkNyZWEgdW5hIG51ZXZhIHRhYmxhIHF1ZSByZXN1bWEgbGEgaW5mb3JtYWNpw7NuIG9yaWdpbmFsLiBQYXJhIGVsbG8sIGRlZmluaW1vcyBsYXMgdmFyaWFibGVzIGRlIHJlc3VtZW4geSBsYXMgZm9ybWFzIGRlIGFncmVnYWNpw7NuLgoKYGBge3J9CkRhdG9zICU+JSAKICBmaWx0ZXIoSU5ESUNBRE9SID09ICJUYXNhIGRlIERlc29jdXBhY2nDs24iKSAlPiUgCiAgc3VtbWFyaXNlKElORElDRV9NQVggPSBtYXgoVEFTQSksCiAgICAgICAgICAgIElORElDRV9NSU4gPSBtaW4oVEFTQSksCiAgICAgICAgICAgIElORElDRV9QUk9NID0gbWVhbihUQVNBKSkKYGBgCgojIyMjIGdyb3VwX2J5CgpFc3RhIGZ1bmNpw7NuIHBlcm1pdGUgcmVhbGl6YXIgb3BlcmFjaW9uZXMgZGUgZm9ybWEgYWdydXBhZGEuIExvIHF1ZSBoYWNlIGxhIGZ1bmNpw7NuIGVzICJzZXBhcmFyIiBhIGxhIHRhYmxhIHNlZ8O6biBsb3MgdmFsb3JlcyBkZSBsYSB2YXJpYWJsZSBpbmRpY2FkYSB5IHJlYWxpemFyIGxhcyBvcGVyYWNpb25lcyBxdWUgc2UgZXNwZWNpZmljYW4gYSBjb250aW51YWNpw7NuLCBkZSBtYW5lcmEgaW5kZXBlbmRpZW50ZSBwYXJhIGNhZGEgdW5hIGRlIGxhcyAic3VidGFibGFzIi4gRW4gbnVlc3RybyBlamVtcGxvLCBwb2Ryw61hIHNlciDDunRpbCBwYXJhIGNhbGN1bGFyIGVsIHByb21lZGlvIGRlIGxhcyB0YXNhcyBwb3IgX0lORElDQURPUl8uIAoKYGBge3J9CkRhdG9zICU+JSAKICBncm91cF9ieShJTkRJQ0FET1IpICU+JQogIHN1bW1hcmlzZShJTkRJQ0VfUFJPTSA9IG1lYW4oVEFTQSkpCmBgYAoKIyMjIEpvaW5zCgpPdHJhIGltcGxlbWVudGFjacOzbiBtdXkgaW1wb3J0YW50ZSBkZWwgcGFxdWV0ZSBkcGx5ciBzb24gbGFzIGZ1bmNpb25lcyBwYXJhIHVuaXIgdGFibGFzIChqb2lucykuCgohW2Z1ZW50ZTogaHR0cDovL3JzdHVkaW8tcHVicy1zdGF0aWMuczMuYW1hem9uYXdzLmNvbS8yMjcxNzFfNjE4ZWJkY2UwYjlkNDRmM2FmNjU3MDBlODMzNTkzZGIuaHRtbF0oaW1nL2pvaW5zLnBuZykgICAgICAgICAKCgojIyMjIGxlZnRfam9pbiAgICAKClZlYW1vcyB1biBlamVtcGxvIGRlIGxhIGZ1bmNpw7NuIF9fbGVmdF9qb2luX18gKHVuYSBkZSBsYXMgbcOhcyB1dGlsaXphZGFzIGVuIGxhIHByw6FjdGljYSkuICAgICAgIApQYXJhIGVsbG8gY3JlYXJlbW9zIHByZXZpYW1lbnRlIHVuIERhdGFmcmFtZSBxdWUgY29udGVuZ2EgbGFzIGNhbnRpZGFkZXMgZGUgcG9ibGFjacOzbiB0b3RhbCB5IHBvYmxhY2nDs24gZWNvbsOzbWljYW1lbnRlIGFjdGl2YSBwYXJhIGNhZGEgdW5vIGRlIGxvcyBwZXLDrW9kb3MgZGVsIERhdGFmcmFtZSBfRGF0b3NfLgoKYGBge3J9ClBvYmxhY2lvbmVzIDwtIGRhdGEuZnJhbWUoRkVDSEEgPSBjKCIyMDE4LjNUIiwgIjIwMTguNFQiLCAiMjAxOS4xVCIpLAogICAgICAgICAgICAgICAgICAgICAgICAgIFBPQkxBQ0lPTl9taWxlcyA9IGMoMjc4NDIsIDI3OTE0LCAyODI2MSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgUEVBX21pbGVzID0gYygxMjk5MCwgMTI5NzksIDEzMjg1KSkKClBvYmxhY2lvbmVzCmBgYAoKVW5pbW9zIG51ZXN0cmFzIGRvcyB0YWJsYXMuIExhIHNpZ3VpZW50ZSBmb3JtYSBkZSByZWFsaXphcmxvIGVzIGVxdWl2YWxlbnRlIGE6ICAgCmBEYXRvc19qb2luIDwtIGxlZnRfam9pbihEYXRvcywgUG9ibGFjaW9uZXMsIGJ5ID0gIkZFQ0hBIilgCgpgYGB7cn0KRGF0b3Nfam9pbiA8LSBEYXRvcyAlPiUgCiAgbGVmdF9qb2luKFBvYmxhY2lvbmVzLCBieSA9ICJGRUNIQSIpCgpEYXRvc19qb2luCmBgYAoKRmluYWxtZW50ZSwgcG9kZW1vcyBjYWxjdWxhciBsYSBjYW50aWRhZCBkZSBwZXJzb25hcyBkZXNvY3VwYWRhcyBlbiBjYWRhIHVubyBkZSBsb3MgcGVyw61vZG9zIGNvbiBsb3MgcXVlIGNvbnRhbW9zLgoKYGBge3J9CkRhdG9zX2pvaW4gJT4lIAogIGZpbHRlcihJTkRJQ0FET1IgPT0gIlRhc2EgZGUgRGVzb2N1cGFjacOzbiIpICU+JSAKICBncm91cF9ieShGRUNIQSkgJT4lIAogIHN1bW1hcmlzZShERVNPQ1VQX21pbGVzID0gcm91bmQoVEFTQS8xMDAgKiBQRUFfbWlsZXMsIDApKQpgYGAKCiMjIyBUaWR5cgoKRWwgcGFxdWV0ZSB0aWR5ciBlc3TDoSBwZW5zYWRvIHBhcmEgZmFjaWxpdGFyIGVsIGVtcHJvbGlqYW1pZW50byBkZSBsb3MgZGF0b3MuCgpfX0dhdGhlcl9fIGVzIHVuYSBmdW5jacOzbiBxdWUgbm9zIHBlcm1pdGUgcGFzYXIgbG9zIGRhdG9zIGRlIGZvcm1hIGhvcml6b250YWwgYSB1bmEgZm9ybWEgdmVydGljYWwuIAoKX19zcHJlYWRfXyBlcyB1bmEgZnVuY2nDs24gcXVlIG5vcyBwZXJtaXRlIHBhc2FyIGxvcyBkYXRvcyBkZSBmb3JtYSB2ZXJ0aWNhbCBhIHVuYSBmb3JtYSBob3Jpem9udGFsLgoKIVtmdWVudGU6IGh0dHA6Ly93d3cuZ2lzLWJsb2cuY29tL2RhdGEtbWFuYWdlbWVudC13aXRoLXItdGlkeXItcGFydC0xL10oaW1nL3NwcmVhZFZTZ2F0aGVyLnBuZykKCmBgYHtyfQojIFV0aWxpemFtb3MgdW4gY29uanVudG8gZGUgZGF0b3MgcXVlIHZpZW5lIGNvbiBsYSBsaWJyZXLDrWEgZGF0YXNldHMKbGlicmFyeShkYXRhc2V0cykKCmhlYWQoaXJpcykKYGBgCgpgYGB7cn0KaXJpcyA8LSBpcmlzICU+JSAKICBtdXRhdGUoaWQgPSAxOm5yb3coLikpICU+JSAgIyBsZSBhZ3JlZ28gdW4gSUQKICBzZWxlY3QoaWQsIGV2ZXJ5dGhpbmcoKSkgICAgIyBsbyBhY29tb2RvIHBhcmEgcXVlIGVsIGlkIGVzdGUgcHJpbWVyby4gCgpoZWFkKGlyaXMpCmBgYAoKIyMjIyBHYXRoZXIgeSBTcHJlYWQKCmBgYHtyfQppcmlzX3ZlcnRpY2FsIDwtIGlyaXMgJT4lIGdhdGhlciguLCAjIGVsIC4gbGxhbWEgYSBsbyBxdWUgZXN0YSBhdHJhcyBkZWwgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXkgICA9IFZhcmlhYmxlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBWYWxvcmVzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAyOjUpICNsZSBpbmRpY28gcXXDqSBjb2x1bW5hcyBqdW50YXIKaGVhZChpcmlzX3ZlcnRpY2FsKQpgYGAKClBvZGVtb3MgZGVzaGFjZXIgZWwgX19nYXRoZXJfXyBjb24gdW4gX19TcHJlYWRfXwpgYGB7cn0KaXJpc19ob3Jpem9udGFsIDwtIGlyaXNfdmVydGljYWwgJT4lCiAgc3ByZWFkKC4gLAogICAgICAgICBrZXkgICA9IFZhcmlhYmxlcywgIyBsYSBsbGF2ZSBlcyBsYSB2YXJpYWJsZSBxdWUgdmEgYSBkYXIgbG9zIG5vbWJyZXMgZGUgY29sdW1uYQogICAgICAgICB2YWx1ZSA9IFZhbG9yZXMpICMgbG9zIHZhbG9yZXMgY29uIHF1ZSBzZSBsbGVuYW4gbGFzIGNlbGRhcwoKaGVhZChpcmlzX2hvcml6b250YWwpCmBgYAoKIyMjIEx1YnJpZGF0ZQoKRWwgcGFxdWV0ZSBsdWJyaWRhdGUgZXN0w6EgcGVuc2FkbyBwYXJhIHRyYWJhamFyIGNvbiBsb3MgZGF0b3MgdGlwbyBmZWNoYSAoZGF0ZSkgbyBmZWNoYS1ob3JhIChkYXRldGltZSkgcGFyYSBjYW1iaWFybGVzIGVsIGZvcm1hdG8sIHJlYWxpemFyIG9wZXJhY2lvbmVzIHkgZXh0cmFlciBpbmZvcm1hY2nDs24KCmBgYHtyfQpsaWJyYXJ5KGx1YnJpZGF0ZSkKYGBgCgojIyMjIENhbWJpbyBkZSBmb3JtYXRvCgpFeGlzdGUgdW5hIGdyYW4gY2FudGlkYWQgZGUgZnVuY2lvbmVzIHBhcmEgcmVhbGl6YXIgZXN0by4gTGEgaWRlYSBnZW5lcmFsIGVzIHBvZGVyIGxsZXZhciBsb3Mgb2JqZXRvcyBkYXRldGltZSBhIHVuIGZvcm1hdG8gY29tw7puIGNvbXB1ZXN0byBkZSBsb3MgZWxlbWVudG9zOiBhw7FvLCBtZXMsIGTDrWEsIGhvcmEsIG1pbnV0byB5IHNlZ3VuZG8gKHRhbWJpw6luIHNlIHB1ZWRlIHNldGVhciBlbCBodXNvIGhvcmFyaW8pCgpgYGB7cn0KZmVjaGEgIDwtICIwNC8xMi85MiAxNzozNToxNiIKZmVjaGEKYGBgCgpDb24gbGEgZnVuY2nDs24gZG15X2htcyBwb2RlbW9zIGNvbnZlcnRpciBlc3RlIHN0cmluZyBhIHVuYSBmZWNoYTogZXN0YW1vcyBpbmRpY2FuZG8gcXVlIGVsIGZvcm1hdG8gZGUgbGEgZmVjaGEgZXMgZMOtYShkKSwgbWVzKG0pLCBhw7FvKHkpLCBob3JhKGgpLCBtaW51dG8obSkgeSBzZWd1bmRvKHMpLgoKYGBge3J9CmZlY2hhICA8LSBkbXlfaG1zKGZlY2hhKQpmZWNoYQpgYGAKCk11Y2hhcyBmdW5jaW9uZXMgZGUgbHVicmlkYXRlIG9wZXJhbiBjb24gZXN0YSBtaXNtYSBsw7NnaWNhLiAgIAoKT3RyYSBmdW5jacOzbiBwYXJhIHJlYWxpemFyIHVuIGNhbWJpbyBkZSBmb3JtYXRvIGVzICpwYXJzZV9kYXRlX3RpbWUqLiBQZXJtaXRlIGNvbnN0cnVpciBvYmpldG9zIGRhdGV0aW1lIGEgcGFydGlyIGRlIGRhdG9zIG3DoXMgY29tcGxlam9zLCBjb21vIHBvciBlamVtcGxvIGN1YW5kbyBhcGFyZWNlIGVsIG5vbWJyZSBkZWwgbWVzIHkgZWwgYcOxby4gICAgCgpFbiBlbCBwYXLDoW1ldHJvICp4KiBwYXNhbW9zIGVsIGRhdG8gZGUgbGEgZmVjaGEgeSBlbiBlbCBwYXLDoW1ldHJvICpvcmRlcnMqIGVzcGVjaWZpY2Ftb3MgZWwgb3JkZW4gZW4gZWwgY3VhbCBzZSBlbmN1ZW50cmEgbGEgaW5mb3JtYWNpw7NuIGRlIGxhIGZlY2hhLgoKYGBge3J9CmZlY2hhMiAgPC0gIkRlYy05MiIKZmVjaGEyIDwtIHBhcnNlX2RhdGVfdGltZShmZWNoYTIsIG9yZGVycyA9ICdteScpCmZlY2hhMgpgYGAKCiMjIyMgRXh0cmFjY2nDs24gZGUgaW5mb3JtYWNpw7NuCgpFeGlzdGVuIG11Y2hhcyBmdW5jaW9uZXMgbXV5IHNlbmNpbGxhcyBwYXJhIGV4dHJhZXIgaW5mb3JtYWNpw7NuIGRlIHVuIG9iamV0byBkYXRldGltZS4gQWxndW5hcyBzb246CgpgYGB7cn0KeWVhcihmZWNoYSkgICMgT2J0ZW5lciBlbCBhw7FvCm1vbnRoKGZlY2hhKSAjIE9idGVuZXIgZWwgbWVzCmRheShmZWNoYSkgICAjIE9idGVuZXIgZWwgZMOtYQp3ZGF5KGZlY2hhLCBsYWJlbCA9IFRSVUUpICMgT2J0ZW5lciBlbCBub21icmUgZGVsIGTDrWEKaG91cihmZWNoYSkgIyBPYnRlbmVyIGxhIGhvcmEKYGBgCgojIyMjIE9wZXJhY2lvbmVzCgpQb2RlbW9zIHN1bWFyIG8gcmVzdGFybGUgY3VhbHF1aWVyIHBlcsOtb2RvIGRlIHRpZW1wbyBhIHVuIG9iamV0byBkYXRldGltZQoKYGBge3J9CiMgU3VtbyBkb3MgZMOtYXMgCmZlY2hhICsgZGF5cygyKQojIFJlc3RvIDEgc2VtYW5hIHkgZG9zIGhvcmFzCmZlY2hhIC0gKHdlZWtzKDEpICsgaG91cnMoMikpCmBgYAoK