Introduction

The dataset constructed in previous scripts is a panel-data set and is the type of data which would typically analyzed in a social science. As such we will use the linear models typical of those fields, however for this project we will also be using modeling techniques typically found in predictive analytics, i.e. crossvalidation, with our primary focus being prediction. This notebook will be focused on these models, see previous notebooks and scripts for data-sourcing, wrangling, and EDA.

Loading Data and Packages

library(zoo)
library(MASS)
library(tidyverse)
library(lme4)
library(caTools)
library(caret)
final_data <- read.csv("final_data.csv")

Setting Up the Data for Modeling

First we need to handle some lingering issues with the dataset, i.e. missing values so we can approprately model our data. First we interpolate missing values for GDP_Per_Capita by country. This should only leave us with countries that have no infromation on GDP_Per_capita, which we will omit.

model_data <- final_data %>% 
  group_by(Entity) %>% 
  mutate(GDP_Per_Capita = na.approx(GDP_Per_Capita, maxgap = 25, rule = 2))

model_data <- model_data %>% 
  na.omit()

First Model

We’ll begin with a basic two-way fixed effects model with year and country as the fixed effects. We can evaluate how well this model does by examining our RMSE and residual plots. Here we run into one of our first obstacles in the modeling process, since our data is panel-data when we segment our data with validation techniques, K-fold CV, data segmentation, etc. we run into issues with stability in our regression. There is however a technique called LOOCV which will be helpful for the modeling here. LOOCV fits a model with the ith observation removed and then calculates the residual at that data point. This is alternatively known as the PRESS residual. I write a function which returns a dataframe with the actual value, predicted value, residual value, standardized residual value, PRESS residual value, and studentized residual value. We will save this data frame as well as one containing the PRESS statistic (PRESS RMSE), and train test error for comparison later on.

The following plots depict these residuals in multiple ways. The first plots our actual values against our predicted values. The interpretation of this plot is that the closer the points match the diagonal line the more appropriately the model fits the data. The further the points stray from the line we may find issues such as heteroskedasticity, or lack of fit. The next four depict the residual vs predicted plots. These plots ideally should resemble a random cloud. Additionally, for studentized and standardized residuals we may depict outliers, those points which lie beyond 4.

formula <- (cardiovasc_deaths ~ mean_bmi +percent_obesity + percent_severe_obesity  + GDP_Per_Capita +factor(Entity) + factor(Year))

model1  <- lm(formula, data=model_data)

PRESS <- function(linear.model) {
  #' calculate the predictive residuals
  pr <- residuals(linear.model)/(1-lm.influence(linear.model)$hat)
  #' calculate the PRESS
  PRESS <- sum(pr^2)
  
  return(pr)
}

model_eval<- function(model, data){

  stanresid<-model %>% 
    rstandard()
  
  yhat <-model %>% 
    predict()
  
  resid <- model$residuals
  
  pressres <- model %>% 
    PRESS()
  
  stures <-  model %>% 
  studres()
  
  stanres <- model %>% 
    rstandard()
  fulldf<-cbind(data$cardiovasc_deaths, yhat, resid, stanres, pressres, stures)
  fulldf = as.data.frame(fulldf)
  names(fulldf) <- c("Y","Y_Hat", "residuals", "standardized_residuals", "press_residuals", "studentized_residuals")
  return(fulldf)
}

residuals_model1<- model_eval(model1, model_data)

errors<- data.frame("Full_Test" = numeric(),"LOOCV" = numeric(),"Segment_Test" = numeric(),"Segment_Train" = numeric())
errors[1,1] <- sqrt(sum((residuals_model1$Y - residuals_model1$Y_Hat)^2)/length(residuals_model1[,1]))
up <-sum(residuals_model1$press_residuals^2)/length(residuals_model1[,1])   
errors[1,2]<-sqrt(up)

#Split data into train and test sets to approximate error for new obs.
set.seed(789)
training_samples <- model_data$cardiovasc_deaths %>% 
    createDataPartition(p= .9, list= FALSE)

training_samples <- training_samples %>% 
  as.vector()

train_data <- model_data[training_samples, ]
test_data <- model_data[-training_samples, ]

train_model  <- lm(formula, data=train_data)

predictions <- train_model %>% 
  predict(test_data)

errors[1,3] <- RMSE(predictions, test_data$cardiovasc_deaths)

predictions <- train_model %>% 
  predict(train_data)

errors[1,4] <- RMSE(predictions, train_data$cardiovasc_deaths)
ggplot(residuals_model1, aes( Y, Y_Hat))+
       labs(x='Actual', y ='Predicted')+
       geom_point()+
      geom_abline(intercept =0, slope =  1, color='red', size =2)


ggplot(residuals_model1, aes( Y_Hat, residuals))+
  labs(x='Predicted', y ='Residual')+
  geom_point()+
  geom_abline(intercept =0, slope =  0, color='blue', size =1)


ggplot(residuals_model1, aes( Y_Hat, standardized_residuals))+
  labs(x='Predicted', y ='Standardized Residual')+
  geom_point()+
  geom_abline(intercept =0, slope =  0, color='blue', size =1)


ggplot(residuals_model1, aes( Y_Hat, press_residuals))+
  labs(x='Predicted', y ='PRESS Residual')+
  geom_point()+
  geom_abline(intercept =0, slope =  0, color='blue', size =1)


ggplot(residuals_model1, aes( Y_Hat, studentized_residuals))+
  labs(x='Predicted', y ='Studentized Residual')+
  geom_point()+
  geom_abline(intercept =0, slope =  0, color='blue', size =1)

Second Model

As we see our model appears to fit well. There does appear to be a degree of heteroskedasticity in the higher ranges of our data, this can be seen in all four plots. We will try to overcome this by introducing a transformation for our response variable.

formula <- (log(cardiovasc_deaths) ~ mean_bmi +percent_obesity + percent_severe_obesity  + GDP_Per_Capita +factor(Entity) + factor(Year))

model2  <- lm(formula, data=model_data)


residuals_model2 <- model_eval(model2, model_data)

residuals_model2$Y <- log(residuals_model2$Y)

