library(arules)
library(dplyr)
library(ggplot2)
library(plotly)
library(rpart)
library(rpart.plot)
library(caret)
Loading required package: lattice

Dataset

From Pima Indians Diabetes Database.

Meaning of each rows are

raw <- read.csv("diabetes.csv", header = T)
raw

Research questions

What are predictive and protective factors for the outcomes (of whether having diabtes or not)?

Duplicate detection

Data exploration

We should see first, which columns and pair of columns might have clustering.

Histogram graph is also available on Kaggle.

ggplotly(ggplot(raw) + geom_histogram(aes(Glucose)))
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Single count/continuous variables here usually have Bell curve distribution, with zero values, probably in place of NA’s. Such columns include

I also plotted using scatter plots, hoping more associations will be shown. (Yes, I tried to perform all 9C2.)

Therefore, as for now, I see no real clustering, but there need to be cleaning for zero values.

Data cleaning

First, I check how many rows have complete data.

raw_fixed <- raw %>%
  filter(Insulin !=0 & Glucose != 0 & BloodPressure != 0 & SkinThickness != 0)
raw_fixed

And take a peak at the data.

ggplotly(ggplot(raw_fixed) + geom_histogram(aes(Outcome)))
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Association mining

data <- discretizeDF(raw_fixed)
The calculated breaks are: 0, 0, 0, 1
  Only unique breaks are used reducing the number of intervals. Look at ? discretize for details.
data

We can see that outcome is broken; therefore, we should fix it.

data$Outcome <- as.factor(raw_fixed$Outcome)
data
ts <- as(data, "transactions")

Test out Apriori rules. We aim for ones with high confidence. High or low support has different meanings.

as(
  apriori(ts,
          parameter = list(sup = 0.1, conf = 0.9, target="rules"),
          appearance = list(rhs = c("Outcome=0", "Outcome=1"))
          ),
  "data.frame"
) %>% arrange(desc(confidence))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 39 

