Archivos csv

Existen muchas funciones distintas para leer datos dependiendo del formato en el que están guardados. Para datos tabulares, la forma más útil es el formato csv, que es un archivo de texto plano con datos separados por coma. Vamos a trabajar con datos de temperatura de una estación meteorológica en Bariloche:

library(readr)
bariloche <- read_csv("datos/bariloche.csv")
## Rows: 3024 Columns: 2
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (1): Temperatura
## 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.

Todo ese texto naranja/rojo es intimidante pero no te preocupes, es sólo un mensaje que nos informa que los datos se leyeron y qué tipo de dato tiene cada columna. ¿Notás algo raro? A pesar de que claramente la temperatura es un número, la columna Temperatura se está leyendo como un caracter. Otra forma de ver esto es usando la a función str() (de structure en inglés):

str(bariloche)
## spec_tbl_df [3,024 × 2] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ Fecha      : POSIXct[1:3024], format: "2011-05-28" "2011-05-29" ...
##  $ Temperatura: chr [1:3024] NA "4.272223" "-999.99" "3.473611" ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   Fecha = col_datetime(format = ""),
##   ..   Temperatura = col_character()
##   .. )
##  - attr(*, "problems")=<externalptr>

Donde se ve que la columna Temperatura tiene clase chr (caracter).

Seguramente lo primero que tratarás en un caso así es simplemente cambiarle el tipo a la columna

library(dplyr)
bariloche %>% 
  mutate(Temperatura = as.numeric(Temperatura))
## Warning in mask$eval_all_mutate(quo): NAs introduced by coercion
## # A tibble: 3,024 × 2
##    Fecha               Temperatura
##    <dttm>                    <dbl>
##  1 2011-05-28 00:00:00       NA   
##  2 2011-05-29 00:00:00        4.27
##  3 2019-06-15 00:00:00    -1000.  
##  4 2011-05-31 00:00:00        3.47
##  5 2011-06-01 00:00:00        2.90
##  6 2011-06-02 00:00:00        6.26
##  7 2011-06-03 00:00:00        3.45
##  8 2016-03-22 00:00:00    -1000.  
##  9 2011-06-05 00:00:00        5.00
## 10 2011-06-06 00:00:00        5.45
## # … with 3,014 more rows

Pero R dice que hay un problema. NAs introduced by coercion significa que algunos valores no se pudieron transformar en numéricos y entonces as.numeric los dejó como valores faltantes. Casi con seguridad no querés eso. Para ver qué está pasando, hay que investigar qué contenidos tienen esas filas. Para eso, podés filtrar las filas de bariloche que no tienen NA en Temperatura pero que se convierten en NA al hacer as.numeric(Temperatura):

bariloche %>% 
  filter(!is.na(Temperatura) & is.na(as.numeric(Temperatura)))
## Warning in mask$eval_all_filter(dots, env_filter): NAs introduced by coercion
## # A tibble: 302 × 2
##    Fecha               Temperatura
##    <dttm>              <chr>      
##  1 2018-12-16 00:00:00 N/A        
##  2 2011-06-02 00:00:00 N/A        
##  3 2019-07-19 00:00:00 N/A        
##  4 2020-05-13 00:00:00 N/A        
##  5 2015-12-08 00:00:00 N/A        
##  6 2015-05-23 00:00:00 N/A        
##  7 2015-04-27 00:00:00 N/A        
##  8 2014-09-10 00:00:00 N/A        
##  9 2019-12-23 00:00:00 N/A        
## 10 2019-07-10 00:00:00 N/A        
## # … with 292 more rows

Ajá! Se ve que a alguien se le ocurrió usar los caracteres “N/A” para designar algunos datos faltantes. Con esta información, estaría bueno volver a leer los datos, diciéndole a read_csv que este archivo usa dos formas para referirse a datos faltantes.

Desafío andá a la ayuda de read_csv y fijate si hay algún argumento que controle la lectura de datos faltantes. Sabiendo que hay valores faltantes codificados como “N/A”, ¿qué deberías pasarle a ese argumento para leer correctamente los datos?

read_csv tiene un argumento na que controla qué valores van a interpretarse como valores faltantes. En este caso, sabés que “N/A” es uno. Entonces debería funcionar buen leer los datos así:

bariloche <- read_csv("datos/bariloche.csv", na = "N/A")
## Rows: 3024 Columns: 2
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (1): Temperatura
## 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.

¡No! Otra vez Temperatura se leyó como caracter. Si usás str podés ver qué pasó:

str(bariloche)
## spec_tbl_df [3,024 × 2] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
##  $ Fecha      : POSIXct[1:3024], format: "2011-05-28" "2011-05-29" ...
##  $ Temperatura: chr [1:3024] "NA" "4.272223" "-999.99" "3.473611" ...
##  - attr(*, "spec")=
##   .. cols(
##   ..   Fecha = col_datetime(format = ""),
##   ..   Temperatura = col_character()
##   .. )
##  - attr(*, "problems")=<externalptr>

La primera fila, que antes se leía como dato faltante, ahora se lee como “NA”.

Desafío ¿Por qué ahora read_csv está leyendo ese dato incorrectamente? Consejo: volvé a la ayuda de read_csv y fijate el valor por default del argumento na.

Antes, na estaba como el valor por defecto, que es c("", "NA"), lo que significa que tanto valores vacíos como valores "NA" son considerados valores faltantes. Lo que tenés que hacer es agregar también el valor "N/A":

bariloche <- read_csv("datos/bariloche.csv", na = c("",  "NA", "N/A"))
## Rows: 3024 Columns: 2
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl  (1): Temperatura
## 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.

Bien. Ahí la columna Temperatura se leyó como doble. Pero, ¿se leyeron bien? Una forma rápida de chequear los datos es usando la función summary:

summary(bariloche)
##      Fecha                         Temperatura       
##  Min.   :2011-05-28 00:00:00.00   Min.   : -999.990  
##  1st Qu.:2013-06-06 00:00:00.00   1st Qu.:    4.617  
##  Median :2016-04-30 12:00:00.00   Median :    8.620  
##  Mean   :2016-02-25 15:23:48.56   Mean   :  -98.634  
##  3rd Qu.:2018-07-04 06:00:00.00   3rd Qu.:   13.243  
##  Max.   :2020-09-12 00:00:00.00   Max.   :   25.710  
##                                   NA's   :501

summary devuelve algunas estadísticas simples sobre cada columna. La columna Temperatura tiene 501 valores faltantes, su máximo es 25.70972 y su mínimo es… ¿-999.990? Eso no puede ser. Seguramente a otro alguien se le ocurrió usar “-999.99” para marcar los datos faltantes. Una vez que sabés eso, podés volver a leer otra vez los datos agregando otro marcador más para los datos faltantes.

bariloche <- read_csv("datos/bariloche.csv", na = c("", "NA", "N/A", "-999.99"))
## Rows: 3024 Columns: 2
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl  (1): Temperatura
## 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.

Y ahora summary da:

summary(bariloche)
##      Fecha                         Temperatura    
##  Min.   :2011-05-28 00:00:00.00   Min.   :-4.806  
##  1st Qu.:2013-06-06 00:00:00.00   1st Qu.: 5.994  
##  Median :2016-04-30 12:00:00.00   Median : 9.531  
##  Mean   :2016-02-25 15:23:48.56   Mean   : 9.832  
##  3rd Qu.:2018-07-04 06:00:00.00   3rd Qu.:13.681  
##  Max.   :2020-09-12 00:00:00.00   Max.   :25.710  
##                                   NA's   :772

Ahora el rango de temperatura es razonable y podés tener confianza de que los datos están bien leídos.

Junto a read_csv() hay una familia de funciones similares que leen archivos con distintos tipos de delimitadores distintos de la coma. La buena noticia es que todas las funciones tiene argumentos similares y se usan casi indistintamente.

Si quisieras leer archivos que están en formato “xlsx” (Excel) podés usar el paquete {readxl}.

Archivos NetCDF

Luego de esta epopeya para conseguir leer estos datos correctamente está bueno reflexionar sobre la importancia de los metadatos; los datos sobre los datos que, entre otras cosas, podrían indicar cómo están codificados los datos faltantes. Una alternativa es usar formatos de datos “autodescriptivos”. Estos son tipos de archivos que contienen sus propios metadatos.

Uno de estos tipos de formatos muy utilizado en ciencias para guardar datos atmosféricos grillados es el NetCDF. Existen varios paquetes para estos archivos. Uno de ellos es {metR}.

library(metR)

Los datos en archivos NetCDF puede ser bastante grandes y destruir tu sesión de R si tratás de leerlos enteros en memoria. Por eso, siempre es bueno primero fijarse qué tiene un archivo nuevo. Para eso, {metR} tiene la función GlanceNetCDF que muestra un vistazo de los metadatos del archivo. Vamos a trabajar con un archivo en este formato que contiene datos de reanálisis del NCEP.

GlanceNetCDF("datos/temperatura.nc")
## ----- Variables ----- 
## air:
##     mean Daily Air temperature in degK
##     Dimensions: lon by lat by level by time
## 
## 
## ----- Dimensions ----- 
##   time: 1 values from 2010-07-09 to 2010-07-09 
##   level: 17 values from 10 to 1000 millibar
##   lat: 73 values from -90 to 90 degrees_north
##   lon: 144 values from 0 to 357.5 degrees_east

La salida de esta función muestra las variables que tiene el archivo y sus dimensiones.

Desafío ¿Qué información podés deducir sobre el contenido de temperatura.nc a partir de esto?

En este caso, el archivo tiene una sola variable, llamada air que es la temperatura media en Kelvin (un archivo NetCDF puede tener muchas variables y no todas en la misma grilla). De las dimensiones del archivo, se ve que tiene dimensiones de tiempo, nivel, longitud y latitud. La latitud va de -90 a 90 y la longitud de 0 a 357.5, por lo que es un campo global. level es la coordenada vertical, que va de 1000 milibares (básicamente la superficie) a 10 millibares (la estratósfera media).

Para leer los datos, se usa ReadNetCDF():

temperatura <- ReadNetCDF("datos/temperatura.nc", vars = "air")
temperatura
##               time level lat   lon    air
##      1: 2010-07-09  1000  90   0.0 274.87
##      2: 2010-07-09  1000  90   2.5 274.87
##      3: 2010-07-09  1000  90   5.0 274.87
##      4: 2010-07-09  1000  90   7.5 274.87
##      5: 2010-07-09  1000  90  10.0 274.87
##     ---                                  
## 178700: 2010-07-09    10 -90 347.5 188.25
## 178701: 2010-07-09    10 -90 350.0 188.25
## 178702: 2010-07-09    10 -90 352.5 188.25
## 178703: 2010-07-09    10 -90 355.0 188.25
## 178704: 2010-07-09    10 -90 357.5 188.25

Si no le ponés vars = "air", ReadNetCDF() va a tratar de leer todas las variables presentes en el archivo. Esto a veces puede causar problemas si las grillas de las variables no son compatibles (por ejemplo, si una variable es la temperatura del aire definida en distintas alturas, y otra es la temperatura del suelo). En este caso, como “temperatura.nc” tiene sólo una variable, no haría falta ponerlo.

Es muy posible que quieras datos sólo de una región. Una opción es leer todo el archivo y luego filtrar los datos. Si te interesa la temperatura de superficie de Argentina, podrías hacer algo así:

temperatura %>% 
  filter(level == 1000 & between(lat, -65, -20) & between(lon, 280, 310))
