Goals:

  • Illustrate tree-based methods

  • and the difference between global and local methods


Data generating process

Consider a model with only one covariate: \(Y = \underbrace{2X - X^3}_{CEF} + \epsilon\), where \(X \sim uniform(0,1)\) and \(\epsilon \sim N(0,1/3)\).

Plot a random draw with \(N=500\) and the true CEF.

# Load the packages required for later
if (!require("tidyverse")) install.packages("tidyverse", dependencies = TRUE); library(tidyverse)
if (!require("rpart")) install.packages("rpart", dependencies = TRUE); library(rpart)
if (!require("partykit")) install.packages("partykit", dependencies = TRUE); library(partykit)
if (!require("grf")) install.packages("grf", dependencies = TRUE); library(grf)
if (!require("rpart.plot")) install.packages("rpart.plot", dependencies = TRUE); library(rpart.plot)

set.seed(1234) # For replicability

cef = function(x){-x^3 + 2*x}

n = 500

x = runif(n)
y = cef(x) + rnorm(n,0,1/3)

df = data.frame(x=x,y=y)
ggplot(df) + stat_function(fun=cef,size=1) + 
            geom_point(aes(x=x,y=y),color="black",alpha = 0.4)



Different ways to fit the data

OLS

Plain OLS without any polynomials fits a straight line through the data cloud missing the non-linear shape but providing the best linear approximation:

df$y_hat_ols = predict(lm(y~x))
ggplot(df,aes(x=x,y=y)) + stat_function(fun=cef,size=1) + 
  geom_point(color="black",alpha = 0.4) + 
  geom_point(aes(x=x,y=y_hat_ols),shape="square",color="blue") + 
  geom_smooth(formula="y~x", method='lm') 


Regression tree

The regression tree provides a step function approximation of the non-linear shape:

tree = rpart(y~x,data = df)
rpart.plot(tree)

df$y_hat_tree = predict(tree)

ggplot(df) + stat_function(fun=cef,size=1) + 
  geom_point(aes(x=x,y=y),color="black",alpha = 0.4) +
  geom_point(aes(x=x,y=y_hat_tree),shape="square",color="blue") 

Notably the leaves are more narrow in the left part region where the curve is steeper, while more observation are pooled on the right where the functions is relatively flat. This makes intuitive sense.


Random Forest

Random Forest combines many of such stepwise approximation and provides a relatively good and smooth approximation of non-linear CEF:

rf = regression_forest(as.matrix(x),y,tune.parameters = "all",num.trees = 2000)
df$y_hat_rf = predict(rf)$predictions

ggplot(df) + stat_function(fun=cef,size=1) + 
  geom_point(aes(x=x,y=y),color="black",alpha = 0.4) +
  geom_point(aes(x=x,y=y_hat_rf),shape="square",color="blue")



Global vs. local predictors

To understand the difference between global and local methods, consider that we want to predict the outcome value at \(x_0 = 0.2\). Each of the three estimators uses just a weighted average of observed outcomes to form this prediction.

test_point = 0.2

OLS

