Science/Health Science/Data Science Module
Cluster Analysis
Purpose
No answer required.
Clustering Techniques
No answer required.
Preparations
install.packages(c("factoextra", "ggfortify", "palmerpenguins"))
library(cluster)
library(factoextra)
library(ggfortify)
library(palmerpenguins)
Note: If you are in the Data Science module, you should already have the palmerpenguins
package installed.
Penguin Data
No answer required.
head(penguins)
## # A tibble: 6 × 8
## species island bill_length_mm bill_depth_mm flipper_l…¹ body_…² sex year
## <fct> <fct> <dbl> <dbl> <int> <int> <fct> <int>
## 1 Adelie Torgersen 39.1 18.7 181 3750 male 2007
## 2 Adelie Torgersen 39.5 17.4 186 3800 fema… 2007
## 3 Adelie Torgersen 40.3 18 195 3250 fema… 2007
## 4 Adelie Torgersen NA NA NA NA <NA> 2007
## 5 Adelie Torgersen 36.7 19.3 193 3450 fema… 2007
## 6 Adelie Torgersen 39.3 20.6 190 3650 male 2007
## # … with abbreviated variable names ¹flipper_length_mm, ²body_mass_g
plot(penguins)

For several of the continuous random variable scatter plots, there does appear to be visible clustering occuring - 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.
Preparing data for Cluster Analysis
No answer required.
penguins <- na.omit(penguins)
penguins_subset <- penguins[, 3:6]
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
k-means Clustering
kmeans_fit3 <- kmeans(penguins_scaled, 3)
We can check the cluster membership as follows:
kmeans_fit3$cluster
## [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 1 1 1 1 1 1 1 1 1
## [38] 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 1 1 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 1 1 1 1 1 1 1 1
## [112] 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 1 1 1 1 1 1 2 3
## [149] 2 3 3 2 2 3 2 2 2 3 2 3 2 3 2 3 2 3 3 2 2 2 2 2 3 2 3 3 2 2 3 3 3 2 3 2 3
## [186] 2 3 2 2 3 2 2 3 2 3 2 3 2 3 2 2 2 2 2 3 2 3 2 3 2 3 3 2 3 2 3 3 2 2 3 2 3
## [223] 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 2 3 3 2 2 3 2 3 2 3 3 2 3 2 3 3 3 2 3 2 3
## [260] 3 2 2 3 2 3 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 1 1
## [297] 1 1 1 1 1 1 3 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 1
The between_ss /total_ss
value is 72.1%.
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!
plot(as.numeric(penguins$species), col = kmeans_fit3$cluster)

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.
kmeans_fit2 <- kmeans(penguins_scaled, 2)
kmeans_fit4 <- kmeans(penguins_scaled, 4)
kmeans_fit5 <- kmeans(penguins_scaled, 5)
The between_ss /total_ss
results for these new cluster fits are:
- 58.5% for \(k=2\),
- 77.1% for \(k=4\),
- 82.8% for \(k=5\)
The between_ss /total_ss
value actually increases as we increase the number of clusters, which isn’t necessarily accurate - we know there aren’t five species of penguin in our data (what happens if we use \(k=10\)?!).
No answer required.
kmeans_wss <- fviz_nbclust(penguins_scaled, kmeans, method = "wss")
kmeans_wss

Based on this plot, it seems that the optimal number of clusters is three. After \(k=3\), the Total Within Sum of Squares variance does not decrease as rapidly when more clusters are used.
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.
kmeans_gap <- fviz_nbclust(penguins_scaled, kmeans, method = "gap_stat")
kmeans_gap

Based on this plot, it seems that the optimal number of clusters is four.
fviz_cluster(kmeans_fit3, data = penguins_scaled)

fviz_cluster(kmeans_fit2, data = penguins_scaled)

fviz_cluster(kmeans_fit4, data = penguins_scaled)

fviz_cluster(kmeans_fit5, data = penguins_scaled)

Answers may vary here. Three or four clusters seem reasonable.
PAM Clustering
pam_fit2 <- pam(penguins_scaled, 2)
pam_fit3 <- pam(penguins_scaled, 3)
pam_fit4 <- pam(penguins_scaled, 4)
pam_fit5 <- pam(penguins_scaled, 5)
pam_wss <- fviz_nbclust(penguins_scaled, pam, method = "wss")
pam_sil <- fviz_nbclust(penguins_scaled, pam, method = "silhouette")
pam_gap <- fviz_nbclust(penguins_scaled, pam, method = "gap_stat")
pam_wss

