Note: Some results may differ from the hard copy book due to the changing of sampling procedures introduced in R 3.6.0. See http://bit.ly/35D1SW7 for more details. Access and run the source code for this notebook here.

Hidden chapter requirements used in the book to set the plotting theme and load packages used in hidden code chunks:

# Set global R options
options(scipen = 999)

# Set the graphical theme
ggplot2::theme_set(ggplot2::theme_light())

# Set global knitr chunk options
knitr::opts_chunk$set(
  warning = FALSE, 
  message = FALSE
)

# Load required packages
library(ggplot2)
library(rsample)

Prerequisites

In this chapter we’ll make use of the following packages:

# Helper packages
library(dplyr)       # for data wrangling
library(ggplot2)     # for awesome plotting
library(doParallel)  # for parallel backend to foreach
library(foreach)     # for parallel processing with for loops

# Modeling packages
library(caret)       # for general model fitting
library(rpart)       # for fitting decision trees
library(ipred)       # for fitting bagged decision trees

We’ll continue to illustrate the main concepts with the ames_train data:

# create Ames training data
set.seed(123)
ames <- AmesHousing::make_ames()
split  <- initial_split(ames, prop = 0.7, strata = "Sale_Price")
ames_train  <- training(split)
ames_test  <- testing(split)

Why and when bagging works

Figure 10.1:

# Simulate some nonlinear monotonic data
set.seed(123)  # for reproducibility
x <- seq(from = 0, to = 2 * pi, length = 500)
y <- sin(x) + rnorm(length(x), sd = 0.3)
df <- data.frame(x, y) %>%
  filter(x < 4.5)

# bootstrapped polynomial model fit
bootstrap_n <- 100
bootstrap_results <- NULL
for(i in seq_len(bootstrap_n)) {
  # reproducible sampled data frames
  set.seed(i)
  index <- sample(seq_len(nrow(df)), nrow(df), replace = TRUE)
  df_sim <- df[index, ]
  
  # fit model and add predictions to results data frame
  fit <- lm(y ~ I(x^3), data = df_sim)
  df_sim$predictions <- predict(fit, df_sim)
  df_sim$model <- paste0("model", i)
  df_sim$ob <- index
  bootstrap_results <- rbind(bootstrap_results, df_sim)
}

p1 <- ggplot(bootstrap_results, aes(x, predictions)) +
  geom_point(data = df, aes(x, y), alpha = .25) +
  geom_line(aes(group = model), show.legend = FALSE, size = .5, alpha = .2) +
  stat_summary(fun.y = "mean", colour = "red", size = 1, geom = "line") +
  scale_y_continuous("Response", limits = c(-2, 2), expand = c(0, 0)) +
  scale_x_continuous(limits = c(0, 5), expand = c(0, 0)) +
  ggtitle("A) Polynomial regression")

# bootstrapped MARS model fit
bootstrap_n <- 100
bootstrap_results <- NULL
for(i in seq_len(bootstrap_n)) {
  # reproducible sampled data frames
  set.seed(i)
  index <- sample(seq_len(nrow(df)), nrow(df), replace = TRUE)
  df_sim <- df[index, ]
  
  # fit model and add predictions to results data frame
  fit <- earth::earth(y ~ x, data = df_sim)
  df_sim$predictions <- predict(fit, df_sim)
  df_sim$model <- paste0("model", i)
  df_sim$ob <- index
  bootstrap_results <- rbind(bootstrap_results, df_sim)
}

p2 <- ggplot(bootstrap_results, aes(x, predictions)) +
  geom_point(data = df, aes(x, y), alpha = .25) +
  geom_line(aes(group = model), show.legend = FALSE, size = .5, alpha = .2) +
  stat_summary(fun.y = "mean", colour = "red", size = 1, geom = "line") +
  scale_y_continuous(NULL, limits = c(-2, 2), expand = c(0, 0)) +
  scale_x_continuous(limits = c(0, 5), expand = c(0, 0)) +
  ggtitle("B) MARS")

# bootstrapped decision trees fit
bootstrap_n <- 100
bootstrap_results <- NULL
for(i in seq_len(bootstrap_n)) {
  # reproducible sampled data frames
  set.seed(i)
  index <- sample(seq_len(nrow(df)), nrow(df), replace = TRUE)
  df_sim <- df[index, ]
  
  # fit model and add predictions to results data frame
  fit <- rpart::rpart(y ~ x, data = df_sim)
  df_sim$predictions <- predict(fit, df_sim)
  df_sim$model <- paste0("model", i)
  df_sim$ob <- index
  bootstrap_results <- rbind(bootstrap_results, df_sim)
}

p3 <- ggplot(bootstrap_results, aes(x, predictions)) +
  geom_point(data = df, aes(x, y), alpha = .25) +
  geom_line(aes(group = model), show.legend = FALSE, size = .5, alpha = .2) +
  stat_summary(fun.y = "mean", colour = "red", size = 1, geom = "line") +
  scale_y_continuous(NULL, limits = c(-2, 2), expand = c(0, 0)) +
  scale_x_continuous(limits = c(0, 5), expand = c(0, 0)) +
  ggtitle("C) Decision trees")

gridExtra::grid.arrange(p1, p2, p3, nrow = 1)

Implementation

