Data Science Stream
Preparations
Load Required Packages
# Specify required packages
ml_packages <- c("caret", "magrittr", "rpart.plot")
# Install missing packages
install.packages(setdiff(ml_packages, rownames(installed.packages())))
# Load all packages
lapply(ml_packages, library, character.only = TRUE)
Predicting Penguin Species
Penguin Data
No answer required.
library(palmerpenguins)
ml_penguins <- na.omit(penguins[, -8]) # note we ignore the year variable here
Aim
Since we have multiple feature variables, and a known outcome variable, we are dealing with a supervised learning multi-class classification problem.
Data Visualisation
featurePlot(x = ml_penguins[, -1], y = ml_penguins$species,
plot = "pairs", auto.key = list(columns = 3))

We observe in several of the scatter plots that the observations for the Adelie and Chinstrap penguins overlap - this could potentially mean that it will be difficult to distinguish between them when classifying penguins.
Pre-Processing the Penguin data
Dummy Variables
# Load a package to help with the restructure of the data
library(tibble)
# Use the dummayVars function to create a full set of dummy variables for the ml_penguins data
dummy_penguins <- dummyVars(species ~ ., data = ml_penguins)
# Use the predict function to update our ml_penguins feature variables with
# both island and sex dummy variables
ml_penguins_updated <- as_tibble(predict(dummy_penguins, newdata = ml_penguins))
# Prepend the outcome variable to our updated data set, otherwise it will be lost
ml_penguins_updated <- cbind(species = ml_penguins$species, ml_penguins_updated)
head(ml_penguins_updated)
## species island.Biscoe island.Dream island.Torgersen bill_length_mm
## 1 Adelie 0 0 1 39.1
## 2 Adelie 0 0 1 39.5
## 3 Adelie 0 0 1 40.3
## 4 Adelie 0 0 1 36.7
## 5 Adelie 0 0 1 39.3
## 6 Adelie 0 0 1 38.9
## bill_depth_mm flipper_length_mm body_mass_g sex.female sex.male
## 1 18.7 181 3750 0 1
## 2 17.4 186 3800 1 0
## 3 18.0 195 3250 1 0
## 4 19.3 193 3450 1 0
## 5 20.6 190 3650 0 1
## 6 17.8 181 3625 1 0
Highly Influential Samples
nearZeroVar(ml_penguins_updated, saveMetrics = F)
## integer(0)
nearZeroVar(ml_penguins_updated, saveMetrics = T)
## freqRatio percentUnique zeroVar nzv
## species 1.226891 0.9009009 FALSE FALSE
## island.Biscoe 1.042945 0.6006006 FALSE FALSE
## island.Dream 1.707317 0.6006006 FALSE FALSE
## island.Torgersen 6.085106 0.6006006 FALSE FALSE
## bill_length_mm 1.166667 48.9489489 FALSE FALSE
## bill_depth_mm 1.200000 23.7237237 FALSE FALSE
## flipper_length_mm 1.235294 16.2162162 FALSE FALSE
## body_mass_g 1.200000 27.9279279 FALSE FALSE
## sex.female 1.018182 0.6006006 FALSE FALSE
## sex.male 1.018182 0.6006006 FALSE FALSE
nearZeroVar(ml_penguins_updated, saveMetrics = T, freqCut = 2, uniqueCut = 5)
## freqRatio percentUnique zeroVar nzv
## species 1.226891 0.9009009 FALSE FALSE
## island.Biscoe 1.042945 0.6006006 FALSE FALSE
## island.Dream 1.707317 0.6006006 FALSE FALSE
## island.Torgersen 6.085106 0.6006006 FALSE TRUE
## bill_length_mm 1.166667 48.9489489 FALSE FALSE
## bill_depth_mm 1.200000 23.7237237 FALSE FALSE
## flipper_length_mm 1.235294 16.2162162 FALSE FALSE
## body_mass_g 1.200000 27.9279279 FALSE FALSE
## sex.female 1.018182 0.6006006 FALSE FALSE
## sex.male 1.018182 0.6006006 FALSE FALSE
There do not appear to be any feature variables which need to be removed due to their freqCut
and uniqueCut
values. While the variables for the different islands and sexes have low percentUnique
values, and in the case of the island.Torgersen
variable a high freqRatio
value,
this is not cause for concern as these are dummy variables.
base_cor <- cor(ml_penguins_updated[, 5:8])
extreme_cor <- sum(abs(base_cor[upper.tri(base_cor)]) > .999)
extreme_cor
## [1] 0
The result of 0 here tells us that no feature variables have extremely high correlation with other feature variables.
base_cor
## bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
## bill_length_mm 1.0000000 -0.2286256 0.6530956 0.5894511
## bill_depth_mm -0.2286256 1.0000000 -0.5777917 -0.4720157
## flipper_length_mm 0.6530956 -0.5777917 1.0000000 0.8729789
## body_mass_g 0.5894511 -0.4720157 0.8729789 1.0000000
The largest negative correlation value is -0.5777917, between flipper_length_mm
and bill_depth_mm
.
The largest positive correlation value is 0.8729789, between flipper_length_mm
and body_mass_g
.
The correlation value of could potentially be problematic.
ml_penguins_filtered <- ml_penguins_updated[, - 7] # flipper_length_mm has been removed
Training and Validation Data
set.seed(1650)
train_index <- createDataPartition(ml_penguins_filtered$species,
p = .8, # here p designates the split - 80/20
list = FALSE, times = 1)
penguin_train <- ml_penguins_filtered[train_index, ]
penguin_validate <- ml_penguins_filtered[-train_index, ]
Fitting a Decision Tree Machine Learning Model
Decision Tree
set.seed(1650)
penguin_decision_tree <- train(species ~ .,
data = penguin_train,
method = "rpart")
penguin_decision_tree
## CART
##
## 268 samples
## 8 predictor
## 3 classes: 'Adelie', 'Chinstrap', 'Gentoo'
##
## No pre-processing
## Resampling: Bootstrapped (25 reps)
## Summary of sample sizes: 268, 268, 268, 268, 268, 268, ...
## Resampling results across tuning parameters:
##
## cp Accuracy Kappa
## 0.01324503 0.9436108 0.9117821
## 0.35761589 0.8089728 0.6885822
## 0.56291391 0.5649392 0.2597508
##
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was cp = 0.01324503.
rpart.plot(penguin_decision_tree$finalModel)

