Un mismo conjunto de datos puede estar en un formato “ancho” o “largo”. Los datos en formato “largo” o “tidy”, son aquellos en los cuales:

  • cada fila es una observación
  • cada columna es una variable

En el formato “ancho” es un poco más complejo de definirlo pero la idea general es que:

  • cada fila es un “ítem”
  • cada columna es una variable

Una tabla en formato largo va a tener una cierta cantidad de columnas que cumplen el rol de identificadores y cuya combinación identifican una única observación y una única columna con el valor de la observación. En el ejemplo de arriba, pais y anio son las columnas identificadoras y casos es la columna que contiene el valor de las observaciones.

En una tabla ancha, cada observación única se identifica a partir de la intersección de filas y columnas. En el ejemplo, los países están en las filas y los años en las columnas.

En general, el formato ancho es más compacto y legible por humanos mientras que el largo es más fácil de manejar con la computadora. Si te fijás en las tablas de arriba, es más fácil comparar los valores entre países y entre años en la tabla ancha. Pero el nombre de las columnas (“1999”, “2000”) en realidad ¡son datos! Además este formato se empieza a complicar en cuanto hay más de dos identificadores.

Un mismo set de datos puede ser representado de forma completamente “larga”, completamente “ancha” o –lo que es más común– en un formato intermedio pero no existe una forma “correcta” de organizar los datos; cada una tiene sus ventajas y desventajas. Por esto es que es muy normal que durante un análisis los datos vayan y vuelvan entre distintos formatos dependiendo de los métodos estadísticos que se le aplican. Entonces, aprender a transformar datos anchos en largos y viceversa es un habilidad muy útil.

Desafío

En las tablas de ejemplo cada país tiene el un valor observado de “casos” para cada año. ¿Cómo agregarías una nueva variable con información sobre “precios”? Dibujá un esquema en papel y lápiz en formato ancho y uno en formato largo. ¿En qué formato es más “natural” esa extensión?

En esta sección vas a usar el paquete {tidyr} para manipular datos. Si no lo tenés instalado, instalalo con el comando:

install.packages("tidyr")

Y luego cargá {tidyr} y {dplyr} (que usaste en una sección anterior) con:

library(tidyr)
library(dplyr)

De ancho a largo con pivot_longer()

En secciones anteriores usaste datos de una estación meteorológica en Bariloche:

bariloche <- readr::read_csv("datos/bariloche_enlimpio.csv")
## Rows: 3024 Columns: 31
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr   (3): Direccion_Viento_200cm, Direccion_Viento_1000cm, mes
## dbl  (13): Temperatura_Abrigo_150cm, Temperatura_Abrigo_150cm_Maxima, Temper...
## lgl  (14): Temperatura_Intemperie_5cm_Minima, Temperatura_Intemperie_50cm_Mi...
## dttm  (1): Fecha
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
bariloche
## # A tibble: 3,024 × 31
##    Fecha               Temperatura_Abrigo_150… Temperatura_Abr… Temperatura_Abr…
##    <dttm>                                <dbl>            <dbl>            <dbl>
##  1 2011-05-28 00:00:00                   NA                NA               NA  
##  2 2011-05-29 00:00:00                    4.27             12.8             -1.2
##  3 2011-05-30 00:00:00                    1.69              9.2             -2.8
##  4 2011-05-31 00:00:00                    3.47              9.6              0.5
##  5 2011-06-01 00:00:00                    2.90              9.6             -1.5
##  6 2011-06-02 00:00:00                    6.26             13.3              2.9
##  7 2011-06-03 00:00:00                    3.45             12.6             -1.8
##  8 2011-06-04 00:00:00                    6.09             10.2              3.9
##  9 2011-06-05 00:00:00                    5.00              7.1              3.4
## 10 2011-06-06 00:00:00                    5.45              9.5              3.2
## # … with 3,014 more rows, and 27 more variables:
## #   Temperatura_Intemperie_5cm_Minima <lgl>,
## #   Temperatura_Intemperie_50cm_Minima <lgl>,
## #   Temperatura_Suelo_5cm_Media <lgl>, Temperatura_Suelo_10cm_Media <dbl>,
## #   Temperatura_Inte_5cm <lgl>, Temperatura_Intemperie_150cm_Minima <lgl>,
## #   Humedad_Suelo <lgl>, Precipitacion_Pluviometrica <dbl>,
## #   Precipitacion_Cronologica <dbl>, Precipitacion_Maxima_30minutos <dbl>, …

¿Notaste que en el código anterior no usaste library(readr) para cargar el paquete y luego leer? Con la notación paquete::funcion() podés acceder a las funciones de un paquete sin tener que cargarlo. Es una buena forma de no tener que cargar un montón de paquetes innecesarios si vas a correr una única función de un paquete pocas veces.

¿Cómo harías para calcular el valor medio mensual de cada variable relacionada con la temperatura? En el formato en el que están ahora los datos, tendrías que hacer algo como esto

bariloche %>% 
  group_by(mes) %>% 
  summarise(Temperatura_abrigo_150cm = mean(Temperatura_Abrigo_150cm, na.rm = TRUE),
            Temperatura_Abrigo_150cm_Maxima = mean(Temperatura_Abrigo_150cm_Maxima, na.rm = TRUE), 
            Temperatura_Abrigo_150cm_Minima = mean(Temperatura_Abrigo_150cm_Minima, na.rm = TRUE))
## # A tibble: 12 × 4
##    mes        Temperatura_abrigo_150cm Temperatura_Abrigo_150c… Temperatura_Abr…
##    <chr>                         <dbl>                    <dbl>            <dbl>
##  1 abril                         10.5                     17.4             5.16 
##  2 agosto                         4.83                    10.0             0.937
##  3 diciembre                     14.0                     20.6             7.90 
##  4 enero                         16.6                     23.8             9.74 
##  5 febrero                       16.0                     23.6             9.13 
##  6 julio                          3.69                     8.29            0.220
##  7 junio                          5.18                     9.17            1.99 
##  8 marzo                         13.8                     21.8             6.77 
##  9 mayo                           7.34                    12.5             3.35 
## 10 noviembre                     11.6                     18.0             5.85 
## 11 octubre                        8.59                    15.1             3.19 
## 12 septiembre                     6.43                    12.6             1.66

Desafío

Extendé el código de arriba para calcular el promedio de todas las 30 variables incluidas en la tabla bariloche.

¡No! Sería un ejercicio altamente tedioso.

Para no tener que escribir 30 líneas todas iguales, sería mejor poder hacer un promedio para cada mes y para cada variable. Algo como esto:

bariloche %>% 
  group_by(mes, variable) %>% 
  summarise(promedio = mean(valor, na.rm = TRUE))

Para poder hacer eso hay que tener los datos en un formato más largo. Para convertirlo en una tabla más larga, se usa pivot_longer() (“longer” es “más largo” en inglés):

bariloche_largo <- bariloche %>% 
  select(Fecha, starts_with("Temperatura")) %>% 
  pivot_longer(cols = -Fecha,
               names_to = "variable_lugar_altura_tipo",
               values_to = "valor")

bariloche_largo
## # A tibble: 27,216 × 3
##    Fecha               variable_lugar_altura_tipo          valor
##    <dttm>              <chr>                               <dbl>
##  1 2011-05-28 00:00:00 Temperatura_Abrigo_150cm            NA   
##  2 2011-05-28 00:00:00 Temperatura_Abrigo_150cm_Maxima     NA   
##  3 2011-05-28 00:00:00 Temperatura_Abrigo_150cm_Minima     NA   
##  4 2011-05-28 00:00:00 Temperatura_Intemperie_5cm_Minima   NA   
##  5 2011-05-28 00:00:00 Temperatura_Intemperie_50cm_Minima  NA   
##  6 2011-05-28 00:00:00 Temperatura_Suelo_5cm_Media         NA   
##  7 2011-05-28 00:00:00 Temperatura_Suelo_10cm_Media        NA   
##  8 2011-05-28 00:00:00 Temperatura_Inte_5cm                NA   
##  9 2011-05-28 00:00:00 Temperatura_Intemperie_150cm_Minima NA   
## 10 2011-05-29 00:00:00 Temperatura_Abrigo_150cm             4.27
## # … with 27,206 more rows

Lo primero que hace este código es seleccionar sólo las columnas de interés: la Fecha y las columnas de temperatura. Para no tener que escribir los nombres enteros de las columnas de temperatura (¡que son muy largos!) usa la función starts_with() que, como su nombre (en inglés) lo indica, selecciona todas las columnas que empiezan con “Temperatura”.

Existen otras funciones accesorias para seleccionar muchas funciones encapsuladas en el paquete “tidyselect”. Si querés leer más detalles de las distintas formas que podés seleccionar variables leé la documentación usando ?tidyselect::language.

Luego, el código usa pivot_longer() para “alargar” la tabla es la tabla que va a modificar: bariloche. El segundo argumento se llama cols y es un vector con las columnas que tienen los valores a “alargar”. Podría ser un vector escrito a mano (algo como c("Temperatura_Abrigo_150cm", "Temperatura_Abrigo_150cm_Maxima"...)) pero con más de 30 columnas, escribir todo eso sería tedioso y probablemente estaría lleno de errores. El código de arriba usa la sintaxis de “-columna” para indicar que son todas las columnas menos la columnas “Fecha”.

El tercer y cuarto argumento son los nombres de las columnas de “nombre” y de “valor” que va a tener la nueva tabla. Como la nueva columna de identificación tiene los datos de la variable, el lugar donde se mide, la altura y el tipo de variable (mínima, máxima) , “variable_lugar_altura_tipo” es un buen nombre. Y la columna de valor va a tener… bueno, el valor.

Tomate un momento para visualizar lo que acaba de pasar. La tabla ancha tenía un montón de columnas con distintos datos. Ahora estos datos están uno arriba de otro en la columna “valor”, pero para identificar el nombre de la columna de la cual vinieron, se agrega la columna “variable”.

Proceso de largo a ancho

La columna variable_lugar_altura_tipo todavía no es muy útil porque contiene 4 datos, la variable (temperatura), el lugar, la altura y el tipo de observación. Sería mejor separar esta información en dos columnas llamadas “variable”, “lugar”, “altura” y “tipo”. Para eso está la función separate().

bariloche_largo <- separate(bariloche_largo, 
                            col = variable_lugar_altura_tipo, 
                            into = c("variable", "lugar", "altura", "tipo"), 
                            sep = "_")
