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=