library(mlbench)
library(dplyr)
library(ggplot2)
library(tsibble)
library(tidyr)
library(corrplot)
library(cowplot)
library(psych)
library(MASS)
library(gridExtra)
library(tidyr)

Instructions

Do problems 3.1 and 3.2 in the Kuhn and Johnson book Applied Predictive Modeling. Please submit your Rpubs link along with your .pdf for your run code.

3.1

The UC Irvine Machine Learning Repository6 contains a data set related to glass identification. The data consist of 214 glass samples labeled as one of seven class categories. There are nine predictors, including the refractive index and percentages of eight elements: Na, Mg, Al, Si, K, Ca, Ba, and Fe. The data can be accessed via:

data(Glass)
str(Glass)
'data.frame':   214 obs. of  10 variables:
 $ RI  : num  1.52 1.52 1.52 1.52 1.52 ...
 $ Na  : num  13.6 13.9 13.5 13.2 13.3 ...
 $ Mg  : num  4.49 3.6 3.55 3.69 3.62 3.61 3.6 3.61 3.58 3.6 ...
 $ Al  : num  1.1 1.36 1.54 1.29 1.24 1.62 1.14 1.05 1.37 1.36 ...
 $ Si  : num  71.8 72.7 73 72.6 73.1 ...
 $ K   : num  0.06 0.48 0.39 0.57 0.55 0.64 0.58 0.57 0.56 0.57 ...
 $ Ca  : num  8.75 7.83 7.78 8.22 8.07 8.07 8.17 8.24 8.3 8.4 ...
 $ Ba  : num  0 0 0 0 0 0 0 0 0 0 ...
 $ Fe  : num  0 0 0 0 0 0.26 0 0 0 0.11 ...
 $ Type: Factor w/ 6 levels "1","2","3","5",..: 1 1 1 1 1 1 1 1 1 1 ...

(a)

Using visualizations, explore the predictor variables to understand their distributions as well as the relationships between predictors.

Glass %>%
  dplyr::select(-10)%>%
  gather() %>%
  ggplot(aes(x=value))+
  geom_histogram(fill="lightgreen")+
  facet_wrap(~key,scales = "free")



corrplot(cor(Glass%>%dplyr::select(-10)),type="lower")


Glass%>%
  dplyr::select("RI","Ca")%>%
  ggplot(aes(x=RI,y=Ca))+
  geom_point()+
  geom_smooth(method="lm", se = FALSE,color="red")+
  labs(x = "Refractive Index (RI)", y = "Calcium (Ca)", 
       title = "RI vs Ca Scatterplot" )

Glass%>%
  dplyr::select("RI","Mg")%>%
  ggplot(aes(x=RI,y=Mg))+
  geom_point()+
  labs(x = "Refractive Index (RI)", y = "Magnesium (Mg)", 
       title = "RI vs Mg Scatterplot" )

In order to make the plots Type was excluded, as the values were not numeric and did not provide any real insight if it were to be plotted.

Noting the plots I can assess the following:

  • All plots with exception of Mg and Si appear to be right skewed to some degree
  • Of the right skewed properties Ba and Fe is centered around 0
  • Ai and Ba is the most centered
  • RI and Ca is shown to have the highest positive correlation while RI and Mg has the lowest.
  • none of the degrees of relationship seems prominent

(b)

Do there appear to be any outliers in the data? Are any predictors skewed?

Glass %>%
  dplyr::select(-10)%>%
  gather() %>%
  ggplot(aes(value))+
  geom_boxplot()+
  facet_wrap(~key,scales = "free")

All of the attribute except for Mg have outliers in the data. Skewness is shown below. As noted all have a degree of skewness, right and left skewness is described in (a) with Ba having the most notable degree.

data.frame(describe(Glass))%>%
                      dplyr::select(skew)

(c)

Are there any relevant transformations of one or more predictors that might improve the classification model?

df_glass<-Glass
df_glass$log_Ba<-log(Glass$Ba)
df_glass$log_Fe<-log(Glass$Fe)
df_glass$log_K<-log(Glass$K)

plot_Ba <- ggplot(df_glass, aes(x = log_Ba)) +
  geom_histogram(bins = 20, fill = "darkblue",
                 color = "black") +
  labs(title = "Histogram of log-transformed Ba",
       x = "log(Be)", y = "Frequency")

plot_Fe<-ggplot(df_glass, aes(x = log_Fe)) +
  geom_histogram(bins = 20, fill = "blue",
                 color = "black") +
  labs(title = "Histogram of log-transformed Fe",
       x = "log(Be)", y = "Frequency")

plot_K<-ggplot(df_glass, aes(x = log_K)) +
  geom_histogram(bins = 20, fill = "lightblue",
                 color = "black") +
  labs(title = "Histogram of log-transformed K",
       x = "log(Be)", y = "Frequency")

plot_grid(plot_Ba,plot_Fe,plot_K, ncol = 3)

# Box-Cox transformation for specified columns
df_glass_transformed <- df_glass

# Columns for Box-Cox transformation
columns <- c("RI", "Na", "Al", "Si", "Ca")

