Science/Health Science/Data Science Module

Topic 6B: Big Data I (Clustering)


Example R code solutions for the Week 6 Science/Health Science/Data Science Computer Lab, 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

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.

2.1 Penguin Data

No answer required.

2.2

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.

2.3 Preparing data for Cluster Analysis

No answer required.

2.3.1

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

2.3.2

No answer required.

2.3.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

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 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%.

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.4

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.

3.5

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\)?!).

3.6

No answer required.

3.6.1

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.

3.6.2

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.6.3

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.

3.7

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)

3.8

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

4 PAM Clustering

4.1

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

4.2

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.

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 1: 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_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.

5.4

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)

6 Extension 2: 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)
fuzzy_fit5 <- fanny(penguins_scaled, 5)

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

6.2

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).

6.3

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.


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

LS0tDQp0aXRsZTogIlNUTTEwMDE6IENvbXB1dGVyIExhYiA2QiBTb2x1dGlvbnMiDQpvdXRwdXQ6DQogIGJvb2tkb3duOjpodG1sX2RvY3VtZW50MjogDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRoZW1lOiByZWFkYWJsZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KYmlibGlvZ3JhcGh5OiBTVE0xMDAxX0RTX0NMX3JlZmVyZW5jZXMuYmliIA0KbGluay1jaXRhdGlvbnM6IHllcw0KLS0tDQoNCjxzdHlsZT4NCiNUT0Mgew0KICBiYWNrZ3JvdW5kOiB1cmwoImh0dHBzOi8vd3d3LmxhdHJvYmUuZWR1LmF1L19tZWRpYS9sYS10cm9iZS1hcGkvdjUvaW1nL2xvZ28uc3ZnIik7DQogIGJhY2tncm91bmQtc2l6ZTogY29udGFpbjsNCiAgcGFkZGluZy10b3A6IDgwcHggIWltcG9ydGFudDsNCiAgYmFja2dyb3VuZC1yZXBlYXQ6IG5vLXJlcGVhdDsNCn0NCjwvc3R5bGU+DQoNCiMjIyBTY2llbmNlL0hlYWx0aCBTY2llbmNlL0RhdGEgU2NpZW5jZSBNb2R1bGUgey19DQoNCg0KIyMjIFRvcGljIDZCOiBCaWcgRGF0YSBJIChDbHVzdGVyaW5nKSB7LX0NCg0KPGJyPg0KDQpFeGFtcGxlIFIgY29kZSBzb2x1dGlvbnMgZm9yIHRoZSBbV2VlayA2IFNjaWVuY2UvSGVhbHRoIFNjaWVuY2UvRGF0YSBTY2llbmNlIENvbXB1dGVyIExhYl0oaHR0cHM6Ly9ycHVicy5jb20vTFRVX1NUTTEwMDEvU01EU01DTDZfUyksIHdoaWNoIHVzZXMgcGVuZ3VpbiBkYXRhIGZyb20gdGhlIGBwYWxtZXJwZW5ndWluc2AgUiBwYWNrYWdlXltXZSBhbHNvIHV0aWxpc2UgdGhlIHBhY2thZ2VzIGBmYWN0b2V4dHJhYCBbQGZhY3RvXSwgYGdnZm9ydGlmeWAgW0BnZ10gYW5kIGBjbHVzdGVyYFtAUl0uXSBbQHBlbmd1aW5zXSwgYXJlIHByZXNlbnRlZCBiZWxvdy4NCg0KIyBDbHVzdGVyIEFuYWx5c2lzDQoNCiMjIyBQdXJwb3NlIHstfQ0KDQpObyBhbnN3ZXIgcmVxdWlyZWQuDQoNCiMjIyBDbHVzdGVyaW5nIFRlY2huaXF1ZXMgey19DQoNCk5vIGFuc3dlciByZXF1aXJlZC4NCg0KIyBQcmVwYXJhdGlvbnMgeyNwcmVwfQ0KDQpgYGB7ciBldmFsID0gVCwgaW5jbHVkZSA9IEZ9DQpsaWJyYXJ5KGNsdXN0ZXIpDQpsaWJyYXJ5KGZhY3RvZXh0cmEpDQpsaWJyYXJ5KGdnZm9ydGlmeSkNCmxpYnJhcnkocGFsbWVycGVuZ3VpbnMpDQpgYGANCg0KYGBge3IgZXZhbCA9IEYsIGVjaG8gPSBUfQ0KaW5zdGFsbC5wYWNrYWdlcyhjKCJmYWN0b2V4dHJhIiwgImdnZm9ydGlmeSIsICJwYWxtZXJwZW5ndWlucyIpKQ0KbGlicmFyeShjbHVzdGVyKQ0KbGlicmFyeShmYWN0b2V4dHJhKQ0KbGlicmFyeShnZ2ZvcnRpZnkpDQpsaWJyYXJ5KHBhbG1lcnBlbmd1aW5zKQ0KYGBgDQoNCipOb3RlOiBJZiB5b3UgYXJlIGluIHRoZSBEYXRhIFNjaWVuY2UgbW9kdWxlLCB5b3Ugc2hvdWxkIGFscmVhZHkgaGF2ZSB0aGUgYHBhbG1lcnBlbmd1aW5zYCBwYWNrYWdlIGluc3RhbGxlZC4qDQoNCiMjIFBlbmd1aW4gRGF0YQ0KDQpObyBhbnN3ZXIgcmVxdWlyZWQuDQoNCiMjDQoNCg0KYGBge3IgZXZhbCA9IFQsIGVjaG8gPSBUfQ0KaGVhZChwZW5ndWlucykNCnBsb3QocGVuZ3VpbnMpDQpgYGANCg0KRm9yIHNldmVyYWwgb2YgdGhlIGNvbnRpbnVvdXMgcmFuZG9tIHZhcmlhYmxlIHNjYXR0ZXIgcGxvdHMsIHRoZXJlIGRvZXMgYXBwZWFyIHRvIGJlIHZpc2libGUgY2x1c3RlcmluZyBvY2N1cmluZyAtIGUuZy4gYGZsaXBwZXJfbGVuZ3RoX21tYCB2cyBgYmlsbF9kZXB0aF9tbWAgc3VnZ2VzdHMgdGhhdCB0aGVyZSBhcmUgdHdvIGRpc3RpbmN0IGNsdXN0ZXJzLiBUaGVyZWZvcmUgYXBwbHlpbmcgYSBjbHVzdGVyaW5nIHRlY2huaXF1ZSBkb2VzIHNlZW0gdG8gYmUgd2FycmFudGVkLg0KDQojIyBQcmVwYXJpbmcgZGF0YSBmb3IgQ2x1c3RlciBBbmFseXNpcw0KDQpObyBhbnN3ZXIgcmVxdWlyZWQuDQoNCiMjIw0KDQpgYGB7ciBldmFsID0gVCwgZWNobyA9IFR9DQpwZW5ndWlucyA8LSBuYS5vbWl0KHBlbmd1aW5zKQ0KcGVuZ3VpbnNfc3Vic2V0IDwtIHBlbmd1aW5zWywgMzo2XQ0KYGBgDQoNCiMjIw0KDQpObyBhbnN3ZXIgcmVxdWlyZWQuDQoNCiMjIw0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBUfQ0KcGVuZ3VpbnNfc2NhbGVkIDwtIHNjYWxlKHBlbmd1aW5zX3N1YnNldCkNCmhlYWQocGVuZ3VpbnNfc2NhbGVkKQ0KYGBgDQoNCiMgay1tZWFucyBDbHVzdGVyaW5nDQoNCiMjIHsja21lYW5zM30NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBUfQ0Ka21lYW5zX2ZpdDMgPC0ga21lYW5zKHBlbmd1aW5zX3NjYWxlZCwgMykNCmBgYA0KDQojIw0KDQpXZSBjYW4gY2hlY2sgdGhlIGNsdXN0ZXIgbWVtYmVyc2hpcCBhcyBmb2xsb3dzOg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFR9DQprbWVhbnNfZml0MyRjbHVzdGVyDQpgYGANCg0KVGhlIGBiZXR3ZWVuX3NzIC90b3RhbF9zc2AgdmFsdWUgaXMgNzIuMSUuDQoNCiMjIFZpc3VhbGlzaW5nIG91ciByZXN1bHRzIHsja21lYW5zdmlzdWFsaXNlfQ0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFQsIGZpZy5kaW0gPSBjKDgsIDEwKX0NCnBsb3QocGVuZ3VpbnNbLCBjKDEsMzo2KV0sIGNvbCA9IGttZWFuc19maXQzJGNsdXN0ZXIpDQpgYGANCg0KQXMgd2UgY2FuIHNlZSwgdGhlIGNsdXN0ZXJzIGFyZSBhY3R1YWxseSBkb2luZyBhIHByZXR0eSBnb29kIGpvYiBvZiBzZXBhcmF0aW5nIHBlbmd1aW5zIGJ5IHNwZWNpZXMhDQoNCiMjDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGNhY2hlID0gVCwgZmlnLmRpbSA9IGMoNiwgNil9DQpwbG90KGFzLm51bWVyaWMocGVuZ3VpbnMkc3BlY2llcyksIGNvbCA9IGttZWFuc19maXQzJGNsdXN0ZXIpDQpgYGANCg0KQmFzZWQgb24gdGhlc2UgcGxvdHMsIGl0IHNlZW1zIHRoYXQgdGhlIGstbWVhbnMgY2x1c3RlcmluZyBoYXMgZG9uZSBhIGdyZWF0IGpvYiBvZiBjbHVzdGVyaW5nIHRoZSBwZW5ndWlucyBpbnRvIGNsdXN0ZXJzIG9mIGRpZmZlcmVudCBzcGVjaWVzLiBJZiB5b3UgbG9vayBjYXJlZnVsbHksIHRoZXJlIGlzIHNvbWUgZXJyb3Igd2hlbiBkaXN0aW5ndWlzaGluZyBiZXR3ZWVuIENoaW5zdHJhcCBhbmQgQWRlbGllIHBlbmd1aW5zLiBJZiB3ZSBhbHNvIGluY2x1ZGUgbW9yZSBkYXRhIHN1Y2ggYXMgYHNleGAgb3IgYGlzbGFuZGAsIHdlIG1pZ2h0IGJlIGFibGUgdG8gbW9yZSBjbGVhcmx5IGRpc3Rpbmd1aXNoIGJldHdlZW4gdGhlc2UgdHdvIHNwZWNpZXMuDQoNCiMjIHsja21lYW5zbW9yZX0NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBUfQ0Ka21lYW5zX2ZpdDIgPC0ga21lYW5zKHBlbmd1aW5zX3NjYWxlZCwgMikNCmttZWFuc19maXQ0IDwtIGttZWFucyhwZW5ndWluc19zY2FsZWQsIDQpDQprbWVhbnNfZml0NSA8LSBrbWVhbnMocGVuZ3VpbnNfc2NhbGVkLCA1KQ0KYGBgDQoNClRoZSBgYmV0d2Vlbl9zcyAvdG90YWxfc3NgIHJlc3VsdHMgZm9yIHRoZXNlIG5ldyBjbHVzdGVyIGZpdHMgYXJlOg0KDQoqIDU4LjUlIGZvciAkaz0yJCwNCiogNzcuMSUgZm9yICRrPTQkLA0KKiA4Mi44JSBmb3IgJGs9NSQNCg0KVGhlIGBiZXR3ZWVuX3NzIC90b3RhbF9zc2AgdmFsdWUgYWN0dWFsbHkgaW5jcmVhc2VzIGFzIHdlIGluY3JlYXNlIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMsIHdoaWNoIGlzbid0IG5lY2Vzc2FyaWx5IGFjY3VyYXRlIC0gd2Uga25vdyB0aGVyZSBhcmVuJ3QgZml2ZSBzcGVjaWVzIG9mIHBlbmd1aW4gaW4gb3VyIGRhdGEgKHdoYXQgaGFwcGVucyBpZiB3ZSB1c2UgJGs9MTAkPyEpLg0KDQojIyB7I25iY2x1c3R9DQoNCk5vIGFuc3dlciByZXF1aXJlZC4NCg0KIyMjDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGNhY2hlID0gVCwgZmlnLmRpbSA9IGMoNiwgNil9DQprbWVhbnNfd3NzIDwtIGZ2aXpfbmJjbHVzdChwZW5ndWluc19zY2FsZWQsIGttZWFucywgbWV0aG9kID0gIndzcyIpDQprbWVhbnNfd3NzDQpgYGANCg0KQmFzZWQgb24gdGhpcyBwbG90LCBpdCBzZWVtcyB0aGF0IHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycyBpcyB0aHJlZS4gQWZ0ZXIgJGs9MyQsIHRoZSBUb3RhbCBXaXRoaW4gU3VtIG9mIFNxdWFyZXMgdmFyaWFuY2UgZG9lcyBub3QgZGVjcmVhc2UgYXMgcmFwaWRseSB3aGVuIG1vcmUgY2x1c3RlcnMgYXJlIHVzZWQuDQoNCiMjIw0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFQsIGZpZy5kaW0gPSBjKDYsIDYpfQ0Ka21lYW5zX3NpbCA8LSBmdml6X25iY2x1c3QocGVuZ3VpbnNfc2NhbGVkLCBrbWVhbnMsIG1ldGhvZCA9ICJzaWxob3VldHRlIikNCmttZWFuc19zaWwNCmBgYA0KDQpCYXNlZCBvbiB0aGlzIHBsb3QsIGl0IHNlZW1zIHRoYXQgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzIGlzIHR3by4NCg0KIyMjDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGNhY2hlID0gVCwgZmlnLmRpbSA9IGMoNiwgNil9DQprbWVhbnNfZ2FwIDwtIGZ2aXpfbmJjbHVzdChwZW5ndWluc19zY2FsZWQsIGttZWFucywgbWV0aG9kID0gImdhcF9zdGF0IikNCmttZWFuc19nYXANCmBgYA0KDQpCYXNlZCBvbiB0aGlzIHBsb3QsIGl0IHNlZW1zIHRoYXQgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzIGlzIGZvdXIuDQoNCiMjDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGNhY2hlID0gVCwgZmlnLmRpbSA9IGMoNiwgNil9DQpmdml6X2NsdXN0ZXIoa21lYW5zX2ZpdDMsIGRhdGEgPSBwZW5ndWluc19zY2FsZWQpDQoNCmZ2aXpfY2x1c3RlcihrbWVhbnNfZml0MiwgZGF0YSA9IHBlbmd1aW5zX3NjYWxlZCkNCmZ2aXpfY2x1c3RlcihrbWVhbnNfZml0NCwgZGF0YSA9IHBlbmd1aW5zX3NjYWxlZCkNCmZ2aXpfY2x1c3RlcihrbWVhbnNfZml0NSwgZGF0YSA9IHBlbmd1aW5zX3NjYWxlZCkNCmBgYA0KDQojIyANCg0KQW5zd2VycyBtYXkgdmFyeSBoZXJlLiBUaHJlZSBvciBmb3VyIGNsdXN0ZXJzIHNlZW0gcmVhc29uYWJsZS4NCg0KIyBQQU0gQ2x1c3RlcmluZw0KDQojIyB7I3BhbX0NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBUfQ0KcGFtX2ZpdDIgPC0gcGFtKHBlbmd1aW5zX3NjYWxlZCwgMikNCnBhbV9maXQzIDwtIHBhbShwZW5ndWluc19zY2FsZWQsIDMpDQpwYW1fZml0NCA8LSBwYW0ocGVuZ3VpbnNfc2NhbGVkLCA0KQ0KcGFtX2ZpdDUgPC0gcGFtKHBlbmd1aW5zX3NjYWxlZCwgNSkNCmBgYA0KDQojIyB7I3BhbWJlc3R9DQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGNhY2hlID0gVCwgZmlnLmRpbSA9IGMoNiwgNil9DQpwYW1fd3NzIDwtIGZ2aXpfbmJjbHVzdChwZW5ndWluc19zY2FsZWQsIHBhbSwgbWV0aG9kID0gIndzcyIpDQpwYW1fc2lsIDwtIGZ2aXpfbmJjbHVzdChwZW5ndWluc19zY2FsZWQsIHBhbSwgbWV0aG9kID0gInNpbGhvdWV0dGUiKQ0KcGFtX2dhcCA8LSBmdml6X25iY2x1c3QocGVuZ3VpbnNfc2NhbGVkLCBwYW0sIG1ldGhvZCA9ICJnYXBfc3RhdCIpDQoNCnBhbV93c3MNCnBhbV9zaWwNCnBhbV9nYXANCmBgYA0KDQpXZSBoYXZlIGRpZmZlcmVudCByZXN1bHRzIGhlcmUsIG9mIHRocmVlLCB0d28gYW5kIGZpdmUgY2x1c3RlcnMgYmVpbmcgb3B0aW1hbCwgcmVzcGVjdGl2ZWx5Lg0KDQojIw0KDQpUaGUgUiBjb2RlIGJlbG93IHNob3dzIHJlc3VsdHMgZm9yIGEgY2x1c3RlciBjaG9pY2Ugb2YgdGhyZWUuDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGNhY2hlID0gVCwgZmlnLmRpbSA9IGMoNiwgNil9DQpmdml6X2NsdXN0ZXIocGFtX2ZpdDMsIGRhdGEgPSBwZW5ndWluc19zY2FsZWQpDQpgYGANCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBULCBmaWcuZGltID0gYyg4LCA2KX0NCnBsb3QoYXMubnVtZXJpYyhwZW5ndWlucyRzcGVjaWVzKSwgY29sID0gcGFtX2ZpdDMkY2x1c3RlcmluZykNCmBgYA0KDQpXZSBjYW4gc2VlIGZyb20gdGhlc2UgcGxvdHMgdGhhdCB0aGUgUEFNIGNsdXN0ZXJpbmcgZG9lcyBhIHZlcnkgZ29vZCBqb2Igb2YgY2x1c3RlcmluZyBwZW5ndWlucyBpbnRvIHNwZWNpZXMgKGFsdGhvdWdoIHRoZXJlIGlzIHN0aWxsIHNvbWUgdHJvdWJsZSBkaXN0aW5ndWlzaGluZyBBZGVsaWUgYW5kIENoaW5zdHJhcCBwZW5ndWlucykuIEZvciB0aGUgbGFyZ2VyIG51bWJlcnMgb2YgY2x1c3RlcnMsIHRoZSBhbGdvcml0aG0gbWF5IGFsc28gYmUgZGlzdGluZ3Vpc2hpbmcgYmV0d2VlbiBtYWxlIGFuZCBmZW1hbGUgcGVuZ3VpbnMgLSB0cnkgY2hlY2tpbmcgaWYgdGhpcyBpcyB0cnVlLg0KDQojIEV4dGVuc2lvbiAxOiBIaWVyYXJjaGljYWwgQ2x1c3RlcmluZw0KDQojIyB7I2hpZXJhcmNoaWNhbH0NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBUfQ0KaGllcl9maXQgPC0gYWduZXMocGVuZ3VpbnNfc2NhbGVkKQ0KYGBgDQoNCiMjDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGZpZy5kaW0gPSBjKDgsIDYpLCBjYWNoZSA9IFR9DQpwbG90KGhpZXJfZml0LCB3aGljaCA9IDIpDQpgYGANCg0KIyMNCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBUfQ0KaGllcl9maXQgPC0gYWduZXMocGVuZ3VpbnNfc2NhbGVkKQ0KaGllcl9maXRfc2luZ2xlIDwtIGFnbmVzKHBlbmd1aW5zX3NjYWxlZCwgbWV0aG9kID0gInNpbmdsZSIpDQpoaWVyX2ZpdF9jb21wbGV0ZSA8LSBhZ25lcyhwZW5ndWluc19zY2FsZWQsIG1ldGhvZCA9ICJjb21wbGV0ZSIpDQpoaWVyX2ZpdF93YXJkIDwtIGFnbmVzKHBlbmd1aW5zX3NjYWxlZCwgbWV0aG9kID0gIndhcmQiKQ0KYGBgDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGZpZy5kaW0gPSBjKDgsIDYpLCBjYWNoZSA9IFR9DQpwbG90KGhpZXJfZml0X3NpbmdsZSwgd2hpY2ggPSAyKQ0KcGxvdChoaWVyX2ZpdF9jb21wbGV0ZSwgd2hpY2ggPSAyKQ0KcGxvdChoaWVyX2ZpdF93YXJkLCB3aGljaCA9IDIpDQpgYGANCg0KVGhlIGRlbmRyb2dyYW1zIGFsbCBsb29rIHF1aXRlIGRpZmZlcmVudC4gVGhlIGBjb21wbGV0ZWAgb3IgYHdhcmRgIG1ldGhvZCBvbmVzIGxvb2sgcGVyaGFwcyB0aGUgY2xlYW5lc3QgYW5kIGVhc2llc3QgdG8gYXNzZXNzLg0KDQojIyANCg0KVGhlIGNvZGUgc2hvd24gYmVsb3cgcHJvZHVjZXMgYSBjb2xvdXJlZCBkZW5kcm9ncmFtIHdpdGggdGhyZWUgY2x1c3RlcnMsIGZvciB0aGUgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgYWxnb3JpdGhtIHVzaW5nIHRoZSAiY29tcGxldGUiIG1lYXN1cmVtZW50IG1ldGhvZC4NCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBULCBmaWcuZGltID0gYyg4LCA2KSwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KcGVuZ3Vpbl9kZW5kcm8gPC0gaGN1dChwZW5ndWluc19zY2FsZWQsIA0KICAgICAgICAgICAgICAgIGsgPSAzLCANCiAgICAgICAgICAgICAgICBoY19mdW5jID0gImFnbmVzIiwNCiAgICAgICAgICAgICAgICBoY19tZXRob2QgPSAiY29tcGxldGUiLA0KICAgICAgICAgICAgICAgIGhjX21ldHJpYyA9ICJldWNsaWRlYW4iKSANCg0KZnZpel9kZW5kKHBlbmd1aW5fZGVuZHJvKQ0KYGBgDQoNCiMgRXh0ZW5zaW9uIDI6IEZ1enp5IENsdXN0ZXJpbmcNCg0KIyMNCg0KVGhlIFIgY29kZSBiZWxvdyBzaG93cyBmaXRzIGZvciB0aGUgZGlmZmVyZW50IGNsdXN0ZXIgbnVtYmVyIG9wdGlvbnMuDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3ciLCBldmFsID0gVCwgZWNobyA9IFQsIGNhY2hlID0gVH0NCmZ1enp5X2ZpdDIgPC0gZmFubnkocGVuZ3VpbnNfc2NhbGVkLCAyKQ0KZnV6enlfZml0MyA8LSBmYW5ueShwZW5ndWluc19zY2FsZWQsIDMpDQpmdXp6eV9maXQ0IDwtIGZhbm55KHBlbmd1aW5zX3NjYWxlZCwgNCkNCmZ1enp5X2ZpdDUgPC0gZmFubnkocGVuZ3VpbnNfc2NhbGVkLCA1KQ0KYGBgDQoNClBsZWFzZSBub3RlIHRoYXQgb3V0cHV0IGZvciB0aGVzZSBmaXRzIGlzIG9taXR0ZWQgZHVlIHRvIHRoZSBsZW5ndGggb2YgdGhlIHJlc3VsdHMuDQoNCiMjIHsjZnV6enliZXN0fQ0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFQsIGZpZy5kaW0gPSBjKDYsIDYpLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQpmdXp6eV93c3MgPC0gZnZpel9uYmNsdXN0KHBlbmd1aW5zX3NjYWxlZCwgZmFubnksIG1ldGhvZCA9ICJ3c3MiKQ0KZnV6enlfc2lsIDwtIGZ2aXpfbmJjbHVzdChwZW5ndWluc19zY2FsZWQsIGZhbm55LCBtZXRob2QgPSAic2lsaG91ZXR0ZSIpDQoNCmZ1enp5X3dzcw0KZnV6enlfc2lsDQpgYGANCg0KV2Ugb2J0YWluIHRoZSBzYW1lIHJlc3VsdHMgYXMgZm9yIHRoZSBrLW1lYW5zIGFuZCBQQU0gY2x1c3RlcmluZyBoZXJlLCBpLmUuIHdlIGhhdmUgZGlmZmVyZW50IHJlc3VsdHMgb2YgdGhyZWUgYW5kIHR3bywgcmVzcGVjdGl2ZWx5Lg0KKElmIHdlIHJhbiB0aGUgYGdhcF9zdGF0YCBtZXRob2QsIHdlIHdvdWxkIGFycml2ZSBhdCBmaXZlIGNsdXN0ZXJzIGJlaW5nIG9wdGltYWwsIGJ1dCB0aGlzIHRha2VzIHF1aXRlIGEgd2hpbGUgdG8gcnVuKS4NCg0KIyMNCg0KVGhlIFIgY29kZSBiZWxvdyBzaG93cyByZXN1bHRzIGZvciBhIGNsdXN0ZXIgY2hvaWNlIG9mIHRocmVlLg0KDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93IiwgZXZhbCA9IFQsIGVjaG8gPSBULCBjYWNoZSA9IFQsIGZpZy5kaW0gPSBjKDYsIDYpfQ0KZnZpel9jbHVzdGVyKGZ1enp5X2ZpdDMsIGRhdGEgPSBwZW5ndWluc19zY2FsZWQpDQpgYGANCg0KYGBge3IgY2xhc3Muc291cmNlID0gImZvbGQtc2hvdyIsIGV2YWwgPSBULCBlY2hvID0gVCwgY2FjaGUgPSBULCBmaWcuZGltID0gYyg4LCA2KX0NCiMgVGhpcyBjb2RlIGFzc3VtZXMgeW91IGFyZSBhc3Nlc3NpbmcgYSByZXN1bHQgc3RvcmVkIGluIHRoZSBvYmplY3QgZnV6enlfZml0Mw0KcGxvdChhcy5udW1lcmljKHBlbmd1aW5zJHNwZWNpZXMpLCBjb2wgPSBmdXp6eV9maXQzJGNsdXN0ZXIpDQpgYGANCg0KRnJvbSB0aGVzZSBwbG90cyB3ZSBjYW4gc2VlIHRoYXQgdGhlIGZ1enp5IGNsdXN0ZXJpbmcgcGVyZm9ybXMgc2ltaWxhcmx5IHRvIHRoZSBwcmV2aW91cyBtZXRob2RzLg0KDQo8YnI+DQoNCiMjIyMgVGhhdCdzIGFsbCB0aGUgY29udGVudCBjb3ZlcmVkLiAjIyMjIHstfQ0KDQo8YnI+DQoNCiMgUmVmZXJlbmNlcyB7LSAjUmVmfQ0KPGRpdiBpZD0icmVmcyI+PC9kaXY+DQoNCjxicj4NCg0KPGZvbnQgY29sb3IgPSAiZ3JleSI+DQpUaGVzZSBub3RlcyBoYXZlIGJlZW4gcHJlcGFyZWQgYnkgUnVwZXJ0IEt1dmVrZS4gUGxlYXNlIG5vdGUgdGhhdCBzb21lIG9mIHRoZSBjb250ZW50IGluIHRoZXNlIG5vdGVzIGhhcyBiZWVuIGRldmVsb3BlZCBmcm9tIGNvbnRlbnQgaW4gQE1vZFN0YXQuIFRoZSBjb3B5cmlnaHQgZm9yIHRoZSBtYXRlcmlhbCBpbiB0aGVzZSBub3RlcyByZXNpZGVzIHdpdGggdGhlIGF1dGhvcnMgbmFtZWQgYWJvdmUsIHdpdGggdGhlIERlcGFydG1lbnQgb2YgTWF0aGVtYXRpY3MgYW5kIFN0YXRpc3RpY3MgYW5kIHdpdGggTGEgVHJvYmUgVW5pdmVyc2l0eS4gQ29weXJpZ2h0IGluIHRoaXMgd29yayBpcyB2ZXN0ZWQgaW4gTGEgVHJvYmUgVW5pdmVyc2l0eSBpbmNsdWRpbmcgYWxsIExhIFRyb2JlIFVuaXZlcnNpdHkgYnJhbmRpbmcgYW5kIG5hbWluZy4gVW5sZXNzIG90aGVyd2lzZSBzdGF0ZWQsIG1hdGVyaWFsIHdpdGhpbiB0aGlzIHdvcmsgaXMgbGljZW5zZWQgdW5kZXIgYSBDcmVhdGl2ZSBDb21tb25zIEF0dHJpYnV0aW9uLU5vbiBDb21tZXJjaWFsLU5vbiBEZXJpdmF0aXZlcyBMaWNlbnNlIA0KPGEgaHJlZiA9ICJodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtbmQvNC4wL0NDIiB0YXJnZXQ9Il9ibGFuayI+IEJZLU5DLU5ELiA8L2E+DQo8L2ZvbnQ+