Data Science Stream

Topic 7B: Big Data I (Clustering)


Example R code solutions for the Data Science Computer Lab 7, which uses penguin data from the palmerpenguins R package1 (Horst, Hill, and Gorman 2020), are presented below.

1 Cluster Analysis

Purpose

No answer required.

Clustering Techniques

No answer required.

2 Preparations

# Specify required packages
cluster_packages <- c("cluster", "factoextra", "ggfortify", "palmerpenguins")
# Install missing packages
install.packages(setdiff(cluster_packages, rownames(installed.packages())))
# Load all packages
lapply(cluster_packages, library, character.only = TRUE)

2.1 Penguin Data

No answer required.

2.2

plot(penguins)

2.3

For several of the continuous random variable scatter plots, there does appear to be visible clustering occurring - e.g. flipper_length_mm vs bill_depth_mm suggests that there are two distinct clusters. Therefore applying a clustering technique does seem to be warranted.

2.4 Preparing data for Cluster Analysis

No answer required.

2.4.1

penguins <- na.omit(penguins)
penguins_subset <- penguins[, 3:6]

2.4.2

No answer required.

2.4.3

penguins_scaled <- scale(penguins_subset)
head(penguins_scaled)
##      bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
## [1,]     -0.8946955     0.7795590        -1.4246077  -0.5676206
## [2,]     -0.8215515     0.1194043        -1.0678666  -0.5055254
## [3,]     -0.6752636     0.4240910        -0.4257325  -1.1885721
## [4,]     -1.3335592     1.0842457        -0.5684290  -0.9401915
## [5,]     -0.8581235     1.7444004        -0.7824736  -0.6918109
## [6,]     -0.9312674     0.3225288        -1.4246077  -0.7228585

3 \(k\)-means Clustering

3.1

set.seed(1) # included for reproducibility purposes
kmeans_fit3 <- kmeans(penguins_scaled, 3)

3.2

We can check the cluster membership as follows:

kmeans_fit3$cluster
##   [1] 1 1 1 1 1 1 1 1 1 1 1 1 2 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##  [38] 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1
##  [75] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 2 1 1 1 1 1
## [112] 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 3
## [149] 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
## [186] 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
## [223] 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
## [260] 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 1 2 1 2 2 2 2 2 2 2 1
## [297] 2 1 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 2 2 2 2 2

The between_ss /total_ss value is 72.1%.

3.3 Visualising our results

plot(penguins[, c(1,3:6)], col = kmeans_fit3$cluster)

As we can see, the clusters are actually doing a pretty good job of separating penguins by species!

3.3.1

plot(as.numeric(penguins$species), col = kmeans_fit3$cluster, 
     xlab = "penguin ID", 
     ylab = "(Adelie = 1, Chinstrap = 2, Gentoo = 3", las = 1)

Based on these plots, it seems that the \(k\)-means clustering has done a great job of clustering the penguins into clusters of different species. If you look carefully, there is some error when distinguishing between Chinstrap and Adelie penguins. If we also include more data such as sex or island, we might be able to more clearly distinguish between these two species.

3.3.2 Two-way Table Summary

💻

two_way_table <- table(as.numeric(penguins$species), kmeans_fit3$cluster)
dimnames(two_way_table) <- list(levels(penguins$species),
                                c("Cluster 1", "Cluster 2", "Cluster 3")
                                )
two_way_table
##           Cluster 1 Cluster 2 Cluster 3
## Adelie           22         0       124
## Chinstrap        63         0         5
## Gentoo            0       119         0

The two-way table highlights the accuracy of the clustering - only a few Chinstrap penguins and some Adelie penguins have been misclassified.

3.3.3 Cluster plots

fviz_cluster(kmeans_fit3, data = penguins_scaled)

3.4

kmeans_fit2 <- kmeans(penguins_scaled, 2)
kmeans_fit4 <- kmeans(penguins_scaled, 4)

The between_ss /total_ss results for these new cluster fits are:

  • 58.5% for \(k=2\).
  • 77.1% for \(k=4\).

The between_ss /total_ss value actually increases as we increase the number of clusters, which isn’t necessarily accurate - we know e.g. that there are not four species of penguin in our data (what happens if we use \(k=10\)?!).

3.5 Diagnostics

No answer required.

3.5.1 The silhouette method

kmeans_sil <- fviz_nbclust(penguins_scaled, kmeans, method = "silhouette")
kmeans_sil

Based on this plot, it seems that the optimal number of clusters is two.

