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")
nrow(parole)
[1] 675

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

sum(parole$violator==1)
[1] 78



2 整理資料框 Creating Our Prediction Model

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

parole$state = factor(parole$state)
parole$crime = factor(parole$crime)
summary(parole)
      male            race           age       state    time.served    max.sentence  multiple.offenses crime  
 Min.   :0.000   Min.   :1.00   Min.   :18.4   1:143   Min.   :0.00   Min.   : 1.0   Min.   :0.000     1:315  
 1st Qu.:1.000   1st Qu.:1.00   1st Qu.:25.4   2:120   1st Qu.:3.25   1st Qu.:12.0   1st Qu.:0.000     2:106  
 Median :1.000   Median :1.00   Median :33.7   3: 82   Median :4.40   Median :12.0   Median :1.000     3:153  
 Mean   :0.807   Mean   :1.42   Mean   :34.5   4:330   Mean   :4.20   Mean   :13.1   Mean   :0.536     4:101  
 3rd Qu.:1.000   3rd Qu.:2.00   3rd Qu.:42.5           3rd Qu.:5.20   3rd Qu.:15.0   3rd Qu.:1.000            
 Max.   :1.000   Max.   :2.00   Max.   :67.0           Max.   :6.00   Max.   :18.0   Max.   :1.000            
    violator    
 Min.   :0.000  
 1st Qu.:0.000  
 Median :0.000  
 Mean   :0.116  
 3rd Qu.:0.000  
 Max.   :1.000  

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

set.seed(144)
library(caTools)
split = sample.split(parole$violator, SplitRatio = 0.7)
train = subset(parole, split == TRUE)
test = subset(parole, split == FALSE)
"0.7 training 0.3 testing"
[1] "0.7 training 0.3 testing"



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

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

paroleglm=glm(violator~.,train,family="binomial")
summary(paroleglm)

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    Pr(>|z|)    
(Intercept)       -4.241157   1.293885   -3.28       0.001 ** 
male               0.386990   0.437961    0.88       0.377    
race               0.886719   0.395066    2.24       0.025 *  
age               -0.000176   0.016085   -0.01       0.991    
state2             0.443301   0.481662    0.92       0.357    
state3             0.834980   0.556270    1.50       0.133    
state4            -3.396788   0.611586   -5.55 0.000000028 ***
time.served       -0.123887   0.120423   -1.03       0.304    
max.sentence       0.080295   0.055375    1.45       0.147    
multiple.offenses  1.611992   0.385305    4.18 0.000028683 ***
crime2             0.683714   0.500355    1.37       0.172    
crime3            -0.278105   0.432836   -0.64       0.521    
crime4            -0.011763   0.571304   -0.02       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
"race state4 multiple.offenses are significant"
[1] "race state4 multiple.offenses are significant"

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?

odd=-4.241157+0.386990+0.886719+50*(-0.000176)+3*(-0.123887)+12*0.080295+0.683714
exp(odd)
[1] 0.1826
1/(1+exp(-odd))
[1] 0.1544



4 建立模型 Building a Logistic Regression Model

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

x=table(actual = test$violator, predict = predictmd>=0.5)
x
      predict
actual FALSE TRUE
     0   167   12
     1    11   12
sensitivity=12/(12+11)
specificity=167/(167+12)
accuracy=(12+167)/(12+167+12+11)
sensitivity
[1] 0.5217
specificity
[1] 0.933
accuracy
[1] 0.8861

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

table(test$violator)

  0   1 
179  23 
179/(179+23)
[1] 0.8861

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

library(caTools)
colAUC(predictmd, test$violator)
          [,1]
0 vs. 1 0.8946



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

5.1 從測試資料預測機率】What is the maximum predicted probability of a violation?

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

5.2 從混淆矩陣計算敏感性、明確性、正確率】What is the model’s sensitivity, specificity, accuracy?

preResult <- predict(model1,newdata = test, type = "response")
table(test$violator, as.numeric(preResult >= 0.5))
   
      0   1
  0 167  12
  1  11  12
