1 Introduction

The plotLearnerPrediction() function consists of an integrated procedure:

  • Training and cross-validation: The learner will be trained many times upon 75% of the original dataset in classification Task. For each iteration, trained model will be tested on either train subset (75%) and test subset (remaning 25% of original dataset). The model’s performance (by default: mean missclassification error- mmce; it could be customised by user to include more performance metrics) will be averaged. The learner’s name and its cross-validation result will be reported as subtitle on the final graph.

  • A two dimensional data space will be set from X and Y variables as introduced by the ‘features’ arguments. Then a scatter dot plot will be generated by geom_point() function in ggplot2.

  • Prediction boundaries will be generated using geom_tile() function in ggplot2 with either predicted probability (when available) or predicted classes. When probabilities are used, the prediction will be labelled by color fill and alpha is coded by probability values.

The final output consists of a ggplot2 object and therefore could be customised by scale_fill_color() or ggplot2’s themes setting.

2 Example: The IRIS classification task

Here is the association between Sepal’s length and width and the two dimensional distribution of iris species in our datset:

library(ggplot2)

ggplot(iris,aes(x=Sepal.Length,y=Sepal.Width))+stat_density2d(geom="polygon",aes(fill=iris$Species,alpha = ..level..))+geom_point(aes(shape=Species),color="black",size=2)+theme_bw()+scale_fill_manual(values=c("#ff0061","#11a6fc","#ffae00"))

The same analysis could be done for Petal’s length and width:

ggplot(iris,aes(x=Petal.Length,y=Petal.Width))+stat_density2d(geom="polygon",aes(fill=iris$Species,alpha = ..level..))+geom_point(aes(shape=Species),color="black",size=2)+theme_bw()+scale_fill_manual(values=c("#ff0061","#11a6fc","#ffae00"))

As usual, we will set a classification task in mlr then introduce some learners

#Make a multiclass classification task in mlr

library(mlr)
## Loading required package: ParamHelpers
## Warning: replacing previous import 'BBmisc::isFALSE' by
## 'backports::isFALSE' when loading 'mlr'
taskiris=makeClassifTask(id="iris",data=iris,target="Species")

# Making 7 different Learners (algorithm)

learnerCART=makeLearner(id="CART","classif.rpart", predict.type = "prob")

learnerRF=makeLearner(id="RF","classif.randomForestSRC", predict.type = "prob")

learnerSVM=makeLearner(id="SVM","classif.svm", predict.type = "prob")

learnerGBM=makeLearner(id="GBM","classif.gbm", predict.type = "prob")

learnerGLMN=makeLearner(id="Elasticnet","classif.glmnet", predict.type = "prob")

learnerKNN=makeLearner(id="KNN","classif.knn")

learnerLDA=makeLearner(id="LDA","classif.lda", predict.type = "prob")

3 The syntax of plotLearnerPrediction( ) function

** plotLearnerPrediction(learner, task, features = NULL, measures, cv = 10L, …, gridsize, pointsize = 2, prob.alpha = TRUE, se.band = TRUE, err.col =”white“, greyscale = FALSE) **

Where:

  • learner is object’s name for learner task is the object name for task

  • features argument : up to 2 features could be introduced here. By default the first 2 features are used

  • measures indicate Performance measure(s) to evaluate. Default is the default measure for the task

  • cv for setting the cross-validation and reporting its result as plot title. Number of folds. cv=0 means no CV. Default is 10.

  • gridsize is the grid resolution per axis for background predictions. Default is 100 for 2D.

  • Pointsize for ggplot2 geom_point for data points. Default is 2.

  • prob.alpha is a logical argument, for setting alpha value of background to probability for predicted class? Allows visualization of “confidence” for prediction. If not, only a constant color is displayed in the background for the predicted label. Default is TRUE.

  • se.band: For regression in 1D: Show band for standard error estimation? Default is TRUE.

  • err.col: For classification, Color of misclassified data points. Default is “white”

  • greyscale is a logical argument: Should the plot be greyscale completely? Default is FALSE

CART algorithm

plotLearnerPrediction(learnerCART,taskiris,features=c("Sepal.Length","Sepal.Width"),cv=100L,gridsize=100)+scale_fill_manual(values=c("#ff0061","#11a6fc","#ffae00"))+theme_bw()

plotLearnerPrediction(learnerCART,taskiris,features=c("Petal.Length","Petal.Width"),cv=100L,gridsize=100)+scale_fill_manual(values=c("#ff0061","#11a6fc","#ffae00"))+theme_bw()

** Support vector machine algorithm **

plotLearnerPrediction(learnerSVM,taskiris,features=c("Sepal.Length","Sepal.Width"),cv=100L,gridsize=100)+scale_fill_manual(values=c("#ff0061","#11a6fc","#ffae00"))+theme_bw()

plotLearnerPrediction(learnerSVM,taskiris,features=c("Petal.Length","Petal.Width"),cv=100L,gridsize=100)+scale_fill_manual(values=c("#ff0061","#11a6fc","#ffae00"))+theme_bw()

** Gradient boosting machine algorithm **

plotLearnerPrediction(learnerGBM,taskiris,features=c("Sepal.Length","Sepal.Width"),cv=100L,gridsize=100)+scale_fill_manual(values=c("#ff0061","#11a6fc","#ffae00"))+theme_bw()
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...

plotLearnerPrediction(learnerGBM,taskiris,features=c("Petal.Length","Petal.Width"),cv=100L,gridsize=100)+scale_fill_manual(values=c("#ff0061","#11a6fc","#ffae00"))+theme_bw()
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...
## Distribution not specified, assuming multinomial ...

Elastic net (logistic) algorithm

plotLearnerPrediction(learnerGLMN,taskiris,features=c("Sepal.Length","Sepal.Width"),cv=100L,gridsize=100)+scale_fill_manual(values=c("#ff0061","#11a6fc","#ffae00"))+theme_bw()

plotLearnerPrediction(learnerGLMN,taskiris,features=c("Petal.Length","Petal.Width"),cv=100L,gridsize=100)+scale_fill_manual(values=c("#ff0061","#11a6fc","#ffae00"))+theme_bw()

** KNN algorithm**

plotLearnerPrediction(learnerKNN,taskiris,features=c("Sepal.Length","Sepal.Width"),cv=100L,gridsize=100)+scale_fill_manual(values=c("#ff0061","#11a6fc","#ffae00"))+theme_bw()

plotLearnerPrediction(learnerKNN,taskiris,features=c("Petal.Length","Petal.Width"),cv=100L,gridsize=100)+scale_fill_manual(values=c("#ff0061","#11a6fc","#ffae00"))+theme_bw()

LDA algorithm

plotLearnerPrediction(learnerLDA,taskiris,features=c("Sepal.Length","Sepal.Width"),cv=100L,gridsize=100)+scale_fill_manual(values=c("#ff0061","#11a6fc","#ffae00"))+theme_bw()

plotLearnerPrediction(learnerLDA,taskiris,features=c("Petal.Length","Petal.Width"),cv=100L,gridsize=100)+scale_fill_manual(values=c("#ff0061","#11a6fc","#ffae00"))+theme_bw()

Random Forest algorithm

plotLearnerPrediction(learnerRF,taskiris,features=c("Sepal.Length","Sepal.Width"),cv=100L,gridsize=100)+scale_fill_manual(values=c("#ff0061","#11a6fc","#ffae00"))+theme_bw()

plotLearnerPrediction(learnerRF,taskiris,features=c("Petal.Length","Petal.Width"),cv=100L,gridsize=100)+scale_fill_manual(values=c("#ff0061","#11a6fc","#ffae00"))+theme_bw()

4 Conclusion

plotLearnerPrediction() is a hidden tool in mlr package. This useful function allows to generate beautiful plots for the illustration purpose. These plots provide many information, such as:

  • A visual perception of model’s performance: its ability to classify the instances into two or more classes, the correct classification and error rates.

  • A visual presentation of the underlying mechanism of the model, via the prediction boundaries

  • Assocation between two features and their contribution to model’s prediction.

  • Numerical result of cross-validation: averaged model’s performance metrics

LS0tCnRpdGxlOiA8Y2VudGVyPiBBIGhpZGRlbiBmdW5jdGlvbiBpbiBNTFIgPC9jZW50ZXI+CnN1YnRpdGxlOiA8Y2VudGVyPiBwbG90TGVhcm5lclByZWRpY3Rpb24oKSBmdW5jdGlvbiA8L2NlbnRlcj4KYXV0aG9yOiA8Y2VudGVyPiBPbGVnIEJheWRha292IDwvY2VudGVyPgpkYXRlOiA8Y2VudGVyPiBEZWNlbWJlciAwMiwgMjAxNyA8L2NlbnRlcj4Kb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OiAKICAgIGNvZGVfZG93bmxvYWQ6IHllcwogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBkZl9wcmludDoga2FibGUKICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICB0aGVtZTogZmxhdGx5CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKLS0tCgo8Y2VudGVyPiFbXShoaWRkZW5fZnVuY3Rpb25fbWxyLnBuZyl7IHdpZHRoPTUwJX08L2NlbnRlcj4KPGJyPgoKIyBJbnRyb2R1Y3Rpb24KClRoZSAqKnBsb3RMZWFybmVyUHJlZGljdGlvbigpKiogZnVuY3Rpb24gY29uc2lzdHMgb2YgYW4gaW50ZWdyYXRlZCBwcm9jZWR1cmU6CgorIFRyYWluaW5nIGFuZCBjcm9zcy12YWxpZGF0aW9uOiBUaGUgbGVhcm5lciB3aWxsIGJlIHRyYWluZWQgbWFueSB0aW1lcyB1cG9uIDc1JSBvZiB0aGUgb3JpZ2luYWwgZGF0YXNldCBpbiBjbGFzc2lmaWNhdGlvbiBUYXNrLiBGb3IgZWFjaCBpdGVyYXRpb24sIHRyYWluZWQgbW9kZWwgd2lsbCBiZSB0ZXN0ZWQgb24gZWl0aGVyIHRyYWluIHN1YnNldCAoNzUlKSBhbmQgdGVzdCBzdWJzZXQgKHJlbWFuaW5nIDI1JSBvZiBvcmlnaW5hbCBkYXRhc2V0KS4gVGhlIG1vZGVs4oCZcyBwZXJmb3JtYW5jZSAoYnkgZGVmYXVsdDogbWVhbiBtaXNzY2xhc3NpZmljYXRpb24gZXJyb3ItIG1tY2U7IGl0IGNvdWxkIGJlIGN1c3RvbWlzZWQgYnkgdXNlciB0byBpbmNsdWRlIG1vcmUgcGVyZm9ybWFuY2UgbWV0cmljcykgd2lsbCBiZSBhdmVyYWdlZC4gVGhlIGxlYXJuZXLigJlzIG5hbWUgYW5kIGl0cyBjcm9zcy12YWxpZGF0aW9uIHJlc3VsdCB3aWxsIGJlIHJlcG9ydGVkIGFzIHN1YnRpdGxlIG9uIHRoZSBmaW5hbCBncmFwaC4KCisgQSB0d28gZGltZW5zaW9uYWwgZGF0YSBzcGFjZSB3aWxsIGJlIHNldCBmcm9tIFggYW5kIFkgdmFyaWFibGVzIGFzIGludHJvZHVjZWQgYnkgdGhlIOKAmGZlYXR1cmVz4oCZIGFyZ3VtZW50cy4gVGhlbiBhIHNjYXR0ZXIgZG90IHBsb3Qgd2lsbCBiZSBnZW5lcmF0ZWQgYnkgZ2VvbV9wb2ludCgpIGZ1bmN0aW9uIGluIGdncGxvdDIuCgorIFByZWRpY3Rpb24gYm91bmRhcmllcyB3aWxsIGJlIGdlbmVyYXRlZCB1c2luZyBnZW9tX3RpbGUoKSBmdW5jdGlvbiBpbiBnZ3Bsb3QyIHdpdGggZWl0aGVyIHByZWRpY3RlZCBwcm9iYWJpbGl0eSAod2hlbiBhdmFpbGFibGUpIG9yIHByZWRpY3RlZCBjbGFzc2VzLiBXaGVuIHByb2JhYmlsaXRpZXMgYXJlIHVzZWQsIHRoZSBwcmVkaWN0aW9uIHdpbGwgYmUgbGFiZWxsZWQgYnkgY29sb3IgZmlsbCBhbmQgYWxwaGEgaXMgY29kZWQgYnkgcHJvYmFiaWxpdHkgdmFsdWVzLgoKVGhlIGZpbmFsIG91dHB1dCBjb25zaXN0cyBvZiBhIGdncGxvdDIgb2JqZWN0IGFuZCB0aGVyZWZvcmUgY291bGQgYmUgY3VzdG9taXNlZCBieSBzY2FsZV9maWxsX2NvbG9yKCkgb3IgZ2dwbG90MuKAmXMgdGhlbWVzIHNldHRpbmcuCgojIEV4YW1wbGU6IFRoZSBJUklTIGNsYXNzaWZpY2F0aW9uIHRhc2sKSGVyZSBpcyB0aGUgYXNzb2NpYXRpb24gYmV0d2VlbiBTZXBhbOKAmXMgbGVuZ3RoIGFuZCB3aWR0aCBhbmQgdGhlIHR3byBkaW1lbnNpb25hbCBkaXN0cmlidXRpb24gb2YgaXJpcyBzcGVjaWVzIGluIG91ciBkYXRzZXQ6CmBgYHtyfQpsaWJyYXJ5KGdncGxvdDIpCgpnZ3Bsb3QoaXJpcyxhZXMoeD1TZXBhbC5MZW5ndGgseT1TZXBhbC5XaWR0aCkpK3N0YXRfZGVuc2l0eTJkKGdlb209InBvbHlnb24iLGFlcyhmaWxsPWlyaXMkU3BlY2llcyxhbHBoYSA9IC4ubGV2ZWwuLikpK2dlb21fcG9pbnQoYWVzKHNoYXBlPVNwZWNpZXMpLGNvbG9yPSJibGFjayIsc2l6ZT0yKSt0aGVtZV9idygpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjZmYwMDYxIiwiIzExYTZmYyIsIiNmZmFlMDAiKSkKYGBgClRoZSBzYW1lIGFuYWx5c2lzIGNvdWxkIGJlIGRvbmUgZm9yIFBldGFs4oCZcyBsZW5ndGggYW5kIHdpZHRoOgpgYGB7cn0KZ2dwbG90KGlyaXMsYWVzKHg9UGV0YWwuTGVuZ3RoLHk9UGV0YWwuV2lkdGgpKStzdGF0X2RlbnNpdHkyZChnZW9tPSJwb2x5Z29uIixhZXMoZmlsbD1pcmlzJFNwZWNpZXMsYWxwaGEgPSAuLmxldmVsLi4pKStnZW9tX3BvaW50KGFlcyhzaGFwZT1TcGVjaWVzKSxjb2xvcj0iYmxhY2siLHNpemU9MikrdGhlbWVfYncoKStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygiI2ZmMDA2MSIsIiMxMWE2ZmMiLCIjZmZhZTAwIikpCmBgYAoKQXMgdXN1YWwsIHdlIHdpbGwgc2V0IGEgY2xhc3NpZmljYXRpb24gdGFzayBpbiBtbHIgdGhlbiBpbnRyb2R1Y2Ugc29tZSBsZWFybmVycwpgYGB7cn0KI01ha2UgYSBtdWx0aWNsYXNzIGNsYXNzaWZpY2F0aW9uIHRhc2sgaW4gbWxyCgpsaWJyYXJ5KG1scikKCnRhc2tpcmlzPW1ha2VDbGFzc2lmVGFzayhpZD0iaXJpcyIsZGF0YT1pcmlzLHRhcmdldD0iU3BlY2llcyIpCgojIE1ha2luZyA3IGRpZmZlcmVudCBMZWFybmVycyAoYWxnb3JpdGhtKQoKbGVhcm5lckNBUlQ9bWFrZUxlYXJuZXIoaWQ9IkNBUlQiLCJjbGFzc2lmLnJwYXJ0IiwgcHJlZGljdC50eXBlID0gInByb2IiKQoKbGVhcm5lclJGPW1ha2VMZWFybmVyKGlkPSJSRiIsImNsYXNzaWYucmFuZG9tRm9yZXN0U1JDIiwgcHJlZGljdC50eXBlID0gInByb2IiKQoKbGVhcm5lclNWTT1tYWtlTGVhcm5lcihpZD0iU1ZNIiwiY2xhc3NpZi5zdm0iLCBwcmVkaWN0LnR5cGUgPSAicHJvYiIpCgpsZWFybmVyR0JNPW1ha2VMZWFybmVyKGlkPSJHQk0iLCJjbGFzc2lmLmdibSIsIHByZWRpY3QudHlwZSA9ICJwcm9iIikKCmxlYXJuZXJHTE1OPW1ha2VMZWFybmVyKGlkPSJFbGFzdGljbmV0IiwiY2xhc3NpZi5nbG1uZXQiLCBwcmVkaWN0LnR5cGUgPSAicHJvYiIpCgpsZWFybmVyS05OPW1ha2VMZWFybmVyKGlkPSJLTk4iLCJjbGFzc2lmLmtubiIpCgpsZWFybmVyTERBPW1ha2VMZWFybmVyKGlkPSJMREEiLCJjbGFzc2lmLmxkYSIsIHByZWRpY3QudHlwZSA9ICJwcm9iIikKYGBgCgojIFRoZSBzeW50YXggb2YgcGxvdExlYXJuZXJQcmVkaWN0aW9uKCApIGZ1bmN0aW9uCgoqKiBwbG90TGVhcm5lclByZWRpY3Rpb24obGVhcm5lciwgdGFzaywgZmVhdHVyZXMgPSBOVUxMLCBtZWFzdXJlcywgY3YgPSAxMEwsIOKApiwgZ3JpZHNpemUsIHBvaW50c2l6ZSA9IDIsIHByb2IuYWxwaGEgPSBUUlVFLCBzZS5iYW5kID0gVFJVRSwgZXJyLmNvbCA94oCdd2hpdGXigJwsIGdyZXlzY2FsZSA9IEZBTFNFKSAqKgoKV2hlcmU6CgorIGxlYXJuZXIgaXMgb2JqZWN04oCZcyBuYW1lIGZvciBsZWFybmVyIHRhc2sgaXMgdGhlIG9iamVjdCBuYW1lIGZvciB0YXNrCgorIGZlYXR1cmVzIGFyZ3VtZW50IDogdXAgdG8gMiBmZWF0dXJlcyBjb3VsZCBiZSBpbnRyb2R1Y2VkIGhlcmUuIEJ5IGRlZmF1bHQgdGhlIGZpcnN0IDIgZmVhdHVyZXMgYXJlIHVzZWQKCisgbWVhc3VyZXMgaW5kaWNhdGUgUGVyZm9ybWFuY2UgbWVhc3VyZShzKSB0byBldmFsdWF0ZS4gRGVmYXVsdCBpcyB0aGUgZGVmYXVsdCBtZWFzdXJlIGZvciB0aGUgdGFzawoKKyBjdiBmb3Igc2V0dGluZyB0aGUgY3Jvc3MtdmFsaWRhdGlvbiBhbmQgcmVwb3J0aW5nIGl0cyByZXN1bHQgYXMgcGxvdCB0aXRsZS4gTnVtYmVyIG9mIGZvbGRzLiBjdj0wIG1lYW5zIG5vIENWLiBEZWZhdWx0IGlzIDEwLgoKKyBncmlkc2l6ZSBpcyB0aGUgZ3JpZCByZXNvbHV0aW9uIHBlciBheGlzIGZvciBiYWNrZ3JvdW5kIHByZWRpY3Rpb25zLiBEZWZhdWx0IGlzIDEwMCBmb3IgMkQuCgorIFBvaW50c2l6ZSBmb3IgZ2dwbG90MiBnZW9tX3BvaW50IGZvciBkYXRhIHBvaW50cy4gRGVmYXVsdCBpcyAyLgoKKyBwcm9iLmFscGhhIGlzIGEgbG9naWNhbCBhcmd1bWVudCwgZm9yIHNldHRpbmcgYWxwaGEgdmFsdWUgb2YgYmFja2dyb3VuZCB0byBwcm9iYWJpbGl0eSBmb3IgcHJlZGljdGVkIGNsYXNzPyBBbGxvd3MgdmlzdWFsaXphdGlvbiBvZiDigJxjb25maWRlbmNl4oCdIGZvciBwcmVkaWN0aW9uLiBJZiBub3QsIG9ubHkgYSBjb25zdGFudCBjb2xvciBpcyBkaXNwbGF5ZWQgaW4gdGhlIGJhY2tncm91bmQgZm9yIHRoZSBwcmVkaWN0ZWQgbGFiZWwuIERlZmF1bHQgaXMgVFJVRS4KCisgc2UuYmFuZDogRm9yIHJlZ3Jlc3Npb24gaW4gMUQ6IFNob3cgYmFuZCBmb3Igc3RhbmRhcmQgZXJyb3IgZXN0aW1hdGlvbj8gRGVmYXVsdCBpcyBUUlVFLgoKKyBlcnIuY29sOiBGb3IgY2xhc3NpZmljYXRpb24sIENvbG9yIG9mIG1pc2NsYXNzaWZpZWQgZGF0YSBwb2ludHMuIERlZmF1bHQgaXMg4oCcd2hpdGXigJ0KCisgZ3JleXNjYWxlIGlzIGEgbG9naWNhbCBhcmd1bWVudDogU2hvdWxkIHRoZSBwbG90IGJlIGdyZXlzY2FsZSBjb21wbGV0ZWx5PyBEZWZhdWx0IGlzIEZBTFNFCgoqKkNBUlQgYWxnb3JpdGhtKioKYGBge3J9CnBsb3RMZWFybmVyUHJlZGljdGlvbihsZWFybmVyQ0FSVCx0YXNraXJpcyxmZWF0dXJlcz1jKCJTZXBhbC5MZW5ndGgiLCJTZXBhbC5XaWR0aCIpLGN2PTEwMEwsZ3JpZHNpemU9MTAwKStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygiI2ZmMDA2MSIsIiMxMWE2ZmMiLCIjZmZhZTAwIikpK3RoZW1lX2J3KCkKYGBgCmBgYHtyfQpwbG90TGVhcm5lclByZWRpY3Rpb24obGVhcm5lckNBUlQsdGFza2lyaXMsZmVhdHVyZXM9YygiUGV0YWwuTGVuZ3RoIiwiUGV0YWwuV2lkdGgiKSxjdj0xMDBMLGdyaWRzaXplPTEwMCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiNmZjAwNjEiLCIjMTFhNmZjIiwiI2ZmYWUwMCIpKSt0aGVtZV9idygpCmBgYAoqKiBTdXBwb3J0IHZlY3RvciBtYWNoaW5lIGFsZ29yaXRobSAqKgpgYGB7cn0KcGxvdExlYXJuZXJQcmVkaWN0aW9uKGxlYXJuZXJTVk0sdGFza2lyaXMsZmVhdHVyZXM9YygiU2VwYWwuTGVuZ3RoIiwiU2VwYWwuV2lkdGgiKSxjdj0xMDBMLGdyaWRzaXplPTEwMCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiNmZjAwNjEiLCIjMTFhNmZjIiwiI2ZmYWUwMCIpKSt0aGVtZV9idygpCmBgYApgYGB7cn0KcGxvdExlYXJuZXJQcmVkaWN0aW9uKGxlYXJuZXJTVk0sdGFza2lyaXMsZmVhdHVyZXM9YygiUGV0YWwuTGVuZ3RoIiwiUGV0YWwuV2lkdGgiKSxjdj0xMDBMLGdyaWRzaXplPTEwMCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiNmZjAwNjEiLCIjMTFhNmZjIiwiI2ZmYWUwMCIpKSt0aGVtZV9idygpCmBgYAoqKiBHcmFkaWVudCBib29zdGluZyBtYWNoaW5lIGFsZ29yaXRobSAqKgpgYGB7cn0KcGxvdExlYXJuZXJQcmVkaWN0aW9uKGxlYXJuZXJHQk0sdGFza2lyaXMsZmVhdHVyZXM9YygiU2VwYWwuTGVuZ3RoIiwiU2VwYWwuV2lkdGgiKSxjdj0xMDBMLGdyaWRzaXplPTEwMCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiNmZjAwNjEiLCIjMTFhNmZjIiwiI2ZmYWUwMCIpKSt0aGVtZV9idygpCmBgYAoKYGBge3J9CnBsb3RMZWFybmVyUHJlZGljdGlvbihsZWFybmVyR0JNLHRhc2tpcmlzLGZlYXR1cmVzPWMoIlBldGFsLkxlbmd0aCIsIlBldGFsLldpZHRoIiksY3Y9MTAwTCxncmlkc2l6ZT0xMDApK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjZmYwMDYxIiwiIzExYTZmYyIsIiNmZmFlMDAiKSkrdGhlbWVfYncoKQpgYGAKKipFbGFzdGljIG5ldCAobG9naXN0aWMpIGFsZ29yaXRobSoqCmBgYHtyfQpwbG90TGVhcm5lclByZWRpY3Rpb24obGVhcm5lckdMTU4sdGFza2lyaXMsZmVhdHVyZXM9YygiU2VwYWwuTGVuZ3RoIiwiU2VwYWwuV2lkdGgiKSxjdj0xMDBMLGdyaWRzaXplPTEwMCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiNmZjAwNjEiLCIjMTFhNmZjIiwiI2ZmYWUwMCIpKSt0aGVtZV9idygpCmBgYApgYGB7cn0KcGxvdExlYXJuZXJQcmVkaWN0aW9uKGxlYXJuZXJHTE1OLHRhc2tpcmlzLGZlYXR1cmVzPWMoIlBldGFsLkxlbmd0aCIsIlBldGFsLldpZHRoIiksY3Y9MTAwTCxncmlkc2l6ZT0xMDApK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjZmYwMDYxIiwiIzExYTZmYyIsIiNmZmFlMDAiKSkrdGhlbWVfYncoKQpgYGAKKiogS05OIGFsZ29yaXRobSoqCmBgYHtyfQpwbG90TGVhcm5lclByZWRpY3Rpb24obGVhcm5lcktOTix0YXNraXJpcyxmZWF0dXJlcz1jKCJTZXBhbC5MZW5ndGgiLCJTZXBhbC5XaWR0aCIpLGN2PTEwMEwsZ3JpZHNpemU9MTAwKStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygiI2ZmMDA2MSIsIiMxMWE2ZmMiLCIjZmZhZTAwIikpK3RoZW1lX2J3KCkKYGBgCmBgYHtyfQpwbG90TGVhcm5lclByZWRpY3Rpb24obGVhcm5lcktOTix0YXNraXJpcyxmZWF0dXJlcz1jKCJQZXRhbC5MZW5ndGgiLCJQZXRhbC5XaWR0aCIpLGN2PTEwMEwsZ3JpZHNpemU9MTAwKStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygiI2ZmMDA2MSIsIiMxMWE2ZmMiLCIjZmZhZTAwIikpK3RoZW1lX2J3KCkKYGBgCgoqKkxEQSBhbGdvcml0aG0qKgpgYGB7cn0KcGxvdExlYXJuZXJQcmVkaWN0aW9uKGxlYXJuZXJMREEsdGFza2lyaXMsZmVhdHVyZXM9YygiU2VwYWwuTGVuZ3RoIiwiU2VwYWwuV2lkdGgiKSxjdj0xMDBMLGdyaWRzaXplPTEwMCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiNmZjAwNjEiLCIjMTFhNmZjIiwiI2ZmYWUwMCIpKSt0aGVtZV9idygpCmBgYAoKYGBge3J9CnBsb3RMZWFybmVyUHJlZGljdGlvbihsZWFybmVyTERBLHRhc2tpcmlzLGZlYXR1cmVzPWMoIlBldGFsLkxlbmd0aCIsIlBldGFsLldpZHRoIiksY3Y9MTAwTCxncmlkc2l6ZT0xMDApK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjZmYwMDYxIiwiIzExYTZmYyIsIiNmZmFlMDAiKSkrdGhlbWVfYncoKQpgYGAKKipSYW5kb20gRm9yZXN0IGFsZ29yaXRobSoqCmBgYHtyfQpwbG90TGVhcm5lclByZWRpY3Rpb24obGVhcm5lclJGLHRhc2tpcmlzLGZlYXR1cmVzPWMoIlNlcGFsLkxlbmd0aCIsIlNlcGFsLldpZHRoIiksY3Y9MTAwTCxncmlkc2l6ZT0xMDApK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCIjZmYwMDYxIiwiIzExYTZmYyIsIiNmZmFlMDAiKSkrdGhlbWVfYncoKQpgYGAKYGBge3J9CnBsb3RMZWFybmVyUHJlZGljdGlvbihsZWFybmVyUkYsdGFza2lyaXMsZmVhdHVyZXM9YygiUGV0YWwuTGVuZ3RoIiwiUGV0YWwuV2lkdGgiKSxjdj0xMDBMLGdyaWRzaXplPTEwMCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiNmZjAwNjEiLCIjMTFhNmZjIiwiI2ZmYWUwMCIpKSt0aGVtZV9idygpCmBgYAoKIyBDb25jbHVzaW9uCioqcGxvdExlYXJuZXJQcmVkaWN0aW9uKCkqKiBpcyBhIGhpZGRlbiB0b29sIGluIG1sciBwYWNrYWdlLiBUaGlzIHVzZWZ1bCBmdW5jdGlvbiBhbGxvd3MgdG8gZ2VuZXJhdGUgYmVhdXRpZnVsIHBsb3RzIGZvciB0aGUgaWxsdXN0cmF0aW9uIHB1cnBvc2UuIFRoZXNlIHBsb3RzIHByb3ZpZGUgbWFueSBpbmZvcm1hdGlvbiwgc3VjaCBhczoKCisgQSB2aXN1YWwgcGVyY2VwdGlvbiBvZiBtb2RlbOKAmXMgcGVyZm9ybWFuY2U6IGl0cyBhYmlsaXR5IHRvIGNsYXNzaWZ5IHRoZSBpbnN0YW5jZXMgaW50byB0d28gb3IgbW9yZSBjbGFzc2VzLCB0aGUgY29ycmVjdCBjbGFzc2lmaWNhdGlvbiBhbmQgZXJyb3IgcmF0ZXMuCgorIEEgdmlzdWFsIHByZXNlbnRhdGlvbiBvZiB0aGUgdW5kZXJseWluZyBtZWNoYW5pc20gb2YgdGhlIG1vZGVsLCB2aWEgdGhlIHByZWRpY3Rpb24gYm91bmRhcmllcwoKKyBBc3NvY2F0aW9uIGJldHdlZW4gdHdvIGZlYXR1cmVzIGFuZCB0aGVpciBjb250cmlidXRpb24gdG8gbW9kZWzigJlzIHByZWRpY3Rpb24uCgorIE51bWVyaWNhbCByZXN1bHQgb2YgY3Jvc3MtdmFsaWRhdGlvbjogYXZlcmFnZWQgbW9kZWzigJlzIHBlcmZvcm1hbmNlIG1ldHJpY3MKCg==