##            time level lat   lon    air
##   1: 2010-07-09  1000 -20 280.0 287.62
##   2: 2010-07-09  1000 -20 282.5 287.70
##   3: 2010-07-09  1000 -20 285.0 289.72
##   4: 2010-07-09  1000 -20 287.5 293.25
##   5: 2010-07-09  1000 -20 290.0 296.32
##  ---                                  
## 243: 2010-07-09  1000 -65 300.0 267.92
## 244: 2010-07-09  1000 -65 302.5 268.15
## 245: 2010-07-09  1000 -65 305.0 268.67
## 246: 2010-07-09  1000 -65 307.5 269.47
## 247: 2010-07-09  1000 -65 310.0 270.35

Pero es mucho más eficiente leer sólo los datos que te interesan, especialmente en archivos NetCDF grandes, que incluso no puede ser leídos en memoria enteros. ReadNetCDF() tiene un argumento llamado subset que sirve para especificar qué datos leer:

ReadNetCDF("datos/temperatura.nc", vars = "air",
           subset = list(level = 1000,
                         lat = c(-65, -20),
                         lon = c(280, 310))) 
##            time level lat   lon    air
##   1: 2010-07-09  1000 -20 280.0 287.62
##   2: 2010-07-09  1000 -20 282.5 287.70
##   3: 2010-07-09  1000 -20 285.0 289.72
##   4: 2010-07-09  1000 -20 287.5 293.25
##   5: 2010-07-09  1000 -20 290.0 296.32
##  ---                                  
## 243: 2010-07-09  1000 -65 300.0 267.92
## 244: 2010-07-09  1000 -65 302.5 268.15
## 245: 2010-07-09  1000 -65 305.0 268.67
## 246: 2010-07-09  1000 -65 307.5 269.47
## 247: 2010-07-09  1000 -65 310.0 270.35

A subset hay que pasarle una lista donde cada elemento tiene el nombre de una dimensión con un vector cuyo rango define el rango de los datos a leer. El código de arriba, entonces, dice que de la dimensión level lea únicamente el valor 1000, de la dimensión lat, lea los valores que van entre -65 y -20, y de la dimensión lon, los valores entre 280 y 310.

Desafío Elegí una región del mundo y completá el código que sigue para que lea la temperatura en superficie de esa región.

temperatura <- ReadNetCDF("datos/temperatura.nc", vars = "air",
           subset = list(level = 1000,
                         lat = c(___, ___),
                         lon = c(___, ___)))

Para revisar si leíste los datos correctos, corré este código (no te preocupes si no usas ggplot, en una sección más adelante lo veremos)

library(ggplot2)
ggplot(temperatura, aes(lon, lat)) +
  geom_raster(aes(fill = air)) +
  geom_path(data = map_data("world2"), aes(long, lat, group = group))

Ponerle nombre a las variables es a veces la parte más difícil de escribir código. A R no le viene bien cualquier nombre de variable siempre y cuando no empiece con un número o un “_“. Pero a los seres humanos que lean el código y tengan que interpretarlos les va a resultas más fácil entender qué hace la variable temperatura_cordoba que la variable xxy1.

El consejo es tratar en lo posible usar nombre descriptivos y consistentes. Por ejemplo, siempre usar minúsculas y separar palabras con “_“.

Tip: Para hacerse la vida más fácil existen “guías de estilo” para programar que explicitan reglas específicas para escribir código. Por ejemplo esta o esta otra. Se trata de reglas únicamente para los ojos humanos, y que no afectan en absoluto la eficiencia o correctitud de la programación. En general, no existen guías buenas o malas, la idea es elegir una y ser consistente. De esta manera, vas a poder entender tu código con más facilidad.

