Ebeer

# set working directory using however you want to folder where data is stored.  I'll use 
ebeer <- read_csv("ebeer.csv")

# load ebeer, remove account number column
ebeer<-ebeer[-c(1)]

Training-Test Samples:

# drop the ID column, select customers that received a mailing only
ebeer_test<-subset(ebeer, mailing ==1)

# create ebeer rollout data
ebeer_rollout<-subset(ebeer, mailing ==0)

# rename ebeer_test ebeer
ebeer<-ebeer_test

Telco

# load telco
telco <- read_csv("telco.csv")
telco <- strings2factors(telco)
## 
## The following character variables were converted to factors
##  gender Partner Dependents PhoneService MultipleLines InternetService OnlineSecurity OnlineBackup DeviceProtection TechSupport StreamingTV StreamingMovies Contract PaperlessBilling PaymentMethod Churn
# drop ID column, divide Total charges by 1000
telco<-subset(telco, select=-customerID)
telco$TotalCharges<-telco$TotalCharges/1000

Training-Test:

# create 70% test and 30% holdout sample
set.seed(19103)
n <- nrow(telco)
sample <- sample(c(TRUE, FALSE), n, replace=TRUE, prob=c(0.7, 0.3))
telco.test <- telco[sample, ]
telco.holdout <- telco[!sample, ]

#call test telco, and full data set telco.all
telco.all<-telco
telco<-telco.test

Decision Trees

We’ll use ebeer for the trees. I first run a tree and graph it so we can talk about what it is. Later on I’ll explain the parameters

# DV needs to be factor variable so it knows to use a classification tree
tree<-tree(as.factor(respmail) ~ ., data=subset(ebeer, select = c(respmail, F, student)),mindev=.005)
par(mfrow=c(1,1))
plot(tree, col=8, lwd=2)
# cex controls the size of the type, 1 is the default.  
# label="yprob" gives the probability
text(tree, label = "yprob", cex=.75, font=2, digits = 2, pretty=0)

tree$frame
##        var    n  dev yval splits.cutleft splits.cutright yprob.0 yprob.1
## 1  student 4952 3720    0           <0.5            >0.5   0.876   0.124
## 2   <leaf> 2334    0    0                                  1.000   0.000
## 3        F 2618 2857    0           <2.5            >2.5   0.765   0.235
## 6        F  986  500    0           <1.5            >1.5   0.930   0.070
## 12  <leaf>  354    0    0                                  1.000   0.000
## 13  <leaf>  632  436    0                                  0.891   0.109
## 7        F 1632 2082    0           <4.5            >4.5   0.665   0.335
## 14  <leaf>  243  255    0                                  0.782   0.218
## 15  <leaf> 1389 1808    0                                  0.644   0.356

Here’s how to make predictions with trees, using the same data.

pred_tree <- predict(tree,ebeer, type = "vector")
head(pred_tree) %>% 
  kbl() %>%
  kable_styling()
0 1
0.644 0.356
1.000 0.000
1.000 0.000
0.891 0.109
0.782 0.218
0.891 0.109

Let’s look at the confusion matrix:

# take probability that responds
prob_resp <- pred_tree[,2]

# there is no predicted probability over 0.5
sum(prob_resp > 0.5)
## [1] 0
confusion_matrix <- (table(ebeer$respmail, prob_resp > 0.5))
confusion_matrix <- as.data.frame.matrix(confusion_matrix)
colnames(confusion_matrix) <- c("No")
confusion_matrix %>% 
  kbl() %>%
  kable_styling()
No
0 4336
1 616

The misclassification rate is the proportion of responders, since all probabilities were under 0.5 so the model predicted no one responded. This is the same missclass. (This is a good example where using the default threshold of 0.5 would be not so good.)

confusion_matrix[1]/sum(confusion_matrix)
##      No
## 0 0.876
## 1 0.124

Residual mean deviance is the total residual deviance divided by the number of observations - number of terminal nodes, \(n-df\). The deviance is 2500.

Trees in R

We’ll use the tree package in R. Here’s how the tree fits the data:

# mean two graphs side-by-side
par(mfrow=c(1,2), oma=c(0,0,2,0))
# same model as above

tree <- tree(as.factor(respmail) ~ ., data=subset(ebeer, select = c(respmail, F, student)),mindev=.005)

plot(tree, col=8, lwd=2)

# cex controls the size of the type, 1 is the default.  
# label="yprob" gives the probability
text(tree, cex=.75, label="yprob", font=2, digits = 2, pretty = 0)



par(mai=c(.8,.8,.2,.2))

# create an aggregate table of response by frequency and student
tbl<- ebeer %>% group_by(student, F) %>% summarise(mean=mean(respmail)) %>% data.frame()
## `summarise()` has grouped output by 'student'. You can override using the `.groups` argument.

pred<-predict(tree,tbl, type = "vector")[,2]

tbl<-tbl %>% mutate(pred = pred)

# plot it
par(mai=c(.8,.8,.2,.2))
plot(tbl$F[1:12],tbl$mean[1:12], col = "red", xlab="Frequency", ylab="mean response",ylim=c(-.05,0.5), pch=20)
points(tbl$F[13:24],tbl$mean[13:24], col = "blue", pch=20)
legend(7.5, 0.5, legend=c("Student = no", "Student= yes"), col=c("red", "blue"), pch=20, cex=0.8)

# create predictions from tree for every F x student combo
newF <- seq(1,12,length=12)
lines(tbl$F[1:12], tbl$pred[1:12], col=2, lwd=2)
lines(tbl$F[1:12], tbl$pred[13:24], col=4, lwd=2)
mtext("A simple tree",outer=TRUE,cex=1.5)

Nonparametric

This method is nonparametric because it doesn’t make an assumption about the relationships between the independent and dependent variables.

By setting the mindev and mincut to zero, we can make the tree very complex and fit the data arbitrarily close.

