Exercises

1.

data <- read.csv("Exam2Data.csv")
fitControl <- trainControl(method = "cv", number = 5)

2.

set.seed(1)
model.cv <- train(Proline ~.,
                  data = data,
                  method = "lm",
                  trControl = fitControl)

model.cv$finalModel

Call:
lm(formula = .outcome ~ ., data = dat)

Coefficients:
   (Intercept)         RegionB         RegionC         Alcohol       MalicAcid  
       502.560        -506.343        -513.535          18.659         -16.079  
           Ash      Alcalinity       Magnesium         Phenols      Flavanoids  
         6.000          -1.205           1.789          54.315         -54.122  
  Nonflavanoid         Proanth  ColorIntensity             Hue           OD280  
       -54.780          34.304          24.828         184.853         -54.195  

When all other variables are held constant, a 1 unit increase in Color Intensity results in a 24.828 unit increase in Proline. When all other variables are held constant, RegionB is associated with 506.343 lower Proline than Region A. When all other variables are held constant, RegionC is associated with 513.535 lower Proline than Region A.

3.

model.cv$results
  intercept     RMSE  Rsquared      MAE   RMSESD RsquaredSD    MAESD
1      TRUE 168.0626 0.7333614 129.8525 12.42053 0.03451967 12.82251

The test RMSE was 168.0626, meaning the model’s predictions are typically about 168 units away from the true Proline values.

4.

model.lm <- lm(Proline ~., data = data)
rmse <- sqrt(mean(residuals(model.lm)^2))
rmse
[1] 154.4184

The training RMSE is 154.4184. The training RMSE is less than the testing RMSE since the model is being evaluated on data it was trained on.

5.

set.seed(1)

tune.grid <- expand.grid(alpha = 1, lambda = 10^seq(-2, 1, length = 25))

model.lasso <- train(Proline ~ .,
                     data = data,
                     method = "glmnet",
                     tuneGrid = tune.grid,
                     standardize = TRUE,
                     trControl = fitControl)
model.lasso
glmnet 

178 samples
 13 predictor

No pre-processing
Resampling: Cross-Validated (5 fold) 
Summary of sample sizes: 142, 143, 142, 143, 142 
Resampling results across tuning parameters:

  lambda       RMSE      Rsquared   MAE     
   0.01000000  167.7167  0.7343222  129.7797
   0.01333521  167.7167  0.7343222  129.7797
   0.01778279  167.7167  0.7343222  129.7797
   0.02371374  167.7167  0.7343222  129.7797
   0.03162278  167.7167  0.7343222  129.7797
   0.04216965  167.7167  0.7343222  129.7797
   0.05623413  167.7167  0.7343222  129.7797
   0.07498942  167.7167  0.7343222  129.7797
   0.10000000  167.7167  0.7343222  129.7797
   0.13335214  167.7167  0.7343222  129.7797
   0.17782794  167.7144  0.7343355  129.8004
   0.23713737  167.7047  0.7343841  129.8424
   0.31622777  167.6579  0.7345308  129.8712
   0.42169650  167.5510  0.7348530  129.8712
   0.56234133  167.4477  0.7351406  129.8815
   0.74989421  167.3700  0.7353262  129.9154
   1.00000000  167.3575  0.7352228  130.0115
   1.33352143  167.4027  0.7348441  130.3183
   1.77827941  167.4069  0.7346477  130.5143
   2.37137371  167.4025  0.7344871  130.6858
   3.16227766  167.4484  0.7346740  130.9283
   4.21696503  167.7314  0.7346227  131.4540
   5.62341325  167.8945  0.7350463  132.0989
   7.49894209  168.1832  0.7352647  132.8636
  10.00000000  169.0439  0.7338634  134.1911

Tuning parameter 'alpha' was held constant at a value of 1
RMSE was used to select the optimal model using the smallest value.
The final values used for the model were alpha = 1 and lambda = 1.

The test RMSE is 167.3575. We did better with Ridge than OLS regression since Ridge is allowed to set the coefficients of unhelpful variables close to zero. These variables won’t contribute as much to the models predictions, reducing noise, overfitting, and collinearity.

6.

classes <- data %>% group_by(Region) %>% summarise(n = n())
kable(classes)
Region n
A 59
B 71
C 48

There are three regions: A, B, and C. C has less observations than both A and B, and B has the most, but there isn’t an extreme class imbalance.

7.

set.seed(1)
kGrid <- expand.grid(k = seq(1, 13, 2))

model.knn <- train(Region ~.,
                        data = data,
                        method = "knn",
                        preProc = c("center", "scale"),
                        trControl = fitControl,
                        tuneGrid = kGrid)
model.knn
k-Nearest Neighbors 

178 samples
 13 predictor
  3 classes: 'A', 'B', 'C' 

Pre-processing: centered (13), scaled (13) 
Resampling: Cross-Validated (5 fold) 
Summary of sample sizes: 141, 143, 143, 143, 142 
Resampling results across tuning parameters:

  k   Accuracy   Kappa    
   1  0.9604676  0.9403076
   3  0.9661819  0.9488648
   5  0.9661819  0.9488648
   7  0.9604676  0.9402229
   9  0.9715873  0.9569956
  11  0.9491978  0.9234248
  13  0.9604676  0.9402229

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

Optimal value for k was 9, with a test accuracy of 0.9715873 and kappa statistic of 0.9569956. The model correctly predicted the region 97.16% of the time. The kappa statistic alters the accuracy to reflect the fact that some of it could be due to random chance. Since the kappa statistic is still really high this means our model is good and there isn’t a class imbalance (which we noted above).