LS0tCnRpdGxlOiAiTGVjdHVyYSBkZSBkYXRvcyIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGhpZ2hsaWdodDogdGFuZ28KLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCgojIEFyY2hpdm9zIGNzdgoKRXhpc3RlbiBtdWNoYXMgZnVuY2lvbmVzIGRpc3RpbnRhcyBwYXJhIGxlZXIgZGF0b3MgZGVwZW5kaWVuZG8gZGVsIGZvcm1hdG8gZW4gZWwgcXVlIGVzdMOhbiBndWFyZGFkb3MuIFBhcmEgZGF0b3MgdGFidWxhcmVzLCBsYSBmb3JtYSBtw6FzIMO6dGlsIGVzIGVsIGZvcm1hdG8gY3N2LCBxdWUgZXMgdW4gYXJjaGl2byBkZSB0ZXh0byBwbGFubyBjb24gZGF0b3Mgc2VwYXJhZG9zIHBvciBjb21hLiBWYW1vcyBhIHRyYWJhamFyIGNvbiBkYXRvcyBkZSB0ZW1wZXJhdHVyYSBkZSB1bmEgZXN0YWNpw7NuIG1ldGVvcm9sw7NnaWNhIGVuIEJhcmlsb2NoZToKCmBgYHtyfQpsaWJyYXJ5KHJlYWRyKQpiYXJpbG9jaGUgPC0gcmVhZF9jc3YoImRhdG9zL2Jhcmlsb2NoZS5jc3YiKQpgYGAKVG9kbyBlc2UgdGV4dG8gbmFyYW5qYS9yb2pvIGVzIGludGltaWRhbnRlIHBlcm8gbm8gdGUgcHJlb2N1cGVzLCBlcyBzw7NsbyB1biBtZW5zYWplIHF1ZSBub3MgaW5mb3JtYSBxdWUgbG9zIGRhdG9zIHNlIGxleWVyb24geSBxdcOpIHRpcG8gZGUgZGF0byB0aWVuZSBjYWRhIGNvbHVtbmEuIMK/Tm90w6FzIGFsZ28gcmFybz8gQSBwZXNhciBkZSBxdWUgY2xhcmFtZW50ZSBsYSB0ZW1wZXJhdHVyYSBlcyB1biBuw7ptZXJvLCBsYSBjb2x1bW5hIGBUZW1wZXJhdHVyYWAgc2UgZXN0w6EgbGV5ZW5kbyBjb21vIHVuIGNhcmFjdGVyLiBPdHJhIGZvcm1hIGRlIHZlciBlc3RvIGVzIHVzYW5kbyBsYSBhIGZ1bmNpw7NuIGBzdHIoKWAgKGRlICpzdHJ1Y3R1cmUqIGVuIGluZ2zDqXMpOgoKCmBgYHtyfQpzdHIoYmFyaWxvY2hlKQpgYGAKCkRvbmRlIHNlIHZlIHF1ZSBsYSBjb2x1bW5hIGBUZW1wZXJhdHVyYWAgdGllbmUgY2xhc2UgYGNocmAgKGNhcmFjdGVyKS4KClNlZ3VyYW1lbnRlIGxvIHByaW1lcm8gcXVlIHRyYXRhcsOhcyBlbiB1biBjYXNvIGFzw60gZXMgc2ltcGxlbWVudGUgY2FtYmlhcmxlIGVsIHRpcG8gYSBsYSBjb2x1bW5hCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShkcGx5cikKYmFyaWxvY2hlICU+JSAKICBtdXRhdGUoVGVtcGVyYXR1cmEgPSBhcy5udW1lcmljKFRlbXBlcmF0dXJhKSkKYGBgClBlcm8gUiBkaWNlIHF1ZSBoYXkgdW4gcHJvYmxlbWEuIGBOQXMgaW50cm9kdWNlZCBieSBjb2VyY2lvbmAgc2lnbmlmaWNhIHF1ZSBhbGd1bm9zIHZhbG9yZXMgbm8gc2UgcHVkaWVyb24gdHJhbnNmb3JtYXIgZW4gbnVtw6lyaWNvcyB5IGVudG9uY2VzIGBhcy5udW1lcmljYCBsb3MgZGVqw7MgY29tbyB2YWxvcmVzIGZhbHRhbnRlcy4gQ2FzaSBjb24gc2VndXJpZGFkIG5vIHF1ZXLDqXMgZXNvLiBQYXJhIHZlciBxdcOpIGVzdMOhIHBhc2FuZG8sIGhheSBxdWUgaW52ZXN0aWdhciBxdcOpIGNvbnRlbmlkb3MgdGllbmVuIGVzYXMgZmlsYXMuIFBhcmEgZXNvLCBwb2TDqXMgZmlsdHJhciBsYXMgZmlsYXMgZGUgYGJhcmlsb2NoZWAgcXVlIG5vIHRpZW5lbiBgTkFgIGVuIGBUZW1wZXJhdHVyYWAgcGVybyBxdWUgc2UgY29udmllcnRlbiBlbiBgTkFgIGFsIGhhY2VyIGBhcy5udW1lcmljKFRlbXBlcmF0dXJhKWA6CgpgYGB7cn0KYmFyaWxvY2hlICU+JSAKICBmaWx0ZXIoIWlzLm5hKFRlbXBlcmF0dXJhKSAmIGlzLm5hKGFzLm51bWVyaWMoVGVtcGVyYXR1cmEpKSkKYGBgCgoKQWrDoSEgU2UgdmUgcXVlIGEgYWxndWllbiBzZSBsZSBvY3VycmnDsyB1c2FyIGxvcyBjYXJhY3RlcmVzICJOL0EiIHBhcmEgZGVzaWduYXIgYWxndW5vcyBkYXRvcyBmYWx0YW50ZXMuIENvbiBlc3RhIGluZm9ybWFjacOzbiwgZXN0YXLDrWEgYnVlbm8gdm9sdmVyIGEgbGVlciBsb3MgZGF0b3MsIGRpY2nDqW5kb2xlIGEgYHJlYWRfY3N2YCBxdWUgZXN0ZSBhcmNoaXZvIHVzYSBkb3MgZm9ybWFzIHBhcmEgcmVmZXJpcnNlIGEgZGF0b3MgZmFsdGFudGVzLiAKCjo6OnsuYWxlcnQgLmFsZXJ0LWluZm99IAoqKkRlc2Fmw61vKiogYW5kw6EgYSBsYSBheXVkYSBkZSBgcmVhZF9jc3ZgIHkgZmlqYXRlIHNpIGhheSBhbGfDum4gYXJndW1lbnRvIHF1ZSBjb250cm9sZSBsYSBsZWN0dXJhIGRlIGRhdG9zIGZhbHRhbnRlcy4gU2FiaWVuZG8gcXVlIGhheSB2YWxvcmVzIGZhbHRhbnRlcyBjb2RpZmljYWRvcyBjb21vICJOL0EiLCDCv3F1w6kgZGViZXLDrWFzIHBhc2FybGUgYSBlc2UgYXJndW1lbnRvIHBhcmEgbGVlciBjb3JyZWN0YW1lbnRlIGxvcyBkYXRvcz8KOjo6CgpgcmVhZF9jc3ZgIHRpZW5lIHVuIGFyZ3VtZW50byBgbmFgIHF1ZSBjb250cm9sYSBxdcOpIHZhbG9yZXMgdmFuIGEgaW50ZXJwcmV0YXJzZSBjb21vIHZhbG9yZXMgZmFsdGFudGVzLiBFbiBlc3RlIGNhc28sIHNhYsOpcyBxdWUgIk4vQSIgZXMgdW5vLiBFbnRvbmNlcyBkZWJlcsOtYSBmdW5jaW9uYXIgYnVlbiBsZWVyIGxvcyBkYXRvcyBhc8OtOgoKYGBge3J9CmJhcmlsb2NoZSA8LSByZWFkX2NzdigiZGF0b3MvYmFyaWxvY2hlLmNzdiIsIG5hID0gIk4vQSIpCmBgYArCoU5vISBPdHJhIHZleiBgVGVtcGVyYXR1cmFgIHNlIGxlecOzIGNvbW8gY2FyYWN0ZXIuIFNpIHVzw6FzIGBzdHJgIHBvZMOpcyB2ZXIgcXXDqSBwYXPDszoKCmBgYHtyfQpzdHIoYmFyaWxvY2hlKQpgYGAKTGEgcHJpbWVyYSBmaWxhLCBxdWUgYW50ZXMgc2UgbGXDrWEgY29tbyBkYXRvIGZhbHRhbnRlLCBhaG9yYSBzZSBsZWUgY29tbyAiTkEiLiAgCgo6Ojp7LmFsZXJ0IC5hbGVydC1pbmZvfSAKKipEZXNhZsOtbyoqIMK/UG9yIHF1w6kgYWhvcmEgYHJlYWRfY3N2YCBlc3TDoSBsZXllbmRvIGVzZSBkYXRvIGluY29ycmVjdGFtZW50ZT8gQ29uc2Vqbzogdm9sdsOpIGEgbGEgYXl1ZGEgZGUgYHJlYWRfY3N2YCB5IGZpamF0ZSBlbCB2YWxvciBwb3IgZGVmYXVsdCBkZWwgYXJndW1lbnRvIGBuYWAuCjo6OgoKQW50ZXMsIGBuYWAgZXN0YWJhIGNvbW8gZWwgdmFsb3IgcG9yIGRlZmVjdG8sIHF1ZSBlcyBgYygiIiwgIk5BIilgLCBsbyBxdWUgc2lnbmlmaWNhIHF1ZSB0YW50byB2YWxvcmVzIHZhY8Otb3MgY29tbyB2YWxvcmVzIGAiTkEiYCBzb24gY29uc2lkZXJhZG9zIHZhbG9yZXMgZmFsdGFudGVzLiBMbyBxdWUgdGVuw6lzIHF1ZSBoYWNlciBlcyBhZ3JlZ2FyIHRhbWJpw6luIGVsIHZhbG9yIGAiTi9BImA6CgpgYGB7cn0KYmFyaWxvY2hlIDwtIHJlYWRfY3N2KCJkYXRvcy9iYXJpbG9jaGUuY3N2IiwgbmEgPSBjKCIiLCAgIk5BIiwgIk4vQSIpKQpgYGAKCkJpZW4uIEFow60gbGEgY29sdW1uYSBgVGVtcGVyYXR1cmFgIHNlIGxlecOzIGNvbW8gZG9ibGUuIFBlcm8sIMK/c2UgbGV5ZXJvbiBiaWVuPyBVbmEgZm9ybWEgcsOhcGlkYSBkZSBjaGVxdWVhciBsb3MgZGF0b3MgZXMgdXNhbmRvIGxhIGZ1bmNpw7NuIGBzdW1tYXJ5YDoKCmBgYHtyfQpzdW1tYXJ5KGJhcmlsb2NoZSkKYGBgCmBzdW1tYXJ5YCBkZXZ1ZWx2ZSBhbGd1bmFzIGVzdGFkw61zdGljYXMgc2ltcGxlcyBzb2JyZSBjYWRhIGNvbHVtbmEuIExhIGNvbHVtbmEgYFRlbXBlcmF0dXJhYCB0aWVuZSA1MDEgdmFsb3JlcyBmYWx0YW50ZXMsIHN1IG3DoXhpbW8gZXMgYHIgbWF4KGJhcmlsb2NoZSRUZW1wZXJhdHVyYSwgbmEucm0gPSBUUlVFKWAgeSBzdSBtw61uaW1vIGVzLi4uIMK/LTk5OS45OTA/IEVzbyBubyBwdWVkZSBzZXIuIFNlZ3VyYW1lbnRlIGEgb3RybyBhbGd1aWVuIHNlIGxlIG9jdXJyacOzIHVzYXIgIi05OTkuOTkiIHBhcmEgbWFyY2FyIGxvcyBkYXRvcyBmYWx0YW50ZXMuIFVuYSB2ZXogcXVlIHNhYsOpcyBlc28sIHBvZMOpcyB2b2x2ZXIgYSBsZWVyIG90cmEgdmV6IGxvcyBkYXRvcyBhZ3JlZ2FuZG8gb3RybyBtYXJjYWRvciBtw6FzIHBhcmEgbG9zIGRhdG9zIGZhbHRhbnRlcy4KCmBgYHtyfQpiYXJpbG9jaGUgPC0gcmVhZF9jc3YoImRhdG9zL2Jhcmlsb2NoZS5jc3YiLCBuYSA9IGMoIiIsICJOQSIsICJOL0EiLCAiLTk5OS45OSIpKQpgYGAKWSBhaG9yYSBgc3VtbWFyeWAgZGE6CgpgYGB7cn0Kc3VtbWFyeShiYXJpbG9jaGUpCmBgYAoKQWhvcmEgZWwgcmFuZ28gZGUgdGVtcGVyYXR1cmEgZXMgcmF6b25hYmxlIHkgcG9kw6lzIHRlbmVyIGNvbmZpYW56YSBkZSBxdWUgbG9zIGRhdG9zIGVzdMOhbiBiaWVuIGxlw61kb3MuIAoKOjo6ey5hbGVydCAuYWxlcnQtc3VjY2Vzc30KCkp1bnRvIGEgYHJlYWRfY3N2KClgIGhheSB1bmEgZmFtaWxpYSBkZSBmdW5jaW9uZXMgc2ltaWxhcmVzIHF1ZSBsZWVuIGFyY2hpdm9zIGNvbiBkaXN0aW50b3MgdGlwb3MgZGUgZGVsaW1pdGFkb3JlcyBkaXN0aW50b3MgZGUgbGEgY29tYS4gTGEgYnVlbmEgbm90aWNpYSBlcyBxdWUgdG9kYXMgbGFzIGZ1bmNpb25lcyB0aWVuZSBhcmd1bWVudG9zIHNpbWlsYXJlcyB5IHNlIHVzYW4gY2FzaSBpbmRpc3RpbnRhbWVudGUuCgpTaSBxdWlzaWVyYXMgbGVlciBhcmNoaXZvcyBxdWUgZXN0w6FuIGVuIGZvcm1hdG8gInhsc3giIChFeGNlbCkgcG9kw6lzIHVzYXIgZWwgcGFxdWV0ZSBge3JlYWR4bH1gLgoKOjo6CgoKIyBBcmNoaXZvcyBOZXRDREYKCkx1ZWdvIGRlIGVzdGEgZXBvcGV5YSBwYXJhIGNvbnNlZ3VpciBsZWVyIGVzdG9zIGRhdG9zIGNvcnJlY3RhbWVudGUgZXN0w6EgYnVlbm8gcmVmbGV4aW9uYXIgc29icmUgbGEgaW1wb3J0YW5jaWEgZGUgbG9zICoqbWV0YWRhdG9zKio7IGxvcyBkYXRvcyBzb2JyZSBsb3MgZGF0b3MgcXVlLCBlbnRyZSBvdHJhcyBjb3NhcywgcG9kcsOtYW4gaW5kaWNhciBjw7NtbyBlc3TDoW4gY29kaWZpY2Fkb3MgbG9zIGRhdG9zIGZhbHRhbnRlcy4gVW5hIGFsdGVybmF0aXZhIGVzIHVzYXIgZm9ybWF0b3MgZGUgZGF0b3MgImF1dG9kZXNjcmlwdGl2b3MiLiBFc3RvcyBzb24gdGlwb3MgZGUgYXJjaGl2b3MgcXVlIGNvbnRpZW5lbiBzdXMgcHJvcGlvcyBtZXRhZGF0b3MuIAoKVW5vIGRlIGVzdG9zIHRpcG9zIGRlIGZvcm1hdG9zIG11eSB1dGlsaXphZG8gZW4gY2llbmNpYXMgcGFyYSBndWFyZGFyIGRhdG9zIGF0bW9zZsOpcmljb3MgZ3JpbGxhZG9zIGVzIGVsIFtOZXRDREZdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL05ldENERikuIEV4aXN0ZW4gdmFyaW9zIHBhcXVldGVzIHBhcmEgZXN0b3MgYXJjaGl2b3MuIFVubyBkZSBlbGxvcyBlcyB7bWV0Un0uIAoKYGBge3J9CmxpYnJhcnkobWV0UikKYGBgCgoKTG9zIGRhdG9zIGVuIGFyY2hpdm9zIE5ldENERiBwdWVkZSBzZXIgYmFzdGFudGUgZ3JhbmRlcyB5IGRlc3RydWlyIHR1IHNlc2nDs24gZGUgUiBzaSB0cmF0w6FzIGRlIGxlZXJsb3MgZW50ZXJvcyBlbiBtZW1vcmlhLiBQb3IgZXNvLCBzaWVtcHJlIGVzIGJ1ZW5vIHByaW1lcm8gZmlqYXJzZSBxdcOpIHRpZW5lIHVuIGFyY2hpdm8gbnVldm8uIFBhcmEgZXNvLCB7bWV0Un0gdGllbmUgbGEgZnVuY2nDs24gYEdsYW5jZU5ldENERmAgcXVlIG11ZXN0cmEgdW4gdmlzdGF6byBkZSBsb3MgbWV0YWRhdG9zIGRlbCBhcmNoaXZvLiBWYW1vcyBhIHRyYWJhamFyIGNvbiB1biBhcmNoaXZvIGVuIGVzdGUgZm9ybWF0byBxdWUgY29udGllbmUgZGF0b3MgZGUgcmVhbsOhbGlzaXMgZGVsIE5DRVAuCgpgYGB7cn0KR2xhbmNlTmV0Q0RGKCJkYXRvcy90ZW1wZXJhdHVyYS5uYyIpCmBgYAoKTGEgc2FsaWRhIGRlIGVzdGEgZnVuY2nDs24gbXVlc3RyYSBsYXMgdmFyaWFibGVzIHF1ZSB0aWVuZSBlbCBhcmNoaXZvIHkgc3VzIGRpbWVuc2lvbmVzLiAKCjo6OnsuYWxlcnQgLmFsZXJ0LWluZm99CioqRGVzYWbDrW8qKiDCv1F1w6kgaW5mb3JtYWNpw7NuIHBvZMOpcyBkZWR1Y2lyIHNvYnJlIGVsIGNvbnRlbmlkbyBkZSB0ZW1wZXJhdHVyYS5uYyBhIHBhcnRpciBkZSBlc3RvPwo6OjoKCkVuIGVzdGUgY2FzbywgZWwgYXJjaGl2byB0aWVuZSB1bmEgc29sYSB2YXJpYWJsZSwgbGxhbWFkYSBgYWlyYCBxdWUgZXMgbGEgdGVtcGVyYXR1cmEgbWVkaWEgZW4gS2VsdmluICh1biBhcmNoaXZvIE5ldENERiBwdWVkZSB0ZW5lciBtdWNoYXMgdmFyaWFibGVzIHkgbm8gdG9kYXMgZW4gbGEgbWlzbWEgZ3JpbGxhKS4gRGUgbGFzIGRpbWVuc2lvbmVzIGRlbCBhcmNoaXZvLCBzZSB2ZSBxdWUgdGllbmUgZGltZW5zaW9uZXMgZGUgdGllbXBvLCBuaXZlbCwgbG9uZ2l0dWQgeSBsYXRpdHVkLiBMYSBsYXRpdHVkIHZhIGRlIC05MCBhIDkwIHkgbGEgbG9uZ2l0dWQgZGUgMCBhIDM1Ny41LCBwb3IgbG8gcXVlIGVzIHVuIGNhbXBvIGdsb2JhbC4gYGxldmVsYCBlcyBsYSBjb29yZGVuYWRhIHZlcnRpY2FsLCBxdWUgdmEgZGUgMTAwMCBtaWxpYmFyZXMgKGLDoXNpY2FtZW50ZSBsYSBzdXBlcmZpY2llKSBhIDEwIG1pbGxpYmFyZXMgKGxhIGVzdHJhdMOzc2ZlcmEgbWVkaWEpLiAKClBhcmEgbGVlciBsb3MgZGF0b3MsIHNlIHVzYSBgUmVhZE5ldENERigpYDoKCmBgYHtyfQp0ZW1wZXJhdHVyYSA8LSBSZWFkTmV0Q0RGKCJkYXRvcy90ZW1wZXJhdHVyYS5uYyIsIHZhcnMgPSAiYWlyIikKdGVtcGVyYXR1cmEKYGBgCgo6Ojp7LmFsZXJ0IC5hbGVydC1zdWNjZXNzfQpTaSBubyBsZSBwb27DqXMgYHZhcnMgPSAiYWlyImAsIGBSZWFkTmV0Q0RGKClgIHZhIGEgdHJhdGFyIGRlIGxlZXIgdG9kYXMgbGFzIHZhcmlhYmxlcyBwcmVzZW50ZXMgZW4gZWwgYXJjaGl2by4gRXN0byBhIHZlY2VzIHB1ZWRlIGNhdXNhciBwcm9ibGVtYXMgc2kgbGFzIGdyaWxsYXMgZGUgbGFzIHZhcmlhYmxlcyBubyBzb24gY29tcGF0aWJsZXMgKHBvciBlamVtcGxvLCBzaSB1bmEgdmFyaWFibGUgZXMgbGEgdGVtcGVyYXR1cmEgZGVsIGFpcmUgZGVmaW5pZGEgZW4gZGlzdGludGFzIGFsdHVyYXMsIHkgb3RyYSBlcyBsYSB0ZW1wZXJhdHVyYSBkZWwgc3VlbG8pLiBFbiBlc3RlIGNhc28sIGNvbW8gInRlbXBlcmF0dXJhLm5jIiB0aWVuZSBzw7NsbyB1bmEgdmFyaWFibGUsIG5vIGhhcsOtYSBmYWx0YSBwb25lcmxvLgo6OjoKCkVzIG11eSBwb3NpYmxlIHF1ZSBxdWllcmFzIGRhdG9zIHPDs2xvIGRlIHVuYSByZWdpw7NuLiBVbmEgb3BjacOzbiBlcyBsZWVyIHRvZG8gZWwgYXJjaGl2byB5IGx1ZWdvIGZpbHRyYXIgbG9zIGRhdG9zLiBTaSB0ZSBpbnRlcmVzYSBsYSB0ZW1wZXJhdHVyYSBkZSBzdXBlcmZpY2llIGRlIEFyZ2VudGluYSwgcG9kcsOtYXMgaGFjZXIgYWxnbyBhc8OtOgoKYGBge3J9CnRlbXBlcmF0dXJhICU+JSAKICBmaWx0ZXIobGV2ZWwgPT0gMTAwMCAmIGJldHdlZW4obGF0LCAtNjUsIC0yMCkgJiBiZXR3ZWVuKGxvbiwgMjgwLCAzMTApKQpgYGAKClBlcm8gZXMgbXVjaG8gbcOhcyBlZmljaWVudGUgbGVlciBzw7NsbyBsb3MgZGF0b3MgcXVlIHRlIGludGVyZXNhbiwgZXNwZWNpYWxtZW50ZSBlbiBhcmNoaXZvcyBOZXRDREYgZ3JhbmRlcywgcXVlIGluY2x1c28gbm8gcHVlZGUgc2VyIGxlw61kb3MgZW4gbWVtb3JpYSBlbnRlcm9zLiBgUmVhZE5ldENERigpYCB0aWVuZSB1biBhcmd1bWVudG8gbGxhbWFkbyBgc3Vic2V0YCBxdWUgc2lydmUgcGFyYSBlc3BlY2lmaWNhciBxdcOpIGRhdG9zIGxlZXI6CgpgYGB7cn0KUmVhZE5ldENERigiZGF0b3MvdGVtcGVyYXR1cmEubmMiLCB2YXJzID0gImFpciIsCiAgICAgICAgICAgc3Vic2V0ID0gbGlzdChsZXZlbCA9IDEwMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICBsYXQgPSBjKC02NSwgLTIwKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGxvbiA9IGMoMjgwLCAzMTApKSkgCmBgYAoKQSBgc3Vic2V0YCBoYXkgcXVlIHBhc2FybGUgdW5hIGxpc3RhIGRvbmRlIGNhZGEgZWxlbWVudG8gdGllbmUgZWwgbm9tYnJlIGRlIHVuYSBkaW1lbnNpw7NuIGNvbiB1biB2ZWN0b3IgY3V5byByYW5nbyBkZWZpbmUgZWwgcmFuZ28gZGUgbG9zIGRhdG9zIGEgbGVlci4gRWwgY8OzZGlnbyBkZSBhcnJpYmEsIGVudG9uY2VzLCBkaWNlIHF1ZSBkZSBsYSBkaW1lbnNpw7NuIGBsZXZlbGAgbGVhIMO6bmljYW1lbnRlIGVsIHZhbG9yIDEwMDAsIGRlIGxhIGRpbWVuc2nDs24gYGxhdGAsIGxlYSBsb3MgdmFsb3JlcyBxdWUgdmFuIGVudHJlIC02NSB5IC0yMCwgeSBkZSBsYSBkaW1lbnNpw7NuIGBsb25gLCBsb3MgdmFsb3JlcyBlbnRyZSAyODAgeSAzMTAuIAoKCjo6OnsuYWxlcnQgLmFsZXJ0LWluZm99CioqRGVzYWbDrW8qKiBFbGVnw60gdW5hIHJlZ2nDs24gZGVsIG11bmRvIHkgY29tcGxldMOhIGVsIGPDs2RpZ28gcXVlIHNpZ3VlIHBhcmEgcXVlIGxlYSBsYSB0ZW1wZXJhdHVyYSBlbiBzdXBlcmZpY2llIGRlIGVzYSByZWdpw7NuLiAKCmBgYHtyLCBldmFsID0gRkFMU0V9CnRlbXBlcmF0dXJhIDwtIFJlYWROZXRDREYoImRhdG9zL3RlbXBlcmF0dXJhLm5jIiwgdmFycyA9ICJhaXIiLAogICAgICAgICAgIHN1YnNldCA9IGxpc3QobGV2ZWwgPSAxMDAwLAogICAgICAgICAgICAgICAgICAgICAgICAgbGF0ID0gYyhfX18sIF9fXyksCiAgICAgICAgICAgICAgICAgICAgICAgICBsb24gPSBjKF9fXywgX19fKSkpCmBgYApQYXJhIHJldmlzYXIgc2kgbGXDrXN0ZSBsb3MgZGF0b3MgY29ycmVjdG9zLCBjb3Jyw6kgZXN0ZSBjw7NkaWdvIChubyB0ZSBwcmVvY3VwZXMgc2kgbm8gdXNhcyBnZ3Bsb3QsIGVuIHVuYSBzZWNjacOzbiBtw6FzIGFkZWxhbnRlIGxvIHZlcmVtb3MpCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpsaWJyYXJ5KGdncGxvdDIpCmdncGxvdCh0ZW1wZXJhdHVyYSwgYWVzKGxvbiwgbGF0KSkgKwogIGdlb21fcmFzdGVyKGFlcyhmaWxsID0gYWlyKSkgKwogIGdlb21fcGF0aChkYXRhID0gbWFwX2RhdGEoIndvcmxkMiIpLCBhZXMobG9uZywgbGF0LCBncm91cCA9IGdyb3VwKSkKYGBgCgo6OjoKCgo6Ojp7LmFsZXJ0IC5hbGVydC1zdWNjZXNzfQpQb25lcmxlIG5vbWJyZSBhIGxhcyB2YXJpYWJsZXMgZXMgYSB2ZWNlcyBsYSBwYXJ0ZSBtw6FzIGRpZsOtY2lsIGRlIGVzY3JpYmlyIGPDs2RpZ28uIEEgUiBubyBsZSB2aWVuZSBiaWVuIGN1YWxxdWllciBub21icmUgZGUgdmFyaWFibGUgc2llbXByZSB5IGN1YW5kbyBubyBlbXBpZWNlIGNvbiB1biBuw7ptZXJvIG8gdW4gIl8iLiBQZXJvIGEgbG9zIHNlcmVzIGh1bWFub3MgcXVlIGxlYW4gZWwgY8OzZGlnbyB5IHRlbmdhbiBxdWUgaW50ZXJwcmV0YXJsb3MgbGVzIHZhIGEgcmVzdWx0YXMgbcOhcyBmw6FjaWwgZW50ZW5kZXIgcXXDqSBoYWNlIGxhIHZhcmlhYmxlIGB0ZW1wZXJhdHVyYV9jb3Jkb2JhYCBxdWUgbGEgdmFyaWFibGUgYHh4eTFgLiAKCkVsIGNvbnNlam8gZXMgdHJhdGFyIGVuIGxvIHBvc2libGUgdXNhciBub21icmUgZGVzY3JpcHRpdm9zIHkgY29uc2lzdGVudGVzLiBQb3IgZWplbXBsbywgc2llbXByZSB1c2FyIG1pbsO6c2N1bGFzIHkgc2VwYXJhciBwYWxhYnJhcyBjb24gIl8iLiAKCioqVGlwKio6IFBhcmEgaGFjZXJzZSBsYSB2aWRhIG3DoXMgZsOhY2lsIGV4aXN0ZW4gImd1w61hcyBkZSBlc3RpbG8iIHBhcmEgcHJvZ3JhbWFyIHF1ZSBleHBsaWNpdGFuIHJlZ2xhcyBlc3BlY8OtZmljYXMgcGFyYSBlc2NyaWJpciBjw7NkaWdvLiBQb3IgZWplbXBsbyBbZXN0YV0oaHR0cHM6Ly9ycHVicy5jb20vRnZEL2d1aWEtZXN0aWxvLXIpey5hbGVydC1saW5rfSBvIFtlc3RhIG90cmFdKGh0dHBzOi8vZ2l0aHViLmNvbS9lbGlvY2FtcC90ZXNpcy9ibG9iL21hc3Rlci9kb2NzL2d1JUMzJUFEYV9kZV9lc3RpbG8ubWQpey5hbGVydC1saW5rfS4gU2UgdHJhdGEgZGUgcmVnbGFzIMO6bmljYW1lbnRlIHBhcmEgbG9zIG9qb3MgaHVtYW5vcywgeSBxdWUgbm8gYWZlY3RhbiBlbiBhYnNvbHV0byBsYSBlZmljaWVuY2lhIG8gY29ycmVjdGl0dWQgZGUgbGEgcHJvZ3JhbWFjacOzbi4gRW4gZ2VuZXJhbCwgbm8gZXhpc3RlbiBndcOtYXMgYnVlbmFzIG8gbWFsYXMsIGxhIGlkZWEgZXMgZWxlZ2lyIHVuYSB5IHNlciBjb25zaXN0ZW50ZS4gRGUgZXN0YSBtYW5lcmEsIHZhcyBhIHBvZGVyIGVudGVuZGVyIHR1IGPDs2RpZ28gY29uIG3DoXMgZmFjaWxpZGFkLiAKOjo6CgoKCjxkaXYgY2xhc3M9ImJ0bi1ncm91cCIgcm9sZT0iZ3JvdXAiIGFyaWEtbGFiZWw9Ik5hdmVnYWNpw7NuIj4KICA8YSBocmVmPSAiMDMtcmVwb3J0ZXMtSS5odG1sIiBjbGFzcyA9ICJidG4gYnRuLXByaW1hcnkiPkFudGVyaW9yPC9hPgogIDxhIGhyZWY9ICIwNS1kcGx5ci5odG1sIiBjbGFzcyA9ICJidG4gYnRuLXByaW1hcnkiPlNpZ3VpZW50ZTwvYT4KPC9kaXY+