Introduction

This notebook is based on this (Esophageal Cancer) project. These techniques are important for contextualizing data and creating predictions based on modeling and visualizations. The data set used for this project is from the (Induced abortion and secondary infertility) study.

Objective

  • Exploring the data set (infert) which comes in the “R” data sets package.

  • Here is a data usage example below:


Call:
glm(formula = case ~ spontaneous + induced, family = binomial(), 
    data = infert)

Coefficients:
            Estimate Std. Error z value Pr(>|z|)    
(Intercept)  -1.7079     0.2677  -6.380 1.78e-10 ***
spontaneous   1.1972     0.2116   5.657 1.54e-08 ***
induced       0.4181     0.2056   2.033    0.042 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 316.17  on 247  degrees of freedom
Residual deviance: 279.61  on 245  degrees of freedom
AIC: 285.61

Number of Fisher Scoring iterations: 4


Call:
glm(formula = case ~ age + parity + education + spontaneous + 
    induced, family = binomial(), data = infert)

Coefficients:
                 Estimate Std. Error z value Pr(>|z|)    
(Intercept)      -1.14924    1.41220  -0.814   0.4158    
age               0.03958    0.03120   1.269   0.2046    
parity           -0.82828    0.19649  -4.215 2.49e-05 ***
education6-11yrs -1.04424    0.79255  -1.318   0.1876    
education12+ yrs -1.40321    0.83416  -1.682   0.0925 .  
spontaneous       2.04591    0.31016   6.596 4.21e-11 ***
induced           1.28876    0.30146   4.275 1.91e-05 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 316.17  on 247  degrees of freedom
Residual deviance: 257.80  on 241  degrees of freedom
AIC: 271.8

Number of Fisher Scoring iterations: 4
Loading required package: survival
Call:
coxph(formula = Surv(rep(1, 248L), case) ~ spontaneous + induced + 
    strata(stratum), data = infert, method = "exact")

  n= 248, number of events= 83 

              coef exp(coef) se(coef)     z Pr(>|z|)    
spontaneous 1.9859    7.2854   0.3524 5.635 1.75e-08 ***
induced     1.4090    4.0919   0.3607 3.906 9.38e-05 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

            exp(coef) exp(-coef) lower .95 upper .95
spontaneous     7.285     0.1373     3.651    14.536
induced         4.092     0.2444     2.018     8.298

Concordance= 0.776  (se = 0.044 )
Likelihood ratio test= 53.15  on 2 df,   p=3e-12
Wald test            = 31.84  on 2 df,   p=1e-07
Score (logrank) test = 48.44  on 2 df,   p=3e-11
  • Visualizing the relationship between spontaneous abortion case occurrence and age / education / induced abortions.

  • Identifying the groups at risk via useful analyzes and graphs.

  • Building a well-developed generalized linear model.

  • Predicting spontaneous abortion percentages among the groups.

  • Testing the robustness of the model via leave-one-out cross validation.

Data exploration

library(tidyverse)
── Attaching core tidyverse packages ────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.2     ✔ readr     2.1.4
✔ forcats   1.0.0     ✔ stringr   1.5.0
✔ ggplot2   3.4.3     ✔ tibble    3.2.1
✔ lubridate 1.9.2     ✔ tidyr     1.3.0
✔ purrr     1.0.1     ── Conflicts ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(ggplot2)
library(knitr)
library(MASS)
Warning: package ‘MASS’ was built under R version 4.3.2
Attaching package: ‘MASS’

The following object is masked from ‘package:dplyr’:

    select

Data set overview

  • The data comes from a study investigating the role of induced (and spontaneous) abortions in the etiology of secondary sterility.

  • Obstetric and gynecologic histories were obtained from 100 women with secondary infertility admitted to the First Department of Obstetrics and Gynecology of the University of Athens Medical School and to the Division of Fertility and Sterility of that Department.

  • For every patient, researchers tried to find two healthy control subjects from the same hospital with matching for age, parity, and level of education.

  • Two control subjects each were found for 83 of the index patients.

  • Data frame with 248 records for education/ age/ parity/ induced/ case/ spontaneous/ stratum/ pooled.stratum.

head(infert)
summary(infert)
   education        age            parity         induced            case         spontaneous        stratum      pooled.stratum 
 0-5yrs : 12   Min.   :21.00   Min.   :1.000   Min.   :0.0000   Min.   :0.0000   Min.   :0.0000   Min.   : 1.00   Min.   : 1.00  
 6-11yrs:120   1st Qu.:28.00   1st Qu.:1.000   1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:21.00   1st Qu.:19.00  
 12+ yrs:116   Median :31.00   Median :2.000   Median :0.0000   Median :0.0000   Median :0.0000   Median :42.00   Median :36.00  
               Mean   :31.50   Mean   :2.093   Mean   :0.5726   Mean   :0.3347   Mean   :0.5766   Mean   :41.87   Mean   :33.58  
               3rd Qu.:35.25   3rd Qu.:3.000   3rd Qu.:1.0000   3rd Qu.:1.0000   3rd Qu.:1.0000   3rd Qu.:62.25   3rd Qu.:48.25  
               Max.   :44.00   Max.   :6.000   Max.   :2.0000   Max.   :1.0000   Max.   :2.0000   Max.   :83.00   Max.   :63.00  
str(infert)
'data.frame':   248 obs. of  8 variables:
 $ education     : Factor w/ 3 levels "0-5yrs","6-11yrs",..: 1 1 1 1 2 2 2 2 2 2 ...
 $ age           : num  26 42 39 34 35 36 23 32 21 28 ...
 $ parity        : num  6 1 6 4 3 4 1 2 1 2 ...
 $ induced       : num  1 1 2 2 1 2 0 0 0 0 ...
 $ case          : num  1 1 1 1 1 1 1 1 1 1 ...
 $ spontaneous   : num  2 0 0 0 1 1 0 0 1 0 ...
 $ stratum       : int  1 2 3 4 5 6 7 8 9 10 ...
 $ pooled.stratum: num  3 1 4 2 32 36 6 22 5 19 ...

Data visualization

Data grappling

infert2 <- infert %>% 
  mutate(
    # Create categories
    age_group = dplyr::case_when(
      age <= 25            ~ "0-25",
      age > 25 & age <= 30 ~ "25-30",
      age > 30 & age <= 35 ~ "30-35",
      age > 35 & age <= 40 ~ "35-40",
      age > 40             ~ "> 40"
    ),
    # Convert to factor
    age_group = factor(
      age_group,
      level = c("0-25", "25-30","30-35","35-40", "> 40")
    )
  )
infert2 <- na.omit(infert2)
head(infert2)
# Create strip chart (works better with bins)
stripchart(spontaneous ~ age_group, data=infert2)

stripchart(induced ~ age_group, data=infert2)

Observations

  • We can say that age has an effect on the amount spontaneous and induced abortions.

Abortion Case Proportions

infert2 %>%
 group_by(age_group) %>%
 summarise(spontaneous_cases = sum(spontaneous), 
 cases = sum(case),
 percentage = 100 * cases / (cases+spontaneous_cases)) %>%
 ggplot(., aes(x = age_group, y = percentage, fill = age_group)) +
 geom_bar(stat = "identity", position = "dodge") +
 labs(title = "Proportion of Spontaneous Abortion Cases over Age Groups", subtitle = "Data Source:
`infert2`", x = 'Ages', y = "% of Spontaneous Cases") +
 theme_minimal() +
 theme(legend.position = "none") +
 geom_text(aes(label = paste(format(percentage,digits=1), "%")), size=4.5, position =
position_stack(vjust = 0.5))

infert2 %>%
 group_by(age_group) %>%
 summarise(induced_cases = sum(induced), 
 cases = sum(case),
 percentage = 100 * cases / (cases+induced_cases)) %>%
 ggplot(., aes(x = age_group, y = percentage, fill = age_group)) +
 geom_bar(stat = "identity", position = "dodge") +
 labs(title = "Proportion of Induced Abortion Cases over Age Groups", subtitle = "Data Source:
`infert2`", x = 'Ages', y = "% of Induced Cases") +
 theme_minimal() +
 theme(legend.position = "none") +
 geom_text(aes(label = paste(format(percentage,digits=1), "%")), size=4.5, position =
position_stack(vjust = 0.5))

infert2 %>%
 group_by(education) %>%
 summarise(induced_cases = sum(induced), 
 cases = sum(case),
 percentage = 100 * cases / (cases+induced_cases)) %>%
 ggplot(., aes(x = education, y = percentage, fill = education)) +
 geom_bar(stat = "identity", position = "dodge") +
 labs(title = "Proportion of Induced Abortion Cases vs. Education", subtitle = "Data Source:
`infert2`", x = 'Education', y = "% of Induceds Cases") +
 theme_minimal() +
 theme(legend.position = "none") +
 geom_text(aes(label = paste(format(percentage,digits=1), "%")), size=4.5, position =
position_stack(vjust = 0.5))

infert2 %>%
 group_by(education) %>%
 summarise(spontaneous_cases = sum(spontaneous), 
 cases = sum(case),
 percentage = 100 * cases / (cases+spontaneous_cases)) %>%
 ggplot(., aes(x = education, y = percentage, fill = education)) +
 geom_bar(stat = "identity", position = "dodge") +
 labs(title = "Proportion of Spontaneous Abortion Cases vs. Education", subtitle = "Data Source:
`infert2`", x = 'Education', y = "% of Spontaneous Cases") +
 theme_minimal() +
 theme(legend.position = "none") +
 geom_text(aes(label = paste(format(percentage,digits=1), "%")), size=4.5, position =
position_stack(vjust = 0.5))

Abortion Case Distribution

infert2 %>% 
  group_by(age_group, induced) %>%
  summarize(total_cases = sum(case)) %>%
  group_by(age_group) %>%
  mutate(percentage = 100 * total_cases / sum(total_cases)) %>%
  filter(percentage != "NaN" & percentage != 0) %>%
  ggplot(., aes(x = age_group, y = percentage, fill = induced)) +
  geom_col(stat = "identity", position = "fill") +
  theme_minimal() +
  geom_text(aes(label = paste(format(percentage,digits=1), "%")), size=4, position = "fill", hjust = 0.5, vjust = 1.1) +
  scale_y_continuous(labels = scales::percent_format()) +
  labs(title = "Stacked Bar Chart of Case Distribution of Induced Abortions by Age Groups", subtitle = "Data Source: `infert2`", x = "Age Groups", y = "% of Abortion Cases", fill = "Induced")
`summarise()` has grouped output by 'age_group'. You can override using the `.groups` argument.Warning: Ignoring unknown parameters: `stat`

infert2 %>% 
  group_by(age_group, spontaneous) %>%
  summarize(total_cases = sum(case)) %>%
  group_by(age_group) %>%
  mutate(percentage = 100 * total_cases / sum(total_cases)) %>%
  filter(percentage != "NaN" & percentage != 0) %>%
  ggplot(., aes(x = age_group, y = percentage, fill = spontaneous)) +
  geom_col(stat = "identity", position = "fill") +
  theme_minimal() +
  geom_text(aes(label = paste(format(percentage,digits=1), "%")), size=4, position = "fill", hjust = 0.5, vjust = 1.1) +
  scale_y_continuous(labels = scales::percent_format()) +
  labs(title = "Stacked Bar Chart of Case Distribution of Spontaneous Abortions by Age Groups", subtitle = "Data Source: `infert2`", x = "Age Groups", y = "% of Abortion Cases", fill = "Spontaneous")
`summarise()` has grouped output by 'age_group'. You can override using the `.groups` argument.Warning: Ignoring unknown parameters: `stat`

infert2 %>% 
  group_by(education, induced) %>%
  summarize(total_cases = sum(case)) %>%
  group_by(education) %>%
  mutate(percentage = 100 * total_cases / sum(total_cases)) %>%
  filter(percentage != "NaN" & percentage != 0) %>%
  ggplot(., aes(x = education, y = percentage, fill = induced)) +
  geom_col(stat = "identity", position = "fill") +
  theme_minimal() +
  geom_text(aes(label = paste(format(percentage,digits=1), "%")), size=4, position = "fill", hjust = 0.5, vjust = 1.1) +
  scale_y_continuous(labels = scales::percent_format()) +
  labs(title = "Stacked Bar Chart of Case Distribution of Induced Abortions by Education", subtitle = "Data Source: `infert2`", x = "Education", y = "% of Abortion Cases", fill = "Induced")
`summarise()` has grouped output by 'education'. You can override using the `.groups` argument.Warning: Ignoring unknown parameters: `stat`

infert2 %>% 
  group_by(education, spontaneous) %>%
  summarize(total_cases = sum(case)) %>%
  group_by(education) %>%
  mutate(percentage = 100 * total_cases / sum(total_cases)) %>%
  filter(percentage != "NaN" & percentage != 0) %>%
  ggplot(., aes(x = education, y = percentage, fill = spontaneous)) +
  geom_col(stat = "identity", position = "fill") +
  theme_minimal() +
  geom_text(aes(label = paste(format(percentage,digits=1), "%")), size=4, position = "fill", hjust = 0.5, vjust = 1.1) +
  scale_y_continuous(labels = scales::percent_format()) +
  labs(title = "Stacked Bar Chart of Case Distribution of Spontaneous Abortions by Education", subtitle = "Data Source: `infert2`", x = "Education", y = "% of Abortion Cases", fill = "Spontaneous")
`summarise()` has grouped output by 'education'. You can override using the `.groups` argument.Warning: Ignoring unknown parameters: `stat`

Heat-map if Abortion Case Distribution

infert2 %>% 
  group_by(age_group) %>%
  mutate(total_cases = sum(case), 
            total_stratum = sum(stratum),
            percentage = 100 * total_cases / (total_cases+total_stratum)) %>%
  ggplot(., aes(x = induced, y = spontaneous, fill = percentage)) +
  geom_tile() +
  facet_wrap(~age_group) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
  scale_fill_gradient2(low="white", high="red3", guide="colorbar") +
  labs(title = "Heatmap of Abortion Cases", x = "Induced", subtitle = "Data Source: `infert2`", y = "Spontaneous", fill = "Cases (%)")

Data modeling

Data models are used to describe the relationship between variables.

Linear models

Regression analysis is an important statistical method for the analysis of medical data. It enables the identification and characterization of relationships among multiple factors. It also enables the identification of prognostically relevant risk factors and the calculation of risk scores for individual prognostication (NIH, 2010).

ANOVA test (change to show the affect of everything on spontaneous)

infert2$percentage_s <- infert2$case / (infert2$spontaneous+infert2$case)
infert2

model <- lm(percentage_s ~ age_group + education + induced, data = infert2) #Linear model is created in order to apply anova test
anova(model)
Analysis of Variance Table

Response: percentage_s
           Df  Sum Sq Mean Sq F value    Pr(>F)    
age_group   4  1.7346 0.43366  3.7853  0.006081 ** 
education   2  0.0888 0.04441  0.3877  0.679435    
induced     1  2.3971 2.39712 20.9236 1.121e-05 ***
Residuals 127 14.5498 0.11457                      
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

According to the results of the ANOVA test, it was observed that age_group , and amount of induced abortions had the greatest effect on the amount of spontaneous abortions.

Akaike’s Information Criterion

The Akaike’s information criterion model (AIC), achieves parsimony via a fit-complexity trade-off and is used as a relative measure to compare and rank several competing models fit to the same data, where the model with the lowest AIC is considered the best (NIH, 2023). This script will help use decide if we should remove education from the model.

AIC(glm(percentage_s ~  age_group + education + induced, data = infert2, family = binomial(link = "logit"))) #with all
Warning: non-integer #successes in a binomial glm!
[1] 149.0238
AIC(glm(percentage_s ~  education + induced, data = infert2, family = binomial(link = "logit"))) 
Warning: non-integer #successes in a binomial glm!
[1] 148.0439
AIC(glm(percentage_s ~  age_group + induced, data = infert2, family = binomial(link = "logit"))) #best model
Warning: non-integer #successes in a binomial glm!
[1] 144.9319
AIC(glm(percentage_s ~  age_group + education, data = infert2, family = binomial(link = "logit")))
Warning: non-integer #successes in a binomial glm!
[1] 163.5102
AIC(glm(percentage_s ~ age_group, data = infert2, family = binomial(link = "logit"))) #with age_group
Warning: non-integer #successes in a binomial glm!
[1] 160.5734
AIC(glm(percentage_s ~  education, data = infert2, family = binomial(link = "logit"))) #with education
Warning: non-integer #successes in a binomial glm!
[1] 159.8967
AIC(glm(percentage_s ~  induced, data = infert2, family = binomial(link = "logit"))) #with induced 
Warning: non-integer #successes in a binomial glm!
[1] 145.2095

The third model containing age_group and induced data produced the lowest AIC.

Logistic Regression

Logistic regression analysis is a statistical technique to evaluate the relationship between various predictor variables (either categorical or continuous) and an outcome which is binary (dichotomous) (NIH, 2017). Logistic regression is an important research tool used for disease prediction.

model <- glm(percentage_s ~ age_group + induced, data = infert2, family = binomial(link = "logit")) #Logistic regression
Warning: non-integer #successes in a binomial glm!
summary(model)

Call:
glm(formula = percentage_s ~ age_group + induced, family = binomial(link = "logit"), 
    data = infert2)

Coefficients:
               Estimate Std. Error z value Pr(>|z|)   
(Intercept)     -1.0254     0.5324  -1.926  0.05411 . 
age_group25-30  -0.2126     0.5994  -0.355  0.72282   
age_group30-35   0.7015     0.6405   1.095  0.27345   
age_group35-40  -0.3660     0.6694  -0.547  0.58454   
age_group> 40    2.6105     1.6111   1.620  0.10516   
induced          0.8582     0.2697   3.182  0.00146 **
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 105.964  on 134  degrees of freedom
Residual deviance:  87.701  on 129  degrees of freedom
  (113 observations deleted due to missingness)
AIC: 144.93

Number of Fisher Scoring iterations: 4
  • The induced p-value shows that it makes a significant difference in this model.
  • An ideal model would include multiple p-values of less than 5 percent (check out this example)

Test the model

Predicted Spontaneous Abortion Risk Percentages among Induced, Education, and Age Groups

After creating the model we can visualize the predicted spontaneous abortion cases.

Age Group

predict_spontaneous_percentages <- data.frame()
for (i in 1:5) {
  for (j in 1:4) {
    predict_spontaneous_percentages[i,j] <- plogis(predict(model, data.frame(age_group = unique(infert2$age_group)[i], induced = unique(infert2$induced)[j]))) #Prediction
  }
}
pivot_longer(predict_spontaneous_percentages, cols=everything(), names_to = "Induced_Abortion", values_to = "Spontaneous_Percentage") %>%
  add_column(.before="Induced_Abortion", Age_Group = c(rep("0-25",4), rep("25-30",4),rep("30-35",4),rep("35-40",4),rep("40<",4))) %>%
  ggplot(.,aes(x=Age_Group, y=Spontaneous_Percentage, fill = Induced_Abortion)) +
  geom_bar(stat = "identity", position = "dodge") + 
  scale_y_continuous(labels = scales::percent_format()) +
  scale_fill_discrete(name = "Induced_Abortion", labels = c("0", "1", "2")) +
  labs(title = "Predicted Spontaneous Abortion Risk Percentages among Induced and Age Groups", subtitle = "Prediction Based on Logistic Regression", x = "Age Groups", y = "Predicted Percentage")

  • These predictions represent the estimated spontaneous abortion percentages by age group and induced abortions.
  • According to the predictions, patients with one induced abortion and in the 25-30yr age group have the highest percentage of spontaneous abortions.
  • According to the predictions, patients with more than one induced abortions and in the 30-35yr age group have the lowest percentage of spontaneous abortion.
  • The risk levels between the 0-25yr, 30-35yr, and 40< are very similar.
predict_spontaneous_percentages <- data.frame(row.names = c("0-25 years","25-30 years","30-35 years","35-40 years","40< years"))
for (i in 1:5) {
  for (j in 1:4) {
    predict_spontaneous_percentages[i,j] <- paste(round(100*(plogis(predict(model, data.frame(age_group = unique(infert2$age_group)[i], induced = unique(infert2$induced)[j])))),0),"%",sep="")
  }
}
colnames(predict_spontaneous_percentages) <- c("0","1","2")
kable(predict_spontaneous_percentages, caption = "Predicted Spontaneous Abortion Percentages corresp. to Age and Induced Abortion Groups")
Predicted Spontaneous Abortion Percentages corresp. to Age and Induced Abortion Groups
0 1 2 NA
0-25 years 41% 62% 22% NA%
25-30 years 92% 96% 83% NA%
30-35 years 37% 58% 20% NA%
35-40 years 63% 80% 42% NA%
40< years 46% 67% 26% NA%
  • This table shows the percentage values.

Leave-one-out Cross Validation

Cross-validation is a re-sampling method that uses different portions of the data to test and train a model on different iterations. It is mainly used in settings where the goal is prediction, and one wants to estimate how accurately a predictive model will perform in practice (Cross-validation).

For more information on cross-validation:

Cross-validation under separate sampling: strong bias and how to correct it

Impact of the Choice of Cross-Validation Techniques on the Results of Machine Learning-Based Diagnostic Applications

pred_length <- nrow(infert2)
fit_glm_error <- c()
fit_glm_sq_error <- c()
for(i in 1:pred_length){
  fit_glm <- glm(percentage_s ~ age_group + induced, family = binomial(link = "logit"), data = infert2[-i,]) #Leave-one-out Cross Validation
  fit_glm_pred <- (predict(fit_glm, infert2[i,]))^2
  fit_glm_error[i] <- infert2$percentage_s[i] - fit_glm_pred
  fit_glm_sq_error[i] = (infert2$percentage_s[i] - fit_glm_pred)^2
}
Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!Warning: non-integer #successes in a binomial glm!
hist(fit_glm_error, breaks = 50, xlim = range(-50,50), title = "Histogram of Errors", xlab = "Fitted GLM Errors")

This histogram of errors shows that there are some errors that are significantly greater than 30. However, the errors are close to zero so we established a good model.

fit_glm_sq_error <- na.omit(fit_glm_sq_error) #remove NaN
rmse_fit_glm <- sqrt(mean(fit_glm_sq_error))
rmse_fit_glm #Root Mean Square Error
[1] 22.12173

The root mean square error (RMSE) is a metric that tells us how far apart our predicted values are from our observed values in a regression analysis, on average. The larger the RMSE, the larger the difference between the predicted and observed values, which means the worse a regression model fits the data. Conversely, the smaller the RMSE, the better a model is able to fit the data (How to Calculate RMSE in R).

Conclusion

In conclusion, we worked with data grappling, exploratory data analysis, and linear modeling, to build and test a predictive model .

Resources

Esophageal Cancer Project

Induced abortion and secondary infertility study

Infertility data

Linear Regression Analysis

Practical advice on variable selection and reporting using Akaike information criterion

Common pitfalls in statistical analysis: Logistic regression

Cross-validation

Cross-validation under separate sampling: strong bias and how to correct it

Impact of the Choice of Cross-Validation Techniques on the Results of Machine Learning-Based Diagnostic Applications

How to Calculate RMSE in R

LS0tDQp0aXRsZTogIkRhdGEgRXhwbG9yYXRpb24gYW5kIFByZWRpY3RpdmUgTW9kZWxpbmc6IFNwb250YW5lb3VzIEFib3J0aW9uIFByZWRpY3Rpb24iDQphdXRob3I6ICJQZWFjZSBNYWRkb3giDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIEludHJvZHVjdGlvbg0KDQpUaGlzIG5vdGVib29rIGlzIGJhc2VkIG9uIHRoaXMgKFtFc29waGFnZWFsIENhbmNlcl0oaHR0cHM6Ly9wam91cm5hbC5naXRodWIuaW8vYm91bjAxLWNhbmF5dG9yZS9hc3NpZ25tZW50M19lc29waCkpIHByb2plY3QuIFRoZXNlIHRlY2huaXF1ZXMgYXJlIGltcG9ydGFudCBmb3IgY29udGV4dHVhbGl6aW5nIGRhdGEgYW5kIGNyZWF0aW5nIHByZWRpY3Rpb25zIGJhc2VkIG9uIG1vZGVsaW5nIGFuZCB2aXN1YWxpemF0aW9ucy4gVGhlIGRhdGEgc2V0IHVzZWQgZm9yIHRoaXMgcHJvamVjdCBpcyBmcm9tIHRoZSAoW0luZHVjZWQgYWJvcnRpb24gYW5kIHNlY29uZGFyeSBpbmZlcnRpbGl0eV0oaHR0cHM6Ly9vYmd5bi5vbmxpbmVsaWJyYXJ5LndpbGV5LmNvbS9kb2kvMTAuMTExMS9qLjE0NzEtMDUyOC4xOTc2LnRiMDA5MDQueCkpIHN0dWR5Lg0KDQojIyBPYmplY3RpdmUNCg0KLSAgIEV4cGxvcmluZyB0aGUgZGF0YSBzZXQgKFtpbmZlcnRdKGh0dHBzOi8vc3RhdC5ldGh6LmNoL1ItbWFudWFsL1ItZGV2ZWwvbGlicmFyeS9kYXRhc2V0cy9odG1sL2luZmVydC5odG1sKSkgd2hpY2ggY29tZXMgaW4gdGhlICJSIiBkYXRhIHNldHMgcGFja2FnZS4NCg0KLSAgIEhlcmUgaXMgYSBkYXRhIHVzYWdlIGV4YW1wbGUgYmVsb3c6DQoNCmBgYHtyIGVjaG89RkFMU0V9DQpyZXF1aXJlKHN0YXRzKQ0KbW9kZWwxIDwtIGdsbShjYXNlIH4gc3BvbnRhbmVvdXMraW5kdWNlZCwgZGF0YSA9IGluZmVydCwgZmFtaWx5ID0gYmlub21pYWwoKSkNCnN1bW1hcnkobW9kZWwxKQ0KIyMgYWRqdXN0ZWQgZm9yIG90aGVyIHBvdGVudGlhbCBjb25mb3VuZGVyczoNCnN1bW1hcnkobW9kZWwyIDwtIGdsbShjYXNlIH4gYWdlK3Bhcml0eStlZHVjYXRpb24rc3BvbnRhbmVvdXMraW5kdWNlZCwNCiAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBpbmZlcnQsIGZhbWlseSA9IGJpbm9taWFsKCkpKQ0KIyMgUmVhbGx5IHNob3VsZCBiZSBhbmFseXNlZCBieSBjb25kaXRpb25hbCBsb2dpc3RpYyByZWdyZXNzaW9uDQojIyB3aGljaCBpcyBpbiB0aGUgc3Vydml2YWwgcGFja2FnZQ0KaWYocmVxdWlyZShzdXJ2aXZhbCkpew0KICBtb2RlbDMgPC0gY2xvZ2l0KGNhc2UgfiBzcG9udGFuZW91cytpbmR1Y2VkK3N0cmF0YShzdHJhdHVtKSwgZGF0YSA9IGluZmVydCkNCiAgcHJpbnQoc3VtbWFyeShtb2RlbDMpKQ0KICBkZXRhY2goKSAgIyBzdXJ2aXZhbCAoY29uZmxpY3RzKQ0KfQ0KYGBgDQoNCi0gICBWaXN1YWxpemluZyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gc3BvbnRhbmVvdXMgYWJvcnRpb24gY2FzZSBvY2N1cnJlbmNlIGFuZCBhZ2UgLyBlZHVjYXRpb24gLyBpbmR1Y2VkIGFib3J0aW9ucy4NCg0KLSAgIElkZW50aWZ5aW5nIHRoZSBncm91cHMgYXQgcmlzayB2aWEgdXNlZnVsIGFuYWx5emVzIGFuZCBncmFwaHMuDQoNCi0gICBCdWlsZGluZyBhIHdlbGwtZGV2ZWxvcGVkIGdlbmVyYWxpemVkIGxpbmVhciBtb2RlbC4NCg0KLSAgIFByZWRpY3Rpbmcgc3BvbnRhbmVvdXMgYWJvcnRpb24gcGVyY2VudGFnZXMgYW1vbmcgdGhlIGdyb3Vwcy4NCg0KLSAgIFRlc3RpbmcgdGhlIHJvYnVzdG5lc3Mgb2YgdGhlIG1vZGVsIHZpYSBsZWF2ZS1vbmUtb3V0IGNyb3NzIHZhbGlkYXRpb24uDQoNCiMgRGF0YSBleHBsb3JhdGlvbg0KDQpgYGB7ciBwYWNrYWdlc30NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoTUFTUykNCmBgYA0KDQojIyBEYXRhIHNldCBvdmVydmlldw0KDQotICAgVGhlIGRhdGEgY29tZXMgZnJvbSBhIHN0dWR5IGludmVzdGlnYXRpbmcgdGhlIHJvbGUgb2YgaW5kdWNlZCAoYW5kIHNwb250YW5lb3VzKSBhYm9ydGlvbnMgaW4gdGhlIGV0aW9sb2d5IG9mIHNlY29uZGFyeSBzdGVyaWxpdHkuDQoNCi0gICBPYnN0ZXRyaWMgYW5kIGd5bmVjb2xvZ2ljIGhpc3RvcmllcyB3ZXJlIG9idGFpbmVkIGZyb20gMTAwIHdvbWVuIHdpdGggc2Vjb25kYXJ5IGluZmVydGlsaXR5IGFkbWl0dGVkIHRvIHRoZSBGaXJzdCBEZXBhcnRtZW50IG9mIE9ic3RldHJpY3MgYW5kIEd5bmVjb2xvZ3kgb2YgdGhlIFVuaXZlcnNpdHkgb2YgQXRoZW5zIE1lZGljYWwgU2Nob29sIGFuZCB0byB0aGUgRGl2aXNpb24gb2YgRmVydGlsaXR5IGFuZCBTdGVyaWxpdHkgb2YgdGhhdCBEZXBhcnRtZW50Lg0KDQotICAgRm9yIGV2ZXJ5IHBhdGllbnQsIHJlc2VhcmNoZXJzIHRyaWVkIHRvIGZpbmQgdHdvIGhlYWx0aHkgY29udHJvbCBzdWJqZWN0cyBmcm9tIHRoZSBzYW1lIGhvc3BpdGFsIHdpdGggbWF0Y2hpbmcgZm9yIGFnZSwgcGFyaXR5LCBhbmQgbGV2ZWwgb2YgZWR1Y2F0aW9uLg0KDQotICAgVHdvIGNvbnRyb2wgc3ViamVjdHMgZWFjaCB3ZXJlIGZvdW5kIGZvciA4MyBvZiB0aGUgaW5kZXggcGF0aWVudHMuDQoNCi0gICBEYXRhIGZyYW1lIHdpdGggMjQ4IHJlY29yZHMgZm9yIGBlZHVjYXRpb24vIGFnZS8gcGFyaXR5LyBpbmR1Y2VkLyBjYXNlLyBzcG9udGFuZW91cy8gc3RyYXR1bS8gcG9vbGVkLnN0cmF0dW1gLg0KDQpgYGB7ciBkYXRhIGV4cGxvcmF0aW9ufQ0KaGVhZChpbmZlcnQpDQpzdW1tYXJ5KGluZmVydCkNCnN0cihpbmZlcnQpDQpgYGANCg0KIyMgRGF0YSB2aXN1YWxpemF0aW9uDQoNCiMjIyBEYXRhIGdyYXBwbGluZw0KDQpgYGB7ciBhZ2VfYmluc30NCmluZmVydDIgPC0gaW5mZXJ0ICU+JSANCiAgbXV0YXRlKA0KICAgICMgQ3JlYXRlIGNhdGVnb3JpZXMNCiAgICBhZ2VfZ3JvdXAgPSBkcGx5cjo6Y2FzZV93aGVuKA0KICAgICAgYWdlIDw9IDI1ICAgICAgICAgICAgfiAiMC0yNSIsDQogICAgICBhZ2UgPiAyNSAmIGFnZSA8PSAzMCB+ICIyNS0zMCIsDQogICAgICBhZ2UgPiAzMCAmIGFnZSA8PSAzNSB+ICIzMC0zNSIsDQogICAgICBhZ2UgPiAzNSAmIGFnZSA8PSA0MCB+ICIzNS00MCIsDQogICAgICBhZ2UgPiA0MCAgICAgICAgICAgICB+ICI+IDQwIg0KICAgICksDQogICAgIyBDb252ZXJ0IHRvIGZhY3Rvcg0KICAgIGFnZV9ncm91cCA9IGZhY3RvcigNCiAgICAgIGFnZV9ncm91cCwNCiAgICAgIGxldmVsID0gYygiMC0yNSIsICIyNS0zMCIsIjMwLTM1IiwiMzUtNDAiLCAiPiA0MCIpDQogICAgKQ0KICApDQppbmZlcnQyIDwtIG5hLm9taXQoaW5mZXJ0MikNCmhlYWQoaW5mZXJ0MikNCmBgYA0KDQpgYGAgICAgICAgICANCmBgYA0KDQpgYGB7ciBzdHJpcF9jaGFydH0NCiMgQ3JlYXRlIHN0cmlwIGNoYXJ0ICh3b3JrcyBiZXR0ZXIgd2l0aCBiaW5zKQ0Kc3RyaXBjaGFydChzcG9udGFuZW91cyB+IGFnZV9ncm91cCwgZGF0YT1pbmZlcnQyKQ0Kc3RyaXBjaGFydChpbmR1Y2VkIH4gYWdlX2dyb3VwLCBkYXRhPWluZmVydDIpDQpgYGANCg0KKk9ic2VydmF0aW9ucyoNCg0KLSAgIFdlIGNhbiBzYXkgdGhhdCBhZ2UgaGFzIGFuIGVmZmVjdCBvbiB0aGUgYW1vdW50IGBzcG9udGFuZW91c2AgYW5kIGBpbmR1Y2VkYCBhYm9ydGlvbnMuDQoNCiMjIyBBYm9ydGlvbiBDYXNlIFByb3BvcnRpb25zDQoNCmBgYHtyfQ0KaW5mZXJ0MiAlPiUNCiBncm91cF9ieShhZ2VfZ3JvdXApICU+JQ0KIHN1bW1hcmlzZShzcG9udGFuZW91c19jYXNlcyA9IHN1bShzcG9udGFuZW91cyksIA0KIGNhc2VzID0gc3VtKGNhc2UpLA0KIHBlcmNlbnRhZ2UgPSAxMDAgKiBjYXNlcyAvIChjYXNlcytzcG9udGFuZW91c19jYXNlcykpICU+JQ0KIGdncGxvdCguLCBhZXMoeCA9IGFnZV9ncm91cCwgeSA9IHBlcmNlbnRhZ2UsIGZpbGwgPSBhZ2VfZ3JvdXApKSArDQogZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgKw0KIGxhYnModGl0bGUgPSAiUHJvcG9ydGlvbiBvZiBTcG9udGFuZW91cyBBYm9ydGlvbiBDYXNlcyBvdmVyIEFnZSBHcm91cHMiLCBzdWJ0aXRsZSA9ICJEYXRhIFNvdXJjZToNCmBpbmZlcnQyYCIsIHggPSAnQWdlcycsIHkgPSAiJSBvZiBTcG9udGFuZW91cyBDYXNlcyIpICsNCiB0aGVtZV9taW5pbWFsKCkgKw0KIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KIGdlb21fdGV4dChhZXMobGFiZWwgPSBwYXN0ZShmb3JtYXQocGVyY2VudGFnZSxkaWdpdHM9MSksICIlIikpLCBzaXplPTQuNSwgcG9zaXRpb24gPQ0KcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpKQ0KDQpgYGANCg0KYGBge3J9DQppbmZlcnQyICU+JQ0KIGdyb3VwX2J5KGFnZV9ncm91cCkgJT4lDQogc3VtbWFyaXNlKGluZHVjZWRfY2FzZXMgPSBzdW0oaW5kdWNlZCksIA0KIGNhc2VzID0gc3VtKGNhc2UpLA0KIHBlcmNlbnRhZ2UgPSAxMDAgKiBjYXNlcyAvIChjYXNlcytpbmR1Y2VkX2Nhc2VzKSkgJT4lDQogZ2dwbG90KC4sIGFlcyh4ID0gYWdlX2dyb3VwLCB5ID0gcGVyY2VudGFnZSwgZmlsbCA9IGFnZV9ncm91cCkpICsNCiBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSArDQogbGFicyh0aXRsZSA9ICJQcm9wb3J0aW9uIG9mIEluZHVjZWQgQWJvcnRpb24gQ2FzZXMgb3ZlciBBZ2UgR3JvdXBzIiwgc3VidGl0bGUgPSAiRGF0YSBTb3VyY2U6DQpgaW5mZXJ0MmAiLCB4ID0gJ0FnZXMnLCB5ID0gIiUgb2YgSW5kdWNlZCBDYXNlcyIpICsNCiB0aGVtZV9taW5pbWFsKCkgKw0KIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KIGdlb21fdGV4dChhZXMobGFiZWwgPSBwYXN0ZShmb3JtYXQocGVyY2VudGFnZSxkaWdpdHM9MSksICIlIikpLCBzaXplPTQuNSwgcG9zaXRpb24gPQ0KcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpKQ0KDQpgYGANCg0KYGBge3J9DQppbmZlcnQyICU+JQ0KIGdyb3VwX2J5KGVkdWNhdGlvbikgJT4lDQogc3VtbWFyaXNlKGluZHVjZWRfY2FzZXMgPSBzdW0oaW5kdWNlZCksIA0KIGNhc2VzID0gc3VtKGNhc2UpLA0KIHBlcmNlbnRhZ2UgPSAxMDAgKiBjYXNlcyAvIChjYXNlcytpbmR1Y2VkX2Nhc2VzKSkgJT4lDQogZ2dwbG90KC4sIGFlcyh4ID0gZWR1Y2F0aW9uLCB5ID0gcGVyY2VudGFnZSwgZmlsbCA9IGVkdWNhdGlvbikpICsNCiBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZG9kZ2UiKSArDQogbGFicyh0aXRsZSA9ICJQcm9wb3J0aW9uIG9mIEluZHVjZWQgQWJvcnRpb24gQ2FzZXMgdnMuIEVkdWNhdGlvbiIsIHN1YnRpdGxlID0gIkRhdGEgU291cmNlOg0KYGluZmVydDJgIiwgeCA9ICdFZHVjYXRpb24nLCB5ID0gIiUgb2YgSW5kdWNlZHMgQ2FzZXMiKSArDQogdGhlbWVfbWluaW1hbCgpICsNCiB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiBnZW9tX3RleHQoYWVzKGxhYmVsID0gcGFzdGUoZm9ybWF0KHBlcmNlbnRhZ2UsZGlnaXRzPTEpLCAiJSIpKSwgc2l6ZT00LjUsIHBvc2l0aW9uID0NCnBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC41KSkNCmBgYA0KDQpgYGB7cn0NCmluZmVydDIgJT4lDQogZ3JvdXBfYnkoZWR1Y2F0aW9uKSAlPiUNCiBzdW1tYXJpc2Uoc3BvbnRhbmVvdXNfY2FzZXMgPSBzdW0oc3BvbnRhbmVvdXMpLCANCiBjYXNlcyA9IHN1bShjYXNlKSwNCiBwZXJjZW50YWdlID0gMTAwICogY2FzZXMgLyAoY2FzZXMrc3BvbnRhbmVvdXNfY2FzZXMpKSAlPiUNCiBnZ3Bsb3QoLiwgYWVzKHggPSBlZHVjYXRpb24sIHkgPSBwZXJjZW50YWdlLCBmaWxsID0gZWR1Y2F0aW9uKSkgKw0KIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsNCiBsYWJzKHRpdGxlID0gIlByb3BvcnRpb24gb2YgU3BvbnRhbmVvdXMgQWJvcnRpb24gQ2FzZXMgdnMuIEVkdWNhdGlvbiIsIHN1YnRpdGxlID0gIkRhdGEgU291cmNlOg0KYGluZmVydDJgIiwgeCA9ICdFZHVjYXRpb24nLCB5ID0gIiUgb2YgU3BvbnRhbmVvdXMgQ2FzZXMiKSArDQogdGhlbWVfbWluaW1hbCgpICsNCiB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiBnZW9tX3RleHQoYWVzKGxhYmVsID0gcGFzdGUoZm9ybWF0KHBlcmNlbnRhZ2UsZGlnaXRzPTEpLCAiJSIpKSwgc2l6ZT00LjUsIHBvc2l0aW9uID0NCnBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC41KSkNCmBgYA0KDQojIyMgQWJvcnRpb24gQ2FzZSBEaXN0cmlidXRpb24NCg0KYGBge3J9DQppbmZlcnQyICU+JSANCiAgZ3JvdXBfYnkoYWdlX2dyb3VwLCBpbmR1Y2VkKSAlPiUNCiAgc3VtbWFyaXplKHRvdGFsX2Nhc2VzID0gc3VtKGNhc2UpKSAlPiUNCiAgZ3JvdXBfYnkoYWdlX2dyb3VwKSAlPiUNCiAgbXV0YXRlKHBlcmNlbnRhZ2UgPSAxMDAgKiB0b3RhbF9jYXNlcyAvIHN1bSh0b3RhbF9jYXNlcykpICU+JQ0KICBmaWx0ZXIocGVyY2VudGFnZSAhPSAiTmFOIiAmIHBlcmNlbnRhZ2UgIT0gMCkgJT4lDQogIGdncGxvdCguLCBhZXMoeCA9IGFnZV9ncm91cCwgeSA9IHBlcmNlbnRhZ2UsIGZpbGwgPSBpbmR1Y2VkKSkgKw0KICBnZW9tX2NvbChzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZmlsbCIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHBhc3RlKGZvcm1hdChwZXJjZW50YWdlLGRpZ2l0cz0xKSwgIiUiKSksIHNpemU9NCwgcG9zaXRpb24gPSAiZmlsbCIsIGhqdXN0ID0gMC41LCB2anVzdCA9IDEuMSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpKSArDQogIGxhYnModGl0bGUgPSAiU3RhY2tlZCBCYXIgQ2hhcnQgb2YgQ2FzZSBEaXN0cmlidXRpb24gb2YgSW5kdWNlZCBBYm9ydGlvbnMgYnkgQWdlIEdyb3VwcyIsIHN1YnRpdGxlID0gIkRhdGEgU291cmNlOiBgaW5mZXJ0MmAiLCB4ID0gIkFnZSBHcm91cHMiLCB5ID0gIiUgb2YgQWJvcnRpb24gQ2FzZXMiLCBmaWxsID0gIkluZHVjZWQiKQ0KYGBgDQoNCmBgYHtyfQ0KaW5mZXJ0MiAlPiUgDQogIGdyb3VwX2J5KGFnZV9ncm91cCwgc3BvbnRhbmVvdXMpICU+JQ0KICBzdW1tYXJpemUodG90YWxfY2FzZXMgPSBzdW0oY2FzZSkpICU+JQ0KICBncm91cF9ieShhZ2VfZ3JvdXApICU+JQ0KICBtdXRhdGUocGVyY2VudGFnZSA9IDEwMCAqIHRvdGFsX2Nhc2VzIC8gc3VtKHRvdGFsX2Nhc2VzKSkgJT4lDQogIGZpbHRlcihwZXJjZW50YWdlICE9ICJOYU4iICYgcGVyY2VudGFnZSAhPSAwKSAlPiUNCiAgZ2dwbG90KC4sIGFlcyh4ID0gYWdlX2dyb3VwLCB5ID0gcGVyY2VudGFnZSwgZmlsbCA9IHNwb250YW5lb3VzKSkgKw0KICBnZW9tX2NvbChzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZmlsbCIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHBhc3RlKGZvcm1hdChwZXJjZW50YWdlLGRpZ2l0cz0xKSwgIiUiKSksIHNpemU9NCwgcG9zaXRpb24gPSAiZmlsbCIsIGhqdXN0ID0gMC41LCB2anVzdCA9IDEuMSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpKSArDQogIGxhYnModGl0bGUgPSAiU3RhY2tlZCBCYXIgQ2hhcnQgb2YgQ2FzZSBEaXN0cmlidXRpb24gb2YgU3BvbnRhbmVvdXMgQWJvcnRpb25zIGJ5IEFnZSBHcm91cHMiLCBzdWJ0aXRsZSA9ICJEYXRhIFNvdXJjZTogYGluZmVydDJgIiwgeCA9ICJBZ2UgR3JvdXBzIiwgeSA9ICIlIG9mIEFib3J0aW9uIENhc2VzIiwgZmlsbCA9ICJTcG9udGFuZW91cyIpDQpgYGANCg0KYGBge3J9DQppbmZlcnQyICU+JSANCiAgZ3JvdXBfYnkoZWR1Y2F0aW9uLCBpbmR1Y2VkKSAlPiUNCiAgc3VtbWFyaXplKHRvdGFsX2Nhc2VzID0gc3VtKGNhc2UpKSAlPiUNCiAgZ3JvdXBfYnkoZWR1Y2F0aW9uKSAlPiUNCiAgbXV0YXRlKHBlcmNlbnRhZ2UgPSAxMDAgKiB0b3RhbF9jYXNlcyAvIHN1bSh0b3RhbF9jYXNlcykpICU+JQ0KICBmaWx0ZXIocGVyY2VudGFnZSAhPSAiTmFOIiAmIHBlcmNlbnRhZ2UgIT0gMCkgJT4lDQogIGdncGxvdCguLCBhZXMoeCA9IGVkdWNhdGlvbiwgeSA9IHBlcmNlbnRhZ2UsIGZpbGwgPSBpbmR1Y2VkKSkgKw0KICBnZW9tX2NvbChzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb24gPSAiZmlsbCIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHBhc3RlKGZvcm1hdChwZXJjZW50YWdlLGRpZ2l0cz0xKSwgIiUiKSksIHNpemU9NCwgcG9zaXRpb24gPSAiZmlsbCIsIGhqdXN0ID0gMC41LCB2anVzdCA9IDEuMSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdCgpKSArDQogIGxhYnModGl0bGUgPSAiU3RhY2tlZCBCYXIgQ2hhcnQgb2YgQ2FzZSBEaXN0cmlidXRpb24gb2YgSW5kdWNlZCBBYm9ydGlvbnMgYnkgRWR1Y2F0aW9uIiwgc3VidGl0bGUgPSAiRGF0YSBTb3VyY2U6IGBpbmZlcnQyYCIsIHggPSAiRWR1Y2F0aW9uIiwgeSA9ICIlIG9mIEFib3J0aW9uIENhc2VzIiwgZmlsbCA9ICJJbmR1Y2VkIikNCmBgYA0KDQpgYGB7cn0NCmluZmVydDIgJT4lIA0KICBncm91cF9ieShlZHVjYXRpb24sIHNwb250YW5lb3VzKSAlPiUNCiAgc3VtbWFyaXplKHRvdGFsX2Nhc2VzID0gc3VtKGNhc2UpKSAlPiUNCiAgZ3JvdXBfYnkoZWR1Y2F0aW9uKSAlPiUNCiAgbXV0YXRlKHBlcmNlbnRhZ2UgPSAxMDAgKiB0b3RhbF9jYXNlcyAvIHN1bSh0b3RhbF9jYXNlcykpICU+JQ0KICBmaWx0ZXIocGVyY2VudGFnZSAhPSAiTmFOIiAmIHBlcmNlbnRhZ2UgIT0gMCkgJT4lDQogIGdncGxvdCguLCBhZXMoeCA9IGVkdWNhdGlvbiwgeSA9IHBlcmNlbnRhZ2UsIGZpbGwgPSBzcG9udGFuZW91cykpICsNCiAgZ2VvbV9jb2woc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImZpbGwiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBwYXN0ZShmb3JtYXQocGVyY2VudGFnZSxkaWdpdHM9MSksICIlIikpLCBzaXplPTQsIHBvc2l0aW9uID0gImZpbGwiLCBoanVzdCA9IDAuNSwgdmp1c3QgPSAxLjEpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoKSkgKw0KICBsYWJzKHRpdGxlID0gIlN0YWNrZWQgQmFyIENoYXJ0IG9mIENhc2UgRGlzdHJpYnV0aW9uIG9mIFNwb250YW5lb3VzIEFib3J0aW9ucyBieSBFZHVjYXRpb24iLCBzdWJ0aXRsZSA9ICJEYXRhIFNvdXJjZTogYGluZmVydDJgIiwgeCA9ICJFZHVjYXRpb24iLCB5ID0gIiUgb2YgQWJvcnRpb24gQ2FzZXMiLCBmaWxsID0gIlNwb250YW5lb3VzIikNCmBgYA0KDQojIyMgSGVhdC1tYXAgaWYgQWJvcnRpb24gQ2FzZSBEaXN0cmlidXRpb24NCg0KYGBge3J9DQppbmZlcnQyICU+JSANCiAgZ3JvdXBfYnkoYWdlX2dyb3VwKSAlPiUNCiAgbXV0YXRlKHRvdGFsX2Nhc2VzID0gc3VtKGNhc2UpLCANCiAgICAgICAgICAgIHRvdGFsX3N0cmF0dW0gPSBzdW0oc3RyYXR1bSksDQogICAgICAgICAgICBwZXJjZW50YWdlID0gMTAwICogdG90YWxfY2FzZXMgLyAodG90YWxfY2FzZXMrdG90YWxfc3RyYXR1bSkpICU+JQ0KICBnZ3Bsb3QoLiwgYWVzKHggPSBpbmR1Y2VkLCB5ID0gc3BvbnRhbmVvdXMsIGZpbGwgPSBwZXJjZW50YWdlKSkgKw0KICBnZW9tX3RpbGUoKSArDQogIGZhY2V0X3dyYXAofmFnZV9ncm91cCkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdz0id2hpdGUiLCBoaWdoPSJyZWQzIiwgZ3VpZGU9ImNvbG9yYmFyIikgKw0KICBsYWJzKHRpdGxlID0gIkhlYXRtYXAgb2YgQWJvcnRpb24gQ2FzZXMiLCB4ID0gIkluZHVjZWQiLCBzdWJ0aXRsZSA9ICJEYXRhIFNvdXJjZTogYGluZmVydDJgIiwgeSA9ICJTcG9udGFuZW91cyIsIGZpbGwgPSAiQ2FzZXMgKCUpIikNCg0KYGBgDQoNCiMgRGF0YSBtb2RlbGluZw0KDQpEYXRhIG1vZGVscyBhcmUgdXNlZCB0byBkZXNjcmliZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdmFyaWFibGVzLg0KDQojIyBMaW5lYXIgbW9kZWxzDQoNClJlZ3Jlc3Npb24gYW5hbHlzaXMgaXMgYW4gaW1wb3J0YW50IHN0YXRpc3RpY2FsIG1ldGhvZCBmb3IgdGhlIGFuYWx5c2lzIG9mIG1lZGljYWwgZGF0YS4gSXQgZW5hYmxlcyB0aGUgaWRlbnRpZmljYXRpb24gYW5kIGNoYXJhY3Rlcml6YXRpb24gb2YgcmVsYXRpb25zaGlwcyBhbW9uZyBtdWx0aXBsZSBmYWN0b3JzLiBJdCBhbHNvIGVuYWJsZXMgdGhlIGlkZW50aWZpY2F0aW9uIG9mIHByb2dub3N0aWNhbGx5IHJlbGV2YW50IHJpc2sgZmFjdG9ycyBhbmQgdGhlIGNhbGN1bGF0aW9uIG9mIHJpc2sgc2NvcmVzIGZvciBpbmRpdmlkdWFsIHByb2dub3N0aWNhdGlvbiAoW05JSCwgMjAxMF0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DMjk5MjAxOC8pKS4NCg0KIyMjIEFOT1ZBIHRlc3QgKGNoYW5nZSB0byBzaG93IHRoZSBhZmZlY3Qgb2YgZXZlcnl0aGluZyBvbiBzcG9udGFuZW91cykNCg0KYGBge3J9DQppbmZlcnQyJHBlcmNlbnRhZ2VfcyA8LSBpbmZlcnQyJGNhc2UgLyAoaW5mZXJ0MiRzcG9udGFuZW91cytpbmZlcnQyJGNhc2UpICNjcmVhdGUgYSBzcG9udGFuZW91cyBwZXJjZW50YWdlIGNvbHVtbg0KaW5mZXJ0Mg0KDQptb2RlbCA8LSBsbShwZXJjZW50YWdlX3MgfiBhZ2VfZ3JvdXAgKyBlZHVjYXRpb24gKyBpbmR1Y2VkLCBkYXRhID0gaW5mZXJ0MikgI0xpbmVhciBtb2RlbCBpcyBjcmVhdGVkIGluIG9yZGVyIHRvIGFwcGx5IGFub3ZhIHRlc3QNCmFub3ZhKG1vZGVsKQ0KYGBgDQoNCkFjY29yZGluZyB0byB0aGUgcmVzdWx0cyBvZiB0aGUgQU5PVkEgdGVzdCwgaXQgd2FzIG9ic2VydmVkIHRoYXQgYGFnZV9ncm91cGAgLCBhbmQgYW1vdW50IG9mIGBpbmR1Y2VkYCBhYm9ydGlvbnMgaGFkIHRoZSBncmVhdGVzdCBlZmZlY3Qgb24gdGhlIGFtb3VudCBvZiBzcG9udGFuZW91cyBhYm9ydGlvbnMuDQoNCiMjIyBBa2Fpa2UncyBJbmZvcm1hdGlvbiBDcml0ZXJpb24NCg0KVGhlIEFrYWlrZSdzIGluZm9ybWF0aW9uIGNyaXRlcmlvbiBtb2RlbCAoQUlDKSwgYWNoaWV2ZXMgcGFyc2ltb255IHZpYSBhIGZpdC1jb21wbGV4aXR5IHRyYWRlLW9mZiBhbmQgaXMgdXNlZCBhcyBhIHJlbGF0aXZlIG1lYXN1cmUgdG8gY29tcGFyZSBhbmQgcmFuayBzZXZlcmFsIGNvbXBldGluZyBtb2RlbHMgZml0IHRvIHRoZSBzYW1lIGRhdGEsIHdoZXJlIHRoZSBtb2RlbCB3aXRoIHRoZSBsb3dlc3QgQUlDIGlzIGNvbnNpZGVyZWQgdGhlIGJlc3QgKFtOSUgsIDIwMjNdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzEwNTIzMDcxLykpLiBUaGlzIHNjcmlwdCB3aWxsIGhlbHAgdXNlIGRlY2lkZSBpZiB3ZSBzaG91bGQgcmVtb3ZlIGBlZHVjYXRpb25gIGZyb20gdGhlIG1vZGVsLg0KDQpgYGB7cn0NCkFJQyhnbG0ocGVyY2VudGFnZV9zIH4gIGFnZV9ncm91cCArIGVkdWNhdGlvbiArIGluZHVjZWQsIGRhdGEgPSBpbmZlcnQyLCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gImxvZ2l0IikpKSAjd2l0aCBhbGwNCkFJQyhnbG0ocGVyY2VudGFnZV9zIH4gIGVkdWNhdGlvbiArIGluZHVjZWQsIGRhdGEgPSBpbmZlcnQyLCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gImxvZ2l0IikpKSANCkFJQyhnbG0ocGVyY2VudGFnZV9zIH4gIGFnZV9ncm91cCArIGluZHVjZWQsIGRhdGEgPSBpbmZlcnQyLCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gImxvZ2l0IikpKSAjYmVzdCBtb2RlbA0KQUlDKGdsbShwZXJjZW50YWdlX3MgfiAgYWdlX2dyb3VwICsgZWR1Y2F0aW9uLCBkYXRhID0gaW5mZXJ0MiwgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJsb2dpdCIpKSkNCkFJQyhnbG0ocGVyY2VudGFnZV9zIH4gYWdlX2dyb3VwLCBkYXRhID0gaW5mZXJ0MiwgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJsb2dpdCIpKSkgI3dpdGggYWdlX2dyb3VwDQpBSUMoZ2xtKHBlcmNlbnRhZ2VfcyB+ICBlZHVjYXRpb24sIGRhdGEgPSBpbmZlcnQyLCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gImxvZ2l0IikpKSAjd2l0aCBlZHVjYXRpb24NCkFJQyhnbG0ocGVyY2VudGFnZV9zIH4gIGluZHVjZWQsIGRhdGEgPSBpbmZlcnQyLCBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gImxvZ2l0IikpKSAjd2l0aCBpbmR1Y2VkIA0KYGBgDQoNClRoZSB0aGlyZCBtb2RlbCBjb250YWluaW5nIGBhZ2VfZ3JvdXBgIGFuZCBgaW5kdWNlZGAgZGF0YSBwcm9kdWNlZCB0aGUgbG93ZXN0IEFJQy4NCg0KIyMjIExvZ2lzdGljIFJlZ3Jlc3Npb24NCg0KTG9naXN0aWMgcmVncmVzc2lvbiBhbmFseXNpcyBpcyBhIHN0YXRpc3RpY2FsIHRlY2huaXF1ZSB0byBldmFsdWF0ZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdmFyaW91cyBwcmVkaWN0b3IgdmFyaWFibGVzIChlaXRoZXIgY2F0ZWdvcmljYWwgb3IgY29udGludW91cykgYW5kIGFuIG91dGNvbWUgd2hpY2ggaXMgYmluYXJ5IChkaWNob3RvbW91cykgKFtOSUgsIDIwMTddKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzU1NDM3NjcvKSkuIExvZ2lzdGljIHJlZ3Jlc3Npb24gaXMgYW4gaW1wb3J0YW50IHJlc2VhcmNoIHRvb2wgdXNlZCBmb3IgZGlzZWFzZSBwcmVkaWN0aW9uLg0KDQpgYGB7cn0NCm1vZGVsIDwtIGdsbShwZXJjZW50YWdlX3MgfiBhZ2VfZ3JvdXAgKyBpbmR1Y2VkLCBkYXRhID0gaW5mZXJ0MiwgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJsb2dpdCIpKSAjTG9naXN0aWMgcmVncmVzc2lvbg0Kc3VtbWFyeShtb2RlbCkNCmBgYA0KDQotICAgVGhlIGBpbmR1Y2VkYCBwLXZhbHVlIHNob3dzIHRoYXQgaXQgbWFrZXMgYSBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGluIHRoaXMgbW9kZWwuDQotICAgQW4gaWRlYWwgbW9kZWwgd291bGQgaW5jbHVkZSBtdWx0aXBsZSBwLXZhbHVlcyBvZiBsZXNzIHRoYW4gNSBwZXJjZW50IChbY2hlY2sgb3V0IHRoaXMgZXhhbXBsZV0oaHR0cHM6Ly9wam91cm5hbC5naXRodWIuaW8vYm91bjAxLWNhbmF5dG9yZS9hc3NpZ25tZW50M19lc29waCNBa2Fpa2UlRTIlODAlOTlzX0luZm9ybWF0aW9uX0NyaXRlcmlvbjp+OnRleHQ9dG8lMjBvdXIlMjBtb2RlbC4tLExvZ2lzdGljJTIwUmVncmVzc2lvbiwtbW9kZWwlMjAlM0MlMkQlMjBnbG0pKQ0KDQojIFRlc3QgdGhlIG1vZGVsDQoNCiMjIFByZWRpY3RlZCBTcG9udGFuZW91cyBBYm9ydGlvbiBSaXNrIFBlcmNlbnRhZ2VzIGFtb25nIEluZHVjZWQsIEVkdWNhdGlvbiwgYW5kIEFnZSBHcm91cHMNCg0KQWZ0ZXIgY3JlYXRpbmcgdGhlIG1vZGVsIHdlIGNhbiB2aXN1YWxpemUgdGhlIHByZWRpY3RlZCBzcG9udGFuZW91cyBhYm9ydGlvbiBjYXNlcy4NCg0KIyMjIEFnZSBHcm91cA0KDQpgYGB7cn0NCnByZWRpY3Rfc3BvbnRhbmVvdXNfcGVyY2VudGFnZXMgPC0gZGF0YS5mcmFtZSgpDQpmb3IgKGkgaW4gMTo1KSB7DQogIGZvciAoaiBpbiAxOjQpIHsNCiAgICBwcmVkaWN0X3Nwb250YW5lb3VzX3BlcmNlbnRhZ2VzW2ksal0gPC0gcGxvZ2lzKHByZWRpY3QobW9kZWwsIGRhdGEuZnJhbWUoYWdlX2dyb3VwID0gdW5pcXVlKGluZmVydDIkYWdlX2dyb3VwKVtpXSwgaW5kdWNlZCA9IHVuaXF1ZShpbmZlcnQyJGluZHVjZWQpW2pdKSkpICNQcmVkaWN0aW9uDQogIH0NCn0NCnBpdm90X2xvbmdlcihwcmVkaWN0X3Nwb250YW5lb3VzX3BlcmNlbnRhZ2VzLCBjb2xzPWV2ZXJ5dGhpbmcoKSwgbmFtZXNfdG8gPSAiSW5kdWNlZF9BYm9ydGlvbiIsIHZhbHVlc190byA9ICJTcG9udGFuZW91c19QZXJjZW50YWdlIikgJT4lDQogIGFkZF9jb2x1bW4oLmJlZm9yZT0iSW5kdWNlZF9BYm9ydGlvbiIsIEFnZV9Hcm91cCA9IGMocmVwKCIwLTI1Iiw0KSwgcmVwKCIyNS0zMCIsNCkscmVwKCIzMC0zNSIsNCkscmVwKCIzNS00MCIsNCkscmVwKCI0MDwiLDQpKSkgJT4lDQogIGdncGxvdCguLGFlcyh4PUFnZV9Hcm91cCwgeT1TcG9udGFuZW91c19QZXJjZW50YWdlLCBmaWxsID0gSW5kdWNlZF9BYm9ydGlvbikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgKyANCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoKSkgKw0KICBzY2FsZV9maWxsX2Rpc2NyZXRlKG5hbWUgPSAiSW5kdWNlZF9BYm9ydGlvbiIsIGxhYmVscyA9IGMoIjAiLCAiMSIsICIyIikpICsNCiAgbGFicyh0aXRsZSA9ICJQcmVkaWN0ZWQgU3BvbnRhbmVvdXMgQWJvcnRpb24gUmlzayBQZXJjZW50YWdlcyBhbW9uZyBJbmR1Y2VkIGFuZCBBZ2UgR3JvdXBzIiwgc3VidGl0bGUgPSAiUHJlZGljdGlvbiBCYXNlZCBvbiBMb2dpc3RpYyBSZWdyZXNzaW9uIiwgeCA9ICJBZ2UgR3JvdXBzIiwgeSA9ICJQcmVkaWN0ZWQgUGVyY2VudGFnZSIpDQoNCmBgYA0KDQotICAgVGhlc2UgcHJlZGljdGlvbnMgcmVwcmVzZW50IHRoZSBlc3RpbWF0ZWQgc3BvbnRhbmVvdXMgYWJvcnRpb24gcGVyY2VudGFnZXMgYnkgYWdlIGdyb3VwIGFuZCBpbmR1Y2VkIGFib3J0aW9ucy4NCi0gICBBY2NvcmRpbmcgdG8gdGhlIHByZWRpY3Rpb25zLCBwYXRpZW50cyB3aXRoIG9uZSBpbmR1Y2VkIGFib3J0aW9uIGFuZCBpbiB0aGUgMjUtMzB5ciBhZ2UgZ3JvdXAgaGF2ZSB0aGUgaGlnaGVzdCBwZXJjZW50YWdlIG9mIHNwb250YW5lb3VzIGFib3J0aW9ucy4NCi0gICBBY2NvcmRpbmcgdG8gdGhlIHByZWRpY3Rpb25zLCBwYXRpZW50cyB3aXRoIG1vcmUgdGhhbiBvbmUgaW5kdWNlZCBhYm9ydGlvbnMgYW5kIGluIHRoZSAzMC0zNXlyIGFnZSBncm91cCBoYXZlIHRoZSBsb3dlc3QgcGVyY2VudGFnZSBvZiBzcG9udGFuZW91cyBhYm9ydGlvbi4NCi0gICBUaGUgcmlzayBsZXZlbHMgYmV0d2VlbiB0aGUgMC0yNXlyLCAzMC0zNXlyLCBhbmQgNDBcPCBhcmUgdmVyeSBzaW1pbGFyLg0KDQpgYGB7cn0NCnByZWRpY3Rfc3BvbnRhbmVvdXNfcGVyY2VudGFnZXMgPC0gZGF0YS5mcmFtZShyb3cubmFtZXMgPSBjKCIwLTI1IHllYXJzIiwiMjUtMzAgeWVhcnMiLCIzMC0zNSB5ZWFycyIsIjM1LTQwIHllYXJzIiwiNDA8IHllYXJzIikpDQpmb3IgKGkgaW4gMTo1KSB7DQogIGZvciAoaiBpbiAxOjQpIHsNCiAgICBwcmVkaWN0X3Nwb250YW5lb3VzX3BlcmNlbnRhZ2VzW2ksal0gPC0gcGFzdGUocm91bmQoMTAwKihwbG9naXMocHJlZGljdChtb2RlbCwgZGF0YS5mcmFtZShhZ2VfZ3JvdXAgPSB1bmlxdWUoaW5mZXJ0MiRhZ2VfZ3JvdXApW2ldLCBpbmR1Y2VkID0gdW5pcXVlKGluZmVydDIkaW5kdWNlZClbal0pKSkpLDApLCIlIixzZXA9IiIpDQogIH0NCn0NCmNvbG5hbWVzKHByZWRpY3Rfc3BvbnRhbmVvdXNfcGVyY2VudGFnZXMpIDwtIGMoIjAiLCIxIiwiMiIpDQprYWJsZShwcmVkaWN0X3Nwb250YW5lb3VzX3BlcmNlbnRhZ2VzLCBjYXB0aW9uID0gIlByZWRpY3RlZCBTcG9udGFuZW91cyBBYm9ydGlvbiBQZXJjZW50YWdlcyBjb3JyZXNwLiB0byBBZ2UgYW5kIEluZHVjZWQgQWJvcnRpb24gR3JvdXBzIikNCmBgYA0KDQotICAgVGhpcyB0YWJsZSBzaG93cyB0aGUgcGVyY2VudGFnZSB2YWx1ZXMuDQoNCiMjIyBMZWF2ZS1vbmUtb3V0IENyb3NzIFZhbGlkYXRpb24NCg0KQ3Jvc3MtdmFsaWRhdGlvbiBpcyBhIHJlLXNhbXBsaW5nIG1ldGhvZCB0aGF0IHVzZXMgZGlmZmVyZW50IHBvcnRpb25zIG9mIHRoZSBkYXRhIHRvIHRlc3QgYW5kIHRyYWluIGEgbW9kZWwgb24gZGlmZmVyZW50IGl0ZXJhdGlvbnMuIEl0IGlzIG1haW5seSB1c2VkIGluIHNldHRpbmdzIHdoZXJlIHRoZSBnb2FsIGlzIHByZWRpY3Rpb24sIGFuZCBvbmUgd2FudHMgdG8gZXN0aW1hdGUgaG93IGFjY3VyYXRlbHkgYSBwcmVkaWN0aXZlIG1vZGVsIHdpbGwgcGVyZm9ybSBpbiBwcmFjdGljZSAoW0Nyb3NzLXZhbGlkYXRpb25dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0Nyb3NzLXZhbGlkYXRpb25fKHN0YXRpc3RpY3MpKSkuDQoNCkZvciBtb3JlIGluZm9ybWF0aW9uIG9uIGNyb3NzLXZhbGlkYXRpb246DQoNCltDcm9zcy12YWxpZGF0aW9uIHVuZGVyIHNlcGFyYXRlIHNhbXBsaW5nOiBzdHJvbmcgYmlhcyBhbmQgaG93IHRvIGNvcnJlY3QgaXRdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzQyOTYxNDMvKXsudXJpfQ0KDQpbSW1wYWN0IG9mIHRoZSBDaG9pY2Ugb2YgQ3Jvc3MtVmFsaWRhdGlvbiBUZWNobmlxdWVzIG9uIHRoZSBSZXN1bHRzIG9mIE1hY2hpbmUgTGVhcm5pbmctQmFzZWQgRGlhZ25vc3RpYyBBcHBsaWNhdGlvbnNdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzgzNjkwNTMvKXsudXJpfQ0KDQpgYGB7cn0NCnByZWRfbGVuZ3RoIDwtIG5yb3coaW5mZXJ0MikNCmZpdF9nbG1fZXJyb3IgPC0gYygpDQpmaXRfZ2xtX3NxX2Vycm9yIDwtIGMoKQ0KZm9yKGkgaW4gMTpwcmVkX2xlbmd0aCl7DQogIGZpdF9nbG0gPC0gZ2xtKHBlcmNlbnRhZ2VfcyB+IGFnZV9ncm91cCArIGluZHVjZWQsIGZhbWlseSA9IGJpbm9taWFsKGxpbmsgPSAibG9naXQiKSwgZGF0YSA9IGluZmVydDJbLWksXSkgI0xlYXZlLW9uZS1vdXQgQ3Jvc3MgVmFsaWRhdGlvbg0KICBmaXRfZ2xtX3ByZWQgPC0gKHByZWRpY3QoZml0X2dsbSwgaW5mZXJ0MltpLF0pKV4yDQogIGZpdF9nbG1fZXJyb3JbaV0gPC0gaW5mZXJ0MiRwZXJjZW50YWdlX3NbaV0gLSBmaXRfZ2xtX3ByZWQNCiAgZml0X2dsbV9zcV9lcnJvcltpXSA9IChpbmZlcnQyJHBlcmNlbnRhZ2Vfc1tpXSAtIGZpdF9nbG1fcHJlZCleMg0KfQ0KaGlzdChmaXRfZ2xtX2Vycm9yLCBicmVha3MgPSA1MCwgeGxpbSA9IHJhbmdlKC01MCw1MCksIHRpdGxlID0gIkhpc3RvZ3JhbSBvZiBFcnJvcnMiLCB4bGFiID0gIkZpdHRlZCBHTE0gRXJyb3JzIikNCmBgYA0KDQpUaGlzIGhpc3RvZ3JhbSBvZiBlcnJvcnMgc2hvd3MgdGhhdCB0aGVyZSBhcmUgc29tZSBlcnJvcnMgdGhhdCBhcmUgc2lnbmlmaWNhbnRseSBncmVhdGVyIHRoYW4gMzAuIEhvd2V2ZXIsIHRoZSBlcnJvcnMgYXJlIGNsb3NlIHRvIHplcm8gc28gd2UgZXN0YWJsaXNoZWQgYSBnb29kIG1vZGVsLg0KDQpgYGB7cn0NCmZpdF9nbG1fc3FfZXJyb3IgPC0gbmEub21pdChmaXRfZ2xtX3NxX2Vycm9yKSAjcmVtb3ZlIE5hTg0Kcm1zZV9maXRfZ2xtIDwtIHNxcnQobWVhbihmaXRfZ2xtX3NxX2Vycm9yKSkNCnJtc2VfZml0X2dsbSAjUm9vdCBNZWFuIFNxdWFyZSBFcnJvcg0KYGBgDQoNClRoZSByb290IG1lYW4gc3F1YXJlIGVycm9yIChSTVNFKSBpcyBhIG1ldHJpYyB0aGF0IHRlbGxzIHVzIGhvdyBmYXIgYXBhcnQgb3VyIHByZWRpY3RlZCB2YWx1ZXMgYXJlIGZyb20gb3VyIG9ic2VydmVkIHZhbHVlcyBpbiBhIHJlZ3Jlc3Npb24gYW5hbHlzaXMsIG9uIGF2ZXJhZ2UuIFRoZSBsYXJnZXIgdGhlIFJNU0UsIHRoZSBsYXJnZXIgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgcHJlZGljdGVkIGFuZCBvYnNlcnZlZCB2YWx1ZXMsIHdoaWNoIG1lYW5zIHRoZSB3b3JzZSBhIHJlZ3Jlc3Npb24gbW9kZWwgZml0cyB0aGUgZGF0YS4gQ29udmVyc2VseSwgdGhlIHNtYWxsZXIgdGhlIFJNU0UsIHRoZSBiZXR0ZXIgYSBtb2RlbCBpcyBhYmxlIHRvIGZpdCB0aGUgZGF0YSAoW0hvdyB0byBDYWxjdWxhdGUgUk1TRSBpbiBSXShodHRwczovL3d3dy5zdGF0b2xvZ3kub3JnL2hvdy10by1jYWxjdWxhdGUtcm1zZS1pbi1yLykpLg0KDQojIENvbmNsdXNpb24NCg0KSW4gY29uY2x1c2lvbiwgd2Ugd29ya2VkIHdpdGggZGF0YSBncmFwcGxpbmcsIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMsIGFuZCBsaW5lYXIgbW9kZWxpbmcsIHRvIGJ1aWxkIGFuZCB0ZXN0IGEgcHJlZGljdGl2ZSBtb2RlbCAuDQoNCiMgUmVzb3VyY2VzDQoNCltFc29waGFnZWFsIENhbmNlciBQcm9qZWN0XShodHRwczovL3Bqb3VybmFsLmdpdGh1Yi5pby9ib3VuMDEtY2FuYXl0b3JlL2Fzc2lnbm1lbnQzX2Vzb3BoKQ0KDQpbSW5kdWNlZCBhYm9ydGlvbiBhbmQgc2Vjb25kYXJ5IGluZmVydGlsaXR5IHN0dWR5XShodHRwczovL29iZ3luLm9ubGluZWxpYnJhcnkud2lsZXkuY29tL2RvaS8xMC4xMTExL2ouMTQ3MS0wNTI4LjE5NzYudGIwMDkwNC54KQ0KDQpbSW5mZXJ0aWxpdHkgZGF0YV0oaHR0cHM6Ly9zdGF0LmV0aHouY2gvUi1tYW51YWwvUi1kZXZlbC9saWJyYXJ5L2RhdGFzZXRzL2h0bWwvaW5mZXJ0Lmh0bWwpDQoNCltMaW5lYXIgUmVncmVzc2lvbiBBbmFseXNpc10oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DMjk5MjAxOC8pDQoNCltQcmFjdGljYWwgYWR2aWNlIG9uIHZhcmlhYmxlIHNlbGVjdGlvbiBhbmQgcmVwb3J0aW5nIHVzaW5nIEFrYWlrZSBpbmZvcm1hdGlvbiBjcml0ZXJpb25dKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzEwNTIzMDcxLykNCg0KW0NvbW1vbiBwaXRmYWxscyBpbiBzdGF0aXN0aWNhbCBhbmFseXNpczogTG9naXN0aWMgcmVncmVzc2lvbl0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNTU0Mzc2Ny8pDQoNCltDcm9zcy12YWxpZGF0aW9uXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Dcm9zcy12YWxpZGF0aW9uXyhzdGF0aXN0aWNzKSkNCg0KW0Nyb3NzLXZhbGlkYXRpb24gdW5kZXIgc2VwYXJhdGUgc2FtcGxpbmc6IHN0cm9uZyBiaWFzIGFuZCBob3cgdG8gY29ycmVjdCBpdF0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNDI5NjE0My8pey51cml9DQoNCltJbXBhY3Qgb2YgdGhlIENob2ljZSBvZiBDcm9zcy1WYWxpZGF0aW9uIFRlY2huaXF1ZXMgb24gdGhlIFJlc3VsdHMgb2YgTWFjaGluZSBMZWFybmluZy1CYXNlZCBEaWFnbm9zdGljIEFwcGxpY2F0aW9uc10oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DODM2OTA1My8pey51cml9DQoNCltIb3cgdG8gQ2FsY3VsYXRlIFJNU0UgaW4gUl0oaHR0cHM6Ly93d3cuc3RhdG9sb2d5Lm9yZy9ob3ctdG8tY2FsY3VsYXRlLXJtc2UtaW4tci8pDQo=