8.

set.seed(1)

grid <- expand.grid(
  usekernel = FALSE, 
  fL = 0, 
  adjust = 1 
)

model.nb <- train(data = data,
                  Region ~ .,
                  method = "nb",
                  trControl = fitControl,
                  tuneGrid = grid)
model.nb
Naive Bayes 

178 samples
 13 predictor
  3 classes: 'A', 'B', 'C' 

No pre-processing
Resampling: Cross-Validated (5 fold) 
Summary of sample sizes: 141, 143, 143, 143, 142 
Resampling results:

  Accuracy   Kappa   
  0.9666409  0.949126

Tuning parameter 'fL' was held constant at a value of 0
Tuning
 parameter 'usekernel' was held constant at a value of FALSE
Tuning
 parameter 'adjust' was held constant at a value of 1

The test accuracy with naive bayes was 0.9666409.

9.

t <- data %>%
  group_by(Region) %>%
  summarise(across(
    where(is.numeric),
    list(mean = mean, sd = sd),
    .names = "{.col}_{.fn}" #got code from web
  ))

kable(t)
Region Alcohol_mean Alcohol_sd MalicAcid_mean MalicAcid_sd Ash_mean Ash_sd Alcalinity_mean Alcalinity_sd Magnesium_mean Magnesium_sd Phenols_mean Phenols_sd Flavanoids_mean Flavanoids_sd Nonflavanoid_mean Nonflavanoid_sd Proanth_mean Proanth_sd ColorIntensity_mean ColorIntensity_sd Hue_mean Hue_sd OD280_mean OD280_sd Proline_mean Proline_sd
A 13.74475 0.4621254 2.010678 0.6885489 2.455593 0.2271660 17.03729 2.546323 106.3390 10.49895 2.840170 0.3389614 2.9823729 0.3974936 0.290000 0.0700492 1.899322 0.4121092 5.528305 1.2385728 1.0620339 0.1164826 3.157797 0.3570766 1115.7119 221.5208
B 12.27873 0.5379642 1.932676 1.0155687 2.244789 0.3154673 20.23803 3.349770 94.5493 16.75350 2.258873 0.5453611 2.0808451 0.7057008 0.363662 0.1239613 1.630282 0.6020678 3.086620 0.9249293 1.0562817 0.2029368 2.785352 0.4965735 519.5070 157.2112
C 13.15375 0.5302413 3.333750 1.0879057 2.437083 0.1846902 21.41667 2.258161 99.3125 10.89047 1.678750 0.3569709 0.7814583 0.2935041 0.447500 0.1241396 1.153542 0.4088359 7.396250 2.3109421 0.6827083 0.1144411 1.683542 0.2721114 629.8958 115.0970

You assume each variable is normally distributed, which allows you to find the probability of certain values for each variable. So if an observation has an alcohol value of 14, you can standardize it using the mean and standard deviation of alcohol levels and get the probability from a z-score table for each region. Now you know the probability of the observation being in Region A, B, or C based solely on its alcohol value. If you do this for every variable and multiply the probabilities together, you get a good estimate of what region it belongs to. This is essentially what Naive Bayes does.

10.

KNN estimates probability based on nearby points by looking at the k closest observations and taking the proportion of those that fall into each class. An observation is assigned the class that the majority of its k nearest neighbors are in. Logistic regression models the probability directly by plugging the predictors into a linear equation and passing it through a function to keep it between 0 and 1. An observation is assigned to class 1 if the predicted probability is above a threshold (usually 0.5), otherwise it is assigned to class 0.

11.

set.seed(1)

model.tree.prune <- train(Region ~ .,
                          data = data,
                          method = "rpart",
                          trControl = fitControl,
                          tuneLength = 10)

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

There are 5 terminal nodes, their values are their predicted regions (A, B, or C).

testing <- data %>% filter(Proline >= 755 & Flavanoids >= 2.165) %>% group_by(Region) %>% summarise(n = n())
kable(testing)
Region n
A 57
B 2

Since majority of the observations where Proline is >= 755 and Flavanoids >= 2.165 are in Region A, that terminal node has the correct value.

12.

The Gini Index measures how pure a node is. A node is pure if most of the observations in it belong to one class. A Gini value of 0 means the node is perfectly pure, while higher values indicate the classes are more mixed. When building trees, we choose splits that reduce the Gini Index the most, meaning they create more pure groups. We use Gini instead of RMSE because RMSE is for regression, and instead of accuracy because accuracy isn’t sensitive enough to small improvements in splits.

13.

prop.table(table(data$Region))

        A         B         C 
0.3314607 0.3988764 0.2696629 
0.3314607*(1-0.3314607) + 0.3988764*(1-0.3988764) + 0.2696629*(1-0.2696629)
[1] 0.6583133

The gini index with no splits is 0.6583133.

14.

test <- data %>% filter(Proline >= 755) %>% group_by(Region) %>% summarise(n = n())
kable(test)
Region n
A 57
B 4
C 6
test <- data %>% filter(Proline < 755) %>% group_by(Region) %>% summarise(n = n())
kable(test)
Region n
A 2
B 67
C 42
gini_progr <- (57/67)*(1-57/67) + (4/67)*(1-4/67) + (6/67)*(1-6/67)
gini_prole <- (2/111)*(1-2/111) + (67/111)*(1-67/111) + (42/111)*(1-42/111)
gini_imp <- (67/178) * gini_progr + (111/178) * gini_prole
gini_imp
[1] 0.4065279

