Overview
In this homework assignment, you will work through various classification metrics. You will be asked to create functions in R to carry out the various calculations. You will also investigate some functions in packages that will let you obtain the equivalent results. Finally, you will create graphical output that also can be used to evaluate the output of classification models, such as binary logistic regression.
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(tidyr)
library(knitr)
library(zoo)
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
1.Download the classification output data set.
git <- "https://raw.githubusercontent.com/nancunjie4560/Data621/master/classification-output-data.csv"
data <- read.csv(git)
head(data, 10)
## pregnant glucose diastolic skinfold insulin bmi pedigree age class
## 1 7 124 70 33 215 25.5 0.161 37 0
## 2 2 122 76 27 200 35.9 0.483 26 0
## 3 3 107 62 13 48 22.9 0.678 23 1
## 4 1 91 64 24 0 29.2 0.192 21 0
## 5 4 83 86 19 0 29.3 0.317 34 0
## 6 1 100 74 12 46 19.5 0.149 28 0
## 7 9 89 62 0 0 22.5 0.142 33 0
## 8 8 120 78 0 0 25.0 0.409 64 0
## 9 1 79 60 42 48 43.5 0.678 23 0
## 10 2 123 48 32 165 42.1 0.520 26 0
## scored.class scored.probability
## 1 0 0.32845226
## 2 0 0.27319044
## 3 0 0.10966039
## 4 0 0.05599835
## 5 0 0.10049072
## 6 0 0.05515460
## 7 0 0.10711542
## 8 0 0.45994744
## 9 0 0.11702368
## 10 0 0.31536320
2. The data set has three key columns we will use:
class: the actual class for the observation.
scored.class: the predicted class for the observation (based on a threshold of 0.5).
scored.probability: the predicted probability of success for the observation.
Use the table() function to get the raw confusion matrix for this scored dataset. Make sure you understand the output. In particular, do the rows represent the actual or predicted class? The columns?
rcm<-table(data$scored.class,data$class)[2:1,2:1]
rcm
##
## 1 0
## 1 27 5
## 0 30 119
As the table shows, the rows are predicted classes and the comlumns are actual classes.
3.Write a function that takes the data set as a dataframe, with actual and predicted classifications identified, and returns the accuracy of the predictions.
predict_accuracy<- function(x){
a <- sum(x$class == 1 & x$scored.class == 1)
d <- sum(x$class == 0 & x$scored.class == 0)
(a + d)/nrow(x)}
predict_accuracy(data)
## [1] 0.8066298
4.Write a function that takes the data set as a dataframe, with actual and predicted classifications identified, and returns the classification error rate of the predictions.
predict_error_rate<-function(x){
b<-sum(data$class == 0 & data$scored.class == 1)
c<-sum(data$class == 1 & data$scored.class == 0)
(b + c)/nrow(data)}
predict_error_rate(data)
## [1] 0.1933702
5. Write a function that takes the data set as a dataframe, with actual and predicted classifications identified, and returns the precision of the predictions.
predict_precision<-function(x){
a <- sum(data$class == 1 & data$scored.class == 1)
b<-sum(data$class == 0 & data$scored.class == 1)
a/(a+b)}
predict_precision(data)
## [1] 0.84375
6. Write a function that takes the data set as a dataframe, with actual and predicted classifications identified, and returns the sensitivity of the predictions. Sensitivity is also known as recall.
predict_sensitivity<-function(x){
a <- sum(data$class == 1 & data$scored.class == 1)
c<-sum(data$class == 1 & data$scored.class == 0)
a/(a+c)}
predict_sensitivity(data)
## [1] 0.4736842
7. Write a function that takes the data set as a dataframe, with actual and predicted classifications identified, and returns the specificity of the predictions.
predict_specificity<-function(x){
d <- sum(data$class == 0 & data$scored.class == 0)
b<-sum(data$class == 0 & data$scored.class == 1)
d/(d+b)}
predict_specificity(data)
## [1] 0.9596774
8. Write a function that takes the data set as a dataframe, with actual and predicted classifications identified, and returns the F1 score of the predictions.
predict_F1Score<-function(x){
(2*predict_precision(x)*predict_sensitivity(x))/(predict_precision(x)+predict_sensitivity(x))}
predict_F1Score(data)
## [1] 0.6067416
9. Before we move on, let’s consider a question that was asked: What are the bounds on the F1 score? Show that the F1 score will always be between 0 and 1.
We calculate the F1 socre with precision and the sensitivity. The precision and the sensitivity are bounded between 0 and 1, and any calculation with numbers bounded between 0 and 1 is also results bounded between 0 and 1. Therefore, F1 score will be between 0 and 1.
10. Write a function that generates an ROC curve from a data set with a true classification column (class in our example) and a probability column (scored.probability in our example). Your function should return a list that includes the plot of the ROC curve and a vector that contains the calculated area under the curve (AUC). Note that I recommend using a sequence of thresholds ranging from 0 to 1 at 0.01 intervals.
ROC <- function(x, y){
x <- x[order(y, decreasing = TRUE)]
TPR <- cumsum(x) / sum(x)
FPR <- cumsum(!x) / sum(!x)
df <- data.frame(TPR, FPR, x)
FPR_df <- c(diff(df$FPR), 0)
TPR_df <- c(diff(df$TPR), 0)
area_under_curve <- sum(df$TPR * FPR_df) + sum(TPR_df * FPR_df)/2
print(area_under_curve)
plot(df$FPR, df$TPR, type = "l",
main = "ROC ",
xlab = "FPR",
ylab = "TPR")
abline(a = 0, b = 1)
}
ROC(data$class,data$scored.probability)
## [1] 0.8503113

