Question 1

heart <- read.csv("HW7data.csv")

a.

heart$target <- factor(heart$target, levels = c("Yes", "No")) #I changed the levels because it hurts my brain when no is the positive class.

heart$sex <- as.character(heart$sex)
heart$cp <- as.character(heart$cp)
heart$fbs <- as.character(heart$fbs)
heart$restecg <- as.character(heart$restecg)
heart$exang <- as.character(heart$exang)
heart$slope <- as.character(heart$slope)
heart$thal <- as.character(heart$thal)

imbalance <- heart %>% group_by(target) %>% summarise(n = n(), p = n / 303)
imbalance
# A tibble: 2 × 3
  target     n     p
  <fct>  <int> <dbl>
1 Yes      165 0.545
2 No       138 0.455

There are more yes than no’s, but the imbalance is not severe.

b.

set.seed(1)

fitControl <- trainControl(method = "cv", number = 10, classProbs = TRUE, savePredictions = "all", summaryFunction = twoClassSummary)

model.logit <- train(data = heart,
                     target ~ .,
                     method = "glm",
                     family = "binomial",
                     trControl = fitControl)
model.logit
Generalized Linear Model 

303 samples
 13 predictor
  2 classes: 'Yes', 'No' 

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 274, 272, 273, 272, 273, 272, ... 
Resampling results:

  ROC        Sens       Spec     
  0.8888474  0.8797794  0.7769231

Test accuracy of 83.31%.

c.

set.seed(1)

freq_class <- table(heart$target)

weights_vector <- ifelse(heart$target == "No", nrow(heart)/(length(freq_class)*freq_class[2]), nrow(heart)/(length(freq_class)*freq_class[1]))

model.logit2 <- train(data = heart,
                     target ~ .,
                     method = "glm",
                     family = "quasibinomial",
                     weights = weights_vector,
                     trControl = fitControl)
model.logit2
Generalized Linear Model 

303 samples
 13 predictor
  2 classes: 'Yes', 'No' 

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 274, 272, 273, 272, 273, 272, ... 
Resampling results:

  ROC        Sens       Spec     
  0.8892069  0.8492647  0.7917582

The new test accuracy is 82.33%. We did worse because there was no severe class imbalance.

d.

The no class would be the positive class if I hadn’t changed the levels. I changed the levels so the yes class is the positive class since your testing if a person has heart disease. Sensitivity measures the proportion of actual positives (person has heart disease) that the model correctly identified. So out of the people with heart disease, how many times did the model correctly label them as ‘Yes’. Specificity measures the proportion of actual negatives. So out of the people who didn’t have heart disease, how many times did the model correctly label them as ‘No’. The sensitivity and specificity for regular log reg were 0.8797794 and 0.7769231. The sensitivity and specificity for weighted log reg were 0.8492647 and 0.7917582. So the weighted log reg did better with the people who didn’t have heart disease, which makes sense since these people were assigned larger weights.

e.

thresholder(model.logit, threshold = 0.50) 
  parameter prob_threshold Sensitivity Specificity Pos Pred Value
1      none            0.5   0.8797794   0.7769231      0.8287918
  Neg Pred Value Precision    Recall        F1 Prevalence Detection Rate
1      0.8513736 0.8287918 0.8797794 0.8516563  0.5445384      0.4789247
  Detection Prevalence Balanced Accuracy  Accuracy     Kappa         J
1            0.5802373         0.8283512 0.8330738 0.6608442 0.6567025
       Dist
1 0.2629323
thresholder(model.logit, threshold = 0.25) 
  parameter prob_threshold Sensitivity Specificity Pos Pred Value
1      none           0.25   0.9272059   0.6434066      0.7649135
  Neg Pred Value Precision    Recall       F1 Prevalence Detection Rate
1      0.8821503 0.7649135 0.9272059 0.836072  0.5445384      0.5049537
  Detection Prevalence Balanced Accuracy  Accuracy     Kappa         J
1             0.666548         0.7853062 0.7988209 0.5825124 0.5706125
       Dist
1 0.3675353
thresholder(model.logit, threshold = 0.75) 
  parameter prob_threshold Sensitivity Specificity Pos Pred Value
1      none           0.75   0.6797794   0.9065934      0.8990113
  Neg Pred Value Precision    Recall        F1 Prevalence Detection Rate
1      0.7072092 0.8990113 0.6797794 0.7712848  0.5445384       0.370178
  Detection Prevalence Balanced Accuracy  Accuracy     Kappa         J
1             0.412551         0.7931864 0.7832666 0.5735424 0.5863728
       Dist
1 0.3413992