set item appearances ...[2 item(s)] done [0.00s].
set transactions ...[26 item(s), 393 transaction(s)] done [0.00s].
sorting and recoding items ... [26 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 3 4 done [0.00s].
writing ... [41 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].

Lower glucose, pregnancies, BMI, and insulin are important protective factors. However, no pregnancies doesn’t predict better outcome.

Therefore, I plot the data just to be sure.

ggplotly(ggplot(raw_fixed %>% filter(Outcome == 0)) + geom_histogram(aes(Pregnancies)))
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
ggplotly(ggplot(raw_fixed %>% filter(Outcome == 1)) + geom_histogram(aes(Pregnancies)))
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

What about outcome = 1?

as(
  apriori(ts,
          parameter = list(sup = 0.1, conf = 0.6, target="rules"),
          appearance = list(rhs = c("Outcome=1"))
          ),
  "data.frame"
) %>% arrange(desc(support))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 39 

set item appearances ...[1 item(s)] done [0.00s].
set transactions ...[26 item(s), 393 transaction(s)] done [0.00s].
sorting and recoding items ... [26 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 3 4 done [0.00s].
writing ... [6 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].

It is harder to find predictive factors for diabetes with confidence. I have to decrease confidence to at least 0.7 to get some results.

Important predictive factors are higher blood glucose level, insulin, more age, and multiparity.

We want to see lower supports as well.

as(
  apriori(ts,
          parameter = list(sup = 0.03, conf = 0.7, maxlen = 4, target="rules"),
          appearance = list(rhs = c("Outcome=0", "Outcome=1"))
          ),
  "data.frame"
) %>% filter(support < 0.1) %>% arrange(desc(lift))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 11 

set item appearances ...[2 item(s)] done [0.00s].
set transactions ...[26 item(s), 393 transaction(s)] done [0.00s].
sorting and recoding items ... [26 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 3 4
Mining stopped (maxlen reached). Only patterns up to a length of 4 returned!
 done [0.00s].
writing ... [517 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].

Rarer ones with decent counts still include higher blood glucose level, insulin, more age, and multiparity, which are predictive of poorer outcomes. An additional factor here is diabetes pedigree function.

What’s the distribution of diabetes pedigree function, BTW?

ggplotly(ggplot(raw_fixed) + geom_histogram(aes(DiabetesPedigreeFunction)))
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Therefore, more diabetes pedigree function values are predictive of diabetes, but the data supporting this is rarer.

As Outcome=0 is undermined, I have to run this again for Outcome=0.

as(
  apriori(ts,
          parameter = list(sup = 0.03, conf = 0.9, maxlen = 4, target="rules"),
          appearance = list(rhs = c("Outcome=0"))
          ),
  "data.frame"
) %>% filter(support < 0.1) %>% arrange(desc(lift))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 11 

set item appearances ...[1 item(s)] done [0.00s].
set transactions ...[26 item(s), 393 transaction(s)] done [0.00s].
sorting and recoding items ... [26 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 3 4
Mining stopped (maxlen reached). Only patterns up to a length of 4 returned!
 done [0.00s].
writing ... [193 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].

Younger ages less than 25, lower insulin, lower skin thickness, and lower glucose levels less than 105, are protective factors. But not much so for DiabetesPedigreeFunction < 0.325.

ggplotly(ggplot(raw_fixed) + geom_histogram(aes(SkinThickness)))
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Decision tree analysis

We train the decision tree with 80% of the data, and 20% for checking.

n80 <- nrow(raw_fixed) * 0.8
training <- head(raw_fixed, n80)
training

And use the remaining 20% for proofing

Now, we create a decision tree with training data.

model <- rpart(Outcome~., data = training, method = "class",
               control=rpart.control(minsplit=20, minbucket=10, cp=0.01))
prp(model)

And test the training result.

testdata$Outcome <- as.factor(testdata$Outcome)
predicted <- predict(model, testdata[, -9], type = "class")
actual <- testdata[, 9]
comp <- data.frame(predicted, actual)
comp

And, confusion matrix.

confusionMatrix(comp$predicted, comp$actual)
Confusion Matrix and Statistics

          Reference
Prediction  0  1
         0 46  7
         1  8 18
                                          
               Accuracy : 0.8101          
                 95% CI : (0.7062, 0.8897)
    No Information Rate : 0.6835          
    P-Value [Acc > NIR] : 0.008608        
                                          
                  Kappa : 0.5658          
                                          
 Mcnemar's Test P-Value : 1.000000        
                                          
            Sensitivity : 0.8519          
            Specificity : 0.7200          
         Pos Pred Value : 0.8679          
         Neg Pred Value : 0.6923          
             Prevalence : 0.6835          
         Detection Rate : 0.5823          
   Detection Prevalence : 0.6709          
      Balanced Accuracy : 0.7859          
                                          
       'Positive' Class : 0               
                                          

Both PPV and NPV are quite good, of 0.87 and 0.70.

I think, the generated model is valid for both positive and negative prediction, as well as not too complex.

Conclusions

LS0tCnRpdGxlOiAiRGlhYmV0ZXMgcnVsZSBtaW5pbmciCmF1dGhvcjogIlBhY2hhcmFwb2wgV2l0aGF5YXNha3B1bnQiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyIHNldHVwfQpsaWJyYXJ5KGFydWxlcykKbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShycGFydCkKbGlicmFyeShycGFydC5wbG90KQpsaWJyYXJ5KGNhcmV0KQpgYGAKCiMjIERhdGFzZXQKCkZyb20gW1BpbWEgSW5kaWFucyBEaWFiZXRlcyBEYXRhYmFzZV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS91Y2ltbC9waW1hLWluZGlhbnMtZGlhYmV0ZXMtZGF0YWJhc2UpLgoKTWVhbmluZyBvZiBlYWNoIHJvd3MgYXJlCgotIFByZWduYW5jaWVzIC0gTnVtYmVyIG9mIHRpbWVzIHByZWduYW50Ci0gR2x1Y29zZSAtIFBsYXNtYSBnbHVjb3NlIGNvbmNlbnRyYXRpb24gYSAyIGhvdXJzIGluIGFuIG9yYWwgZ2x1Y29zZSB0b2xlcmFuY2UgdGVzdAotIEJsb29kUHJlc3N1cmUgLSBEaWFzdG9saWMgYmxvb2QgcHJlc3N1cmUgKG1tIEhnKQotIFNraW5UaGlja25lc3MgLSBUcmljZXBzIHNraW4gZm9sZCB0aGlja25lc3MgKG1tKQotIEluc3VsaW4gLSAyLUhvdXIgc2VydW0gaW5zdWxpbiAobXUgVS9tbCkKLSBCTUkgLSBCb2R5IG1hc3MgaW5kZXggKHdlaWdodCBpbiBrZy8oaGVpZ2h0IGluIG0pXjIpCi0gRGlhYmV0ZXNQZWRpZ3JlZUZ1bmN0aW9uIC0gRGlhYmV0ZXMgcGVkaWdyZWUgZnVuY3Rpb24KICAtIERpYWJldGVzIFBlZGlncmVlIEZ1bmN0aW9uLCBwZWRpLiBJdCBwcm92aWRlZCBzb21lIGRhdGEgb24gZGlhYmV0ZXMgbWVsbGl0dXMgaGlzdG9yeSBpbiByZWxhdGl2ZXMgYW5kIHRoZSBnZW5ldGljIHJlbGF0aW9uc2hpcCBvZiB0aG9zZSByZWxhdGl2ZXMgdG8gdGhlIHBhdGllbnQuIFRoaXMgbWVhc3VyZSBvZiBnZW5ldGljIGluZmx1ZW5jZSBnYXZlIHVzIGFuIGlkZWEgb2YgdGhlIGhlcmVkaXRhcnkgcmlzayBvbmUgbWlnaHQgaGF2ZSB3aXRoIHRoZSBvbnNldCBvZiBkaWFiZXRlcyBtZWxsaXR1cy4gLS0gPGh0dHBzOi8vbWFjaGluZWxlYXJuaW5nbWFzdGVyeS5jb20vY2FzZS1zdHVkeS1wcmVkaWN0aW5nLXRoZS1vbnNldC1vZi1kaWFiZXRlcy13aXRoaW4tZml2ZS15ZWFycy1wYXJ0LTEtb2YtMy8+Ci0gQWdlIC0gQWdlICh5ZWFycykKLSBPdXRjb21lIC0gQ2xhc3MgdmFyaWFibGUgKDAgb3IgMSkgMjY4IG9mIDc2OCBhcmUgMSwgdGhlIG90aGVycyBhcmUgMAoKYGBge3J9CnJhdyA8LSByZWFkLmNzdigiZGlhYmV0ZXMuY3N2IiwgaGVhZGVyID0gVCkKcmF3CmBgYAoKIyMgUmVzZWFyY2ggcXVlc3Rpb25zCgpXaGF0IGFyZSBwcmVkaWN0aXZlIGFuZCBwcm90ZWN0aXZlIGZhY3RvcnMgZm9yIHRoZSBvdXRjb21lcyAob2Ygd2hldGhlciBoYXZpbmcgZGlhYnRlcyBvciBub3QpPwoKIyMgRHVwbGljYXRlIGRldGVjdGlvbgoKLSBJIGFzc3VtZSB0aGVyZSBpcyBubyBkdXBsaWNhdGVzLCBiZWNhdXNlIHRoZXJlIGlzIG5vIElEIHRvIGZpeCB0aGF0LgoKIyMgRGF0YSBleHBsb3JhdGlvbgoKV2Ugc2hvdWxkIHNlZSBmaXJzdCwgd2hpY2ggY29sdW1ucyBhbmQgcGFpciBvZiBjb2x1bW5zIG1pZ2h0IGhhdmUgY2x1c3RlcmluZy4KCkhpc3RvZ3JhbSBncmFwaCBpcyBhbHNvIGF2YWlsYWJsZSBvbiBLYWdnbGUuCgpgYGB7cn0KZ2dwbG90bHkoZ2dwbG90KHJhdykgKyBnZW9tX2hpc3RvZ3JhbShhZXMoR2x1Y29zZSkpKQpgYGAKClNpbmdsZSBjb3VudC9jb250aW51b3VzIHZhcmlhYmxlcyBoZXJlIHVzdWFsbHkgaGF2ZSBCZWxsIGN1cnZlIGRpc3RyaWJ1dGlvbiwgd2l0aCB6ZXJvIHZhbHVlcywgcHJvYmFibHkgaW4gcGxhY2Ugb2YgTkEncy4gU3VjaCBjb2x1bW5zIGluY2x1ZGUKCi0gR2x1Y29zZQotIEJsb29kUHJlc3N1cmUKLSBTa2luVGhpY2tuZXNzCi0gSW5zdWxpbgoKSSBhbHNvIHBsb3R0ZWQgdXNpbmcgc2NhdHRlciBwbG90cywgaG9waW5nIG1vcmUgYXNzb2NpYXRpb25zIHdpbGwgYmUgc2hvd24uIChZZXMsIEkgdHJpZWQgdG8gcGVyZm9ybSBhbGwgOUMyLikKCmBgYHtyfQpnZ3Bsb3RseShnZ3Bsb3QocmF3KSArIGdlb21fcG9pbnQoYWVzKERpYWJldGVzUGVkaWdyZWVGdW5jdGlvbiwgQWdlKSkpCmBgYAoKVGhlcmVmb3JlLCBhcyBmb3Igbm93LCBJIHNlZSBubyByZWFsIGNsdXN0ZXJpbmcsIGJ1dCB0aGVyZSBuZWVkIHRvIGJlIGNsZWFuaW5nIGZvciB6ZXJvIHZhbHVlcy4KCiMjIERhdGEgY2xlYW5pbmcKCkZpcnN0LCBJIGNoZWNrIGhvdyBtYW55IHJvd3MgaGF2ZSBjb21wbGV0ZSBkYXRhLgoKYGBge3J9CnJhd19maXhlZCA8LSByYXcgJT4lCiAgZmlsdGVyKEluc3VsaW4gIT0wICYgR2x1Y29zZSAhPSAwICYgQmxvb2RQcmVzc3VyZSAhPSAwICYgU2tpblRoaWNrbmVzcyAhPSAwKQpyYXdfZml4ZWQKYGBgCgpBbmQgdGFrZSBhIHBlYWsgYXQgdGhlIGRhdGEuCgpgYGB7cn0KZ2dwbG90bHkoZ2dwbG90KHJhd19maXhlZCkgKyBnZW9tX2hpc3RvZ3JhbShhZXMoT3V0Y29tZSkpKQpgYGAKCiMjIEFzc29jaWF0aW9uIG1pbmluZwoKYGBge3J9CmRhdGEgPC0gZGlzY3JldGl6ZURGKHJhd19maXhlZCkKZGF0YQpgYGAKCldlIGNhbiBzZWUgdGhhdCBvdXRjb21lIGlzIGJyb2tlbjsgdGhlcmVmb3JlLCB3ZSBzaG91bGQgZml4IGl0LgoKYGBge3J9CmRhdGEkT3V0Y29tZSA8LSBhcy5mYWN0b3IocmF3X2ZpeGVkJE91dGNvbWUpCmRhdGEKYGBgCgpgYGB7cn0KdHMgPC0gYXMoZGF0YSwgInRyYW5zYWN0aW9ucyIpCmBgYAoKVGVzdCBvdXQgQXByaW9yaSBydWxlcy4gV2UgYWltIGZvciBvbmVzIHdpdGggaGlnaCBjb25maWRlbmNlLiBIaWdoIG9yIGxvdyBzdXBwb3J0IGhhcyBkaWZmZXJlbnQgbWVhbmluZ3MuCgpgYGB7cn0KYXMoCiAgYXByaW9yaSh0cywKICAgICAgICAgIHBhcmFtZXRlciA9IGxpc3Qoc3VwID0gMC4xLCBjb25mID0gMC45LCB0YXJnZXQ9InJ1bGVzIiksCiAgICAgICAgICBhcHBlYXJhbmNlID0gbGlzdChyaHMgPSBjKCJPdXRjb21lPTAiLCAiT3V0Y29tZT0xIikpCiAgICAgICAgICApLAogICJkYXRhLmZyYW1lIgopICU+JSBhcnJhbmdlKGRlc2MoY29uZmlkZW5jZSkpCmBgYApMb3dlciBnbHVjb3NlLCBwcmVnbmFuY2llcywgQk1JLCBhbmQgaW5zdWxpbiBhcmUgaW1wb3J0YW50IHByb3RlY3RpdmUgZmFjdG9ycy4gSG93ZXZlciwgbm8gcHJlZ25hbmNpZXMgZG9lc24ndCBwcmVkaWN0IGJldHRlciBvdXRjb21lLgoKVGhlcmVmb3JlLCBJIHBsb3QgdGhlIGRhdGEganVzdCB0byBiZSBzdXJlLgoKYGBge3J9CmdncGxvdGx5KGdncGxvdChyYXdfZml4ZWQgJT4lIGZpbHRlcihPdXRjb21lID09IDApKSArIGdlb21faGlzdG9ncmFtKGFlcyhQcmVnbmFuY2llcykpKQpgYGAKCmBgYHtyfQpnZ3Bsb3RseShnZ3Bsb3QocmF3X2ZpeGVkICU+JSBmaWx0ZXIoT3V0Y29tZSA9PSAxKSkgKyBnZW9tX2hpc3RvZ3JhbShhZXMoUHJlZ25hbmNpZXMpKSkKYGBgCgpXaGF0IGFib3V0IG91dGNvbWUgPSAxPwoKYGBge3J9CmFzKAogIGFwcmlvcmkodHMsCiAgICAgICAgICBwYXJhbWV0ZXIgPSBsaXN0KHN1cCA9IDAuMSwgY29uZiA9IDAuNiwgdGFyZ2V0PSJydWxlcyIpLAogICAgICAgICAgYXBwZWFyYW5jZSA9IGxpc3QocmhzID0gYygiT3V0Y29tZT0xIikpCiAgICAgICAgICApLAogICJkYXRhLmZyYW1lIgopICU+JSBhcnJhbmdlKGRlc2Moc3VwcG9ydCkpCmBgYAoKSXQgaXMgaGFyZGVyIHRvIGZpbmQgcHJlZGljdGl2ZSBmYWN0b3JzIGZvciBkaWFiZXRlcyB3aXRoIGNvbmZpZGVuY2UuIEkgaGF2ZSB0byBkZWNyZWFzZSBjb25maWRlbmNlIHRvIGF0IGxlYXN0IDAuNyB0byBnZXQgc29tZSByZXN1bHRzLgoKSW1wb3J0YW50IHByZWRpY3RpdmUgZmFjdG9ycyBhcmUgaGlnaGVyIGJsb29kIGdsdWNvc2UgbGV2ZWwsIGluc3VsaW4sIG1vcmUgYWdlLCBhbmQgbXVsdGlwYXJpdHkuCgpXZSB3YW50IHRvIHNlZSBsb3dlciBzdXBwb3J0cyBhcyB3ZWxsLgoKYGBge3J9CmFzKAogIGFwcmlvcmkodHMsCiAgICAgICAgICBwYXJhbWV0ZXIgPSBsaXN0KHN1cCA9IDAuMDMsIGNvbmYgPSAwLjcsIG1heGxlbiA9IDQsIHRhcmdldD0icnVsZXMiKSwKICAgICAgICAgIGFwcGVhcmFuY2UgPSBsaXN0KHJocyA9IGMoIk91dGNvbWU9MCIsICJPdXRjb21lPTEiKSkKICAgICAgICAgICksCiAgImRhdGEuZnJhbWUiCikgJT4lIGZpbHRlcihzdXBwb3J0IDwgMC4xKSAlPiUgYXJyYW5nZShkZXNjKGxpZnQpKQpgYGAKUmFyZXIgb25lcyB3aXRoIGRlY2VudCBjb3VudHMgc3RpbGwgaW5jbHVkZSBoaWdoZXIgYmxvb2QgZ2x1Y29zZSBsZXZlbCwgaW5zdWxpbiwgbW9yZSBhZ2UsIGFuZCBtdWx0aXBhcml0eSwgd2hpY2ggYXJlIHByZWRpY3RpdmUgb2YgcG9vcmVyIG91dGNvbWVzLiBBbiBhZGRpdGlvbmFsIGZhY3RvciBoZXJlIGlzIGRpYWJldGVzIHBlZGlncmVlIGZ1bmN0aW9uLgoKV2hhdCdzIHRoZSBkaXN0cmlidXRpb24gb2YgZGlhYmV0ZXMgcGVkaWdyZWUgZnVuY3Rpb24sIEJUVz8KCmBgYHtyfQpnZ3Bsb3RseShnZ3Bsb3QocmF3X2ZpeGVkKSArIGdlb21faGlzdG9ncmFtKGFlcyhEaWFiZXRlc1BlZGlncmVlRnVuY3Rpb24pKSkKYGBgCgpUaGVyZWZvcmUsIG1vcmUgZGlhYmV0ZXMgcGVkaWdyZWUgZnVuY3Rpb24gdmFsdWVzIGFyZSBwcmVkaWN0aXZlIG9mIGRpYWJldGVzLCBidXQgdGhlIGRhdGEgc3VwcG9ydGluZyB0aGlzIGlzIHJhcmVyLgoKQXMgYE91dGNvbWU9MGAgaXMgdW5kZXJtaW5lZCwgSSBoYXZlIHRvIHJ1biB0aGlzIGFnYWluIGZvciBgT3V0Y29tZT0wYC4KCmBgYHtyfQphcygKICBhcHJpb3JpKHRzLAogICAgICAgICAgcGFyYW1ldGVyID0gbGlzdChzdXAgPSAwLjAzLCBjb25mID0gMC45LCBtYXhsZW4gPSA0LCB0YXJnZXQ9InJ1bGVzIiksCiAgICAgICAgICBhcHBlYXJhbmNlID0gbGlzdChyaHMgPSBjKCJPdXRjb21lPTAiKSkKICAgICAgICAgICksCiAgImRhdGEuZnJhbWUiCikgJT4lIGZpbHRlcihzdXBwb3J0IDwgMC4xKSAlPiUgYXJyYW5nZShkZXNjKGxpZnQpKQpgYGAKWW91bmdlciBhZ2VzIGxlc3MgdGhhbiAyNSwgbG93ZXIgaW5zdWxpbiwgbG93ZXIgc2tpbiB0aGlja25lc3MsIGFuZCBsb3dlciBnbHVjb3NlIGxldmVscyBsZXNzIHRoYW4gMTA1LCBhcmUgcHJvdGVjdGl2ZSBmYWN0b3JzLiBCdXQgbm90IG11Y2ggc28gZm9yIERpYWJldGVzUGVkaWdyZWVGdW5jdGlvbiA8IDAuMzI1LgoKYGBge3J9CmdncGxvdGx5KGdncGxvdChyYXdfZml4ZWQpICsgZ2VvbV9oaXN0b2dyYW0oYWVzKFNraW5UaGlja25lc3MpKSkKYGBgCgojIyBEZWNpc2lvbiB0cmVlIGFuYWx5c2lzCgpXZSB0cmFpbiB0aGUgZGVjaXNpb24gdHJlZSB3aXRoIDgwJSBvZiB0aGUgZGF0YSwgYW5kIDIwJSBmb3IgY2hlY2tpbmcuCgpgYGB7cn0KbjgwIDwtIG5yb3cocmF3X2ZpeGVkKSAqIDAuOAp0cmFpbmluZyA8LSBoZWFkKHJhd19maXhlZCwgbjgwKQp0cmFpbmluZwpgYGAKCkFuZCB1c2UgdGhlIHJlbWFpbmluZyAyMCUgZm9yIHByb29maW5nCgpgYGB7cn0KdGVzdGRhdGEgPC0gdGFpbChyYXdfZml4ZWQsIC1uODApCnRlc3RkYXRhCmBgYAoKTm93LCB3ZSBjcmVhdGUgYSBkZWNpc2lvbiB0cmVlIHdpdGggdHJhaW5pbmcgZGF0YS4KCmBgYHtyfQptb2RlbCA8LSBycGFydChPdXRjb21lfi4sIGRhdGEgPSB0cmFpbmluZywgbWV0aG9kID0gImNsYXNzIiwKICAgICAgICAgICAgICAgY29udHJvbD1ycGFydC5jb250cm9sKG1pbnNwbGl0PTIwLCBtaW5idWNrZXQ9MTAsIGNwPTAuMDEpKQpwcnAobW9kZWwpCmBgYAoKQW5kIHRlc3QgdGhlIHRyYWluaW5nIHJlc3VsdC4KCmBgYHtyfQp0ZXN0ZGF0YSRPdXRjb21lIDwtIGFzLmZhY3Rvcih0ZXN0ZGF0YSRPdXRjb21lKQpjb21wIDwtIGRhdGEuZnJhbWUoCiAgcHJlZGljdGVkID0gcHJlZGljdChtb2RlbCwgdGVzdGRhdGFbLCAtOV0sIHR5cGUgPSAiY2xhc3MiKSwKICBhY3R1YWwgPSB0ZXN0ZGF0YVssIDldCikKY29tcApgYGAKQW5kLCBjb25mdXNpb24gbWF0cml4LgoKYGBge3J9CmNvbmZ1c2lvbk1hdHJpeChjb21wJHByZWRpY3RlZCwgY29tcCRhY3R1YWwpCmBgYApCb3RoIFBQViBhbmQgTlBWIGFyZSBxdWl0ZSBnb29kLCBvZiAwLjg3IGFuZCAwLjcwLgoKSSB0aGluaywgdGhlIGdlbmVyYXRlZCBtb2RlbCBpcyB2YWxpZCBmb3IgYm90aCBwb3NpdGl2ZSBhbmQgbmVnYXRpdmUgcHJlZGljdGlvbiwgYXMgd2VsbCBhcyBub3QgdG9vIGNvbXBsZXguCgojIyBDb25jbHVzaW9ucwoKLSBObyBjbHVzdGVyaW5nIGlzIGlkZW50aWZpZWQuCi0gTG93ZXIgZ2x1Y29zZSwgcHJlZ25hbmNpZXMsIEJNSSwgYW5kIGluc3VsaW4gYXJlIGltcG9ydGFudCBwcm90ZWN0aXZlIGZhY3RvcnMuIEhvd2V2ZXIsIG5vIHByZWduYW5jaWVzIGRvZXNuJ3QgcHJlZGljdCBiZXR0ZXIgb3V0Y29tZS4KLSBJbXBvcnRhbnQgcHJlZGljdGl2ZSBmYWN0b3JzIGZvciBkaWFiZXRlcyBhcmUgaGlnaGVyIGJsb29kIGdsdWNvc2UgbGV2ZWwsIGluc3VsaW4sIG1vcmUgYWdlLCBhbmQgbXVsdGlwYXJpdHkuCi0gTW9yZSBkaWFiZXRlcyBwZWRpZ3JlZSBmdW5jdGlvbiAoaS5lLiBnZW5ldGljcykgbWF5IGJlIHByZWRpY3RpdmUgb2YgZGlhYmV0ZXMuCi0gTGVzcyBza2luIHRoaWNrbmVzcyBtYXkgYmUgcHJvdGVjdGl2ZSBvZiBkaWFiZXRlcy4KLSBUaGUgZ2VuZXJhdGVkIG1vZGVsIGlzIHZhbGlkIGZvciBib3RoIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBwcmVkaWN0aW9uLCBhcyB3ZWxsIGFzIG5vdCB0b28gY29tcGxleC4K