- Use your created R functions and the provided classification output data set to produce all of the classification metrics discussed above.
all_metrics <- c(predict_accuracy(data), predict_error_rate(data), predict_precision(data), predict_sensitivity(data), predict_specificity(data), predict_F1Score(data))
names(all_metrics) <- c("Accuracy", "Error Rate", "Precision", "Sensitivity", "Specificity", "F1 Score")
kable(all_metrics, col.names = "Metrics")
| Accuracy |
0.8066298 |
| Error Rate |
0.1933702 |
| Precision |
0.8437500 |
| Sensitivity |
0.4736842 |
| Specificity |
0.9596774 |
| F1 Score |
0.6067416 |
- Investigate the caret package. In particular, consider the functions confusionMatrix, sensitivity, and specificity. Apply the functions to the data set. How do the results compare with your own functions?
## Loading required package: lattice
## Loading required package: ggplot2
## Confusion Matrix and Statistics
##
##
## 1 0
## 1 27 5
## 0 30 119
##
## Accuracy : 0.8066
## 95% CI : (0.7415, 0.8615)
## No Information Rate : 0.6851
## P-Value [Acc > NIR] : 0.0001712
##
## Kappa : 0.4916
##
## Mcnemar's Test P-Value : 4.976e-05
##
## Sensitivity : 0.4737
## Specificity : 0.9597
## Pos Pred Value : 0.8438
## Neg Pred Value : 0.7987
## Prevalence : 0.3149
## Detection Rate : 0.1492
## Detection Prevalence : 0.1768
## Balanced Accuracy : 0.7167
##
## 'Positive' Class : 1
##
predict_sensitivity(data)
## [1] 0.4736842
predict_specificity(data)
## [1] 0.9596774
According to the Confusion Matrix, we can conclude that the two results are matched.
- Investigate the pROC package. Use it to generate an ROC curve for the data set. How do the results compare with your own functions?
## Warning: package 'pROC' was built under R version 3.6.3
## Type 'citation("pROC")' for a citation.
##
## Attaching package: 'pROC'
## The following objects are masked from 'package:stats':
##
## cov, smooth, var
par(mfrow=c(1,2))
ROC(data$class, data$scored.probability)
## [1] 0.8503113
plot(roc(data$class,data$scored.probability),print.auc = TRUE)
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases

According to the graph above, it shows the results are the same for ROC with 0.850.
LS0tDQp0aXRsZTogIkFzc2lnbm1lbnQtMiINCmF1dGhvcjogQW5pbCBBa3lpbGRpcmltLCBKb2huIEsuIEhhbmNvY2ssIEpvaG4gU3VoLCBFbW1hbnVlbCBIYXlibGUtR29tZXMsIENodW5qaWUNCiAgTmFuDQpkYXRlOiAiMi8yOS8yMDIwIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGhpZ2hsaWdodDogcHlnbWVudHMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogIHBkZl9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICB3b3JkX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQotLS0NCg0KIyMjIE92ZXJ2aWV3DQoNCkluIHRoaXMgaG9tZXdvcmsgYXNzaWdubWVudCwgeW91IHdpbGwgd29yayB0aHJvdWdoIHZhcmlvdXMgY2xhc3NpZmljYXRpb24gbWV0cmljcy4gWW91IHdpbGwgYmUgYXNrZWQgdG8gY3JlYXRlIGZ1bmN0aW9ucyBpbiBSIHRvIGNhcnJ5IG91dCB0aGUgdmFyaW91cyBjYWxjdWxhdGlvbnMuIFlvdSB3aWxsIGFsc28gaW52ZXN0aWdhdGUgc29tZSBmdW5jdGlvbnMgaW4gcGFja2FnZXMgdGhhdCB3aWxsIGxldCB5b3Ugb2J0YWluIHRoZSBlcXVpdmFsZW50IHJlc3VsdHMuIEZpbmFsbHksIHlvdSB3aWxsIGNyZWF0ZSBncmFwaGljYWwgb3V0cHV0IHRoYXQgYWxzbyBjYW4gYmUgdXNlZCB0byBldmFsdWF0ZSB0aGUgb3V0cHV0IG9mIGNsYXNzaWZpY2F0aW9uIG1vZGVscywgc3VjaCBhcyBiaW5hcnkgbG9naXN0aWMgcmVncmVzc2lvbi4NCg0KYGBge3J9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KHpvbykNCmBgYA0KIyMjIDEuRG93bmxvYWQgdGhlIGNsYXNzaWZpY2F0aW9uIG91dHB1dCBkYXRhIHNldC4NCmBgYHtyfQ0KZ2l0IDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vbmFuY3VuamllNDU2MC9EYXRhNjIxL21hc3Rlci9jbGFzc2lmaWNhdGlvbi1vdXRwdXQtZGF0YS5jc3YiDQpkYXRhIDwtIHJlYWQuY3N2KGdpdCkNCmhlYWQoZGF0YSwgMTApDQpgYGANCiMjIyAyLiBUaGUgZGF0YSBzZXQgaGFzIHRocmVlIGtleSBjb2x1bW5zIHdlIHdpbGwgdXNlOg0KDQpjbGFzczogdGhlIGFjdHVhbCBjbGFzcyBmb3IgdGhlIG9ic2VydmF0aW9uLg0KDQpzY29yZWQuY2xhc3M6IHRoZSBwcmVkaWN0ZWQgY2xhc3MgZm9yIHRoZSBvYnNlcnZhdGlvbiAoYmFzZWQgb24gYSB0aHJlc2hvbGQgb2YgMC41KS4NCg0Kc2NvcmVkLnByb2JhYmlsaXR5OiB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXR5IG9mIHN1Y2Nlc3MgZm9yIHRoZSBvYnNlcnZhdGlvbi4NCg0KVXNlIHRoZSB0YWJsZSgpIGZ1bmN0aW9uIHRvIGdldCB0aGUgcmF3IGNvbmZ1c2lvbiBtYXRyaXggZm9yIHRoaXMgc2NvcmVkIGRhdGFzZXQuIE1ha2Ugc3VyZSB5b3UgdW5kZXJzdGFuZCB0aGUgb3V0cHV0LiBJbiBwYXJ0aWN1bGFyLCBkbyB0aGUgcm93cyByZXByZXNlbnQgdGhlIGFjdHVhbCBvciBwcmVkaWN0ZWQgY2xhc3M/IFRoZSBjb2x1bW5zPw0KDQpgYGB7cn0NCnJjbTwtdGFibGUoZGF0YSRzY29yZWQuY2xhc3MsZGF0YSRjbGFzcylbMjoxLDI6MV0NCnJjbQ0KYGBgDQoNCkFzIHRoZSB0YWJsZSBzaG93cywgdGhlIHJvd3MgYXJlIHByZWRpY3RlZCBjbGFzc2VzIGFuZCB0aGUgY29tbHVtbnMgYXJlIGFjdHVhbCBjbGFzc2VzLg0KDQojIyMgMy5Xcml0ZSBhIGZ1bmN0aW9uIHRoYXQgdGFrZXMgdGhlIGRhdGEgc2V0IGFzIGEgZGF0YWZyYW1lLCB3aXRoIGFjdHVhbCBhbmQgcHJlZGljdGVkIGNsYXNzaWZpY2F0aW9ucyBpZGVudGlmaWVkLCBhbmQgcmV0dXJucyB0aGUgYWNjdXJhY3kgb2YgdGhlIHByZWRpY3Rpb25zLg0KDQpgYGB7cn0NCnByZWRpY3RfYWNjdXJhY3k8LSBmdW5jdGlvbih4KXsNCmEgPC0gc3VtKHgkY2xhc3MgPT0gMSAmIHgkc2NvcmVkLmNsYXNzID09IDEpDQpkIDwtIHN1bSh4JGNsYXNzID09IDAgJiB4JHNjb3JlZC5jbGFzcyA9PSAwKQ0KICAoYSArIGQpL25yb3coeCl9DQpwcmVkaWN0X2FjY3VyYWN5KGRhdGEpDQpgYGANCg0KDQojIyMgNC5Xcml0ZSBhIGZ1bmN0aW9uIHRoYXQgdGFrZXMgdGhlIGRhdGEgc2V0IGFzIGEgZGF0YWZyYW1lLCB3aXRoIGFjdHVhbCBhbmQgcHJlZGljdGVkIGNsYXNzaWZpY2F0aW9ucyBpZGVudGlmaWVkLCBhbmQgcmV0dXJucyB0aGUgY2xhc3NpZmljYXRpb24gZXJyb3IgcmF0ZSBvZiB0aGUgcHJlZGljdGlvbnMuDQoNCmBgYHtyfQ0KcHJlZGljdF9lcnJvcl9yYXRlPC1mdW5jdGlvbih4KXsNCmI8LXN1bShkYXRhJGNsYXNzID09IDAgJiBkYXRhJHNjb3JlZC5jbGFzcyA9PSAxKQ0KYzwtc3VtKGRhdGEkY2xhc3MgPT0gMSAmIGRhdGEkc2NvcmVkLmNsYXNzID09IDApDQogIChiICsgYykvbnJvdyhkYXRhKX0NCnByZWRpY3RfZXJyb3JfcmF0ZShkYXRhKQ0KYGBgDQoNCiMjIyA1LiBXcml0ZSBhIGZ1bmN0aW9uIHRoYXQgdGFrZXMgdGhlIGRhdGEgc2V0IGFzIGEgZGF0YWZyYW1lLCB3aXRoIGFjdHVhbCBhbmQgcHJlZGljdGVkIGNsYXNzaWZpY2F0aW9ucyBpZGVudGlmaWVkLCBhbmQgcmV0dXJucyB0aGUgcHJlY2lzaW9uIG9mIHRoZSBwcmVkaWN0aW9ucy4NCg0KYGBge3J9DQpwcmVkaWN0X3ByZWNpc2lvbjwtZnVuY3Rpb24oeCl7DQphIDwtIHN1bShkYXRhJGNsYXNzID09IDEgJiBkYXRhJHNjb3JlZC5jbGFzcyA9PSAxKQ0KYjwtc3VtKGRhdGEkY2xhc3MgPT0gMCAmIGRhdGEkc2NvcmVkLmNsYXNzID09IDEpDQogIGEvKGErYil9DQpwcmVkaWN0X3ByZWNpc2lvbihkYXRhKQ0KYGBgDQoNCiMjIyA2LiBXcml0ZSBhIGZ1bmN0aW9uIHRoYXQgdGFrZXMgdGhlIGRhdGEgc2V0IGFzIGEgZGF0YWZyYW1lLCB3aXRoIGFjdHVhbCBhbmQgcHJlZGljdGVkIGNsYXNzaWZpY2F0aW9ucyBpZGVudGlmaWVkLCBhbmQgcmV0dXJucyB0aGUgc2Vuc2l0aXZpdHkgb2YgdGhlIHByZWRpY3Rpb25zLiBTZW5zaXRpdml0eSBpcyBhbHNvIGtub3duIGFzIHJlY2FsbC4NCg0KYGBge3J9DQpwcmVkaWN0X3NlbnNpdGl2aXR5PC1mdW5jdGlvbih4KXsNCmEgPC0gc3VtKGRhdGEkY2xhc3MgPT0gMSAmIGRhdGEkc2NvcmVkLmNsYXNzID09IDEpDQpjPC1zdW0oZGF0YSRjbGFzcyA9PSAxICYgZGF0YSRzY29yZWQuY2xhc3MgPT0gMCkNCiAgYS8oYStjKX0NCnByZWRpY3Rfc2Vuc2l0aXZpdHkoZGF0YSkNCmBgYA0KDQojIyMgNy4gV3JpdGUgYSBmdW5jdGlvbiB0aGF0IHRha2VzIHRoZSBkYXRhIHNldCBhcyBhIGRhdGFmcmFtZSwgd2l0aCBhY3R1YWwgYW5kIHByZWRpY3RlZCBjbGFzc2lmaWNhdGlvbnMgaWRlbnRpZmllZCwgYW5kIHJldHVybnMgdGhlIHNwZWNpZmljaXR5IG9mIHRoZSBwcmVkaWN0aW9ucy4NCg0KYGBge3J9DQpwcmVkaWN0X3NwZWNpZmljaXR5PC1mdW5jdGlvbih4KXsNCmQgPC0gc3VtKGRhdGEkY2xhc3MgPT0gMCAmIGRhdGEkc2NvcmVkLmNsYXNzID09IDApDQpiPC1zdW0oZGF0YSRjbGFzcyA9PSAwICYgZGF0YSRzY29yZWQuY2xhc3MgPT0gMSkNCiAgZC8oZCtiKX0NCnByZWRpY3Rfc3BlY2lmaWNpdHkoZGF0YSkNCmBgYA0KDQojIyMgOC4gV3JpdGUgYSBmdW5jdGlvbiB0aGF0IHRha2VzIHRoZSBkYXRhIHNldCBhcyBhIGRhdGFmcmFtZSwgd2l0aCBhY3R1YWwgYW5kIHByZWRpY3RlZCBjbGFzc2lmaWNhdGlvbnMgaWRlbnRpZmllZCwgYW5kIHJldHVybnMgdGhlIEYxIHNjb3JlIG9mIHRoZSBwcmVkaWN0aW9ucy4NCg0KYGBge3J9DQpwcmVkaWN0X0YxU2NvcmU8LWZ1bmN0aW9uKHgpew0KKDIqcHJlZGljdF9wcmVjaXNpb24oeCkqcHJlZGljdF9zZW5zaXRpdml0eSh4KSkvKHByZWRpY3RfcHJlY2lzaW9uKHgpK3ByZWRpY3Rfc2Vuc2l0aXZpdHkoeCkpfQ0KcHJlZGljdF9GMVNjb3JlKGRhdGEpDQpgYGANCg0KIyMjIDkuIEJlZm9yZSB3ZSBtb3ZlIG9uLCBsZXTigJlzIGNvbnNpZGVyIGEgcXVlc3Rpb24gdGhhdCB3YXMgYXNrZWQ6IFdoYXQgYXJlIHRoZSBib3VuZHMgb24gdGhlIEYxIHNjb3JlPyBTaG93IHRoYXQgdGhlIEYxIHNjb3JlIHdpbGwgYWx3YXlzIGJlIGJldHdlZW4gMCBhbmQgMS4gDQoNCldlIGNhbGN1bGF0ZSB0aGUgRjEgc29jcmUgd2l0aCBwcmVjaXNpb24gYW5kIHRoZSBzZW5zaXRpdml0eS4gVGhlIHByZWNpc2lvbiBhbmQgdGhlIHNlbnNpdGl2aXR5IGFyZSBib3VuZGVkIGJldHdlZW4gMCBhbmQgMSwgYW5kIGFueSBjYWxjdWxhdGlvbiB3aXRoIG51bWJlcnMgYm91bmRlZCBiZXR3ZWVuIDAgYW5kIDEgaXMgYWxzbyByZXN1bHRzIGJvdW5kZWQgYmV0d2VlbiAwIGFuZCAxLiBUaGVyZWZvcmUsIEYxIHNjb3JlIHdpbGwgYmUgYmV0d2VlbiAwIGFuZCAxLg0KDQojIyMgMTAuIFdyaXRlIGEgZnVuY3Rpb24gdGhhdCBnZW5lcmF0ZXMgYW4gUk9DIGN1cnZlIGZyb20gYSBkYXRhIHNldCB3aXRoIGEgdHJ1ZSBjbGFzc2lmaWNhdGlvbiBjb2x1bW4gKGNsYXNzIGluIG91ciBleGFtcGxlKSBhbmQgYSBwcm9iYWJpbGl0eSBjb2x1bW4gKHNjb3JlZC5wcm9iYWJpbGl0eSBpbiBvdXIgZXhhbXBsZSkuIFlvdXIgZnVuY3Rpb24gc2hvdWxkIHJldHVybiBhIGxpc3QgdGhhdCBpbmNsdWRlcyB0aGUgcGxvdCBvZiB0aGUgUk9DIGN1cnZlIGFuZCBhIHZlY3RvciB0aGF0IGNvbnRhaW5zIHRoZSBjYWxjdWxhdGVkIGFyZWEgdW5kZXIgdGhlIGN1cnZlIChBVUMpLiBOb3RlIHRoYXQgSSByZWNvbW1lbmQgdXNpbmcgYSBzZXF1ZW5jZSBvZiB0aHJlc2hvbGRzIHJhbmdpbmcgZnJvbSAwIHRvIDEgYXQgMC4wMSBpbnRlcnZhbHMuDQoNCmBgYHtyfQ0KUk9DIDwtIGZ1bmN0aW9uKHgsIHkpew0KICB4IDwtIHhbb3JkZXIoeSwgZGVjcmVhc2luZyA9IFRSVUUpXQ0KICBUUFIgPC0gY3Vtc3VtKHgpIC8gc3VtKHgpDQogIEZQUiA8LSBjdW1zdW0oIXgpIC8gc3VtKCF4KQ0KICBkZiA8LSBkYXRhLmZyYW1lKFRQUiwgRlBSLCB4KQ0KICANCiAgRlBSX2RmIDwtIGMoZGlmZihkZiRGUFIpLCAwKQ0KICBUUFJfZGYgPC0gYyhkaWZmKGRmJFRQUiksIDApDQogIGFyZWFfdW5kZXJfY3VydmUgPC0gc3VtKGRmJFRQUiAqIEZQUl9kZikgKyBzdW0oVFBSX2RmICogRlBSX2RmKS8yDQogIHByaW50KGFyZWFfdW5kZXJfY3VydmUpDQogIA0KICBwbG90KGRmJEZQUiwgZGYkVFBSLCB0eXBlID0gImwiLA0KICAgICAgIG1haW4gPSAiUk9DICIsDQogICAgICAgeGxhYiA9ICJGUFIiLA0KICAgICAgIHlsYWIgPSAiVFBSIikNCiAgYWJsaW5lKGEgPSAwLCBiID0gMSkNCiAgDQp9DQoNClJPQyhkYXRhJGNsYXNzLGRhdGEkc2NvcmVkLnByb2JhYmlsaXR5KQ0KDQpgYGANCg0KMTEuIFVzZSB5b3VyIGNyZWF0ZWQgUiBmdW5jdGlvbnMgYW5kIHRoZSBwcm92aWRlZCBjbGFzc2lmaWNhdGlvbiBvdXRwdXQgZGF0YSBzZXQgdG8gcHJvZHVjZSBhbGwgb2YgdGhlIGNsYXNzaWZpY2F0aW9uIG1ldHJpY3MgZGlzY3Vzc2VkIGFib3ZlLg0KDQpgYGB7cn0NCmFsbF9tZXRyaWNzIDwtIGMocHJlZGljdF9hY2N1cmFjeShkYXRhKSwgcHJlZGljdF9lcnJvcl9yYXRlKGRhdGEpLCBwcmVkaWN0X3ByZWNpc2lvbihkYXRhKSwgcHJlZGljdF9zZW5zaXRpdml0eShkYXRhKSwgcHJlZGljdF9zcGVjaWZpY2l0eShkYXRhKSwgcHJlZGljdF9GMVNjb3JlKGRhdGEpKQ0KDQpuYW1lcyhhbGxfbWV0cmljcykgPC0gYygiQWNjdXJhY3kiLCAiRXJyb3IgUmF0ZSIsICJQcmVjaXNpb24iLCAiU2Vuc2l0aXZpdHkiLCAiU3BlY2lmaWNpdHkiLCAiRjEgU2NvcmUiKQ0KDQprYWJsZShhbGxfbWV0cmljcywgY29sLm5hbWVzID0gIk1ldHJpY3MiKQ0KYGBgDQoNCjEyLiBJbnZlc3RpZ2F0ZSB0aGUgY2FyZXQgcGFja2FnZS4gSW4gcGFydGljdWxhciwgY29uc2lkZXIgdGhlIGZ1bmN0aW9ucyBjb25mdXNpb25NYXRyaXgsIHNlbnNpdGl2aXR5LCBhbmQgc3BlY2lmaWNpdHkuIEFwcGx5IHRoZSBmdW5jdGlvbnMgdG8gdGhlIGRhdGEgc2V0LiBIb3cgZG8gdGhlIHJlc3VsdHMgY29tcGFyZSB3aXRoIHlvdXIgb3duIGZ1bmN0aW9ucz8NCmBgYHtyfQ0KbGlicmFyeShjYXJldCkNCmNvbmZ1c2lvbk1hdHJpeChyY20pDQoNCnByZWRpY3Rfc2Vuc2l0aXZpdHkoZGF0YSkNCnByZWRpY3Rfc3BlY2lmaWNpdHkoZGF0YSkNCg0KYGBgDQoNCkFjY29yZGluZyB0byB0aGUgQ29uZnVzaW9uIE1hdHJpeCwgd2UgY2FuIGNvbmNsdWRlIHRoYXQgdGhlIHR3byByZXN1bHRzIGFyZSBtYXRjaGVkLg0KDQoxMy4gSW52ZXN0aWdhdGUgdGhlIHBST0MgcGFja2FnZS4gVXNlIGl0IHRvIGdlbmVyYXRlIGFuIFJPQyBjdXJ2ZSBmb3IgdGhlIGRhdGEgc2V0LiBIb3cgZG8gdGhlIHJlc3VsdHMgY29tcGFyZSB3aXRoIHlvdXIgb3duIGZ1bmN0aW9ucz8NCg0KYGBge3J9DQpsaWJyYXJ5KHBST0MpDQpwYXIobWZyb3c9YygxLDIpKQ0KUk9DKGRhdGEkY2xhc3MsIGRhdGEkc2NvcmVkLnByb2JhYmlsaXR5KQ0KcGxvdChyb2MoZGF0YSRjbGFzcyxkYXRhJHNjb3JlZC5wcm9iYWJpbGl0eSkscHJpbnQuYXVjID0gVFJVRSkNCg0KYGBgDQoNCkFjY29yZGluZyB0byB0aGUgZ3JhcGggYWJvdmUsIGl0IHNob3dzIHRoZSByZXN1bHRzIGFyZSB0aGUgc2FtZSBmb3IgUk9DIHdpdGggMC44NTAu