When we decrease the threshold (0.25), we make it easier for the model to label someone as a ‘Yes”. As a result, the sensitivity goes up and the specificity goes down. When we increase the threshold (0.75), we make it harder for the model to label someone as a ’Yes’. As a result, the sensitivity goes down and the specificity goes up.

f.

cpGrid <- expand.grid(cp = 0) # No pruning
set.seed(1)
fitControl <- trainControl(method = "cv", number = 10)

model.tree <- train(target ~ .,
                    data = heart,
                    method = "rpart",
                    trControl = fitControl,
                    tuneGrid = cpGrid)
par(xpd = NA)
plot(model.tree$finalModel)
text(model.tree$finalModel)

The first decision is when thal is equal to 2. Go to the left if it equals 2, go to the right otherwise.

g.

p <- prop.table(table(heart$target))
(0.5445545)*(0.4554455) + (0.5445545)*(0.4554455)
[1] 0.4960298

The Gini index with no decisions is 0.4960298.

h.

heart %>%
  filter(thal == "2") %>%
  group_by(target) %>%
  summarize(n = n())
# A tibble: 2 × 2
  target     n
  <fct>  <int>
1 Yes      130
2 No        36
heart %>%
  filter(thal != "2") %>%
  group_by(target) %>%
  summarize(n = n())
# A tibble: 2 × 2
  target     n
  <fct>  <int>
1 Yes       35
2 No       102
gini_thal2 <- (130/166)*(1 - 130/166) + (36/166)*(1 - 36/166)
gini_not_thal2 <- (35/137)*(1 - 35/137) + (102/137)*(1 - 102/137)
split_gini <- (166/303)*gini_thal2 + (137/303)*gini_not_thal2
split_gini
[1] 0.3580935

They are now more pure.

i.

heart %>%
  filter(thal == "3") %>%
  group_by(target) %>%
  summarize(n = n())
# A tibble: 2 × 2
  target     n
  <fct>  <int>
1 Yes       28
2 No        89
heart %>%
  filter(thal != "3") %>%
  group_by(target) %>%
  summarize(n = n())
# A tibble: 2 × 2
  target     n
  <fct>  <int>
1 Yes      137
2 No        49
gini_thal3 <- (137/186)*(1 - 137/186) + (49/186)*(1 - 49/186)
gini_not_thal3 <- (28/117)*(1 - 28/117) + (89/117)*(1 - 89/117)
split_gini <- (186/303)*gini_thal3 + (117/303)*gini_not_thal3
split_gini
[1] 0.3788155

Slightly less pure now.

j.

heart %>%
  filter(cp == "2") %>%
  group_by(target) %>%
  summarize(n = n())
# A tibble: 2 × 2
  target     n
  <fct>  <int>
1 Yes       69
2 No        18
heart %>%
  filter(cp != "2") %>%
  group_by(target) %>%
  summarize(n = n())
# A tibble: 2 × 2
  target     n
  <fct>  <int>
1 Yes       96
2 No       120
gini_cp2 <- (69/87)*(1 - 69/87) + (18/87)*(1 - 18/87)
gini_not_cp2 <- (96/216)*(1 - 96/216) + (120/216)*(1 - 120/216)
split_gini <- (87/303)*gini_cp2 + (216/303)*gini_not_cp2
split_gini
[1] 0.4462653

Less pure then the split on thal2.

k.

set.seed(1)

model.tree.prune <- train(target ~ .,
                          data = heart,
                          method = "rpart",
                          trControl = fitControl,
                          tuneLength = 10) # Let function pick 10 reasonable tuning parameters to prune the tree
model.tree.prune
CART 

303 samples
 13 predictor
  2 classes: 'Yes', 'No' 

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 274, 272, 273, 272, 273, 272, ... 
Resampling results across tuning parameters:

  cp          Accuracy   Kappa     
  0.00000000  0.7338821  0.46342416
  0.05394525  0.7534594  0.50171008
  0.10789050  0.7667927  0.52861591
  0.16183575  0.7667927  0.52861591
  0.21578100  0.7667927  0.52861591
  0.26972625  0.7667927  0.52861591
  0.32367150  0.7667927  0.52861591
  0.37761675  0.7667927  0.52861591
  0.43156200  0.7667927  0.52861591
  0.48550725  0.5800222  0.09607575

Accuracy was used to select the optimal model using the largest value.
The final value used for the model was cp = 0.431562.

The accuracy when not pruning was 0.7338821, the best accuracy with pruning was 0.7667927, so we did better with pruning. This makes sense since pruning limits the number of splits a tree can do, keeping it from overfitting and removing unnecessary splits.

