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