El paquete {dplyr} provee una enorme cantidad de funciones útiles para manipular y analizar datos de manera intuitiva y expresiva.

El espíritu detrás de {dplyr} es que la enorme mayoría de los análisis, por más complicados que sean, involucran combinar y encadenar una serie relativamente acotada de acciones (o verbos). En este curso vamos a centrarnos las cinco más comunes:

dplyr y tablas dinámicas:

A grosso modo, las operaciones de {dplyr} permiten hacer en R lo que se hace en tablas dinámicas (pivot tables) en Excel. filter() vendría a ser como la sección de “Filtros”, group_by(), la sección de “Filas”, select(), la sección de “Columnas” y summarise(), la parte de “Valores”.

Primer desafío:

Te dieron una tabla con datos de temperatura mínima y máxima para distintas estaciones meteorológicas de todo el país durante los 365 días de un año. Las columnas son: id_estacion, temperatura_maxima, temperatura_minima y provincia. En base a esos datos, te piden que computen la temperatura media anual de cada estación únicamente de las estaciones de La Pampa.

¿En qué orden ejecutarías estos pasos para obtener el resultado deseado?

Para usar {dplyr} primero hay que instalarlo (esto hay que hacerlo una sola vez) con el comando:

install.packages('dplyr')

y luego cargarlo en memoria con

library(dplyr)
## Warning: replacing previous import 'vctrs::data_frame' by 'tibble::data_frame'
## when loading 'dplyr'
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union

Volvé a cargar los datos de países (para un recordatorio, podés ir a Lectura de datos ordenados):

library(readr)
paises <- read_csv("datos/paises.csv")
## Parsed with column specification:
## cols(
##   pais = col_character(),
##   continente = col_character(),
##   anio = col_double(),
##   esperanza_de_vida = col_double(),
##   poblacion = col_double(),
##   pib_per_capita = col_double()
## )

Seleccionando columnas con select()

Para quedarse únicamente con las columnas de año, país y PBI per cápita, usá select()

select(paises, anio, pais, pib_per_capita)
## # A tibble: 1,704 x 3
##     anio pais       pib_per_capita
##    <dbl> <chr>               <dbl>
##  1  1952 Afganistán           779.
##  2  1957 Afganistán           821.
##  3  1962 Afganistán           853.
##  4  1967 Afganistán           836.
##  5  1972 Afganistán           740.
##  6  1977 Afganistán           786.
##  7  1982 Afganistán           978.
##  8  1987 Afganistán           852.
##  9  1992 Afganistán           649.
## 10  1997 Afganistán           635.
## # … with 1,694 more rows

¿Dónde quedó este resultado? Si te fijás en la variable paises, ésta está intacta:

paises
## # A tibble: 1,704 x 6
##    pais       continente  anio esperanza_de_vida poblacion pib_per_capita
##    <chr>      <chr>      <dbl>             <dbl>     <dbl>          <dbl>
##  1 Afganistán Asia        1952              28.8   8425333           779.
##  2 Afganistán Asia        1957              30.3   9240934           821.
##  3 Afganistán Asia        1962              32.0  10267083           853.
##  4 Afganistán Asia        1967              34.0  11537966           836.
##  5 Afganistán Asia        1972              36.1  13079460           740.
##  6 Afganistán Asia        1977              38.4  14880372           786.
##  7 Afganistán Asia        1982              39.9  12881816           978.
##  8 Afganistán Asia        1987              40.8  13867957           852.
##  9 Afganistán Asia        1992              41.7  16317921           649.
## 10 Afganistán Asia        1997              41.8  22227415           635.
## # … with 1,694 more rows

select() y el resto de las funciones de {dplyr} siempre generan una nueva tabla y nunca modifican la tabla original. Para guardar la tabla con las tres columnas anio, pais y pbi_per_capita tenés que asignar el resultado a una nueva variable.

anio_pais_pbi <- select(paises, anio, pais, pib_per_capita)
anio_pais_pbi
## # A tibble: 1,704 x 3
##     anio pais       pib_per_capita
##    <dbl> <chr>               <dbl>
##  1  1952 Afganistán           779.
##  2  1957 Afganistán           821.
##  3  1962 Afganistán           853.
##  4  1967 Afganistán           836.
##  5  1972 Afganistán           740.
##  6  1977 Afganistán           786.
##  7  1982 Afganistán           978.
##  8  1987 Afganistán           852.
##  9  1992 Afganistán           649.
## 10  1997 Afganistán           635.
## # … with 1,694 more rows

Cómo funciona select()

Filtrando filas con filter()

Ahora podés usar filter() para quedarte con sólo unas filas. Por ejemplo, para ver los datos de Argentina:

filter(anio_pais_pbi, pais == "Argentina")
## # A tibble: 12 x 3
##     anio pais      pib_per_capita
##    <dbl> <chr>              <dbl>
##  1  1952 Argentina          5911.
##  2  1957 Argentina          6857.
##  3  1962 Argentina          7133.
##  4  1967 Argentina          8053.
##  5  1972 Argentina          9443.
##  6  1977 Argentina         10079.
##  7  1982 Argentina          8998.
##  8  1987 Argentina          9140.
##  9  1992 Argentina          9308.
## 10  1997 Argentina         10967.
## 11  2002 Argentina          8798.
## 12  2007 Argentina         12779.

La mayoría de los análisis consisten en varios pasos (en el primer desafío usaste 4 pasos para calcular las temperaturas medias de una serie de estaciones). La única tabla que te interesa es la última, por lo que ir asignando variables nuevas en cada paso intermedio es tedioso y poco práctico. Para eso se usa el operador ‘pipe’ (%>%).

El operador ‘pipe’ (%>%) agarra el resultado de una función y se lo pasa a la siguiente. Esto permite escribir el código como una cadena de funciones que van operando sobre el resultado de la anterior.

Las dos operaciones anteriores (seleccionar tres columnas y luego filtrar las filas correspondientes a Argentina) se pueden escribir uno después del otro y sin asignar los resultados intermedios a nuevas variables de esta forma:

paises %>% 
  select(anio, pais, pib_per_capita) %>% 
  filter(pais == "Argentina")
## # A tibble: 12 x 3
##     anio pais      pib_per_capita
##    <dbl> <chr>              <dbl>
##  1  1952 Argentina          5911.
##  2  1957 Argentina          6857.
##  3  1962 Argentina          7133.
##  4  1967 Argentina          8053.
##  5  1972 Argentina          9443.
##  6  1977 Argentina         10079.
##  7  1982 Argentina          8998.
##  8  1987 Argentina          9140.
##  9  1992 Argentina          9308.
## 10  1997 Argentina         10967.
## 11  2002 Argentina          8798.
## 12  2007 Argentina         12779.

La forma de “leer” esto es “Tomá la variable paises, después aplicale select(anio, pais, pib_per_capita), después aplicale filter(pais == "Argentina")”.

Cómo vimos, el primero argumento de todas las funciones de dplyr espera un data.frame y justamente el operador %>% toma el data.frame paises y se lo pasa al primer argumento de select(). Luego el data.frame resultante de seleccionar las columnas anio, país y pib_per_capita pasa como el primer argumento de la función filter() gracias al %>%.

Tip:

En RStudio podés escribir %>% usando el atajo de teclado Ctr + Shift + M. ¡Probalo!

Desafío:

Completá esta cadena para producir una tabla que contenga los valores de expectativa de vida, país y año únicamente para los países africanos.

paises %>% 
  filter(continente == ___) %>%
  select(___, ___, ___)

Agrupando y reduciendo con group_by() %>% summarise()

Si querés calcular la expectativa de vida media para cada continente, tenés que usar el combo group_by() %>% summarise(). Es decir, primero agrupar la tabla según la columna continente y luego calcular un promedio para cada grupo.

Para agrupar la tabla países según el continente usamos el siguiente código:

paises %>% 
  group_by(continente) 
## # A tibble: 1,704 x 6
## # Groups:   continente [5]
##    pais       continente  anio esperanza_de_vida poblacion pib_per_capita
##    <chr>      <chr>      <dbl>             <dbl>     <dbl>          <dbl>
##  1 Afganistán Asia        1952              28.8   8425333           779.
##  2 Afganistán Asia        1957              30.3   9240934           821.
##  3 Afganistán Asia        1962              32.0  10267083           853.
##  4 Afganistán Asia        1967              34.0  11537966           836.
##  5 Afganistán Asia        1972              36.1  13079460           740.
##  6 Afganistán Asia        1977              38.4  14880372           786.
##  7 Afganistán Asia        1982              39.9  12881816           978.
##  8 Afganistán Asia        1987              40.8  13867957           852.
##  9 Afganistán Asia        1992              41.7  16317921           649.
## 10 Afganistán Asia        1997              41.8  22227415           635.
## # … with 1,694 more rows

A primera vista parecería que la función no hizo nada, pero fijate que el resultado ahora dice que tiene grupos (“Groups:”), y nos dice qué columna es la que agrupa los datos (“continente”) y cuántos grupos hay (“5”). Las operaciones subsiguientes que le hagamos a esta tabla van a hacerse para cada grupo.

Para ver esto en acción, usá summarise() para computar el promedio de la esperanza de vida:

paises %>% 
  group_by(continente) %>% 
  summarise(esperanza_de_vida_media = mean(esperanza_de_vida))
## `summarise()` ungrouping output (override with `.groups` argument)
## # A tibble: 5 x 2
##   continente esperanza_de_vida_media
##   <chr>                        <dbl>
## 1 África                        48.9
## 2 Américas                      64.7
## 3 Asia                          60.1
## 4 Europa                        71.9
## 5 Oceanía                       74.3

¡Tadá! summarise() devuelve una tabla con una columna para el continente y otra nueva, llamada “esperanza_de_vida_media” que contiene el promedio de esperanza_de_vida para cada grupo. Esta operación es equivalente a esta tabla dinámica en Excel:

group_by() permite agrupar en base a múltiples columnas y summarise() permite generar múltiples columnas de resumen. El siguiente código calcula la esperanza de vida media y su desvío estándar para cada continente y cada año.

paises %>% 
  group_by(continente, anio) %>% 
  summarise(esperanza_de_vida_media = mean(esperanza_de_vida),
            esperanza_de_vida_sd = sd(esperanza_de_vida))
## `summarise()` regrouping output by 'continente' (override with `.groups` argument)
## # A tibble: 60 x 4
## # Groups:   continente [5]
##    continente  anio esperanza_de_vida_media esperanza_de_vida_sd
##    <chr>      <dbl>                   <dbl>                <dbl>
##  1 África      1952                    39.1                 5.15
##  2 África      1957                    41.3                 5.62
##  3 África      1962                    43.3                 5.88
##  4 África      1967                    45.3                 6.08
##  5 África      1972                    47.5                 6.42
##  6 África      1977                    49.6                 6.81
##  7 África      1982                    51.6                 7.38
##  8 África      1987                    53.3                 7.86
##  9 África      1992                    53.6                 9.46
## 10 África      1997                    53.6                 9.10
## # … with 50 more rows

El resultado va a siempre ser una tabla con la misma cantidad de filas que grupos y una cantidad de columnas igual a la cantidad de columnas usadas para agrupar y los estadísticos computados.

Desafío:

¿Cuál te imaginás que va a ser el resultado del siguiente código? ¿Cuántas filas y columnas va a tener? (Tratá de pensarlo antes de correrlo.)

paises %>% 
  summarise(esperanza_de_vida_media = mean(esperanza_de_vida))

El combo group_by() %>% summarise() se puede resumir en esta figura. Las filas de una tabla se dividen en grupos, y luego cada grupo se “resume” en una fila en función del estadístico usado.

Al igual que hicimos “cuentas” usando algunas variables numéricas para obtener información nueva, también podemos utilizar variables categóricas. No tiene sentido calcular mean(continente) ya que contienen caracteres, pero tal vez nos interese contar la cantidad de observaciones por continente:

