Introduction to Regression - Lesson 1

Putting it into perspective

✅ There are many types of regression methods, and which one you pick depends on the answer you’re looking for. If you want to predict the probable height for a person of a given age, you’d use linear regression, as you’re seeking a numeric value. If you’re interested in discovering whether a type of cuisine should be considered vegan or not, you’re looking for a category assignment so you would use logistic regression. You’ll learn more about logistic regression later. Think a bit about some questions you can ask of data, and which of these methods would be more appropriate.

In this section, you will work with a small dataset about diabetes. Imagine that you wanted to test a treatment for diabetic patients. Machine Learning models might help you determine which patients would respond better to the treatment, based on combinations of variables. Even a very basic regression model, when visualized, might show information about variables that would help you organize your theoretical clinical trials.

That said, let’s get started on this task!

Artwork by @allison_horst

1. Loading up our tool set

For this task, we’ll require the following packages:

You can have them installed as:

install.packages(c("tidyverse", "tidymodels"))

The script below checks whether you have the packages required to complete this module and installs them for you in case they are missing.

if (!require("pacman")) install.packages("pacman")
pacman::p_load(tidyverse, tidymodels)

Now, let’s load these awesome packages and make them available in our current R session. (This is for mere illustration, pacman::p_load() already did that for you)

# load the core Tidyverse packages
library(tidyverse)

# load the core Tidymodels packages
library(tidymodels)

2. The diabetes dataset

In this exercise, we’ll put our regression skills into display by making predictions on a diabetes dataset. The diabetes dataset includes 442 samples of data around diabetes, with 10 predictor feature variables, age, sex, body mass index, average blood pressure, and six blood serum measurements as well as an outcome variable y: a quantitative measure of disease progression one year after baseline.

Number of observations 442
Number of predictors First 10 columns are numeric predictive values
Outcome/Target Column 11 is a quantitative measure of disease progression one year after baseline
Predictor Information
  • age age in years
  • sex
  • bmi body mass index
  • bp average blood pressure
  • s1 tc, total serum cholesterol
  • s2 ldl, low-density lipoproteins
  • s3 hdl, high-density lipoproteins
  • s4 tch, total cholesterol / HDL
  • s5 ltg, possibly log of serum triglycerides level
  • s6 glu, blood sugar level

🎓 Remember, this is supervised learning, and we need a named ‘y’ target.

Before you can manipulate data with R, you need to import the data into R’s memory, or build a connection to the data that R can use to access the data remotely.

The readr package, which is part of the Tidyverse, provides a fast and friendly way to read rectangular data into R.

Now, let’s load the diabetes dataset provided in this source URL: https://www4.stat.ncsu.edu/~boos/var.select/diabetes.html

Also, we’ll perform a sanity check on our data using glimpse() and dsiplay the first 5 rows using slice().

Before going any further, let’s introduce something you will encounter quite often in R code: the pipe operator %>%

The pipe operator (%>%) performs operations in logical sequence by passing an object forward into a function or call expression. You can think of the pipe operator as saying “and then” in your code.

# Import the data set
diabetes <- read_table2(file = "https://www4.stat.ncsu.edu/~boos/var.select/diabetes.rwrite1.txt")


# Get a glimpse and dimensions of the data
glimpse(diabetes)
## Rows: 442
## Columns: 11
## $ age <dbl> 0.038075906, -0.001882017, 0.085298906, -0.089062939, 0.005383060,~
## $ sex <dbl> 0.05068012, -0.04464164, 0.05068012, -0.04464164, -0.04464164, -0.~
## $ bmi <dbl> 0.061696207, -0.051474061, 0.044451213, -0.011595015, -0.036384692~
## $ map <dbl> 0.021872355, -0.026327835, -0.005670611, -0.036656447, 0.021872355~
## $ tc  <dbl> -0.044223498, -0.008448724, -0.045599451, 0.012190569, 0.003934852~
## $ ldl <dbl> -3.482076e-02, -1.916334e-02, -3.419447e-02, 2.499059e-02, 1.55961~
## $ hdl <dbl> -0.043400846, 0.074411564, -0.032355932, -0.036037570, 0.008142084~
## $ tch <dbl> -0.002592262, -0.039493383, -0.002592262, 0.034308859, -0.00259226~
## $ ltg <dbl> 0.019908421, -0.068329744, 0.002863771, 0.022692023, -0.031991445,~
## $ glu <dbl> -0.017646125, -0.092204050, -0.025930339, -0.009361911, -0.0466408~
## $ y   <dbl> 151, 75, 141, 206, 135, 97, 138, 63, 110, 310, 101, 69, 179, 185, ~
# Select the first 5 rows of the data
diabetes %>% 
  slice(1:5)

glimpse() shows us that this data has 442 rows and 11 columns with all the columns being of data type double

glimpse() and slice() are functions in dplyr. Dplyr, part of the Tidyverse, is a grammar of data manipulation that provides a consistent set of verbs that help you solve the most common data manipulation challenges

Now that we have the data, let’s narrow down to one feature (bmi) to target for this exercise. This will require us to select the desired columns. So, how do we do this?

dplyr::select() allows us to select (and optionally rename) columns in a data frame.

# Select predictor feature `bmi` and outcome `y`
diabetes_select <- diabetes %>% 
  select(c(bmi, y))

# Print the first 5 rows
diabetes_select %>% 
  slice(1:5)

3. Training and Testing data

It’s common practice in supervised learning to split the data into two subsets; a (typically larger) set with which to train the model, and a smaller “hold-back” set with which to see how the model performed.

