ABOUT

This is an example of a notebook to demonstrate concepts of Data Science. In this example we will do some exploratory data analysis on the famous Iris dataset.

The Iris Dataset contains four features (length and width of sepals and petals) of 50 samples of three species of Iris (Iris setosa, Iris virginica and Iris versicolor). These measures were used to create a linear discriminant model to classify the species. The dataset is often used in data mining, classification and clustering examples and to test algorithms.

Information about the original paper and usages of the dataset can be found in the UCI Machine Learning Repository – Iris Data Set.

Refernce Pictures of the Three Flower Species

DATA OVERVIEW

The iris dataset is a built-in dataset in R that contains measurements on 4 different attributes (in centimeters) for 50 flowers from 3 different species.

str(iris)

We can use the dim() function to get the dimensions of the dataset in terms of number of rows and number of columns:

dim(iris)

Take a look at the first six rows of the dataset by using the head() function:

head(iris)
# Get first 5 rows of each subset
subset(iris, Species == "setosa")[1:5,]

DATA CLASSES

subset(iris, Species == "versicolor")[1:5,]
subset(iris, Species == "virginica")[1:5,]

DATA SUMMARY

summary(iris)
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width          Species  
 Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100   setosa    :50  
 1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300   versicolor:50  
 Median :5.800   Median :3.000   Median :4.350   Median :1.300   virginica :50  
 Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199                  
 3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800                  
 Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500                  

For each of the numeric variables we can see the following information:

  • Min: The minimum value.
  • 1st Qu: The value of the first quartile (25th percentile).
  • Median: The median value.
  • Mean: The mean value.
  • 3rd Qu: The value of the third quartile (75th percentile).
  • Max: The maximum value.

For the only categorical variable in the dataset (Species) we see a frequency count of each value:

  • setosa: This species occurs 50 times.
  • versicolor: This species occurs 50 times.
  • virginica: This species occurs 50 times.

VISUALIZATION

Use hist() function.

hist(iris$Sepal.Length,
     col='steelblue',
     main='Histogram',
     xlab='Length',
     ylab='Frequency')

par(mfrow=c(1,3))
hist(irisVer$Petal.Length,breaks=seq(0,8,l=17),xlim=c(0,8),ylim=c(0,40))
hist(irisSet$Petal.Length,breaks=seq(0,8,l=17),xlim=c(0,8),ylim=c(0,40))
hist(irisVir$Petal.Length,breaks=seq(0,8,l=17),xlim=c(0,8),ylim=c(0,40))

Use plot() function.

#create scatterplot of sepal width vs. sepal length
plot(iris$Petal.Width, iris$Petal.Length,
     col='steelblue',
     main='Scatterplot',
     xlab='Sepal Width',
     ylab='Sepal Length',
     pch=19)

library(beanplot)
xiris <- iris
xiris$Species <- NULL
beanplot(xiris, main = "Iris flowers",col=c('#ff8080','#0000FF','#0000FF',
                                            '#FF00FF'), border = "#000000")

Use boxplot() function.

#create scatterplot of sepal width vs. sepal length
boxplot(Sepal.Length~Species,
        data=iris,
        main='Sepal Length by Species',
        xlab='Species',
        ylab='Sepal Length',
        col='steelblue',
        border='black')

The x-axis displays the three species and the y-axis displays the distribution of values for sepal length for each species.

How to Interpret a Boxplot

This gives us a rough estimate of the distribution of the values for each attribute. But maybe it makes more sense to see the distribution of the values considering each class, since we have labels for each class.

irisVer <- subset(iris, Species == "versicolor")
irisSet <- subset(iris, Species == "setosa")
irisVir <- subset(iris, Species == "virginica")
par(mfrow=c(1,3),mar=c(6,3,2,1))
boxplot(irisVer[,1:4], main="Versicolor",ylim = c(0,8),las=2)
boxplot(irisSet[,1:4], main="Setosa",ylim = c(0,8),las=2)
boxplot(irisVir[,1:4], main="Virginica",ylim = c(0,8),las=2)

CORRELATION ANALYSIS

Are any variables correlated?

corr <- cor(iris[,1:4])
round(corr,3)
             Sepal.Length Sepal.Width Petal.Length Petal.Width
