Variables

Datos Iniciales

Train

library(data.table)
train <- fread("../data/train.csv", encoding = "UTF-8") %>% 
  select(-c(Id, property_type, operation_type, currency)) %>% 
  mutate(rooms = factor(rooms),
         bedrooms = factor(bedrooms),
         bathrooms = factor(bathrooms))
head(train)

Test

test <- fread("../data/test.csv", encoding = "UTF-8") %>% 
  select(-c(Id, property_type, operation_type, currency)) %>% 
  mutate(rooms = factor(rooms),
         bedrooms = factor(bedrooms),
         bathrooms = factor(bathrooms))
head(test)

Sample Submission

sampleSub <- fread("../data/sampleSub.csv", encoding = "UTF-8")
head(sampleSub)

Exploratorio Train

Tamaño muestral

library(tidyverse)
library(treemap)
train %>% 
  group_by(pais, provincia_departamento) %>% 
  count(name = "total") %>% 
  treemap(.,
        index = c("pais","provincia_departamento"),
        vSize = "total", 
        type = "index", 
        palette = c("#1C8356", "#C4451C"),
        title = "Tamaño muestral: País - Departamento",   
        fontsize.title = 12
 
)

NA
NA
train %>% 
  group_by(pais, rooms) %>% 
  count(name = "total") %>% 
  treemap(.,
        index = c("pais","rooms"),
        vSize = "total", 
        type = "index", 
        palette = c("#1C8356", "#C4451C"),
        title = "Tamaño muestral: País - # de Salas",   
        fontsize.title = 12
 
)

train %>% 
  group_by(pais, bedrooms) %>% 
  count(name = "total") %>% 
  treemap(.,
        index = c("pais","bedrooms"),
        vSize = "total", 
        type = "index", 
        palette = c("#1C8356", "#C4451C"),
        title = "Tamaño muestral: País - # de Dormitorios",   
        fontsize.title = 12
 
)

train %>% 
  group_by(pais, bathrooms) %>% 
  count(name = "total") %>% 
  treemap(.,
        index = c("pais","bathrooms"),
        vSize = "total", 
        type = "index", 
        palette = c("#1C8356", "#C4451C"),
        title = "Tamaño muestral: País - # de Baños",   
        fontsize.title = 12
 
)

Distribuciones

  • Baños, Dormitorios y Salas:
library(ggthemes)
train %>% 
  select(rooms, bedrooms, bathrooms) %>% 
  gather() %>% 
  group_by(key, value) %>% 
  count(name = "Total") %>% 
  ggplot(aes(x = value, y = Total)) +
  facet_wrap(~key, scales = "free") +
  geom_point(size = 2, color = "#C4451C") +
  geom_segment(aes(y = 0, xend = value, yend = Total), color = "#1C8356") +
  scale_x_continuous(n.breaks = 10) +
  theme_fivethirtyeight()

  • Precio y área en escala original y logarítmica:

Comparativos

  • Distribución de precios y área por número de habitaciones:
train %>% 
  select(rooms, price, surface_total) %>% 
  mutate(rooms = factor(rooms)) %>% 
  mutate(priceLog = log(price),
         surfaceLog = log(surface_total)) %>% 
  gather(key = "key", value = "valor", -c(rooms)) %>% 
  ggplot(aes(x = rooms, y = valor)) +
  facet_wrap(~key, scales = "free") +
  geom_boxplot(outlier.alpha = 0.01, fill = "#1C8356", alpha = 0.5,
               color = "#C4451C", size = 0.1) +
  stat_summary(fun.y = mean, geom = "point", color = "#C4451C", size = 2,
               shape = 17) +
  theme_fivethirtyeight() +
  labs(caption = "Triángulo = promedio", subtitle = "Habitaciones")

  • Distribución de precios y área por número de dormitorios:
train %>% 
  select(bedrooms, price, surface_total) %>% 
  mutate(bedrooms = factor(bedrooms)) %>% 
  mutate(priceLog = log(price),
         surfaceLog = log(surface_total)) %>% 
  gather(key = "key", value = "valor", -c(bedrooms)) %>% 
  ggplot(aes(x = bedrooms, y = valor)) +
  facet_wrap(~key, scales = "free") +
  geom_boxplot(outlier.alpha = 0.01, fill = "#1C8356", alpha = 0.5,
               color = "#C4451C", size = 0.1) +
  stat_summary(fun.y = mean, geom = "point", color = "#C4451C", size = 2,
               shape = 17) +
  theme_fivethirtyeight() +
  labs(caption = "Triángulo = promedio", subtitle = "Dormitorios")

  • Distribución de precios y área por número de baños:
train %>% 
  select(bathrooms, price, surface_total) %>% 
  mutate(bathrooms = factor(bathrooms)) %>% 
  mutate(priceLog = log(price),
         surfaceLog = log(surface_total)) %>% 
  gather(key = "key", value = "valor", -c(bathrooms)) %>% 
  ggplot(aes(x = bathrooms, y = valor)) +
  facet_wrap(~key, scales = "free") +
  geom_boxplot(outlier.alpha = 0.01, fill = "#1C8356", alpha = 0.5,
               color = "#C4451C", size = 0.1) +
  stat_summary(fun.y = mean, geom = "point", color = "#C4451C", size = 2,
               shape = 17) +
  theme_fivethirtyeight() +
  labs(caption = "Triángulo = promedio", subtitle = "Baños")

Dispersiones

  • Relación general de área vs precio: como son más de 25 mil observaciones es preferible utilizar geom_bin2d() en lugar de geom_point().
train %>% 
  ggplot(aes(x = surface_total, y = price)) +
  geom_bin2d(color = "white", alpha = 0.8) +
  scale_fill_gradient2(low = "white", mid = "#1C8356", high = "#C4451C") +
  geom_smooth(method = "lm", color = "#C4451C", size = 2, se = FALSE) +
  theme_fivethirtyeight() +
  theme(legend.position = "right", legend.direction = "vertical")

NA

GLMNET

Train - Test

library(tidymodels)
set.seed(123)
datosTrain <- train %>% 
  select(-c(Id, property_type, operation_type, currency)) %>% 
  mutate(rooms = factor(rooms),
         bedrooms = factor(bedrooms),
         bathrooms = factor(bathrooms))
split_inicial <- initial_split(
                    data   = datosTrain,
                    prop   = 0.8,
                    strata = price
                 )
datos_train <- training(split_inicial)
datos_test  <- testing(split_inicial)

Modelo GLM - Tuning

# Modelo
mod_glm <- linear_reg(mode    = "regression",
                      penalty = tune(),
                      mixture = tune()) %>%
  set_engine(engine = "glmnet")

# Preprocesamiento
receta <- recipe(formula = price ~ .,
                 data =  datos_train) %>%
  step_center(all_numeric(), -all_outcomes()) %>%
  step_scale(all_numeric(), -all_outcomes()) %>%
  step_dummy(all_nominal(), -all_outcomes())

# Validación del modelo: validación cruzada K-folds con k = 10
set.seed(1992)
crossVal <- vfold_cv(data = datos_train,
                     v = 10,
                     strata = price)

# WORKFLOW
# =============================================================================
flujo_modelo <- workflow() %>%
  add_recipe(receta) %>%
  add_model(mod_glm)

# Grid de hiperparámetros
hiperpar_grid <- grid_regular(
  penalty(range = c(0, 1), trans = NULL),
  mixture(range = c(0, 1), trans = NULL),
  levels = c(10, 10))

# EJECUCIÓN DE LA OPTIMIZACIÓN DE HIPERPARÁMETROS
# =============================================================================
registerDoParallel(cores = parallel::detectCores() - 1)
myGrid <- tune_grid(
  object = flujo_modelo,
  resamples = crossVal,
  metrics = metric_set(rmse),
  control = control_resamples(save_pred = TRUE),
  grid = hiperpar_grid
)
stopImplicitCluster()
  • Mejores 10 modelos:
show_best(myGrid, metric = "rmse", n = 10)

Modelo GLM Final

mejorGrid <- select_best(myGrid, metric = "rmse")

flujo_final <- finalize_workflow(x = flujo_modelo, parameters = mejorGrid)


glm_final <-  flujo_final %>%
  fit(data = train)

Predichos GLM

predicciones <- glm_final %>%
  predict(new_data = datos_test,
          type = "numeric")
predicciones[is.na(predicciones)] <- 0
  • Error de test:
predicciones <- predicciones %>% 
                bind_cols(datos_test %>% select(price))

error_test_glm  <- rmse(
  data = predicciones,
  truth = price,
  estimate = .pred,
  na_rm = TRUE
) %>%
  mutate(modelo = "GLM")
error_test_glm

Predichos - Nuevos

prediccionesGLM_Subm1 <- glm_final %>%
  predict(new_data = test,
          type = "numeric")
prediccionesGLM_Subm1[is.na(prediccionesGLM_Subm1)] <- 0
prediccionesGLM_Subm1[prediccionesGLM_Subm1 < 0 ] <- 0
hist(prediccionesGLM_Subm1$.pred)

  • Submission 1:
subm1_glmnet <- data.frame(Id = sampleSub$Id,
                           price = prediccionesGLM_Subm1$.pred)
write.csv(subm1_glmnet, file = "Subm1.csv", row.names = FALSE)
  • Score: 2.72416885190957 - Posición 32.