paises %>% 
  group_by(continente) %>% 
  summarise(cantidad = n())
## `summarise()` ungrouping output (override with `.groups` argument)
## # A tibble: 5 x 2
##   continente cantidad
##   <chr>         <int>
## 1 África          624
## 2 Américas        300
## 3 Asia            396
## 4 Europa          360
## 5 Oceanía          24

Creando nuevas columnas con mutate()

Todo esto está bien para hacer cálculos con columnas previamente existentes, pero muchas veces tenés que crear nuevas columnas.

La tabla paises tiene información de PBI per cápita y de población, por lo que es posible computar el PBI de cada país multiplicando los valores de estas columnas. mutate() permite agregar una nueva columna llamada “pbi” con esa información:

paises %>% 
  mutate(pbi = pib_per_capita*poblacion)
## # A tibble: 1,704 x 7
##    pais     continente  anio esperanza_de_vi… poblacion pib_per_capita       pbi
##    <chr>    <chr>      <dbl>            <dbl>     <dbl>          <dbl>     <dbl>
##  1 Afganis… Asia        1952             28.8   8425333           779.   6.57e 9
##  2 Afganis… Asia        1957             30.3   9240934           821.   7.59e 9
##  3 Afganis… Asia        1962             32.0  10267083           853.   8.76e 9
##  4 Afganis… Asia        1967             34.0  11537966           836.   9.65e 9
##  5 Afganis… Asia        1972             36.1  13079460           740.   9.68e 9
##  6 Afganis… Asia        1977             38.4  14880372           786.   1.17e10
##  7 Afganis… Asia        1982             39.9  12881816           978.   1.26e10
##  8 Afganis… Asia        1987             40.8  13867957           852.   1.18e10
##  9 Afganis… Asia        1992             41.7  16317921           649.   1.06e10
## 10 Afganis… Asia        1997             41.8  22227415           635.   1.41e10
## # … with 1,694 more rows

Recordá que las funciones de {dplyr} nunca modifican la tabla original. mutate() devolvió una nueva tabla que es igual a la tabla paises pero con la columna “pbi” agregada. La tabla paises sigue intacta.

Desagrupando con ungroup()

En general, la mayoría de las funciones de {dplyr} “entienden” cuando una tabla está agrupada y realizan las operaciones para cada grupo.

Desafío:

¿Cuál de estos dos códigos agrega una columna llamada “pbi_continente” con el pbi promedio del continente correspondiente a cada país? ¿Qué hace el otro?

paises %>% 
  group_by(continente) %>% 
  mutate(pbi_continente = mean(pib_per_capita*poblacion)) 

paises %>% 
  mutate(pbi_continente = mean(pib_per_capita*poblacion)) 

En otras palabras, los resultados de mutate(), filter(), summarise() y otras funciones cambian según si la tabla está agrupada o no. Como a veces uno se puede olvidar que quedaron grupos, es conveniente usar la función ungroup() una vez que dejás de trabajar con grupos:

paises %>% 
  group_by(continente) %>% 
  mutate(pbi_continente = mean(pib_per_capita*poblacion)) %>% 
  ungroup()
