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). Vamos a comenzar a trabajar con las cinco más comunes:

  • select(): selecciona columnas de una tabla.
  • filter(): selecciona (o filtra) filas de una tabla a partir de una o más condiciones lógicas.
  • group_by(): agrupa una tabla en base al valor de una o más columnas.
  • mutate(): agrega nuevas columnas a una tabla.
  • summarise(): calcula estadísticas para cada grupo de una tabla.

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? Todavía sin correr ninguna línea de código.

  • usar summarise() para calcular la estadística mean(temperatura_media) para cada id_estacion
  • usar group_by() para agrupar por la columna id_estacion
  • usar mutate() para agregar una columna llamada temperatura_media que sea (temperatura_minima + temperatura_maxima)/2.
  • usar filter() para seleccionar solo las filas donde la columnas provincia es igual a “La Pampa”

Para usar {dplyr} primero hay que instalarlo con el comando:

install.packages('dplyr')

o junto con todos los paquetes del universo de Tidyverse y luego cargarlo en memoria con

library(dplyr)

Cargar los datos de la estación de Bariloche (para un recordatorio, podés ir a Lectura de datos ordenados):

library(readr)
bariloche <- 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.

Seleccionando columnas con select()

Para quedarse únicamente con las columnas de Fecha, Temperatura_Abrigo_150cm y Humedad_Media podés usar select(), el primer argumento es siempre el data.frame:

select(bariloche, Fecha, Temperatura_Abrigo_150cm, Humedad_Media) # Ojo con las mayúsculas!
## # A tibble: 3,024 × 3
##    Fecha               Temperatura_Abrigo_150cm Humedad_Media
##    <dttm>                                 <dbl>         <dbl>
##  1 2011-05-28 00:00:00                    NA               NA
##  2 2011-05-29 00:00:00                     4.27            79
##  3 2011-05-30 00:00:00                     1.69            93
##  4 2011-05-31 00:00:00                     3.47            96
##  5 2011-06-01 00:00:00                     2.90            98
##  6 2011-06-02 00:00:00                     6.26            93
##  7 2011-06-03 00:00:00                     3.45            88
##  8 2011-06-04 00:00:00                     6.09            87
##  9 2011-06-05 00:00:00                     5.00           100
## 10 2011-06-06 00:00:00                     5.45            91
## # … with 3,014 more rows

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

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>, …

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 Fecha, Temperatura_Abrigo_150cm y Humedad_Media tenés que asignar el resultado a una nueva variable.

temp_hum <- select(bariloche, Fecha, Temperatura_Abrigo_150cm, Humedad_Media)
temp_hum
## # A tibble: 3,024 × 3
##    Fecha               Temperatura_Abrigo_150cm Humedad_Media
##    <dttm>                                 <dbl>         <dbl>
##  1 2011-05-28 00:00:00                    NA               NA
##  2 2011-05-29 00:00:00                     4.27            79
##  3 2011-05-30 00:00:00                     1.69            93
##  4 2011-05-31 00:00:00                     3.47            96
##  5 2011-06-01 00:00:00                     2.90            98
##  6 2011-06-02 00:00:00                     6.26            93
##  7 2011-06-03 00:00:00                     3.45            88
##  8 2011-06-04 00:00:00                     6.09            87
##  9 2011-06-05 00:00:00                     5.00           100
## 10 2011-06-06 00:00:00                     5.45            91
## # … with 3,014 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 aquellos días con temperatura menor a 0 grados:

filter(temp_hum, Temperatura_Abrigo_150cm < 0)
## # A tibble: 38 × 3
##    Fecha               Temperatura_Abrigo_150cm Humedad_Media
##    <dttm>                                 <dbl>         <dbl>
##  1 2011-06-25 00:00:00                   -1.91             86
##  2 2011-07-02 00:00:00                   -0.865            79
##  3 2011-07-03 00:00:00                   -1.77             80
##  4 2011-07-04 00:00:00                   -1.18             82
##  5 2011-07-05 00:00:00                   -1.58             90
##  6 2011-07-17 00:00:00                   -0.647            86
##  7 2011-07-18 00:00:00                   -0.126            86
##  8 2011-07-31 00:00:00                   -0.756            89
##  9 2012-06-06 00:00:00                   -1.92             83
## 10 2012-06-07 00:00:00                   -0.915            85
## # … with 28 more rows

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 filtar las filas correspondientes temperaturas menores a 0°C) se pueden escribir uno después del otro y sin asignar los resultados intermedios a nuevas variables de esta forma:

bariloche %>% 
  select(Fecha, Temperatura_Abrigo_150cm, Humedad_Media) %>% 
  filter(Temperatura_Abrigo_150cm < 0)
## # A tibble: 38 × 3
##    Fecha               Temperatura_Abrigo_150cm Humedad_Media
##    <dttm>                                 <dbl>         <dbl>
##  1 2011-06-25 00:00:00                   -1.91             86
##  2 2011-07-02 00:00:00                   -0.865            79
##  3 2011-07-03 00:00:00                   -1.77             80
##  4 2011-07-04 00:00:00                   -1.18             82
##  5 2011-07-05 00:00:00                   -1.58             90
##  6 2011-07-17 00:00:00                   -0.647            86
##  7 2011-07-18 00:00:00                   -0.126            86
##  8 2011-07-31 00:00:00                   -0.756            89
##  9 2012-06-06 00:00:00                   -1.92             83
## 10 2012-06-07 00:00:00                   -0.915            85
## # … with 28 more rows

La forma de “leer” esto es “Tomá la variable bariloche, después aplicale select(Fecha, Temperatura_Abrigo_150cm, Humedad_Media), después aplicale filter(Temperatura_Abrigo_150cm < 0)”.

Cómo vimos, el primero argumento de todas las funciones de dplyr espera un data.frame y justamente el operador %>% toma el data.frame bariloche y se lo pasa al primer argumento de select(). Luego el data.frame resultante de seleccionar las columnas Fecha, Temperatura_Abrigo_150cm y Humedad_Media 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 Fecha, Temperatura del suelo y la Humedad únicamente cuando la Humedad es igual a 100%.

bariloche %>% 
  filter(Humedad_Media == ___) %>%
  select(___, ___, ___)

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

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

Para agrupar la tabla bariloche según el mes usamos el siguiente código:

bariloche %>% 
  group_by(mes) 
## # A tibble: 3,024 × 31
## # Groups:   mes [12]
##    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>, …

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 (“mes”) y cuántos grupos hay (“12”). 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 temperatura:

bariloche %>% 
  group_by(mes) %>% 
  summarise(Temperatura_media = mean(Temperatura_Abrigo_150cm))
## # A tibble: 12 × 2
##    mes        Temperatura_media
##    <chr>                  <dbl>
##  1 abril                   NA  
##  2 agosto                  NA  
##  3 diciembre               14.0
##  4 enero                   NA  
##  5 febrero                 NA  
##  6 julio                   NA  
##  7 junio                   NA  
##  8 marzo                   NA  
##  9 mayo                    NA  
## 10 noviembre               NA  
## 11 octubre                 NA  
## 12 septiembre              NA

