Processing math: 100%
  • Práctica Guiada
  • Ejemplo 1: Iterando en la EPH
  • Ejemplo 2. Regresión lineal
  • Ejemplo 3: Gráficos en serie
library(fs)
library(tidyverse)
── Attaching packages ────────────────────────────────────────────────────────────────────── tidyverse 1.2.1 ──
✔ ggplot2 3.2.0     ✔ purrr   0.3.2
✔ tibble  2.1.3     ✔ dplyr   0.8.3
✔ tidyr   0.8.3     ✔ stringr 1.4.0
✔ readr   1.3.1     ✔ forcats 0.4.0
── Conflicts ───────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter()  masks stats::filter()
✖ purrr::is_null() masks testthat::is_null()
✖ dplyr::lag()     masks stats::lag()
✖ dplyr::matches() masks testthat::matches()
library(openxlsx)
library(glue)

Attaching package: ‘glue’

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

    collapse

Ejemplo 1: Iterando en la EPH

Lo primero que necesitamos es definir un vector o lista sobre el que iterar.

Por ejemplo, podemos armar un vector con los path a las bases individuales, con el comando fs::dir_ls


bases_individuales_path <- dir_ls(path = '../fuentes/', regexp= 'individual')
bases_individuales_path
../fuentes/usu_individual_t119.txt ../fuentes/usu_individual_t418.txt 

Luego, como en la función que usamos para leer las bases definimos muchos parametros, nos podemos armar una función wrapper que sólo necesite un parámetro, y que simplifique la escritura del map


leer_base_eph <- function(path) {
  read.table(path,sep=";", dec=",", header = TRUE, fill = TRUE) %>% 
    select(ANO4,TRIMESTRE,REGION,P21,CH04, CH06)
}

bases_df <- tibble(bases_individuales_path) %>%
  mutate(base = map(bases_individuales_path, leer_base_eph))
bases_df
ABCDEFGHIJ0123456789
bases_individuales_path
<S3: fs_path>
base
<list>
../fuentes/usu_individual_t119.txt<data.frame [59,369 × 6]>
../fuentes/usu_individual_t418.txt<data.frame [57,418 × 6]>

El resultado es un DF donde la columna base tiene en cada fila, otro DF con la base de la EPH de ese período. Esto es lo que llamamos un nested DF o dataframe nesteado pa les pibes.

Si queremos juntar todo, podemos usar unnest()

bases_df <- bases_df %>% unnest()
bases_df
ABCDEFGHIJ0123456789
bases_individuales_path
<S3: fs_path>
ANO4
<int>
TRIMESTRE
<int>
REGION
<int>
P21
<int>
CH04
<int>
CH06
<int>
../fuentes/usu_individual_t119.txt20191410228
../fuentes/usu_individual_t119.txt20191410213
../fuentes/usu_individual_t119.txt2019141011
../fuentes/usu_individual_t119.txt20191415000241
../fuentes/usu_individual_t119.txt2019141029
../fuentes/usu_individual_t119.txt20191418000151
../fuentes/usu_individual_t119.txt20191410163
../fuentes/usu_individual_t119.txt20191410262
../fuentes/usu_individual_t119.txt20191410224
../fuentes/usu_individual_t119.txt20191413000174

¿Qué pasa si los DF que tenemos nesteados no tienen la misma cantidad de columnas?

Esto mismo lo podemos usar para fragmentar el datastet por alguna variable, con el group_by()

bases_df %>% 
  group_by(REGION) %>% 
  nest()
ABCDEFGHIJ0123456789
REGION
<int>
data
<list>
41<tibble>
44<tibble>
42<tibble>
43<tibble>
40<tibble>
1<tibble>
NA

Así, para cada región tenemos un DF.

¿ De qué sirve todo esto?

No todo en la vida es un Dataframe. Hay estucturas de datos que no se pueden normalizar a filas y columnas. En esos casos recurríamos tradicionalmente a los loops. Con MAP podemos tener los elementos agrupados en un sólo objeto y aún conservar sus formas diferentes.

