1. Consider the following customer: Age = 40, Experience = 10, Income = 84, Family = 2, CCAvg = 2, Education_1 = 0, Education_2 = 1, Education_3 = 0, Mortgage = 0, Securities Account = 0, CD Account = 0, Online = 1, and Credit Card =
  1. Perform a k-NN classification with all predictors except ID and ZIP code using k = 1. Remember to transform categorical predictors with more than two categories into dummy variables first. Specify the success class as 1 (loan acceptance), and use the default cutoff value of 0.5. How would this customer be classified?
library("dplyr")
library("tidyr")
library("ggplot2")
library("ROCR")
library("rpart")
library("rpart.plot")
library("caret")
library("randomForest")
library("tidyverse")
library("tm")
library("SnowballC")
library("softImpute")
library("glmnet")
library("Hmisc")
library("dummies")
library('tinytex')
library('GGally')
library('gplots')
library('FNN')
library("dplyr")
library("tidyr")
library("caTools")
library("ggpubr")
library("reshape")
  1. Consider the following customer: Age = 40, Experience = 10, Income = 84, Family = 2, CCAvg = 2, Education_1 = 0, Education_2 = 1, Education_3 = 0, Mortgage = 0, Securities Account = 0, CD Account = 0, Online = 1, and Credit Card = 1. Perform a k-NN classification with all predictors except ID and ZIP code using k = 1. Remember to transform categorical predictors with more than two categories into dummy variables first. Specify the success class as 1 (loan acceptance), and use the default cutoff value of 0.5. How would this customer be classified?
rm(list=ls())
setwd("/Users/kayhanbabakan/OneDrive/MIT/Data Mining/Data_export")
bank = read.csv("/Users/kayhanbabakan/OneDrive/MIT/Data Mining/Data_export/UniversalBank.csv")
bank$Education = as.factor(bank$Education)

bank_dummy = dummy.data.frame(select(bank,-c(ZIP.Code,ID)))
bank_dummy$Personal.Loan = as.factor(bank_dummy$Personal.Loan)
bank_dummy$CCAvg = as.integer(bank_dummy$CCAvg)

set.seed(1)
train.index <- sample(row.names(bank_dummy), 0.6*dim(bank_dummy)[1])  ## need to look at hints
test.index <- setdiff(row.names(bank_dummy), train.index) 
train.df <- bank_dummy[train.index, ]
valid.df <- bank_dummy[test.index, ]

new.df = data.frame(Age = as.integer(40), Experience = as.integer(10), Income = as.integer(84), Family = as.integer(2), CCAvg = as.integer(2), Education1 = as.integer(0), Education2 = as.integer(1), Education3 = as.integer(0), Mortgage = as.integer(0), Securities.Account = as.integer(0), CD.Account = as.integer(0), Online = as.integer(1), CreditCard = as.integer(1))


norm.values <- preProcess(train.df[, -c(10)], method=c("center", "scale"))
train.df[, -c(10)] <- predict(norm.values, train.df[, -c(10)])
valid.df[, -c(10)] <- predict(norm.values, valid.df[, -c(10)])
new.df <- predict(norm.values, new.df)

knn.1 <- knn(train = train.df[,-c(10)],test = new.df, cl = train.df[,10], k=5, prob=TRUE)
knn.attributes <- attributes(knn.1)
knn.attributes[1]
$levels
[1] "0"
knn.attributes[3]
$prob
[1] 1

all 5 nearest neighbors will classified as a 0, in turn the customer will be classified as a 0.

  1. What is a choice of k that balances between overfitting and ignoring the predictor information?
accuracy.df <- data.frame(k = seq(1, 14, 1), accuracy = rep(0, 14))

for(i in 1:14) {
  knn.2 <- knn(train = train.df[,-10],test = valid.df[,-10], cl = train.df[,10], k=i, prob=TRUE)
  accuracy.df[i, 2] <- confusionMatrix(knn.2, valid.df[,10])$overall[1]
}
accuracy.df