3.5.2 The gap statistic method

kmeans_gap <- fviz_nbclust(penguins_scaled, kmeans, method = "gap")
kmeans_gap

Based on this plot, it seems that the optimal number of clusters is four.

3.5.3

fviz_cluster(kmeans_fit2, data = penguins_scaled)

fviz_cluster(kmeans_fit4, data = penguins_scaled)

3.6

Answers may vary here. Three or four clusters seem reasonable.

4 Extension 1: PAM Clustering

4.1

pam_fit2 <- pam(penguins_scaled, 2)
pam_fit3 <- pam(penguins_scaled, 3)
pam_fit4 <- pam(penguins_scaled, 4)

4.2

pam_sil <- fviz_nbclust(penguins_scaled, pam, method = "silhouette")
pam_sil

These results suggest that two clusters are optimal.

4.3

The R code below shows results for a cluster choice of three.

fviz_cluster(pam_fit3, data = penguins_scaled)

plot(as.numeric(penguins$species), col = pam_fit3$clustering)

We can see from these plots that the PAM clustering does a very good job of clustering penguins into species (although there is still some trouble distinguishing Adelie and Chinstrap penguins). For the larger numbers of clusters, the algorithm may also be distinguishing between male and female penguins - try checking if this is true.

5 Extension 2: Hierarchical Clustering

5.1

hier_fit <- agnes(penguins_scaled)

5.2

plot(hier_fit, which = 2)

5.3

hier_fit <- agnes(penguins_scaled)
hier_fit_single <- agnes(penguins_scaled, method = "single")
hier_fit_ward <- agnes(penguins_scaled, method = "ward")
plot(hier_fit_single, which = 2)

plot(hier_fit_ward, which = 2)

The dendrograms all look quite different. The ward method one looks perhaps the cleanest and easiest to assess.

5.4

The code shown below produces a coloured dendrogram with three clusters, for the hierarchical clustering algorithm using the “ward” measurement method.

penguin_dendro <- hcut(penguins_scaled, 
                k = 3, 
                hc_func = "agnes",
                hc_method = "ward",
                hc_metric = "euclidean") 

fviz_dend(penguin_dendro)

6 Extension 3: Fuzzy Clustering

6.1

The R code below shows fits for the different cluster number options.

fuzzy_fit2 <- fanny(penguins_scaled, 2)
fuzzy_fit3 <- fanny(penguins_scaled, 3)
fuzzy_fit4 <- fanny(penguins_scaled, 4)

Please note that output for these fits is omitted due to the length of the results.

6.2

fuzzy_sil <- fviz_nbclust(penguins_scaled, fanny, method = "silhouette")
fuzzy_sil

We obtain the same results as for the \(k\)-means and PAM clustering here, i.e. we have a result of two clusters.

6.3

The R code below shows results for a cluster choice of two.

fviz_cluster(fuzzy_fit2, data = penguins_scaled)

# This code assumes you are assessing a result stored in the object fuzzy_fit2
plot(as.numeric(penguins$species), col = fuzzy_fit2$cluster)

From these plots we can see that the fuzzy clustering performs similarly to the previous methods.


That’s all the content covered.


References

Horikoshi, M., Y. Tang, A. Dickey, M. Grenie, R. Thompson, L. Selzer, D. Strbenac, K. Voronin, and D. Pulatov. 2021. ggfortify: Data Visualization Tools for Statistical Analysis Results. https://github.com/sinhrks/ggfortify.
Horst, Allison Marie, Alison Presmanes Hill, and Kristen B Gorman. 2020. Palmerpenguins: Palmer Archipelago (Antarctica) Penguin Data. https://doi.org/10.5281/zenodo.3960218.
Kassambara, A., and F. Mundt. 2020. factoextra: Extract and Visualize the Results of Multivariate Data Analyses. http://www.sthda.com/english/rpkgs/factoextra.
R Core Team. 2021. R: A Language and Environment for Statistical Computing. Vienna, Austria: R Foundation for Statistical Computing. https://www.R-project.org/.
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.


  1. We also utilise the packages factoextra (Kassambara and Mundt 2020), ggfortify (Horikoshi et al. 2021) and cluster(R Core Team 2021).↩︎