We can do the same analysis on our log-transformed response model. The plots for these are provided below as well as the previously defined function which saves our residuals, both LOOCV, and train-test split based errors are saved.

formula <- (log(cardiovasc_deaths) ~ mean_bmi +percent_obesity + percent_severe_obesity  + GDP_Per_Capita +factor(Entity) + factor(Year))

model2  <- lm(formula, data=model_data)


residuals_model2 <- model_eval(model2, model_data)

residuals_model2$Y <-  log(residuals_model2$Y)

errors[2,1] <- RMSE(exp(residuals_model2$Y), exp(residuals_model2$Y_Hat))

Yresid <- exp(residuals_model2$Y) - exp(residuals_model2$Y_Hat)
pr <- Yresid/(1-lm.influence(model2)$hat)
errors[2,2] <- sqrt(sum(pr^2)/length(pr))

train_model  <- lm(formula, data=train_data)

predictions <- train_model %>% 
  predict(test_data) %>% 
  exp()

errors[2,3] <- RMSE(predictions, test_data$cardiovasc_deaths)

predictions <- train_model %>% 
  predict(train_data) %>% 
  exp()
errors[2,4] <- RMSE(predictions, train_data$cardiovasc_deaths)


#Same plots as before
ggplot(residuals_model2, aes( Y, Y_Hat))+
  labs(x='Actual', y ='Predicted')+
  geom_point()+
  geom_abline(intercept =0, slope =  1, color='red', size =2)


ggplot(residuals_model2, aes( Y_Hat, residuals))+
  labs(x='Predicted', y ='Residual')+
  geom_point()+
  geom_abline(intercept =0, slope =  0, color='blue', size =1)


ggplot(residuals_model2, aes( Y_Hat, standardized_residuals))+
  labs(x='Predicted', y ='Standardized Residual')+
  geom_point()+
  geom_abline(intercept =0, slope =  0, color='blue', size =1)


ggplot(residuals_model2, aes( Y_Hat, press_residuals))+
  labs(x='Predicted', y ='PRESS Residual')+
  geom_point()+
  geom_abline(intercept =0, slope =  0, color='blue', size =1)


ggplot(residuals_model2, aes( Y_Hat, studentized_residuals))+
  labs(x='Predicted', y ='Studentized Residual')+
  geom_point()+
  geom_abline(intercept =0, slope =  0, color='blue', size =1)

Final Model

In the above plots we see our second model does appear to fit the data better. However, there are a couple of serious problems with this form of our model. First, looking at our cross-validation methods we see that the error for this term is higher in all metrics than the previous model. Additionally, there is difficulty in the interpretation of our coefficients when we introduce transformations into the model.

With this in mind let’s try a final different model. Our two previous models have both been fixed effects, we may be able to improve our model by introducing what are known as random effects by country for mean_bmi. These random effects allow the slope, for mean_bmi, and the intercept to vary depending on the entity. LOOCV are not as easy to derive computationally for this type of model, so we will have to omit them in our model evaluation. The evaluation plots for this model are provided below.

library(lme4)
formula <- (cardiovasc_deaths ~ percent_obesity + percent_severe_obesity + GDP_Per_Capita + 1+(1 + mean_bmi|Entity) + factor(Year))
model_3 <- lmer(formula, data=model_data, REML=FALSE)
Some predictor variables are on very different scales: consider rescalingModel failed to converge with max|grad| = 0.00319404 (tol = 0.002, component 1)
predictions <- model_3 %>% 
  predict(model_data)

errors[3,1]<- RMSE( model_data$cardiovasc_deaths, predictions)

train_model <- lmer(formula, data=train_data, REML=FALSE)
Some predictor variables are on very different scales: consider rescaling
predictions <- train_model %>% 
  predict(test_data)
errors[3,3]<- RMSE( test_data$cardiovasc_deaths, predictions)

predictions <- train_model %>% 
  predict(train_data)
errors[3,4]<- RMSE( train_data$cardiovasc_deaths, predictions)

predictions <- model_3 %>% 
  predict(model_data)
residuals_model3 <- as.data.frame(cbind(predictions, model_data$cardiovasc_deaths))
residuals_model3$residuals <- residuals_model3$V2 - residuals_model3$predictions 
names(residuals_model3)[2] <- "actual"
residuals_model3$Entity <- model_data$Entity

ggplot(residuals_model3, aes( actual, predictions))+
  labs(x='Actual', y ='Predicted')+
  geom_point()+
  geom_abline(intercept =0, slope =  1, color='red', size =2)


ggplot(residuals_model3, aes( predictions, residuals))+
  labs(x='Predicted', y ='Residual')+
  geom_point()+
  geom_abline(intercept =0, slope =  0, color='blue', size =1)

We see from the above plots that our new model fits the data well. From our residual plot however it does appear as though we have reintroduced heteroskedasticity. Generally heteroskedasticity is considered to be less of a severe violation of our linear regression assumptions.

errors[1, 5] <- "model_1"
errors[2, 5] <- "model_2"
errors[3, 5] <- "model_3"
names(errors)[5] <- "Model"
  
ggplot(errors, aes(x=Model, y=Segment_Test ))+
  labs( x= "Model", y= "Test RMSE") +
  geom_bar(stat="identity", width=.009)

Model Comparison and Conclusion

Plotted above is the test RMSE for all three models. We see from this that our third model clearly fits the data the best, additionally none of the models seem to seriously violate linear assumptions. Using this information, we can now predict the decrease in deaths which would result from a country decreasing their mean_bmi. This is an extremely useful insight for public health officials, and though the models presented here are relatively simple, should not be underestimated.