12/(11+12)
[1] 0.5217
167/(167+12)
[1] 0.933
(167+12)/(167+12+11+12)
[1] 0.8861
# 利用 table() 計算出 confusion matrix
# 並利用 confusion matrix 計算 Sensitivity、Specificity 以及 Accuracy

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 
179/(179+23)
[1] 0.8861
# 底線機率單純看其 violater 變數項是否為 1

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. ", quote = FALSE)
[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 正確率 vs 辨識率】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. ", quote = FALSE)
[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?

library(ROCR)
pred <- prediction(preResult, test$violator)
as.numeric(performance(pred, "auc")@y.values)
[1] 0.8946

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.", quote = FALSE)
[1] The probability the model can correctly differentiate between a randomly selected parole violator and a randomly selected parole non-violator.
# 模型能夠猜對假釋犯不會違反規定與會違反規定的概率



6 抽樣偏差 Identifying Bias in Observational Data

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.", quote = FALSE)
[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.
# 面對缺漏值
# 若是將該假釋犯的 violator 自動補為 0
# 會造成模型偏向放大不會犯罪的可能性
# 若是將該假釋犯的 violator 自動補為 NA
# R 會自動在建立模型的時候剔除這些觀測值
# 最好的方式就是去追蹤這些假釋犯至其假釋期間結束






LS0tDQp0aXRsZTogIkFTMy0yIFByZWRpY3RpbmcgUGFyb2xlIFZpb2xhdG9ycyINCmF1dGhvcjogIkdyb3VwIDIiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7ciBlY2hvPVQsIG1lc3NhZ2U9RiwgY2FjaGU9Riwgd2FybmluZz1GfQ0Kcm0obGlzdD1scyhhbGw9VCkpDQpvcHRpb25zKGRpZ2l0cz00LCBzY2lwZW49MTIpDQpsaWJyYXJ5KG1hZ3JpdHRyKQ0KYGBgDQoNCi0gLSAtDQoNCiMjIyBJbnRyb2R1Y3Rpb24NCg0KKirorbDpoYzvvJrnlKjnlLPoq4vlgYfph4vogIXnmoTlsazmgKfvvIzpoJDmuKzku5bmnIPkuI3mnIPpgZXlj43lgYfph4vopo/lrpoqKg0KDQoqKuWtuOe/kumHjem7nu+8mioqDQoNCisg6Kit5a6a6Zqo5qmf56iu5a2Qc2V0LnNlZWQoKe+8jOS+neavlOS+i+WIhuWJsuizh+aWmQ0KKyDlvp7pgo/ovK/lvI/lm57mrbjnmoTkv4Lmlbjmjqjnrpfoh6rorormlbjnmoTpgorpmpvmlYjmnpwNCisg5Yud546H5ZKM5qmf546H44CB5Yud546H5q+U5ZKM5qmf546H5beuIOS5i+mWk+eahOaPm+eulyANCisg6Ieo55WM5qmf546H5bCN5re35reG55+p6ZmjKOacn+acm+WgsemFrCnnmoTlvbHpn79wYXlvZmYgPSBtYXRyaXgoYygwLC0xMDAsLTEwLC01MCksMiwyKTsgcGF5b2ZmDQorIEFVQ+eahOWvpuizquaEj+e+qXBheW9mZiA9IG1hdHJpeChjKDEwMCwgLTgwLCAtMjAsIDEwMCksMiwyKTsgcGF5b2ZmDQorIOWmguS9lSjlvp7loLHphaznn6npmaMp5rG65a6a6Ieo55WM5qmf546HIA0KKyDku4DpurzmmK/mir3mqKPlgY/lt64s5aaC5L2V6YG/5YWN44CB5aaC5L2V5L+u5q2jDQoNCjxicj4NCg0KLSAtIC0NCg0KIyMjIyAxIOizh+aWmeiZleeQhiBMb2FkaW5nIHRoZSBEYXRhc2V0DQoNCuOAkCoqMS4xIOiugOmAsuizh+aWmSoq44CRSG93IG1hbnkgcGFyb2xlZXMgYXJlIGNvbnRhaW5lZCBpbiB0aGUgZGF0YXNldD8NCmBgYHtyfQ0KDQpwYXJvbGUgPC0gcmVhZC5jc3YoImRhdGEvcGFyb2xlLmNzdiIpDQpucm93KHBhcm9sZSkNCg0KYGBgDQoNCuOAkCoqMS4yIOW6lee3muapn+eOhyoq44CRSG93IG1hbnkgb2YgdGhlIHBhcm9sZWVzIGluIHRoZSBkYXRhc2V0IHZpb2xhdGVkIHRoZSB0ZXJtcyBvZiB0aGVpciBwYXJvbGU/DQpgYGB7cn0NCg0KbnJvdyhwYXJvbGVbcGFyb2xlJHZpb2xhdG9yID09IDEsXSkNCg0KYGBgDQo8YnI+DQoNCi0gLSAtDQoNCiMjIyMgMiDmlbTnkIbos4fmlpnmoYYgQ3JlYXRpbmcgT3VyIFByZWRpY3Rpb24gTW9kZWwNCg0K44CQKioyLjEg6aGe5Yil6K6K5pW4KirjgJFXaGljaCB2YXJpYWJsZXMgaW4gdGhpcyBkYXRhc2V0IGFyZSB1bm9yZGVyZWQgZmFjdG9ycyB3aXRoIGF0IGxlYXN0IHRocmVlIGxldmVscz8gDQpgYGB7cn0NCg0KcGFyb2xlMiA8LSBkYXRhLmZyYW1lKHNhcHBseShwYXJvbGUsYXMuZmFjdG9yKSkNCnN1bW1hcnkocGFyb2xlMikNCnByaW50KCJzdGF0ZSIscXVvdGUgPSBGQUxTRSkNCnByaW50KCJjcmltZSIscXVvdGUgPSBGQUxTRSkNCg0KcGFyb2xlJG1hbGUgPC0gcGFyb2xlMiRtYWxlDQpwYXJvbGUkcmFjZSA8LSBwYXJvbGUyJHJhY2UNCnBhcm9sZSRzdGF0ZSA8LSBwYXJvbGUyJHN0YXRlDQpwYXJvbGUkbXVsdGlwbGUub2ZmZW5zZXMgPC0gcGFyb2xlMiRtdWx0aXBsZS5vZmZlbnNlcw0KcGFyb2xlJGNyaW1lIDwtIHBhcm9sZTIkY3JpbWUNCnBhcm9sZSR2aW9sYXRvciA8LSBwYXJvbGUyJHZpb2xhdG9yDQoNCiMg5bCH5Y6f5aeL6LOH5paZ5YWI5YWo6YOo6L2J5oiQIGZhY3RvciDlrZjliLDlj6bkuIDlgIsgZGF0YS5mcmFtZQ0KIyDop4Dlr5/pgJnlgIsgZGF0YS5mcmFtZSBzdW1tYXJ5KCkg55qE57WQ5p6cDQojIOaMkeWHuuaYr+mhnuWIpeeahOiuiuaVuOS4puabtOaWsOWOn+WniyBkYXRhLmZyYW1lIOS4remCo+W5vuWAi+iuiuaVuOeCuiBmYWN0b3Ig6LOH5paZDQoNCmBgYA0KDQrjgJAqKjIuMiDos4fmlpnmoYbmkZjopoEqKuOAkUhvdyBkb2VzIHRoZSBvdXRwdXQgb2YgYHN1bW1hcnkoKWAgY2hhbmdlIGZvciBhIGZhY3RvciB2YXJpYWJsZSBhcyBjb21wYXJlZCB0byBhIG51bWVyaWNhbCB2YXJpYWJsZT8gDQpgYGB7cn0NCg0KcHJpbnQoIlRoZSBvdXRwdXQgYmVjb21lcyBzaW1pbGFyIHRvIHRoYXQgb2YgdGhlIHRhYmxlKCkgZnVuY3Rpb24gYXBwbGllZCB0byB0aGF0IHZhcmlhYmxlIC4iLHF1b3RlID0gRkFMU0UpDQoNCiMgZmFjdG9yIOiuiuaVuOeahCBzdW1tYXJ5IOacg+aYr+WQhOWAi+mhnuWIpeeahOWHuuePvuasoeaVuA0KDQpgYGANCjxicj4NCg0KLSAtIC0NCg0KIyMjIyAzIOizh+aWmeWIhuWJsiBTcGxpdHRpbmcgaW50byBhIFRyYWluaW5nIGFuZCBUZXN0aW5nIFNldA0KDQrjgJAqKjMuMSDmjIflrprpmqjmqZ/nqK7lrZDjgIHkvp3mr5TkvovliIblibLos4fmlpkqKuOAkVJvdWdobHkgd2hhdCBwcm9wb3J0aW9uIG9mIHBhcm9sZWVzIGhhdmUgYmVlbiBhbGxvY2F0ZWQgdG8gdGhlIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHNldHM/DQpgYGB7cn0NCg0Kc2V0LnNlZWQoMTQ0KQ0KbGlicmFyeShjYVRvb2xzKQ0Kc3BsaXQgPC0gc2FtcGxlLnNwbGl0KHBhcm9sZSR2aW9sYXRvciwgU3BsaXRSYXRpbyA9IDAuNykNCnRyYWluIDwtIHN1YnNldChwYXJvbGUsIHNwbGl0ID09IFRSVUUpDQp0ZXN0IDwtIHN1YnNldChwYXJvbGUsIHNwbGl0ID09IEZBTFNFKQ0KbnJvdyh0cmFpbikvKG5yb3codHJhaW4pK25yb3codGVzdCkpDQpwcmludCgiNzAlIHRvIHRoZSB0cmFpbmluZyBzZXQsIDMwJSB0byB0aGUgdGVzdGluZyBzZXQgLiAiLHF1b3RlID0gRkFMU0UpDQoNCiMg5oyH5a6a6Zqo5qmf56iu5a2Q5Lim5oyH5a6a5q+U5L6L5L6G5YiG5Ymy6LOH5paZDQojIOS4g+aIkOeCuiB0cmFpbmluZyBkYXRh44CB5LiJ5oiQ54K6IHRlc3RpbmcgZGF0YQ0KDQpgYGANCg0K44CQKiozLjIg6Zqo5qmf56iu5a2Q55qE5Yqf55SoKirjgJFOb3csIHN1cHBvc2UgeW91IHJlLXJhbiBsaW5lcyBbMV0tWzVdIG9mIFByb2JsZW0gMy4xLiBXaGF0IHdvdWxkIHlvdSBleHBlY3Q/IElmIHlvdSBpbnN0ZWFkIE9OTFkgcmUtcmFuIGxpbmVzIFszXS1bNV0sIHdoYXQgd291bGQgeW91IGV4cGVjdD8gSWYgeW91IGluc3RlYWQgY2FsbGVkIHNldC5zZWVkKCkgd2l0aCBhIGRpZmZlcmVudCBudW1iZXIgYW5kIHRoZW4gcmUtcmFuIGxpbmVzIFszXS1bNV0gb2YgUHJvYmxlbSAzLjEsIHdoYXQgd291bGQgeW91IGV4cGVjdD8NCmBgYHtyfQ0KDQpwcmludCgiVGhlIGV4YWN0IHNhbWUgdHJhaW5pbmcvdGVzdGluZyBzZXQgc3BsaXQgYXMgdGhlIGZpcnN0IGV4ZWN1dGlvbiBvZiBbMV0tWzVdIC4iLHF1b3RlID0gRkFMU0UpDQpwcmludCgiQSBkaWZmZXJlbnQgdHJhaW5pbmcvdGVzdGluZyBzZXQgc3BsaXQgZnJvbSB0aGUgZmlyc3QgZXhlY3V0aW9uIG9mIFsxXS1bNV0gLiAiLHF1b3RlID0gRkFMU0UpDQpwcmludCgiQSBkaWZmZXJlbnQgdHJhaW5pbmcvdGVzdGluZyBzZXQgc3BsaXQgZnJvbSB0aGUgZmlyc3QgZXhlY3V0aW9uIG9mIFsxXS1bNV0gLiAiLHF1b3RlID0gRkFMU0UpDQoNCiMg6Iul5piv5pyJ5oyH5a6a5aW96Zqo5qmf56iu5a2QDQojIOaOpeS4i+S+huWcqOS9v+eUqOebuOmXnOWHveW8j+eahOaZguWAmemDveacg+W+l+WIsOebuOWQjOeahOe1kOaenA0KIyDlm6DmrKHoi6XmmK/mspLmnInoqK3lrprpmqjmqZ/nqK7lrZDnmoTmlbjlrZfjgIHmiJbmmK/mjIflrprkuobkuI3lkIznmoTnqK7lrZANCiMg5ZCM5qij55qE5Ye95byP5bCH5pyD5pyJ5LiN5ZCM55qE55Si5Ye6DQoNCmBgYA0KPGJyPg0KDQotIC0gLQ0KDQojIyMjIDQg5bu656uL5qih5Z6LIEJ1aWxkaW5nIGEgTG9naXN0aWMgUmVncmVzc2lvbiBNb2RlbA0KDQrjgJAqKjQuMSDpoa/okZfmgKcqKuOAkVdoYXQgdmFyaWFibGVzIGFyZSBzaWduaWZpY2FudCBpbiB0aGlzIG1vZGVsPw0KYGBge3J9DQoNCm1vZGVsMSA8LSBnbG0odmlvbGF0b3IgfiAuICwgZGF0YSA9IHRyYWluICwgZmFtaWx5ID0gImJpbm9taWFsIikNCnN1bW1hcnkobW9kZWwxKQ0KcHJpbnQoInJhY2UgLCBzdGF0ZTQgLCBtdWx0aXBsZS5vZmZlbnNlcyIscXVvdGUgPSBGQUxTRSkNCg0KIyDliKnnlKggZ2xtIOWHveW8j+aQremFjSBmYW1pbHkg5oyH5a6a54K6IGJpbm9taWFsIOS+huW7uueri+mCj+i8r+aWr+WbnuatuOaooeWeiw0KIyDpgJnpgorlu7rnq4vnmoTmqKHlnovngrogdmlvbGF0b3Ig54K65oeJ6K6K5pW4DQojIOWFtuS7liBkYXRhLmZyYW1lIOS4reeahOiuiuaVuOmAmumAmueCuuiHquiuiuaVuA0KDQpgYGANCg0K44CQKio0LjIg5b6e5Zue5q245L+C5pW45Lyw6KiI6YKK6Zqb5pWI55SoKirjgJFXaGF0IGNhbiB3ZSBzYXkgYmFzZWQgb24gdGhlIGNvZWZmaWNpZW50IG9mIHRoZSBgbXVsdGlwbGUub2ZmZW5zZXNgIHZhcmlhYmxlPw0KYGBge3J9DQoNCmV4cCgxLjYxMTk5MikNCnByaW50KCJPdXIgbW9kZWwgcHJlZGljdHMgdGhhdCBhIHBhcm9sZWUgd2hvIGNvbW1pdHRlZCBtdWx0aXBsZSBvZmZlbnNlcyBoYXMgNS4wMSB0aW1lcyBoaWdoZXIgb2RkcyBvZiBiZWluZyBhIHZpb2xhdG9yIHRoYW4gYSBwYXJvbGVlIHdobyBkaWQgbm90IGNvbW1pdCBtdWx0aXBsZSBvZmZlbnNlcyBidXQgaXMgb3RoZXJ3aXNlIGlkZW50aWNhbC4gIiwgcXVvdGUgPSBGQUxTRSkNCg0KIyDmk4HmnIkgbXVsdGlwbGUgb2ZmZW5zZXMg57SA6YyE55qE5YGH6YeL6ICFDQojIOaciSA1LjAxIOWAjeeahOapn+eOh+mBleWPjeWBh+mHi+acn+mWk+eahOimj+Wumg0KDQpgYGANCg0K44CQKio0LjMg5b6e6aCQ5ris5YC85Lyw6KiI5Yud546H5ZKM5qmf546HKirjgJFBY2NvcmRpbmcgdG8gdGhlIG1vZGVsLCB3aGF0IGFyZSB0aGUgb2RkcyB0aGlzIGluZGl2aWR1YWwgaXMgYSB2aW9sYXRvcj8gIFdoYXQgaXMgdGhlIHByb2JhYmlsaXR5IHRoaXMgaW5kaXZpZHVhbCBpcyBhIHZpb2xhdG9yPw0KYGBge3J9DQoNCmxvZ2l0IDwtIHByZWRpY3QobW9kZWwxLG5ld2RhdGEgPSBkYXRhLmZyYW1lKG1hbGUgPSAiMSIscmFjZSA9ICIxIixhZ2UgPSA1MCwgc3RhdGUgPSAiMSIsIHRpbWUuc2VydmVkID0gMyAsIG1heC5zZW50ZW5jZSA9IDEyLCBtdWx0aXBsZS5vZmZlbnNlcyA9ICIwIixjcmltZSA9ICIyIikpDQpuYW1lcyhsb2dpdCkgPC0gTlVMTA0KZXhwKGxvZ2l0KQ0KMS8oMStleHAoLWxvZ2l0KSkNCg0KIyDlsIfoqbLlgYfph4vogIXnmoTos4foqIrkuJ/lhaXmqKHlnovpoJDmuKzlvowNCiMg5oqK57WQ5p6c5LmLIGxvZ2l0IOioiOeul+aIkCBvZGRzIOS7peWPiiBwcm9iYWJpbGl0eQ0KDQpgYGANCjxicj4NCg0KLSAtIC0NCg0KIyMjIyA1IOmpl+itieaooeWeiyBFdmFsdWF0aW5nIHRoZSBNb2RlbCBvbiB0aGUgVGVzdGluZyBTZXQNCg0K44CQKio1LjEg5b6e5ris6Kmm6LOH5paZ6aCQ5ris5qmf546HKirjgJFXaGF0IGlzIHRoZSBtYXhpbXVtIHByZWRpY3RlZCBwcm9iYWJpbGl0eSBvZiBhIHZpb2xhdGlvbj8NCmBgYHtyfQ0KDQptYXgocHJlZGljdChtb2RlbDEsbmV3ZGF0YSA9IHRlc3QsIHR5cGUgPSAicmVzcG9uc2UiKSkNCg0KYGBgDQoNCuOAkCoqNS4yIOW+nua3t+a3huefqemZo+ioiOeul+aVj+aEn+aAp+OAgeaYjueiuuaAp+OAgeato+eiuueOhyoq44CRV2hhdCBpcyB0aGUgbW9kZWwncyBgc2Vuc2l0aXZpdHlgLCBgc3BlY2lmaWNpdHlgLCBgYWNjdXJhY3lgPw0KYGBge3J9DQoNCnByZVJlc3VsdCA8LSBwcmVkaWN0KG1vZGVsMSxuZXdkYXRhID0gdGVzdCwgdHlwZSA9ICJyZXNwb25zZSIpDQp0YWJsZSh0ZXN0JHZpb2xhdG9yLCBhcy5udW1lcmljKHByZVJlc3VsdCA+PSAwLjUpKQ0KMTIvKDExKzEyKQ0KMTY3LygxNjcrMTIpDQooMTY3KzEyKS8oMTY3KzEyKzExKzEyKQ0KDQojIOWIqeeUqCB0YWJsZSgpIOioiOeul+WHuiBjb25mdXNpb24gbWF0cml4DQojIOS4puWIqeeUqCBjb25mdXNpb24gbWF0cml4IOioiOeulyBTZW5zaXRpdml0eeOAgVNwZWNpZmljaXR5IOS7peWPiiBBY2N1cmFjeQ0KDQpgYGANCg0K44CQKio1LjMg5bqV57ea5qmf546HKirjgJFXaGF0IGlzIHRoZSBhY2N1cmFjeSBvZiBhIHNpbXBsZSBtb2RlbCB0aGF0IHByZWRpY3RzIHRoYXQgZXZlcnkgcGFyb2xlZSBpcyBhIG5vbi12aW9sYXRvcj8NCmBgYHtyfQ0KDQp0YWJsZSh0ZXN0JHZpb2xhdG9yKQ0KMTc5LygxNzkrMjMpDQoNCiMg5bqV57ea5qmf546H5Zau57SU55yL5YW2IHZpb2xhdGVyIOiuiuaVuOmgheaYr+WQpueCuiAxDQoNCmBgYA0KDQrjgJAqKjUuNCDmoLnmk5rloLHlhJ/nn6npmaPoqr/mlbToh6jnlYzmqZ/njocqKuOAkVdoaWNoIG9mIHRoZSBmb2xsb3dpbmcgbW9zdCBsaWtlbHkgZGVzY3JpYmVzIHRoZWlyIHByZWZlcmVuY2VzIGFuZCBiZXN0IGNvdXJzZSBvZiBhY3Rpb24/DQpgYGB7cn0NCg0KcHJpbnQoIlRoZSBib2FyZCBhc3NpZ25zIG1vcmUgY29zdCB0byBhIGZhbHNlIG5lZ2F0aXZlIHRoYW4gYSBmYWxzZSBwb3NpdGl2ZSwgYW5kIHNob3VsZCB0aGVyZWZvcmUgdXNlIGEgbG9naXN0aWMgcmVncmVzc2lvbiBjdXRvZmYgbGVzcyB0aGFuIDAuNS4gIiwgcXVvdGUgPSBGQUxTRSkNCg0KIyDmlL7pjK/kurrmr5Tpl5zpjK/kurrnmoTmiJDmnKzopoHlpKflvpflpJrvvIzlm6DmraToqZXkvLDoqbLlgYfph4vniq/mmK/lkKbmnIPpgZXlj43opo/lrprnmoTmqJnmupbmh4noqbLopoHlmrTmoLzkuIDkupsNCg0KYGBgDQoNCuOAkCoqNS41IOato+eiuueOhyB2cyDovqjorZjnjocqKuOAkVdoaWNoIG9mIHRoZSBmb2xsb3dpbmcgaXMgdGhlIG1vc3QgYWNjdXJhdGUgYXNzZXNzbWVudCBvZiB0aGUgdmFsdWUgb2YgdGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgd2l0aCBhIGN1dG9mZiAwLjUgdG8gYSBwYXJvbGUgYm9hcmQsIGJhc2VkIG9uIHRoZSBtb2RlbCdzIGFjY3VyYWN5IGFzIGNvbXBhcmVkIHRvIHRoZSBzaW1wbGUgYmFzZWxpbmUgbW9kZWw/DQpgYGB7cn0NCg0KcHJpbnQoIlRoZSBtb2RlbCBpcyBsaWtlbHkgb2YgdmFsdWUgdG8gdGhlIGJvYXJkLCBhbmQgdXNpbmcgYSBkaWZmZXJlbnQgbG9naXN0aWMgcmVncmVzc2lvbiBjdXRvZmYgaXMgbGlrZWx5IHRvIGltcHJvdmUgdGhlIG1vZGVsJ3MgdmFsdWUuICIsIHF1b3RlID0gRkFMU0UpDQoNCiMg5Y+v5Lul5bCH5L2O5pS+6Yyv5Lq655qE5qyh5pW4DQoNCmBgYA0KDQrjgJAqKjUuNiDoqIjnrpfovqjorZjnjocqKuOAkVVzaW5nIHRoZSBgUk9DUmAgcGFja2FnZSwgd2hhdCBpcyB0aGUgQVVDIHZhbHVlIGZvciB0aGUgbW9kZWw/DQpgYGB7cn0NCg0KbGlicmFyeShST0NSKQ0KcHJlZCA8LSBwcmVkaWN0aW9uKHByZVJlc3VsdCwgdGVzdCR2aW9sYXRvcikNCmFzLm51bWVyaWMocGVyZm9ybWFuY2UocHJlZCwgImF1YyIpQHkudmFsdWVzKQ0KDQpgYGANCg0K44CQKio1Ljcg6L6o6K2Y546H55qE5a6a576pKirjgJFEZXNjcmliZSB0aGUgbWVhbmluZyBvZiBBVUMgaW4gdGhpcyBjb250ZXh0Lg0KYGBge3J9DQoNCnByaW50KCJUaGUgcHJvYmFiaWxpdHkgdGhlIG1vZGVsIGNhbiBjb3JyZWN0bHkgZGlmZmVyZW50aWF0ZSBiZXR3ZWVuIGEgcmFuZG9tbHkgc2VsZWN0ZWQgcGFyb2xlIHZpb2xhdG9yIGFuZCBhIHJhbmRvbWx5IHNlbGVjdGVkIHBhcm9sZSBub24tdmlvbGF0b3IuIiwgcXVvdGUgPSBGQUxTRSkNCg0KIyDmqKHlnovog73lpKDnjJzlsI3lgYfph4vniq/kuI3mnIPpgZXlj43opo/lrproiIfmnIPpgZXlj43opo/lrprnmoTmpoLnjocNCg0KYGBgDQo8YnI+DQoNCi0gLSAtDQoNCiMjIyMgNiDmir3mqKPlgY/lt64gSWRlbnRpZnlpbmcgQmlhcyBpbiBPYnNlcnZhdGlvbmFsIERhdGENCg0K44CQKio2LjEg5aaC5L2V6YG/5YWN44CB6Ki65pa344CB5L+u5q2j5oq95qij5YGP5beuKirjgJFIb3cgY291bGQgd2UgaW1wcm92ZSBvdXIgZGF0YXNldCB0byBiZXN0IGFkZHJlc3Mgc2VsZWN0aW9uIGJpYXM/DQpgYGB7cn0NCg0KcHJpbnQoIldlIHNob3VsZCB1c2UgYSBkYXRhc2V0IHRyYWNraW5nIGEgZ3JvdXAgb2YgcGFyb2xlZXMgZnJvbSB0aGUgc3RhcnQgb2YgdGhlaXIgcGFyb2xlIHVudGlsIGVpdGhlciB0aGV5IHZpb2xhdGVkIHBhcm9sZSBvciB0aGV5IGNvbXBsZXRlZCB0aGVpciB0ZXJtLiIsIHF1b3RlID0gRkFMU0UpDQoNCiMg6Z2i5bCN57y65ryP5YC8DQojIOiLpeaYr+Wwh+ipsuWBh+mHi+eKr+eahCB2aW9sYXRvciDoh6rli5Xoo5zngrogMA0KIyDmnIPpgKDmiJDmqKHlnovlgY/lkJHmlL7lpKfkuI3mnIPniq/nvarnmoTlj6/og73mgKcNCiMg6Iul5piv5bCH6Kmy5YGH6YeL54qv55qEIHZpb2xhdG9yIOiHquWLleijnOeCuiBOQQ0KIyBSIOacg+iHquWLleWcqOW7uueri+aooeWei+eahOaZguWAmeWJlOmZpOmAmeS6m+ingOa4rOWAvA0KDQojIOacgOWlveeahOaWueW8j+WwseaYr+WOu+i/vei5pOmAmeS6m+WBh+mHi+eKr+iHs+WFtuWBh+mHi+acn+mWk+e1kOadnw0KDQpgYGANCjxicj4NCg0KLSAtIC0NCg0KPGJyPjxicj48YnI+DQo=