The gini index is now 0.4065279, so we did better.

15.

test <- data %>% filter(Proline >= 800) %>% group_by(Region) %>% summarise(n = n())
kable(test)
Region n
A 53
B 4
C 5
test <- data %>% filter(Proline < 800) %>% group_by(Region) %>% summarise(n = n())
kable(test)
Region n
A 6
B 67
C 43
gini_progr <- (53/62)*(1-53/62) + (4/62)*(1-4/62) + (5/62)*(1-5/62)
gini_prole <- (6/116)*(1-6/116) + (67/116)*(1-67/116) + (43/116)*(1-43/116)
gini_imp <- (62/178) * gini_progr + (116/178) * gini_prole
gini_imp
[1] 0.4330561

The gini index is now 0.4330561, so we did worse than the previous split.

16.

test <- data %>% filter(Flavanoids >= 2.165) %>% group_by(Region) %>% summarise(n = n())
kable(test)
Region n
A 59
B 29
test <- data %>% filter(Flavanoids < 2.165) %>% group_by(Region) %>% summarise(n = n())
kable(test)
Region n
B 42
C 48
gini_progr <- (59/88)*(1-59/88) + (29/88)*(1-29/88) + (0/88)*(1-0/88)
gini_prole <- (0/90)*(1-0/90) + (42/90)*(1-42/90) + (48/90)*(1-48/90)
gini_imp <- (88/178) * gini_progr + (90/178) * gini_prole
gini_imp
[1] 0.4701481

The new Gini index is 0.4701481, so once again we did worse. The initial split chosen by the train() function did the best. This function loops through possible initial splits and chooses the one that results in the lowest Gini index, so any other split will generally perform the same or worse at that step.