Ejemplo 2. Regresión lineal

Si bien no nos vamos a meter en el detalle del modelo lineal hoy, es útil usarlo como ejemplo de lo que podemos hacer con MAP.

Planteamos el modelo P21=β0+β1∗CH04+β2∗CH06 Osea, un modleo que explica el ingreso según sexo y edad


lmfit <- lm(P21~factor(CH04)+CH06,data = bases_df)

summary(lmfit)

Call:
lm(formula = P21 ~ factor(CH04) + CH06, data = bases_df)

Residuals:
   Min     1Q Median     3Q    Max 
-15472  -6606  -3367   2148 590198 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)    4853.196     74.509   65.14   <2e-16 ***
factor(CH04)2 -4063.112     72.200  -56.27   <2e-16 ***
CH06            103.095      1.612   63.97   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 12300 on 116784 degrees of freedom
Multiple R-squared:  0.05511,   Adjusted R-squared:  0.0551 
F-statistic:  3406 on 2 and 116784 DF,  p-value: < 2.2e-16

(al final de la clase podemos charlar sobre los resultados, si hay interés :-) )

De forma Tidy, la librería broom nos da los resultados en un DF.

broom::tidy(lmfit)
ABCDEFGHIJ0123456789
term
<chr>
estimate
<dbl>
std.error
<dbl>
statistic
<dbl>
p.value
<dbl>
(Intercept)4853.195774.50866965.135990
factor(CH04)2-4063.111672.200472-56.275420
CH06103.09491.61155263.972420

Si lo queremos hacer por region

Loopeando


resultados <- tibble()

for (region in unique(bases_df$REGION)) {
  
  data <- bases_df %>% 
    filter(REGION==region)
  
  lmfit <- lm(P21~factor(CH04)+CH06,data = data)
  
  lmtidy <- broom::tidy(lmfit)
  lmtidy$region <- region
  resultados <- bind_rows(resultados,lmtidy)

}

resultados
ABCDEFGHIJ0123456789
term
<chr>
estimate
<dbl>
std.error
<dbl>
statistic
<dbl>
p.value
<dbl>
region
<int>
(Intercept)3768.18249185.42841720.321493.151614e-9041
factor(CH04)2-3814.35153179.857478-21.207636.002874e-9841
CH06105.899904.18265425.318831.117625e-13741
(Intercept)7156.15707291.13475024.580221.087102e-13044
factor(CH04)2-5937.71621278.112122-21.350081.420577e-9944
CH06145.020396.31610022.960431.398609e-11444
(Intercept)4930.01605230.62745821.376542.152210e-9942
factor(CH04)2-4007.23821224.154340-17.877141.706548e-7042
CH0697.806674.95427719.741872.676399e-8542
(Intercept)5107.40367131.11718038.952970.000000e+0043

Usando MAP

Primero me armo una funcion que me simplifica el codigo

fun<-function(porcion,grupo) {  broom::tidy(lm(P21~factor(CH04)+CH06,data = porcion))}
bases_df_lm <- bases_df %>% 
  group_by(REGION) %>%
  nest() %>% 
  mutate(lm = map(data,fun))
bases_df_lm
ABCDEFGHIJ0123456789
REGION
<int>
data
<list>
lm
<list>
41<tibble><tibble>
44<tibble><tibble>
42<tibble><tibble>
43<tibble><tibble>
40<tibble><tibble>
1<tibble><tibble>
bases_df_lm %>% 
  unnest(lm)
ABCDEFGHIJ0123456789
REGION
<int>
term
<chr>
estimate
<dbl>
std.error
<dbl>
statistic
<dbl>
p.value
<dbl>
41(Intercept)3768.18249185.42841720.321493.151614e-90
41factor(CH04)2-3814.35153179.857478-21.207636.002874e-98
41CH06105.899904.18265425.318831.117625e-137
44(Intercept)7156.15707291.13475024.580221.087102e-130
44factor(CH04)2-5937.71621278.112122-21.350081.420577e-99
44CH06145.020396.31610022.960431.398609e-114
42(Intercept)4930.01605230.62745821.376542.152210e-99
42factor(CH04)2-4007.23821224.154340-17.877141.706548e-70
42CH0697.806674.95427719.741872.676399e-85
43(Intercept)5107.40367131.11718038.952970.000000e+00