# make bootstrapping reproducible
set.seed(123)

# train bagged model
ames_bag1 <- bagging(
  formula = Sale_Price ~ .,
  data = ames_train,
  nbagg = 100,  
  coob = TRUE,
  control = rpart.control(minsplit = 2, cp = 0)
)

ames_bag1

Bagging regression trees with 100 bootstrap replications 

Call: bagging.data.frame(formula = Sale_Price ~ ., data = ames_train, 
    nbagg = 100, coob = TRUE, control = rpart.control(minsplit = 2, 
        cp = 0))

Out-of-bag estimate of root mean squared error:  27767.13 

Figure 10.2:

# # assess 10-200 bagged trees
# ntree <- seq(10, 200, by = 2)
# 
# # create empty vector to store OOB RMSE values
# rmse <- vector(mode = "numeric", length = length(ntree))
# 
# for (i in seq_along(ntree)) {
#   # reproducibility
#   set.seed(123)
#   # perform bagged model
#   model <- bagging(
#   formula = Sale_Price ~ .,
#   data    = ames_train,
#   coob    = TRUE,
#   control = rpart.control(minsplit = 2, cp = 0),
#   nbagg   = ntree[i]
# )
#   # get OOB error
#   rmse[i] <- model$err
# }
# 
# bagging_errors <- data.frame(ntree, rmse)

# using ranger to do the same as above.  Will allow for bagging under 10 trees
# and is much faster!
ntree <- seq(1, 200, by = 2)
# create empty vector to store OOB RMSE values
rmse <- vector(mode = "numeric", length = length(ntree))

for (i in seq_along(ntree)) {
  # reproducibility
  set.seed(123)
  # perform bagged model
  model <- ranger::ranger(
  formula = Sale_Price ~ .,
  data    = ames_train,
  num.trees = ntree[i],
  mtry = ncol(ames_train) - 1,
  min.node.size = 1
)
  # get OOB error
  rmse[i] <- sqrt(model$prediction.error)
}

bagging_errors <- data.frame(ntree, rmse)

ggplot(bagging_errors, aes(ntree, rmse)) +
  geom_line() +
  geom_hline(yintercept = 41019, lty = "dashed", color = "grey50") +
  annotate("text", x = 100, y = 41385, label = "Best individual pruned tree", vjust = 0, hjust = 0, color = "grey50") +
  annotate("text", x = 100, y = 26750, label = "Bagged trees", vjust = 0, hjust = 0) +
  ylab("RMSE") +
  xlab("Number of trees")

Do to output size, the code chunks that follow should not be ran on RStudio Cloud

ames_bag2 <- train(
  Sale_Price ~ .,
  data = ames_train,
  method = "treebag",
  trControl = trainControl(method = "cv", number = 10),
  nbagg = 200,  
  control = rpart.control(minsplit = 2, cp = 0)
)
ames_bag2
Bagged CART 

2053 samples
  80 predictor

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 1847, 1849, 1847, 1847, 1848, 1847, ... 
Resampling results:

  RMSE      Rsquared   MAE     
  27460.99  0.8862784  16755.65

Easily parallelize

# Create a parallel socket cluster
cl <- makeCluster(8) # use 8 workers
registerDoParallel(cl) # register the parallel backend

# Fit trees in parallel and compute predictions on the test set
predictions <- foreach(
  icount(160), 
  .packages = "rpart", 
  .combine = cbind
  ) %dopar% {
    # bootstrap copy of training data
    index <- sample(nrow(ames_train), replace = TRUE)
    ames_train_boot <- ames_train[index, ]  
  
    # fit tree to bootstrap copy
    bagged_tree <- rpart(
      Sale_Price ~ ., 
      control = rpart.control(minsplit = 2, cp = 0),
      data = ames_train_boot
      ) 
    
    predict(bagged_tree, newdata = ames_test)
}

predictions[1:5, 1:7]
  result.1 result.2 result.3 result.4 result.5 result.6 result.7
1   220000   284700   176500   235000   190000   243000   244000
2   191000   173000   140500   195500   189500   146900   188000
3   189000   294000   170000   200000   180000   130000   211500
4   173000   144000   158000   173000   180400   173000   173000
5   189000   270000   254000   209500   279700   190000   201000
predictions %>%
  as.data.frame() %>%
  mutate(
    observation = 1:n(),
    actual = ames_test$Sale_Price) %>%
  tidyr::gather(tree, predicted, -c(observation, actual)) %>%
  group_by(observation) %>%
  mutate(tree = stringr::str_extract(tree, '\\d+') %>% as.numeric()) %>%
  ungroup() %>%
  arrange(observation, tree) %>%
  group_by(observation) %>%
  mutate(avg_prediction = cummean(predicted)) %>%
  group_by(tree) %>%
  summarize(RMSE = RMSE(avg_prediction, actual)) %>%
  ggplot(aes(tree, RMSE)) +
  geom_line() +
  xlab('Number of trees')

# Shutdown parallel cluster
stopCluster(cl)

Feature interpretation

vip::vip(ames_bag2, num_features = 40, bar = FALSE)

# Construct partial dependence plots
p1 <- pdp::partial(
  ames_bag2, 
  pred.var = "Lot_Area",
  grid.resolution = 20
  ) %>% 
  autoplot()