## Warning: Expected 4 pieces. Missing pieces filled with `NA` in 6048 rows [1, 8,
## 10, 17, 19, 26, 28, 35, 37, 44, 46, 53, 55, 62, 64, 71, 73, 80, 82, 89, ...].
bariloche_largo
## # A tibble: 27,216 × 6
##    Fecha               variable    lugar      altura tipo   valor
##    <dttm>              <chr>       <chr>      <chr>  <chr>  <dbl>
##  1 2011-05-28 00:00:00 Temperatura Abrigo     150cm  <NA>   NA   
##  2 2011-05-28 00:00:00 Temperatura Abrigo     150cm  Maxima NA   
##  3 2011-05-28 00:00:00 Temperatura Abrigo     150cm  Minima NA   
##  4 2011-05-28 00:00:00 Temperatura Intemperie 5cm    Minima NA   
##  5 2011-05-28 00:00:00 Temperatura Intemperie 50cm   Minima NA   
##  6 2011-05-28 00:00:00 Temperatura Suelo      5cm    Media  NA   
##  7 2011-05-28 00:00:00 Temperatura Suelo      10cm   Media  NA   
##  8 2011-05-28 00:00:00 Temperatura Inte       5cm    <NA>   NA   
##  9 2011-05-28 00:00:00 Temperatura Intemperie 150cm  Minima NA   
## 10 2011-05-29 00:00:00 Temperatura Abrigo     150cm  <NA>    4.27
## # … with 27,206 more rows

El primer argumento, como siempre, es la tabla a procesar. El segundo, col, es la columna a separar en dos (o más) columnas nuevas. El tercero, into es el nombre de las nuevas columnas que separate() va a crear. El último argumento es sep que define cómo realizar la separación. Por defecto, sep es una expresión regular que captura cualquier caracter no alfanumérico.

Habrás notado un problema. Para “Temperatura_Abrigo_150cm” y para “Temperatura_Inte_5cm”, la columna “tipo” es NA. Esto es porque el texto no tiene 4 “pedazos”. Sería conveniente agregarle algo. Asumiendo que es una temperatura media, se puede modificar con un if_else():

bariloche_largo <- bariloche_largo %>%
  mutate(tipo = if_else(is.na(tipo), "Media", tipo))
bariloche_largo
## # A tibble: 27,216 × 6
##    Fecha               variable    lugar      altura tipo   valor
##    <dttm>              <chr>       <chr>      <chr>  <chr>  <dbl>
##  1 2011-05-28 00:00:00 Temperatura Abrigo     150cm  Media  NA   
##  2 2011-05-28 00:00:00 Temperatura Abrigo     150cm  Maxima NA   
##  3 2011-05-28 00:00:00 Temperatura Abrigo     150cm  Minima NA   
##  4 2011-05-28 00:00:00 Temperatura Intemperie 5cm    Minima NA   
##  5 2011-05-28 00:00:00 Temperatura Intemperie 50cm   Minima NA   
##  6 2011-05-28 00:00:00 Temperatura Suelo      5cm    Media  NA   
##  7 2011-05-28 00:00:00 Temperatura Suelo      10cm   Media  NA   
##  8 2011-05-28 00:00:00 Temperatura Inte       5cm    Media  NA   
##  9 2011-05-28 00:00:00 Temperatura Intemperie 150cm  Minima NA   
## 10 2011-05-29 00:00:00 Temperatura Abrigo     150cm  Media   4.27
## # … with 27,206 more rows

Desafío

Juntá todos los pasos anteriores en una sola cadena de operaciones usando %>%.

Desafío

Esta tabla sólo tiene datos de temperatura. ¿Se podría incluir cualquier otra variable? ¿Humedad, dirección del viento, estado del tiempo? ¿Cómo lo harías?

De largo a ancho con pivot_wider()

Ahora la variable bariloche_largo está en el formato más largo posible. Tiene 6 columnas, de las cuales sólo una es la columnas con valores. Pero con los datos así no podrías hacer un gráfico de puntos que muestre, por ejemplo, la relación entre la temperatura mínima y la temperatura máxima. En este caso todos los valores en la columna valor tienen las mismas unidades, pero podría tener otras variables, como humedad o presión. En ese caso, no tendrían las mismas unidades, por lo que operar con ese vector podría dar resultados sin sentido. Muchas veces es conveniente y natural tener los datos en un formato intermedio en donde hay múltiples columnas con los valores de distintas variables observadas.

Pasa “ensanchar” una tabla está la función pivot_wider() (“wider” es “más ancha” en inglés) y el código para conseguir este formato intermedio es:

bariloche_medio <- pivot_wider(bariloche_largo, names_from = tipo, values_from = valor)
bariloche_medio
## # A tibble: 21,168 × 7
##    Fecha               variable    lugar      altura Media Maxima Minima
##    <dttm>              <chr>       <chr>      <chr>  <dbl>  <dbl>  <dbl>
##  1 2011-05-28 00:00:00 Temperatura Abrigo     150cm  NA      NA     NA  
##  2 2011-05-28 00:00:00 Temperatura Intemperie 5cm    NA      NA     NA  
##  3 2011-05-28 00:00:00 Temperatura Intemperie 50cm   NA      NA     NA  
##  4 2011-05-28 00:00:00 Temperatura Suelo      5cm    NA      NA     NA  
##  5 2011-05-28 00:00:00 Temperatura Suelo      10cm   NA      NA     NA  
##  6 2011-05-28 00:00:00 Temperatura Inte       5cm    NA      NA     NA  
##  7 2011-05-28 00:00:00 Temperatura Intemperie 150cm  NA      NA     NA  
##  8 2011-05-29 00:00:00 Temperatura Abrigo     150cm   4.27   12.8   -1.2
##  9 2011-05-29 00:00:00 Temperatura Intemperie 5cm    NA      NA     NA  
## 10 2011-05-29 00:00:00 Temperatura Intemperie 50cm   NA      NA     NA  
## # … with 21,158 more rows

Nuevamente el primer argumento es la tabla original. El segundo, names_from es la columna cuyos valores únicos van a convertirse en nuevas columnas. La columna tipo tiene los valores "Media", "Maxima" y "Minima" y entonces la tabla nueva tendrá tres columnas con esos nombres. El tercer argumento, values_from, es la columna de la cual sacar los valores.

Para volver al formato más ancho, basta con agregar más columnas en el argumento names_from:

pivot_wider(bariloche_largo, 
            names_from = c(variable, lugar, altura, tipo), 
            names_sep = "_",
            values_from = valor)
## # A tibble: 3,024 × 10
##    Fecha               Temperatura_Abrigo_150… Temperatura_Abr… Temperatura_Abr…
##    <dttm>                                <dbl>            <dbl>            <dbl>
##  1 2011-05-28 00:00:00                   NA                NA               NA  
##  2 2011-05-29 00:00:00                    4.27             12.8             -1.2
##  3 2011-05-30 00:00:00                    1.69              9.2             -2.8
##  4 2011-05-31 00:00:00                    3.47              9.6              0.5
##  5 2011-06-01 00:00:00                    2.90              9.6             -1.5
##  6 2011-06-02 00:00:00                    6.26             13.3              2.9
##  7 2011-06-03 00:00:00                    3.45             12.6             -1.8
##  8 2011-06-04 00:00:00                    6.09             10.2              3.9
##  9 2011-06-05 00:00:00                    5.00              7.1              3.4
## 10 2011-06-06 00:00:00                    5.45              9.5              3.2
## # … with 3,014 more rows, and 6 more variables:
## #   Temperatura_Intemperie_5cm_Minima <dbl>,
## #   Temperatura_Intemperie_50cm_Minima <dbl>,
## #   Temperatura_Suelo_5cm_Media <dbl>, Temperatura_Suelo_10cm_Media <dbl>,
## #   Temperatura_Inte_5cm_Media <dbl>, Temperatura_Intemperie_150cm_Minima <dbl>

En esta llamada también está el argumento names_sep, que determina el caracter que se usa para crear el nombre de las nuevas columnas.

Desafío

  • Creá una nueva tabla, llamada bariloche_superduper_ancho que sea la tabla más ancha posible que podés generar con estos datos. ¿Cómo es la tabla más ancha posible que podés generar con estos datos? ¿Cuántas filas y columnas tiene?

Uniendo tablas

Hasta ahora todo lo que usaste de {dplyr} involucra trabajar y modificar con una sola tabla a la vez, pero es muy común tener dos o más tablas con datos relacionados. En ese caso, tenemos que unir estas tablas. a partir de una o más variables en común o keys. En Excel u otro programa de hojas de cálculo, esto se resuelve con la función “VLOOKUP” o “BUSCARV”, en R y en particular dentro del mundo de {dplyr} hay que usar la familia de funciones *_join(). Hay una función cada tipo de unión que queramos hacer.

Asumiendo que querés unir dos data.frames o tablas x e y que tienen en común una variable A:

  • full_join(): devuelve todas las filas y todas las columnas de ambas tablas x e y. Cuando no coinciden los elementos en x1, devuelve NA (dato faltante). Esto significa que no se pierden filas de ninguna de las dos tablas aún cuando no hay coincidencia. Está es la manera más segura de unir tablas.

  • left_join(): devuelve todas las filas de x y todas las columnas de x e y. Las filas en x que no tengan coincidencia con y tendrán NA en las nuevas columnas. Si hay múltiples coincidencias entre xe y, devuelve todas las coincidencias posibles.

  • right_join(): es igual que left_join() pero intercambiando el orden de x e y. En otras palabras, right_join(x, y) es idéntico a left_join(y, x).

  • inner_join(): devuelve todas las filas de x donde hay coincidencias con y y todas las columnas de x e y. Si hay múltiples coincidencias entre x e y, entonces devuelve todas las coincidencias. Esto significa que eliminará las filas (observaciones) que no coincidan en ambas tablas, lo que puede ser peligroso.

En el archivo “estaciones_smn.csv” hay metadatos de las estaciones meteorológicas del Servicio Meteorológico Nacional (nombre, provincia, ubicación, etc…):

estaciones <- readr::read_csv("datos/estaciones_smn.csv") 
estaciones
## # A tibble: 123 × 5
##    nombre                   provincia      lon   lat altua
##    <chr>                    <chr>        <dbl> <dbl> <dbl>
##  1 BASE BELGRANO II         ANTARTIDA    -34.6 -77.9   256
##  2 BASE CARLINI (EX JUBANY) ANTARTIDA    -58.6 -62.2    11
##  3 BASE ESPERANZA           ANTARTIDA    -57.0 -63.4    24
##  4 BASE MARAMBIO            ANTARTIDA    -56.7 -64.2   198
##  5 BASE ORCADAS             ANTARTIDA    -44.7 -60.8    12
##  6 BASE SAN MARTIN          ANTARTIDA    -67.1 -68.1     7
##  7 AZUL AERO                BUENOS AIRES -59.9 -36.8   147
##  8 BAHIA BLANCA AERO        BUENOS AIRES -62.2 -38.7    83
##  9 BENITO JUAREZ AERO       BUENOS AIRES -59.8 -37.7   207
## 10 BOLIVAR AERO             BUENOS AIRES -61.1 -36.2    94
## # … with 113 more rows

Por otra parte, en el archivo “observaciones_smn.csv” hay datos diarios de temperatura mínima y máxima observados en cada una cada una y distintas fechas:

observaciones <- readr::read_csv("datos/observaciones_smn.csv")
## Rows: 3812 Columns: 4
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (1): station
## dbl  (2): tmax, tmin
## date (1): date
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
observaciones
## # A tibble: 3,812 × 4
##     tmax  tmin station                  date      
##    <dbl> <dbl> <chr>                    <date>    
##  1  16.1   9.4 AEROPARQUE AERO          2020-08-01
##  2  17.6  -1.6 AZUL AERO                2020-08-01
##  3  18.6   2.4 BAHIA BLANCA AERO        2020-08-01
##  4   9     1.6 BARILOCHE AERO           2020-08-01
##  5  NA   -33.5 BASE BELGRANO II         2020-08-01
##  6   2.1  -3.3 BASE CARLINI (EX JUBANY) 2020-08-01
##  7  -0.4  -2.6 BASE ESPERANZA           2020-08-01
##  8  -2.2  -7.4 BASE MARAMBIO            2020-08-01
##  9  -0.5  -7.8 BASE ORCADAS             2020-08-01
## 10   1.4  -7.6 BASE SAN MARTIN          2020-08-01
## # … with 3,802 more rows

Sería muy útil unir ambas tablas de manera de tener la información de la temperatura en cada ubicación.

Para unir las dos tablas, cualquier función join requiere cierta información:

  • las tablas a unir: son los dos primeros argumentos.
  • qué variable o variables (se puede usar más de una!) usar para identificar coincidencias: el argumento by.

Unamos observaciones y estaciones primero con full_join():

estaciones_obs <- full_join(observaciones, estaciones, by = c("station" = "nombre"))
estaciones_obs
## # A tibble: 3,813 × 8
##     tmax  tmin station                  date       provincia     lon   lat altua
##    <dbl> <dbl> <chr>                    <date>     <chr>       <dbl> <dbl> <dbl>
##  1  16.1   9.4 AEROPARQUE AERO          2020-08-01 CAPITAL FE… -58.4 -34.6     6
##  2  17.6  -1.6 AZUL AERO                2020-08-01 BUENOS AIR… -59.9 -36.8   147
##  3  18.6   2.4 BAHIA BLANCA AERO        2020-08-01 BUENOS AIR… -62.2 -38.7    83
##  4   9     1.6 BARILOCHE AERO           2020-08-01 RIO NEGRO   -71.2 -41.2   835
##  5  NA   -33.5 BASE BELGRANO II         2020-08-01 ANTARTIDA   -34.6 -77.9   256
##  6   2.1  -3.3 BASE CARLINI (EX JUBANY) 2020-08-01 ANTARTIDA   -58.6 -62.2    11
##  7  -0.4  -2.6 BASE ESPERANZA           2020-08-01 ANTARTIDA   -57.0 -63.4    24
##  8  -2.2  -7.4 BASE MARAMBIO            2020-08-01 ANTARTIDA   -56.7 -64.2   198
##  9  -0.5  -7.8 BASE ORCADAS             2020-08-01 ANTARTIDA   -44.7 -60.8    12
## 10   1.4  -7.6 BASE SAN MARTIN          2020-08-01 ANTARTIDA   -67.1 -68.1     7
## # … with 3,803 more rows

Como el nombre de la estación en la tabla observaciones está en una columna llamada “station”, mientras que en la tabla estaciones, está en una columna llamada “nombre”. Cuando pasa eso, en el argumento by hay que poner un vector con nombres.

Si mirás de cerca la tabla unida vas a ver un par de cosas:

  • Todas las columnas de observaciones y de estaciones están presentes.
  • Todas las observaciones están presentes, aún los estaciones que están presentes en estaciones pero no en observaciones y viceversa.

¿Existe alguna estación que no esté en ambas tablas? Si comparás la cantidad de filas de observaciones y de estaciones_obs, vas a ver que la primera tiene 3812 filas y la segunda, 3813. Esto ya da una pista de que probablemente haya una estación en estaciones que no está en observaciones que es la fila extra.

Para buscar sistemáticamente estas estaciones errantes hay que usar anti_join, que devuelve las filas de la tabla de la izquierda que no está en la derecha. Entonces, usando exactamente el mismo código de arriba pero cambiando full_join por anti_join, queda:

anti_join(observaciones, estaciones, by = c("station" = "nombre"))
## # A tibble: 31 × 4
##     tmax  tmin station                           date      
##    <dbl> <dbl> <chr>                             <date>    
##  1  33.6  13.5 PRESIDENCIA ROQUE SAENZ PE�A AERO 2020-08-01
##  2  34    17.3 PRESIDENCIA ROQUE SAENZ PE�A AERO 2020-08-02
##  3  34.1  17.3 PRESIDENCIA ROQUE SAENZ PE�A AERO 2020-08-03
##  4  34.2  16   PRESIDENCIA ROQUE SAENZ PE�A AERO 2020-08-04
##  5  35.1  18   PRESIDENCIA ROQUE SAENZ PE�A AERO 2020-08-05
##  6  34.5  16.3 PRESIDENCIA ROQUE SAENZ PE�A AERO 2020-08-06
##  7  24    11.5 PRESIDENCIA ROQUE SAENZ PE�A AERO 2020-08-07
##  8  32.7  10.1 PRESIDENCIA ROQUE SAENZ PE�A AERO 2020-08-08
##  9  35    11.7 PRESIDENCIA ROQUE SAENZ PE�A AERO 2020-08-09
## 10  36.2  15   PRESIDENCIA ROQUE SAENZ PE�A AERO 2020-08-10
## # … with 21 more rows

En la tabla observaciones, la estación “PRESIDENCIA ROQUE SAENZ PEÑA AERO” existe, tiene un problema de codificación! ¿Qué pasa si intercambiás el orden de las variables (y el orden de los nombres en el vector que le pasamos a by)?

anti_join(estaciones, observaciones, by = c("nombre" = "station"))
## # A tibble: 1 × 5
##   nombre                            provincia   lon   lat altua
##   <chr>                             <chr>     <dbl> <dbl> <dbl>
## 1 PRESIDENCIA ROQUE SAENZ PEÑA AERO CHACO     -60.4 -26.8    93

Esto nos dice que la estación “PRESIDENCIA ROQUE SAENZ PEÑA AERO”, está en la tabla estaciones con la codificación correcta. anti_join es muy útil para encontrar problemas como estos.

