Overview

In this walkthrough, we’ll explore how pre-process data using the recipes package. Data preprocessing is the process of preparing our data for modeling. As we’ve seen, data comes in all shapes and sizes. Some data is dirty and needs to be cleaned before building a model with it. Specifically, data can be:

  • Scaled and/or skewed
  • Missing
  • Be non-numeric (and thus not something a machine can process) – e.g. categorical or character data.

Pre-processing is necessary step used to resolve these issues.Some common pre-processing tasks are:

  • Scaling and transforming continuous values

  • Converting categorical variables to dummy variables.

  • Detecting and imputing missing values

Dependencies

Here are the packages we’ll use today in this walkthrough.

require(tidyverse) 
require(recipes) # For data pre-processing
require(rsample) # For generating train-test splits

Data

The provided data captures whether or not a person will pay back a bank loan. The outcome variable is status, which is a qualitative outcome that takes one of two values: good if the individual has good credit, otherwise bad. There are 13 other variables tracking features about the debtor and the loan.

We’ll encounter this data later on when we learn more about modeling. Right now, we’ll use it to walkthrough pre-processing concepts.

Parsed with column specification:
cols(
  Status = col_character(),
  Seniority = col_double(),
  Home = col_character(),
  Time = col_double(),
  Age = col_double(),
  Marital = col_character(),
  Records = col_character(),
  Job = col_character(),
  Expenses = col_double(),
  Income = col_double(),
  Assets = col_double(),
  Debt = col_double(),
  Amount = col_double(),
  Price = col_double()
)

Train-Test Split

Before doing anything with our data, we want to split it into a training and test dataset. We don’t want to learn anything about our test data, and that even includes summary statistics. Thus before we look at and explore our data (to figure out how to preprocess it), we want to split it into a training and test dataset.

We can easily break our data up into a training and test dataset using the rsample package. First, we want to generate a split object using the initial_split() function. Note that the prop= argument dictates the proportion of the data we want to keep as training data.

set.seed(123) # We set a seed so we can reproduce the random split
splits <- initial_split(credit_data,prop = .75)
splits
<Analysis/Assess/Total>
<3341/1113/4454>

Then break into training and test datasets.

train_dat <- training(splits)
test_dat <- testing(splits)

If we look at the number of observations, we see that we get precisely the proportions that were requested in each dataset.

nrow(train_dat)/nrow(credit_data)
[1] 0.7501123
nrow(test_dat)/nrow(credit_data)
[1] 0.2498877

Note: never look at the training data. Maintaining this rule is how we can do good social science using machine learning methods. More on this later.

Data Exploration

Data Types: Note we have variables of different classes/types. How we preprocess these data will differ given its type.

train_dat %>% 
  summarize_all(class) %>% 
  glimpse()
Rows: 1
Columns: 14
$ status    <chr> "factor"
$ seniority <chr> "numeric"
$ home      <chr> "factor"
$ time      <chr> "numeric"
$ age       <chr> "numeric"
$ marital   <chr> "factor"
$ records   <chr> "factor"
$ job       <chr> "factor"
$ expenses  <chr> "numeric"
$ income    <chr> "numeric"
$ assets    <chr> "numeric"
$ debt      <chr> "numeric"
$ amount    <chr> "numeric"
$ price     <chr> "numeric"

Of the numberic variable, we clearly have variables on different scales… with some missing values!

train_dat %>% 
  summarize_if(is.numeric, function(x) mean(x)) %>% 
  glimpse()
Rows: 1
Columns: 9
$ seniority <dbl> 7.973661
$ time      <dbl> 46.57947
$ age       <dbl> 37.06435
$ expenses  <dbl> 55.48399
$ income    <dbl> NA
$ assets    <dbl> NA
$ debt      <dbl> NA
$ amount    <dbl> 1044.601
$ price     <dbl> 1470.798

We can drop missing values when calculating a summary statistic with the na.rm = T argument.

train_dat %>% 
  summarize_if(is.numeric, function(x) mean(x,na.rm = T)) %>% 
  glimpse()
Rows: 1
Columns: 9
$ seniority <dbl> 7.973661
$ time      <dbl> 46.57947
$ age       <dbl> 37.06435
$ expenses  <dbl> 55.48399
$ income    <dbl> 140.7199
$ assets    <dbl> 5285.176
$ debt      <dbl> 320.4593
$ amount    <dbl> 1044.601
$ price     <dbl> 1470.798

Of the categorical variable, we also have missing values

train_dat %>% 
  select_if(is.factor) %>% 
  glimpse()
Rows: 3,341
Columns: 5
$ status  <fct> good, bad, good, good, good, good, bad, good, good, good, good, bad, good…
$ home    <fct> rent, owner, rent, owner, owner, parents, parents, owner, owner, owner, p…
$ marital <fct> married, married, single, married, married, single, married, married, mar…
$ records <fct> no, yes, no, no, no, no, no, no, no, no, no, yes, no, no, no, no, no, no,…
$ job     <fct> freelance, freelance, fixed, fixed, fixed, fixed, partime, freelance, fix…
train_dat %>% 
  select_if(is.factor) %>% 
  summarize_all(function(x) sum(is.na(x)))

