Question 1
A telecommunications company (like KPN) wants to implement a
proactive churn policy, using logistic regression to predict churn. They
assemble a data set of past customers who either churned or stayed,
along with several variables that can be used to predict this decision.
This data set is called telco_test.csv.
test <- read_csv("test.csv")
Make sure all non-metric variables like gender, senior citizen,
partner are coded as a factor (as done in the lab session) except for 3
variables — tenure, monthly charges, and total charges. Use this
code:
test$gender <- as.factor(test$gender)
test$SeniorCitizen <- as.factor(test$SeniorCitizen)
test$Partner <- as.factor(test$Partner)
test$PaymentMethod <- as.factor(test$PaymentMethod)
# Change Churn from "no" "yes" to 0 1
test <- test %>%
mutate(Churn = ifelse(Churn == "No",0,1))
Predict churn using gender, senior citizen, and tenure (as a
continuous variable). There should be 4 coefficients estimated.
According to this model, what is the probability that a male
senior who has been a customer for one month churns?
Provide your answer with two decimals separated
by a dot, not a comma (e.g. 0.17).
First, we fit the model:
model_1 <- glm(Churn ~ gender + SeniorCitizen + tenure, data=test, family = binomial(link="logit"))
We now make the prediction:
new <- data.frame(gender="Male", SeniorCitizen=as.factor(1), tenure=1)
pred <- predict(model_1, newdata = new, type = "response")
## The churn probability of a male senior who has been a customer for one month is 0.7
Question 2
Gender and senior citizen status may interact to create a different
effect on churn. Add an interaction term to the model so that now there
are 5 coefficients estimated in total.
What is the probability that a male senior with 1-month
tenure churns?
Provide your answer with two decimals separated
by a dot, not a comma (e.g. 0.17).
Model 2:
model_2 <- glm(Churn ~ gender*SeniorCitizen + tenure, data=test, family = binomial(link="logit"))
The Churn Probability of a male senior with 1-month
tenure:
new <- data.frame(gender="Male", SeniorCitizen=as.factor(1), tenure=1)
pred <- predict(model_2, newdata = new, type = "response")
## The churn probability of a male senior with one month of tenure is 0.71
Question 3
Predict churn using all variables. There
should be 24 coefficients estimated.
What’s the R2 of this model?
Provide your answer with two decimals separated
by a dot, not a comma (e.g. 0.17).
Model 3:
model_3 <- glm(Churn ~ ., data=test, family = binomial(link="logit"))
R-Squared by hand:
D <- model_3$deviance
D0 <- model_3$null.deviance
R2 <- 1-D/D0
round(R2,2)
## [1] 0.29
Question 4
Now instead of treating tenure as continuous, we are going to create
3 groups — low, medium, and high — from it (Hint: use
ntiles
).
Make sure it is a factor variable. Estimate a model now with tenure
group instead of tenure. There should be 25 coefficients estimated.
How much more or less likely are customers in the high tenure
group to churn relative to the low tenure group? Report the
percentage change in odds rounded to the
nearest whole percentage.
Provide your answer with zero
decimals and without a percent sign (e.g. 17
or -17).
We start by creating the 3-tiles:
ntiles <- function(x, bins) {
quantiles = seq(from=0, to = 1, length.out=bins+1)
cut(ecdf(x)(x),breaks=quantiles, labels=F)
}
## Tenure: 3 groups called "tenure_group"
test$tenure_group <- ntiles(test$tenure, bins=3)
test$tenure_group <- as.factor(test$tenure_group)
Model 4:
test1 <- test %>% select(-tenure)
model_4 <- glm(Churn ~ ., data=test1, family = binomial(link="logit"))
Model Interpretation: The lowest tenure group
is omitted or absorved by the intercept:
round((exp(coef(model_4)["tenure_group3"])-1)*100,0)
## tenure_group3
## -55
Question 5
Apply this model (model 4) to the holdout data set, telco_holdout.csv.
telco_holdout <- read_csv("telco_holdout.csv")
We create factor variables:
telco_holdout$gender <- as.factor(telco_holdout$gender)
telco_holdout$SeniorCitizen <- as.factor(telco_holdout$SeniorCitizen)
telco_holdout$Partner <- as.factor(telco_holdout$Partner)
telco_holdout$PaymentMethod <- as.factor(telco_holdout$PaymentMethod)
# Change Churn from "no" "yes" to 0 1
telco_holdout <- telco_holdout %>%
mutate(Churn = ifelse(Churn == "No",0,1))
# DO NOT FORGET THE 3 GROUPS
telco_holdout$tenure_group <- ntiles(telco_holdout$tenure, bins=3)
telco_holdout$tenure_group <- as.factor(telco_holdout$tenure_group)
What is the hit rate, i.e., the true positive rate, as a
whole percentage?
Provide your answer with zero
decimals and without a percent sign (e.g. 17
or -17).
# Predicted x'beta
xb <- predict(model_4, newdata = telco_holdout, type="link")
# Predicted probability
prob <- predict(model_4, newdata = telco_holdout, type="response")
# re-order
ind <- order(prob)
Confusion Matrix:
confusion_matrix <- (table(telco_holdout$Churn, prob > 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
## 0 1371 142 90.61
## 1 282 288 50.53
cat('Hit Rate:', round((confusion_matrix[2,2]/(confusion_matrix[2,1]+confusion_matrix[2,2]))*100),0)
## Hit Rate: 51 0
Question 6
Using this model to target the top 2 deciles would yield how
many actual churners as a percentage of total churners?
Provide your answer with zero
decimals and without a percent sign (e.g. 17
or -17).
We create the deciles:
# create deciles
prob_decile = ntiles(prob, 10)
# prob, decile and actual
pred <- data.frame(cbind(prob, prob_decile, telco_holdout$Churn))
colnames(pred)<-c("predicted","decile", "actual")
## create average churn
rbar_ho <- mean(telco_holdout$Churn)
We construct the lifts by decile:
lift_table <- pred %>%
group_by(decile) %>%
summarize(actual_churn = mean(actual),
lift = actual_churn/rbar_ho,
n_customers=n()) %>%
arrange(desc(decile)) %>%
mutate(cum_customers=cumsum(n_customers)) %>%
mutate(cum_lift=cumsum(actual_churn)/sum(actual_churn)*100)
head(lift_table) %>%
kbl() %>%
kable_styling()
decile
|
actual_churn
|
lift
|
n_customers
|
cum_customers
|
cum_lift
|
10
|
0.7560
|
2.7626
|
209
|
209
|
27.64
|
9
|
0.6058
|
2.2137
|
208
|
417
|
49.79
|
8
|
0.4423
|
1.6164
|
208
|
625
|
65.97
|
7
|
0.2919
|
1.0666
|
209
|
834
|
76.64
|
6
|
0.2548
|
0.9312
|
208
|
1042
|
85.96
|
5
|
0.1827
|
0.6676
|
208
|
1250
|
92.64
|
Question 7
Let’s use the lift table to find the optimal number of top deciles to
target, using the framework of BKN. Let’s assume the probability of
being rescued if the person is actually a churner is 0.25, and the
lifetime value of a customer is 250. The cost of the incentive is 30 and
the cost of contact is 1. The rest of the parameters are the same as in
the workbook.
How many deciles would you target and what would be the
expected profit ?
Provide your answer with zero
decimals (e.g. 17000).
gamma = 0.25 # probability that customer is rescued if he or she is a churner
LTV = 250 # lifetime value of rescued customer
delta = 30 # cost of incentive
c = 1 # cost of contact
# re-order lift from highest to lowest
# add columns to our lift table
profit_table <- lift_table %>% mutate(
cum_prop_churners = cumsum(actual_churn*n_customers)/cum_customers,
profit = cum_customers*((gamma*LTV+delta*(1-gamma))*cum_prop_churners-delta-c),
decile=11-decile)
Figure:
par(mai=c(.9,.8,.2,.2))
bp<-barplot(profit_table$profit ~ profit_table$decile, main="expected profits by # of deciles targeted", xlab="# deciles targeted", ylab="expected profits")
LS0tDQp0aXRsZTogIlF1aXogMzogTG9naXN0aWNzIFJlZ3Jlc3Npb24iDQphdXRob3I6ICJEYW5pZWwgUmVkZWwiDQpkYXRlOiAiMjAyMy0wMS0yNSINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogICAgY29kZV9kb3dubG9hZDogVFJVRQ0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Kcm0obGlzdCA9IGxzKCkpDQpsaWJyYXJ5KGNhcikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShwUk9DKQ0KbGlicmFyeShwbG90cml4KSAgIyBwbG90dGluZyB3aXRoIGNvbmZpZGVuY2UgaW50ZXJ2YWxzDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkocmVhZHIpDQoNCm9wdGlvbnMoInNjaXBlbiI9MjAwLCAiZGlnaXRzIj00KQ0KYGBgDQoNCiMgUXVlc3Rpb24gMQ0KDQpBIHRlbGVjb21tdW5pY2F0aW9ucyBjb21wYW55IChsaWtlIEtQTikgd2FudHMgdG8gaW1wbGVtZW50IGEgcHJvYWN0aXZlIGNodXJuIHBvbGljeSwgdXNpbmcgbG9naXN0aWMgcmVncmVzc2lvbiB0byBwcmVkaWN0IGNodXJuLiBUaGV5IGFzc2VtYmxlIGEgZGF0YSBzZXQgb2YgcGFzdCBjdXN0b21lcnMgd2hvIGVpdGhlciBjaHVybmVkIG9yIHN0YXllZCwgYWxvbmcgd2l0aCBzZXZlcmFsIHZhcmlhYmxlcyB0aGF0IGNhbiBiZSB1c2VkIHRvIHByZWRpY3QgdGhpcyBkZWNpc2lvbi4gVGhpcyBkYXRhIHNldCBpcyBjYWxsZWQgKnRlbGNvX3Rlc3QuY3N2Ki4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp0ZXN0IDwtIHJlYWRfY3N2KCJ0ZXN0LmNzdiIpDQpgYGANCg0KTWFrZSBzdXJlIGFsbCBub24tbWV0cmljIHZhcmlhYmxlcyBsaWtlIGdlbmRlciwgc2VuaW9yIGNpdGl6ZW4sIHBhcnRuZXIgYXJlIGNvZGVkIGFzIGEgZmFjdG9yIChhcyBkb25lIGluIHRoZSBsYWIgc2Vzc2lvbikgZXhjZXB0IGZvciAzIHZhcmlhYmxlcyAtLS0gdGVudXJlLCBtb250aGx5IGNoYXJnZXMsIGFuZCB0b3RhbCBjaGFyZ2VzLiBVc2UgdGhpcyBjb2RlOg0KDQpgYGB7cn0NCnRlc3QkZ2VuZGVyIDwtIGFzLmZhY3Rvcih0ZXN0JGdlbmRlcikNCnRlc3QkU2VuaW9yQ2l0aXplbiA8LSBhcy5mYWN0b3IodGVzdCRTZW5pb3JDaXRpemVuKQ0KdGVzdCRQYXJ0bmVyIDwtIGFzLmZhY3Rvcih0ZXN0JFBhcnRuZXIpDQp0ZXN0JFBheW1lbnRNZXRob2QgPC0gYXMuZmFjdG9yKHRlc3QkUGF5bWVudE1ldGhvZCkNCg0KIyBDaGFuZ2UgQ2h1cm4gZnJvbSAibm8iICJ5ZXMiIHRvIDAgMQ0KdGVzdCA8LSB0ZXN0ICU+JQ0KbXV0YXRlKENodXJuID0gaWZlbHNlKENodXJuID09ICJObyIsMCwxKSkNCmBgYA0KDQpQcmVkaWN0IGNodXJuIHVzaW5nIGdlbmRlciwgc2VuaW9yIGNpdGl6ZW4sIGFuZCB0ZW51cmUgKGFzIGEgY29udGludW91cyB2YXJpYWJsZSkuIFRoZXJlIHNob3VsZCBiZSA0IGNvZWZmaWNpZW50cyBlc3RpbWF0ZWQuDQoNCioqQWNjb3JkaW5nIHRvIHRoaXMgbW9kZWwsIHdoYXQgaXMgdGhlIHByb2JhYmlsaXR5IHRoYXQgYSBtYWxlIHNlbmlvciB3aG8gaGFzIGJlZW4gYSBjdXN0b21lciBmb3Igb25lIG1vbnRoIGNodXJucz8qKsKgDQoNCipQcm92aWRlIHlvdXIgYW5zd2VyIHdpdGjCoCoqdHdvKirCoGRlY2ltYWxzIHNlcGFyYXRlZCBieSBhIGRvdCwgbm90IGEgY29tbWEgKGUuZy4gMC4xNykuKg0KDQpGaXJzdCwgd2UgZml0IHRoZSBtb2RlbDoNCg0KYGBge3J9DQptb2RlbF8xIDwtIGdsbShDaHVybiB+IGdlbmRlciArIFNlbmlvckNpdGl6ZW4gKyB0ZW51cmUsIGRhdGE9dGVzdCwgZmFtaWx5ID0gYmlub21pYWwobGluaz0ibG9naXQiKSkNCmBgYA0KDQpXZSBub3cgbWFrZSB0aGUgcHJlZGljdGlvbjoNCg0KYGBge3J9DQpuZXcgPC0gZGF0YS5mcmFtZShnZW5kZXI9Ik1hbGUiLCBTZW5pb3JDaXRpemVuPWFzLmZhY3RvcigxKSwgdGVudXJlPTEpDQpwcmVkIDwtIHByZWRpY3QobW9kZWxfMSwgbmV3ZGF0YSA9IG5ldywgdHlwZSA9ICJyZXNwb25zZSIpDQpgYGANCg0KYGBge3IsIGVjaG89RkFMU0V9DQpjYXQoIlRoZSBjaHVybiBwcm9iYWJpbGl0eSBvZiBhIG1hbGUgc2VuaW9yIHdobyBoYXMgYmVlbiBhIGN1c3RvbWVyIGZvciBvbmUgbW9udGggaXMiLCByb3VuZChwcmVkWzFdLCAyKSkNCmBgYA0KDQojIFF1ZXN0aW9uIDINCg0KR2VuZGVyIGFuZCBzZW5pb3IgY2l0aXplbiBzdGF0dXMgbWF5IGludGVyYWN0IHRvIGNyZWF0ZSBhIGRpZmZlcmVudCBlZmZlY3Qgb24gY2h1cm4uIEFkZCBhbiBpbnRlcmFjdGlvbiB0ZXJtIHRvIHRoZSBtb2RlbCBzbyB0aGF0IG5vdyB0aGVyZSBhcmUgNSBjb2VmZmljaWVudHMgZXN0aW1hdGVkIGluIHRvdGFsLg0KDQoqKldoYXQgaXMgdGhlIHByb2JhYmlsaXR5IHRoYXQgYSBtYWxlIHNlbmlvciB3aXRoIDEtbW9udGggdGVudXJlIGNodXJucz8qKg0KDQoqUHJvdmlkZSB5b3VyIGFuc3dlciB3aXRoICoqdHdvIGRlY2ltYWxzKirCoHNlcGFyYXRlZCBieSBhIGRvdCwgbm90IGEgY29tbWEgKGUuZy4gMC4xNykuKg0KDQpbKipNb2RlbCAyKipdey51bmRlcmxpbmV9Og0KDQpgYGB7cn0NCm1vZGVsXzIgPC0gZ2xtKENodXJuIH4gZ2VuZGVyKlNlbmlvckNpdGl6ZW4gKyB0ZW51cmUsIGRhdGE9dGVzdCwgZmFtaWx5ID0gYmlub21pYWwobGluaz0ibG9naXQiKSkNCmBgYA0KDQoqKipUaGUgQ2h1cm4gUHJvYmFiaWxpdHkgb2YgYSBtYWxlIHNlbmlvciB3aXRoIDEtbW9udGggdGVudXJlKioqOg0KDQpgYGB7cn0NCm5ldyA8LSBkYXRhLmZyYW1lKGdlbmRlcj0iTWFsZSIsIFNlbmlvckNpdGl6ZW49YXMuZmFjdG9yKDEpLCB0ZW51cmU9MSkNCnByZWQgPC0gcHJlZGljdChtb2RlbF8yLCBuZXdkYXRhID0gbmV3LCB0eXBlID0gInJlc3BvbnNlIikNCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmNhdCgiVGhlIGNodXJuIHByb2JhYmlsaXR5IG9mIGEgbWFsZSBzZW5pb3Igd2l0aCBvbmUgbW9udGggb2YgdGVudXJlIGlzIiwgcm91bmQocHJlZFsxXSwgMikpDQpgYGANCg0KIyBRdWVzdGlvbiAzDQoNClByZWRpY3QgY2h1cm4gdXNpbmcgKioqYWxsIHZhcmlhYmxlcyoqKi4gVGhlcmUgc2hvdWxkIGJlIDI0IGNvZWZmaWNpZW50cyBlc3RpbWF0ZWQuDQoNCioqV2hhdCdzIHRoZSBSXjJeIG9mIHRoaXMgbW9kZWw/KioNCg0KKlByb3ZpZGUgeW91ciBhbnN3ZXIgd2l0aMKgKip0d28gZGVjaW1hbHMqKsKgc2VwYXJhdGVkIGJ5IGEgZG90LCBub3QgYSBjb21tYSAoZS5nLiAwLjE3KSouDQoNClsqKk1vZGVsIDMqKl17LnVuZGVybGluZX06DQoNCmBgYHtyfQ0KbW9kZWxfMyA8LSBnbG0oQ2h1cm4gfiAuLCBkYXRhPXRlc3QsIGZhbWlseSA9IGJpbm9taWFsKGxpbms9ImxvZ2l0IikpDQpgYGANCg0KKioqUi1TcXVhcmVkIGJ5IGhhbmQqKio6DQoNCmBgYHtyfQ0KRCA8LSBtb2RlbF8zJGRldmlhbmNlDQpEMCA8LSBtb2RlbF8zJG51bGwuZGV2aWFuY2UNClIyIDwtIDEtRC9EMA0Kcm91bmQoUjIsMikNCmBgYA0KDQojIFF1ZXN0aW9uIDQNCg0KTm93IGluc3RlYWQgb2YgdHJlYXRpbmcgdGVudXJlIGFzIGNvbnRpbnVvdXMsIHdlIGFyZSBnb2luZyB0byBjcmVhdGUgMyBncm91cHMgLS0tIGxvdywgbWVkaXVtLCBhbmQgaGlnaCAtLS0gZnJvbSBpdCAoSGludDogdXNlICpgbnRpbGVzYCopLg0KDQpNYWtlIHN1cmUgaXQgaXMgYSBmYWN0b3IgdmFyaWFibGUuIEVzdGltYXRlIGEgbW9kZWwgbm93IHdpdGggdGVudXJlIGdyb3VwIGluc3RlYWQgb2YgdGVudXJlLiBUaGVyZSBzaG91bGQgYmUgMjUgY29lZmZpY2llbnRzIGVzdGltYXRlZC4NCg0KKipIb3cgbXVjaCBtb3JlIG9yIGxlc3MgbGlrZWx5IGFyZSBjdXN0b21lcnMgaW4gdGhlIGhpZ2ggdGVudXJlIGdyb3VwIHRvIGNodXJuIHJlbGF0aXZlIHRvIHRoZSBsb3cgdGVudXJlIGdyb3VwPyoqwqBSZXBvcnQgdGhlIHBlcmNlbnRhZ2UgKioqY2hhbmdlIGluIG9kZHMqKiogcm91bmRlZCB0byB0aGUgbmVhcmVzdCB3aG9sZSBwZXJjZW50YWdlLg0KDQoqUHJvdmlkZSB5b3VyIGFuc3dlciB3aXRowqAqKnplcm8gZGVjaW1hbHMqKsKgYW5kwqAqKndpdGhvdXQgYSBwZXJjZW50IHNpZ24qKsKgKGUuZy4gMTcgb3IgLTE3KS4qDQoNCldlIHN0YXJ0IGJ5IGNyZWF0aW5nIHRoZSAzLXRpbGVzOg0KDQpgYGB7cn0NCm50aWxlcyA8LSBmdW5jdGlvbih4LCBiaW5zKSB7DQogIHF1YW50aWxlcyA9IHNlcShmcm9tPTAsIHRvID0gMSwgbGVuZ3RoLm91dD1iaW5zKzEpDQogIGN1dChlY2RmKHgpKHgpLGJyZWFrcz1xdWFudGlsZXMsIGxhYmVscz1GKQ0KfQ0KDQojIyBUZW51cmU6IDMgZ3JvdXBzIGNhbGxlZCAidGVudXJlX2dyb3VwIg0KdGVzdCR0ZW51cmVfZ3JvdXAgPC0gbnRpbGVzKHRlc3QkdGVudXJlLCBiaW5zPTMpICANCnRlc3QkdGVudXJlX2dyb3VwIDwtIGFzLmZhY3Rvcih0ZXN0JHRlbnVyZV9ncm91cCkNCmBgYA0KDQpbKipNb2RlbCA0Kipdey51bmRlcmxpbmV9Og0KDQpgYGB7cn0NCnRlc3QxIDwtIHRlc3QgJT4lIHNlbGVjdCgtdGVudXJlKQ0KbW9kZWxfNCA8LSBnbG0oQ2h1cm4gfiAuLCBkYXRhPXRlc3QxLCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rPSJsb2dpdCIpKQ0KYGBgDQoNClsqKk1vZGVsIEludGVycHJldGF0aW9uKipdey51bmRlcmxpbmV9OiBUaGUgbG93ZXN0IHRlbnVyZSBncm91cCBpcyBvbWl0dGVkIG9yIGFic29ydmVkIGJ5IHRoZSBpbnRlcmNlcHQ6DQoNCmBgYHtyfQ0Kcm91bmQoKGV4cChjb2VmKG1vZGVsXzQpWyJ0ZW51cmVfZ3JvdXAzIl0pLTEpKjEwMCwwKQ0KYGBgDQoNCiMgUXVlc3Rpb24gNQ0KDQpBcHBseSB0aGlzIG1vZGVsICgqbW9kZWwgNCopIHRvIHRoZSBob2xkb3V0IGRhdGEgc2V0LMKgW3RlbGNvX2hvbGRvdXQuY3N2XShodHRwczovL3RpbGJ1cmd1bml2ZXJzaXR5Lmluc3RydWN0dXJlLmNvbS9jb3Vyc2VzLzEwOTE5L2ZpbGVzLzE5NDkwMzU/d3JhcD0xKS4NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQp0ZWxjb19ob2xkb3V0IDwtIHJlYWRfY3N2KCJ0ZWxjb19ob2xkb3V0LmNzdiIpDQpgYGANCg0KV2UgY3JlYXRlIGZhY3RvciB2YXJpYWJsZXM6DQoNCmBgYHtyfQ0KdGVsY29faG9sZG91dCRnZW5kZXIgPC0gYXMuZmFjdG9yKHRlbGNvX2hvbGRvdXQkZ2VuZGVyKQ0KdGVsY29faG9sZG91dCRTZW5pb3JDaXRpemVuIDwtIGFzLmZhY3Rvcih0ZWxjb19ob2xkb3V0JFNlbmlvckNpdGl6ZW4pDQp0ZWxjb19ob2xkb3V0JFBhcnRuZXIgPC0gYXMuZmFjdG9yKHRlbGNvX2hvbGRvdXQkUGFydG5lcikNCnRlbGNvX2hvbGRvdXQkUGF5bWVudE1ldGhvZCA8LSBhcy5mYWN0b3IodGVsY29faG9sZG91dCRQYXltZW50TWV0aG9kKQ0KDQojIENoYW5nZSBDaHVybiBmcm9tICJubyIgInllcyIgdG8gMCAxDQp0ZWxjb19ob2xkb3V0IDwtIHRlbGNvX2hvbGRvdXQgJT4lDQptdXRhdGUoQ2h1cm4gPSBpZmVsc2UoQ2h1cm4gPT0gIk5vIiwwLDEpKQ0KDQojIERPIE5PVCBGT1JHRVQgVEhFIDMgR1JPVVBTDQp0ZWxjb19ob2xkb3V0JHRlbnVyZV9ncm91cCA8LSBudGlsZXModGVsY29faG9sZG91dCR0ZW51cmUsIGJpbnM9MykgIA0KdGVsY29faG9sZG91dCR0ZW51cmVfZ3JvdXAgPC0gYXMuZmFjdG9yKHRlbGNvX2hvbGRvdXQkdGVudXJlX2dyb3VwKQ0KYGBgDQoNCioqV2hhdCBpcyB0aGUgaGl0IHJhdGUsIGkuZS4sIHRoZSB0cnVlIHBvc2l0aXZlIHJhdGUsIGFzIGEgd2hvbGUgcGVyY2VudGFnZT8qKsKgDQoNCipQcm92aWRlIHlvdXIgYW5zd2VyIHdpdGjCoCoqemVybyBkZWNpbWFscyoqwqBhbmTCoCoqd2l0aG91dCBhIHBlcmNlbnQgc2lnbioqwqAoZS5nLiAxNyBvciAtMTcpLioNCg0KYGBge3J9DQojIFByZWRpY3RlZCB4J2JldGENCnhiIDwtIHByZWRpY3QobW9kZWxfNCwgbmV3ZGF0YSA9IHRlbGNvX2hvbGRvdXQsIHR5cGU9ImxpbmsiKQ0KIyBQcmVkaWN0ZWQgcHJvYmFiaWxpdHkNCnByb2IgPC0gcHJlZGljdChtb2RlbF80LCBuZXdkYXRhID0gdGVsY29faG9sZG91dCwgdHlwZT0icmVzcG9uc2UiKQ0KIyByZS1vcmRlcg0KaW5kIDwtIG9yZGVyKHByb2IpDQpgYGANCg0KKipDb25mdXNpb24gTWF0cml4Kio6DQoNCmBgYHtyfQ0KY29uZnVzaW9uX21hdHJpeCA8LSAodGFibGUodGVsY29faG9sZG91dCRDaHVybiwgcHJvYiA+IDAuNSkpDQpjb25mdXNpb25fbWF0cml4IDwtIGFzLmRhdGEuZnJhbWUubWF0cml4KGNvbmZ1c2lvbl9tYXRyaXgpDQoNCmNvbG5hbWVzKGNvbmZ1c2lvbl9tYXRyaXgpIDwtIGMoIk5vIiwgIlllcyIpDQoNCmNvbmZ1c2lvbl9tYXRyaXgkUGVyY2VudGFnZV9Db3JyZWN0IDwtIGNvbmZ1c2lvbl9tYXRyaXhbMSxdJE5vLyhjb25mdXNpb25fbWF0cml4WzEsXSRObytjb25mdXNpb25fbWF0cml4WzEsXSRZZXMpKjEwMA0KY29uZnVzaW9uX21hdHJpeFsyLF0kUGVyY2VudGFnZV9Db3JyZWN0IDwtIGNvbmZ1c2lvbl9tYXRyaXhbMixdJFllcy8oY29uZnVzaW9uX21hdHJpeFsyLF0kTm8rY29uZnVzaW9uX21hdHJpeFsyLF0kWWVzKSoxMDANCnByaW50KGNvbmZ1c2lvbl9tYXRyaXgpDQpgYGANCg0KYGBge3J9DQpjYXQoJ0hpdCBSYXRlOicsIHJvdW5kKChjb25mdXNpb25fbWF0cml4WzIsMl0vKGNvbmZ1c2lvbl9tYXRyaXhbMiwxXStjb25mdXNpb25fbWF0cml4WzIsMl0pKSoxMDApLDApDQpgYGANCg0KIyBRdWVzdGlvbiA2DQoNCioqVXNpbmcgdGhpcyBtb2RlbCB0byB0YXJnZXQgdGhlIHRvcCAyIGRlY2lsZXMgd291bGQgeWllbGQgaG93IG1hbnkgYWN0dWFsIGNodXJuZXJzIGFzIGEgcGVyY2VudGFnZSBvZiB0b3RhbCBjaHVybmVycz8qKg0KDQoqUHJvdmlkZSB5b3VyIGFuc3dlciB3aXRowqAqKnplcm8gZGVjaW1hbHMqKsKgYW5kwqAqKndpdGhvdXQgYSBwZXJjZW50IHNpZ24qKsKgKGUuZy4gMTcgb3IgLTE3KS4qDQoNCldlIGNyZWF0ZSB0aGUgZGVjaWxlczoNCg0KYGBge3J9DQojIGNyZWF0ZSBkZWNpbGVzDQpwcm9iX2RlY2lsZSA9IG50aWxlcyhwcm9iLCAxMCkNCg0KIyBwcm9iLCBkZWNpbGUgYW5kIGFjdHVhbA0KcHJlZCA8LSBkYXRhLmZyYW1lKGNiaW5kKHByb2IsIHByb2JfZGVjaWxlLCB0ZWxjb19ob2xkb3V0JENodXJuKSkNCmNvbG5hbWVzKHByZWQpPC1jKCJwcmVkaWN0ZWQiLCJkZWNpbGUiLCAiYWN0dWFsIikNCg0KIyMgY3JlYXRlIGF2ZXJhZ2UgY2h1cm4NCnJiYXJfaG8gPC0gbWVhbih0ZWxjb19ob2xkb3V0JENodXJuKQ0KYGBgDQoNCldlIGNvbnN0cnVjdCB0aGUgbGlmdHMgYnkgZGVjaWxlOg0KDQpgYGB7cn0NCmxpZnRfdGFibGUgPC0gcHJlZCAlPiUgDQoJZ3JvdXBfYnkoZGVjaWxlKSAlPiUgIA0KCXN1bW1hcml6ZShhY3R1YWxfY2h1cm4gPSBtZWFuKGFjdHVhbCksIA0KCQkJCQkJbGlmdCA9IGFjdHVhbF9jaHVybi9yYmFyX2hvLCANCgkJCQkJCW5fY3VzdG9tZXJzPW4oKSkgJT4lIA0KCWFycmFuZ2UoZGVzYyhkZWNpbGUpKSAlPiUgDQoJbXV0YXRlKGN1bV9jdXN0b21lcnM9Y3Vtc3VtKG5fY3VzdG9tZXJzKSkgJT4lIA0KCW11dGF0ZShjdW1fbGlmdD1jdW1zdW0oYWN0dWFsX2NodXJuKS9zdW0oYWN0dWFsX2NodXJuKSoxMDApDQoNCmhlYWQobGlmdF90YWJsZSkgJT4lIA0KICBrYmwoKSAlPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KIyBRdWVzdGlvbiA3DQoNCkxldCdzIHVzZSB0aGUgbGlmdCB0YWJsZSB0byBmaW5kIHRoZSBvcHRpbWFsIG51bWJlciBvZiB0b3AgZGVjaWxlcyB0byB0YXJnZXQsIHVzaW5nIHRoZSBmcmFtZXdvcmsgb2YgQktOLiBMZXQncyBhc3N1bWUgdGhlIHByb2JhYmlsaXR5IG9mIGJlaW5nIHJlc2N1ZWQgaWYgdGhlIHBlcnNvbiBpcyBhY3R1YWxseSBhIGNodXJuZXIgaXMgMC4yNSwgYW5kIHRoZSBsaWZldGltZSB2YWx1ZSBvZiBhIGN1c3RvbWVyIGlzIDI1MC4gVGhlIGNvc3Qgb2YgdGhlIGluY2VudGl2ZSBpcyAzMCBhbmQgdGhlIGNvc3Qgb2YgY29udGFjdCBpcyAxLiBUaGUgcmVzdCBvZiB0aGUgcGFyYW1ldGVycyBhcmUgdGhlIHNhbWUgYXMgaW4gdGhlIHdvcmtib29rLg0KDQoqKkhvdyBtYW55IGRlY2lsZXMgd291bGQgeW91IHRhcmdldCBhbmQgd2hhdCB3b3VsZCBiZSB0aGUgZXhwZWN0ZWQgcHJvZml0ID8qKg0KDQoqUHJvdmlkZSB5b3VyIGFuc3dlciB3aXRowqAqKnplcm8gZGVjaW1hbHMqKsKgKGUuZy4gMTcwMDApLioNCg0KYGBge3J9DQoNCmdhbW1hID0gMC4yNSAgIyBwcm9iYWJpbGl0eSB0aGF0IGN1c3RvbWVyIGlzIHJlc2N1ZWQgaWYgaGUgb3Igc2hlIGlzIGEgY2h1cm5lcg0KTFRWID0gMjUwICAgIyBsaWZldGltZSB2YWx1ZSBvZiByZXNjdWVkIGN1c3RvbWVyDQpkZWx0YSA9IDMwICAjIGNvc3Qgb2YgaW5jZW50aXZlDQpjID0gMSAgIyBjb3N0IG9mIGNvbnRhY3QNCg0KIyByZS1vcmRlciBsaWZ0IGZyb20gaGlnaGVzdCB0byBsb3dlc3QNCiMgYWRkIGNvbHVtbnMgdG8gb3VyIGxpZnQgdGFibGUNCg0KcHJvZml0X3RhYmxlIDwtIGxpZnRfdGFibGUgJT4lIG11dGF0ZSgNCiAgY3VtX3Byb3BfY2h1cm5lcnMgPSBjdW1zdW0oYWN0dWFsX2NodXJuKm5fY3VzdG9tZXJzKS9jdW1fY3VzdG9tZXJzLCANCiAgcHJvZml0ID0gY3VtX2N1c3RvbWVycyooKGdhbW1hKkxUVitkZWx0YSooMS1nYW1tYSkpKmN1bV9wcm9wX2NodXJuZXJzLWRlbHRhLWMpLA0KICBkZWNpbGU9MTEtZGVjaWxlKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KYGBgDQoNClsqKkZpZ3VyZSoqXXsudW5kZXJsaW5lfToNCg0KYGBge3J9DQpwYXIobWFpPWMoLjksLjgsLjIsLjIpKQ0KYnA8LWJhcnBsb3QocHJvZml0X3RhYmxlJHByb2ZpdCB+IHByb2ZpdF90YWJsZSRkZWNpbGUsIG1haW49ImV4cGVjdGVkIHByb2ZpdHMgYnkgIyBvZiBkZWNpbGVzIHRhcmdldGVkIiwgeGxhYj0iIyBkZWNpbGVzIHRhcmdldGVkIiwgeWxhYj0iZXhwZWN0ZWQgcHJvZml0cyIpDQpgYGANCg==