O incluso más facil, utilizando group_modify (que es un atajo que solo acepta DF)

bases_df %>% 
  group_by(REGION) %>% 
  group_modify(fun)
ABCDEFGHIJ0123456789
REGION
<int>
term
<chr>
estimate
<dbl>
std.error
<dbl>
statistic
<dbl>
p.value
<dbl>
1(Intercept)5195.90933196.75550126.407953.447022e-151
1factor(CH04)2-4051.49425189.365315-21.395121.797246e-100
1CH0688.151234.12105021.390481.981157e-100
40(Intercept)3328.84915127.99666726.007314.116772e-147
40factor(CH04)2-3239.41116124.976931-25.920073.743213e-146
40CH06122.047762.88895942.246280.000000e+00
41(Intercept)3768.18249185.42841720.321493.151614e-90
41factor(CH04)2-3814.35153179.857478-21.207636.002874e-98
41CH06105.899904.18265425.318831.117625e-137
42(Intercept)4930.01605230.62745821.376542.152210e-99

Pero MAP sirve para operar con cualquier objeto de R.

Por ejemplo podemos guardar el objeto S3:lm que es la regresion lineal entrenada. Ese objeto no es ni un vector, ni una lista, ni un DF. No es una estructura de datos, sino que es algo distinto, con propiedades como predict() para predecir, el summary() que vimos, etc.

fun<-function(porcion,grupo) {  lm(P21~factor(CH04)+CH06,data = porcion)}

bases_df %>% 
  group_by(REGION) %>%
  nest() %>%  
  mutate(lm = map(data,fun))
ABCDEFGHIJ0123456789
REGION
<int>
data
<list>
lm
<list>
41<tibble><S3: lm>
44<tibble><S3: lm>
42<tibble><S3: lm>
43<tibble><S3: lm>
40<tibble><S3: lm>
1<tibble><S3: lm>

Ejemplo 3: Gráficos en serie

Veamos un tercer ejemplo con otra base de datos que ya conocemos: Gapminder, que muestra algunos datos sobre la población de los países por año.

El objetivo de este ejercicio es hacer un gráfico por país de forma automática.

  • Primero veamos los datos

library(gapminder)


gapminder_unfiltered %>% 
  sample_n(10)
ABCDEFGHIJ0123456789
country
<fctr>
continent
<fctr>
year
<int>
lifeExp
<dbl>
pop
<int>
gdpPercap
<dbl>
TaiwanAsia197971.310174501366656.7008
Guinea-BissauAfrica197737.465745228764.7260
United StatesAmericas195068.12015227100012922.2343
Sierra LeoneAfrica195230.3312143249879.7877
Slovak RepublicEurope197970.790492255811008.5129
NigeriaAfrica195737.802371733401100.5926
ItalyEurope200780.5465814773328569.7197
BelarusFSU199868.380103944075411.8618
BulgariaEurope197071.27084895746162.4685
NetherlandsEurope196173.5601163871312434.5779
NA

la base tiene la siguiente info:

  • country: Nombre del país
  • continent: Nombre del continente
  • year: año
  • lifeExp: Esperanza de vida al nacer
  • pop: Población
  • gdpPercap

  • Vamos a hacer un gráfico sencillo para Argentina


data_argentina <- gapminder_unfiltered %>% 
  filter(country=='Argentina')

ggplot(data_argentina, aes(year, lifeExp, size= pop, color=gdpPercap))+
  geom_point()+
  geom_line(alpha=0.6)+
  labs(title = unique(data_argentina$country))

  • Ahora que tenemos una idea de lo que queremos gráficar lo podemos poner adentro de una función que grafique.