¡Tadá! summarise() devuelve una tabla con una columna para el continente y otra nueva, llamada “Temperatura_media” que contiene el promedio de Temperatura_Abrigo_150cm para cada grupo. Pero… aparecen muchos datos faltantes, cuando en realidad hay muchas observaciones para cada mes.

Si revisamos la ayuda de la función mean(), vemos que tiene un argumento que por defecto es na.rm = FALSE, esto significa que si al menos una observación es NA, el resultado será NA. Cambiemos este argumento a TRUE para cambiar el comportamiento de la función.

bariloche %>% 
  group_by(mes) %>% 
  summarise(Temperatura_media = mean(Temperatura_Abrigo_150cm, na.rm = TRUE))
## # A tibble: 12 × 2
##    mes        Temperatura_media
##    <chr>                  <dbl>
##  1 abril                  10.5 
##  2 agosto                  4.83
##  3 diciembre              14.0 
##  4 enero                  16.6 
##  5 febrero                16.0 
##  6 julio                   3.69
##  7 junio                   5.18
##  8 marzo                  13.8 
##  9 mayo                    7.34
## 10 noviembre              11.6 
## 11 octubre                 8.59
## 12 septiembre              6.43

Ahora si, tenemos la temperatura media para cada mes. group_by() permite agrupar en base a múltiples columnas y summarise() permite generar múltiples columnas de resumen. 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.)

bariloche %>% 
  summarise(Temperatura_media = mean(Temperatura_Abrigo_150cm))

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(mes) ya que contienen caracteres, pero tal vez nos interese contar la cantidad de observaciones por mes:

bariloche %>% 
  group_by(mes) %>% 
  summarise(cantidad = n())
## # A tibble: 12 × 2
##    mes        cantidad
##    <chr>         <int>
##  1 abril           239
##  2 agosto          276
##  3 diciembre       248
##  4 enero           248
##  5 febrero         227
##  6 julio           273
##  7 junio           254
##  8 marzo           248
##  9 mayo            239
## 10 noviembre       241
## 11 octubre         279
## 12 septiembre      252

En R se puede resolver un problema de muchas maneras, por ejemplo, el código anterior y el que sigue dan resultados equivalentes:

bariloche %>% 
  group_by(mes) %>% 
  count()
## # A tibble: 12 × 2
## # Groups:   mes [12]
##    mes            n
##    <chr>      <int>
##  1 abril        239
##  2 agosto       276
##  3 diciembre    248
##  4 enero        248
##  5 febrero      227
##  6 julio        273
##  7 junio        254
##  8 marzo        248
##  9 mayo         239
## 10 noviembre    241
## 11 octubre      279
## 12 septiembre   252

En este caso count() es análogo a summarise(cantidad = n()).

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 bariloche tiene información de temperaturas en grados centígrados y puede ser necesario convertirlas a kelvin. mutate() permite agregar una nueva columna llamada “Temperatura_kelvin” con esa información:

bariloche %>% 
  mutate(Temperatura_kelvin = Temperatura_Abrigo_150cm + 273,15)
## # A tibble: 3,024 × 33
##    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 29 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>, …

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

Si quisiéramos aplicar la misma operación a todas las columnas que tienen temperatura, la función across() brinda un atajo para no tener que aplicar mutate() una y otra vez a cada cada columna.

bariloche %>% 
  summarise(across(starts_with("Temperatura"), ~.x + 273,15))
## # A tibble: 3,024 × 9
##    Temperatura_Abrigo_150cm Temperatura_Abrig… Temperatura_Abr… Temperatura_Int…
##                       <dbl>              <dbl>            <dbl>            <dbl>
##  1                      NA                 NA               NA                NA
##  2                     277.               286.             272.               NA
##  3                     275.               282.             270.               NA
##  4                     276.               283.             274.               NA
##  5                     276.               283.             272.               NA
##  6                     279.               286.             276.               NA
##  7                     276.               286.             271.               NA
##  8                     279.               283.             277.               NA
##  9                     278.               280.             276.               NA
## 10                     278.               282.             276.               NA
## # … with 3,014 more rows, and 5 more variables:
## #   Temperatura_Intemperie_50cm_Minima <dbl>,
## #   Temperatura_Suelo_5cm_Media <dbl>, Temperatura_Suelo_10cm_Media <dbl>,
## #   Temperatura_Inte_5cm <dbl>, Temperatura_Intemperie_150cm_Minima <dbl>

Varias cosas pasaron acá:

  • El resultado solo incluye las columnas asociadas a la temperatura.
  • No fue necesario escribir el nombre de cada columna, la función start_with() automáticamente busca las columnas que comienzan con la palabra que indicamos, en este caso “Temperatura”.
  • Usamos la notación funcional de R, que arranca con ~ y luego le indicamos que a .x (cada columna) le sume 273,15.

Desafío

Si quisieras calcular la temperatura media para cada día y guardarla en una nueva variable, ¿cómo completarías el siguiente código?

bariloche %>% 
  mutate(____ = ____)

Ahora imaginemos que necesitamos modificar algunas observaciones, aquellas que cumplen con una condición. Por ejemplo, descubrimos que cuando la humedad es 100%, el sensor de temperatura no funciona correctamente y necesitamos aplicarle una corrección de -2° en esas situaciones.

Podríamos filtrar las observaciones que cumplen con que Humedad_Media == 100 y luego modificar la temperatura, pero entonces tendríamos una base de datos para las observaciones con mayor humedad y la temperatura corregida y otra base de datos sin la corrección. Necesitamos poder modificar solo algunas observaciones y que el resto se mantenga igual.

Para eso podemos utilizar la función if_else(), que aplica una operación si ocurre algo y otra operación si no. En este caso si Humedad_Media == 100 es verdadera, restamos 2 grados a la Temperatura, si es falsa no la modificamos. Y por supuesto se la aplicamos a la columna con mutate()

bariloche %>% 
  mutate(Temperatura_Abrigo_150cm = if_else(Humedad_Media == 100,       # Condición
                                            Temperatura_Abrigo_150cm -2,# Si es verdadera
                                            Temperatura_Abrigo_150cm))  # Si es falsa
## # 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                    3.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>, …

La mayoría de las funciones de R pueden trabajar sobre vectores (y las columnas de un data.frame son como vectores ordenados!) y lo hacen elemento a elemento.

En este caso if_else() recorre la columna con la información de humedad y elemento a elemento revisa si el valor es igual a 100. Luego decide si debe modificar o no cada elemento, en el orden en el que aparecen.

Desafío

Ahora es tu turno, “arreglá” las variables Temperatura_Abrigo_150cm_Maxima y Temperatura_Abrigo_150cm_Minima para aplicar la misma corrección de 2 grados.

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 “Temperatura_max_media” con el la temperatura máxima media para cada mes? ¿Qué hace el otro?

bariloche %>% 
  group_by(mes) %>% 
  mutate(Temperatura_max_media = mean(Temperatura_Abrigo_150cm_Maxima, na.rm = TRUE)) 