full_join es la opción más segura si no sabés si todas las observaciones de una tabla están presente en a otra. Si sólo te interesa conservar las filas de la tabla de la izquierda (en este caso estaciones entonces:

obs_estaciones <- left_join(observaciones, estaciones,  by = c("station" = "nombre"))
obs_estaciones
## # A tibble: 3,812 × 8
##     tmax  tmin station                  date       provincia     lon   lat altua
##    <dbl> <dbl> <chr>                    <date>     <chr>       <dbl> <dbl> <dbl>
##  1  16.1   9.4 AEROPARQUE AERO          2020-08-01 CAPITAL FE… -58.4 -34.6     6
##  2  17.6  -1.6 AZUL AERO                2020-08-01 BUENOS AIR… -59.9 -36.8   147
##  3  18.6   2.4 BAHIA BLANCA AERO        2020-08-01 BUENOS AIR… -62.2 -38.7    83
##  4   9     1.6 BARILOCHE AERO           2020-08-01 RIO NEGRO   -71.2 -41.2   835
##  5  NA   -33.5 BASE BELGRANO II         2020-08-01 ANTARTIDA   -34.6 -77.9   256
##  6   2.1  -3.3 BASE CARLINI (EX JUBANY) 2020-08-01 ANTARTIDA   -58.6 -62.2    11
##  7  -0.4  -2.6 BASE ESPERANZA           2020-08-01 ANTARTIDA   -57.0 -63.4    24
##  8  -2.2  -7.4 BASE MARAMBIO            2020-08-01 ANTARTIDA   -56.7 -64.2   198
##  9  -0.5  -7.8 BASE ORCADAS             2020-08-01 ANTARTIDA   -44.7 -60.8    12
## 10   1.4  -7.6 BASE SAN MARTIN          2020-08-01 ANTARTIDA   -67.1 -68.1     7
## # … with 3,802 more rows

Esta tabla tiene la misma cantidad de filas que observaciones. ¿Qué pasó con la estación que está mal codificada? Filtrando los datos:

obs_estaciones %>% 
  filter(startsWith(station, "PRESIDENCIA ROQUE"))
## # A tibble: 31 × 8
##     tmax  tmin station                    date       provincia   lon   lat altua
##    <dbl> <dbl> <chr>                      <date>     <chr>     <dbl> <dbl> <dbl>
##  1  33.6  13.5 PRESIDENCIA ROQUE SAENZ P… 2020-08-01 <NA>         NA    NA    NA
##  2  34    17.3 PRESIDENCIA ROQUE SAENZ P… 2020-08-02 <NA>         NA    NA    NA
##  3  34.1  17.3 PRESIDENCIA ROQUE SAENZ P… 2020-08-03 <NA>         NA    NA    NA
##  4  34.2  16   PRESIDENCIA ROQUE SAENZ P… 2020-08-04 <NA>         NA    NA    NA
##  5  35.1  18   PRESIDENCIA ROQUE SAENZ P… 2020-08-05 <NA>         NA    NA    NA
##  6  34.5  16.3 PRESIDENCIA ROQUE SAENZ P… 2020-08-06 <NA>         NA    NA    NA
##  7  24    11.5 PRESIDENCIA ROQUE SAENZ P… 2020-08-07 <NA>         NA    NA    NA
##  8  32.7  10.1 PRESIDENCIA ROQUE SAENZ P… 2020-08-08 <NA>         NA    NA    NA
##  9  35    11.7 PRESIDENCIA ROQUE SAENZ P… 2020-08-09 <NA>         NA    NA    NA
## 10  36.2  15   PRESIDENCIA ROQUE SAENZ P… 2020-08-10 <NA>         NA    NA    NA
## # … with 21 more rows

left_join le puso NA en las filas de observaciones que no tienen coincidencia con estaciones.

Finalmente, si quisieras quedarte sólo con las observaciones que están presentes en ambas tablas usamos inner_join().

obs_estaciones <- inner_join(observaciones, estaciones,  by = c("station" = "nombre"))
obs_estaciones
## # A tibble: 3,781 × 8
##     tmax  tmin station                  date       provincia     lon   lat altua
##    <dbl> <dbl> <chr>                    <date>     <chr>       <dbl> <dbl> <dbl>
##  1  16.1   9.4 AEROPARQUE AERO          2020-08-01 CAPITAL FE… -58.4 -34.6     6
##  2  17.6  -1.6 AZUL AERO                2020-08-01 BUENOS AIR… -59.9 -36.8   147
##  3  18.6   2.4 BAHIA BLANCA AERO        2020-08-01 BUENOS AIR… -62.2 -38.7    83
##  4   9     1.6 BARILOCHE AERO           2020-08-01 RIO NEGRO   -71.2 -41.2   835
##  5  NA   -33.5 BASE BELGRANO II         2020-08-01 ANTARTIDA   -34.6 -77.9   256
##  6   2.1  -3.3 BASE CARLINI (EX JUBANY) 2020-08-01 ANTARTIDA   -58.6 -62.2    11
##  7  -0.4  -2.6 BASE ESPERANZA           2020-08-01 ANTARTIDA   -57.0 -63.4    24
##  8  -2.2  -7.4 BASE MARAMBIO            2020-08-01 ANTARTIDA   -56.7 -64.2   198
##  9  -0.5  -7.8 BASE ORCADAS             2020-08-01 ANTARTIDA   -44.7 -60.8    12
## 10   1.4  -7.6 BASE SAN MARTIN          2020-08-01 ANTARTIDA   -67.1 -68.1     7
## # … with 3,771 more rows

En este caso, perdemos las filas de observaciones que no encontraron coincidencia en estaciones y viceversa.

Desafío

En el archivo “radiación_smn.csv” hay datos de radiación media diaria medidos en dos estaciones del Servicio Meteorológico Nacional. El objetivo es que unas observaciones con esos datos teniendo en cuenta tanto la estación como la fecha.

  1. Lee la base de datos radiacion_smn.csv en una nueva variable que se llame radiacion.
  2. Revisá el nombre de las variables en esta base de datos, ¿se llaman igual que las variables en observaciones?
  3. Si te interesa saber la relación entre la temperatura y la radiación medida, ¿qué tipo de join creés que te conviene usar? (Ayuda: ¿Te sirve de algo tener datos de estaciones y fechas donde se midió temperatura pero no radiación o donde se midió radiación pero no temperatura?)
  4. Uní las tabla usando la función join que elegiste. Tené en cuenta que ahora usamos dos variables llave station y date. Buscá en la documentación cómo indicarle eso a la función.
LS0tCnRpdGxlOiAiTWFuaXB1bGFjacOzbiBkZSBkYXRvcyBvcmRlbmFkb3MgdXNhbmRvIHtkcGx5cn0geSB7dGlkeXJ9IElJIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgaGlnaGxpZ2h0OiB0YW5nbwotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKVW4gbWlzbW8gY29uanVudG8gZGUgZGF0b3MgcHVlZGUgZXN0YXIgZW4gdW4gZm9ybWF0byAiYW5jaG8iIG8gImxhcmdvIi4gTG9zIGRhdG9zIGVuIGZvcm1hdG8gImxhcmdvIiBvICJ0aWR5Iiwgc29uIGFxdWVsbG9zIGVuIGxvcyBjdWFsZXM6CgotICAgY2FkYSBmaWxhIGVzIHVuYSBvYnNlcnZhY2nDs24KLSAgIGNhZGEgY29sdW1uYSBlcyB1bmEgdmFyaWFibGUKCkVuIGVsIGZvcm1hdG8gImFuY2hvIiBlcyB1biBwb2NvIG3DoXMgY29tcGxlam8gZGUgZGVmaW5pcmxvIHBlcm8gbGEgaWRlYSBnZW5lcmFsIGVzIHF1ZToKCi0gICBjYWRhIGZpbGEgZXMgdW4gIsOtdGVtIgotICAgY2FkYSBjb2x1bW5hIGVzIHVuYSB2YXJpYWJsZQoKIVtdKGltZy9sYXJnby1hbmNoby5wbmcpCgpVbmEgdGFibGEgZW4gZm9ybWF0byBsYXJnbyB2YSBhIHRlbmVyIHVuYSBjaWVydGEgY2FudGlkYWQgZGUgY29sdW1uYXMgcXVlIGN1bXBsZW4gZWwgcm9sIGRlIGlkZW50aWZpY2Fkb3JlcyB5IGN1eWEgY29tYmluYWNpw7NuIGlkZW50aWZpY2FuIHVuYSDDum5pY2Egb2JzZXJ2YWNpw7NuIHkgdW5hIMO6bmljYSBjb2x1bW5hIGNvbiBlbCB2YWxvciBkZSBsYSBvYnNlcnZhY2nDs24uIEVuIGVsIGVqZW1wbG8gZGUgYXJyaWJhLCBgcGFpc2AgeSBgYW5pb2Agc29uIGxhcyBjb2x1bW5hcyBpZGVudGlmaWNhZG9yYXMgeSBgY2Fzb3NgIGVzIGxhIGNvbHVtbmEgcXVlIGNvbnRpZW5lIGVsIHZhbG9yIGRlIGxhcyBvYnNlcnZhY2lvbmVzLgoKRW4gdW5hIHRhYmxhIGFuY2hhLCBjYWRhIG9ic2VydmFjacOzbiDDum5pY2Egc2UgaWRlbnRpZmljYSBhIHBhcnRpciBkZSBsYSBpbnRlcnNlY2Npw7NuIGRlIGZpbGFzIHkgY29sdW1uYXMuIEVuIGVsIGVqZW1wbG8sIGxvcyBwYcOtc2VzIGVzdMOhbiBlbiBsYXMgZmlsYXMgeSBsb3MgYcOxb3MgZW4gbGFzIGNvbHVtbmFzLgoKRW4gZ2VuZXJhbCwgZWwgZm9ybWF0byBhbmNobyBlcyBtw6FzIGNvbXBhY3RvIHkgbGVnaWJsZSBwb3IgaHVtYW5vcyBtaWVudHJhcyBxdWUgZWwgbGFyZ28gZXMgbcOhcyBmw6FjaWwgZGUgbWFuZWphciBjb24gbGEgY29tcHV0YWRvcmEuIFNpIHRlIGZpasOhcyBlbiBsYXMgdGFibGFzIGRlIGFycmliYSwgZXMgbcOhcyBmw6FjaWwgY29tcGFyYXIgbG9zIHZhbG9yZXMgZW50cmUgcGHDrXNlcyB5IGVudHJlIGHDsW9zIGVuIGxhIHRhYmxhIGFuY2hhLiBQZXJvIGVsIG5vbWJyZSBkZSBsYXMgY29sdW1uYXMgKCIxOTk5IiwgIjIwMDAiKSBlbiByZWFsaWRhZCDCoXNvbiBkYXRvcyEgQWRlbcOhcyBlc3RlIGZvcm1hdG8gc2UgZW1waWV6YSBhIGNvbXBsaWNhciBlbiBjdWFudG8gaGF5IG3DoXMgZGUgZG9zIGlkZW50aWZpY2Fkb3Jlcy4KClVuIG1pc21vIHNldCBkZSBkYXRvcyBwdWVkZSBzZXIgcmVwcmVzZW50YWRvIGRlIGZvcm1hIGNvbXBsZXRhbWVudGUgImxhcmdhIiwgY29tcGxldGFtZW50ZSAiYW5jaGEiIG8gLS1sbyBxdWUgZXMgbcOhcyBjb23Dum4tLSBlbiB1biBmb3JtYXRvIGludGVybWVkaW8gcGVybyBubyBleGlzdGUgdW5hIGZvcm1hICJjb3JyZWN0YSIgZGUgb3JnYW5pemFyIGxvcyBkYXRvczsgY2FkYSB1bmEgdGllbmUgc3VzIHZlbnRhamFzIHkgZGVzdmVudGFqYXMuIFBvciBlc3RvIGVzIHF1ZSBlcyBtdXkgbm9ybWFsIHF1ZSBkdXJhbnRlIHVuIGFuw6FsaXNpcyBsb3MgZGF0b3MgdmF5YW4geSB2dWVsdmFuIGVudHJlIGRpc3RpbnRvcyBmb3JtYXRvcyBkZXBlbmRpZW5kbyBkZSBsb3MgbcOpdG9kb3MgZXN0YWTDrXN0aWNvcyBxdWUgc2UgbGUgYXBsaWNhbi4gRW50b25jZXMsIGFwcmVuZGVyIGEgdHJhbnNmb3JtYXIgZGF0b3MgYW5jaG9zIGVuIGxhcmdvcyB5IHZpY2V2ZXJzYSBlcyB1biBoYWJpbGlkYWQgbXV5IMO6dGlsLgoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqRGVzYWbDrW8qKgoKRW4gbGFzIHRhYmxhcyBkZSBlamVtcGxvIGNhZGEgcGHDrXMgdGllbmUgZWwgdW4gdmFsb3Igb2JzZXJ2YWRvIGRlICJjYXNvcyIgcGFyYSBjYWRhIGHDsW8uIMK/Q8OzbW8gYWdyZWdhcsOtYXMgdW5hIG51ZXZhIHZhcmlhYmxlIGNvbiBpbmZvcm1hY2nDs24gc29icmUgInByZWNpb3MiPyBEaWJ1asOhIHVuIGVzcXVlbWEgZW4gcGFwZWwgeSBsw6FwaXogZW4gZm9ybWF0byBhbmNobyB5IHVubyBlbiBmb3JtYXRvIGxhcmdvLiDCv0VuIHF1w6kgZm9ybWF0byBlcyBtw6FzICJuYXR1cmFsIiBlc2EgZXh0ZW5zacOzbj8KOjo6CgpFbiBlc3RhIHNlY2Npw7NuIHZhcyBhIHVzYXIgZWwgcGFxdWV0ZSB7dGlkeXJ9IHBhcmEgbWFuaXB1bGFyIGRhdG9zLiBTaSBubyBsbyB0ZW7DqXMgaW5zdGFsYWRvLCBpbnN0YWxhbG8gY29uIGVsIGNvbWFuZG86CgpgYGB7ciBldmFsID0gRkFMU0V9Cmluc3RhbGwucGFja2FnZXMoInRpZHlyIikKYGBgCgpZIGx1ZWdvIGNhcmfDoSB7dGlkeXJ9IHkge2RwbHlyfSAocXVlIHVzYXN0ZSBlbiBbdW5hIHNlY2Npw7NuIGFudGVyaW9yXSgwNS1kcGx5ci5odG1sKSkgY29uOgoKYGBge3J9CmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZHBseXIpCmBgYAoKIyMgRGUgYW5jaG8gYSBsYXJnbyBjb24gYHBpdm90X2xvbmdlcigpYAoKRW4gc2VjY2lvbmVzIGFudGVyaW9yZXMgdXNhc3RlIGRhdG9zIGRlIHVuYSBlc3RhY2nDs24gbWV0ZW9yb2zDs2dpY2EgZW4gQmFyaWxvY2hlOgoKYGBge3J9CmJhcmlsb2NoZSA8LSByZWFkcjo6cmVhZF9jc3YoImRhdG9zL2Jhcmlsb2NoZV9lbmxpbXBpby5jc3YiKQpiYXJpbG9jaGUKYGBgCgo6Ojogey5hbGVydCAuYWxlcnQtc3VjY2Vzc30Kwr9Ob3Rhc3RlIHF1ZSBlbiBlbCBjw7NkaWdvIGFudGVyaW9yIG5vIHVzYXN0ZSBgbGlicmFyeShyZWFkcilgIHBhcmEgY2FyZ2FyIGVsIHBhcXVldGUgeSBsdWVnbyBsZWVyPyBDb24gbGEgbm90YWNpw7NuIGBwYXF1ZXRlOjpmdW5jaW9uKClgIHBvZMOpcyBhY2NlZGVyIGEgbGFzIGZ1bmNpb25lcyBkZSB1biBwYXF1ZXRlIHNpbiB0ZW5lciBxdWUgY2FyZ2FybG8uIEVzIHVuYSBidWVuYSBmb3JtYSBkZSBubyB0ZW5lciBxdWUgY2FyZ2FyIHVuIG1vbnTDs24gZGUgcGFxdWV0ZXMgaW5uZWNlc2FyaW9zIHNpIHZhcyBhIGNvcnJlciB1bmEgw7puaWNhIGZ1bmNpw7NuIGRlIHVuIHBhcXVldGUgcG9jYXMgdmVjZXMuCjo6OgoKwr9Dw7NtbyBoYXLDrWFzIHBhcmEgY2FsY3VsYXIgZWwgdmFsb3IgbWVkaW8gbWVuc3VhbCBkZSBjYWRhIHZhcmlhYmxlIHJlbGFjaW9uYWRhIGNvbiBsYSB0ZW1wZXJhdHVyYT8gRW4gZWwgZm9ybWF0byBlbiBlbCBxdWUgZXN0w6FuIGFob3JhIGxvcyBkYXRvcywgdGVuZHLDrWFzIHF1ZSBoYWNlciBhbGdvIGNvbW8gZXN0bwoKYGBge3J9CmJhcmlsb2NoZSAlPiUgCiAgZ3JvdXBfYnkobWVzKSAlPiUgCiAgc3VtbWFyaXNlKFRlbXBlcmF0dXJhX2Ficmlnb18xNTBjbSA9IG1lYW4oVGVtcGVyYXR1cmFfQWJyaWdvXzE1MGNtLCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgICBUZW1wZXJhdHVyYV9BYnJpZ29fMTUwY21fTWF4aW1hID0gbWVhbihUZW1wZXJhdHVyYV9BYnJpZ29fMTUwY21fTWF4aW1hLCBuYS5ybSA9IFRSVUUpLCAKICAgICAgICAgICAgVGVtcGVyYXR1cmFfQWJyaWdvXzE1MGNtX01pbmltYSA9IG1lYW4oVGVtcGVyYXR1cmFfQWJyaWdvXzE1MGNtX01pbmltYSwgbmEucm0gPSBUUlVFKSkKYGBgCgo6Ojogey5hbGVydCAuYWxlcnQtaW5mb30KKipEZXNhZsOtbyoqCgpFeHRlbmTDqSBlbCBjw7NkaWdvIGRlIGFycmliYSBwYXJhIGNhbGN1bGFyIGVsIHByb21lZGlvIGRlIHRvZGFzIGxhcyAzMCB2YXJpYWJsZXMgaW5jbHVpZGFzIGVuIGxhIHRhYmxhIGBiYXJpbG9jaGVgLgoKwqFObyEgU2Vyw61hIHVuIGVqZXJjaWNpbyBhbHRhbWVudGUgdGVkaW9zby4KOjo6CgpQYXJhIG5vIHRlbmVyIHF1ZSBlc2NyaWJpciAzMCBsw61uZWFzIHRvZGFzIGlndWFsZXMsIHNlcsOtYSBtZWpvciBwb2RlciBoYWNlciB1biBwcm9tZWRpbyBwYXJhIGNhZGEgbWVzICoqeSBwYXJhIGNhZGEgdmFyaWFibGUqKi4gQWxnbyBjb21vIGVzdG86CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpiYXJpbG9jaGUgJT4lIAogIGdyb3VwX2J5KG1lcywgdmFyaWFibGUpICU+JSAKICBzdW1tYXJpc2UocHJvbWVkaW8gPSBtZWFuKHZhbG9yLCBuYS5ybSA9IFRSVUUpKQpgYGAKClBhcmEgcG9kZXIgaGFjZXIgZXNvIGhheSBxdWUgdGVuZXIgbG9zIGRhdG9zIGVuIHVuIGZvcm1hdG8gbcOhcyBsYXJnby4gUGFyYSBjb252ZXJ0aXJsbyBlbiB1bmEgdGFibGEgbcOhcyBsYXJnYSwgc2UgdXNhIGBwaXZvdF9sb25nZXIoKWAgKCJsb25nZXIiIGVzICJtw6FzIGxhcmdvIiBlbiBpbmdsw6lzKToKCmBgYHtyfQpiYXJpbG9jaGVfbGFyZ28gPC0gYmFyaWxvY2hlICU+JSAKICBzZWxlY3QoRmVjaGEsIHN0YXJ0c193aXRoKCJUZW1wZXJhdHVyYSIpKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSAtRmVjaGEsCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gInZhcmlhYmxlX2x1Z2FyX2FsdHVyYV90aXBvIiwKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbG9yIikKCmJhcmlsb2NoZV9sYXJnbwpgYGAKCkxvIHByaW1lcm8gcXVlIGhhY2UgZXN0ZSBjw7NkaWdvIGVzIHNlbGVjY2lvbmFyIHPDs2xvIGxhcyBjb2x1bW5hcyBkZSBpbnRlcsOpczogbGEgRmVjaGEgeSBsYXMgY29sdW1uYXMgZGUgdGVtcGVyYXR1cmEuIFBhcmEgbm8gdGVuZXIgcXVlIGVzY3JpYmlyIGxvcyBub21icmVzIGVudGVyb3MgZGUgbGFzIGNvbHVtbmFzIGRlIHRlbXBlcmF0dXJhICjCoXF1ZSBzb24gbXV5IGxhcmdvcyEpIHVzYSBsYSBmdW5jacOzbiBgc3RhcnRzX3dpdGgoKWAgcXVlLCBjb21vIHN1IG5vbWJyZSAoZW4gaW5nbMOpcykgbG8gaW5kaWNhLCBzZWxlY2Npb25hIHRvZGFzIGxhcyBjb2x1bW5hcyBxdWUgZW1waWV6YW4gY29uICJUZW1wZXJhdHVyYSIuCgo6Ojogey5hbGVydCAuYWxlcnQtc3VjY2Vzc30KRXhpc3RlbiBvdHJhcyBmdW5jaW9uZXMgYWNjZXNvcmlhcyBwYXJhIHNlbGVjY2lvbmFyIG11Y2hhcyBmdW5jaW9uZXMgZW5jYXBzdWxhZGFzIGVuIGVsIHBhcXVldGUgInRpZHlzZWxlY3QiLiBTaSBxdWVyw6lzIGxlZXIgbcOhcyBkZXRhbGxlcyBkZSBsYXMgZGlzdGludGFzIGZvcm1hcyBxdWUgcG9kw6lzIHNlbGVjY2lvbmFyIHZhcmlhYmxlcyBsZcOpIGxhIGRvY3VtZW50YWNpw7NuIHVzYW5kbyBgP3RpZHlzZWxlY3Q6Omxhbmd1YWdlYC4KOjo6CgpMdWVnbywgZWwgY8OzZGlnbyB1c2EgYHBpdm90X2xvbmdlcigpYCBwYXJhICJhbGFyZ2FyIiBsYSB0YWJsYSBlcyBsYSB0YWJsYSBxdWUgdmEgYSBtb2RpZmljYXI6IGBiYXJpbG9jaGVgLiBFbCBzZWd1bmRvIGFyZ3VtZW50byBzZSBsbGFtYSBgY29sc2AgeSBlcyB1biB2ZWN0b3IgY29uIGxhcyBjb2x1bW5hcyBxdWUgdGllbmVuIGxvcyB2YWxvcmVzIGEgImFsYXJnYXIiLiBQb2Ryw61hIHNlciB1biB2ZWN0b3IgZXNjcml0byBhIG1hbm8gKGFsZ28gY29tbyBgYygiVGVtcGVyYXR1cmFfQWJyaWdvXzE1MGNtIiwgIlRlbXBlcmF0dXJhX0Ficmlnb18xNTBjbV9NYXhpbWEiLi4uKWApIHBlcm8gY29uIG3DoXMgZGUgMzAgY29sdW1uYXMsIGVzY3JpYmlyIHRvZG8gZXNvIHNlcsOtYSB0ZWRpb3NvIHkgcHJvYmFibGVtZW50ZSBlc3RhcsOtYSBsbGVubyBkZSBlcnJvcmVzLiBFbCBjw7NkaWdvIGRlIGFycmliYSB1c2EgbGEgc2ludGF4aXMgZGUgIi1jb2x1bW5hIiBwYXJhIGluZGljYXIgcXVlIHNvbiB0b2RhcyBsYXMgY29sdW1uYXMgbWVub3MgbGEgY29sdW1uYXMgIkZlY2hhIi4KCkVsIHRlcmNlciB5IGN1YXJ0byBhcmd1bWVudG8gc29uIGxvcyBub21icmVzIGRlIGxhcyBjb2x1bW5hcyBkZSAibm9tYnJlIiB5IGRlICJ2YWxvciIgcXVlIHZhIGEgdGVuZXIgbGEgbnVldmEgdGFibGEuIENvbW8gbGEgbnVldmEgY29sdW1uYSBkZSBpZGVudGlmaWNhY2nDs24gdGllbmUgbG9zIGRhdG9zIGRlIGxhIHZhcmlhYmxlLCBlbCBsdWdhciBkb25kZSBzZSBtaWRlLCBsYSBhbHR1cmEgeSBlbCB0aXBvIGRlIHZhcmlhYmxlIChtw61uaW1hLCBtw6F4aW1hKSAsICJ2YXJpYWJsZVxfbHVnYXJcX2FsdHVyYVxfdGlwbyIgZXMgdW4gYnVlbiBub21icmUuIFkgbGEgY29sdW1uYSBkZSB2YWxvciB2YSBhIHRlbmVyLi4uIGJ1ZW5vLCBlbCB2YWxvci4KClRvbWF0ZSB1biBtb21lbnRvIHBhcmEgdmlzdWFsaXphciBsbyBxdWUgYWNhYmEgZGUgcGFzYXIuIExhIHRhYmxhIGFuY2hhIHRlbsOtYSB1biBtb250w7NuIGRlIGNvbHVtbmFzIGNvbiBkaXN0aW50b3MgZGF0b3MuIEFob3JhIGVzdG9zIGRhdG9zIGVzdMOhbiB1bm8gYXJyaWJhIGRlIG90cm8gZW4gbGEgY29sdW1uYSAidmFsb3IiLCBwZXJvIHBhcmEgaWRlbnRpZmljYXIgZWwgbm9tYnJlIGRlIGxhIGNvbHVtbmEgZGUgbGEgY3VhbCB2aW5pZXJvbiwgc2UgYWdyZWdhIGxhIGNvbHVtbmEgInZhcmlhYmxlIi4KCiFbUHJvY2VzbyBkZSBsYXJnbyBhIGFuY2hvXShpbWcvYW5jaG8tYS1sYXJnby5wbmcpCgpMYSBjb2x1bW5hIGB2YXJpYWJsZV9sdWdhcl9hbHR1cmFfdGlwb2AgdG9kYXbDrWEgbm8gZXMgbXV5IMO6dGlsIHBvcnF1ZSBjb250aWVuZSA0IGRhdG9zLCBsYSB2YXJpYWJsZSAodGVtcGVyYXR1cmEpLCBlbCBsdWdhciwgbGEgYWx0dXJhIHkgZWwgdGlwbyBkZSBvYnNlcnZhY2nDs24uIFNlcsOtYSBtZWpvciBzZXBhcmFyIGVzdGEgaW5mb3JtYWNpw7NuIGVuIGRvcyBjb2x1bW5hcyBsbGFtYWRhcyAidmFyaWFibGUiLCAibHVnYXIiLCAiYWx0dXJhIiB5ICJ0aXBvIi4gUGFyYSBlc28gZXN0w6EgbGEgZnVuY2nDs24gYHNlcGFyYXRlKClgLgoKYGBge3J9CmJhcmlsb2NoZV9sYXJnbyA8LSBzZXBhcmF0ZShiYXJpbG9jaGVfbGFyZ28sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sID0gdmFyaWFibGVfbHVnYXJfYWx0dXJhX3RpcG8sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgaW50byA9IGMoInZhcmlhYmxlIiwgImx1Z2FyIiwgImFsdHVyYSIsICJ0aXBvIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIl8iKQpiYXJpbG9jaGVfbGFyZ28KYGBgCgpFbCBwcmltZXIgYXJndW1lbnRvLCBjb21vIHNpZW1wcmUsIGVzIGxhIHRhYmxhIGEgcHJvY2VzYXIuIEVsIHNlZ3VuZG8sIGBjb2xgLCBlcyBsYSBjb2x1bW5hIGEgc2VwYXJhciBlbiBkb3MgKG8gbcOhcykgY29sdW1uYXMgbnVldmFzLiBFbCB0ZXJjZXJvLCBgaW50b2AgZXMgZWwgbm9tYnJlIGRlIGxhcyBudWV2YXMgY29sdW1uYXMgcXVlIGBzZXBhcmF0ZSgpYCB2YSBhIGNyZWFyLiBFbCDDumx0aW1vIGFyZ3VtZW50byBlcyBgc2VwYCBxdWUgZGVmaW5lIGPDs21vIHJlYWxpemFyIGxhIHNlcGFyYWNpw7NuLiBQb3IgZGVmZWN0bywgYHNlcGAgZXMgdW5hIFtleHByZXNpw7NuIHJlZ3VsYXJdKGh0dHBzOi8vZXMud2lraXBlZGlhLm9yZy93aWtpL0V4cHJlc2klQzMlQjNuX3JlZ3VsYXIpIHF1ZSBjYXB0dXJhIGN1YWxxdWllciBjYXJhY3RlciBubyBhbGZhbnVtw6lyaWNvLgoKSGFicsOhcyBub3RhZG8gdW4gcHJvYmxlbWEuIFBhcmEgIlRlbXBlcmF0dXJhXF9BYnJpZ29cXzE1MGNtIiB5IHBhcmEgIlRlbXBlcmF0dXJhXF9JbnRlXF81Y20iLCBsYSBjb2x1bW5hICJ0aXBvIiBlcyBOQS4gRXN0byBlcyBwb3JxdWUgZWwgdGV4dG8gbm8gdGllbmUgNCAicGVkYXpvcyIuIFNlcsOtYSBjb252ZW5pZW50ZSBhZ3JlZ2FybGUgYWxnby4gQXN1bWllbmRvIHF1ZSBlcyB1bmEgdGVtcGVyYXR1cmEgbWVkaWEsIHNlIHB1ZWRlIG1vZGlmaWNhciBjb24gdW4gYGlmX2Vsc2UoKWA6CgpgYGB7cn0KYmFyaWxvY2hlX2xhcmdvIDwtIGJhcmlsb2NoZV9sYXJnbyAlPiUKICBtdXRhdGUodGlwbyA9IGlmX2Vsc2UoaXMubmEodGlwbyksICJNZWRpYSIsIHRpcG8pKQpiYXJpbG9jaGVfbGFyZ28KYGBgCgo6Ojogey5hbGVydCAuYWxlcnQtaW5mb30KKipEZXNhZsOtbyoqCgpKdW50w6EgdG9kb3MgbG9zIHBhc29zIGFudGVyaW9yZXMgZW4gdW5hIHNvbGEgY2FkZW5hIGRlIG9wZXJhY2lvbmVzIHVzYW5kbyBgJT4lYC4KOjo6Cgo6Ojogey5hbGVydCAuYWxlcnQtaW5mb30gCioqRGVzYWbDrW8qKiAKCkVzdGEgdGFibGEgc8OzbG8gdGllbmUgZGF0b3MgZGUgdGVtcGVyYXR1cmEuIMK/U2UgcG9kcsOtYSBpbmNsdWlyIGN1YWxxdWllciBvdHJhIHZhcmlhYmxlPyDCv0h1bWVkYWQsIGRpcmVjY2nDs24gZGVsIHZpZW50bywgZXN0YWRvIGRlbCB0aWVtcG8/IMK/Q8OzbW8gbG8gaGFyw61hcz8KOjo6CgojIyBEZSBsYXJnbyBhIGFuY2hvIGNvbiBgcGl2b3Rfd2lkZXIoKWAKCkFob3JhIGxhIHZhcmlhYmxlIGBiYXJpbG9jaGVfbGFyZ29gIGVzdMOhIGVuIGVsIGZvcm1hdG8gbcOhcyBsYXJnbyBwb3NpYmxlLiBUaWVuZSA2IGNvbHVtbmFzLCBkZSBsYXMgY3VhbGVzIHPDs2xvIHVuYSBlcyBsYSBjb2x1bW5hcyBjb24gdmFsb3Jlcy4gUGVybyBjb24gbG9zIGRhdG9zIGFzw60gbm8gcG9kcsOtYXMgaGFjZXIgdW4gZ3LDoWZpY28gZGUgcHVudG9zIHF1ZSBtdWVzdHJlLCBwb3IgZWplbXBsbywgbGEgcmVsYWNpw7NuIGVudHJlIGxhIHRlbXBlcmF0dXJhIG3DrW5pbWEgeSBsYSB0ZW1wZXJhdHVyYSBtw6F4aW1hLiBFbiBlc3RlIGNhc28gdG9kb3MgbG9zIHZhbG9yZXMgZW4gbGEgY29sdW1uYSBgdmFsb3JgIHRpZW5lbiBsYXMgbWlzbWFzIHVuaWRhZGVzLCBwZXJvIHBvZHLDrWEgdGVuZXIgb3RyYXMgdmFyaWFibGVzLCBjb21vIGh1bWVkYWQgbyBwcmVzacOzbi4gRW4gZXNlIGNhc28sIG5vIHRlbmRyw61hbiBsYXMgbWlzbWFzIHVuaWRhZGVzLCBwb3IgbG8gcXVlIG9wZXJhciBjb24gZXNlIHZlY3RvciBwb2Ryw61hIGRhciByZXN1bHRhZG9zIHNpbiBzZW50aWRvLiBNdWNoYXMgdmVjZXMgZXMgY29udmVuaWVudGUgeSBuYXR1cmFsIHRlbmVyIGxvcyBkYXRvcyBlbiB1biBmb3JtYXRvIGludGVybWVkaW8gZW4gZG9uZGUgaGF5IG3Dumx0aXBsZXMgY29sdW1uYXMgY29uIGxvcyB2YWxvcmVzIGRlIGRpc3RpbnRhcyB2YXJpYWJsZXMgb2JzZXJ2YWRhcy4KClBhc2EgImVuc2FuY2hhciIgdW5hIHRhYmxhIGVzdMOhIGxhIGZ1bmNpw7NuIGBwaXZvdF93aWRlcigpYCAoIndpZGVyIiBlcyAibcOhcyBhbmNoYSIgZW4gaW5nbMOpcykgeSBlbCBjw7NkaWdvIHBhcmEgY29uc2VndWlyIGVzdGUgZm9ybWF0byBpbnRlcm1lZGlvIGVzOgoKYGBge3J9CmJhcmlsb2NoZV9tZWRpbyA8LSBwaXZvdF93aWRlcihiYXJpbG9jaGVfbGFyZ28sIG5hbWVzX2Zyb20gPSB0aXBvLCB2YWx1ZXNfZnJvbSA9IHZhbG9yKQpiYXJpbG9jaGVfbWVkaW8KYGBgCgpOdWV2YW1lbnRlIGVsIHByaW1lciBhcmd1bWVudG8gZXMgbGEgdGFibGEgb3JpZ2luYWwuIEVsIHNlZ3VuZG8sIGBuYW1lc19mcm9tYCBlcyBsYSBjb2x1bW5hIGN1eW9zIHZhbG9yZXMgw7puaWNvcyB2YW4gYSBjb252ZXJ0aXJzZSBlbiBudWV2YXMgY29sdW1uYXMuIExhIGNvbHVtbmEgYHRpcG9gIHRpZW5lIGxvcyB2YWxvcmVzIGAiTWVkaWEiYCwgYCJNYXhpbWEiYCB5IGAiTWluaW1hImAgeSBlbnRvbmNlcyBsYSB0YWJsYSBudWV2YSB0ZW5kcsOhIHRyZXMgY29sdW1uYXMgY29uIGVzb3Mgbm9tYnJlcy4gRWwgdGVyY2VyIGFyZ3VtZW50bywgYHZhbHVlc19mcm9tYCwgZXMgbGEgY29sdW1uYSBkZSBsYSBjdWFsIHNhY2FyIGxvcyB2YWxvcmVzLgoKUGFyYSB2b2x2ZXIgYWwgZm9ybWF0byBtw6FzIGFuY2hvLCBiYXN0YSBjb24gYWdyZWdhciBtw6FzIGNvbHVtbmFzIGVuIGVsIGFyZ3VtZW50byBgbmFtZXNfZnJvbWA6CgpgYGB7cn0KcGl2b3Rfd2lkZXIoYmFyaWxvY2hlX2xhcmdvLCAKICAgICAgICAgICAgbmFtZXNfZnJvbSA9IGModmFyaWFibGUsIGx1Z2FyLCBhbHR1cmEsIHRpcG8pLCAKICAgICAgICAgICAgbmFtZXNfc2VwID0gIl8iLAogICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IHZhbG9yKQpgYGAKCkVuIGVzdGEgbGxhbWFkYSB0YW1iacOpbiBlc3TDoSBlbCBhcmd1bWVudG8gYG5hbWVzX3NlcGAsIHF1ZSBkZXRlcm1pbmEgZWwgY2FyYWN0ZXIgcXVlIHNlIHVzYSBwYXJhIGNyZWFyIGVsIG5vbWJyZSBkZSBsYXMgbnVldmFzIGNvbHVtbmFzLgoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqRGVzYWbDrW8qKgoKLSBDcmXDoSB1bmEgbnVldmEgdGFibGEsIGxsYW1hZGEgYGJhcmlsb2NoZV9zdXBlcmR1cGVyX2FuY2hvYCBxdWUgc2VhIGxhIHRhYmxhIG3DoXMgYW5jaGEgcG9zaWJsZSBxdWUgcG9kw6lzIGdlbmVyYXIgY29uIGVzdG9zIGRhdG9zLiDCv0PDs21vIGVzIGxhIHRhYmxhIG3DoXMgYW5jaGEgcG9zaWJsZSBxdWUgcG9kw6lzIGdlbmVyYXIgY29uIGVzdG9zIGRhdG9zPyDCv0N1w6FudGFzIGZpbGFzIHkgY29sdW1uYXMgdGllbmU/Cgo6OjoKCiMjIFVuaWVuZG8gdGFibGFzCgoKSGFzdGEgYWhvcmEgdG9kbyBsbyBxdWUgdXNhc3RlIGRlIHtkcGx5cn0gaW52b2x1Y3JhIHRyYWJhamFyIHkgbW9kaWZpY2FyIGNvbiB1bmEgc29sYSB0YWJsYSBhIGxhIHZleiwgcGVybyBlcyBtdXkgY29tw7puIHRlbmVyIGRvcyBvIG3DoXMgdGFibGFzIGNvbiBkYXRvcyByZWxhY2lvbmFkb3MuIEVuIGVzZSBjYXNvLCB0ZW5lbW9zIHF1ZSB1bmlyIGVzdGFzIHRhYmxhcy4gYSBwYXJ0aXIgZGUgdW5hIG8gbcOhcyB2YXJpYWJsZXMgZW4gY29tw7puIG8ga2V5cy4gRW4gRXhjZWwgdSBvdHJvIHByb2dyYW1hIGRlIGhvamFzIGRlIGPDoWxjdWxvLCBlc3RvIHNlIHJlc3VlbHZlIGNvbiBsYSBmdW5jacOzbiAiVkxPT0tVUCIgbyAiQlVTQ0FSViIsIGVuIFIgeSBlbiBwYXJ0aWN1bGFyIGRlbnRybyBkZWwgbXVuZG8gZGUge2RwbHlyfSBoYXkgcXVlIHVzYXIgbGEgZmFtaWxpYSBkZSBmdW5jaW9uZXMgYCpfam9pbigpYC4gSGF5IHVuYSBmdW5jacOzbiBjYWRhIHRpcG8gZGUgdW5pw7NuIHF1ZSBxdWVyYW1vcyBoYWNlci4KCkFzdW1pZW5kbyBxdWUgcXVlcsOpcyB1bmlyIGRvcyBkYXRhLmZyYW1lcyBvIHRhYmxhcyBgeGAgZSBgeWAgcXVlIHRpZW5lbiBlbiBjb23Dum4gdW5hIHZhcmlhYmxlIGBBYDoKCiFbXShpbWcvam9pbi5wbmcpCgotICAgYGZ1bGxfam9pbigpYDogZGV2dWVsdmUgdG9kYXMgbGFzIGZpbGFzIHkgdG9kYXMgbGFzIGNvbHVtbmFzIGRlIGFtYmFzIHRhYmxhcyBgeGAgZSBgeWAuIEN1YW5kbyBubyBjb2luY2lkZW4gbG9zIGVsZW1lbnRvcyBlbiBgeDFgLCBkZXZ1ZWx2ZSBgTkFgIChkYXRvIGZhbHRhbnRlKS4gRXN0byBzaWduaWZpY2EgcXVlIG5vIHNlIHBpZXJkZW4gZmlsYXMgZGUgbmluZ3VuYSBkZSBsYXMgZG9zIHRhYmxhcyBhw7puIGN1YW5kbyBubyBoYXkgY29pbmNpZGVuY2lhLiBFc3TDoSBlcyBsYSBtYW5lcmEgbcOhcyBzZWd1cmEgZGUgdW5pciB0YWJsYXMuCgotICAgYGxlZnRfam9pbigpYDogZGV2dWVsdmUgdG9kYXMgbGFzIGZpbGFzIGRlIGB4YCB5IHRvZGFzIGxhcyBjb2x1bW5hcyBkZSBgeGAgZSBgeWAuIExhcyBmaWxhcyBlbiBgeGAgcXVlIG5vIHRlbmdhbiBjb2luY2lkZW5jaWEgY29uIGB5YCB0ZW5kcsOhbiBgTkFgIGVuIGxhcyBudWV2YXMgY29sdW1uYXMuIFNpIGhheSBtw7psdGlwbGVzIGNvaW5jaWRlbmNpYXMgZW50cmUgYHhgZSBgeWAsIGRldnVlbHZlIHRvZGFzIGxhcyBjb2luY2lkZW5jaWFzIHBvc2libGVzLgoKLSAgIGByaWdodF9qb2luKClgOiBlcyBpZ3VhbCBxdWUgYGxlZnRfam9pbigpYCBwZXJvIGludGVyY2FtYmlhbmRvIGVsIG9yZGVuIGRlIGB4YCBlIGB5YC4gRW4gb3RyYXMgcGFsYWJyYXMsIGByaWdodF9qb2luKHgsIHkpYCBlcyBpZMOpbnRpY28gYSBgbGVmdF9qb2luKHksIHgpYC4KCi0gICBgaW5uZXJfam9pbigpYDogZGV2dWVsdmUgdG9kYXMgbGFzIGZpbGFzIGRlIGB4YCBkb25kZSBoYXkgY29pbmNpZGVuY2lhcyBjb24gYHlgIHkgdG9kYXMgbGFzIGNvbHVtbmFzIGRlIGB4YCBlIGB5YC4gU2kgaGF5IG3Dumx0aXBsZXMgY29pbmNpZGVuY2lhcyBlbnRyZSBgeGAgZSBgeWAsIGVudG9uY2VzIGRldnVlbHZlIHRvZGFzIGxhcyBjb2luY2lkZW5jaWFzLiBFc3RvIHNpZ25pZmljYSBxdWUgZWxpbWluYXLDoSBsYXMgZmlsYXMgKG9ic2VydmFjaW9uZXMpIHF1ZSBubyBjb2luY2lkYW4gZW4gYW1iYXMgdGFibGFzLCBsbyBxdWUgcHVlZGUgc2VyIHBlbGlncm9zby4KCiFbXShpbWcvam9pbl9mYW1pbHkucG5nKQoKRW4gZWwgYXJjaGl2byAiZXN0YWNpb25lc19zbW4uY3N2IiBoYXkgbWV0YWRhdG9zIGRlIGxhcyBlc3RhY2lvbmVzIG1ldGVvcm9sw7NnaWNhcyBkZWwgU2VydmljaW8gTWV0ZW9yb2zDs2dpY28gTmFjaW9uYWwgKG5vbWJyZSwgcHJvdmluY2lhLCB1YmljYWNpw7NuLCBldGMuLi4pOgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmVzdGFjaW9uZXMgPC0gcmVhZHI6OnJlYWRfY3N2KCJkYXRvcy9lc3RhY2lvbmVzX3Ntbi5jc3YiKSAKZXN0YWNpb25lcwpgYGAKClBvciBvdHJhIHBhcnRlLCBlbiBlbCBhcmNoaXZvICJvYnNlcnZhY2lvbmVzX3Ntbi5jc3YiIGhheSBkYXRvcyBkaWFyaW9zIGRlIHRlbXBlcmF0dXJhIG3DrW5pbWEgeSBtw6F4aW1hIG9ic2VydmFkb3MgZW4gY2FkYSB1bmEgY2FkYSB1bmEgeSBkaXN0aW50YXMgZmVjaGFzOgoKYGBge3J9Cm9ic2VydmFjaW9uZXMgPC0gcmVhZHI6OnJlYWRfY3N2KCJkYXRvcy9vYnNlcnZhY2lvbmVzX3Ntbi5jc3YiKQpvYnNlcnZhY2lvbmVzCmBgYAoKU2Vyw61hIG11eSDDunRpbCB1bmlyIGFtYmFzIHRhYmxhcyBkZSBtYW5lcmEgZGUgdGVuZXIgbGEgaW5mb3JtYWNpw7NuIGRlIGxhIHRlbXBlcmF0dXJhIGVuIGNhZGEgdWJpY2FjacOzbi4gCgpQYXJhIHVuaXIgbGFzIGRvcyB0YWJsYXMsIGN1YWxxdWllciBmdW5jacOzbiBqb2luIHJlcXVpZXJlIGNpZXJ0YSBpbmZvcm1hY2nDs246CgotICAgbGFzIHRhYmxhcyBhIHVuaXI6IHNvbiBsb3MgZG9zIHByaW1lcm9zIGFyZ3VtZW50b3MuCi0gICBxdcOpIHZhcmlhYmxlIG8gdmFyaWFibGVzIChzZSBwdWVkZSB1c2FyIG3DoXMgZGUgdW5hISkgdXNhciBwYXJhIGlkZW50aWZpY2FyIGNvaW5jaWRlbmNpYXM6IGVsIGFyZ3VtZW50byBgYnlgLgoKVW5hbW9zIGBvYnNlcnZhY2lvbmVzYCB5IGBlc3RhY2lvbmVzYCBwcmltZXJvIGNvbiBgZnVsbF9qb2luKClgOgoKYGBge3J9CmVzdGFjaW9uZXNfb2JzIDwtIGZ1bGxfam9pbihvYnNlcnZhY2lvbmVzLCBlc3RhY2lvbmVzLCBieSA9IGMoInN0YXRpb24iID0gIm5vbWJyZSIpKQplc3RhY2lvbmVzX29icwpgYGAKCkNvbW8gZWwgbm9tYnJlIGRlIGxhIGVzdGFjacOzbiBlbiBsYSB0YWJsYSBgb2JzZXJ2YWNpb25lc2AgZXN0w6EgZW4gdW5hIGNvbHVtbmEgbGxhbWFkYSAic3RhdGlvbiIsIG1pZW50cmFzIHF1ZSBlbiBsYSB0YWJsYSBgZXN0YWNpb25lc2AsIGVzdMOhIGVuIHVuYSBjb2x1bW5hIGxsYW1hZGEgIm5vbWJyZSIuIEN1YW5kbyBwYXNhIGVzbywgZW4gZWwgYXJndW1lbnRvIGBieWAgaGF5IHF1ZSBwb25lciB1biB2ZWN0b3IgY29uIG5vbWJyZXMuCgpTaSBtaXLDoXMgZGUgY2VyY2EgbGEgdGFibGEgdW5pZGEgdmFzIGEgdmVyIHVuIHBhciBkZSBjb3NhczoKCi0gICBUb2RhcyBsYXMgY29sdW1uYXMgZGUgYG9ic2VydmFjaW9uZXNgIHkgZGUgYGVzdGFjaW9uZXNgIGVzdMOhbiBwcmVzZW50ZXMuCi0gICBUb2RhcyBsYXMgb2JzZXJ2YWNpb25lcyBlc3TDoW4gcHJlc2VudGVzLCBhw7puIGxvcyBlc3RhY2lvbmVzIHF1ZSBlc3TDoW4gcHJlc2VudGVzIGVuIGBlc3RhY2lvbmVzYCBwZXJvIG5vIGVuIGBvYnNlcnZhY2lvbmVzYCB5IHZpY2V2ZXJzYS4gCgrCv0V4aXN0ZSBhbGd1bmEgZXN0YWNpw7NuIHF1ZSBubyBlc3TDqSBlbiBhbWJhcyB0YWJsYXM/IFNpIGNvbXBhcsOhcyBsYSBjYW50aWRhZCBkZSBmaWxhcyBkZSBgb2JzZXJ2YWNpb25lc2AgeSBkZSBgZXN0YWNpb25lc19vYnNgLCB2YXMgYSB2ZXIgcXVlIGxhIHByaW1lcmEgdGllbmUgYHIgbnJvdyhvYnNlcnZhY2lvbmVzKWAgZmlsYXMgeSBsYSBzZWd1bmRhLCBgciBucm93KGVzdGFjaW9uZXNfb2JzKWAuIEVzdG8geWEgZGEgdW5hIHBpc3RhIGRlIHF1ZSBwcm9iYWJsZW1lbnRlIGhheWEgdW5hIGVzdGFjacOzbiBlbiBgZXN0YWNpb25lc2AgcXVlIG5vIGVzdMOhIGVuIGBvYnNlcnZhY2lvbmVzYCBxdWUgZXMgbGEgZmlsYSBleHRyYS4KClBhcmEgYnVzY2FyIHNpc3RlbcOhdGljYW1lbnRlIGVzdGFzIGVzdGFjaW9uZXMgZXJyYW50ZXMgaGF5IHF1ZSB1c2FyIGBhbnRpX2pvaW5gLCBxdWUgZGV2dWVsdmUgbGFzIGZpbGFzIGRlIGxhIHRhYmxhIGRlIGxhIGl6cXVpZXJkYSBxdWUgbm8gZXN0w6EgZW4gbGEgZGVyZWNoYS4gRW50b25jZXMsIHVzYW5kbyBleGFjdGFtZW50ZSBlbCBtaXNtbyBjw7NkaWdvIGRlIGFycmliYSBwZXJvIGNhbWJpYW5kbyBgZnVsbF9qb2luYCBwb3IgYGFudGlfam9pbmAsIHF1ZWRhOgoKYGBge3J9CmFudGlfam9pbihvYnNlcnZhY2lvbmVzLCBlc3RhY2lvbmVzLCBieSA9IGMoInN0YXRpb24iID0gIm5vbWJyZSIpKQpgYGAKCkVuIGxhIHRhYmxhIGBvYnNlcnZhY2lvbmVzYCwgbGEgZXN0YWNpw7NuICJQUkVTSURFTkNJQSBST1FVRSBTQUVOWiBQRcORQSBBRVJPIiBleGlzdGUsIHRpZW5lIHVuIHByb2JsZW1hIGRlIGNvZGlmaWNhY2nDs24hIMK/UXXDqSBwYXNhIHNpIGludGVyY2FtYmnDoXMgZWwgb3JkZW4gZGUgbGFzIHZhcmlhYmxlcyAoeSBlbCBvcmRlbiBkZSBsb3Mgbm9tYnJlcyBlbiBlbCB2ZWN0b3IgcXVlIGxlIHBhc2Ftb3MgYSBgYnlgKT8KCgpgYGB7cn0KYW50aV9qb2luKGVzdGFjaW9uZXMsIG9ic2VydmFjaW9uZXMsIGJ5ID0gYygibm9tYnJlIiA9ICJzdGF0aW9uIikpCmBgYApFc3RvIG5vcyBkaWNlIHF1ZSBsYSBlc3RhY2nDs24gIlBSRVNJREVOQ0lBIFJPUVVFIFNBRU5aIFBFw5FBIEFFUk8iLCBlc3TDoSBlbiBsYSB0YWJsYSBgZXN0YWNpb25lc2AgY29uIGxhIGNvZGlmaWNhY2nDs24gY29ycmVjdGEuIGBhbnRpX2pvaW5gIGVzIG11eSDDunRpbCBwYXJhIGVuY29udHJhciBwcm9ibGVtYXMgY29tbyBlc3Rvcy4gCgpgZnVsbF9qb2luYCBlcyBsYSBvcGNpw7NuIG3DoXMgc2VndXJhIHNpIG5vIHNhYsOpcyBzaSB0b2RhcyBsYXMgb2JzZXJ2YWNpb25lcyBkZSB1bmEgdGFibGEgZXN0w6FuIHByZXNlbnRlIGVuIGEgb3RyYS4gU2kgc8OzbG8gdGUgaW50ZXJlc2EgY29uc2VydmFyIGxhcyBmaWxhcyBkZSBsYSB0YWJsYSBkZSBsYSBpenF1aWVyZGEgKGVuIGVzdGUgY2FzbyBgZXN0YWNpb25lc2AgZW50b25jZXM6CgpgYGB7cn0Kb2JzX2VzdGFjaW9uZXMgPC0gbGVmdF9qb2luKG9ic2VydmFjaW9uZXMsIGVzdGFjaW9uZXMsICBieSA9IGMoInN0YXRpb24iID0gIm5vbWJyZSIpKQpvYnNfZXN0YWNpb25lcwpgYGAKCkVzdGEgdGFibGEgdGllbmUgbGEgbWlzbWEgY2FudGlkYWQgZGUgZmlsYXMgcXVlIGBvYnNlcnZhY2lvbmVzYC4gwr9RdcOpIHBhc8OzIGNvbiBsYSBlc3RhY2nDs24gcXVlIGVzdMOhIG1hbCBjb2RpZmljYWRhPyBGaWx0cmFuZG8gbG9zIGRhdG9zOgoKYGBge3J9Cm9ic19lc3RhY2lvbmVzICU+JSAKICBmaWx0ZXIoc3RhcnRzV2l0aChzdGF0aW9uLCAiUFJFU0lERU5DSUEgUk9RVUUiKSkKYGBgCgpgbGVmdF9qb2luYCBsZSBwdXNvIGBOQWAgZW4gbGFzIGZpbGFzIGRlIGBvYnNlcnZhY2lvbmVzYCBxdWUgbm8gdGllbmVuIGNvaW5jaWRlbmNpYSBjb24gYGVzdGFjaW9uZXNgLgoKRmluYWxtZW50ZSwgc2kgcXVpc2llcmFzIHF1ZWRhcnRlIHPDs2xvIGNvbiBsYXMgb2JzZXJ2YWNpb25lcyBxdWUgZXN0w6FuIHByZXNlbnRlcyBlbiBhbWJhcyB0YWJsYXMgdXNhbW9zIGBpbm5lcl9qb2luKClgLgoKYGBge3J9Cm9ic19lc3RhY2lvbmVzIDwtIGlubmVyX2pvaW4ob2JzZXJ2YWNpb25lcywgZXN0YWNpb25lcywgIGJ5ID0gYygic3RhdGlvbiIgPSAibm9tYnJlIikpCm9ic19lc3RhY2lvbmVzCmBgYAoKRW4gZXN0ZSBjYXNvLCBwZXJkZW1vcyBsYXMgZmlsYXMgZGUgYG9ic2VydmFjaW9uZXNgIHF1ZSBubyBlbmNvbnRyYXJvbiBjb2luY2lkZW5jaWEgZW4gYGVzdGFjaW9uZXNgIHkgdmljZXZlcnNhLgoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqRGVzYWbDrW8qKgoKRW4gZWwgYXJjaGl2byAicmFkaWFjacOzbl9zbW4uY3N2IiBoYXkgZGF0b3MgZGUgcmFkaWFjacOzbiBtZWRpYSBkaWFyaWEgbWVkaWRvcyBlbiBkb3MgZXN0YWNpb25lcyBkZWwgU2VydmljaW8gTWV0ZW9yb2zDs2dpY28gTmFjaW9uYWwuIEVsIG9iamV0aXZvIGVzIHF1ZSB1bmFzIGBvYnNlcnZhY2lvbmVzYCBjb24gZXNvcyBkYXRvcyB0ZW5pZW5kbyBlbiBjdWVudGEgdGFudG8gbGEgZXN0YWNpw7NuIGNvbW8gbGEgZmVjaGEuCgoxLiAgTGVlIGxhIGJhc2UgZGUgZGF0b3MgYHJhZGlhY2lvbl9zbW4uY3N2YCBlbiB1bmEgbnVldmEgdmFyaWFibGUgcXVlIHNlIGxsYW1lIGByYWRpYWNpb25gLgoyLiAgUmV2aXPDoSBlbCBub21icmUgZGUgbGFzIHZhcmlhYmxlcyBlbiBlc3RhIGJhc2UgZGUgZGF0b3MsIMK/c2UgbGxhbWFuIGlndWFsIHF1ZSBsYXMgdmFyaWFibGVzIGVuIGBvYnNlcnZhY2lvbmVzYD8KMy4gU2kgdGUgaW50ZXJlc2Egc2FiZXIgbGEgcmVsYWNpw7NuIGVudHJlIGxhIHRlbXBlcmF0dXJhIHkgbGEgcmFkaWFjacOzbiBtZWRpZGEsIMK/cXXDqSB0aXBvIGRlIGpvaW4gY3Jlw6lzIHF1ZSB0ZSBjb252aWVuZSB1c2FyPyAoQXl1ZGE6IMK/VGUgc2lydmUgZGUgYWxnbyB0ZW5lciBkYXRvcyBkZSBlc3RhY2lvbmVzIHkgZmVjaGFzIGRvbmRlIHNlIG1pZGnDsyB0ZW1wZXJhdHVyYSBwZXJvIG5vIHJhZGlhY2nDs24gbyBkb25kZSBzZSBtaWRpw7MgcmFkaWFjacOzbiBwZXJvIG5vIHRlbXBlcmF0dXJhPykKNC4gVW7DrSBsYXMgdGFibGEgdXNhbmRvIGxhIGZ1bmNpw7NuIGpvaW4gcXVlIGVsZWdpc3RlLiBUZW7DqSBlbiBjdWVudGEgcXVlIGFob3JhIHVzYW1vcyBkb3MgdmFyaWFibGVzIGxsYXZlIGBzdGF0aW9uYCB5IGBkYXRlYC4gQnVzY8OhIGVuIGxhIGRvY3VtZW50YWNpw7NuIGPDs21vIGluZGljYXJsZSBlc28gYSBsYSBmdW5jacOzbi4KOjo6Cgo6Ojogey5idG4tZ3JvdXAgcm9sZT0iZ3JvdXAiIGFyaWEtbGFiZWw9Ik5hdmVnYWNpw7NuIn0KPGEgaHJlZj0gIjA1LWRwbHlyLmh0bWwiIGNsYXNzID0gImJ0biBidG4tcHJpbWFyeSI+QW50ZXJpb3I8L2E+IAo8YSBocmVmPSAiMDctZ3JhZmljb3MtSS5odG1sIiBjbGFzcyA9ICJidG4gYnRuLXByaW1hcnkiPlNpZ3VpZW50ZTwvYT4KOjo6CgoK