# definimos la función
graficar_pais <- function(data, pais){
  
  ggplot(data, aes(year, lifeExp, size= pop, color=gdpPercap))+
    geom_point()+
    geom_line(alpha=0.6)+
    labs(title = pais)
}

probamos la función para un caso

graficar_pais(data_argentina, 'Argentina')

  • Nos armamos un dataset nesteado

gapminder_nest <- gapminder_unfiltered %>% 
  group_by(country) %>% 
  nest()

gapminder_nest %>% 
  sample_n(10)
ABCDEFGHIJ0123456789
country
<fctr>
data
<list>
Chad<tibble>
Costa Rica<tibble>
Australia<tibble>
Pakistan<tibble>
Egypt<tibble>
New Zealand<tibble>
Tunisia<tibble>
Nepal<tibble>
South Africa<tibble>
Comoros<tibble>
NA
  • Ahora podemos crear una nueva columna que contenga los gráficos
gapminder_nest <- gapminder_nest %>% 
  mutate(grafico= map2(.x = data, .y = country,.f =  graficar_pais))

gapminder_nest %>% 
  sample_n(10)
ABCDEFGHIJ0123456789
country
<fctr>
data
<list>
grafico
<list>
Sao Tome and Principe<tibble><S3: gg>
Lebanon<tibble><S3: gg>
Vietnam<tibble><S3: gg>
Bahamas<tibble><S3: gg>
Cuba<tibble><S3: gg>
Bangladesh<tibble><S3: gg>
Japan<tibble><S3: gg>
India<tibble><S3: gg>
Tunisia<tibble><S3: gg>
Bulgaria<tibble><S3: gg>

Veamos un ejemplo

gapminder_nest$grafico[2]
[[1]]

Ahora podemos guardar todos los gráficos en un archivo PDF

