Victor Enchautegui
10/25/2020
Data Preparation
A.1. Data Loading and Initial Transformation
Load data and transform the default.payment.next.month variable into a nominal (factor) variable:
library(ggplot2)
cc <- read.csv("data/UCI_Credit_Card.csv")
cc$default.payment.next.month <- factor(cc$default.payment.next.month,levels=c(0,1), labels=c("No","Yes"))
A.2. Demographic Variables
Education
ggplot(cc, aes(x=EDUCATION, fill=default.payment.next.month, color=default.payment.next.month)) +
geom_histogram(binwidth=1, position="stack") +
scale_color_manual(values=c("black","black")) +
scale_fill_manual(values=c("darkolivegreen4", "red"))

Analysis: EDUCATION: (1=graduate school, 2=university, 3=high school, 4=others, 5=unknown, 6=unknown). Majority of the population has an university education or greater. This variable will help with our analysis as it seems there is a relationship with university level education and defaulting.
Age
ggplot(cc, aes(x=AGE, fill=default.payment.next.month, color=default.payment.next.month)) +
geom_histogram(binwidth=1, position="stack") +
scale_color_manual(values=c("black","black")) +
scale_fill_manual(values=c("darkolivegreen4", "red"))

Analysis: AGE: Age in years. There seems to be a relationship between the younger age group and defaulting, however this can be due to the fact the population sample is heavy on the left.
A.3. Payment Status Variables
PAY_0
ggplot(cc, aes(x=PAY_0, fill=default.payment.next.month, color=default.payment.next.month)) +
geom_histogram(binwidth=1, position="stack") +
scale_color_manual(values=c("black","black")) +
scale_fill_manual(values=c("darkolivegreen4", "red"))

Analysis: PAY_0: Repayment status in September, 2005 (-1=pay duly, 1=payment delay for one month, 2=payment delay for two months, … 8=payment delay for eight months, 9=payment delay for nine months and above). However, there are two unnamed labels that are represented within the data, which are -2 and 0. If -1 = ‘paid on-time’, what 0 represent? I read two thread on Kaggle for this issue, and it continues to be unclear and the data didn’t match the responses. For the sake of this analysis, I will retrofit having -2 start as “Pay duly”.
I picked this pay statement as it is the initial statement and there is a focused median.
PAY_3
ggplot(cc, aes(x=PAY_3, fill=default.payment.next.month, color=default.payment.next.month)) +
geom_histogram(binwidth=1, position="stack") +
scale_color_manual(values=c("black","black")) +
scale_fill_manual(values=c("darkolivegreen4", "red"))

Analysis: There is a spike increase in label 0 of defaulted claims. This may mean that the probability of having consecutive ‘no default’ decreases over time.
PAY_5
ggplot(cc, aes(x=PAY_5, fill=default.payment.next.month, color=default.payment.next.month)) +
geom_histogram(binwidth=1, position="stack") +
scale_color_manual(values=c("black","black")) +
scale_fill_manual(values=c("pink", "red"))