Distributions

How is the continuous data distributed? And what does this tell us?

train_dat %>% 
  
  # only select the numeric variables
  select_if(is.numeric) %>% 
  
  # Pivot to longer data format (for faceting)
  pivot_longer(cols = everything()) %>% 
  
  # Plot histograms for each variable
  ggplot(aes(value)) +
  
  geom_histogram() +
  
  facet_wrap(~name,scales="free",ncol=3)

Pre-Processing

Building a recipe

The package operates by laying out a series of steps that are then itemized. Once we have all our steps in place we then bake the recipe (i.e. execute and transform the data all at once).


step_s we need to perform:

  1. impute any missing values.

  2. Log transform amount, assets, debt, income, seniority, expenses and price

  3. scale the continuous variables

  4. convert the categorical variables to dummy variables

Initialize a recipe object

First, let’s initialize the recipe object.

our_recipe <- recipe(status ~ ., data = train_dat)
our_recipe
Data Recipe

Inputs:

(1) Impute any missing values

recipes offers many different forms of imputation.

Imputation Methods
step_bagimpute
step_knnimpute
step_lowerimpute
step_meanimpute
step_medianimpute
step_modeimpute
step_rollimpute
step_bagimpute
step_knnimpute
step_meanimpute
step_rollimpute

See the package documentation on how any one of these methods works (.e.g step_knnimpute).

Below I’m using mean imputation for all numeric variables, which fills in the average value for variable to fill in any missing values, and mode imputation for the categorical variables, which fills in the most common category for any missing values.

Note the all_numeric() and all_nominal() functions. This tells the recipe to perform a particular step on certain types of variables. We can directly reference specific variables using the variable name.

our_recipe <-
  our_recipe %>% 
  step_meanimpute(all_numeric()) %>% 
  step_modeimpute(all_nominal())
our_recipe
Data Recipe

Inputs:

Operations:

Mean Imputation for all_numeric()
Mode Imputation for all_nominal()

Question

  • What assumptions are we making when we delete/drop missing values?
  • What assumptions are we making when we impute missing values?
  • What is the best way to deal with missing data?

(2) Transform some continuous variables

Recall there was a right skew in the following variables: amount, assests, debt, income, seniority, expenses and price. We can log transform these features so that the extreme values in their tails exhibit less of an influence.

The offset=1 argument adds one to each of the variables before logging. Why do you think this is?

our_recipe <-
  our_recipe %>%
  step_log(amount,assets,debt,income,
           seniority,expenses,price,offset = 1) 
our_recipe
Data Recipe

Inputs:

Operations:

Mean Imputation for all_numeric()
Mode Imputation for all_nominal()
Log transformation on amount, assets, debt, income, seniority, expenses, price

(3) Scale the continuous variables

our_recipe <-
  our_recipe %>%
  step_normalize(all_numeric()) # Center mean around 0 and Set variance to 1
our_recipe
Data Recipe

Inputs:

Operations:

Mean Imputation for all_numeric()
Mode Imputation for all_nominal()
Log transformation on amount, assets, debt, income, seniority, expenses, price
Centering and scaling for all_numeric()

(4) Convert the categories to dummies

our_recipe <- 
  our_recipe %>% 
  step_dummy(all_nominal())
our_recipe
Data Recipe

Inputs:

Operations:

Mean Imputation for all_numeric()
Mode Imputation for all_nominal()
Log transformation on amount, assets, debt, income, seniority, expenses, price
Centering and scaling for all_numeric()
Dummy variables from all_nominal()

Prepare the recipe

prep() calculates all the necessary statistics and values for the transformations. It then stores this information for use later on. When might we use this data later?

prepared_recipe <- our_recipe %>% prep()
prepared_recipe
Data Recipe

Inputs:

Training data contained 3341 data points and 312 incomplete rows. 

Operations:

Mean Imputation for seniority, time, age, expenses, income, assets, ... [trained]
Mode Imputation for home, marital, records, job, status [trained]
Log transformation on amount, assets, debt, income, seniority, expenses, price [trained]
Centering and scaling for seniority, time, age, expenses, income, assets, ... [trained]
Dummy variables from home, marital, records, job, status [trained]

Bake!

Now that the recipe is prepared, we can apply our preprocessing steps to our data, which transforms the data as requested.

Before