LS0tDQp0aXRsZTogIlByZWRpY2Npw7NuIGRlIHByZWNpbyBkZSBhcGFydGFtZW50b3MiDQpzdWJ0aXRsZTogIlJldG8gRGF0YVNvdXJjZSINCmF1dGhvcjogIltFZGltZXIgKFNpZGVyZXVzKV0oaHR0cHM6Ly9lZGltZXIuZ2l0aHViLmlvLykiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiANCiAgICAgIHNtb290aF9zY3JvbGw6IGZhbHNlDQogICAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrDQogICAgdGhlbWU6IHNwYWNlbGFiDQogICAgY3NzOiBlc3RpbG8uY3NzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQotLS0NCg0KPGNlbnRlcj4NCjxpbWcgc3JjID0gIi4uL2ltZy9jb21wZXRlbmNpYS5wbmciIC8+DQo8L2NlbnRlcj4NCg0KYGBge3IsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBmaWcuYWxpZ24gPSAiY2VudGVyIikNCmBgYA0KDQotIFtTaXRpbyBvZmljaWFsIGRlbCByZXRvIGVuIERhdGFTb3VyY2UuXShodHRwczovL3d3dy5kYXRhc291cmNlLmFpL2VzL2hvbWUvY29tcGV0aXRpb25zL3ByZWRpY2Npb24tZGUtcHJlY2lvcy1kZS1hcGFydGFtZW50b3MtZW4tYXJnZW50aW5hLXktY29sb21iaWEpDQoNCiMgVmFyaWFibGVzDQoNCjxjZW50ZXI+DQo8aW1nIHNyYyA9ICIuLi9pbWcvdmFyaWFibGVzLnBuZyIgLz4NCjwvY2VudGVyPg0KDQojIERhdG9zIEluaWNpYWxlcyB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30NCg0KIyMgVHJhaW4NCg0KYGBge3J9DQpsaWJyYXJ5KGRhdGEudGFibGUpDQp0cmFpbiA8LSBmcmVhZCgiLi4vZGF0YS90cmFpbi5jc3YiLCBlbmNvZGluZyA9ICJVVEYtOCIpICU+JSANCiAgc2VsZWN0KC1jKElkLCBwcm9wZXJ0eV90eXBlLCBvcGVyYXRpb25fdHlwZSwgY3VycmVuY3kpKSAlPiUgDQogIG11dGF0ZShyb29tcyA9IGZhY3Rvcihyb29tcyksDQogICAgICAgICBiZWRyb29tcyA9IGZhY3RvcihiZWRyb29tcyksDQogICAgICAgICBiYXRocm9vbXMgPSBmYWN0b3IoYmF0aHJvb21zKSkNCmhlYWQodHJhaW4pDQpgYGANCg0KIyMgVGVzdA0KDQpgYGB7cn0NCnRlc3QgPC0gZnJlYWQoIi4uL2RhdGEvdGVzdC5jc3YiLCBlbmNvZGluZyA9ICJVVEYtOCIpICU+JSANCiAgc2VsZWN0KC1jKElkLCBwcm9wZXJ0eV90eXBlLCBvcGVyYXRpb25fdHlwZSwgY3VycmVuY3kpKSAlPiUgDQogIG11dGF0ZShyb29tcyA9IGZhY3Rvcihyb29tcyksDQogICAgICAgICBiZWRyb29tcyA9IGZhY3RvcihiZWRyb29tcyksDQogICAgICAgICBiYXRocm9vbXMgPSBmYWN0b3IoYmF0aHJvb21zKSkNCmhlYWQodGVzdCkNCmBgYA0KDQojIyBTYW1wbGUgU3VibWlzc2lvbg0KDQpgYGB7cn0NCnNhbXBsZVN1YiA8LSBmcmVhZCgiLi4vZGF0YS9zYW1wbGVTdWIuY3N2IiwgZW5jb2RpbmcgPSAiVVRGLTgiKQ0KaGVhZChzYW1wbGVTdWIpDQpgYGANCiMgRXhwbG9yYXRvcmlvIFRyYWluIHsudGFic2V0IC50YWJzZXQtZmFkZSAudGFic2V0LXBpbGxzfQ0KDQojIyBUYW1hw7FvIG11ZXN0cmFsDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHRyZWVtYXApDQp0cmFpbiAlPiUgDQogIGdyb3VwX2J5KHBhaXMsIHByb3ZpbmNpYV9kZXBhcnRhbWVudG8pICU+JSANCiAgY291bnQobmFtZSA9ICJ0b3RhbCIpICU+JSANCiAgdHJlZW1hcCguLA0KICAgICAgICBpbmRleCA9IGMoInBhaXMiLCJwcm92aW5jaWFfZGVwYXJ0YW1lbnRvIiksDQogICAgICAgIHZTaXplID0gInRvdGFsIiwgDQogICAgICAgIHR5cGUgPSAiaW5kZXgiLCANCiAgICAgICAgcGFsZXR0ZSA9IGMoIiMxQzgzNTYiLCAiI0M0NDUxQyIpLA0KICAgICAgICB0aXRsZSA9ICJUYW1hw7FvIG11ZXN0cmFsOiBQYcOtcyAtIERlcGFydGFtZW50byIsICAgDQogICAgICAgIGZvbnRzaXplLnRpdGxlID0gMTINCiANCikNCg0KICAgICANCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnRyYWluICU+JSANCiAgZ3JvdXBfYnkocGFpcywgcm9vbXMpICU+JSANCiAgY291bnQobmFtZSA9ICJ0b3RhbCIpICU+JSANCiAgdHJlZW1hcCguLA0KICAgICAgICBpbmRleCA9IGMoInBhaXMiLCJyb29tcyIpLA0KICAgICAgICB2U2l6ZSA9ICJ0b3RhbCIsIA0KICAgICAgICB0eXBlID0gImluZGV4IiwgDQogICAgICAgIHBhbGV0dGUgPSBjKCIjMUM4MzU2IiwgIiNDNDQ1MUMiKSwNCiAgICAgICAgdGl0bGUgPSAiVGFtYcOxbyBtdWVzdHJhbDogUGHDrXMgLSAjIGRlIFNhbGFzIiwgICANCiAgICAgICAgZm9udHNpemUudGl0bGUgPSAxMg0KIA0KKQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdHJhaW4gJT4lIA0KICBncm91cF9ieShwYWlzLCBiZWRyb29tcykgJT4lIA0KICBjb3VudChuYW1lID0gInRvdGFsIikgJT4lIA0KICB0cmVlbWFwKC4sDQogICAgICAgIGluZGV4ID0gYygicGFpcyIsImJlZHJvb21zIiksDQogICAgICAgIHZTaXplID0gInRvdGFsIiwgDQogICAgICAgIHR5cGUgPSAiaW5kZXgiLCANCiAgICAgICAgcGFsZXR0ZSA9IGMoIiMxQzgzNTYiLCAiI0M0NDUxQyIpLA0KICAgICAgICB0aXRsZSA9ICJUYW1hw7FvIG11ZXN0cmFsOiBQYcOtcyAtICMgZGUgRG9ybWl0b3Jpb3MiLCAgIA0KICAgICAgICBmb250c2l6ZS50aXRsZSA9IDEyDQogDQopDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp0cmFpbiAlPiUgDQogIGdyb3VwX2J5KHBhaXMsIGJhdGhyb29tcykgJT4lIA0KICBjb3VudChuYW1lID0gInRvdGFsIikgJT4lIA0KICB0cmVlbWFwKC4sDQogICAgICAgIGluZGV4ID0gYygicGFpcyIsImJhdGhyb29tcyIpLA0KICAgICAgICB2U2l6ZSA9ICJ0b3RhbCIsIA0KICAgICAgICB0eXBlID0gImluZGV4IiwgDQogICAgICAgIHBhbGV0dGUgPSBjKCIjMUM4MzU2IiwgIiNDNDQ1MUMiKSwNCiAgICAgICAgdGl0bGUgPSAiVGFtYcOxbyBtdWVzdHJhbDogUGHDrXMgLSAjIGRlIEJhw7FvcyIsICAgDQogICAgICAgIGZvbnRzaXplLnRpdGxlID0gMTINCiANCikNCmBgYA0KDQojIyBEaXN0cmlidWNpb25lcw0KDQotICoqQmHDsW9zLCBEb3JtaXRvcmlvcyB5IFNhbGFzOioqDQoNCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD0zLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeShnZ3RoZW1lcykNCnRyYWluICU+JSANCiAgc2VsZWN0KHJvb21zLCBiZWRyb29tcywgYmF0aHJvb21zKSAlPiUgDQogIGdhdGhlcigpICU+JSANCiAgZ3JvdXBfYnkoa2V5LCB2YWx1ZSkgJT4lIA0KICBjb3VudChuYW1lID0gIlRvdGFsIikgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSB2YWx1ZSwgeSA9IFRvdGFsKSkgKw0KICBmYWNldF93cmFwKH5rZXksIHNjYWxlcyA9ICJmcmVlIikgKw0KICBnZW9tX3BvaW50KHNpemUgPSAyLCBjb2xvciA9ICIjQzQ0NTFDIikgKw0KICBnZW9tX3NlZ21lbnQoYWVzKHkgPSAwLCB4ZW5kID0gdmFsdWUsIHllbmQgPSBUb3RhbCksIGNvbG9yID0gIiMxQzgzNTYiKSArDQogIHNjYWxlX3hfY29udGludW91cyhuLmJyZWFrcyA9IDEwKSArDQogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpDQoNCmBgYA0KDQotICoqUHJlY2lvIHkgw6FyZWEgZW4gZXNjYWxhIG9yaWdpbmFsIHkgbG9nYXLDrXRtaWNhOioqDQoNCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD01LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KdHJhaW4gJT4lIA0KICBzZWxlY3QocHJpY2UsIHN1cmZhY2VfdG90YWwpICU+JSANCiAgbXV0YXRlKHByaWNlTG9nID0gbG9nKHByaWNlKSwNCiAgICAgICAgIHN1cmZhY2VMb2cgPSBsb2coc3VyZmFjZV90b3RhbCkpICU+JSANCiAgZ2F0aGVyKCkgJT4lIA0KICBncm91cF9ieShrZXkpICU+JSANCiAgc3VtbWFyaXNlKG1lZGlhID0gbWVhbih2YWx1ZSwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgIGRlID0gc2QodmFsdWUsIG5hLnJtID0gVFJVRSkpICU+JSANCiAgdW5ncm91cCgpICU+JSANCiAgbXV0YXRlKG1lZGlhX21hc18xREUgPSBtZWRpYSArIGRlLA0KICAgICAgICAgbWVkaWFfbWVub3NfMURFID0gbWVkaWEgLSBkZSktPg0KICBtZWRpYXMNCg0KdHJhaW4gJT4lIA0KICBzZWxlY3QocHJpY2UsIHN1cmZhY2VfdG90YWwpICU+JSANCiAgbXV0YXRlKHByaWNlTG9nID0gbG9nKHByaWNlKSwNCiAgICAgICAgIHN1cmZhY2VMb2cgPSBsb2coc3VyZmFjZV90b3RhbCkpICU+JSANCiAgZ2F0aGVyKCkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSB2YWx1ZSkpICsNCiAgZmFjZXRfd3JhcCh+a2V5LCBzY2FsZXMgPSAiZnJlZSIpICsNCiAgZ2VvbV9kZW5zaXR5KHNpemUgPSAwLjUsIGNvbG9yID0gIiNDNDQ1MUMiLCBmaWxsID0gIiMxQzgzNTYiLCBhbHBoYSA9IDAuNSkgKw0KICBnZW9tX3ZsaW5lKGRhdGEgPSBtZWRpYXMsIGFlcyh4aW50ZXJjZXB0ID0gbWVkaWEpLA0KICAgICAgICAgICAgIGNvbG9yID0gIiNDNDQ1MUMiLCBzaXplID0gMSkgKw0KICBnZW9tX3ZsaW5lKGRhdGEgPSBtZWRpYXMsIGFlcyh4aW50ZXJjZXB0ID0gbWVkaWFfbWFzXzFERSksDQogICAgICAgICAgICAgY29sb3IgPSAiIzFDODM1NiIsIHNpemUgPSAxLCBsdHkgPSAyKSArDQogIGdlb21fdmxpbmUoZGF0YSA9IG1lZGlhcywgYWVzKHhpbnRlcmNlcHQgPSBtZWRpYV9tZW5vc18xREUpLA0KICAgICAgICAgICAgIGNvbG9yID0gIiMxQzgzNTYiLCBzaXplID0gMSwgbHR5ID0gMikgKw0KICB0aGVtZV9maXZldGhpcnR5ZWlnaHQoKSArDQogIGxhYnMoY2FwdGlvbiA9ICJMw61uZWEgc8OzbGlkYTogcHJvbWVkaW9cbkzDrW5lYSBwdW50ZWFkYTogKzEgeSAtMSBERSIpDQoNCmBgYA0KDQojIyBDb21wYXJhdGl2b3MNCg0KLSAqKkRpc3RyaWJ1Y2nDs24gZGUgcHJlY2lvcyB5IMOhcmVhIHBvciBuw7ptZXJvIGRlIGhhYml0YWNpb25lczoqKg0KDQpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9NSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCnRyYWluICU+JSANCiAgc2VsZWN0KHJvb21zLCBwcmljZSwgc3VyZmFjZV90b3RhbCkgJT4lIA0KICBtdXRhdGUocm9vbXMgPSBmYWN0b3Iocm9vbXMpKSAlPiUgDQogIG11dGF0ZShwcmljZUxvZyA9IGxvZyhwcmljZSksDQogICAgICAgICBzdXJmYWNlTG9nID0gbG9nKHN1cmZhY2VfdG90YWwpKSAlPiUgDQogIGdhdGhlcihrZXkgPSAia2V5IiwgdmFsdWUgPSAidmFsb3IiLCAtYyhyb29tcykpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gcm9vbXMsIHkgPSB2YWxvcikpICsNCiAgZmFjZXRfd3JhcCh+a2V5LCBzY2FsZXMgPSAiZnJlZSIpICsNCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuYWxwaGEgPSAwLjAxLCBmaWxsID0gIiMxQzgzNTYiLCBhbHBoYSA9IDAuNSwNCiAgICAgICAgICAgICAgIGNvbG9yID0gIiNDNDQ1MUMiLCBzaXplID0gMC4xKSArDQogIHN0YXRfc3VtbWFyeShmdW4ueSA9IG1lYW4sIGdlb20gPSAicG9pbnQiLCBjb2xvciA9ICIjQzQ0NTFDIiwgc2l6ZSA9IDIsDQogICAgICAgICAgICAgICBzaGFwZSA9IDE3KSArDQogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpICsNCiAgbGFicyhjYXB0aW9uID0gIlRyacOhbmd1bG8gPSBwcm9tZWRpbyIsIHN1YnRpdGxlID0gIkhhYml0YWNpb25lcyIpDQoNCmBgYA0KDQotICoqRGlzdHJpYnVjacOzbiBkZSBwcmVjaW9zIHkgw6FyZWEgcG9yIG7Dum1lcm8gZGUgZG9ybWl0b3Jpb3M6KioNCiAgDQpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9NSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCnRyYWluICU+JSANCiAgc2VsZWN0KGJlZHJvb21zLCBwcmljZSwgc3VyZmFjZV90b3RhbCkgJT4lIA0KICBtdXRhdGUoYmVkcm9vbXMgPSBmYWN0b3IoYmVkcm9vbXMpKSAlPiUgDQogIG11dGF0ZShwcmljZUxvZyA9IGxvZyhwcmljZSksDQogICAgICAgICBzdXJmYWNlTG9nID0gbG9nKHN1cmZhY2VfdG90YWwpKSAlPiUgDQogIGdhdGhlcihrZXkgPSAia2V5IiwgdmFsdWUgPSAidmFsb3IiLCAtYyhiZWRyb29tcykpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gYmVkcm9vbXMsIHkgPSB2YWxvcikpICsNCiAgZmFjZXRfd3JhcCh+a2V5LCBzY2FsZXMgPSAiZnJlZSIpICsNCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuYWxwaGEgPSAwLjAxLCBmaWxsID0gIiMxQzgzNTYiLCBhbHBoYSA9IDAuNSwNCiAgICAgICAgICAgICAgIGNvbG9yID0gIiNDNDQ1MUMiLCBzaXplID0gMC4xKSArDQogIHN0YXRfc3VtbWFyeShmdW4ueSA9IG1lYW4sIGdlb20gPSAicG9pbnQiLCBjb2xvciA9ICIjQzQ0NTFDIiwgc2l6ZSA9IDIsDQogICAgICAgICAgICAgICBzaGFwZSA9IDE3KSArDQogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpICsNCiAgbGFicyhjYXB0aW9uID0gIlRyacOhbmd1bG8gPSBwcm9tZWRpbyIsIHN1YnRpdGxlID0gIkRvcm1pdG9yaW9zIikNCg0KYGBgDQoNCi0gKipEaXN0cmlidWNpw7NuIGRlIHByZWNpb3MgeSDDoXJlYSBwb3IgbsO6bWVybyBkZSBiYcOxb3M6KioNCiAgDQpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9NSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCnRyYWluICU+JSANCiAgc2VsZWN0KGJhdGhyb29tcywgcHJpY2UsIHN1cmZhY2VfdG90YWwpICU+JSANCiAgbXV0YXRlKGJhdGhyb29tcyA9IGZhY3RvcihiYXRocm9vbXMpKSAlPiUgDQogIG11dGF0ZShwcmljZUxvZyA9IGxvZyhwcmljZSksDQogICAgICAgICBzdXJmYWNlTG9nID0gbG9nKHN1cmZhY2VfdG90YWwpKSAlPiUgDQogIGdhdGhlcihrZXkgPSAia2V5IiwgdmFsdWUgPSAidmFsb3IiLCAtYyhiYXRocm9vbXMpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IGJhdGhyb29tcywgeSA9IHZhbG9yKSkgKw0KICBmYWNldF93cmFwKH5rZXksIHNjYWxlcyA9ICJmcmVlIikgKw0KICBnZW9tX2JveHBsb3Qob3V0bGllci5hbHBoYSA9IDAuMDEsIGZpbGwgPSAiIzFDODM1NiIsIGFscGhhID0gMC41LA0KICAgICAgICAgICAgICAgY29sb3IgPSAiI0M0NDUxQyIsIHNpemUgPSAwLjEpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gbWVhbiwgZ2VvbSA9ICJwb2ludCIsIGNvbG9yID0gIiNDNDQ1MUMiLCBzaXplID0gMiwNCiAgICAgICAgICAgICAgIHNoYXBlID0gMTcpICsNCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKw0KICBsYWJzKGNhcHRpb24gPSAiVHJpw6FuZ3VsbyA9IHByb21lZGlvIiwgc3VidGl0bGUgPSAiQmHDsW9zIikNCg0KYGBgDQoNCiMjIERpc3BlcnNpb25lcw0KDQotICoqUmVsYWNpw7NuIGdlbmVyYWwgZGUgw6FyZWEgdnMgcHJlY2lvOioqIGNvbW8gc29uIG3DoXMgZGUgMjUgbWlsIG9ic2VydmFjaW9uZXMgZXMgcHJlZmVyaWJsZSB1dGlsaXphciBgZ2VvbV9iaW4yZCgpYCBlbiBsdWdhciBkZSBgZ2VvbV9wb2ludCgpYC4NCg0KYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTUsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQp0cmFpbiAlPiUgDQogIGdncGxvdChhZXMoeCA9IHN1cmZhY2VfdG90YWwsIHkgPSBwcmljZSkpICsNCiAgZ2VvbV9iaW4yZChjb2xvciA9ICJ3aGl0ZSIsIGFscGhhID0gMC44KSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdyA9ICJ3aGl0ZSIsIG1pZCA9ICIjMUM4MzU2IiwgaGlnaCA9ICIjQzQ0NTFDIikgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBjb2xvciA9ICIjQzQ0NTFDIiwgc2l6ZSA9IDIsIHNlID0gRkFMU0UpICsNCiAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiLCBsZWdlbmQuZGlyZWN0aW9uID0gInZlcnRpY2FsIikNCiAgDQpgYGANCg0KIyBHTE1ORVQgey50YWJzZXQgLnRhYnNldC1mYWRlIC50YWJzZXQtcGlsbHN9DQoNCiMjIFRyYWluIC0gVGVzdA0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeW1vZGVscykNCnNldC5zZWVkKDEyMykNCmRhdG9zVHJhaW4gPC0gdHJhaW4gJT4lIA0KICBzZWxlY3QoLWMoSWQsIHByb3BlcnR5X3R5cGUsIG9wZXJhdGlvbl90eXBlLCBjdXJyZW5jeSkpICU+JSANCiAgbXV0YXRlKHJvb21zID0gZmFjdG9yKHJvb21zKSwNCiAgICAgICAgIGJlZHJvb21zID0gZmFjdG9yKGJlZHJvb21zKSwNCiAgICAgICAgIGJhdGhyb29tcyA9IGZhY3RvcihiYXRocm9vbXMpKQ0Kc3BsaXRfaW5pY2lhbCA8LSBpbml0aWFsX3NwbGl0KA0KICAgICAgICAgICAgICAgICAgICBkYXRhICAgPSBkYXRvc1RyYWluLA0KICAgICAgICAgICAgICAgICAgICBwcm9wICAgPSAwLjgsDQogICAgICAgICAgICAgICAgICAgIHN0cmF0YSA9IHByaWNlDQogICAgICAgICAgICAgICAgICkNCmRhdG9zX3RyYWluIDwtIHRyYWluaW5nKHNwbGl0X2luaWNpYWwpDQpkYXRvc190ZXN0ICA8LSB0ZXN0aW5nKHNwbGl0X2luaWNpYWwpDQpgYGANCg0KIyMgTW9kZWxvIEdMTSAtIFR1bmluZw0KDQpgYGB7cn0NCiMgTW9kZWxvDQptb2RfZ2xtIDwtIGxpbmVhcl9yZWcobW9kZSAgICA9ICJyZWdyZXNzaW9uIiwNCiAgICAgICAgICAgICAgICAgICAgICBwZW5hbHR5ID0gdHVuZSgpLA0KICAgICAgICAgICAgICAgICAgICAgIG1peHR1cmUgPSB0dW5lKCkpICU+JQ0KICBzZXRfZW5naW5lKGVuZ2luZSA9ICJnbG1uZXQiKQ0KDQojIFByZXByb2Nlc2FtaWVudG8NCnJlY2V0YSA8LSByZWNpcGUoZm9ybXVsYSA9IHByaWNlIH4gLiwNCiAgICAgICAgICAgICAgICAgZGF0YSA9ICBkYXRvc190cmFpbikgJT4lDQogIHN0ZXBfY2VudGVyKGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lDQogIHN0ZXBfc2NhbGUoYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUNCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpLCAtYWxsX291dGNvbWVzKCkpDQoNCiMgVmFsaWRhY2nDs24gZGVsIG1vZGVsbzogdmFsaWRhY2nDs24gY3J1emFkYSBLLWZvbGRzIGNvbiBrID0gMTANCnNldC5zZWVkKDE5OTIpDQpjcm9zc1ZhbCA8LSB2Zm9sZF9jdihkYXRhID0gZGF0b3NfdHJhaW4sDQogICAgICAgICAgICAgICAgICAgICB2ID0gMTAsDQogICAgICAgICAgICAgICAgICAgICBzdHJhdGEgPSBwcmljZSkNCg0KIyBXT1JLRkxPVw0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KZmx1am9fbW9kZWxvIDwtIHdvcmtmbG93KCkgJT4lDQogIGFkZF9yZWNpcGUocmVjZXRhKSAlPiUNCiAgYWRkX21vZGVsKG1vZF9nbG0pDQoNCiMgR3JpZCBkZSBoaXBlcnBhcsOhbWV0cm9zDQpoaXBlcnBhcl9ncmlkIDwtIGdyaWRfcmVndWxhcigNCiAgcGVuYWx0eShyYW5nZSA9IGMoMCwgMSksIHRyYW5zID0gTlVMTCksDQogIG1peHR1cmUocmFuZ2UgPSBjKDAsIDEpLCB0cmFucyA9IE5VTEwpLA0KICBsZXZlbHMgPSBjKDEwLCAxMCkpDQoNCiMgRUpFQ1VDScOTTiBERSBMQSBPUFRJTUlaQUNJw5NOIERFIEhJUEVSUEFSw4FNRVRST1MNCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCnJlZ2lzdGVyRG9QYXJhbGxlbChjb3JlcyA9IHBhcmFsbGVsOjpkZXRlY3RDb3JlcygpIC0gMSkNCm15R3JpZCA8LSB0dW5lX2dyaWQoDQogIG9iamVjdCA9IGZsdWpvX21vZGVsbywNCiAgcmVzYW1wbGVzID0gY3Jvc3NWYWwsDQogIG1ldHJpY3MgPSBtZXRyaWNfc2V0KHJtc2UpLA0KICBjb250cm9sID0gY29udHJvbF9yZXNhbXBsZXMoc2F2ZV9wcmVkID0gVFJVRSksDQogIGdyaWQgPSBoaXBlcnBhcl9ncmlkDQopDQpzdG9wSW1wbGljaXRDbHVzdGVyKCkNCmBgYA0KDQotICoqTWVqb3JlcyAxMCBtb2RlbG9zOioqDQoNCmBgYHtyfQ0Kc2hvd19iZXN0KG15R3JpZCwgbWV0cmljID0gInJtc2UiLCBuID0gMTApDQpgYGANCg0KIyMgTW9kZWxvIEdMTSBGaW5hbA0KDQpgYGB7cn0NCm1lam9yR3JpZCA8LSBzZWxlY3RfYmVzdChteUdyaWQsIG1ldHJpYyA9ICJybXNlIikNCg0KZmx1am9fZmluYWwgPC0gZmluYWxpemVfd29ya2Zsb3coeCA9IGZsdWpvX21vZGVsbywgcGFyYW1ldGVycyA9IG1lam9yR3JpZCkNCg0KDQpnbG1fZmluYWwgPC0gIGZsdWpvX2ZpbmFsICU+JQ0KICBmaXQoZGF0YSA9IHRyYWluKQ0KYGBgDQoNCiMjIFByZWRpY2hvcyBHTE0NCg0KYGBge3J9DQpwcmVkaWNjaW9uZXMgPC0gZ2xtX2ZpbmFsICU+JQ0KICBwcmVkaWN0KG5ld19kYXRhID0gZGF0b3NfdGVzdCwNCiAgICAgICAgICB0eXBlID0gIm51bWVyaWMiKQ0KcHJlZGljY2lvbmVzW2lzLm5hKHByZWRpY2Npb25lcyldIDwtIDANCmBgYA0KDQotICoqRXJyb3IgZGUgdGVzdDoqKg0KDQpgYGB7cn0NCnByZWRpY2Npb25lcyA8LSBwcmVkaWNjaW9uZXMgJT4lIA0KICAgICAgICAgICAgICAgIGJpbmRfY29scyhkYXRvc190ZXN0ICU+JSBzZWxlY3QocHJpY2UpKQ0KDQplcnJvcl90ZXN0X2dsbSAgPC0gcm1zZSgNCiAgZGF0YSA9IHByZWRpY2Npb25lcywNCiAgdHJ1dGggPSBwcmljZSwNCiAgZXN0aW1hdGUgPSAucHJlZCwNCiAgbmFfcm0gPSBUUlVFDQopICU+JQ0KICBtdXRhdGUobW9kZWxvID0gIkdMTSIpDQplcnJvcl90ZXN0X2dsbQ0KYGBgDQoNCiMjIFByZWRpY2hvcyAtIE51ZXZvcw0KDQpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCnByZWRpY2Npb25lc0dMTV9TdWJtMSA8LSBnbG1fZmluYWwgJT4lDQogIHByZWRpY3QobmV3X2RhdGEgPSB0ZXN0LA0KICAgICAgICAgIHR5cGUgPSAibnVtZXJpYyIpDQpwcmVkaWNjaW9uZXNHTE1fU3VibTFbaXMubmEocHJlZGljY2lvbmVzR0xNX1N1Ym0xKV0gPC0gMA0KcHJlZGljY2lvbmVzR0xNX1N1Ym0xW3ByZWRpY2Npb25lc0dMTV9TdWJtMSA8IDAgXSA8LSAwDQpoaXN0KHByZWRpY2Npb25lc0dMTV9TdWJtMSQucHJlZCkNCmBgYA0KDQotICoqU3VibWlzc2lvbiAxOioqDQoNCmBgYHtyfQ0Kc3VibTFfZ2xtbmV0IDwtIGRhdGEuZnJhbWUoSWQgPSBzYW1wbGVTdWIkSWQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBwcmljZSA9IHByZWRpY2Npb25lc0dMTV9TdWJtMSQucHJlZCkNCndyaXRlLmNzdihzdWJtMV9nbG1uZXQsIGZpbGUgPSAiU3VibTEuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpDQpgYGANCg0KLSAqKlNjb3JlOioqIDIuNzI0MTY4ODUxOTA5NTcgLSBQb3NpY2nDs24gMzIuDQo=