bariloche %>% 
  mutate(Temperatura_max_media = mean(Temperatura_Abrigo_150cm_Maxima, na.rm = TRUE))  

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:

bariloche %>% 
  group_by(mes) %>% 
  mutate(Temperatura_max_media = mean(Temperatura_Abrigo_150cm_Maxima, na.rm = TRUE)) %>%  
  ungroup()
## # A tibble: 3,024 × 32
##    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 28 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>, …
LS0tCnRpdGxlOiAiTWFuaXB1bGFjacOzbiBkZSBkYXRvcyBvcmRlbmFkb3MgdXNhbmRvIHtkcGx5cn0gLSBQcmltZXJhIHBhcnRlIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgaGlnaGxpZ2h0OiB0YW5nbwotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKCkVsIHBhcXVldGUge2RwbHlyfSBwcm92ZWUgdW5hIGVub3JtZSBjYW50aWRhZCBkZSBmdW5jaW9uZXMgw7p0aWxlcyBwYXJhIG1hbmlwdWxhciB5IGFuYWxpemFyIGRhdG9zIGRlIG1hbmVyYSBpbnR1aXRpdmEgeSBleHByZXNpdmEuIAoKRWwgZXNww61yaXR1IGRldHLDoXMgZGUge2RwbHlyfSBlcyBxdWUgbGEgZW5vcm1lIG1heW9yw61hIGRlIGxvcyBhbsOhbGlzaXMsIHBvciBtw6FzIGNvbXBsaWNhZG9zIHF1ZSBzZWFuLCBpbnZvbHVjcmFuIGNvbWJpbmFyIHkgZW5jYWRlbmFyIHVuYSBzZXJpZSByZWxhdGl2YW1lbnRlIGFjb3RhZGEgZGUgYWNjaW9uZXMgKG8gdmVyYm9zKS4gVmFtb3MgYSBjb21lbnphciBhIHRyYWJhamFyIGNvbiBsYXMgY2luY28gbcOhcyBjb211bmVzOgoKICAqIGBzZWxlY3QoKWA6IHNlbGVjY2lvbmEgY29sdW1uYXMgZGUgdW5hIHRhYmxhLgogICogYGZpbHRlcigpYDogc2VsZWNjaW9uYSAobyBmaWx0cmEpIGZpbGFzIGRlIHVuYSB0YWJsYSBhIHBhcnRpciBkZSB1bmEgbyBtw6FzIGNvbmRpY2lvbmVzIGzDs2dpY2FzLgogICogYGdyb3VwX2J5KClgOiBhZ3J1cGEgdW5hIHRhYmxhIGVuIGJhc2UgYWwgdmFsb3IgZGUgdW5hIG8gbcOhcyBjb2x1bW5hcy4KICAqIGBtdXRhdGUoKWA6IGFncmVnYSBudWV2YXMgY29sdW1uYXMgYSB1bmEgdGFibGEuCiAgKiBgc3VtbWFyaXNlKClgOiBjYWxjdWxhIGVzdGFkw61zdGljYXMgcGFyYSBjYWRhIGdydXBvIGRlIHVuYSB0YWJsYS4KCjo6OiB7LmFsZXJ0IC5hbGVydC1pbmZvfQoqKlByaW1lciBkZXNhZsOtbzoqKgoKVGUgZGllcm9uIHVuYSB0YWJsYSBjb24gZGF0b3MgZGUgdGVtcGVyYXR1cmEgbcOtbmltYSB5IG3DoXhpbWEgcGFyYSBkaXN0aW50YXMgZXN0YWNpb25lcyBtZXRlb3JvbMOzZ2ljYXMgZGUgdG9kbyBlbCBwYcOtcyBkdXJhbnRlIGxvcyAzNjUgZMOtYXMgZGUgdW4gYcOxby4gTGFzIGNvbHVtbmFzIHNvbjogYGlkX2VzdGFjaW9uYCwgYHRlbXBlcmF0dXJhX21heGltYWAsIGB0ZW1wZXJhdHVyYV9taW5pbWFgIHkgYHByb3ZpbmNpYWAuIEVuIGJhc2UgYSBlc29zIGRhdG9zLCB0ZSBwaWRlbiBxdWUgY29tcHV0ZW4gbGEgdGVtcGVyYXR1cmEgbWVkaWEgYW51YWwgZGUgY2FkYSBlc3RhY2nDs24gw7puaWNhbWVudGUgZGUgbGFzIGVzdGFjaW9uZXMgZGUgTGEgUGFtcGEuIAoKwr9FbiBxdcOpIG9yZGVuIGVqZWN1dGFyw61hcyBlc3RvcyBwYXNvcyBwYXJhIG9idGVuZXIgZWwgcmVzdWx0YWRvIGRlc2VhZG8/IFRvZGF2w61hIHNpbiBjb3JyZXIgbmluZ3VuYSBsw61uZWEgZGUgY8OzZGlnby4KCiogdXNhciBgc3VtbWFyaXNlKClgIHBhcmEgY2FsY3VsYXIgbGEgZXN0YWTDrXN0aWNhIGBtZWFuKHRlbXBlcmF0dXJhX21lZGlhKWAgcGFyYSBjYWRhIGBpZF9lc3RhY2lvbmAKKiB1c2FyIGBncm91cF9ieSgpYCBwYXJhIGFncnVwYXIgcG9yIGxhIGNvbHVtbmEgYGlkX2VzdGFjaW9uYAoqIHVzYXIgYG11dGF0ZSgpYCBwYXJhIGFncmVnYXIgdW5hIGNvbHVtbmEgbGxhbWFkYSBgdGVtcGVyYXR1cmFfbWVkaWFgIHF1ZSBzZWEgYCh0ZW1wZXJhdHVyYV9taW5pbWEgKyB0ZW1wZXJhdHVyYV9tYXhpbWEpLzJgLgoqIHVzYXIgYGZpbHRlcigpYCBwYXJhIHNlbGVjY2lvbmFyIHNvbG8gbGFzIGZpbGFzIGRvbmRlIGxhIGNvbHVtbmFzIGBwcm92aW5jaWFgIGVzIGlndWFsIGEgIkxhIFBhbXBhIgo6OjoKClBhcmEgdXNhciB7ZHBseXJ9IHByaW1lcm8gaGF5IHF1ZSBpbnN0YWxhcmxvIGNvbiBlbCBjb21hbmRvOgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KaW5zdGFsbC5wYWNrYWdlcygnZHBseXInKQpgYGAKCm8ganVudG8gY29uIHRvZG9zIGxvcyBwYXF1ZXRlcyBkZWwgdW5pdmVyc28gZGUgVGlkeXZlcnNlIHkgbHVlZ28gY2FyZ2FybG8gZW4gbWVtb3JpYSBjb24KCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpgYGAKCkNhcmdhciBsb3MgZGF0b3MgZGUgbGEgZXN0YWNpw7NuIGRlIEJhcmlsb2NoZSAocGFyYSB1biByZWNvcmRhdG9yaW8sIHBvZMOpcyBpciBhIFtMZWN0dXJhIGRlIGRhdG9zIG9yZGVuYWRvc10oMDQtbGVjdHVyYS1kYXRvcy5odG1sKSk6CgpgYGB7cn0KbGlicmFyeShyZWFkcikKYmFyaWxvY2hlIDwtIHJlYWRfY3N2KCJkYXRvcy9iYXJpbG9jaGVfZW5saW1waW8uY3N2IikKYGBgCgojIyBTZWxlY2Npb25hbmRvIGNvbHVtbmFzIGNvbiBgc2VsZWN0KClgCgpQYXJhIHF1ZWRhcnNlIMO6bmljYW1lbnRlIGNvbiBsYXMgY29sdW1uYXMgZGUgRmVjaGEsIFRlbXBlcmF0dXJhX0Ficmlnb18xNTBjbSB5IEh1bWVkYWRfTWVkaWEgcG9kw6lzIHVzYXIgYHNlbGVjdCgpYCwgZWwgcHJpbWVyIGFyZ3VtZW50byBlcyBzaWVtcHJlIGVsIGRhdGEuZnJhbWU6CiAgCmBgYHtyfQpzZWxlY3QoYmFyaWxvY2hlLCBGZWNoYSwgVGVtcGVyYXR1cmFfQWJyaWdvXzE1MGNtLCBIdW1lZGFkX01lZGlhKSAjIE9qbyBjb24gbGFzIG1hecO6c2N1bGFzIQpgYGAKCsK/RMOzbmRlIHF1ZWTDsyBlc3RlIHJlc3VsdGFkbz8gU2kgdGUgZmlqw6FzIGVuIGxhIHZhcmlhYmxlIGBiYXJpbG9jaGVgLCDDqXN0YSBlc3TDoSBpbnRhY3RhOgoKYGBge3J9CmJhcmlsb2NoZQpgYGAKCmBzZWxlY3QoKWAgeSBlbCByZXN0byBkZSBsYXMgZnVuY2lvbmVzIGRlIHtkcGx5cn0gc2llbXByZSBnZW5lcmFuIHVuYSBudWV2YSB0YWJsYSB5IG51bmNhIG1vZGlmaWNhbiBsYSB0YWJsYSBvcmlnaW5hbC4gUGFyYSBndWFyZGFyIGxhIHRhYmxhIGNvbiBsYXMgdHJlcyBjb2x1bW5hcyBgRmVjaGFgLCBgVGVtcGVyYXR1cmFfQWJyaWdvXzE1MGNtYCB5IGBIdW1lZGFkX01lZGlhYCB0ZW7DqXMgcXVlIGFzaWduYXIgZWwgcmVzdWx0YWRvIGEgdW5hIG51ZXZhIHZhcmlhYmxlLgoKYGBge3J9CnRlbXBfaHVtIDwtIHNlbGVjdChiYXJpbG9jaGUsIEZlY2hhLCBUZW1wZXJhdHVyYV9BYnJpZ29fMTUwY20sIEh1bWVkYWRfTWVkaWEpCnRlbXBfaHVtCmBgYAoKIVtDw7NtbyBmdW5jaW9uYSBgc2VsZWN0KClgXShpbWcvZHBseXItc2VsZWN0LnBuZykKCiMjIEZpbHRyYW5kbyBmaWxhcyBjb24gYGZpbHRlcigpYAoKQWhvcmEgcG9kw6lzIHVzYXIgYGZpbHRlcigpYCBwYXJhIHF1ZWRhcnRlIGNvbiBzw7NsbyB1bmFzIGZpbGFzLiBQb3IgZWplbXBsbywgcGFyYSB2ZXIgYXF1ZWxsb3MgZMOtYXMgY29uIHRlbXBlcmF0dXJhIG1lbm9yIGEgMCBncmFkb3M6CgpgYGB7cn0KZmlsdGVyKHRlbXBfaHVtLCBUZW1wZXJhdHVyYV9BYnJpZ29fMTUwY20gPCAwKQpgYGAKCkxhIG1heW9yw61hIGRlIGxvcyBhbsOhbGlzaXMgY29uc2lzdGVuIGVuIHZhcmlvcyBwYXNvcyAoZW4gZWwgcHJpbWVyIGRlc2Fmw61vIHVzYXN0ZSA0IHBhc29zIHBhcmEgY2FsY3VsYXIgbGFzIHRlbXBlcmF0dXJhcyBtZWRpYXMgZGUgdW5hIHNlcmllIGRlIGVzdGFjaW9uZXMpLiBMYSDDum5pY2EgdGFibGEgcXVlIHRlIGludGVyZXNhIGVzIGxhIMO6bHRpbWEsIHBvciBsbyBxdWUgaXIgYXNpZ25hbmRvIHZhcmlhYmxlcyBudWV2YXMgZW4gY2FkYSBwYXNvIGludGVybWVkaW8gZXMgdGVkaW9zbyB5IHBvY28gcHLDoWN0aWNvLiBQYXJhIGVzbyBzZSB1c2EgZWwgb3BlcmFkb3IgJ3BpcGUnIChgJT4lYCkuIAoKRWwgb3BlcmFkb3IgJ3BpcGUnIChgJT4lYCkgYWdhcnJhIGVsIHJlc3VsdGFkbyBkZSB1bmEgZnVuY2nDs24geSBzZSBsbyBwYXNhIGEgbGEgc2lndWllbnRlLiBFc3RvIHBlcm1pdGUgZXNjcmliaXIgZWwgY8OzZGlnbyBjb21vIHVuYSBjYWRlbmEgZGUgZnVuY2lvbmVzIHF1ZSB2YW4gb3BlcmFuZG8gc29icmUgZWwgcmVzdWx0YWRvIGRlIGxhIGFudGVyaW9yLgoKTGFzIGRvcyBvcGVyYWNpb25lcyBhbnRlcmlvcmVzIChzZWxlY2Npb25hciB0cmVzIGNvbHVtbmFzIHkgbHVlZ28gZmlsdGFyIGxhcyBmaWxhcyBjb3JyZXNwb25kaWVudGVzIHRlbXBlcmF0dXJhcyBtZW5vcmVzIGEgMMKwQykgc2UgcHVlZGVuIGVzY3JpYmlyIHVubyBkZXNwdcOpcyBkZWwgb3RybyB5IHNpbiBhc2lnbmFyIGxvcyByZXN1bHRhZG9zIGludGVybWVkaW9zIGEgbnVldmFzIHZhcmlhYmxlcyBkZSBlc3RhIGZvcm1hOgoKYGBge3J9CmJhcmlsb2NoZSAlPiUgCiAgc2VsZWN0KEZlY2hhLCBUZW1wZXJhdHVyYV9BYnJpZ29fMTUwY20sIEh1bWVkYWRfTWVkaWEpICU+JSAKICBmaWx0ZXIoVGVtcGVyYXR1cmFfQWJyaWdvXzE1MGNtIDwgMCkKYGBgCgpMYSBmb3JtYSBkZSAibGVlciIgZXN0byBlcyAiVG9tw6EgbGEgdmFyaWFibGUgYGJhcmlsb2NoZWAsICoqZGVzcHXDqXMqKiBhcGxpY2FsZSBgc2VsZWN0KEZlY2hhLCBUZW1wZXJhdHVyYV9BYnJpZ29fMTUwY20sIEh1bWVkYWRfTWVkaWEpYCwgKipkZXNwdcOpcyoqIGFwbGljYWxlIGBmaWx0ZXIoVGVtcGVyYXR1cmFfQWJyaWdvXzE1MGNtIDwgMClgIi4gCgpDw7NtbyB2aW1vcywgZWwgcHJpbWVybyBhcmd1bWVudG8gZGUgdG9kYXMgbGFzIGZ1bmNpb25lcyBkZSBkcGx5ciBlc3BlcmEgdW4gZGF0YS5mcmFtZSB5IGp1c3RhbWVudGUgZWwgb3BlcmFkb3IgYCU+JWAgdG9tYSBlbCBkYXRhLmZyYW1lIGBiYXJpbG9jaGVgIHkgc2UgbG8gcGFzYSBhbCBwcmltZXIgYXJndW1lbnRvIGRlIGBzZWxlY3QoKWAuIEx1ZWdvIGVsIGRhdGEuZnJhbWUgcmVzdWx0YW50ZSBkZSBzZWxlY2Npb25hciBsYXMgY29sdW1uYXMgRmVjaGEsIFRlbXBlcmF0dXJhX0Ficmlnb18xNTBjbSB5IEh1bWVkYWRfTWVkaWEgKnBhc2EqIGNvbW8gZWwgcHJpbWVyIGFyZ3VtZW50byBkZSBsYSBmdW5jacOzbiBgZmlsdGVyKClgIGdyYWNpYXMgYWwgYCU+JWAuCgoKOjo6IHsuYWxlcnQgLmFsZXJ0LXN1Y2Nlc3N9CioqVGlwOioqCgpFbiBSU3R1ZGlvIHBvZMOpcyBlc2NyaWJpciBgJT4lYCB1c2FuZG8gZWwgYXRham8gZGUgdGVjbGFkbyBDdHIgKyBTaGlmdCArIE0uIMKhUHJvYmFsbyEKOjo6CgoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqRGVzYWbDrW86KioKCkNvbXBsZXTDoSBlc3RhIGNhZGVuYSBwYXJhIHByb2R1Y2lyIHVuYSB0YWJsYSBxdWUgY29udGVuZ2EgbG9zIHZhbG9yZXMgZGUgRmVjaGEsIFRlbXBlcmF0dXJhIGRlbCBzdWVsbyB5IGxhIEh1bWVkYWQgw7puaWNhbWVudGUgY3VhbmRvIGxhIEh1bWVkYWQgZXMgaWd1YWwgYSAxMDAlLiAKCmBgYHtyLCBldmFsID0gRkFMU0V9CmJhcmlsb2NoZSAlPiUgCiAgZmlsdGVyKEh1bWVkYWRfTWVkaWEgPT0gX19fKSAlPiUKICBzZWxlY3QoX19fLCBfX18sIF9fXykKYGBgCgo6OjoKCiMjIEFncnVwYW5kbyB5IHJlZHVjaWVuZG8gY29uIGBncm91cF9ieSgpICU+JSBzdW1tYXJpc2UoKWAKClNpIHF1ZXLDqXMgY2FsY3VsYXIgbGEgdGVtcGVyYXR1cmEgbWVkaWEgcGFyYSBjYWRhIG1lcywgdGVuw6lzIHF1ZSB1c2FyIGVsIGNvbWJvIGBncm91cF9ieSgpICU+JSBzdW1tYXJpc2UoKWAuIEVzIGRlY2lyLCBwcmltZXJvIGFncnVwYXIgbGEgdGFibGEgc2Vnw7puIGxhIGNvbHVtbmEgYG1lc2AgeSBsdWVnbyBjYWxjdWxhciB1biBwcm9tZWRpbyBkZSBgVGVtcGVyYXR1cmFfQWJyaWdvXzE1MGNtYCBwYXJhIGNhZGEgZ3J1cG8uCgpQYXJhIGFncnVwYXIgbGEgdGFibGEgYGJhcmlsb2NoZWAgc2Vnw7puIGVsIG1lcyB1c2Ftb3MgZWwgc2lndWllbnRlIGPDs2RpZ286CgpgYGB7cn0KYmFyaWxvY2hlICU+JSAKICBncm91cF9ieShtZXMpIApgYGAKCkEgcHJpbWVyYSB2aXN0YSBwYXJlY2Vyw61hIHF1ZSBsYSBmdW5jacOzbiBubyBoaXpvIG5hZGEsIHBlcm8gZmlqYXRlIHF1ZSBlbCByZXN1bHRhZG8gYWhvcmEgZGljZSBxdWUgdGllbmUgZ3J1cG9zICgiR3JvdXBzOiAiKSwgeSBub3MgZGljZSBxdcOpIGNvbHVtbmEgZXMgbGEgcXVlIGFncnVwYSBsb3MgZGF0b3MgKCJtZXMiKSB5IGN1w6FudG9zIGdydXBvcyBoYXkgKCIxMiIpLiBMYXMgb3BlcmFjaW9uZXMgc3Vic2lndWllbnRlcyBxdWUgbGUgaGFnYW1vcyBhIGVzdGEgdGFibGEgdmFuIGEgaGFjZXJzZSAqcGFyYSBjYWRhIGdydXBvKi4gCgpQYXJhIHZlciBlc3RvIGVuIGFjY2nDs24sIHVzw6EgYHN1bW1hcmlzZSgpYCBwYXJhIGNvbXB1dGFyIGVsIHByb21lZGlvIGRlIGxhIHRlbXBlcmF0dXJhOgoKYGBge3J9CmJhcmlsb2NoZSAlPiUgCiAgZ3JvdXBfYnkobWVzKSAlPiUgCiAgc3VtbWFyaXNlKFRlbXBlcmF0dXJhX21lZGlhID0gbWVhbihUZW1wZXJhdHVyYV9BYnJpZ29fMTUwY20pKQpgYGAKCsKhVGFkw6EhIGBzdW1tYXJpc2UoKWAgZGV2dWVsdmUgdW5hIHRhYmxhIGNvbiB1bmEgY29sdW1uYSBwYXJhIGVsIGNvbnRpbmVudGUgeSBvdHJhIG51ZXZhLCBsbGFtYWRhICJUZW1wZXJhdHVyYV9tZWRpYSIgcXVlIGNvbnRpZW5lIGVsIHByb21lZGlvIGRlIGBUZW1wZXJhdHVyYV9BYnJpZ29fMTUwY21gIHBhcmEgY2FkYSBncnVwby4gUGVyby4uLiBhcGFyZWNlbiBtdWNob3MgZGF0b3MgZmFsdGFudGVzLCBjdWFuZG8gZW4gcmVhbGlkYWQgaGF5IG11Y2hhcyBvYnNlcnZhY2lvbmVzIHBhcmEgY2FkYSBtZXMuIAoKU2kgcmV2aXNhbW9zIGxhIGF5dWRhIGRlIGxhIGZ1bmNpw7NuIGBtZWFuKClgLCB2ZW1vcyBxdWUgdGllbmUgdW4gYXJndW1lbnRvIHF1ZSBwb3IgZGVmZWN0byBlcyBgbmEucm0gPSBGQUxTRWAsIGVzdG8gc2lnbmlmaWNhIHF1ZSBzaSBhbCBtZW5vcyB1bmEgb2JzZXJ2YWNpw7NuIGVzIGBOQWAsIGVsIHJlc3VsdGFkbyBzZXLDoSBgTkFgLiBDYW1iaWVtb3MgZXN0ZSBhcmd1bWVudG8gYSBgVFJVRWAgcGFyYSBjYW1iaWFyIGVsIGNvbXBvcnRhbWllbnRvIGRlIGxhIGZ1bmNpw7NuLgoKYGBge3J9CmJhcmlsb2NoZSAlPiUgCiAgZ3JvdXBfYnkobWVzKSAlPiUgCiAgc3VtbWFyaXNlKFRlbXBlcmF0dXJhX21lZGlhID0gbWVhbihUZW1wZXJhdHVyYV9BYnJpZ29fMTUwY20sIG5hLnJtID0gVFJVRSkpCmBgYAoKQWhvcmEgc2ksIHRlbmVtb3MgbGEgdGVtcGVyYXR1cmEgbWVkaWEgcGFyYSBjYWRhIG1lcy4gYGdyb3VwX2J5KClgIHBlcm1pdGUgYWdydXBhciBlbiBiYXNlIGEgbcO6bHRpcGxlcyBjb2x1bW5hcyB5IGBzdW1tYXJpc2UoKWAgcGVybWl0ZSBnZW5lcmFyIG3Dumx0aXBsZXMgY29sdW1uYXMgZGUgcmVzdW1lbi4gRWwgcmVzdWx0YWRvIHZhIGEgc2llbXByZSBzZXIgdW5hIHRhYmxhIGNvbiBsYSBtaXNtYSBjYW50aWRhZCBkZSBmaWxhcyBxdWUgZ3J1cG9zIHkgdW5hIGNhbnRpZGFkIGRlIGNvbHVtbmFzIGlndWFsIGEgbGEgY2FudGlkYWQgZGUgY29sdW1uYXMgdXNhZGFzIHBhcmEgYWdydXBhciB5IGxvcyBlc3RhZMOtc3RpY29zIGNvbXB1dGFkb3MuIAoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqRGVzYWbDrW86KioKCsK/Q3XDoWwgdGUgaW1hZ2luw6FzIHF1ZSB2YSBhIHNlciBlbCByZXN1bHRhZG8gZGVsIHNpZ3VpZW50ZSBjw7NkaWdvPyDCv0N1w6FudGFzIGZpbGFzIHkgY29sdW1uYXMgdmEgYSB0ZW5lcj8gKFRyYXTDoSBkZSBwZW5zYXJsbyBhbnRlcyBkZSBjb3JyZXJsby4pCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpiYXJpbG9jaGUgJT4lIAogIHN1bW1hcmlzZShUZW1wZXJhdHVyYV9tZWRpYSA9IG1lYW4oVGVtcGVyYXR1cmFfQWJyaWdvXzE1MGNtKSkKYGBgCjo6OgoKRWwgY29tYm8gYGdyb3VwX2J5KCkgJT4lIHN1bW1hcmlzZSgpYCBzZSBwdWVkZSByZXN1bWlyIGVuIGVzdGEgZmlndXJhLiBMYXMgZmlsYXMgZGUgdW5hIHRhYmxhIHNlIGRpdmlkZW4gZW4gZ3J1cG9zLCB5IGx1ZWdvIGNhZGEgZ3J1cG8gc2UgInJlc3VtZSIgZW4gdW5hIGZpbGEgZW4gZnVuY2nDs24gZGVsIGVzdGFkw61zdGljbyB1c2Fkby4gCgohW10oaW1nL2dyb3VwX2J5LXN1bW1hcml6ZS5wbmcpCgpBbCBpZ3VhbCBxdWUgaGljaW1vcyAiY3VlbnRhcyIgdXNhbmRvIGFsZ3VuYXMgdmFyaWFibGVzIG51bcOpcmljYXMgcGFyYSBvYnRlbmVyIGluZm9ybWFjacOzbiBudWV2YSwgdGFtYmnDqW4gcG9kZW1vcyB1dGlsaXphciB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzLiBObyB0aWVuZSBzZW50aWRvIGNhbGN1bGFyIGBtZWFuKG1lcylgIHlhIHF1ZSBjb250aWVuZW4gY2FyYWN0ZXJlcywgcGVybyB0YWwgdmV6IG5vcyBpbnRlcmVzZSAqY29udGFyKiBsYSBjYW50aWRhZCBkZSBvYnNlcnZhY2lvbmVzIHBvciBtZXM6CgpgYGB7cn0KYmFyaWxvY2hlICU+JSAKICBncm91cF9ieShtZXMpICU+JSAKICBzdW1tYXJpc2UoY2FudGlkYWQgPSBuKCkpCmBgYAoKOjo6ey5hbGVydCAuYWxlcnQtc3VjY2Vzc30KCkVuIFIgc2UgcHVlZGUgcmVzb2x2ZXIgdW4gcHJvYmxlbWEgZGUgbXVjaGFzIG1hbmVyYXMsIHBvciBlamVtcGxvLCBlbCBjw7NkaWdvIGFudGVyaW9yIHkgZWwgcXVlIHNpZ3VlIGRhbiByZXN1bHRhZG9zIGVxdWl2YWxlbnRlczoKYGBge3J9CmJhcmlsb2NoZSAlPiUgCiAgZ3JvdXBfYnkobWVzKSAlPiUgCiAgY291bnQoKQpgYGAKCkVuIGVzdGUgY2FzbyBgY291bnQoKWAgZXMgYW7DoWxvZ28gYSBgc3VtbWFyaXNlKGNhbnRpZGFkID0gbigpKWAuCjo6OgoKIyMgQ3JlYW5kbyBudWV2YXMgY29sdW1uYXMgY29uIGBtdXRhdGUoKWAKClRvZG8gZXN0byBlc3TDoSBiaWVuIHBhcmEgaGFjZXIgY8OhbGN1bG9zIGNvbiBjb2x1bW5hcyBwcmV2aWFtZW50ZSBleGlzdGVudGVzLCBwZXJvIG11Y2hhcyB2ZWNlcyB0ZW7DqXMgcXVlIGNyZWFyIG51ZXZhcyBjb2x1bW5hcy4gCgpMYSB0YWJsYSBgYmFyaWxvY2hlYCB0aWVuZSBpbmZvcm1hY2nDs24gZGUgdGVtcGVyYXR1cmFzIGVuIGdyYWRvcyBjZW50w61ncmFkb3MgeSBwdWVkZSBzZXIgbmVjZXNhcmlvIGNvbnZlcnRpcmxhcyBhIGtlbHZpbi4gYG11dGF0ZSgpYCBwZXJtaXRlIGFncmVnYXIgdW5hIG51ZXZhIGNvbHVtbmEgbGxhbWFkYSAiVGVtcGVyYXR1cmFfa2VsdmluIiBjb24gZXNhIGluZm9ybWFjacOzbjoKCmBgYHtyfQpiYXJpbG9jaGUgJT4lIAogIG11dGF0ZShUZW1wZXJhdHVyYV9rZWx2aW4gPSBUZW1wZXJhdHVyYV9BYnJpZ29fMTUwY20gKyAyNzMsMTUpCmBgYAoKUmVjb3Jkw6EgcXVlIGxhcyBmdW5jaW9uZXMgZGUge2RwbHlyfSBudW5jYSBtb2RpZmljYW4gbGEgdGFibGEgb3JpZ2luYWwuIGBtdXRhdGUoKWAgZGV2b2x2acOzIHVuYSBudWV2YSB0YWJsYSBxdWUgZXMgaWd1YWwgYSBsYSB0YWJsYSBgYmFyaWxvY2hlYCBwZXJvIGNvbiBsYSBjb2x1bW5hICJUZW1wZXJhdHVyYV9rZWx2aW4iIGFncmVnYWRhLiBMYSB0YWJsYSBgYmFyaWxvY2hlYCBzaWd1ZSBpbnRhY3RhLgoKU2kgcXVpc2nDqXJhbW9zIGFwbGljYXIgbGEgbWlzbWEgb3BlcmFjacOzbiBhIHRvZGFzIGxhcyBjb2x1bW5hcyBxdWUgdGllbmVuIHRlbXBlcmF0dXJhLCBsYSBmdW5jacOzbiBgYWNyb3NzKClgIGJyaW5kYSB1biBhdGFqbyBwYXJhIG5vIHRlbmVyIHF1ZSBhcGxpY2FyIGBtdXRhdGUoKWAgdW5hIHkgb3RyYSB2ZXogYSBjYWRhIGNhZGEgY29sdW1uYS4KCmBgYHtyfQpiYXJpbG9jaGUgJT4lIAogIHN1bW1hcmlzZShhY3Jvc3Moc3RhcnRzX3dpdGgoIlRlbXBlcmF0dXJhIiksIH4ueCArIDI3MywxNSkpCmBgYAoKVmFyaWFzIGNvc2FzIHBhc2Fyb24gYWPDoToKCi0gRWwgcmVzdWx0YWRvICpzb2xvKiBpbmNsdXllIGxhcyBjb2x1bW5hcyBhc29jaWFkYXMgYSBsYSB0ZW1wZXJhdHVyYS4KLSBObyBmdWUgbmVjZXNhcmlvIGVzY3JpYmlyIGVsIG5vbWJyZSBkZSBjYWRhIGNvbHVtbmEsIGxhIGZ1bmNpw7NuIGBzdGFydF93aXRoKClgIGF1dG9tw6F0aWNhbWVudGUgYnVzY2EgbGFzIGNvbHVtbmFzIHF1ZSAqY29taWVuemFuIGNvbiogbGEgcGFsYWJyYSBxdWUgaW5kaWNhbW9zLCBlbiBlc3RlIGNhc28gIlRlbXBlcmF0dXJhIi4KLSBVc2Ftb3MgbGEgbm90YWNpw7NuICpmdW5jaW9uYWwqIGRlIFIsIHF1ZSBhcnJhbmNhIGNvbiBgfmAgeSBsdWVnbyBsZSBpbmRpY2Ftb3MgcXVlIGEgLnggKGNhZGEgY29sdW1uYSkgbGUgc3VtZSAyNzMsMTUuIAoKOjo6ey5hbGVydCAuYWxlcnQtaW5mb30KCioqRGVzYWbDrW8qKgoKU2kgcXVpc2llcmFzIGNhbGN1bGFyIGxhIHRlbXBlcmF0dXJhIG1lZGlhIHBhcmEgY2FkYSBkw61hIHkgZ3VhcmRhcmxhIGVuIHVuYSBudWV2YSB2YXJpYWJsZSwgwr9jw7NtbyBjb21wbGV0YXLDrWFzIGVsIHNpZ3VpZW50ZSBjw7NkaWdvPwoKYGBge3IgZXZhbD1GQUxTRX0KYmFyaWxvY2hlICU+JSAKICBtdXRhdGUoX19fXyA9IF9fX18pCmBgYAo6OjoKCkFob3JhIGltYWdpbmVtb3MgcXVlIG5lY2VzaXRhbW9zIG1vZGlmaWNhciBhbGd1bmFzIG9ic2VydmFjaW9uZXMsIGFxdWVsbGFzIHF1ZSBjdW1wbGVuIGNvbiB1bmEgY29uZGljacOzbi4gUG9yIGVqZW1wbG8sIGRlc2N1YnJpbW9zIHF1ZSBjdWFuZG8gbGEgaHVtZWRhZCBlcyAxMDAlLCBlbCBzZW5zb3IgZGUgdGVtcGVyYXR1cmEgbm8gZnVuY2lvbmEgY29ycmVjdGFtZW50ZSB5IG5lY2VzaXRhbW9zIGFwbGljYXJsZSB1bmEgY29ycmVjY2nDs24gZGUgLTLCsCBlbiBlc2FzIHNpdHVhY2lvbmVzLiAKClBvZHLDrWFtb3MgZmlsdHJhciBsYXMgb2JzZXJ2YWNpb25lcyBxdWUgY3VtcGxlbiBjb24gcXVlIGBIdW1lZGFkX01lZGlhID09IDEwMGAgeSBsdWVnbyBtb2RpZmljYXIgbGEgdGVtcGVyYXR1cmEsIHBlcm8gZW50b25jZXMgdGVuZHLDrWFtb3MgdW5hIGJhc2UgZGUgZGF0b3MgcGFyYSBsYXMgb2JzZXJ2YWNpb25lcyBjb24gbWF5b3IgaHVtZWRhZCB5IGxhIHRlbXBlcmF0dXJhIGNvcnJlZ2lkYSB5IG90cmEgYmFzZSBkZSBkYXRvcyBzaW4gbGEgY29ycmVjY2nDs24uIE5lY2VzaXRhbW9zIHBvZGVyIG1vZGlmaWNhciAqc29sbyBhbGd1bmFzKiBvYnNlcnZhY2lvbmVzIHkgcXVlIGVsIHJlc3RvIHNlIG1hbnRlbmdhIGlndWFsLgoKUGFyYSBlc28gcG9kZW1vcyB1dGlsaXphciBsYSBmdW5jacOzbiBgaWZfZWxzZSgpYCwgcXVlIGFwbGljYSB1bmEgb3BlcmFjacOzbiAqc2kgb2N1cnJlIGFsZ28qIHkgb3RyYSBvcGVyYWNpw7NuICpzaSBubyouIEVuIGVzdGUgY2FzbyBzaSBgSHVtZWRhZF9NZWRpYSA9PSAxMDBgIGVzIHZlcmRhZGVyYSwgcmVzdGFtb3MgMiBncmFkb3MgYSBsYSBUZW1wZXJhdHVyYSwgc2kgZXMgZmFsc2Egbm8gbGEgbW9kaWZpY2Ftb3MuIFkgcG9yIHN1cHVlc3RvIHNlIGxhIGFwbGljYW1vcyBhIGxhIGNvbHVtbmEgY29uIGBtdXRhdGUoKWAKCmBgYHtyfQpiYXJpbG9jaGUgJT4lIAogIG11dGF0ZShUZW1wZXJhdHVyYV9BYnJpZ29fMTUwY20gPSBpZl9lbHNlKEh1bWVkYWRfTWVkaWEgPT0gMTAwLCAgICAgICAjIENvbmRpY2nDs24KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUZW1wZXJhdHVyYV9BYnJpZ29fMTUwY20gLTIsIyBTaSBlcyB2ZXJkYWRlcmEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUZW1wZXJhdHVyYV9BYnJpZ29fMTUwY20pKSAgIyBTaSBlcyBmYWxzYQpgYGAKCjo6OnsuYWxlcnQgLmFsZXJ0LXN1Y2Nlc3N9CgpMYSBtYXlvcsOtYSBkZSBsYXMgZnVuY2lvbmVzIGRlIFIgcHVlZGVuIHRyYWJhamFyIHNvYnJlIHZlY3RvcmVzICh5IGxhcyBjb2x1bW5hcyBkZSB1biBkYXRhLmZyYW1lIHNvbiBjb21vIHZlY3RvcmVzIG9yZGVuYWRvcyEpIHkgbG8gaGFjZW4gKiplbGVtZW50byBhIGVsZW1lbnRvKiouIAoKRW4gZXN0ZSBjYXNvIGBpZl9lbHNlKClgIHJlY29ycmUgbGEgY29sdW1uYSBjb24gbGEgaW5mb3JtYWNpw7NuIGRlIGh1bWVkYWQgeSAqZWxlbWVudG8gYSBlbGVtZW50byogcmV2aXNhIHNpIGVsIHZhbG9yIGVzIGlndWFsIGEgMTAwLiBMdWVnbyBkZWNpZGUgc2kgZGViZSBtb2RpZmljYXIgbyBubyBjYWRhIGVsZW1lbnRvLCBlbiBlbCBvcmRlbiBlbiBlbCBxdWUgYXBhcmVjZW4uCjo6OgoKOjo6ey5hbGVydCAuYWxlcnQtaW5mb30KCioqRGVzYWbDrW8qKgoKQWhvcmEgZXMgdHUgdHVybm8sICJhcnJlZ2zDoSIgbGFzIHZhcmlhYmxlcyBgVGVtcGVyYXR1cmFfQWJyaWdvXzE1MGNtX01heGltYWAgeSBgVGVtcGVyYXR1cmFfQWJyaWdvXzE1MGNtX01pbmltYWAgcGFyYSBhcGxpY2FyIGxhIG1pc21hIGNvcnJlY2Npw7NuIGRlIDIgZ3JhZG9zLiAKOjo6CgojIyBEZXNhZ3J1cGFuZG8gY29uIGB1bmdyb3VwKClgCgpFbiBnZW5lcmFsLCBsYSBtYXlvcsOtYSBkZSBsYXMgZnVuY2lvbmVzIGRlIHtkcGx5cn0gImVudGllbmRlbiIgY3VhbmRvIHVuYSB0YWJsYSBlc3TDoSBhZ3J1cGFkYSB5IHJlYWxpemFuIGxhcyBvcGVyYWNpb25lcyBwYXJhIGNhZGEgZ3J1cG8uIAoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqRGVzYWbDrW86KioKCsK/Q3XDoWwgZGUgZXN0b3MgZG9zIGPDs2RpZ29zIGFncmVnYSB1bmEgY29sdW1uYSBsbGFtYWRhICJUZW1wZXJhdHVyYV9tYXhfbWVkaWEiIGNvbiBlbCBsYSB0ZW1wZXJhdHVyYSBtw6F4aW1hIG1lZGlhIHBhcmEgY2FkYSBtZXM/IMK/UXXDqSBoYWNlIGVsIG90cm8/CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpiYXJpbG9jaGUgJT4lIAogIGdyb3VwX2J5KG1lcykgJT4lIAogIG11dGF0ZShUZW1wZXJhdHVyYV9tYXhfbWVkaWEgPSBtZWFuKFRlbXBlcmF0dXJhX0Ficmlnb18xNTBjbV9NYXhpbWEsIG5hLnJtID0gVFJVRSkpIAoKYmFyaWxvY2hlICU+JSAKICBtdXRhdGUoVGVtcGVyYXR1cmFfbWF4X21lZGlhID0gbWVhbihUZW1wZXJhdHVyYV9BYnJpZ29fMTUwY21fTWF4aW1hLCBuYS5ybSA9IFRSVUUpKSAgCmBgYAo6OjoKCkVuIG90cmFzIHBhbGFicmFzLCBsb3MgcmVzdWx0YWRvcyBkZSBgbXV0YXRlKClgLCBgZmlsdGVyKClgLCBgc3VtbWFyaXNlKClgIHkgb3RyYXMgZnVuY2lvbmVzIGNhbWJpYW4gc2Vnw7puIHNpIGxhIHRhYmxhIGVzdMOhIGFncnVwYWRhIG8gbm8uIENvbW8gYSB2ZWNlcyB1bm8gc2UgcHVlZGUgb2x2aWRhciBxdWUgcXVlZGFyb24gZ3J1cG9zLCBlcyBjb252ZW5pZW50ZSB1c2FyIGxhIGZ1bmNpw7NuIGB1bmdyb3VwKClgIHVuYSB2ZXogcXVlIGRlasOhcyBkZSB0cmFiYWphciBjb24gZ3J1cG9zOgoKYGBge3J9CmJhcmlsb2NoZSAlPiUgCiAgZ3JvdXBfYnkobWVzKSAlPiUgCiAgbXV0YXRlKFRlbXBlcmF0dXJhX21heF9tZWRpYSA9IG1lYW4oVGVtcGVyYXR1cmFfQWJyaWdvXzE1MGNtX01heGltYSwgbmEucm0gPSBUUlVFKSkgJT4lICAKICB1bmdyb3VwKCkKYGBgCgo8ZGl2IGNsYXNzPSJidG4tZ3JvdXAiIHJvbGU9Imdyb3VwIiBhcmlhLWxhYmVsPSJOYXZlZ2FjacOzbiI+CiAgPGEgaHJlZj0gIjA0LWxlY3R1cmEuaHRtbCIgY2xhc3MgPSAiYnRuIGJ0bi1wcmltYXJ5Ij5BbnRlcmlvcjwvYT4KICA8YSBocmVmPSAiMDYtZHBseXItdGlkeXItSUkuaHRtbCIgY2xhc3MgPSAiYnRuIGJ0bi1wcmltYXJ5Ij5TaWd1aWVudGU8L2E+CjwvZGl2PgoK