rm(list=ls(all=T))
options(digits=4, scipen=12)
library(magrittr)

Introduction

議題:用申請假釋者的屬性,預測他會不會違反假釋規定

學習重點:



1 資料處理 Loading the Dataset

1.1 讀進資料】How many parolees are contained in the dataset?

parole = read.csv("data/parole.csv")
str(parole)
'data.frame':   675 obs. of  9 variables:
 $ male             : int  1 0 1 1 1 1 1 0 0 1 ...
 $ race             : int  1 1 2 1 2 2 1 1 1 2 ...
 $ age              : num  33.2 39.7 29.5 22.4 21.6 46.7 31 24.6 32.6 29.1 ...
 $ state            : int  1 1 1 1 1 1 1 1 1 1 ...
 $ time.served      : num  5.5 5.4 5.6 5.7 5.4 6 6 4.8 4.5 4.7 ...
 $ max.sentence     : int  18 12 12 18 12 18 18 12 13 12 ...
 $ multiple.offenses: int  0 0 0 0 0 0 0 0 0 0 ...
 $ crime            : int  4 3 3 1 1 4 3 1 3 2 ...
 $ violator         : int  0 0 0 0 0 0 0 0 0 0 ...
print("675")
[1] "675"

1.2 底線機率】How many of the parolees in the dataset violated the terms of their parole?

table(parole$violator)

  0   1 
597  78 
print("78")
[1] "78"



2 整理資料框 Creating Our Prediction Model

2.1 類別變數】Which variables in this dataset are unordered factors with at least three levels?

str(parole)
'data.frame':   675 obs. of  9 variables:
 $ male             : int  1 0 1 1 1 1 1 0 0 1 ...
 $ race             : int  1 1 2 1 2 2 1 1 1 2 ...
 $ age              : num  33.2 39.7 29.5 22.4 21.6 46.7 31 24.6 32.6 29.1 ...
 $ state            : int  1 1 1 1 1 1 1 1 1 1 ...
 $ time.served      : num  5.5 5.4 5.6 5.7 5.4 6 6 4.8 4.5 4.7 ...
 $ max.sentence     : int  18 12 12 18 12 18 18 12 13 12 ...
 $ multiple.offenses: int  0 0 0 0 0 0 0 0 0 0 ...
 $ crime            : int  4 3 3 1 1 4 3 1 3 2 ...
 $ violator         : int  0 0 0 0 0 0 0 0 0 0 ...
table(parole$state)

  1   2   3   4 
143 120  82 330 
table(parole$race)

  1   2 
389 286 
table(parole$crime)

  1   2   3   4 
315 106 153 101 
print("crime, state")
[1] "crime, state"

2.2 資料框摘要】How does the output of summary() change for a factor variable as compared to a numerical variable?

str(parole)
'data.frame':   675 obs. of  9 variables:
 $ male             : int  1 0 1 1 1 1 1 0 0 1 ...
 $ race             : int  1 1 2 1 2 2 1 1 1 2 ...
 $ age              : num  33.2 39.7 29.5 22.4 21.6 46.7 31 24.6 32.6 29.1 ...
 $ state            : int  1 1 1 1 1 1 1 1 1 1 ...
 $ time.served      : num  5.5 5.4 5.6 5.7 5.4 6 6 4.8 4.5 4.7 ...
 $ max.sentence     : int  18 12 12 18 12 18 18 12 13 12 ...
 $ multiple.offenses: int  0 0 0 0 0 0 0 0 0 0 ...
 $ crime            : int  4 3 3 1 1 4 3 1 3 2 ...
 $ violator         : int  0 0 0 0 0 0 0 0 0 0 ...
parole$state = as.factor(parole$state)
parole$crime = as.factor(parole$crime)
str(parole)
'data.frame':   675 obs. of  9 variables:
 $ male             : int  1 0 1 1 1 1 1 0 0 1 ...
 $ race             : int  1 1 2 1 2 2 1 1 1 2 ...
 $ age              : num  33.2 39.7 29.5 22.4 21.6 46.7 31 24.6 32.6 29.1 ...
 $ state            : Factor w/ 4 levels "1","2","3","4": 1 1 1 1 1 1 1 1 1 1 ...
 $ time.served      : num  5.5 5.4 5.6 5.7 5.4 6 6 4.8 4.5 4.7 ...
 $ max.sentence     : int  18 12 12 18 12 18 18 12 13 12 ...
 $ multiple.offenses: int  0 0 0 0 0 0 0 0 0 0 ...
 $ crime            : Factor w/ 4 levels "1","2","3","4": 4 3 3 1 1 4 3 1 3 2 ...
 $ violator         : int  0 0 0 0 0 0 0 0 0 0 ...
print("The output becomes similar to that of the table() function applied to that variable")
[1] "The output becomes similar to that of the table() function applied to that variable"



3 資料分割 Splitting into a Training and Testing Set