for (col in columns) {
  transformed_col <- boxcox(df_glass[[col]] ~ 1, plotit=FALSE)
  lambda <- transformed_col$x[which.max(transformed_col$y)]
  if (lambda == 0) {
    df_glass_transformed[[paste0("boxcox_", col)]] <- log(df_glass[[col]])
  } else {
    df_glass_transformed[[paste0("boxcox_", col)]] <- (df_glass[[col]]^lambda - 1) / lambda
  }
}

# Replace null values with 0
df_glass_transformed[is.na(df_glass_transformed)] <- 0

# Create ggplot visualizations for each transformed column
plots <- list()
for (col in paste0("boxcox_", columns)) {
  plots[[col]] <- ggplot(df_glass_transformed, aes(x = !!sym(col))) +
    geom_histogram(bins = 20, fill = "orange", color = "black") +
    labs(title = paste("Histogram of", col), x = col, y = "Frequency")
}

# Arrange plots in columns of 3
grid.arrange(grobs = plots, ncol = 3)

rm(list = ls(pattern = "(lambda|plot|glass|^col|col$)"))

3.2

The soybean data can also be found at the UC Irvine Machine Learning Repository. Data were collected to predict disease in 683 soybeans. The 35 predictors are mostly categorical and include information on the environmen￾tal conditions (e.g., temperature, precipitation) and plant conditions (e.g., left spots, mold growth). The outcome labels consist of 19 distinct classes.

The data can be loaded via:

data(Soybean)
## See ?Soybean for details

(a)

Investigate the frequency distributions for the categorical predictors. Are any of the distributions degenerate in the ways discussed earlier in this chapter?

str(Soybean)
'data.frame':   683 obs. of  36 variables:
 $ Class          : Factor w/ 19 levels "2-4-d-injury",..: 11 11 11 11 11 11 11 11 11 11 ...
 $ date           : Factor w/ 7 levels "0","1","2","3",..: 7 5 4 4 7 6 6 5 7 5 ...
 $ plant.stand    : Ord.factor w/ 2 levels "0"<"1": 1 1 1 1 1 1 1 1 1 1 ...
 $ precip         : Ord.factor w/ 3 levels "0"<"1"<"2": 3 3 3 3 3 3 3 3 3 3 ...
 $ temp           : Ord.factor w/ 3 levels "0"<"1"<"2": 2 2 2 2 2 2 2 2 2 2 ...
 $ hail           : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 2 1 1 ...
 $ crop.hist      : Factor w/ 4 levels "0","1","2","3": 2 3 2 2 3 4 3 2 4 3 ...
 $ area.dam       : Factor w/ 4 levels "0","1","2","3": 2 1 1 1 1 1 1 1 1 1 ...
 $ sever          : Factor w/ 3 levels "0","1","2": 2 3 3 3 2 2 2 2 2 3 ...
 $ seed.tmt       : Factor w/ 3 levels "0","1","2": 1 2 2 1 1 1 2 1 2 1 ...
 $ germ           : Ord.factor w/ 3 levels "0"<"1"<"2": 1 2 3 2 3 2 1 3 2 3 ...
 $ plant.growth   : Factor w/ 2 levels "0","1": 2 2 2 2 2 2 2 2 2 2 ...
 $ leaves         : Factor w/ 2 levels "0","1": 2 2 2 2 2 2 2 2 2 2 ...
 $ leaf.halo      : Factor w/ 3 levels "0","1","2": 1 1 1 1 1 1 1 1 1 1 ...
 $ leaf.marg      : Factor w/ 3 levels "0","1","2": 3 3 3 3 3 3 3 3 3 3 ...
 $ leaf.size      : Ord.factor w/ 3 levels "0"<"1"<"2": 3 3 3 3 3 3 3 3 3 3 ...
 $ leaf.shread    : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
 $ leaf.malf      : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
 $ leaf.mild      : Factor w/ 3 levels "0","1","2": 1 1 1 1 1 1 1 1 1 1 ...
 $ stem           : Factor w/ 2 levels "0","1": 2 2 2 2 2 2 2 2 2 2 ...
 $ lodging        : Factor w/ 2 levels "0","1": 2 1 1 1 1 1 2 1 1 1 ...
 $ stem.cankers   : Factor w/ 4 levels "0","1","2","3": 4 4 4 4 4 4 4 4 4 4 ...
 $ canker.lesion  : Factor w/ 4 levels "0","1","2","3": 2 2 1 1 2 1 2 2 2 2 ...
 $ fruiting.bodies: Factor w/ 2 levels "0","1": 2 2 2 2 2 2 2 2 2 2 ...
 $ ext.decay      : Factor w/ 3 levels "0","1","2": 2 2 2 2 2 2 2 2 2 2 ...
 $ mycelium       : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
 $ int.discolor   : Factor w/ 3 levels "0","1","2": 1 1 1 1 1 1 1 1 1 1 ...
 $ sclerotia      : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
 $ fruit.pods     : Factor w/ 4 levels "0","1","2","3": 1 1 1 1 1 1 1 1 1 1 ...
 $ fruit.spots    : Factor w/ 4 levels "0","1","2","4": 4 4 4 4 4 4 4 4 4 4 ...
 $ seed           : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
 $ mold.growth    : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
 $ seed.discolor  : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
 $ seed.size      : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
 $ shriveling     : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
 $ roots          : Factor w/ 3 levels "0","1","2": 1 1 1 1 1 1 1 1 1 1 ...
df_soybean <- Soybean#%>%dplyr::select(-1)

par(mfrow=c(2,2))
for (col in 2:ncol(Soybean)) {
    hist( as.numeric(Soybean[,col]),main =   colnames(Soybean)[col], xlab = colnames(Soybean)[col])
}

# Function to count distinct numeric values, including NAs, in a column
count_distinct_numeric <- function(column) {
  n_distinct(na.omit(column))
}

# Apply the function to each column of the data frame 'B'
distinct_counts <- sapply(Soybean, count_distinct_numeric)

# Reorder distinct counts in ascending order
distinct_counts <- distinct_counts[order(distinct_counts)]

# Remove columns with NA counts from the print output
distinct_counts <- distinct_counts[!is.na(distinct_counts)]

# Print the distinct counts for each column
print(distinct_counts)
    plant.stand            hail    plant.growth          leaves     leaf.shread 
              2               2               2               2               2 
      leaf.malf            stem         lodging fruiting.bodies        mycelium 
              2               2               2               2               2 
      sclerotia            seed     mold.growth   seed.discolor       seed.size 
              2               2               2               2               2 
     shriveling          precip            temp           sever        seed.tmt 
              2               3               3               3               3 
           germ       leaf.halo       leaf.marg       leaf.size       leaf.mild 
              3               3               3               3               3 
      ext.decay    int.discolor           roots       crop.hist        area.dam 
              3               3               3               4               4 
   stem.cankers   canker.lesion      fruit.pods     fruit.spots            date 
              4               4               4               4               7 
          Class 
             19 

The distribution of mycelium and sclerotia appears to be degenerate, considering the low frequency count and minimal distinct values. They are significantly less then the other attributes to where visually mycelium almost appeared to have just 1 value.

(b)

Roughly 18 % of the data are missing. Are there particular predictors that are more likely to be missing? Is the pattern of missing data related to the classes?

df_soybean%>%
  summarise_all(list(~is.na(.))) %>%
  pivot_longer(everything(), names_to = "variables", values_to = "missing") %>%
  count(variables, missing) %>%
  ggplot(aes(y = reorder(variables, n), x = n, fill = missing)) +
  geom_col(position = "fill") +
  geom_text(aes(label = ifelse(missing, "NA", "Non-NA")), 
            position = position_fill(vjust = 0.5), 
            color = "white", size = 4) +  # Add data labels
  labs(title = "NA Proportion",
       x = "Proportion") +
  scale_fill_manual(values = c("grey", "darkgreen")) +
  theme_minimal()

df_incomplete <- df_soybean[!complete.cases(df_soybean),]
(df_incomplete %>%
                group_by(Class) %>%
                  tally())
df_soybean%>%
  filter(!Class %in% c("2-4-d-injury","cyst-nematode",
                  "diaporthe-pod-&-stem-blight",
                  "herbicide-injury","phytophthora-rot"))%>%
  summarise_all(list(~is.na(.))) %>%
  pivot_longer(everything(), 
               names_to = "variables", 
               values_to = "missing") %>%
  count(variables, missing) %>%
  ggplot(aes(y = reorder(variables, n), 
             x = n, fill = missing)) +
  geom_col(position = "fill") +
  geom_text(aes(label = ifelse(missing, 
                               "NA", "Non-NA")), 
            position = position_fill(vjust = 0.5), 
            color = "white", size = 4) +  # Add data labels
  labs(title = "NA Proportion",
       x = "Proportion") +
  scale_fill_manual(values = c("grey", "darkgreen")) +
  theme_minimal()

(c)

Develop a strategy for handling missing data, either by eliminating predictors or imputation.

I wouldnt just remove the classes although its seems much easier to just do that. I would say replace with zero where it makes sense, for binary values like hail or lodging since it is more logical to defualt to “no” unless reason to believe otherwise. I say this assuming hail=yes likelihood being significantly smaller if presuming. For values like severe I’d much rather not put a value, unless an “unknown” metric is put in, as it’s frequency is likely to be very low. For the remaining, I would likely want to get the frequency and substitute the categorical metric with its mode for normalization with box-cox, and see if it better fits the model we want to predict with.

LS0tDQp0aXRsZTogJ0RBVEEgNjI0OiBQUkVESUNUSVZFIEFOQUxZVElDUyBIVzQnDQphdXRob3I6ICJHYWJyaWVsIENhbXBvcyINCmRhdGU6ICJMYXN0IGVkaXRlZCBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCICVkLCAlWScpYCINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICBnZW9tZXRyeTogbGVmdD0wLjVjbSxyaWdodD0wLjVjbSx0b3A9MWNtLGJvdHRvbT0yY20NCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgcGRmX2RvY3VtZW50Og0KICAgIGxhdGV4X2VuZ2luZTogeGVsYXRleA0KdXJsY29sb3I6IGJsdWUNCi0tLQ0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkobWxiZW5jaCkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHRzaWJibGUpDQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkoY293cGxvdCkNCmxpYnJhcnkocHN5Y2gpDQpsaWJyYXJ5KE1BU1MpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkodGlkeXIpDQoNCmBgYA0KDQoNCiMgSW5zdHJ1Y3Rpb25zDQoNCkRvIHByb2JsZW1zIDMuMSBhbmQgMy4yIGluIHRoZSBLdWhuIGFuZCBKb2huc29uIGJvb2sgQXBwbGllZCBQcmVkaWN0aXZlIE1vZGVsaW5nLiAgUGxlYXNlIHN1Ym1pdCB5b3VyIFJwdWJzIGxpbmsgYWxvbmcgd2l0aCB5b3VyIC5wZGYgZm9yIHlvdXIgcnVuIGNvZGUuDQoNCiMgMy4xDQoNClRoZSBVQyBJcnZpbmUgTWFjaGluZSBMZWFybmluZyBSZXBvc2l0b3J5NiBjb250YWlucyBhIGRhdGEgc2V0IHJlbGF0ZWQNCnRvIGdsYXNzIGlkZW50aWZpY2F0aW9uLiBUaGUgZGF0YSBjb25zaXN0IG9mIDIxNCBnbGFzcyBzYW1wbGVzIGxhYmVsZWQgYXMgb25lDQpvZiBzZXZlbiBjbGFzcyBjYXRlZ29yaWVzLiBUaGVyZSBhcmUgbmluZSBwcmVkaWN0b3JzLCBpbmNsdWRpbmcgdGhlIHJlZnJhY3RpdmUNCmluZGV4IGFuZCBwZXJjZW50YWdlcyBvZiBlaWdodCBlbGVtZW50czogTmEsIE1nLCBBbCwgU2ksIEssIENhLCBCYSwgYW5kIEZlLg0KVGhlIGRhdGEgY2FuIGJlIGFjY2Vzc2VkIHZpYToNCg0KYGBge3J9DQpkYXRhKEdsYXNzKQ0Kc3RyKEdsYXNzKQ0KYGBgDQojIyAoYSkNCg0KVXNpbmcgdmlzdWFsaXphdGlvbnMsIGV4cGxvcmUgdGhlIHByZWRpY3RvciB2YXJpYWJsZXMgdG8gdW5kZXJzdGFuZCB0aGVpcg0KZGlzdHJpYnV0aW9ucyBhcyB3ZWxsIGFzIHRoZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gcHJlZGljdG9ycy4NCg0KDQpgYGB7cn0NCkdsYXNzICU+JQ0KICBkcGx5cjo6c2VsZWN0KC0xMCklPiUNCiAgZ2F0aGVyKCkgJT4lDQogIGdncGxvdChhZXMoeD12YWx1ZSkpKw0KICBnZW9tX2hpc3RvZ3JhbShmaWxsPSJsaWdodGdyZWVuIikrDQogIGZhY2V0X3dyYXAofmtleSxzY2FsZXMgPSAiZnJlZSIpDQoNCg0KY29ycnBsb3QoY29yKEdsYXNzJT4lZHBseXI6OnNlbGVjdCgtMTApKSx0eXBlPSJsb3dlciIpDQoNCkdsYXNzJT4lDQogIGRwbHlyOjpzZWxlY3QoIlJJIiwiQ2EiKSU+JQ0KICBnZ3Bsb3QoYWVzKHg9UkkseT1DYSkpKw0KICBnZW9tX3BvaW50KCkrDQogIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iLCBzZSA9IEZBTFNFLGNvbG9yPSJyZWQiKSsNCiAgbGFicyh4ID0gIlJlZnJhY3RpdmUgSW5kZXggKFJJKSIsIHkgPSAiQ2FsY2l1bSAoQ2EpIiwgDQogICAgICAgdGl0bGUgPSAiUkkgdnMgQ2EgU2NhdHRlcnBsb3QiICkNCkdsYXNzJT4lDQogIGRwbHlyOjpzZWxlY3QoIlJJIiwiTWciKSU+JQ0KICBnZ3Bsb3QoYWVzKHg9UkkseT1NZykpKw0KICBnZW9tX3BvaW50KCkrDQogIGxhYnMoeCA9ICJSZWZyYWN0aXZlIEluZGV4IChSSSkiLCB5ID0gIk1hZ25lc2l1bSAoTWcpIiwgDQogICAgICAgdGl0bGUgPSAiUkkgdnMgTWcgU2NhdHRlcnBsb3QiICkNCg0KYGBgDQoNCkluIG9yZGVyIHRvIG1ha2UgdGhlIHBsb3RzIGBUeXBlYCB3YXMgZXhjbHVkZWQsIGFzIHRoZSB2YWx1ZXMgd2VyZSBub3QgbnVtZXJpYyBhbmQgZGlkIG5vdCBwcm92aWRlIGFueSByZWFsIGluc2lnaHQgaWYgaXQgd2VyZSB0byBiZSBwbG90dGVkLg0KDQpOb3RpbmcgdGhlIHBsb3RzIEkgY2FuIGFzc2VzcyB0aGUgZm9sbG93aW5nOg0KDQoqIEFsbCBwbG90cyB3aXRoIGV4Y2VwdGlvbiBvZiBgTWdgIGFuZCBgU2lgIGFwcGVhciB0byBiZSByaWdodCBza2V3ZWQgdG8gc29tZSBkZWdyZWUNCiogT2YgdGhlIHJpZ2h0IHNrZXdlZCBwcm9wZXJ0aWVzIGBCYWAgYW5kIGBGZWAgaXMgY2VudGVyZWQgYXJvdW5kIDANCiogYEFpYCBhbmQgYEJhYCBpcyB0aGUgbW9zdCBjZW50ZXJlZA0KKiBgUklgIGFuZCBgQ2FgIGlzIHNob3duIHRvIGhhdmUgdGhlIGhpZ2hlc3QgcG9zaXRpdmUgY29ycmVsYXRpb24gd2hpbGUgYFJJYCBhbmQgYE1nYCBoYXMgdGhlIGxvd2VzdC4NCiogbm9uZSBvZiB0aGUgZGVncmVlcyBvZiByZWxhdGlvbnNoaXAgc2VlbXMgcHJvbWluZW50DQoNCiMjIChiKQ0KDQpEbyB0aGVyZSBhcHBlYXIgdG8gYmUgYW55IG91dGxpZXJzIGluIHRoZSBkYXRhPyBBcmUgYW55IHByZWRpY3RvcnMgc2tld2VkPw0KDQpgYGB7cn0NCkdsYXNzICU+JQ0KICBkcGx5cjo6c2VsZWN0KC0xMCklPiUNCiAgZ2F0aGVyKCkgJT4lDQogIGdncGxvdChhZXModmFsdWUpKSsNCiAgZ2VvbV9ib3hwbG90KCkrDQogIGZhY2V0X3dyYXAofmtleSxzY2FsZXMgPSAiZnJlZSIpDQpgYGANCg0KQWxsIG9mIHRoZSBhdHRyaWJ1dGUgZXhjZXB0IGZvciBgTWdgIGhhdmUgb3V0bGllcnMgaW4gdGhlIGRhdGEuDQpTa2V3bmVzcyBpcyBzaG93biBiZWxvdy4gQXMgbm90ZWQgYWxsIGhhdmUgYSBkZWdyZWUgb2Ygc2tld25lc3MsIHJpZ2h0IGFuZCBsZWZ0IHNrZXduZXNzIGlzIGRlc2NyaWJlZCBpbiAoYSkgd2l0aCBgQmFgIGhhdmluZyB0aGUgbW9zdCBub3RhYmxlIGRlZ3JlZS4NCg0KYGBge3J9DQpkYXRhLmZyYW1lKGRlc2NyaWJlKEdsYXNzKSklPiUNCiAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KHNrZXcpDQpgYGANCg0KDQojIyAoYykNCg0KQXJlIHRoZXJlIGFueSByZWxldmFudCB0cmFuc2Zvcm1hdGlvbnMgb2Ygb25lIG9yIG1vcmUgcHJlZGljdG9ycyB0aGF0DQptaWdodCBpbXByb3ZlIHRoZSBjbGFzc2lmaWNhdGlvbiBtb2RlbD8NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQpkZl9nbGFzczwtR2xhc3MNCmRmX2dsYXNzJGxvZ19CYTwtbG9nKEdsYXNzJEJhKQ0KZGZfZ2xhc3MkbG9nX0ZlPC1sb2coR2xhc3MkRmUpDQpkZl9nbGFzcyRsb2dfSzwtbG9nKEdsYXNzJEspDQoNCnBsb3RfQmEgPC0gZ2dwbG90KGRmX2dsYXNzLCBhZXMoeCA9IGxvZ19CYSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDIwLCBmaWxsID0gImRhcmtibHVlIiwNCiAgICAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siKSArDQogIGxhYnModGl0bGUgPSAiSGlzdG9ncmFtIG9mIGxvZy10cmFuc2Zvcm1lZCBCYSIsDQogICAgICAgeCA9ICJsb2coQmUpIiwgeSA9ICJGcmVxdWVuY3kiKQ0KDQpwbG90X0ZlPC1nZ3Bsb3QoZGZfZ2xhc3MsIGFlcyh4ID0gbG9nX0ZlKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMjAsIGZpbGwgPSAiYmx1ZSIsDQogICAgICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIikgKw0KICBsYWJzKHRpdGxlID0gIkhpc3RvZ3JhbSBvZiBsb2ctdHJhbnNmb3JtZWQgRmUiLA0KICAgICAgIHggPSAibG9nKEJlKSIsIHkgPSAiRnJlcXVlbmN5IikNCg0KcGxvdF9LPC1nZ3Bsb3QoZGZfZ2xhc3MsIGFlcyh4ID0gbG9nX0spKSArDQogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAyMCwgZmlsbCA9ICJsaWdodGJsdWUiLA0KICAgICAgICAgICAgICAgICBjb2xvciA9ICJibGFjayIpICsNCiAgbGFicyh0aXRsZSA9ICJIaXN0b2dyYW0gb2YgbG9nLXRyYW5zZm9ybWVkIEsiLA0KICAgICAgIHggPSAibG9nKEJlKSIsIHkgPSAiRnJlcXVlbmN5IikNCg0KcGxvdF9ncmlkKHBsb3RfQmEscGxvdF9GZSxwbG90X0ssIG5jb2wgPSAzKQ0KDQpgYGANCg0KDQpgYGB7cn0NCiMgQm94LUNveCB0cmFuc2Zvcm1hdGlvbiBmb3Igc3BlY2lmaWVkIGNvbHVtbnMNCmRmX2dsYXNzX3RyYW5zZm9ybWVkIDwtIGRmX2dsYXNzDQoNCiMgQ29sdW1ucyBmb3IgQm94LUNveCB0cmFuc2Zvcm1hdGlvbg0KY29sdW1ucyA8LSBjKCJSSSIsICJOYSIsICJBbCIsICJTaSIsICJDYSIpDQoNCmZvciAoY29sIGluIGNvbHVtbnMpIHsNCiAgdHJhbnNmb3JtZWRfY29sIDwtIGJveGNveChkZl9nbGFzc1tbY29sXV0gfiAxLCBwbG90aXQ9RkFMU0UpDQogIGxhbWJkYSA8LSB0cmFuc2Zvcm1lZF9jb2wkeFt3aGljaC5tYXgodHJhbnNmb3JtZWRfY29sJHkpXQ0KICBpZiAobGFtYmRhID09IDApIHsNCiAgICBkZl9nbGFzc190cmFuc2Zvcm1lZFtbcGFzdGUwKCJib3hjb3hfIiwgY29sKV1dIDwtIGxvZyhkZl9nbGFzc1tbY29sXV0pDQogIH0gZWxzZSB7DQogICAgZGZfZ2xhc3NfdHJhbnNmb3JtZWRbW3Bhc3RlMCgiYm94Y294XyIsIGNvbCldXSA8LSAoZGZfZ2xhc3NbW2NvbF1dXmxhbWJkYSAtIDEpIC8gbGFtYmRhDQogIH0NCn0NCg0KIyBSZXBsYWNlIG51bGwgdmFsdWVzIHdpdGggMA0KZGZfZ2xhc3NfdHJhbnNmb3JtZWRbaXMubmEoZGZfZ2xhc3NfdHJhbnNmb3JtZWQpXSA8LSAwDQoNCiMgQ3JlYXRlIGdncGxvdCB2aXN1YWxpemF0aW9ucyBmb3IgZWFjaCB0cmFuc2Zvcm1lZCBjb2x1bW4NCnBsb3RzIDwtIGxpc3QoKQ0KZm9yIChjb2wgaW4gcGFzdGUwKCJib3hjb3hfIiwgY29sdW1ucykpIHsNCiAgcGxvdHNbW2NvbF1dIDwtIGdncGxvdChkZl9nbGFzc190cmFuc2Zvcm1lZCwgYWVzKHggPSAhIXN5bShjb2wpKSkgKw0KICAgIGdlb21faGlzdG9ncmFtKGJpbnMgPSAyMCwgZmlsbCA9ICJvcmFuZ2UiLCBjb2xvciA9ICJibGFjayIpICsNCiAgICBsYWJzKHRpdGxlID0gcGFzdGUoIkhpc3RvZ3JhbSBvZiIsIGNvbCksIHggPSBjb2wsIHkgPSAiRnJlcXVlbmN5IikNCn0NCg0KIyBBcnJhbmdlIHBsb3RzIGluIGNvbHVtbnMgb2YgMw0KZ3JpZC5hcnJhbmdlKGdyb2JzID0gcGxvdHMsIG5jb2wgPSAzKQ0KDQpgYGANCg0KYGBge3J9DQpybShsaXN0ID0gbHMocGF0dGVybiA9ICIobGFtYmRhfHBsb3R8Z2xhc3N8XmNvbHxjb2wkKSIpKQ0KYGBgDQoNCiMgMy4yDQoNClRoZSBzb3liZWFuIGRhdGEgY2FuIGFsc28gYmUgZm91bmQgYXQgdGhlIFVDIElydmluZSBNYWNoaW5lIExlYXJuaW5nDQpSZXBvc2l0b3J5LiBEYXRhIHdlcmUgY29sbGVjdGVkIHRvIHByZWRpY3QgZGlzZWFzZSBpbiA2ODMgc295YmVhbnMuIFRoZSAzNQ0KcHJlZGljdG9ycyBhcmUgbW9zdGx5IGNhdGVnb3JpY2FsIGFuZCBpbmNsdWRlIGluZm9ybWF0aW9uIG9uIHRoZSBlbnZpcm9ubWVu77++dGFsIGNvbmRpdGlvbnMgKGUuZy4sIHRlbXBlcmF0dXJlLCBwcmVjaXBpdGF0aW9uKSBhbmQgcGxhbnQgY29uZGl0aW9ucyAoZS5nLiwgbGVmdA0Kc3BvdHMsIG1vbGQgZ3Jvd3RoKS4gVGhlIG91dGNvbWUgbGFiZWxzIGNvbnNpc3Qgb2YgMTkgZGlzdGluY3QgY2xhc3Nlcy4NCg0KVGhlIGRhdGEgY2FuIGJlIGxvYWRlZCB2aWE6DQoNCmBgYHtyfQ0KZGF0YShTb3liZWFuKQ0KIyMgU2VlID9Tb3liZWFuIGZvciBkZXRhaWxzDQpgYGANCg0KIyMgKGEpDQoNCkludmVzdGlnYXRlIHRoZSBmcmVxdWVuY3kgZGlzdHJpYnV0aW9ucyBmb3IgdGhlIGNhdGVnb3JpY2FsIHByZWRpY3RvcnMuIEFyZQ0KYW55IG9mIHRoZSBkaXN0cmlidXRpb25zIGRlZ2VuZXJhdGUgaW4gdGhlIHdheXMgZGlzY3Vzc2VkIGVhcmxpZXIgaW4gdGhpcw0KY2hhcHRlcj8NCg0KYGBge3J9DQpzdHIoU295YmVhbikNCmBgYA0KDQpgYGB7cn0NCmRmX3NveWJlYW4gPC0gU295YmVhbiMlPiVkcGx5cjo6c2VsZWN0KC0xKQ0KDQpwYXIobWZyb3c9YygyLDIpKQ0KZm9yIChjb2wgaW4gMjpuY29sKFNveWJlYW4pKSB7DQogICAgaGlzdCggYXMubnVtZXJpYyhTb3liZWFuWyxjb2xdKSxtYWluID0gICBjb2xuYW1lcyhTb3liZWFuKVtjb2xdLCB4bGFiID0gY29sbmFtZXMoU295YmVhbilbY29sXSkNCn0NCmBgYA0KDQpgYGB7cn0NCiMgRnVuY3Rpb24gdG8gY291bnQgZGlzdGluY3QgbnVtZXJpYyB2YWx1ZXMsIGluY2x1ZGluZyBOQXMsIGluIGEgY29sdW1uDQpjb3VudF9kaXN0aW5jdF9udW1lcmljIDwtIGZ1bmN0aW9uKGNvbHVtbikgew0KICBuX2Rpc3RpbmN0KG5hLm9taXQoY29sdW1uKSkNCn0NCg0KIyBBcHBseSB0aGUgZnVuY3Rpb24gdG8gZWFjaCBjb2x1bW4gb2YgdGhlIGRhdGEgZnJhbWUgJ0InDQpkaXN0aW5jdF9jb3VudHMgPC0gc2FwcGx5KFNveWJlYW4sIGNvdW50X2Rpc3RpbmN0X251bWVyaWMpDQoNCiMgUmVvcmRlciBkaXN0aW5jdCBjb3VudHMgaW4gYXNjZW5kaW5nIG9yZGVyDQpkaXN0aW5jdF9jb3VudHMgPC0gZGlzdGluY3RfY291bnRzW29yZGVyKGRpc3RpbmN0X2NvdW50cyldDQoNCiMgUmVtb3ZlIGNvbHVtbnMgd2l0aCBOQSBjb3VudHMgZnJvbSB0aGUgcHJpbnQgb3V0cHV0DQpkaXN0aW5jdF9jb3VudHMgPC0gZGlzdGluY3RfY291bnRzWyFpcy5uYShkaXN0aW5jdF9jb3VudHMpXQ0KDQojIFByaW50IHRoZSBkaXN0aW5jdCBjb3VudHMgZm9yIGVhY2ggY29sdW1uDQpwcmludChkaXN0aW5jdF9jb3VudHMpDQoNCmBgYA0KDQpUaGUgZGlzdHJpYnV0aW9uIG9mIGBteWNlbGl1bWAgYW5kIGBzY2xlcm90aWFgIGFwcGVhcnMgdG8gYmUgZGVnZW5lcmF0ZSwgY29uc2lkZXJpbmcgdGhlIGxvdyBmcmVxdWVuY3kgY291bnQgYW5kIG1pbmltYWwgZGlzdGluY3QgdmFsdWVzLiBUaGV5IGFyZSBzaWduaWZpY2FudGx5IGxlc3MgdGhlbiB0aGUgb3RoZXIgYXR0cmlidXRlcyB0byB3aGVyZSB2aXN1YWxseSBgbXljZWxpdW1gIGFsbW9zdCBhcHBlYXJlZCB0byBoYXZlIGp1c3QgMSB2YWx1ZS4NCg0KIyMgKGIpDQoNClJvdWdobHkgMTggJSBvZiB0aGUgZGF0YSBhcmUgbWlzc2luZy4gQXJlIHRoZXJlIHBhcnRpY3VsYXIgcHJlZGljdG9ycyB0aGF0DQphcmUgbW9yZSBsaWtlbHkgdG8gYmUgbWlzc2luZz8gSXMgdGhlIHBhdHRlcm4gb2YgbWlzc2luZyBkYXRhIHJlbGF0ZWQgdG8NCnRoZSBjbGFzc2VzPw0KDQpgYGB7ciwgZmlnLmhlaWdodD05fQ0KZGZfc295YmVhbiU+JQ0KICBzdW1tYXJpc2VfYWxsKGxpc3QofmlzLm5hKC4pKSkgJT4lDQogIHBpdm90X2xvbmdlcihldmVyeXRoaW5nKCksIG5hbWVzX3RvID0gInZhcmlhYmxlcyIsIHZhbHVlc190byA9ICJtaXNzaW5nIikgJT4lDQogIGNvdW50KHZhcmlhYmxlcywgbWlzc2luZykgJT4lDQogIGdncGxvdChhZXMoeSA9IHJlb3JkZXIodmFyaWFibGVzLCBuKSwgeCA9IG4sIGZpbGwgPSBtaXNzaW5nKSkgKw0KICBnZW9tX2NvbChwb3NpdGlvbiA9ICJmaWxsIikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gaWZlbHNlKG1pc3NpbmcsICJOQSIsICJOb24tTkEiKSksIA0KICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9maWxsKHZqdXN0ID0gMC41KSwgDQogICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsIHNpemUgPSA0KSArICAjIEFkZCBkYXRhIGxhYmVscw0KICBsYWJzKHRpdGxlID0gIk5BIFByb3BvcnRpb24iLA0KICAgICAgIHggPSAiUHJvcG9ydGlvbiIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiZ3JleSIsICJkYXJrZ3JlZW4iKSkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQoNCmBgYHtyfQ0KZGZfaW5jb21wbGV0ZSA8LSBkZl9zb3liZWFuWyFjb21wbGV0ZS5jYXNlcyhkZl9zb3liZWFuKSxdDQooZGZfaW5jb21wbGV0ZSAlPiUNCiAgICAgICAgICAgICAgICBncm91cF9ieShDbGFzcykgJT4lDQogICAgICAgICAgICAgICAgICB0YWxseSgpKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTl9DQpkZl9zb3liZWFuJT4lDQogIGZpbHRlcighQ2xhc3MgJWluJSBjKCIyLTQtZC1pbmp1cnkiLCJjeXN0LW5lbWF0b2RlIiwNCiAgICAgICAgICAgICAgICAgICJkaWFwb3J0aGUtcG9kLSYtc3RlbS1ibGlnaHQiLA0KICAgICAgICAgICAgICAgICAgImhlcmJpY2lkZS1pbmp1cnkiLCJwaHl0b3BodGhvcmEtcm90IikpJT4lDQogIHN1bW1hcmlzZV9hbGwobGlzdCh+aXMubmEoLikpKSAlPiUNCiAgcGl2b3RfbG9uZ2VyKGV2ZXJ5dGhpbmcoKSwgDQogICAgICAgICAgICAgICBuYW1lc190byA9ICJ2YXJpYWJsZXMiLCANCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJtaXNzaW5nIikgJT4lDQogIGNvdW50KHZhcmlhYmxlcywgbWlzc2luZykgJT4lDQogIGdncGxvdChhZXMoeSA9IHJlb3JkZXIodmFyaWFibGVzLCBuKSwgDQogICAgICAgICAgICAgeCA9IG4sIGZpbGwgPSBtaXNzaW5nKSkgKw0KICBnZW9tX2NvbChwb3NpdGlvbiA9ICJmaWxsIikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gaWZlbHNlKG1pc3NpbmcsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJOQSIsICJOb24tTkEiKSksIA0KICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9maWxsKHZqdXN0ID0gMC41KSwgDQogICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsIHNpemUgPSA0KSArICAjIEFkZCBkYXRhIGxhYmVscw0KICBsYWJzKHRpdGxlID0gIk5BIFByb3BvcnRpb24iLA0KICAgICAgIHggPSAiUHJvcG9ydGlvbiIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiZ3JleSIsICJkYXJrZ3JlZW4iKSkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQoNCg0KIyMgKGMpIA0KDQpEZXZlbG9wIGEgc3RyYXRlZ3kgZm9yIGhhbmRsaW5nIG1pc3NpbmcgZGF0YSwgZWl0aGVyIGJ5IGVsaW1pbmF0aW5nDQpwcmVkaWN0b3JzIG9yIGltcHV0YXRpb24uDQoNCkkgd291bGRudCBqdXN0IHJlbW92ZSB0aGUgY2xhc3NlcyBhbHRob3VnaCBpdHMgc2VlbXMgbXVjaCBlYXNpZXIgdG8ganVzdCBkbyB0aGF0Lg0KSSB3b3VsZCBzYXkgcmVwbGFjZSB3aXRoIHplcm8gd2hlcmUgaXQgbWFrZXMgc2Vuc2UsIGZvciBiaW5hcnkgdmFsdWVzIGxpa2UgYGhhaWxgIG9yIGBsb2RnaW5nYCBzaW5jZSBpdCBpcyBtb3JlIGxvZ2ljYWwgdG8gZGVmdWFsdCB0byAibm8iIHVubGVzcyByZWFzb24gdG8gYmVsaWV2ZSBvdGhlcndpc2UuIEkgc2F5IHRoaXMgYXNzdW1pbmcgYGhhaWxgPXllcyBsaWtlbGlob29kIGJlaW5nIHNpZ25pZmljYW50bHkgc21hbGxlciBpZiBwcmVzdW1pbmcuIEZvciB2YWx1ZXMgbGlrZSBgc2V2ZXJlYCBJJ2QgbXVjaCByYXRoZXIgbm90IHB1dCBhIHZhbHVlLCB1bmxlc3MgYW4gInVua25vd24iIG1ldHJpYyBpcyBwdXQgaW4sIGFzIGl0J3MgZnJlcXVlbmN5IGlzIGxpa2VseSB0byBiZSB2ZXJ5IGxvdy4gRm9yIHRoZSByZW1haW5pbmcsIEkgd291bGQgbGlrZWx5IHdhbnQgdG8gZ2V0IHRoZSBmcmVxdWVuY3kgYW5kIHN1YnN0aXR1dGUgdGhlIGNhdGVnb3JpY2FsIG1ldHJpYyB3aXRoIGl0cyBtb2RlIGZvciBub3JtYWxpemF0aW9uIHdpdGggYm94LWNveCwgYW5kIHNlZSBpZiBpdCBiZXR0ZXIgZml0cyB0aGUgbW9kZWwgd2Ugd2FudCB0byBwcmVkaWN0IHdpdGgu