p2 <- pdp::partial(
  ames_bag2, 
  pred.var = "Lot_Frontage", 
  grid.resolution = 20
  ) %>% 
  autoplot()

gridExtra::grid.arrange(p1, p2, nrow = 1)

Final thoughts

Figure 10.6:

library(caret)
library(randomForest)
iter = 6
par(mfrow = c(3, 3))
for(i in 1:iter){
  set.seed(i+30)
  # create train/test sets
  train_index <- caret::createDataPartition(pdp::boston$cmedv, p = .6333,
                                     list = FALSE,
                                     times = 1)
  
  train_DF <- pdp::boston[train_index,]
  validate_DF <- pdp::boston[-train_index,]
  
  train_y <- train_DF$cmedv
  train_x <- train_DF[, setdiff(names(train_DF), "cmedv")]
  
  validate_y <- validate_DF$cmedv
  validate_x <- validate_DF[, setdiff(names(validate_DF), "cmedv")]
  
  d_tree <- rpart::rpart(cmedv ~ ., train_DF)
  
  # graphs
  
  rpart.plot::rpart.plot(d_tree, main = paste0("Decision Tree ", i), type = 0, extra = 0) 
  
}

LS0tCnRpdGxlOiAiQ2hhcHRlciAxMDogQmFnZ2luZyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKX19Ob3RlX186IFNvbWUgcmVzdWx0cyBtYXkgZGlmZmVyIGZyb20gdGhlIGhhcmQgY29weSBib29rIGR1ZSB0byB0aGUgY2hhbmdpbmcgb2Ygc2FtcGxpbmcgcHJvY2VkdXJlcyBpbnRyb2R1Y2VkIGluIFIgMy42LjAuIFNlZSBodHRwOi8vYml0Lmx5LzM1RDFTVzcgZm9yIG1vcmUgZGV0YWlscy4gQWNjZXNzIGFuZCBydW4gdGhlIHNvdXJjZSBjb2RlIGZvciB0aGlzIG5vdGVib29rIFtoZXJlXShodHRwczovL3JzdHVkaW8uY2xvdWQvcHJvamVjdC84MDExODUpLiAKCkhpZGRlbiBjaGFwdGVyIHJlcXVpcmVtZW50cyB1c2VkIGluIHRoZSBib29rIHRvIHNldCB0aGUgcGxvdHRpbmcgdGhlbWUgYW5kIGxvYWQgcGFja2FnZXMgdXNlZCBpbiBoaWRkZW4gY29kZSBjaHVua3M6CgpgYGB7ciBzZXR1cH0KIyBTZXQgZ2xvYmFsIFIgb3B0aW9ucwpvcHRpb25zKHNjaXBlbiA9IDk5OSkKCiMgU2V0IHRoZSBncmFwaGljYWwgdGhlbWUKZ2dwbG90Mjo6dGhlbWVfc2V0KGdncGxvdDI6OnRoZW1lX2xpZ2h0KCkpCgojIFNldCBnbG9iYWwga25pdHIgY2h1bmsgb3B0aW9ucwprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgd2FybmluZyA9IEZBTFNFLCAKICBtZXNzYWdlID0gRkFMU0UKKQoKIyBMb2FkIHJlcXVpcmVkIHBhY2thZ2VzCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShyc2FtcGxlKQpgYGAKCiMjIFByZXJlcXVpc2l0ZXMKCkluIHRoaXMgY2hhcHRlciB3ZSdsbCBtYWtlIHVzZSBvZiB0aGUgZm9sbG93aW5nIHBhY2thZ2VzOgoKYGBge3J9CiMgSGVscGVyIHBhY2thZ2VzCmxpYnJhcnkoZHBseXIpICAgICAgICMgZm9yIGRhdGEgd3JhbmdsaW5nCmxpYnJhcnkoZ2dwbG90MikgICAgICMgZm9yIGF3ZXNvbWUgcGxvdHRpbmcKbGlicmFyeShkb1BhcmFsbGVsKSAgIyBmb3IgcGFyYWxsZWwgYmFja2VuZCB0byBmb3JlYWNoCmxpYnJhcnkoZm9yZWFjaCkgICAgICMgZm9yIHBhcmFsbGVsIHByb2Nlc3Npbmcgd2l0aCBmb3IgbG9vcHMKCiMgTW9kZWxpbmcgcGFja2FnZXMKbGlicmFyeShjYXJldCkgICAgICAgIyBmb3IgZ2VuZXJhbCBtb2RlbCBmaXR0aW5nCmxpYnJhcnkocnBhcnQpICAgICAgICMgZm9yIGZpdHRpbmcgZGVjaXNpb24gdHJlZXMKbGlicmFyeShpcHJlZCkgICAgICAgIyBmb3IgZml0dGluZyBiYWdnZWQgZGVjaXNpb24gdHJlZXMKYGBgCgpXZSdsbCBjb250aW51ZSB0byBpbGx1c3RyYXRlIHRoZSBtYWluIGNvbmNlcHRzIHdpdGggdGhlIGBhbWVzX3RyYWluYCBkYXRhOgoKYGBge3IgMDgtYW1lcy10cmFpbiwgZWNobz1UUlVFfQojIGNyZWF0ZSBBbWVzIHRyYWluaW5nIGRhdGEKc2V0LnNlZWQoMTIzKQphbWVzIDwtIEFtZXNIb3VzaW5nOjptYWtlX2FtZXMoKQpzcGxpdCAgPC0gaW5pdGlhbF9zcGxpdChhbWVzLCBwcm9wID0gMC43LCBzdHJhdGEgPSAiU2FsZV9QcmljZSIpCmFtZXNfdHJhaW4gIDwtIHRyYWluaW5nKHNwbGl0KQphbWVzX3Rlc3QgIDwtIHRlc3Rpbmcoc3BsaXQpCmBgYAoKIyMgV2h5IGFuZCB3aGVuIGJhZ2dpbmcgd29ya3MKCkZpZ3VyZSAxMC4xOgoKYGBge3IgYmFnZ2luZy1tdWx0aXBsZS1tb2RlbHMsIGVjaG89VFJVRSwgZmlnLndpZHRoPTEyLCBmaWcuY2FwPSJUaGUgZWZmZWN0IG9mIGJhZ2dpbmcgMTAwIGJhc2UgbGVhcm5lcnMuIEhpZ2ggdmFyaWFuY2UgbW9kZWxzIHN1Y2ggYXMgZGVjaXNpb24gdHJlZXMgKEMpIGJlbmVmaXQgdGhlIG1vc3QgZnJvbSB0aGUgYWdncmVnYXRpb24gZWZmZWN0IGluIGJhZ2dpbmcsIHdoZXJlYXMgbG93IHZhcmlhbmNlIG1vZGVscyBzdWNoIGFzIHBvbHlub21pYWwgcmVncmVzc2lvbiAoQSkgc2hvdyBsaXR0bGUgaW1wcm92ZW1lbnQuICJ9CiMgU2ltdWxhdGUgc29tZSBub25saW5lYXIgbW9ub3RvbmljIGRhdGEKc2V0LnNlZWQoMTIzKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CnggPC0gc2VxKGZyb20gPSAwLCB0byA9IDIgKiBwaSwgbGVuZ3RoID0gNTAwKQp5IDwtIHNpbih4KSArIHJub3JtKGxlbmd0aCh4KSwgc2QgPSAwLjMpCmRmIDwtIGRhdGEuZnJhbWUoeCwgeSkgJT4lCiAgZmlsdGVyKHggPCA0LjUpCgojIGJvb3RzdHJhcHBlZCBwb2x5bm9taWFsIG1vZGVsIGZpdApib290c3RyYXBfbiA8LSAxMDAKYm9vdHN0cmFwX3Jlc3VsdHMgPC0gTlVMTApmb3IoaSBpbiBzZXFfbGVuKGJvb3RzdHJhcF9uKSkgewogICMgcmVwcm9kdWNpYmxlIHNhbXBsZWQgZGF0YSBmcmFtZXMKICBzZXQuc2VlZChpKQogIGluZGV4IDwtIHNhbXBsZShzZXFfbGVuKG5yb3coZGYpKSwgbnJvdyhkZiksIHJlcGxhY2UgPSBUUlVFKQogIGRmX3NpbSA8LSBkZltpbmRleCwgXQogIAogICMgZml0IG1vZGVsIGFuZCBhZGQgcHJlZGljdGlvbnMgdG8gcmVzdWx0cyBkYXRhIGZyYW1lCiAgZml0IDwtIGxtKHkgfiBJKHheMyksIGRhdGEgPSBkZl9zaW0pCiAgZGZfc2ltJHByZWRpY3Rpb25zIDwtIHByZWRpY3QoZml0LCBkZl9zaW0pCiAgZGZfc2ltJG1vZGVsIDwtIHBhc3RlMCgibW9kZWwiLCBpKQogIGRmX3NpbSRvYiA8LSBpbmRleAogIGJvb3RzdHJhcF9yZXN1bHRzIDwtIHJiaW5kKGJvb3RzdHJhcF9yZXN1bHRzLCBkZl9zaW0pCn0KCnAxIDwtIGdncGxvdChib290c3RyYXBfcmVzdWx0cywgYWVzKHgsIHByZWRpY3Rpb25zKSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGRmLCBhZXMoeCwgeSksIGFscGhhID0gLjI1KSArCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IG1vZGVsKSwgc2hvdy5sZWdlbmQgPSBGQUxTRSwgc2l6ZSA9IC41LCBhbHBoYSA9IC4yKSArCiAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gIm1lYW4iLCBjb2xvdXIgPSAicmVkIiwgc2l6ZSA9IDEsIGdlb20gPSAibGluZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoIlJlc3BvbnNlIiwgbGltaXRzID0gYygtMiwgMiksIGV4cGFuZCA9IGMoMCwgMCkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCA1KSwgZXhwYW5kID0gYygwLCAwKSkgKwogIGdndGl0bGUoIkEpIFBvbHlub21pYWwgcmVncmVzc2lvbiIpCgojIGJvb3RzdHJhcHBlZCBNQVJTIG1vZGVsIGZpdApib290c3RyYXBfbiA8LSAxMDAKYm9vdHN0cmFwX3Jlc3VsdHMgPC0gTlVMTApmb3IoaSBpbiBzZXFfbGVuKGJvb3RzdHJhcF9uKSkgewogICMgcmVwcm9kdWNpYmxlIHNhbXBsZWQgZGF0YSBmcmFtZXMKICBzZXQuc2VlZChpKQogIGluZGV4IDwtIHNhbXBsZShzZXFfbGVuKG5yb3coZGYpKSwgbnJvdyhkZiksIHJlcGxhY2UgPSBUUlVFKQogIGRmX3NpbSA8LSBkZltpbmRleCwgXQogIAogICMgZml0IG1vZGVsIGFuZCBhZGQgcHJlZGljdGlvbnMgdG8gcmVzdWx0cyBkYXRhIGZyYW1lCiAgZml0IDwtIGVhcnRoOjplYXJ0aCh5IH4geCwgZGF0YSA9IGRmX3NpbSkKICBkZl9zaW0kcHJlZGljdGlvbnMgPC0gcHJlZGljdChmaXQsIGRmX3NpbSkKICBkZl9zaW0kbW9kZWwgPC0gcGFzdGUwKCJtb2RlbCIsIGkpCiAgZGZfc2ltJG9iIDwtIGluZGV4CiAgYm9vdHN0cmFwX3Jlc3VsdHMgPC0gcmJpbmQoYm9vdHN0cmFwX3Jlc3VsdHMsIGRmX3NpbSkKfQoKcDIgPC0gZ2dwbG90KGJvb3RzdHJhcF9yZXN1bHRzLCBhZXMoeCwgcHJlZGljdGlvbnMpKSArCiAgZ2VvbV9wb2ludChkYXRhID0gZGYsIGFlcyh4LCB5KSwgYWxwaGEgPSAuMjUpICsKICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gbW9kZWwpLCBzaG93LmxlZ2VuZCA9IEZBTFNFLCBzaXplID0gLjUsIGFscGhhID0gLjIpICsKICBzdGF0X3N1bW1hcnkoZnVuLnkgPSAibWVhbiIsIGNvbG91ciA9ICJyZWQiLCBzaXplID0gMSwgZ2VvbSA9ICJsaW5lIikgKwogIHNjYWxlX3lfY29udGludW91cyhOVUxMLCBsaW1pdHMgPSBjKC0yLCAyKSwgZXhwYW5kID0gYygwLCAwKSkgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDUpLCBleHBhbmQgPSBjKDAsIDApKSArCiAgZ2d0aXRsZSgiQikgTUFSUyIpCgojIGJvb3RzdHJhcHBlZCBkZWNpc2lvbiB0cmVlcyBmaXQKYm9vdHN0cmFwX24gPC0gMTAwCmJvb3RzdHJhcF9yZXN1bHRzIDwtIE5VTEwKZm9yKGkgaW4gc2VxX2xlbihib290c3RyYXBfbikpIHsKICAjIHJlcHJvZHVjaWJsZSBzYW1wbGVkIGRhdGEgZnJhbWVzCiAgc2V0LnNlZWQoaSkKICBpbmRleCA8LSBzYW1wbGUoc2VxX2xlbihucm93KGRmKSksIG5yb3coZGYpLCByZXBsYWNlID0gVFJVRSkKICBkZl9zaW0gPC0gZGZbaW5kZXgsIF0KICAKICAjIGZpdCBtb2RlbCBhbmQgYWRkIHByZWRpY3Rpb25zIHRvIHJlc3VsdHMgZGF0YSBmcmFtZQogIGZpdCA8LSBycGFydDo6cnBhcnQoeSB+IHgsIGRhdGEgPSBkZl9zaW0pCiAgZGZfc2ltJHByZWRpY3Rpb25zIDwtIHByZWRpY3QoZml0LCBkZl9zaW0pCiAgZGZfc2ltJG1vZGVsIDwtIHBhc3RlMCgibW9kZWwiLCBpKQogIGRmX3NpbSRvYiA8LSBpbmRleAogIGJvb3RzdHJhcF9yZXN1bHRzIDwtIHJiaW5kKGJvb3RzdHJhcF9yZXN1bHRzLCBkZl9zaW0pCn0KCnAzIDwtIGdncGxvdChib290c3RyYXBfcmVzdWx0cywgYWVzKHgsIHByZWRpY3Rpb25zKSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGRmLCBhZXMoeCwgeSksIGFscGhhID0gLjI1KSArCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IG1vZGVsKSwgc2hvdy5sZWdlbmQgPSBGQUxTRSwgc2l6ZSA9IC41LCBhbHBoYSA9IC4yKSArCiAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gIm1lYW4iLCBjb2xvdXIgPSAicmVkIiwgc2l6ZSA9IDEsIGdlb20gPSAibGluZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoTlVMTCwgbGltaXRzID0gYygtMiwgMiksIGV4cGFuZCA9IGMoMCwgMCkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCA1KSwgZXhwYW5kID0gYygwLCAwKSkgKwogIGdndGl0bGUoIkMpIERlY2lzaW9uIHRyZWVzIikKCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHAxLCBwMiwgcDMsIG5yb3cgPSAxKQpgYGAKCiMjIEltcGxlbWVudGF0aW9uCgpgYGB7ciBmaXJzdC1iYWdnZWQtYW1lcy1tb2RlbH0KIyBtYWtlIGJvb3RzdHJhcHBpbmcgcmVwcm9kdWNpYmxlCnNldC5zZWVkKDEyMykKCiMgdHJhaW4gYmFnZ2VkIG1vZGVsCmFtZXNfYmFnMSA8LSBiYWdnaW5nKAogIGZvcm11bGEgPSBTYWxlX1ByaWNlIH4gLiwKICBkYXRhID0gYW1lc190cmFpbiwKICBuYmFnZyA9IDEwMCwgIAogIGNvb2IgPSBUUlVFLAogIGNvbnRyb2wgPSBycGFydC5jb250cm9sKG1pbnNwbGl0ID0gMiwgY3AgPSAwKQopCgphbWVzX2JhZzEKYGBgCgpGaWd1cmUgMTAuMjoKCmBgYHtyIG4tYmFncy1wbG90LCBlY2hvPVRSVUUsIGZpZy5jYXA9IkVycm9yIGN1cnZlIGZvciBiYWdnaW5nIDEtMjAwIGRlZXAsIHVucHJ1bmVkIGRlY2lzaW9uIHRyZWVzLiBUaGUgYmVuZWZpdCBvZiBiYWdnaW5nIGlzIG9wdGltaXplZCBhdCAxODcgdHJlZXMgYWx0aG91Z2ggdGhlIG1ham9yaXR5IG9mIGVycm9yIHJlZHVjdGlvbiBvY2N1cnJlZCB3aXRoaW4gdGhlIGZpcnN0IDEwMCB0cmVlcy4ifQojICMgYXNzZXNzIDEwLTIwMCBiYWdnZWQgdHJlZXMKIyBudHJlZSA8LSBzZXEoMTAsIDIwMCwgYnkgPSAyKQojIAojICMgY3JlYXRlIGVtcHR5IHZlY3RvciB0byBzdG9yZSBPT0IgUk1TRSB2YWx1ZXMKIyBybXNlIDwtIHZlY3Rvcihtb2RlID0gIm51bWVyaWMiLCBsZW5ndGggPSBsZW5ndGgobnRyZWUpKQojIAojIGZvciAoaSBpbiBzZXFfYWxvbmcobnRyZWUpKSB7CiMgICAjIHJlcHJvZHVjaWJpbGl0eQojICAgc2V0LnNlZWQoMTIzKQojICAgIyBwZXJmb3JtIGJhZ2dlZCBtb2RlbAojICAgbW9kZWwgPC0gYmFnZ2luZygKIyAgIGZvcm11bGEgPSBTYWxlX1ByaWNlIH4gLiwKIyAgIGRhdGEgICAgPSBhbWVzX3RyYWluLAojICAgY29vYiAgICA9IFRSVUUsCiMgICBjb250cm9sID0gcnBhcnQuY29udHJvbChtaW5zcGxpdCA9IDIsIGNwID0gMCksCiMgICBuYmFnZyAgID0gbnRyZWVbaV0KIyApCiMgICAjIGdldCBPT0IgZXJyb3IKIyAgIHJtc2VbaV0gPC0gbW9kZWwkZXJyCiMgfQojIAojIGJhZ2dpbmdfZXJyb3JzIDwtIGRhdGEuZnJhbWUobnRyZWUsIHJtc2UpCgojIHVzaW5nIHJhbmdlciB0byBkbyB0aGUgc2FtZSBhcyBhYm92ZS4gIFdpbGwgYWxsb3cgZm9yIGJhZ2dpbmcgdW5kZXIgMTAgdHJlZXMKIyBhbmQgaXMgbXVjaCBmYXN0ZXIhCm50cmVlIDwtIHNlcSgxLCAyMDAsIGJ5ID0gMikKIyBjcmVhdGUgZW1wdHkgdmVjdG9yIHRvIHN0b3JlIE9PQiBSTVNFIHZhbHVlcwpybXNlIDwtIHZlY3Rvcihtb2RlID0gIm51bWVyaWMiLCBsZW5ndGggPSBsZW5ndGgobnRyZWUpKQoKZm9yIChpIGluIHNlcV9hbG9uZyhudHJlZSkpIHsKICAjIHJlcHJvZHVjaWJpbGl0eQogIHNldC5zZWVkKDEyMykKICAjIHBlcmZvcm0gYmFnZ2VkIG1vZGVsCiAgbW9kZWwgPC0gcmFuZ2VyOjpyYW5nZXIoCiAgZm9ybXVsYSA9IFNhbGVfUHJpY2UgfiAuLAogIGRhdGEgICAgPSBhbWVzX3RyYWluLAogIG51bS50cmVlcyA9IG50cmVlW2ldLAogIG10cnkgPSBuY29sKGFtZXNfdHJhaW4pIC0gMSwKICBtaW4ubm9kZS5zaXplID0gMQopCiAgIyBnZXQgT09CIGVycm9yCiAgcm1zZVtpXSA8LSBzcXJ0KG1vZGVsJHByZWRpY3Rpb24uZXJyb3IpCn0KCmJhZ2dpbmdfZXJyb3JzIDwtIGRhdGEuZnJhbWUobnRyZWUsIHJtc2UpCgpnZ3Bsb3QoYmFnZ2luZ19lcnJvcnMsIGFlcyhudHJlZSwgcm1zZSkpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gNDEwMTksIGx0eSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmV5NTAiKSArCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gMTAwLCB5ID0gNDEzODUsIGxhYmVsID0gIkJlc3QgaW5kaXZpZHVhbCBwcnVuZWQgdHJlZSIsIHZqdXN0ID0gMCwgaGp1c3QgPSAwLCBjb2xvciA9ICJncmV5NTAiKSArCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gMTAwLCB5ID0gMjY3NTAsIGxhYmVsID0gIkJhZ2dlZCB0cmVlcyIsIHZqdXN0ID0gMCwgaGp1c3QgPSAwKSArCiAgeWxhYigiUk1TRSIpICsKICB4bGFiKCJOdW1iZXIgb2YgdHJlZXMiKQpgYGAKCl9fX0RvIHRvIG91dHB1dCBzaXplLCB0aGUgY29kZSBjaHVua3MgdGhhdCBmb2xsb3cgc2hvdWxkIG5vdCBiZSByYW4gb24gUlN0dWRpbyBDbG91ZF9fXwoKYGBge3IgYW1lcy1iYWctd2l0aC1jYXJldH0KYW1lc19iYWcyIDwtIHRyYWluKAogIFNhbGVfUHJpY2UgfiAuLAogIGRhdGEgPSBhbWVzX3RyYWluLAogIG1ldGhvZCA9ICJ0cmVlYmFnIiwKICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTApLAogIG5iYWdnID0gMjAwLCAgCiAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2wobWluc3BsaXQgPSAyLCBjcCA9IDApCikKYW1lc19iYWcyCmBgYAoKIyMgRWFzaWx5IHBhcmFsbGVsaXplCgpgYGB7ciBiYWdnaW5nLXBhcmFsbGVsfQojIENyZWF0ZSBhIHBhcmFsbGVsIHNvY2tldCBjbHVzdGVyCmNsIDwtIG1ha2VDbHVzdGVyKDgpICMgdXNlIDggd29ya2VycwpyZWdpc3RlckRvUGFyYWxsZWwoY2wpICMgcmVnaXN0ZXIgdGhlIHBhcmFsbGVsIGJhY2tlbmQKCiMgRml0IHRyZWVzIGluIHBhcmFsbGVsIGFuZCBjb21wdXRlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IHNldApwcmVkaWN0aW9ucyA8LSBmb3JlYWNoKAogIGljb3VudCgxNjApLCAKICAucGFja2FnZXMgPSAicnBhcnQiLCAKICAuY29tYmluZSA9IGNiaW5kCiAgKSAlZG9wYXIlIHsKICAgICMgYm9vdHN0cmFwIGNvcHkgb2YgdHJhaW5pbmcgZGF0YQogICAgaW5kZXggPC0gc2FtcGxlKG5yb3coYW1lc190cmFpbiksIHJlcGxhY2UgPSBUUlVFKQogICAgYW1lc190cmFpbl9ib290IDwtIGFtZXNfdHJhaW5baW5kZXgsIF0gIAogIAogICAgIyBmaXQgdHJlZSB0byBib290c3RyYXAgY29weQogICAgYmFnZ2VkX3RyZWUgPC0gcnBhcnQoCiAgICAgIFNhbGVfUHJpY2UgfiAuLCAKICAgICAgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2wobWluc3BsaXQgPSAyLCBjcCA9IDApLAogICAgICBkYXRhID0gYW1lc190cmFpbl9ib290CiAgICAgICkgCiAgICAKICAgIHByZWRpY3QoYmFnZ2VkX3RyZWUsIG5ld2RhdGEgPSBhbWVzX3Rlc3QpCn0KCnByZWRpY3Rpb25zWzE6NSwgMTo3XQpgYGAKCmBgYHtyIHBsb3R0aW5nLXBhcmFsbGVsLWJhZywgZmlnLmNhcD0iRXJyb3IgY3VydmUgZm9yIGN1c3RvbSBwYXJhbGxlbCBiYWdnaW5nIG9mIDEtMTYwIGRlZXAsIHVucHJ1bmVkIGRlY2lzaW9uIHRyZWVzLiJ9CnByZWRpY3Rpb25zICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBtdXRhdGUoCiAgICBvYnNlcnZhdGlvbiA9IDE6bigpLAogICAgYWN0dWFsID0gYW1lc190ZXN0JFNhbGVfUHJpY2UpICU+JQogIHRpZHlyOjpnYXRoZXIodHJlZSwgcHJlZGljdGVkLCAtYyhvYnNlcnZhdGlvbiwgYWN0dWFsKSkgJT4lCiAgZ3JvdXBfYnkob2JzZXJ2YXRpb24pICU+JQogIG11dGF0ZSh0cmVlID0gc3RyaW5ncjo6c3RyX2V4dHJhY3QodHJlZSwgJ1xcZCsnKSAlPiUgYXMubnVtZXJpYygpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgYXJyYW5nZShvYnNlcnZhdGlvbiwgdHJlZSkgJT4lCiAgZ3JvdXBfYnkob2JzZXJ2YXRpb24pICU+JQogIG11dGF0ZShhdmdfcHJlZGljdGlvbiA9IGN1bW1lYW4ocHJlZGljdGVkKSkgJT4lCiAgZ3JvdXBfYnkodHJlZSkgJT4lCiAgc3VtbWFyaXplKFJNU0UgPSBSTVNFKGF2Z19wcmVkaWN0aW9uLCBhY3R1YWwpKSAlPiUKICBnZ3Bsb3QoYWVzKHRyZWUsIFJNU0UpKSArCiAgZ2VvbV9saW5lKCkgKwogIHhsYWIoJ051bWJlciBvZiB0cmVlcycpCmBgYAoKYGBge3Igc2h1dGRvd24tY2x1c3Rlcn0KIyBTaHV0ZG93biBwYXJhbGxlbCBjbHVzdGVyCnN0b3BDbHVzdGVyKGNsKQpgYGAKCiMjIEZlYXR1cmUgaW50ZXJwcmV0YXRpb24gCgpgYGB7ciBiYWctdmlwLCBmaWcuaGVpZ2h0PTUuNzUsIGZpZy5jYXA9IlZhcmlhYmxlIGltcG9ydGFuY2UgZm9yIDIwMCBiYWdnZWQgdHJlZXMgZm9yIHRoZSBBbWVzIEhvdXNpbmcgZGF0YS4ifQp2aXA6OnZpcChhbWVzX2JhZzIsIG51bV9mZWF0dXJlcyA9IDQwLCBiYXIgPSBGQUxTRSkKYGBgCgpgYGB7ciBiYWctcGRwLCBmaWcud2lkdGg9MTAsIGZpZy5jYXA9IlBhcnRpYWwgZGVwZW5kZW5jZSBwbG90cyB0byB1bmRlcnN0YW5kIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBzYWxlcyBwcmljZSBhbmQgdGhlIGxvdCBhcmVhIGFuZCBmcm9udGFnZSBzaXplIGZlYXR1cmVzLiJ9CiMgQ29uc3RydWN0IHBhcnRpYWwgZGVwZW5kZW5jZSBwbG90cwpwMSA8LSBwZHA6OnBhcnRpYWwoCiAgYW1lc19iYWcyLCAKICBwcmVkLnZhciA9ICJMb3RfQXJlYSIsCiAgZ3JpZC5yZXNvbHV0aW9uID0gMjAKICApICU+JSAKICBhdXRvcGxvdCgpCgpwMiA8LSBwZHA6OnBhcnRpYWwoCiAgYW1lc19iYWcyLCAKICBwcmVkLnZhciA9ICJMb3RfRnJvbnRhZ2UiLCAKICBncmlkLnJlc29sdXRpb24gPSAyMAogICkgJT4lIAogIGF1dG9wbG90KCkKCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHAxLCBwMiwgbnJvdyA9IDEpCmBgYAoKCiMjIEZpbmFsIHRob3VnaHRzCgpGaWd1cmUgMTAuNjoKCmBgYHtyIHRyZWUtY29ycmVsYXRpb24sIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLmNhcD0iU2l4IGRlY2lzaW9uIHRyZWVzIGJhc2VkIG9uIGRpZmZlcmVudCBib290c3RyYXAgc2FtcGxlcy4iLCBlY2hvPVRSVUUsIGRldj0ncG5nJ30KbGlicmFyeShjYXJldCkKbGlicmFyeShyYW5kb21Gb3Jlc3QpCml0ZXIgPSA2CnBhcihtZnJvdyA9IGMoMywgMykpCmZvcihpIGluIDE6aXRlcil7CiAgc2V0LnNlZWQoaSszMCkKICAjIGNyZWF0ZSB0cmFpbi90ZXN0IHNldHMKICB0cmFpbl9pbmRleCA8LSBjYXJldDo6Y3JlYXRlRGF0YVBhcnRpdGlvbihwZHA6OmJvc3RvbiRjbWVkdiwgcCA9IC42MzMzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZXMgPSAxKQogIAogIHRyYWluX0RGIDwtIHBkcDo6Ym9zdG9uW3RyYWluX2luZGV4LF0KICB2YWxpZGF0ZV9ERiA8LSBwZHA6OmJvc3RvblstdHJhaW5faW5kZXgsXQogIAogIHRyYWluX3kgPC0gdHJhaW5fREYkY21lZHYKICB0cmFpbl94IDwtIHRyYWluX0RGWywgc2V0ZGlmZihuYW1lcyh0cmFpbl9ERiksICJjbWVkdiIpXQogIAogIHZhbGlkYXRlX3kgPC0gdmFsaWRhdGVfREYkY21lZHYKICB2YWxpZGF0ZV94IDwtIHZhbGlkYXRlX0RGWywgc2V0ZGlmZihuYW1lcyh2YWxpZGF0ZV9ERiksICJjbWVkdiIpXQogIAogIGRfdHJlZSA8LSBycGFydDo6cnBhcnQoY21lZHYgfiAuLCB0cmFpbl9ERikKICAKICAjIGdyYXBocwogIAogIHJwYXJ0LnBsb3Q6OnJwYXJ0LnBsb3QoZF90cmVlLCBtYWluID0gcGFzdGUwKCJEZWNpc2lvbiBUcmVlICIsIGkpLCB0eXBlID0gMCwgZXh0cmEgPSAwKSAKICAKfQpgYGA=