Analysis: Similar analysis as PAY_3, however there is an increase in -2, -1, 0 and a decrease in 2.
A.4. Transforming Nominal Variables
Transform related demographic variables into nominal values with proper labels:
cc$AGE <- cut(cc$AGE,breaks=c(0,20,30,40,50,60,70,80,100), labels=c("<20s","20s", "30s", "40s", "50s", "60s", "70s", "80s+"))
cc$AGE <- factor(cc$AGE)
cc$EDUCATION <- factor(cc$EDUCATION,levels=c(1,2,3,4,5,6), labels=c("Grad School", "University", "High School", "Others", "Unknown", "Unknown"))
Transformed the additional variables to help with section B.2 of the assignment:
cc$PAY_0 <- factor(cc$PAY_0 , levels=c(-2,-1,0,1,2,3,4,5,6,7,8,9), labels=c("Pay duly", "1mo", "2mos", "3mos", "4mos", "5mos", "6mos", "7mos", "8mos", "9mos", "10mos", "11mos"))
cc$PAY_2 <- factor(cc$PAY_2 , levels=c(-2,-1,0,1,2,3,4,5,6,7,8,9), labels=c("Pay duly", "1mo", "2mos", "3mos", "4mos", "5mos", "6mos", "7mos", "8mos", "9mos", "10mos", "11mos"))
cc$PAY_3 <- factor(cc$PAY_3 , levels=c(-2,-1,0,1,2,3,4,5,6,7,8,9), labels=c("Pay duly", "1mo", "2mos", "3mos", "4mos", "5mos", "6mos", "7mos", "8mos", "9mos", "10mos", "11mos"))
View Education:
View(cc$EDUCATION)
View Age:
View(cc$AGE)
A.5. Selection of Training Data
train <- cc[sample(nrow(cc), 5000), ]
Check train data:
nrow(train)
[1] 5000
A.6. Selection of Testing Data
test <- cc[c(18,3600),]
test
Data Classification
B.1. Naive Bayes using Demographic Variables
library(e1071)
nbDem <- naiveBayes(default.payment.next.month ~ SEX + EDUCATION + AGE, train)
nbDem
Naive Bayes Classifier for Discrete Predictors
Call:
naiveBayes.default(x = X, y = Y, laplace = laplace)
A-priori probabilities:
Y
No Yes
0.764 0.236
Conditional probabilities:
SEX
Y [,1] [,2]
No 1.614398 0.4868009
Yes 1.571186 0.4951164
EDUCATION
Y Grad School University High School Others Unknown
No 0.3592563498 0.4642576591 0.1550144017 0.0060225190 0.0154490704
Yes 0.3033898305 0.5076271186 0.1838983051 0.0008474576 0.0042372881
AGE
Y 20s 30s 40s 50s 60s 70s
No 0.365706806 0.374345550 0.191623037 0.060471204 0.007853403 0.000000000
Yes 0.372033898 0.315254237 0.209322034 0.091525424 0.011864407 0.000000000
SEX: The probability of defaulting is higher for women than it is for men. However it does not make sense as the probability of not defaulting is higher for women than it is for men.
EDUCATION: The probability of defaulting is higher for University than it is for others. However it does not make sense as the probability of not defaulting is higher for University than it is for others
AGE: The probability of defaulting is higher for younger individuals than it is for others. However it does not make sense as the probability of not defaulting is higher for younger individuals than it is for others
At first, the data does not make sense when comparing the ‘Yes’ and ‘No’, however the model created the conditional probability for each feature separately; not comparing it to the total distribution:
P(Female | no default) P(Male | no default) P(Female | default) P(Male | default)
Run model on first row of test data:
predict(nbDem, test[1,])
[1] No
Levels: No Yes
Run model on second row of test data:
predict(nbDem, test[2,])
[1] No
Levels: No Yes
The predictions are correct for both as probability of defaulting is low for both individuals for each conditional (except for the sex conditional which slightly higher to default for women). Looking at the overall probability of not defaulting is skewed to not defaulting by 78%, so the predictions are correct.
B.2. Naive Bayes using Demographic Variables
nbPay <- naiveBayes(default.payment.next.month ~ PAY_0 + PAY_2 + PAY_3, train)
nbPay
Naive Bayes Classifier for Discrete Predictors
Call:
naiveBayes.default(x = X, y = Y, laplace = laplace)
A-priori probabilities:
Y
No Yes
0.764 0.236
Conditional probabilities:
PAY_0
Y Pay duly 1mo 2mos 3mos 4mos 5mos 6mos 7mos 8mos 9mos 10mos 11mos
No 0.1005235602 0.1965968586 0.5599476440 0.1036649215 0.0345549738 0.0036649215 0.0002617801 0.0002617801 0.0000000000 0.0002617801 0.0002617801 0.0000000000
Yes 0.0491525424 0.1457627119 0.3101694915 0.1872881356 0.2593220339 0.0381355932 0.0067796610 0.0016949153 0.0000000000 0.0000000000 0.0016949153 0.0000000000
PAY_2
Y Pay duly 1mo 2mos 3mos 4mos 5mos 6mos 7mos 8mos 9mos 10mos 11mos
No 0.1311518325 0.2172774869 0.5657068063 0.0002617801 0.0798429319 0.0036649215 0.0013089005 0.0000000000 0.0005235602 0.0002617801 0.0000000000 0.0000000000
Yes 0.0949152542 0.1415254237 0.3991525424 0.0016949153 0.3313559322 0.0220338983 0.0059322034 0.0008474576 0.0008474576 0.0016949153 0.0000000000 0.0000000000
PAY_3
Y Pay duly 1mo 2mos 3mos 4mos 5mos 6mos 7mos 8mos 9mos 10mos 11mos
No 0.1350785340 0.2185863874 0.5541884817 0.0000000000 0.0861256545 0.0031413613 0.0013089005 0.0005235602 0.0002617801 0.0005235602 0.0002617801 0.0000000000
Yes 0.1161016949 0.1313559322 0.4228813559 0.0008474576 0.3000000000 0.0203389831 0.0025423729 0.0016949153 0.0025423729 0.0016949153 0.0000000000 0.0000000000
For ‘Pay duly’ and payments delay for 1 or 2 month, the probability of defaulting is higher than not defaulting. However, this switches after 3 or more months for each pay statement (variables). If the client has more than 3 months of late payments, there’s a probability of defaulting again.
Run model on first row of test data:
predict(nbPay, test[1,])
[1] No
Levels: No Yes
Run model on second row of test data:
predict(nbPay, test[2,])
[1] No
Levels: No Yes
Analysis: The first individual had 2 months of delayed payments, however he made payment to avoid being delayed 3 months. He is was marked as ‘Pay duly’, which corresponds with the prediction.
The second individuals was always on-time with her payments and she continued to be on-time, which corresponds with the prediction.
After reviewing both individuals, the modal has proven to predicted correctly.
B.3. Smoothed Naive Bayes using Payment Status
nbPay <- naiveBayes(default.payment.next.month ~ PAY_0 + PAY_2 + PAY_3, train, laplace=1.5)
predict(nbPay, test[1,])
[1] No
Levels: No Yes
nbPay <- naiveBayes(default.payment.next.month ~ PAY_0 + PAY_2 + PAY_3, train, laplace=1.5)
predict(nbPay, test[2,])
[1] No
Levels: No Yes
Laplace smoothing did not produce any differences within the predictions for both individuals.
Classification with Decision Tree
C.1. Basic Decision Tree
library("rpart")
library("rpart.plot")
dtPay <- rpart(default.payment.next.month ~ PAY_0 + PAY_2 + PAY_3,
method="class",
data=train, parms=list(split='information'),
minsplit=20, cp=0.02)
rpart.plot(dtPay, type=4, extra=1)

Analysis: The decision tree visualization provides a decision stump (one-level decision tree). A decision stump makes a prediction based on the value of just a single input feature. There is additional variables to consider that will improve this decision tree.
Run model on first row of test data:
predict(nbPay, test[1,])
[1] No
Levels: No Yes
Run model on second row of test data:
predict(nbPay, test[2,])
[1] No
Levels: No Yes
Analysis: Both individuals are unlikely to default. After reviewing the actual results, the predictions are correct that both individuals did not default. The probability of not defaulting is 78%.
C.2. Decision Tree with a Different Complexity Parameter
dtPay <- rpart(default.payment.next.month ~ PAY_0 + PAY_2 + PAY_3,
method="class",
data=train, parms=list(split='information'),
minsplit=20, cp=0.001)
rpart.plot(dtPay, type=4, extra=1)