Sepal.Length        1.000      -0.118        0.872       0.818
Sepal.Width        -0.118       1.000       -0.428      -0.366
Petal.Length        0.872      -0.428        1.000       0.963
Petal.Width         0.818      -0.366        0.963       1.000

+1 means variables are correlated, -1 inversely correlated.

pairs(iris[,1:4])

Let’s color the scatterplot to help visually:

pairs(iris[,1:4],col=iris[,5],oma=c(4,4,6,12))
par(xpd=TRUE)
legend(0.85,0.6, as.vector(unique(iris$Species)),fill=c(1,2,3))

CLASSIFICATION

Create a model that predicts the species from the petal and sepal width and length using a decision tree.

library(C50)
input <- iris[,1:4]
output <- iris[,5]
model1 <- C5.0(input, output, control = C5.0Control(noGlobalPruning = TRUE,minCases=1))
plot(model1, main="C5.0 Decision Tree - Unpruned, min=1")

Adjust Paramters of the desicion tree.

model2 <- C5.0(input, output, control = C5.0Control(noGlobalPruning = FALSE))
plot(model2, main="C5.0 Decision Tree - Pruned")

Retrieve model infromation.

summary(model2)

Call:
C5.0.default(x = input, y = output, control = C5.0Control(noGlobalPruning = FALSE))


C5.0 [Release 2.07 GPL Edition]     Thu Jan 13 17:04:45 2022
-------------------------------