l.

par(xpd = NA)
plot(model.tree.prune$finalModel)
text(model.tree.prune$finalModel)

heart %>%
  filter(thal == "2") %>%
  group_by(target) %>%
  summarize(n = n())
# A tibble: 2 × 2
  target     n
  <fct>  <int>
1 Yes      130
2 No        36
heart %>%
  filter(thal != "2") %>%
  group_by(target) %>%
  summarize(n = n())
# A tibble: 2 × 2
  target     n
  <fct>  <int>
1 Yes       35
2 No       102
232/303
[1] 0.7656766

Everything checks out.

LS0tCnRpdGxlOiAiSG9tZXdvcmsgNyIKYXV0aG9yOiAiQ2hhcmxpZSBNb3JnYW4iCmRhdGU6ICIgRHVlOiAwNS8wMy8yNiIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6IAogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNAogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIG51bWJlcl9zZWN0aW9uczogbm8KICAgIHRvY19jb2xsYXBzZWQ6IHllcwogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMKICAgIHNtb290aF9zY3JvbGw6IHllcwogICAgdGhlbWU6IGx1bWVuCiAgcGRmX2RvY3VtZW50OiAKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDQKICAgIGZpZ19jYXB0aW9uOiB5ZXMKICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICBmaWdfd2lkdGg6IDMKICAgIGZpZ19oZWlnaHQ6IDMKICB3b3JkX2RvY3VtZW50OiAKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDQKICAgIGZpZ19jYXB0aW9uOiB5ZXMKICAgIGtlZXBfbWQ6IHllcwplZGl0b3Jfb3B0aW9uczogCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQotLS0KCmBgYHtjc3MsIGVjaG8gPSBGQUxTRX0KI1RPQzo6YmVmb3JlIHsKICBjb250ZW50OiAiVGFibGUgb2YgQ29udGVudHMiOwogIGZvbnQtd2VpZ2h0OiBib2xkOwogIGZvbnQtc2l6ZTogMS4yZW07CiAgZGlzcGxheTogYmxvY2s7CiAgY29sb3I6IG5hdnk7CiAgbWFyZ2luLWJvdHRvbTogMTBweDsKfQoKCmRpdiNUT0MgbGkgeyAgICAgLyogdGFibGUgb2YgY29udGVudCAgKi8KICAgIGxpc3Qtc3R5bGU6dXBwZXItcm9tYW47CiAgICBiYWNrZ3JvdW5kLWltYWdlOm5vbmU7CiAgICBiYWNrZ3JvdW5kLXJlcGVhdDpub25lOwogICAgYmFja2dyb3VuZC1wb3NpdGlvbjowOwp9CgpoMS50aXRsZSB7ICAgIC8qIGxldmVsIDEgaGVhZGVyIG9mIHRpdGxlICAqLwogIGZvbnQtc2l6ZTogMjJweDsKICBmb250LXdlaWdodDogYm9sZDsKICBjb2xvcjogRGFya1JlZDsKICB0ZXh0LWFsaWduOiBjZW50ZXI7CiAgZm9udC1mYW1pbHk6ICJHaWxsIFNhbnMiLCBzYW5zLXNlcmlmOwp9CgpoNC5hdXRob3IgeyAvKiBIZWFkZXIgNCAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLwogIGZvbnQtc2l6ZTogMTVweDsKICBmb250LXdlaWdodDogYm9sZDsKICBmb250LWZhbWlseTogc3lzdGVtLXVpOwogIGNvbG9yOiBuYXZ5OwogIHRleHQtYWxpZ246IGNlbnRlcjsKfQoKaDQuZGF0ZSB7IC8qIEhlYWRlciA0IC0gYW5kIHRoZSBhdXRob3IgYW5kIGRhdGEgaGVhZGVycyB1c2UgdGhpcyB0b28gICovCiAgZm9udC1zaXplOiAxOHB4OwogIGZvbnQtd2VpZ2h0OiBib2xkOwogIGZvbnQtZmFtaWx5OiAiR2lsbCBTYW5zIiwgc2Fucy1zZXJpZjsKICBjb2xvcjogRGFya0JsdWU7CiAgdGV4dC1hbGlnbjogY2VudGVyOwp9CgpoMSB7IC8qIEhlYWRlciAxIC0gYW5kIHRoZSBhdXRob3IgYW5kIGRhdGEgaGVhZGVycyB1c2UgdGhpcyB0b28gICovCiAgICBmb250LXNpemU6IDIwcHg7CiAgICBmb250LXdlaWdodDogYm9sZDsKICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOwogICAgY29sb3I6IGRhcmtyZWQ7CiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7Cn0KCmgyIHsgLyogSGVhZGVyIDIgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8KICAgIGZvbnQtc2l6ZTogMThweDsKICAgIGZvbnQtd2VpZ2h0OiBib2xkOwogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7CiAgICBjb2xvcjogbmF2eTsKICAgIHRleHQtYWxpZ246IGxlZnQ7Cn0KCmgzIHsgLyogSGVhZGVyIDMgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8KICAgIGZvbnQtc2l6ZTogMTZweDsKICAgIGZvbnQtd2VpZ2h0OiBib2xkOwogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7CiAgICBjb2xvcjogbmF2eTsKICAgIHRleHQtYWxpZ246IGxlZnQ7Cn0KCmg0IHsgLyogSGVhZGVyIDQgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8KICAgIGZvbnQtc2l6ZTogMTRweDsKICBmb250LXdlaWdodDogYm9sZDsKICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOwogICAgY29sb3I6IGRhcmtyZWQ7CiAgICB0ZXh0LWFsaWduOiBsZWZ0Owp9CgovKiBBZGQgZG90cyBhZnRlciBudW1iZXJlZCBoZWFkZXJzICovCi5oZWFkZXItc2VjdGlvbi1udW1iZXI6OmFmdGVyIHsKICBjb250ZW50OiAiLiI7Cgpib2R5IHsgYmFja2dyb3VuZC1jb2xvcjp3aGl0ZTsgfQoKLmhpZ2hsaWdodG1lIHsgYmFja2dyb3VuZC1jb2xvcjp5ZWxsb3c7IH0KCnAgeyBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOyB9Cgp9CmBgYAoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CiMgY29kZSBjaHVuayBzcGVjaWZpZXMgd2hldGhlciB0aGUgUiBjb2RlLCB3YXJuaW5ncywgYW5kIG91dHB1dCAKIyB3aWxsIGJlIGluY2x1ZGVkIGluIHRoZSBvdXRwdXQgZmlsZXMuCmlmICghcmVxdWlyZSgia25pdHIiKSkgewogICBpbnN0YWxsLnBhY2thZ2VzKCJrbml0ciIpCiAgIGxpYnJhcnkoa25pdHIpCn0KaWYgKCFyZXF1aXJlKCJmYWN0b2V4dHJhIikpIHsKICBpbnN0YWxsLnBhY2thZ2VzKCJmYWN0b2V4dHJhIikKICBsaWJyYXJ5KGZhY3RvZXh0cmEpCn0KCmlmICghcmVxdWlyZSgidGlkeXZlcnNlIikpIHsKICBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQogIGxpYnJhcnkodGlkeXZlcnNlKQp9CgppZiAoIXJlcXVpcmUoIkZhY3RvTWluZVIiKSkgewogIGluc3RhbGwucGFja2FnZXMoIkZhY3RvTWluZVIiKQogIGxpYnJhcnkoRmFjdG9NaW5lUikKfQoKaWYgKCFyZXF1aXJlKCJjb3JycGxvdCIpKSB7CiAgaW5zdGFsbC5wYWNrYWdlcygiY29ycnBsb3QiKQogIGxpYnJhcnkoY29ycnBsb3QpCn0KCmlmICghcmVxdWlyZSgibWljZSIpKSB7CiAgaW5zdGFsbC5wYWNrYWdlcygibWljZSIpCiAgbGlicmFyeShtaWNlKQp9CgppZiAoIXJlcXVpcmUoImthYmxlRXh0cmEiKSkgewogIGluc3RhbGwucGFja2FnZXMoImthYmxlRXh0cmEiKQogIGxpYnJhcnkoa2FibGVFeHRyYSkKfQoKaWYgKCFyZXF1aXJlKCJjbHVzdGVyIikpIHsKICBpbnN0YWxsLnBhY2thZ2VzKCJjbHVzdGVyIikKICBsaWJyYXJ5KGNsdXN0ZXIpCn0KCmlmICghcmVxdWlyZSgibWNsdXN0IikpIHsKICBpbnN0YWxsLnBhY2thZ2VzKCJtY2x1c3QiKQogIGxpYnJhcnkobWNsdXN0KQp9CgppZiAoIXJlcXVpcmUoImRic2NhbiIpKSB7CiAgaW5zdGFsbC5wYWNrYWdlcygiZGJzY2FuIikKICBsaWJyYXJ5KGRic2NhbikKfQoKaWYgKCFyZXF1aXJlKCJjYXJldCIpKSB7CiAgaW5zdGFsbC5wYWNrYWdlcygiY2FyZXQiKQogIGxpYnJhcnkoY2FyZXQpCn0KIyMjIwprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsICAgICAgICMgaW5jbHVkZSBjb2RlIGNodW5rIGluIHRoZSBvdXRwdXQgZmlsZQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLCAgICMgc29tZXRpbWVzLCB5b3UgY29kZSBtYXkgcHJvZHVjZSB3YXJuaW5nIG1lc3NhZ2VzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgeW91IGNhbiBjaG9vc2UgdG8gaW5jbHVkZSB0aGUgd2FybmluZyBtZXNzYWdlcyBpbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgdGhlIG91dHB1dCBmaWxlLiAKICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdHMgPSBUUlVFLCAgICAjIHlvdSBjYW4gYWxzbyBkZWNpZGUgd2hldGhlciB0byBpbmNsdWRlIHRoZSBvdXRwdXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGluIHRoZSBvdXRwdXQgZmlsZS4KICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIGNvbW1lbnQgPSBOQQogICAgICAgICAgICAgICAgICAgICAgKSAgCgpgYGAKCiMgUXVlc3Rpb24gMQoKYGBge3J9CmhlYXJ0IDwtIHJlYWQuY3N2KCJIVzdkYXRhLmNzdiIpCmBgYAoKIyMgYS4KYGBge3J9CmhlYXJ0JHRhcmdldCA8LSBmYWN0b3IoaGVhcnQkdGFyZ2V0LCBsZXZlbHMgPSBjKCJZZXMiLCAiTm8iKSkgI0kgY2hhbmdlZCB0aGUgbGV2ZWxzIGJlY2F1c2UgaXQgaHVydHMgbXkgYnJhaW4gd2hlbiBubyBpcyB0aGUgcG9zaXRpdmUgY2xhc3MuCgpoZWFydCRzZXggPC0gYXMuY2hhcmFjdGVyKGhlYXJ0JHNleCkKaGVhcnQkY3AgPC0gYXMuY2hhcmFjdGVyKGhlYXJ0JGNwKQpoZWFydCRmYnMgPC0gYXMuY2hhcmFjdGVyKGhlYXJ0JGZicykKaGVhcnQkcmVzdGVjZyA8LSBhcy5jaGFyYWN0ZXIoaGVhcnQkcmVzdGVjZykKaGVhcnQkZXhhbmcgPC0gYXMuY2hhcmFjdGVyKGhlYXJ0JGV4YW5nKQpoZWFydCRzbG9wZSA8LSBhcy5jaGFyYWN0ZXIoaGVhcnQkc2xvcGUpCmhlYXJ0JHRoYWwgPC0gYXMuY2hhcmFjdGVyKGhlYXJ0JHRoYWwpCgppbWJhbGFuY2UgPC0gaGVhcnQgJT4lIGdyb3VwX2J5KHRhcmdldCkgJT4lIHN1bW1hcmlzZShuID0gbigpLCBwID0gbiAvIDMwMykKaW1iYWxhbmNlCmBgYApUaGVyZSBhcmUgbW9yZSB5ZXMgdGhhbiBubydzLCBidXQgdGhlIGltYmFsYW5jZSBpcyBub3Qgc2V2ZXJlLgoKIyMgYi4KYGBge3J9CnNldC5zZWVkKDEpCgpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCwgY2xhc3NQcm9icyA9IFRSVUUsIHNhdmVQcmVkaWN0aW9ucyA9ICJhbGwiLCBzdW1tYXJ5RnVuY3Rpb24gPSB0d29DbGFzc1N1bW1hcnkpCgptb2RlbC5sb2dpdCA8LSB0cmFpbihkYXRhID0gaGVhcnQsCiAgICAgICAgICAgICAgICAgICAgIHRhcmdldCB+IC4sCiAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJnbG0iLAogICAgICAgICAgICAgICAgICAgICBmYW1pbHkgPSAiYmlub21pYWwiLAogICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQptb2RlbC5sb2dpdApgYGAKVGVzdCBhY2N1cmFjeSBvZiA4My4zMSUuCgojIyBjLgpgYGB7cn0Kc2V0LnNlZWQoMSkKCmZyZXFfY2xhc3MgPC0gdGFibGUoaGVhcnQkdGFyZ2V0KQoKd2VpZ2h0c192ZWN0b3IgPC0gaWZlbHNlKGhlYXJ0JHRhcmdldCA9PSAiTm8iLCBucm93KGhlYXJ0KS8obGVuZ3RoKGZyZXFfY2xhc3MpKmZyZXFfY2xhc3NbMl0pLCBucm93KGhlYXJ0KS8obGVuZ3RoKGZyZXFfY2xhc3MpKmZyZXFfY2xhc3NbMV0pKQoKbW9kZWwubG9naXQyIDwtIHRyYWluKGRhdGEgPSBoZWFydCwKICAgICAgICAgICAgICAgICAgICAgdGFyZ2V0IH4gLiwKICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImdsbSIsCiAgICAgICAgICAgICAgICAgICAgIGZhbWlseSA9ICJxdWFzaWJpbm9taWFsIiwKICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0cyA9IHdlaWdodHNfdmVjdG9yLAogICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQptb2RlbC5sb2dpdDIKYGBgClRoZSBuZXcgdGVzdCBhY2N1cmFjeSBpcyA4Mi4zMyUuIFdlIGRpZCB3b3JzZSBiZWNhdXNlIHRoZXJlIHdhcyBubyBzZXZlcmUgY2xhc3MgaW1iYWxhbmNlLgoKIyMgZC4KVGhlIG5vIGNsYXNzIHdvdWxkIGJlIHRoZSBwb3NpdGl2ZSBjbGFzcyBpZiBJIGhhZG4ndCBjaGFuZ2VkIHRoZSBsZXZlbHMuIEkgY2hhbmdlZCB0aGUgbGV2ZWxzIHNvIHRoZSB5ZXMgY2xhc3MgaXMgdGhlIHBvc2l0aXZlIGNsYXNzIHNpbmNlIHlvdXIgdGVzdGluZyBpZiBhIHBlcnNvbiBoYXMgaGVhcnQgZGlzZWFzZS4gU2Vuc2l0aXZpdHkgbWVhc3VyZXMgdGhlIHByb3BvcnRpb24gb2YgYWN0dWFsIHBvc2l0aXZlcyAocGVyc29uIGhhcyBoZWFydCBkaXNlYXNlKSB0aGF0IHRoZSBtb2RlbCBjb3JyZWN0bHkgaWRlbnRpZmllZC4gU28gb3V0IG9mIHRoZSBwZW9wbGUgd2l0aCBoZWFydCBkaXNlYXNlLCBob3cgbWFueSB0aW1lcyBkaWQgdGhlIG1vZGVsIGNvcnJlY3RseSBsYWJlbCB0aGVtIGFzICdZZXMnLiBTcGVjaWZpY2l0eSBtZWFzdXJlcyB0aGUgcHJvcG9ydGlvbiBvZiBhY3R1YWwgbmVnYXRpdmVzLiBTbyBvdXQgb2YgdGhlIHBlb3BsZSB3aG8gZGlkbid0IGhhdmUgaGVhcnQgZGlzZWFzZSwgaG93IG1hbnkgdGltZXMgZGlkIHRoZSBtb2RlbCBjb3JyZWN0bHkgbGFiZWwgdGhlbSBhcyAnTm8nLiBUaGUgc2Vuc2l0aXZpdHkgYW5kIHNwZWNpZmljaXR5IGZvciByZWd1bGFyIGxvZyByZWcgd2VyZSAwLjg3OTc3OTQgYW5kIDAuNzc2OTIzMS4gVGhlIHNlbnNpdGl2aXR5IGFuZCBzcGVjaWZpY2l0eSBmb3Igd2VpZ2h0ZWQgbG9nIHJlZyB3ZXJlICAwLjg0OTI2NDcgYW5kIDAuNzkxNzU4Mi4gU28gdGhlIHdlaWdodGVkIGxvZyByZWcgZGlkIGJldHRlciB3aXRoIHRoZSBwZW9wbGUgd2hvIGRpZG4ndCBoYXZlIGhlYXJ0IGRpc2Vhc2UsIHdoaWNoIG1ha2VzIHNlbnNlIHNpbmNlIHRoZXNlIHBlb3BsZSB3ZXJlIGFzc2lnbmVkIGxhcmdlciB3ZWlnaHRzLgoKIyMgZS4KYGBge3J9CnRocmVzaG9sZGVyKG1vZGVsLmxvZ2l0LCB0aHJlc2hvbGQgPSAwLjUwKSAKYGBgCmBgYHtyfQp0aHJlc2hvbGRlcihtb2RlbC5sb2dpdCwgdGhyZXNob2xkID0gMC4yNSkgCmBgYApgYGB7cn0KdGhyZXNob2xkZXIobW9kZWwubG9naXQsIHRocmVzaG9sZCA9IDAuNzUpIApgYGAKV2hlbiB3ZSBkZWNyZWFzZSB0aGUgdGhyZXNob2xkICgwLjI1KSwgd2UgbWFrZSBpdCBlYXNpZXIgZm9yIHRoZSBtb2RlbCB0byBsYWJlbCBzb21lb25lIGFzIGEgJ1llcyIuIEFzIGEgcmVzdWx0LCB0aGUgc2Vuc2l0aXZpdHkgZ29lcyB1cCBhbmQgdGhlIHNwZWNpZmljaXR5IGdvZXMgZG93bi4gV2hlbiB3ZSBpbmNyZWFzZSB0aGUgdGhyZXNob2xkICgwLjc1KSwgd2UgbWFrZSBpdCBoYXJkZXIgZm9yIHRoZSBtb2RlbCB0byBsYWJlbCBzb21lb25lIGFzIGEgJ1llcycuIEFzIGEgcmVzdWx0LCB0aGUgc2Vuc2l0aXZpdHkgZ29lcyBkb3duIGFuZCB0aGUgc3BlY2lmaWNpdHkgZ29lcyB1cC4gCgojIyBmLgpgYGB7cn0KY3BHcmlkIDwtIGV4cGFuZC5ncmlkKGNwID0gMCkgIyBObyBwcnVuaW5nCnNldC5zZWVkKDEpCmZpdENvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQoKbW9kZWwudHJlZSA8LSB0cmFpbih0YXJnZXQgfiAuLAogICAgICAgICAgICAgICAgICAgIGRhdGEgPSBoZWFydCwKICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAicnBhcnQiLAogICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wsCiAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBjcEdyaWQpCnBhcih4cGQgPSBOQSkKcGxvdChtb2RlbC50cmVlJGZpbmFsTW9kZWwpCnRleHQobW9kZWwudHJlZSRmaW5hbE1vZGVsKQpgYGAKClRoZSBmaXJzdCBkZWNpc2lvbiBpcyB3aGVuIHRoYWwgaXMgZXF1YWwgdG8gMi4gR28gdG8gdGhlIGxlZnQgaWYgaXQgZXF1YWxzIDIsIGdvIHRvIHRoZSByaWdodCBvdGhlcndpc2UuCgojIyBnLgpgYGB7cn0KcCA8LSBwcm9wLnRhYmxlKHRhYmxlKGhlYXJ0JHRhcmdldCkpCigwLjU0NDU1NDUpKigwLjQ1NTQ0NTUpICsgKDAuNTQ0NTU0NSkqKDAuNDU1NDQ1NSkKYGBgClRoZSBHaW5pIGluZGV4IHdpdGggbm8gZGVjaXNpb25zIGlzIDAuNDk2MDI5OC4KCiMjIGguCmBgYHtyfQpoZWFydCAlPiUKICBmaWx0ZXIodGhhbCA9PSAiMiIpICU+JQogIGdyb3VwX2J5KHRhcmdldCkgJT4lCiAgc3VtbWFyaXplKG4gPSBuKCkpCmBgYApgYGB7cn0KaGVhcnQgJT4lCiAgZmlsdGVyKHRoYWwgIT0gIjIiKSAlPiUKICBncm91cF9ieSh0YXJnZXQpICU+JQogIHN1bW1hcml6ZShuID0gbigpKQpgYGAKCmBgYHtyfQpnaW5pX3RoYWwyIDwtICgxMzAvMTY2KSooMSAtIDEzMC8xNjYpICsgKDM2LzE2NikqKDEgLSAzNi8xNjYpCmdpbmlfbm90X3RoYWwyIDwtICgzNS8xMzcpKigxIC0gMzUvMTM3KSArICgxMDIvMTM3KSooMSAtIDEwMi8xMzcpCnNwbGl0X2dpbmkgPC0gKDE2Ni8zMDMpKmdpbmlfdGhhbDIgKyAoMTM3LzMwMykqZ2luaV9ub3RfdGhhbDIKc3BsaXRfZ2luaQpgYGAKVGhleSBhcmUgbm93IG1vcmUgcHVyZS4KCiMjIGkuCmBgYHtyfQpoZWFydCAlPiUKICBmaWx0ZXIodGhhbCA9PSAiMyIpICU+JQogIGdyb3VwX2J5KHRhcmdldCkgJT4lCiAgc3VtbWFyaXplKG4gPSBuKCkpCmBgYApgYGB7cn0KaGVhcnQgJT4lCiAgZmlsdGVyKHRoYWwgIT0gIjMiKSAlPiUKICBncm91cF9ieSh0YXJnZXQpICU+JQogIHN1bW1hcml6ZShuID0gbigpKQpgYGAKCmBgYHtyfQpnaW5pX3RoYWwzIDwtICgxMzcvMTg2KSooMSAtIDEzNy8xODYpICsgKDQ5LzE4NikqKDEgLSA0OS8xODYpCmdpbmlfbm90X3RoYWwzIDwtICgyOC8xMTcpKigxIC0gMjgvMTE3KSArICg4OS8xMTcpKigxIC0gODkvMTE3KQpzcGxpdF9naW5pIDwtICgxODYvMzAzKSpnaW5pX3RoYWwzICsgKDExNy8zMDMpKmdpbmlfbm90X3RoYWwzCnNwbGl0X2dpbmkKYGBgClNsaWdodGx5IGxlc3MgcHVyZSBub3cuCgojIyBqLgoKYGBge3J9CmhlYXJ0ICU+JQogIGZpbHRlcihjcCA9PSAiMiIpICU+JQogIGdyb3VwX2J5KHRhcmdldCkgJT4lCiAgc3VtbWFyaXplKG4gPSBuKCkpCmBgYAoKYGBge3J9CmhlYXJ0ICU+JQogIGZpbHRlcihjcCAhPSAiMiIpICU+JQogIGdyb3VwX2J5KHRhcmdldCkgJT4lCiAgc3VtbWFyaXplKG4gPSBuKCkpCmBgYAoKYGBge3J9CmdpbmlfY3AyIDwtICg2OS84NykqKDEgLSA2OS84NykgKyAoMTgvODcpKigxIC0gMTgvODcpCmdpbmlfbm90X2NwMiA8LSAoOTYvMjE2KSooMSAtIDk2LzIxNikgKyAoMTIwLzIxNikqKDEgLSAxMjAvMjE2KQpzcGxpdF9naW5pIDwtICg4Ny8zMDMpKmdpbmlfY3AyICsgKDIxNi8zMDMpKmdpbmlfbm90X2NwMgpzcGxpdF9naW5pCmBgYApMZXNzIHB1cmUgdGhlbiB0aGUgc3BsaXQgb24gdGhhbDIuCgojIyBrLgpgYGB7cn0Kc2V0LnNlZWQoMSkKCm1vZGVsLnRyZWUucHJ1bmUgPC0gdHJhaW4odGFyZ2V0IH4gLiwKICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gaGVhcnQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJwYXJ0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sLAogICAgICAgICAgICAgICAgICAgICAgICAgIHR1bmVMZW5ndGggPSAxMCkgIyBMZXQgZnVuY3Rpb24gcGljayAxMCByZWFzb25hYmxlIHR1bmluZyBwYXJhbWV0ZXJzIHRvIHBydW5lIHRoZSB0cmVlCm1vZGVsLnRyZWUucHJ1bmUKYGBgClRoZSBhY2N1cmFjeSB3aGVuIG5vdCBwcnVuaW5nIHdhcyAwLjczMzg4MjEsIHRoZSBiZXN0IGFjY3VyYWN5IHdpdGggcHJ1bmluZyB3YXMgMC43NjY3OTI3LCBzbyB3ZSBkaWQgYmV0dGVyIHdpdGggcHJ1bmluZy4gVGhpcyBtYWtlcyBzZW5zZSBzaW5jZSBwcnVuaW5nIGxpbWl0cyB0aGUgbnVtYmVyIG9mIHNwbGl0cyBhIHRyZWUgY2FuIGRvLCBrZWVwaW5nIGl0IGZyb20gb3ZlcmZpdHRpbmcgYW5kIHJlbW92aW5nIHVubmVjZXNzYXJ5IHNwbGl0cy4KCiMjIGwuCmBgYHtyfQpwYXIoeHBkID0gTkEpCnBsb3QobW9kZWwudHJlZS5wcnVuZSRmaW5hbE1vZGVsKQp0ZXh0KG1vZGVsLnRyZWUucHJ1bmUkZmluYWxNb2RlbCkKYGBgCmBgYHtyfQpoZWFydCAlPiUKICBmaWx0ZXIodGhhbCA9PSAiMiIpICU+JQogIGdyb3VwX2J5KHRhcmdldCkgJT4lCiAgc3VtbWFyaXplKG4gPSBuKCkpCmBgYApgYGB7cn0KaGVhcnQgJT4lCiAgZmlsdGVyKHRoYWwgIT0gIjIiKSAlPiUKICBncm91cF9ieSh0YXJnZXQpICU+JQogIHN1bW1hcml6ZShuID0gbigpKQpgYGAKYGBge3J9CjIzMi8zMDMKYGBgCkV2ZXJ5dGhpbmcgY2hlY2tzIG91dC4=