the best choice of k which also balances the model from overfitting is k = 3

  1. Show the confusion matrix for the validation data that results from using the best k.
knn.3 <- knn(train = train.df[,-10],test = valid.df[,-10], cl = train.df[,10], k=3, prob=TRUE)
confusionMatrix(knn.3, valid.df[,10])
Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 1787   64
         1    8  141
                                          
               Accuracy : 0.964           
                 95% CI : (0.9549, 0.9717)
    No Information Rate : 0.8975          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.7774          
                                          
 Mcnemar's Test P-Value : 9.063e-11       
                                          
            Sensitivity : 0.9955          
            Specificity : 0.6878          
         Pos Pred Value : 0.9654          
         Neg Pred Value : 0.9463          
             Prevalence : 0.8975          
         Detection Rate : 0.8935          
   Detection Prevalence : 0.9255          
      Balanced Accuracy : 0.8417          
                                          
       'Positive' Class : 0               
                                          

confusion matrix as per above

  1. Consider the following customer: Age = 40, Experience = 10, Income = 84, Family = 2, CCAvg = 2, Education_1 = 0, Education_2 = 1, Education_3 = 0, Mortgage = 0, Securities Account = 0, CD Account = 0, Online = 1 and Credit Card = 1. Classify the customer using the best k.
customer.df= data.frame(Age = 40, Experience = 10, Income = 84, Family = 2, CCAvg = 2, Education_1 = 0, Education_2 = 1, Education_3 = 0, Mortgage = 0, Securities.Account = 0, CD.Account = 0, Online = 1, CreditCard = 1)
knn.4 <- knn(train = train.df[,-10],test = customer.df, cl = train.df[,10], k=3, prob=TRUE)
knn.4
[1] 1
attr(,"prob")
[1] 1
attr(,"nn.index")
     [,1] [,2] [,3]
[1,] 2721 2146  939
attr(,"nn.dist")
         [,1]     [,2]     [,3]
[1,] 90.51969 90.53808 90.56426
Levels: 1

customer is classified as a 1 with 100% probability

  1. Repartition the data, this time into training, validation, and test sets (50% : 30% : 20%). Apply the k-NN method with the k chosen above. Compare the confusion matrix of the test set with that of the training and validation sets. Comment on the differences and their reason.

bank_dummy = dummy.data.frame(select(bank,-c(ZIP.Code,ID)))
bank_dummy$Personal.Loan = as.factor(bank_dummy$Personal.Loan)
bank_dummy$CCAvg = as.integer(bank_dummy$CCAvg)

set.seed(1)
train.index <- sample(rownames(bank_dummy), 0.5*dim(bank_dummy)[1])  ## need to look at hints
set.seed(1)
valid.index <- sample(setdiff(rownames(bank_dummy),train.index), 0.3*dim(bank_dummy)[1])
test.index = setdiff(rownames(bank_dummy), union(train.index, valid.index))

train.df <- bank_dummy[train.index, ]
valid.df <- bank_dummy[valid.index, ]
test.df <- bank_dummy[test.index, ]

norm.values <- preProcess(train.df[, -c(10)], method=c("center", "scale"))
train.df[, -c(10)] <- predict(norm.values, train.df[, -c(10)])
valid.df[, -c(10)] <- predict(norm.values, valid.df[, -c(10)])
test.df[,-c(10)] <- predict(norm.values, test.df[,-c(10)])

testknn <- knn(train = train.df[,-c(10)],test = test.df[,-c(10)], cl = train.df[,10], k=3, prob=TRUE)
validknn <- knn(train = train.df[,-c(10)],test = valid.df[,-c(10)], cl = train.df[,10], k=3, prob=TRUE)
trainknn <- knn(train = train.df[,-c(10)],test = train.df[,-c(10)], cl = train.df[,10], k=3, prob=TRUE)

