Question 1
Let’s use a decision tree to predict churn. Use the data set telco_test.csv.
Make sure all the string variables are loaded as factors by using the
following statement:
telco <- read.csv('telco_test.csv', stringsAsFactors = TRUE)
# Churn as dummy
Churn.num <- as.numeric(as.factor(telco$Churn))-1
rbar <- mean(Churn.num)
Fit a tree that stops growing when the child branch has below 50
observations or when the deviance improves by less than 0.001.
What variables are used in this tree? Check all that
apply.
tree <- tree(as.factor(Churn) ~., data = telco, mindev=0.001, mincut=50)
summary(tree)
##
## Classification tree:
## tree(formula = as.factor(Churn) ~ ., data = telco, mindev = 0.001,
## mincut = 50)
## Variables actually used in tree construction:
## [1] "Contract" "InternetService" "StreamingMovies" "TotalCharges" "PaymentMethod" "tenure" "MonthlyCharges" "PaperlessBilling" "PhoneService" "MultipleLines"
## Number of terminal nodes: 34
## Residual mean deviance: 0.798 = 3920 / 4920
## Misclassification error rate: 0.191 = 946 / 4949
par(mfrow=c(1,1))
plot(tree, col=8, lwd=2)
text(tree, label = "yprob", cex=.75, font=2, digits = 2, pretty=0)
Question 2
According to this model, what is the probability that a female senior
citizen, with no partners, no dependents, who has been a customer for 12
months, with phone service, without multiple lines, with DSL, no online
security, no online backup, no device protection, with tech support and
streaming TV, without streaming movies, on a one year contract, no
paperless billing, pays by mailed check, with monthly charges of 30 and
total charges of 1000, churns?
Provide your answer with a dot and two decimals
(e.g. 0.12)
newdata1 <- data.frame(gender = as.factor(2), SeniorCitizen=1,
Partner=as.factor(1), Dependents=as.factor(1),
tenure=12,PhoneService=as.factor(2),
MultipleLines=as.factor(1), InternetService=as.factor(2),
OnlineSecurity=as.factor(1), OnlineBackup=as.factor(1),
DeviceProtection=as.factor(1), TechSupport=as.factor(2),
StreamingTV=as.factor(2), StreamingMovies=as.factor(1),
Contract=as.factor(levels(telco$Contract))[2],
PaperlessBilling=as.factor(1), PaymentMethod=as.factor(1),
MonthlyCharges=30, TotalCharges=10)
Now we can predict:
pred_tree <- predict(tree, newdata = newdata1, type="vector")
round(pred_tree[2],2)
## [1] 0.04
Question 3
What’s the percent of correctly predicted churners, the true
positive rate, using a threshold of 0.5 to classify
predictions?
Provide your answer without a percent sign and with zero decimals
(e.g. 12)
We predict again, but now using all the data:
pred_tree <- predict(tree, newdata = telco, type="vector")
prob_resp <- pred_tree[,2]
sum(prob_resp>0.5) # but there is no above 0.5
## [1] 1109
confusion_matrix <- (table(telco$Churn, prob_resp > 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)
## No Yes Percentage_Correct
## No 3272 378 89.6
## Yes 568 731 56.3
## Hit Rate: 56.3
Question 4
We’re now going to use 10-fold cross-validation to prune the tree.
Start with the most complex tree by setting mindev and mincut to 0. Use
10 fold cross-validation to find the smallest tree that has the lowest
deviance rounded to the first whole number.
How many leaves (terminal nodes) are in this
tree?
We start with the complex tree:
tree_complex <- tree(as.factor(Churn) ~., data = telco, mindev=0, mincut=0)
we now can do the cross-validation K=10:
cv.tree.complex <- cv.tree(tree_complex, K=10)
How many leaves?
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)
Question 5
According to this pruned model, what’s the probability of
someone with a one-year contract, who has been a customer for 12 months,
and has no internet service, churns?
Provide your answer with a dot and three decimals
(e.g. 0.123)
We choose our best model with 6 leaves:
tree_cut <- prune.tree(tree_complex, best=6)
summary(tree_cut)
##
## Classification tree:
## snip.tree(tree = tree_complex, nodes = c(13L, 15L, 5L, 4L, 12L,
## 14L))
## Variables actually used in tree construction:
## [1] "Contract" "InternetService" "tenure"
## Number of terminal nodes: 6
## Residual mean deviance: 0.872 = 4310 / 4940
## Misclassification error rate: 0.209 = 1034 / 4949
We can predict now:
par(mfrow=c(1,2), oma = c(0, 0, 2, 0))
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")
Question 6
Fit a random forest to the data, with 1000 trees, minimum node size
of 25, using average probabilities rather than classifications
(probability = TRUE).
What is the most important variable according to this? Give
the variable name.
telco_rf <- ranger(Churn ~ ., data = telco,
write.forest=TRUE,
num.trees = 1000,
min.node.size = 25,
importance = "impurity",
probability=TRUE,
seed = 19103)
Variable Importance:
sort(telco_rf$variable.importance, decreasing = TRUE)
## tenure MonthlyCharges Contract TotalCharges InternetService PaymentMethod TechSupport OnlineSecurity PaperlessBilling SeniorCitizen MultipleLines OnlineBackup StreamingTV Dependents StreamingMovies gender Partner
## 187.23 168.65 163.55 158.63 72.32 48.20 28.58 27.63 25.72 19.50 16.37 15.92 15.68 15.52 14.03 13.89 13.39
## DeviceProtection PhoneService
## 11.63 6.67
par(mfrow=c(1,1))
par(mai=c(.9,.8,.2,.2))
barplot(sort(telco_rf$variable.importance, decreasing = TRUE), ylab = "variable importance")
Question 7
Apply the most complex tree that you started with in question 4; also
apply the pruned tree and the random forest to the
holdout data set.
telco.holdout <- read.csv('telco_holdout.csv', stringsAsFactors = TRUE)
# Churn as dummy
Churn.num <- as.numeric(as.factor(telco.holdout$Churn))-1
What is the area under the curve for the tree?
Provide your answer with a dot and two decimals
(e.g. 0.12)
Let’s apply every model at once to the holdout:
#take only "Yes"
pred_cut <- predict(tree_cut, newdata=telco.holdout, type="vector")[,2]
pred_complex <- predict(tree_complex, newdata=telco.holdout, type="vector")[,2]
pred_rf <- predict(telco_rf, data=telco.holdout)$predictions[,2]
# Observed churn as number
churn.num <- as.numeric(telco.holdout$Churn)-1
Area Under the Curve:
roc(churn.num, pred_cut)
##
## Call:
## roc.default(response = churn.num, predictor = pred_cut)
##
## Data: pred_cut in 1513 controls (churn.num 0) < 570 cases (churn.num 1).
## Area under the curve: 0.809
par(mfrow=c(1,3))
par(mai=c(.9,.8,.2,.2))
plot(roc(as.numeric(telco.holdout$Churn)-1, pred_cut), print.auc=TRUE, ylim=c(0,1),
col="black", lwd=1, main="ROC curve", xlab="Specificity: true negative rate", ylab="Sensitivity: true positive rate", xlim=c(1,0))
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
Question 8
What is the area under the curve for the random
forest?
Provide your answer with a dot and two decimals
(e.g. 0.12)
roc(churn.num, pred_rf)
##
## Call:
## roc.default(response = churn.num, predictor = pred_rf)
##
## Data: pred_rf in 1513 controls (churn.num 0) < 570 cases (churn.num 1).
## Area under the curve: 0.84
Question 9
What is the area under the curve for the complex
tree?
Provide your answer with a dot and two decimals
(e.g. 0.12)
round(roc(churn.num, pred_complex)$auc,2)
## [1] 0.7
Question 10
Using the random forest in the holdout data, if you were targeting
the top 3 deciles, what percentage of total churners would you
target?
Provide your answer without a percent sign and with zero decimals
(e.g. 12)
We start creating the deciles:
ntiles <- function(x, bins) {
quantiles = seq(from=0, to = 1, length.out=bins+1)
cut(ecdf(x)(x),breaks=quantiles, labels=F)
}
# Deciles
telco.holdout$prob <-pred_rf
prob_decile <- ntiles(telco.holdout$prob, 10)
# Churn as numeric:
telco.holdout$Churn.num <- as.numeric(as.factor(telco.holdout$Churn))-1
# Dataset:
tbl <- data.frame(cbind(telco.holdout$prob, prob_decile, telco.holdout$Churn.num))
colnames(tbl)<-c("predicted","decile", "actual")
Create lift table by decile
# First: average churn rate by decile
lift <- aggregate(actual~decile, data = tbl, mean)
colnames(lift)[2]<-"actual churn rate"
# lift is the actual churn rate in the decile divided by average overall churn rate
lift[,3]<-lift[,2]/mean(telco.holdout$Churn.num)
colnames(lift)[3]<-"lift"
# order for highest to lowest
lift<-lift[order(-lift$decile),]
lift[,4]<-cumsum(lift$actual)/sum(lift$actual)*100
colnames(lift)[4]<-"cumulative lift"
lift %>%
kbl() %>%
kable_styling()
|
decile
|
actual churn rate
|
lift
|
cumulative lift
|
10
|
10
|
0.761
|
2.780
|
27.8
|
9
|
9
|
0.606
|
2.214
|
50.0
|
8
|
8
|
0.418
|
1.529
|
65.3
|
7
|
7
|
0.359
|
1.311
|
78.4
|
6
|
6
|
0.226
|
0.826
|
86.7
|
5
|
5
|
0.149
|
0.545
|
92.1
|
4
|
4
|
0.105
|
0.385
|
96.0
|
3
|
3
|
0.072
|
0.264
|
98.6
|
2
|
2
|
0.019
|
0.070
|
99.3
|
1
|
1
|
0.019
|
0.070
|
100.0
|
LS0tDQp0aXRsZTogIlF1aXogNDogU3Vic2V0IFNlbGVjdGlvbiwgTEFTU08gJiBUcmVlcyINCmF1dGhvcjogIkRhbmllbCBSZWRlbCINCmRhdGU6ICIyMDIzLTAxLTI1Ig0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpybShsaXN0PWxzKCkpDQpsaWJyYXJ5KHRyZWUpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShqYW5pdG9yKQ0KbGlicmFyeShjYXIpDQpsaWJyYXJ5KHBST0MpDQpsaWJyYXJ5KHJhbmdlcikNCmxpYnJhcnkoZ2xtbmV0KQ0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCg0Kb3B0aW9ucygic2NpcGVuIj0yMDAsICJkaWdpdHMiPTMpDQpgYGANCg0KIyBRdWVzdGlvbiAxDQoNCkxldCdzIHVzZSBhIGRlY2lzaW9uIHRyZWUgdG8gcHJlZGljdCBjaHVybi4gVXNlIHRoZSBkYXRhIHNldCBbdGVsY29fdGVzdC5jc3ZdKGh0dHBzOi8vdGlsYnVyZ3VuaXZlcnNpdHkuaW5zdHJ1Y3R1cmUuY29tL2NvdXJzZXMvMTA5MTkvZmlsZXMvMTk0ODk2NT93cmFwPTEpLiBNYWtlIHN1cmUgYWxsIHRoZSBzdHJpbmcgdmFyaWFibGVzIGFyZSBsb2FkZWQgYXMgZmFjdG9ycyBieSB1c2luZyB0aGUgZm9sbG93aW5nIHN0YXRlbWVudDoNCg0KYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQp0ZWxjbyA8LSByZWFkLmNzdigndGVsY29fdGVzdC5jc3YnLCBzdHJpbmdzQXNGYWN0b3JzID0gVFJVRSkNCg0KIyBDaHVybiBhcyBkdW1teQ0KQ2h1cm4ubnVtIDwtIGFzLm51bWVyaWMoYXMuZmFjdG9yKHRlbGNvJENodXJuKSktMQ0KcmJhciA8LSBtZWFuKENodXJuLm51bSkNCmBgYA0KDQpGaXQgYSB0cmVlIHRoYXQgc3RvcHMgZ3Jvd2luZyB3aGVuIHRoZSBjaGlsZCBicmFuY2ggaGFzIGJlbG93IDUwIG9ic2VydmF0aW9ucyBvciB3aGVuIHRoZSBkZXZpYW5jZSBpbXByb3ZlcyBieSBsZXNzIHRoYW4gMC4wMDEuDQoNCioqV2hhdCB2YXJpYWJsZXMgYXJlIHVzZWQgaW4gdGhpcyB0cmVlPyBDaGVjayBhbGwgdGhhdCBhcHBseS4qKg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCnRyZWUgPC0gdHJlZShhcy5mYWN0b3IoQ2h1cm4pIH4uLCBkYXRhID0gdGVsY28sIG1pbmRldj0wLjAwMSwgbWluY3V0PTUwKQ0Kc3VtbWFyeSh0cmVlKQ0KYGBgDQoNCmBgYHtyfQ0KcGFyKG1mcm93PWMoMSwxKSkNCnBsb3QodHJlZSwgY29sPTgsIGx3ZD0yKQ0KdGV4dCh0cmVlLCBsYWJlbCA9ICJ5cHJvYiIsIGNleD0uNzUsIGZvbnQ9MiwgZGlnaXRzID0gMiwgcHJldHR5PTApDQpgYGANCg0KIyBRdWVzdGlvbiAyDQoNCkFjY29yZGluZyB0byB0aGlzIG1vZGVsLCB3aGF0IGlzIHRoZSBwcm9iYWJpbGl0eSB0aGF0IGEgZmVtYWxlIHNlbmlvciBjaXRpemVuLCB3aXRoIG5vIHBhcnRuZXJzLCBubyBkZXBlbmRlbnRzLCB3aG8gaGFzIGJlZW4gYSBjdXN0b21lciBmb3IgMTIgbW9udGhzLCB3aXRoIHBob25lIHNlcnZpY2UsIHdpdGhvdXQgbXVsdGlwbGUgbGluZXMsIHdpdGggRFNMLCBubyBvbmxpbmUgc2VjdXJpdHksIG5vIG9ubGluZSBiYWNrdXAsIG5vIGRldmljZSBwcm90ZWN0aW9uLCB3aXRoIHRlY2ggc3VwcG9ydCBhbmQgc3RyZWFtaW5nIFRWLCB3aXRob3V0IHN0cmVhbWluZyBtb3ZpZXMsIG9uIGEgb25lIHllYXIgY29udHJhY3QsIG5vIHBhcGVybGVzcyBiaWxsaW5nLCBwYXlzIGJ5IG1haWxlZCBjaGVjaywgd2l0aCBtb250aGx5IGNoYXJnZXMgb2YgMzAgYW5kIHRvdGFsIGNoYXJnZXMgb2YgMTAwMCwgY2h1cm5zPw0KDQoqUHJvdmlkZSB5b3VyIGFuc3dlciB3aXRoIGEgZG90IGFuZCB0d28gZGVjaW1hbHMgKGUuZy4gMC4xMikqDQoNCmBgYHtyfQ0KbmV3ZGF0YTEgPC0gZGF0YS5mcmFtZShnZW5kZXIgPSBhcy5mYWN0b3IoMiksIFNlbmlvckNpdGl6ZW49MSwgDQogICAgICAgICAgICAgICAgICAgICAgIFBhcnRuZXI9YXMuZmFjdG9yKDEpLCBEZXBlbmRlbnRzPWFzLmZhY3RvcigxKSwgDQogICAgICAgICAgICAgICAgICAgICAgIHRlbnVyZT0xMixQaG9uZVNlcnZpY2U9YXMuZmFjdG9yKDIpLA0KICAgICAgICAgICAgICAgICAgICAgICBNdWx0aXBsZUxpbmVzPWFzLmZhY3RvcigxKSwgSW50ZXJuZXRTZXJ2aWNlPWFzLmZhY3RvcigyKSwgDQogICAgICAgICAgICAgICAgICAgICAgIE9ubGluZVNlY3VyaXR5PWFzLmZhY3RvcigxKSwgT25saW5lQmFja3VwPWFzLmZhY3RvcigxKSwNCiAgICAgICAgICAgICAgICAgICAgICAgRGV2aWNlUHJvdGVjdGlvbj1hcy5mYWN0b3IoMSksIFRlY2hTdXBwb3J0PWFzLmZhY3RvcigyKSwgDQogICAgICAgICAgICAgICAgICAgICAgIFN0cmVhbWluZ1RWPWFzLmZhY3RvcigyKSwgU3RyZWFtaW5nTW92aWVzPWFzLmZhY3RvcigxKSwgDQogICAgICAgICAgICAgICAgICAgICAgIENvbnRyYWN0PWFzLmZhY3RvcihsZXZlbHModGVsY28kQ29udHJhY3QpKVsyXSwgDQogICAgICAgICAgICAgICAgICAgICAgIFBhcGVybGVzc0JpbGxpbmc9YXMuZmFjdG9yKDEpLCBQYXltZW50TWV0aG9kPWFzLmZhY3RvcigxKSwgDQogICAgICAgICAgICAgICAgICAgICAgIE1vbnRobHlDaGFyZ2VzPTMwLCBUb3RhbENoYXJnZXM9MTApDQpgYGANCg0KTm93IHdlIGNhbiBwcmVkaWN0Og0KDQpgYGB7cn0NCnByZWRfdHJlZSA8LSBwcmVkaWN0KHRyZWUsIG5ld2RhdGEgPSBuZXdkYXRhMSwgdHlwZT0idmVjdG9yIikNCnJvdW5kKHByZWRfdHJlZVsyXSwyKQ0KYGBgDQoNCiMgUXVlc3Rpb24gMw0KDQoqKldoYXQncyB0aGUgcGVyY2VudCBvZiBjb3JyZWN0bHkgcHJlZGljdGVkIGNodXJuZXJzLCB0aGUgdHJ1ZSBwb3NpdGl2ZSByYXRlLCB1c2luZyBhIHRocmVzaG9sZCBvZiAwLjUgdG8gY2xhc3NpZnkgcHJlZGljdGlvbnM/KioNCg0KKlByb3ZpZGUgeW91ciBhbnN3ZXIgd2l0aG91dCBhIHBlcmNlbnQgc2lnbiBhbmQgd2l0aCB6ZXJvIGRlY2ltYWxzIChlLmcuIDEyKSoNCg0KV2UgcHJlZGljdCBhZ2FpbiwgYnV0IG5vdyB1c2luZyBhbGwgdGhlIGRhdGE6DQoNCmBgYHtyfQ0KcHJlZF90cmVlIDwtIHByZWRpY3QodHJlZSwgbmV3ZGF0YSA9IHRlbGNvLCB0eXBlPSJ2ZWN0b3IiKQ0KYGBgDQoNCmBgYHtyfQ0KcHJvYl9yZXNwIDwtIHByZWRfdHJlZVssMl0NCnN1bShwcm9iX3Jlc3A+MC41KSAjIGJ1dCB0aGVyZSBpcyBubyBhYm92ZSAwLjUNCg0KDQpjb25mdXNpb25fbWF0cml4IDwtICh0YWJsZSh0ZWxjbyRDaHVybiwgcHJvYl9yZXNwID4gMC41KSkNCmNvbmZ1c2lvbl9tYXRyaXggPC0gYXMuZGF0YS5mcmFtZS5tYXRyaXgoY29uZnVzaW9uX21hdHJpeCkNCg0KY29sbmFtZXMoY29uZnVzaW9uX21hdHJpeCkgPC0gYygiTm8iLCAiWWVzIikNCg0KY29uZnVzaW9uX21hdHJpeCRQZXJjZW50YWdlX0NvcnJlY3QgPC0gY29uZnVzaW9uX21hdHJpeFsxLF0kTm8vKGNvbmZ1c2lvbl9tYXRyaXhbMSxdJE5vK2NvbmZ1c2lvbl9tYXRyaXhbMSxdJFllcykqMTAwDQpjb25mdXNpb25fbWF0cml4WzIsXSRQZXJjZW50YWdlX0NvcnJlY3QgPC0gY29uZnVzaW9uX21hdHJpeFsyLF0kWWVzLyhjb25mdXNpb25fbWF0cml4WzIsXSRObytjb25mdXNpb25fbWF0cml4WzIsXSRZZXMpKjEwMA0KDQpgYGANCg0KYGBge3J9DQpwcmludChjb25mdXNpb25fbWF0cml4KQ0KYGBgDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KY2F0KCdIaXQgUmF0ZTonLCAoY29uZnVzaW9uX21hdHJpeFsyLDJdLyhjb25mdXNpb25fbWF0cml4WzIsMV0rY29uZnVzaW9uX21hdHJpeFsyLDJdKSkqMTAwKQ0KYGBgDQoNCiMgUXVlc3Rpb24gNA0KDQpXZSdyZSBub3cgZ29pbmcgdG8gdXNlIDEwLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiB0byBwcnVuZSB0aGUgdHJlZS4gU3RhcnQgd2l0aCB0aGUgbW9zdCBjb21wbGV4IHRyZWUgYnkgc2V0dGluZyBtaW5kZXYgYW5kIG1pbmN1dCB0byAwLiBVc2UgMTAgZm9sZCBjcm9zcy12YWxpZGF0aW9uIHRvIGZpbmQgdGhlIHNtYWxsZXN0IHRyZWUgdGhhdCBoYXMgdGhlIGxvd2VzdCBkZXZpYW5jZSByb3VuZGVkIHRvIHRoZSBmaXJzdCB3aG9sZSBudW1iZXIuIMKgDQoNCioqSG93IG1hbnkgbGVhdmVzICh0ZXJtaW5hbCBub2RlcykgYXJlIGluIHRoaXMgdHJlZT8qKg0KDQpXZSBzdGFydCB3aXRoIHRoZSBjb21wbGV4IHRyZWU6DQoNCmBgYHtyfQ0KdHJlZV9jb21wbGV4IDwtIHRyZWUoYXMuZmFjdG9yKENodXJuKSB+LiwgZGF0YSA9IHRlbGNvLCBtaW5kZXY9MCwgbWluY3V0PTApDQpgYGANCg0Kd2Ugbm93IGNhbiBkbyB0aGUgY3Jvc3MtdmFsaWRhdGlvbiBgSz0xMDpgDQoNCmBgYHtyfQ0KY3YudHJlZS5jb21wbGV4IDwtIGN2LnRyZWUodHJlZV9jb21wbGV4LCBLPTEwKQ0KYGBgDQoNCkhvdyBtYW55IGxlYXZlcz8NCg0KYGBge3J9DQpwYXIobWZyb3c9YygxLDEpKQ0KcGxvdChjdi50cmVlLmNvbXBsZXgkc2l6ZSwgY3YudHJlZS5jb21wbGV4JGRldiwgeGxhYj0idHJlZSBzaXplIChjb21wbGV4aXR5KSIsIHlsYWI9Ik91dC1vZi1zYW1wbGUgZGV2aWFuY2UgKGVycm9yKSIsIHBjaD0yMCkNCg0KYGBgDQoNCiMgUXVlc3Rpb24gNQ0KDQpBY2NvcmRpbmcgdG8gdGhpcyBwcnVuZWQgbW9kZWwsICoqd2hhdCdzIHRoZSBwcm9iYWJpbGl0eSBvZiBzb21lb25lIHdpdGggYSBvbmUteWVhciBjb250cmFjdCwgd2hvIGhhcyBiZWVuIGEgY3VzdG9tZXIgZm9yIDEyIG1vbnRocywgYW5kIGhhcyBubyBpbnRlcm5ldCBzZXJ2aWNlLCBjaHVybnM/KioNCg0KKlByb3ZpZGUgeW91ciBhbnN3ZXIgd2l0aCBhIGRvdCBhbmQgdGhyZWUgZGVjaW1hbHMgKGUuZy4gMC4xMjMpKg0KDQpXZSBjaG9vc2Ugb3VyIGJlc3QgbW9kZWwgd2l0aCA2IGxlYXZlczoNCg0KYGBge3J9DQp0cmVlX2N1dCA8LSBwcnVuZS50cmVlKHRyZWVfY29tcGxleCwgYmVzdD02KQ0Kc3VtbWFyeSh0cmVlX2N1dCkNCmBgYA0KDQpXZSBjYW4gcHJlZGljdCBub3c6DQoNCmBgYHtyfQ0KcGFyKG1mcm93PWMoMSwyKSwgb21hID0gYygwLCAwLCAyLCAwKSkNCnBsb3QodHJlZV9jdXQsIGNvbD0xMCwgbHdkPTIpDQp0ZXh0KHRyZWVfY3V0LCBjZXg9MSwgbGFiZWw9Inlwcm9iIiwgZm9udD0yLCBkaWdpdHMgPSAyLCBwcmV0dHkgPSAwKQ0KdGl0bGUobWFpbj0iQSBwcnVuZWQgdHJlZSIpDQpgYGANCg0KIyBRdWVzdGlvbiA2DQoNCkZpdCBhIHJhbmRvbSBmb3Jlc3QgdG8gdGhlIGRhdGEsIHdpdGggMTAwMCB0cmVlcywgbWluaW11bSBub2RlIHNpemUgb2YgMjUsIHVzaW5nIGF2ZXJhZ2UgcHJvYmFiaWxpdGllcyByYXRoZXIgdGhhbiBjbGFzc2lmaWNhdGlvbnMgKHByb2JhYmlsaXR5ID0gVFJVRSkuIMKgDQoNCioqV2hhdCBpcyB0aGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGUgYWNjb3JkaW5nIHRvIHRoaXM/IEdpdmUgdGhlIHZhcmlhYmxlIG5hbWUuKioNCg0KYGBge3IsIGNhY2hlPVRSVUV9DQp0ZWxjb19yZiA8LSByYW5nZXIoQ2h1cm4gfiAuLCBkYXRhID0gdGVsY28sIA0KICAgICAgICAgICAgICAgICAgIHdyaXRlLmZvcmVzdD1UUlVFLCANCiAgICAgICAgICAgICAgICAgICBudW0udHJlZXMgPSAxMDAwLCANCiAgICAgICAgICAgICAgICAgICBtaW4ubm9kZS5zaXplID0gMjUsIA0KICAgICAgICAgICAgICAgICAgIGltcG9ydGFuY2UgPSAiaW1wdXJpdHkiLCANCiAgICAgICAgICAgICAgICAgICBwcm9iYWJpbGl0eT1UUlVFLCANCiAgICAgICAgICAgICAgICAgICBzZWVkID0gMTkxMDMpDQpgYGANCg0KKipWYXJpYWJsZSBJbXBvcnRhbmNlKio6DQoNCmBgYHtyfQ0Kc29ydCh0ZWxjb19yZiR2YXJpYWJsZS5pbXBvcnRhbmNlLCBkZWNyZWFzaW5nID0gVFJVRSkNCmBgYA0KDQpgYGB7cn0NCnBhcihtZnJvdz1jKDEsMSkpDQpwYXIobWFpPWMoLjksLjgsLjIsLjIpKQ0KYmFycGxvdChzb3J0KHRlbGNvX3JmJHZhcmlhYmxlLmltcG9ydGFuY2UsIGRlY3JlYXNpbmcgPSBUUlVFKSwgeWxhYiA9ICJ2YXJpYWJsZSBpbXBvcnRhbmNlIikNCg0KYGBgDQoNCiMgUXVlc3Rpb24gNw0KDQpBcHBseSB0aGUgbW9zdCBjb21wbGV4IHRyZWUgdGhhdCB5b3Ugc3RhcnRlZCB3aXRoIGluIHF1ZXN0aW9uIDQ7IGFsc28gYXBwbHkgdGhlIHBydW5lZCB0cmVlIGFuZCB0aGUgcmFuZG9tIGZvcmVzdCB0byB0aGUgWyoqKmhvbGRvdXQgZGF0YSoqKl17LnVuZGVybGluZX0gc2V0Lg0KDQpgYGB7cn0NCg0KdGVsY28uaG9sZG91dCA8LSByZWFkLmNzdigndGVsY29faG9sZG91dC5jc3YnLCBzdHJpbmdzQXNGYWN0b3JzID0gVFJVRSkNCg0KIyBDaHVybiBhcyBkdW1teQ0KQ2h1cm4ubnVtIDwtIGFzLm51bWVyaWMoYXMuZmFjdG9yKHRlbGNvLmhvbGRvdXQkQ2h1cm4pKS0xDQpgYGANCg0KKipXaGF0IGlzIHRoZSBhcmVhIHVuZGVyIHRoZSBjdXJ2ZSBmb3IgdGhlIHRyZWU/KioNCg0KKlByb3ZpZGUgeW91ciBhbnN3ZXIgd2l0aCBhIGRvdCBhbmQgdHdvIGRlY2ltYWxzIChlLmcuIDAuMTIpKg0KDQpMZXQncyBhcHBseSBldmVyeSBtb2RlbCBhdCBvbmNlIHRvIHRoZSBob2xkb3V0Og0KDQpgYGB7cn0NCiN0YWtlIG9ubHkgIlllcyINCnByZWRfY3V0IDwtIHByZWRpY3QodHJlZV9jdXQsIG5ld2RhdGE9dGVsY28uaG9sZG91dCwgdHlwZT0idmVjdG9yIilbLDJdDQoNCnByZWRfY29tcGxleCA8LSBwcmVkaWN0KHRyZWVfY29tcGxleCwgbmV3ZGF0YT10ZWxjby5ob2xkb3V0LCB0eXBlPSJ2ZWN0b3IiKVssMl0NCg0KcHJlZF9yZiA8LSBwcmVkaWN0KHRlbGNvX3JmLCBkYXRhPXRlbGNvLmhvbGRvdXQpJHByZWRpY3Rpb25zWywyXQ0KDQojIE9ic2VydmVkIGNodXJuIGFzIG51bWJlcg0KY2h1cm4ubnVtIDwtIGFzLm51bWVyaWModGVsY28uaG9sZG91dCRDaHVybiktMQ0KYGBgDQoNClsqKkFyZWEgVW5kZXIgdGhlIEN1cnZlKipdey51bmRlcmxpbmV9Og0KDQpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCnJvYyhjaHVybi5udW0sIHByZWRfY3V0KQ0KYGBgDQoNCmBgYHtyfQ0KcGFyKG1mcm93PWMoMSwzKSkNCnBhcihtYWk9YyguOSwuOCwuMiwuMikpDQpwbG90KHJvYyhhcy5udW1lcmljKHRlbGNvLmhvbGRvdXQkQ2h1cm4pLTEsIHByZWRfY3V0KSwgcHJpbnQuYXVjPVRSVUUsIHlsaW09YygwLDEpLA0KICAgICBjb2w9ImJsYWNrIiwgbHdkPTEsIG1haW49IlJPQyBjdXJ2ZSIsIHhsYWI9IlNwZWNpZmljaXR5OiB0cnVlIG5lZ2F0aXZlIHJhdGUiLCB5bGFiPSJTZW5zaXRpdml0eTogdHJ1ZSBwb3NpdGl2ZSByYXRlIiwgeGxpbT1jKDEsMCkpDQoNCmBgYA0KDQojIFF1ZXN0aW9uIDgNCg0KKipXaGF0IGlzIHRoZSBhcmVhIHVuZGVyIHRoZSBjdXJ2ZSBmb3IgdGhlIHJhbmRvbSBmb3Jlc3Q/KioNCg0KKlByb3ZpZGUgeW91ciBhbnN3ZXIgd2l0aCBhIGRvdCBhbmQgdHdvIGRlY2ltYWxzIChlLmcuIDAuMTIpKg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCnJvYyhjaHVybi5udW0sIHByZWRfcmYpDQpgYGANCg0KIyBRdWVzdGlvbiA5DQoNCioqV2hhdCBpcyB0aGUgYXJlYSB1bmRlciB0aGUgY3VydmUgZm9yIHRoZSBjb21wbGV4IHRyZWU/KioNCg0KKlByb3ZpZGUgeW91ciBhbnN3ZXIgd2l0aCBhIGRvdCBhbmQgdHdvIGRlY2ltYWxzIChlLmcuIDAuMTIpKg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCnJvdW5kKHJvYyhjaHVybi5udW0sIHByZWRfY29tcGxleCkkYXVjLDIpDQpgYGANCg0KIyBRdWVzdGlvbiAxMA0KDQpVc2luZyB0aGUgcmFuZG9tIGZvcmVzdCBpbiB0aGUgaG9sZG91dCBkYXRhLCBpZiB5b3Ugd2VyZSB0YXJnZXRpbmcgdGhlIHRvcCAzIGRlY2lsZXMsICoqd2hhdCBwZXJjZW50YWdlIG9mIHRvdGFsIGNodXJuZXJzIHdvdWxkIHlvdSB0YXJnZXQ/KioNCg0KKlByb3ZpZGUgeW91ciBhbnN3ZXIgd2l0aG91dCBhIHBlcmNlbnQgc2lnbiBhbmQgd2l0aCB6ZXJvIGRlY2ltYWxzIChlLmcuIDEyKSoNCg0KV2Ugc3RhcnQgY3JlYXRpbmcgdGhlIGRlY2lsZXM6DQoNCmBgYHtyfQ0KbnRpbGVzIDwtIGZ1bmN0aW9uKHgsIGJpbnMpIHsNCiAgcXVhbnRpbGVzID0gc2VxKGZyb209MCwgdG8gPSAxLCBsZW5ndGgub3V0PWJpbnMrMSkNCiAgY3V0KGVjZGYoeCkoeCksYnJlYWtzPXF1YW50aWxlcywgbGFiZWxzPUYpDQp9DQoNCiMgRGVjaWxlcw0KdGVsY28uaG9sZG91dCRwcm9iIDwtcHJlZF9yZg0KcHJvYl9kZWNpbGUgPC0gbnRpbGVzKHRlbGNvLmhvbGRvdXQkcHJvYiwgMTApDQoNCiMgQ2h1cm4gYXMgbnVtZXJpYzoNCnRlbGNvLmhvbGRvdXQkQ2h1cm4ubnVtIDwtIGFzLm51bWVyaWMoYXMuZmFjdG9yKHRlbGNvLmhvbGRvdXQkQ2h1cm4pKS0xDQoNCiMgRGF0YXNldDoNCnRibCA8LSBkYXRhLmZyYW1lKGNiaW5kKHRlbGNvLmhvbGRvdXQkcHJvYiwgcHJvYl9kZWNpbGUsIHRlbGNvLmhvbGRvdXQkQ2h1cm4ubnVtKSkNCmNvbG5hbWVzKHRibCk8LWMoInByZWRpY3RlZCIsImRlY2lsZSIsICJhY3R1YWwiKQ0KDQpgYGANCg0KQ3JlYXRlIGxpZnQgdGFibGUgYnkgZGVjaWxlDQoNCmBgYHtyfQ0KIyBGaXJzdDogYXZlcmFnZSBjaHVybiByYXRlIGJ5IGRlY2lsZQ0KbGlmdCA8LSBhZ2dyZWdhdGUoYWN0dWFsfmRlY2lsZSwgZGF0YSA9IHRibCwgbWVhbikNCmNvbG5hbWVzKGxpZnQpWzJdPC0iYWN0dWFsIGNodXJuIHJhdGUiDQoNCiMgbGlmdCBpcyB0aGUgYWN0dWFsIGNodXJuIHJhdGUgaW4gdGhlIGRlY2lsZSBkaXZpZGVkIGJ5IGF2ZXJhZ2Ugb3ZlcmFsbCBjaHVybiByYXRlDQpsaWZ0WywzXTwtbGlmdFssMl0vbWVhbih0ZWxjby5ob2xkb3V0JENodXJuLm51bSkNCmNvbG5hbWVzKGxpZnQpWzNdPC0ibGlmdCINCg0KIyBvcmRlciBmb3IgaGlnaGVzdCB0byBsb3dlc3QNCmxpZnQ8LWxpZnRbb3JkZXIoLWxpZnQkZGVjaWxlKSxdDQoNCmxpZnRbLDRdPC1jdW1zdW0obGlmdCRhY3R1YWwpL3N1bShsaWZ0JGFjdHVhbCkqMTAwDQpjb2xuYW1lcyhsaWZ0KVs0XTwtImN1bXVsYXRpdmUgbGlmdCINCg0KbGlmdCAlPiUgDQogIGtibCgpICU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCg0KYGBgDQo=