0.0.1 Project Overview

  1. Based on the latest topics presented, bring a dataset of your choice and create a Decision Tree where you can solve a classification or regression problem and predict the outcome of a particular feature or detail of the data used.

  2. Switch variables to generate 2 decision trees and compare the results.

  3. Create a random forest for regression and analyze the results.

  4. Based on real cases where desicion trees went wrong, and ‘the bad & ugly’ aspects of decision trees (https://decizone.com/blog/the-good-the-bad-the-ugly-of-using-decision-trees), how can you change this perception when using the decision tree you created to solve a real problem?

0.0.2 Data Description

For this project, I would like to use the the Loan application data set from the Kaggle’s website. https://www.kaggle.com/datasets/angadgupta/loanapplicantdata The data set is structured with 13 variables and 768 observations.Among the 13 variables, the first variable Loan_ID is deleted due to the Loan_ID is not a influential factors among the data set. The variable - Loan_Status in the data is the target variable. The object of this project is to determine the factors that affect the Loan applications resulting either approved or rejected.

Loan ID - Unique Loan ID

Gender - Male/Female

Married - Applicant married(Y/N)

Dependents - Number of Dependents

Education - Application Education(Graduate/Under Graduate)

Self_Employed - Self-Employed(Y/N)

ApplicantIncome - Applicant Income

CoapplicantIncome - Applicant Income

LoanAmount - Loan Amount in Thousands

Loan_Amount_Term - Term of Loan in Months

Credit_History - Credit History(1 for Yes, 0 for No)

Property_Area - Property Areas (Semiurban, Urban, Other)

Loan_Status - Loan Status(True, False)

0.0.3 Libraries

library(readr)
library(dplyr)
## 
## 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(skimr)
library(rpart)
library(rpart.plot)
library(DMwR)
## Loading required package: lattice
## Loading required package: grid
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo
library(ggplot2)
library(randomForest)
## randomForest 4.7-1
## Type rfNews() to see new features/changes/bug fixes.
## 
## Attaching package: 'randomForest'
## The following object is masked from 'package:ggplot2':
## 
##     margin
## The following object is masked from 'package:dplyr':
## 
##     combine
library(caret)

0.0.4 Import and Subset Data

Loan<-read_csv("https://storage.googleapis.com/kagglesdsdata/datasets/1419016/2350351/LoanApplicantData.csv?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=gcp-kaggle-com%40kaggle-161607.iam.gserviceaccount.com%2F20220330%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20220330T005456Z&X-Goog-Expires=259199&X-Goog-SignedHeaders=host&X-Goog-Signature=01e66c108411ae0cdb4712ae4dc548d6fe1488d024b0e2c21cddce41fe945c8b48d335b56ddb323cecee6f1d10cf8843c47a19395cf61a4ca0d6304a8b5f7a03cee7f4d3a052aef3cb05a80f06c3ab7827d63a79c0b92d3179a946e78c10b27fc404897c8840838b50c78f93bf0f4ea0cc93ba459d8b8abe5b8702f81cc017a7cc28f21bfbd4792fb0e29a0a37354d237cc3b067e93f97a4008bb64abb993354bc15e620970e86273bc75093de351005319a1c2cdaac60ff35b71f6914b2c520b92dd1ccf5c8d0f77635a232a26ed7e60a383ef41de08f3501dba19bf8fe652dfe141ca5ee76d6b03fffe7f465df51ae7940b204222e03df508bbf0f50e8aa94", col_types = 'fffnffnnnnfff')

skim(Loan)
## Warning in sorted_count(x): Variable contains value(s) of "" that have been
## converted to "empty".

## Warning in sorted_count(x): Variable contains value(s) of "" that have been
## converted to "empty".

## Warning in sorted_count(x): Variable contains value(s) of "" that have been
## converted to "empty".

## Warning in sorted_count(x): Variable contains value(s) of "" that have been
## converted to "empty".
Data summary
Name Loan
Number of rows 614
Number of columns 13
_______________________
Column type frequency:
factor 8
numeric 5
________________________
Group variables None

Variable type: factor

skim_variable n_missing complete_rate ordered n_unique top_counts
Loan_ID 0 1 FALSE 614 LP0: 1, LP0: 1, LP0: 1, LP0: 1
Gender 0 1 FALSE 3 Mal: 489, Fem: 112, emp: 13
Married 0 1 FALSE 3 Yes: 398, No: 213, emp: 3
Education 0 1 FALSE 2 Gra: 480, Not: 134
Self_Employed 0 1 FALSE 3 No: 500, Yes: 82, emp: 32
Credit_History 0 1 FALSE 3 1: 475, 0: 89, emp: 50
Property_Area 0 1 FALSE 3 Sem: 233, Urb: 202, Rur: 179
Loan_Status 0 1 FALSE 2 Y: 422, N: 192

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
Dependents 15 0.98 0.76 1.02 0 0.0 0.0 2.00 3 ▇▂▁▂▁
ApplicantIncome 0 1.00 5403.46 6109.04 150 2877.5 3812.5 5795.00 81000 ▇▁▁▁▁
CoapplicantIncome 0 1.00 1621.25 2926.25 0 0.0 1188.5 2297.25 41667 ▇▁▁▁▁
LoanAmount 22 0.96 146.41 85.59 9 100.0 128.0 168.00 700 ▇▃▁▁▁
Loan_Amount_Term 14 0.98 342.00 65.12 12 360.0 360.0 360.00 480 ▁▁▁▇▁

The skim function present that there are missing values in columns of Dependents, LoanAmount and Loan_Amount Term the data set, and the target variable Loan_Status has 422 ‘Yes’ and 192 ‘No’, it is the signal of class imbalance, will handle the class imbalance problem with SMOTE function.

ggplot(Loan, aes(x = Loan_Status, y = ApplicantIncome))+geom_boxplot()+ylim(0,30000)
## Warning: Removed 7 rows containing non-finite values (stat_boxplot).

ggplot(Loan, aes(x = Loan_Status, y = CoapplicantIncome))+geom_boxplot()+ylim(0,10000)
## Warning: Removed 6 rows containing non-finite values (stat_boxplot).

According to the Box plot, In terms of income, the median income of Applicants who either approved or rejected seems no difference. However the median income for Co-applicants who get approved are higher than the one who get rejected.

ggplot(Loan, aes(x=Loan_Status, y= Education, color = Education)) +
  geom_bar(stat="identity")

ggplot(Loan, aes(x=Loan_Status, y= Property_Area, color = Property_Area)) +
  geom_bar(stat="identity")

The bar plot shows that the Higher portions of Graduate individuals has approved the loan compare to the one who got approved from Not Graduate group. Also, individuals who have property in Urban have the higher chance to get approved the loan, followed by Rural and Semiurban.

0.0.5 Feature Engineering

First, remove the unnecessary variable - Loan_ID. The decision tree model is very convenient to build, it handles the noise and missing values by itself. The only concern is the class imbalance problem, as the skim() function shows, there are Y: 422, N: 192 in the Loan_Status variable. Therefore, use SMOTE() to fix the class imbalance problem.

Loan<-Loan[,-1]
round(prop.table(table(select(Loan, Loan_Status),exclude = NULL)),4)*100
## 
##     Y     N 
## 68.73 31.27
set.seed(1234)
Loan<-SMOTE(factor(Loan_Status)~., data.frame(Loan),perc.over = 100,perc.under = 200)
round(prop.table(table(select(Loan, Loan_Status),exclude = NULL)),4)*100
## 
##  Y  N 
## 50 50

Now, the proportion of Y and N is 50,50.

Split the data set in to 75/25 for training set and test set.

set.seed(1234)
split <- sample(nrow(Loan), round(nrow(Loan)*0.75), replace = F) 
train <- Loan[split,]
test <- Loan[-split,]
round(prop.table(table(select(train, Loan_Status),exclude = NULL)),4)*100
## 
##     Y     N 
## 49.83 50.17
round(prop.table(table(select(test, Loan_Status),exclude = NULL)),4)*100
## 
##     Y     N 
## 50.52 49.48

With 75/25 split, there are 460 observations in the training data, 154 observations in the test data.

0.0.6 Decision Tree Modeling

mod<-rpart(Loan_Status~.,method = 'class' ,data = Loan)
rpart.plot(mod)

According to the rpart.plot function, the key variable or the root node is the Credit_History. If there is not credit history, the decision directly go to no approval. The second key variable is Co-applicants’ income, and followed by the property area located at urban. Since the Credit_History and Co-applicants’ income are the key facotrs, I’m going to build another tree model without these two variables to compare the result.

loan<-Loan[,-c(7,10)]
mod2<-rpart(Loan_Status~.,method = 'class' ,data = loan)
rpart.plot(mod2)

Without the credit history and Co-Applicants income info, the Loan_Amount became the root node, and the key variables followed by marital status and applicants’ income and property locations.

0.0.7 Decision Tree Model Evaluation

mod_pred <- predict(mod, test, type = 'class')
mod_table<-table(test$Loan_Status, mod_pred)
mod_table
##    mod_pred
##      Y  N
##   Y 89  8
##   N 33 62
mod_accuracy<- sum(diag(mod_table))/nrow(test)
print(paste('The first tree model accuracy is',mod_accuracy))
## [1] "The first tree model accuracy is 0.786458333333333"

The first decision tree model presents 78.65% of the accuracy against the test data. Let’s try the second decision tree model to see how does the accuracy change.

mod2_pred <- predict(mod2, test, type = 'class')
mod2_table<-table(test$Loan_Status, mod2_pred)
mod2_table
##    mod2_pred
##      Y  N
##   Y 80 17
##   N 37 58
mod2_accuracy<- sum(diag(mod2_table))/nrow(test)
print(paste('The second tree model accuracy is',mod2_accuracy))
## [1] "The second tree model accuracy is 0.71875"

The model accuracy shows that the model included the “Credit_History” and “Co-ApplicantIncome” has higher accuracy about 7% against the test data.

0.0.8 Random Forest Modeling and Evaluation

forest_mod <- randomForest(Loan_Status ~ .,ntree = 2000, importance = T,data = train, na.action = na.roughfix)

forest_pred <- predict(forest_mod, test)

confusionMatrix(forest_pred, test$Loan_Status)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  Y  N
##          Y 74 27
##          N 10 61
##                                           
##                Accuracy : 0.7849          
##                  95% CI : (0.7159, 0.8438)
##     No Information Rate : 0.5116          
##     P-Value [Acc > NIR] : 1.25e-13        
##                                           
##                   Kappa : 0.5715          
##                                           
##  Mcnemar's Test P-Value : 0.008529        
##                                           
##             Sensitivity : 0.8810          
##             Specificity : 0.6932          
##          Pos Pred Value : 0.7327          
##          Neg Pred Value : 0.8592          
##              Prevalence : 0.4884          
##          Detection Rate : 0.4302          
##    Detection Prevalence : 0.5872          
##       Balanced Accuracy : 0.7871          
##                                           
##        'Positive' Class : Y               
## 

The condition na.action = na.roughfix allows the random forest model replacing the NA to median values. In this case, the random forest performs the best with accuracy of 79%, and it is slightly better than the first decision tree model about 1 %.

Base on the article, the decision tree can be very complicated when the control variables in change, Therefore, when creating and maintain the decision tree model for providing the best decision to the Loan approval, need to consider include the all the variables that affect the Loan approval at the beginning when making the decision to avoid any change that may cause failing in decision later.

LS0tCnRpdGxlOiAiREFUQTYyMl9IVzJfQ2h1bmppZU5hbiIKYXV0aG9yOiBDaHVuamllIE5hbgpkYXRlOiAiMDMvMzAvMjAyMiIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgaGlnaGxpZ2h0OiBweWdtZW50cwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiBmbGF0bHkKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogIHBkZl9kb2N1bWVudDoKICAgIHRvYzogeWVzCi0tLQoKIyMjIFByb2plY3QgT3ZlcnZpZXcKCjEuIEJhc2VkIG9uIHRoZSBsYXRlc3QgdG9waWNzIHByZXNlbnRlZCwgYnJpbmcgYSBkYXRhc2V0IG9mIHlvdXIgY2hvaWNlIGFuZCBjcmVhdGUgYSBEZWNpc2lvbiBUcmVlIHdoZXJlIHlvdSBjYW4gc29sdmUgYSBjbGFzc2lmaWNhdGlvbiBvciByZWdyZXNzaW9uIHByb2JsZW0gYW5kIHByZWRpY3QgdGhlIG91dGNvbWUgb2YgYSBwYXJ0aWN1bGFyIGZlYXR1cmUgb3IgZGV0YWlsIG9mIHRoZSBkYXRhIHVzZWQuCgoyLiBTd2l0Y2ggdmFyaWFibGVzIHRvIGdlbmVyYXRlIDIgZGVjaXNpb24gdHJlZXMgYW5kIGNvbXBhcmUgdGhlIHJlc3VsdHMuIAoKMy4gQ3JlYXRlIGEgcmFuZG9tIGZvcmVzdCBmb3IgcmVncmVzc2lvbiBhbmQgYW5hbHl6ZSB0aGUgcmVzdWx0cy4KCjQuIEJhc2VkIG9uIHJlYWwgY2FzZXMgd2hlcmUgZGVzaWNpb24gdHJlZXMgd2VudCB3cm9uZywgYW5kICd0aGUgYmFkICYgdWdseScgYXNwZWN0cyBvZiBkZWNpc2lvbiB0cmVlcyAoaHR0cHM6Ly9kZWNpem9uZS5jb20vYmxvZy90aGUtZ29vZC10aGUtYmFkLXRoZS11Z2x5LW9mLXVzaW5nLWRlY2lzaW9uLXRyZWVzKSwgaG93IGNhbiB5b3UgY2hhbmdlIHRoaXMgcGVyY2VwdGlvbiB3aGVuIHVzaW5nIHRoZSBkZWNpc2lvbiB0cmVlIHlvdSBjcmVhdGVkIHRvIHNvbHZlIGEgcmVhbCBwcm9ibGVtPwoKCgojIyMgRGF0YSBEZXNjcmlwdGlvbgoKRm9yIHRoaXMgcHJvamVjdCwgSSB3b3VsZCBsaWtlIHRvIHVzZSB0aGUgdGhlIExvYW4gYXBwbGljYXRpb24gZGF0YSBzZXQgZnJvbSB0aGUgS2FnZ2xlJ3Mgd2Vic2l0ZS4KaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9kYXRhc2V0cy9hbmdhZGd1cHRhL2xvYW5hcHBsaWNhbnRkYXRhClRoZSBkYXRhIHNldCBpcyBzdHJ1Y3R1cmVkIHdpdGggMTMgdmFyaWFibGVzIGFuZCA3Njggb2JzZXJ2YXRpb25zLkFtb25nIHRoZSAxMyB2YXJpYWJsZXMsIHRoZSBmaXJzdCB2YXJpYWJsZSBMb2FuX0lEIGlzIGRlbGV0ZWQgZHVlIHRvIHRoZSBMb2FuX0lEIGlzIG5vdCBhIGluZmx1ZW50aWFsIGZhY3RvcnMgYW1vbmcgdGhlIGRhdGEgc2V0LiBUaGUgdmFyaWFibGUgLSBMb2FuX1N0YXR1cyBpbiB0aGUgZGF0YSBpcyB0aGUgdGFyZ2V0IHZhcmlhYmxlLiBUaGUgb2JqZWN0IG9mIHRoaXMgcHJvamVjdCBpcyB0byBkZXRlcm1pbmUgdGhlIGZhY3RvcnMgdGhhdCBhZmZlY3QgdGhlIExvYW4gYXBwbGljYXRpb25zIHJlc3VsdGluZyBlaXRoZXIgYXBwcm92ZWQgb3IgcmVqZWN0ZWQuCgoKTG9hbiBJRCAtIFVuaXF1ZSBMb2FuIElECgpHZW5kZXIgLSBNYWxlL0ZlbWFsZQoKTWFycmllZCAtIEFwcGxpY2FudCBtYXJyaWVkKFkvTikKCkRlcGVuZGVudHMgLSBOdW1iZXIgb2YgRGVwZW5kZW50cwoKRWR1Y2F0aW9uIC0gQXBwbGljYXRpb24gRWR1Y2F0aW9uKEdyYWR1YXRlL1VuZGVyIEdyYWR1YXRlKQoKU2VsZl9FbXBsb3llZCAtIFNlbGYtRW1wbG95ZWQoWS9OKQoKQXBwbGljYW50SW5jb21lIC0gQXBwbGljYW50IEluY29tZQoKQ29hcHBsaWNhbnRJbmNvbWUgLSBBcHBsaWNhbnQgSW5jb21lCgpMb2FuQW1vdW50IC0gTG9hbiBBbW91bnQgaW4gVGhvdXNhbmRzCgpMb2FuX0Ftb3VudF9UZXJtIC0gVGVybSBvZiBMb2FuIGluIE1vbnRocwoKQ3JlZGl0X0hpc3RvcnkgLSBDcmVkaXQgSGlzdG9yeSgxIGZvciBZZXMsIDAgZm9yIE5vKQoKUHJvcGVydHlfQXJlYSAtIFByb3BlcnR5IEFyZWFzIChTZW1pdXJiYW4sIFVyYmFuLCBPdGhlcikKCkxvYW5fU3RhdHVzIC0gTG9hbiBTdGF0dXMoVHJ1ZSwgRmFsc2UpCgoKCiMjIyBMaWJyYXJpZXMKYGBge3J9CmxpYnJhcnkocmVhZHIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoc2tpbXIpCmxpYnJhcnkocnBhcnQpCmxpYnJhcnkocnBhcnQucGxvdCkKbGlicmFyeShETXdSKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocmFuZG9tRm9yZXN0KQpsaWJyYXJ5KGNhcmV0KQpgYGAKCgojIyMgSW1wb3J0IGFuZCBTdWJzZXQgRGF0YQoKYGBge3J9CkxvYW48LXJlYWRfY3N2KCJodHRwczovL3N0b3JhZ2UuZ29vZ2xlYXBpcy5jb20va2FnZ2xlc2RzZGF0YS9kYXRhc2V0cy8xNDE5MDE2LzIzNTAzNTEvTG9hbkFwcGxpY2FudERhdGEuY3N2P1gtR29vZy1BbGdvcml0aG09R09PRzQtUlNBLVNIQTI1NiZYLUdvb2ctQ3JlZGVudGlhbD1nY3Ata2FnZ2xlLWNvbSU0MGthZ2dsZS0xNjE2MDcuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20lMkYyMDIyMDMzMCUyRmF1dG8lMkZzdG9yYWdlJTJGZ29vZzRfcmVxdWVzdCZYLUdvb2ctRGF0ZT0yMDIyMDMzMFQwMDU0NTZaJlgtR29vZy1FeHBpcmVzPTI1OTE5OSZYLUdvb2ctU2lnbmVkSGVhZGVycz1ob3N0JlgtR29vZy1TaWduYXR1cmU9MDFlNjZjMTA4NDExYWUwY2RiNDcxMmFlNGRjNTQ4ZDZmZTE0ODhkMDI0YjBlMmMyMWNkZGNlNDFmZTk0NWM4YjQ4ZDMzNWI1NmRkYjMyM2NlY2VlNmYxZDEwY2Y4ODQzYzQ3YTE5Mzk1Y2Y2MWE0Y2EwZDYzMDRhOGI1ZjdhMDNjZWU3ZjRkM2EwNTJhZWYzY2IwNWE4MGYwNmMzYWI3ODI3ZDYzYTc5YzBiOTJkMzE3OWE5NDZlNzhjMTBiMjdmYzQwNDg5N2M4ODQwODM4YjUwYzc4ZjkzYmYwZjRlYTBjYzkzYmE0NTlkOGI4YWJlNWI4NzAyZjgxY2MwMTdhN2NjMjhmMjFiZmJkNDc5MmZiMGUyOWEwYTM3MzU0ZDIzN2NjM2IwNjdlOTNmOTdhNDAwOGJiNjRhYmI5OTMzNTRiYzE1ZTYyMDk3MGU4NjI3M2JjNzUwOTNkZTM1MTAwNTMxOWExYzJjZGFhYzYwZmYzNWI3MWY2OTE0YjJjNTIwYjkyZGQxY2NmNWM4ZDBmNzc2MzVhMjMyYTI2ZWQ3ZTYwYTM4M2VmNDFkZTA4ZjM1MDFkYmExOWJmOGZlNjUyZGZlMTQxY2E1ZWU3NmQ2YjAzZmZmZTdmNDY1ZGY1MWFlNzk0MGIyMDQyMjJlMDNkZjUwOGJiZjBmNTBlOGFhOTQiLCBjb2xfdHlwZXMgPSAnZmZmbmZmbm5ubmZmZicpCgpza2ltKExvYW4pCmBgYApUaGUgc2tpbSBmdW5jdGlvbiBwcmVzZW50IHRoYXQgdGhlcmUgYXJlIG1pc3NpbmcgdmFsdWVzIGluIGNvbHVtbnMgb2YgRGVwZW5kZW50cywgTG9hbkFtb3VudCBhbmQgTG9hbl9BbW91bnQgVGVybSB0aGUgZGF0YSBzZXQsIGFuZCB0aGUgdGFyZ2V0IHZhcmlhYmxlIExvYW5fU3RhdHVzIGhhcyA0MjIgJ1llcycgYW5kIDE5MiAnTm8nLCBpdCBpcyB0aGUgc2lnbmFsIG9mIGNsYXNzIGltYmFsYW5jZSwgd2lsbCBoYW5kbGUgdGhlIGNsYXNzIGltYmFsYW5jZSBwcm9ibGVtIHdpdGggU01PVEUgZnVuY3Rpb24uCgoKYGBge3J9CmdncGxvdChMb2FuLCBhZXMoeCA9IExvYW5fU3RhdHVzLCB5ID0gQXBwbGljYW50SW5jb21lKSkrZ2VvbV9ib3hwbG90KCkreWxpbSgwLDMwMDAwKQpnZ3Bsb3QoTG9hbiwgYWVzKHggPSBMb2FuX1N0YXR1cywgeSA9IENvYXBwbGljYW50SW5jb21lKSkrZ2VvbV9ib3hwbG90KCkreWxpbSgwLDEwMDAwKQoKYGBgCkFjY29yZGluZyB0byB0aGUgQm94IHBsb3QsIEluIHRlcm1zIG9mIGluY29tZSwgdGhlIG1lZGlhbiBpbmNvbWUgb2YgQXBwbGljYW50cyB3aG8gZWl0aGVyIGFwcHJvdmVkIG9yIHJlamVjdGVkIHNlZW1zIG5vIGRpZmZlcmVuY2UuIEhvd2V2ZXIgdGhlIG1lZGlhbiBpbmNvbWUgZm9yIENvLWFwcGxpY2FudHMgIHdobyBnZXQgYXBwcm92ZWQgYXJlIGhpZ2hlciB0aGFuIHRoZSBvbmUgd2hvIGdldCByZWplY3RlZC4KCmBgYHtyfQpnZ3Bsb3QoTG9hbiwgYWVzKHg9TG9hbl9TdGF0dXMsIHk9IEVkdWNhdGlvbiwgY29sb3IgPSBFZHVjYXRpb24pKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKQoKZ2dwbG90KExvYW4sIGFlcyh4PUxvYW5fU3RhdHVzLCB5PSBQcm9wZXJ0eV9BcmVhLCBjb2xvciA9IFByb3BlcnR5X0FyZWEpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKQoKYGBgClRoZSBiYXIgcGxvdCBzaG93cyB0aGF0IHRoZSBIaWdoZXIgcG9ydGlvbnMgb2YgR3JhZHVhdGUgaW5kaXZpZHVhbHMgaGFzIGFwcHJvdmVkIHRoZSBsb2FuIGNvbXBhcmUgdG8gdGhlIG9uZSB3aG8gZ290IGFwcHJvdmVkIGZyb20gTm90IEdyYWR1YXRlIGdyb3VwLiBBbHNvLCBpbmRpdmlkdWFscyB3aG8gaGF2ZSBwcm9wZXJ0eSBpbiBVcmJhbiBoYXZlIHRoZSBoaWdoZXIgY2hhbmNlIHRvIGdldCBhcHByb3ZlZCB0aGUgbG9hbiwgZm9sbG93ZWQgYnkgUnVyYWwgYW5kIFNlbWl1cmJhbi4gCgoKIyMjIEZlYXR1cmUgRW5naW5lZXJpbmcKCkZpcnN0LCByZW1vdmUgdGhlIHVubmVjZXNzYXJ5IHZhcmlhYmxlIC0gTG9hbl9JRC4KVGhlIGRlY2lzaW9uIHRyZWUgbW9kZWwgaXMgdmVyeSBjb252ZW5pZW50IHRvIGJ1aWxkLCBpdCBoYW5kbGVzIHRoZSBub2lzZSBhbmQgbWlzc2luZyB2YWx1ZXMgYnkgaXRzZWxmLiAgVGhlIG9ubHkgY29uY2VybiBpcyB0aGUgY2xhc3MgaW1iYWxhbmNlIHByb2JsZW0sIGFzIHRoZSBza2ltKCkgZnVuY3Rpb24gc2hvd3MsIHRoZXJlIGFyZSBZOiA0MjIsIE46IDE5MiBpbiB0aGUgTG9hbl9TdGF0dXMgdmFyaWFibGUuIFRoZXJlZm9yZSwgdXNlIFNNT1RFKCkgdG8gZml4IHRoZSBjbGFzcyBpbWJhbGFuY2UgcHJvYmxlbS4KCgpgYGB7cn0KTG9hbjwtTG9hblssLTFdCnJvdW5kKHByb3AudGFibGUodGFibGUoc2VsZWN0KExvYW4sIExvYW5fU3RhdHVzKSxleGNsdWRlID0gTlVMTCkpLDQpKjEwMApzZXQuc2VlZCgxMjM0KQpMb2FuPC1TTU9URShmYWN0b3IoTG9hbl9TdGF0dXMpfi4sIGRhdGEuZnJhbWUoTG9hbikscGVyYy5vdmVyID0gMTAwLHBlcmMudW5kZXIgPSAyMDApCnJvdW5kKHByb3AudGFibGUodGFibGUoc2VsZWN0KExvYW4sIExvYW5fU3RhdHVzKSxleGNsdWRlID0gTlVMTCkpLDQpKjEwMAoKYGBgCk5vdywgdGhlIHByb3BvcnRpb24gb2YgWSBhbmQgTiBpcyA1MCw1MC4KCgpTcGxpdCB0aGUgZGF0YSBzZXQgaW4gdG8gNzUvMjUgZm9yIHRyYWluaW5nIHNldCBhbmQgdGVzdCBzZXQuCmBgYHtyfQpzZXQuc2VlZCgxMjM0KQpzcGxpdCA8LSBzYW1wbGUobnJvdyhMb2FuKSwgcm91bmQobnJvdyhMb2FuKSowLjc1KSwgcmVwbGFjZSA9IEYpIAp0cmFpbiA8LSBMb2FuW3NwbGl0LF0KdGVzdCA8LSBMb2FuWy1zcGxpdCxdCnJvdW5kKHByb3AudGFibGUodGFibGUoc2VsZWN0KHRyYWluLCBMb2FuX1N0YXR1cyksZXhjbHVkZSA9IE5VTEwpKSw0KSoxMDAKcm91bmQocHJvcC50YWJsZSh0YWJsZShzZWxlY3QodGVzdCwgTG9hbl9TdGF0dXMpLGV4Y2x1ZGUgPSBOVUxMKSksNCkqMTAwCmBgYAoKCldpdGggNzUvMjUgc3BsaXQsIHRoZXJlIGFyZSA0NjAgb2JzZXJ2YXRpb25zIGluIHRoZSB0cmFpbmluZyBkYXRhLCAxNTQgb2JzZXJ2YXRpb25zIGluIHRoZSB0ZXN0IGRhdGEuCgoKCiMjIyBEZWNpc2lvbiBUcmVlIE1vZGVsaW5nCgpgYGB7cn0KbW9kPC1ycGFydChMb2FuX1N0YXR1c34uLG1ldGhvZCA9ICdjbGFzcycgLGRhdGEgPSBMb2FuKQpycGFydC5wbG90KG1vZCkKYGBgCkFjY29yZGluZyB0byB0aGUgcnBhcnQucGxvdCBmdW5jdGlvbiwgdGhlIGtleSB2YXJpYWJsZSBvciB0aGUgcm9vdCBub2RlIGlzIHRoZSBDcmVkaXRfSGlzdG9yeS4gSWYgdGhlcmUgaXMgbm90IGNyZWRpdCBoaXN0b3J5LCB0aGUgZGVjaXNpb24gZGlyZWN0bHkgZ28gdG8gbm8gYXBwcm92YWwuIFRoZSBzZWNvbmQga2V5IHZhcmlhYmxlIGlzIENvLWFwcGxpY2FudHMnIGluY29tZSwgYW5kIGZvbGxvd2VkIGJ5IHRoZSBwcm9wZXJ0eSBhcmVhIGxvY2F0ZWQgYXQgdXJiYW4uIFNpbmNlIHRoZSBDcmVkaXRfSGlzdG9yeSBhbmQgQ28tYXBwbGljYW50cycgaW5jb21lIGFyZSB0aGUga2V5IGZhY290cnMsIEknbSBnb2luZyB0byBidWlsZCBhbm90aGVyIHRyZWUgbW9kZWwgd2l0aG91dCB0aGVzZSB0d28gdmFyaWFibGVzIHRvIGNvbXBhcmUgdGhlIHJlc3VsdC4KCmBgYHtyfQpsb2FuPC1Mb2FuWywtYyg3LDEwKV0KbW9kMjwtcnBhcnQoTG9hbl9TdGF0dXN+LixtZXRob2QgPSAnY2xhc3MnICxkYXRhID0gbG9hbikKcnBhcnQucGxvdChtb2QyKQpgYGAKV2l0aG91dCB0aGUgY3JlZGl0IGhpc3RvcnkgYW5kIENvLUFwcGxpY2FudHMgaW5jb21lIGluZm8sIHRoZSBMb2FuX0Ftb3VudCBiZWNhbWUgdGhlIHJvb3Qgbm9kZSwgYW5kIHRoZSBrZXkgdmFyaWFibGVzIGZvbGxvd2VkIGJ5IG1hcml0YWwgc3RhdHVzIGFuZCBhcHBsaWNhbnRzJyBpbmNvbWUgYW5kIHByb3BlcnR5IGxvY2F0aW9ucy4KCgojIyMgRGVjaXNpb24gVHJlZSBNb2RlbCBFdmFsdWF0aW9uCgpgYGB7cn0KbW9kX3ByZWQgPC0gcHJlZGljdChtb2QsIHRlc3QsIHR5cGUgPSAnY2xhc3MnKQptb2RfdGFibGU8LXRhYmxlKHRlc3QkTG9hbl9TdGF0dXMsIG1vZF9wcmVkKQptb2RfdGFibGUKbW9kX2FjY3VyYWN5PC0gc3VtKGRpYWcobW9kX3RhYmxlKSkvbnJvdyh0ZXN0KQpwcmludChwYXN0ZSgnVGhlIGZpcnN0IHRyZWUgbW9kZWwgYWNjdXJhY3kgaXMnLG1vZF9hY2N1cmFjeSkpCmBgYAoKVGhlIGZpcnN0IGRlY2lzaW9uIHRyZWUgbW9kZWwgcHJlc2VudHMgNzguNjUlIG9mIHRoZSBhY2N1cmFjeSBhZ2FpbnN0IHRoZSB0ZXN0IGRhdGEuIExldCdzIHRyeSB0aGUgc2Vjb25kIGRlY2lzaW9uIHRyZWUgbW9kZWwgdG8gc2VlIGhvdyBkb2VzIHRoZSBhY2N1cmFjeSBjaGFuZ2UuCgpgYGB7cn0KbW9kMl9wcmVkIDwtIHByZWRpY3QobW9kMiwgdGVzdCwgdHlwZSA9ICdjbGFzcycpCm1vZDJfdGFibGU8LXRhYmxlKHRlc3QkTG9hbl9TdGF0dXMsIG1vZDJfcHJlZCkKbW9kMl90YWJsZQptb2QyX2FjY3VyYWN5PC0gc3VtKGRpYWcobW9kMl90YWJsZSkpL25yb3codGVzdCkKcHJpbnQocGFzdGUoJ1RoZSBzZWNvbmQgdHJlZSBtb2RlbCBhY2N1cmFjeSBpcycsbW9kMl9hY2N1cmFjeSkpCmBgYAoKVGhlIG1vZGVsIGFjY3VyYWN5IHNob3dzIHRoYXQgdGhlIG1vZGVsIGluY2x1ZGVkIHRoZSAiQ3JlZGl0X0hpc3RvcnkiIGFuZCAiQ28tQXBwbGljYW50SW5jb21lIiBoYXMgaGlnaGVyIGFjY3VyYWN5IGFib3V0IDclIGFnYWluc3QgdGhlIHRlc3QgZGF0YS4gCgoKIyMjIFJhbmRvbSBGb3Jlc3QgTW9kZWxpbmcgYW5kIEV2YWx1YXRpb24KCmBgYHtyfQpmb3Jlc3RfbW9kIDwtIHJhbmRvbUZvcmVzdChMb2FuX1N0YXR1cyB+IC4sbnRyZWUgPSAyMDAwLCBpbXBvcnRhbmNlID0gVCxkYXRhID0gdHJhaW4sIG5hLmFjdGlvbiA9IG5hLnJvdWdoZml4KQoKZm9yZXN0X3ByZWQgPC0gcHJlZGljdChmb3Jlc3RfbW9kLCB0ZXN0KQoKY29uZnVzaW9uTWF0cml4KGZvcmVzdF9wcmVkLCB0ZXN0JExvYW5fU3RhdHVzKQoKCmBgYCAKVGhlIGNvbmRpdGlvbiBuYS5hY3Rpb24gPSBuYS5yb3VnaGZpeCBhbGxvd3MgdGhlIHJhbmRvbSBmb3Jlc3QgbW9kZWwgcmVwbGFjaW5nIHRoZSBOQSB0byBtZWRpYW4gdmFsdWVzLgpJbiB0aGlzIGNhc2UsIHRoZSByYW5kb20gZm9yZXN0IHBlcmZvcm1zIHRoZSBiZXN0IHdpdGggYWNjdXJhY3kgb2YgNzklLCBhbmQgaXQgaXMgc2xpZ2h0bHkgYmV0dGVyIHRoYW4gdGhlIGZpcnN0IGRlY2lzaW9uIHRyZWUgbW9kZWwgYWJvdXQgMSAlLiAKCkJhc2Ugb24gdGhlIGFydGljbGUsIHRoZSBkZWNpc2lvbiB0cmVlIGNhbiBiZSB2ZXJ5IGNvbXBsaWNhdGVkIHdoZW4gdGhlIGNvbnRyb2wgdmFyaWFibGVzIGluIGNoYW5nZSwgVGhlcmVmb3JlLCB3aGVuIGNyZWF0aW5nIGFuZCBtYWludGFpbiB0aGUgZGVjaXNpb24gdHJlZSBtb2RlbCBmb3IgcHJvdmlkaW5nIHRoZSBiZXN0IGRlY2lzaW9uIHRvIHRoZSBMb2FuIGFwcHJvdmFsLCBuZWVkIHRvIGNvbnNpZGVyIGluY2x1ZGUgdGhlIGFsbCB0aGUgdmFyaWFibGVzIHRoYXQgYWZmZWN0IHRoZSBMb2FuIGFwcHJvdmFsIGF0IHRoZSBiZWdpbm5pbmcgd2hlbiBtYWtpbmcgdGhlIGRlY2lzaW9uIHRvIGF2b2lkIGFueSBjaGFuZ2UgdGhhdCBtYXkgY2F1c2UgZmFpbGluZyBpbiBkZWNpc2lvbiBsYXRlci4KCgoKCgoK