glimpse(train_dat) 
Rows: 3,341
Columns: 14
$ status    <fct> good, bad, good, good, good, good, bad, good, good, good, good, bad, go…
$ seniority <dbl> 9, 10, 0, 1, 29, 9, 0, 6, 7, 8, 19, 0, 0, 15, 0, 1, 2, 5, 1, 27, 26, 12…
$ home      <fct> rent, owner, rent, owner, owner, parents, parents, owner, owner, owner,…
$ time      <dbl> 60, 36, 36, 60, 60, 12, 48, 48, 36, 60, 36, 18, 24, 24, 48, 60, 60, 60,…
$ age       <dbl> 30, 46, 26, 36, 44, 27, 41, 34, 29, 30, 37, 21, 68, 52, 36, 31, 25, 22,…
$ marital   <fct> married, married, single, married, married, single, married, married, m…
$ records   <fct> no, yes, no, no, no, no, no, no, no, no, no, yes, no, no, no, no, no, n…
$ job       <fct> freelance, freelance, fixed, fixed, fixed, fixed, partime, freelance, f…
$ expenses  <dbl> 73, 90, 46, 75, 75, 35, 90, 60, 60, 75, 75, 35, 75, 35, 45, 35, 46, 45,…
$ income    <dbl> 129, 200, 107, 214, 125, 80, 80, 125, 121, 199, 170, 50, 131, 330, 130,…
$ assets    <dbl> 0, 3000, 0, 3500, 10000, 0, 0, 4000, 3000, 5000, 3500, 0, 4162, 16500, …
$ debt      <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 2500, 260, 0, 0, 0, 0, 0, 0, 0, 500, 0, 0, 0…
$ amount    <dbl> 800, 2000, 310, 650, 1600, 200, 1200, 1150, 650, 1500, 600, 400, 900, 1…
$ price     <dbl> 846, 2985, 910, 1645, 1800, 1093, 1468, 1577, 915, 1650, 940, 500, 1186…

After

dat_processed <- bake(prepared_recipe,new_data = train_dat)
glimpse(dat_processed) 
Rows: 3,341
Columns: 23
$ seniority         <dbl> 0.55268042, 0.64683872, -1.72207650, -1.03730643, 1.63801530, 0…
$ time              <dbl> 0.91886117, -0.72434246, -0.72434246, 0.91886117, 0.91886117, -…
$ age               <dbl> -0.643976061, 0.814560686, -1.008610248, -0.097024781, 0.632243…
$ expenses          <dbl> 0.9909869, 1.6198766, -0.3894374, 1.0720886, 1.0720886, -1.2002…
$ income            <dbl> 0.059610992, 0.971859888, -0.328515103, 1.112816127, -0.0058135…
$ assets            <dbl> -1.3069752, 0.5963635, -1.3069752, 0.6329966, 0.8825144, -1.306…
$ debt              <dbl> -0.4426112, -0.4426112, -0.4426112, -0.4426112, -0.4426112, -0.…
$ amount            <dbl> -0.31003590, 1.51548962, -2.19642943, -0.72347991, 1.07080608, …
$ price             <dbl> -1.131668339, 1.897728092, -0.956533589, 0.465757253, 0.6821300…
$ home_other        <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ home_owner        <dbl> 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, …
$ home_parents      <dbl> 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, …
$ home_priv         <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, …
$ home_rent         <dbl> 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, …
$ marital_married   <dbl> 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, …
$ marital_separated <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ marital_single    <dbl> 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, …
$ marital_widow     <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ records_yes       <dbl> 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ job_freelance     <dbl> 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, …
$ job_others        <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
$ job_partime       <dbl> 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, …
$ status_good       <dbl> 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, …

Preprocessing does not distort distributions

train_dat %>% 
  ggplot(aes(age)) +
  geom_density(fill="pink",
               alpha=.5)

dat_processed %>% 
  ggplot(aes(age)) +
  geom_density(fill="pink",
               alpha=.5)

In one go!

In practice, we can build up our data preprocessing method in one go.

our_recipe <- 
  recipe(status ~ ., data = train_dat) %>% 
  step_meanimpute(all_numeric()) %>% 
  step_modeimpute(all_nominal()) %>% 
  step_log(amount,assets,debt,income,
           seniority,expenses,price,offset = 1) %>% 
  step_normalize(all_numeric()) %>% 
  step_dummy(all_nominal()) %>% 
  prep()

Then just implement on the training data prior to running any model.

train_dat2 <- bake(our_recipe,new_data = train_dat)

And on the test data before checking performance.

test_dat2 <- bake(our_recipe,new_data = test_dat)

Again, why is this so important?