Analysis: This decision tree shows all the possible branches and likelihood of path based upon the sample size of 5,000 individuals.
The left side represent individuals that did not default; right side represent those that defaulted.
Following to the root of the tree, only 3,476 (70%) of the 5,000 population did not defaulted consecutively.
Run model on first row of test data:
predict(nbPay, test[1,])
[1] No
Levels: No Yes
Run model on second row of test data:
predict(nbPay, test[2,])
[1] No
Levels: No Yes
Analysis: Both individuals are unlikely to have default.
Conclusion
Both models predictions were correct. However, I feel there was a large population of “No defaults” which made it feel too obvious too predict.
As for the two models, I prefer to utilize the Naive Bayes model as the data was easily to understand and read from this workbook. With the Decision Tree, I had to perform several methods to zoom into the decision tree to understand how to use it.
Issues with the data. So, there is no clear understanding of -2 and 0. I have read the threads on Kaggle, however the suggested commentary did not aligned with the data points. Also, how can 110 individuals (for example: 46, 275, 2298, 2538, 3151) with ZERO for all billing statements and marked as ‘default’ for next month? It does not make sense.
LS0tDQp0aXRsZTogIklORk8gNjU5IEFzc2lnbm1lbnQgIzIgKDEwIHBvaW50cykiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KPGgzPlZpY3RvciBFbmNoYXV0ZWd1aTwvaDM+DQo8aDQ+MTAvMjUvMjAyMDwvaDQ+DQo8YnI+DQo8aDI+PGI+RGF0YSBQcmVwYXJhdGlvbjwvYj48L2gyPg0KPGgzPjxiPkEuMS4gRGF0YSBMb2FkaW5nIGFuZCBJbml0aWFsIFRyYW5zZm9ybWF0aW9uPC9iPjwvaDM+DQpMb2FkIGRhdGEgYW5kIHRyYW5zZm9ybSB0aGUgZGVmYXVsdC5wYXltZW50Lm5leHQubW9udGggdmFyaWFibGUgaW50byBhIG5vbWluYWwgKGZhY3RvcikgdmFyaWFibGU6DQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCmNjIDwtIHJlYWQuY3N2KCJkYXRhL1VDSV9DcmVkaXRfQ2FyZC5jc3YiKQ0KY2MkZGVmYXVsdC5wYXltZW50Lm5leHQubW9udGggPC0gZmFjdG9yKGNjJGRlZmF1bHQucGF5bWVudC5uZXh0Lm1vbnRoLGxldmVscz1jKDAsMSksIGxhYmVscz1jKCJObyIsIlllcyIpKQ0KDQpgYGANCjxoMz48Yj5BLjIuIERlbW9ncmFwaGljIFZhcmlhYmxlczwvYj48L2gzPg0KPGg0Pjx1PkVkdWNhdGlvbjwvdT48L2g0Pg0KYGBge3J9DQpnZ3Bsb3QoY2MsIGFlcyh4PUVEVUNBVElPTiwgZmlsbD1kZWZhdWx0LnBheW1lbnQubmV4dC5tb250aCwgY29sb3I9ZGVmYXVsdC5wYXltZW50Lm5leHQubW9udGgpKSArIA0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aD0xLCBwb3NpdGlvbj0ic3RhY2siKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiYmxhY2siLCJibGFjayIpKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCJkYXJrb2xpdmVncmVlbjQiLCAicmVkIikpDQpgYGANCjxiPkFuYWx5c2lzOjwvYj4NCkVEVUNBVElPTjogKDE9Z3JhZHVhdGUgc2Nob29sLCAyPXVuaXZlcnNpdHksIDM9aGlnaCBzY2hvb2wsIDQ9b3RoZXJzLCA1PXVua25vd24sIDY9dW5rbm93bikuIE1ham9yaXR5IG9mIHRoZSBwb3B1bGF0aW9uIGhhcyBhbiB1bml2ZXJzaXR5IGVkdWNhdGlvbiBvciBncmVhdGVyLiBUaGlzIHZhcmlhYmxlIHdpbGwgaGVscCB3aXRoIG91ciBhbmFseXNpcyBhcyBpdCBzZWVtcyB0aGVyZSBpcyBhIHJlbGF0aW9uc2hpcCB3aXRoIHVuaXZlcnNpdHkgbGV2ZWwgZWR1Y2F0aW9uIGFuZCBkZWZhdWx0aW5nLiANCg0KPGJyPg0KPGg0Pjx1PkFnZTwvdT48L2g0Pg0KYGBge3J9DQpnZ3Bsb3QoY2MsIGFlcyh4PUFHRSwgZmlsbD1kZWZhdWx0LnBheW1lbnQubmV4dC5tb250aCwgY29sb3I9ZGVmYXVsdC5wYXltZW50Lm5leHQubW9udGgpKSArIA0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aD0xLCBwb3NpdGlvbj0ic3RhY2siKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiYmxhY2siLCJibGFjayIpKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCJkYXJrb2xpdmVncmVlbjQiLCAicmVkIikpDQpgYGANCjxiPkFuYWx5c2lzOjwvYj4NCkFHRTogQWdlIGluIHllYXJzLiBUaGVyZSBzZWVtcyB0byBiZSBhIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSB5b3VuZ2VyIGFnZSBncm91cCBhbmQgZGVmYXVsdGluZywgaG93ZXZlciB0aGlzIGNhbiBiZSBkdWUgdG8gdGhlIGZhY3QgdGhlIHBvcHVsYXRpb24gc2FtcGxlIGlzIGhlYXZ5IG9uIHRoZSBsZWZ0Lg0KDQo8YnI+DQo8aDM+PGI+QS4zLiBQYXltZW50IFN0YXR1cyBWYXJpYWJsZXM8L2I+PC9oMz4NCjxoND48dT5QQVlfMDwvdT48L2g0Pg0KYGBge3J9DQpnZ3Bsb3QoY2MsIGFlcyh4PVBBWV8wLCBmaWxsPWRlZmF1bHQucGF5bWVudC5uZXh0Lm1vbnRoLCBjb2xvcj1kZWZhdWx0LnBheW1lbnQubmV4dC5tb250aCkpICsgDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoPTEsIHBvc2l0aW9uPSJzdGFjayIpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJibGFjayIsImJsYWNrIikpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoImRhcmtvbGl2ZWdyZWVuNCIsICJyZWQiKSkNCmBgYA0KPGI+QW5hbHlzaXM6PC9iPg0KUEFZXzA6IFJlcGF5bWVudCBzdGF0dXMgaW4gU2VwdGVtYmVyLCAyMDA1ICgtMT1wYXkgZHVseSwgMT1wYXltZW50IGRlbGF5IGZvciBvbmUgbW9udGgsIDI9cGF5bWVudCBkZWxheSBmb3IgdHdvIG1vbnRocywg4oCmIDg9cGF5bWVudCBkZWxheSBmb3IgZWlnaHQgbW9udGhzLCA5PXBheW1lbnQgZGVsYXkgZm9yIG5pbmUgbW9udGhzIGFuZCBhYm92ZSkuIEhvd2V2ZXIsIHRoZXJlIGFyZSB0d28gdW5uYW1lZCBsYWJlbHMgdGhhdCBhcmUgcmVwcmVzZW50ZWQgd2l0aGluIHRoZSBkYXRhLCB3aGljaCBhcmUgLTIgYW5kIDAuIElmIC0xID0gJ3BhaWQgb24tdGltZScsIHdoYXQgMCByZXByZXNlbnQ/IEkgcmVhZCB0d28gdGhyZWFkIG9uIEthZ2dsZSBmb3IgdGhpcyBpc3N1ZSwgYW5kIGl0IGNvbnRpbnVlcyB0byBiZSB1bmNsZWFyIGFuZCB0aGUgZGF0YSBkaWRuJ3QgbWF0Y2ggdGhlIHJlc3BvbnNlcy4gIEZvciB0aGUgc2FrZSBvZiB0aGlzIGFuYWx5c2lzLCBJIHdpbGwgcmV0cm9maXQgaGF2aW5nIC0yIHN0YXJ0IGFzICJQYXkgZHVseSIuDQoNCkkgcGlja2VkIHRoaXMgcGF5IHN0YXRlbWVudCBhcyBpdCBpcyB0aGUgaW5pdGlhbCBzdGF0ZW1lbnQgYW5kIHRoZXJlIGlzIGEgZm9jdXNlZCBtZWRpYW4uIA0KPGJyPg0KPGg0Pjx1PlBBWV8zPC91PjwvaDQ+DQpgYGB7cn0NCmdncGxvdChjYywgYWVzKHg9UEFZXzMsIGZpbGw9ZGVmYXVsdC5wYXltZW50Lm5leHQubW9udGgsIGNvbG9yPWRlZmF1bHQucGF5bWVudC5uZXh0Lm1vbnRoKSkgKyANCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGg9MSwgcG9zaXRpb249InN0YWNrIikgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoImJsYWNrIiwiYmxhY2siKSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygiZGFya29saXZlZ3JlZW40IiwgInJlZCIpKQ0KYGBgDQo8Yj5BbmFseXNpczo8L2I+DQpUaGVyZSBpcyBhIHNwaWtlIGluY3JlYXNlIGluIGxhYmVsIDAgb2YgZGVmYXVsdGVkIGNsYWltcy4gVGhpcyBtYXkgbWVhbiB0aGF0IHRoZSBwcm9iYWJpbGl0eSBvZiBoYXZpbmcgY29uc2VjdXRpdmUgJ25vIGRlZmF1bHQnIGRlY3JlYXNlcyBvdmVyIHRpbWUuDQo8YnI+DQo8aDQ+PHU+UEFZXzU8L3U+PC9oND4NCmBgYHtyfQ0KZ2dwbG90KGNjLCBhZXMoeD1QQVlfNSwgZmlsbD1kZWZhdWx0LnBheW1lbnQubmV4dC5tb250aCwgY29sb3I9ZGVmYXVsdC5wYXltZW50Lm5leHQubW9udGgpKSArIA0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aD0xLCBwb3NpdGlvbj0ic3RhY2siKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiYmxhY2siLCJibGFjayIpKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCJwaW5rIiwgInJlZCIpKQ0KYGBgDQo8Yj5BbmFseXNpczo8L2I+DQpTaW1pbGFyIGFuYWx5c2lzIGFzIFBBWV8zLCBob3dldmVyIHRoZXJlIGlzIGFuIGluY3JlYXNlIGluIC0yLCAtMSwgMCBhbmQgYSBkZWNyZWFzZSBpbiAyLg0KDQo8YnI+DQoNCjxoMz48Yj5BLjQuIFRyYW5zZm9ybWluZyBOb21pbmFsIFZhcmlhYmxlczwvYj48L2gzPg0KPGg0PlRyYW5zZm9ybSByZWxhdGVkIGRlbW9ncmFwaGljIHZhcmlhYmxlcyBpbnRvIG5vbWluYWwgdmFsdWVzIHdpdGggcHJvcGVyIGxhYmVsczo8L2g0Pg0KYGBge3J9DQpjYyRBR0UgPC0gY3V0KGNjJEFHRSxicmVha3M9YygwLDIwLDMwLDQwLDUwLDYwLDcwLDgwLDEwMCksIGxhYmVscz1jKCI8MjBzIiwiMjBzIiwgIjMwcyIsICI0MHMiLCAiNTBzIiwgIjYwcyIsICI3MHMiLCAiODBzKyIpKSANCmBgYA0KYGBge3J9DQpjYyRBR0UgPC0gZmFjdG9yKGNjJEFHRSkgDQpgYGANCmBgYHtyfQ0KY2MkRURVQ0FUSU9OIDwtIGZhY3RvcihjYyRFRFVDQVRJT04sbGV2ZWxzPWMoMSwyLDMsNCw1LDYpLCBsYWJlbHM9YygiR3JhZCBTY2hvb2wiLCAiVW5pdmVyc2l0eSIsICJIaWdoIFNjaG9vbCIsICJPdGhlcnMiLCAiVW5rbm93biIsICJVbmtub3duIikpDQpgYGANCg0KVHJhbnNmb3JtZWQgdGhlIGFkZGl0aW9uYWwgdmFyaWFibGVzIHRvIGhlbHAgd2l0aCBzZWN0aW9uIEIuMiBvZiB0aGUgYXNzaWdubWVudDoNCmBgYHtyfQ0KY2MkUEFZXzAgIDwtIGZhY3RvcihjYyRQQVlfMCAsIGxldmVscz1jKC0yLC0xLDAsMSwyLDMsNCw1LDYsNyw4LDkpLCBsYWJlbHM9YygiUGF5IGR1bHkiLCAiMW1vIiwgIjJtb3MiLCAiM21vcyIsICI0bW9zIiwgICI1bW9zIiwgIjZtb3MiLCAiN21vcyIsICAiOG1vcyIsICI5bW9zIiwgIjEwbW9zIiwgIjExbW9zIikpDQpgYGANCmBgYHtyfQ0KY2MkUEFZXzIgIDwtIGZhY3RvcihjYyRQQVlfMiAsIGxldmVscz1jKC0yLC0xLDAsMSwyLDMsNCw1LDYsNyw4LDkpLCBsYWJlbHM9YygiUGF5IGR1bHkiLCAiMW1vIiwgIjJtb3MiLCAiM21vcyIsICI0bW9zIiwgICI1bW9zIiwgIjZtb3MiLCAiN21vcyIsICAiOG1vcyIsICI5bW9zIiwgIjEwbW9zIiwgIjExbW9zIikpDQpgYGANCmBgYHtyfQ0KY2MkUEFZXzMgIDwtIGZhY3RvcihjYyRQQVlfMyAsIGxldmVscz1jKC0yLC0xLDAsMSwyLDMsNCw1LDYsNyw4LDkpLCBsYWJlbHM9YygiUGF5IGR1bHkiLCAiMW1vIiwgIjJtb3MiLCAiM21vcyIsICI0bW9zIiwgICI1bW9zIiwgIjZtb3MiLCAiN21vcyIsICAiOG1vcyIsICI5bW9zIiwgIjEwbW9zIiwgIjExbW9zIikpDQpgYGANCjxicj4NCjxoND5WaWV3IEVkdWNhdGlvbjo8L2g0Pg0KYGBge3J9DQpWaWV3KGNjJEVEVUNBVElPTikgDQpgYGANCjxicj4NCjxoND5WaWV3IEFnZTo8L2g0Pg0KYGBge3J9DQpWaWV3KGNjJEFHRSkNCmBgYA0KPGJyPg0KDQo8aDM+PGI+QS41LiBTZWxlY3Rpb24gb2YgVHJhaW5pbmcgRGF0YTwvYj48L2gzPg0KYGBge3J9DQp0cmFpbiA8LSBjY1tzYW1wbGUobnJvdyhjYyksIDUwMDApLCBdIA0KYGBgDQo8aDQ+Q2hlY2sgdHJhaW4gZGF0YTo8L2g0Pg0KYGBge3J9DQpucm93KHRyYWluKQ0KYGBgDQoNCjxoMz48Yj5BLjYuIFNlbGVjdGlvbiBvZiBUZXN0aW5nIERhdGE8L2I+PC9oMz4NCmBgYHtyfQ0KdGVzdCA8LSBjY1tjKDE4LDM2MDApLF0NCnRlc3QNCmBgYA0KDQoNCjxoMj48Yj5EYXRhIENsYXNzaWZpY2F0aW9uPC9iPjwvaDI+DQo8aDM+PGI+Qi4xLiBOYWl2ZSBCYXllcyB1c2luZyBEZW1vZ3JhcGhpYyBWYXJpYWJsZXM8L2I+PC9oMz4NCmBgYHtyfQ0KbGlicmFyeShlMTA3MSkNCm5iRGVtIDwtIG5haXZlQmF5ZXMoZGVmYXVsdC5wYXltZW50Lm5leHQubW9udGggfiBTRVggKyBFRFVDQVRJT04gKyBBR0UsIHRyYWluKQ0KbmJEZW0NCmBgYA0KPGI+PHU+U0VYOjwvdT48L2I+IFRoZSBwcm9iYWJpbGl0eSBvZiBkZWZhdWx0aW5nIGlzIGhpZ2hlciBmb3Igd29tZW4gdGhhbiBpdCBpcyBmb3IgbWVuLiBIb3dldmVyIGl0IGRvZXMgbm90IG1ha2Ugc2Vuc2UgYXMgdGhlIHByb2JhYmlsaXR5IG9mIG5vdCBkZWZhdWx0aW5nIGlzIGhpZ2hlciBmb3Igd29tZW4gdGhhbiBpdCBpcyBmb3IgbWVuLiANCg0KPGI+PHU+RURVQ0FUSU9OOjwvdT48L2I+IFRoZSBwcm9iYWJpbGl0eSBvZiBkZWZhdWx0aW5nIGlzIGhpZ2hlciBmb3IgVW5pdmVyc2l0eSB0aGFuIGl0IGlzIGZvciBvdGhlcnMuIEhvd2V2ZXIgaXQgZG9lcyBub3QgbWFrZSBzZW5zZSBhcyB0aGUgcHJvYmFiaWxpdHkgb2Ygbm90IGRlZmF1bHRpbmcgaXMgaGlnaGVyIGZvciBVbml2ZXJzaXR5IHRoYW4gaXQgaXMgZm9yIG90aGVycyANCg0KPGI+PHU+QUdFOjwvdT48L2I+IFRoZSBwcm9iYWJpbGl0eSBvZiBkZWZhdWx0aW5nIGlzIGhpZ2hlciBmb3IgeW91bmdlciBpbmRpdmlkdWFscyB0aGFuIGl0IGlzIGZvciBvdGhlcnMuIEhvd2V2ZXIgaXQgZG9lcyBub3QgbWFrZSBzZW5zZSBhcyB0aGUgcHJvYmFiaWxpdHkgb2Ygbm90IGRlZmF1bHRpbmcgaXMgaGlnaGVyIGZvciB5b3VuZ2VyIGluZGl2aWR1YWxzIHRoYW4gaXQgaXMgZm9yIG90aGVycyANCg0KQXQgZmlyc3QsIHRoZSBkYXRhIGRvZXMgbm90IG1ha2Ugc2Vuc2Ugd2hlbiBjb21wYXJpbmcgdGhlICdZZXMnIGFuZCAnTm8nLCBob3dldmVyIHRoZSBtb2RlbCBjcmVhdGVkIHRoZSBjb25kaXRpb25hbCBwcm9iYWJpbGl0eSBmb3IgZWFjaCBmZWF0dXJlIHNlcGFyYXRlbHk7IG5vdCBjb21wYXJpbmcgaXQgdG8gdGhlIHRvdGFsIGRpc3RyaWJ1dGlvbjoNCg0KUChGZW1hbGUgfCBubyBkZWZhdWx0KSAgICAgUChNYWxlIHwgbm8gZGVmYXVsdCkgDQpQKEZlbWFsZSB8IGRlZmF1bHQpICAgICAgICBQKE1hbGUgfCBkZWZhdWx0KQ0KPGJyPg0KPGg0PjxiPlJ1biBtb2RlbCBvbiBmaXJzdCByb3cgb2YgdGVzdCBkYXRhOjwvYj48L2g0Pg0KYGBge3J9DQpwcmVkaWN0KG5iRGVtLCB0ZXN0WzEsXSkNCmBgYA0KPGJyPg0KPGg0PjxiPlJ1biBtb2RlbCBvbiBzZWNvbmQgcm93IG9mIHRlc3QgZGF0YTo8L2I+PC9oND4NCmBgYHtyfQ0KcHJlZGljdChuYkRlbSwgdGVzdFsyLF0pDQpgYGANClRoZSBwcmVkaWN0aW9ucyBhcmUgY29ycmVjdCBmb3IgYm90aCBhcyBwcm9iYWJpbGl0eSBvZiBkZWZhdWx0aW5nIGlzIGxvdyBmb3IgYm90aCBpbmRpdmlkdWFscyBmb3IgZWFjaCBjb25kaXRpb25hbCAoZXhjZXB0IGZvciB0aGUgc2V4IGNvbmRpdGlvbmFsIHdoaWNoIHNsaWdodGx5IGhpZ2hlciB0byBkZWZhdWx0IGZvciB3b21lbikuIExvb2tpbmcgYXQgdGhlIG92ZXJhbGwgcHJvYmFiaWxpdHkgb2Ygbm90IGRlZmF1bHRpbmcgaXMgc2tld2VkIHRvIG5vdCBkZWZhdWx0aW5nIGJ5IDc4JSwgc28gdGhlIHByZWRpY3Rpb25zIGFyZSBjb3JyZWN0Lg0KPGJyPg0KPGgzPjxiPkIuMi4gTmFpdmUgQmF5ZXMgdXNpbmcgRGVtb2dyYXBoaWMgVmFyaWFibGVzPC9iPjwvaDM+DQpgYGB7cn0NCm5iUGF5IDwtIG5haXZlQmF5ZXMoZGVmYXVsdC5wYXltZW50Lm5leHQubW9udGggfiBQQVlfMCArIFBBWV8yICsgUEFZXzMsIHRyYWluKQ0KbmJQYXkNCmBgYA0KRm9yICdQYXkgZHVseScgYW5kIHBheW1lbnRzIGRlbGF5IGZvciAxIG9yIDIgbW9udGgsIHRoZSBwcm9iYWJpbGl0eSBvZiBkZWZhdWx0aW5nIGlzIGhpZ2hlciB0aGFuIG5vdCBkZWZhdWx0aW5nLiBIb3dldmVyLCB0aGlzIHN3aXRjaGVzIGFmdGVyIDMgb3IgbW9yZSBtb250aHMgZm9yIGVhY2ggcGF5IHN0YXRlbWVudCAodmFyaWFibGVzKS4gSWYgdGhlIGNsaWVudCBoYXMgbW9yZSB0aGFuIDMgbW9udGhzIG9mIGxhdGUgcGF5bWVudHMsIHRoZXJlJ3MgYSBwcm9iYWJpbGl0eSBvZiBkZWZhdWx0aW5nIGFnYWluLg0KPGJyPg0KPGg0PjxiPlJ1biBtb2RlbCBvbiBmaXJzdCByb3cgb2YgdGVzdCBkYXRhOjwvYj48L2g0Pg0KYGBge3J9DQpwcmVkaWN0KG5iUGF5LCB0ZXN0WzEsXSkNCmBgYA0KPGg0PjxiPlJ1biBtb2RlbCBvbiBzZWNvbmQgcm93IG9mIHRlc3QgZGF0YTo8L2I+PC9oND4NCmBgYHtyfQ0KcHJlZGljdChuYlBheSwgdGVzdFsyLF0pDQpgYGANCjxiPkFuYWx5c2lzOjwvYj4NClRoZSBmaXJzdCBpbmRpdmlkdWFsIGhhZCAyIG1vbnRocyBvZiBkZWxheWVkIHBheW1lbnRzLCBob3dldmVyIGhlIG1hZGUgcGF5bWVudCB0byBhdm9pZCBiZWluZyBkZWxheWVkIDMgbW9udGhzLiBIZSBpcyB3YXMgbWFya2VkIGFzICdQYXkgZHVseScsIHdoaWNoIGNvcnJlc3BvbmRzIHdpdGggdGhlIHByZWRpY3Rpb24uDQoNClRoZSBzZWNvbmQgaW5kaXZpZHVhbHMgd2FzIGFsd2F5cyBvbi10aW1lIHdpdGggaGVyIHBheW1lbnRzIGFuZCBzaGUgY29udGludWVkIHRvIGJlIG9uLXRpbWUsIHdoaWNoIGNvcnJlc3BvbmRzIHdpdGggdGhlIHByZWRpY3Rpb24uDQoNCkFmdGVyIHJldmlld2luZyBib3RoIGluZGl2aWR1YWxzLCB0aGUgbW9kYWwgaGFzIHByb3ZlbiB0byBwcmVkaWN0ZWQgY29ycmVjdGx5LiANCjxicj4NCjxoMz48Yj5CLjMuIFNtb290aGVkIE5haXZlIEJheWVzIHVzaW5nIFBheW1lbnQgU3RhdHVzPC9iPjwvaDM+DQoNCmBgYHtyfQ0KbmJQYXkgPC0gbmFpdmVCYXllcyhkZWZhdWx0LnBheW1lbnQubmV4dC5tb250aCB+IFBBWV8wICsgUEFZXzIgKyBQQVlfMywgdHJhaW4sIGxhcGxhY2U9MS41KQ0KcHJlZGljdChuYlBheSwgdGVzdFsxLF0pDQpgYGANCmBgYHtyfQ0KbmJQYXkgPC0gbmFpdmVCYXllcyhkZWZhdWx0LnBheW1lbnQubmV4dC5tb250aCB+IFBBWV8wICsgUEFZXzIgKyBQQVlfMywgdHJhaW4sIGxhcGxhY2U9MS41KQ0KcHJlZGljdChuYlBheSwgdGVzdFsyLF0pDQpgYGANCkxhcGxhY2Ugc21vb3RoaW5nIGRpZCBub3QgcHJvZHVjZSBhbnkgZGlmZmVyZW5jZXMgd2l0aGluIHRoZSBwcmVkaWN0aW9ucyBmb3IgYm90aCBpbmRpdmlkdWFscy4NCjxoMj48Yj5DbGFzc2lmaWNhdGlvbiB3aXRoIERlY2lzaW9uIFRyZWU8L2I+PC9oMj4NCjxoMz48Yj5DLjEuIEJhc2ljIERlY2lzaW9uIFRyZWU8L2I+PC9oMz4NCmBgYHtyfQ0KbGlicmFyeSgicnBhcnQiKQ0KbGlicmFyeSgicnBhcnQucGxvdCIpDQpkdFBheSA8LSBycGFydChkZWZhdWx0LnBheW1lbnQubmV4dC5tb250aCB+IFBBWV8wICsgUEFZXzIgKyBQQVlfMywNCiAgICAgICAgICAgIG1ldGhvZD0iY2xhc3MiLA0KICAgICAgICAgICAgZGF0YT10cmFpbiwgcGFybXM9bGlzdChzcGxpdD0naW5mb3JtYXRpb24nKSwgDQogICAgICAgICAgICBtaW5zcGxpdD0yMCwgY3A9MC4wMikNCmBgYA0KYGBge3J9DQpycGFydC5wbG90KGR0UGF5LCB0eXBlPTQsIGV4dHJhPTEpDQpgYGANCjxiPkFuYWx5c2lzOjwvYj4NClRoZSBkZWNpc2lvbiB0cmVlIHZpc3VhbGl6YXRpb24gcHJvdmlkZXMgYSBkZWNpc2lvbiBzdHVtcCAob25lLWxldmVsIGRlY2lzaW9uIHRyZWUpLiBBIGRlY2lzaW9uIHN0dW1wIG1ha2VzIGEgcHJlZGljdGlvbiBiYXNlZCBvbiB0aGUgdmFsdWUgb2YganVzdCBhIHNpbmdsZSBpbnB1dCBmZWF0dXJlLiBUaGVyZSBpcyBhZGRpdGlvbmFsIHZhcmlhYmxlcyB0byBjb25zaWRlciB0aGF0IHdpbGwgaW1wcm92ZSB0aGlzIGRlY2lzaW9uIHRyZWUuDQo8YnI+DQo8aDQ+PGI+UnVuIG1vZGVsIG9uIGZpcnN0IHJvdyBvZiB0ZXN0IGRhdGE6PC9iPjwvaDQ+DQpgYGB7cn0NCnByZWRpY3QobmJQYXksIHRlc3RbMSxdKQ0KYGBgDQo8aDQ+PGI+UnVuIG1vZGVsIG9uIHNlY29uZCByb3cgb2YgdGVzdCBkYXRhOjwvYj48L2g0Pg0KYGBge3J9DQpwcmVkaWN0KG5iUGF5LCB0ZXN0WzIsXSkNCmBgYA0KPGI+QW5hbHlzaXM6PC9iPg0KQm90aCBpbmRpdmlkdWFscyBhcmUgdW5saWtlbHkgdG8gZGVmYXVsdC4gQWZ0ZXIgcmV2aWV3aW5nIHRoZSBhY3R1YWwgcmVzdWx0cywgdGhlIHByZWRpY3Rpb25zIGFyZSBjb3JyZWN0IHRoYXQgYm90aCBpbmRpdmlkdWFscyBkaWQgbm90IGRlZmF1bHQuIFRoZSBwcm9iYWJpbGl0eSBvZiBub3QgZGVmYXVsdGluZyBpcyA3OCUuIA0KPGJyPg0KPGgzPjxiPkMuMi4gRGVjaXNpb24gVHJlZSB3aXRoIGEgRGlmZmVyZW50IENvbXBsZXhpdHkgUGFyYW1ldGVyPC9iPjwvaDM+DQpgYGB7cn0NCmR0UGF5IDwtIHJwYXJ0KGRlZmF1bHQucGF5bWVudC5uZXh0Lm1vbnRoIH4gUEFZXzAgKyBQQVlfMiArIFBBWV8zLA0KICAgICAgICAgICAgbWV0aG9kPSJjbGFzcyIsDQogICAgICAgICAgICBkYXRhPXRyYWluLCBwYXJtcz1saXN0KHNwbGl0PSdpbmZvcm1hdGlvbicpLCANCiAgICAgICAgICAgIG1pbnNwbGl0PTIwLCBjcD0wLjAwMSkNCmBgYA0KDQpgYGB7cn0NCnJwYXJ0LnBsb3QoZHRQYXksIHR5cGU9NCwgZXh0cmE9MSkNCmBgYA0KPGI+QW5hbHlzaXM6PC9iPg0KVGhpcyBkZWNpc2lvbiB0cmVlIHNob3dzIGFsbCB0aGUgcG9zc2libGUgYnJhbmNoZXMgYW5kIGxpa2VsaWhvb2Qgb2YgcGF0aCBiYXNlZCB1cG9uIHRoZSBzYW1wbGUgc2l6ZSBvZiA1LDAwMCBpbmRpdmlkdWFscy4NCg0KVGhlIGxlZnQgc2lkZSByZXByZXNlbnQgaW5kaXZpZHVhbHMgdGhhdCBkaWQgbm90IGRlZmF1bHQ7IHJpZ2h0IHNpZGUgcmVwcmVzZW50IHRob3NlIHRoYXQgZGVmYXVsdGVkLg0KDQpGb2xsb3dpbmcgdG8gdGhlIHJvb3Qgb2YgdGhlIHRyZWUsIG9ubHkgMyw0NzYgKDcwJSkgb2YgdGhlIDUsMDAwIHBvcHVsYXRpb24gZGlkIG5vdCBkZWZhdWx0ZWQgY29uc2VjdXRpdmVseS4NCjxicj4NCjxoND48Yj5SdW4gbW9kZWwgb24gZmlyc3Qgcm93IG9mIHRlc3QgZGF0YTo8L2I+PC9oND4NCmBgYHtyfQ0KcHJlZGljdChuYlBheSwgdGVzdFsxLF0pDQpgYGANCjxoND48Yj5SdW4gbW9kZWwgb24gc2Vjb25kIHJvdyBvZiB0ZXN0IGRhdGE6PC9iPjwvaDQ+DQpgYGB7cn0NCnByZWRpY3QobmJQYXksIHRlc3RbMixdKQ0KYGBgDQo8Yj5BbmFseXNpczo8L2I+DQpCb3RoIGluZGl2aWR1YWxzIGFyZSB1bmxpa2VseSB0byBoYXZlIGRlZmF1bHQuDQoNCjxoMj48Yj5Db25jbHVzaW9uPC9iPjwvaDI+DQpCb3RoIG1vZGVscyBwcmVkaWN0aW9ucyB3ZXJlIGNvcnJlY3QuIEhvd2V2ZXIsIEkgZmVlbCB0aGVyZSB3YXMgYSBsYXJnZSBwb3B1bGF0aW9uIG9mICJObyBkZWZhdWx0cyIgd2hpY2ggbWFkZSBpdCBmZWVsIHRvbyBvYnZpb3VzIHRvbyBwcmVkaWN0Lg0KDQpBcyBmb3IgdGhlIHR3byBtb2RlbHMsIEkgcHJlZmVyIHRvIHV0aWxpemUgdGhlIE5haXZlIEJheWVzIG1vZGVsIGFzIHRoZSBkYXRhIHdhcyBlYXNpbHkgdG8gdW5kZXJzdGFuZCBhbmQgcmVhZCBmcm9tIHRoaXMgd29ya2Jvb2suIFdpdGggdGhlIERlY2lzaW9uIFRyZWUsIEkgaGFkIHRvIHBlcmZvcm0gc2V2ZXJhbCBtZXRob2RzIHRvIHpvb20gaW50byB0aGUgZGVjaXNpb24gdHJlZSB0byB1bmRlcnN0YW5kIGhvdyB0byB1c2UgaXQuDQoNCklzc3VlcyB3aXRoIHRoZSBkYXRhLiBTbywgdGhlcmUgaXMgbm8gY2xlYXIgdW5kZXJzdGFuZGluZyBvZiAtMiBhbmQgMC4gSSBoYXZlIHJlYWQgdGhlIHRocmVhZHMgb24gS2FnZ2xlLCBob3dldmVyIHRoZSBzdWdnZXN0ZWQgY29tbWVudGFyeSBkaWQgbm90IGFsaWduZWQgd2l0aCB0aGUgZGF0YSBwb2ludHMuIEFsc28sIGhvdyBjYW4gMTEwIGluZGl2aWR1YWxzIChmb3IgZXhhbXBsZTogNDYsIDI3NSwgMjI5OCwgMjUzOCwgMzE1MSkgd2l0aCBaRVJPIGZvciBhbGwgYmlsbGluZyBzdGF0ZW1lbnRzIGFuZCBtYXJrZWQgYXMgJ2RlZmF1bHQnIGZvciBuZXh0IG1vbnRoPyBJdCBkb2VzIG5vdCBtYWtlIHNlbnNlLiANCg==