# mean two graphs side-by-side
par(mfrow=c(1,2), oma = c(0, 0, 2, 0))
# same model as above
tree<-tree(as.factor(respmail) ~ ., data=subset(ebeer, select = c(respmail, F, student)),mindev=0, mincut=0)
plot(tree, col=8, lwd=2)
# cex controls the size of the type, 1 is the default.  
# label="yprob" gives the probability
text(tree, cex=.5, label="yprob", font=2, digits = 2, pretty = 0)

par(mai=c(.8,.8,.2,.2))

# create an aggregate table of response by frequency and student
tbl<- ebeer %>% group_by(student, F) %>% summarise(mean=mean(respmail)) %>% data.frame()
## `summarise()` has grouped output by 'student'. You can override using the `.groups` argument.
pred<-predict(tree,tbl, type = "vector")[,2]

tbl<-tbl %>% mutate(pred = pred)

# plot it
par(mai=c(.8,.8,.2,.2))
plot(tbl$F[1:12],tbl$mean[1:12], col = "red", xlab="Frequency", ylab="mean response",ylim=c(-.05,0.5), pch=20)
points(tbl$F[13:24],tbl$mean[13:24], col = "blue", pch=20)
legend(7.5, 0.5, legend=c("Student = no", "Student= yes"), col=c("red", "blue"), pch=20, cex=0.8)

# create predictions from tree for every F x student combo
newF <- seq(1,12,length=12)
lines(tbl$F[1:12], tbl$pred[1:12], col=2, lwd=2)
lines(tbl$F[1:12], tbl$pred[13:24], col=4, lwd=2)
mtext("A simple tree",outer=TRUE,cex=1.5)

Overfitting & K-fold cross validation

We begin by fitting a complicated tree by setting the mindev=0 and mincut to some low number or zero.

tree_complex<-tree(as.factor(respmail) ~ . , data=ebeer, mindev=0, mincut=100)

par(mfrow=c(1,1))
par(mai=c(.8,.8,.2,.2))
plot(tree_complex, col=10, lwd=2)
text(tree_complex, cex=.5, label="yprob", font=2, digits = 2, pretty = 0)
title(main="Classification Tree: complex")

I specify K=10 cross fold validation. The size is the resulting number of leaves. The out-of-sample error measure is the deviance. We want that as low as possible.

cv.tree_complex<-cv.tree(tree_complex, K=10)
cv.tree_complex$size
##  [1] 19 18 17 16 15 14 13 11 10  7  6  5  4  3  2  1
round(cv.tree_complex$dev)
##  [1] 2531 2531 2531 2531 2531 2531 2531 2531 2531 2531 2531 2531 2531 2589 2860 3724
par(mfrow=c(1,1))
plot(cv.tree_complex$size, cv.tree_complex$dev, xlab="tree size (complexity)", ylab="Out-of-sample deviance (error)", pch=20)

Choose the tree with the minimum out-of-sample error. Here the error remains the same after 4. So I choose the simplest model with the lowest OOS error, a tree with 4 leaves.

par(mfrow=c(1,2), oma = c(0, 0, 2, 0))
tree_cut<-prune.tree(tree_complex, best=4)
plot(tree_cut, col=10, lwd=2)
text(tree_cut, cex=1, label="yprob", font=2, digits = 2, pretty = 0)
title(main="A pruned tree")
summary(tree_cut)
## 
## Classification tree:
## snip.tree(tree = tree_complex, nodes = c(13L, 7L))
## Variables actually used in tree construction:
## [1] "student" "F"      
## Number of terminal nodes:  4 
## Residual mean deviance:  0.509 = 2520 / 4950 
## Misclassification error rate: 0.124 = 616 / 4952
pred_tree_ebeer<-predict(tree_cut, data=ebeer)[,2]

plot(roc(ebeer$respmail, pred_tree_ebeer), print.auc=TRUE,
     col="black", lwd=1, main="ROC curve", xlab="Specificity: true negative rate", ylab="Sensitivity: true positive rate", xlim=c(1,0))

Telco: In other data sets, the “optimal” size may be different. Here it is with telco.

# make a somewhat big tree
par(mfrow=c(1,2),oma = c(0, 0, 2, 0))
tree_telco<-tree(Churn ~ ., data=telco, mindev=0.005, mincut=0)
plot(tree_telco, col=10, lwd=2)
text(tree_telco, cex=.4, font=1, digits = 2, pretty = 0)

cv.tree_telco<-cv.tree(tree_telco, K=10)
cv.tree_telco
## $size
## [1] 9 8 7 6 5 4 3 2 1
## 
## $dev
## [1] 4363 4363 4363 4363 4476 4481 4579 4760 5698
## 
## $k
## [1]  -Inf  31.9  39.2  39.7  71.5  78.6 113.0 182.2 941.2
## 
## $method
## [1] "deviance"
## 
## attr(,"class")
## [1] "prune"         "tree.sequence"
plot(cv.tree_telco$size, cv.tree_telco$dev, xlab="tree size (complexity)", ylab="Out-of-sample deviance (error)", pch=20)
mtext("Another example: telco",outer=TRUE,cex=1.5)

The deviance doesn’t decrease any more once we get to 6 leaves. So I’ll set 6 as the best.

par(mfrow=c(1,1))
tree_cut<-prune.tree(tree_telco, best=6)
plot(tree_cut, col=10, lwd=2)
text(tree_cut, cex=1, font=1, digits = 2, pretty = 0, label="yprob")

With the telco data it looks like any number of leaves after 6 gives you the same OOS performance. So, going forward, the simplest model with the best OOS performance is 6.

Random Forests

We will fit random forests in R. We specify the number of trees and the minimum number of observations in a leaf (25), as well as the importance .

ebeer_rf<- ranger(respmail ~ ., data=ebeer, write.forest=TRUE, num.trees = 1000, min.node.size = 25, importance = "impurity", probability=TRUE, seed = 19103)