Class specified by attribute `outcome'

Read 150 cases (5 attributes) from undefined.data

Decision tree:

Petal.Length <= 1.9: setosa (50)
Petal.Length > 1.9:
:...Petal.Width > 1.7: virginica (46/1)
    Petal.Width <= 1.7:
    :...Petal.Length <= 4.9: versicolor (48/1)
        Petal.Length > 4.9: virginica (6/2)


Evaluation on training data (150 cases):

        Decision Tree   
      ----------------  
      Size      Errors  

         4    4( 2.7%)   <<


       (a)   (b)   (c)    <-classified as
      ----  ----  ----
        50                (a): class setosa
              47     3    (b): class versicolor
               1    49    (c): class virginica


    Attribute usage:

    100.00% Petal.Length
     66.67% Petal.Width


Time: 0.0 secs

Observe features used to create model.

C5imp(model2,metric='usage')

Predict classes from numerical attributes:

newcases <- iris[c(1:3,51:53,101:103),]
newcases
predicted <- predict(model2, newcases, type="class")
predicted
[1] setosa     setosa     setosa     versicolor versicolor versicolor virginica  virginica 
[9] virginica 
Levels: setosa versicolor virginica

K MEANS CLUSTERING

K-means Clustering is used with unlabeled data, but in this case, we have a labeled dataset so we have to use the iris data without the Species column. In this way, algorithm will cluster the data and we will be able to compare the predicted results with the original results, getting the accuracy of the model.

library(ggplot2)
df <- iris
head(iris)
ggplot(df, aes(Petal.Length, Petal.Width)) + geom_point(aes(col=Species), size=4)

As we can see, setosa is going to be clustered easier. Meanwhile, there is noise between versicolor and virginica even when they look like perfectly clustered.

Let’s run the model. kmeans is installed in the base package from R, so we don’t have to install any package.

In the kmeans function, it is necessary to set center, which is the number of groups we want to cluster to. In this case, we know this value will be 3. Let’s set that.

Let’s see how we would build the model if we didn’t know it.

set.seed(101)
irisCluster <- kmeans(df[,1:4], center=3, nstart=20)
irisCluster
K-means clustering with 3 clusters of sizes 38, 62, 50

Cluster means:
  Sepal.Length Sepal.Width Petal.Length Petal.Width
1     6.850000    3.073684     5.742105    2.071053
2     5.901613    2.748387     4.393548    1.433871
3     5.006000    3.428000     1.462000    0.246000

Clustering vector:
  [1] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
 [44] 3 3 3 3 3 3 3 2 2 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 2 2 2 2 2 2 2 2
 [87] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 2 1 1 1 1 2 1 1 1 1 1 1 2 2 1 1 1 1 2 1 2 1 2 1 1 2 2 1
[130] 1 1 1 1 2 1 1 1 1 2 1 1 1 2 1 1 1 2 1 1 2

Within cluster sum of squares by cluster:
[1] 23.87947 39.82097 15.15100
 (between_SS / total_SS =  88.4 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
[6] "betweenss"    "size"         "iter"         "ifault"      

Compare the predicted clusters with the original data.

table(irisCluster$cluster, df$Species)
   
    setosa versicolor virginica
  1      0          2        36
  2      0         48        14
  3     50          0         0

Plot out these clusters:

library(cluster)
clusplot(iris, irisCluster$cluster, color=T, shade=T, labels=0, lines=0)

We can see the setosa cluster perfectly explained, meanwhile virginica and versicolor have a little noise between their clusters.

If we would want to know the exactly number of centers, we should have built the elbow method.

tot.withinss <- vector(mode="character", length=10)
for (i in 1:10){
  irisCluster <- kmeans(df[,1:4], center=i, nstart=20)
  tot.withinss[i] <- irisCluster$tot.withinss
}
plot(1:10, tot.withinss, type="b", pch=19)

This shows the optimal number of clusters as 3.

LS0tCnRpdGxlOiAiSVJJUyBEQVRBU0VUIEVYUExPUkFUSU9OIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyB7LnRhYnNldH0KCiMjIyBBQk9VVAoKVGhpcyBpcyBhbiBleGFtcGxlIG9mIGEgbm90ZWJvb2sgdG8gZGVtb25zdHJhdGUgY29uY2VwdHMgb2YgRGF0YSBTY2llbmNlLiBJbiB0aGlzIGV4YW1wbGUgd2Ugd2lsbCBkbyBzb21lIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMgb24gdGhlIGZhbW91cyBJcmlzIGRhdGFzZXQuCgpUaGUgSXJpcyBEYXRhc2V0IGNvbnRhaW5zIGZvdXIgZmVhdHVyZXMgKGxlbmd0aCBhbmQgd2lkdGggb2Ygc2VwYWxzIGFuZCBwZXRhbHMpIG9mIDUwIHNhbXBsZXMgb2YgdGhyZWUgc3BlY2llcyBvZiBJcmlzIChJcmlzIHNldG9zYSwgSXJpcyB2aXJnaW5pY2EgYW5kIElyaXMgdmVyc2ljb2xvcikuIFRoZXNlIG1lYXN1cmVzIHdlcmUgdXNlZCB0byBjcmVhdGUgYSBsaW5lYXIgZGlzY3JpbWluYW50IG1vZGVsIHRvIGNsYXNzaWZ5IHRoZSBzcGVjaWVzLiBUaGUgZGF0YXNldCBpcyBvZnRlbiB1c2VkIGluIGRhdGEgbWluaW5nLCBjbGFzc2lmaWNhdGlvbiBhbmQgY2x1c3RlcmluZyBleGFtcGxlcyBhbmQgdG8gdGVzdCBhbGdvcml0aG1zLgoKSW5mb3JtYXRpb24gYWJvdXQgdGhlIG9yaWdpbmFsIHBhcGVyIGFuZCB1c2FnZXMgb2YgdGhlIGRhdGFzZXQgY2FuIGJlIGZvdW5kIGluIHRoZSBbVUNJIE1hY2hpbmUgTGVhcm5pbmcgUmVwb3NpdG9yeSAtLSBJcmlzIERhdGEgU2V0XShodHRwOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9kYXRhc2V0cy9JcmlzKS4KCiFbUmVmZXJuY2UgUGljdHVyZXMgb2YgdGhlIFRocmVlIEZsb3dlciBTcGVjaWVzXShpcmlzLW1hY2hpbmVsZWFybmluZy5wbmcpCgojIyMgREFUQSBPVkVSVklFVwoKVGhlIGlyaXMgZGF0YXNldCBpcyBhIGJ1aWx0LWluIGRhdGFzZXQgaW4gUiB0aGF0IGNvbnRhaW5zIG1lYXN1cmVtZW50cyBvbiA0IGRpZmZlcmVudCBhdHRyaWJ1dGVzIChpbiBjZW50aW1ldGVycykgZm9yIDUwIGZsb3dlcnMgZnJvbSAzIGRpZmZlcmVudCBzcGVjaWVzLgoKYGBge3IgTG9hZCBEYXRhc2V0LCBpbmNsdWRlPUZBTFNFfQpkYXRhKGlyaXMpCmBgYAoKYGBge3IgQ2hlY2sgRGF0YX0Kc3RyKGlyaXMpCmBgYAoKV2UgY2FuIHVzZSB0aGUgZGltKCkgZnVuY3Rpb24gdG8gZ2V0IHRoZSBkaW1lbnNpb25zIG9mIHRoZSBkYXRhc2V0IGluIHRlcm1zIG9mIG51bWJlciBvZiByb3dzIGFuZCBudW1iZXIgb2YgY29sdW1uczoKCmBgYHtyIERpbWVuc2lvbnN9CmRpbShpcmlzKQpgYGAKClRha2UgYSBsb29rIGF0IHRoZSBmaXJzdCBzaXggcm93cyBvZiB0aGUgZGF0YXNldCBieSB1c2luZyB0aGUgaGVhZCgpIGZ1bmN0aW9uOgoKYGBge3IgUmV2aWV3IERhdGFzZXR9CmhlYWQoaXJpcykKYGBgCgpgYGB7ciBWaWV3IENsYXNzIFNldG9zYX0KIyBHZXQgZmlyc3QgNSByb3dzIG9mIGVhY2ggc3Vic2V0CnN1YnNldChpcmlzLCBTcGVjaWVzID09ICJzZXRvc2EiKVsxOjUsXQpgYGAKIyMjIERBVEEgQ0xBU1NFUwoKYGBge3IgVmlldyBDbGFzcyBWZXJpc2NvbG9yfQpzdWJzZXQoaXJpcywgU3BlY2llcyA9PSAidmVyc2ljb2xvciIpWzE6NSxdCmBgYAoKYGBge3IgVmlldyBDbGFzcyBWaXJnaW5pY2F9CnN1YnNldChpcmlzLCBTcGVjaWVzID09ICJ2aXJnaW5pY2EiKVsxOjUsXQpgYGAKIyMjIERBVEEgU1VNTUFSWQoKYGBge3IgU3VtbWFyaXplIHRoZSBJcmlzIERhdGFzZXR9CnN1bW1hcnkoaXJpcykKYGBgCkZvciBlYWNoIG9mIHRoZSBudW1lcmljIHZhcmlhYmxlcyB3ZSBjYW4gc2VlIHRoZSBmb2xsb3dpbmcgaW5mb3JtYXRpb246CgoqICoqTWluOioqIFRoZSBtaW5pbXVtIHZhbHVlLgoqICoqMXN0IFF1OioqIFRoZSB2YWx1ZSBvZiB0aGUgZmlyc3QgcXVhcnRpbGUgKDI1dGggcGVyY2VudGlsZSkuCiogKipNZWRpYW46KiogVGhlIG1lZGlhbiB2YWx1ZS4KKiAqKk1lYW46KiogVGhlIG1lYW4gdmFsdWUuCiogKiozcmQgUXU6KiogVGhlIHZhbHVlIG9mIHRoZSB0aGlyZCBxdWFydGlsZSAoNzV0aCBwZXJjZW50aWxlKS4KKiAqKk1heDoqKiBUaGUgbWF4aW11bSB2YWx1ZS4KCkZvciB0aGUgb25seSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBpbiB0aGUgZGF0YXNldCAoU3BlY2llcykgd2Ugc2VlIGEgZnJlcXVlbmN5IGNvdW50IG9mIGVhY2ggdmFsdWU6CgoqICoqc2V0b3NhOioqIFRoaXMgc3BlY2llcyBvY2N1cnMgNTAgdGltZXMuCiogKip2ZXJzaWNvbG9yOioqIFRoaXMgc3BlY2llcyBvY2N1cnMgNTAgdGltZXMuCiogKip2aXJnaW5pY2E6KiogVGhpcyBzcGVjaWVzIG9jY3VycyA1MCB0aW1lcy4KCiMjIyBWSVNVQUxJWkFUSU9OCgpVc2UgKipoaXN0KCkqKiBmdW5jdGlvbi4KCmBgYHtyIENyZWF0ZSBIaXN0b3JncmFtfQojY3JlYXRlIGhpc3RvZ3JhbSBvZiB2YWx1ZXMgZm9yIHNlcGFsIGxlbmd0aApoaXN0KGlyaXMkU2VwYWwuTGVuZ3RoLAogICAgIGNvbD0nc3RlZWxibHVlJywKICAgICBtYWluPSdIaXN0b2dyYW0nLAogICAgIHhsYWI9J0xlbmd0aCcsCiAgICAgeWxhYj0nRnJlcXVlbmN5JykKYGBgCgpgYGB7ciBIaXN0b2dyYW0gZm9yZSBlYWNoIGNsYXNzfQpwYXIobWZyb3c9YygxLDMpKQpoaXN0KGlyaXNWZXIkUGV0YWwuTGVuZ3RoLGJyZWFrcz1zZXEoMCw4LGw9MTcpLHhsaW09YygwLDgpLHlsaW09YygwLDQwKSkKaGlzdChpcmlzU2V0JFBldGFsLkxlbmd0aCxicmVha3M9c2VxKDAsOCxsPTE3KSx4bGltPWMoMCw4KSx5bGltPWMoMCw0MCkpCmhpc3QoaXJpc1ZpciRQZXRhbC5MZW5ndGgsYnJlYWtzPXNlcSgwLDgsbD0xNykseGxpbT1jKDAsOCkseWxpbT1jKDAsNDApKQpgYGAKClVzZSAqKnBsb3QoKSoqIGZ1bmN0aW9uLgoKYGBge3IgQ3JlYXRlIFNjYXR0ZXJwbG90IG9mIFBhaXJ3aXNlIERhdGF9CiNjcmVhdGUgc2NhdHRlcnBsb3Qgb2Ygc2VwYWwgd2lkdGggdnMuIHNlcGFsIGxlbmd0aApwbG90KGlyaXMkUGV0YWwuV2lkdGgsIGlyaXMkUGV0YWwuTGVuZ3RoLAogICAgIGNvbD0nc3RlZWxibHVlJywKICAgICBtYWluPSdTY2F0dGVycGxvdCcsCiAgICAgeGxhYj0nU2VwYWwgV2lkdGgnLAogICAgIHlsYWI9J1NlcGFsIExlbmd0aCcsCiAgICAgcGNoPTE5KQpgYGAKCmBgYHtyfQpsaWJyYXJ5KGJlYW5wbG90KQp4aXJpcyA8LSBpcmlzCnhpcmlzJFNwZWNpZXMgPC0gTlVMTApiZWFucGxvdCh4aXJpcywgbWFpbiA9ICJJcmlzIGZsb3dlcnMiLGNvbD1jKCcjZmY4MDgwJywnIzAwMDBGRicsJyMwMDAwRkYnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcjRkYwMEZGJyksIGJvcmRlciA9ICIjMDAwMDAwIikKYGBgCgpVc2UgKipib3hwbG90KCkqKiBmdW5jdGlvbi4KCmBgYHtyIENyZWF0ZSBCb3hwbG90fQojY3JlYXRlIHNjYXR0ZXJwbG90IG9mIHNlcGFsIHdpZHRoIHZzLiBzZXBhbCBsZW5ndGgKYm94cGxvdChTZXBhbC5MZW5ndGh+U3BlY2llcywKICAgICAgICBkYXRhPWlyaXMsCiAgICAgICAgbWFpbj0nU2VwYWwgTGVuZ3RoIGJ5IFNwZWNpZXMnLAogICAgICAgIHhsYWI9J1NwZWNpZXMnLAogICAgICAgIHlsYWI9J1NlcGFsIExlbmd0aCcsCiAgICAgICAgY29sPSdzdGVlbGJsdWUnLAogICAgICAgIGJvcmRlcj0nYmxhY2snKQpgYGAKVGhlIHgtYXhpcyBkaXNwbGF5cyB0aGUgdGhyZWUgc3BlY2llcyBhbmQgdGhlIHktYXhpcyBkaXNwbGF5cyB0aGUgZGlzdHJpYnV0aW9uIG9mIHZhbHVlcyBmb3Igc2VwYWwgbGVuZ3RoIGZvciBlYWNoIHNwZWNpZXMuCgohW0hvdyB0byBJbnRlcnByZXQgYSBCb3hwbG90XShpbnRlcnByZXRfYm94cGxvdHMucG5nKQoKVGhpcyBnaXZlcyB1cyBhIHJvdWdoIGVzdGltYXRlIG9mIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHZhbHVlcyBmb3IgZWFjaCBhdHRyaWJ1dGUuIEJ1dCBtYXliZSBpdCBtYWtlcyBtb3JlIHNlbnNlIHRvIHNlZSB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSB2YWx1ZXMgY29uc2lkZXJpbmcgZWFjaCBjbGFzcywgc2luY2Ugd2UgaGF2ZSBsYWJlbHMgZm9yIGVhY2ggY2xhc3MuCgpgYGB7ciBCb3hwbG90IGJ5IENsYXNzfQppcmlzVmVyIDwtIHN1YnNldChpcmlzLCBTcGVjaWVzID09ICJ2ZXJzaWNvbG9yIikKaXJpc1NldCA8LSBzdWJzZXQoaXJpcywgU3BlY2llcyA9PSAic2V0b3NhIikKaXJpc1ZpciA8LSBzdWJzZXQoaXJpcywgU3BlY2llcyA9PSAidmlyZ2luaWNhIikKcGFyKG1mcm93PWMoMSwzKSxtYXI9Yyg2LDMsMiwxKSkKYm94cGxvdChpcmlzVmVyWywxOjRdLCBtYWluPSJWZXJzaWNvbG9yIix5bGltID0gYygwLDgpLGxhcz0yKQpib3hwbG90KGlyaXNTZXRbLDE6NF0sIG1haW49IlNldG9zYSIseWxpbSA9IGMoMCw4KSxsYXM9MikKYm94cGxvdChpcmlzVmlyWywxOjRdLCBtYWluPSJWaXJnaW5pY2EiLHlsaW0gPSBjKDAsOCksbGFzPTIpCmBgYAojIyMgQ09SUkVMQVRJT04gQU5BTFlTSVMKCkFyZSBhbnkgdmFyaWFibGVzIGNvcnJlbGF0ZWQ/CgpgYGB7ciBUYWJ1bGF0ZWQgQ29ycmVsYXRpb259CmNvcnIgPC0gY29yKGlyaXNbLDE6NF0pCnJvdW5kKGNvcnIsMykKYGBgCgorMSBtZWFucyB2YXJpYWJsZXMgYXJlIGNvcnJlbGF0ZWQsIC0xIGludmVyc2VseSBjb3JyZWxhdGVkLgoKYGBge3IgQ29ycmVsYXRpb24gU2NhdHRlcnBsb3R9CnBhaXJzKGlyaXNbLDE6NF0pCmBgYApMZXQncyBjb2xvciB0aGUgc2NhdHRlcnBsb3QgdG8gaGVscCB2aXN1YWxseToKCmBgYHtyIENvbG9yZWQgU2NhdHRlcnBsb3RzfQpwYWlycyhpcmlzWywxOjRdLGNvbD1pcmlzWyw1XSxvbWE9Yyg0LDQsNiwxMikpCnBhcih4cGQ9VFJVRSkKbGVnZW5kKDAuODUsMC42LCBhcy52ZWN0b3IodW5pcXVlKGlyaXMkU3BlY2llcykpLGZpbGw9YygxLDIsMykpCmBgYAojIyMgQ0xBU1NJRklDQVRJT04KCkNyZWF0ZSBhIG1vZGVsIHRoYXQgcHJlZGljdHMgdGhlIHNwZWNpZXMgZnJvbSB0aGUgcGV0YWwgYW5kIHNlcGFsIHdpZHRoIGFuZCBsZW5ndGggdXNpbmcgYSBkZWNpc2lvbiB0cmVlLgoKYGBge3IgRGVjaXNpb24gVHJlZX0KbGlicmFyeShDNTApCmlucHV0IDwtIGlyaXNbLDE6NF0Kb3V0cHV0IDwtIGlyaXNbLDVdCm1vZGVsMSA8LSBDNS4wKGlucHV0LCBvdXRwdXQsIGNvbnRyb2wgPSBDNS4wQ29udHJvbChub0dsb2JhbFBydW5pbmcgPSBUUlVFLG1pbkNhc2VzPTEpKQpwbG90KG1vZGVsMSwgbWFpbj0iQzUuMCBEZWNpc2lvbiBUcmVlIC0gVW5wcnVuZWQsIG1pbj0xIikKYGBgCkFkanVzdCBQYXJhbXRlcnMgb2YgdGhlIGRlc2ljaW9uIHRyZWUuCgpgYGB7ciBBZGp1c3QgUGFyYW10ZXJzfQptb2RlbDIgPC0gQzUuMChpbnB1dCwgb3V0cHV0LCBjb250cm9sID0gQzUuMENvbnRyb2wobm9HbG9iYWxQcnVuaW5nID0gRkFMU0UpKQpwbG90KG1vZGVsMiwgbWFpbj0iQzUuMCBEZWNpc2lvbiBUcmVlIC0gUHJ1bmVkIikKYGBgClJldHJpZXZlIG1vZGVsIGluZnJvbWF0aW9uLgoKYGBge3J9CnN1bW1hcnkobW9kZWwyKQpgYGAKCk9ic2VydmUgZmVhdHVyZXMgdXNlZCB0byBjcmVhdGUgbW9kZWwuCgpgYGB7ciBPYnNlcnZlIGZlYXR1cmVzIHVzZWR9CkM1aW1wKG1vZGVsMixtZXRyaWM9J3VzYWdlJykKYGBgCgpQcmVkaWN0IGNsYXNzZXMgZnJvbSBudW1lcmljYWwgYXR0cmlidXRlczoKCmBgYHtyfQpuZXdjYXNlcyA8LSBpcmlzW2MoMTozLDUxOjUzLDEwMToxMDMpLF0KbmV3Y2FzZXMKYGBgCgpgYGB7cn0KcHJlZGljdGVkIDwtIHByZWRpY3QobW9kZWwyLCBuZXdjYXNlcywgdHlwZT0iY2xhc3MiKQpwcmVkaWN0ZWQKYGBgCgojIyMgSyBNRUFOUyBDTFVTVEVSSU5HCgpLLW1lYW5zIENsdXN0ZXJpbmcgaXMgdXNlZCB3aXRoIHVubGFiZWxlZCBkYXRhLCBidXQgaW4gdGhpcyBjYXNlLCB3ZSBoYXZlIGEgbGFiZWxlZCBkYXRhc2V0IHNvIHdlIGhhdmUgdG8gdXNlIHRoZSBpcmlzIGRhdGEgd2l0aG91dCB0aGUgU3BlY2llcyBjb2x1bW4uIEluIHRoaXMgd2F5LCBhbGdvcml0aG0gd2lsbCBjbHVzdGVyIHRoZSBkYXRhIGFuZCB3ZSB3aWxsIGJlIGFibGUgdG8gY29tcGFyZSB0aGUgcHJlZGljdGVkIHJlc3VsdHMgd2l0aCB0aGUgb3JpZ2luYWwgcmVzdWx0cywgZ2V0dGluZyB0aGUgYWNjdXJhY3kgb2YgdGhlIG1vZGVsLgoKYGBge3IgSW1wb3J0IGdncGxvdDJ9CmxpYnJhcnkoZ2dwbG90MikKYGBgCgpgYGB7ciBCdWlsZCBuZXcgZGF0ZnJhbWV9CmRmIDwtIGlyaXMKaGVhZChpcmlzKQpgYGAKCmBgYHtyIFBsb3QgRGF0YX0KZ2dwbG90KGRmLCBhZXMoUGV0YWwuTGVuZ3RoLCBQZXRhbC5XaWR0aCkpICsgZ2VvbV9wb2ludChhZXMoY29sPVNwZWNpZXMpLCBzaXplPTQpCmBgYAoKQXMgd2UgY2FuIHNlZSwgc2V0b3NhIGlzIGdvaW5nIHRvIGJlIGNsdXN0ZXJlZCBlYXNpZXIuIE1lYW53aGlsZSwgdGhlcmUgaXMgbm9pc2UgYmV0d2VlbiB2ZXJzaWNvbG9yIGFuZCB2aXJnaW5pY2EgZXZlbiB3aGVuIHRoZXkgbG9vayBsaWtlIHBlcmZlY3RseSBjbHVzdGVyZWQuCgpMZXTigJlzIHJ1biB0aGUgbW9kZWwuIGttZWFucyBpcyBpbnN0YWxsZWQgaW4gdGhlIGJhc2UgcGFja2FnZSBmcm9tIFIsIHNvIHdlIGRvbuKAmXQgaGF2ZSB0byBpbnN0YWxsIGFueSBwYWNrYWdlLgoKSW4gdGhlIGttZWFucyBmdW5jdGlvbiwgaXQgaXMgbmVjZXNzYXJ5IHRvIHNldCBjZW50ZXIsIHdoaWNoIGlzIHRoZSBudW1iZXIgb2YgZ3JvdXBzIHdlIHdhbnQgdG8gY2x1c3RlciB0by4gSW4gdGhpcyBjYXNlLCB3ZSBrbm93IHRoaXMgdmFsdWUgd2lsbCBiZSAzLiBMZXTigJlzIHNldCB0aGF0LgoKTGV04oCZcyBzZWUgaG93IHdlIHdvdWxkIGJ1aWxkIHRoZSBtb2RlbCBpZiB3ZSBkaWRu4oCZdCBrbm93IGl0LgoKYGBge3IgRmluZGluZyBDZW50ZXJ9CnNldC5zZWVkKDEwMSkKaXJpc0NsdXN0ZXIgPC0ga21lYW5zKGRmWywxOjRdLCBjZW50ZXI9MywgbnN0YXJ0PTIwKQppcmlzQ2x1c3RlcgpgYGAKIENvbXBhcmUgdGhlIHByZWRpY3RlZCBjbHVzdGVycyB3aXRoIHRoZSBvcmlnaW5hbCBkYXRhLgogCmBgYHtyIENvbXBhcmUgcHJlZGljdGVkIHdpdGggb3JpZ2luYWx9CnRhYmxlKGlyaXNDbHVzdGVyJGNsdXN0ZXIsIGRmJFNwZWNpZXMpCmBgYAogUGxvdCBvdXQgdGhlc2UgY2x1c3RlcnM6CiAKYGBge3J9CmxpYnJhcnkoY2x1c3RlcikKY2x1c3Bsb3QoaXJpcywgaXJpc0NsdXN0ZXIkY2x1c3RlciwgY29sb3I9VCwgc2hhZGU9VCwgbGFiZWxzPTAsIGxpbmVzPTApCmBgYApXZSBjYW4gc2VlIHRoZSBzZXRvc2EgY2x1c3RlciBwZXJmZWN0bHkgZXhwbGFpbmVkLCBtZWFud2hpbGUgdmlyZ2luaWNhIGFuZCB2ZXJzaWNvbG9yIGhhdmUgYSBsaXR0bGUgbm9pc2UgYmV0d2VlbiB0aGVpciBjbHVzdGVycy4KCklmIHdlIHdvdWxkIHdhbnQgdG8ga25vdyB0aGUgZXhhY3RseSBudW1iZXIgb2YgY2VudGVycywgd2Ugc2hvdWxkIGhhdmUgYnVpbHQgdGhlIGVsYm93IG1ldGhvZC4KCmBgYHtyfQp0b3Qud2l0aGluc3MgPC0gdmVjdG9yKG1vZGU9ImNoYXJhY3RlciIsIGxlbmd0aD0xMCkKZm9yIChpIGluIDE6MTApewogIGlyaXNDbHVzdGVyIDwtIGttZWFucyhkZlssMTo0XSwgY2VudGVyPWksIG5zdGFydD0yMCkKICB0b3Qud2l0aGluc3NbaV0gPC0gaXJpc0NsdXN0ZXIkdG90LndpdGhpbnNzCn0KYGBgCgpgYGB7cn0KcGxvdCgxOjEwLCB0b3Qud2l0aGluc3MsIHR5cGU9ImIiLCBwY2g9MTkpCmBgYApUaGlzIHNob3dzIHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycyBhcyAzLgo=