These are the goals of today:

  • Hand-code S-learner, T-learner, Causal Forest (at least a little bit), R-learner and DR-learner


401(k) dataset again

We again use the data of the hdm package. The data was used in Chernozhukov and Hansen (2004). Their paper investigates the effect of participation in the employer-sponsored 401(k) retirement savings plan (p401) on net assets (net_tfa). Since then, the data was used to showcase many new methods. It is not the most comprehensive dataset with basically ten covariates/regressors/predictors:

  • age: age

  • db: defined benefit pension

  • educ: education (in years)

  • fsize: family size

  • hown: home owner

  • inc: income (in US $)

  • male: male

  • marr: married

  • pira: participation in individual retirement account (IRA)

  • twoearn: two earners

However, it is publicly available and the few controls ensure that the programs run not as long as with datasets that you hope to have for your applications.

library(hdm)
library(grf)
library(tidyverse)
library(psych)
library(estimatr)
# library(devtools)
# install_github("susanathey/causalTree")
library(causalTree)
# library(devtools)
# install_github(repo="MCKnaus/causalDML")
library(causalDML)

set.seed(1234) # for replicability

# Get data
data(pension)
# Outcome
Y = pension$net_tfa
# Treatment
W = pension$p401
# Treatment
Z = pension$e401
# Create main effects matrix
X = model.matrix(~ 0 + age + db + educ + fsize + hown + inc + male + marr + pira + twoearn, data = pension)

We want to predict effects of 401(k) participation on net assets using the following estimators:

  • S-learner (to be defined)

  • T-learner

  • Causal Tree

  • Causal Forest

  • R-learner

  • DR-learner



S-learner

We did not discuss it in class, but another straightforward way of getting CATEs is to model \(E[Y|W,X]\) and then evaluate it at \(E[Y|1,X] - E[Y|0,X]\)

WX = cbind(W,X)
rf = regression_forest(WX,Y)
W0X = cbind(rep(0,length(Y)),X)
W1X = cbind(rep(1,length(Y)),X)
cate_sl = predict(rf,W1X)$predictions - predict(rf,W0X)$predictions
hist(cate_sl)



T-learner

For the T-learner, we implement the following procedure:

  1. Use ML estimator of your choice to fit model \(\hat{m}(1,X)\) in treated}

  2. Use ML estimator of your choice to fit model \(\hat{m}(0,X)\) in controls

  3. Estimate CATE as \(\hat{\tau}(x) = \hat{m}(1,x) - \hat{m}(0,x)\)

rfm1 = regression_forest(X[W==1,],Y[W==1])
rfm0 = regression_forest(X[W==0,],Y[W==0])
cate_tl = predict(rfm1, X)$predictions - predict(rfm0, X)$predictions
hist(cate_tl)



Causal Tree

Let’s move on with the Causal Tree, which is implemented in the package and function causalTree. The package is build for experiments, so we do not control for confounding here. However, if the data were experimental, this is what you would do to implement the method:

# Prepare data frame
df = data.frame(X,Y)
# Implemented causalTree adapting specification from R example
ctree = causalTree(Y~X, data = df, treatment = W,
                   split.Rule = "CT", cv.option = "CT", split.Honest = T,split.Bucket = F, xval = 5, 
                   cp = 0, minsize = 20)
opcp = ctree$cptable[,1][which.min(ctree$cptable[,4])]
opfit = prune(ctree, opcp)
rpart.plot(opfit)

We see in the parent node that the “treatment effect” of $27k is much larger then what we get by controlling for confounding where the effects ranged from $11k to $15k. We also do not know whether the splits are driven by effect heterogeneity or heterogeneous selection bias.

However, if we ignore confounding for the sake of the argument, the tree shows substantial heterogeneity regarding age and some other variables.

Given the obvious selection bias, we do not form predictions for causal trees.



Causal Forest

grf package

Causal Forests are implemented most conveniently with the causal_forest function of the grf package. If we use the predict function for the created object, we get out-of-bag estimated IATEs for every individual in our sample:

cf = causal_forest(X, Y, W)
cate_cf = predict(cf)$predictions
hist(cate_cf)

Replicated using weighted ROR regression

Now let’s see what the Causal Forest does in the background. To this end, we want to “manually” replicate the predicted effect of individual one. It has a predicted effect of 5292.817192 and has the following characteristics:

X[1,]
    age      db    educ   fsize    hown     inc    male    marr    pira twoearn 
     31       0      12       5       1   28146       0       1       0       0 

Recall that causal forest estimates CATEs using this formula: \[ \begin{equation} \hat{\tau}^{cf}(x) = argmin_{\tau} \left\{ \sum_{i=1}^N \alpha_i(x) \left[(Y_i - \hat{m}(X_i)) - \tau(x)(W_i - \hat{e}(X_i)) \right]^2 \right\}, \end{equation} \] where \(\alpha(x)\) are \(x\)-specific weights.

We can implement this ourselves this because the causal forest saves all components that were used in the process.

  1. We extract the nuisance parameters estimates:
mhat = cf$Y.hat
ehat = cf$W.hat
  1. We extract the weights used for individual one using get_forest_weights:
alphax = get_forest_weights(cf)[1,]
hist(as.numeric(alphax))

  1. We create the residuals and run a weighted residual-on-residual regression (RORR) w/o constant:
Yres = Y - mhat
Wres = W - ehat
manual_cate = lm(Yres ~ 0 + Wres,weights = as.numeric(alphax))$coefficients
manual_cate
    Wres 
5306.271 

Let’s check whether this is equal to the causal_forest prediction:

all.equal(as.numeric(cate_cf[1]),as.numeric(manual_cate))
[1] "Mean relative difference: 0.002541852"

Nope :-(

What did we miss?

The causal_forest runs the weighted RORR with constant. This constant should asymptotically be zero, but in finite samples it is not:

Yres = Y - mhat
Wres = W - ehat
manual_cate_const = lm(Yres ~ Wres,weights = as.numeric(alphax))
summary(manual_cate_const)

Call:
lm(formula = Yres ~ Wres, weights = as.numeric(alphax))

Weighted Residuals:
   Min     1Q Median     3Q    Max 
 -4662      0      0      0   3353 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   -604.9      187.4  -3.228  0.00126 ** 
Wres          5292.8      479.2  11.046  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 187.4 on 3151 degrees of freedom
Multiple R-squared:  0.03728,   Adjusted R-squared:  0.03697 
F-statistic:   122 on 1 and 3151 DF,  p-value: < 2.2e-16

With constant the results from the package and our “manually” coded version coincide:

all.equal(as.numeric(cate_cf[1]),as.numeric(manual_cate_const$coefficients[2]))
[1] TRUE

After all, it boiled down to run OLS…



R-learner

Hand-coded using modified covariates (OLS)

To illustrate how we can implement the R-leaner like this for assuming a linear CATE model \[ \begin{equation} \hat{\beta}^{rl} = argmin_{\beta} \sum_{i=1}^N \big(Y_i - \hat{m}(X_i) - X_i^* \beta \big)^2 \end{equation} \] we recycle the nuisance parameters of above and just need to define the modified/pseudo-covariates \(X^* = X(W - \hat{e}(X))\)

# Create residuals
res_y = Y-mhat
res_w = W-ehat

# Modify covariates (multiply each column including constant with residual)
n = length(Y)
X_wc = cbind(rep(1,n),X)
Xstar = X_wc * res_w
# Regress outcome residual on modified covariates
rl_ols = lm(res_y ~ 0 + Xstar)
summary(rl_ols)

Call:
lm(formula = res_y ~ 0 + Xstar)

Residuals:
    Min      1Q  Median      3Q     Max 
-507194  -10540   -1235    2272 1439820 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)  
Xstar        -1.614e+04  1.081e+04  -1.493   0.1354  
Xstarage      3.173e+02  1.469e+02   2.160   0.0308 *
Xstardb       5.268e+03  2.904e+03   1.814   0.0697 .
Xstareduc     4.398e+02  5.561e+02   0.791   0.4291  
Xstarfsize   -8.555e+01  1.098e+03  -0.078   0.9379  
Xstarhown     6.090e+03  3.259e+03   1.868   0.0617 .
Xstarinc      6.168e-02  6.567e-02   0.939   0.3476  
Xstarmale     5.816e+03  3.631e+03   1.602   0.1092  
Xstarmarr     3.476e+03  4.600e+03   0.756   0.4499  
Xstarpira    -9.279e+02  3.128e+03  -0.297   0.7667  
Xstartwoearn -2.412e+03  3.658e+03  -0.659   0.5097  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 53830 on 9904 degrees of freedom
Multiple R-squared:  0.01336,   Adjusted R-squared:  0.01226 
F-statistic: 12.19 on 11 and 9904 DF,  p-value: < 2.2e-16

Now we need to take the coefficients of this model to get fitted values of the CATEs as \(X \beta\) (!fitted values need to be calculated using \(X\), not \(X^*\), do not repeat a mistake I did in the beginning !)

cate_rl_ols = X_wc %*% rl_ols$coefficients
hist(cate_rl_ols)