## # A tibble: 1,704 x 7
##    pais  continente  anio esperanza_de_vi… poblacion pib_per_capita
##    <chr> <chr>      <dbl>            <dbl>     <dbl>          <dbl>
##  1 Afga… Asia        1952             28.8   8425333           779.
##  2 Afga… Asia        1957             30.3   9240934           821.
##  3 Afga… Asia        1962             32.0  10267083           853.
##  4 Afga… Asia        1967             34.0  11537966           836.
##  5 Afga… Asia        1972             36.1  13079460           740.
##  6 Afga… Asia        1977             38.4  14880372           786.
##  7 Afga… Asia        1982             39.9  12881816           978.
##  8 Afga… Asia        1987             40.8  13867957           852.
##  9 Afga… Asia        1992             41.7  16317921           649.
## 10 Afga… Asia        1997             41.8  22227415           635.
## # … with 1,694 more rows, and 1 more variable: pbi_continente <dbl>
LS0tCnRpdGxlOiAiTWFuaXB1bGFjacOzbiBkZSBkYXRvcyBvcmRlbmFkb3MgdXNhbmRvIHtkcGx5cn0gLSBQcmltZXJhIHBhcnRlIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogZmFsc2UKICAgIGhpZ2hsaWdodDogdGFuZ28KLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCgpFbCBwYXF1ZXRlIHtkcGx5cn0gcHJvdmVlIHVuYSBlbm9ybWUgY2FudGlkYWQgZGUgZnVuY2lvbmVzIMO6dGlsZXMgcGFyYSBtYW5pcHVsYXIgeSBhbmFsaXphciBkYXRvcyBkZSBtYW5lcmEgaW50dWl0aXZhIHkgZXhwcmVzaXZhLiAKCkVsIGVzcMOtcml0dSBkZXRyw6FzIGRlIHtkcGx5cn0gZXMgcXVlIGxhIGVub3JtZSBtYXlvcsOtYSBkZSBsb3MgYW7DoWxpc2lzLCBwb3IgbcOhcyBjb21wbGljYWRvcyBxdWUgc2VhbiwgaW52b2x1Y3JhbiBjb21iaW5hciB5IGVuY2FkZW5hciB1bmEgc2VyaWUgcmVsYXRpdmFtZW50ZSBhY290YWRhIGRlIGFjY2lvbmVzIChvIHZlcmJvcykuIEVuIGVzdGUgY3Vyc28gdmFtb3MgYSBjZW50cmFybm9zIGxhcyBjaW5jbyBtw6FzIGNvbXVuZXM6CgogICogYHNlbGVjdCgpYDogc2VsZWNjaW9uYSBjb2x1bW5hcyBkZSB1bmEgdGFibGEuCiAgKiBgZmlsdGVyKClgOiBzZWxlY2Npb25hIChvIGZpbHRyYSkgZmlsYXMgZGUgdW5hIHRhYmxhIGEgcGFydGlyIGRlIHVuYSBvIG3DoXMgY29uZGljaW9uZXMgbMOzZ2ljYXMuCiAgKiBgZ3JvdXBfYnkoKWA6IGFncnVwYSB1bmEgdGFibGEgZW4gYmFzZSBhbCB2YWxvciBkZSB1bmEgbyBtw6FzIGNvbHVtbmFzLgogICogYG11dGF0ZSgpYDogYWdyZWdhIG51ZXZhcyBjb2x1bW5hcyBhIHVuYSB0YWJsYS4KICAqIGBzdW1tYXJpc2UoKWA6IGNhbGN1bGEgZXN0YWTDrXN0aWNhcyBwYXJhIGNhZGEgZ3J1cG8gZGUgdW5hIHRhYmxhLgogIAoKOjo6IHsuYWxlcnQgLmFsZXJ0LXN1Y2Nlc3N9CioqZHBseXIgeSB0YWJsYXMgZGluw6FtaWNhczoqKgoKQSBncm9zc28gbW9kbywgbGFzIG9wZXJhY2lvbmVzIGRlIHtkcGx5cn0gcGVybWl0ZW4gaGFjZXIgZW4gUiBsbyBxdWUgc2UgaGFjZSBlbiB0YWJsYXMgZGluw6FtaWNhcyAocGl2b3QgdGFibGVzKSBlbiBFeGNlbC4gYGZpbHRlcigpYCB2ZW5kcsOtYSBhIHNlciBjb21vIGxhIHNlY2Npw7NuIGRlICJGaWx0cm9zIiwgYGdyb3VwX2J5KClgLCBsYSBzZWNjacOzbiBkZSAiRmlsYXMiLCBgc2VsZWN0KClgLCBsYSBzZWNjacOzbiBkZSAiQ29sdW1uYXMiIHkgYHN1bW1hcmlzZSgpYCwgbGEgcGFydGUgZGUgIlZhbG9yZXMiLiAKOjo6Cgo6Ojogey5hbGVydCAuYWxlcnQtaW5mb30KKipQcmltZXIgZGVzYWbDrW86KioKClRlIGRpZXJvbiB1bmEgdGFibGEgY29uIGRhdG9zIGRlIHRlbXBlcmF0dXJhIG3DrW5pbWEgeSBtw6F4aW1hIHBhcmEgZGlzdGludGFzIGVzdGFjaW9uZXMgbWV0ZW9yb2zDs2dpY2FzIGRlIHRvZG8gZWwgcGHDrXMgZHVyYW50ZSBsb3MgMzY1IGTDrWFzIGRlIHVuIGHDsW8uIExhcyBjb2x1bW5hcyBzb246IGBpZF9lc3RhY2lvbmAsIGB0ZW1wZXJhdHVyYV9tYXhpbWFgLCBgdGVtcGVyYXR1cmFfbWluaW1hYCB5IGBwcm92aW5jaWFgLiBFbiBiYXNlIGEgZXNvcyBkYXRvcywgdGUgcGlkZW4gcXVlIGNvbXB1dGVuIGxhIHRlbXBlcmF0dXJhIG1lZGlhIGFudWFsIGRlIGNhZGEgZXN0YWNpw7NuIMO6bmljYW1lbnRlIGRlIGxhcyBlc3RhY2lvbmVzIGRlIExhIFBhbXBhLiAKCsK/RW4gcXXDqSBvcmRlbiBlamVjdXRhcsOtYXMgZXN0b3MgcGFzb3MgcGFyYSBvYnRlbmVyIGVsIHJlc3VsdGFkbyBkZXNlYWRvPwoKKiB1c2FyIGBzdW1tYXJpc2UoKWAgcGFyYSBjYWxjdWxhciBsYSBlc3RhZMOtc3RpY2EgYG1lYW4odGVtcGVyYXR1cmFfbWVkaWEpYCBwYXJhIGNhZGEgYGlkX2VzdGFjaW9uYAoqIHVzYXIgYGdyb3VwX2J5KClgIHBhcmEgYWdydXBhciBwb3IgbGEgY29sdW1uYSBgaWRfZXN0YWNpb25gCiogdXNhciBgbXV0YXRlKClgIHBhcmEgYWdyZWdhciB1bmEgY29sdW1uYSBsbGFtYWRhIGB0ZW1wZXJhdHVyYV9tZWRpYWAgcXVlIHNlYSBgKHRlbXBlcmF0dXJhX21pbmltYSArIHRlbXBlcmF0dXJhX21heGltYSkvMmAuCiogdXNhciBgZmlsdGVyKClgIHBhcmEgc2VsZWNjaW9uYXIgc29sbyBsYXMgZmlsYXMgZG9uZGUgbGEgY29sdW1uYXMgYHByb3ZpbmNpYWAgZXMgaWd1YWwgYSAiTGEgUGFtcGEiCjo6OgoKUGFyYSB1c2FyIHtkcGx5cn0gcHJpbWVybyBoYXkgcXVlIGluc3RhbGFybG8gKGVzdG8gaGF5IHF1ZSBoYWNlcmxvIHVuYSBzb2xhIHZleikgY29uIGVsIGNvbWFuZG86CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQppbnN0YWxsLnBhY2thZ2VzKCdkcGx5cicpCmBgYAoKeSBsdWVnbyBjYXJnYXJsbyBlbiBtZW1vcmlhIGNvbgoKYGBge3J9CmxpYnJhcnkoZHBseXIpCmBgYAoKVm9sdsOpIGEgY2FyZ2FyIGxvcyBkYXRvcyBkZSBwYcOtc2VzIChwYXJhIHVuIHJlY29yZGF0b3JpbywgcG9kw6lzIGlyIGEgW0xlY3R1cmEgZGUgZGF0b3Mgb3JkZW5hZG9zXSgwNC1sZWN0dXJhLWRhdG9zLmh0bWwpKToKCmBgYHtyfQpsaWJyYXJ5KHJlYWRyKQpwYWlzZXMgPC0gcmVhZF9jc3YoImRhdG9zL3BhaXNlcy5jc3YiKQpgYGAKCiMjIFNlbGVjY2lvbmFuZG8gY29sdW1uYXMgY29uIGBzZWxlY3QoKWAKClBhcmEgcXVlZGFyc2Ugw7puaWNhbWVudGUgY29uIGxhcyBjb2x1bW5hcyBkZSBhw7FvLCBwYcOtcyB5IFBCSSBwZXIgY8OhcGl0YSwgdXPDoSBgc2VsZWN0KClgCiAgCmBgYHtyfQpzZWxlY3QocGFpc2VzLCBhbmlvLCBwYWlzLCBwaWJfcGVyX2NhcGl0YSkKYGBgCgrCv0TDs25kZSBxdWVkw7MgZXN0ZSByZXN1bHRhZG8/IFNpIHRlIGZpasOhcyBlbiBsYSB2YXJpYWJsZSBgcGFpc2VzYCwgw6lzdGEgZXN0w6EgaW50YWN0YToKCmBgYHtyfQpwYWlzZXMKYGBgCgpgc2VsZWN0KClgIHkgZWwgcmVzdG8gZGUgbGFzIGZ1bmNpb25lcyBkZSB7ZHBseXJ9IHNpZW1wcmUgZ2VuZXJhbiB1bmEgbnVldmEgdGFibGEgeSBudW5jYSBtb2RpZmljYW4gbGEgdGFibGEgb3JpZ2luYWwuIFBhcmEgZ3VhcmRhciBsYSB0YWJsYSBjb24gbGFzIHRyZXMgY29sdW1uYXMgYGFuaW9gLCBgcGFpc2AgeSBgcGJpX3Blcl9jYXBpdGFgIHRlbsOpcyBxdWUgYXNpZ25hciBlbCByZXN1bHRhZG8gYSB1bmEgbnVldmEgdmFyaWFibGUuCgpgYGB7cn0KYW5pb19wYWlzX3BiaSA8LSBzZWxlY3QocGFpc2VzLCBhbmlvLCBwYWlzLCBwaWJfcGVyX2NhcGl0YSkKYW5pb19wYWlzX3BiaQpgYGAKCiFbQ8OzbW8gZnVuY2lvbmEgYHNlbGVjdCgpYF0oaW1nL2RwbHlyLXNlbGVjdC5wbmcpCgojIyBGaWx0cmFuZG8gZmlsYXMgY29uIGBmaWx0ZXIoKWAKCkFob3JhIHBvZMOpcyB1c2FyIGBmaWx0ZXIoKWAgcGFyYSBxdWVkYXJ0ZSBjb24gc8OzbG8gdW5hcyBmaWxhcy4gUG9yIGVqZW1wbG8sIHBhcmEgdmVyIGxvcyBkYXRvcyBkZSBBcmdlbnRpbmE6CgpgYGB7cn0KZmlsdGVyKGFuaW9fcGFpc19wYmksIHBhaXMgPT0gIkFyZ2VudGluYSIpCmBgYAoKTGEgbWF5b3LDrWEgZGUgbG9zIGFuw6FsaXNpcyBjb25zaXN0ZW4gZW4gdmFyaW9zIHBhc29zIChlbiBlbCBwcmltZXIgZGVzYWbDrW8gdXNhc3RlIDQgcGFzb3MgcGFyYSBjYWxjdWxhciBsYXMgdGVtcGVyYXR1cmFzIG1lZGlhcyBkZSB1bmEgc2VyaWUgZGUgZXN0YWNpb25lcykuIExhIMO6bmljYSB0YWJsYSBxdWUgdGUgaW50ZXJlc2EgZXMgbGEgw7psdGltYSwgcG9yIGxvIHF1ZSBpciBhc2lnbmFuZG8gdmFyaWFibGVzIG51ZXZhcyBlbiBjYWRhIHBhc28gaW50ZXJtZWRpbyBlcyB0ZWRpb3NvIHkgcG9jbyBwcsOhY3RpY28uIFBhcmEgZXNvIHNlIHVzYSBlbCBvcGVyYWRvciAncGlwZScgKGAlPiVgKS4gCgpFbCBvcGVyYWRvciAncGlwZScgKGAlPiVgKSBhZ2FycmEgZWwgcmVzdWx0YWRvIGRlIHVuYSBmdW5jacOzbiB5IHNlIGxvIHBhc2EgYSBsYSBzaWd1aWVudGUuIEVzdG8gcGVybWl0ZSBlc2NyaWJpciBlbCBjw7NkaWdvIGNvbW8gdW5hIGNhZGVuYSBkZSBmdW5jaW9uZXMgcXVlIHZhbiBvcGVyYW5kbyBzb2JyZSBlbCByZXN1bHRhZG8gZGUgbGEgYW50ZXJpb3IuCgpMYXMgZG9zIG9wZXJhY2lvbmVzIGFudGVyaW9yZXMgKHNlbGVjY2lvbmFyIHRyZXMgY29sdW1uYXMgeSBsdWVnbyBmaWx0cmFyIGxhcyBmaWxhcyBjb3JyZXNwb25kaWVudGVzIGEgQXJnZW50aW5hKSBzZSBwdWVkZW4gZXNjcmliaXIgdW5vIGRlc3B1w6lzIGRlbCBvdHJvIHkgc2luIGFzaWduYXIgbG9zIHJlc3VsdGFkb3MgaW50ZXJtZWRpb3MgYSBudWV2YXMgdmFyaWFibGVzIGRlIGVzdGEgZm9ybWE6CgpgYGB7cn0KcGFpc2VzICU+JSAKICBzZWxlY3QoYW5pbywgcGFpcywgcGliX3Blcl9jYXBpdGEpICU+JSAKICBmaWx0ZXIocGFpcyA9PSAiQXJnZW50aW5hIikKYGBgCgpMYSBmb3JtYSBkZSAibGVlciIgZXN0byBlcyAiVG9tw6EgbGEgdmFyaWFibGUgYHBhaXNlc2AsICoqZGVzcHXDqXMqKiBhcGxpY2FsZSBgc2VsZWN0KGFuaW8sIHBhaXMsIHBpYl9wZXJfY2FwaXRhKWAsICoqZGVzcHXDqXMqKiBhcGxpY2FsZSBgZmlsdGVyKHBhaXMgPT0gIkFyZ2VudGluYSIpYCIuIAoKQ8OzbW8gdmltb3MsIGVsIHByaW1lcm8gYXJndW1lbnRvIGRlIHRvZGFzIGxhcyBmdW5jaW9uZXMgZGUgZHBseXIgZXNwZXJhIHVuIGRhdGEuZnJhbWUgeSBqdXN0YW1lbnRlIGVsIG9wZXJhZG9yIGAlPiVgIHRvbWEgZWwgZGF0YS5mcmFtZSBgcGFpc2VzYCB5IHNlIGxvIHBhc2EgYWwgcHJpbWVyIGFyZ3VtZW50byBkZSBgc2VsZWN0KClgLiBMdWVnbyBlbCBkYXRhLmZyYW1lIHJlc3VsdGFudGUgZGUgc2VsZWNjaW9uYXIgbGFzIGNvbHVtbmFzIGFuaW8sIHBhw61zIHkgcGliX3Blcl9jYXBpdGEgKnBhc2EqIGNvbW8gZWwgcHJpbWVyIGFyZ3VtZW50byBkZSBsYSBmdW5jacOzbiBgZmlsdGVyKClgIGdyYWNpYXMgYWwgYCU+JWAuCgoKOjo6IHsuYWxlcnQgLmFsZXJ0LXN1Y2Nlc3N9CioqVGlwOioqCgpFbiBSU3R1ZGlvIHBvZMOpcyBlc2NyaWJpciBgJT4lYCB1c2FuZG8gZWwgYXRham8gZGUgdGVjbGFkbyBDdHIgKyBTaGlmdCArIE0uIMKhUHJvYmFsbyEKOjo6CgoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqRGVzYWbDrW86KioKCkNvbXBsZXTDoSBlc3RhIGNhZGVuYSBwYXJhIHByb2R1Y2lyIHVuYSB0YWJsYSBxdWUgY29udGVuZ2EgbG9zIHZhbG9yZXMgZGUgZXhwZWN0YXRpdmEgZGUgdmlkYSwgcGHDrXMgeSBhw7FvIMO6bmljYW1lbnRlIHBhcmEgbG9zIHBhw61zZXMgYWZyaWNhbm9zLiAKCmBgYHtyLCBldmFsID0gRkFMU0V9CnBhaXNlcyAlPiUgCiAgZmlsdGVyKGNvbnRpbmVudGUgPT0gX19fKSAlPiUKICBzZWxlY3QoX19fLCBfX18sIF9fXykKYGBgCgo6OjoKCiMjIEFncnVwYW5kbyB5IHJlZHVjaWVuZG8gY29uIGBncm91cF9ieSgpICU+JSBzdW1tYXJpc2UoKWAKClNpIHF1ZXLDqXMgY2FsY3VsYXIgbGEgZXhwZWN0YXRpdmEgZGUgdmlkYSBtZWRpYSBwYXJhIGNhZGEgY29udGluZW50ZSwgdGVuw6lzIHF1ZSB1c2FyIGVsIGNvbWJvIGBncm91cF9ieSgpICU+JSBzdW1tYXJpc2UoKWAuIEVzIGRlY2lyLCBwcmltZXJvIGFncnVwYXIgbGEgdGFibGEgc2Vnw7puIGxhIGNvbHVtbmEgY29udGluZW50ZSB5IGx1ZWdvIGNhbGN1bGFyIHVuIHByb21lZGlvIHBhcmEgY2FkYSBncnVwby4KClBhcmEgYWdydXBhciBsYSB0YWJsYSBwYcOtc2VzIHNlZ8O6biBlbCBjb250aW5lbnRlIHVzYW1vcyBlbCBzaWd1aWVudGUgY8OzZGlnbzoKCmBgYHtyfQpwYWlzZXMgJT4lIAogIGdyb3VwX2J5KGNvbnRpbmVudGUpIApgYGAKCkEgcHJpbWVyYSB2aXN0YSBwYXJlY2Vyw61hIHF1ZSBsYSBmdW5jacOzbiBubyBoaXpvIG5hZGEsIHBlcm8gZmlqYXRlIHF1ZSBlbCByZXN1bHRhZG8gYWhvcmEgZGljZSBxdWUgdGllbmUgZ3J1cG9zICgiR3JvdXBzOiAiKSwgeSBub3MgZGljZSBxdcOpIGNvbHVtbmEgZXMgbGEgcXVlIGFncnVwYSBsb3MgZGF0b3MgKCJjb250aW5lbnRlIikgeSBjdcOhbnRvcyBncnVwb3MgaGF5ICgiNSIpLiBMYXMgb3BlcmFjaW9uZXMgc3Vic2lndWllbnRlcyBxdWUgbGUgaGFnYW1vcyBhIGVzdGEgdGFibGEgdmFuIGEgaGFjZXJzZSAqcGFyYSBjYWRhIGdydXBvKi4gCgpQYXJhIHZlciBlc3RvIGVuIGFjY2nDs24sIHVzw6EgYHN1bW1hcmlzZSgpYCBwYXJhIGNvbXB1dGFyIGVsIHByb21lZGlvIGRlIGxhIGVzcGVyYW56YSBkZSB2aWRhOgoKYGBge3J9CnBhaXNlcyAlPiUgCiAgZ3JvdXBfYnkoY29udGluZW50ZSkgJT4lIAogIHN1bW1hcmlzZShlc3BlcmFuemFfZGVfdmlkYV9tZWRpYSA9IG1lYW4oZXNwZXJhbnphX2RlX3ZpZGEpKQpgYGAKCsKhVGFkw6EhIGBzdW1tYXJpc2UoKWAgZGV2dWVsdmUgdW5hIHRhYmxhIGNvbiB1bmEgY29sdW1uYSBwYXJhIGVsIGNvbnRpbmVudGUgeSBvdHJhIG51ZXZhLCBsbGFtYWRhICJlc3BlcmFuemFfZGVfdmlkYV9tZWRpYSIgcXVlIGNvbnRpZW5lIGVsIHByb21lZGlvIGRlIGBlc3BlcmFuemFfZGVfdmlkYWAgcGFyYSBjYWRhIGdydXBvLiBFc3RhIG9wZXJhY2nDs24gZXMgZXF1aXZhbGVudGUgYSBlc3RhIHRhYmxhIGRpbsOhbWljYSBlbiBFeGNlbDoKCjxmaWd1cmU+Cjx2aWRlbyB3aWR0aD03NzBweCBjb250cm9scz4KICA8c291cmNlIHNyYz0iaW1nL3Bpdm90LXZpZC53ZWJtIiB0eXBlPSJ2aWRlby93ZWJtIj4KPC92aWRlbz4KPC9maWd1cmU+CgpgZ3JvdXBfYnkoKWAgcGVybWl0ZSBhZ3J1cGFyIGVuIGJhc2UgYSBtw7psdGlwbGVzIGNvbHVtbmFzIHkgYHN1bW1hcmlzZSgpYCBwZXJtaXRlIGdlbmVyYXIgbcO6bHRpcGxlcyBjb2x1bW5hcyBkZSByZXN1bWVuLiBFbCBzaWd1aWVudGUgY8OzZGlnbyBjYWxjdWxhIGxhIGVzcGVyYW56YSBkZSB2aWRhIG1lZGlhIHkgc3UgZGVzdsOtbyBlc3TDoW5kYXIgcGFyYSBjYWRhIGNvbnRpbmVudGUgeSBjYWRhIGHDsW8uCgpgYGB7cn0KcGFpc2VzICU+JSAKICBncm91cF9ieShjb250aW5lbnRlLCBhbmlvKSAlPiUgCiAgc3VtbWFyaXNlKGVzcGVyYW56YV9kZV92aWRhX21lZGlhID0gbWVhbihlc3BlcmFuemFfZGVfdmlkYSksCiAgICAgICAgICAgIGVzcGVyYW56YV9kZV92aWRhX3NkID0gc2QoZXNwZXJhbnphX2RlX3ZpZGEpKQpgYGAKCkVsIHJlc3VsdGFkbyB2YSBhIHNpZW1wcmUgc2VyIHVuYSB0YWJsYSBjb24gbGEgbWlzbWEgY2FudGlkYWQgZGUgZmlsYXMgcXVlIGdydXBvcyB5IHVuYSBjYW50aWRhZCBkZSBjb2x1bW5hcyBpZ3VhbCBhIGxhIGNhbnRpZGFkIGRlIGNvbHVtbmFzIHVzYWRhcyBwYXJhIGFncnVwYXIgeSBsb3MgZXN0YWTDrXN0aWNvcyBjb21wdXRhZG9zLiAKCjo6OiB7LmFsZXJ0IC5hbGVydC1pbmZvfQoqKkRlc2Fmw61vOioqCgrCv0N1w6FsIHRlIGltYWdpbsOhcyBxdWUgdmEgYSBzZXIgZWwgcmVzdWx0YWRvIGRlbCBzaWd1aWVudGUgY8OzZGlnbz8gwr9DdcOhbnRhcyBmaWxhcyB5IGNvbHVtbmFzIHZhIGEgdGVuZXI/IChUcmF0w6EgZGUgcGVuc2FybG8gYW50ZXMgZGUgY29ycmVybG8uKQoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcGFpc2VzICU+JSAKICBzdW1tYXJpc2UoZXNwZXJhbnphX2RlX3ZpZGFfbWVkaWEgPSBtZWFuKGVzcGVyYW56YV9kZV92aWRhKSkKYGBgCjo6OgoKRWwgY29tYm8gYGdyb3VwX2J5KCkgJT4lIHN1bW1hcmlzZSgpYCBzZSBwdWVkZSByZXN1bWlyIGVuIGVzdGEgZmlndXJhLiBMYXMgZmlsYXMgZGUgdW5hIHRhYmxhIHNlIGRpdmlkZW4gZW4gZ3J1cG9zLCB5IGx1ZWdvIGNhZGEgZ3J1cG8gc2UgInJlc3VtZSIgZW4gdW5hIGZpbGEgZW4gZnVuY2nDs24gZGVsIGVzdGFkw61zdGljbyB1c2Fkby4gCgohW10oaW1nL2dyb3VwX2J5LXN1bW1hcml6ZS5wbmcpCgpBbCBpZ3VhbCBxdWUgaGljaW1vcyAiY3VlbnRhcyIgdXNhbmRvIGFsZ3VuYXMgdmFyaWFibGVzIG51bcOpcmljYXMgcGFyYSBvYnRlbmVyIGluZm9ybWFjacOzbiBudWV2YSwgdGFtYmnDqW4gcG9kZW1vcyB1dGlsaXphciB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzLiBObyB0aWVuZSBzZW50aWRvIGNhbGN1bGFyIGBtZWFuKGNvbnRpbmVudGUpYCB5YSBxdWUgY29udGllbmVuIGNhcmFjdGVyZXMsIHBlcm8gdGFsIHZleiBub3MgaW50ZXJlc2UgKmNvbnRhciogbGEgY2FudGlkYWQgZGUgb2JzZXJ2YWNpb25lcyBwb3IgY29udGluZW50ZToKCmBgYHtyfQpwYWlzZXMgJT4lIAogIGdyb3VwX2J5KGNvbnRpbmVudGUpICU+JSAKICBzdW1tYXJpc2UoY2FudGlkYWQgPSBuKCkpCmBgYAoKCiMjIENyZWFuZG8gbnVldmFzIGNvbHVtbmFzIGNvbiBgbXV0YXRlKClgCgpUb2RvIGVzdG8gZXN0w6EgYmllbiBwYXJhIGhhY2VyIGPDoWxjdWxvcyBjb24gY29sdW1uYXMgcHJldmlhbWVudGUgZXhpc3RlbnRlcywgcGVybyBtdWNoYXMgdmVjZXMgdGVuw6lzIHF1ZSBjcmVhciBudWV2YXMgY29sdW1uYXMuIAoKTGEgdGFibGEgYHBhaXNlc2AgdGllbmUgaW5mb3JtYWNpw7NuIGRlIFBCSSBwZXIgY8OhcGl0YSB5IGRlIHBvYmxhY2nDs24sIHBvciBsbyBxdWUgZXMgcG9zaWJsZSBjb21wdXRhciBlbCBQQkkgZGUgY2FkYSBwYcOtcyBtdWx0aXBsaWNhbmRvIGxvcyB2YWxvcmVzIGRlIGVzdGFzIGNvbHVtbmFzLiBgbXV0YXRlKClgIHBlcm1pdGUgYWdyZWdhciB1bmEgbnVldmEgY29sdW1uYSBsbGFtYWRhICJwYmkiIGNvbiBlc2EgaW5mb3JtYWNpw7NuOgoKYGBge3J9CnBhaXNlcyAlPiUgCiAgbXV0YXRlKHBiaSA9IHBpYl9wZXJfY2FwaXRhKnBvYmxhY2lvbikKYGBgCgpSZWNvcmTDoSBxdWUgbGFzIGZ1bmNpb25lcyBkZSB7ZHBseXJ9IG51bmNhIG1vZGlmaWNhbiBsYSB0YWJsYSBvcmlnaW5hbC4gYG11dGF0ZSgpYCBkZXZvbHZpw7MgdW5hIG51ZXZhIHRhYmxhIHF1ZSBlcyBpZ3VhbCBhIGxhIHRhYmxhIGBwYWlzZXNgIHBlcm8gY29uIGxhIGNvbHVtbmEgInBiaSIgYWdyZWdhZGEuIExhIHRhYmxhIGBwYWlzZXNgIHNpZ3VlIGludGFjdGEuCgojIyBEZXNhZ3J1cGFuZG8gY29uIGB1bmdyb3VwKClgCgpFbiBnZW5lcmFsLCBsYSBtYXlvcsOtYSBkZSBsYXMgZnVuY2lvbmVzIGRlIHtkcGx5cn0gImVudGllbmRlbiIgY3VhbmRvIHVuYSB0YWJsYSBlc3TDoSBhZ3J1cGFkYSB5IHJlYWxpemFuIGxhcyBvcGVyYWNpb25lcyBwYXJhIGNhZGEgZ3J1cG8uIAoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqRGVzYWbDrW86KioKCsK/Q3XDoWwgZGUgZXN0b3MgZG9zIGPDs2RpZ29zIGFncmVnYSB1bmEgY29sdW1uYSBsbGFtYWRhICJwYmlfY29udGluZW50ZSIgY29uIGVsIHBiaSBwcm9tZWRpbyBkZWwgY29udGluZW50ZSBjb3JyZXNwb25kaWVudGUgYSBjYWRhIHBhw61zPyDCv1F1w6kgaGFjZSBlbCBvdHJvPwoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcGFpc2VzICU+JSAKICBncm91cF9ieShjb250aW5lbnRlKSAlPiUgCiAgbXV0YXRlKHBiaV9jb250aW5lbnRlID0gbWVhbihwaWJfcGVyX2NhcGl0YSpwb2JsYWNpb24pKSAKCnBhaXNlcyAlPiUgCiAgbXV0YXRlKHBiaV9jb250aW5lbnRlID0gbWVhbihwaWJfcGVyX2NhcGl0YSpwb2JsYWNpb24pKSAKYGBgCjo6OgoKRW4gb3RyYXMgcGFsYWJyYXMsIGxvcyByZXN1bHRhZG9zIGRlIGBtdXRhdGUoKWAsIGBmaWx0ZXIoKWAsIGBzdW1tYXJpc2UoKWAgeSBvdHJhcyBmdW5jaW9uZXMgY2FtYmlhbiBzZWfDum4gc2kgbGEgdGFibGEgZXN0w6EgYWdydXBhZGEgbyBuby4gQ29tbyBhIHZlY2VzIHVubyBzZSBwdWVkZSBvbHZpZGFyIHF1ZSBxdWVkYXJvbiBncnVwb3MsIGVzIGNvbnZlbmllbnRlIHVzYXIgbGEgZnVuY2nDs24gYHVuZ3JvdXAoKWAgdW5hIHZleiBxdWUgZGVqw6FzIGRlIHRyYWJhamFyIGNvbiBncnVwb3M6CgpgYGB7cn0KcGFpc2VzICU+JSAKICBncm91cF9ieShjb250aW5lbnRlKSAlPiUgCiAgbXV0YXRlKHBiaV9jb250aW5lbnRlID0gbWVhbihwaWJfcGVyX2NhcGl0YSpwb2JsYWNpb24pKSAlPiUgCiAgdW5ncm91cCgpCmBgYAoKCgoKPGRpdiBjbGFzcz0iYnRuLWdyb3VwIiByb2xlPSJncm91cCIgYXJpYS1sYWJlbD0iTmF2ZWdhY2nDs24iPgogIDxhIGhyZWY9ICIwNC1sZWN0dXJhLWRhdG9zLmh0bWwiIGNsYXNzID0gImJ0biBidG4tcHJpbWFyeSI+QW50ZXJpb3I8L2E+CiAgPGEgaHJlZj0gIjA2LWdyYWZpY29zLUkuaHRtbCIgY2xhc3MgPSAiYnRuIGJ0bi1wcmltYXJ5Ij5TaWd1aWVudGU8L2E+CjwvZGl2Pg==