Note: Some results may differ from the hard copy book due to the changing of sampling procedures introduced in R 3.6.0. See http://bit.ly/35D1SW7 for more details. Access and run the source code for this notebook here.

Hidden chapter requirements used in the book to set the plotting theme and load packages used in hidden code chunks:

knitr::opts_chunk$set(
  message = FALSE, 
  warning = FALSE, 
  cache = FALSE
)

# Set the graphical theme
ggplot2::theme_set(ggplot2::theme_light())

# packages for hidden code chunks
library(kableExtra)
library(pca3d)

Prerequisites

This chapter leverages the following packages:

library(dplyr)       # basic data manipulation and plotting
library(ggplot2)     # data visualization
library(h2o)         # performing dimension reduction

To illustrate dimension reduction techniques, we’ll use the my_basket data set:

url <- "https://koalaverse.github.io/homlr/data/my_basket.csv"
my_basket <- readr::read_csv(url)
Parsed with column specification:
cols(
  .default = col_double()
)
See spec(...) for full column specifications.
dim(my_basket)  
[1] 2000   42

The idea

Table 17.1:

# compute feature correlation
m <- cor(my_basket)

# plot features with highest correlations
data.frame(
  row = rownames(m)[row(m)[upper.tri(m)]],
  col = colnames(m)[col(m)[upper.tri(m)]],
  corr = m[upper.tri(m)],
  stringsAsFactors = FALSE
  ) %>%
  filter(corr < 1 & corr > .25) %>%
  rename(`Item 1` = row, `Item 2` = col, Correlation = corr) %>%
  mutate(Correlation = round(Correlation, 3)) %>%
  arrange(desc(Correlation)) %>%
  kable(caption = "Various items in our my basket data that are correlated.") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Various items in our my basket data that are correlated.
Item 1 Item 2 Correlation
cheese mayonnaise 0.345
bulmers fosters 0.335
cheese bread 0.320
lasagna pizza 0.316
pepsi coke 0.309
red.wine fosters 0.308
milk muesli 0.302
mars twix 0.301
red.wine bulmers 0.298
bulmers kronenbourg 0.289
milk tea 0.288
red.wine kronenbourg 0.286
7up coke 0.282
spinach broccoli 0.282
mayonnaise bread 0.278
peas potatoes 0.271
peas carrots 0.270
tea instant.coffee 0.270
milk instant.coffee 0.267
bread lettuce 0.264
twix kitkat 0.259
mars kitkat 0.255
muesli instant.coffee 0.251

Finding principal components

Figure 17.1:

df <- AmesHousing::make_ames() %>%
  select(var1 = First_Flr_SF, var2 = Gr_Liv_Area) %>%
  filter(var1 != var2) %>%
  mutate_all(log) %>%
  scale() %>% 
  data.frame() %>%
  filter(var1 < 4)
ggplot(df, aes(var1, var2)) +
  geom_jitter(alpha = .2, size = 1, color = "dodgerblue") +
  geom_segment(
    aes(x = 0, xend = 1.5 , y = 0, yend = 1.5),
    arrow = arrow(length = unit(0.25,"cm")), size = 0.75, color = "black"
    ) +
  annotate("text", x = 1, y = .2, label = "First principal component", size = 2.5, hjust = 0) +
  annotate("text", x = -3, y = .8, label = "Second principal component", size = 2.5, hjust = 0) +
  geom_segment(
    aes(x = 0, xend = -0.27 , y = 0, yend = .65),
    arrow = arrow(length = unit(0.25,"cm")), size = 0.75, color = "black"
    ) +
  xlab("Feature 2") +
  ylab("Feature 1") +
  theme_bw()

Creates Figure 17.2. Uncomment snapshotPCA3d(file = "3D-PCA.png") to save image to desired location.

df <- AmesHousing::make_ames() %>%
  select(var1 = First_Flr_SF, var2 = Gr_Liv_Area, var3 = TotRms_AbvGrd) %>%
  filter(var1 != var2) %>%
  mutate_at(vars(var1, var2), log)

pca <- prcomp(df, scale = FALSE)
pca3d(pca)
[1] 0.135693940 0.022140269 0.009712809
Creating new device
#snapshotPCA3d(file="3D-PCA.png")

Imports Figure 17.2:

knitr::include_graphics("images/3D-PCA.png")

Performing PCA in R

h2o.no_progress()  # turn off progress bars for brevity
h2o.init(max_mem_size = "5g")  # connect to H2O instance
# convert data to h2o object
my_basket.h2o <- as.h2o(my_basket)

# run PCA
my_pca <- h2o.prcomp(
  training_frame = my_basket.h2o,
  pca_method = "GramSVD",
  k = ncol(my_basket.h2o), 
  transform = "STANDARDIZE", 
  impute_missing = TRUE,
  max_runtime_secs = 1000
)
my_pca
Model Details:
==============

H2ODimReductionModel: pca
Model ID:  PCA_model_R_1577585270073_1 
Importance of components: 


H2ODimReductionMetrics: pca

No model metrics available for PCA
my_pca@model$eigenvectors %>% 
  as.data.frame() %>% 
  mutate(feature = row.names(.)) %>%
  ggplot(aes(pc1, reorder(feature, pc1))) +
  geom_point()

my_pca@model$eigenvectors %>% 
  as.data.frame() %>% 
  mutate(feature = row.names(.)) %>%
  ggplot(aes(pc1, pc2, label = feature)) +
  geom_text()

Selecting the number of principal components

Eigenvalue criterion

# Compute eigenvalues
eigen <- my_pca@model$importance["Standard deviation", ] %>%
  as.vector() %>%
  .^2
  
# Sum of all eigenvalues equals number of variables
sum(eigen)
[1] 42
# Find PCs where the sum of eigenvalues is greater than or equal to 1
which(eigen >= 1)
 [1]  1  2  3  4  5  6  7  8  9 10

Figure 17.5:

data.frame(
  PC = seq_along(eigen),
  Eigenvalue = unlist(eigen)
) %>%
  ggplot(aes(PC, Eigenvalue)) +
  geom_point() +
  geom_hline(yintercept = 1, lty = "dashed", color = "red") +
  scale_y_continuous(breaks = 0:6) +
  xlab("PC") +
  annotate("text", x = 15, y = 1, label = "eigenvalue criteria cutoff", color = "red", size = 5, hjust = 0, vjust = -1) 

Proportion of variance explained criterion

# Extract and plot PVE and CVE
data.frame(
  PC  = my_pca@model$importance %>% seq_along(),
  PVE = my_pca@model$importance %>% .[2,] %>% unlist(),
  CVE = my_pca@model$importance %>% .[3,] %>% unlist()
) %>%
  tidyr::gather(metric, variance_explained, -PC) %>%
  ggplot(aes(PC, variance_explained)) +
  geom_point() +
  facet_wrap(~ metric, ncol = 1, scales = "free")

ve <- data.frame(
  PC  = my_pca@model$importance %>% names(),
  PVE = my_pca@model$importance %>% .[2,] %>% unlist(),
  CVE = my_pca@model$importance %>% .[3,] %>% unlist()
)
# How many PCs required to explain at least 75% of total variability
min(which(ve$CVE >= 0.75))
[1] 27

Scree plot criterion

data.frame(
  PC  = my_pca@model$importance %>% seq_along,
  PVE = my_pca@model$importance %>% .[2,] %>% unlist()
) %>%
  ggplot(aes(PC, PVE, group = 1, label = PC)) +
  geom_point() +
  geom_line() +
  geom_text(nudge_y = -.002)

h2o.shutdown(prompt = FALSE)
LS0tCnRpdGxlOiAiQ2hhcHRlciAxNzogUHJpbmNpcGFsIENvbXBvbmVudHMgQW5hbHlzaXMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCl9fTm90ZV9fOiBTb21lIHJlc3VsdHMgbWF5IGRpZmZlciBmcm9tIHRoZSBoYXJkIGNvcHkgYm9vayBkdWUgdG8gdGhlIGNoYW5naW5nIG9mIHNhbXBsaW5nIHByb2NlZHVyZXMgaW50cm9kdWNlZCBpbiBSIDMuNi4wLiBTZWUgaHR0cDovL2JpdC5seS8zNUQxU1c3IGZvciBtb3JlIGRldGFpbHMuIEFjY2VzcyBhbmQgcnVuIHRoZSBzb3VyY2UgY29kZSBmb3IgdGhpcyBub3RlYm9vayBbaGVyZV0oaHR0cHM6Ly9yc3R1ZGlvLmNsb3VkL3Byb2plY3QvODAxMTg1KS4KCkhpZGRlbiBjaGFwdGVyIHJlcXVpcmVtZW50cyB1c2VkIGluIHRoZSBib29rIHRvIHNldCB0aGUgcGxvdHRpbmcgdGhlbWUgYW5kIGxvYWQgcGFja2FnZXMgdXNlZCBpbiBoaWRkZW4gY29kZSBjaHVua3M6CgpgYGB7ciBzZXR1cH0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIG1lc3NhZ2UgPSBGQUxTRSwgCiAgd2FybmluZyA9IEZBTFNFLCAKICBjYWNoZSA9IEZBTFNFCikKCiMgU2V0IHRoZSBncmFwaGljYWwgdGhlbWUKZ2dwbG90Mjo6dGhlbWVfc2V0KGdncGxvdDI6OnRoZW1lX2xpZ2h0KCkpCgojIHBhY2thZ2VzIGZvciBoaWRkZW4gY29kZSBjaHVua3MKbGlicmFyeShrYWJsZUV4dHJhKQpsaWJyYXJ5KHBjYTNkKQpgYGAKCiMjIFByZXJlcXVpc2l0ZXMKClRoaXMgY2hhcHRlciBsZXZlcmFnZXMgdGhlIGZvbGxvd2luZyBwYWNrYWdlczoKCmBgYHtyIHBjYS1wa2ctcHJlcmVxfQpsaWJyYXJ5KGRwbHlyKSAgICAgICAjIGJhc2ljIGRhdGEgbWFuaXB1bGF0aW9uIGFuZCBwbG90dGluZwpsaWJyYXJ5KGdncGxvdDIpICAgICAjIGRhdGEgdmlzdWFsaXphdGlvbgpsaWJyYXJ5KGgybykgICAgICAgICAjIHBlcmZvcm1pbmcgZGltZW5zaW9uIHJlZHVjdGlvbgpgYGAKClRvIGlsbHVzdHJhdGUgZGltZW5zaW9uIHJlZHVjdGlvbiB0ZWNobmlxdWVzLCB3ZSdsbCB1c2UgdGhlIGBteV9iYXNrZXRgIGRhdGEgc2V0OgoKYGBge3IgcGNhLWRhdGEtcHJlcmVxfQp1cmwgPC0gImh0dHBzOi8va29hbGF2ZXJzZS5naXRodWIuaW8vaG9tbHIvZGF0YS9teV9iYXNrZXQuY3N2IgpteV9iYXNrZXQgPC0gcmVhZHI6OnJlYWRfY3N2KHVybCkKZGltKG15X2Jhc2tldCkgIApgYGAKCiMjIFRoZSBpZGVhCgpUYWJsZSAxNy4xOgoKYGBge3IgdW5zdXBlcnZpc2VkLWNvcnJlbGF0aW9uLCBmaWcuaGVpZ2h0PTUsIGZpZy5jYXA9IlRvcCAxMCB2YXJpYWJsZXMgY29udGFpbmluZyB0aGUgc3Ryb25nZXN0IGNvcnJlbGF0aW9uIHdpdGggYXQgbGVhc3Qgb25lIG90aGVyIGZlYXR1cmUuIn0KIyBjb21wdXRlIGZlYXR1cmUgY29ycmVsYXRpb24KbSA8LSBjb3IobXlfYmFza2V0KQoKIyBwbG90IGZlYXR1cmVzIHdpdGggaGlnaGVzdCBjb3JyZWxhdGlvbnMKZGF0YS5mcmFtZSgKICByb3cgPSByb3duYW1lcyhtKVtyb3cobSlbdXBwZXIudHJpKG0pXV0sCiAgY29sID0gY29sbmFtZXMobSlbY29sKG0pW3VwcGVyLnRyaShtKV1dLAogIGNvcnIgPSBtW3VwcGVyLnRyaShtKV0sCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKSAlPiUKICBmaWx0ZXIoY29yciA8IDEgJiBjb3JyID4gLjI1KSAlPiUKICByZW5hbWUoYEl0ZW0gMWAgPSByb3csIGBJdGVtIDJgID0gY29sLCBDb3JyZWxhdGlvbiA9IGNvcnIpICU+JQogIG11dGF0ZShDb3JyZWxhdGlvbiA9IHJvdW5kKENvcnJlbGF0aW9uLCAzKSkgJT4lCiAgYXJyYW5nZShkZXNjKENvcnJlbGF0aW9uKSkgJT4lCiAga2FibGUoY2FwdGlvbiA9ICJWYXJpb3VzIGl0ZW1zIGluIG91ciBteSBiYXNrZXQgZGF0YSB0aGF0IGFyZSBjb3JyZWxhdGVkLiIpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiksIGZ1bGxfd2lkdGggPSBGQUxTRSkKYGBgCgojIyBGaW5kaW5nIHByaW5jaXBhbCBjb21wb25lbnRzCgpGaWd1cmUgMTcuMToKCmBgYHtyIGNyZWF0ZS1wY2EtaW1hZ2UsIGZpZy53aWR0aD0zLjc1LCBmaWcuaGVpZ2h0PTIuNzUsIGZpZy5jYXA9IlByaW5jaXBhbCBjb21wb25lbnRzIG9mIHR3byBmZWF0dXJlcyB0aGF0IGhhdmUgMC41NiBjb3JyZWxhdGlvbi4ifQpkZiA8LSBBbWVzSG91c2luZzo6bWFrZV9hbWVzKCkgJT4lCiAgc2VsZWN0KHZhcjEgPSBGaXJzdF9GbHJfU0YsIHZhcjIgPSBHcl9MaXZfQXJlYSkgJT4lCiAgZmlsdGVyKHZhcjEgIT0gdmFyMikgJT4lCiAgbXV0YXRlX2FsbChsb2cpICU+JQogIHNjYWxlKCkgJT4lIAogIGRhdGEuZnJhbWUoKSAlPiUKICBmaWx0ZXIodmFyMSA8IDQpCmdncGxvdChkZiwgYWVzKHZhcjEsIHZhcjIpKSArCiAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAuMiwgc2l6ZSA9IDEsIGNvbG9yID0gImRvZGdlcmJsdWUiKSArCiAgZ2VvbV9zZWdtZW50KAogICAgYWVzKHggPSAwLCB4ZW5kID0gMS41ICwgeSA9IDAsIHllbmQgPSAxLjUpLAogICAgYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuMjUsImNtIikpLCBzaXplID0gMC43NSwgY29sb3IgPSAiYmxhY2siCiAgICApICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSAxLCB5ID0gLjIsIGxhYmVsID0gIkZpcnN0IHByaW5jaXBhbCBjb21wb25lbnQiLCBzaXplID0gMi41LCBoanVzdCA9IDApICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSAtMywgeSA9IC44LCBsYWJlbCA9ICJTZWNvbmQgcHJpbmNpcGFsIGNvbXBvbmVudCIsIHNpemUgPSAyLjUsIGhqdXN0ID0gMCkgKwogIGdlb21fc2VnbWVudCgKICAgIGFlcyh4ID0gMCwgeGVuZCA9IC0wLjI3ICwgeSA9IDAsIHllbmQgPSAuNjUpLAogICAgYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuMjUsImNtIikpLCBzaXplID0gMC43NSwgY29sb3IgPSAiYmxhY2siCiAgICApICsKICB4bGFiKCJGZWF0dXJlIDIiKSArCiAgeWxhYigiRmVhdHVyZSAxIikgKwogIHRoZW1lX2J3KCkKYGBgCgpDcmVhdGVzIEZpZ3VyZSAxNy4yLiBVbmNvbW1lbnQgYHNuYXBzaG90UENBM2QoZmlsZSA9ICIzRC1QQ0EucG5nIilgIHRvIHNhdmUgaW1hZ2UKdG8gZGVzaXJlZCBsb2NhdGlvbi4KCmBgYHtyIGNyZWF0ZS0zRC1wY2EtaW1hZ2UsIHdhcm5pbmc9RkFMU0V9CmRmIDwtIEFtZXNIb3VzaW5nOjptYWtlX2FtZXMoKSAlPiUKICBzZWxlY3QodmFyMSA9IEZpcnN0X0Zscl9TRiwgdmFyMiA9IEdyX0xpdl9BcmVhLCB2YXIzID0gVG90Um1zX0FidkdyZCkgJT4lCiAgZmlsdGVyKHZhcjEgIT0gdmFyMikgJT4lCiAgbXV0YXRlX2F0KHZhcnModmFyMSwgdmFyMiksIGxvZykKCnBjYSA8LSBwcmNvbXAoZGYsIHNjYWxlID0gRkFMU0UpCnBjYTNkKHBjYSkKI3NuYXBzaG90UENBM2QoZmlsZT0iM0QtUENBLnBuZyIpCmBgYAoKSW1wb3J0cyBGaWd1cmUgMTcuMjoKCmBgYHtyIHBjYS0zZC1wbG90LCBmaWcuY2FwPSJQcmluY2lwYWwgY29tcG9uZW50cyBvZiB0aHJlZSBmZWF0dXJlcy4iLCBvdXQuaGVpZ2h0PSI3NSUiLCBvdXQud2lkdGg9Ijc1JSJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWFnZXMvM0QtUENBLnBuZyIpCmBgYAoKIyMgUGVyZm9ybWluZyBQQ0EgaW4gUgoKYGBge3IgcGNhLWgyby1pbml0fQpoMm8ubm9fcHJvZ3Jlc3MoKSAgIyB0dXJuIG9mZiBwcm9ncmVzcyBiYXJzIGZvciBicmV2aXR5Cmgyby5pbml0KG1heF9tZW1fc2l6ZSA9ICI1ZyIpICAjIGNvbm5lY3QgdG8gSDJPIGluc3RhbmNlCmBgYAoKYGBge3IgcGNhLW1vZGVsLTF9CiMgY29udmVydCBkYXRhIHRvIGgybyBvYmplY3QKbXlfYmFza2V0LmgybyA8LSBhcy5oMm8obXlfYmFza2V0KQoKIyBydW4gUENBCm15X3BjYSA8LSBoMm8ucHJjb21wKAogIHRyYWluaW5nX2ZyYW1lID0gbXlfYmFza2V0LmgybywKICBwY2FfbWV0aG9kID0gIkdyYW1TVkQiLAogIGsgPSBuY29sKG15X2Jhc2tldC5oMm8pLCAKICB0cmFuc2Zvcm0gPSAiU1RBTkRBUkRJWkUiLCAKICBpbXB1dGVfbWlzc2luZyA9IFRSVUUsCiAgbWF4X3J1bnRpbWVfc2VjcyA9IDEwMDAKKQpgYGAKCmBgYHtyIHBjYS1yZXN1bHRzfQpteV9wY2EKYGBgCgpgYGB7ciBwY2EtY29udHJpYnV0aW9ucywgZmlnLmhlaWdodD01LCBmaWcuY2FwPSJGZWF0dXJlIGxvYWRpbmdzIGlsbHVzdHJhdGluZyB0aGUgaW5mbHVlbmNlIHRoYXQgZWFjaCB2YXJpYWJsZSBoYXMgb24gdGhlIGZpcnN0IHByaW5jaXBhbCBjb21wb25lbnQuIn0KbXlfcGNhQG1vZGVsJGVpZ2VudmVjdG9ycyAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpICU+JSAKICBtdXRhdGUoZmVhdHVyZSA9IHJvdy5uYW1lcyguKSkgJT4lCiAgZ2dwbG90KGFlcyhwYzEsIHJlb3JkZXIoZmVhdHVyZSwgcGMxKSkpICsKICBnZW9tX3BvaW50KCkKYGBgCgpgYGB7ciBwYzEtcGMyLWNvbnRyaWJ1dGlvbnMsIGZpZy5oZWlnaHQ9NCwgZmlnLmNhcD0iRmVhdHVyZSBjb250cmlidXRpb24gZm9yIHByaW5jaXBhbCBjb21wb25lbnRzIG9uZSBhbmQgdHdvLiJ9Cm15X3BjYUBtb2RlbCRlaWdlbnZlY3RvcnMgJT4lIAogIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgbXV0YXRlKGZlYXR1cmUgPSByb3cubmFtZXMoLikpICU+JQogIGdncGxvdChhZXMocGMxLCBwYzIsIGxhYmVsID0gZmVhdHVyZSkpICsKICBnZW9tX3RleHQoKQpgYGAKCiMjIFNlbGVjdGluZyB0aGUgbnVtYmVyIG9mIHByaW5jaXBhbCBjb21wb25lbnRzCgojIyMgRWlnZW52YWx1ZSBjcml0ZXJpb24KCmBgYHtyIGVpZ2VuLWNyaXRlcmlvbn0KIyBDb21wdXRlIGVpZ2VudmFsdWVzCmVpZ2VuIDwtIG15X3BjYUBtb2RlbCRpbXBvcnRhbmNlWyJTdGFuZGFyZCBkZXZpYXRpb24iLCBdICU+JQogIGFzLnZlY3RvcigpICU+JQogIC5eMgogIAojIFN1bSBvZiBhbGwgZWlnZW52YWx1ZXMgZXF1YWxzIG51bWJlciBvZiB2YXJpYWJsZXMKc3VtKGVpZ2VuKQoKIyBGaW5kIFBDcyB3aGVyZSB0aGUgc3VtIG9mIGVpZ2VudmFsdWVzIGlzIGdyZWF0ZXIgdGhhbiBvciBlcXVhbCB0byAxCndoaWNoKGVpZ2VuID49IDEpCmBgYAoKRmlndXJlIDE3LjU6CgpgYGB7ciBlaWdlbi1jcml0ZXJpb24tcGxvdCwgZmlnLmNhcD0iRWlnZW52YWx1ZSBjcml0ZXJpb24ga2VlcHMgYWxsIHByaW5jaXBhbCBjb21wb25lbnRzIHdoZXJlIHRoZSBzdW0gb2YgdGhlIGVpZ2VudmFsdWVzIGFyZSBhYm92ZSBvciBlcXVhbCB0byBhIHZhbHVlIG9mIG9uZS4iLCBmaWcuaGVpZ2h0PTMuNSwgZmlnLndpZHRoPTl9CmRhdGEuZnJhbWUoCiAgUEMgPSBzZXFfYWxvbmcoZWlnZW4pLAogIEVpZ2VudmFsdWUgPSB1bmxpc3QoZWlnZW4pCikgJT4lCiAgZ2dwbG90KGFlcyhQQywgRWlnZW52YWx1ZSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEsIGx0eSA9ICJkYXNoZWQiLCBjb2xvciA9ICJyZWQiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IDA6NikgKwogIHhsYWIoIlBDIikgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IDE1LCB5ID0gMSwgbGFiZWwgPSAiZWlnZW52YWx1ZSBjcml0ZXJpYSBjdXRvZmYiLCBjb2xvciA9ICJyZWQiLCBzaXplID0gNSwgaGp1c3QgPSAwLCB2anVzdCA9IC0xKSAKYGBgCgoKIyMjIFByb3BvcnRpb24gb2YgdmFyaWFuY2UgZXhwbGFpbmVkIGNyaXRlcmlvbgoKYGBge3IgcHZlLWN2ZS1wbG90LCBmaWcuY2FwPSJQVkUgY3JpdGVyaW9uIGtlZXBzIGFsbCBwcmluY2lwYWwgY29tcG9uZW50cyB0aGF0IGFyZSBhYm92ZSBvciBlcXVhbCB0byBhIHByZS1zcGVjaWZpZWQgdGhyZXNob2xkIG9mIHRvdGFsIHZhcmlhYmlsaXR5IGV4cGxhaW5lZC4iLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD05fQojIEV4dHJhY3QgYW5kIHBsb3QgUFZFIGFuZCBDVkUKZGF0YS5mcmFtZSgKICBQQyAgPSBteV9wY2FAbW9kZWwkaW1wb3J0YW5jZSAlPiUgc2VxX2Fsb25nKCksCiAgUFZFID0gbXlfcGNhQG1vZGVsJGltcG9ydGFuY2UgJT4lIC5bMixdICU+JSB1bmxpc3QoKSwKICBDVkUgPSBteV9wY2FAbW9kZWwkaW1wb3J0YW5jZSAlPiUgLlszLF0gJT4lIHVubGlzdCgpCikgJT4lCiAgdGlkeXI6OmdhdGhlcihtZXRyaWMsIHZhcmlhbmNlX2V4cGxhaW5lZCwgLVBDKSAlPiUKICBnZ3Bsb3QoYWVzKFBDLCB2YXJpYW5jZV9leHBsYWluZWQpKSArCiAgZ2VvbV9wb2ludCgpICsKICBmYWNldF93cmFwKH4gbWV0cmljLCBuY29sID0gMSwgc2NhbGVzID0gImZyZWUiKQpgYGAKCmBgYHtyIGNvbXB1dGUtcHZlLXNpbGVudGx5fQp2ZSA8LSBkYXRhLmZyYW1lKAogIFBDICA9IG15X3BjYUBtb2RlbCRpbXBvcnRhbmNlICU+JSBuYW1lcygpLAogIFBWRSA9IG15X3BjYUBtb2RlbCRpbXBvcnRhbmNlICU+JSAuWzIsXSAlPiUgdW5saXN0KCksCiAgQ1ZFID0gbXlfcGNhQG1vZGVsJGltcG9ydGFuY2UgJT4lIC5bMyxdICU+JSB1bmxpc3QoKQopCmBgYAoKYGBge3J9CiMgSG93IG1hbnkgUENzIHJlcXVpcmVkIHRvIGV4cGxhaW4gYXQgbGVhc3QgNzUlIG9mIHRvdGFsIHZhcmlhYmlsaXR5Cm1pbih3aGljaCh2ZSRDVkUgPj0gMC43NSkpCmBgYAoKIyMjIFNjcmVlIHBsb3QgY3JpdGVyaW9uCgpgYGB7ciBwY2Etc2NyZWUtcGxvdC1jcml0ZXJpb24sIGZpZy5jYXA9IlNjcmVlIHBsb3QgY3JpdGVyaW9uIGxvb2tzIGZvciB0aGUgJ2VsYm93JyBpbiB0aGUgY3VydmUgYW5kIGtlZXBzIGFsbCBwcmluY2lwYWwgY29tcG9uZW50cyBiZWZvcmUgdGhlIGxpbmUgZmxhdHRlbnMgb3V0LiIsIGZpZy5oZWlnaHQ9My41LCBmaWcud2lkdGg9OX0KZGF0YS5mcmFtZSgKICBQQyAgPSBteV9wY2FAbW9kZWwkaW1wb3J0YW5jZSAlPiUgc2VxX2Fsb25nLAogIFBWRSA9IG15X3BjYUBtb2RlbCRpbXBvcnRhbmNlICU+JSAuWzIsXSAlPiUgdW5saXN0KCkKKSAlPiUKICBnZ3Bsb3QoYWVzKFBDLCBQVkUsIGdyb3VwID0gMSwgbGFiZWwgPSBQQykpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3RleHQobnVkZ2VfeSA9IC0uMDAyKQpgYGAKCmBgYHtyfQpoMm8uc2h1dGRvd24ocHJvbXB0ID0gRkFMU0UpCmBgYAoK