confusionMatrix(testknn, test.df[,10])
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0 890  34
         1   2  74
                                          
               Accuracy : 0.964           
                 95% CI : (0.9505, 0.9747)
    No Information Rate : 0.892           
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.7852          
                                          
 Mcnemar's Test P-Value : 2.383e-07       
                                          
            Sensitivity : 0.9978          
            Specificity : 0.6852          
         Pos Pred Value : 0.9632          
         Neg Pred Value : 0.9737          
             Prevalence : 0.8920          
         Detection Rate : 0.8900          
   Detection Prevalence : 0.9240          
      Balanced Accuracy : 0.8415          
                                          
       'Positive' Class : 0               
                                          
confusionMatrix(validknn, valid.df[,10])
Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 1355   45
         1    5   95
                                          
               Accuracy : 0.9667          
                 95% CI : (0.9563, 0.9752)
    No Information Rate : 0.9067          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.7741          
                                          
 Mcnemar's Test P-Value : 3.479e-08       
                                          
            Sensitivity : 0.9963          
            Specificity : 0.6786          
         Pos Pred Value : 0.9679          
         Neg Pred Value : 0.9500          
             Prevalence : 0.9067          
         Detection Rate : 0.9033          
   Detection Prevalence : 0.9333          
      Balanced Accuracy : 0.8374          
                                          
       'Positive' Class : 0               
                                          
confusionMatrix(trainknn, train.df[,10])
Confusion Matrix and Statistics

          Reference
Prediction    0    1
         0 2264   51
         1    4  181
                                          
               Accuracy : 0.978           
                 95% CI : (0.9715, 0.9834)
    No Information Rate : 0.9072          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.8563          
                                          
 Mcnemar's Test P-Value : 5.552e-10       
                                          
            Sensitivity : 0.9982          
            Specificity : 0.7802          
         Pos Pred Value : 0.9780          
         Neg Pred Value : 0.9784          
             Prevalence : 0.9072          
         Detection Rate : 0.9056          
   Detection Prevalence : 0.9260          
      Balanced Accuracy : 0.8892          
                                          
       'Positive' Class : 0               
                                          

Test Accuracy : 0.964
Valid Accuracy: 0.9667
Train Accuracy: 0.978

As the model is being fit on the training data it would make intuitive sense that the classifications are most accurate on the training data set and least accurate on the test datasets.