Now that we have data ready, we can see if a machine can help determine a logical split between the numbers in this dataset. We can use the rsample package, which is part of the Tidymodels framework, to create an object that contains the information on how to split the data, and then two more rsample functions to extract the created training and testing sets:

set.seed(2056)
# Split 67% of the data for training and the rest for testing
diabetes_split <- diabetes_select %>% 
  initial_split(prop = 0.67)

# Extract the resulting train and test sets
diabetes_train <- training(diabetes_split)
diabetes_test <- testing(diabetes_split)

# Print the first 3 rows of the training set
diabetes_train %>% 
  slice(1:3)

4. Train a linear regression model with Tidymodels

Now we are ready to train our model!

In Tidymodels, you specify models using parsnip() by specifying three concepts:

  • Model type differentiates models such as linear regression, logistic regression, decision tree models, and so forth.

  • Model mode includes common options like regression and classification; some model types support either of these while some only have one mode.

  • Model engine is the computational tool which will be used to fit the model. Often these are R packages, such as "lm" or "ranger"

This modeling information is captured in a model specification, so let’s build one!

# Build a linear model specification
lm_spec <- 
  # Type
  linear_reg() %>% 
  # Engine
  set_engine("lm") %>% 
  # Mode
  set_mode("regression")


# Print the model specification
lm_spec
## Linear Regression Model Specification (regression)
## 
## Computational engine: lm

After a model has been specified, the model can be estimated or trained using the fit() function, typically using a formula and some data.

y ~ . means we’ll fit y as the predicted quantity/target, explained by all the predictors/features ie, . (in this case, we only have one predictor: bmi )

# Build a linear model specification
lm_spec <- linear_reg() %>% 
  set_engine("lm") %>%
  set_mode("regression")


# Train a linear regression model
lm_mod <- lm_spec %>% 
  fit(y ~ ., data = diabetes_train)

# Print the model
lm_mod
## parsnip model object
## 
## 
## Call:
## stats::lm(formula = y ~ ., data = data)
## 
## Coefficients:
## (Intercept)          bmi  
##       154.7        996.3

From the model output, we can see the coefficients learned during training. They represent the coefficients of the line of best fit that gives us the lowest overall error between the actual and predicted variable.

5. Make predictions on the test set

Now that we’ve trained a model, we can use it to predict the disease progression y for the test dataset using parsnip::predict(). This will be used to draw the line between data groups.

# Make predictions for the test set
predictions <- lm_mod %>% 
  predict(new_data = diabetes_test)

# Print out some of the predictions
predictions %>% 
  slice(1:5)

Woohoo! 💃🕺 We just trained a model and used it to make predictions!

When making predictions, the tidymodels convention is to always produce a tibble/data frame of results with standardized column names. This makes it easy to combine the original data and the predictions in a usable format for subsequent operations such as plotting.

dplyr::bind_cols() efficiently binds multiple data frames column.

# Combine the predictions and the original test set
results <- diabetes_test %>% 
  bind_cols(predictions)


results %>% 
  slice(1:5)

6. Plot modelling results

Now, its time to see this visually 📈. We’ll create a scatter plot of all the y and bmi values of the test set, then use the predictions to draw a line in the most appropriate place, between the model’s data groupings.

R has several systems for making graphs, but ggplot2 is one of the most elegant and most versatile. This allows you to compose graphs by combining independent components.

# Set a theme for the plot
theme_set(theme_light())
# Create a scatter plot
results %>% 
  ggplot(aes(x = bmi)) +
  # Add a scatter plot
  geom_point(aes(y = y), size = 1.6) +
  # Add a line plot
  geom_line(aes(y = .pred), color = "blue", size = 1.5)

✅ Think a bit about what’s going on here. A straight line is running through many small dots of data, but what is it doing exactly? Can you see how you should be able to use this line to predict where a new, unseen data point should fit in relationship to the plot’s y axis? Try to put into words the practical use of this model.

Congratulations, you built your first linear regression model, created a prediction with it, and displayed it in a plot!