pdf('../resultados/graficos_gapminder.pdf')
gapminder_nest$grafico
dev.off()
LS0tCnRpdGxlOiBQcm9ncmFtYWNpb24gRnVuY2lvbmFsCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCmRhdGU6ICIiCnN1YnRpdGxlOiBQcsOhY3RpY2EgR3VpYWRhCi0tLQoKYGBge3J9CmxpYnJhcnkoZnMpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KG9wZW54bHN4KQpsaWJyYXJ5KGdsdWUpCgpgYGAKCiMjIyBFamVtcGxvIDE6IEl0ZXJhbmRvIGVuIGxhIEVQSAoKTG8gcHJpbWVybyBxdWUgbmVjZXNpdGFtb3MgZXMgZGVmaW5pciB1biB2ZWN0b3IgbyBsaXN0YSBzb2JyZSBlbCBxdWUgaXRlcmFyLiAKClBvciBlamVtcGxvLCBwb2RlbW9zIGFybWFyIHVuIHZlY3RvciBjb24gbG9zIHBhdGggYSBsYXMgYmFzZXMgaW5kaXZpZHVhbGVzLCBjb24gZWwgY29tYW5kbyBgZnM6OmRpcl9sc2AKCmBgYHtyfQoKYmFzZXNfaW5kaXZpZHVhbGVzX3BhdGggPC0gZGlyX2xzKHBhdGggPSAnLi4vZnVlbnRlcy8nLCByZWdleHA9ICdpbmRpdmlkdWFsJykKYmFzZXNfaW5kaXZpZHVhbGVzX3BhdGgKYGBgCgpMdWVnbywgY29tbyBlbiBsYSBmdW5jacOzbiBxdWUgdXNhbW9zIHBhcmEgbGVlciBsYXMgYmFzZXMgZGVmaW5pbW9zIG11Y2hvcyBwYXJhbWV0cm9zLCBub3MgcG9kZW1vcyBhcm1hciB1bmEgZnVuY2nDs24gX3dyYXBwZXJfIHF1ZSBzw7NsbyBuZWNlc2l0ZSB1biBwYXLDoW1ldHJvLCB5IHF1ZSBzaW1wbGlmaXF1ZSBsYSBlc2NyaXR1cmEgZGVsIG1hcAoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmxlZXJfYmFzZV9lcGggPC0gZnVuY3Rpb24ocGF0aCkgewogIHJlYWQudGFibGUocGF0aCxzZXA9IjsiLCBkZWM9IiwiLCBoZWFkZXIgPSBUUlVFLCBmaWxsID0gVFJVRSkgJT4lIAogICAgc2VsZWN0KEFOTzQsVFJJTUVTVFJFLFJFR0lPTixQMjEsQ0gwNCwgQ0gwNikKfQoKYmFzZXNfZGYgPC0gdGliYmxlKGJhc2VzX2luZGl2aWR1YWxlc19wYXRoKSAlPiUKICBtdXRhdGUoYmFzZSA9IG1hcChiYXNlc19pbmRpdmlkdWFsZXNfcGF0aCwgbGVlcl9iYXNlX2VwaCkpCgpgYGAKCmBgYHtyfQpiYXNlc19kZgpgYGAKCkVsIHJlc3VsdGFkbyBlcyB1biBERiBkb25kZSBsYSBjb2x1bW5hIF9fYmFzZV9fIHRpZW5lIGVuIGNhZGEgZmlsYSwgb3RybyBERiBjb24gbGEgYmFzZSBkZSBsYSBFUEggZGUgZXNlIHBlcsOtb2RvLiBFc3RvIGVzIGxvIHF1ZSBsbGFtYW1vcyB1biBfbmVzdGVkIERGXyBvIGRhdGFmcmFtZSBuZXN0ZWFkbyBwYSBsZXMgcGliZXMuCgpTaSBxdWVyZW1vcyBqdW50YXIgdG9kbywgcG9kZW1vcyB1c2FyIGB1bm5lc3QoKWAKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmJhc2VzX2RmIDwtIGJhc2VzX2RmICU+JSB1bm5lc3QoKQpiYXNlc19kZgpgYGAKCj4gwr9RdcOpIHBhc2Egc2kgbG9zIERGIHF1ZSB0ZW5lbW9zIG5lc3RlYWRvcyBubyB0aWVuZW4gbGEgbWlzbWEgY2FudGlkYWQgZGUgY29sdW1uYXM/CgoKRXN0byBtaXNtbyBsbyBwb2RlbW9zIHVzYXIgcGFyYSBmcmFnbWVudGFyIGVsIGRhdGFzdGV0IHBvciBhbGd1bmEgdmFyaWFibGUsIGNvbiBlbCBgZ3JvdXBfYnkoKWAKCgpgYGB7cn0KYmFzZXNfZGYgJT4lIAogIGdyb3VwX2J5KFJFR0lPTikgJT4lIAogIG5lc3QoKQoKYGBgCgpBc8OtLCBwYXJhIGNhZGEgcmVnacOzbiB0ZW5lbW9zIHVuIERGLgoKPiDCvyBEZSBxdcOpIHNpcnZlIHRvZG8gZXN0bz8KCk5vIHRvZG8gZW4gbGEgdmlkYSBlcyB1biBEYXRhZnJhbWUuIEhheSBlc3R1Y3R1cmFzIGRlIGRhdG9zIHF1ZSBubyBzZSBwdWVkZW4gbm9ybWFsaXphciBhIGZpbGFzIHkgY29sdW1uYXMuIEVuIGVzb3MgY2Fzb3MgcmVjdXJyw61hbW9zIHRyYWRpY2lvbmFsbWVudGUgYSBsb3MgbG9vcHMuIENvbiBNQVAgcG9kZW1vcyB0ZW5lciBsb3MgZWxlbWVudG9zIGFncnVwYWRvcyBlbiB1biBzw7NsbyBvYmpldG8geSBhw7puIGNvbnNlcnZhciBzdXMgZm9ybWFzIGRpZmVyZW50ZXMuCgojIyMgRWplbXBsbyAyLiBSZWdyZXNpw7NuIGxpbmVhbAoKU2kgYmllbiBubyBub3MgdmFtb3MgYSBtZXRlciBlbiBlbCBkZXRhbGxlIGRlbCBtb2RlbG8gbGluZWFsIGhveSwgZXMgw7p0aWwgdXNhcmxvIGNvbW8gZWplbXBsbyBkZSBsbyBxdWUgcG9kZW1vcyBoYWNlciBjb24gTUFQLgoKUGxhbnRlYW1vcyBlbCBtb2RlbG8gCiQkClAyMSA9IFxiZXRhXzAgKyBcYmV0YV8xKkNIMDQgKyBcYmV0YV8yKkNIMDYKJCQKT3NlYSwgdW4gbW9kbGVvIHF1ZSBleHBsaWNhIGVsIGluZ3Jlc28gc2Vnw7puIHNleG8geSBlZGFkCgoKYGBge3J9CgpsbWZpdCA8LSBsbShQMjF+ZmFjdG9yKENIMDQpK0NIMDYsZGF0YSA9IGJhc2VzX2RmKQoKc3VtbWFyeShsbWZpdCkKYGBgCihhbCBmaW5hbCBkZSBsYSBjbGFzZSBwb2RlbW9zIGNoYXJsYXIgc29icmUgbG9zIHJlc3VsdGFkb3MsIHNpIGhheSBpbnRlcsOpcyA6LSkgKQoKRGUgZm9ybWEgVGlkeSwgbGEgbGlicmVyw61hIGBicm9vbWAgbm9zIGRhIGxvcyByZXN1bHRhZG9zIGVuIHVuIERGLgoKYGBge3J9CmJyb29tOjp0aWR5KGxtZml0KQpgYGAKIAogU2kgbG8gcXVlcmVtb3MgaGFjZXIgcG9yIHJlZ2lvbgogCiAKIyMjIyBMb29wZWFuZG8KCmBgYHtyfQoKcmVzdWx0YWRvcyA8LSB0aWJibGUoKQoKZm9yIChyZWdpb24gaW4gdW5pcXVlKGJhc2VzX2RmJFJFR0lPTikpIHsKICAKICBkYXRhIDwtIGJhc2VzX2RmICU+JSAKICAgIGZpbHRlcihSRUdJT049PXJlZ2lvbikKICAKICBsbWZpdCA8LSBsbShQMjF+ZmFjdG9yKENIMDQpK0NIMDYsZGF0YSA9IGRhdGEpCiAgCiAgbG10aWR5IDwtIGJyb29tOjp0aWR5KGxtZml0KQogIGxtdGlkeSRyZWdpb24gPC0gcmVnaW9uCiAgcmVzdWx0YWRvcyA8LSBiaW5kX3Jvd3MocmVzdWx0YWRvcyxsbXRpZHkpCgp9CgpyZXN1bHRhZG9zCmBgYAoKIyMjIyBVc2FuZG8gTUFQCgpQcmltZXJvIG1lIGFybW8gdW5hIGZ1bmNpb24gcXVlIG1lIHNpbXBsaWZpY2EgZWwgY29kaWdvCmBgYHtyfQpmdW48LWZ1bmN0aW9uKHBvcmNpb24sZ3J1cG8pIHsgIGJyb29tOjp0aWR5KGxtKFAyMX5mYWN0b3IoQ0gwNCkrQ0gwNixkYXRhID0gcG9yY2lvbikpfQpgYGAKCmBgYHtyfQpiYXNlc19kZl9sbSA8LSBiYXNlc19kZiAlPiUgCiAgZ3JvdXBfYnkoUkVHSU9OKSAlPiUKICBuZXN0KCkgJT4lIAogIG11dGF0ZShsbSA9IG1hcChkYXRhLGZ1bikpCmJhc2VzX2RmX2xtCmJhc2VzX2RmX2xtICU+JSAKICB1bm5lc3QobG0pCmBgYAoKTyBpbmNsdXNvIG3DoXMgZmFjaWwsIHV0aWxpemFuZG8gYGdyb3VwX21vZGlmeWAgKHF1ZSBlcyB1biBhdGFqbyBxdWUgc29sbyBhY2VwdGEgREYpCgoKCmBgYHtyfQpiYXNlc19kZiAlPiUgCiAgZ3JvdXBfYnkoUkVHSU9OKSAlPiUgCiAgZ3JvdXBfbW9kaWZ5KGZ1bikKYGBgCgoKUGVybyBNQVAgc2lydmUgcGFyYSBvcGVyYXIgY29uIGN1YWxxdWllciBvYmpldG8gZGUgUi4KClBvciBlamVtcGxvIHBvZGVtb3MgZ3VhcmRhciBlbCBfX29iamV0b19fIGBTMzpsbWAgcXVlIGVzIGxhIHJlZ3Jlc2lvbiBsaW5lYWwgZW50cmVuYWRhLiBFc2Ugb2JqZXRvIG5vIGVzIG5pIHVuIHZlY3RvciwgbmkgdW5hIGxpc3RhLCBuaSB1biBERi4gTm8gZXMgdW5hIGVzdHJ1Y3R1cmEgZGUgZGF0b3MsIHNpbm8gcXVlIGVzIGFsZ28gZGlzdGludG8sIGNvbiBfcHJvcGllZGFkZXNfIGNvbW8gYHByZWRpY3QoKWAgcGFyYSBwcmVkZWNpciwgZWwgYHN1bW1hcnkoKWAgcXVlIHZpbW9zLCBldGMuCgpgYGB7cn0KZnVuPC1mdW5jdGlvbihwb3JjaW9uLGdydXBvKSB7ICBsbShQMjF+ZmFjdG9yKENIMDQpK0NIMDYsZGF0YSA9IHBvcmNpb24pfQoKYmFzZXNfZGYgJT4lIAogIGdyb3VwX2J5KFJFR0lPTikgJT4lCiAgbmVzdCgpICU+JSAgCiAgbXV0YXRlKGxtID0gbWFwKGRhdGEsZnVuKSkKYGBgCgojIyMgRWplbXBsbyAzOiBHcsOhZmljb3MgZW4gc2VyaWUKClZlYW1vcyB1biB0ZXJjZXIgZWplbXBsbyBjb24gb3RyYSBiYXNlIGRlIGRhdG9zIHF1ZSB5YSBjb25vY2Vtb3M6IEdhcG1pbmRlciwgcXVlIG11ZXN0cmEgYWxndW5vcyBkYXRvcyBzb2JyZSBsYSBwb2JsYWNpw7NuIGRlIGxvcyBwYcOtc2VzIHBvciBhw7FvLgoKRWwgb2JqZXRpdm8gZGUgZXN0ZSBlamVyY2ljaW8gZXMgaGFjZXIgdW4gZ3LDoWZpY28gcG9yIHBhw61zIGRlIGZvcm1hIGF1dG9tw6F0aWNhLiAKCi0gUHJpbWVybyB2ZWFtb3MgbG9zIGRhdG9zCgoKYGBge3J9CgpsaWJyYXJ5KGdhcG1pbmRlcikKCgpnYXBtaW5kZXJfdW5maWx0ZXJlZCAlPiUgCiAgc2FtcGxlX24oMTApCgpgYGAKCmxhIGJhc2UgdGllbmUgbGEgc2lndWllbnRlIGluZm86CgotIGNvdW50cnk6IE5vbWJyZSBkZWwgcGHDrXMKLSBjb250aW5lbnQ6IE5vbWJyZSBkZWwgY29udGluZW50ZQotIHllYXI6IGHDsW8KLSBsaWZlRXhwOiBFc3BlcmFuemEgZGUgdmlkYSBhbCBuYWNlcgotIHBvcDogUG9ibGFjacOzbgotIGdkcFBlcmNhcAoKCi0gVmFtb3MgYSBoYWNlciB1biBncsOhZmljbyBzZW5jaWxsbyBwYXJhIEFyZ2VudGluYQoKYGBge3J9CgpkYXRhX2FyZ2VudGluYSA8LSBnYXBtaW5kZXJfdW5maWx0ZXJlZCAlPiUgCiAgZmlsdGVyKGNvdW50cnk9PSdBcmdlbnRpbmEnKQoKZ2dwbG90KGRhdGFfYXJnZW50aW5hLCBhZXMoeWVhciwgbGlmZUV4cCwgc2l6ZT0gcG9wLCBjb2xvcj1nZHBQZXJjYXApKSsKICBnZW9tX3BvaW50KCkrCiAgZ2VvbV9saW5lKGFscGhhPTAuNikrCiAgbGFicyh0aXRsZSA9IHVuaXF1ZShkYXRhX2FyZ2VudGluYSRjb3VudHJ5KSkKYGBgCgoKLSBBaG9yYSBxdWUgdGVuZW1vcyB1bmEgaWRlYSBkZSBsbyBxdWUgcXVlcmVtb3MgZ3LDoWZpY2FyIGxvIHBvZGVtb3MgcG9uZXIgYWRlbnRybyBkZSB1bmEgZnVuY2nDs24gcXVlIGdyYWZpcXVlLgoKYGBge3J9CgoKIyBkZWZpbmltb3MgbGEgZnVuY2nDs24KZ3JhZmljYXJfcGFpcyA8LSBmdW5jdGlvbihkYXRhLCBwYWlzKXsKICAKICBnZ3Bsb3QoZGF0YSwgYWVzKHllYXIsIGxpZmVFeHAsIHNpemU9IHBvcCwgY29sb3I9Z2RwUGVyY2FwKSkrCiAgICBnZW9tX3BvaW50KCkrCiAgICBnZW9tX2xpbmUoYWxwaGE9MC42KSsKICAgIGxhYnModGl0bGUgPSBwYWlzKQp9CgpgYGAKCgpwcm9iYW1vcyBsYSBmdW5jacOzbiBwYXJhIHVuIGNhc28KCmBgYHtyfQpncmFmaWNhcl9wYWlzKGRhdGFfYXJnZW50aW5hLCAnQXJnZW50aW5hJykKCmBgYAoKCi0gTm9zIGFybWFtb3MgdW4gZGF0YXNldCBuZXN0ZWFkbwoKYGBge3J9CgpnYXBtaW5kZXJfbmVzdCA8LSBnYXBtaW5kZXJfdW5maWx0ZXJlZCAlPiUgCiAgZ3JvdXBfYnkoY291bnRyeSkgJT4lIAogIG5lc3QoKQoKZ2FwbWluZGVyX25lc3QgJT4lIAogIHNhbXBsZV9uKDEwKQoKYGBgCgotIEFob3JhIHBvZGVtb3MgY3JlYXIgdW5hIG51ZXZhIGNvbHVtbmEgcXVlIGNvbnRlbmdhIGxvcyBncsOhZmljb3MKCmBgYHtyfQpnYXBtaW5kZXJfbmVzdCA8LSBnYXBtaW5kZXJfbmVzdCAlPiUgCiAgbXV0YXRlKGdyYWZpY289IG1hcDIoLnggPSBkYXRhLCAueSA9IGNvdW50cnksLmYgPSAgZ3JhZmljYXJfcGFpcykpCgpnYXBtaW5kZXJfbmVzdCAlPiUgCiAgc2FtcGxlX24oMTApCmBgYAoKVmVhbW9zIHVuIGVqZW1wbG8KCgpgYGB7ciB9CmdhcG1pbmRlcl9uZXN0JGdyYWZpY29bMl0KYGBgCgoKQWhvcmEgcG9kZW1vcyBndWFyZGFyIHRvZG9zIGxvcyBncsOhZmljb3MgZW4gdW4gYXJjaGl2byBQREYKCmBgYHtyZXZhbD1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpwZGYoJy4uL3Jlc3VsdGFkb3MvZ3JhZmljb3NfZ2FwbWluZGVyLnBkZicpCmdhcG1pbmRlcl9uZXN0JGdyYWZpY28KZGV2Lm9mZigpCmBgYAoKCg==