Regression-based imputation for the Social Indicators Survey. See Chapter 17 in Regression and Other Stories.


Load packages

library("rprojroot")
root<-has_file(".ROS-Examples-root")$make_fix_file()
library("rstanarm")

Load data

SIS <- read.csv(root("Imputation/data","SIS.csv"))
head(SIS)
  earnings retirement interest assistance other male over65 white immig educ_r
1     84.0        0.7     0.20          0   0.0    1      0     1     1      4
2       NA        0.0     0.00          0   0.0    0      0     0     1      4
3     27.5        0.0     0.16          0   0.0    1      0     0     1      2
4     85.0       12.0     5.00          0    NA    1      1     1     0      4
5    135.0        0.0     0.10          0   0.1    1      0     0     0      4
6      0.0         NA       NA         NA    NA    0      0     0     0      4
  workmos workhrs_top any_ssi any_welfare any_charity
1      12          40       0           0           0
2      12          40       0           0           0
3      10          40       0           0           0
4      11           8       0           0           0
5      12          40       0           0           0
6       0          40       0           0           0
summary(SIS)
    earnings         retirement        interest         assistance     
 Min.   :   0.00   Min.   :  0.00   Min.   :  0.000   Min.   : 0.0000  
 1st Qu.:   4.80   1st Qu.:  0.00   1st Qu.:  0.000   1st Qu.: 0.0000  
 Median :  28.00   Median :  0.00   Median :  0.000   Median : 0.0000  
 Mean   :  52.19   Mean   :  2.19   Mean   :  1.722   Mean   : 0.6821  
 3rd Qu.:  65.00   3rd Qu.:  0.00   3rd Qu.:  0.000   3rd Qu.: 0.0000  
 Max.   :3250.00   Max.   :300.00   Max.   :650.000   Max.   :36.4000  
 NA's   :241       NA's   :123      NA's   :277       NA's   :128      
     other             male            over65            white       
 Min.   : 0.000   Min.   :0.0000   Min.   :0.00000   Min.   :0.0000  
 1st Qu.: 0.000   1st Qu.:0.0000   1st Qu.:0.00000   1st Qu.:0.0000  
 Median : 0.000   Median :0.0000   Median :0.00000   Median :0.0000  
 Mean   : 0.577   Mean   :0.3618   Mean   :0.07728   Mean   :0.3185  
 3rd Qu.: 0.025   3rd Qu.:1.0000   3rd Qu.:0.00000   3rd Qu.:1.0000  
 Max.   :96.000   Max.   :1.0000   Max.   :1.00000   Max.   :1.0000  
 NA's   :133                                                         
     immig            educ_r         workmos        workhrs_top   
 Min.   :0.0000   Min.   :1.000   Min.   : 0.000   Min.   : 0.00  
 1st Qu.:0.0000   1st Qu.:2.000   1st Qu.: 7.000   1st Qu.:26.00  
 Median :0.0000   Median :3.000   Median :12.000   Median :40.00  
 Mean   :0.3924   Mean   :2.747   Mean   : 8.985   Mean   :30.19  
 3rd Qu.:1.0000   3rd Qu.:4.000   3rd Qu.:12.000   3rd Qu.:40.00  
 Max.   :1.0000   Max.   :4.000   Max.   :12.000   Max.   :40.00  
                                                                  
    any_ssi         any_welfare       any_charity      
 Min.   :0.00000   Min.   :0.00000   Min.   :0.000000  
 1st Qu.:0.00000   1st Qu.:0.00000   1st Qu.:0.000000  
 Median :0.00000   Median :0.00000   Median :0.000000  
 Mean   :0.03664   Mean   :0.03931   Mean   :0.009993  
 3rd Qu.:0.00000   3rd Qu.:0.00000   3rd Qu.:0.000000  
 Max.   :1.00000   Max.   :1.00000   Max.   :1.000000  
                                                       

Imputation helper functions

Create a completed data vector using imputations

impute <- function(a, a_impute) {
  ifelse(is.na(a), a_impute, a)
}

Top code function

topcode <- function(a, top) {
  ifelse(a > top, top, a)
}

Deterministic imputation

Impute 0 earnings using the logical rule (if worked 0 months and 0 hrs/wk)

SIS$earnings_top <- topcode(SIS$earnings, 100)
SIS$earnings_top[SIS$workhrs_top==0 & SIS$workmos==0] <- 0

Create a dataset with all predictor variables

n <- nrow(SIS)
SIS_predictors <- SIS[,c("male","over65","white","immig","educ_r","workmos",
                         "workhrs_top","any_ssi","any_welfare","any_charity")]