LS0tCnRpdGxlOiAiRXhhbSAyIgphdXRob3I6ICJDaGFybGllIE1vcmdhbiIKZGF0ZTogIiBEdWU6IDA1LzAzLzI2IgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDogCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiA0CiAgICB0b2NfZmxvYXQ6IHllcwogICAgbnVtYmVyX3NlY3Rpb25zOiBubwogICAgdG9jX2NvbGxhcHNlZDogeWVzCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGNvZGVfZG93bmxvYWQ6IHllcwogICAgc21vb3RoX3Njcm9sbDogeWVzCiAgICB0aGVtZTogbHVtZW4KICBwZGZfZG9jdW1lbnQ6IAogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNAogICAgZmlnX2NhcHRpb246IHllcwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIGZpZ193aWR0aDogMwogICAgZmlnX2hlaWdodDogMwogIHdvcmRfZG9jdW1lbnQ6IAogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNAogICAgZmlnX2NhcHRpb246IHllcwogICAga2VlcF9tZDogeWVzCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQoKYGBge2NzcywgZWNobyA9IEZBTFNFfQojVE9DOjpiZWZvcmUgewogIGNvbnRlbnQ6ICJUYWJsZSBvZiBDb250ZW50cyI7CiAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgZm9udC1zaXplOiAxLjJlbTsKICBkaXNwbGF5OiBibG9jazsKICBjb2xvcjogbmF2eTsKICBtYXJnaW4tYm90dG9tOiAxMHB4Owp9CgoKZGl2I1RPQyBsaSB7ICAgICAvKiB0YWJsZSBvZiBjb250ZW50ICAqLwogICAgbGlzdC1zdHlsZTp1cHBlci1yb21hbjsKICAgIGJhY2tncm91bmQtaW1hZ2U6bm9uZTsKICAgIGJhY2tncm91bmQtcmVwZWF0Om5vbmU7CiAgICBiYWNrZ3JvdW5kLXBvc2l0aW9uOjA7Cn0KCmgxLnRpdGxlIHsgICAgLyogbGV2ZWwgMSBoZWFkZXIgb2YgdGl0bGUgICovCiAgZm9udC1zaXplOiAyMnB4OwogIGZvbnQtd2VpZ2h0OiBib2xkOwogIGNvbG9yOiBEYXJrUmVkOwogIHRleHQtYWxpZ246IGNlbnRlcjsKICBmb250LWZhbWlseTogIkdpbGwgU2FucyIsIHNhbnMtc2VyaWY7Cn0KCmg0LmF1dGhvciB7IC8qIEhlYWRlciA0IC0gYW5kIHRoZSBhdXRob3IgYW5kIGRhdGEgaGVhZGVycyB1c2UgdGhpcyB0b28gICovCiAgZm9udC1zaXplOiAxNXB4OwogIGZvbnQtd2VpZ2h0OiBib2xkOwogIGZvbnQtZmFtaWx5OiBzeXN0ZW0tdWk7CiAgY29sb3I6IG5hdnk7CiAgdGV4dC1hbGlnbjogY2VudGVyOwp9CgpoNC5kYXRlIHsgLyogSGVhZGVyIDQgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8KICBmb250LXNpemU6IDE4cHg7CiAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgZm9udC1mYW1pbHk6ICJHaWxsIFNhbnMiLCBzYW5zLXNlcmlmOwogIGNvbG9yOiBEYXJrQmx1ZTsKICB0ZXh0LWFsaWduOiBjZW50ZXI7Cn0KCmgxIHsgLyogSGVhZGVyIDEgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8KICAgIGZvbnQtc2l6ZTogMjBweDsKICAgIGZvbnQtd2VpZ2h0OiBib2xkOwogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7CiAgICBjb2xvcjogZGFya3JlZDsKICAgIHRleHQtYWxpZ246IGNlbnRlcjsKfQoKaDIgeyAvKiBIZWFkZXIgMiAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLwogICAgZm9udC1zaXplOiAxOHB4OwogICAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsKICAgIGNvbG9yOiBuYXZ5OwogICAgdGV4dC1hbGlnbjogbGVmdDsKfQoKaDMgeyAvKiBIZWFkZXIgMyAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLwogICAgZm9udC1zaXplOiAxNnB4OwogICAgZm9udC13ZWlnaHQ6IGJvbGQ7CiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsKICAgIGNvbG9yOiBuYXZ5OwogICAgdGV4dC1hbGlnbjogbGVmdDsKfQoKaDQgeyAvKiBIZWFkZXIgNCAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLwogICAgZm9udC1zaXplOiAxNHB4OwogIGZvbnQtd2VpZ2h0OiBib2xkOwogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7CiAgICBjb2xvcjogZGFya3JlZDsKICAgIHRleHQtYWxpZ246IGxlZnQ7Cn0KCi8qIEFkZCBkb3RzIGFmdGVyIG51bWJlcmVkIGhlYWRlcnMgKi8KLmhlYWRlci1zZWN0aW9uLW51bWJlcjo6YWZ0ZXIgewogIGNvbnRlbnQ6ICIuIjsKCmJvZHkgeyBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOyB9CgouaGlnaGxpZ2h0bWUgeyBiYWNrZ3JvdW5kLWNvbG9yOnllbGxvdzsgfQoKcCB7IGJhY2tncm91bmQtY29sb3I6d2hpdGU7IH0KCn0KYGBgCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KIyBjb2RlIGNodW5rIHNwZWNpZmllcyB3aGV0aGVyIHRoZSBSIGNvZGUsIHdhcm5pbmdzLCBhbmQgb3V0cHV0IAojIHdpbGwgYmUgaW5jbHVkZWQgaW4gdGhlIG91dHB1dCBmaWxlcy4KaWYgKCFyZXF1aXJlKCJrbml0ciIpKSB7CiAgIGluc3RhbGwucGFja2FnZXMoImtuaXRyIikKICAgbGlicmFyeShrbml0cikKfQppZiAoIXJlcXVpcmUoImZhY3RvZXh0cmEiKSkgewogIGluc3RhbGwucGFja2FnZXMoImZhY3RvZXh0cmEiKQogIGxpYnJhcnkoZmFjdG9leHRyYSkKfQoKaWYgKCFyZXF1aXJlKCJ0aWR5dmVyc2UiKSkgewogIGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpCiAgbGlicmFyeSh0aWR5dmVyc2UpCn0KCmlmICghcmVxdWlyZSgiRmFjdG9NaW5lUiIpKSB7CiAgaW5zdGFsbC5wYWNrYWdlcygiRmFjdG9NaW5lUiIpCiAgbGlicmFyeShGYWN0b01pbmVSKQp9CgppZiAoIXJlcXVpcmUoImNvcnJwbG90IikpIHsKICBpbnN0YWxsLnBhY2thZ2VzKCJjb3JycGxvdCIpCiAgbGlicmFyeShjb3JycGxvdCkKfQoKaWYgKCFyZXF1aXJlKCJtaWNlIikpIHsKICBpbnN0YWxsLnBhY2thZ2VzKCJtaWNlIikKICBsaWJyYXJ5KG1pY2UpCn0KCmlmICghcmVxdWlyZSgia2FibGVFeHRyYSIpKSB7CiAgaW5zdGFsbC5wYWNrYWdlcygia2FibGVFeHRyYSIpCiAgbGlicmFyeShrYWJsZUV4dHJhKQp9CgppZiAoIXJlcXVpcmUoImNsdXN0ZXIiKSkgewogIGluc3RhbGwucGFja2FnZXMoImNsdXN0ZXIiKQogIGxpYnJhcnkoY2x1c3RlcikKfQoKaWYgKCFyZXF1aXJlKCJtY2x1c3QiKSkgewogIGluc3RhbGwucGFja2FnZXMoIm1jbHVzdCIpCiAgbGlicmFyeShtY2x1c3QpCn0KCmlmICghcmVxdWlyZSgiZGJzY2FuIikpIHsKICBpbnN0YWxsLnBhY2thZ2VzKCJkYnNjYW4iKQogIGxpYnJhcnkoZGJzY2FuKQp9CgppZiAoIXJlcXVpcmUoImNhcmV0IikpIHsKICBpbnN0YWxsLnBhY2thZ2VzKCJjYXJldCIpCiAgbGlicmFyeShjYXJldCkKfQojIyMjCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgICAgICAgIyBpbmNsdWRlIGNvZGUgY2h1bmsgaW4gdGhlIG91dHB1dCBmaWxlCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsICAgIyBzb21ldGltZXMsIHlvdSBjb2RlIG1heSBwcm9kdWNlIHdhcm5pbmcgbWVzc2FnZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyB5b3UgY2FuIGNob29zZSB0byBpbmNsdWRlIHRoZSB3YXJuaW5nIG1lc3NhZ2VzIGluCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyB0aGUgb3V0cHV0IGZpbGUuIAogICAgICAgICAgICAgICAgICAgICAgcmVzdWx0cyA9IFRSVUUsICAgICMgeW91IGNhbiBhbHNvIGRlY2lkZSB3aGV0aGVyIHRvIGluY2x1ZGUgdGhlIG91dHB1dAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgaW4gdGhlIG91dHB1dCBmaWxlLgogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgY29tbWVudCA9IE5BCiAgICAgICAgICAgICAgICAgICAgICApICAKCmBgYAoKIyBFeGVyY2lzZXMKCiMjIDEuCmBgYHtyfQpkYXRhIDwtIHJlYWQuY3N2KCJFeGFtMkRhdGEuY3N2IikKZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gNSkKYGBgCgojIyAyLgpgYGB7cn0Kc2V0LnNlZWQoMSkKbW9kZWwuY3YgPC0gdHJhaW4oUHJvbGluZSB+LiwKICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGEsCiAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJsbSIsCiAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCgptb2RlbC5jdiRmaW5hbE1vZGVsCmBgYApXaGVuIGFsbCBvdGhlciB2YXJpYWJsZXMgYXJlIGhlbGQgY29uc3RhbnQsIGEgMSB1bml0IGluY3JlYXNlIGluIENvbG9yIEludGVuc2l0eSByZXN1bHRzIGluIGEgMjQuODI4IHVuaXQgaW5jcmVhc2UgaW4gUHJvbGluZS4gV2hlbiBhbGwgb3RoZXIgdmFyaWFibGVzIGFyZSBoZWxkIGNvbnN0YW50LCBSZWdpb25CIGlzIGFzc29jaWF0ZWQgd2l0aCA1MDYuMzQzIGxvd2VyIFByb2xpbmUgdGhhbiBSZWdpb24gQS4gV2hlbiBhbGwgb3RoZXIgdmFyaWFibGVzIGFyZSBoZWxkIGNvbnN0YW50LCBSZWdpb25DIGlzIGFzc29jaWF0ZWQgd2l0aCA1MTMuNTM1IGxvd2VyIFByb2xpbmUgdGhhbiBSZWdpb24gQS4KCiMjIDMuCmBgYHtyfQptb2RlbC5jdiRyZXN1bHRzCmBgYApUaGUgdGVzdCBSTVNFIHdhcyAxNjguMDYyNiwgbWVhbmluZyB0aGUgbW9kZWzigJlzIHByZWRpY3Rpb25zIGFyZSB0eXBpY2FsbHkgYWJvdXQgMTY4IHVuaXRzIGF3YXkgZnJvbSB0aGUgdHJ1ZSBQcm9saW5lIHZhbHVlcy4KCiMjIDQuCmBgYHtyfQptb2RlbC5sbSA8LSBsbShQcm9saW5lIH4uLCBkYXRhID0gZGF0YSkKcm1zZSA8LSBzcXJ0KG1lYW4ocmVzaWR1YWxzKG1vZGVsLmxtKV4yKSkKcm1zZQpgYGAKVGhlIHRyYWluaW5nIFJNU0UgaXMgMTU0LjQxODQuIFRoZSB0cmFpbmluZyBSTVNFIGlzIGxlc3MgdGhhbiB0aGUgdGVzdGluZyBSTVNFIHNpbmNlIHRoZSBtb2RlbCBpcyBiZWluZyBldmFsdWF0ZWQgb24gZGF0YSBpdCB3YXMgdHJhaW5lZCBvbi4KCiMjIDUuCmBgYHtyfQpzZXQuc2VlZCgxKQoKdHVuZS5ncmlkIDwtIGV4cGFuZC5ncmlkKGFscGhhID0gMSwgbGFtYmRhID0gMTBec2VxKC0yLCAxLCBsZW5ndGggPSAyNSkpCgptb2RlbC5sYXNzbyA8LSB0cmFpbihQcm9saW5lIH4gLiwKICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGEsCiAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJnbG1uZXQiLAogICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IHR1bmUuZ3JpZCwKICAgICAgICAgICAgICAgICAgICAgc3RhbmRhcmRpemUgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQptb2RlbC5sYXNzbwpgYGAKVGhlIHRlc3QgUk1TRSBpcyAxNjcuMzU3NS4gV2UgZGlkIGJldHRlciB3aXRoIFJpZGdlIHRoYW4gT0xTIHJlZ3Jlc3Npb24gc2luY2UgUmlkZ2UgaXMgYWxsb3dlZCB0byBzZXQgdGhlIGNvZWZmaWNpZW50cyBvZiB1bmhlbHBmdWwgdmFyaWFibGVzIGNsb3NlIHRvIHplcm8uIFRoZXNlIHZhcmlhYmxlcyB3b24ndCBjb250cmlidXRlIGFzIG11Y2ggdG8gdGhlIG1vZGVscyBwcmVkaWN0aW9ucywgcmVkdWNpbmcgbm9pc2UsIG92ZXJmaXR0aW5nLCBhbmQgY29sbGluZWFyaXR5LgoKIyMgNi4KYGBge3J9CmNsYXNzZXMgPC0gZGF0YSAlPiUgZ3JvdXBfYnkoUmVnaW9uKSAlPiUgc3VtbWFyaXNlKG4gPSBuKCkpCmthYmxlKGNsYXNzZXMpCmBgYApUaGVyZSBhcmUgdGhyZWUgcmVnaW9uczogQSwgQiwgYW5kIEMuIEMgaGFzIGxlc3Mgb2JzZXJ2YXRpb25zIHRoYW4gYm90aCBBIGFuZCBCLCBhbmQgQiBoYXMgdGhlIG1vc3QsIGJ1dCB0aGVyZSBpc24ndCBhbiBleHRyZW1lIGNsYXNzIGltYmFsYW5jZS4KCiMjIDcuCmBgYHtyfQpzZXQuc2VlZCgxKQprR3JpZCA8LSBleHBhbmQuZ3JpZChrID0gc2VxKDEsIDEzLCAyKSkKCm1vZGVsLmtubiA8LSB0cmFpbihSZWdpb24gfi4sCiAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRhLAogICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAia25uIiwKICAgICAgICAgICAgICAgICAgICAgICAgcHJlUHJvYyA9IGMoImNlbnRlciIsICJzY2FsZSIpLAogICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sLAogICAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGtHcmlkKQptb2RlbC5rbm4KYGBgCk9wdGltYWwgdmFsdWUgZm9yIGsgd2FzIDksIHdpdGggYSB0ZXN0IGFjY3VyYWN5IG9mIDAuOTcxNTg3MyBhbmQga2FwcGEgc3RhdGlzdGljIG9mIDAuOTU2OTk1Ni4gVGhlIG1vZGVsIGNvcnJlY3RseSBwcmVkaWN0ZWQgdGhlIHJlZ2lvbiA5Ny4xNiUgb2YgdGhlIHRpbWUuIFRoZSBrYXBwYSBzdGF0aXN0aWMgYWx0ZXJzIHRoZSBhY2N1cmFjeSB0byByZWZsZWN0IHRoZSBmYWN0IHRoYXQgc29tZSBvZiBpdCBjb3VsZCBiZSBkdWUgdG8gcmFuZG9tIGNoYW5jZS4gU2luY2UgdGhlIGthcHBhIHN0YXRpc3RpYyBpcyBzdGlsbCByZWFsbHkgaGlnaCB0aGlzIG1lYW5zIG91ciBtb2RlbCBpcyBnb29kIGFuZCB0aGVyZSBpc24ndCBhIGNsYXNzIGltYmFsYW5jZSAod2hpY2ggd2Ugbm90ZWQgYWJvdmUpLgoKIyMgOC4KYGBge3J9CnNldC5zZWVkKDEpCgpncmlkIDwtIGV4cGFuZC5ncmlkKAogIHVzZWtlcm5lbCA9IEZBTFNFLCAKICBmTCA9IDAsIAogIGFkanVzdCA9IDEgCikKCm1vZGVsLm5iIDwtIHRyYWluKGRhdGEgPSBkYXRhLAogICAgICAgICAgICAgICAgICBSZWdpb24gfiAuLAogICAgICAgICAgICAgICAgICBtZXRob2QgPSAibmIiLAogICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sLAogICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGdyaWQpCm1vZGVsLm5iCmBgYApUaGUgdGVzdCBhY2N1cmFjeSB3aXRoIG5haXZlIGJheWVzIHdhcyAwLjk2NjY0MDkuCgojIyA5LgpgYGB7cn0KdCA8LSBkYXRhICU+JQogIGdyb3VwX2J5KFJlZ2lvbikgJT4lCiAgc3VtbWFyaXNlKGFjcm9zcygKICAgIHdoZXJlKGlzLm51bWVyaWMpLAogICAgbGlzdChtZWFuID0gbWVhbiwgc2QgPSBzZCksCiAgICAubmFtZXMgPSAiey5jb2x9X3suZm59IiAjZ290IGNvZGUgZnJvbSB3ZWIKICApKQoKa2FibGUodCkKYGBgCllvdSBhc3N1bWUgZWFjaCB2YXJpYWJsZSBpcyBub3JtYWxseSBkaXN0cmlidXRlZCwgd2hpY2ggYWxsb3dzIHlvdSB0byBmaW5kIHRoZSBwcm9iYWJpbGl0eSBvZiBjZXJ0YWluIHZhbHVlcyBmb3IgZWFjaCB2YXJpYWJsZS4gU28gaWYgYW4gb2JzZXJ2YXRpb24gaGFzIGFuIGFsY29ob2wgdmFsdWUgb2YgMTQsIHlvdSBjYW4gc3RhbmRhcmRpemUgaXQgdXNpbmcgdGhlIG1lYW4gYW5kIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBhbGNvaG9sIGxldmVscyBhbmQgZ2V0IHRoZSBwcm9iYWJpbGl0eSBmcm9tIGEgei1zY29yZSB0YWJsZSBmb3IgZWFjaCByZWdpb24uIE5vdyB5b3Uga25vdyB0aGUgcHJvYmFiaWxpdHkgb2YgdGhlIG9ic2VydmF0aW9uIGJlaW5nIGluIFJlZ2lvbiBBLCBCLCBvciBDIGJhc2VkIHNvbGVseSBvbiBpdHMgYWxjb2hvbCB2YWx1ZS4gSWYgeW91IGRvIHRoaXMgZm9yIGV2ZXJ5IHZhcmlhYmxlIGFuZCBtdWx0aXBseSB0aGUgcHJvYmFiaWxpdGllcyB0b2dldGhlciwgeW91IGdldCBhIGdvb2QgZXN0aW1hdGUgb2Ygd2hhdCByZWdpb24gaXQgYmVsb25ncyB0by4gVGhpcyBpcyBlc3NlbnRpYWxseSB3aGF0IE5haXZlIEJheWVzIGRvZXMuCgojIyAxMC4KCktOTiBlc3RpbWF0ZXMgcHJvYmFiaWxpdHkgYmFzZWQgb24gbmVhcmJ5IHBvaW50cyBieSBsb29raW5nIGF0IHRoZSBrIGNsb3Nlc3Qgb2JzZXJ2YXRpb25zIGFuZCB0YWtpbmcgdGhlIHByb3BvcnRpb24gb2YgdGhvc2UgdGhhdCBmYWxsIGludG8gZWFjaCBjbGFzcy4gQW4gb2JzZXJ2YXRpb24gaXMgYXNzaWduZWQgdGhlIGNsYXNzIHRoYXQgdGhlIG1ham9yaXR5IG9mIGl0cyBrIG5lYXJlc3QgbmVpZ2hib3JzIGFyZSBpbi4gTG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbHMgdGhlIHByb2JhYmlsaXR5IGRpcmVjdGx5IGJ5IHBsdWdnaW5nIHRoZSBwcmVkaWN0b3JzIGludG8gYSBsaW5lYXIgZXF1YXRpb24gYW5kIHBhc3NpbmcgaXQgdGhyb3VnaCBhIGZ1bmN0aW9uIHRvIGtlZXAgaXQgYmV0d2VlbiAwIGFuZCAxLiBBbiBvYnNlcnZhdGlvbiBpcyBhc3NpZ25lZCB0byBjbGFzcyAxIGlmIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgaXMgYWJvdmUgYSB0aHJlc2hvbGQgKHVzdWFsbHkgMC41KSwgb3RoZXJ3aXNlIGl0IGlzIGFzc2lnbmVkIHRvIGNsYXNzIDAuCgojIyAxMS4KYGBge3J9CnNldC5zZWVkKDEpCgptb2RlbC50cmVlLnBydW5lIDwtIHRyYWluKFJlZ2lvbiB+IC4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJwYXJ0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sLAogICAgICAgICAgICAgICAgICAgICAgICAgIHR1bmVMZW5ndGggPSAxMCkKCnBhcih4cGQgPSBOQSkKcGxvdChtb2RlbC50cmVlLnBydW5lJGZpbmFsTW9kZWwpCnRleHQobW9kZWwudHJlZS5wcnVuZSRmaW5hbE1vZGVsKQpgYGAKClRoZXJlIGFyZSA1IHRlcm1pbmFsIG5vZGVzLCB0aGVpciB2YWx1ZXMgYXJlIHRoZWlyIHByZWRpY3RlZCByZWdpb25zIChBLCBCLCBvciBDKS4KYGBge3J9CnRlc3RpbmcgPC0gZGF0YSAlPiUgZmlsdGVyKFByb2xpbmUgPj0gNzU1ICYgRmxhdmFub2lkcyA+PSAyLjE2NSkgJT4lIGdyb3VwX2J5KFJlZ2lvbikgJT4lIHN1bW1hcmlzZShuID0gbigpKQprYWJsZSh0ZXN0aW5nKQpgYGAKU2luY2UgbWFqb3JpdHkgb2YgdGhlIG9ic2VydmF0aW9ucyB3aGVyZSBQcm9saW5lIGlzID49IDc1NSBhbmQgRmxhdmFub2lkcyA+PSAyLjE2NSBhcmUgaW4gUmVnaW9uIEEsIHRoYXQgdGVybWluYWwgbm9kZSBoYXMgdGhlIGNvcnJlY3QgdmFsdWUuCgojIyAxMi4KVGhlIEdpbmkgSW5kZXggbWVhc3VyZXMgaG93IHB1cmUgYSBub2RlIGlzLiBBIG5vZGUgaXMgcHVyZSBpZiBtb3N0IG9mIHRoZSBvYnNlcnZhdGlvbnMgaW4gaXQgYmVsb25nIHRvIG9uZSBjbGFzcy4gQSBHaW5pIHZhbHVlIG9mIDAgbWVhbnMgdGhlIG5vZGUgaXMgcGVyZmVjdGx5IHB1cmUsIHdoaWxlIGhpZ2hlciB2YWx1ZXMgaW5kaWNhdGUgdGhlIGNsYXNzZXMgYXJlIG1vcmUgbWl4ZWQuIFdoZW4gYnVpbGRpbmcgdHJlZXMsIHdlIGNob29zZSBzcGxpdHMgdGhhdCByZWR1Y2UgdGhlIEdpbmkgSW5kZXggdGhlIG1vc3QsIG1lYW5pbmcgdGhleSBjcmVhdGUgbW9yZSBwdXJlIGdyb3Vwcy4gV2UgdXNlIEdpbmkgaW5zdGVhZCBvZiBSTVNFIGJlY2F1c2UgUk1TRSBpcyBmb3IgcmVncmVzc2lvbiwgYW5kIGluc3RlYWQgb2YgYWNjdXJhY3kgYmVjYXVzZSBhY2N1cmFjeSBpc27igJl0IHNlbnNpdGl2ZSBlbm91Z2ggdG8gc21hbGwgaW1wcm92ZW1lbnRzIGluIHNwbGl0cy4KCiMjIDEzLgpgYGB7cn0KcHJvcC50YWJsZSh0YWJsZShkYXRhJFJlZ2lvbikpCjAuMzMxNDYwNyooMS0wLjMzMTQ2MDcpICsgMC4zOTg4NzY0KigxLTAuMzk4ODc2NCkgKyAwLjI2OTY2MjkqKDEtMC4yNjk2NjI5KQpgYGAKVGhlIGdpbmkgaW5kZXggd2l0aCBubyBzcGxpdHMgaXMgMC42NTgzMTMzLgoKIyMgMTQuCmBgYHtyfQp0ZXN0IDwtIGRhdGEgJT4lIGZpbHRlcihQcm9saW5lID49IDc1NSkgJT4lIGdyb3VwX2J5KFJlZ2lvbikgJT4lIHN1bW1hcmlzZShuID0gbigpKQprYWJsZSh0ZXN0KQpgYGAKYGBge3J9CnRlc3QgPC0gZGF0YSAlPiUgZmlsdGVyKFByb2xpbmUgPCA3NTUpICU+JSBncm91cF9ieShSZWdpb24pICU+JSBzdW1tYXJpc2UobiA9IG4oKSkKa2FibGUodGVzdCkKYGBgCgpgYGB7cn0KZ2luaV9wcm9nciA8LSAoNTcvNjcpKigxLTU3LzY3KSArICg0LzY3KSooMS00LzY3KSArICg2LzY3KSooMS02LzY3KQpnaW5pX3Byb2xlIDwtICgyLzExMSkqKDEtMi8xMTEpICsgKDY3LzExMSkqKDEtNjcvMTExKSArICg0Mi8xMTEpKigxLTQyLzExMSkKZ2luaV9pbXAgPC0gKDY3LzE3OCkgKiBnaW5pX3Byb2dyICsgKDExMS8xNzgpICogZ2luaV9wcm9sZQpnaW5pX2ltcApgYGAKVGhlIGdpbmkgaW5kZXggaXMgbm93IDAuNDA2NTI3OSwgc28gd2UgZGlkIGJldHRlci4KCiMjIDE1LgpgYGB7cn0KdGVzdCA8LSBkYXRhICU+JSBmaWx0ZXIoUHJvbGluZSA+PSA4MDApICU+JSBncm91cF9ieShSZWdpb24pICU+JSBzdW1tYXJpc2UobiA9IG4oKSkKa2FibGUodGVzdCkKYGBgCgpgYGB7cn0KdGVzdCA8LSBkYXRhICU+JSBmaWx0ZXIoUHJvbGluZSA8IDgwMCkgJT4lIGdyb3VwX2J5KFJlZ2lvbikgJT4lIHN1bW1hcmlzZShuID0gbigpKQprYWJsZSh0ZXN0KQpgYGAKCmBgYHtyfQpnaW5pX3Byb2dyIDwtICg1My82MikqKDEtNTMvNjIpICsgKDQvNjIpKigxLTQvNjIpICsgKDUvNjIpKigxLTUvNjIpCmdpbmlfcHJvbGUgPC0gKDYvMTE2KSooMS02LzExNikgKyAoNjcvMTE2KSooMS02Ny8xMTYpICsgKDQzLzExNikqKDEtNDMvMTE2KQpnaW5pX2ltcCA8LSAoNjIvMTc4KSAqIGdpbmlfcHJvZ3IgKyAoMTE2LzE3OCkgKiBnaW5pX3Byb2xlCmdpbmlfaW1wCmBgYApUaGUgZ2luaSBpbmRleCBpcyBub3cgMC40MzMwNTYxLCBzbyB3ZSBkaWQgd29yc2UgdGhhbiB0aGUgcHJldmlvdXMgc3BsaXQuCgojIyAxNi4KYGBge3J9CnRlc3QgPC0gZGF0YSAlPiUgZmlsdGVyKEZsYXZhbm9pZHMgPj0gMi4xNjUpICU+JSBncm91cF9ieShSZWdpb24pICU+JSBzdW1tYXJpc2UobiA9IG4oKSkKa2FibGUodGVzdCkKYGBgCgpgYGB7cn0KdGVzdCA8LSBkYXRhICU+JSBmaWx0ZXIoRmxhdmFub2lkcyA8IDIuMTY1KSAlPiUgZ3JvdXBfYnkoUmVnaW9uKSAlPiUgc3VtbWFyaXNlKG4gPSBuKCkpCmthYmxlKHRlc3QpCmBgYAoKYGBge3J9CmdpbmlfcHJvZ3IgPC0gKDU5Lzg4KSooMS01OS84OCkgKyAoMjkvODgpKigxLTI5Lzg4KSArICgwLzg4KSooMS0wLzg4KQpnaW5pX3Byb2xlIDwtICgwLzkwKSooMS0wLzkwKSArICg0Mi85MCkqKDEtNDIvOTApICsgKDQ4LzkwKSooMS00OC85MCkKZ2luaV9pbXAgPC0gKDg4LzE3OCkgKiBnaW5pX3Byb2dyICsgKDkwLzE3OCkgKiBnaW5pX3Byb2xlCmdpbmlfaW1wCmBgYApUaGUgbmV3IEdpbmkgaW5kZXggaXMgMC40NzAxNDgxLCBzbyBvbmNlIGFnYWluIHdlIGRpZCB3b3JzZS4gVGhlIGluaXRpYWwgc3BsaXQgY2hvc2VuIGJ5IHRoZSB0cmFpbigpIGZ1bmN0aW9uIGRpZCB0aGUgYmVzdC4gVGhpcyBmdW5jdGlvbiBsb29wcyB0aHJvdWdoIHBvc3NpYmxlIGluaXRpYWwgc3BsaXRzIGFuZCBjaG9vc2VzIHRoZSBvbmUgdGhhdCByZXN1bHRzIGluIHRoZSBsb3dlc3QgR2luaSBpbmRleCwgc28gYW55IG90aGVyIHNwbGl0IHdpbGwgZ2VuZXJhbGx5IHBlcmZvcm0gdGhlIHNhbWUgb3Igd29yc2UgYXQgdGhhdCBzdGVwLg==