Hand-coded using pseudo-outcomes and weights (OLS)

The more generic alternative is to use the unmodified covariates in a weighted regression with pseudo outcomes: \[\hat{\tau}^{rl}(x) = argmin_{\tau} \sum_{i=1}^N \underbrace{(W_i - \hat{e}(X_i))^2}_{\text{weight}} \left(\underbrace{\frac{Y_i - \hat{m}(X_i)}{W_i - \hat{e}(X_i)}}_{\text{pseudo-outcome}} - X_i\beta \right)^2\]

For the sake of the argument run it again with OLS

# Create pseudo-outcome (outcome res divided by treatment res)
pseudo_rl = res_y / res_w

# Create weights
weights_rl = res_w^2

# Weighted regression of pseudo-outcome on covariates
rols_fit = lm(pseudo_rl ~ X, weights=weights_rl)
summary(rols_fit)

Call:
lm(formula = pseudo_rl ~ X, weights = weights_rl)

Weighted Residuals:
     Min       1Q   Median       3Q      Max 
-1217226    -6108      -72     6672  1439820 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)  
(Intercept) -1.614e+04  1.081e+04  -1.493   0.1354  
Xage         3.173e+02  1.469e+02   2.160   0.0308 *
Xdb          5.268e+03  2.904e+03   1.814   0.0697 .
Xeduc        4.398e+02  5.561e+02   0.791   0.4291  
Xfsize      -8.555e+01  1.098e+03  -0.078   0.9379  
Xhown        6.090e+03  3.259e+03   1.868   0.0617 .
Xinc         6.168e-02  6.567e-02   0.939   0.3476  
Xmale        5.816e+03  3.631e+03   1.602   0.1092  
Xmarr        3.476e+03  4.600e+03   0.756   0.4499  
Xpira       -9.279e+02  3.128e+03  -0.297   0.7667  
Xtwoearn    -2.412e+03  3.658e+03  -0.659   0.5097  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 53830 on 9904 degrees of freedom
Multiple R-squared:  0.00233,   Adjusted R-squared:  0.001322 
F-statistic: 2.313 on 10 and 9904 DF,  p-value: 0.01034

and observe that both implementations deliver identical results:

all.equal(as.numeric(rl_ols$coefficients),as.numeric(rols_fit$coefficients))
[1] TRUE

This was just for illustration that both representations provide identical results when solved with a method without random components. In practice, we can apply anything that solves the (weighted) minimization problem.


Hand-coded using pseudo-outcomes and weights (Random Forest)

Going beyond pure illustration, we implement the R-learner now using random forest:

# Weighted regression with RF
rrf_fit = regression_forest(X,pseudo_rl, sample.weights = weights_rl)
cate_rl_rf = predict(rrf_fit)$predictions
hist(cate_rl_rf)



DR-learner

Finally, run the DR-learner that creates the pseudo-outcome in a first step \[ \begin{align} \tilde{Y}_{ATE} = \underbrace{\hat{m}(1,X) - \hat{m}(0,X)}_{\text{outcome predictions}} + \underbrace{\frac{W (Y - \hat{m}(1,X))}{\hat{e}(X)} - \frac{(1-W) (Y - \hat{m}(0,X))}{1-\hat{e}(X)}}_{\text{weighted residuals}} \nonumber \end{align} \]

and uses it in a final regression step.

mwhat0 = mwhat1 = rep(NA,length(Y))
rfm0 = regression_forest(X[W==0,],Y[W==0])
mwhat0[W==0] = predict(rfm0)$predictions
mwhat0[W==1] = predict(rfm0,X[W==1,])$predictions

rfm1 = regression_forest(X[W==1,],Y[W==1])
mwhat1 = predict(rfm1)$predictions
mwhat1[W==1] = predict(rfm1)$predictions
mwhat1[W==0] = predict(rfm1,X[W==0,])$predictions

Y_tilde = mwhat1 - mwhat0 + W * (Y - mwhat1) / ehat - (1 - W) * (Y - mwhat0) / (1-ehat)

cate_dr = predict(regression_forest(X,Y_tilde))$predictions
hist(cate_dr)

Plot the results against each other to see that they are correlated, but far from finding the same predictions.

# Store and plot predictions
results = cbind(cate_sl,cate_tl,cate_cf,cate_rl_rf,cate_dr)
colnames(results) = c("S-learner","T-learner","Causal Forest","R-learner","DR-learner")
pairs.panels(results,method = "pearson")

describe(results)

DIY extensions:

  • Implement the R-learner with Lasso and different dictionaries or any other method that solves a weighted least squares problem.

  • Implement the DR-learner with the dr_learner().



Effect heterogeneity and its validation/inspection

To illustrate how the ideas for validating experiments generalize to the unconfoundedness setting, create a 50:50 sample split:

# Determine the number of rows in X
n_rows <- nrow(X)

# Generate a random vector of indices
indices <- sample(1:n_rows, size = 0.5*n_rows)

# Split the data
X_train <- X[indices,]
X_test <- X[-indices,]
W_train <- W[indices]
W_test <- W[-indices]
Y_train <- Y[indices]
Y_test <- Y[-indices]

Run causal forest in the training sample:

CF = causal_forest(X_train,Y_train,W_train,tune.parameters = "all")
cates = predict(CF,X_test)$predictions
hist(cates)

Disclaimer: Note that the following validation of heterogeneous effect estimates with observational data results from my current reading of the Generic ML paper but has not yet been rigorously assessed in a scientific study.


Best linear predictor (BLP)

Get the pseudo-outcome in the test sample:

aipw_test = DML_aipw(Y_test,W_test,X_test)
pseudoY = aipw_test$ATE$delta

and run the BLP as described in the slides:

demeaned_cates = cates - mean(cates)
lm_blp = lm_robust(pseudoY ~ demeaned_cates)
summary(lm_blp)

Call:
lm_robust(formula = pseudoY ~ demeaned_cates)

Standard error type:  HC2 

Coefficients:
                Estimate Std. Error t value  Pr(>|t|)  CI Lower  CI Upper   DF
(Intercept)    11535.700  1490.5102   7.739 1.202e-14 8613.6404 14457.760 4956
demeaned_cates     1.243     0.5167   2.406 1.614e-02    0.2305     2.256 4956

Multiple R-squared:  0.002278 , Adjusted R-squared:  0.002077 
F-statistic: 5.791 on 1 and 4956 DF,  p-value: 0.01614

This particular training and test split seems to detect systematic effect heterogeneity.


Sorted Group Average Treatment Effects (GATES)

Additionally, we run a GATES analysis as described in the slides with K=5.

First create the slices, then regression of the group indicators on pseudo outcome:

K = 5
slices = factor(as.numeric(cut(cates, breaks=quantile(cates, probs=seq(0,1, length = K+1)),include.lowest=TRUE)))
G_ind = model.matrix(~0+slices)
GATES_woc = lm_robust(pseudoY ~ 0 + G_ind)
# Print results
summary(GATES_woc)

Call:
lm_robust(formula = pseudoY ~ 0 + G_ind)

Standard error type:  HC2 

Coefficients:
             Estimate Std. Error t value  Pr(>|t|) CI Lower CI Upper   DF
G_indslices1     3093       1783   1.735 8.282e-02   -402.1     6588 4953
G_indslices2     6921       1700   4.070 4.770e-05   3587.5    10255 4953
G_indslices3     9511       2144   4.436 9.360e-06   5307.9    13714 4953
G_indslices4    17196       4126   4.168 3.123e-05   9108.3    25284 4953
G_indslices5    20958       5271   3.976 7.095e-05  10625.5    31291 4953

Multiple R-squared:  0.01581 ,  Adjusted R-squared:  0.01482 
F-statistic: 14.49 on 5 and 4953 DF,  p-value: 4.046e-14
# Plot results
se = GATES_woc$std.error
data.frame(Variable = paste("Group",1:K),
           Coefficient = GATES_woc$coefficients,
           cil = GATES_woc$coefficients - 1.96*se,
           ciu = GATES_woc$coefficients + 1.96*se) %>% 
  ggplot(aes(x=Variable,y=Coefficient,ymin=cil,ymax=ciu)) + geom_point(linewidth=2.5) +
  geom_errorbar(width=0.15) + geom_hline(yintercept=0) + geom_hline(yintercept = lm_blp$coefficients[1], linetype = "dashed")
Warnung: Ignoring unknown parameters: `linewidth`

We see a clear monotonic relationship that adds to the BLP evidence that the causal forest detected systematic heterogeneity.

Finally, we test \(H_0:\gamma_5 - \gamma_1 = 0\) by adding the constant to the GATES regression such that Group 1 becomes the reference group:

GATES_wc = lm_robust(pseudoY ~ G_ind[,-1])
summary(GATES_wc)

Call:
lm_robust(formula = pseudoY ~ G_ind[, -1])

Standard error type:  HC2 

Coefficients:
                   Estimate Std. Error t value Pr(>|t|) CI Lower CI Upper   DF