Impute subset of earnings that are nonzero: linear scale

fit_imp_1 <- stan_glm(
  earnings ~ male + over65 + white + immig + educ_r +
              workmos + workhrs_top + any_ssi +
              any_welfare + any_charity,
  data = SIS,
  subset = earnings > 0,
  refresh = 0
)
print(fit_imp_1)
stan_glm
 family:       gaussian [identity]
 formula:      earnings ~ male + over65 + white + immig + educ_r + workmos + 
       workhrs_top + any_ssi + any_welfare + any_charity
 observations: 988
 predictors:   11
------
            Median MAD_SD
(Intercept) -86.3   29.9 
male         -0.3    9.1 
over65      -40.2   38.1 
white        25.9   10.2 
immig       -12.1    9.4 
educ_r       22.4    4.5 
workmos       5.3    2.0 
workhrs_top   0.8    0.6 
any_ssi     -34.9   37.1 
any_welfare -13.0   24.2 
any_charity -29.2   40.2 

Auxiliary parameter(s):
      Median MAD_SD
sigma 132.3    3.0 

------
* For help interpreting the printed output see ?print.stanreg
* For info on the priors used see ?prior_summary.stanreg

point predictions

pred_1 <- colMeans(posterior_linpred(fit_imp_1, newdata = SIS_predictors))  
SIS$earnings_imp_1 <- impute(SIS$earnings, pred_1)

Impute subset of earnings that are nonzero: square root scale and topcoding

fit_imp_2 <- stan_glm(
  sqrt(earnings_top) ~ male + over65 + white + immig +
                       educ_r + workmos + workhrs_top + any_ssi +
                       any_welfare + any_charity,
  data = SIS,
  subset = earnings > 0,
  refresh = 0
)
print(fit_imp_2)
stan_glm
 family:       gaussian [identity]
 formula:      sqrt(earnings_top) ~ male + over65 + white + immig + educ_r + 
       workmos + workhrs_top + any_ssi + any_welfare + any_charity
 observations: 988
 predictors:   11
------
            Median MAD_SD
(Intercept) -1.7    0.4  
male         0.3    0.1  
over65      -1.4    0.6  
white        1.0    0.2  
immig       -0.6    0.1  
educ_r       0.8    0.1  
workmos      0.3    0.0  
workhrs_top  0.1    0.0  
any_ssi     -1.0    0.6  
any_welfare -1.3    0.4  
any_charity -1.2    0.6  

Auxiliary parameter(s):
      Median MAD_SD
sigma 2.0    0.0   

------
* For help interpreting the printed output see ?print.stanreg
* For info on the priors used see ?prior_summary.stanreg

point predictions

pred_2_sqrt <- colMeans(posterior_linpred(fit_imp_2, newdata = SIS_predictors))  
pred_2 <- topcode(pred_2_sqrt^2, 100)
SIS$earnings_imp_2 <- impute(SIS$earnings_top, pred_2)

One random imputation

Linear scale (use fitted model fit_imp_1)

pred_3 <- posterior_predict(fit_imp_1, newdata = SIS_predictors, draws = 1)
SIS$earnings_imp_3 <- impute(SIS$earnings, pred_3)

Square root scale and topcoding (use fitted model fit_imp_2)

pred_4_sqrt <- posterior_predict(fit_imp_2, newdata = SIS_predictors, draws = 1)
pred_4 <- topcode(pred_4_sqrt^2, 100)
SIS$earnings_imp_4 <- impute(SIS$earnings_top, pred_4)

3. Histograms and scatterplots of data and imputations

par(mar=c(3,3,1,1), mgp=c(1.7,.5,0), tck=-.01)
hist(SIS$earnings_top[SIS$earnings>0], breaks=seq(0,100,10), xlab="earnings", ylab="", main="Observed earnings (excluding 0's)")

par(mar=c(3,3,1,1), mgp=c(1.7,.5,0), tck=-.01)
hist(SIS$earnings_imp_2[is.na(SIS$earnings)], breaks=seq(0,100,10),
      xlab="earnings", ylab="", ylim=c(0,48),
      main="Deterministic imputation of earnings")

par(mar=c(3,3,1,1), mgp=c(1.7,.5,0), tck=-.01)
hist(SIS$earnings_imp_4[is.na(SIS$earnings)], breaks=seq(0,100,10),
      xlab="earnings", ylab="", ylim=c(0,48),
     main="Random imputation of earnings")