LS0tCnRpdGxlOiAiUFBPTDY3MCB8IEludHJvZHVjdGlvbiB0byBEYXRhIFNjaWVuY2UgfCBXZWVrIDkiCnN1YnRpdGxlOiAiRGF0YSBQcmVwcm9jZXNzaW5nIHdpdGggdGhlIGByZWNpcGVzYCBwYWNrYWdlIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRoZW1lOiB1bml0ZWQKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICB0b2NfZGVwdGg6IDQKLS0tCgpgYGB7ciBpbmNsdWRlPUYsIHBhZ2VkLnByaW50PUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQod2FybmluZyA9IEYsZXJyb3IgPSBGLG1lc3NhZ2UgPSBGKQpyZXF1aXJlKHRpZHl2ZXJzZSkKcmVxdWlyZSh0aWR5dGV4dCkKcmVxdWlyZSh0b3BpY21vZGVscykKYGBgCgojIyBPdmVydmlldyAKCkluIHRoaXMgd2Fsa3Rocm91Z2gsIHdlJ2xsIGV4cGxvcmUgaG93IHByZS1wcm9jZXNzIGRhdGEgdXNpbmcgdGhlIGByZWNpcGVzYCBwYWNrYWdlLiBEYXRhIHByZXByb2Nlc3NpbmcgaXMgdGhlIHByb2Nlc3Mgb2YgcHJlcGFyaW5nIG91ciBkYXRhIGZvciBtb2RlbGluZy4gQXMgd2UndmUgc2VlbiwgZGF0YSBjb21lcyBpbiBhbGwgc2hhcGVzIGFuZCBzaXplcy4gU29tZSBkYXRhIGlzIGRpcnR5IGFuZCBuZWVkcyB0byBiZSBjbGVhbmVkIGJlZm9yZSBidWlsZGluZyBhIG1vZGVsIHdpdGggaXQuIFNwZWNpZmljYWxseSwgZGF0YSBjYW4gYmU6IAoKLSBTY2FsZWQgYW5kL29yIHNrZXdlZAotIE1pc3NpbmcKLSBCZSBub24tbnVtZXJpYyAoYW5kIHRodXMgbm90IHNvbWV0aGluZyBhIG1hY2hpbmUgY2FuIHByb2Nlc3MpIC0tIGUuZy4gY2F0ZWdvcmljYWwgb3IgY2hhcmFjdGVyIGRhdGEuIAoKUHJlLXByb2Nlc3NpbmcgaXMgbmVjZXNzYXJ5IHN0ZXAgdXNlZCB0byByZXNvbHZlIHRoZXNlIGlzc3Vlcy5Tb21lIGNvbW1vbiBwcmUtcHJvY2Vzc2luZyB0YXNrcyBhcmU6CgotICoqU2NhbGluZyoqIGFuZCB0cmFuc2Zvcm1pbmcgY29udGludW91cyB2YWx1ZXMgCgotIENvbnZlcnRpbmcgY2F0ZWdvcmljYWwgdmFyaWFibGVzIHRvICoqZHVtbXkqKiB2YXJpYWJsZXMuCgotIERldGVjdGluZyBhbmQgKippbXB1dGluZyoqIG1pc3NpbmcgdmFsdWVzCgojIyBEZXBlbmRlbmNpZXMgCgpIZXJlIGFyZSB0aGUgcGFja2FnZXMgd2UnbGwgdXNlIHRvZGF5IGluIHRoaXMgd2Fsa3Rocm91Z2guIAoKYGBge3J9CnJlcXVpcmUodGlkeXZlcnNlKSAKcmVxdWlyZShyZWNpcGVzKSAjIEZvciBkYXRhIHByZS1wcm9jZXNzaW5nCnJlcXVpcmUocnNhbXBsZSkgIyBGb3IgZ2VuZXJhdGluZyB0cmFpbi10ZXN0IHNwbGl0cwpgYGAKCiMjIERhdGEgCgpUaGUgcHJvdmlkZWQgZGF0YSBjYXB0dXJlcyB3aGV0aGVyIG9yIG5vdCBhIHBlcnNvbiB3aWxsIHBheSBiYWNrIGEgYmFuayBsb2FuLiBUaGUgb3V0Y29tZSB2YXJpYWJsZSBpcyBgc3RhdHVzYCwgd2hpY2ggaXMgYSBxdWFsaXRhdGl2ZSBvdXRjb21lIHRoYXQgdGFrZXMgb25lIG9mIHR3byB2YWx1ZXM6IGBnb29kYCBpZiB0aGUgaW5kaXZpZHVhbCBoYXMgZ29vZCBjcmVkaXQsIG90aGVyd2lzZSBgYmFkYC4gVGhlcmUgYXJlIGAxM2Agb3RoZXIgdmFyaWFibGVzIHRyYWNraW5nIGZlYXR1cmVzIGFib3V0IHRoZSBkZWJ0b3IgYW5kIHRoZSBsb2FuLiAKCldlJ2xsIGVuY291bnRlciB0aGlzIGRhdGEgbGF0ZXIgb24gd2hlbiB3ZSBsZWFybiBtb3JlIGFib3V0IG1vZGVsaW5nLiBSaWdodCBub3csIHdlJ2xsIHVzZSBpdCB0byB3YWxrdGhyb3VnaCBwcmUtcHJvY2Vzc2luZyBjb25jZXB0cy4gCgpgYGB7cixlY2hvPUZ9CmNyZWRpdF9kYXRhIDwtIAogIHJlYWRfY3N2KCJEYXRhL2NyZWRpdF9kYXRhLmNzdiIpICU+JSAKICBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLGFzLmZhY3RvcikgJT4lIAogIGphbml0b3I6OmNsZWFuX25hbWVzKCkKCmhlYWQoY3JlZGl0X2RhdGEpCmBgYAoKCiMjIFRyYWluLVRlc3QgU3BsaXQgCgpCZWZvcmUgZG9pbmcgX2FueXRoaW5nXyB3aXRoIG91ciBkYXRhLCB3ZSB3YW50IHRvIHNwbGl0IGl0IGludG8gYSB0cmFpbmluZyBhbmQgdGVzdCBkYXRhc2V0LiBXZSBkb24ndCB3YW50IHRvIGxlYXJuIGFueXRoaW5nIGFib3V0IG91ciB0ZXN0IGRhdGEsIGFuZCB0aGF0IGV2ZW4gaW5jbHVkZXMgc3VtbWFyeSBzdGF0aXN0aWNzLiBUaHVzIGJlZm9yZSB3ZSBsb29rIGF0IGFuZCBleHBsb3JlIG91ciBkYXRhICh0byBmaWd1cmUgb3V0IGhvdyB0byBwcmVwcm9jZXNzIGl0KSwgd2Ugd2FudCB0byBzcGxpdCBpdCBpbnRvIGEgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YXNldC4gCgoKV2UgY2FuIGVhc2lseSBicmVhayBvdXIgZGF0YSB1cCBpbnRvIGEgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YXNldCB1c2luZyB0aGUgYHJzYW1wbGVgIHBhY2thZ2UuIEZpcnN0LCB3ZSB3YW50IHRvIGdlbmVyYXRlIGEgc3BsaXQgb2JqZWN0IHVzaW5nIHRoZSBgaW5pdGlhbF9zcGxpdCgpYCBmdW5jdGlvbi4gTm90ZSB0aGF0IHRoZSBgcHJvcD1gIGFyZ3VtZW50IGRpY3RhdGVzIHRoZSBwcm9wb3J0aW9uIG9mIHRoZSBkYXRhIHdlIHdhbnQgdG8ga2VlcCBhcyB0cmFpbmluZyBkYXRhLiAKCmBgYHtyfQpzZXQuc2VlZCgxMjMpICMgV2Ugc2V0IGEgc2VlZCBzbyB3ZSBjYW4gcmVwcm9kdWNlIHRoZSByYW5kb20gc3BsaXQKc3BsaXRzIDwtIGluaXRpYWxfc3BsaXQoY3JlZGl0X2RhdGEscHJvcCA9IC43NSkKc3BsaXRzCmBgYAoKVGhlbiBicmVhayBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGFzZXRzLiAKCmBgYHtyfQp0cmFpbl9kYXQgPC0gdHJhaW5pbmcoc3BsaXRzKQp0ZXN0X2RhdCA8LSB0ZXN0aW5nKHNwbGl0cykKYGBgCgpJZiB3ZSBsb29rIGF0IHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zLCB3ZSBzZWUgdGhhdCB3ZSBnZXQgcHJlY2lzZWx5IHRoZSBwcm9wb3J0aW9ucyB0aGF0IHdlcmUgcmVxdWVzdGVkIGluIGVhY2ggZGF0YXNldC4KCmBgYHtyfQpucm93KHRyYWluX2RhdCkvbnJvdyhjcmVkaXRfZGF0YSkKYGBgCmBgYHtyfQpucm93KHRlc3RfZGF0KS9ucm93KGNyZWRpdF9kYXRhKQpgYGAKPiBfTm90ZV86ICoqX25ldmVyXyoqIGxvb2sgYXQgdGhlIHRyYWluaW5nIGRhdGEuIE1haW50YWluaW5nIHRoaXMgcnVsZSBpcyBob3cgd2UgY2FuIGRvIGdvb2Qgc29jaWFsIHNjaWVuY2UgdXNpbmcgbWFjaGluZSBsZWFybmluZyBtZXRob2RzLiBNb3JlIG9uIHRoaXMgbGF0ZXIuIAoKCiMjIERhdGEgRXhwbG9yYXRpb24KCgoqKkRhdGEgVHlwZXMqKjogTm90ZSB3ZSBoYXZlIHZhcmlhYmxlcyBvZiBkaWZmZXJlbnQgY2xhc3Nlcy90eXBlcy4gSG93IHdlIHByZXByb2Nlc3MgdGhlc2UgZGF0YSB3aWxsIGRpZmZlciBnaXZlbiBpdHMgdHlwZS4gCgpgYGB7cn0KdHJhaW5fZGF0ICU+JSAKICBzdW1tYXJpemVfYWxsKGNsYXNzKSAlPiUgCiAgZ2xpbXBzZSgpCmBgYAoKT2YgdGhlICoqbnVtYmVyaWMqKiB2YXJpYWJsZSwgd2UgY2xlYXJseSBoYXZlIHZhcmlhYmxlcyBvbiBkaWZmZXJlbnQgc2NhbGVzLi4uIHdpdGggc29tZSBtaXNzaW5nIHZhbHVlcyEKCmBgYHtyfQp0cmFpbl9kYXQgJT4lIAogIHN1bW1hcml6ZV9pZihpcy5udW1lcmljLCBmdW5jdGlvbih4KSBtZWFuKHgpKSAlPiUgCiAgZ2xpbXBzZSgpCmBgYAoKV2UgY2FuIGRyb3AgbWlzc2luZyB2YWx1ZXMgd2hlbiBjYWxjdWxhdGluZyBhIHN1bW1hcnkgc3RhdGlzdGljIHdpdGggdGhlIGBuYS5ybSA9IFRgIGFyZ3VtZW50LiAKCmBgYHtyfQp0cmFpbl9kYXQgJT4lIAogIHN1bW1hcml6ZV9pZihpcy5udW1lcmljLCBmdW5jdGlvbih4KSBtZWFuKHgsbmEucm0gPSBUKSkgJT4lIAogIGdsaW1wc2UoKQpgYGAKCgpPZiB0aGUgKipjYXRlZ29yaWNhbCoqIHZhcmlhYmxlLCB3ZSBhbHNvIGhhdmUgbWlzc2luZyB2YWx1ZXMKCmBgYHtyfQp0cmFpbl9kYXQgJT4lIAogIHNlbGVjdF9pZihpcy5mYWN0b3IpICU+JSAKICBnbGltcHNlKCkKYGBgCgpgYGB7cn0KdHJhaW5fZGF0ICU+JSAKICBzZWxlY3RfaWYoaXMuZmFjdG9yKSAlPiUgCiAgc3VtbWFyaXplX2FsbChmdW5jdGlvbih4KSBzdW0oaXMubmEoeCkpKQpgYGAKCgojIyMgRGlzdHJpYnV0aW9ucyAKCkhvdyBpcyB0aGUgY29udGludW91cyBkYXRhIGRpc3RyaWJ1dGVkPyBBbmQgd2hhdCBkb2VzIHRoaXMgdGVsbCB1cz8KCmBgYHtyLGZpZy5hbGlnbj0iY2VudGVyIixmaWcuaGVpZ2g9NyxmaWcud2lkdGg9IDh9CnRyYWluX2RhdCAlPiUgCiAgCiAgIyBvbmx5IHNlbGVjdCB0aGUgbnVtZXJpYyB2YXJpYWJsZXMKICBzZWxlY3RfaWYoaXMubnVtZXJpYykgJT4lIAogIAogICMgUGl2b3QgdG8gbG9uZ2VyIGRhdGEgZm9ybWF0IChmb3IgZmFjZXRpbmcpCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBldmVyeXRoaW5nKCkpICU+JSAKICAKICAjIFBsb3QgaGlzdG9ncmFtcyBmb3IgZWFjaCB2YXJpYWJsZQogIGdncGxvdChhZXModmFsdWUpKSArCiAgCiAgZ2VvbV9oaXN0b2dyYW0oKSArCiAgCiAgZmFjZXRfd3JhcCh+bmFtZSxzY2FsZXM9ImZyZWUiLG5jb2w9MykKYGBgCgoKCiMjIFByZS1Qcm9jZXNzaW5nIAoKIyMjIEJ1aWxkaW5nIGEgcmVjaXBlCgpUaGUgcGFja2FnZSBvcGVyYXRlcyBieSBsYXlpbmcgb3V0IGEgc2VyaWVzIG9mIHN0ZXBzIHRoYXQgYXJlIHRoZW4gaXRlbWl6ZWQuIE9uY2Ugd2UgaGF2ZSBhbGwgb3VyIHN0ZXBzIGluIHBsYWNlIHdlIHRoZW4gYGJha2VgIHRoZSByZWNpcGUgKGkuZS4gZXhlY3V0ZSBhbmQgdHJhbnNmb3JtIHRoZSBkYXRhIGFsbCBhdCBvbmNlKS4KCjxicj4gCgpgc3RlcF9gcyB3ZSBuZWVkIHRvIHBlcmZvcm06CgoxLiBpbXB1dGUgYW55IG1pc3NpbmcgdmFsdWVzLgoKMi4gTG9nIHRyYW5zZm9ybSBgYW1vdW50YCwgYGFzc2V0c2AsIGBkZWJ0YCwgYGluY29tZWAsIGBzZW5pb3JpdHlgLCBgZXhwZW5zZXNgIGFuZCBgcHJpY2VgCgozLiBzY2FsZSB0aGUgY29udGludW91cyB2YXJpYWJsZXMKCjQuIGNvbnZlcnQgdGhlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyB0byBkdW1teSB2YXJpYWJsZXMKCiMjIyBJbml0aWFsaXplIGEgcmVjaXBlIG9iamVjdAoKRmlyc3QsIGxldCdzIGluaXRpYWxpemUgdGhlIHJlY2lwZSBvYmplY3QuIAoKYGBge3J9Cm91cl9yZWNpcGUgPC0gcmVjaXBlKHN0YXR1cyB+IC4sIGRhdGEgPSB0cmFpbl9kYXQpCm91cl9yZWNpcGUKYGBgCgojIyMgKDEpIEltcHV0ZSBhbnkgbWlzc2luZyB2YWx1ZXMKCmByZWNpcGVzYCBvZmZlcnMgbWFueSBkaWZmZXJlbnQgZm9ybXMgb2YgaW1wdXRhdGlvbi4KCnxJbXB1dGF0aW9uIE1ldGhvZHMgICAgICB8Cnw6LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18CnxzdGVwX2JhZ2ltcHV0ZSAgICAgICAgICB8CnxzdGVwX2tubmltcHV0ZSAgICAgICAgICB8CnxzdGVwX2xvd2VyaW1wdXRlICAgICAgICB8CnxzdGVwX21lYW5pbXB1dGUgICAgICAgICB8CnxzdGVwX21lZGlhbmltcHV0ZSAgICAgICB8CnxzdGVwX21vZGVpbXB1dGUgICAgICAgICB8CnxzdGVwX3JvbGxpbXB1dGUgICAgICAgICB8CnxzdGVwX2JhZ2ltcHV0ZSAgfAp8c3RlcF9rbm5pbXB1dGUgIHwKfHN0ZXBfbWVhbmltcHV0ZSB8CnxzdGVwX3JvbGxpbXB1dGUgfAoKU2VlIHRoZSBwYWNrYWdlIGRvY3VtZW50YXRpb24gb24gaG93IGFueSBvbmUgb2YgdGhlc2UgbWV0aG9kcyB3b3JrcyAoLmUuZyBgc3RlcF9rbm5pbXB1dGVgKS4KCgpCZWxvdyBJJ20gdXNpbmcgKiptZWFuIGltcHV0YXRpb24qKiBmb3IgYWxsIG51bWVyaWMgdmFyaWFibGVzLCB3aGljaCBmaWxscyBpbiB0aGUgYXZlcmFnZSB2YWx1ZSBmb3IgdmFyaWFibGUgdG8gZmlsbCBpbiBhbnkgbWlzc2luZyB2YWx1ZXMsIGFuZCAqKm1vZGUgaW1wdXRhdGlvbioqIGZvciB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzLCB3aGljaCBmaWxscyBpbiB0aGUgbW9zdCBjb21tb24gY2F0ZWdvcnkgZm9yIGFueSBtaXNzaW5nIHZhbHVlcy4KCk5vdGUgdGhlIGBhbGxfbnVtZXJpYygpYCBhbmQgYGFsbF9ub21pbmFsKClgIGZ1bmN0aW9ucy4gVGhpcyB0ZWxscyB0aGUgcmVjaXBlIHRvIHBlcmZvcm0gYSBwYXJ0aWN1bGFyIHN0ZXAgb24gY2VydGFpbiB0eXBlcyBvZiB2YXJpYWJsZXMuIFdlIGNhbiBkaXJlY3RseSByZWZlcmVuY2Ugc3BlY2lmaWMgdmFyaWFibGVzIHVzaW5nIHRoZSB2YXJpYWJsZSBuYW1lLiAKCmBgYHtyfQpvdXJfcmVjaXBlIDwtCiAgb3VyX3JlY2lwZSAlPiUgCiAgc3RlcF9tZWFuaW1wdXRlKGFsbF9udW1lcmljKCkpICU+JSAKICBzdGVwX21vZGVpbXB1dGUoYWxsX25vbWluYWwoKSkKb3VyX3JlY2lwZQpgYGAKCiMjIyMgUXVlc3Rpb24KCi0gV2hhdCBhc3N1bXB0aW9ucyBhcmUgd2UgbWFraW5nIHdoZW4gd2UgZGVsZXRlL2Ryb3AgbWlzc2luZyB2YWx1ZXM/Ci0gV2hhdCBhc3N1bXB0aW9ucyBhcmUgd2UgbWFraW5nIHdoZW4gd2UgaW1wdXRlIG1pc3NpbmcgdmFsdWVzPwotIFdoYXQgaXMgdGhlIGJlc3Qgd2F5IHRvIGRlYWwgd2l0aCBtaXNzaW5nIGRhdGE/CgoKIyMjICgyKSBUcmFuc2Zvcm0gc29tZSBjb250aW51b3VzIHZhcmlhYmxlcyAKClJlY2FsbCB0aGVyZSB3YXMgYSByaWdodCBza2V3IGluIHRoZSBmb2xsb3dpbmcgdmFyaWFibGVzOiBgYW1vdW50YCwgYGFzc2VzdHNgLCBgZGVidGAsIGBpbmNvbWVgLCBgc2VuaW9yaXR5YCwgYGV4cGVuc2VzYCBhbmQgYHByaWNlYC4gV2UgY2FuIGxvZyB0cmFuc2Zvcm0gdGhlc2UgZmVhdHVyZXMgc28gdGhhdCB0aGUgZXh0cmVtZSB2YWx1ZXMgaW4gdGhlaXIgdGFpbHMgZXhoaWJpdCBsZXNzIG9mIGFuIGluZmx1ZW5jZS4gCgpUaGUgYG9mZnNldD0xYCBhcmd1bWVudCBhZGRzIG9uZSB0byBlYWNoIG9mIHRoZSB2YXJpYWJsZXMgYmVmb3JlIGxvZ2dpbmcuIFdoeSBkbyB5b3UgdGhpbmsgdGhpcyBpcz8gCgpgYGB7cn0Kb3VyX3JlY2lwZSA8LQogIG91cl9yZWNpcGUgJT4lCiAgc3RlcF9sb2coYW1vdW50LGFzc2V0cyxkZWJ0LGluY29tZSwKICAgICAgICAgICBzZW5pb3JpdHksZXhwZW5zZXMscHJpY2Usb2Zmc2V0ID0gMSkgCm91cl9yZWNpcGUKYGBgCgoKIyMjICgzKSBTY2FsZSB0aGUgY29udGludW91cyB2YXJpYWJsZXMKCmBgYHtyfQpvdXJfcmVjaXBlIDwtCiAgb3VyX3JlY2lwZSAlPiUKICBzdGVwX25vcm1hbGl6ZShhbGxfbnVtZXJpYygpKSAjIENlbnRlciBtZWFuIGFyb3VuZCAwIGFuZCBTZXQgdmFyaWFuY2UgdG8gMQpvdXJfcmVjaXBlCmBgYAoKIyMjICg0KSBDb252ZXJ0IHRoZSBjYXRlZ29yaWVzIHRvIGR1bW1pZXMKCmBgYHtyfQpvdXJfcmVjaXBlIDwtIAogIG91cl9yZWNpcGUgJT4lIAogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWwoKSkKb3VyX3JlY2lwZQpgYGAKCi0tLQoKIyMjIFByZXBhcmUgdGhlIHJlY2lwZQoKYHByZXAoKWAgY2FsY3VsYXRlcyBhbGwgdGhlIG5lY2Vzc2FyeSBzdGF0aXN0aWNzIGFuZCB2YWx1ZXMgZm9yIHRoZSB0cmFuc2Zvcm1hdGlvbnMuIEl0IHRoZW4gc3RvcmVzIHRoaXMgaW5mb3JtYXRpb24gZm9yIHVzZSBsYXRlciBvbi4gV2hlbiBtaWdodCB3ZSB1c2UgdGhpcyBkYXRhIGxhdGVyPyAKCmBgYHtyfQpwcmVwYXJlZF9yZWNpcGUgPC0gb3VyX3JlY2lwZSAlPiUgcHJlcCgpCnByZXBhcmVkX3JlY2lwZQpgYGAKCgotLS0KCiMjIyBCYWtlIQoKTm93IHRoYXQgdGhlIHJlY2lwZSBpcyBwcmVwYXJlZCwgd2UgY2FuIGFwcGx5IG91ciBwcmVwcm9jZXNzaW5nIHN0ZXBzIHRvIG91ciBkYXRhLCB3aGljaCAqKnRyYW5zZm9ybXMqKiB0aGUgZGF0YSBhcyByZXF1ZXN0ZWQuIAoKX0JlZm9yZV8KYGBge3J9CmdsaW1wc2UodHJhaW5fZGF0KSAKYGBgCgoKX0FmdGVyXwpgYGB7cn0KZGF0X3Byb2Nlc3NlZCA8LSBiYWtlKHByZXBhcmVkX3JlY2lwZSxuZXdfZGF0YSA9IHRyYWluX2RhdCkKZ2xpbXBzZShkYXRfcHJvY2Vzc2VkKSAKYGBgCgojIyMgUHJlcHJvY2Vzc2luZyBkb2VzIG5vdCBkaXN0b3J0IGRpc3RyaWJ1dGlvbnMKYGBge3IsZmlnLmFsaWduPSJjZW50ZXIifQp0cmFpbl9kYXQgJT4lIAogIGdncGxvdChhZXMoYWdlKSkgKwogIGdlb21fZGVuc2l0eShmaWxsPSJwaW5rIiwKICAgICAgICAgICAgICAgYWxwaGE9LjUpCmBgYAoKCgpgYGB7cixmaWcuYWxpZ249ImNlbnRlciJ9CmRhdF9wcm9jZXNzZWQgJT4lIAogIGdncGxvdChhZXMoYWdlKSkgKwogIGdlb21fZGVuc2l0eShmaWxsPSJwaW5rIiwKICAgICAgICAgICAgICAgYWxwaGE9LjUpCmBgYAoKCiMjIEluIG9uZSBnbyEKCkluIHByYWN0aWNlLCB3ZSBjYW4gYnVpbGQgdXAgb3VyIGRhdGEgcHJlcHJvY2Vzc2luZyBtZXRob2QgaW4gb25lIGdvLiAKCmBgYHtyfQpvdXJfcmVjaXBlIDwtIAogIHJlY2lwZShzdGF0dXMgfiAuLCBkYXRhID0gdHJhaW5fZGF0KSAlPiUgCiAgc3RlcF9tZWFuaW1wdXRlKGFsbF9udW1lcmljKCkpICU+JSAKICBzdGVwX21vZGVpbXB1dGUoYWxsX25vbWluYWwoKSkgJT4lIAogIHN0ZXBfbG9nKGFtb3VudCxhc3NldHMsZGVidCxpbmNvbWUsCiAgICAgICAgICAgc2VuaW9yaXR5LGV4cGVuc2VzLHByaWNlLG9mZnNldCA9IDEpICU+JSAKICBzdGVwX25vcm1hbGl6ZShhbGxfbnVtZXJpYygpKSAlPiUgCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpKSAlPiUgCiAgcHJlcCgpCmBgYAoKVGhlbiBqdXN0IGltcGxlbWVudCBvbiB0aGUgdHJhaW5pbmcgZGF0YSBwcmlvciB0byBydW5uaW5nIGFueSBtb2RlbC4gCgpgYGB7cn0KdHJhaW5fZGF0MiA8LSBiYWtlKG91cl9yZWNpcGUsbmV3X2RhdGEgPSB0cmFpbl9kYXQpCmBgYAoKQW5kIG9uIHRoZSB0ZXN0IGRhdGEgYmVmb3JlIGNoZWNraW5nIHBlcmZvcm1hbmNlLiAKCmBgYHtyfQp0ZXN0X2RhdDIgPC0gYmFrZShvdXJfcmVjaXBlLG5ld19kYXRhID0gdGVzdF9kYXQpCmBgYAoKQWdhaW4sIHdoeSBpcyB0aGlzIHNvIGltcG9ydGFudD8K