LS0tDQp0aXRsZTogIk1vZGVsaW5nIE5vdGVib29rIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCiMjIEludHJvZHVjdGlvbg0KVGhlIGRhdGFzZXQgY29uc3RydWN0ZWQgaW4gcHJldmlvdXMgc2NyaXB0cyBpcyBhIHBhbmVsLWRhdGEgc2V0IGFuZCBpcyB0aGUgdHlwZSBvZiBkYXRhIHdoaWNoIHdvdWxkIHR5cGljYWxseSBhbmFseXplZCBpbiBhIHNvY2lhbCBzY2llbmNlLiBBcyBzdWNoIHdlIHdpbGwgdXNlIHRoZSBsaW5lYXIgbW9kZWxzIHR5cGljYWwgb2YgdGhvc2UgZmllbGRzLCBob3dldmVyIGZvciB0aGlzIHByb2plY3Qgd2Ugd2lsbCBhbHNvIGJlIHVzaW5nIG1vZGVsaW5nIHRlY2huaXF1ZXMgdHlwaWNhbGx5IGZvdW5kIGluIHByZWRpY3RpdmUgYW5hbHl0aWNzLCBpLmUuIGNyb3NzdmFsaWRhdGlvbiwgd2l0aCBvdXIgcHJpbWFyeSBmb2N1cyBiZWluZyBwcmVkaWN0aW9uLiBUaGlzIG5vdGVib29rIHdpbGwgYmUgZm9jdXNlZCBvbiB0aGVzZSBtb2RlbHMsIHNlZSBwcmV2aW91cyBub3RlYm9va3MgYW5kIHNjcmlwdHMgZm9yIGRhdGEtc291cmNpbmcsIHdyYW5nbGluZywgYW5kIEVEQS4gDQoNCiMjIyBMb2FkaW5nIERhdGEgYW5kIFBhY2thZ2VzIA0KYGBge3IgcmVzdWx0cyA9ICdoaWRlJ30NCmxpYnJhcnkoem9vKQ0KbGlicmFyeShNQVNTKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGxtZTQpDQpsaWJyYXJ5KGNhVG9vbHMpDQpsaWJyYXJ5KGNhcmV0KQ0KZmluYWxfZGF0YSA8LSByZWFkLmNzdigiZmluYWxfZGF0YS5jc3YiKQ0KYGBgDQojIyMgU2V0dGluZyBVcCB0aGUgRGF0YSBmb3IgTW9kZWxpbmcNCkZpcnN0IHdlIG5lZWQgdG8gaGFuZGxlIHNvbWUgbGluZ2VyaW5nIGlzc3VlcyB3aXRoIHRoZSBkYXRhc2V0LCBpLmUuIG1pc3NpbmcgdmFsdWVzIHNvIHdlIGNhbiBhcHByb3ByYXRlbHkgbW9kZWwgb3VyIGRhdGEuIEZpcnN0IHdlIGludGVycG9sYXRlIG1pc3NpbmcgdmFsdWVzIGZvciBHRFBfUGVyX0NhcGl0YSBieSBjb3VudHJ5LiBUaGlzIHNob3VsZCBvbmx5IGxlYXZlIHVzIHdpdGggY291bnRyaWVzIHRoYXQgaGF2ZSBubyBpbmZyb21hdGlvbiBvbiBHRFBfUGVyX2NhcGl0YSwgd2hpY2ggd2Ugd2lsbCBvbWl0LiANCg0KYGBge3J9DQptb2RlbF9kYXRhIDwtIGZpbmFsX2RhdGEgJT4lIA0KICBncm91cF9ieShFbnRpdHkpICU+JSANCiAgbXV0YXRlKEdEUF9QZXJfQ2FwaXRhID0gbmEuYXBwcm94KEdEUF9QZXJfQ2FwaXRhLCBtYXhnYXAgPSAyNSwgcnVsZSA9IDIpKQ0KDQptb2RlbF9kYXRhIDwtIG1vZGVsX2RhdGEgJT4lIA0KICBuYS5vbWl0KCkNCmBgYA0KDQojIyMgRmlyc3QgTW9kZWwNCldlJ2xsIGJlZ2luIHdpdGggYSBiYXNpYyB0d28td2F5IGZpeGVkIGVmZmVjdHMgbW9kZWwgd2l0aCB5ZWFyIGFuZCBjb3VudHJ5IGFzIHRoZSBmaXhlZCBlZmZlY3RzLiBXZSBjYW4gZXZhbHVhdGUgaG93IHdlbGwgdGhpcyBtb2RlbCBkb2VzIGJ5IGV4YW1pbmluZyBvdXIgUk1TRSBhbmQgcmVzaWR1YWwgcGxvdHMuIEhlcmUgd2UgcnVuIGludG8gb25lIG9mIG91ciBmaXJzdCBvYnN0YWNsZXMgaW4gdGhlIG1vZGVsaW5nIHByb2Nlc3MsIHNpbmNlIG91ciBkYXRhIGlzIHBhbmVsLWRhdGEgd2hlbiB3ZSBzZWdtZW50IG91ciBkYXRhIHdpdGggdmFsaWRhdGlvbiB0ZWNobmlxdWVzLCBLLWZvbGQgQ1YsIGRhdGEgc2VnbWVudGF0aW9uLCBldGMuIHdlIHJ1biBpbnRvIGlzc3VlcyB3aXRoIHN0YWJpbGl0eSBpbiBvdXIgcmVncmVzc2lvbi4gVGhlcmUgaXMgaG93ZXZlciBhIHRlY2huaXF1ZSBjYWxsZWQgTE9PQ1Ygd2hpY2ggd2lsbCBiZSBoZWxwZnVsIGZvciB0aGUgbW9kZWxpbmcgaGVyZS4gTE9PQ1YgZml0cyBhIG1vZGVsIHdpdGggdGhlIGl0aCBvYnNlcnZhdGlvbiByZW1vdmVkIGFuZCB0aGVuIGNhbGN1bGF0ZXMgdGhlIHJlc2lkdWFsIGF0IHRoYXQgZGF0YSBwb2ludC4gVGhpcyBpcyBhbHRlcm5hdGl2ZWx5IGtub3duIGFzIHRoZSBQUkVTUyByZXNpZHVhbC4gSSB3cml0ZSBhIGZ1bmN0aW9uIHdoaWNoIHJldHVybnMgYSBkYXRhZnJhbWUgd2l0aCB0aGUgYWN0dWFsIHZhbHVlLCBwcmVkaWN0ZWQgdmFsdWUsIHJlc2lkdWFsIHZhbHVlLCBzdGFuZGFyZGl6ZWQgcmVzaWR1YWwgdmFsdWUsIFBSRVNTIHJlc2lkdWFsIHZhbHVlLCBhbmQgc3R1ZGVudGl6ZWQgcmVzaWR1YWwgdmFsdWUuIFdlIHdpbGwgc2F2ZSAgdGhpcyBkYXRhIGZyYW1lIGFzIHdlbGwgYXMgb25lIGNvbnRhaW5pbmcgdGhlIFBSRVNTIHN0YXRpc3RpYyAoUFJFU1MgUk1TRSksIGFuZCB0cmFpbiB0ZXN0IGVycm9yIGZvciBjb21wYXJpc29uIGxhdGVyIG9uLg0KDQpUaGUgZm9sbG93aW5nIHBsb3RzIGRlcGljdCB0aGVzZSByZXNpZHVhbHMgaW4gbXVsdGlwbGUgd2F5cy4gVGhlIGZpcnN0IHBsb3RzIG91ciBhY3R1YWwgdmFsdWVzIGFnYWluc3Qgb3VyIHByZWRpY3RlZCB2YWx1ZXMuIFRoZSBpbnRlcnByZXRhdGlvbiBvZiB0aGlzIHBsb3QgaXMgdGhhdCB0aGUgY2xvc2VyIHRoZSBwb2ludHMgbWF0Y2ggdGhlIGRpYWdvbmFsIGxpbmUgdGhlIG1vcmUgYXBwcm9wcmlhdGVseSB0aGUgbW9kZWwgZml0cyB0aGUgZGF0YS4gVGhlIGZ1cnRoZXIgdGhlIHBvaW50cyBzdHJheSBmcm9tIHRoZSBsaW5lIHdlIG1heSBmaW5kIGlzc3VlcyBzdWNoIGFzIGhldGVyb3NrZWRhc3RpY2l0eSwgb3IgbGFjayBvZiBmaXQuIFRoZSBuZXh0IGZvdXIgZGVwaWN0IHRoZSByZXNpZHVhbCB2cyBwcmVkaWN0ZWQgcGxvdHMuIFRoZXNlIHBsb3RzIGlkZWFsbHkgc2hvdWxkIHJlc2VtYmxlIGEgcmFuZG9tIGNsb3VkLiBBZGRpdGlvbmFsbHksIGZvciBzdHVkZW50aXplZCBhbmQgc3RhbmRhcmRpemVkIHJlc2lkdWFscyB3ZSBtYXkgZGVwaWN0IG91dGxpZXJzLCB0aG9zZSBwb2ludHMgd2hpY2ggbGllIGJleW9uZCA0LiAgDQpgYGB7cn0NCmZvcm11bGEgPC0gKGNhcmRpb3Zhc2NfZGVhdGhzIH4gbWVhbl9ibWkgK3BlcmNlbnRfb2Jlc2l0eSArIHBlcmNlbnRfc2V2ZXJlX29iZXNpdHkgICsgR0RQX1Blcl9DYXBpdGEgK2ZhY3RvcihFbnRpdHkpICsgZmFjdG9yKFllYXIpKQ0KDQptb2RlbDEgIDwtIGxtKGZvcm11bGEsIGRhdGE9bW9kZWxfZGF0YSkNCg0KUFJFU1MgPC0gZnVuY3Rpb24obGluZWFyLm1vZGVsKSB7DQogICMnIGNhbGN1bGF0ZSB0aGUgcHJlZGljdGl2ZSByZXNpZHVhbHMNCiAgcHIgPC0gcmVzaWR1YWxzKGxpbmVhci5tb2RlbCkvKDEtbG0uaW5mbHVlbmNlKGxpbmVhci5tb2RlbCkkaGF0KQ0KICAjJyBjYWxjdWxhdGUgdGhlIFBSRVNTDQogIFBSRVNTIDwtIHN1bShwcl4yKQ0KICANCiAgcmV0dXJuKHByKQ0KfQ0KDQptb2RlbF9ldmFsPC0gZnVuY3Rpb24obW9kZWwsIGRhdGEpew0KDQogIHN0YW5yZXNpZDwtbW9kZWwgJT4lIA0KICAgIHJzdGFuZGFyZCgpDQogIA0KICB5aGF0IDwtbW9kZWwgJT4lIA0KICAgIHByZWRpY3QoKQ0KICANCiAgcmVzaWQgPC0gbW9kZWwkcmVzaWR1YWxzDQogIA0KICBwcmVzc3JlcyA8LSBtb2RlbCAlPiUgDQogICAgUFJFU1MoKQ0KICANCiAgc3R1cmVzIDwtICBtb2RlbCAlPiUgDQogIHN0dWRyZXMoKQ0KICANCiAgc3RhbnJlcyA8LSBtb2RlbCAlPiUgDQogICAgcnN0YW5kYXJkKCkNCiAgZnVsbGRmPC1jYmluZChkYXRhJGNhcmRpb3Zhc2NfZGVhdGhzLCB5aGF0LCByZXNpZCwgc3RhbnJlcywgcHJlc3NyZXMsIHN0dXJlcykNCiAgZnVsbGRmID0gYXMuZGF0YS5mcmFtZShmdWxsZGYpDQogIG5hbWVzKGZ1bGxkZikgPC0gYygiWSIsIllfSGF0IiwgInJlc2lkdWFscyIsICJzdGFuZGFyZGl6ZWRfcmVzaWR1YWxzIiwgInByZXNzX3Jlc2lkdWFscyIsICJzdHVkZW50aXplZF9yZXNpZHVhbHMiKQ0KICByZXR1cm4oZnVsbGRmKQ0KfQ0KDQpyZXNpZHVhbHNfbW9kZWwxPC0gbW9kZWxfZXZhbChtb2RlbDEsIG1vZGVsX2RhdGEpDQoNCmVycm9yczwtIGRhdGEuZnJhbWUoIkZ1bGxfVGVzdCIgPSBudW1lcmljKCksIkxPT0NWIiA9IG51bWVyaWMoKSwiU2VnbWVudF9UZXN0IiA9IG51bWVyaWMoKSwiU2VnbWVudF9UcmFpbiIgPSBudW1lcmljKCkpDQplcnJvcnNbMSwxXSA8LSBzcXJ0KHN1bSgocmVzaWR1YWxzX21vZGVsMSRZIC0gcmVzaWR1YWxzX21vZGVsMSRZX0hhdCleMikvbGVuZ3RoKHJlc2lkdWFsc19tb2RlbDFbLDFdKSkNCnVwIDwtc3VtKHJlc2lkdWFsc19tb2RlbDEkcHJlc3NfcmVzaWR1YWxzXjIpL2xlbmd0aChyZXNpZHVhbHNfbW9kZWwxWywxXSkgICANCmVycm9yc1sxLDJdPC1zcXJ0KHVwKQ0KDQojU3BsaXQgZGF0YSBpbnRvIHRyYWluIGFuZCB0ZXN0IHNldHMgdG8gYXBwcm94aW1hdGUgZXJyb3IgZm9yIG5ldyBvYnMuDQpzZXQuc2VlZCg3ODkpDQp0cmFpbmluZ19zYW1wbGVzIDwtIG1vZGVsX2RhdGEkY2FyZGlvdmFzY19kZWF0aHMgJT4lIA0KICAgIGNyZWF0ZURhdGFQYXJ0aXRpb24ocD0gLjksIGxpc3Q9IEZBTFNFKQ0KDQp0cmFpbmluZ19zYW1wbGVzIDwtIHRyYWluaW5nX3NhbXBsZXMgJT4lIA0KICBhcy52ZWN0b3IoKQ0KDQp0cmFpbl9kYXRhIDwtIG1vZGVsX2RhdGFbdHJhaW5pbmdfc2FtcGxlcywgXQ0KdGVzdF9kYXRhIDwtIG1vZGVsX2RhdGFbLXRyYWluaW5nX3NhbXBsZXMsIF0NCg0KdHJhaW5fbW9kZWwgIDwtIGxtKGZvcm11bGEsIGRhdGE9dHJhaW5fZGF0YSkNCg0KcHJlZGljdGlvbnMgPC0gdHJhaW5fbW9kZWwgJT4lIA0KICBwcmVkaWN0KHRlc3RfZGF0YSkNCg0KZXJyb3JzWzEsM10gPC0gUk1TRShwcmVkaWN0aW9ucywgdGVzdF9kYXRhJGNhcmRpb3Zhc2NfZGVhdGhzKQ0KDQpwcmVkaWN0aW9ucyA8LSB0cmFpbl9tb2RlbCAlPiUgDQogIHByZWRpY3QodHJhaW5fZGF0YSkNCg0KZXJyb3JzWzEsNF0gPC0gUk1TRShwcmVkaWN0aW9ucywgdHJhaW5fZGF0YSRjYXJkaW92YXNjX2RlYXRocykNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChyZXNpZHVhbHNfbW9kZWwxLCBhZXMoIFksIFlfSGF0KSkrDQogICAgICAgbGFicyh4PSdBY3R1YWwnLCB5ID0nUHJlZGljdGVkJykrDQogICAgICAgZ2VvbV9wb2ludCgpKw0KICAgICAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0wLCBzbG9wZSA9ICAxLCBjb2xvcj0ncmVkJywgc2l6ZSA9MikNCg0KZ2dwbG90KHJlc2lkdWFsc19tb2RlbDEsIGFlcyggWV9IYXQsIHJlc2lkdWFscykpKw0KICBsYWJzKHg9J1ByZWRpY3RlZCcsIHkgPSdSZXNpZHVhbCcpKw0KICBnZW9tX3BvaW50KCkrDQogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9MCwgc2xvcGUgPSAgMCwgY29sb3I9J2JsdWUnLCBzaXplID0xKQ0KDQpnZ3Bsb3QocmVzaWR1YWxzX21vZGVsMSwgYWVzKCBZX0hhdCwgc3RhbmRhcmRpemVkX3Jlc2lkdWFscykpKw0KICBsYWJzKHg9J1ByZWRpY3RlZCcsIHkgPSdTdGFuZGFyZGl6ZWQgUmVzaWR1YWwnKSsNCiAgZ2VvbV9wb2ludCgpKw0KICBnZW9tX2FibGluZShpbnRlcmNlcHQgPTAsIHNsb3BlID0gIDAsIGNvbG9yPSdibHVlJywgc2l6ZSA9MSkNCg0KZ2dwbG90KHJlc2lkdWFsc19tb2RlbDEsIGFlcyggWV9IYXQsIHByZXNzX3Jlc2lkdWFscykpKw0KICBsYWJzKHg9J1ByZWRpY3RlZCcsIHkgPSdQUkVTUyBSZXNpZHVhbCcpKw0KICBnZW9tX3BvaW50KCkrDQogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9MCwgc2xvcGUgPSAgMCwgY29sb3I9J2JsdWUnLCBzaXplID0xKQ0KDQpnZ3Bsb3QocmVzaWR1YWxzX21vZGVsMSwgYWVzKCBZX0hhdCwgc3R1ZGVudGl6ZWRfcmVzaWR1YWxzKSkrDQogIGxhYnMoeD0nUHJlZGljdGVkJywgeSA9J1N0dWRlbnRpemVkIFJlc2lkdWFsJykrDQogIGdlb21fcG9pbnQoKSsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0wLCBzbG9wZSA9ICAwLCBjb2xvcj0nYmx1ZScsIHNpemUgPTEpDQoNCmBgYA0KIyMjIFNlY29uZCBNb2RlbA0KQXMgd2Ugc2VlIG91ciBtb2RlbCBhcHBlYXJzIHRvIGZpdCB3ZWxsLiBUaGVyZSBkb2VzIGFwcGVhciB0byBiZSBhIGRlZ3JlZSBvZiBoZXRlcm9za2VkYXN0aWNpdHkgaW4gdGhlIGhpZ2hlciByYW5nZXMgb2Ygb3VyIGRhdGEsIHRoaXMgY2FuIGJlIHNlZW4gaW4gYWxsIGZvdXIgcGxvdHMuIFdlIHdpbGwgdHJ5IHRvIG92ZXJjb21lIHRoaXMgYnkgaW50cm9kdWNpbmcgYSB0cmFuc2Zvcm1hdGlvbiBmb3Igb3VyIHJlc3BvbnNlIHZhcmlhYmxlLiANCg0KYGBge3J9DQpmb3JtdWxhIDwtIChsb2coY2FyZGlvdmFzY19kZWF0aHMpIH4gbWVhbl9ibWkgK3BlcmNlbnRfb2Jlc2l0eSArIHBlcmNlbnRfc2V2ZXJlX29iZXNpdHkgICsgR0RQX1Blcl9DYXBpdGEgK2ZhY3RvcihFbnRpdHkpICsgZmFjdG9yKFllYXIpKQ0KDQptb2RlbDIgIDwtIGxtKGZvcm11bGEsIGRhdGE9bW9kZWxfZGF0YSkNCg0KDQpyZXNpZHVhbHNfbW9kZWwyIDwtIG1vZGVsX2V2YWwobW9kZWwyLCBtb2RlbF9kYXRhKQ0KDQpyZXNpZHVhbHNfbW9kZWwyJFkgPC0gbG9nKHJlc2lkdWFsc19tb2RlbDIkWSkNCmBgYA0KDQpXZSBjYW4gZG8gdGhlIHNhbWUgYW5hbHlzaXMgb24gb3VyIGxvZy10cmFuc2Zvcm1lZCByZXNwb25zZSBtb2RlbC4gVGhlIHBsb3RzIGZvciB0aGVzZSBhcmUgcHJvdmlkZWQgYmVsb3cgYXMgd2VsbCBhcyB0aGUgcHJldmlvdXNseSBkZWZpbmVkIGZ1bmN0aW9uIHdoaWNoIHNhdmVzIG91ciByZXNpZHVhbHMsIGJvdGggTE9PQ1YsIGFuZCB0cmFpbi10ZXN0IHNwbGl0IGJhc2VkIGVycm9ycyBhcmUgc2F2ZWQuIA0KYGBge3J9DQpmb3JtdWxhIDwtIChsb2coY2FyZGlvdmFzY19kZWF0aHMpIH4gbWVhbl9ibWkgK3BlcmNlbnRfb2Jlc2l0eSArIHBlcmNlbnRfc2V2ZXJlX29iZXNpdHkgICsgR0RQX1Blcl9DYXBpdGEgK2ZhY3RvcihFbnRpdHkpICsgZmFjdG9yKFllYXIpKQ0KDQptb2RlbDIgIDwtIGxtKGZvcm11bGEsIGRhdGE9bW9kZWxfZGF0YSkNCg0KDQpyZXNpZHVhbHNfbW9kZWwyIDwtIG1vZGVsX2V2YWwobW9kZWwyLCBtb2RlbF9kYXRhKQ0KDQpyZXNpZHVhbHNfbW9kZWwyJFkgPC0gIGxvZyhyZXNpZHVhbHNfbW9kZWwyJFkpDQoNCmVycm9yc1syLDFdIDwtIFJNU0UoZXhwKHJlc2lkdWFsc19tb2RlbDIkWSksIGV4cChyZXNpZHVhbHNfbW9kZWwyJFlfSGF0KSkNCg0KWXJlc2lkIDwtIGV4cChyZXNpZHVhbHNfbW9kZWwyJFkpIC0gZXhwKHJlc2lkdWFsc19tb2RlbDIkWV9IYXQpDQpwciA8LSBZcmVzaWQvKDEtbG0uaW5mbHVlbmNlKG1vZGVsMikkaGF0KQ0KZXJyb3JzWzIsMl0gPC0gc3FydChzdW0ocHJeMikvbGVuZ3RoKHByKSkNCg0KdHJhaW5fbW9kZWwgIDwtIGxtKGZvcm11bGEsIGRhdGE9dHJhaW5fZGF0YSkNCg0KcHJlZGljdGlvbnMgPC0gdHJhaW5fbW9kZWwgJT4lIA0KICBwcmVkaWN0KHRlc3RfZGF0YSkgJT4lIA0KICBleHAoKQ0KDQplcnJvcnNbMiwzXSA8LSBSTVNFKHByZWRpY3Rpb25zLCB0ZXN0X2RhdGEkY2FyZGlvdmFzY19kZWF0aHMpDQoNCnByZWRpY3Rpb25zIDwtIHRyYWluX21vZGVsICU+JSANCiAgcHJlZGljdCh0cmFpbl9kYXRhKSAlPiUgDQogIGV4cCgpDQplcnJvcnNbMiw0XSA8LSBSTVNFKHByZWRpY3Rpb25zLCB0cmFpbl9kYXRhJGNhcmRpb3Zhc2NfZGVhdGhzKQ0KYGBgDQoNCg0KYGBge3J9DQoNCg0KI1NhbWUgcGxvdHMgYXMgYmVmb3JlDQpnZ3Bsb3QocmVzaWR1YWxzX21vZGVsMiwgYWVzKCBZLCBZX0hhdCkpKw0KICBsYWJzKHg9J0FjdHVhbCcsIHkgPSdQcmVkaWN0ZWQnKSsNCiAgZ2VvbV9wb2ludCgpKw0KICBnZW9tX2FibGluZShpbnRlcmNlcHQgPTAsIHNsb3BlID0gIDEsIGNvbG9yPSdyZWQnLCBzaXplID0yKQ0KDQpnZ3Bsb3QocmVzaWR1YWxzX21vZGVsMiwgYWVzKCBZX0hhdCwgcmVzaWR1YWxzKSkrDQogIGxhYnMoeD0nUHJlZGljdGVkJywgeSA9J1Jlc2lkdWFsJykrDQogIGdlb21fcG9pbnQoKSsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0wLCBzbG9wZSA9ICAwLCBjb2xvcj0nYmx1ZScsIHNpemUgPTEpDQoNCmdncGxvdChyZXNpZHVhbHNfbW9kZWwyLCBhZXMoIFlfSGF0LCBzdGFuZGFyZGl6ZWRfcmVzaWR1YWxzKSkrDQogIGxhYnMoeD0nUHJlZGljdGVkJywgeSA9J1N0YW5kYXJkaXplZCBSZXNpZHVhbCcpKw0KICBnZW9tX3BvaW50KCkrDQogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9MCwgc2xvcGUgPSAgMCwgY29sb3I9J2JsdWUnLCBzaXplID0xKQ0KDQpnZ3Bsb3QocmVzaWR1YWxzX21vZGVsMiwgYWVzKCBZX0hhdCwgcHJlc3NfcmVzaWR1YWxzKSkrDQogIGxhYnMoeD0nUHJlZGljdGVkJywgeSA9J1BSRVNTIFJlc2lkdWFsJykrDQogIGdlb21fcG9pbnQoKSsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0wLCBzbG9wZSA9ICAwLCBjb2xvcj0nYmx1ZScsIHNpemUgPTEpDQoNCmdncGxvdChyZXNpZHVhbHNfbW9kZWwyLCBhZXMoIFlfSGF0LCBzdHVkZW50aXplZF9yZXNpZHVhbHMpKSsNCiAgbGFicyh4PSdQcmVkaWN0ZWQnLCB5ID0nU3R1ZGVudGl6ZWQgUmVzaWR1YWwnKSsNCiAgZ2VvbV9wb2ludCgpKw0KICBnZW9tX2FibGluZShpbnRlcmNlcHQgPTAsIHNsb3BlID0gIDAsIGNvbG9yPSdibHVlJywgc2l6ZSA9MSkNCmBgYA0KIyMjIEZpbmFsIE1vZGVsIA0KSW4gdGhlIGFib3ZlIHBsb3RzIHdlIHNlZSBvdXIgc2Vjb25kIG1vZGVsIGRvZXMgYXBwZWFyIHRvIGZpdCB0aGUgZGF0YSBiZXR0ZXIuIEhvd2V2ZXIsIHRoZXJlIGFyZSBhIGNvdXBsZSBvZiBzZXJpb3VzIHByb2JsZW1zIHdpdGggdGhpcyBmb3JtIG9mIG91ciBtb2RlbC4gRmlyc3QsIGxvb2tpbmcgYXQgb3VyIGNyb3NzLXZhbGlkYXRpb24gbWV0aG9kcyB3ZSBzZWUgdGhhdCB0aGUgZXJyb3IgZm9yIHRoaXMgdGVybSBpcyBoaWdoZXIgaW4gYWxsIG1ldHJpY3MgdGhhbiB0aGUgcHJldmlvdXMgbW9kZWwuIEFkZGl0aW9uYWxseSwgdGhlcmUgaXMgZGlmZmljdWx0eSBpbiB0aGUgaW50ZXJwcmV0YXRpb24gb2Ygb3VyIGNvZWZmaWNpZW50cyB3aGVuIHdlIGludHJvZHVjZSB0cmFuc2Zvcm1hdGlvbnMgaW50byB0aGUgbW9kZWwuIA0KDQpXaXRoIHRoaXMgaW4gbWluZCBsZXQncyB0cnkgYSBmaW5hbCBkaWZmZXJlbnQgbW9kZWwuIE91ciB0d28gcHJldmlvdXMgbW9kZWxzIGhhdmUgYm90aCBiZWVuIGZpeGVkIGVmZmVjdHMsIHdlIG1heSBiZSBhYmxlIHRvIGltcHJvdmUgb3VyIG1vZGVsIGJ5IGludHJvZHVjaW5nIHdoYXQgYXJlIGtub3duIGFzIHJhbmRvbSBlZmZlY3RzIGJ5IGNvdW50cnkgZm9yIG1lYW5fYm1pLiBUaGVzZSByYW5kb20gZWZmZWN0cyBhbGxvdyB0aGUgc2xvcGUsIGZvciBtZWFuX2JtaSwgYW5kIHRoZSBpbnRlcmNlcHQgdG8gdmFyeSBkZXBlbmRpbmcgb24gdGhlIGVudGl0eS4gTE9PQ1YgYXJlIG5vdCBhcyBlYXN5IHRvIGRlcml2ZSBjb21wdXRhdGlvbmFsbHkgZm9yIHRoaXMgdHlwZSBvZiBtb2RlbCwgc28gd2Ugd2lsbCBoYXZlIHRvIG9taXQgdGhlbSBpbiBvdXIgbW9kZWwgZXZhbHVhdGlvbi4gVGhlIGV2YWx1YXRpb24gcGxvdHMgZm9yIHRoaXMgbW9kZWwgYXJlIHByb3ZpZGVkIGJlbG93Lg0KDQpgYGB7cn0NCmxpYnJhcnkobG1lNCkNCmZvcm11bGEgPC0gKGNhcmRpb3Zhc2NfZGVhdGhzIH4gcGVyY2VudF9vYmVzaXR5ICsgcGVyY2VudF9zZXZlcmVfb2Jlc2l0eSArIEdEUF9QZXJfQ2FwaXRhICsgMSsoMSArIG1lYW5fYm1pfEVudGl0eSkgKyBmYWN0b3IoWWVhcikpDQptb2RlbF8zIDwtIGxtZXIoZm9ybXVsYSwgZGF0YT1tb2RlbF9kYXRhLCBSRU1MPUZBTFNFKQ0KDQpwcmVkaWN0aW9ucyA8LSBtb2RlbF8zICU+JSANCiAgcHJlZGljdChtb2RlbF9kYXRhKQ0KDQplcnJvcnNbMywxXTwtIFJNU0UoIG1vZGVsX2RhdGEkY2FyZGlvdmFzY19kZWF0aHMsIHByZWRpY3Rpb25zKQ0KDQp0cmFpbl9tb2RlbCA8LSBsbWVyKGZvcm11bGEsIGRhdGE9dHJhaW5fZGF0YSwgUkVNTD1GQUxTRSkNCnByZWRpY3Rpb25zIDwtIHRyYWluX21vZGVsICU+JSANCiAgcHJlZGljdCh0ZXN0X2RhdGEpDQplcnJvcnNbMywzXTwtIFJNU0UoIHRlc3RfZGF0YSRjYXJkaW92YXNjX2RlYXRocywgcHJlZGljdGlvbnMpDQoNCnByZWRpY3Rpb25zIDwtIHRyYWluX21vZGVsICU+JSANCiAgcHJlZGljdCh0cmFpbl9kYXRhKQ0KZXJyb3JzWzMsNF08LSBSTVNFKCB0cmFpbl9kYXRhJGNhcmRpb3Zhc2NfZGVhdGhzLCBwcmVkaWN0aW9ucykNCg0KcHJlZGljdGlvbnMgPC0gbW9kZWxfMyAlPiUgDQogIHByZWRpY3QobW9kZWxfZGF0YSkNCnJlc2lkdWFsc19tb2RlbDMgPC0gYXMuZGF0YS5mcmFtZShjYmluZChwcmVkaWN0aW9ucywgbW9kZWxfZGF0YSRjYXJkaW92YXNjX2RlYXRocykpDQpyZXNpZHVhbHNfbW9kZWwzJHJlc2lkdWFscyA8LSByZXNpZHVhbHNfbW9kZWwzJFYyIC0gcmVzaWR1YWxzX21vZGVsMyRwcmVkaWN0aW9ucyANCm5hbWVzKHJlc2lkdWFsc19tb2RlbDMpWzJdIDwtICJhY3R1YWwiDQpyZXNpZHVhbHNfbW9kZWwzJEVudGl0eSA8LSBtb2RlbF9kYXRhJEVudGl0eQ0KDQpgYGANCmBgYHtyfQ0KDQpnZ3Bsb3QocmVzaWR1YWxzX21vZGVsMywgYWVzKCBhY3R1YWwsIHByZWRpY3Rpb25zKSkrDQogIGxhYnMoeD0nQWN0dWFsJywgeSA9J1ByZWRpY3RlZCcpKw0KICBnZW9tX3BvaW50KCkrDQogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9MCwgc2xvcGUgPSAgMSwgY29sb3I9J3JlZCcsIHNpemUgPTIpDQoNCmdncGxvdChyZXNpZHVhbHNfbW9kZWwzLCBhZXMoIHByZWRpY3Rpb25zLCByZXNpZHVhbHMpKSsNCiAgbGFicyh4PSdQcmVkaWN0ZWQnLCB5ID0nUmVzaWR1YWwnKSsNCiAgZ2VvbV9wb2ludCgpKw0KICBnZW9tX2FibGluZShpbnRlcmNlcHQgPTAsIHNsb3BlID0gIDAsIGNvbG9yPSdibHVlJywgc2l6ZSA9MSkNCmBgYA0KV2Ugc2VlIGZyb20gdGhlIGFib3ZlIHBsb3RzIHRoYXQgb3VyIG5ldyBtb2RlbCBmaXRzIHRoZSBkYXRhIHdlbGwuIEZyb20gb3VyIHJlc2lkdWFsIHBsb3QgaG93ZXZlciBpdCBkb2VzIGFwcGVhciBhcyB0aG91Z2ggd2UgaGF2ZSByZWludHJvZHVjZWQgaGV0ZXJvc2tlZGFzdGljaXR5LiBHZW5lcmFsbHkgaGV0ZXJvc2tlZGFzdGljaXR5IGlzIGNvbnNpZGVyZWQgdG8gYmUgbGVzcyBvZiBhIHNldmVyZSB2aW9sYXRpb24gb2Ygb3VyIGxpbmVhciByZWdyZXNzaW9uIGFzc3VtcHRpb25zLiAgDQoNCmBgYHtyfQ0KZXJyb3JzWzEsIDVdIDwtICJtb2RlbF8xIg0KZXJyb3JzWzIsIDVdIDwtICJtb2RlbF8yIg0KZXJyb3JzWzMsIDVdIDwtICJtb2RlbF8zIg0KbmFtZXMoZXJyb3JzKVs1XSA8LSAiTW9kZWwiDQogIA0KZ2dwbG90KGVycm9ycywgYWVzKHg9TW9kZWwsIHk9U2VnbWVudF9UZXN0ICkpKw0KICBsYWJzKCB4PSAiTW9kZWwiLCB5PSAiVGVzdCBSTVNFIikgKw0KICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIHdpZHRoPS4wMDkpDQpgYGANCiMjIyBNb2RlbCBDb21wYXJpc29uIGFuZCBDb25jbHVzaW9uIA0KUGxvdHRlZCBhYm92ZSBpcyB0aGUgdGVzdCBSTVNFIGZvciBhbGwgdGhyZWUgbW9kZWxzLiBXZSBzZWUgZnJvbSB0aGlzIHRoYXQgb3VyIHRoaXJkIG1vZGVsIGNsZWFybHkgZml0cyB0aGUgZGF0YSB0aGUgYmVzdCwgYWRkaXRpb25hbGx5IG5vbmUgb2YgdGhlIG1vZGVscyBzZWVtIHRvIHNlcmlvdXNseSB2aW9sYXRlIGxpbmVhciBhc3N1bXB0aW9ucy4gVXNpbmcgdGhpcyBpbmZvcm1hdGlvbiwgd2UgY2FuIG5vdyBwcmVkaWN0IHRoZSBkZWNyZWFzZSBpbiBkZWF0aHMgd2hpY2ggd291bGQgcmVzdWx0IGZyb20gYSBjb3VudHJ5IGRlY3JlYXNpbmcgdGhlaXIgbWVhbl9ibWkuIFRoaXMgaXMgYW4gZXh0cmVtZWx5IHVzZWZ1bCBpbnNpZ2h0IGZvciBwdWJsaWMgaGVhbHRoIG9mZmljaWFscywgYW5kIHRob3VnaCB0aGUgbW9kZWxzIHByZXNlbnRlZCBoZXJlIGFyZSByZWxhdGl2ZWx5IHNpbXBsZSwgc2hvdWxkIG5vdCBiZSB1bmRlcmVzdGltYXRlZC4gIA==