3.1 指定隨機種子、依比例分割資料】Roughly what proportion of parolees have been allocated to the training and testing sets?

set.seed(144)
library(caTools)
split = sample.split(parole$violator, SplitRatio = 0.7)
train = subset(parole, split == TRUE)
test = subset(parole, split == FALSE)
nrow(train)
[1] 473
nrow(test)
[1] 202
473/(473+202)
[1] 0.7007

3.2 隨機種子的功用】Now, suppose you re-ran lines [1]-[5] of Problem 3.1. What would you expect? If you instead ONLY re-ran lines [3]-[5], what would you expect? If you instead called set.seed() with a different number and then re-ran lines [3]-[5] of Problem 3.1, what would you expect?

set.seed(144)
library(caTools)
split = sample.split(parole$violator, SplitRatio = 0.7)
train = subset(parole, split == TRUE)
test = subset(parole, split == FALSE)
nrow(train)
[1] 473
nrow(test)
[1] 202
print("Question 1 : Now, suppose you re-ran lines [1]-[5] of Problem 3.1. What would you expect?
      The exact same training/testing set split as the first execution of [1]-[5]")
[1] "Question 1 : Now, suppose you re-ran lines [1]-[5] of Problem 3.1. What would you expect?\n      The exact same training/testing set split as the first execution of [1]-[5]"
split = sample.split(parole$violator, SplitRatio = 0.7)
train = subset(parole, split == TRUE)
test = subset(parole, split == FALSE)
print("Question 2 : If you instead ONLY re-ran lines [3]-[5], what would you expect?
      A different training/testing set split from the first execution of [1]-[5] ")
[1] "Question 2 : If you instead ONLY re-ran lines [3]-[5], what would you expect?\n      A different training/testing set split from the first execution of [1]-[5] "
set.seed(50)
library(caTools)
split = sample.split(parole$violator, SplitRatio = 0.7)
train = subset(parole, split == TRUE)
test = subset(parole, split == FALSE)
print("Question 3 : 
If you instead called set.seed() with a different number and then re-ran lines [3]-[5] of Problem 3.1, what would you expect?
      A different training/testing set split from the first execution of [1]-[5] ")
[1] "Question 3 : \nIf you instead called set.seed() with a different number and then re-ran lines [3]-[5] of Problem 3.1, what would you expect?\n\n      A different training/testing set split from the first execution of [1]-[5] "



4 建立模型 Building a Logistic Regression Model

4.1 顯著性】What variables are significant in this model?

set.seed(144)
library(caTools)
split = sample.split(parole$violator, SplitRatio = 0.7)
train = subset(parole, split == TRUE)
test = subset(parole, split == FALSE)
LRmodel = glm(violator~.,data = train, family = "binomial")
model1 = LRmodel
summary(LRmodel)

Call:
glm(formula = violator ~ ., family = "binomial", data = train)

Deviance Residuals: 
   Min      1Q  Median      3Q     Max  
-1.704  -0.424  -0.272  -0.169   2.837  

Coefficients:
                   Estimate Std. Error z value
(Intercept)       -4.241157   1.293885   -3.28
male               0.386990   0.437961    0.88
race               0.886719   0.395066    2.24
age               -0.000176   0.016085   -0.01
state2             0.443301   0.481662    0.92
state3             0.834980   0.556270    1.50
state4            -3.396788   0.611586   -5.55
time.served       -0.123887   0.120423   -1.03
max.sentence       0.080295   0.055375    1.45
multiple.offenses  1.611992   0.385305    4.18
crime2             0.683714   0.500355    1.37
crime3            -0.278105   0.432836   -0.64
crime4            -0.011763   0.571304   -0.02
                     Pr(>|z|)    
(Intercept)             0.001 ** 
male                    0.377    
race                    0.025 *  
age                     0.991    
state2                  0.357    
state3                  0.133    
state4            0.000000028 ***
time.served             0.304    
max.sentence            0.147    
multiple.offenses 0.000028683 ***
crime2                  0.172    
crime3                  0.521    
crime4                  0.984    
---
Signif. codes:  
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 340.04  on 472  degrees of freedom
Residual deviance: 251.48  on 460  degrees of freedom
AIC: 277.5

Number of Fisher Scoring iterations: 6
print("race, state4, multiple.offenses")
[1] "race, state4, multiple.offenses"

4.2 從回歸係數估計邊際效用】What can we say based on the coefficient of the multiple.offenses variable?

print("For parolees A and B who are identical other than A having committed multiple offenses, the predicted log odds of A is 1.61 more than the predicted log odds of B. Then we have:
ln(odds of A) = ln(odds of B) + 1.61
exp(ln(odds of A)) = exp(ln(odds of B) + 1.61)
exp(ln(odds of A)) = exp(ln(odds of B)) * exp(1.61)
odds of A = exp(1.61) * odds of B
odds of A= 5.01 * odds of B")
[1] "For parolees A and B who are identical other than A having committed multiple offenses, the predicted log odds of A is 1.61 more than the predicted log odds of B. Then we have:\n\nln(odds of A) = ln(odds of B) + 1.61\nexp(ln(odds of A)) = exp(ln(odds of B) + 1.61)\nexp(ln(odds of A)) = exp(ln(odds of B)) * exp(1.61)\nodds of A = exp(1.61) * odds of B\nodds of A= 5.01 * odds of B"
print("Our model predicts that a parolee who committed multiple offenses has 5.01 times higher odds of being a violator than a parolee who did not commit multiple offenses but is otherwise identical.")
[1] "Our model predicts that a parolee who committed multiple offenses has 5.01 times higher odds of being a violator than a parolee who did not commit multiple offenses but is otherwise identical."

4.3 從預測值估計勝率和機率】According to the model, what are the odds this individual is a violator? What is the probability this individual is a violator?

print("log(odds) = -4.2411574 + 0.3869904*male + 0.8867192*race - 0.0001756*age + 0.4433007*state2 + 0.8349797*state3 - 3.3967878*state4 - 0.1238867*time.served + 0.0802954*max.sentence + 1.6119919*multiple.offenses + 0.6837143*crime2 - 0.2781054*crime3 - 0.0117627*crime4")
[1] "log(odds) = -4.2411574 + 0.3869904*male + 0.8867192*race - 0.0001756*age + 0.4433007*state2 + 0.8349797*state3 - 3.3967878*state4 - 0.1238867*time.served + 0.0802954*max.sentence + 1.6119919*multiple.offenses + 0.6837143*crime2 - 0.2781054*crime3 - 0.0117627*crime4"
exp(-4.2411574 + 0.3869904*1 + 0.8867192*1 - 0.0001756*50 + 0.4433007*0 + 0.8349797*0 - 3.3967878*0 - 0.1238867*3 + 0.0802954*12 + 1.6119919*0 + 0.6837143*1 - 0.2781054*0 - 0.0117627*0)
[1] 0.1826
1/(1+exp(1.700629))
[1] 0.1544
print("odds ratio = 0.1826")
[1] "odds ratio = 0.1826"
print("probability = 0.1544")
[1] "probability = 0.1544"



5 驗證模型 Evaluating the Model on the Testing Set

Section-5

§ 5.1 What is the maximum predicted probability of a violation?

predtest1 = predict(model1, newdata = test, type = "response")
max(predtest1)
[1] 0.9073

§ 5.2 What is the model’s sensitivity, specificity, accuracy?

table(test$violator, predtest1 >= 0.5)
   
    FALSE TRUE
  0   167   12
  1    11   12
sensitivity = 12/(11+12)
specificity = 167/(167+12)
accuracy = (167+12)/(167+12+12+11)
print("sensitivity = 0.5217391")
[1] "sensitivity = 0.5217391"
print("specificity = 0.9329609")
[1] "specificity = 0.9329609"
print("accuracy = 0.8861386")
[1] "accuracy = 0.8861386"

§ 5.3 What is the accuracy of a simple model that predicts that every parolee is a non-violator?

table(test$violator)

  0   1 
179  23 
print("accuracy = 179/(179+23) = 0.8861386")
[1] "accuracy = 179/(179+23) = 0.8861386"

§ 5.4 Which of the following most likely describes their preferences and best course of action?

print("The board assigns more cost to a false negative than a false positive, and should therefore use a logistic regression cutoff less than 0.5. ")
[1] "The board assigns more cost to a false negative than a false positive, and should therefore use a logistic regression cutoff less than 0.5. "

§ 5.5 Which of the following is the most accurate assessment of the value of the logistic regression model with a cutoff 0.5 to a parole board, based on the model’s accuracy as compared to the simple baseline model?

print("The model is likely of value to the board, and using a different logistic regression cutoff is likely to improve the model's value")
[1] "The model is likely of value to the board, and using a different logistic regression cutoff is likely to improve the model's value"

§ 5.6 Using the ROCR package, what is the AUC value for the model?

rocrpred = prediction(predtest1, test$violator)
auc = as.numeric(performance(rocrpred, "auc")@y.values)

§ 5.7 Describe the meaning of AUC in this context.

print("The probability the model can correctly differentiate between a randomly selected parole violator and a randomly selected parole non-violator.")
[1] "The probability the model can correctly differentiate between a randomly selected parole violator and a randomly selected parole non-violator."

Section-6

§ 6.1 How could we improve our dataset to best address selection bias?

print("We should use a dataset tracking a group of parolees from the start of their parole until either they violated parole or they completed their term.")
[1] "We should use a dataset tracking a group of parolees from the start of their parole until either they violated parole or they completed their term."




LS0tDQp0aXRsZTogIkFTMy0yIFByZWRpY3RpbmcgUGFyb2xlIFZpb2xhdG9ycyINCmF1dGhvcjogIkdyb3VwMSINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCmBgYHtyIGVjaG89VCwgbWVzc2FnZT1GLCBjYWNoZT1GLCB3YXJuaW5nPUZ9DQpybShsaXN0PWxzKGFsbD1UKSkNCm9wdGlvbnMoZGlnaXRzPTQsIHNjaXBlbj0xMikNCmxpYnJhcnkobWFncml0dHIpDQpgYGANCg0KLSAtIC0NCg0KIyMjIEludHJvZHVjdGlvbg0KDQoqKuitsOmhjO+8mueUqOeUs+iri+WBh+mHi+iAheeahOWxrOaAp++8jOmgkOa4rOS7luacg+S4jeacg+mBleWPjeWBh+mHi+imj+WumioqDQoNCioq5a2457+S6YeN6bue77yaKioNCg0KKyDoqK3lrprpmqjmqZ/nqK7lrZBzZXQuc2VlZCgp77yM5L6d5q+U5L6L5YiG5Ymy6LOH5paZDQorIOW+numCj+i8r+W8j+WbnuatuOeahOS/guaVuOaOqOeul+iHquiuiuaVuOeahOmCiumam+aViOaenA0KKyDli53njoflkozmqZ/njofjgIHli53njofmr5TlkozmqZ/njoflt64g5LmL6ZaT55qE5o+b566XIA0KKyDoh6jnlYzmqZ/njoflsI3mt7fmt4bnn6npmaMo5pyf5pyb5aCx6YWsKeeahOW9semfv3BheW9mZiA9IG1hdHJpeChjKDAsLTEwMCwtMTAsLTUwKSwyLDIpOyBwYXlvZmYNCisgQVVD55qE5a+m6LOq5oSP576pcGF5b2ZmID0gbWF0cml4KGMoMTAwLCAtODAsIC0yMCwgMTAwKSwyLDIpOyBwYXlvZmYNCisg5aaC5L2VKOW+nuWgsemFrOefqemZoynmsbrlrproh6jnlYzmqZ/njocgDQorIOS7gOm6vOaYr+aKveaoo+WBj+W3rizlpoLkvZXpgb/lhY3jgIHlpoLkvZXkv67mraMNCg0KPGJyPg0KDQotIC0gLQ0KDQojIyMjIDEg6LOH5paZ6JmV55CGIExvYWRpbmcgdGhlIERhdGFzZXQNCg0K44CQKioxLjEg6K6A6YCy6LOH5paZKirjgJFIb3cgbWFueSBwYXJvbGVlcyBhcmUgY29udGFpbmVkIGluIHRoZSBkYXRhc2V0Pw0KYGBge3J9DQpwYXJvbGUgPSByZWFkLmNzdigiZGF0YS9wYXJvbGUuY3N2IikNCnN0cihwYXJvbGUpDQoNCnByaW50KCI2NzUiKQ0KYGBgDQoNCuOAkCoqMS4yIOW6lee3muapn+eOhyoq44CRSG93IG1hbnkgb2YgdGhlIHBhcm9sZWVzIGluIHRoZSBkYXRhc2V0IHZpb2xhdGVkIHRoZSB0ZXJtcyBvZiB0aGVpciBwYXJvbGU/DQpgYGB7cn0NCnRhYmxlKHBhcm9sZSR2aW9sYXRvcikNCg0KcHJpbnQoIjc4IikNCmBgYA0KPGJyPg0KDQotIC0gLQ0KDQojIyMjIDIg5pW055CG6LOH5paZ5qGGIENyZWF0aW5nIE91ciBQcmVkaWN0aW9uIE1vZGVsDQoNCuOAkCoqMi4xIOmhnuWIpeiuiuaVuCoq44CRV2hpY2ggdmFyaWFibGVzIGluIHRoaXMgZGF0YXNldCBhcmUgdW5vcmRlcmVkIGZhY3RvcnMgd2l0aCBhdCBsZWFzdCB0aHJlZSBsZXZlbHM/IA0KYGBge3J9DQpzdHIocGFyb2xlKQ0KDQp0YWJsZShwYXJvbGUkc3RhdGUpDQp0YWJsZShwYXJvbGUkcmFjZSkNCnRhYmxlKHBhcm9sZSRjcmltZSkNCg0KcHJpbnQoImNyaW1lLCBzdGF0ZSIpDQpgYGANCg0K44CQKioyLjIg6LOH5paZ5qGG5pGY6KaBKirjgJFIb3cgZG9lcyB0aGUgb3V0cHV0IG9mIGBzdW1tYXJ5KClgIGNoYW5nZSBmb3IgYSBmYWN0b3IgdmFyaWFibGUgYXMgY29tcGFyZWQgdG8gYSBudW1lcmljYWwgdmFyaWFibGU/IA0KYGBge3J9DQpzdHIocGFyb2xlKQ0KcGFyb2xlJHN0YXRlID0gYXMuZmFjdG9yKHBhcm9sZSRzdGF0ZSkNCnBhcm9sZSRjcmltZSA9IGFzLmZhY3RvcihwYXJvbGUkY3JpbWUpDQoNCnN0cihwYXJvbGUpDQoNCnByaW50KCJUaGUgb3V0cHV0IGJlY29tZXMgc2ltaWxhciB0byB0aGF0IG9mIHRoZSB0YWJsZSgpIGZ1bmN0aW9uIGFwcGxpZWQgdG8gdGhhdCB2YXJpYWJsZSIpDQpgYGANCjxicj4NCg0KLSAtIC0NCg0KIyMjIyAzIOizh+aWmeWIhuWJsiBTcGxpdHRpbmcgaW50byBhIFRyYWluaW5nIGFuZCBUZXN0aW5nIFNldA0KDQrjgJAqKjMuMSDmjIflrprpmqjmqZ/nqK7lrZDjgIHkvp3mr5TkvovliIblibLos4fmlpkqKuOAkVJvdWdobHkgd2hhdCBwcm9wb3J0aW9uIG9mIHBhcm9sZWVzIGhhdmUgYmVlbiBhbGxvY2F0ZWQgdG8gdGhlIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHNldHM/DQpgYGB7cn0NCnNldC5zZWVkKDE0NCkNCmxpYnJhcnkoY2FUb29scykNCnNwbGl0ID0gc2FtcGxlLnNwbGl0KHBhcm9sZSR2aW9sYXRvciwgU3BsaXRSYXRpbyA9IDAuNykNCnRyYWluID0gc3Vic2V0KHBhcm9sZSwgc3BsaXQgPT0gVFJVRSkNCnRlc3QgPSBzdWJzZXQocGFyb2xlLCBzcGxpdCA9PSBGQUxTRSkNCg0KbnJvdyh0cmFpbikNCm5yb3codGVzdCkNCjQ3My8oNDczKzIwMikNCmBgYA0KDQrjgJAqKjMuMiDpmqjmqZ/nqK7lrZDnmoTlip/nlKgqKuOAkU5vdywgc3VwcG9zZSB5b3UgcmUtcmFuIGxpbmVzIFsxXS1bNV0gb2YgUHJvYmxlbSAzLjEuIFdoYXQgd291bGQgeW91IGV4cGVjdD8gSWYgeW91IGluc3RlYWQgT05MWSByZS1yYW4gbGluZXMgWzNdLVs1XSwgd2hhdCB3b3VsZCB5b3UgZXhwZWN0PyBJZiB5b3UgaW5zdGVhZCBjYWxsZWQgc2V0LnNlZWQoKSB3aXRoIGEgZGlmZmVyZW50IG51bWJlciBhbmQgdGhlbiByZS1yYW4gbGluZXMgWzNdLVs1XSBvZiBQcm9ibGVtIDMuMSwgd2hhdCB3b3VsZCB5b3UgZXhwZWN0Pw0KYGBge3J9DQpzZXQuc2VlZCgxNDQpDQpsaWJyYXJ5KGNhVG9vbHMpDQpzcGxpdCA9IHNhbXBsZS5zcGxpdChwYXJvbGUkdmlvbGF0b3IsIFNwbGl0UmF0aW8gPSAwLjcpDQp0cmFpbiA9IHN1YnNldChwYXJvbGUsIHNwbGl0ID09IFRSVUUpDQp0ZXN0ID0gc3Vic2V0KHBhcm9sZSwgc3BsaXQgPT0gRkFMU0UpDQpucm93KHRyYWluKQ0KbnJvdyh0ZXN0KQ0KDQoNCg0KcHJpbnQoIlF1ZXN0aW9uIDEgOiBOb3csIHN1cHBvc2UgeW91IHJlLXJhbiBsaW5lcyBbMV0tWzVdIG9mIFByb2JsZW0gMy4xLiBXaGF0IHdvdWxkIHlvdSBleHBlY3Q/DQogICAgICBUaGUgZXhhY3Qgc2FtZSB0cmFpbmluZy90ZXN0aW5nIHNldCBzcGxpdCBhcyB0aGUgZmlyc3QgZXhlY3V0aW9uIG9mIFsxXS1bNV0iKQ0KDQpzcGxpdCA9IHNhbXBsZS5zcGxpdChwYXJvbGUkdmlvbGF0b3IsIFNwbGl0UmF0aW8gPSAwLjcpDQp0cmFpbiA9IHN1YnNldChwYXJvbGUsIHNwbGl0ID09IFRSVUUpDQp0ZXN0ID0gc3Vic2V0KHBhcm9sZSwgc3BsaXQgPT0gRkFMU0UpDQoNCnByaW50KCJRdWVzdGlvbiAyIDogSWYgeW91IGluc3RlYWQgT05MWSByZS1yYW4gbGluZXMgWzNdLVs1XSwgd2hhdCB3b3VsZCB5b3UgZXhwZWN0Pw0KICAgICAgQSBkaWZmZXJlbnQgdHJhaW5pbmcvdGVzdGluZyBzZXQgc3BsaXQgZnJvbSB0aGUgZmlyc3QgZXhlY3V0aW9uIG9mIFsxXS1bNV0gIikNCg0Kc2V0LnNlZWQoNTApDQpsaWJyYXJ5KGNhVG9vbHMpDQpzcGxpdCA9IHNhbXBsZS5zcGxpdChwYXJvbGUkdmlvbGF0b3IsIFNwbGl0UmF0aW8gPSAwLjcpDQp0cmFpbiA9IHN1YnNldChwYXJvbGUsIHNwbGl0ID09IFRSVUUpDQp0ZXN0ID0gc3Vic2V0KHBhcm9sZSwgc3BsaXQgPT0gRkFMU0UpDQoNCg0KcHJpbnQoIlF1ZXN0aW9uIDMgOiANCklmIHlvdSBpbnN0ZWFkIGNhbGxlZCBzZXQuc2VlZCgpIHdpdGggYSBkaWZmZXJlbnQgbnVtYmVyIGFuZCB0aGVuIHJlLXJhbiBsaW5lcyBbM10tWzVdIG9mIFByb2JsZW0gMy4xLCB3aGF0IHdvdWxkIHlvdSBleHBlY3Q/DQoNCiAgICAgIEEgZGlmZmVyZW50IHRyYWluaW5nL3Rlc3Rpbmcgc2V0IHNwbGl0IGZyb20gdGhlIGZpcnN0IGV4ZWN1dGlvbiBvZiBbMV0tWzVdICIpDQoNCmBgYA0KPGJyPg0KDQotIC0gLQ0KDQojIyMjIDQg5bu656uL5qih5Z6LIEJ1aWxkaW5nIGEgTG9naXN0aWMgUmVncmVzc2lvbiBNb2RlbA0KDQrjgJAqKjQuMSDpoa/okZfmgKcqKuOAkVdoYXQgdmFyaWFibGVzIGFyZSBzaWduaWZpY2FudCBpbiB0aGlzIG1vZGVsPw0KYGBge3J9DQpzZXQuc2VlZCgxNDQpDQpsaWJyYXJ5KGNhVG9vbHMpDQpzcGxpdCA9IHNhbXBsZS5zcGxpdChwYXJvbGUkdmlvbGF0b3IsIFNwbGl0UmF0aW8gPSAwLjcpDQp0cmFpbiA9IHN1YnNldChwYXJvbGUsIHNwbGl0ID09IFRSVUUpDQp0ZXN0ID0gc3Vic2V0KHBhcm9sZSwgc3BsaXQgPT0gRkFMU0UpDQoNCkxSbW9kZWwgPSBnbG0odmlvbGF0b3J+LixkYXRhID0gdHJhaW4sIGZhbWlseSA9ICJiaW5vbWlhbCIpDQoNCm1vZGVsMSA9IExSbW9kZWwNCg0Kc3VtbWFyeShMUm1vZGVsKQ0KDQpwcmludCgicmFjZSwgc3RhdGU0LCBtdWx0aXBsZS5vZmZlbnNlcyIpDQpgYGANCg0K44CQKio0LjIg5b6e5Zue5q245L+C5pW45Lyw6KiI6YKK6Zqb5pWI55SoKirjgJFXaGF0IGNhbiB3ZSBzYXkgYmFzZWQgb24gdGhlIGNvZWZmaWNpZW50IG9mIHRoZSBgbXVsdGlwbGUub2ZmZW5zZXNgIHZhcmlhYmxlPw0KYGBge3J9DQpwcmludCgiRm9yIHBhcm9sZWVzIEEgYW5kIEIgd2hvIGFyZSBpZGVudGljYWwgb3RoZXIgdGhhbiBBIGhhdmluZyBjb21taXR0ZWQgbXVsdGlwbGUgb2ZmZW5zZXMsIHRoZSBwcmVkaWN0ZWQgbG9nIG9kZHMgb2YgQSBpcyAxLjYxIG1vcmUgdGhhbiB0aGUgcHJlZGljdGVkIGxvZyBvZGRzIG9mIEIuIFRoZW4gd2UgaGF2ZToNCg0KbG4ob2RkcyBvZiBBKSA9IGxuKG9kZHMgb2YgQikgKyAxLjYxDQpleHAobG4ob2RkcyBvZiBBKSkgPSBleHAobG4ob2RkcyBvZiBCKSArIDEuNjEpDQpleHAobG4ob2RkcyBvZiBBKSkgPSBleHAobG4ob2RkcyBvZiBCKSkgKiBleHAoMS42MSkNCm9kZHMgb2YgQSA9IGV4cCgxLjYxKSAqIG9kZHMgb2YgQg0Kb2RkcyBvZiBBPSA1LjAxICogb2RkcyBvZiBCIikNCg0KcHJpbnQoIk91ciBtb2RlbCBwcmVkaWN0cyB0aGF0IGEgcGFyb2xlZSB3aG8gY29tbWl0dGVkIG11bHRpcGxlIG9mZmVuc2VzIGhhcyA1LjAxIHRpbWVzIGhpZ2hlciBvZGRzIG9mIGJlaW5nIGEgdmlvbGF0b3IgdGhhbiBhIHBhcm9sZWUgd2hvIGRpZCBub3QgY29tbWl0IG11bHRpcGxlIG9mZmVuc2VzIGJ1dCBpcyBvdGhlcndpc2UgaWRlbnRpY2FsLiIpDQpgYGANCg0K44CQKio0LjMg5b6e6aCQ5ris5YC85Lyw6KiI5Yud546H5ZKM5qmf546HKirjgJFBY2NvcmRpbmcgdG8gdGhlIG1vZGVsLCB3aGF0IGFyZSB0aGUgb2RkcyB0aGlzIGluZGl2aWR1YWwgaXMgYSB2aW9sYXRvcj8gIFdoYXQgaXMgdGhlIHByb2JhYmlsaXR5IHRoaXMgaW5kaXZpZHVhbCBpcyBhIHZpb2xhdG9yPw0KYGBge3J9DQpwcmludCgibG9nKG9kZHMpID0gLTQuMjQxMTU3NCArIDAuMzg2OTkwNCptYWxlICsgMC44ODY3MTkyKnJhY2UgLSAwLjAwMDE3NTYqYWdlICsgMC40NDMzMDA3KnN0YXRlMiArIDAuODM0OTc5NypzdGF0ZTMgLSAzLjM5Njc4Nzgqc3RhdGU0IC0gMC4xMjM4ODY3KnRpbWUuc2VydmVkICsgMC4wODAyOTU0Km1heC5zZW50ZW5jZSArIDEuNjExOTkxOSptdWx0aXBsZS5vZmZlbnNlcyArIDAuNjgzNzE0MypjcmltZTIgLSAwLjI3ODEwNTQqY3JpbWUzIC0gMC4wMTE3NjI3KmNyaW1lNCIpDQoNCmV4cCgtNC4yNDExNTc0ICsgMC4zODY5OTA0KjEgKyAwLjg4NjcxOTIqMSAtIDAuMDAwMTc1Nio1MCArIDAuNDQzMzAwNyowICsgMC44MzQ5Nzk3KjAgLSAzLjM5Njc4NzgqMCAtIDAuMTIzODg2NyozICsgMC4wODAyOTU0KjEyICsgMS42MTE5OTE5KjAgKyAwLjY4MzcxNDMqMSAtIDAuMjc4MTA1NCowIC0gMC4wMTE3NjI3KjApDQoNCjEvKDErZXhwKDEuNzAwNjI5KSkNCg0KcHJpbnQoIm9kZHMgcmF0aW8gPSAwLjE4MjYiKQ0KcHJpbnQoInByb2JhYmlsaXR5ID0gMC4xNTQ0IikNCg0KYGBgDQo8YnI+DQoNCi0gLSAtDQoNCiMjIyMgNSDpqZforYnmqKHlnosgRXZhbHVhdGluZyB0aGUgTW9kZWwgb24gdGhlIFRlc3RpbmcgU2V0DQoNCiMjIyMgIFNlY3Rpb24tNQ0KX1/CpyA1LjFfXyAgV2hhdCBpcyB0aGUgbWF4aW11bSBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgb2YgYSB2aW9sYXRpb24/DQpgYGB7cn0NCnByZWR0ZXN0MSA9IHByZWRpY3QobW9kZWwxLCBuZXdkYXRhID0gdGVzdCwgdHlwZSA9ICJyZXNwb25zZSIpDQptYXgocHJlZHRlc3QxKQ0KYGBgDQoNCl9fwqcgNS4yX18gV2hhdCBpcyB0aGUgbW9kZWzigJlzIHNlbnNpdGl2aXR5LCBzcGVjaWZpY2l0eSwgYWNjdXJhY3k/DQpgYGB7cn0NCnRhYmxlKHRlc3QkdmlvbGF0b3IsIHByZWR0ZXN0MSA+PSAwLjUpDQpzZW5zaXRpdml0eSA9IDEyLygxMSsxMikNCnNwZWNpZmljaXR5ID0gMTY3LygxNjcrMTIpDQphY2N1cmFjeSA9ICgxNjcrMTIpLygxNjcrMTIrMTIrMTEpDQpwcmludCgic2Vuc2l0aXZpdHkgPSAwLjUyMTczOTEiKQ0KcHJpbnQoInNwZWNpZmljaXR5ID0gMC45MzI5NjA5IikNCnByaW50KCJhY2N1cmFjeSA9IDAuODg2MTM4NiIpDQpgYGANCg0KX1/CpyA1LjNfXyBXaGF0IGlzIHRoZSBhY2N1cmFjeSBvZiBhIHNpbXBsZSBtb2RlbCB0aGF0IHByZWRpY3RzIHRoYXQgZXZlcnkgcGFyb2xlZSBpcyBhIG5vbi12aW9sYXRvcj8NCmBgYHtyfQ0KdGFibGUodGVzdCR2aW9sYXRvcikNCnByaW50KCJhY2N1cmFjeSA9IDE3OS8oMTc5KzIzKSA9IDAuODg2MTM4NiIpDQpgYGANCg0KX1/CpyA1LjRfXyBXaGljaCBvZiB0aGUgZm9sbG93aW5nIG1vc3QgbGlrZWx5IGRlc2NyaWJlcyB0aGVpciBwcmVmZXJlbmNlcyBhbmQgYmVzdCBjb3Vyc2Ugb2YgYWN0aW9uPw0KYGBge3J9DQpwcmludCgiVGhlIGJvYXJkIGFzc2lnbnMgbW9yZSBjb3N0IHRvIGEgZmFsc2UgbmVnYXRpdmUgdGhhbiBhIGZhbHNlIHBvc2l0aXZlLCBhbmQgc2hvdWxkIHRoZXJlZm9yZSB1c2UgYSBsb2dpc3RpYyByZWdyZXNzaW9uIGN1dG9mZiBsZXNzIHRoYW4gMC41LiAiKQ0KYGBgDQoNCl9fwqcgNS41X18gV2hpY2ggb2YgdGhlIGZvbGxvd2luZyBpcyB0aGUgbW9zdCBhY2N1cmF0ZSBhc3Nlc3NtZW50IG9mIHRoZSB2YWx1ZSBvZiB0aGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB3aXRoIGEgY3V0b2ZmIDAuNSB0byBhIHBhcm9sZSBib2FyZCwgYmFzZWQgb24gdGhlIG1vZGVs4oCZcyBhY2N1cmFjeSBhcyBjb21wYXJlZCB0byB0aGUgc2ltcGxlIGJhc2VsaW5lIG1vZGVsPw0KDQpgYGB7cn0NCnByaW50KCJUaGUgbW9kZWwgaXMgbGlrZWx5IG9mIHZhbHVlIHRvIHRoZSBib2FyZCwgYW5kIHVzaW5nIGEgZGlmZmVyZW50IGxvZ2lzdGljIHJlZ3Jlc3Npb24gY3V0b2ZmIGlzIGxpa2VseSB0byBpbXByb3ZlIHRoZSBtb2RlbCdzIHZhbHVlIikNCmBgYA0KDQpfX8KnIDUuNl9fIFVzaW5nIHRoZSBST0NSIHBhY2thZ2UsIHdoYXQgaXMgdGhlIEFVQyB2YWx1ZSBmb3IgdGhlIG1vZGVsPw0KYGBge3J9DQpyb2NycHJlZCA9IHByZWRpY3Rpb24ocHJlZHRlc3QxLCB0ZXN0JHZpb2xhdG9yKQ0KYXVjID0gYXMubnVtZXJpYyhwZXJmb3JtYW5jZShyb2NycHJlZCwgImF1YyIpQHkudmFsdWVzKQ0KYGBgDQoNCl9fwqcgNS43X18gRGVzY3JpYmUgdGhlIG1lYW5pbmcgb2YgQVVDIGluIHRoaXMgY29udGV4dC4NCmBgYHtyfQ0KcHJpbnQoIlRoZSBwcm9iYWJpbGl0eSB0aGUgbW9kZWwgY2FuIGNvcnJlY3RseSBkaWZmZXJlbnRpYXRlIGJldHdlZW4gYSByYW5kb21seSBzZWxlY3RlZCBwYXJvbGUgdmlvbGF0b3IgYW5kIGEgcmFuZG9tbHkgc2VsZWN0ZWQgcGFyb2xlIG5vbi12aW9sYXRvci4iKQ0KYGBgDQoNCiMjIyMgIFNlY3Rpb24tNg0KX1/CpyA2LjFfXyBIb3cgY291bGQgd2UgaW1wcm92ZSBvdXIgZGF0YXNldCB0byBiZXN0IGFkZHJlc3Mgc2VsZWN0aW9uIGJpYXM/DQoNCmBgYHtyfQ0KcHJpbnQoIldlIHNob3VsZCB1c2UgYSBkYXRhc2V0IHRyYWNraW5nIGEgZ3JvdXAgb2YgcGFyb2xlZXMgZnJvbSB0aGUgc3RhcnQgb2YgdGhlaXIgcGFyb2xlIHVudGlsIGVpdGhlciB0aGV5IHZpb2xhdGVkIHBhcm9sZSBvciB0aGV5IGNvbXBsZXRlZCB0aGVpciB0ZXJtLiIpDQpgYGANCg0KDQoNCi0gLSAtDQoNCjxicj48YnI+PGJyPg0K