(Intercept)            3093       1783   1.735 0.082824   -402.1     6588 4953
G_ind[, -1]slices2     3828       2464   1.554 0.120274  -1001.6     8658 4953
G_ind[, -1]slices3     6418       2788   2.302 0.021387    951.9    11885 4953
G_ind[, -1]slices4    14103       4494   3.138 0.001711   5292.6    22914 4953
G_ind[, -1]slices5    17865       5564   3.211 0.001332   6957.6    28773 4953

Multiple R-squared:  0.003943 , Adjusted R-squared:  0.003139 
F-statistic: 4.785 on 4 and 4953 DF,  p-value: 0.0007482

Given the clear pattern in the graph it is not surprising that we can reject the null suggesting once more that causal forest finds systematic heterogeneity.


Classification analysis (CLAN)

After finding evidence that the heterogeneity is systematic, we want to understand which covariates are most predictive of the effect sizes. This is done by comparing covariate means across the sorted groups:

for (i in 1:ncol(X_test)) {
  print(colnames(X_test)[i])
  print(summary(lm_robust(X_test[,i] ~ slices)))
}
[1] "age"

Call:
lm_robust(formula = X_test[, i] ~ slices)

Standard error type:  HC2 

Coefficients:
            Estimate Std. Error t value   Pr(>|t|) CI Lower CI Upper   DF
(Intercept)   31.685     0.1570  201.79  0.000e+00   31.378    31.99 4953
slices2        5.082     0.3098   16.40  6.201e-59    4.475     5.69 4953
slices3       11.175     0.3385   33.02 2.806e-216   10.512    11.84 4953
slices4       15.437     0.3446   44.79  0.000e+00   14.761    16.11 4953
slices5       15.578     0.3166   49.20  0.000e+00   14.957    16.20 4953

Multiple R-squared:  0.3441 ,   Adjusted R-squared:  0.3436 
F-statistic: 980.1 on 4 and 4953 DF,  p-value: < 2.2e-16
[1] "db"

Call:
lm_robust(formula = X_test[, i] ~ slices)

Standard error type:  HC2 

Coefficients:
            Estimate Std. Error t value   Pr(>|t|) CI Lower CI Upper   DF
(Intercept)  0.04234   0.006396   6.619  3.996e-11  0.02980  0.05488 4953
slices2      0.09086   0.012551   7.239  5.213e-13  0.06625  0.11547 4953
slices3      0.14617   0.013974  10.460  2.418e-25  0.11877  0.17356 4953
slices4      0.31185   0.016491  18.910  4.632e-77  0.27952  0.34418 4953
slices5      0.60081   0.016508  36.395 3.126e-257  0.56844  0.63317 4953

Multiple R-squared:  0.2256 ,   Adjusted R-squared:  0.225 
F-statistic: 380.2 on 4 and 4953 DF,  p-value: < 2.2e-16
[1] "educ"

Call:
lm_robust(formula = X_test[, i] ~ slices)

Standard error type:  HC2 

Coefficients:
            Estimate Std. Error t value  Pr(>|t|) CI Lower CI Upper   DF
(Intercept)  13.4768    0.08795 153.241 0.000e+00  13.3044  13.6492 4953
slices2      -0.7341    0.12446  -5.898 3.912e-09  -0.9781  -0.4901 4953
slices3      -1.0544    0.12691  -8.309 1.238e-16  -1.3032  -0.8056 4953
slices4      -0.7503    0.12120  -6.191 6.480e-10  -0.9879  -0.5127 4953
slices5       0.9970    0.11835   8.424 4.705e-17   0.7650   1.2290 4953

Multiple R-squared:  0.06924 ,  Adjusted R-squared:  0.06848 
F-statistic: 99.88 on 4 and 4953 DF,  p-value: < 2.2e-16
[1] "fsize"

Call:
lm_robust(formula = X_test[, i] ~ slices)

Standard error type:  HC2 

Coefficients:
            Estimate Std. Error t value  Pr(>|t|) CI Lower CI Upper   DF
(Intercept)  2.61190    0.04955 52.7130 0.000e+00   2.5148   2.7090 4953
slices2      0.46984    0.07185  6.5390 6.817e-11   0.3290   0.6107 4953
slices3      0.54738    0.07207  7.5949 3.659e-14   0.4061   0.6887 4953
slices4      0.24078    0.06710  3.5884 3.359e-04   0.1092   0.3723 4953
slices5      0.02823    0.06713  0.4205 6.742e-01  -0.1034   0.1598 4953

Multiple R-squared:  0.02046 ,  Adjusted R-squared:  0.01967 
F-statistic: 24.83 on 4 and 4953 DF,  p-value: < 2.2e-16
[1] "hown"

Call:
lm_robust(formula = X_test[, i] ~ slices)

Standard error type:  HC2 

Coefficients:
            Estimate Std. Error t value   Pr(>|t|) CI Lower CI Upper   DF
(Intercept)   0.1512    0.01138   13.29  1.303e-39   0.1289   0.1735 4953
slices2       0.3553    0.01954   18.18  1.471e-71   0.3170   0.3937 4953
slices3       0.5867    0.01802   32.56 6.608e-211   0.5514   0.6220 4953
slices4       0.7126    0.01576   45.21  0.000e+00   0.6817   0.7435 4953
slices5       0.7641    0.01441   53.02  0.000e+00   0.7359   0.7924 4953

Multiple R-squared:  0.3384 ,   Adjusted R-squared:  0.3378 
F-statistic: 821.8 on 4 and 4953 DF,  p-value: < 2.2e-16
[1] "inc"

Call:
lm_robust(formula = X_test[, i] ~ slices)

Standard error type:  HC2 

Coefficients:
            Estimate Std. Error t value   Pr(>|t|) CI Lower CI Upper   DF
(Intercept)  27442.3      792.4  34.630 1.626e-235    25889    28996 4953
slices2        689.8      975.8   0.707  4.796e-01    -1223     2603 4953
slices3       4561.9     1012.7   4.505  6.804e-06     2576     6547 4953
slices4      10919.7     1022.8  10.676  2.543e-26     8915    12925 4953
slices5      32895.9     1141.8  28.810 6.919e-169    30657    35134 4953

Multiple R-squared:  0.2344 ,   Adjusted R-squared:  0.2338 
F-statistic: 308.5 on 4 and 4953 DF,  p-value: < 2.2e-16
[1] "male"

Call:
lm_robust(formula = X_test[, i] ~ slices)

Standard error type:  HC2 

Coefficients:
             Estimate Std. Error t value  Pr(>|t|) CI Lower   CI Upper   DF
(Intercept)  0.235887    0.01349 17.4908 1.622e-66  0.20945  0.2623263 4953
slices2     -0.036089    0.01853 -1.9476 5.152e-02 -0.07242  0.0002388 4953
slices3     -0.030242    0.01862 -1.6241 1.044e-01 -0.06675  0.0062623 4953
slices4     -0.081498    0.01771 -4.6010 4.310e-06 -0.11622 -0.0467721 4953
slices5     -0.006048    0.01899 -0.3186 7.501e-01 -0.04327  0.0311743 4953

Multiple R-squared:  0.005101 , Adjusted R-squared:  0.004298 
F-statistic: 6.987 on 4 and 4953 DF,  p-value: 1.322e-05
[1] "marr"

Call:
lm_robust(formula = X_test[, i] ~ slices)

Standard error type:  HC2 

Coefficients:
            Estimate Std. Error t value   Pr(>|t|) CI Lower CI Upper   DF
(Intercept)   0.4617    0.01584  29.154 1.363e-172  0.43065   0.4927 4953
slices2       0.1377    0.02221   6.200  6.119e-10  0.09416   0.1812 4953
slices3       0.1704    0.02203   7.732  1.273e-14  0.12717   0.2136 4953
slices4       0.2215    0.02167  10.221  2.775e-24  0.17898   0.2639 4953
slices5       0.1935    0.02188   8.846  1.243e-18  0.15065   0.2364 4953

Multiple R-squared:  0.02508 ,  Adjusted R-squared:  0.02429 
F-statistic: 30.92 on 4 and 4953 DF,  p-value: < 2.2e-16
[1] "pira"

Call:
lm_robust(formula = X_test[, i] ~ slices)

Standard error type:  HC2 

Coefficients:
            Estimate Std. Error t value  Pr(>|t|) CI Lower CI Upper   DF
(Intercept)  0.13810    0.01096 12.6013 7.339e-36  0.11662  0.15959 4953
slices2     -0.01399    0.01516 -0.9225 3.563e-01 -0.04371  0.01574 4953
slices3      0.01815    0.01591  1.1404 2.542e-01 -0.01305  0.04934 4953
slices4      0.15251    0.01812  8.4165 5.022e-17  0.11699  0.18803 4953
slices5      0.34375    0.01929 17.8214 6.573e-69  0.30594  0.38156 4953

Multiple R-squared:  0.1013 ,   Adjusted R-squared:  0.1005 
F-statistic: 113.4 on 4 and 4953 DF,  p-value: < 2.2e-16
[1] "twoearn"

Call:
lm_robust(formula = X_test[, i] ~ slices)

Standard error type:  HC2 

