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
)

library(tidyverse)
library(rpart)
library(rpart.plot)
ames <- AmesHousing::make_ames()

Prerequisites

In this chapter we’ll use the following packages:

# Helper packages
library(dplyr)       # for data wrangling
library(ggplot2)     # for awesome plotting

# Modeling packages
library(rpart)       # direct engine for decision tree application
library(caret)       # meta engine for decision tree application

# Model interpretability packages
library(rpart.plot)  # for plotting decision trees
library(vip)         # for feature importance
library(pdp)         # for feature effects

We’ll continue to illustrate the main concepts using the Ames housing data:

# Create training (70%) set for the Ames housing data.
set.seed(123)
split  <- rsample::initial_split(ames, prop = 0.7, strata = "Sale_Price")
ames_train  <- rsample::training(split)

Structure

Figure 9.1:

knitr::include_graphics("images/exemplar-decision-tree.png")

Figure 9.2:

knitr::include_graphics("images/decision-tree-terminology.png")

Partitioning

Figure 9.3:

# create data
set.seed(1112)  # for reproducibility
df <- tibble::tibble(
  x = seq(from = 0, to = 2 * pi, length = 500),
  y = sin(x) + rnorm(length(x), sd = 0.5),
  truth = sin(x)
)

# run decision stump model
ctrl <- list(cp = 0, minbucket = 5, maxdepth = 1)
fit <- rpart(y ~ x, data = df, control = ctrl)

# plot tree 
par(mar = c(1, 1, 1, 1))
rpart.plot(fit)


# plot decision boundary
df %>%
  mutate(pred = predict(fit, df)) %>%
  ggplot(aes(x, y)) +
  geom_point(alpha = .2, size = 1) +
  geom_line(aes(x, y = truth), color = "blue", size = .75) +
  geom_line(aes(y = pred), color = "red", size = .75) +
  geom_segment(x = 3.1, xend = 3.1, y = -Inf, yend = -.95,
               arrow = arrow(length = unit(0.25,"cm")), size = .25) +
  annotate("text", x = 3.1, y = -Inf, label = "split", hjust = 1.2, vjust = -1, size = 3) +
  geom_segment(x = 5.5, xend = 6, y = 2, yend = 2, size = .75, color = "blue") +
  geom_segment(x = 5.5, xend = 6, y = 1.7, yend = 1.7, size = .75, color = "red") +
  annotate("text", x = 5.3, y = 2, label = "truth", hjust = 1, size = 3, color = "blue") +
  annotate("text", x = 5.3, y = 1.7, label = "decision boundary", hjust = 1, size = 3, color = "red")

Figure 9.4:

# fit depth 3 decision tree
ctrl <- list(cp = 0, minbucket = 5, maxdepth = 3)
fit <- rpart(y ~ x, data = df, control = ctrl)
rpart.plot(fit)


# plot decision boundary
df %>%
  mutate(pred = predict(fit, df)) %>%
  ggplot(aes(x, y)) +
  geom_point(alpha = .2, size = 1) +
  geom_line(aes(x, y = truth), color = "blue", size = .75) +
  geom_line(aes(y = pred), color = "red", size = .75)

Figure 9.5:

# decision tree
iris_fit <- rpart(Species ~ Sepal.Length + Sepal.Width, data = iris)
rpart.plot(iris_fit)


# decision boundary
ggplot(iris, aes(Sepal.Length, Sepal.Width, color = Species, shape = Species)) +
  geom_point(show.legend = FALSE) +
  annotate("rect", xmin = -Inf, xmax = 5.44, ymin = 2.8, ymax = Inf, alpha = .75, fill = "orange") +
  annotate("text", x = 4.0, y = 4.4, label = "setosa", hjust = 0, size = 3) +
  annotate("rect", xmin = -Inf, xmax = 5.44, ymin = 2.79, ymax = -Inf, alpha = .75, fill = "grey") +
  annotate("text", x = 4.0, y = 2, label = "versicolor", hjust = 0, size = 3) +
  annotate("rect", xmin = 5.45, xmax = 6.15, ymin = 3.1, ymax = Inf, alpha = .75, fill = "orange") +
  annotate("text", x = 6, y = 4.4, label = "setosa", hjust = 1, vjust = 0, size = 3) +
  annotate("rect", xmin = 5.45, xmax = 6.15, ymin = 3.09, ymax = -Inf, alpha = .75, fill = "grey") +
  annotate("text", x = 6.15, y = 2, label = "versicolor", hjust = 1, vjust = 0, fill = "grey", size = 3) +
  annotate("rect", xmin = 6.16, xmax = Inf, ymin = -Inf, ymax = Inf, alpha = .75, fill = "green") +
  annotate("text", x = 8, y = 2, label = "virginica", hjust = 1, vjust = 0, fill = "green", size = 3)

How deep?

Figure 9.6:

ctrl <- list(cp = 0, minbucket = 1, maxdepth = 50)
fit <- rpart(y ~ x, data = df, control = ctrl)
rpart.plot(fit)


df %>%
  mutate(pred = predict(fit, df)) %>%
  ggplot(aes(x, y)) +
  geom_point(alpha = .2, size = 1) +
  geom_line(aes(x, y = truth), color = "blue", size = 0.75) +
  geom_line(aes(y = pred), color = "red", size = 0.75)

Early stopping

Figure 9.7:

hyper_grid <- expand.grid(
  maxdepth = c(1, 5, 15),
  minbucket = c(1, 5, 15)
)
results <- data.frame(NULL)