LS0tCnRpdGxlOiAiMTUuMDYyIEhvbWV3b3JrIDIiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KYS4gQ29uc2lkZXIgdGhlIGZvbGxvd2luZyBjdXN0b21lcjoKQWdlID0gNDAsIEV4cGVyaWVuY2UgPSAxMCwgSW5jb21lID0gODQsIEZhbWlseSA9IDIsIENDQXZnID0gMiwgRWR1Y2F0aW9uXzEgPSAwLCBFZHVjYXRpb25fMiA9IDEsIEVkdWNhdGlvbl8zID0gMCwgTW9ydGdhZ2UgPSAwLCBTZWN1cml0aWVzIEFjY291bnQgPSAwLCBDRCBBY2NvdW50ID0gMCwgT25saW5lID0gMSwgYW5kIENyZWRpdCBDYXJkID0gCjEuIFBlcmZvcm0gYSBrLU5OIGNsYXNzaWZpY2F0aW9uIHdpdGggYWxsIHByZWRpY3RvcnMgZXhjZXB0IElEIGFuZCBaSVAgY29kZSB1c2luZyBrID0gMS4gUmVtZW1iZXIgdG8gdHJhbnNmb3JtIGNhdGVnb3JpY2FsIHByZWRpY3RvcnMgd2l0aCBtb3JlIHRoYW4gdHdvIGNhdGVnb3JpZXMgaW50byBkdW1teSB2YXJpYWJsZXMgZmlyc3QuIApTcGVjaWZ5IHRoZSBzdWNjZXNzIGNsYXNzIGFzIDEgKGxvYW4gYWNjZXB0YW5jZSksIGFuZCB1c2UgdGhlIGRlZmF1bHQgY3V0b2ZmIHZhbHVlIG9mIDAuNS4gSG93IHdvdWxkIHRoaXMgY3VzdG9tZXIgYmUgY2xhc3NpZmllZD8gCgoKYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KCJkcGx5ciIpCmxpYnJhcnkoInRpZHlyIikKbGlicmFyeSgiZ2dwbG90MiIpCmxpYnJhcnkoIlJPQ1IiKQpsaWJyYXJ5KCJycGFydCIpCmxpYnJhcnkoInJwYXJ0LnBsb3QiKQpsaWJyYXJ5KCJjYXJldCIpCmxpYnJhcnkoInJhbmRvbUZvcmVzdCIpCmxpYnJhcnkoInRpZHl2ZXJzZSIpCmxpYnJhcnkoInRtIikKbGlicmFyeSgiU25vd2JhbGxDIikKbGlicmFyeSgic29mdEltcHV0ZSIpCmxpYnJhcnkoImdsbW5ldCIpCmxpYnJhcnkoIkhtaXNjIikKbGlicmFyeSgiZHVtbWllcyIpCmxpYnJhcnkoJ3Rpbnl0ZXgnKQpsaWJyYXJ5KCdHR2FsbHknKQpsaWJyYXJ5KCdncGxvdHMnKQpsaWJyYXJ5KCdGTk4nKQpsaWJyYXJ5KCJkcGx5ciIpCmxpYnJhcnkoInRpZHlyIikKbGlicmFyeSgiY2FUb29scyIpCmxpYnJhcnkoImdncHViciIpCmxpYnJhcnkoInJlc2hhcGUiKQpgYGAKCmEuIENvbnNpZGVyIHRoZSBmb2xsb3dpbmcgY3VzdG9tZXI6CkFnZSA9IDQwLCBFeHBlcmllbmNlID0gMTAsIEluY29tZSA9IDg0LCBGYW1pbHkgPSAyLCBDQ0F2ZyA9IDIsIEVkdWNhdGlvbl8xID0gMCwgRWR1Y2F0aW9uXzIgPSAxLCBFZHVjYXRpb25fMyA9IDAsIE1vcnRnYWdlID0gMCwgU2VjdXJpdGllcyBBY2NvdW50ID0gMCwgQ0QgQWNjb3VudCA9IDAsIE9ubGluZSA9IDEsIGFuZCBDcmVkaXQgQ2FyZCA9IDEuIFBlcmZvcm0gYSBrLU5OIGNsYXNzaWZpY2F0aW9uIHdpdGggYWxsIHByZWRpY3RvcnMgZXhjZXB0IElEIGFuZCBaSVAgY29kZSB1c2luZyBrID0gMS4gUmVtZW1iZXIgdG8gdHJhbnNmb3JtIGNhdGVnb3JpY2FsIHByZWRpY3RvcnMgd2l0aCBtb3JlIHRoYW4gdHdvIGNhdGVnb3JpZXMgaW50byBkdW1teSB2YXJpYWJsZXMgZmlyc3QuIFNwZWNpZnkgdGhlIHN1Y2Nlc3MgY2xhc3MgYXMgMSAobG9hbiBhY2NlcHRhbmNlKSwgYW5kIHVzZSB0aGUgZGVmYXVsdCBjdXRvZmYgdmFsdWUgb2YgMC41LiBIb3cgd291bGQgdGhpcyBjdXN0b21lciBiZSBjbGFzc2lmaWVkPwoKYGBge3Igd2FybmluZz1GQUxTRX0Kcm0obGlzdD1scygpKQpzZXR3ZCgiL1VzZXJzL2theWhhbmJhYmFrYW4vT25lRHJpdmUvTUlUL0RhdGEgTWluaW5nL0RhdGFfZXhwb3J0IikKYmFuayA9IHJlYWQuY3N2KCIvVXNlcnMva2F5aGFuYmFiYWthbi9PbmVEcml2ZS9NSVQvRGF0YSBNaW5pbmcvRGF0YV9leHBvcnQvVW5pdmVyc2FsQmFuay5jc3YiKQpiYW5rJEVkdWNhdGlvbiA9IGFzLmZhY3RvcihiYW5rJEVkdWNhdGlvbikKCmJhbmtfZHVtbXkgPSBkdW1teS5kYXRhLmZyYW1lKHNlbGVjdChiYW5rLC1jKFpJUC5Db2RlLElEKSkpCmJhbmtfZHVtbXkkUGVyc29uYWwuTG9hbiA9IGFzLmZhY3RvcihiYW5rX2R1bW15JFBlcnNvbmFsLkxvYW4pCmJhbmtfZHVtbXkkQ0NBdmcgPSBhcy5pbnRlZ2VyKGJhbmtfZHVtbXkkQ0NBdmcpCgpzZXQuc2VlZCgxKQp0cmFpbi5pbmRleCA8LSBzYW1wbGUocm93Lm5hbWVzKGJhbmtfZHVtbXkpLCAwLjYqZGltKGJhbmtfZHVtbXkpWzFdKSAgIyMgbmVlZCB0byBsb29rIGF0IGhpbnRzCnRlc3QuaW5kZXggPC0gc2V0ZGlmZihyb3cubmFtZXMoYmFua19kdW1teSksIHRyYWluLmluZGV4KSAKdHJhaW4uZGYgPC0gYmFua19kdW1teVt0cmFpbi5pbmRleCwgXQp2YWxpZC5kZiA8LSBiYW5rX2R1bW15W3Rlc3QuaW5kZXgsIF0KCm5ldy5kZiA9IGRhdGEuZnJhbWUoQWdlID0gYXMuaW50ZWdlcig0MCksIEV4cGVyaWVuY2UgPSBhcy5pbnRlZ2VyKDEwKSwgSW5jb21lID0gYXMuaW50ZWdlcig4NCksIEZhbWlseSA9IGFzLmludGVnZXIoMiksIENDQXZnID0gYXMuaW50ZWdlcigyKSwgRWR1Y2F0aW9uMSA9IGFzLmludGVnZXIoMCksIEVkdWNhdGlvbjIgPSBhcy5pbnRlZ2VyKDEpLCBFZHVjYXRpb24zID0gYXMuaW50ZWdlcigwKSwgTW9ydGdhZ2UgPSBhcy5pbnRlZ2VyKDApLCBTZWN1cml0aWVzLkFjY291bnQgPSBhcy5pbnRlZ2VyKDApLCBDRC5BY2NvdW50ID0gYXMuaW50ZWdlcigwKSwgT25saW5lID0gYXMuaW50ZWdlcigxKSwgQ3JlZGl0Q2FyZCA9IGFzLmludGVnZXIoMSkpCgoKbm9ybS52YWx1ZXMgPC0gcHJlUHJvY2Vzcyh0cmFpbi5kZlssIC1jKDEwKV0sIG1ldGhvZD1jKCJjZW50ZXIiLCAic2NhbGUiKSkKdHJhaW4uZGZbLCAtYygxMCldIDwtIHByZWRpY3Qobm9ybS52YWx1ZXMsIHRyYWluLmRmWywgLWMoMTApXSkKdmFsaWQuZGZbLCAtYygxMCldIDwtIHByZWRpY3Qobm9ybS52YWx1ZXMsIHZhbGlkLmRmWywgLWMoMTApXSkKbmV3LmRmIDwtIHByZWRpY3Qobm9ybS52YWx1ZXMsIG5ldy5kZikKCmtubi4xIDwtIGtubih0cmFpbiA9IHRyYWluLmRmWywtYygxMCldLHRlc3QgPSBuZXcuZGYsIGNsID0gdHJhaW4uZGZbLDEwXSwgaz01LCBwcm9iPVRSVUUpCmtubi5hdHRyaWJ1dGVzIDwtIGF0dHJpYnV0ZXMoa25uLjEpCmtubi5hdHRyaWJ1dGVzWzFdCmtubi5hdHRyaWJ1dGVzWzNdCmBgYAo8c21hbGw+PGZvbnQgY29sb3IgPSAicmVkIj4KYWxsIDUgbmVhcmVzdCBuZWlnaGJvcnMgd2lsbCBjbGFzc2lmaWVkIGFzIGEgMCwgaW4gdHVybiB0aGUgY3VzdG9tZXIgd2lsbCBiZSBjbGFzc2lmaWVkIGFzIGEgMC4KPC9zbWFsbD48L2ZvbnQ+CgoyLglXaGF0IGlzIGEgY2hvaWNlIG9mIGsgdGhhdCBiYWxhbmNlcyBiZXR3ZWVuIG92ZXJmaXR0aW5nIGFuZCBpZ25vcmluZyB0aGUgcHJlZGljdG9yIGluZm9ybWF0aW9uPyAKYGBge3J9CmFjY3VyYWN5LmRmIDwtIGRhdGEuZnJhbWUoayA9IHNlcSgxLCAxNCwgMSksIGFjY3VyYWN5ID0gcmVwKDAsIDE0KSkKCmZvcihpIGluIDE6MTQpIHsKICBrbm4uMiA8LSBrbm4odHJhaW4gPSB0cmFpbi5kZlssLTEwXSx0ZXN0ID0gdmFsaWQuZGZbLC0xMF0sIGNsID0gdHJhaW4uZGZbLDEwXSwgaz1pLCBwcm9iPVRSVUUpCiAgYWNjdXJhY3kuZGZbaSwgMl0gPC0gY29uZnVzaW9uTWF0cml4KGtubi4yLCB2YWxpZC5kZlssMTBdKSRvdmVyYWxsWzFdCn0KYWNjdXJhY3kuZGYKYGBgCjxzbWFsbD48Zm9udCBjb2xvciA9ICJyZWQiPgp0aGUgYmVzdCBjaG9pY2Ugb2YgayB3aGljaCBhbHNvIGJhbGFuY2VzIHRoZSBtb2RlbCBmcm9tIG92ZXJmaXR0aW5nIGlzIGsgPSAzCjwvc21hbGw+PC9mb250PgoKMy4JU2hvdyB0aGUgY29uZnVzaW9uIG1hdHJpeCBmb3IgdGhlIHZhbGlkYXRpb24gZGF0YSB0aGF0IHJlc3VsdHMgZnJvbSB1c2luZyB0aGUgYmVzdCBrLiAKYGBge3J9Cmtubi4zIDwtIGtubih0cmFpbiA9IHRyYWluLmRmWywtMTBdLHRlc3QgPSB2YWxpZC5kZlssLTEwXSwgY2wgPSB0cmFpbi5kZlssMTBdLCBrPTMsIHByb2I9VFJVRSkKY29uZnVzaW9uTWF0cml4KGtubi4zLCB2YWxpZC5kZlssMTBdKQpgYGAKPHNtYWxsPjxmb250IGNvbG9yID0gInJlZCI+CmNvbmZ1c2lvbiBtYXRyaXggYXMgcGVyIGFib3ZlIDwvc21hbGw+PC9mb250PgoKNC4JQ29uc2lkZXIgdGhlIGZvbGxvd2luZyBjdXN0b21lcjogQWdlID0gNDAsIEV4cGVyaWVuY2UgPSAxMCwgSW5jb21lID0gODQsIEZhbWlseSA9IDIsIENDQXZnID0gMiwgRWR1Y2F0aW9uXzEgPSAwLCBFZHVjYXRpb25fMiA9IDEsIEVkdWNhdGlvbl8zID0gMCwgTW9ydGdhZ2UgPSAwLCBTZWN1cml0aWVzIEFjY291bnQgPSAwLCBDRCBBY2NvdW50ID0gMCwgT25saW5lID0gMSBhbmQgQ3JlZGl0IENhcmQgPSAxLiBDbGFzc2lmeSB0aGUgY3VzdG9tZXIgdXNpbmcgdGhlIGJlc3Qgay4KYGBge3J9CmN1c3RvbWVyLmRmPSBkYXRhLmZyYW1lKEFnZSA9IDQwLCBFeHBlcmllbmNlID0gMTAsIEluY29tZSA9IDg0LCBGYW1pbHkgPSAyLCBDQ0F2ZyA9IDIsIEVkdWNhdGlvbl8xID0gMCwgRWR1Y2F0aW9uXzIgPSAxLCBFZHVjYXRpb25fMyA9IDAsIE1vcnRnYWdlID0gMCwgU2VjdXJpdGllcy5BY2NvdW50ID0gMCwgQ0QuQWNjb3VudCA9IDAsIE9ubGluZSA9IDEsIENyZWRpdENhcmQgPSAxKQprbm4uNCA8LSBrbm4odHJhaW4gPSB0cmFpbi5kZlssLTEwXSx0ZXN0ID0gY3VzdG9tZXIuZGYsIGNsID0gdHJhaW4uZGZbLDEwXSwgaz0zLCBwcm9iPVRSVUUpCmtubi40CmBgYAo8c21hbGw+PGZvbnQgY29sb3IgPSAicmVkIj4KY3VzdG9tZXIgaXMgY2xhc3NpZmllZCBhcyBhIDEgd2l0aCAxMDAlIHByb2JhYmlsaXR5Cjwvc21hbGw+PC9mb250PgoKNS4gUmVwYXJ0aXRpb24gdGhlIGRhdGEsIHRoaXMgdGltZSBpbnRvIHRyYWluaW5nLCB2YWxpZGF0aW9uLCBhbmQgdGVzdCBzZXRzICg1MCUgOiAzMCUgOiAyMCUpLiBBcHBseSB0aGUgay1OTiBtZXRob2Qgd2l0aCB0aGUgayBjaG9zZW4gYWJvdmUuIENvbXBhcmUgdGhlIGNvbmZ1c2lvbiBtYXRyaXggb2YgdGhlIHRlc3Qgc2V0IHdpdGggdGhhdCBvZiB0aGUgdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gc2V0cy4gQ29tbWVudCBvbiB0aGUgZGlmZmVyZW5jZXMgYW5kIHRoZWlyIHJlYXNvbi4gCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgpiYW5rX2R1bW15ID0gZHVtbXkuZGF0YS5mcmFtZShzZWxlY3QoYmFuaywtYyhaSVAuQ29kZSxJRCkpKQpiYW5rX2R1bW15JFBlcnNvbmFsLkxvYW4gPSBhcy5mYWN0b3IoYmFua19kdW1teSRQZXJzb25hbC5Mb2FuKQpiYW5rX2R1bW15JENDQXZnID0gYXMuaW50ZWdlcihiYW5rX2R1bW15JENDQXZnKQoKc2V0LnNlZWQoMSkKdHJhaW4uaW5kZXggPC0gc2FtcGxlKHJvd25hbWVzKGJhbmtfZHVtbXkpLCAwLjUqZGltKGJhbmtfZHVtbXkpWzFdKSAgIyMgbmVlZCB0byBsb29rIGF0IGhpbnRzCnNldC5zZWVkKDEpCnZhbGlkLmluZGV4IDwtIHNhbXBsZShzZXRkaWZmKHJvd25hbWVzKGJhbmtfZHVtbXkpLHRyYWluLmluZGV4KSwgMC4zKmRpbShiYW5rX2R1bW15KVsxXSkKdGVzdC5pbmRleCA9IHNldGRpZmYocm93bmFtZXMoYmFua19kdW1teSksIHVuaW9uKHRyYWluLmluZGV4LCB2YWxpZC5pbmRleCkpCgp0cmFpbi5kZiA8LSBiYW5rX2R1bW15W3RyYWluLmluZGV4LCBdCnZhbGlkLmRmIDwtIGJhbmtfZHVtbXlbdmFsaWQuaW5kZXgsIF0KdGVzdC5kZiA8LSBiYW5rX2R1bW15W3Rlc3QuaW5kZXgsIF0KCm5vcm0udmFsdWVzIDwtIHByZVByb2Nlc3ModHJhaW4uZGZbLCAtYygxMCldLCBtZXRob2Q9YygiY2VudGVyIiwgInNjYWxlIikpCnRyYWluLmRmWywgLWMoMTApXSA8LSBwcmVkaWN0KG5vcm0udmFsdWVzLCB0cmFpbi5kZlssIC1jKDEwKV0pCnZhbGlkLmRmWywgLWMoMTApXSA8LSBwcmVkaWN0KG5vcm0udmFsdWVzLCB2YWxpZC5kZlssIC1jKDEwKV0pCnRlc3QuZGZbLC1jKDEwKV0gPC0gcHJlZGljdChub3JtLnZhbHVlcywgdGVzdC5kZlssLWMoMTApXSkKCnRlc3Rrbm4gPC0ga25uKHRyYWluID0gdHJhaW4uZGZbLC1jKDEwKV0sdGVzdCA9IHRlc3QuZGZbLC1jKDEwKV0sIGNsID0gdHJhaW4uZGZbLDEwXSwgaz0zLCBwcm9iPVRSVUUpCnZhbGlka25uIDwtIGtubih0cmFpbiA9IHRyYWluLmRmWywtYygxMCldLHRlc3QgPSB2YWxpZC5kZlssLWMoMTApXSwgY2wgPSB0cmFpbi5kZlssMTBdLCBrPTMsIHByb2I9VFJVRSkKdHJhaW5rbm4gPC0ga25uKHRyYWluID0gdHJhaW4uZGZbLC1jKDEwKV0sdGVzdCA9IHRyYWluLmRmWywtYygxMCldLCBjbCA9IHRyYWluLmRmWywxMF0sIGs9MywgcHJvYj1UUlVFKQoKY29uZnVzaW9uTWF0cml4KHRlc3Rrbm4sIHRlc3QuZGZbLDEwXSkKY29uZnVzaW9uTWF0cml4KHZhbGlka25uLCB2YWxpZC5kZlssMTBdKQpjb25mdXNpb25NYXRyaXgodHJhaW5rbm4sIHRyYWluLmRmWywxMF0pCmBgYAo8Zm9udCBjb2xvcj0icmVkIj48c21hbGw+ClRlc3QgQWNjdXJhY3kgOiAwLjk2NDwvYnI+ClZhbGlkIEFjY3VyYWN5OiAwLjk2Njc8L2JyPgpUcmFpbiBBY2N1cmFjeTogMC45NzggCgpBcyB0aGUgbW9kZWwgaXMgYmVpbmcgZml0IG9uIHRoZSB0cmFpbmluZyBkYXRhIGl0IHdvdWxkIG1ha2UgaW50dWl0aXZlIHNlbnNlIHRoYXQgdGhlIGNsYXNzaWZpY2F0aW9ucyBhcmUgbW9zdCBhY2N1cmF0ZSBvbiB0aGUgdHJhaW5pbmcgZGF0YSBzZXQgYW5kIGxlYXN0IGFjY3VyYXRlIG9uIHRoZSB0ZXN0IGRhdGFzZXRzLgoKCg==