Coefficients:
            Estimate Std. Error t value  Pr(>|t|) CI Lower CI Upper   DF
(Intercept)   0.2651    0.01402  18.908 4.769e-77  0.23763   0.2926 4953
slices2       0.0931    0.02071   4.496 7.083e-06  0.05251   0.1337 4953
slices3       0.1149    0.02084   5.514 3.684e-08  0.07406   0.1558 4953
slices4       0.1819    0.02113   8.611 9.644e-18  0.14049   0.2233 4953
slices5       0.1956    0.02115   9.247 3.364e-20  0.15410   0.2370 4953

Multiple R-squared:  0.02088 ,  Adjusted R-squared:  0.02009 
F-statistic: 28.19 on 4 and 4953 DF,  p-value: < 2.2e-16

The most and the least affected groups differ substantially along most dimensions (besides male and family size). A general pattern emerges indicating that older and higher income persons have higher effects.


LS0tDQp0aXRsZTogIkNhdXNhbCBNTDogUHJlZGljdGluZyBlZmZlY3RzIg0Kc3VidGl0bGU6ICJBcHBsaWNhdGlvbiBub3RlYm9vayINCmF1dGhvcjogIk1pY2hhZWwgS25hdXMiDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclbS8leScpYCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQotLS0NCg0KDQo8YnI+DQoNClRoZXNlIGFyZSB0aGUgZ29hbHMgb2YgdG9kYXk6DQoNCi0gSGFuZC1jb2RlIFMtbGVhcm5lciwgVC1sZWFybmVyLCBDYXVzYWwgRm9yZXN0IChhdCBsZWFzdCBhIGxpdHRsZSBiaXQpLCBSLWxlYXJuZXIgYW5kIERSLWxlYXJuZXINCg0KPGJyPg0KDQojIDQwMShrKSBkYXRhc2V0IGFnYWluDQoNCldlIGFnYWluIHVzZSB0aGUgZGF0YSBvZiB0aGUgYGhkbWAgcGFja2FnZS4gVGhlIGRhdGEgd2FzIHVzZWQgaW4gW0NoZXJub3podWtvdiBhbmQgSGFuc2VuICgyMDA0KV0oaHR0cHM6Ly9kaXJlY3QubWl0LmVkdS9yZXN0L2FydGljbGUvODYvMy83MzUvNTc1ODYvVGhlLUVmZmVjdHMtb2YtNDAxLUstUGFydGljaXBhdGlvbi1vbi10aGUtV2VhbHRoKS4gVGhlaXIgcGFwZXIgaW52ZXN0aWdhdGVzIHRoZSBlZmZlY3Qgb2YgcGFydGljaXBhdGlvbiBpbiB0aGUgZW1wbG95ZXItc3BvbnNvcmVkIDQwMShrKSByZXRpcmVtZW50IHNhdmluZ3MgcGxhbiAoKnA0MDEqKSBvbiBuZXQgYXNzZXRzICgqbmV0X3RmYSopLiBTaW5jZSB0aGVuLCB0aGUgZGF0YSB3YXMgdXNlZCB0byBzaG93Y2FzZSBtYW55IG5ldyBtZXRob2RzLiBJdCBpcyBub3QgdGhlIG1vc3QgY29tcHJlaGVuc2l2ZSBkYXRhc2V0IHdpdGggYmFzaWNhbGx5IHRlbiBjb3ZhcmlhdGVzL3JlZ3Jlc3NvcnMvcHJlZGljdG9yczoNCg0KLSAqYWdlKjogYWdlDQoNCi0gKmRiKjogZGVmaW5lZCBiZW5lZml0IHBlbnNpb24NCg0KLSAqZWR1Yyo6IGVkdWNhdGlvbiAoaW4geWVhcnMpDQoNCi0gKmZzaXplKjogZmFtaWx5IHNpemUNCg0KLSAqaG93bio6IGhvbWUgb3duZXINCg0KLSAqaW5jKjogaW5jb21lIChpbiBVUyAkKQ0KDQotICptYWxlKjogbWFsZQ0KDQotICptYXJyKjogbWFycmllZA0KDQotICpwaXJhKjogcGFydGljaXBhdGlvbiBpbiBpbmRpdmlkdWFsIHJldGlyZW1lbnQgYWNjb3VudCAoSVJBKQ0KDQotICp0d29lYXJuKjogdHdvIGVhcm5lcnMNCg0KSG93ZXZlciwgaXQgaXMgcHVibGljbHkgYXZhaWxhYmxlIGFuZCB0aGUgZmV3IGNvbnRyb2xzIGVuc3VyZSB0aGF0IHRoZSBwcm9ncmFtcyBydW4gbm90IGFzIGxvbmcgYXMgd2l0aCBkYXRhc2V0cyB0aGF0IHlvdSBob3BlIHRvIGhhdmUgZm9yIHlvdXIgYXBwbGljYXRpb25zLg0KDQpgYGB7ciwgd2FybmluZz1GLG1lc3NhZ2U9Rn0NCmxpYnJhcnkoaGRtKQ0KbGlicmFyeShncmYpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocHN5Y2gpDQpsaWJyYXJ5KGVzdGltYXRyKQ0KIyBsaWJyYXJ5KGRldnRvb2xzKQ0KIyBpbnN0YWxsX2dpdGh1Yigic3VzYW5hdGhleS9jYXVzYWxUcmVlIikNCmxpYnJhcnkoY2F1c2FsVHJlZSkNCiMgbGlicmFyeShkZXZ0b29scykNCiMgaW5zdGFsbF9naXRodWIocmVwbz0iTUNLbmF1cy9jYXVzYWxETUwiKQ0KbGlicmFyeShjYXVzYWxETUwpDQoNCnNldC5zZWVkKDEyMzQpICMgZm9yIHJlcGxpY2FiaWxpdHkNCg0KIyBHZXQgZGF0YQ0KZGF0YShwZW5zaW9uKQ0KIyBPdXRjb21lDQpZID0gcGVuc2lvbiRuZXRfdGZhDQojIFRyZWF0bWVudA0KVyA9IHBlbnNpb24kcDQwMQ0KIyBUcmVhdG1lbnQNClogPSBwZW5zaW9uJGU0MDENCiMgQ3JlYXRlIG1haW4gZWZmZWN0cyBtYXRyaXgNClggPSBtb2RlbC5tYXRyaXgofiAwICsgYWdlICsgZGIgKyBlZHVjICsgZnNpemUgKyBob3duICsgaW5jICsgbWFsZSArIG1hcnIgKyBwaXJhICsgdHdvZWFybiwgZGF0YSA9IHBlbnNpb24pDQpgYGANCg0KV2Ugd2FudCB0byBwcmVkaWN0IGVmZmVjdHMgb2YgNDAxKGspIHBhcnRpY2lwYXRpb24gb24gbmV0IGFzc2V0cyB1c2luZyB0aGUgZm9sbG93aW5nIGVzdGltYXRvcnM6DQoNCi0gUy1sZWFybmVyICh0byBiZSBkZWZpbmVkKQ0KDQotIFQtbGVhcm5lcg0KDQotIENhdXNhbCBUcmVlDQoNCi0gQ2F1c2FsIEZvcmVzdA0KDQotIFItbGVhcm5lcg0KDQotIERSLWxlYXJuZXINCg0KDQo8YnI+DQo8YnI+DQoNCiMgUy1sZWFybmVyDQoNCldlIGRpZCBub3QgZGlzY3VzcyBpdCBpbiBjbGFzcywgYnV0IGFub3RoZXIgc3RyYWlnaHRmb3J3YXJkIHdheSBvZiBnZXR0aW5nIENBVEVzIGlzIHRvIG1vZGVsICRFW1l8VyxYXSQgYW5kIHRoZW4gZXZhbHVhdGUgaXQgYXQgJEVbWXwxLFhdIC0gRVtZfDAsWF0kDQoNCg0KYGBge3J9DQpXWCA9IGNiaW5kKFcsWCkNCnJmID0gcmVncmVzc2lvbl9mb3Jlc3QoV1gsWSkNClcwWCA9IGNiaW5kKHJlcCgwLGxlbmd0aChZKSksWCkNClcxWCA9IGNiaW5kKHJlcCgxLGxlbmd0aChZKSksWCkNCmNhdGVfc2wgPSBwcmVkaWN0KHJmLFcxWCkkcHJlZGljdGlvbnMgLSBwcmVkaWN0KHJmLFcwWCkkcHJlZGljdGlvbnMNCmhpc3QoY2F0ZV9zbCkNCmBgYA0KDQo8YnI+DQo8YnI+DQoNCg0KIyBULWxlYXJuZXINCg0KRm9yIHRoZSBULWxlYXJuZXIsIHdlIGltcGxlbWVudCB0aGUgZm9sbG93aW5nIHByb2NlZHVyZToNCg0KMS4gVXNlIE1MIGVzdGltYXRvciBvZiB5b3VyIGNob2ljZSB0byBmaXQgbW9kZWwgJFxoYXR7bX0oMSxYKSQgaW4gdHJlYXRlZH0NCg0KMi4gVXNlIE1MIGVzdGltYXRvciBvZiB5b3VyIGNob2ljZSB0byBmaXQgbW9kZWwgJFxoYXR7bX0oMCxYKSQgaW4gY29udHJvbHMNCg0KMy4gRXN0aW1hdGUgQ0FURSBhcyAkXGhhdHtcdGF1fSh4KSA9IFxoYXR7bX0oMSx4KSAtIFxoYXR7bX0oMCx4KSQNCg0KDQpgYGB7cn0NCnJmbTEgPSByZWdyZXNzaW9uX2ZvcmVzdChYW1c9PTEsXSxZW1c9PTFdKQ0KcmZtMCA9IHJlZ3Jlc3Npb25fZm9yZXN0KFhbVz09MCxdLFlbVz09MF0pDQpjYXRlX3RsID0gcHJlZGljdChyZm0xLCBYKSRwcmVkaWN0aW9ucyAtIHByZWRpY3QocmZtMCwgWCkkcHJlZGljdGlvbnMNCmhpc3QoY2F0ZV90bCkNCmBgYA0KDQo8YnI+DQo8YnI+DQoNCiMgQ2F1c2FsIFRyZWUNCg0KTGV0J3MgbW92ZSBvbiB3aXRoIHRoZSBDYXVzYWwgVHJlZSwgd2hpY2ggaXMgaW1wbGVtZW50ZWQgaW4gdGhlIHBhY2thZ2UgYW5kIGZ1bmN0aW9uIGBjYXVzYWxUcmVlYC4gVGhlIHBhY2thZ2UgaXMgYnVpbGQgZm9yIGV4cGVyaW1lbnRzLCBzbyB3ZSBkbyBub3QgY29udHJvbCBmb3IgY29uZm91bmRpbmcgaGVyZS4gSG93ZXZlciwgaWYgdGhlIGRhdGEgd2VyZSBleHBlcmltZW50YWwsIHRoaXMgaXMgd2hhdCB5b3Ugd291bGQgZG8gdG8gaW1wbGVtZW50IHRoZSBtZXRob2Q6DQoNCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30NCiMgUHJlcGFyZSBkYXRhIGZyYW1lDQpkZiA9IGRhdGEuZnJhbWUoWCxZKQ0KIyBJbXBsZW1lbnRlZCBjYXVzYWxUcmVlIGFkYXB0aW5nIHNwZWNpZmljYXRpb24gZnJvbSBSIGV4YW1wbGUNCmN0cmVlID0gY2F1c2FsVHJlZShZflgsIGRhdGEgPSBkZiwgdHJlYXRtZW50ID0gVywNCiAgICAgICAgICAgICAgICAgICBzcGxpdC5SdWxlID0gIkNUIiwgY3Yub3B0aW9uID0gIkNUIiwgc3BsaXQuSG9uZXN0ID0gVCxzcGxpdC5CdWNrZXQgPSBGLCB4dmFsID0gNSwgDQogICAgICAgICAgICAgICAgICAgY3AgPSAwLCBtaW5zaXplID0gMjApDQpvcGNwID0gY3RyZWUkY3B0YWJsZVssMV1bd2hpY2gubWluKGN0cmVlJGNwdGFibGVbLDRdKV0NCm9wZml0ID0gcHJ1bmUoY3RyZWUsIG9wY3ApDQpycGFydC5wbG90KG9wZml0KQ0KYGBgDQoNCldlIHNlZSBpbiB0aGUgcGFyZW50IG5vZGUgdGhhdCB0aGUg4oCcdHJlYXRtZW50IGVmZmVjdOKAnSBvZiAkMjdrIGlzIG11Y2ggbGFyZ2VyIHRoZW4gd2hhdCB3ZSBnZXQgYnkgY29udHJvbGxpbmcgZm9yIGNvbmZvdW5kaW5nIHdoZXJlIHRoZSBlZmZlY3RzIHJhbmdlZCBmcm9tICQxMWsgdG8gJDE1ay4gV2UgYWxzbyBkbyBub3Qga25vdyB3aGV0aGVyIHRoZSBzcGxpdHMgYXJlIGRyaXZlbiBieSBlZmZlY3QgaGV0ZXJvZ2VuZWl0eSBvciBoZXRlcm9nZW5lb3VzIHNlbGVjdGlvbiBiaWFzLg0KDQpIb3dldmVyLCBpZiB3ZSBpZ25vcmUgY29uZm91bmRpbmcgZm9yIHRoZSBzYWtlIG9mIHRoZSBhcmd1bWVudCwgdGhlIHRyZWUgc2hvd3Mgc3Vic3RhbnRpYWwgaGV0ZXJvZ2VuZWl0eSByZWdhcmRpbmcgYWdlIGFuZCBzb21lIG90aGVyIHZhcmlhYmxlcy4NCg0KR2l2ZW4gdGhlIG9idmlvdXMgc2VsZWN0aW9uIGJpYXMsIHdlIGRvIG5vdCBmb3JtIHByZWRpY3Rpb25zIGZvciBjYXVzYWwgdHJlZXMuDQoNCjxicj4NCjxicj4NCg0KIyBDYXVzYWwgRm9yZXN0DQoNCiMjIGBncmZgIHBhY2thZ2UNCg0KQ2F1c2FsIEZvcmVzdHMgYXJlIGltcGxlbWVudGVkIG1vc3QgY29udmVuaWVudGx5IHdpdGggdGhlIGBjYXVzYWxfZm9yZXN0YCBmdW5jdGlvbiBvZiB0aGUgYGdyZmAgcGFja2FnZS4gSWYgd2UgdXNlIHRoZSBgcHJlZGljdGAgZnVuY3Rpb24gZm9yIHRoZSBjcmVhdGVkIG9iamVjdCwgd2UgZ2V0IG91dC1vZi1iYWcgZXN0aW1hdGVkIElBVEVzIGZvciBldmVyeSBpbmRpdmlkdWFsIGluIG91ciBzYW1wbGU6DQoNCg0KYGBge3J9DQpjZiA9IGNhdXNhbF9mb3Jlc3QoWCwgWSwgVykNCmNhdGVfY2YgPSBwcmVkaWN0KGNmKSRwcmVkaWN0aW9ucw0KaGlzdChjYXRlX2NmKQ0KYGBgDQoNCiMjIFJlcGxpY2F0ZWQgdXNpbmcgd2VpZ2h0ZWQgUk9SIHJlZ3Jlc3Npb24NCg0KTm93IGxldCdzIHNlZSB3aGF0IHRoZSBDYXVzYWwgRm9yZXN0IGRvZXMgaW4gdGhlIGJhY2tncm91bmQuIFRvIHRoaXMgZW5kLCB3ZSB3YW50IHRvICJtYW51YWxseSIgcmVwbGljYXRlIHRoZSBwcmVkaWN0ZWQgZWZmZWN0IG9mIGluZGl2aWR1YWwgb25lLiBJdCBoYXMgYSBwcmVkaWN0ZWQgZWZmZWN0IG9mIGByIGNhdGVfY2ZbMV1gIGFuZCBoYXMgdGhlIGZvbGxvd2luZyBjaGFyYWN0ZXJpc3RpY3M6DQpgYGB7cn0NClhbMSxdDQpgYGANCg0KUmVjYWxsIHRoYXQgY2F1c2FsIGZvcmVzdCBlc3RpbWF0ZXMgQ0FURXMgdXNpbmcgdGhpcyBmb3JtdWxhOg0KJCQNClxiZWdpbntlcXVhdGlvbn0NCglcaGF0e1x0YXV9XntjZn0oeCkgPSBhcmdtaW5fe1x0YXV9IFxsZWZ0XHsgXHN1bV97aT0xfV5OIFxhbHBoYV9pKHgpIFxsZWZ0WyhZX2kgLSBcaGF0e219KFhfaSkpICAtIFx0YXUoeCkoV19pIC0gXGhhdHtlfShYX2kpKSBccmlnaHRdXjIgXHJpZ2h0XH0sDQpcZW5ke2VxdWF0aW9ufQ0KJCQNCndoZXJlICRcYWxwaGEoeCkkIGFyZSAkeCQtc3BlY2lmaWMgd2VpZ2h0cy4NCg0KV2UgY2FuIGltcGxlbWVudCB0aGlzIG91cnNlbHZlcyB0aGlzIGJlY2F1c2UgdGhlIGNhdXNhbCBmb3Jlc3Qgc2F2ZXMgYWxsIGNvbXBvbmVudHMgdGhhdCB3ZXJlIHVzZWQgaW4gdGhlIHByb2Nlc3MuDQoNCjEuIFdlIGV4dHJhY3QgdGhlIG51aXNhbmNlIHBhcmFtZXRlcnMgZXN0aW1hdGVzOg0KDQpgYGB7cn0NCm1oYXQgPSBjZiRZLmhhdA0KZWhhdCA9IGNmJFcuaGF0DQpgYGANCg0KMi4gV2UgZXh0cmFjdCB0aGUgd2VpZ2h0cyB1c2VkIGZvciBpbmRpdmlkdWFsIG9uZSB1c2luZyBgZ2V0X2ZvcmVzdF93ZWlnaHRzYDoNCg0KYGBge3J9DQphbHBoYXggPSBnZXRfZm9yZXN0X3dlaWdodHMoY2YpWzEsXQ0KaGlzdChhcy5udW1lcmljKGFscGhheCkpDQpgYGANCg0KMy4gV2UgY3JlYXRlIHRoZSByZXNpZHVhbHMgYW5kIHJ1biBhIHdlaWdodGVkIHJlc2lkdWFsLW9uLXJlc2lkdWFsIHJlZ3Jlc3Npb24gKFJPUlIpIHcvbyBjb25zdGFudDoNCg0KYGBge3J9DQpZcmVzID0gWSAtIG1oYXQNCldyZXMgPSBXIC0gZWhhdA0KbWFudWFsX2NhdGUgPSBsbShZcmVzIH4gMCArIFdyZXMsd2VpZ2h0cyA9IGFzLm51bWVyaWMoYWxwaGF4KSkkY29lZmZpY2llbnRzDQptYW51YWxfY2F0ZQ0KYGBgDQoNCkxldCdzIGNoZWNrIHdoZXRoZXIgdGhpcyBpcyBlcXVhbCB0byB0aGUgYGNhdXNhbF9mb3Jlc3RgIHByZWRpY3Rpb246DQoNCmBgYHtyfQ0KYWxsLmVxdWFsKGFzLm51bWVyaWMoY2F0ZV9jZlsxXSksYXMubnVtZXJpYyhtYW51YWxfY2F0ZSkpDQpgYGANCg0KTm9wZSA6LSgNCg0KV2hhdCBkaWQgd2UgbWlzcz8NCg0KVGhlIGBjYXVzYWxfZm9yZXN0YCBydW5zIHRoZSB3ZWlnaHRlZCBST1JSIHdpdGggY29uc3RhbnQuIFRoaXMgY29uc3RhbnQgc2hvdWxkIGFzeW1wdG90aWNhbGx5IGJlIHplcm8sIGJ1dCBpbiBmaW5pdGUgc2FtcGxlcyBpdCBpcyBub3Q6DQoNCmBgYHtyfQ0KWXJlcyA9IFkgLSBtaGF0DQpXcmVzID0gVyAtIGVoYXQNCm1hbnVhbF9jYXRlX2NvbnN0ID0gbG0oWXJlcyB+IFdyZXMsd2VpZ2h0cyA9IGFzLm51bWVyaWMoYWxwaGF4KSkNCnN1bW1hcnkobWFudWFsX2NhdGVfY29uc3QpDQpgYGANCg0KV2l0aCBjb25zdGFudCB0aGUgcmVzdWx0cyBmcm9tIHRoZSBwYWNrYWdlIGFuZCBvdXIgIm1hbnVhbGx5IiBjb2RlZCB2ZXJzaW9uIGNvaW5jaWRlOg0KYGBge3J9DQphbGwuZXF1YWwoYXMubnVtZXJpYyhjYXRlX2NmWzFdKSxhcy5udW1lcmljKG1hbnVhbF9jYXRlX2NvbnN0JGNvZWZmaWNpZW50c1syXSkpDQpgYGANCg0KQWZ0ZXIgYWxsLCBpdCBib2lsZWQgZG93biB0byBydW4gT0xTLi4uDQoNCjxicj4NCjxicj4NCg0KDQojIFItbGVhcm5lcg0KDQojIyBIYW5kLWNvZGVkIHVzaW5nIG1vZGlmaWVkIGNvdmFyaWF0ZXMgKE9MUykNCg0KVG8gaWxsdXN0cmF0ZSBob3cgd2UgY2FuIGltcGxlbWVudCB0aGUgUi1sZWFuZXIgbGlrZSB0aGlzIGZvciBhc3N1bWluZyBhIGxpbmVhciBDQVRFIG1vZGVsDQokJA0KXGJlZ2lue2VxdWF0aW9ufQ0KXGhhdHtcYmV0YX1ee3JsfSA9IGFyZ21pbl97XGJldGF9IFxzdW1fe2k9MX1eTiBcYmlnKFlfaSAtIFxoYXR7bX0oWF9pKSAtICBYX2leKiBcYmV0YSBcYmlnKV4yDQpcZW5ke2VxdWF0aW9ufQ0KJCQNCndlIHJlY3ljbGUgdGhlIG51aXNhbmNlIHBhcmFtZXRlcnMgb2YgYWJvdmUgYW5kIGp1c3QgbmVlZCB0byBkZWZpbmUgdGhlIG1vZGlmaWVkL3BzZXVkby1jb3ZhcmlhdGVzICRYXiogPSBYKFcgLSBcaGF0e2V9KFgpKSQgDQoNCg0KYGBge3J9DQojIENyZWF0ZSByZXNpZHVhbHMNCnJlc195ID0gWS1taGF0DQpyZXNfdyA9IFctZWhhdA0KDQojIE1vZGlmeSBjb3ZhcmlhdGVzIChtdWx0aXBseSBlYWNoIGNvbHVtbiBpbmNsdWRpbmcgY29uc3RhbnQgd2l0aCByZXNpZHVhbCkNCm4gPSBsZW5ndGgoWSkNClhfd2MgPSBjYmluZChyZXAoMSxuKSxYKQ0KWHN0YXIgPSBYX3djICogcmVzX3cNCiMgUmVncmVzcyBvdXRjb21lIHJlc2lkdWFsIG9uIG1vZGlmaWVkIGNvdmFyaWF0ZXMNCnJsX29scyA9IGxtKHJlc195IH4gMCArIFhzdGFyKQ0Kc3VtbWFyeShybF9vbHMpDQpgYGANCg0KTm93IHdlIG5lZWQgdG8gdGFrZSB0aGUgY29lZmZpY2llbnRzIG9mIHRoaXMgbW9kZWwgdG8gZ2V0IGZpdHRlZCB2YWx1ZXMgb2YgdGhlIENBVEVzIGFzICRYIFxiZXRhJCAoIWZpdHRlZCB2YWx1ZXMgbmVlZCB0byBiZSBjYWxjdWxhdGVkIHVzaW5nICRYJCwgbm90ICRYXiokLCBkbyBub3QgcmVwZWF0IGEgbWlzdGFrZSBJIGRpZCBpbiB0aGUgYmVnaW5uaW5nICEpDQoNCmBgYHtyfQ0KY2F0ZV9ybF9vbHMgPSBYX3djICUqJSBybF9vbHMkY29lZmZpY2llbnRzDQpoaXN0KGNhdGVfcmxfb2xzKQ0KYGBgDQoNCjxicj4NCg0KIyMgSGFuZC1jb2RlZCB1c2luZyBwc2V1ZG8tb3V0Y29tZXMgYW5kIHdlaWdodHMgKE9MUykNCg0KVGhlIG1vcmUgZ2VuZXJpYyBhbHRlcm5hdGl2ZSBpcyB0byB1c2UgdGhlIHVubW9kaWZpZWQgY292YXJpYXRlcyBpbiBhIHdlaWdodGVkIHJlZ3Jlc3Npb24gd2l0aCBwc2V1ZG8gb3V0Y29tZXM6DQokJFxoYXR7XHRhdX1ee3JsfSh4KSAgPSBhcmdtaW5fe1x0YXV9IFxzdW1fe2k9MX1eTiBcdW5kZXJicmFjZXsoV19pIC0gXGhhdHtlfShYX2kpKV4yfV97XHRleHR7d2VpZ2h0fX0gXGxlZnQoXHVuZGVyYnJhY2V7XGZyYWN7WV9pIC0gXGhhdHttfShYX2kpfXtXX2kgLSBcaGF0e2V9KFhfaSl9fV97XHRleHR7cHNldWRvLW91dGNvbWV9fSAtICBYX2lcYmV0YSBccmlnaHQpXjIkJA0KDQpGb3IgdGhlIHNha2Ugb2YgdGhlIGFyZ3VtZW50IHJ1biBpdCBhZ2FpbiB3aXRoIE9MUw0KDQpgYGB7cn0NCiMgQ3JlYXRlIHBzZXVkby1vdXRjb21lIChvdXRjb21lIHJlcyBkaXZpZGVkIGJ5IHRyZWF0bWVudCByZXMpDQpwc2V1ZG9fcmwgPSByZXNfeSAvIHJlc193DQoNCiMgQ3JlYXRlIHdlaWdodHMNCndlaWdodHNfcmwgPSByZXNfd14yDQoNCiMgV2VpZ2h0ZWQgcmVncmVzc2lvbiBvZiBwc2V1ZG8tb3V0Y29tZSBvbiBjb3ZhcmlhdGVzDQpyb2xzX2ZpdCA9IGxtKHBzZXVkb19ybCB+IFgsIHdlaWdodHM9d2VpZ2h0c19ybCkNCnN1bW1hcnkocm9sc19maXQpDQpgYGANCg0KYW5kIG9ic2VydmUgdGhhdCBib3RoIGltcGxlbWVudGF0aW9ucyBkZWxpdmVyIGlkZW50aWNhbCByZXN1bHRzOg0KDQpgYGB7cn0NCmFsbC5lcXVhbChhcy5udW1lcmljKHJsX29scyRjb2VmZmljaWVudHMpLGFzLm51bWVyaWMocm9sc19maXQkY29lZmZpY2llbnRzKSkNCmBgYA0KVGhpcyB3YXMganVzdCBmb3IgaWxsdXN0cmF0aW9uIHRoYXQgYm90aCByZXByZXNlbnRhdGlvbnMgcHJvdmlkZSBpZGVudGljYWwgcmVzdWx0cyB3aGVuIHNvbHZlZCB3aXRoIGEgbWV0aG9kIHdpdGhvdXQgcmFuZG9tIGNvbXBvbmVudHMuIEluIHByYWN0aWNlLCB3ZSBjYW4gYXBwbHkgYW55dGhpbmcgdGhhdCBzb2x2ZXMgdGhlICh3ZWlnaHRlZCkgbWluaW1pemF0aW9uIHByb2JsZW0uDQoNCg0KPGJyPg0KDQojIyBIYW5kLWNvZGVkIHVzaW5nIHBzZXVkby1vdXRjb21lcyBhbmQgd2VpZ2h0cyAoUmFuZG9tIEZvcmVzdCkNCg0KR29pbmcgYmV5b25kIHB1cmUgaWxsdXN0cmF0aW9uLCB3ZSBpbXBsZW1lbnQgdGhlIFItbGVhcm5lciBub3cgdXNpbmcgcmFuZG9tIGZvcmVzdDoNCg0KDQpgYGB7cn0NCiMgV2VpZ2h0ZWQgcmVncmVzc2lvbiB3aXRoIFJGDQpycmZfZml0ID0gcmVncmVzc2lvbl9mb3Jlc3QoWCxwc2V1ZG9fcmwsIHNhbXBsZS53ZWlnaHRzID0gd2VpZ2h0c19ybCkNCmNhdGVfcmxfcmYgPSBwcmVkaWN0KHJyZl9maXQpJHByZWRpY3Rpb25zDQpoaXN0KGNhdGVfcmxfcmYpDQpgYGANCg0KPGJyPg0KPGJyPg0KDQojIERSLWxlYXJuZXINCg0KRmluYWxseSwgcnVuIHRoZSBEUi1sZWFybmVyIHRoYXQgY3JlYXRlcyB0aGUgcHNldWRvLW91dGNvbWUgaW4gYSBmaXJzdCBzdGVwDQokJA0KICAgXGJlZ2lue2FsaWdufQ0KICAgICAgICAgXHRpbGRle1l9X3tBVEV9ID0gXHVuZGVyYnJhY2V7XGhhdHttfSgxLFgpIC0gXGhhdHttfSgwLFgpfV97XHRleHR7b3V0Y29tZSBwcmVkaWN0aW9uc319ICsgXHVuZGVyYnJhY2V7XGZyYWN7VyAoWSAtIFxoYXR7bX0oMSxYKSl9e1xoYXR7ZX0oWCl9IC0gXGZyYWN7KDEtVykgKFkgLSBcaGF0e219KDAsWCkpfXsxLVxoYXR7ZX0oWCl9fV97XHRleHR7d2VpZ2h0ZWQgcmVzaWR1YWxzfX0gXG5vbnVtYmVyDQogICAgXGVuZHthbGlnbn0NCiQkDQoNCmFuZCB1c2VzIGl0IGluIGEgZmluYWwgcmVncmVzc2lvbiBzdGVwLg0KDQpgYGB7cn0NCm13aGF0MCA9IG13aGF0MSA9IHJlcChOQSxsZW5ndGgoWSkpDQpyZm0wID0gcmVncmVzc2lvbl9mb3Jlc3QoWFtXPT0wLF0sWVtXPT0wXSkNCm13aGF0MFtXPT0wXSA9IHByZWRpY3QocmZtMCkkcHJlZGljdGlvbnMNCm13aGF0MFtXPT0xXSA9IHByZWRpY3QocmZtMCxYW1c9PTEsXSkkcHJlZGljdGlvbnMNCg0KcmZtMSA9IHJlZ3Jlc3Npb25fZm9yZXN0KFhbVz09MSxdLFlbVz09MV0pDQptd2hhdDEgPSBwcmVkaWN0KHJmbTEpJHByZWRpY3Rpb25zDQptd2hhdDFbVz09MV0gPSBwcmVkaWN0KHJmbTEpJHByZWRpY3Rpb25zDQptd2hhdDFbVz09MF0gPSBwcmVkaWN0KHJmbTEsWFtXPT0wLF0pJHByZWRpY3Rpb25zDQoNCllfdGlsZGUgPSBtd2hhdDEgLSBtd2hhdDAgKyBXICogKFkgLSBtd2hhdDEpIC8gZWhhdCAtICgxIC0gVykgKiAoWSAtIG13aGF0MCkgLyAoMS1laGF0KQ0KDQpjYXRlX2RyID0gcHJlZGljdChyZWdyZXNzaW9uX2ZvcmVzdChYLFlfdGlsZGUpKSRwcmVkaWN0aW9ucw0KaGlzdChjYXRlX2RyKQ0KYGBgDQoNClBsb3QgdGhlIHJlc3VsdHMgYWdhaW5zdCBlYWNoIG90aGVyIHRvIHNlZSB0aGF0IHRoZXkgYXJlIGNvcnJlbGF0ZWQsIGJ1dCBmYXIgZnJvbSBmaW5kaW5nIHRoZSBzYW1lIHByZWRpY3Rpb25zLg0KDQpgYGB7cn0NCiMgU3RvcmUgYW5kIHBsb3QgcHJlZGljdGlvbnMNCnJlc3VsdHMgPSBjYmluZChjYXRlX3NsLGNhdGVfdGwsY2F0ZV9jZixjYXRlX3JsX3JmLGNhdGVfZHIpDQpjb2xuYW1lcyhyZXN1bHRzKSA9IGMoIlMtbGVhcm5lciIsIlQtbGVhcm5lciIsIkNhdXNhbCBGb3Jlc3QiLCJSLWxlYXJuZXIiLCJEUi1sZWFybmVyIikNCnBhaXJzLnBhbmVscyhyZXN1bHRzLG1ldGhvZCA9ICJwZWFyc29uIikNCmRlc2NyaWJlKHJlc3VsdHMpDQpgYGANCg0KIyMgRElZIGV4dGVuc2lvbnM6DQoNCi0gSW1wbGVtZW50IHRoZSBSLWxlYXJuZXIgd2l0aCBMYXNzbyBhbmQgZGlmZmVyZW50IGRpY3Rpb25hcmllcyBvciBhbnkgb3RoZXIgbWV0aG9kIHRoYXQgc29sdmVzIGEgd2VpZ2h0ZWQgbGVhc3Qgc3F1YXJlcyBwcm9ibGVtLg0KDQotIEltcGxlbWVudCB0aGUgRFItbGVhcm5lciB3aXRoIHRoZSBgZHJfbGVhcm5lcigpYC4NCg0KDQo8YnI+DQo8YnI+DQoNCiMgRWZmZWN0IGhldGVyb2dlbmVpdHkgYW5kIGl0cyB2YWxpZGF0aW9uL2luc3BlY3Rpb24NCg0KVG8gaWxsdXN0cmF0ZSBob3cgdGhlIGlkZWFzIGZvciB2YWxpZGF0aW5nIGV4cGVyaW1lbnRzIGdlbmVyYWxpemUgdG8gdGhlIHVuY29uZm91bmRlZG5lc3Mgc2V0dGluZywgY3JlYXRlIGEgNTA6NTAgc2FtcGxlIHNwbGl0Og0KDQpgYGB7cn0NCiMgRGV0ZXJtaW5lIHRoZSBudW1iZXIgb2Ygcm93cyBpbiBYDQpuX3Jvd3MgPC0gbnJvdyhYKQ0KDQojIEdlbmVyYXRlIGEgcmFuZG9tIHZlY3RvciBvZiBpbmRpY2VzDQppbmRpY2VzIDwtIHNhbXBsZSgxOm5fcm93cywgc2l6ZSA9IDAuNSpuX3Jvd3MpDQoNCiMgU3BsaXQgdGhlIGRhdGENClhfdHJhaW4gPC0gWFtpbmRpY2VzLF0NClhfdGVzdCA8LSBYWy1pbmRpY2VzLF0NCldfdHJhaW4gPC0gV1tpbmRpY2VzXQ0KV190ZXN0IDwtIFdbLWluZGljZXNdDQpZX3RyYWluIDwtIFlbaW5kaWNlc10NCllfdGVzdCA8LSBZWy1pbmRpY2VzXQ0KYGBgDQoNClJ1biBjYXVzYWwgZm9yZXN0IGluIHRoZSB0cmFpbmluZyBzYW1wbGU6DQpgYGB7cn0NCkNGID0gY2F1c2FsX2ZvcmVzdChYX3RyYWluLFlfdHJhaW4sV190cmFpbix0dW5lLnBhcmFtZXRlcnMgPSAiYWxsIikNCmNhdGVzID0gcHJlZGljdChDRixYX3Rlc3QpJHByZWRpY3Rpb25zDQpoaXN0KGNhdGVzKQ0KYGBgDQoNCipEaXNjbGFpbWVyOiogTm90ZSB0aGF0IHRoZSBmb2xsb3dpbmcgdmFsaWRhdGlvbiBvZiBoZXRlcm9nZW5lb3VzIGVmZmVjdCBlc3RpbWF0ZXMgd2l0aCBvYnNlcnZhdGlvbmFsIGRhdGEgcmVzdWx0cyBmcm9tIG15IGN1cnJlbnQgcmVhZGluZyBvZiB0aGUgR2VuZXJpYyBNTCBwYXBlciBidXQgaGFzIG5vdCB5ZXQgYmVlbiByaWdvcm91c2x5IGFzc2Vzc2VkIGluIGEgc2NpZW50aWZpYyBzdHVkeS4NCg0KPGJyPg0KDQojIyBCZXN0IGxpbmVhciBwcmVkaWN0b3IgKEJMUCkNCg0KR2V0IHRoZSBwc2V1ZG8tb3V0Y29tZSBpbiB0aGUgdGVzdCBzYW1wbGU6DQoNCmBgYHtyfQ0KYWlwd190ZXN0ID0gRE1MX2FpcHcoWV90ZXN0LFdfdGVzdCxYX3Rlc3QpDQpwc2V1ZG9ZID0gYWlwd190ZXN0JEFURSRkZWx0YQ0KYGBgDQoNCmFuZCBydW4gdGhlIEJMUCBhcyBkZXNjcmliZWQgaW4gdGhlIHNsaWRlczoNCg0KYGBge3J9DQpkZW1lYW5lZF9jYXRlcyA9IGNhdGVzIC0gbWVhbihjYXRlcykNCmxtX2JscCA9IGxtX3JvYnVzdChwc2V1ZG9ZIH4gZGVtZWFuZWRfY2F0ZXMpDQpzdW1tYXJ5KGxtX2JscCkNCmBgYA0KDQpUaGlzIHBhcnRpY3VsYXIgdHJhaW5pbmcgYW5kIHRlc3Qgc3BsaXQgc2VlbXMgdG8gZGV0ZWN0IHN5c3RlbWF0aWMgZWZmZWN0IGhldGVyb2dlbmVpdHkuDQoNCjxicj4NCg0KIyMgU29ydGVkIEdyb3VwIEF2ZXJhZ2UgVHJlYXRtZW50IEVmZmVjdHMgKEdBVEVTKQ0KDQpBZGRpdGlvbmFsbHksIHdlIHJ1biBhIEdBVEVTIGFuYWx5c2lzIGFzIGRlc2NyaWJlZCBpbiB0aGUgc2xpZGVzIHdpdGggYEs9NWAuDQoNCkZpcnN0IGNyZWF0ZSB0aGUgc2xpY2VzLCB0aGVuIHJlZ3Jlc3Npb24gb2YgdGhlIGdyb3VwIGluZGljYXRvcnMgb24gcHNldWRvIG91dGNvbWU6DQoNCmBgYHtyfQ0KSyA9IDUNCnNsaWNlcyA9IGZhY3Rvcihhcy5udW1lcmljKGN1dChjYXRlcywgYnJlYWtzPXF1YW50aWxlKGNhdGVzLCBwcm9icz1zZXEoMCwxLCBsZW5ndGggPSBLKzEpKSxpbmNsdWRlLmxvd2VzdD1UUlVFKSkpDQpHX2luZCA9IG1vZGVsLm1hdHJpeCh+MCtzbGljZXMpDQpHQVRFU193b2MgPSBsbV9yb2J1c3QocHNldWRvWSB+IDAgKyBHX2luZCkNCiMgUHJpbnQgcmVzdWx0cw0Kc3VtbWFyeShHQVRFU193b2MpDQoNCiMgUGxvdCByZXN1bHRzDQpzZSA9IEdBVEVTX3dvYyRzdGQuZXJyb3INCmRhdGEuZnJhbWUoVmFyaWFibGUgPSBwYXN0ZSgiR3JvdXAiLDE6SyksDQogICAgICAgICAgIENvZWZmaWNpZW50ID0gR0FURVNfd29jJGNvZWZmaWNpZW50cywNCiAgICAgICAgICAgY2lsID0gR0FURVNfd29jJGNvZWZmaWNpZW50cyAtIDEuOTYqc2UsDQogICAgICAgICAgIGNpdSA9IEdBVEVTX3dvYyRjb2VmZmljaWVudHMgKyAxLjk2KnNlKSAlPiUgDQogIGdncGxvdChhZXMoeD1WYXJpYWJsZSx5PUNvZWZmaWNpZW50LHltaW49Y2lsLHltYXg9Y2l1KSkgKyBnZW9tX3BvaW50KGxpbmV3aWR0aD0yLjUpICsNCiAgZ2VvbV9lcnJvcmJhcih3aWR0aD0wLjE1KSArIGdlb21faGxpbmUoeWludGVyY2VwdD0wKSArIGdlb21faGxpbmUoeWludGVyY2VwdCA9IGxtX2JscCRjb2VmZmljaWVudHNbMV0sIGxpbmV0eXBlID0gImRhc2hlZCIpDQpgYGANCg0KV2Ugc2VlIGEgY2xlYXIgbW9ub3RvbmljIHJlbGF0aW9uc2hpcCB0aGF0IGFkZHMgdG8gdGhlIEJMUCBldmlkZW5jZSB0aGF0IHRoZSBjYXVzYWwgZm9yZXN0IGRldGVjdGVkIHN5c3RlbWF0aWMgaGV0ZXJvZ2VuZWl0eS4NCg0KRmluYWxseSwgd2UgdGVzdCAkSF8wOlxnYW1tYV81IC0gXGdhbW1hXzEgPSAwJCBieSBhZGRpbmcgdGhlIGNvbnN0YW50IHRvIHRoZSBHQVRFUyByZWdyZXNzaW9uIHN1Y2ggdGhhdCBHcm91cCAxIGJlY29tZXMgdGhlIHJlZmVyZW5jZSBncm91cDoNCg0KYGBge3J9DQpHQVRFU193YyA9IGxtX3JvYnVzdChwc2V1ZG9ZIH4gR19pbmRbLC0xXSkNCnN1bW1hcnkoR0FURVNfd2MpDQpgYGANCg0KR2l2ZW4gdGhlIGNsZWFyIHBhdHRlcm4gaW4gdGhlIGdyYXBoIGl0IGlzIG5vdCBzdXJwcmlzaW5nIHRoYXQgd2UgY2FuIHJlamVjdCB0aGUgbnVsbCBzdWdnZXN0aW5nIG9uY2UgbW9yZSB0aGF0IGNhdXNhbCBmb3Jlc3QgZmluZHMgc3lzdGVtYXRpYyBoZXRlcm9nZW5laXR5Lg0KDQo8YnI+DQoNCiMjIENsYXNzaWZpY2F0aW9uIGFuYWx5c2lzIChDTEFOKQ0KDQpBZnRlciBmaW5kaW5nIGV2aWRlbmNlIHRoYXQgdGhlIGhldGVyb2dlbmVpdHkgaXMgc3lzdGVtYXRpYywgd2Ugd2FudCB0byB1bmRlcnN0YW5kIHdoaWNoIGNvdmFyaWF0ZXMgYXJlIG1vc3QgcHJlZGljdGl2ZSBvZiB0aGUgZWZmZWN0IHNpemVzLiBUaGlzIGlzIGRvbmUgYnkgY29tcGFyaW5nIGNvdmFyaWF0ZSBtZWFucyBhY3Jvc3MgdGhlIHNvcnRlZCBncm91cHM6DQoNCmBgYHtyfQ0KZm9yIChpIGluIDE6bmNvbChYX3Rlc3QpKSB7DQogIHByaW50KGNvbG5hbWVzKFhfdGVzdClbaV0pDQogIHByaW50KHN1bW1hcnkobG1fcm9idXN0KFhfdGVzdFssaV0gfiBzbGljZXMpKSkNCn0NCg0KYGBgYCANCg0KVGhlIG1vc3QgYW5kIHRoZSBsZWFzdCBhZmZlY3RlZCBncm91cHMgZGlmZmVyIHN1YnN0YW50aWFsbHkgYWxvbmcgbW9zdCBkaW1lbnNpb25zIChiZXNpZGVzIG1hbGUgYW5kIGZhbWlseSBzaXplKS4gQSBnZW5lcmFsIHBhdHRlcm4gZW1lcmdlcyBpbmRpY2F0aW5nIHRoYXQgb2xkZXIgYW5kIGhpZ2hlciBpbmNvbWUgcGVyc29ucyBoYXZlIGhpZ2hlciBlZmZlY3RzLg0KDQo8YnI+DQoNCg0K