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)
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)
| 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)
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)
test <- data %>% filter(Proline < 755) %>% group_by(Region) %>% summarise(n = n())
kable(test)
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)
test <- data %>% filter(Proline < 800) %>% group_by(Region) %>% summarise(n = n())
kable(test)
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)
test <- data %>% filter(Flavanoids < 2.165) %>% group_by(Region) %>% summarise(n = n())
kable(test)
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==