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()
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==