Variable importance:

par(mfrow=c(1,1))
par(mai=c(.9,.8,.2,.2))
sort(ebeer_rf$variable.importance, decreasing = TRUE)
##         F         M  firstpur   student         R age_class    single    gender   mailing 
##     124.0     117.1      93.7      83.1      50.0      31.9      12.5      10.3       0.0
barplot(sort(ebeer_rf$variable.importance, decreasing = TRUE), ylab = "variable importance")

Predictions on new data using out-of-the-bag (OOB) samples.

head(ebeer_rf$predictions) %>% 
  kbl() %>%
  kable_styling()
0 1
0.596 0.404
0.991 0.009
1.000 0.000
0.618 0.382
0.847 0.153
0.873 0.127
pred <- ebeer_rf$predictions[,2]

Confusion matrix

confusion_matrix <- (table(ebeer$respmail, pred > 0.5))
confusion_matrix <- as.data.frame.matrix(confusion_matrix)
colnames(confusion_matrix) <- c("No", "Yes")
confusion_matrix$Percentage_Correct <- confusion_matrix[1,]$No/(confusion_matrix[1,]$No+confusion_matrix[1,]$Yes)*100
confusion_matrix[2,]$Percentage_Correct <- confusion_matrix[2,]$Yes/(confusion_matrix[2,]$No+confusion_matrix[2,]$Yes)*100
print(confusion_matrix) %>% 
  kbl() %>%
  kable_styling()
##     No Yes Percentage_Correct
## 0 4290  46              98.94
## 1  586  30               4.87
No Yes Percentage_Correct
0 4290 46 98.94
1 586 30 4.87
cat('Overall Percentage:', (confusion_matrix[1,1]+confusion_matrix[2,2])/nrow(ebeer)*100)
## Overall Percentage: 87.2

ROC

par(mfrow=c(1,1))
par(mai=c(.9,.8,.2,.2))
plot(roc(as.numeric(ebeer$respmail)-1, pred), print.auc=TRUE,
     col="black", lwd=1, main="ROC curve", xlab="Specificity: true negative rate", ylab="Sensitivity: true positive rate", xlim=c(1,0))
lines(roc(as.numeric(ebeer$respmail)-1, pred_tree_ebeer), print.auc=TRUE,  col="red", lwd=1)
legend('bottomright',legend=c("random forest", "decision tree"),col=c("black","red"), lwd=1)

Predictions on rollout data.