Recall that in matrix notation the parameter estimates are \(\beta = (X'X)^{-1}X'Y\), where \(X\) contains a constant. Now the fitted value at the test point is \(\hat{y}_0 = [1~x_0] \beta = \underbrace{[1~x_0] (X'X)^{-1}X'}_{w_{ols}} Y\), where \(w_{ols}\) contains the weights that each outcome receives when forming the fitted value.

We graph the scatterplot of the observed data with larger points indicating a larger absolute outcome weight, and the color indicating whether the outcome weight is positive or negative:

X = cbind(rep(1,n),x)
predict_ols = as.numeric(c(1,test_point) %*% solve(t(X) %*% X) %*% t(X) %*% y)
df$w_ols = t(c(1,test_point) %*% solve(t(X) %*% X) %*% t(X))

w_sign_ols = rep("negative",n)
w_sign_ols[df$w_ols>0] = "positive"
w_sign_ols = factor(w_sign_ols,level=c("negative","positive"))

ggplot(df) + stat_function(fun=cef,size=1) + 
  geom_point(aes(x=x,y=y,size=abs(w_ols),color=w_sign_ols),alpha = 0.2) +
  geom_point(x=test_point,y=predict_ols,shape="cross",size = 3, stroke = 2,color="yellow") + 
  scale_color_manual(values=c("red","blue"))

A global method like OLS uses all available data points to form the prediction and outcomes that are far away of the point of interest receive negative weights.


Regression trees

The predictions of regression trees are formed as an average within the leaf of the point of interest. This means that all outcomes in that leaf receive the same weight and all other outcomes a weight of zero:

predict_tree = predict(tree,newdata=data.frame(x=test_point))

tree2 = as.party(tree)
nodes = predict(tree2,df, type = "node")
node_test = predict(tree2, newdata=data.frame(x=test_point), type = "node")

df$w_tree = (nodes==node_test)  / sum(nodes==node_test)

w_sign_tree = rep("zero",n)
w_sign_tree[df$w_tree>0] = "positive"
w_sign_tree = factor(w_sign_tree,level=c("zero","positive"))

ggplot(df) + stat_function(fun=cef,size=1) + 
  geom_point(aes(x=x,y=y,size=w_tree,color=w_sign_tree),alpha = 0.2) +
  geom_point(x=test_point,y=predict_tree,shape="cross",size = 3, stroke = 2,color="yellow") + 
  scale_color_manual(values=c("darkgray","blue"))

The problem is that predictions at the boundary discard outcomes that are very close.


Random Forest

The Random Forest creates a smoother weighting function and gives larger weights to closer outcomes and ignores outcomes that are further away.

predict_rf = predict(rf,newdata=as.matrix(test_point))$predictions
df$w_rf = t( as.matrix( get_forest_weights(rf,newdata=as.matrix(test_point)) ) )

w_sign_rf = rep("zero",n)
w_sign_rf[df$w_rf>0] = "positive"
w_sign_rf = factor(w_sign_rf,level=c("zero","positive"))

ggplot(df) + stat_function(fun=cef,size=1) + 
  geom_point(aes(x=x,y=y,size=w_rf,color=w_sign_rf),alpha = 0.2) +
  geom_point(x=test_point,y=predict_rf,shape="cross",size = 3, stroke = 2,color="yellow") + 
  scale_color_manual(values=c("darkgray","blue"))



Take-away:

  • Tree based methods use only outcomes close to the prediction point, while global methods like OLS use a (at least for me) less intuitive weighting with negative weights



Suggestions to play with the toy model:

Feel free to play around with the code. This is useful to sharpen and challenge your understanding of the methods. Think about the consequences of a modifications before you run it and check whether the results are in line with your expectation. Some suggestions:

  • Modify DGP (functional form of CEF, noise term, …)

  • Decrease and increase the number of observations

  • Decrease and increase the number of trees for the random forest

  • Add squared or cubic covariates to OLS specification

LS0tDQp0aXRsZTogIlN1cGVydmlzZWQgTUw6IFRyZWUtYmFzZWQgbWV0aG9kcyINCmF1dGhvcjogIk1pY2hhZWwgS25hdXMiDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclbS8leScpYCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQotLS0NCg0KPGJyPg0KDQpHb2FsczoNCg0KLSBJbGx1c3RyYXRlIHRyZWUtYmFzZWQgbWV0aG9kcw0KDQotIGFuZCB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGdsb2JhbCBhbmQgbG9jYWwgbWV0aG9kcw0KDQo8YnI+DQoNCg0KIyMgRGF0YSBnZW5lcmF0aW5nIHByb2Nlc3MNCg0KQ29uc2lkZXIgYSBtb2RlbCB3aXRoIG9ubHkgb25lIGNvdmFyaWF0ZTogJFkgPSBcdW5kZXJicmFjZXsyWCAtIFheM31fe0NFRn0gKyBcZXBzaWxvbiQsIHdoZXJlICRYIFxzaW0gdW5pZm9ybSgwLDEpJCBhbmQgJFxlcHNpbG9uIFxzaW0gTigwLDEvMykkLg0KDQpQbG90IGEgcmFuZG9tIGRyYXcgd2l0aCAkTj01MDAkIGFuZCB0aGUgdHJ1ZSBDRUYuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQojIExvYWQgdGhlIHBhY2thZ2VzIHJlcXVpcmVkIGZvciBsYXRlcg0KaWYgKCFyZXF1aXJlKCJ0aWR5dmVyc2UiKSkgaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkodGlkeXZlcnNlKQ0KaWYgKCFyZXF1aXJlKCJycGFydCIpKSBpbnN0YWxsLnBhY2thZ2VzKCJycGFydCIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KHJwYXJ0KQ0KaWYgKCFyZXF1aXJlKCJwYXJ0eWtpdCIpKSBpbnN0YWxsLnBhY2thZ2VzKCJwYXJ0eWtpdCIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KHBhcnR5a2l0KQ0KaWYgKCFyZXF1aXJlKCJncmYiKSkgaW5zdGFsbC5wYWNrYWdlcygiZ3JmIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkoZ3JmKQ0KaWYgKCFyZXF1aXJlKCJycGFydC5wbG90IikpIGluc3RhbGwucGFja2FnZXMoInJwYXJ0LnBsb3QiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKTsgbGlicmFyeShycGFydC5wbG90KQ0KDQpzZXQuc2VlZCgxMjM0KSAjIEZvciByZXBsaWNhYmlsaXR5DQoNCmNlZiA9IGZ1bmN0aW9uKHgpey14XjMgKyAyKnh9DQoNCm4gPSA1MDANCg0KeCA9IHJ1bmlmKG4pDQp5ID0gY2VmKHgpICsgcm5vcm0obiwwLDEvMykNCg0KZGYgPSBkYXRhLmZyYW1lKHg9eCx5PXkpDQpnZ3Bsb3QoZGYpICsgc3RhdF9mdW5jdGlvbihmdW49Y2VmLHNpemU9MSkgKyANCiAgICAgICAgICAgIGdlb21fcG9pbnQoYWVzKHg9eCx5PXkpLGNvbG9yPSJibGFjayIsYWxwaGEgPSAwLjQpDQpgYGANCg0KPGJyPg0KPGJyPg0KDQojIyBEaWZmZXJlbnQgd2F5cyB0byBmaXQgdGhlIGRhdGENCg0KIyMjIE9MUw0KDQpQbGFpbiBPTFMgd2l0aG91dCBhbnkgcG9seW5vbWlhbHMgZml0cyBhIHN0cmFpZ2h0IGxpbmUgdGhyb3VnaCB0aGUgZGF0YSBjbG91ZCBtaXNzaW5nIHRoZSBub24tbGluZWFyIHNoYXBlIGJ1dCBwcm92aWRpbmcgdGhlIGJlc3QgbGluZWFyIGFwcHJveGltYXRpb246DQoNCmBgYHtyfQ0KZGYkeV9oYXRfb2xzID0gcHJlZGljdChsbSh5fngpKQ0KZ2dwbG90KGRmLGFlcyh4PXgseT15KSkgKyBzdGF0X2Z1bmN0aW9uKGZ1bj1jZWYsc2l6ZT0xKSArIA0KICBnZW9tX3BvaW50KGNvbG9yPSJibGFjayIsYWxwaGEgPSAwLjQpICsgDQogIGdlb21fcG9pbnQoYWVzKHg9eCx5PXlfaGF0X29scyksc2hhcGU9InNxdWFyZSIsY29sb3I9ImJsdWUiKSArIA0KICBnZW9tX3Ntb290aChmb3JtdWxhPSJ5fngiLCBtZXRob2Q9J2xtJykgDQpgYGANCg0KPGJyPg0KDQojIyMgUmVncmVzc2lvbiB0cmVlDQoNClRoZSByZWdyZXNzaW9uIHRyZWUgcHJvdmlkZXMgYSBzdGVwIGZ1bmN0aW9uIGFwcHJveGltYXRpb24gb2YgdGhlIG5vbi1saW5lYXIgc2hhcGU6DQoNCmBgYHtyfQ0KdHJlZSA9IHJwYXJ0KHl+eCxkYXRhID0gZGYpDQpycGFydC5wbG90KHRyZWUpDQpkZiR5X2hhdF90cmVlID0gcHJlZGljdCh0cmVlKQ0KDQpnZ3Bsb3QoZGYpICsgc3RhdF9mdW5jdGlvbihmdW49Y2VmLHNpemU9MSkgKyANCiAgZ2VvbV9wb2ludChhZXMoeD14LHk9eSksY29sb3I9ImJsYWNrIixhbHBoYSA9IDAuNCkgKw0KICBnZW9tX3BvaW50KGFlcyh4PXgseT15X2hhdF90cmVlKSxzaGFwZT0ic3F1YXJlIixjb2xvcj0iYmx1ZSIpIA0KYGBgDQoNCk5vdGFibHkgdGhlIGxlYXZlcyBhcmUgbW9yZSBuYXJyb3cgaW4gdGhlIGxlZnQgcGFydCByZWdpb24gd2hlcmUgdGhlIGN1cnZlIGlzIHN0ZWVwZXIsIHdoaWxlIG1vcmUgb2JzZXJ2YXRpb24gYXJlIHBvb2xlZCBvbiB0aGUgcmlnaHQgd2hlcmUgdGhlIGZ1bmN0aW9ucyBpcyByZWxhdGl2ZWx5IGZsYXQuIFRoaXMgbWFrZXMgaW50dWl0aXZlIHNlbnNlLg0KDQo8YnI+DQoNCiMjIyBSYW5kb20gRm9yZXN0DQoNClJhbmRvbSBGb3Jlc3QgY29tYmluZXMgbWFueSBvZiBzdWNoIHN0ZXB3aXNlIGFwcHJveGltYXRpb24gYW5kIHByb3ZpZGVzIGEgcmVsYXRpdmVseSBnb29kIGFuZCBzbW9vdGggYXBwcm94aW1hdGlvbiBvZiBub24tbGluZWFyIENFRjoNCg0KYGBge3J9DQpyZiA9IHJlZ3Jlc3Npb25fZm9yZXN0KGFzLm1hdHJpeCh4KSx5LHR1bmUucGFyYW1ldGVycyA9ICJhbGwiLG51bS50cmVlcyA9IDIwMDApDQpkZiR5X2hhdF9yZiA9IHByZWRpY3QocmYpJHByZWRpY3Rpb25zDQoNCmdncGxvdChkZikgKyBzdGF0X2Z1bmN0aW9uKGZ1bj1jZWYsc2l6ZT0xKSArIA0KICBnZW9tX3BvaW50KGFlcyh4PXgseT15KSxjb2xvcj0iYmxhY2siLGFscGhhID0gMC40KSArDQogIGdlb21fcG9pbnQoYWVzKHg9eCx5PXlfaGF0X3JmKSxzaGFwZT0ic3F1YXJlIixjb2xvcj0iYmx1ZSIpDQpgYGANCg0KPGJyPg0KPGJyPg0KDQojIyBHbG9iYWwgdnMuIGxvY2FsIHByZWRpY3RvcnMNCg0KVG8gdW5kZXJzdGFuZCB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGdsb2JhbCBhbmQgbG9jYWwgbWV0aG9kcywgY29uc2lkZXIgdGhhdCB3ZSB3YW50IHRvIHByZWRpY3QgdGhlIG91dGNvbWUgdmFsdWUgYXQgJHhfMCA9IDAuMiQuIEVhY2ggb2YgdGhlIHRocmVlIGVzdGltYXRvcnMgdXNlcyBqdXN0IGEgd2VpZ2h0ZWQgYXZlcmFnZSBvZiBvYnNlcnZlZCBvdXRjb21lcyB0byBmb3JtIHRoaXMgcHJlZGljdGlvbi4NCg0KYGBge3J9DQp0ZXN0X3BvaW50ID0gMC4yDQpgYGANCg0KIyMjIE9MUw0KDQpSZWNhbGwgdGhhdCBpbiBtYXRyaXggbm90YXRpb24gdGhlIHBhcmFtZXRlciBlc3RpbWF0ZXMgYXJlICRcYmV0YSA9IChYJ1gpXnstMX1YJ1kkLCB3aGVyZSAkWCQgY29udGFpbnMgYSBjb25zdGFudC4gTm93IHRoZSBmaXR0ZWQgdmFsdWUgYXQgdGhlIHRlc3QgcG9pbnQgaXMgJFxoYXR7eX1fMCA9IFsxfnhfMF0gXGJldGEgPSBcdW5kZXJicmFjZXtbMX54XzBdIChYJ1gpXnstMX1YJ31fe3dfe29sc319IFkkLCB3aGVyZSAkd197b2xzfSQgY29udGFpbnMgdGhlIHdlaWdodHMgdGhhdCBlYWNoIG91dGNvbWUgcmVjZWl2ZXMgd2hlbiBmb3JtaW5nIHRoZSBmaXR0ZWQgdmFsdWUuDQoNCldlIGdyYXBoIHRoZSBzY2F0dGVycGxvdCBvZiB0aGUgb2JzZXJ2ZWQgZGF0YSB3aXRoIGxhcmdlciBwb2ludHMgaW5kaWNhdGluZyBhIGxhcmdlciBhYnNvbHV0ZSBvdXRjb21lIHdlaWdodCwgYW5kIHRoZSBjb2xvciBpbmRpY2F0aW5nIHdoZXRoZXIgdGhlIG91dGNvbWUgd2VpZ2h0IGlzIHBvc2l0aXZlIG9yIG5lZ2F0aXZlOg0KDQpgYGB7cn0NClggPSBjYmluZChyZXAoMSxuKSx4KQ0KcHJlZGljdF9vbHMgPSBhcy5udW1lcmljKGMoMSx0ZXN0X3BvaW50KSAlKiUgc29sdmUodChYKSAlKiUgWCkgJSolIHQoWCkgJSolIHkpDQpkZiR3X29scyA9IHQoYygxLHRlc3RfcG9pbnQpICUqJSBzb2x2ZSh0KFgpICUqJSBYKSAlKiUgdChYKSkNCg0Kd19zaWduX29scyA9IHJlcCgibmVnYXRpdmUiLG4pDQp3X3NpZ25fb2xzW2RmJHdfb2xzPjBdID0gInBvc2l0aXZlIg0Kd19zaWduX29scyA9IGZhY3Rvcih3X3NpZ25fb2xzLGxldmVsPWMoIm5lZ2F0aXZlIiwicG9zaXRpdmUiKSkNCg0KZ2dwbG90KGRmKSArIHN0YXRfZnVuY3Rpb24oZnVuPWNlZixzaXplPTEpICsgDQogIGdlb21fcG9pbnQoYWVzKHg9eCx5PXksc2l6ZT1hYnMod19vbHMpLGNvbG9yPXdfc2lnbl9vbHMpLGFscGhhID0gMC4yKSArDQogIGdlb21fcG9pbnQoeD10ZXN0X3BvaW50LHk9cHJlZGljdF9vbHMsc2hhcGU9ImNyb3NzIixzaXplID0gMywgc3Ryb2tlID0gMixjb2xvcj0ieWVsbG93IikgKyANCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJyZWQiLCJibHVlIikpDQpgYGANCg0KQSBnbG9iYWwgbWV0aG9kIGxpa2UgT0xTIHVzZXMgYWxsIGF2YWlsYWJsZSBkYXRhIHBvaW50cyB0byBmb3JtIHRoZSBwcmVkaWN0aW9uIGFuZCBvdXRjb21lcyB0aGF0IGFyZSBmYXIgYXdheSBvZiB0aGUgcG9pbnQgb2YgaW50ZXJlc3QgcmVjZWl2ZSBuZWdhdGl2ZSB3ZWlnaHRzLg0KDQo8YnI+DQoNCiMjIyBSZWdyZXNzaW9uIHRyZWVzDQoNClRoZSBwcmVkaWN0aW9ucyBvZiByZWdyZXNzaW9uIHRyZWVzIGFyZSBmb3JtZWQgYXMgYW4gYXZlcmFnZSB3aXRoaW4gdGhlIGxlYWYgb2YgdGhlIHBvaW50IG9mIGludGVyZXN0LiBUaGlzIG1lYW5zIHRoYXQgYWxsIG91dGNvbWVzIGluIHRoYXQgbGVhZiByZWNlaXZlIHRoZSBzYW1lIHdlaWdodCBhbmQgYWxsIG90aGVyIG91dGNvbWVzIGEgd2VpZ2h0IG9mIHplcm86DQoNCmBgYHtyfQ0KcHJlZGljdF90cmVlID0gcHJlZGljdCh0cmVlLG5ld2RhdGE9ZGF0YS5mcmFtZSh4PXRlc3RfcG9pbnQpKQ0KDQp0cmVlMiA9IGFzLnBhcnR5KHRyZWUpDQpub2RlcyA9IHByZWRpY3QodHJlZTIsZGYsIHR5cGUgPSAibm9kZSIpDQpub2RlX3Rlc3QgPSBwcmVkaWN0KHRyZWUyLCBuZXdkYXRhPWRhdGEuZnJhbWUoeD10ZXN0X3BvaW50KSwgdHlwZSA9ICJub2RlIikNCg0KZGYkd190cmVlID0gKG5vZGVzPT1ub2RlX3Rlc3QpICAvIHN1bShub2Rlcz09bm9kZV90ZXN0KQ0KDQp3X3NpZ25fdHJlZSA9IHJlcCgiemVybyIsbikNCndfc2lnbl90cmVlW2RmJHdfdHJlZT4wXSA9ICJwb3NpdGl2ZSINCndfc2lnbl90cmVlID0gZmFjdG9yKHdfc2lnbl90cmVlLGxldmVsPWMoInplcm8iLCJwb3NpdGl2ZSIpKQ0KDQpnZ3Bsb3QoZGYpICsgc3RhdF9mdW5jdGlvbihmdW49Y2VmLHNpemU9MSkgKyANCiAgZ2VvbV9wb2ludChhZXMoeD14LHk9eSxzaXplPXdfdHJlZSxjb2xvcj13X3NpZ25fdHJlZSksYWxwaGEgPSAwLjIpICsNCiAgZ2VvbV9wb2ludCh4PXRlc3RfcG9pbnQseT1wcmVkaWN0X3RyZWUsc2hhcGU9ImNyb3NzIixzaXplID0gMywgc3Ryb2tlID0gMixjb2xvcj0ieWVsbG93IikgKyANCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCJkYXJrZ3JheSIsImJsdWUiKSkNCg0KYGBgDQoNClRoZSBwcm9ibGVtIGlzIHRoYXQgcHJlZGljdGlvbnMgYXQgdGhlIGJvdW5kYXJ5IGRpc2NhcmQgb3V0Y29tZXMgdGhhdCBhcmUgdmVyeSBjbG9zZS4NCg0KPGJyPg0KDQoNCiMjIyBSYW5kb20gRm9yZXN0DQoNClRoZSBSYW5kb20gRm9yZXN0IGNyZWF0ZXMgYSBzbW9vdGhlciB3ZWlnaHRpbmcgZnVuY3Rpb24gYW5kIGdpdmVzIGxhcmdlciB3ZWlnaHRzIHRvIGNsb3NlciBvdXRjb21lcyBhbmQgaWdub3JlcyBvdXRjb21lcyB0aGF0IGFyZSBmdXJ0aGVyIGF3YXkuDQoNCmBgYHtyfQ0KcHJlZGljdF9yZiA9IHByZWRpY3QocmYsbmV3ZGF0YT1hcy5tYXRyaXgodGVzdF9wb2ludCkpJHByZWRpY3Rpb25zDQpkZiR3X3JmID0gdCggYXMubWF0cml4KCBnZXRfZm9yZXN0X3dlaWdodHMocmYsbmV3ZGF0YT1hcy5tYXRyaXgodGVzdF9wb2ludCkpICkgKQ0KDQp3X3NpZ25fcmYgPSByZXAoInplcm8iLG4pDQp3X3NpZ25fcmZbZGYkd19yZj4wXSA9ICJwb3NpdGl2ZSINCndfc2lnbl9yZiA9IGZhY3Rvcih3X3NpZ25fcmYsbGV2ZWw9YygiemVybyIsInBvc2l0aXZlIikpDQoNCmdncGxvdChkZikgKyBzdGF0X2Z1bmN0aW9uKGZ1bj1jZWYsc2l6ZT0xKSArIA0KICBnZW9tX3BvaW50KGFlcyh4PXgseT15LHNpemU9d19yZixjb2xvcj13X3NpZ25fcmYpLGFscGhhID0gMC4yKSArDQogIGdlb21fcG9pbnQoeD10ZXN0X3BvaW50LHk9cHJlZGljdF9yZixzaGFwZT0iY3Jvc3MiLHNpemUgPSAzLCBzdHJva2UgPSAyLGNvbG9yPSJ5ZWxsb3ciKSArIA0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoImRhcmtncmF5IiwiYmx1ZSIpKQ0KYGBgDQoNCjxicj4NCjxicj4NCg0KKlRha2UtYXdheSo6DQogDQogLSBUcmVlIGJhc2VkIG1ldGhvZHMgdXNlIG9ubHkgb3V0Y29tZXMgY2xvc2UgdG8gdGhlIHByZWRpY3Rpb24gcG9pbnQsIHdoaWxlIGdsb2JhbCBtZXRob2RzIGxpa2UgT0xTIHVzZSBhIChhdCBsZWFzdCBmb3IgbWUpIGxlc3MgaW50dWl0aXZlIHdlaWdodGluZyB3aXRoIG5lZ2F0aXZlIHdlaWdodHMNCiANCjxicj4NCjxicj4NCiANCiANCipTdWdnZXN0aW9ucyB0byBwbGF5IHdpdGggdGhlIHRveSBtb2RlbCo6DQoNCkZlZWwgZnJlZSB0byBwbGF5IGFyb3VuZCB3aXRoIHRoZSBjb2RlLiBUaGlzIGlzIHVzZWZ1bCB0byBzaGFycGVuIGFuZCBjaGFsbGVuZ2UgeW91ciB1bmRlcnN0YW5kaW5nIG9mIHRoZSBtZXRob2RzLiBUaGluayBhYm91dCB0aGUgY29uc2VxdWVuY2VzIG9mIGEgbW9kaWZpY2F0aW9ucyBiZWZvcmUgeW91IHJ1biBpdCBhbmQgY2hlY2sgd2hldGhlciB0aGUgcmVzdWx0cyBhcmUgaW4gbGluZSB3aXRoIHlvdXIgZXhwZWN0YXRpb24uIFNvbWUgc3VnZ2VzdGlvbnM6DQogDQotIE1vZGlmeSBER1AgKGZ1bmN0aW9uYWwgZm9ybSBvZiBDRUYsIG5vaXNlIHRlcm0sIC4uLikNCg0KLSBEZWNyZWFzZSBhbmQgaW5jcmVhc2UgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMNCg0KLSBEZWNyZWFzZSBhbmQgaW5jcmVhc2UgdGhlIG51bWJlciBvZiB0cmVlcyBmb3IgdGhlIHJhbmRvbSBmb3Jlc3QNCg0KLSBBZGQgc3F1YXJlZCBvciBjdWJpYyBjb3ZhcmlhdGVzIHRvIE9MUyBzcGVjaWZpY2F0aW9uDQog