pam_sil

pam_gap

We have different results here, of three, two and five clusters being optimal, respectively.
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.
Extension 1: Hierarchical Clustering
hier_fit <- agnes(penguins_scaled)
plot(hier_fit, which = 2)

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

plot(hier_fit_complete, which = 2)

plot(hier_fit_ward, which = 2)

The dendrograms all look quite different. The complete
or ward
method ones look perhaps the cleanest and easiest to assess.
The code shown below produces a coloured dendrogram with three clusters, for the hierarchical clustering algorithm using the “complete” measurement method.
penguin_dendro <- hcut(penguins_scaled,
k = 3,
hc_func = "agnes",
hc_method = "complete",
hc_metric = "euclidean")
fviz_dend(penguin_dendro)

Extension 2: Fuzzy Clustering
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)
fuzzy_fit5 <- fanny(penguins_scaled, 5)
Please note that output for these fits is omitted due to the length of the results.
fuzzy_wss <- fviz_nbclust(penguins_scaled, fanny, method = "wss")
fuzzy_sil <- fviz_nbclust(penguins_scaled, fanny, method = "silhouette")
fuzzy_wss

fuzzy_sil

We obtain the same results as for the k-means and PAM clustering here, i.e. we have different results of three and two, respectively.
(If we ran the gap_stat
method, we would arrive at five clusters being optimal, but this takes quite a while to run).
The R code below shows results for a cluster choice of three.
fviz_cluster(fuzzy_fit3, data = penguins_scaled)