ebeer_rollout$p_rf <- predict(ebeer_rf, ebeer_rollout)$predictions[,2]
LS0tDQp0aXRsZTogIlR1dG9yaWFsIDQ6IFN1YnNldCBTZWxlY3Rpb24gJiBMQVNTTyINCmRhdGU6ICIyMDIzLTAxLTMwIg0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQpybShsaXN0PWxzKCkpDQpsaWJyYXJ5KHRyZWUpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShqYW5pdG9yKQ0KbGlicmFyeShjYXIpDQpsaWJyYXJ5KHBST0MpDQpsaWJyYXJ5KHJhbmdlcikNCmxpYnJhcnkoZ2xtbmV0KQ0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCg0Kb3B0aW9ucygic2NpcGVuIj0yMDAsICJkaWdpdHMiPTMpDQpgYGANCg0KRWJlZXINCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIHNldCB3b3JraW5nIGRpcmVjdG9yeSB1c2luZyBob3dldmVyIHlvdSB3YW50IHRvIGZvbGRlciB3aGVyZSBkYXRhIGlzIHN0b3JlZC4gIEknbGwgdXNlIA0KZWJlZXIgPC0gcmVhZF9jc3YoImViZWVyLmNzdiIpDQoNCiMgbG9hZCBlYmVlciwgcmVtb3ZlIGFjY291bnQgbnVtYmVyIGNvbHVtbg0KZWJlZXI8LWViZWVyWy1jKDEpXQ0KYGBgDQoNClRyYWluaW5nLVRlc3QgU2FtcGxlczoNCg0KYGBge3J9DQojIGRyb3AgdGhlIElEIGNvbHVtbiwgc2VsZWN0IGN1c3RvbWVycyB0aGF0IHJlY2VpdmVkIGEgbWFpbGluZyBvbmx5DQplYmVlcl90ZXN0PC1zdWJzZXQoZWJlZXIsIG1haWxpbmcgPT0xKQ0KDQojIGNyZWF0ZSBlYmVlciByb2xsb3V0IGRhdGENCmViZWVyX3JvbGxvdXQ8LXN1YnNldChlYmVlciwgbWFpbGluZyA9PTApDQoNCiMgcmVuYW1lIGViZWVyX3Rlc3QgZWJlZXINCmViZWVyPC1lYmVlcl90ZXN0DQpgYGANCg0KVGVsY28NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQojIGxvYWQgdGVsY28NCnRlbGNvIDwtIHJlYWRfY3N2KCJ0ZWxjby5jc3YiKQ0KdGVsY28gPC0gc3RyaW5nczJmYWN0b3JzKHRlbGNvKQ0KDQojIGRyb3AgSUQgY29sdW1uLCBkaXZpZGUgVG90YWwgY2hhcmdlcyBieSAxMDAwDQp0ZWxjbzwtc3Vic2V0KHRlbGNvLCBzZWxlY3Q9LWN1c3RvbWVySUQpDQp0ZWxjbyRUb3RhbENoYXJnZXM8LXRlbGNvJFRvdGFsQ2hhcmdlcy8xMDAwDQpgYGANCg0KVHJhaW5pbmctVGVzdDoNCg0KYGBge3J9DQojIGNyZWF0ZSA3MCUgdGVzdCBhbmQgMzAlIGhvbGRvdXQgc2FtcGxlDQpzZXQuc2VlZCgxOTEwMykNCm4gPC0gbnJvdyh0ZWxjbykNCnNhbXBsZSA8LSBzYW1wbGUoYyhUUlVFLCBGQUxTRSksIG4sIHJlcGxhY2U9VFJVRSwgcHJvYj1jKDAuNywgMC4zKSkNCnRlbGNvLnRlc3QgPC0gdGVsY29bc2FtcGxlLCBdDQp0ZWxjby5ob2xkb3V0IDwtIHRlbGNvWyFzYW1wbGUsIF0NCg0KI2NhbGwgdGVzdCB0ZWxjbywgYW5kIGZ1bGwgZGF0YSBzZXQgdGVsY28uYWxsDQp0ZWxjby5hbGw8LXRlbGNvDQp0ZWxjbzwtdGVsY28udGVzdA0KYGBgDQoNCiMjIyBEZWNpc2lvbiBUcmVlcw0KDQpXZSdsbCB1c2UgZWJlZXIgZm9yIHRoZSB0cmVlcy4gSSBmaXJzdCBydW4gYSB0cmVlIGFuZCBncmFwaCBpdCBzbyB3ZSBjYW4gdGFsayBhYm91dCB3aGF0IGl0IGlzLiBMYXRlciBvbiBJJ2xsIGV4cGxhaW4gdGhlIHBhcmFtZXRlcnMNCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQojIERWIG5lZWRzIHRvIGJlIGZhY3RvciB2YXJpYWJsZSBzbyBpdCBrbm93cyB0byB1c2UgYSBjbGFzc2lmaWNhdGlvbiB0cmVlDQp0cmVlPC10cmVlKGFzLmZhY3RvcihyZXNwbWFpbCkgfiAuLCBkYXRhPXN1YnNldChlYmVlciwgc2VsZWN0ID0gYyhyZXNwbWFpbCwgRiwgc3R1ZGVudCkpLG1pbmRldj0uMDA1KQ0KcGFyKG1mcm93PWMoMSwxKSkNCnBsb3QodHJlZSwgY29sPTgsIGx3ZD0yKQ0KIyBjZXggY29udHJvbHMgdGhlIHNpemUgb2YgdGhlIHR5cGUsIDEgaXMgdGhlIGRlZmF1bHQuICANCiMgbGFiZWw9Inlwcm9iIiBnaXZlcyB0aGUgcHJvYmFiaWxpdHkNCnRleHQodHJlZSwgbGFiZWwgPSAieXByb2IiLCBjZXg9Ljc1LCBmb250PTIsIGRpZ2l0cyA9IDIsIHByZXR0eT0wKQ0KYGBgDQoNCmBgYHtyfQ0KdHJlZSRmcmFtZQ0KYGBgDQoNCkhlcmUncyBob3cgdG8gbWFrZSBwcmVkaWN0aW9ucyB3aXRoIHRyZWVzLCB1c2luZyB0aGUgc2FtZSBkYXRhLg0KDQpgYGB7cn0NCnByZWRfdHJlZSA8LSBwcmVkaWN0KHRyZWUsZWJlZXIsIHR5cGUgPSAidmVjdG9yIikNCmhlYWQocHJlZF90cmVlKSAlPiUgDQogIGtibCgpICU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCmBgYA0KDQpMZXQncyBsb29rIGF0IHRoZSBjb25mdXNpb24gbWF0cml4Og0KDQpgYGB7cn0NCiMgdGFrZSBwcm9iYWJpbGl0eSB0aGF0IHJlc3BvbmRzDQpwcm9iX3Jlc3AgPC0gcHJlZF90cmVlWywyXQ0KDQojIHRoZXJlIGlzIG5vIHByZWRpY3RlZCBwcm9iYWJpbGl0eSBvdmVyIDAuNQ0Kc3VtKHByb2JfcmVzcCA+IDAuNSkNCg0KY29uZnVzaW9uX21hdHJpeCA8LSAodGFibGUoZWJlZXIkcmVzcG1haWwsIHByb2JfcmVzcCA+IDAuNSkpDQpjb25mdXNpb25fbWF0cml4IDwtIGFzLmRhdGEuZnJhbWUubWF0cml4KGNvbmZ1c2lvbl9tYXRyaXgpDQpjb2xuYW1lcyhjb25mdXNpb25fbWF0cml4KSA8LSBjKCJObyIpDQoNCmBgYA0KDQpgYGB7cn0NCmNvbmZ1c2lvbl9tYXRyaXggJT4lIA0KICBrYmwoKSAlPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KVGhlIG1pc2NsYXNzaWZpY2F0aW9uIHJhdGUgaXMgdGhlIHByb3BvcnRpb24gb2YgcmVzcG9uZGVycywgc2luY2UgYWxsIHByb2JhYmlsaXRpZXMgd2VyZSB1bmRlciAwLjUgc28gdGhlIG1vZGVsIHByZWRpY3RlZCBubyBvbmUgcmVzcG9uZGVkLiBUaGlzIGlzIHRoZSBzYW1lIG1pc3NjbGFzcy4gKFRoaXMgaXMgYSBnb29kIGV4YW1wbGUgd2hlcmUgdXNpbmcgdGhlIGRlZmF1bHQgdGhyZXNob2xkIG9mIDAuNSB3b3VsZCBiZSBub3Qgc28gZ29vZC4pDQoNCmBgYHtyfQ0KY29uZnVzaW9uX21hdHJpeFsxXS9zdW0oY29uZnVzaW9uX21hdHJpeCkNCmBgYA0KDQpSZXNpZHVhbCBtZWFuIGRldmlhbmNlIGlzIHRoZSB0b3RhbCByZXNpZHVhbCBkZXZpYW5jZSBkaXZpZGVkIGJ5IHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIC0gbnVtYmVyIG9mIHRlcm1pbmFsIG5vZGVzLCAkbi1kZiQuIFRoZSBkZXZpYW5jZSBpcyAyNTAwLg0KDQojIyMgVHJlZXMgaW4gUg0KDQpXZSdsbCB1c2UgdGhlIHRyZWUgcGFja2FnZSBpbiBSLiBIZXJlJ3MgaG93IHRoZSB0cmVlIGZpdHMgdGhlIGRhdGE6DQoNCmBgYHtyfQ0KIyBtZWFuIHR3byBncmFwaHMgc2lkZS1ieS1zaWRlDQpwYXIobWZyb3c9YygxLDIpLCBvbWE9YygwLDAsMiwwKSkNCiMgc2FtZSBtb2RlbCBhcyBhYm92ZQ0KDQp0cmVlIDwtIHRyZWUoYXMuZmFjdG9yKHJlc3BtYWlsKSB+IC4sIGRhdGE9c3Vic2V0KGViZWVyLCBzZWxlY3QgPSBjKHJlc3BtYWlsLCBGLCBzdHVkZW50KSksbWluZGV2PS4wMDUpDQoNCnBsb3QodHJlZSwgY29sPTgsIGx3ZD0yKQ0KDQojIGNleCBjb250cm9scyB0aGUgc2l6ZSBvZiB0aGUgdHlwZSwgMSBpcyB0aGUgZGVmYXVsdC4gIA0KIyBsYWJlbD0ieXByb2IiIGdpdmVzIHRoZSBwcm9iYWJpbGl0eQ0KdGV4dCh0cmVlLCBjZXg9Ljc1LCBsYWJlbD0ieXByb2IiLCBmb250PTIsIGRpZ2l0cyA9IDIsIHByZXR0eSA9IDApDQoNCg0KDQpwYXIobWFpPWMoLjgsLjgsLjIsLjIpKQ0KDQojIGNyZWF0ZSBhbiBhZ2dyZWdhdGUgdGFibGUgb2YgcmVzcG9uc2UgYnkgZnJlcXVlbmN5IGFuZCBzdHVkZW50DQp0Ymw8LSBlYmVlciAlPiUgZ3JvdXBfYnkoc3R1ZGVudCwgRikgJT4lIHN1bW1hcmlzZShtZWFuPW1lYW4ocmVzcG1haWwpKSAlPiUgZGF0YS5mcmFtZSgpDQpgYGANCg0KYGBge3J9DQpwcmVkPC1wcmVkaWN0KHRyZWUsdGJsLCB0eXBlID0gInZlY3RvciIpWywyXQ0KDQp0Ymw8LXRibCAlPiUgbXV0YXRlKHByZWQgPSBwcmVkKQ0KDQojIHBsb3QgaXQNCnBhcihtYWk9YyguOCwuOCwuMiwuMikpDQpwbG90KHRibCRGWzE6MTJdLHRibCRtZWFuWzE6MTJdLCBjb2wgPSAicmVkIiwgeGxhYj0iRnJlcXVlbmN5IiwgeWxhYj0ibWVhbiByZXNwb25zZSIseWxpbT1jKC0uMDUsMC41KSwgcGNoPTIwKQ0KcG9pbnRzKHRibCRGWzEzOjI0XSx0YmwkbWVhblsxMzoyNF0sIGNvbCA9ICJibHVlIiwgcGNoPTIwKQ0KbGVnZW5kKDcuNSwgMC41LCBsZWdlbmQ9YygiU3R1ZGVudCA9IG5vIiwgIlN0dWRlbnQ9IHllcyIpLCBjb2w9YygicmVkIiwgImJsdWUiKSwgcGNoPTIwLCBjZXg9MC44KQ0KDQojIGNyZWF0ZSBwcmVkaWN0aW9ucyBmcm9tIHRyZWUgZm9yIGV2ZXJ5IEYgeCBzdHVkZW50IGNvbWJvDQpuZXdGIDwtIHNlcSgxLDEyLGxlbmd0aD0xMikNCmxpbmVzKHRibCRGWzE6MTJdLCB0YmwkcHJlZFsxOjEyXSwgY29sPTIsIGx3ZD0yKQ0KbGluZXModGJsJEZbMToxMl0sIHRibCRwcmVkWzEzOjI0XSwgY29sPTQsIGx3ZD0yKQ0KbXRleHQoIkEgc2ltcGxlIHRyZWUiLG91dGVyPVRSVUUsY2V4PTEuNSkNCg0KYGBgDQoNCiMjIyBOb25wYXJhbWV0cmljDQoNClRoaXMgbWV0aG9kIGlzICoqbm9ucGFyYW1ldHJpYyoqIGJlY2F1c2UgaXQgZG9lc24ndCBtYWtlIGFuIGFzc3VtcHRpb24gYWJvdXQgdGhlIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiB0aGUgaW5kZXBlbmRlbnQgYW5kIGRlcGVuZGVudCB2YXJpYWJsZXMuDQoNCkJ5IHNldHRpbmcgdGhlIG1pbmRldiBhbmQgbWluY3V0IHRvIHplcm8sIHdlIGNhbiBtYWtlIHRoZSB0cmVlIHZlcnkgY29tcGxleCBhbmQgZml0IHRoZSBkYXRhIGFyYml0cmFyaWx5IGNsb3NlLg0KDQpgYGB7cn0NCiMgbWVhbiB0d28gZ3JhcGhzIHNpZGUtYnktc2lkZQ0KcGFyKG1mcm93PWMoMSwyKSwgb21hID0gYygwLCAwLCAyLCAwKSkNCiMgc2FtZSBtb2RlbCBhcyBhYm92ZQ0KdHJlZTwtdHJlZShhcy5mYWN0b3IocmVzcG1haWwpIH4gLiwgZGF0YT1zdWJzZXQoZWJlZXIsIHNlbGVjdCA9IGMocmVzcG1haWwsIEYsIHN0dWRlbnQpKSxtaW5kZXY9MCwgbWluY3V0PTApDQpwbG90KHRyZWUsIGNvbD04LCBsd2Q9MikNCiMgY2V4IGNvbnRyb2xzIHRoZSBzaXplIG9mIHRoZSB0eXBlLCAxIGlzIHRoZSBkZWZhdWx0LiAgDQojIGxhYmVsPSJ5cHJvYiIgZ2l2ZXMgdGhlIHByb2JhYmlsaXR5DQp0ZXh0KHRyZWUsIGNleD0uNSwgbGFiZWw9Inlwcm9iIiwgZm9udD0yLCBkaWdpdHMgPSAyLCBwcmV0dHkgPSAwKQ0KDQpwYXIobWFpPWMoLjgsLjgsLjIsLjIpKQ0KDQojIGNyZWF0ZSBhbiBhZ2dyZWdhdGUgdGFibGUgb2YgcmVzcG9uc2UgYnkgZnJlcXVlbmN5IGFuZCBzdHVkZW50DQp0Ymw8LSBlYmVlciAlPiUgZ3JvdXBfYnkoc3R1ZGVudCwgRikgJT4lIHN1bW1hcmlzZShtZWFuPW1lYW4ocmVzcG1haWwpKSAlPiUgZGF0YS5mcmFtZSgpDQoNCnByZWQ8LXByZWRpY3QodHJlZSx0YmwsIHR5cGUgPSAidmVjdG9yIilbLDJdDQoNCnRibDwtdGJsICU+JSBtdXRhdGUocHJlZCA9IHByZWQpDQoNCiMgcGxvdCBpdA0KcGFyKG1haT1jKC44LC44LC4yLC4yKSkNCnBsb3QodGJsJEZbMToxMl0sdGJsJG1lYW5bMToxMl0sIGNvbCA9ICJyZWQiLCB4bGFiPSJGcmVxdWVuY3kiLCB5bGFiPSJtZWFuIHJlc3BvbnNlIix5bGltPWMoLS4wNSwwLjUpLCBwY2g9MjApDQpwb2ludHModGJsJEZbMTM6MjRdLHRibCRtZWFuWzEzOjI0XSwgY29sID0gImJsdWUiLCBwY2g9MjApDQpsZWdlbmQoNy41LCAwLjUsIGxlZ2VuZD1jKCJTdHVkZW50ID0gbm8iLCAiU3R1ZGVudD0geWVzIiksIGNvbD1jKCJyZWQiLCAiYmx1ZSIpLCBwY2g9MjAsIGNleD0wLjgpDQoNCiMgY3JlYXRlIHByZWRpY3Rpb25zIGZyb20gdHJlZSBmb3IgZXZlcnkgRiB4IHN0dWRlbnQgY29tYm8NCm5ld0YgPC0gc2VxKDEsMTIsbGVuZ3RoPTEyKQ0KbGluZXModGJsJEZbMToxMl0sIHRibCRwcmVkWzE6MTJdLCBjb2w9MiwgbHdkPTIpDQpsaW5lcyh0YmwkRlsxOjEyXSwgdGJsJHByZWRbMTM6MjRdLCBjb2w9NCwgbHdkPTIpDQptdGV4dCgiQSBzaW1wbGUgdHJlZSIsb3V0ZXI9VFJVRSxjZXg9MS41KQ0KDQpgYGANCg0KIyMjIE92ZXJmaXR0aW5nICYgSy1mb2xkIGNyb3NzIHZhbGlkYXRpb24NCg0KV2UgYmVnaW4gYnkgZml0dGluZyBhIGNvbXBsaWNhdGVkIHRyZWUgYnkgc2V0dGluZyB0aGUgbWluZGV2PTAgYW5kIG1pbmN1dCB0byBzb21lIGxvdyBudW1iZXIgb3IgemVyby4NCg0KYGBge3J9DQp0cmVlX2NvbXBsZXg8LXRyZWUoYXMuZmFjdG9yKHJlc3BtYWlsKSB+IC4gLCBkYXRhPWViZWVyLCBtaW5kZXY9MCwgbWluY3V0PTEwMCkNCg0KcGFyKG1mcm93PWMoMSwxKSkNCnBhcihtYWk9YyguOCwuOCwuMiwuMikpDQpwbG90KHRyZWVfY29tcGxleCwgY29sPTEwLCBsd2Q9MikNCnRleHQodHJlZV9jb21wbGV4LCBjZXg9LjUsIGxhYmVsPSJ5cHJvYiIsIGZvbnQ9MiwgZGlnaXRzID0gMiwgcHJldHR5ID0gMCkNCnRpdGxlKG1haW49IkNsYXNzaWZpY2F0aW9uIFRyZWU6IGNvbXBsZXgiKQ0KYGBgDQoNCkkgc3BlY2lmeSBLPTEwIGNyb3NzIGZvbGQgdmFsaWRhdGlvbi4gVGhlIHNpemUgaXMgdGhlIHJlc3VsdGluZyBudW1iZXIgb2YgbGVhdmVzLiBUaGUgb3V0LW9mLXNhbXBsZSBlcnJvciBtZWFzdXJlIGlzIHRoZSBkZXZpYW5jZS4gV2Ugd2FudCB0aGF0IGFzIGxvdyBhcyBwb3NzaWJsZS4NCg0KYGBge3J9DQpjdi50cmVlX2NvbXBsZXg8LWN2LnRyZWUodHJlZV9jb21wbGV4LCBLPTEwKQ0KY3YudHJlZV9jb21wbGV4JHNpemUNCnJvdW5kKGN2LnRyZWVfY29tcGxleCRkZXYpDQpwYXIobWZyb3c9YygxLDEpKQ0KcGxvdChjdi50cmVlX2NvbXBsZXgkc2l6ZSwgY3YudHJlZV9jb21wbGV4JGRldiwgeGxhYj0idHJlZSBzaXplIChjb21wbGV4aXR5KSIsIHlsYWI9Ik91dC1vZi1zYW1wbGUgZGV2aWFuY2UgKGVycm9yKSIsIHBjaD0yMCkNCmBgYA0KDQpDaG9vc2UgdGhlIHRyZWUgd2l0aCB0aGUgbWluaW11bSBvdXQtb2Ytc2FtcGxlIGVycm9yLiBIZXJlIHRoZSBlcnJvciByZW1haW5zIHRoZSBzYW1lIGFmdGVyIDQuIFNvIEkgY2hvb3NlIHRoZSBzaW1wbGVzdCBtb2RlbCB3aXRoIHRoZSBsb3dlc3QgT09TIGVycm9yLCBhIHRyZWUgd2l0aCA0IGxlYXZlcy4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpwYXIobWZyb3c9YygxLDIpLCBvbWEgPSBjKDAsIDAsIDIsIDApKQ0KdHJlZV9jdXQ8LXBydW5lLnRyZWUodHJlZV9jb21wbGV4LCBiZXN0PTQpDQpwbG90KHRyZWVfY3V0LCBjb2w9MTAsIGx3ZD0yKQ0KdGV4dCh0cmVlX2N1dCwgY2V4PTEsIGxhYmVsPSJ5cHJvYiIsIGZvbnQ9MiwgZGlnaXRzID0gMiwgcHJldHR5ID0gMCkNCnRpdGxlKG1haW49IkEgcHJ1bmVkIHRyZWUiKQ0Kc3VtbWFyeSh0cmVlX2N1dCkNCg0KcHJlZF90cmVlX2ViZWVyPC1wcmVkaWN0KHRyZWVfY3V0LCBkYXRhPWViZWVyKVssMl0NCg0KcGxvdChyb2MoZWJlZXIkcmVzcG1haWwsIHByZWRfdHJlZV9lYmVlciksIHByaW50LmF1Yz1UUlVFLA0KICAgICBjb2w9ImJsYWNrIiwgbHdkPTEsIG1haW49IlJPQyBjdXJ2ZSIsIHhsYWI9IlNwZWNpZmljaXR5OiB0cnVlIG5lZ2F0aXZlIHJhdGUiLCB5bGFiPSJTZW5zaXRpdml0eTogdHJ1ZSBwb3NpdGl2ZSByYXRlIiwgeGxpbT1jKDEsMCkpDQoNCg0KYGBgDQoNCioqVGVsY28qKjogSW4gb3RoZXIgZGF0YSBzZXRzLCB0aGUgIm9wdGltYWwiIHNpemUgbWF5IGJlIGRpZmZlcmVudC4gSGVyZSBpdCBpcyB3aXRoIHRlbGNvLg0KDQpgYGB7cn0NCiMgbWFrZSBhIHNvbWV3aGF0IGJpZyB0cmVlDQpwYXIobWZyb3c9YygxLDIpLG9tYSA9IGMoMCwgMCwgMiwgMCkpDQp0cmVlX3RlbGNvPC10cmVlKENodXJuIH4gLiwgZGF0YT10ZWxjbywgbWluZGV2PTAuMDA1LCBtaW5jdXQ9MCkNCnBsb3QodHJlZV90ZWxjbywgY29sPTEwLCBsd2Q9MikNCnRleHQodHJlZV90ZWxjbywgY2V4PS40LCBmb250PTEsIGRpZ2l0cyA9IDIsIHByZXR0eSA9IDApDQoNCmN2LnRyZWVfdGVsY288LWN2LnRyZWUodHJlZV90ZWxjbywgSz0xMCkNCmN2LnRyZWVfdGVsY28NCg0KcGxvdChjdi50cmVlX3RlbGNvJHNpemUsIGN2LnRyZWVfdGVsY28kZGV2LCB4bGFiPSJ0cmVlIHNpemUgKGNvbXBsZXhpdHkpIiwgeWxhYj0iT3V0LW9mLXNhbXBsZSBkZXZpYW5jZSAoZXJyb3IpIiwgcGNoPTIwKQ0KbXRleHQoIkFub3RoZXIgZXhhbXBsZTogdGVsY28iLG91dGVyPVRSVUUsY2V4PTEuNSkNCmBgYA0KDQpUaGUgZGV2aWFuY2UgZG9lc24ndCBkZWNyZWFzZSBhbnkgbW9yZSBvbmNlIHdlIGdldCB0byA2IGxlYXZlcy4gU28gSSdsbCBzZXQgNiBhcyB0aGUgYmVzdC4NCg0KYGBge3J9DQoNCnBhcihtZnJvdz1jKDEsMSkpDQp0cmVlX2N1dDwtcHJ1bmUudHJlZSh0cmVlX3RlbGNvLCBiZXN0PTYpDQpwbG90KHRyZWVfY3V0LCBjb2w9MTAsIGx3ZD0yKQ0KdGV4dCh0cmVlX2N1dCwgY2V4PTEsIGZvbnQ9MSwgZGlnaXRzID0gMiwgcHJldHR5ID0gMCwgbGFiZWw9Inlwcm9iIikNCmBgYA0KDQpXaXRoIHRoZSB0ZWxjbyBkYXRhIGl0IGxvb2tzIGxpa2UgYW55IG51bWJlciBvZiBsZWF2ZXMgYWZ0ZXIgNiBnaXZlcyB5b3UgdGhlIHNhbWUgT09TIHBlcmZvcm1hbmNlLiBTbywgZ29pbmcgZm9yd2FyZCwgdGhlIHNpbXBsZXN0IG1vZGVsIHdpdGggdGhlIGJlc3QgT09TIHBlcmZvcm1hbmNlIGlzIDYuDQoNCiMjIyBSYW5kb20gRm9yZXN0cw0KDQpXZSB3aWxsIGZpdCByYW5kb20gZm9yZXN0cyBpbiBSLiBXZSBzcGVjaWZ5IHRoZSBudW1iZXIgb2YgdHJlZXMgYW5kIHRoZSBtaW5pbXVtIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgaW4gYSBsZWFmICgyNSksIGFzIHdlbGwgYXMgdGhlIGltcG9ydGFuY2UgLg0KDQpgYGB7cn0NCmViZWVyX3JmPC0gcmFuZ2VyKHJlc3BtYWlsIH4gLiwgZGF0YT1lYmVlciwgd3JpdGUuZm9yZXN0PVRSVUUsIG51bS50cmVlcyA9IDEwMDAsIG1pbi5ub2RlLnNpemUgPSAyNSwgaW1wb3J0YW5jZSA9ICJpbXB1cml0eSIsIHByb2JhYmlsaXR5PVRSVUUsIHNlZWQgPSAxOTEwMykNCmBgYA0KDQpWYXJpYWJsZSBpbXBvcnRhbmNlOg0KDQpgYGB7cn0NCnBhcihtZnJvdz1jKDEsMSkpDQpwYXIobWFpPWMoLjksLjgsLjIsLjIpKQ0Kc29ydChlYmVlcl9yZiR2YXJpYWJsZS5pbXBvcnRhbmNlLCBkZWNyZWFzaW5nID0gVFJVRSkNCmJhcnBsb3Qoc29ydChlYmVlcl9yZiR2YXJpYWJsZS5pbXBvcnRhbmNlLCBkZWNyZWFzaW5nID0gVFJVRSksIHlsYWIgPSAidmFyaWFibGUgaW1wb3J0YW5jZSIpDQpgYGANCg0KUHJlZGljdGlvbnMgb24gbmV3IGRhdGEgdXNpbmcgb3V0LW9mLXRoZS1iYWcgKE9PQikgc2FtcGxlcy4NCg0KYGBge3J9DQpoZWFkKGViZWVyX3JmJHByZWRpY3Rpb25zKSAlPiUgDQogIGtibCgpICU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCg0KcHJlZCA8LSBlYmVlcl9yZiRwcmVkaWN0aW9uc1ssMl0NCmBgYA0KDQpDb25mdXNpb24gbWF0cml4DQoNCmBgYHtyfQ0KY29uZnVzaW9uX21hdHJpeCA8LSAodGFibGUoZWJlZXIkcmVzcG1haWwsIHByZWQgPiAwLjUpKQ0KY29uZnVzaW9uX21hdHJpeCA8LSBhcy5kYXRhLmZyYW1lLm1hdHJpeChjb25mdXNpb25fbWF0cml4KQ0KY29sbmFtZXMoY29uZnVzaW9uX21hdHJpeCkgPC0gYygiTm8iLCAiWWVzIikNCmNvbmZ1c2lvbl9tYXRyaXgkUGVyY2VudGFnZV9Db3JyZWN0IDwtIGNvbmZ1c2lvbl9tYXRyaXhbMSxdJE5vLyhjb25mdXNpb25fbWF0cml4WzEsXSRObytjb25mdXNpb25fbWF0cml4WzEsXSRZZXMpKjEwMA0KY29uZnVzaW9uX21hdHJpeFsyLF0kUGVyY2VudGFnZV9Db3JyZWN0IDwtIGNvbmZ1c2lvbl9tYXRyaXhbMixdJFllcy8oY29uZnVzaW9uX21hdHJpeFsyLF0kTm8rY29uZnVzaW9uX21hdHJpeFsyLF0kWWVzKSoxMDANCnByaW50KGNvbmZ1c2lvbl9tYXRyaXgpICU+JSANCiAga2JsKCkgJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KDQoNCmBgYA0KDQpgYGB7cn0NCmNhdCgnT3ZlcmFsbCBQZXJjZW50YWdlOicsIChjb25mdXNpb25fbWF0cml4WzEsMV0rY29uZnVzaW9uX21hdHJpeFsyLDJdKS9ucm93KGViZWVyKSoxMDApDQpgYGANCg0KUk9DDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcGFyKG1mcm93PWMoMSwxKSkNCnBhcihtYWk9YyguOSwuOCwuMiwuMikpDQpwbG90KHJvYyhhcy5udW1lcmljKGViZWVyJHJlc3BtYWlsKS0xLCBwcmVkKSwgcHJpbnQuYXVjPVRSVUUsDQogICAgIGNvbD0iYmxhY2siLCBsd2Q9MSwgbWFpbj0iUk9DIGN1cnZlIiwgeGxhYj0iU3BlY2lmaWNpdHk6IHRydWUgbmVnYXRpdmUgcmF0ZSIsIHlsYWI9IlNlbnNpdGl2aXR5OiB0cnVlIHBvc2l0aXZlIHJhdGUiLCB4bGltPWMoMSwwKSkNCmxpbmVzKHJvYyhhcy5udW1lcmljKGViZWVyJHJlc3BtYWlsKS0xLCBwcmVkX3RyZWVfZWJlZXIpLCBwcmludC5hdWM9VFJVRSwgIGNvbD0icmVkIiwgbHdkPTEpDQpsZWdlbmQoJ2JvdHRvbXJpZ2h0JyxsZWdlbmQ9YygicmFuZG9tIGZvcmVzdCIsICJkZWNpc2lvbiB0cmVlIiksY29sPWMoImJsYWNrIiwicmVkIiksIGx3ZD0xKQ0KYGBgDQoNClByZWRpY3Rpb25zIG9uIHJvbGxvdXQgZGF0YS4NCg0KYGBge3J9DQplYmVlcl9yb2xsb3V0JHBfcmYgPC0gcHJlZGljdChlYmVlcl9yZiwgZWJlZXJfcm9sbG91dCkkcHJlZGljdGlvbnNbLDJdDQpgYGANCg==