Validating Results
# Load magrittr package for piping
library(magrittr)
# count number of observations in validation data
validation_numbers <- nrow(penguin_validate)
# Use the fitted model to predict quality values given the validation data
predict_penguin_decision_tree <- predict(penguin_decision_tree,
newdata =penguin_validate)
# When run, the code below gives us the percentage of correct predictions
dec_tree_accuracy <- sum(predict_penguin_decision_tree ==
penguin_validate$species) / validation_numbers * 100
dec_tree_accuracy %>% round(2)
## [1] 92.31
The decision tree ML model we have trained appears to do an excellent job of predicting the penguin species
, for both the training and validation data sets (94.36% accuracy and 92.31% accuracy respectively).
Great work, that’s everything for today. Don’t worry if you did not complete everything in the designated lab time, there is a lot to learn!
References
Thulin, M. 2021. Modern Statistics with R: From Wrangling and Exploring Data to Inference and Predictive Modelling.
These notes have been prepared by Rupert Kuveke. Please note that some of the content in these notes has been developed from content in Thulin (2021). The copyright for the material in these notes resides with the authors named above, with the Department of Mathematical and Physical Sciences and with La Trobe University. Copyright in this work is vested in La Trobe University including all La Trobe University branding and naming. Unless otherwise stated, material within this work is licensed under a Creative Commons Attribution-Non Commercial-Non Derivatives License
BY-NC-ND.
LS0tDQp0aXRsZTogIlNUTTEwMDE6IENvbXB1dGVyIExhYiA5QiBTb2x1dGlvbnMiDQpvdXRwdXQ6DQogIGJvb2tkb3duOjpodG1sX2RvY3VtZW50MjogDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRoZW1lOiByZWFkYWJsZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KYmlibGlvZ3JhcGh5OiBTVE0xMDAxX0RTX0NMX3JlZmVyZW5jZXMuYmliIA0KbGluay1jaXRhdGlvbnM6IHllcw0KLS0tDQoNCjxzdHlsZT4NCiNUT0Mgew0KICBiYWNrZ3JvdW5kOiB1cmwoImh0dHBzOi8vd3d3LmxhdHJvYmUuZWR1LmF1L19tZWRpYS9sYS10cm9iZS1hcGkvdjUvaW1nL2xvZ28uc3ZnIik7DQogIGJhY2tncm91bmQtc2l6ZTogY29udGFpbjsNCiAgcGFkZGluZy10b3A6IDgwcHggIWltcG9ydGFudDsNCiAgYmFja2dyb3VuZC1yZXBlYXQ6IG5vLXJlcGVhdDsNCn0NCjwvc3R5bGU+DQoNCiMjIyBEYXRhIFNjaWVuY2UgU3RyZWFtIHstfQ0KDQojIyMgVG9waWMgOUI6IE1hY2hpbmUgTGVhcm5pbmcgSSB7LX0NCg0KPGJyPg0KDQpFeGFtcGxlIFIgY29kZSBzb2x1dGlvbnMgZm9yIHRoZSBbRGF0YSBTY2llbmNlIENvbXB1dGVyIExhYiA5XShodHRwczovL3JwdWJzLmNvbS9MVFVfU1RNMTAwMS9EU01DTDkpIGFyZSBwcmVzZW50ZWQgYmVsb3cuDQoNCjxicj4NCg0KIyBQcmVwYXJhdGlvbnMgeyNwcmVwfQ0KDQojIyBMb2FkIFJlcXVpcmVkIFBhY2thZ2VzIHsjbG9hZH0NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBGLCBpbmNsdWRlID0gVCwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KIyBTcGVjaWZ5IHJlcXVpcmVkIHBhY2thZ2VzDQptbF9wYWNrYWdlcyA8LSBjKCJjYXJldCIsICJtYWdyaXR0ciIsICJycGFydC5wbG90IikNCiMgSW5zdGFsbCBtaXNzaW5nIHBhY2thZ2VzDQppbnN0YWxsLnBhY2thZ2VzKHNldGRpZmYobWxfcGFja2FnZXMsIHJvd25hbWVzKGluc3RhbGxlZC5wYWNrYWdlcygpKSkpDQojIExvYWQgYWxsIHBhY2thZ2VzDQpsYXBwbHkobWxfcGFja2FnZXMsIGxpYnJhcnksIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkNCmBgYA0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBGLCBpbmNsdWRlID0gRiwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KIyBTcGVjaWZ5IHJlcXVpcmVkIHBhY2thZ2VzDQptbF9wYWNrYWdlcyA8LSBjKCJjYXJldCIsICJtYWdyaXR0ciIsICJycGFydC5wbG90IikNCiMgSW5zdGFsbCBtaXNzaW5nIHBhY2thZ2VzDQppbnN0YWxsLnBhY2thZ2VzKHNldGRpZmYobWxfcGFja2FnZXMsIHJvd25hbWVzKGluc3RhbGxlZC5wYWNrYWdlcygpKSkpDQojIExvYWQgYWxsIHBhY2thZ2VzDQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGFwcGx5KG1sX3BhY2thZ2VzLCBsaWJyYXJ5LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpKQ0KYGBgDQoNCiMgUHJlZGljdGluZyBQZW5ndWluIFNwZWNpZXMNCg0KIyMgUGVuZ3VpbiBEYXRhIHsjcG5lZ2lufQ0KDQpObyBhbnN3ZXIgcmVxdWlyZWQuDQoNCiMjDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCmxpYnJhcnkocGFsbWVycGVuZ3VpbnMpDQoNCm1sX3Blbmd1aW5zIDwtIG5hLm9taXQocGVuZ3VpbnNbLCAtOF0pICMgbm90ZSB3ZSBpZ25vcmUgdGhlIHllYXIgdmFyaWFibGUgaGVyZQ0KYGBgDQoNCiMjIEFpbQ0KDQpTaW5jZSB3ZSBoYXZlIG11bHRpcGxlIGZlYXR1cmUgdmFyaWFibGVzLCBhbmQgYSBrbm93biBvdXRjb21lIHZhcmlhYmxlLCB3ZSBhcmUgZGVhbGluZyB3aXRoIGEgc3VwZXJ2aXNlZCBsZWFybmluZyBtdWx0aS1jbGFzcyBjbGFzc2lmaWNhdGlvbiBwcm9ibGVtLg0KDQojIyBEYXRhIFZpc3VhbGlzYXRpb24gIHsjcHJvYmxlbX0NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGLCBmaWcuZGltID0gYygxMiwgMTIpLCBmaWcuYWxpZ249J2NlbnRlcid9DQpmZWF0dXJlUGxvdCh4ID0gbWxfcGVuZ3VpbnNbLCAtMV0sIHkgPSBtbF9wZW5ndWlucyRzcGVjaWVzLCANCiAgICAgICAgICAgIHBsb3QgPSAicGFpcnMiLCBhdXRvLmtleSA9IGxpc3QoY29sdW1ucyA9IDMpKQ0KYGBgDQoNCiMjIw0KDQpXZSBvYnNlcnZlIGluIHNldmVyYWwgb2YgdGhlIHNjYXR0ZXIgcGxvdHMgdGhhdCB0aGUgb2JzZXJ2YXRpb25zIGZvciB0aGUgQWRlbGllIGFuZCBDaGluc3RyYXAgcGVuZ3VpbnMgb3ZlcmxhcCAtIHRoaXMgY291bGQgcG90ZW50aWFsbHkgbWVhbiB0aGF0IGl0IHdpbGwgYmUgZGlmZmljdWx0IHRvIGRpc3Rpbmd1aXNoIGJldHdlZW4gdGhlbSB3aGVuIGNsYXNzaWZ5aW5nIHBlbmd1aW5zLg0KDQojIFByZS1Qcm9jZXNzaW5nIHRoZSBQZW5ndWluIGRhdGEgeyNwcmVwcm99DQoNCiMjIER1bW15IFZhcmlhYmxlcyB7I2R1bW15fQ0KDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCiMgTG9hZCBhIHBhY2thZ2UgdG8gaGVscCB3aXRoIHRoZSByZXN0cnVjdHVyZSBvZiB0aGUgZGF0YQ0KbGlicmFyeSh0aWJibGUpIA0KDQojIFVzZSB0aGUgZHVtbWF5VmFycyBmdW5jdGlvbiB0byBjcmVhdGUgYSBmdWxsIHNldCBvZiBkdW1teSB2YXJpYWJsZXMgZm9yIHRoZSBtbF9wZW5ndWlucyBkYXRhDQpkdW1teV9wZW5ndWlucyA8LSBkdW1teVZhcnMoc3BlY2llcyB+IC4sIGRhdGEgPSBtbF9wZW5ndWlucykNCg0KIyBVc2UgdGhlIHByZWRpY3QgZnVuY3Rpb24gdG8gdXBkYXRlIG91ciBtbF9wZW5ndWlucyBmZWF0dXJlIHZhcmlhYmxlcyB3aXRoIA0KIyBib3RoIGlzbGFuZCBhbmQgc2V4IGR1bW15IHZhcmlhYmxlcw0KbWxfcGVuZ3VpbnNfdXBkYXRlZCA8LSBhc190aWJibGUocHJlZGljdChkdW1teV9wZW5ndWlucywgbmV3ZGF0YSA9IG1sX3Blbmd1aW5zKSkNCg0KIyBQcmVwZW5kIHRoZSBvdXRjb21lIHZhcmlhYmxlIHRvIG91ciB1cGRhdGVkIGRhdGEgc2V0LCBvdGhlcndpc2UgaXQgd2lsbCBiZSBsb3N0DQptbF9wZW5ndWluc191cGRhdGVkIDwtIGNiaW5kKHNwZWNpZXMgPSBtbF9wZW5ndWlucyRzcGVjaWVzLCBtbF9wZW5ndWluc191cGRhdGVkKQ0KYGBgDQoNCiMjDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCmhlYWQobWxfcGVuZ3VpbnNfdXBkYXRlZCkNCmBgYA0KDQojIyBIaWdobHkgSW5mbHVlbnRpYWwgU2FtcGxlcyB7I2V4Y2Vzc2l2ZWluZmx1ZW5jZX0NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KbmVhclplcm9WYXIobWxfcGVuZ3VpbnNfdXBkYXRlZCwgc2F2ZU1ldHJpY3MgPSBGKQ0KYGBgDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCm5lYXJaZXJvVmFyKG1sX3Blbmd1aW5zX3VwZGF0ZWQsIHNhdmVNZXRyaWNzID0gVCkNCmBgYA0KDQojIyMgeyNjdXRvZmZ9DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCm5lYXJaZXJvVmFyKG1sX3Blbmd1aW5zX3VwZGF0ZWQsIHNhdmVNZXRyaWNzID0gVCwgZnJlcUN1dCA9IDIsIHVuaXF1ZUN1dCA9IDUpDQpgYGANCg0KIyMjDQoNClRoZXJlIGRvIG5vdCBhcHBlYXIgdG8gYmUgYW55IGZlYXR1cmUgdmFyaWFibGVzIHdoaWNoIG5lZWQgdG8gYmUgcmVtb3ZlZCBkdWUgdG8gdGhlaXIgYGZyZXFDdXRgIGFuZCBgdW5pcXVlQ3V0YCB2YWx1ZXMuIFdoaWxlIHRoZSB2YXJpYWJsZXMgZm9yIHRoZSBkaWZmZXJlbnQgaXNsYW5kcyBhbmQgc2V4ZXMgaGF2ZSBsb3cgYHBlcmNlbnRVbmlxdWVgIHZhbHVlcywgYW5kIGluIHRoZSBjYXNlIG9mIHRoZSBgaXNsYW5kLlRvcmdlcnNlbmAgdmFyaWFibGUgYSBoaWdoIGBmcmVxUmF0aW9gIHZhbHVlLA0KdGhpcyBpcyBub3QgY2F1c2UgZm9yIGNvbmNlcm4gYXMgdGhlc2UgYXJlIGR1bW15IHZhcmlhYmxlcy4NCg0KIyMjDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCmJhc2VfY29yIDwtICBjb3IobWxfcGVuZ3VpbnNfdXBkYXRlZFssIDU6OF0pDQpleHRyZW1lX2NvciA8LSBzdW0oYWJzKGJhc2VfY29yW3VwcGVyLnRyaShiYXNlX2NvcildKSA+IC45OTkpDQpleHRyZW1lX2Nvcg0KYGBgDQoNClRoZSByZXN1bHQgb2YgMCBoZXJlIHRlbGxzIHVzIHRoYXQgbm8gZmVhdHVyZSB2YXJpYWJsZXMgaGF2ZSBleHRyZW1lbHkgaGlnaCBjb3JyZWxhdGlvbiB3aXRoIG90aGVyIGZlYXR1cmUgdmFyaWFibGVzLg0KDQojIyMgDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCmJhc2VfY29yIA0KYGBgDQoNClRoZSBsYXJnZXN0IG5lZ2F0aXZlIGNvcnJlbGF0aW9uIHZhbHVlIGlzIC0wLjU3Nzc5MTcsIGJldHdlZW4gYGZsaXBwZXJfbGVuZ3RoX21tYCBhbmQgYGJpbGxfZGVwdGhfbW1gLg0KVGhlIGxhcmdlc3QgcG9zaXRpdmUgY29ycmVsYXRpb24gdmFsdWUgaXMgMC44NzI5Nzg5LCBiZXR3ZWVuIGBmbGlwcGVyX2xlbmd0aF9tbWAgYW5kIGBib2R5X21hc3NfZ2AuDQoNClRoZSBjb3JyZWxhdGlvbiB2YWx1ZSBvZiBjb3VsZCBwb3RlbnRpYWxseSBiZSBwcm9ibGVtYXRpYy4NCg0KDQojIyMgeyNjb3JsaW1pdH0NCg0KTm8gYW5zd2VyIHJlcXVpcmVkLg0KDQojIyMNCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgd2FybmluZyA9IEZ9DQptbF9wZW5ndWluc19maWx0ZXJlZCA8LSBtbF9wZW5ndWluc191cGRhdGVkWywgLSA3XSAjIGZsaXBwZXJfbGVuZ3RoX21tIGhhcyBiZWVuIHJlbW92ZWQNCmBgYA0KDQojIFRyYWluaW5nIGFuZCBWYWxpZGF0aW9uIERhdGEgeyN0cmFpbn0NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0Kc2V0LnNlZWQoMTY1MCkNCnRyYWluX2luZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24obWxfcGVuZ3VpbnNfZmlsdGVyZWQkc3BlY2llcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcCA9IC44LCAjIGhlcmUgcCBkZXNpZ25hdGVzIHRoZSBzcGxpdCAtIDgwLzIwDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QgPSBGQUxTRSwgdGltZXMgPSAxKSANCmBgYA0KDQojIw0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQpwZW5ndWluX3RyYWluIDwtIG1sX3Blbmd1aW5zX2ZpbHRlcmVkW3RyYWluX2luZGV4LCBdDQpwZW5ndWluX3ZhbGlkYXRlIDwtIG1sX3Blbmd1aW5zX2ZpbHRlcmVkWy10cmFpbl9pbmRleCwgXQ0KYGBgDQoNCiMgRml0dGluZyBhIERlY2lzaW9uIFRyZWUgTWFjaGluZSBMZWFybmluZyBNb2RlbCB7I2ZpdH0NCg0KIyMgRGVjaXNpb24gVHJlZSB7I2RlY3RyZWV9DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRiwgY2FjaGUgPSBUfQ0Kc2V0LnNlZWQoMTY1MCkgDQpwZW5ndWluX2RlY2lzaW9uX3RyZWUgPC0gdHJhaW4oc3BlY2llcyB+IC4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHBlbmd1aW5fdHJhaW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJwYXJ0IikNCnBlbmd1aW5fZGVjaXNpb25fdHJlZQ0KYGBgDQoNCiMjIw0KDQpObyBhbnN3ZXIgcmVxdWlyZWQuDQoNCiMjIw0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEYsIGZpZy5kaW0gPSBjKDgsOCksIGZpZy5hbGlnbj0nY2VudGVyJ30NCnJwYXJ0LnBsb3QocGVuZ3Vpbl9kZWNpc2lvbl90cmVlJGZpbmFsTW9kZWwpDQpgYGANCg0KIyBWYWxpZGF0aW5nIFJlc3VsdHMgeyN2YWx9DQoNCiMjDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCiMgTG9hZCBtYWdyaXR0ciBwYWNrYWdlIGZvciBwaXBpbmcNCmxpYnJhcnkobWFncml0dHIpDQoNCiMgY291bnQgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpbiB2YWxpZGF0aW9uIGRhdGENCnZhbGlkYXRpb25fbnVtYmVycyA8LSBucm93KHBlbmd1aW5fdmFsaWRhdGUpDQoNCiMgVXNlIHRoZSBmaXR0ZWQgbW9kZWwgdG8gcHJlZGljdCBxdWFsaXR5IHZhbHVlcyBnaXZlbiB0aGUgdmFsaWRhdGlvbiBkYXRhDQpwcmVkaWN0X3Blbmd1aW5fZGVjaXNpb25fdHJlZSA8LSBwcmVkaWN0KHBlbmd1aW5fZGVjaXNpb25fdHJlZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID1wZW5ndWluX3ZhbGlkYXRlKQ0KIyBXaGVuIHJ1biwgdGhlIGNvZGUgYmVsb3cgZ2l2ZXMgdXMgdGhlIHBlcmNlbnRhZ2Ugb2YgY29ycmVjdCBwcmVkaWN0aW9ucw0KZGVjX3RyZWVfYWNjdXJhY3kgPC0gc3VtKHByZWRpY3RfcGVuZ3Vpbl9kZWNpc2lvbl90cmVlID09IA0KICAgICAgICAgICAgICAgICAgICAgICAgIHBlbmd1aW5fdmFsaWRhdGUkc3BlY2llcykgLyB2YWxpZGF0aW9uX251bWJlcnMgKiAxMDANCg0KZGVjX3RyZWVfYWNjdXJhY3kgJT4lIHJvdW5kKDIpIA0KYGBgDQoNCiMjDQoNClRoZSBkZWNpc2lvbiB0cmVlIE1MIG1vZGVsIHdlIGhhdmUgdHJhaW5lZCBhcHBlYXJzIHRvIGRvIGFuIGV4Y2VsbGVudCBqb2Igb2YgcHJlZGljdGluZyB0aGUgcGVuZ3VpbiBgc3BlY2llc2AsIGZvciBib3RoIHRoZSB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBkYXRhIHNldHMgKGByIHJvdW5kKDEwMCptYXgocGVuZ3Vpbl9kZWNpc2lvbl90cmVlJHJlc3VsdHMkQWNjdXJhY3kpLCAyKWAlIGFjY3VyYWN5IGFuZCBgciBkZWNfdHJlZV9hY2N1cmFjeSAlPiUgcm91bmQoMilgJSBhY2N1cmFjeSByZXNwZWN0aXZlbHkpLg0KDQo8YnI+DQoNCiMjIyMgR3JlYXQgd29yaywgdGhhdCdzIGV2ZXJ5dGhpbmcgZm9yIHRvZGF5LiBEb24ndCB3b3JyeSBpZiB5b3UgZGlkIG5vdCBjb21wbGV0ZSBldmVyeXRoaW5nIGluIHRoZSBkZXNpZ25hdGVkIGxhYiB0aW1lLCB0aGVyZSBpcyBhIGxvdCB0byBsZWFybiEgIyMjIyB7LX0NCg0KPGJyPg0KDQojIFJlZmVyZW5jZXMgey0gI1JlZn0NCjxkaXYgaWQ9InJlZnMiPjwvZGl2Pg0KDQo8YnI+DQoNCjxmb250IGNvbG9yID0gImdyZXkiPg0KVGhlc2Ugbm90ZXMgaGF2ZSBiZWVuIHByZXBhcmVkIGJ5IFJ1cGVydCBLdXZla2UuIFBsZWFzZSBub3RlIHRoYXQgc29tZSBvZiB0aGUgY29udGVudCBpbiB0aGVzZSBub3RlcyBoYXMgYmVlbiBkZXZlbG9wZWQgZnJvbSBjb250ZW50IGluIEBNb2RTdGF0LiBUaGUgY29weXJpZ2h0IGZvciB0aGUgbWF0ZXJpYWwgaW4gdGhlc2Ugbm90ZXMgcmVzaWRlcyB3aXRoIHRoZSBhdXRob3JzIG5hbWVkIGFib3ZlLCB3aXRoIHRoZSBEZXBhcnRtZW50IG9mIE1hdGhlbWF0aWNhbCBhbmQgUGh5c2ljYWwgU2NpZW5jZXMgYW5kIHdpdGggTGEgVHJvYmUgVW5pdmVyc2l0eS4gQ29weXJpZ2h0IGluIHRoaXMgd29yayBpcyB2ZXN0ZWQgaW4gTGEgVHJvYmUgVW5pdmVyc2l0eSBpbmNsdWRpbmcgYWxsIExhIFRyb2JlIFVuaXZlcnNpdHkgYnJhbmRpbmcgYW5kIG5hbWluZy4gVW5sZXNzIG90aGVyd2lzZSBzdGF0ZWQsIG1hdGVyaWFsIHdpdGhpbiB0aGlzIHdvcmsgaXMgbGljZW5zZWQgdW5kZXIgYSBDcmVhdGl2ZSBDb21tb25zIEF0dHJpYnV0aW9uLU5vbiBDb21tZXJjaWFsLU5vbiBEZXJpdmF0aXZlcyBMaWNlbnNlIA0KPGEgaHJlZiA9ICJodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtbmQvNC4wL0NDIiB0YXJnZXQ9Il9ibGFuayI+IEJZLU5DLU5ELiA8L2E+DQo8L2ZvbnQ+