Exercise 1
Load the MASS package and combine Pima.tr and Pima.tr2 to a data.frame called train and save Pima.te as test. Change the coding of our variable of interest to (type) to 0 (non-diabetic) and 1 (diabetic). Check for and take note of any missing values.
library(MASS)
library(ggplot2)
train <- rbind(Pima.tr, Pima.tr2)
test <- Pima.te
train$type <- as.integer(train$type) - 1L
test$type <- as.integer(test$type) - 1L
# Missing values?
sapply(train, function(x) sum(is.na(x)))
npreg glu bp skin bmi ped age type
0 0 13 98 3 0 0 0
Exercise 2
Take a look at the data. Plot a scatterplot matrix between all the explanatory variables using pairs(), and color code the dots according to diabetic classification. Furthermore, try to plot type as a function of age. Use jitter to make your graph more informative. Bonus: Can you add a logistic fit based on age on top of your plot?
library(GGally)
#ggpairs(iris, aes(colour = Species, alpha = 0.4))
pairs(subset(train, select = -c(type)), col = as.factor(train$type))

#ggpairs(train, columns = 1:7, title = "", colour = "type",
# axisLabels = "show", columnLabels = colnames(train[, columns]))
library(ggplot2)
ggplot(train, aes(x = age, y = type)) +
geom_jitter(width = 0.5, height = 0.03, alpha = .2) +
geom_smooth(method = "glm", se = FALSE,
method.args = list(family = "binomial")) +
labs(y = expression(hat(P)(Diabetic)))

Exercise 3
Using the glm() and the train data fit a logistic model of type on age and bmi. Print out the coefficients and their p-value.
library(broom)
lg1 <- glm(type ~ age + bmi, data = train, family = binomial)
#
#summary(lg1)$coefficients[, c(1, 4)]
tidy(lg1)
Exercise 4
What does the model fitted in exercise 3 predict in terms of probability for someone age 35 with bmi of 32, what about bmi of 22?
predict(lg1, type = "response",newdata = data.frame(bmi = c(32, 22), age = 35))
1 2
0.3470908 0.1600962
broom::augment(x=lg1, newdata = data.frame(bmi = c(32, 22), age = 35), type.predict = "response")
Or manually define the logistic function
# Or manually
lgs_fun <- function(par, x) {
1 / (1 + exp(-x %*% par)) # x %*% par is linear algebra equivalent of b_0 + b_1*age + b2*bmi
}
lgs_fun(lg1$coefficients, c(1, 35, 32))
[,1]
[1,] 0.3470908
## [,1]
## [1,] 0.3470908
lgs_fun(lg1$coefficients, c(1, 35, 22))
[,1]
[1,] 0.1600962
Exercise 5
According to our model what are the odds that a woman in our sample is diabetic given age 55 and a bmi 37? Remember that odds in this context have a very precise definition which is different from probability.
exp(predict(lg1, response = "link", newdata = data.frame(age = 55, bmi = 37)))
1
2.605789
Or literally
lgs_fun(lg1$coefficients, c(1, 55, 37)) / (1 - lgs_fun(lg1$coefficients, c(1, 55, 37)))
[,1]
[1,] 2.605789
# or simply
exp(c(1, 55, 37) %*% lg1$coefficients)
[,1]
[1,] 2.605789
Exercise 6
Build the confusion matrix, a table of actual diabetic classification against model prediction. Use a cutoff value of 0.5, meaning that women who the model estimates to have at least 0.5 chance of being diabetic are predicted to be diabetic. What is the prediction accuracy?
# Remember data is not complete ... we have missing values for bmi...
cm1 <- table(train$type[!is.na(train$bmi)], predict(lg1, type = "response") >= 0.5)
cm1
FALSE TRUE
0 276 48
1 114 59
# Prediction accuracy
sum(diag(cm1)) / sum(cm1)
[1] 0.6740443
#
# sensitivity or recall
sensitivity(actuals = train$type[!is.na(train$bmi)], predictedScores = predict(lg1, type = "response") >= 0.5)
[1] 0.3410405
Exercise 7
Apply the fitted model to the test set. Print the confusion matrix and prediction accuracy.
# Check test set for missing values
sapply(test, function(x) sum(is.na(x)))
npreg glu bp skin bmi ped age type
0 0 0 0 0 0 0 0
# Get predictions
test_pred <- predict(lg1, type = "response", newdata = test)
Or manually
test_predm <- lgs_fun(lg1$coefficients, as.matrix(cbind(1, test[, c("age", "bmi")])))
cm1_test <- table(test$type,
test_pred >= 0.5)
cm1_test
FALSE TRUE
0 195 28
1 67 42
# Prediction accuracy
sum(diag(cm1_test)) / sum(cm1_test)
cm2_test = table(predicted = as.numeric(test_pred >= 0.5), actual = test$type)
library(caret)
train_con_mat = confusionMatrix(cm2_test)
c(train_con_mat$overall["Accuracy"],
train_con_mat$byClass["Sensitivity"],
train_con_mat$byClass["Specificity"])
Accuracy Sensitivity Specificity
0.7138554 0.8744395 0.3853211
Exercise 8
Draw up the ROC curve and calculate the AUC.
library(ROCR)
# first install.packages("ROCR")
## Warning: package 'ROCR' was built under R version 3.4.2
# ROC curve
predROCR <- prediction(test_predm, test$type)
perfROCR <- performance(predROCR, "tpr", "fpr")
plot(perfROCR, colorize = TRUE)