for(i in seq_len(nrow(hyper_grid))) {
 ctrl <- list(cp = 0, maxdepth = hyper_grid$maxdepth[i], minbucket = hyper_grid$minbucket[i])
 fit <- rpart(y ~ x, data = df, control = ctrl) 
 
 predictions <- mutate(
   df, 
   minbucket = factor(paste("Min node size =", hyper_grid$minbucket[i]), ordered = TRUE),
   maxdepth = factor(paste("Max tree depth =", hyper_grid$maxdepth[i]), ordered = TRUE)
   )
 predictions$pred <- predict(fit, df)
 results <- rbind(results, predictions)
   
}

ggplot(results, aes(x, y)) +
  geom_point(alpha = .2, size = 1) +
  geom_line(aes(x, y = truth), color = "blue", size = .75) +
  geom_line(aes(y = pred), color = "red", size = 1) +
  facet_grid(minbucket ~ maxdepth)

Pruning

Figure 9.8:

ctrl <- list(cp = 0, minbucket = 1, maxdepth = 50)
fit <- rpart(y ~ x, data = df, control = ctrl)

p1 <- df %>%
  mutate(pred = predict(fit, df)) %>%
  ggplot(aes(x, y)) +
  geom_point(alpha = .3, size = 2) +
  geom_line(aes(x, y = truth), color = "blue", size = 1) +
  geom_line(aes(y = pred), color = "red", size = 1)

fit2 <- rpart(y ~ x, data = df)

p2 <- df %>%
  mutate(pred2 = predict(fit2, df)) %>%
  ggplot(aes(x, y)) +
  geom_point(alpha = .3, size = 2) +
  geom_line(aes(x, y = truth), color = "blue", size = 1) +
  geom_line(aes(y = pred2), color = "red", size = 1)

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

Ames housing example

ames_dt1 <- rpart(
  formula = Sale_Price ~ .,
  data    = ames_train,
  method  = "anova"
)
ames_dt1
n= 2053 

node), split, n, deviance, yval
      * denotes terminal node

 1) root 2053 13217940000000 180996.30  
   2) Overall_Qual=Very_Poor,Poor,Fair,Below_Average,Average,Above_Average,Good 1722  4107888000000 156954.70  
     4) Neighborhood=North_Ames,Old_Town,Edwards,Sawyer,Mitchell,Brookside,Iowa_DOT_and_Rail_Road,South_and_West_of_Iowa_State_University,Meadow_Village,Briardale,Northpark_Villa,Blueste 1022  1332227000000 132318.00  
       8) Overall_Qual=Very_Poor,Poor,Fair,Below_Average 199   179295400000  98856.51 *
       9) Overall_Qual=Average,Above_Average,Good 823   876239900000 140409.00  
        18) First_Flr_SF< 1089 517   290531200000 129244.00 *
        19) First_Flr_SF>=1089 306   412375700000 159272.60 *
     5) Neighborhood=College_Creek,Somerset,Northridge_Heights,Gilbert,Northwest_Ames,Sawyer_West,Crawford,Timberland,Northridge,Stone_Brook,Clear_Creek,Bloomington_Heights,Veenker,Green_Hills 700  1249681000000 192924.20  
      10) Gr_Liv_Area< 1477 287   250826800000 165395.90 *
      11) Gr_Liv_Area>=1477 413   630227700000 212054.00  
        22) Total_Bsmt_SF< 959.5 199   139087700000 192493.10 *
        23) Total_Bsmt_SF>=959.5 214   344191200000 230243.70 *
   3) Overall_Qual=Very_Good,Excellent,Very_Excellent 331  2936700000000 306070.70  
     6) Overall_Qual=Very_Good 231   946974600000 270626.10  
      12) Gr_Liv_Area< 1919 142   334978300000 244016.60 *
      13) Gr_Liv_Area>=1919 89   351030800000 313081.60 *
     7) Overall_Qual=Excellent,Very_Excellent 100  1029126000000 387948.00  
      14) Total_Bsmt_SF< 1907.5 72   314985900000 350532.40 *
      15) Total_Bsmt_SF>=1907.5 28   354159900000 484159.40 *
rpart.plot(ames_dt1)

plotcp(ames_dt1)

ames_dt2 <- rpart(
    formula = Sale_Price ~ .,
    data    = ames_train,
    method  = "anova", 
    control = list(cp = 0, xval = 10)
)

plotcp(ames_dt2)
abline(v = 11, lty = "dashed")

# rpart cross validation results
ames_dt1$cptable
           CP nsplit rel error    xerror       xstd
1  0.46704344      0 1.0000000 1.0015334 0.06051267
2  0.11544770      1 0.5329566 0.5343697 0.03079312
3  0.07267387      2 0.4175089 0.4209603 0.03007122
4  0.02788834      3 0.3448350 0.3502963 0.02145751
5  0.02723422      4 0.3169466 0.3319341 0.02225037
6  0.02093301      5 0.2897124 0.3125117 0.02150290
7  0.01974328      6 0.2687794 0.2986956 0.02139660
8  0.01311346      7 0.2490361 0.2726862 0.01738257
9  0.01111737      8 0.2359227 0.2654669 0.01725615
10 0.01000000      9 0.2248053 0.2584346 0.01721996
# caret cross validation results
ames_dt3 <- train(
  Sale_Price ~ .,
  data = ames_train,
  method = "rpart",
  trControl = trainControl(method = "cv", number = 10),
  tuneLength = 20
)

ggplot(ames_dt3)

Feature interpretation