par(mar=c(3,3,2,1), mgp=c(1.7,.5,0), tck=-.01)
plot(range(SIS$earnings_imp_2[is.na(SIS$earnings)]), c(0,100),
      xlab="Regression prediction", ylab="Earnings",
      main="Deterministic imputation", type="n", bty="l")
points(SIS$earnings_imp_2[is.na(SIS$earnings)], SIS$earnings_imp_2[is.na(SIS$earnings)], pch=19, cex=.5)
points(pred_2, SIS$earnings_top, pch=20, col="darkgray", cex=.5)

par(mar=c(3,3,2,1), mgp=c(1.7,.5,0), tck=-.01)
plot(range(SIS$earnings_imp_2[is.na(SIS$earnings)]), c(0,100),
      xlab="Regression prediction", ylab="Earnings",
      main="Random imputation", type="n", bty="l")
points(SIS$earnings_imp_2[is.na(SIS$earnings)], SIS$earnings_imp_4[is.na(SIS$earnings)], pch=19, cex=.5)
points(pred_2, SIS$earnings_top, pch=20, col="darkgray", cex=.5)

Two-stage imputation model

Fit the 2 models

fit_positive <- stan_glm((earnings>0) ~ male + over65 + white + immig +
  educ_r + any_ssi + any_welfare + any_charity,
  data=SIS, family=binomial(link=logit), refresh = 0)
print(fit_positive)
stan_glm
 family:       binomial [logit]
 formula:      (earnings > 0) ~ male + over65 + white + immig + educ_r + any_ssi + 
       any_welfare + any_charity
 observations: 1260
 predictors:   9
------
            Median MAD_SD
(Intercept)  0.4    0.3  
male         0.3    0.2  
over65      -4.0    0.3  
white       -0.1    0.2  
immig        0.2    0.2  
educ_r       0.6    0.1  
any_ssi     -2.5    0.4  
any_welfare -0.6    0.3  
any_charity  0.5    0.8  

------
* For help interpreting the printed output see ?print.stanreg
* For info on the priors used see ?prior_summary.stanreg
fit_positive_sqrt <- stan_glm(sqrt(earnings_top) ~ male + over65 + white + immig +
  educ_r + any_ssi + any_welfare + any_charity,
  data=SIS, subset=earnings>0, refresh = 0)  # (same as fit_imp_2 from above)
print(fit_positive_sqrt)
stan_glm
 family:       gaussian [identity]
 formula:      sqrt(earnings_top) ~ male + over65 + white + immig + educ_r + 
       any_ssi + any_welfare + any_charity
 observations: 988
 predictors:   9
------
            Median MAD_SD
(Intercept)  3.7    0.2  
male         0.4    0.1  
over65      -2.1    0.6  
white        1.0    0.2  
immig       -0.6    0.1  
educ_r       0.9    0.1  
any_ssi     -1.2    0.6  
any_welfare -2.8    0.4  
any_charity -1.7    0.7  

Auxiliary parameter(s):
      Median MAD_SD
sigma 2.2    0.0   

------
* For help interpreting the printed output see ?print.stanreg
* For info on the priors used see ?prior_summary.stanreg

Predict the sign and then the earnings (if positive)

# one random imp
pred_sign <- posterior_predict(fit_positive, newdata = SIS_predictors, draws = 1)
# one random imp
pred_pos_sqrt <- posterior_predict(fit_positive_sqrt, newdata = SIS_predictors,
                                   draws = 1)
pred_pos <- topcode(pred_pos_sqrt^2, 100)
SIS$earnings_imp <- impute(SIS$earnings, pred_sign*pred_pos)

Iterative regression imputation

Starting values

random_imp <- function (a){
  missing <- is.na(a)
  n_missing <- sum(missing)
  a_obs <- a[!missing]
  imputed <- a
  imputed[missing] <- sample(a_obs, n_missing)
  imputed
}
SIS$interest_imp <- random_imp(SIS$interest)
SIS$earnings_imp <- random_imp(SIS$earnings)

Simplest regression imputation