# Compute AUC
performance(predROCR, "auc")@y.values
[[1]]
[1] 0.7558522
Exercise 9
Add number of pregnancies and age squared as an explanatory variables and redraw the ROC curve on the test set and calculate its AUC.
lg2 <- glm(type ~ age + bmi + npreg, data = train, x = TRUE, family = binomial)
test_pred <- predict(lg2, type = "response", newdata = test)
predROCR <- prediction(test_pred, test$type)
perfROCR <- performance(predROCR, "tpr", "fpr")
plot(perfROCR, colorize = TRUE)

performance(predROCR, "auc")@y.values
[[1]]
[1] 0.7606451
Exercise 10
For a woman aged 35 and mother of 2 children, by how much does the probability of diabetes increase, if her bmi was 35 instead of 25 according to the model? What about the marginal effect at bmi = 25?
ex10data <- data.frame(age = 35, bmi = c(25, 35), npreg = 2)
diff(predict(lg2, type = "response", newdata = ex10data))
diff(predict(lg2, type = "response", newdata = ex10data)) /
predict(lg2, type = "response", newdata = ex10data)[1]
# Marginal effect at the age = 35, bmi = 25, npreg = 2
p <- lgs_fun(lg2$coefficients, c(1, 35, 25, 2))
p*(1 - p)*lg2$coefficients[3]
LS0tDQp0aXRsZTogIkxvZ2lzdGljIHJlZ3Jlc3Npb246IGV4Y2VyY2lzZXMiDQphdXRob3I6ICJIaWNoYW0gWm1hcnJvdSINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzDQogICAgbnVtYmVyX3NlY3Rpb25zOiBubw0KICAgIHRoZW1lOiBjb3Ntbw0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0b2M6IHllcw0KICB3b3JkX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQotLS0NCg0KDQpgYGB7ciBnbG9iYWxfb3B0aW9ucywgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9OCwgZmlnLnBhdGg9J2ltZy8nLA0KICAgICAgICAgICAgICAgICAgICAgIGVjaG89VFJVRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSxkaWdpdHMgPSAzKQ0KbGlicmFyeShicm9vbSkNCmBgYA0KDQoNCg0KIyMjIEV4ZXJjaXNlIDENCkxvYWQgdGhlIE1BU1MgcGFja2FnZSBhbmQgY29tYmluZSBQaW1hLnRyIGFuZCBQaW1hLnRyMiB0byBhIGRhdGEuZnJhbWUgY2FsbGVkIHRyYWluIGFuZCBzYXZlIFBpbWEudGUgYXMgdGVzdC4gDQpDaGFuZ2UgdGhlIGNvZGluZyBvZiBvdXIgdmFyaWFibGUgb2YgaW50ZXJlc3QgdG8gKHR5cGUpIHRvIDAgKG5vbi1kaWFiZXRpYykgYW5kIDEgKGRpYWJldGljKS4gQ2hlY2sgZm9yIGFuZCB0YWtlIG5vdGUgb2YgYW55IG1pc3NpbmcgdmFsdWVzLg0KDQpgYGB7cn0NCmxpYnJhcnkoTUFTUykNCmxpYnJhcnkoZ2dwbG90MikNCg0KdHJhaW4gPC0gcmJpbmQoUGltYS50ciwgUGltYS50cjIpDQp0ZXN0ICA8LSBQaW1hLnRlDQoNCnRyYWluJHR5cGUgPC0gYXMuaW50ZWdlcih0cmFpbiR0eXBlKSAtIDFMDQp0ZXN0JHR5cGUgPC0gYXMuaW50ZWdlcih0ZXN0JHR5cGUpIC0gMUwNCg0KIyBNaXNzaW5nIHZhbHVlcz8NCg0Kc2FwcGx5KHRyYWluLCBmdW5jdGlvbih4KSBzdW0oaXMubmEoeCkpKQ0KYGBgDQoNCg0KDQojIyMgRXhlcmNpc2UgMg0KVGFrZSBhIGxvb2sgYXQgdGhlIGRhdGEuIFBsb3QgYSBzY2F0dGVycGxvdCBtYXRyaXggYmV0d2VlbiBhbGwgdGhlIGV4cGxhbmF0b3J5IHZhcmlhYmxlcyB1c2luZyBwYWlycygpLCBhbmQgY29sb3IgY29kZSB0aGUgZG90cyBhY2NvcmRpbmcgdG8gZGlhYmV0aWMgY2xhc3NpZmljYXRpb24uIEZ1cnRoZXJtb3JlLCB0cnkgdG8gcGxvdCB0eXBlIGFzIGEgZnVuY3Rpb24gb2YgYWdlLiBVc2Ugaml0dGVyIHRvIG1ha2UgeW91ciBncmFwaCBtb3JlIGluZm9ybWF0aXZlLiBCb251czogQ2FuIHlvdSBhZGQgYSBsb2dpc3RpYyBmaXQgYmFzZWQgb24gYWdlIG9uIHRvcCBvZiB5b3VyIHBsb3Q/DQogIA0KYGBge3J9DQpsaWJyYXJ5KEdHYWxseSkNCiNnZ3BhaXJzKGlyaXMsIGFlcyhjb2xvdXIgPSBTcGVjaWVzLCBhbHBoYSA9IDAuNCkpDQpwYWlycyhzdWJzZXQodHJhaW4sIHNlbGVjdCA9IC1jKHR5cGUpKSwgY29sID0gYXMuZmFjdG9yKHRyYWluJHR5cGUpKQ0KI2dncGFpcnModHJhaW4sIGNvbHVtbnMgPSAxOjcsIHRpdGxlID0gIiIsICBjb2xvdXIgPSAidHlwZSIsDQojICBheGlzTGFiZWxzID0gInNob3ciLCBjb2x1bW5MYWJlbHMgPSBjb2xuYW1lcyh0cmFpblssIGNvbHVtbnNdKSkNCmBgYA0KICANCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KZ2dwbG90KHRyYWluLCBhZXMoeCA9IGFnZSwgeSA9IHR5cGUpKSArDQogIGdlb21faml0dGVyKHdpZHRoID0gMC41LCBoZWlnaHQgPSAwLjAzLCBhbHBoYSA9IC4yKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJnbG0iLCBzZSA9IEZBTFNFLA0KICAgICAgICAgICAgICBtZXRob2QuYXJncyA9IGxpc3QoZmFtaWx5ID0gImJpbm9taWFsIikpICsNCiAgbGFicyh5ID0gZXhwcmVzc2lvbihoYXQoUCkoRGlhYmV0aWMpKSkNCmBgYA0KICANCiAgDQogIA0KIyMjIEV4ZXJjaXNlIDMNCg0KVXNpbmcgdGhlIGdsbSgpIGFuZCB0aGUgdHJhaW4gZGF0YSBmaXQgYSBsb2dpc3RpYyBtb2RlbCBvZiB0eXBlIG9uIGFnZSBhbmQgYm1pLiBQcmludCBvdXQgdGhlIGNvZWZmaWNpZW50cyBhbmQgdGhlaXIgcC12YWx1ZS4NCg0KYGBge3J9DQpsaWJyYXJ5KGJyb29tKQ0KbGcxIDwtIGdsbSh0eXBlIH4gYWdlICsgYm1pLCBkYXRhID0gdHJhaW4sIGZhbWlseSA9IGJpbm9taWFsKQ0KIw0KI3N1bW1hcnkobGcxKSRjb2VmZmljaWVudHNbLCBjKDEsIDQpXQ0KdGlkeShsZzEpDQpgYGANCg0KDQojIyMgRXhlcmNpc2UgNA0KV2hhdCBkb2VzIHRoZSBtb2RlbCBmaXR0ZWQgaW4gZXhlcmNpc2UgMyBwcmVkaWN0IGluIHRlcm1zIG9mIHByb2JhYmlsaXR5IGZvciBzb21lb25lIGFnZSAzNSB3aXRoIGJtaSBvZiAzMiwgd2hhdCBhYm91dCBibWkgb2YgMjI/DQogIA0KYGBge3J9DQpwcmVkaWN0KGxnMSwgdHlwZSA9ICJyZXNwb25zZSIsbmV3ZGF0YSA9IGRhdGEuZnJhbWUoYm1pID0gYygzMiwgMjIpLCBhZ2UgPSAzNSkpDQpicm9vbTo6YXVnbWVudCh4PWxnMSwgbmV3ZGF0YSA9IGRhdGEuZnJhbWUoYm1pID0gYygzMiwgMjIpLCBhZ2UgPSAzNSksIHR5cGUucHJlZGljdCA9ICJyZXNwb25zZSIpDQpgYGANCg0KT3IgbWFudWFsbHkgZGVmaW5lIHRoZSBsb2dpc3RpYyBmdW5jdGlvbg0KDQpgYGB7cn0NCiMgT3IgbWFudWFsbHkNCg0KbGdzX2Z1biAgIDwtIGZ1bmN0aW9uKHBhciwgeCkgew0KICAgICAgICAgICAgMSAvICgxICsgZXhwKC14ICUqJSBwYXIpKSAjIHggJSolIHBhciBpcyBsaW5lYXIgYWxnZWJyYSBlcXVpdmFsZW50IG9mIGJfMCArIGJfMSphZ2UgKyBiMipibWkNCiAgICB9DQoNCmxnc19mdW4obGcxJGNvZWZmaWNpZW50cywgYygxLCAzNSwgMzIpKQ0KIyMgICAgICAgICAgIFssMV0NCiMjIFsxLF0gMC4zNDcwOTA4DQpsZ3NfZnVuKGxnMSRjb2VmZmljaWVudHMsIGMoMSwgMzUsIDIyKSkNCmBgYA0KDQoNCiMjIyBFeGVyY2lzZSA1DQoNCkFjY29yZGluZyB0byBvdXIgbW9kZWwgd2hhdCBhcmUgdGhlIG9kZHMgdGhhdCBhIHdvbWFuIGluIG91ciBzYW1wbGUgaXMgZGlhYmV0aWMgZ2l2ZW4gYWdlIDU1IGFuZCBhIGJtaSAzNz8gUmVtZW1iZXIgdGhhdCBvZGRzIGluIHRoaXMgY29udGV4dCBoYXZlIGEgdmVyeSBwcmVjaXNlIGRlZmluaXRpb24gd2hpY2ggaXMgZGlmZmVyZW50IGZyb20gcHJvYmFiaWxpdHkuDQoNCmBgYHtyfQ0KZXhwKHByZWRpY3QobGcxLCByZXNwb25zZSA9ICJsaW5rIiwgbmV3ZGF0YSA9IGRhdGEuZnJhbWUoYWdlID0gNTUsIGJtaSA9IDM3KSkpDQpgYGANCiBPciBsaXRlcmFsbHkNCg0KYGBge3J9DQpsZ3NfZnVuKGxnMSRjb2VmZmljaWVudHMsIGMoMSwgNTUsIDM3KSkgLyAoMSAtIGxnc19mdW4obGcxJGNvZWZmaWNpZW50cywgYygxLCA1NSwgMzcpKSkNCiMgb3Igc2ltcGx5IA0KZXhwKGMoMSwgNTUsIDM3KSAlKiUgbGcxJGNvZWZmaWNpZW50cykNCg0KYGBgDQoNCiMjIyBFeGVyY2lzZSA2DQpCdWlsZCB0aGUgY29uZnVzaW9uIG1hdHJpeCwgYSB0YWJsZSBvZiBhY3R1YWwgZGlhYmV0aWMgY2xhc3NpZmljYXRpb24gYWdhaW5zdCBtb2RlbCBwcmVkaWN0aW9uLiBVc2UgYSBjdXRvZmYgdmFsdWUgb2YgMC41LCBtZWFuaW5nIHRoYXQgd29tZW4gd2hvIHRoZSBtb2RlbCBlc3RpbWF0ZXMgdG8gaGF2ZSBhdCBsZWFzdCAwLjUgY2hhbmNlIG9mIGJlaW5nIGRpYWJldGljIGFyZSBwcmVkaWN0ZWQgdG8gYmUgZGlhYmV0aWMuIFdoYXQgaXMgdGhlIHByZWRpY3Rpb24gYWNjdXJhY3k/DQoNCg0KYGBge3J9DQojIFJlbWVtYmVyIGRhdGEgaXMgbm90IGNvbXBsZXRlIC4uLiB3ZSBoYXZlIG1pc3NpbmcgdmFsdWVzIGZvciBibWkuLi4NCg0KY20xIDwtIHRhYmxlKHRyYWluJHR5cGVbIWlzLm5hKHRyYWluJGJtaSldLCBwcmVkaWN0KGxnMSwgdHlwZSA9ICJyZXNwb25zZSIpID49IDAuNSkNCmNtMQ0KIyBQcmVkaWN0aW9uIGFjY3VyYWN5DQoNCnN1bShkaWFnKGNtMSkpIC8gc3VtKGNtMSkgDQojIA0KIyBzZW5zaXRpdml0eSBvciByZWNhbGwNCnNlbnNpdGl2aXR5KGFjdHVhbHMgPSB0cmFpbiR0eXBlWyFpcy5uYSh0cmFpbiRibWkpXSwgcHJlZGljdGVkU2NvcmVzID0gcHJlZGljdChsZzEsIHR5cGUgPSAicmVzcG9uc2UiKSA+PSAwLjUpDQoNCmBgYA0KICANCiAgDQojIyMgRXhlcmNpc2UgNw0KQXBwbHkgdGhlIGZpdHRlZCBtb2RlbCB0byB0aGUgdGVzdCBzZXQuIFByaW50IHRoZSBjb25mdXNpb24gbWF0cml4IGFuZCBwcmVkaWN0aW9uIGFjY3VyYWN5Lg0KDQpgYGB7cn0NCiMgQ2hlY2sgdGVzdCBzZXQgZm9yIG1pc3NpbmcgdmFsdWVzDQoNCnNhcHBseSh0ZXN0LCBmdW5jdGlvbih4KSBzdW0oaXMubmEoeCkpKSANCmBgYA0KYGBge3J9DQojIEdldCBwcmVkaWN0aW9ucw0KDQp0ZXN0X3ByZWQgPC0gcHJlZGljdChsZzEsIHR5cGUgPSAicmVzcG9uc2UiLCBuZXdkYXRhID0gdGVzdCkNCmBgYA0KDQpPciBtYW51YWxseQ0KYGBge3J9DQp0ZXN0X3ByZWRtIDwtIGxnc19mdW4obGcxJGNvZWZmaWNpZW50cywgYXMubWF0cml4KGNiaW5kKDEsIHRlc3RbLCBjKCJhZ2UiLCAiYm1pIildKSkpDQoNCmNtMV90ZXN0IDwtIHRhYmxlKHRlc3QkdHlwZSwNCiAgICAgICAgICAgICAgICAgIHRlc3RfcHJlZCA+PSAwLjUpDQoNCmNtMV90ZXN0DQpgYGANCmBgYHtyfQ0KIyBQcmVkaWN0aW9uIGFjY3VyYWN5DQoNCnN1bShkaWFnKGNtMV90ZXN0KSkgLyBzdW0oY20xX3Rlc3QpIA0KYGBgDQoNCg0KYGBge3J9DQpjbTJfdGVzdCA9IHRhYmxlKHByZWRpY3RlZCA9IGFzLm51bWVyaWModGVzdF9wcmVkID49IDAuNSksIGFjdHVhbCA9IHRlc3QkdHlwZSkNCmxpYnJhcnkoY2FyZXQpDQp0cmFpbl9jb25fbWF0ID0gY29uZnVzaW9uTWF0cml4KGNtMl90ZXN0KQ0KYyh0cmFpbl9jb25fbWF0JG92ZXJhbGxbIkFjY3VyYWN5Il0sIA0KICB0cmFpbl9jb25fbWF0JGJ5Q2xhc3NbIlNlbnNpdGl2aXR5Il0sIA0KICB0cmFpbl9jb25fbWF0JGJ5Q2xhc3NbIlNwZWNpZmljaXR5Il0pDQpgYGANCg0KDQojIyMgRXhlcmNpc2UgOA0KRHJhdyB1cCB0aGUgUk9DIGN1cnZlIGFuZCBjYWxjdWxhdGUgdGhlIEFVQy4NCg0KYGBge3J9DQpsaWJyYXJ5KFJPQ1IpIA0KIyBmaXJzdCBpbnN0YWxsLnBhY2thZ2VzKCJST0NSIikNCg0KIyMgV2FybmluZzogcGFja2FnZSAnUk9DUicgd2FzIGJ1aWx0IHVuZGVyIFIgdmVyc2lvbiAzLjQuMg0KIyBST0MgY3VydmUNCg0KcHJlZFJPQ1IgPC0gcHJlZGljdGlvbih0ZXN0X3ByZWRtLCB0ZXN0JHR5cGUpDQpwZXJmUk9DUiA8LSBwZXJmb3JtYW5jZShwcmVkUk9DUiwgInRwciIsICJmcHIiKQ0KcGxvdChwZXJmUk9DUiwgY29sb3JpemUgPSBUUlVFKQ0KDQoNCmBgYA0KDQoNCg0KYGBge3J9DQojIENvbXB1dGUgQVVDDQoNCnBlcmZvcm1hbmNlKHByZWRST0NSLCAiYXVjIilAeS52YWx1ZXMNCmBgYA0KDQoNCiMjIyBFeGVyY2lzZSA5DQpBZGQgbnVtYmVyIG9mIHByZWduYW5jaWVzIGFuZCBhZ2Ugc3F1YXJlZCBhcyBhbiBleHBsYW5hdG9yeSB2YXJpYWJsZXMgYW5kIHJlZHJhdyB0aGUgUk9DIGN1cnZlIG9uIHRoZSB0ZXN0IHNldCBhbmQgY2FsY3VsYXRlIGl0cyBBVUMuDQoNCmBgYHtyfQ0KbGcyIDwtIGdsbSh0eXBlIH4gYWdlICsgYm1pICsgbnByZWcsIGRhdGEgPSB0cmFpbiwgeCA9IFRSVUUsIGZhbWlseSA9IGJpbm9taWFsKQ0KDQp0ZXN0X3ByZWQgPC0gcHJlZGljdChsZzIsIHR5cGUgPSAicmVzcG9uc2UiLCBuZXdkYXRhID0gdGVzdCkNCnByZWRST0NSIDwtIHByZWRpY3Rpb24odGVzdF9wcmVkLCB0ZXN0JHR5cGUpDQpwZXJmUk9DUiA8LSBwZXJmb3JtYW5jZShwcmVkUk9DUiwgInRwciIsICJmcHIiKQ0KcGxvdChwZXJmUk9DUiwgY29sb3JpemUgPSBUUlVFKQ0KYGBgDQoNCg0KYGBge3J9DQpwZXJmb3JtYW5jZShwcmVkUk9DUiwgImF1YyIpQHkudmFsdWVzDQpgYGANCg0KIyMjIEV4ZXJjaXNlIDEwDQpGb3IgYSB3b21hbiBhZ2VkIDM1IGFuZCBtb3RoZXIgb2YgMiBjaGlsZHJlbiwgYnkgaG93IG11Y2ggZG9lcyB0aGUgcHJvYmFiaWxpdHkgb2YgZGlhYmV0ZXMgaW5jcmVhc2UsIGlmIGhlciBibWkgd2FzIDM1IGluc3RlYWQgb2YgMjUgYWNjb3JkaW5nIHRvIHRoZSBtb2RlbD8gV2hhdCBhYm91dCB0aGUgbWFyZ2luYWwgZWZmZWN0IGF0IGJtaSA9IDI1Pw0KDQoNCmBgYHtyfQ0KZXgxMGRhdGEgPC0gZGF0YS5mcmFtZShhZ2UgPSAzNSwgYm1pID0gYygyNSwgMzUpLCBucHJlZyA9IDIpDQpkaWZmKHByZWRpY3QobGcyLCB0eXBlID0gInJlc3BvbnNlIiwgbmV3ZGF0YSA9IGV4MTBkYXRhKSkgDQoNCmRpZmYocHJlZGljdChsZzIsIHR5cGUgPSAicmVzcG9uc2UiLCBuZXdkYXRhID0gZXgxMGRhdGEpKSAvDQogIHByZWRpY3QobGcyLCB0eXBlID0gInJlc3BvbnNlIiwgbmV3ZGF0YSA9IGV4MTBkYXRhKVsxXSANCiMgTWFyZ2luYWwgZWZmZWN0IGF0IHRoZSBhZ2UgPSAzNSwgYm1pID0gMjUsIG5wcmVnID0gMg0KDQpwIDwtIGxnc19mdW4obGcyJGNvZWZmaWNpZW50cywgYygxLCAzNSwgMjUsIDIpKQ0KcCooMSAtIHApKmxnMiRjb2VmZmljaWVudHNbM10gDQpgYGANCg0KDQoNCg0KDQoNCg==