vip(ames_dt3, num_features = 40, bar = FALSE)

# Construct partial dependence plots
p1 <- partial(ames_dt3, pred.var = "Gr_Liv_Area") %>% autoplot()
p2 <- partial(ames_dt3, pred.var = "Year_Built") %>% autoplot()
p3 <- partial(ames_dt3, pred.var = c("Gr_Liv_Area", "Year_Built")) %>% 
  plotPartial(levelplot = FALSE, zlab = "yhat", drape = TRUE, 
              colorkey = TRUE, screen = list(z = -20, x = -60))

# Display plots side by side
gridExtra::grid.arrange(p1, p2, p3, ncol = 3)

LS0tCnRpdGxlOiAiQ2hhcHRlciA5OiBEZWNpc2lvbiBUcmVlcyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKX19Ob3RlX186IFNvbWUgcmVzdWx0cyBtYXkgZGlmZmVyIGZyb20gdGhlIGhhcmQgY29weSBib29rIGR1ZSB0byB0aGUgY2hhbmdpbmcgb2Ygc2FtcGxpbmcgcHJvY2VkdXJlcyBpbnRyb2R1Y2VkIGluIFIgMy42LjAuIFNlZSBodHRwOi8vYml0Lmx5LzM1RDFTVzcgZm9yIG1vcmUgZGV0YWlscy4gQWNjZXNzIGFuZCBydW4gdGhlIHNvdXJjZSBjb2RlIGZvciB0aGlzIG5vdGVib29rIFtoZXJlXShodHRwczovL3JzdHVkaW8uY2xvdWQvcHJvamVjdC84MDExODUpLiAKCkhpZGRlbiBjaGFwdGVyIHJlcXVpcmVtZW50cyB1c2VkIGluIHRoZSBib29rIHRvIHNldCB0aGUgcGxvdHRpbmcgdGhlbWUgYW5kIGxvYWQgcGFja2FnZXMgdXNlZCBpbiBoaWRkZW4gY29kZSBjaHVua3M6CgpgYGB7ciBzZXR1cH0KIyBTZXQgZ2xvYmFsIFIgb3B0aW9ucwpvcHRpb25zKHNjaXBlbiA9IDk5OSkKCiMgU2V0IHRoZSBncmFwaGljYWwgdGhlbWUKZ2dwbG90Mjo6dGhlbWVfc2V0KGdncGxvdDI6OnRoZW1lX2xpZ2h0KCkpCgojIFNldCBnbG9iYWwga25pdHIgY2h1bmsgb3B0aW9ucwprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgd2FybmluZyA9IEZBTFNFLCAKICBtZXNzYWdlID0gRkFMU0UKKQoKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocnBhcnQpCmxpYnJhcnkocnBhcnQucGxvdCkKYW1lcyA8LSBBbWVzSG91c2luZzo6bWFrZV9hbWVzKCkKYGBgCgojIyBQcmVyZXF1aXNpdGVzCgpJbiB0aGlzIGNoYXB0ZXIgd2UnbGwgdXNlIHRoZSBmb2xsb3dpbmcgcGFja2FnZXM6CgpgYGB7ciBkdC1wa2dzfQojIEhlbHBlciBwYWNrYWdlcwpsaWJyYXJ5KGRwbHlyKSAgICAgICAjIGZvciBkYXRhIHdyYW5nbGluZwpsaWJyYXJ5KGdncGxvdDIpICAgICAjIGZvciBhd2Vzb21lIHBsb3R0aW5nCgojIE1vZGVsaW5nIHBhY2thZ2VzCmxpYnJhcnkocnBhcnQpICAgICAgICMgZGlyZWN0IGVuZ2luZSBmb3IgZGVjaXNpb24gdHJlZSBhcHBsaWNhdGlvbgpsaWJyYXJ5KGNhcmV0KSAgICAgICAjIG1ldGEgZW5naW5lIGZvciBkZWNpc2lvbiB0cmVlIGFwcGxpY2F0aW9uCgojIE1vZGVsIGludGVycHJldGFiaWxpdHkgcGFja2FnZXMKbGlicmFyeShycGFydC5wbG90KSAgIyBmb3IgcGxvdHRpbmcgZGVjaXNpb24gdHJlZXMKbGlicmFyeSh2aXApICAgICAgICAgIyBmb3IgZmVhdHVyZSBpbXBvcnRhbmNlCmxpYnJhcnkocGRwKSAgICAgICAgICMgZm9yIGZlYXR1cmUgZWZmZWN0cwpgYGAKCldlJ2xsIGNvbnRpbnVlIHRvIGlsbHVzdHJhdGUgdGhlIG1haW4gY29uY2VwdHMgdXNpbmcgdGhlIEFtZXMgaG91c2luZyBkYXRhOgoKYGBge3IgZHQtZGF0YS1wcmVyZXEsIGVjaG89VFJVRX0KIyBDcmVhdGUgdHJhaW5pbmcgKDcwJSkgc2V0IGZvciB0aGUgQW1lcyBob3VzaW5nIGRhdGEuCnNldC5zZWVkKDEyMykKc3BsaXQgIDwtIHJzYW1wbGU6OmluaXRpYWxfc3BsaXQoYW1lcywgcHJvcCA9IDAuNywgc3RyYXRhID0gIlNhbGVfUHJpY2UiKQphbWVzX3RyYWluICA8LSByc2FtcGxlOjp0cmFpbmluZyhzcGxpdCkKYGBgCgoKIyMgU3RydWN0dXJlCgpGaWd1cmUgOS4xOgoKYGBge3IgZXhlbXBsYXItZGVjaXNpb24tdHJlZSwgZWNobz1UUlVFLCBmaWcuY2FwPSJFeGVtcGxhciBkZWNpc2lvbiB0cmVlIHByZWRpY3Rpbmcgd2hldGhlciBvciBub3QgYSBjdXN0b21lciB3aWxsIHJlZGVlbSBhIGNvdXBvbiAoeWVzIG9yIG5vKSBiYXNlZCBvbiB0aGUgY3VzdG9tZXIncyBsb3lhbHR5LCBob3VzZWhvbGQgaW5jb21lLCBsYXN0IG1vbnRoJ3Mgc3BlbmQsIGNvdXBvbiBwbGFjZW1lbnQsIGFuZCBzaG9wcGluZyBtb2RlLiIsIG91dC5oZWlnaHQ9IjEwMCUiLCBvdXQud2lkdGg9IjEwMCUifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzL2V4ZW1wbGFyLWRlY2lzaW9uLXRyZWUucG5nIikKYGBgCgpGaWd1cmUgOS4yOgoKYGBge3IgZGVjaXNpb24tdHJlZS10ZXJtaW5vbG9neSwgZWNobz1UUlVFLCBmaWcuY2FwPSJUZXJtaW5vbG9neSBvZiBhIGRlY2lzaW9uIHRyZWUuIiwgb3V0LmhlaWdodD0iODAlIiwgb3V0LndpZHRoPSI4MCUifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzL2RlY2lzaW9uLXRyZWUtdGVybWlub2xvZ3kucG5nIikKYGBgCgoKIyMgUGFydGl0aW9uaW5nCgpGaWd1cmUgOS4zOgoKYGBge3IgZGVjaXNpb24tc3R1bXAsIGVjaG89VFJVRSwgZmlnLndpZHRoPTQsIGZpZy5oZWlnaHQ9MywgZmlnLnNob3c9J2hvbGQnLCBmaWcuY2FwPSJEZWNpc2lvbiB0cmVlIGlsbHVzdHJhdGluZyB0aGUgc2luZ2xlIHNwbGl0IG9uIGZlYXR1cmUgeCAobGVmdCkuIFRoZSByZXN1bHRpbmcgZGVjaXNpb24gYm91bmRhcnkgaWxsdXN0cmF0ZXMgdGhlIHByZWRpY3RlZCB2YWx1ZSB3aGVuIHggPCAzLjEgKDAuNjQpLCBhbmQgd2hlbiB4ID4gMy4xICgtMC42NykgKHJpZ2h0KS4iLCBvdXQud2lkdGg9IjQ4JSJ9CiMgY3JlYXRlIGRhdGEKc2V0LnNlZWQoMTExMikgICMgZm9yIHJlcHJvZHVjaWJpbGl0eQpkZiA8LSB0aWJibGU6OnRpYmJsZSgKICB4ID0gc2VxKGZyb20gPSAwLCB0byA9IDIgKiBwaSwgbGVuZ3RoID0gNTAwKSwKICB5ID0gc2luKHgpICsgcm5vcm0obGVuZ3RoKHgpLCBzZCA9IDAuNSksCiAgdHJ1dGggPSBzaW4oeCkKKQoKIyBydW4gZGVjaXNpb24gc3R1bXAgbW9kZWwKY3RybCA8LSBsaXN0KGNwID0gMCwgbWluYnVja2V0ID0gNSwgbWF4ZGVwdGggPSAxKQpmaXQgPC0gcnBhcnQoeSB+IHgsIGRhdGEgPSBkZiwgY29udHJvbCA9IGN0cmwpCgojIHBsb3QgdHJlZSAKcGFyKG1hciA9IGMoMSwgMSwgMSwgMSkpCnJwYXJ0LnBsb3QoZml0KQoKIyBwbG90IGRlY2lzaW9uIGJvdW5kYXJ5CmRmICU+JQogIG11dGF0ZShwcmVkID0gcHJlZGljdChmaXQsIGRmKSkgJT4lCiAgZ2dwbG90KGFlcyh4LCB5KSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAuMiwgc2l6ZSA9IDEpICsKICBnZW9tX2xpbmUoYWVzKHgsIHkgPSB0cnV0aCksIGNvbG9yID0gImJsdWUiLCBzaXplID0gLjc1KSArCiAgZ2VvbV9saW5lKGFlcyh5ID0gcHJlZCksIGNvbG9yID0gInJlZCIsIHNpemUgPSAuNzUpICsKICBnZW9tX3NlZ21lbnQoeCA9IDMuMSwgeGVuZCA9IDMuMSwgeSA9IC1JbmYsIHllbmQgPSAtLjk1LAogICAgICAgICAgICAgICBhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4yNSwiY20iKSksIHNpemUgPSAuMjUpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSAzLjEsIHkgPSAtSW5mLCBsYWJlbCA9ICJzcGxpdCIsIGhqdXN0ID0gMS4yLCB2anVzdCA9IC0xLCBzaXplID0gMykgKwogIGdlb21fc2VnbWVudCh4ID0gNS41LCB4ZW5kID0gNiwgeSA9IDIsIHllbmQgPSAyLCBzaXplID0gLjc1LCBjb2xvciA9ICJibHVlIikgKwogIGdlb21fc2VnbWVudCh4ID0gNS41LCB4ZW5kID0gNiwgeSA9IDEuNywgeWVuZCA9IDEuNywgc2l6ZSA9IC43NSwgY29sb3IgPSAicmVkIikgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IDUuMywgeSA9IDIsIGxhYmVsID0gInRydXRoIiwgaGp1c3QgPSAxLCBzaXplID0gMywgY29sb3IgPSAiYmx1ZSIpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSA1LjMsIHkgPSAxLjcsIGxhYmVsID0gImRlY2lzaW9uIGJvdW5kYXJ5IiwgaGp1c3QgPSAxLCBzaXplID0gMywgY29sb3IgPSAicmVkIikKYGBgCgpGaWd1cmUgOS40OgoKYGBge3IgZGVwdGgtMy1kZWNpc2lvbi10cmVlLCBlY2hvPVRSVUUsIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTMsIGZpZy5zaG93PSdob2xkJywgZmlnLmNhcD0iRGVjaXNpb24gdHJlZSBpbGx1c3RyYXRpbmcgd2l0aCBkZXB0aCA9IDMsIHJlc3VsdGluZyBpbiA3IGRlY2lzaW9uIHNwbGl0cyBhbG9uZyB2YWx1ZXMgb2YgZmVhdHVyZSB4IGFuZCA4IHByZWRpY3Rpb24gcmVnaW9ucyAobGVmdCkuIFRoZSByZXN1bHRpbmcgZGVjaXNpb24gYm91bmRhcnkgKHJpZ2h0KS4iLCBvdXQud2lkdGg9IjQ4JSJ9CiMgZml0IGRlcHRoIDMgZGVjaXNpb24gdHJlZQpjdHJsIDwtIGxpc3QoY3AgPSAwLCBtaW5idWNrZXQgPSA1LCBtYXhkZXB0aCA9IDMpCmZpdCA8LSBycGFydCh5IH4geCwgZGF0YSA9IGRmLCBjb250cm9sID0gY3RybCkKcnBhcnQucGxvdChmaXQpCgojIHBsb3QgZGVjaXNpb24gYm91bmRhcnkKZGYgJT4lCiAgbXV0YXRlKHByZWQgPSBwcmVkaWN0KGZpdCwgZGYpKSAlPiUKICBnZ3Bsb3QoYWVzKHgsIHkpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IC4yLCBzaXplID0gMSkgKwogIGdlb21fbGluZShhZXMoeCwgeSA9IHRydXRoKSwgY29sb3IgPSAiYmx1ZSIsIHNpemUgPSAuNzUpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBwcmVkKSwgY29sb3IgPSAicmVkIiwgc2l6ZSA9IC43NSkKYGBgCgpGaWd1cmUgOS41OgoKYGBge3IgaXJpcy1kZWNpc2lvbi10cmVlLCBlY2hvPVRSVUUsIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTMsIGZpZy5zaG93PSdob2xkJywgZmlnLmNhcD0iRGVjaXNpb24gdHJlZSBmb3IgdGhlIGlyaXMgY2xhc3NpZmljYXRpb24gcHJvYmxlbSAobGVmdCkuIFRoZSBkZWNpc2lvbiBib3VuZGFyeSByZXN1bHRzIGluIHJlY3Rhbmd1bGFyIHJlZ2lvbnMgdGhhdCBlbmNsb3NlIHRoZSBvYnNlcnZhdGlvbnMuICBUaGUgY2xhc3Mgd2l0aCB0aGUgaGlnaGVzdCBwcm9wb3J0aW9uIGluIGVhY2ggcmVnaW9uIGlzIHRoZSBwcmVkaWN0ZWQgdmFsdWUgKHJpZ2h0KS4iLCBvdXQud2lkdGg9IjQ4JSJ9CiMgZGVjaXNpb24gdHJlZQppcmlzX2ZpdCA8LSBycGFydChTcGVjaWVzIH4gU2VwYWwuTGVuZ3RoICsgU2VwYWwuV2lkdGgsIGRhdGEgPSBpcmlzKQpycGFydC5wbG90KGlyaXNfZml0KQoKIyBkZWNpc2lvbiBib3VuZGFyeQpnZ3Bsb3QoaXJpcywgYWVzKFNlcGFsLkxlbmd0aCwgU2VwYWwuV2lkdGgsIGNvbG9yID0gU3BlY2llcywgc2hhcGUgPSBTcGVjaWVzKSkgKwogIGdlb21fcG9pbnQoc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGFubm90YXRlKCJyZWN0IiwgeG1pbiA9IC1JbmYsIHhtYXggPSA1LjQ0LCB5bWluID0gMi44LCB5bWF4ID0gSW5mLCBhbHBoYSA9IC43NSwgZmlsbCA9ICJvcmFuZ2UiKSArCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gNC4wLCB5ID0gNC40LCBsYWJlbCA9ICJzZXRvc2EiLCBoanVzdCA9IDAsIHNpemUgPSAzKSArCiAgYW5ub3RhdGUoInJlY3QiLCB4bWluID0gLUluZiwgeG1heCA9IDUuNDQsIHltaW4gPSAyLjc5LCB5bWF4ID0gLUluZiwgYWxwaGEgPSAuNzUsIGZpbGwgPSAiZ3JleSIpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSA0LjAsIHkgPSAyLCBsYWJlbCA9ICJ2ZXJzaWNvbG9yIiwgaGp1c3QgPSAwLCBzaXplID0gMykgKwogIGFubm90YXRlKCJyZWN0IiwgeG1pbiA9IDUuNDUsIHhtYXggPSA2LjE1LCB5bWluID0gMy4xLCB5bWF4ID0gSW5mLCBhbHBoYSA9IC43NSwgZmlsbCA9ICJvcmFuZ2UiKSArCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gNiwgeSA9IDQuNCwgbGFiZWwgPSAic2V0b3NhIiwgaGp1c3QgPSAxLCB2anVzdCA9IDAsIHNpemUgPSAzKSArCiAgYW5ub3RhdGUoInJlY3QiLCB4bWluID0gNS40NSwgeG1heCA9IDYuMTUsIHltaW4gPSAzLjA5LCB5bWF4ID0gLUluZiwgYWxwaGEgPSAuNzUsIGZpbGwgPSAiZ3JleSIpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSA2LjE1LCB5ID0gMiwgbGFiZWwgPSAidmVyc2ljb2xvciIsIGhqdXN0ID0gMSwgdmp1c3QgPSAwLCBmaWxsID0gImdyZXkiLCBzaXplID0gMykgKwogIGFubm90YXRlKCJyZWN0IiwgeG1pbiA9IDYuMTYsIHhtYXggPSBJbmYsIHltaW4gPSAtSW5mLCB5bWF4ID0gSW5mLCBhbHBoYSA9IC43NSwgZmlsbCA9ICJncmVlbiIpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSA4LCB5ID0gMiwgbGFiZWwgPSAidmlyZ2luaWNhIiwgaGp1c3QgPSAxLCB2anVzdCA9IDAsIGZpbGwgPSAiZ3JlZW4iLCBzaXplID0gMykKCmBgYAoKCiMjIEhvdyBkZWVwPwoKRmlndXJlIDkuNjoKCmBgYHtyIGRlZXAtb3ZlcmZpdC10cmVlLCBlY2hvPVRSVUUsIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTMsIGZpZy5zaG93PSdob2xkJywgZmlnLmNhcD0iT3ZlcmZpdCBkZWNpc2lvbiB0cmVlIHdpdGggNTYgc3BsaXRzLiIsIG91dC53aWR0aD0iNDglIn0KY3RybCA8LSBsaXN0KGNwID0gMCwgbWluYnVja2V0ID0gMSwgbWF4ZGVwdGggPSA1MCkKZml0IDwtIHJwYXJ0KHkgfiB4LCBkYXRhID0gZGYsIGNvbnRyb2wgPSBjdHJsKQpycGFydC5wbG90KGZpdCkKCmRmICU+JQogIG11dGF0ZShwcmVkID0gcHJlZGljdChmaXQsIGRmKSkgJT4lCiAgZ2dwbG90KGFlcyh4LCB5KSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAuMiwgc2l6ZSA9IDEpICsKICBnZW9tX2xpbmUoYWVzKHgsIHkgPSB0cnV0aCksIGNvbG9yID0gImJsdWUiLCBzaXplID0gMC43NSkgKwogIGdlb21fbGluZShhZXMoeSA9IHByZWQpLCBjb2xvciA9ICJyZWQiLCBzaXplID0gMC43NSkKYGBgCgojIyMgRWFybHkgc3RvcHBpbmcKCkZpZ3VyZSA5Ljc6CgpgYGB7ciBkdC1lYXJseS1zdG9wcGluZywgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTgsIGZpZy5jYXA9IklsbHVzdHJhdGlvbiBvZiBob3cgZWFybHkgc3RvcHBpbmcgYWZmZWN0cyB0aGUgZGVjaXNpb24gYm91bmRhcnkgb2YgYSByZWdyZXNzaW9uIGRlY2lzaW9uIHRyZWUuIFRoZSBjb2x1bW5zIGlsbHVzdHJhdGUgaG93IHRyZWUgZGVwdGggaW1wYWN0cyB0aGUgZGVjaXNpb24gYm91bmRhcnkgYW5kIHRoZSByb3dzIGlsbHVzdHJhdGUgaG93IHRoZSBtaW5pbXVtIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgaW4gdGhlIHRlcm1pbmFsIG5vZGUgaW5mbHVlbmNlcyB0aGUgZGVjaXNpb24gYm91bmRhcnkuIiwgZWNobz1UUlVFfQpoeXBlcl9ncmlkIDwtIGV4cGFuZC5ncmlkKAogIG1heGRlcHRoID0gYygxLCA1LCAxNSksCiAgbWluYnVja2V0ID0gYygxLCA1LCAxNSkKKQpyZXN1bHRzIDwtIGRhdGEuZnJhbWUoTlVMTCkKCmZvcihpIGluIHNlcV9sZW4obnJvdyhoeXBlcl9ncmlkKSkpIHsKIGN0cmwgPC0gbGlzdChjcCA9IDAsIG1heGRlcHRoID0gaHlwZXJfZ3JpZCRtYXhkZXB0aFtpXSwgbWluYnVja2V0ID0gaHlwZXJfZ3JpZCRtaW5idWNrZXRbaV0pCiBmaXQgPC0gcnBhcnQoeSB+IHgsIGRhdGEgPSBkZiwgY29udHJvbCA9IGN0cmwpIAogCiBwcmVkaWN0aW9ucyA8LSBtdXRhdGUoCiAgIGRmLCAKICAgbWluYnVja2V0ID0gZmFjdG9yKHBhc3RlKCJNaW4gbm9kZSBzaXplID0iLCBoeXBlcl9ncmlkJG1pbmJ1Y2tldFtpXSksIG9yZGVyZWQgPSBUUlVFKSwKICAgbWF4ZGVwdGggPSBmYWN0b3IocGFzdGUoIk1heCB0cmVlIGRlcHRoID0iLCBoeXBlcl9ncmlkJG1heGRlcHRoW2ldKSwgb3JkZXJlZCA9IFRSVUUpCiAgICkKIHByZWRpY3Rpb25zJHByZWQgPC0gcHJlZGljdChmaXQsIGRmKQogcmVzdWx0cyA8LSByYmluZChyZXN1bHRzLCBwcmVkaWN0aW9ucykKICAgCn0KCmdncGxvdChyZXN1bHRzLCBhZXMoeCwgeSkpICsKICBnZW9tX3BvaW50KGFscGhhID0gLjIsIHNpemUgPSAxKSArCiAgZ2VvbV9saW5lKGFlcyh4LCB5ID0gdHJ1dGgpLCBjb2xvciA9ICJibHVlIiwgc2l6ZSA9IC43NSkgKwogIGdlb21fbGluZShhZXMoeSA9IHByZWQpLCBjb2xvciA9ICJyZWQiLCBzaXplID0gMSkgKwogIGZhY2V0X2dyaWQobWluYnVja2V0IH4gbWF4ZGVwdGgpCmBgYAoKIyMjIFBydW5pbmcKCkZpZ3VyZSA5Ljg6CgpgYGB7ciBwcnVuZWQtdHJlZSwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0ID0gNCwgZmlnLmNhcD0iVG8gcHJ1bmUgYSB0cmVlLCB3ZSBncm93IGFuIG92ZXJseSBjb21wbGV4IHRyZWUgKGxlZnQpIGFuZCB0aGVuIHVzZSBhIGNvc3QgY29tcGxleGl0eSBwYXJhbWV0ZXIgdG8gaWRlbnRpZnkgdGhlIG9wdGltYWwgc3VidHJlZSAocmlnaHQpLiIsIGVjaG89VFJVRX0KY3RybCA8LSBsaXN0KGNwID0gMCwgbWluYnVja2V0ID0gMSwgbWF4ZGVwdGggPSA1MCkKZml0IDwtIHJwYXJ0KHkgfiB4LCBkYXRhID0gZGYsIGNvbnRyb2wgPSBjdHJsKQoKcDEgPC0gZGYgJT4lCiAgbXV0YXRlKHByZWQgPSBwcmVkaWN0KGZpdCwgZGYpKSAlPiUKICBnZ3Bsb3QoYWVzKHgsIHkpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IC4zLCBzaXplID0gMikgKwogIGdlb21fbGluZShhZXMoeCwgeSA9IHRydXRoKSwgY29sb3IgPSAiYmx1ZSIsIHNpemUgPSAxKSArCiAgZ2VvbV9saW5lKGFlcyh5ID0gcHJlZCksIGNvbG9yID0gInJlZCIsIHNpemUgPSAxKQoKZml0MiA8LSBycGFydCh5IH4geCwgZGF0YSA9IGRmKQoKcDIgPC0gZGYgJT4lCiAgbXV0YXRlKHByZWQyID0gcHJlZGljdChmaXQyLCBkZikpICU+JQogIGdncGxvdChhZXMoeCwgeSkpICsKICBnZW9tX3BvaW50KGFscGhhID0gLjMsIHNpemUgPSAyKSArCiAgZ2VvbV9saW5lKGFlcyh4LCB5ID0gdHJ1dGgpLCBjb2xvciA9ICJibHVlIiwgc2l6ZSA9IDEpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBwcmVkMiksIGNvbG9yID0gInJlZCIsIHNpemUgPSAxKQoKZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UocDEsIHAyLCBucm93ID0gMSkKYGBgCgoKIyMgQW1lcyBob3VzaW5nIGV4YW1wbGUKCmBgYHtyIGJhc2ljLWFtZXMtdHJlZX0KYW1lc19kdDEgPC0gcnBhcnQoCiAgZm9ybXVsYSA9IFNhbGVfUHJpY2UgfiAuLAogIGRhdGEgICAgPSBhbWVzX3RyYWluLAogIG1ldGhvZCAgPSAiYW5vdmEiCikKYGBgCgpgYGB7ciBiYXNpYy1hbWVzLWR0LXRyZWUtcmVzdWx0cywgbGluZXdpZHRoID0gNzB9CmFtZXNfZHQxCmBgYAoKYGBge3IgYmFzaWMtYW1lcy10cmVlLXBsb3QsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD02LCBmaWcuY2FwPSJEaWFncmFtIGRpc3BsYXlpbmcgdGhlIHBydW5lZCBkZWNpc2lvbiB0cmVlIGZvciB0aGUgQW1lcyBIb3VzaW5nIGRhdGEuIn0KcnBhcnQucGxvdChhbWVzX2R0MSkKYGBgCgpgYGB7ciBwbG90LWNwLCBmaWcud2lkdGggPSA1LCBmaWcuaGVpZ2h0PTMuNSwgZmlnLmNhcD0iUHJ1bmluZyBjb21wbGV4aXR5IHBhcmFtZXRlciAoY3ApIHBsb3QgaWxsdXN0cmF0aW5nIHRoZSByZWxhdGl2ZSBjcm9zcyB2YWxpZGF0aW9uIGVycm9yICh5LWF4aXMpIGZvciB2YXJpb3VzIGNwIHZhbHVlcyAobG93ZXIgeC1heGlzKS4gU21hbGxlciBjcCB2YWx1ZXMgbGVhZCB0byBsYXJnZXIgdHJlZXMgKHVwcGVyIHgtYXhpcykuIFVzaW5nIHRoZSAxLVNFIHJ1bGUsIGEgdHJlZSBzaXplIG9mIDEwLTEyIHByb3ZpZGVzIG9wdGltYWwgY3Jvc3MgdmFsaWRhdGlvbiByZXN1bHRzLiJ9CnBsb3RjcChhbWVzX2R0MSkKYGBgCgpgYGB7ciBuby1jcC10cmVlLCBmaWcuY2FwPSJQcnVuaW5nIGNvbXBsZXhpdHkgcGFyYW1ldGVyIHBsb3QgZm9yIGEgZnVsbHkgZ3Jvd24gdHJlZS4gU2lnbmlmaWNhbnQgcmVkdWN0aW9uIGluIHRoZSBjcm9zcyB2YWxpZGF0aW9uIGVycm9yIGlzIGFjaGlldmVkIHdpdGggdHJlZSBzaXplcyA2LTIwIGFuZCB0aGVuIHRoZSBjcm9zcyB2YWxpZGF0aW9uIGVycm9yIGxldmVscyBvZmYgd2l0aCBtaW5pbWFsIG9yIG5vIGFkZGl0aW9uYWwgaW1wcm92ZW1lbnRzLiJ9CmFtZXNfZHQyIDwtIHJwYXJ0KAogICAgZm9ybXVsYSA9IFNhbGVfUHJpY2UgfiAuLAogICAgZGF0YSAgICA9IGFtZXNfdHJhaW4sCiAgICBtZXRob2QgID0gImFub3ZhIiwgCiAgICBjb250cm9sID0gbGlzdChjcCA9IDAsIHh2YWwgPSAxMCkKKQoKcGxvdGNwKGFtZXNfZHQyKQphYmxpbmUodiA9IDExLCBsdHkgPSAiZGFzaGVkIikKYGBgCgpgYGB7ciBjcC10YWJsZSwgZmlnLmNhcD0iQ3Jvc3MtdmFsaWRhdGVkIGFjY3VyYWN5IHJhdGUgZm9yIHRoZSAyMCBkaWZmZXJlbnQgJFxcYWxwaGEkIHBhcmFtZXRlciB2YWx1ZXMgaW4gb3VyIGdyaWQgc2VhcmNoLiBMb3dlciAkXFxhbHBoYSQgdmFsdWVzIChkZWVwZXIgdHJlZXMpIGhlbHAgdG8gbWluaW1pemUgZXJyb3JzLiIsIGZpZy5oZWlnaHQ9M30KIyBycGFydCBjcm9zcyB2YWxpZGF0aW9uIHJlc3VsdHMKYW1lc19kdDEkY3B0YWJsZQoKIyBjYXJldCBjcm9zcyB2YWxpZGF0aW9uIHJlc3VsdHMKYW1lc19kdDMgPC0gdHJhaW4oCiAgU2FsZV9QcmljZSB+IC4sCiAgZGF0YSA9IGFtZXNfdHJhaW4sCiAgbWV0aG9kID0gInJwYXJ0IiwKICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTApLAogIHR1bmVMZW5ndGggPSAyMAopCgpnZ3Bsb3QoYW1lc19kdDMpCmBgYAoKIyMgRmVhdHVyZSBpbnRlcnByZXRhdGlvbiAKCmBgYHtyIGR0LXZpcCwgZmlnLmhlaWdodD01LjUsIGZpZy5jYXA9IlZhcmlhYmxlIGltcG9ydGFuY2UgYmFzZWQgb24gdGhlIHRvdGFsIHJlZHVjdGlvbiBpbiBNU0UgZm9yIHRoZSBBbWVzIEhvdXNpbmcgZGVjaXNpb24gdHJlZS4ifQp2aXAoYW1lc19kdDMsIG51bV9mZWF0dXJlcyA9IDQwLCBiYXIgPSBGQUxTRSkKYGBgCgpgYGB7ciBkdC1wZHAsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0gMy41LCBmaWcuY2FwPSJQYXJ0aWFsIGRlcGVuZGVuY2UgcGxvdHMgdG8gdW5kZXJzdGFuZCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gc2FsZSBwcmljZSBhbmQgdGhlIGxpdmluZyBzcGFjZSwgYW5kIHllYXIgYnVpbHQgZmVhdHVyZXMuIn0KIyBDb25zdHJ1Y3QgcGFydGlhbCBkZXBlbmRlbmNlIHBsb3RzCnAxIDwtIHBhcnRpYWwoYW1lc19kdDMsIHByZWQudmFyID0gIkdyX0xpdl9BcmVhIikgJT4lIGF1dG9wbG90KCkKcDIgPC0gcGFydGlhbChhbWVzX2R0MywgcHJlZC52YXIgPSAiWWVhcl9CdWlsdCIpICU+JSBhdXRvcGxvdCgpCnAzIDwtIHBhcnRpYWwoYW1lc19kdDMsIHByZWQudmFyID0gYygiR3JfTGl2X0FyZWEiLCAiWWVhcl9CdWlsdCIpKSAlPiUgCiAgcGxvdFBhcnRpYWwobGV2ZWxwbG90ID0gRkFMU0UsIHpsYWIgPSAieWhhdCIsIGRyYXBlID0gVFJVRSwgCiAgICAgICAgICAgICAgY29sb3JrZXkgPSBUUlVFLCBzY3JlZW4gPSBsaXN0KHogPSAtMjAsIHggPSAtNjApKQoKIyBEaXNwbGF5IHBsb3RzIHNpZGUgYnkgc2lkZQpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShwMSwgcDIsIHAzLCBuY29sID0gMykKYGBgCg==