# This code assumes you are assessing a result stored in the object fuzzy_fit3
plot(as.numeric(penguins$species), col = fuzzy_fit3$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 Mathematics and Statistics 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.
LS0tDQp0aXRsZTogIlNUTTEwMDE6IENvbXB1dGVyIExhYiA2QiBTb2x1dGlvbnMiDQpvdXRwdXQ6DQogIGJvb2tkb3duOjpodG1sX2RvY3VtZW50MjogDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRoZW1lOiByZWFkYWJsZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KYmlibGlvZ3JhcGh5OiBTVE0xMDAxX0RTX0NMX3JlZmVyZW5jZXMuYmliIA0KbGluay1jaXRhdGlvbnM6IHllcw0KLS0tDQoNCjxzdHlsZT4NCiNUT0Mgew0KICBiYWNrZ3JvdW5kOiB1cmwoImh0dHBzOi8vd3d3LmxhdHJvYmUuZWR1LmF1L19tZWRpYS9sYS10cm9iZS1hcGkvdjUvaW1nL2xvZ28uc3ZnIik7DQogIGJhY2tncm91bmQtc2l6ZTogY29udGFpbjsNCiAgcGFkZGluZy10b3A6IDgwcHggIWltcG9ydGFudDsNCiAgYmFja2dyb3VuZC1yZXBlYXQ6IG5vLXJlcGVhdDsNCn0NCjwvc3R5bGU+DQoNCiMjIyBTY2llbmNlL0hlYWx0aCBTY2llbmNlL0RhdGEgU2NpZW5jZSBNb2R1bGUgey19DQoNCg0KIyMjIFRvcGljIDZCOiBCaWcgRGF0YSBJIChDbHVzdGVyaW5nKSB7LX0NCg0KPGJyPg0KDQpFeGFtcGxlIFIgY29kZSBzb2x1dGlvbnMgZm9yIHRoZSBbV2VlayA2IFNjaWVuY2UvSGVhbHRoIFNjaWVuY2UvRGF0YSBTY2llbmNlIENvbXB1dGVyIExhYl0oaHR0cHM6Ly9ycHVicy5jb20vTFRVX1NUTTEwMDEvU01EU01DTDZfUyksIHdoaWNoIHVzZXMgcGVuZ3VpbiBkYXRhIGZyb20gdGhlIGBwYWxtZXJwZW5ndWluc2AgUiBwYWNrYWdlXltXZSBhbHNvIHV0aWxpc2UgdGhlIHBhY2thZ2VzIGBmYWN0b2V4dHJhYCBbQGZhY3RvXSwgYGdnZm9ydGlmeWAgW0BnZ10gYW5kIGBjbHVzdGVyYFtAUl0uXSBbQHBlbmd1aW5zXSwgYXJlIHByZXNlbnRlZCBiZWxvdy4NCg0KIyBDbHVzdGVyIEFuYWx5c2lzDQoNCiMjIyBQdXJwb3NlIHstfQ0KDQpObyBhbnN3ZXIgcmVxdWlyZWQuDQoNCiMjIyBDbHVzdGVyaW5nIFRlY2huaXF1ZXMgey19DQoNCk5vIGFuc3dlciByZXF1aXJlZC4NCg0KIyBQcmVwYXJhdGlvbnMgeyNwcmVwfQ0KDQpgYGB7ciBldmFsID0gVCwgaW5jbHVkZSA9IEZ9DQpsaWJyYXJ5KGNsdXN0ZXIpDQpsaWJyYXJ5KGZhY3RvZXh0cmEpDQpsaWJyYXJ5KGdnZm9ydGlmeSkNCmxpYnJhcnkocGFsbWVycGVuZ3VpbnMpDQpgYGANCg0KYGBge3IgZXZhbCA9IEYsIGVjaG8gPSBUfQ0KaW5zdGFsbC5wYWNrYWdlcyhjKCJmYWN0b2V4dHJhIiwgImdnZm9ydGlmeSIsICJwYWxtZXJwZW5ndWlucyIpKQ0KbGlicmFyeShjbHVzdGVyKQ0KbGlicmFyeShmYWN0b2V4dHJhKQ0KbGlicmFyeShnZ2ZvcnRpZnkpDQpsaWJyYXJ5KHBhbG1lcnBlbmd1aW5zKQ0KYGBgDQoNCipOb3RlOiBJZiB5b3UgYXJlIGluIHRoZSBEYXRhIFNjaWVuY2UgbW9kdWxlLCB5b3Ugc2hvdWxkIGFscmVhZHkgaGF2ZSB0aGUgYHBhbG1lcnBlbmd1aW5zYCBwYWNrYWdlIGluc3RhbGxlZC4qDQoNCiMjIFBlbmd1aW4gRGF0YQ0KDQpObyBhbnN3ZXIgcmVxdWlyZWQuDQoNCiMjDQoNCg0KYGBge3IgZXZhbCA9IFQsIGVjaG8gPSBUfQ0KaGVhZChwZW5ndWlucykNCnBsb3QocGVuZ3VpbnMpDQpgYGANCg0KRm9yIHNldmVyYWwgb2YgdGhlIGNvbnRpbnVvdXMgcmFuZG9tIHZhcmlhYmxlIHNjYXR0ZXIgcGxvdHMsIHRoZXJlIGRvZXMgYXBwZWFyIHRvIGJlIHZpc2libGUgY2x1c3RlcmluZyBvY2N1cmluZyAtIGUuZy4gYGZsaXBwZXJfbGVuZ3RoX21tYCB2cyBgYmlsbF9kZXB0aF9tbWAgc3VnZ2VzdHMgdGhhdCB0aGVyZSBhcmUgdHdvIGRpc3RpbmN0IGNsdXN0ZXJzLiBUaGVyZWZvcmUgYXBwbHlpbmcgYSBjbHVzdGVyaW5nIHRlY2huaXF1ZSBkb2VzIHNlZW0gdG8gYmUgd2FycmFudGVkLg0KDQojIyBQcmVwYXJpbmcgZGF0YSBmb3IgQ2x1c3RlciBBbmFseXNpcw0KDQpObyBhbnN3ZXIgcmVxdWlyZWQuDQoNCiMjIw0KDQpgYGB7ciBldmFsID0gVCwgZWNobyA9IFR9DQpwZW5ndWlucyA8LSBuYS5vbWl0KHBlbmd1aW5zKQ0KcGVuZ3VpbnNfc3Vic2V0IDwtIHBlbmd1aW5zWywgMzo2XQ0KYGBgDQoNCiMjIw0KDQpObyBhbnN3ZXIgcmVxdWlyZWQuDQoNCiMjIw0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBUfQ0KcGVuZ3VpbnNfc2NhbGVkIDwtIHNjYWxlKHBlbmd1aW5zX3N1YnNldCkNCmhlYWQocGVuZ3VpbnNfc2NhbGVkKQ0KYGBgDQoNCiMgay1tZWFucyBDbHVzdGVyaW5nDQoNCiMjIHsja21lYW5zM30NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBUfQ0Ka21lYW5zX2ZpdDMgPC0ga21lYW5zKHBlbmd1aW5zX3NjYWxlZCwgMykNCmBgYA0KDQojIw0KDQpXZSBjYW4gY2hlY2sgdGhlIGNsdXN0ZXIgbWVtYmVyc2hpcCBhcyBmb2xsb3dzOg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFR9DQprbWVhbnNfZml0MyRjbHVzdGVyDQpgYGANCg0KVGhlIGBiZXR3ZWVuX3NzIC90b3RhbF9zc2AgdmFsdWUgaXMgNzIuMSUuDQoNCiMjIFZpc3VhbGlzaW5nIG91ciByZXN1bHRzIHsja21lYW5zdmlzdWFsaXNlfQ0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFQsIGZpZy5kaW0gPSBjKDgsIDEwKX0NCnBsb3QocGVuZ3VpbnNbLCBjKDEsMzo2KV0sIGNvbCA9IGttZWFuc19maXQzJGNsdXN0ZXIpDQpgYGANCg0KQXMgd2UgY2FuIHNlZSwgdGhlIGNsdXN0ZXJzIGFyZSBhY3R1YWxseSBkb2luZyBhIHByZXR0eSBnb29kIGpvYiBvZiBzZXBhcmF0aW5nIHBlbmd1aW5zIGJ5IHNwZWNpZXMhDQoNCiMjDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGNhY2hlID0gVCwgZmlnLmRpbSA9IGMoNiwgNil9DQpwbG90KGFzLm51bWVyaWMocGVuZ3VpbnMkc3BlY2llcyksIGNvbCA9IGttZWFuc19maXQzJGNsdXN0ZXIpDQpgYGANCg0KQmFzZWQgb24gdGhlc2UgcGxvdHMsIGl0IHNlZW1zIHRoYXQgdGhlIGstbWVhbnMgY2x1c3RlcmluZyBoYXMgZG9uZSBhIGdyZWF0IGpvYiBvZiBjbHVzdGVyaW5nIHRoZSBwZW5ndWlucyBpbnRvIGNsdXN0ZXJzIG9mIGRpZmZlcmVudCBzcGVjaWVzLiBJZiB5b3UgbG9vayBjYXJlZnVsbHksIHRoZXJlIGlzIHNvbWUgZXJyb3Igd2hlbiBkaXN0aW5ndWlzaGluZyBiZXR3ZWVuIENoaW5zdHJhcCBhbmQgQWRlbGllIHBlbmd1aW5zLiBJZiB3ZSBhbHNvIGluY2x1ZGUgbW9yZSBkYXRhIHN1Y2ggYXMgYHNleGAgb3IgYGlzbGFuZGAsIHdlIG1pZ2h0IGJlIGFibGUgdG8gbW9yZSBjbGVhcmx5IGRpc3Rpbmd1aXNoIGJldHdlZW4gdGhlc2UgdHdvIHNwZWNpZXMuDQoNCiMjIHsja21lYW5zbW9yZX0NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBUfQ0Ka21lYW5zX2ZpdDIgPC0ga21lYW5zKHBlbmd1aW5zX3NjYWxlZCwgMikNCmttZWFuc19maXQ0IDwtIGttZWFucyhwZW5ndWluc19zY2FsZWQsIDQpDQprbWVhbnNfZml0NSA8LSBrbWVhbnMocGVuZ3VpbnNfc2NhbGVkLCA1KQ0KYGBgDQoNClRoZSBgYmV0d2Vlbl9zcyAvdG90YWxfc3NgIHJlc3VsdHMgZm9yIHRoZXNlIG5ldyBjbHVzdGVyIGZpdHMgYXJlOg0KDQoqIDU4LjUlIGZvciAkaz0yJCwNCiogNzcuMSUgZm9yICRrPTQkLA0KKiA4Mi44JSBmb3IgJGs9NSQNCg0KVGhlIGBiZXR3ZWVuX3NzIC90b3RhbF9zc2AgdmFsdWUgYWN0dWFsbHkgaW5jcmVhc2VzIGFzIHdlIGluY3JlYXNlIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMsIHdoaWNoIGlzbid0IG5lY2Vzc2FyaWx5IGFjY3VyYXRlIC0gd2Uga25vdyB0aGVyZSBhcmVuJ3QgZml2ZSBzcGVjaWVzIG9mIHBlbmd1aW4gaW4gb3VyIGRhdGEgKHdoYXQgaGFwcGVucyBpZiB3ZSB1c2UgJGs9MTAkPyEpLg0KDQojIyB7I25iY2x1c3R9DQoNCk5vIGFuc3dlciByZXF1aXJlZC4NCg0KIyMjDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGNhY2hlID0gVCwgZmlnLmRpbSA9IGMoNiwgNil9DQprbWVhbnNfd3NzIDwtIGZ2aXpfbmJjbHVzdChwZW5ndWluc19zY2FsZWQsIGttZWFucywgbWV0aG9kID0gIndzcyIpDQprbWVhbnNfd3NzDQpgYGANCg0KQmFzZWQgb24gdGhpcyBwbG90LCBpdCBzZWVtcyB0aGF0IHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycyBpcyB0aHJlZS4gQWZ0ZXIgJGs9MyQsIHRoZSBUb3RhbCBXaXRoaW4gU3VtIG9mIFNxdWFyZXMgdmFyaWFuY2UgZG9lcyBub3QgZGVjcmVhc2UgYXMgcmFwaWRseSB3aGVuIG1vcmUgY2x1c3RlcnMgYXJlIHVzZWQuDQoNCiMjIw0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFQsIGZpZy5kaW0gPSBjKDYsIDYpfQ0Ka21lYW5zX3NpbCA8LSBmdml6X25iY2x1c3QocGVuZ3VpbnNfc2NhbGVkLCBrbWVhbnMsIG1ldGhvZCA9ICJzaWxob3VldHRlIikNCmttZWFuc19zaWwNCmBgYA0KDQpCYXNlZCBvbiB0aGlzIHBsb3QsIGl0IHNlZW1zIHRoYXQgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzIGlzIHR3by4NCg0KIyMjDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGNhY2hlID0gVCwgZmlnLmRpbSA9IGMoNiwgNil9DQprbWVhbnNfZ2FwIDwtIGZ2aXpfbmJjbHVzdChwZW5ndWluc19zY2FsZWQsIGttZWFucywgbWV0aG9kID0gImdhcF9zdGF0IikNCmttZWFuc19nYXANCmBgYA0KDQpCYXNlZCBvbiB0aGlzIHBsb3QsIGl0IHNlZW1zIHRoYXQgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzIGlzIGZvdXIuDQoNCiMjDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGNhY2hlID0gVCwgZmlnLmRpbSA9IGMoNiwgNil9DQpmdml6X2NsdXN0ZXIoa21lYW5zX2ZpdDMsIGRhdGEgPSBwZW5ndWluc19zY2FsZWQpDQoNCmZ2aXpfY2x1c3RlcihrbWVhbnNfZml0MiwgZGF0YSA9IHBlbmd1aW5zX3NjYWxlZCkNCmZ2aXpfY2x1c3RlcihrbWVhbnNfZml0NCwgZGF0YSA9IHBlbmd1aW5zX3NjYWxlZCkNCmZ2aXpfY2x1c3RlcihrbWVhbnNfZml0NSwgZGF0YSA9IHBlbmd1aW5zX3NjYWxlZCkNCmBgYA0KDQojIyANCg0KQW5zd2VycyBtYXkgdmFyeSBoZXJlLiBUaHJlZSBvciBmb3VyIGNsdXN0ZXJzIHNlZW0gcmVhc29uYWJsZS4NCg0KIyBQQU0gQ2x1c3RlcmluZw0KDQojIyB7I3BhbX0NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBUfQ0KcGFtX2ZpdDIgPC0gcGFtKHBlbmd1aW5zX3NjYWxlZCwgMikNCnBhbV9maXQzIDwtIHBhbShwZW5ndWluc19zY2FsZWQsIDMpDQpwYW1fZml0NCA8LSBwYW0ocGVuZ3VpbnNfc2NhbGVkLCA0KQ0KcGFtX2ZpdDUgPC0gcGFtKHBlbmd1aW5zX3NjYWxlZCwgNSkNCmBgYA0KDQojIyB7I3BhbWJlc3R9DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGNhY2hlID0gVCwgZmlnLmRpbSA9IGMoNiwgNil9DQpwYW1fd3NzIDwtIGZ2aXpfbmJjbHVzdChwZW5ndWluc19zY2FsZWQsIHBhbSwgbWV0aG9kID0gIndzcyIpDQpwYW1fc2lsIDwtIGZ2aXpfbmJjbHVzdChwZW5ndWluc19zY2FsZWQsIHBhbSwgbWV0aG9kID0gInNpbGhvdWV0dGUiKQ0KcGFtX2dhcCA8LSBmdml6X25iY2x1c3QocGVuZ3VpbnNfc2NhbGVkLCBwYW0sIG1ldGhvZCA9ICJnYXBfc3RhdCIpDQoNCnBhbV93c3MNCnBhbV9zaWwNCnBhbV9nYXANCmBgYA0KDQpXZSBoYXZlIGRpZmZlcmVudCByZXN1bHRzIGhlcmUsIG9mIHRocmVlLCB0d28gYW5kIGZpdmUgY2x1c3RlcnMgYmVpbmcgb3B0aW1hbCwgcmVzcGVjdGl2ZWx5Lg0KDQojIw0KDQpUaGUgUiBjb2RlIGJlbG93IHNob3dzIHJlc3VsdHMgZm9yIGEgY2x1c3RlciBjaG9pY2Ugb2YgdGhyZWUuDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGNhY2hlID0gVCwgZmlnLmRpbSA9IGMoNiwgNil9DQpmdml6X2NsdXN0ZXIocGFtX2ZpdDMsIGRhdGEgPSBwZW5ndWluc19zY2FsZWQpDQpgYGANCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBULCBmaWcuZGltID0gYyg4LCA2KX0NCnBsb3QoYXMubnVtZXJpYyhwZW5ndWlucyRzcGVjaWVzKSwgY29sID0gcGFtX2ZpdDMkY2x1c3RlcmluZykNCmBgYA0KDQpXZSBjYW4gc2VlIGZyb20gdGhlc2UgcGxvdHMgdGhhdCB0aGUgUEFNIGNsdXN0ZXJpbmcgZG9lcyBhIHZlcnkgZ29vZCBqb2Igb2YgY2x1c3RlcmluZyBwZW5ndWlucyBpbnRvIHNwZWNpZXMgKGFsdGhvdWdoIHRoZXJlIGlzIHN0aWxsIHNvbWUgdHJvdWJsZSBkaXN0aW5ndWlzaGluZyBBZGVsaWUgYW5kIENoaW5zdHJhcCBwZW5ndWlucykuIEZvciB0aGUgbGFyZ2VyIG51bWJlcnMgb2YgY2x1c3RlcnMsIHRoZSBhbGdvcml0aG0gbWF5IGFsc28gYmUgZGlzdGluZ3Vpc2hpbmcgYmV0d2VlbiBtYWxlIGFuZCBmZW1hbGUgcGVuZ3VpbnMgLSB0cnkgY2hlY2tpbmcgaWYgdGhpcyBpcyB0cnVlLg0KDQojIEV4dGVuc2lvbiAxOiBIaWVyYXJjaGljYWwgQ2x1c3RlcmluZw0KDQojIyB7I2hpZXJhcmNoaWNhbH0NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBUfQ0KaGllcl9maXQgPC0gYWduZXMocGVuZ3VpbnNfc2NhbGVkKQ0KYGBgDQoNCiMjDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGZpZy5kaW0gPSBjKDgsIDYpLCBjYWNoZSA9IFR9DQpwbG90KGhpZXJfZml0LCB3aGljaCA9IDIpDQpgYGANCg0KIyMNCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBUfQ0KaGllcl9maXQgPC0gYWduZXMocGVuZ3VpbnNfc2NhbGVkKQ0KaGllcl9maXRfc2luZ2xlIDwtIGFnbmVzKHBlbmd1aW5zX3NjYWxlZCwgbWV0aG9kID0gInNpbmdsZSIpDQpoaWVyX2ZpdF9jb21wbGV0ZSA8LSBhZ25lcyhwZW5ndWluc19zY2FsZWQsIG1ldGhvZCA9ICJjb21wbGV0ZSIpDQpoaWVyX2ZpdF93YXJkIDwtIGFnbmVzKHBlbmd1aW5zX3NjYWxlZCwgbWV0aG9kID0gIndhcmQiKQ0KYGBgDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGZpZy5kaW0gPSBjKDgsIDYpLCBjYWNoZSA9IFR9DQpwbG90KGhpZXJfZml0X3NpbmdsZSwgd2hpY2ggPSAyKQ0KcGxvdChoaWVyX2ZpdF9jb21wbGV0ZSwgd2hpY2ggPSAyKQ0KcGxvdChoaWVyX2ZpdF93YXJkLCB3aGljaCA9IDIpDQpgYGANCg0KVGhlIGRlbmRyb2dyYW1zIGFsbCBsb29rIHF1aXRlIGRpZmZlcmVudC4gVGhlIGBjb21wbGV0ZWAgb3IgYHdhcmRgIG1ldGhvZCBvbmVzIGxvb2sgcGVyaGFwcyB0aGUgY2xlYW5lc3QgYW5kIGVhc2llc3QgdG8gYXNzZXNzLg0KDQojIyANCg0KVGhlIGNvZGUgc2hvd24gYmVsb3cgcHJvZHVjZXMgYSBjb2xvdXJlZCBkZW5kcm9ncmFtIHdpdGggdGhyZWUgY2x1c3RlcnMsIGZvciB0aGUgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgYWxnb3JpdGhtIHVzaW5nIHRoZSAiY29tcGxldGUiIG1lYXN1cmVtZW50IG1ldGhvZC4NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBULCBmaWcuZGltID0gYyg4LCA2KSwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KcGVuZ3Vpbl9kZW5kcm8gPC0gaGN1dChwZW5ndWluc19zY2FsZWQsIA0KICAgICAgICAgICAgICAgIGsgPSAzLCANCiAgICAgICAgICAgICAgICBoY19mdW5jID0gImFnbmVzIiwNCiAgICAgICAgICAgICAgICBoY19tZXRob2QgPSAiY29tcGxldGUiLA0KICAgICAgICAgICAgICAgIGhjX21ldHJpYyA9ICJldWNsaWRlYW4iKSANCg0KZnZpel9kZW5kKHBlbmd1aW5fZGVuZHJvKQ0KYGBgDQoNCiMgRXh0ZW5zaW9uIDI6IEZ1enp5IENsdXN0ZXJpbmcNCg0KIyMNCg0KVGhlIFIgY29kZSBiZWxvdyBzaG93cyBmaXRzIGZvciB0aGUgZGlmZmVyZW50IGNsdXN0ZXIgbnVtYmVyIG9wdGlvbnMuDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGNhY2hlID0gVH0NCmZ1enp5X2ZpdDIgPC0gZmFubnkocGVuZ3VpbnNfc2NhbGVkLCAyKQ0KZnV6enlfZml0MyA8LSBmYW5ueShwZW5ndWluc19zY2FsZWQsIDMpDQpmdXp6eV9maXQ0IDwtIGZhbm55KHBlbmd1aW5zX3NjYWxlZCwgNCkNCmZ1enp5X2ZpdDUgPC0gZmFubnkocGVuZ3VpbnNfc2NhbGVkLCA1KQ0KYGBgDQoNClBsZWFzZSBub3RlIHRoYXQgb3V0cHV0IGZvciB0aGVzZSBmaXRzIGlzIG9taXR0ZWQgZHVlIHRvIHRoZSBsZW5ndGggb2YgdGhlIHJlc3VsdHMuDQoNCiMjIHsjZnV6enliZXN0fQ0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFQsIGZpZy5kaW0gPSBjKDYsIDYpLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQpmdXp6eV93c3MgPC0gZnZpel9uYmNsdXN0KHBlbmd1aW5zX3NjYWxlZCwgZmFubnksIG1ldGhvZCA9ICJ3c3MiKQ0KZnV6enlfc2lsIDwtIGZ2aXpfbmJjbHVzdChwZW5ndWluc19zY2FsZWQsIGZhbm55LCBtZXRob2QgPSAic2lsaG91ZXR0ZSIpDQoNCmZ1enp5X3dzcw0KZnV6enlfc2lsDQpgYGANCg0KV2Ugb2J0YWluIHRoZSBzYW1lIHJlc3VsdHMgYXMgZm9yIHRoZSBrLW1lYW5zIGFuZCBQQU0gY2x1c3RlcmluZyBoZXJlLCBpLmUuIHdlIGhhdmUgZGlmZmVyZW50IHJlc3VsdHMgb2YgdGhyZWUgYW5kIHR3bywgcmVzcGVjdGl2ZWx5Lg0KKElmIHdlIHJhbiB0aGUgYGdhcF9zdGF0YCBtZXRob2QsIHdlIHdvdWxkIGFycml2ZSBhdCBmaXZlIGNsdXN0ZXJzIGJlaW5nIG9wdGltYWwsIGJ1dCB0aGlzIHRha2VzIHF1aXRlIGEgd2hpbGUgdG8gcnVuKS4NCg0KIyMNCg0KVGhlIFIgY29kZSBiZWxvdyBzaG93cyByZXN1bHRzIGZvciBhIGNsdXN0ZXIgY2hvaWNlIG9mIHRocmVlLg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFQsIGZpZy5kaW0gPSBjKDYsIDYpfQ0KZnZpel9jbHVzdGVyKGZ1enp5X2ZpdDMsIGRhdGEgPSBwZW5ndWluc19zY2FsZWQpDQpgYGANCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBULCBmaWcuZGltID0gYyg4LCA2KX0NCiMgVGhpcyBjb2RlIGFzc3VtZXMgeW91IGFyZSBhc3Nlc3NpbmcgYSByZXN1bHQgc3RvcmVkIGluIHRoZSBvYmplY3QgZnV6enlfZml0Mw0KcGxvdChhcy5udW1lcmljKHBlbmd1aW5zJHNwZWNpZXMpLCBjb2wgPSBmdXp6eV9maXQzJGNsdXN0ZXIpDQpgYGANCg0KRnJvbSB0aGVzZSBwbG90cyB3ZSBjYW4gc2VlIHRoYXQgdGhlIGZ1enp5IGNsdXN0ZXJpbmcgcGVyZm9ybXMgc2ltaWxhcmx5IHRvIHRoZSBwcmV2aW91cyBtZXRob2RzLg0KDQo8YnI+DQoNCiMjIyMgVGhhdCdzIGFsbCB0aGUgY29udGVudCBjb3ZlcmVkLiAjIyMjIHstfQ0KDQo8YnI+DQoNCiMgUmVmZXJlbmNlcyB7LSAjUmVmfQ0KPGRpdiBpZD0icmVmcyI+PC9kaXY+DQoNCjxicj4NCg0KPGZvbnQgY29sb3IgPSAiZ3JleSI+DQpUaGVzZSBub3RlcyBoYXZlIGJlZW4gcHJlcGFyZWQgYnkgUnVwZXJ0IEt1dmVrZS4gUGxlYXNlIG5vdGUgdGhhdCBzb21lIG9mIHRoZSBjb250ZW50IGluIHRoZXNlIG5vdGVzIGhhcyBiZWVuIGRldmVsb3BlZCBmcm9tIGNvbnRlbnQgaW4gQE1vZFN0YXQuIFRoZSBjb3B5cmlnaHQgZm9yIHRoZSBtYXRlcmlhbCBpbiB0aGVzZSBub3RlcyByZXNpZGVzIHdpdGggdGhlIGF1dGhvcnMgbmFtZWQgYWJvdmUsIHdpdGggdGhlIERlcGFydG1lbnQgb2YgTWF0aGVtYXRpY3MgYW5kIFN0YXRpc3RpY3MgYW5kIHdpdGggTGEgVHJvYmUgVW5pdmVyc2l0eS4gQ29weXJpZ2h0IGluIHRoaXMgd29yayBpcyB2ZXN0ZWQgaW4gTGEgVHJvYmUgVW5pdmVyc2l0eSBpbmNsdWRpbmcgYWxsIExhIFRyb2JlIFVuaXZlcnNpdHkgYnJhbmRpbmcgYW5kIG5hbWluZy4gVW5sZXNzIG90aGVyd2lzZSBzdGF0ZWQsIG1hdGVyaWFsIHdpdGhpbiB0aGlzIHdvcmsgaXMgbGljZW5zZWQgdW5kZXIgYSBDcmVhdGl2ZSBDb21tb25zIEF0dHJpYnV0aW9uLU5vbiBDb21tZXJjaWFsLU5vbiBEZXJpdmF0aXZlcyBMaWNlbnNlIA0KPGEgaHJlZiA9ICJodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtbmQvNC4wL0NDIiB0YXJnZXQ9Il9ibGFuayI+IEJZLU5DLU5ELiA8L2E+DQo8L2ZvbnQ+