LS0tDQp0aXRsZTogIlNUTTEwMDE6IENvbXB1dGVyIExhYiA3QiBTb2x1dGlvbnMiDQpvdXRwdXQ6DQogIGJvb2tkb3duOjpodG1sX2RvY3VtZW50MjogDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRoZW1lOiByZWFkYWJsZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KYmlibGlvZ3JhcGh5OiBTVE0xMDAxX0RTX0NMX3JlZmVyZW5jZXMuYmliIA0KbGluay1jaXRhdGlvbnM6IHllcw0KLS0tDQoNCjxzdHlsZT4NCiNUT0Mgew0KICBiYWNrZ3JvdW5kOiB1cmwoImh0dHBzOi8vd3d3LmxhdHJvYmUuZWR1LmF1L19tZWRpYS9sYS10cm9iZS1hcGkvdjUvaW1nL2xvZ28uc3ZnIik7DQogIGJhY2tncm91bmQtc2l6ZTogY29udGFpbjsNCiAgcGFkZGluZy10b3A6IDgwcHggIWltcG9ydGFudDsNCiAgYmFja2dyb3VuZC1yZXBlYXQ6IG5vLXJlcGVhdDsNCn0NCjwvc3R5bGU+DQoNCiMjIyBEYXRhIFNjaWVuY2UgU3RyZWFtIHstfQ0KDQoNCiMjIyBUb3BpYyA3QjogQmlnIERhdGEgSSAoQ2x1c3RlcmluZykgey19DQoNCjxicj4NCg0KRXhhbXBsZSBSIGNvZGUgc29sdXRpb25zIGZvciB0aGUgW0RhdGEgU2NpZW5jZSBDb21wdXRlciBMYWIgN10oaHR0cHM6Ly9ycHVicy5jb20vTFRVX1NUTTEwMDEvU01EU01DTDcpLCB3aGljaCB1c2VzIHBlbmd1aW4gZGF0YSBmcm9tIHRoZSBgcGFsbWVycGVuZ3VpbnNgIFIgcGFja2FnZV5bV2UgYWxzbyB1dGlsaXNlIHRoZSBwYWNrYWdlcyBgZmFjdG9leHRyYWAgW0BmYWN0b10sIGBnZ2ZvcnRpZnlgIFtAZ2ddIGFuZCBgY2x1c3RlcmBbQFJdLl0gW0BwZW5ndWluc10sIGFyZSBwcmVzZW50ZWQgYmVsb3cuDQoNCiMgQ2x1c3RlciBBbmFseXNpcw0KDQojIyBQdXJwb3NlIHstfQ0KDQpObyBhbnN3ZXIgcmVxdWlyZWQuDQoNCiMjIENsdXN0ZXJpbmcgVGVjaG5pcXVlcyB7LX0NCg0KTm8gYW5zd2VyIHJlcXVpcmVkLg0KDQojIFByZXBhcmF0aW9ucyB7I3ByZXB9DQoNCmBgYHtyIGV2YWwgPSBULCBpbmNsdWRlID0gRn0NCmxpYnJhcnkoY2x1c3RlcikNCmxpYnJhcnkoZmFjdG9leHRyYSkNCmxpYnJhcnkoZ2dmb3J0aWZ5KQ0KbGlicmFyeShwYWxtZXJwZW5ndWlucykNCmBgYA0KDQpgYGB7ciBldmFsID0gRiwgZWNobyA9IFR9DQojIFNwZWNpZnkgcmVxdWlyZWQgcGFja2FnZXMNCmNsdXN0ZXJfcGFja2FnZXMgPC0gYygiY2x1c3RlciIsICJmYWN0b2V4dHJhIiwgImdnZm9ydGlmeSIsICJwYWxtZXJwZW5ndWlucyIpDQojIEluc3RhbGwgbWlzc2luZyBwYWNrYWdlcw0KaW5zdGFsbC5wYWNrYWdlcyhzZXRkaWZmKGNsdXN0ZXJfcGFja2FnZXMsIHJvd25hbWVzKGluc3RhbGxlZC5wYWNrYWdlcygpKSkpDQojIExvYWQgYWxsIHBhY2thZ2VzDQpsYXBwbHkoY2x1c3Rlcl9wYWNrYWdlcywgbGlicmFyeSwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQ0KYGBgDQoNCiMjIFBlbmd1aW4gRGF0YQ0KDQpObyBhbnN3ZXIgcmVxdWlyZWQuDQoNCiMjDQoNCmBgYHtyIGV2YWwgPSBULCBlY2hvID0gVCwgZmlnLmRpbSA9IGMoOCwxMCl9DQpwbG90KHBlbmd1aW5zKQ0KYGBgDQoNCiMjDQoNCkZvciBzZXZlcmFsIG9mIHRoZSBjb250aW51b3VzIHJhbmRvbSB2YXJpYWJsZSBzY2F0dGVyIHBsb3RzLCB0aGVyZSBkb2VzIGFwcGVhciB0byBiZSB2aXNpYmxlIGNsdXN0ZXJpbmcgb2NjdXJyaW5nIC0gZS5nLiBgZmxpcHBlcl9sZW5ndGhfbW1gIHZzIGBiaWxsX2RlcHRoX21tYCBzdWdnZXN0cyB0aGF0IHRoZXJlIGFyZSB0d28gZGlzdGluY3QgY2x1c3RlcnMuIFRoZXJlZm9yZSBhcHBseWluZyBhIGNsdXN0ZXJpbmcgdGVjaG5pcXVlIGRvZXMgc2VlbSB0byBiZSB3YXJyYW50ZWQuDQoNCiMjIFByZXBhcmluZyBkYXRhIGZvciBDbHVzdGVyIEFuYWx5c2lzDQoNCk5vIGFuc3dlciByZXF1aXJlZC4NCg0KIyMjDQoNCmBgYHtyIGV2YWwgPSBULCBlY2hvID0gVH0NCnBlbmd1aW5zIDwtIG5hLm9taXQocGVuZ3VpbnMpDQpwZW5ndWluc19zdWJzZXQgPC0gcGVuZ3VpbnNbLCAzOjZdDQpgYGANCg0KIyMjDQoNCk5vIGFuc3dlciByZXF1aXJlZC4NCg0KIyMjDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFR9DQpwZW5ndWluc19zY2FsZWQgPC0gc2NhbGUocGVuZ3VpbnNfc3Vic2V0KQ0KaGVhZChwZW5ndWluc19zY2FsZWQpDQpgYGANCg0KIyAkayQtbWVhbnMgQ2x1c3RlcmluZw0KDQojIyB7I2ttZWFuczN9DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGNhY2hlID0gVH0NCnNldC5zZWVkKDEpICMgaW5jbHVkZWQgZm9yIHJlcHJvZHVjaWJpbGl0eSBwdXJwb3Nlcw0Ka21lYW5zX2ZpdDMgPC0ga21lYW5zKHBlbmd1aW5zX3NjYWxlZCwgMykNCmBgYA0KDQojIw0KDQpXZSBjYW4gY2hlY2sgdGhlIGNsdXN0ZXIgbWVtYmVyc2hpcCBhcyBmb2xsb3dzOg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFR9DQprbWVhbnNfZml0MyRjbHVzdGVyDQpgYGANCg0KVGhlIGBiZXR3ZWVuX3NzIC90b3RhbF9zc2AgdmFsdWUgaXMgNzIuMSUuDQoNCiMjIFZpc3VhbGlzaW5nIG91ciByZXN1bHRzIHsja21lYW5zdmlzdWFsaXNlfQ0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFQsIGZpZy5kaW0gPSBjKDgsIDEwKX0NCnBsb3QocGVuZ3VpbnNbLCBjKDEsMzo2KV0sIGNvbCA9IGttZWFuc19maXQzJGNsdXN0ZXIpDQpgYGANCg0KQXMgd2UgY2FuIHNlZSwgdGhlIGNsdXN0ZXJzIGFyZSBhY3R1YWxseSBkb2luZyBhIHByZXR0eSBnb29kIGpvYiBvZiBzZXBhcmF0aW5nIHBlbmd1aW5zIGJ5IHNwZWNpZXMhDQoNCiMjIyANCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBULCBmaWcuZGltID0gYyg2LCA0KX0NCnBsb3QoYXMubnVtZXJpYyhwZW5ndWlucyRzcGVjaWVzKSwgY29sID0ga21lYW5zX2ZpdDMkY2x1c3RlciwgDQogICAgIHhsYWIgPSAicGVuZ3VpbiBJRCIsIA0KICAgICB5bGFiID0gIihBZGVsaWUgPSAxLCBDaGluc3RyYXAgPSAyLCBHZW50b28gPSAzIiwgbGFzID0gMSkNCmBgYA0KDQpCYXNlZCBvbiB0aGVzZSBwbG90cywgaXQgc2VlbXMgdGhhdCB0aGUgJGskLW1lYW5zIGNsdXN0ZXJpbmcgaGFzIGRvbmUgYSBncmVhdCBqb2Igb2YgY2x1c3RlcmluZyB0aGUgcGVuZ3VpbnMgaW50byBjbHVzdGVycyBvZiBkaWZmZXJlbnQgc3BlY2llcy4gSWYgeW91IGxvb2sgY2FyZWZ1bGx5LCB0aGVyZSBpcyBzb21lIGVycm9yIHdoZW4gZGlzdGluZ3Vpc2hpbmcgYmV0d2VlbiBDaGluc3RyYXAgYW5kIEFkZWxpZSBwZW5ndWlucy4gSWYgd2UgYWxzbyBpbmNsdWRlIG1vcmUgZGF0YSBzdWNoIGFzIGBzZXhgIG9yIGBpc2xhbmRgLCB3ZSBtaWdodCBiZSBhYmxlIHRvIG1vcmUgY2xlYXJseSBkaXN0aW5ndWlzaCBiZXR3ZWVuIHRoZXNlIHR3byBzcGVjaWVzLg0KDQojIyMgVHdvLXdheSBUYWJsZSBTdW1tYXJ5DQoNCmByIGVtbzo6amkoImNvbXB1dGVyIilgIA0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBUfQ0KdHdvX3dheV90YWJsZSA8LSB0YWJsZShhcy5udW1lcmljKHBlbmd1aW5zJHNwZWNpZXMpLCBrbWVhbnNfZml0MyRjbHVzdGVyKQ0KZGltbmFtZXModHdvX3dheV90YWJsZSkgPC0gbGlzdChsZXZlbHMocGVuZ3VpbnMkc3BlY2llcyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoIkNsdXN0ZXIgMSIsICJDbHVzdGVyIDIiLCAiQ2x1c3RlciAzIikNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQ0KdHdvX3dheV90YWJsZQ0KDQpgYGANCg0KVGhlIHR3by13YXkgdGFibGUgaGlnaGxpZ2h0cyB0aGUgYWNjdXJhY3kgb2YgdGhlIGNsdXN0ZXJpbmcgLSBvbmx5IGEgZmV3IENoaW5zdHJhcCBwZW5ndWlucyBhbmQgc29tZSBBZGVsaWUgcGVuZ3VpbnMgaGF2ZSBiZWVuIG1pc2NsYXNzaWZpZWQuDQoNCiMjIyBDbHVzdGVyIHBsb3RzICB7I2ttZWFuc3Zpen0NCg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBUfQ0KZnZpel9jbHVzdGVyKGttZWFuc19maXQzLCBkYXRhID0gcGVuZ3VpbnNfc2NhbGVkKQ0KYGBgDQoNCiMjIHsja21lYW5zbW9yZX0NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBUfQ0Ka21lYW5zX2ZpdDIgPC0ga21lYW5zKHBlbmd1aW5zX3NjYWxlZCwgMikNCmttZWFuc19maXQ0IDwtIGttZWFucyhwZW5ndWluc19zY2FsZWQsIDQpDQpgYGANCg0KVGhlIGBiZXR3ZWVuX3NzIC90b3RhbF9zc2AgcmVzdWx0cyBmb3IgdGhlc2UgbmV3IGNsdXN0ZXIgZml0cyBhcmU6DQoNCiogNTguNSUgZm9yICRrPTIkLg0KKiA3Ny4xJSBmb3IgJGs9NCQuDQoNClRoZSBgYmV0d2Vlbl9zcyAvdG90YWxfc3NgIHZhbHVlIGFjdHVhbGx5IGluY3JlYXNlcyBhcyB3ZSBpbmNyZWFzZSB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzLCB3aGljaCBpc24ndCBuZWNlc3NhcmlseSBhY2N1cmF0ZSAtIHdlIGtub3cgZS5nLiB0aGF0IHRoZXJlIGFyZSBub3QgZm91ciBzcGVjaWVzIG9mIHBlbmd1aW4gaW4gb3VyIGRhdGEgKHdoYXQgaGFwcGVucyBpZiB3ZSB1c2UgJGs9MTAkPyEpLg0KDQojIyBEaWFnbm9zdGljcyB7I25iY2x1c3R9DQoNCk5vIGFuc3dlciByZXF1aXJlZC4NCg0KIyMjIFRoZSBzaWxob3VldHRlIG1ldGhvZA0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFQsIGZpZy5kaW0gPSBjKDYsIDQpfQ0Ka21lYW5zX3NpbCA8LSBmdml6X25iY2x1c3QocGVuZ3VpbnNfc2NhbGVkLCBrbWVhbnMsIG1ldGhvZCA9ICJzaWxob3VldHRlIikNCmttZWFuc19zaWwNCmBgYA0KDQpCYXNlZCBvbiB0aGlzIHBsb3QsIGl0IHNlZW1zIHRoYXQgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzIGlzIHR3by4NCg0KIyMjIFRoZSBnYXAgc3RhdGlzdGljIG1ldGhvZA0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFQsIGZpZy5kaW0gPSBjKDYsIDQpfQ0Ka21lYW5zX2dhcCA8LSBmdml6X25iY2x1c3QocGVuZ3VpbnNfc2NhbGVkLCBrbWVhbnMsIG1ldGhvZCA9ICJnYXAiKQ0Ka21lYW5zX2dhcA0KYGBgDQoNCkJhc2VkIG9uIHRoaXMgcGxvdCwgaXQgc2VlbXMgdGhhdCB0aGUgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMgaXMgZm91ci4NCg0KIyMjIA0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFQsIGZpZy5kaW0gPSBjKDYsIDQpfQ0KZnZpel9jbHVzdGVyKGttZWFuc19maXQyLCBkYXRhID0gcGVuZ3VpbnNfc2NhbGVkKQ0KZnZpel9jbHVzdGVyKGttZWFuc19maXQ0LCBkYXRhID0gcGVuZ3VpbnNfc2NhbGVkKQ0KYGBgDQoNCiMjIA0KDQpBbnN3ZXJzIG1heSB2YXJ5IGhlcmUuIFRocmVlIG9yIGZvdXIgY2x1c3RlcnMgc2VlbSByZWFzb25hYmxlLg0KDQojIEV4dGVuc2lvbiAxOiBQQU0gQ2x1c3RlcmluZw0KDQojIyB7I3BhbX0NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBUfQ0KcGFtX2ZpdDIgPC0gcGFtKHBlbmd1aW5zX3NjYWxlZCwgMikNCnBhbV9maXQzIDwtIHBhbShwZW5ndWluc19zY2FsZWQsIDMpDQpwYW1fZml0NCA8LSBwYW0ocGVuZ3VpbnNfc2NhbGVkLCA0KQ0KYGBgDQoNCiMjIHsjcGFtYmVzdH0NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBULCBmaWcuZGltID0gYyg2LCA0KX0NCnBhbV9zaWwgPC0gZnZpel9uYmNsdXN0KHBlbmd1aW5zX3NjYWxlZCwgcGFtLCBtZXRob2QgPSAic2lsaG91ZXR0ZSIpDQpwYW1fc2lsDQpgYGANCg0KVGhlc2UgcmVzdWx0cyBzdWdnZXN0IHRoYXQgdHdvIGNsdXN0ZXJzIGFyZSBvcHRpbWFsLg0KDQojIw0KDQpUaGUgUiBjb2RlIGJlbG93IHNob3dzIHJlc3VsdHMgZm9yIGEgY2x1c3RlciBjaG9pY2Ugb2YgdGhyZWUuDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGNhY2hlID0gVCwgZmlnLmRpbSA9IGMoNiwgNCl9DQpmdml6X2NsdXN0ZXIocGFtX2ZpdDMsIGRhdGEgPSBwZW5ndWluc19zY2FsZWQpDQpgYGANCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBULCBmaWcuZGltID0gYyg2LCA0KX0NCnBsb3QoYXMubnVtZXJpYyhwZW5ndWlucyRzcGVjaWVzKSwgY29sID0gcGFtX2ZpdDMkY2x1c3RlcmluZykNCmBgYA0KDQpXZSBjYW4gc2VlIGZyb20gdGhlc2UgcGxvdHMgdGhhdCB0aGUgUEFNIGNsdXN0ZXJpbmcgZG9lcyBhIHZlcnkgZ29vZCBqb2Igb2YgY2x1c3RlcmluZyBwZW5ndWlucyBpbnRvIHNwZWNpZXMgKGFsdGhvdWdoIHRoZXJlIGlzIHN0aWxsIHNvbWUgdHJvdWJsZSBkaXN0aW5ndWlzaGluZyBBZGVsaWUgYW5kIENoaW5zdHJhcCBwZW5ndWlucykuIEZvciB0aGUgbGFyZ2VyIG51bWJlcnMgb2YgY2x1c3RlcnMsIHRoZSBhbGdvcml0aG0gbWF5IGFsc28gYmUgZGlzdGluZ3Vpc2hpbmcgYmV0d2VlbiBtYWxlIGFuZCBmZW1hbGUgcGVuZ3VpbnMgLSB0cnkgY2hlY2tpbmcgaWYgdGhpcyBpcyB0cnVlLg0KDQojIEV4dGVuc2lvbiAyOiBIaWVyYXJjaGljYWwgQ2x1c3RlcmluZw0KDQojIyB7I2hpZXJhcmNoaWNhbH0NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBUfQ0KaGllcl9maXQgPC0gYWduZXMocGVuZ3VpbnNfc2NhbGVkKQ0KYGBgDQoNCiMjDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGZpZy5kaW0gPSBjKDgsIDYpLCBjYWNoZSA9IFR9DQpwbG90KGhpZXJfZml0LCB3aGljaCA9IDIpDQpgYGANCg0KIyMNCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBUfQ0KaGllcl9maXQgPC0gYWduZXMocGVuZ3VpbnNfc2NhbGVkKQ0KaGllcl9maXRfc2luZ2xlIDwtIGFnbmVzKHBlbmd1aW5zX3NjYWxlZCwgbWV0aG9kID0gInNpbmdsZSIpDQpoaWVyX2ZpdF93YXJkIDwtIGFnbmVzKHBlbmd1aW5zX3NjYWxlZCwgbWV0aG9kID0gIndhcmQiKQ0KYGBgDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGZpZy5kaW0gPSBjKDgsIDYpLCBjYWNoZSA9IFR9DQpwbG90KGhpZXJfZml0X3NpbmdsZSwgd2hpY2ggPSAyKQ0KcGxvdChoaWVyX2ZpdF93YXJkLCB3aGljaCA9IDIpDQpgYGANCg0KVGhlIGRlbmRyb2dyYW1zIGFsbCBsb29rIHF1aXRlIGRpZmZlcmVudC4gVGhlIGB3YXJkYCBtZXRob2Qgb25lIGxvb2tzIHBlcmhhcHMgdGhlIGNsZWFuZXN0IGFuZCBlYXNpZXN0IHRvIGFzc2Vzcy4NCg0KIyMgDQoNClRoZSBjb2RlIHNob3duIGJlbG93IHByb2R1Y2VzIGEgY29sb3VyZWQgZGVuZHJvZ3JhbSB3aXRoIHRocmVlIGNsdXN0ZXJzLCBmb3IgdGhlIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGFsZ29yaXRobSB1c2luZyB0aGUgIndhcmQiIG1lYXN1cmVtZW50IG1ldGhvZC4NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBULCBmaWcuZGltID0gYyg4LCA2KSwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KcGVuZ3Vpbl9kZW5kcm8gPC0gaGN1dChwZW5ndWluc19zY2FsZWQsIA0KICAgICAgICAgICAgICAgIGsgPSAzLCANCiAgICAgICAgICAgICAgICBoY19mdW5jID0gImFnbmVzIiwNCiAgICAgICAgICAgICAgICBoY19tZXRob2QgPSAid2FyZCIsDQogICAgICAgICAgICAgICAgaGNfbWV0cmljID0gImV1Y2xpZGVhbiIpIA0KDQpmdml6X2RlbmQocGVuZ3Vpbl9kZW5kcm8pDQpgYGANCg0KIyBFeHRlbnNpb24gMzogRnV6enkgQ2x1c3RlcmluZw0KDQojIw0KDQpUaGUgUiBjb2RlIGJlbG93IHNob3dzIGZpdHMgZm9yIHRoZSBkaWZmZXJlbnQgY2x1c3RlciBudW1iZXIgb3B0aW9ucy4NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBUfQ0KZnV6enlfZml0MiA8LSBmYW5ueShwZW5ndWluc19zY2FsZWQsIDIpDQpmdXp6eV9maXQzIDwtIGZhbm55KHBlbmd1aW5zX3NjYWxlZCwgMykNCmZ1enp5X2ZpdDQgPC0gZmFubnkocGVuZ3VpbnNfc2NhbGVkLCA0KQ0KYGBgDQoNClBsZWFzZSBub3RlIHRoYXQgb3V0cHV0IGZvciB0aGVzZSBmaXRzIGlzIG9taXR0ZWQgZHVlIHRvIHRoZSBsZW5ndGggb2YgdGhlIHJlc3VsdHMuDQoNCiMjIHsjZnV6enliZXN0fQ0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFQsIGZpZy5kaW0gPSBjKDYsIDQpLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQpmdXp6eV9zaWwgPC0gZnZpel9uYmNsdXN0KHBlbmd1aW5zX3NjYWxlZCwgZmFubnksIG1ldGhvZCA9ICJzaWxob3VldHRlIikNCmZ1enp5X3NpbA0KYGBgDQoNCldlIG9idGFpbiB0aGUgc2FtZSByZXN1bHRzIGFzIGZvciB0aGUgJGskLW1lYW5zIGFuZCBQQU0gY2x1c3RlcmluZyBoZXJlLCBpLmUuIHdlIGhhdmUgYSByZXN1bHQgb2YgdHdvIGNsdXN0ZXJzLg0KDQojIw0KDQpUaGUgUiBjb2RlIGJlbG93IHNob3dzIHJlc3VsdHMgZm9yIGEgY2x1c3RlciBjaG9pY2Ugb2YgdHdvLg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFQsIGZpZy5kaW0gPSBjKDYsIDQpfQ0KZnZpel9jbHVzdGVyKGZ1enp5X2ZpdDIsIGRhdGEgPSBwZW5ndWluc19zY2FsZWQpDQpgYGANCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBULCBmaWcuZGltID0gYyg2LCA0KX0NCiMgVGhpcyBjb2RlIGFzc3VtZXMgeW91IGFyZSBhc3Nlc3NpbmcgYSByZXN1bHQgc3RvcmVkIGluIHRoZSBvYmplY3QgZnV6enlfZml0Mg0KcGxvdChhcy5udW1lcmljKHBlbmd1aW5zJHNwZWNpZXMpLCBjb2wgPSBmdXp6eV9maXQyJGNsdXN0ZXIpDQpgYGANCg0KRnJvbSB0aGVzZSBwbG90cyB3ZSBjYW4gc2VlIHRoYXQgdGhlIGZ1enp5IGNsdXN0ZXJpbmcgcGVyZm9ybXMgc2ltaWxhcmx5IHRvIHRoZSBwcmV2aW91cyBtZXRob2RzLg0KDQo8YnI+DQoNCiMjIyMgVGhhdCdzIGFsbCB0aGUgY29udGVudCBjb3ZlcmVkLiAjIyMjIHstfQ0KDQo8YnI+DQoNCiMgUmVmZXJlbmNlcyB7LSAjUmVmfQ0KPGRpdiBpZD0icmVmcyI+PC9kaXY+DQoNCjxicj4NCg0KPGZvbnQgY29sb3IgPSAiZ3JleSI+DQpUaGVzZSBub3RlcyBoYXZlIGJlZW4gcHJlcGFyZWQgYnkgUnVwZXJ0IEt1dmVrZS4gUGxlYXNlIG5vdGUgdGhhdCBzb21lIG9mIHRoZSBjb250ZW50IGluIHRoZXNlIG5vdGVzIGhhcyBiZWVuIGRldmVsb3BlZCBmcm9tIGNvbnRlbnQgaW4gQE1vZFN0YXQuIFRoZSBjb3B5cmlnaHQgZm9yIHRoZSBtYXRlcmlhbCBpbiB0aGVzZSBub3RlcyByZXNpZGVzIHdpdGggdGhlIGF1dGhvcnMgbmFtZWQgYWJvdmUsIHdpdGggdGhlIERlcGFydG1lbnQgb2YgTWF0aGVtYXRpY2FsIGFuZCBQaHlzaWNhbCBTY2llbmNlcyBhbmQgd2l0aCBMYSBUcm9iZSBVbml2ZXJzaXR5LiBDb3B5cmlnaHQgaW4gdGhpcyB3b3JrIGlzIHZlc3RlZCBpbiBMYSBUcm9iZSBVbml2ZXJzaXR5IGluY2x1ZGluZyBhbGwgTGEgVHJvYmUgVW5pdmVyc2l0eSBicmFuZGluZyBhbmQgbmFtaW5nLiBVbmxlc3Mgb3RoZXJ3aXNlIHN0YXRlZCwgbWF0ZXJpYWwgd2l0aGluIHRoaXMgd29yayBpcyBsaWNlbnNlZCB1bmRlciBhIENyZWF0aXZlIENvbW1vbnMgQXR0cmlidXRpb24tTm9uIENvbW1lcmNpYWwtTm9uIERlcml2YXRpdmVzIExpY2Vuc2UgDQo8YSBocmVmID0gImh0dHBzOi8vY3JlYXRpdmVjb21tb25zLm9yZy9saWNlbnNlcy9ieS1uYy1uZC80LjAvQ0MiIHRhcmdldD0iX2JsYW5rIj4gQlktTkMtTkQuIDwvYT4NCjwvZm9udD4=