LS0tDQp0aXRsZTogJ0J1aWxkIGEgcmVncmVzc2lvbiBtb2RlbDogR2V0IHN0YXJ0ZWQgd2l0aCBSIGFuZCBUaWR5bW9kZWxzIGZvciByZWdyZXNzaW9uIG1vZGVscycNCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0aGVtZTogZmxhdGx5DQogICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KLS0tDQoNCiMjIEludHJvZHVjdGlvbiB0byBSZWdyZXNzaW9uIC0gTGVzc29uIDENCg0KIyMjIyBQdXR0aW5nIGl0IGludG8gcGVyc3BlY3RpdmUNCg0K4pyFIFRoZXJlIGFyZSBtYW55IHR5cGVzIG9mIHJlZ3Jlc3Npb24gbWV0aG9kcywgYW5kIHdoaWNoIG9uZSB5b3UgcGljayBkZXBlbmRzIG9uIHRoZSBhbnN3ZXIgeW91J3JlIGxvb2tpbmcgZm9yLiBJZiB5b3Ugd2FudCB0byBwcmVkaWN0IHRoZSBwcm9iYWJsZSBoZWlnaHQgZm9yIGEgcGVyc29uIG9mIGEgZ2l2ZW4gYWdlLCB5b3UnZCB1c2UgYGxpbmVhciByZWdyZXNzaW9uYCwgYXMgeW91J3JlIHNlZWtpbmcgYSAqKm51bWVyaWMgdmFsdWUqKi4gSWYgeW91J3JlIGludGVyZXN0ZWQgaW4gZGlzY292ZXJpbmcgd2hldGhlciBhIHR5cGUgb2YgY3Vpc2luZSBzaG91bGQgYmUgY29uc2lkZXJlZCB2ZWdhbiBvciBub3QsIHlvdSdyZSBsb29raW5nIGZvciBhICoqY2F0ZWdvcnkgYXNzaWdubWVudCoqIHNvIHlvdSB3b3VsZCB1c2UgYGxvZ2lzdGljIHJlZ3Jlc3Npb25gLiBZb3UnbGwgbGVhcm4gbW9yZSBhYm91dCBsb2dpc3RpYyByZWdyZXNzaW9uIGxhdGVyLiBUaGluayBhIGJpdCBhYm91dCBzb21lIHF1ZXN0aW9ucyB5b3UgY2FuIGFzayBvZiBkYXRhLCBhbmQgd2hpY2ggb2YgdGhlc2UgbWV0aG9kcyB3b3VsZCBiZSBtb3JlIGFwcHJvcHJpYXRlLg0KDQpJbiB0aGlzIHNlY3Rpb24sIHlvdSB3aWxsIHdvcmsgd2l0aCBhIFtzbWFsbCBkYXRhc2V0IGFib3V0IGRpYWJldGVzXShodHRwczovL3d3dzQuc3RhdC5uY3N1LmVkdS9+Ym9vcy92YXIuc2VsZWN0L2RpYWJldGVzLmh0bWwpLiBJbWFnaW5lIHRoYXQgeW91IHdhbnRlZCB0byB0ZXN0IGEgdHJlYXRtZW50IGZvciBkaWFiZXRpYyBwYXRpZW50cy4gTWFjaGluZSBMZWFybmluZyBtb2RlbHMgbWlnaHQgaGVscCB5b3UgZGV0ZXJtaW5lIHdoaWNoIHBhdGllbnRzIHdvdWxkIHJlc3BvbmQgYmV0dGVyIHRvIHRoZSB0cmVhdG1lbnQsIGJhc2VkIG9uIGNvbWJpbmF0aW9ucyBvZiB2YXJpYWJsZXMuIEV2ZW4gYSB2ZXJ5IGJhc2ljIHJlZ3Jlc3Npb24gbW9kZWwsIHdoZW4gdmlzdWFsaXplZCwgbWlnaHQgc2hvdyBpbmZvcm1hdGlvbiBhYm91dCB2YXJpYWJsZXMgdGhhdCB3b3VsZCBoZWxwIHlvdSBvcmdhbml6ZSB5b3VyIHRoZW9yZXRpY2FsIGNsaW5pY2FsIHRyaWFscy4NCg0KVGhhdCBzYWlkLCBsZXQncyBnZXQgc3RhcnRlZCBvbiB0aGlzIHRhc2shDQoNCiFbQXJ0d29yayBieSBcQGFsbGlzb25faG9yc3RdKC4uLy4uL2ltYWdlcy9lbmNvdVJhZ2UuanBnKXt3aWR0aD0iNjMwIn0NCg0KIyMgMS4gTG9hZGluZyB1cCBvdXIgdG9vbCBzZXQNCg0KRm9yIHRoaXMgdGFzaywgd2UnbGwgcmVxdWlyZSB0aGUgZm9sbG93aW5nIHBhY2thZ2VzOg0KDQotICAgYHRpZHl2ZXJzZWA6IFRoZSBbdGlkeXZlcnNlXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLykgaXMgYSBbY29sbGVjdGlvbiBvZiBSIHBhY2thZ2VzXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnL3BhY2thZ2VzKSBkZXNpZ25lZCB0byBtYWtlcyBkYXRhIHNjaWVuY2UgZmFzdGVyLCBlYXNpZXIgYW5kIG1vcmUgZnVuIQ0KDQotICAgYHRpZHltb2RlbHNgOiBUaGUgW3RpZHltb2RlbHNdKGh0dHBzOi8vd3d3LnRpZHltb2RlbHMub3JnLykgZnJhbWV3b3JrIGlzIGEgW2NvbGxlY3Rpb24gb2YgcGFja2FnZXNdKGh0dHBzOi8vd3d3LnRpZHltb2RlbHMub3JnL3BhY2thZ2VzLykgZm9yIG1vZGVsaW5nIGFuZCBtYWNoaW5lIGxlYXJuaW5nLg0KDQpZb3UgY2FuIGhhdmUgdGhlbSBpbnN0YWxsZWQgYXM6DQoNCmBpbnN0YWxsLnBhY2thZ2VzKGMoInRpZHl2ZXJzZSIsICJ0aWR5bW9kZWxzIikpYA0KDQpUaGUgc2NyaXB0IGJlbG93IGNoZWNrcyB3aGV0aGVyIHlvdSBoYXZlIHRoZSBwYWNrYWdlcyByZXF1aXJlZCB0byBjb21wbGV0ZSB0aGlzIG1vZHVsZSBhbmQgaW5zdGFsbHMgdGhlbSBmb3IgeW91IGluIGNhc2UgdGhleSBhcmUgbWlzc2luZy4NCg0KYGBge3IsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0KaWYgKCFyZXF1aXJlKCJwYWNtYW4iKSkgaW5zdGFsbC5wYWNrYWdlcygicGFjbWFuIikNCnBhY21hbjo6cF9sb2FkKHRpZHl2ZXJzZSwgdGlkeW1vZGVscykNCmBgYA0KDQpOb3csIGxldCdzIGxvYWQgdGhlc2UgYXdlc29tZSBwYWNrYWdlcyBhbmQgbWFrZSB0aGVtIGF2YWlsYWJsZSBpbiBvdXIgY3VycmVudCBSIHNlc3Npb24uIChUaGlzIGlzIGZvciBtZXJlIGlsbHVzdHJhdGlvbiwgYHBhY21hbjo6cF9sb2FkKClgIGFscmVhZHkgZGlkIHRoYXQgZm9yIHlvdSkNCg0KYGBge3IgbG9hZF90aWR5X3ZlcnNlX21vZGVscywgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9DQojIGxvYWQgdGhlIGNvcmUgVGlkeXZlcnNlIHBhY2thZ2VzDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KIyBsb2FkIHRoZSBjb3JlIFRpZHltb2RlbHMgcGFja2FnZXMNCmxpYnJhcnkodGlkeW1vZGVscykNCg0KDQpgYGANCg0KIyMgMi4gVGhlIGRpYWJldGVzIGRhdGFzZXQNCg0KSW4gdGhpcyBleGVyY2lzZSwgd2UnbGwgcHV0IG91ciByZWdyZXNzaW9uIHNraWxscyBpbnRvIGRpc3BsYXkgYnkgbWFraW5nIHByZWRpY3Rpb25zIG9uIGEgZGlhYmV0ZXMgZGF0YXNldC4gVGhlIFtkaWFiZXRlcyBkYXRhc2V0XShodHRwczovL3d3dzQuc3RhdC5uY3N1LmVkdS9+Ym9vcy92YXIuc2VsZWN0L2RpYWJldGVzLnJ3cml0ZTEudHh0KSBpbmNsdWRlcyBgNDQyIHNhbXBsZXNgIG9mIGRhdGEgYXJvdW5kIGRpYWJldGVzLCB3aXRoIDEwIHByZWRpY3RvciBmZWF0dXJlIHZhcmlhYmxlcywgYGFnZWAsIGBzZXhgLCBgYm9keSBtYXNzIGluZGV4YCwgYGF2ZXJhZ2UgYmxvb2QgcHJlc3N1cmVgLCBhbmQgYHNpeCBibG9vZCBzZXJ1bSBtZWFzdXJlbWVudHNgIGFzIHdlbGwgYXMgYW4gb3V0Y29tZSB2YXJpYWJsZSBgeWA6IGEgcXVhbnRpdGF0aXZlIG1lYXN1cmUgb2YgZGlzZWFzZSBwcm9ncmVzc2lvbiBvbmUgeWVhciBhZnRlciBiYXNlbGluZS4NCg0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCAqKk51bWJlciBvZiBvYnNlcnZhdGlvbnMqKiB8ICoqNDQyKiogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKz09PT09PT09PT09PT09PT09PT09PT09PT09PT0rPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Kw0KfCAqKk51bWJlciBvZiBwcmVkaWN0b3JzKiogICB8IEZpcnN0IDEwIGNvbHVtbnMgYXJlIG51bWVyaWMgcHJlZGljdGl2ZSB2YWx1ZXMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCAqKk91dGNvbWUvVGFyZ2V0KiogICAgICAgICB8IENvbHVtbiAxMSBpcyBhIHF1YW50aXRhdGl2ZSBtZWFzdXJlIG9mIGRpc2Vhc2UgcHJvZ3Jlc3Npb24gb25lIHllYXIgYWZ0ZXIgYmFzZWxpbmUgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCAqKlByZWRpY3RvciBJbmZvcm1hdGlvbioqICB8IC0gICBhZ2UgYWdlIGluIHllYXJzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IC0gICBzZXggICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IC0gICBibWkgYm9keSBtYXNzIGluZGV4ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IC0gICBicCBhdmVyYWdlIGJsb29kIHByZXNzdXJlICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IC0gICBzMSB0YywgdG90YWwgc2VydW0gY2hvbGVzdGVyb2wgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IC0gICBzMiBsZGwsIGxvdy1kZW5zaXR5IGxpcG9wcm90ZWlucyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IC0gICBzMyBoZGwsIGhpZ2gtZGVuc2l0eSBsaXBvcHJvdGVpbnMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IC0gICBzNCB0Y2gsIHRvdGFsIGNob2xlc3Rlcm9sIC8gSERMICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IC0gICBzNSBsdGcsIHBvc3NpYmx5IGxvZyBvZiBzZXJ1bSB0cmlnbHljZXJpZGVzIGxldmVsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IC0gICBzNiBnbHUsIGJsb29kIHN1Z2FyIGxldmVsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KDQo+IPCfjpMgUmVtZW1iZXIsIHRoaXMgaXMgc3VwZXJ2aXNlZCBsZWFybmluZywgYW5kIHdlIG5lZWQgYSBuYW1lZCAneScgdGFyZ2V0Lg0KDQpCZWZvcmUgeW91IGNhbiBtYW5pcHVsYXRlIGRhdGEgd2l0aCBSLCB5b3UgbmVlZCB0byBpbXBvcnQgdGhlIGRhdGEgaW50byBSJ3MgbWVtb3J5LCBvciBidWlsZCBhIGNvbm5lY3Rpb24gdG8gdGhlIGRhdGEgdGhhdCBSIGNhbiB1c2UgdG8gYWNjZXNzIHRoZSBkYXRhIHJlbW90ZWx5LlwNCg0KPiBUaGUgW3JlYWRyXShodHRwczovL3JlYWRyLnRpZHl2ZXJzZS5vcmcvKSBwYWNrYWdlLCB3aGljaCBpcyBwYXJ0IG9mIHRoZSBUaWR5dmVyc2UsIHByb3ZpZGVzIGEgZmFzdCBhbmQgZnJpZW5kbHkgd2F5IHRvIHJlYWQgcmVjdGFuZ3VsYXIgZGF0YSBpbnRvIFIuDQoNCk5vdywgbGV0J3MgbG9hZCB0aGUgZGlhYmV0ZXMgZGF0YXNldCBwcm92aWRlZCBpbiB0aGlzIHNvdXJjZSBVUkw6IDxodHRwczovL3d3dzQuc3RhdC5uY3N1LmVkdS9+Ym9vcy92YXIuc2VsZWN0L2RpYWJldGVzLmh0bWw+DQoNCkFsc28sIHdlJ2xsIHBlcmZvcm0gYSBzYW5pdHkgY2hlY2sgb24gb3VyIGRhdGEgdXNpbmcgYGdsaW1wc2UoKWAgYW5kIGRzaXBsYXkgdGhlIGZpcnN0IDUgcm93cyB1c2luZyBgc2xpY2UoKWAuDQoNCkJlZm9yZSBnb2luZyBhbnkgZnVydGhlciwgbGV0J3MgaW50cm9kdWNlIHNvbWV0aGluZyB5b3Ugd2lsbCBlbmNvdW50ZXIgcXVpdGUgb2Z0ZW4gaW4gUiBjb2RlOiB0aGUgcGlwZSBvcGVyYXRvciBgJT4lYA0KDQpUaGUgcGlwZSBvcGVyYXRvciAoYCU+JWApIHBlcmZvcm1zIG9wZXJhdGlvbnMgaW4gbG9naWNhbCBzZXF1ZW5jZSBieSBwYXNzaW5nIGFuIG9iamVjdCBmb3J3YXJkIGludG8gYSBmdW5jdGlvbiBvciBjYWxsIGV4cHJlc3Npb24uIFlvdSBjYW4gdGhpbmsgb2YgdGhlIHBpcGUgb3BlcmF0b3IgYXMgc2F5aW5nICJhbmQgdGhlbiIgaW4geW91ciBjb2RlLlwNCg0KYGBge3IgbG9hZF9kYXRhc2V0LCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0NCiMgSW1wb3J0IHRoZSBkYXRhIHNldA0KZGlhYmV0ZXMgPC0gcmVhZF90YWJsZTIoZmlsZSA9ICJodHRwczovL3d3dzQuc3RhdC5uY3N1LmVkdS9+Ym9vcy92YXIuc2VsZWN0L2RpYWJldGVzLnJ3cml0ZTEudHh0IikNCg0KDQojIEdldCBhIGdsaW1wc2UgYW5kIGRpbWVuc2lvbnMgb2YgdGhlIGRhdGENCmdsaW1wc2UoZGlhYmV0ZXMpDQoNCg0KIyBTZWxlY3QgdGhlIGZpcnN0IDUgcm93cyBvZiB0aGUgZGF0YQ0KZGlhYmV0ZXMgJT4lIA0KICBzbGljZSgxOjUpDQoNCmBgYA0KDQpgZ2xpbXBzZSgpYCBzaG93cyB1cyB0aGF0IHRoaXMgZGF0YSBoYXMgNDQyIHJvd3MgYW5kIDExIGNvbHVtbnMgd2l0aCBhbGwgdGhlIGNvbHVtbnMgYmVpbmcgb2YgZGF0YSB0eXBlIGBkb3VibGVgDQoNCj4gZ2xpbXBzZSgpIGFuZCBzbGljZSgpIGFyZSBmdW5jdGlvbnMgaW4gW2BkcGx5cmBdKGh0dHBzOi8vZHBseXIudGlkeXZlcnNlLm9yZy8pLiBEcGx5ciwgcGFydCBvZiB0aGUgVGlkeXZlcnNlLCBpcyBhIGdyYW1tYXIgb2YgZGF0YSBtYW5pcHVsYXRpb24gdGhhdCBwcm92aWRlcyBhIGNvbnNpc3RlbnQgc2V0IG9mIHZlcmJzIHRoYXQgaGVscCB5b3Ugc29sdmUgdGhlIG1vc3QgY29tbW9uIGRhdGEgbWFuaXB1bGF0aW9uIGNoYWxsZW5nZXMNCg0KTm93IHRoYXQgd2UgaGF2ZSB0aGUgZGF0YSwgbGV0J3MgbmFycm93IGRvd24gdG8gb25lIGZlYXR1cmUgKGBibWlgKSB0byB0YXJnZXQgZm9yIHRoaXMgZXhlcmNpc2UuIFRoaXMgd2lsbCByZXF1aXJlIHVzIHRvIHNlbGVjdCB0aGUgZGVzaXJlZCBjb2x1bW5zLiBTbywgaG93IGRvIHdlIGRvIHRoaXM/DQoNCltgZHBseXI6OnNlbGVjdCgpYF0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9zZWxlY3QuaHRtbCkgYWxsb3dzIHVzIHRvICpzZWxlY3QqIChhbmQgb3B0aW9uYWxseSByZW5hbWUpIGNvbHVtbnMgaW4gYSBkYXRhIGZyYW1lLg0KDQpgYGB7ciBzZWxlY3QsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0KIyBTZWxlY3QgcHJlZGljdG9yIGZlYXR1cmUgYGJtaWAgYW5kIG91dGNvbWUgYHlgDQpkaWFiZXRlc19zZWxlY3QgPC0gZGlhYmV0ZXMgJT4lIA0KICBzZWxlY3QoYyhibWksIHkpKQ0KDQojIFByaW50IHRoZSBmaXJzdCA1IHJvd3MNCmRpYWJldGVzX3NlbGVjdCAlPiUgDQogIHNsaWNlKDE6NSkNCmBgYA0KDQojIyAzLiBUcmFpbmluZyBhbmQgVGVzdGluZyBkYXRhDQoNCkl0J3MgY29tbW9uIHByYWN0aWNlIGluIHN1cGVydmlzZWQgbGVhcm5pbmcgdG8gKnNwbGl0KiB0aGUgZGF0YSBpbnRvIHR3byBzdWJzZXRzOyBhICh0eXBpY2FsbHkgbGFyZ2VyKSBzZXQgd2l0aCB3aGljaCB0byB0cmFpbiB0aGUgbW9kZWwsIGFuZCBhIHNtYWxsZXIgImhvbGQtYmFjayIgc2V0IHdpdGggd2hpY2ggdG8gc2VlIGhvdyB0aGUgbW9kZWwgcGVyZm9ybWVkLg0KDQpOb3cgdGhhdCB3ZSBoYXZlIGRhdGEgcmVhZHksIHdlIGNhbiBzZWUgaWYgYSBtYWNoaW5lIGNhbiBoZWxwIGRldGVybWluZSBhIGxvZ2ljYWwgc3BsaXQgYmV0d2VlbiB0aGUgbnVtYmVycyBpbiB0aGlzIGRhdGFzZXQuIFdlIGNhbiB1c2UgdGhlIFtyc2FtcGxlXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3JzYW1wbGUvKSBwYWNrYWdlLCB3aGljaCBpcyBwYXJ0IG9mIHRoZSBUaWR5bW9kZWxzIGZyYW1ld29yaywgdG8gY3JlYXRlIGFuIG9iamVjdCB0aGF0IGNvbnRhaW5zIHRoZSBpbmZvcm1hdGlvbiBvbiAqaG93KiB0byBzcGxpdCB0aGUgZGF0YSwgYW5kIHRoZW4gdHdvIG1vcmUgcnNhbXBsZSBmdW5jdGlvbnMgdG8gZXh0cmFjdCB0aGUgY3JlYXRlZCB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzOg0KDQpgYGB7ciBzcGxpdCwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9DQpzZXQuc2VlZCgyMDU2KQ0KIyBTcGxpdCA2NyUgb2YgdGhlIGRhdGEgZm9yIHRyYWluaW5nIGFuZCB0aGUgcmVzdCBmb3IgdGVzdGluZw0KZGlhYmV0ZXNfc3BsaXQgPC0gZGlhYmV0ZXNfc2VsZWN0ICU+JSANCiAgaW5pdGlhbF9zcGxpdChwcm9wID0gMC42NykNCg0KIyBFeHRyYWN0IHRoZSByZXN1bHRpbmcgdHJhaW4gYW5kIHRlc3Qgc2V0cw0KZGlhYmV0ZXNfdHJhaW4gPC0gdHJhaW5pbmcoZGlhYmV0ZXNfc3BsaXQpDQpkaWFiZXRlc190ZXN0IDwtIHRlc3RpbmcoZGlhYmV0ZXNfc3BsaXQpDQoNCiMgUHJpbnQgdGhlIGZpcnN0IDMgcm93cyBvZiB0aGUgdHJhaW5pbmcgc2V0DQpkaWFiZXRlc190cmFpbiAlPiUgDQogIHNsaWNlKDE6MykNCg0KYGBgDQoNCiMjIDQuIFRyYWluIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgd2l0aCBUaWR5bW9kZWxzDQoNCk5vdyB3ZSBhcmUgcmVhZHkgdG8gdHJhaW4gb3VyIG1vZGVsIQ0KDQpJbiBUaWR5bW9kZWxzLCB5b3Ugc3BlY2lmeSBtb2RlbHMgdXNpbmcgYHBhcnNuaXAoKWAgYnkgc3BlY2lmeWluZyB0aHJlZSBjb25jZXB0czoNCg0KLSAgIE1vZGVsICoqdHlwZSoqIGRpZmZlcmVudGlhdGVzIG1vZGVscyBzdWNoIGFzIGxpbmVhciByZWdyZXNzaW9uLCBsb2dpc3RpYyByZWdyZXNzaW9uLCBkZWNpc2lvbiB0cmVlIG1vZGVscywgYW5kIHNvIGZvcnRoLg0KDQotICAgTW9kZWwgKiptb2RlKiogaW5jbHVkZXMgY29tbW9uIG9wdGlvbnMgbGlrZSByZWdyZXNzaW9uIGFuZCBjbGFzc2lmaWNhdGlvbjsgc29tZSBtb2RlbCB0eXBlcyBzdXBwb3J0IGVpdGhlciBvZiB0aGVzZSB3aGlsZSBzb21lIG9ubHkgaGF2ZSBvbmUgbW9kZS4NCg0KLSAgIE1vZGVsICoqZW5naW5lKiogaXMgdGhlIGNvbXB1dGF0aW9uYWwgdG9vbCB3aGljaCB3aWxsIGJlIHVzZWQgdG8gZml0IHRoZSBtb2RlbC4gT2Z0ZW4gdGhlc2UgYXJlIFIgcGFja2FnZXMsIHN1Y2ggYXMgKipgImxtImAqKiBvciAqKmAicmFuZ2VyImAqKg0KDQpUaGlzIG1vZGVsaW5nIGluZm9ybWF0aW9uIGlzIGNhcHR1cmVkIGluIGEgbW9kZWwgc3BlY2lmaWNhdGlvbiwgc28gbGV0J3MgYnVpbGQgb25lIQ0KDQpgYGB7ciBsbV9tb2RlbF9zcGVjLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0NCiMgQnVpbGQgYSBsaW5lYXIgbW9kZWwgc3BlY2lmaWNhdGlvbg0KbG1fc3BlYyA8LSANCiAgIyBUeXBlDQogIGxpbmVhcl9yZWcoKSAlPiUgDQogICMgRW5naW5lDQogIHNldF9lbmdpbmUoImxtIikgJT4lIA0KICAjIE1vZGUNCiAgc2V0X21vZGUoInJlZ3Jlc3Npb24iKQ0KDQoNCiMgUHJpbnQgdGhlIG1vZGVsIHNwZWNpZmljYXRpb24NCmxtX3NwZWMNCg0KYGBgDQoNCkFmdGVyIGEgbW9kZWwgaGFzIGJlZW4gKnNwZWNpZmllZCosIHRoZSBtb2RlbCBjYW4gYmUgYGVzdGltYXRlZGAgb3IgYHRyYWluZWRgIHVzaW5nIHRoZSBbYGZpdCgpYF0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9maXQuaHRtbCkgZnVuY3Rpb24sIHR5cGljYWxseSB1c2luZyBhIGZvcm11bGEgYW5kIHNvbWUgZGF0YS4NCg0KYHkgfiAuYCBtZWFucyB3ZSdsbCBmaXQgYHlgIGFzIHRoZSBwcmVkaWN0ZWQgcXVhbnRpdHkvdGFyZ2V0LCBleHBsYWluZWQgYnkgYWxsIHRoZSBwcmVkaWN0b3JzL2ZlYXR1cmVzIGllLCBgLmAgKGluIHRoaXMgY2FzZSwgd2Ugb25seSBoYXZlIG9uZSBwcmVkaWN0b3I6IGBibWlgICkNCg0KYGBge3IgdHJhaW4sIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0KIyBCdWlsZCBhIGxpbmVhciBtb2RlbCBzcGVjaWZpY2F0aW9uDQpsbV9zcGVjIDwtIGxpbmVhcl9yZWcoKSAlPiUgDQogIHNldF9lbmdpbmUoImxtIikgJT4lDQogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikNCg0KDQojIFRyYWluIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwNCmxtX21vZCA8LSBsbV9zcGVjICU+JSANCiAgZml0KHkgfiAuLCBkYXRhID0gZGlhYmV0ZXNfdHJhaW4pDQoNCiMgUHJpbnQgdGhlIG1vZGVsDQpsbV9tb2QNCmBgYA0KDQpGcm9tIHRoZSBtb2RlbCBvdXRwdXQsIHdlIGNhbiBzZWUgdGhlIGNvZWZmaWNpZW50cyBsZWFybmVkIGR1cmluZyB0cmFpbmluZy4gVGhleSByZXByZXNlbnQgdGhlIGNvZWZmaWNpZW50cyBvZiB0aGUgbGluZSBvZiBiZXN0IGZpdCB0aGF0IGdpdmVzIHVzIHRoZSBsb3dlc3Qgb3ZlcmFsbCBlcnJvciBiZXR3ZWVuIHRoZSBhY3R1YWwgYW5kIHByZWRpY3RlZCB2YXJpYWJsZS4NCg0KIyMgNS4gTWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBzZXQNCg0KTm93IHRoYXQgd2UndmUgdHJhaW5lZCBhIG1vZGVsLCB3ZSBjYW4gdXNlIGl0IHRvIHByZWRpY3QgdGhlIGRpc2Vhc2UgcHJvZ3Jlc3Npb24geSBmb3IgdGhlIHRlc3QgZGF0YXNldCB1c2luZyBbcGFyc25pcDo6cHJlZGljdCgpXShodHRwczovL3BhcnNuaXAudGlkeW1vZGVscy5vcmcvcmVmZXJlbmNlL3ByZWRpY3QubW9kZWxfZml0Lmh0bWwpLiBUaGlzIHdpbGwgYmUgdXNlZCB0byBkcmF3IHRoZSBsaW5lIGJldHdlZW4gZGF0YSBncm91cHMuDQoNCmBgYHtyIHRlc3QsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0KIyBNYWtlIHByZWRpY3Rpb25zIGZvciB0aGUgdGVzdCBzZXQNCnByZWRpY3Rpb25zIDwtIGxtX21vZCAlPiUgDQogIHByZWRpY3QobmV3X2RhdGEgPSBkaWFiZXRlc190ZXN0KQ0KDQojIFByaW50IG91dCBzb21lIG9mIHRoZSBwcmVkaWN0aW9ucw0KcHJlZGljdGlvbnMgJT4lIA0KICBzbGljZSgxOjUpDQpgYGANCg0KV29vaG9vISDwn5KD8J+VuiBXZSBqdXN0IHRyYWluZWQgYSBtb2RlbCBhbmQgdXNlZCBpdCB0byBtYWtlIHByZWRpY3Rpb25zIQ0KDQpXaGVuIG1ha2luZyBwcmVkaWN0aW9ucywgdGhlIHRpZHltb2RlbHMgY29udmVudGlvbiBpcyB0byBhbHdheXMgcHJvZHVjZSBhIHRpYmJsZS9kYXRhIGZyYW1lIG9mIHJlc3VsdHMgd2l0aCBzdGFuZGFyZGl6ZWQgY29sdW1uIG5hbWVzLiBUaGlzIG1ha2VzIGl0IGVhc3kgdG8gY29tYmluZSB0aGUgb3JpZ2luYWwgZGF0YSBhbmQgdGhlIHByZWRpY3Rpb25zIGluIGEgdXNhYmxlIGZvcm1hdCBmb3Igc3Vic2VxdWVudCBvcGVyYXRpb25zIHN1Y2ggYXMgcGxvdHRpbmcuDQoNCmBkcGx5cjo6YmluZF9jb2xzKClgIGVmZmljaWVudGx5IGJpbmRzIG11bHRpcGxlIGRhdGEgZnJhbWVzIGNvbHVtbi4NCg0KYGBge3IgdGVzdF9wcmVkLCBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0NCiMgQ29tYmluZSB0aGUgcHJlZGljdGlvbnMgYW5kIHRoZSBvcmlnaW5hbCB0ZXN0IHNldA0KcmVzdWx0cyA8LSBkaWFiZXRlc190ZXN0ICU+JSANCiAgYmluZF9jb2xzKHByZWRpY3Rpb25zKQ0KDQoNCnJlc3VsdHMgJT4lIA0KICBzbGljZSgxOjUpDQpgYGANCg0KIyMgNi4gUGxvdCBtb2RlbGxpbmcgcmVzdWx0cw0KDQpOb3csIGl0cyB0aW1lIHRvIHNlZSB0aGlzIHZpc3VhbGx5IPCfk4guIFdlJ2xsIGNyZWF0ZSBhIHNjYXR0ZXIgcGxvdCBvZiBhbGwgdGhlIGB5YCBhbmQgYGJtaWAgdmFsdWVzIG9mIHRoZSB0ZXN0IHNldCwgdGhlbiB1c2UgdGhlIHByZWRpY3Rpb25zIHRvIGRyYXcgYSBsaW5lIGluIHRoZSBtb3N0IGFwcHJvcHJpYXRlIHBsYWNlLCBiZXR3ZWVuIHRoZSBtb2RlbCdzIGRhdGEgZ3JvdXBpbmdzLg0KDQpSIGhhcyBzZXZlcmFsIHN5c3RlbXMgZm9yIG1ha2luZyBncmFwaHMsIGJ1dCBgZ2dwbG90MmAgaXMgb25lIG9mIHRoZSBtb3N0IGVsZWdhbnQgYW5kIG1vc3QgdmVyc2F0aWxlLiBUaGlzIGFsbG93cyB5b3UgdG8gY29tcG9zZSBncmFwaHMgYnkgKipjb21iaW5pbmcgaW5kZXBlbmRlbnQgY29tcG9uZW50cyoqLg0KDQpgYGB7ciBwbG90X3ByZWQsIG1lc3NhZ2U9Riwgd2FybmluZz1GfQ0KIyBTZXQgYSB0aGVtZSBmb3IgdGhlIHBsb3QNCnRoZW1lX3NldCh0aGVtZV9saWdodCgpKQ0KIyBDcmVhdGUgYSBzY2F0dGVyIHBsb3QNCnJlc3VsdHMgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBibWkpKSArDQogICMgQWRkIGEgc2NhdHRlciBwbG90DQogIGdlb21fcG9pbnQoYWVzKHkgPSB5KSwgc2l6ZSA9IDEuNikgKw0KICAjIEFkZCBhIGxpbmUgcGxvdA0KICBnZW9tX2xpbmUoYWVzKHkgPSAucHJlZCksIGNvbG9yID0gImJsdWUiLCBzaXplID0gMS41KQ0KICANCmBgYA0KDQo+IOKchSBUaGluayBhIGJpdCBhYm91dCB3aGF0J3MgZ29pbmcgb24gaGVyZS4gQSBzdHJhaWdodCBsaW5lIGlzIHJ1bm5pbmcgdGhyb3VnaCBtYW55IHNtYWxsIGRvdHMgb2YgZGF0YSwgYnV0IHdoYXQgaXMgaXQgZG9pbmcgZXhhY3RseT8gQ2FuIHlvdSBzZWUgaG93IHlvdSBzaG91bGQgYmUgYWJsZSB0byB1c2UgdGhpcyBsaW5lIHRvIHByZWRpY3Qgd2hlcmUgYSBuZXcsIHVuc2VlbiBkYXRhIHBvaW50IHNob3VsZCBmaXQgaW4gcmVsYXRpb25zaGlwIHRvIHRoZSBwbG90J3MgeSBheGlzPyBUcnkgdG8gcHV0IGludG8gd29yZHMgdGhlIHByYWN0aWNhbCB1c2Ugb2YgdGhpcyBtb2RlbC4NCg0KQ29uZ3JhdHVsYXRpb25zLCB5b3UgYnVpbHQgeW91ciBmaXJzdCBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCwgY3JlYXRlZCBhIHByZWRpY3Rpb24gd2l0aCBpdCwgYW5kIGRpc3BsYXllZCBpdCBpbiBhIHBsb3QhDQo=