n_loop <- 10
for (s in 1:n_loop){
  fit <- stan_glm(earnings ~ interest_imp + male + over65 + white +
    immig + educ_r + workmos + workhrs_top + any_ssi + any_welfare +
    any_charity, data=SIS, refresh = 0)
  SIS_predictors <- SIS[,c("male","over65","white","immig","educ_r","workmos",
                           "workhrs_top","any_ssi","any_welfare","any_charity",
                           "interest_imp", "earnings_imp")]
  pred1 <- posterior_predict(fit, newdata = SIS_predictors, draws = 1)
  SIS$earnings_imp <- impute(SIS$earnings, pred1)
  
  fit <- stan_glm(interest ~ earnings_imp + male + over65 + white +
    immig + educ_r + workmos + workhrs_top + any_ssi + any_welfare +
    any_charity, data=SIS, refresh = 0)
  SIS_predictors <- SIS[,c("male","over65","white","immig","educ_r","workmos",
                           "workhrs_top","any_ssi","any_welfare","any_charity",
                           "interest_imp", "earnings_imp")]
  pred2 <- posterior_predict(fit, newdata = SIS_predictors, draws = 1)
  SIS$interest_imp <- impute(SIS$interest, pred2)
}
LS0tCnRpdGxlOiAiUmVncmVzc2lvbiBhbmQgT3RoZXIgU3RvcmllczogSW1wdXRhdGlvbiIKYXV0aG9yOiAiQW5kcmV3IEdlbG1hbiwgSmVubmlmZXIgSGlsbCwgQWtpIFZlaHRhcmkiCmRhdGU6ICJgciBmb3JtYXQoU3lzLkRhdGUoKSlgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRoZW1lOiByZWFkYWJsZQogICAgdG9jOiB0cnVlCiAgICB0b2NfZGVwdGg6IDIKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KUmVncmVzc2lvbi1iYXNlZCBpbXB1dGF0aW9uIGZvciB0aGUgU29jaWFsIEluZGljYXRvcnMgU3VydmV5LiBTZWUKQ2hhcHRlciAxNyBpbiBSZWdyZXNzaW9uIGFuZCBPdGhlciBTdG9yaWVzLgoKLS0tLS0tLS0tLS0tLQoKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQobWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNvbW1lbnQ9TkEpCiMgc3dpdGNoIHRoaXMgdG8gVFJVRSB0byBzYXZlIGZpZ3VyZXMgaW4gc2VwYXJhdGUgZmlsZXMKc2F2ZWZpZ3MgPC0gRkFMU0UKYGBgCgojIyMjIExvYWQgcGFja2FnZXMKCmBgYHtyIH0KbGlicmFyeSgicnByb2pyb290IikKcm9vdDwtaGFzX2ZpbGUoIi5ST1MtRXhhbXBsZXMtcm9vdCIpJG1ha2VfZml4X2ZpbGUoKQpsaWJyYXJ5KCJyc3RhbmFybSIpCmBgYAoKIyMjIyBMb2FkIGRhdGEKCmBgYHtyIH0KU0lTIDwtIHJlYWQuY3N2KHJvb3QoIkltcHV0YXRpb24vZGF0YSIsIlNJUy5jc3YiKSkKaGVhZChTSVMpCnN1bW1hcnkoU0lTKQpgYGAKCiMjIyMgSW1wdXRhdGlvbiBoZWxwZXIgZnVuY3Rpb25zPC9icj4KQ3JlYXRlIGEgY29tcGxldGVkIGRhdGEgdmVjdG9yIHVzaW5nIGltcHV0YXRpb25zCgpgYGB7ciB9CmltcHV0ZSA8LSBmdW5jdGlvbihhLCBhX2ltcHV0ZSkgewogIGlmZWxzZShpcy5uYShhKSwgYV9pbXB1dGUsIGEpCn0KYGBgCgpUb3AgY29kZSBmdW5jdGlvbgoKYGBge3IgfQp0b3Bjb2RlIDwtIGZ1bmN0aW9uKGEsIHRvcCkgewogIGlmZWxzZShhID4gdG9wLCB0b3AsIGEpCn0KYGBgCgojIyBEZXRlcm1pbmlzdGljIGltcHV0YXRpb24KIyMjIyBJbXB1dGUgMCBlYXJuaW5ncyB1c2luZyB0aGUgbG9naWNhbCBydWxlIChpZiB3b3JrZWQgMCBtb250aHMgYW5kIDAgaHJzL3drKQoKYGBge3IgfQpTSVMkZWFybmluZ3NfdG9wIDwtIHRvcGNvZGUoU0lTJGVhcm5pbmdzLCAxMDApClNJUyRlYXJuaW5nc190b3BbU0lTJHdvcmtocnNfdG9wPT0wICYgU0lTJHdvcmttb3M9PTBdIDwtIDAKYGBgCgojIyMjIENyZWF0ZSBhIGRhdGFzZXQgd2l0aCBhbGwgcHJlZGljdG9yIHZhcmlhYmxlcwoKYGBge3IgfQpuIDwtIG5yb3coU0lTKQpTSVNfcHJlZGljdG9ycyA8LSBTSVNbLGMoIm1hbGUiLCJvdmVyNjUiLCJ3aGl0ZSIsImltbWlnIiwiZWR1Y19yIiwid29ya21vcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAid29ya2hyc190b3AiLCJhbnlfc3NpIiwiYW55X3dlbGZhcmUiLCJhbnlfY2hhcml0eSIpXQpgYGAKCiMjIyMgSW1wdXRlIHN1YnNldCBvZiBlYXJuaW5ncyB0aGF0IGFyZSBub256ZXJvOiAgbGluZWFyIHNjYWxlCgpgYGB7ciB9CmZpdF9pbXBfMSA8LSBzdGFuX2dsbSgKICBlYXJuaW5ncyB+IG1hbGUgKyBvdmVyNjUgKyB3aGl0ZSArIGltbWlnICsgZWR1Y19yICsKICAgICAgICAgICAgICB3b3JrbW9zICsgd29ya2hyc190b3AgKyBhbnlfc3NpICsKICAgICAgICAgICAgICBhbnlfd2VsZmFyZSArIGFueV9jaGFyaXR5LAogIGRhdGEgPSBTSVMsCiAgc3Vic2V0ID0gZWFybmluZ3MgPiAwLAogIHJlZnJlc2ggPSAwCikKcHJpbnQoZml0X2ltcF8xKQpgYGAKCnBvaW50IHByZWRpY3Rpb25zCgpgYGB7ciB9CnByZWRfMSA8LSBjb2xNZWFucyhwb3N0ZXJpb3JfbGlucHJlZChmaXRfaW1wXzEsIG5ld2RhdGEgPSBTSVNfcHJlZGljdG9ycykpICAKU0lTJGVhcm5pbmdzX2ltcF8xIDwtIGltcHV0ZShTSVMkZWFybmluZ3MsIHByZWRfMSkKYGBgCgojIyMjIEltcHV0ZSBzdWJzZXQgb2YgZWFybmluZ3MgdGhhdCBhcmUgbm9uemVybzogIHNxdWFyZSByb290IHNjYWxlIGFuZCB0b3Bjb2RpbmcKCmBgYHtyIH0KZml0X2ltcF8yIDwtIHN0YW5fZ2xtKAogIHNxcnQoZWFybmluZ3NfdG9wKSB+IG1hbGUgKyBvdmVyNjUgKyB3aGl0ZSArIGltbWlnICsKICAgICAgICAgICAgICAgICAgICAgICBlZHVjX3IgKyB3b3JrbW9zICsgd29ya2hyc190b3AgKyBhbnlfc3NpICsKICAgICAgICAgICAgICAgICAgICAgICBhbnlfd2VsZmFyZSArIGFueV9jaGFyaXR5LAogIGRhdGEgPSBTSVMsCiAgc3Vic2V0ID0gZWFybmluZ3MgPiAwLAogIHJlZnJlc2ggPSAwCikKcHJpbnQoZml0X2ltcF8yKQpgYGAKCnBvaW50IHByZWRpY3Rpb25zCgpgYGB7ciB9CnByZWRfMl9zcXJ0IDwtIGNvbE1lYW5zKHBvc3Rlcmlvcl9saW5wcmVkKGZpdF9pbXBfMiwgbmV3ZGF0YSA9IFNJU19wcmVkaWN0b3JzKSkgIApwcmVkXzIgPC0gdG9wY29kZShwcmVkXzJfc3FydF4yLCAxMDApClNJUyRlYXJuaW5nc19pbXBfMiA8LSBpbXB1dGUoU0lTJGVhcm5pbmdzX3RvcCwgcHJlZF8yKQpgYGAKCiMjIE9uZSByYW5kb20gaW1wdXRhdGlvbgojIyMjIExpbmVhciBzY2FsZSAodXNlIGZpdHRlZCBtb2RlbCBmaXRfaW1wXzEpCgpgYGB7ciB9CnByZWRfMyA8LSBwb3N0ZXJpb3JfcHJlZGljdChmaXRfaW1wXzEsIG5ld2RhdGEgPSBTSVNfcHJlZGljdG9ycywgZHJhd3MgPSAxKQpTSVMkZWFybmluZ3NfaW1wXzMgPC0gaW1wdXRlKFNJUyRlYXJuaW5ncywgcHJlZF8zKQpgYGAKCiMjIyMgU3F1YXJlIHJvb3Qgc2NhbGUgYW5kIHRvcGNvZGluZyAodXNlIGZpdHRlZCBtb2RlbCBmaXRfaW1wXzIpCgpgYGB7ciB9CnByZWRfNF9zcXJ0IDwtIHBvc3Rlcmlvcl9wcmVkaWN0KGZpdF9pbXBfMiwgbmV3ZGF0YSA9IFNJU19wcmVkaWN0b3JzLCBkcmF3cyA9IDEpCnByZWRfNCA8LSB0b3Bjb2RlKHByZWRfNF9zcXJ0XjIsIDEwMCkKU0lTJGVhcm5pbmdzX2ltcF80IDwtIGltcHV0ZShTSVMkZWFybmluZ3NfdG9wLCBwcmVkXzQpCmBgYAoKIyMjIDMuICBIaXN0b2dyYW1zIGFuZCBzY2F0dGVycGxvdHMgb2YgZGF0YSBhbmQgaW1wdXRhdGlvbnMKCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmlmIChzYXZlZmlncykgcGRmKHJvb3QoIkltcHV0YXRpb24vZmlncyIsImltcHV0ZV9oaXN0Mi5wZGYiKSwgaGVpZ2h0PTQsIHdpZHRoPTUuNSkKYGBgCmBgYHtyIH0KcGFyKG1hcj1jKDMsMywxLDEpLCBtZ3A9YygxLjcsLjUsMCksIHRjaz0tLjAxKQpoaXN0KFNJUyRlYXJuaW5nc190b3BbU0lTJGVhcm5pbmdzPjBdLCBicmVha3M9c2VxKDAsMTAwLDEwKSwgeGxhYj0iZWFybmluZ3MiLCB5bGFiPSIiLCBtYWluPSJPYnNlcnZlZCBlYXJuaW5ncyAoZXhjbHVkaW5nIDAncykiKQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBkZXYub2ZmKCkKCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIHBkZihyb290KCJJbXB1dGF0aW9uL2ZpZ3MiLCJpbXB1dGVfaGlzdDMucGRmIiksIGhlaWdodD00LCB3aWR0aD01LjUpCmBgYApgYGB7ciB9CnBhcihtYXI9YygzLDMsMSwxKSwgbWdwPWMoMS43LC41LDApLCB0Y2s9LS4wMSkKaGlzdChTSVMkZWFybmluZ3NfaW1wXzJbaXMubmEoU0lTJGVhcm5pbmdzKV0sIGJyZWFrcz1zZXEoMCwxMDAsMTApLAogICAgICB4bGFiPSJlYXJuaW5ncyIsIHlsYWI9IiIsIHlsaW09YygwLDQ4KSwKICAgICAgbWFpbj0iRGV0ZXJtaW5pc3RpYyBpbXB1dGF0aW9uIG9mIGVhcm5pbmdzIikKYGBgCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmlmIChzYXZlZmlncykgZGV2Lm9mZigpCgpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBwZGYocm9vdCgiSW1wdXRhdGlvbi9maWdzIiwiaW1wdXRlX2hpc3Q0LnBkZiIpLCBoZWlnaHQ9NCwgd2lkdGg9NS41KQpgYGAKYGBge3IgfQpwYXIobWFyPWMoMywzLDEsMSksIG1ncD1jKDEuNywuNSwwKSwgdGNrPS0uMDEpCmhpc3QoU0lTJGVhcm5pbmdzX2ltcF80W2lzLm5hKFNJUyRlYXJuaW5ncyldLCBicmVha3M9c2VxKDAsMTAwLDEwKSwKICAgICAgeGxhYj0iZWFybmluZ3MiLCB5bGFiPSIiLCB5bGltPWMoMCw0OCksCiAgICAgbWFpbj0iUmFuZG9tIGltcHV0YXRpb24gb2YgZWFybmluZ3MiKQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBkZXYub2ZmKCkKCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIHBkZihyb290KCJJbXB1dGF0aW9uL2ZpZ3MiLCJpbXB1dGVfc2NhdF8xLnBkZiIpLCBoZWlnaHQ9NCwgd2lkdGg9NSkKYGBgCmBgYHtyIH0KcGFyKG1hcj1jKDMsMywyLDEpLCBtZ3A9YygxLjcsLjUsMCksIHRjaz0tLjAxKQpwbG90KHJhbmdlKFNJUyRlYXJuaW5nc19pbXBfMltpcy5uYShTSVMkZWFybmluZ3MpXSksIGMoMCwxMDApLAogICAgICB4bGFiPSJSZWdyZXNzaW9uIHByZWRpY3Rpb24iLCB5bGFiPSJFYXJuaW5ncyIsCiAgICAgIG1haW49IkRldGVybWluaXN0aWMgaW1wdXRhdGlvbiIsIHR5cGU9Im4iLCBidHk9ImwiKQpwb2ludHMoU0lTJGVhcm5pbmdzX2ltcF8yW2lzLm5hKFNJUyRlYXJuaW5ncyldLCBTSVMkZWFybmluZ3NfaW1wXzJbaXMubmEoU0lTJGVhcm5pbmdzKV0sIHBjaD0xOSwgY2V4PS41KQpwb2ludHMocHJlZF8yLCBTSVMkZWFybmluZ3NfdG9wLCBwY2g9MjAsIGNvbD0iZGFya2dyYXkiLCBjZXg9LjUpCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIGRldi5vZmYoKQoKYGBgCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmlmIChzYXZlZmlncykgcGRmKHJvb3QoIkltcHV0YXRpb24vZmlncyIsImltcHV0ZV9zY2F0XzIucGRmIiksIGhlaWdodD00LCB3aWR0aD01KQpgYGAKYGBge3IgfQpwYXIobWFyPWMoMywzLDIsMSksIG1ncD1jKDEuNywuNSwwKSwgdGNrPS0uMDEpCnBsb3QocmFuZ2UoU0lTJGVhcm5pbmdzX2ltcF8yW2lzLm5hKFNJUyRlYXJuaW5ncyldKSwgYygwLDEwMCksCiAgICAgIHhsYWI9IlJlZ3Jlc3Npb24gcHJlZGljdGlvbiIsIHlsYWI9IkVhcm5pbmdzIiwKICAgICAgbWFpbj0iUmFuZG9tIGltcHV0YXRpb24iLCB0eXBlPSJuIiwgYnR5PSJsIikKcG9pbnRzKFNJUyRlYXJuaW5nc19pbXBfMltpcy5uYShTSVMkZWFybmluZ3MpXSwgU0lTJGVhcm5pbmdzX2ltcF80W2lzLm5hKFNJUyRlYXJuaW5ncyldLCBwY2g9MTksIGNleD0uNSkKcG9pbnRzKHByZWRfMiwgU0lTJGVhcm5pbmdzX3RvcCwgcGNoPTIwLCBjb2w9ImRhcmtncmF5IiwgY2V4PS41KQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBkZXYub2ZmKCkKYGBgCgojIyBUd28tc3RhZ2UgaW1wdXRhdGlvbiBtb2RlbAojIyMjIEZpdCB0aGUgMiBtb2RlbHMKCmBgYHtyIH0KZml0X3Bvc2l0aXZlIDwtIHN0YW5fZ2xtKChlYXJuaW5ncz4wKSB+IG1hbGUgKyBvdmVyNjUgKyB3aGl0ZSArIGltbWlnICsKICBlZHVjX3IgKyBhbnlfc3NpICsgYW55X3dlbGZhcmUgKyBhbnlfY2hhcml0eSwKICBkYXRhPVNJUywgZmFtaWx5PWJpbm9taWFsKGxpbms9bG9naXQpLCByZWZyZXNoID0gMCkKcHJpbnQoZml0X3Bvc2l0aXZlKQpmaXRfcG9zaXRpdmVfc3FydCA8LSBzdGFuX2dsbShzcXJ0KGVhcm5pbmdzX3RvcCkgfiBtYWxlICsgb3ZlcjY1ICsgd2hpdGUgKyBpbW1pZyArCiAgZWR1Y19yICsgYW55X3NzaSArIGFueV93ZWxmYXJlICsgYW55X2NoYXJpdHksCiAgZGF0YT1TSVMsIHN1YnNldD1lYXJuaW5ncz4wLCByZWZyZXNoID0gMCkgICMgKHNhbWUgYXMgZml0X2ltcF8yIGZyb20gYWJvdmUpCnByaW50KGZpdF9wb3NpdGl2ZV9zcXJ0KQpgYGAKCiMjIyMgUHJlZGljdCB0aGUgc2lnbiBhbmQgdGhlbiB0aGUgZWFybmluZ3MgKGlmIHBvc2l0aXZlKQoKYGBge3IgfQojIG9uZSByYW5kb20gaW1wCnByZWRfc2lnbiA8LSBwb3N0ZXJpb3JfcHJlZGljdChmaXRfcG9zaXRpdmUsIG5ld2RhdGEgPSBTSVNfcHJlZGljdG9ycywgZHJhd3MgPSAxKQojIG9uZSByYW5kb20gaW1wCnByZWRfcG9zX3NxcnQgPC0gcG9zdGVyaW9yX3ByZWRpY3QoZml0X3Bvc2l0aXZlX3NxcnQsIG5ld2RhdGEgPSBTSVNfcHJlZGljdG9ycywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcmF3cyA9IDEpCnByZWRfcG9zIDwtIHRvcGNvZGUocHJlZF9wb3Nfc3FydF4yLCAxMDApClNJUyRlYXJuaW5nc19pbXAgPC0gaW1wdXRlKFNJUyRlYXJuaW5ncywgcHJlZF9zaWduKnByZWRfcG9zKQpgYGAKCiMjIEl0ZXJhdGl2ZSByZWdyZXNzaW9uIGltcHV0YXRpb24KIyMjIyBTdGFydGluZyB2YWx1ZXMKCmBgYHtyIH0KcmFuZG9tX2ltcCA8LSBmdW5jdGlvbiAoYSl7CiAgbWlzc2luZyA8LSBpcy5uYShhKQogIG5fbWlzc2luZyA8LSBzdW0obWlzc2luZykKICBhX29icyA8LSBhWyFtaXNzaW5nXQogIGltcHV0ZWQgPC0gYQogIGltcHV0ZWRbbWlzc2luZ10gPC0gc2FtcGxlKGFfb2JzLCBuX21pc3NpbmcpCiAgaW1wdXRlZAp9ClNJUyRpbnRlcmVzdF9pbXAgPC0gcmFuZG9tX2ltcChTSVMkaW50ZXJlc3QpClNJUyRlYXJuaW5nc19pbXAgPC0gcmFuZG9tX2ltcChTSVMkZWFybmluZ3MpCmBgYAoKIyMjIyBTaW1wbGVzdCByZWdyZXNzaW9uIGltcHV0YXRpb24KCmBgYHtyIH0Kbl9sb29wIDwtIDEwCmZvciAocyBpbiAxOm5fbG9vcCl7CiAgZml0IDwtIHN0YW5fZ2xtKGVhcm5pbmdzIH4gaW50ZXJlc3RfaW1wICsgbWFsZSArIG92ZXI2NSArIHdoaXRlICsKICAgIGltbWlnICsgZWR1Y19yICsgd29ya21vcyArIHdvcmtocnNfdG9wICsgYW55X3NzaSArIGFueV93ZWxmYXJlICsKICAgIGFueV9jaGFyaXR5LCBkYXRhPVNJUywgcmVmcmVzaCA9IDApCiAgU0lTX3ByZWRpY3RvcnMgPC0gU0lTWyxjKCJtYWxlIiwib3ZlcjY1Iiwid2hpdGUiLCJpbW1pZyIsImVkdWNfciIsIndvcmttb3MiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAid29ya2hyc190b3AiLCJhbnlfc3NpIiwiYW55X3dlbGZhcmUiLCJhbnlfY2hhcml0eSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJpbnRlcmVzdF9pbXAiLCAiZWFybmluZ3NfaW1wIildCiAgcHJlZDEgPC0gcG9zdGVyaW9yX3ByZWRpY3QoZml0LCBuZXdkYXRhID0gU0lTX3ByZWRpY3RvcnMsIGRyYXdzID0gMSkKICBTSVMkZWFybmluZ3NfaW1wIDwtIGltcHV0ZShTSVMkZWFybmluZ3MsIHByZWQxKQogIAogIGZpdCA8LSBzdGFuX2dsbShpbnRlcmVzdCB+IGVhcm5pbmdzX2ltcCArIG1hbGUgKyBvdmVyNjUgKyB3aGl0ZSArCiAgICBpbW1pZyArIGVkdWNfciArIHdvcmttb3MgKyB3b3JraHJzX3RvcCArIGFueV9zc2kgKyBhbnlfd2VsZmFyZSArCiAgICBhbnlfY2hhcml0eSwgZGF0YT1TSVMsIHJlZnJlc2ggPSAwKQogIFNJU19wcmVkaWN0b3JzIDwtIFNJU1ssYygibWFsZSIsIm92ZXI2NSIsIndoaXRlIiwiaW1taWciLCJlZHVjX3IiLCJ3b3JrbW9zIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIndvcmtocnNfdG9wIiwiYW55X3NzaSIsImFueV93ZWxmYXJlIiwiYW55X2NoYXJpdHkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAiaW50ZXJlc3RfaW1wIiwgImVhcm5pbmdzX2ltcCIpXQogIHByZWQyIDwtIHBvc3Rlcmlvcl9wcmVkaWN0KGZpdCwgbmV3ZGF0YSA9IFNJU19wcmVkaWN0b3JzLCBkcmF3cyA9IDEpCiAgU0lTJGludGVyZXN0X2ltcCA8LSBpbXB1dGUoU0lTJGludGVyZXN0LCBwcmVkMikKfQpgYGAKCg==