Binned residual plots for a logistic regression model: wells in Bangladesh. See Chapter 14 in Regression and Other Stories.


Load packages

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

Load data

wells <- read.csv(root("Arsenic/data","wells.csv"))
head(wells)
  switch arsenic   dist dist100 assoc educ educ4
1      1    2.36 16.826 0.16826     0    0  0.00
2      1    0.71 47.322 0.47322     0    0  0.00
3      0    2.07 20.967 0.20967     0   10  2.50
4      1    1.15 21.486 0.21486     0   12  3.00
5      1    1.10 40.874 0.40874     1   14  3.50
6      1    3.90 69.518 0.69518     1    9  2.25
n <- nrow(wells)

Predict switching

with distance, arsenic, education and intercations

wells$c_dist100 <- wells$dist100 - mean(wells$dist100)
wells$c_arsenic <- wells$arsenic - mean(wells$arsenic)
wells$c_educ4 <- wells$educ4 - mean(wells$educ4)
fit_8 <- stan_glm(switch ~ c_dist100 + c_arsenic + c_educ4 +
                      c_dist100:c_educ4 + c_arsenic:c_educ4,
                  family = binomial(link="logit"), data = wells)
pred8 <- fitted(fit_8)

Error rates

error_rate_null <- mean(round(abs(wells$switch-mean(pred8))))
round(error_rate_null, 2)
[1] 0.42
error_rate <- mean(round(abs(wells$switch-pred8)))
round(error_rate, 2)
[1] 0.38

Residual plot

plot(c(0,1), c(-1,1), xlab="Estimated  Pr (switching)", ylab="Observed - estimated",
     type="n", main="Residual plot", mgp=c(2,.5,0))
abline(0,0, col="gray", lwd=.5)
points(pred8, wells$switch-pred8, pch=20, cex=.2)

Binned residual plots

Function for binning residuals

binned_resids <- function (x, y, nclass=sqrt(length(x))){
  breaks.index <- floor(length(x)*(1:(nclass-1))/nclass)
  breaks <- c (-Inf, sort(x)[breaks.index], Inf)
  output <- NULL
  xbreaks <- NULL
  x.binned <- as.numeric (cut (x, breaks))
  for (i in 1:nclass){
    items <- (1:length(x))[x.binned==i]
    x.range <- range(x[items])
    xbar <- mean(x[items])
    ybar <- mean(y[items])
    n <- length(items)
    sdev <- sd(y[items])
    output <- rbind (output, c(xbar, ybar, n, x.range, 2*sdev/sqrt(n)))
  }
  colnames (output) <- c ("xbar", "ybar", "n", "x.lo", "x.hi", "2se")
  return (list (binned=output, xbreaks=xbreaks))
}

Binned residual plot with respect to predicted probability

br8 <- binned_resids(pred8, wells$switch-pred8, nclass=40)$binned
plot(range(br8[,1]), range(br8[,2],br8[,6],-br8[,6]),
     xlab="Estimated  Pr (switching)", ylab="Average residual",
     type="n", main="Binned residual plot", mgp=c(2,.5,0))
abline(0,0, col="gray", lwd=.5)
lines(br8[,1], br8[,6], col="gray", lwd=.5)
lines(br8[,1], -br8[,6], col="gray", lwd=.5)
points(br8[,1], br8[,2], pch=20, cex=.5)

Binned residual plots with respect to predictors

br <- binned_resids(wells$dist, wells$switch-pred8, nclass=40)$binned
plot(range(br[,1]), range(br[,2],br[,6],-br[,6]),
     xlab="Distance to nearest safe well", ylab="Average residual",
     type="n", main="Binned residual plot", mgp=c(2,.5,0))
abline(0,0, col="gray", lwd=.5)
n_within_bin <- length(wells$switch)/nrow(br)
lines(br[,1], br[,6], col="gray", lwd=.5)
lines(br[,1], -br[,6], col="gray", lwd=.5)
points(br[,1], br[,2], pch=20, cex=.5)

br <- binned_resids(wells$arsenic, wells$switch-pred8, nclass=40)$binned
plot(range(0,br[,1]), range(br[,2],br[,6],-br[,6]),
     xlab="Arsenic level", ylab="Average residual",
     type="n", main="Binned residual plot", mgp=c(2,.5,0))
abline (0,0, col="gray", lwd=.5)
lines (br[,1], br[,6], col="gray", lwd=.5)
lines (br[,1], -br[,6], col="gray", lwd=.5)
points (br[,1], br[,2], pch=20, cex=.5)

Predict switching with distance, log(arsenic), education and intercations

Use non-centered predictors for easier plotting

wells$log_arsenic <- log(wells$arsenic)
fit_8b <- stan_glm(switch ~ dist100 + log_arsenic + educ4 +
                      dist100:educ4 + log_arsenic:educ4,
                   family = binomial(link="logit"), data = wells)

Predict switching with distance, log(arsenic), education and intercations

Use non-centered predictors for easier plotting

wells$log_arsenic <- log(wells$arsenic)
fit_8b <- stan_glm(switch ~ dist100 + log_arsenic + educ4 +
                      dist100:educ4 + log_arsenic:educ4,
                   family = binomial(link="logit"), data = wells)
pred8b <- fitted(fit_8b)

Error rate

error_rate <- mean(round(abs(wells$switch-pred8b)))
round(error_rate, 2)
[1] 0.36

Plots for log model

jitter_binary <- function(a, jitt=.05){
  a + (1-2*a)*runif(length(a),0,jitt)
}
plot(c(0,max(wells$arsenic,na.rm=T)*1.02), c(0,1),
     xlab="Arsenic concentration in well water", ylab="Pr (switching)",
     type="n", xaxs="i", yaxs="i", mgp=c(2,.5,0))
points(wells$arsenic, jitter_binary(wells$switch), pch=20, cex=.1)
curve(invlogit(coef(fit_8b)[1]+coef(fit_8b)[2]*0+coef(fit_8b)[3]*log(x)+coef(fit_8b)[4]*mean(wells$educ4)+coef(fit_8b)[5]*0*mean(wells$educ4)+coef(fit_8b)[6]*log(x)*mean(wells$educ4)), from=.5, lwd=.5, add=T)
curve(invlogit(coef(fit_8b)[1]+coef(fit_8b)[2]*.5+coef(fit_8b)[3]*log(x)+coef(fit_8b)[4]*mean(wells$educ4)+coef(fit_8b)[5]*.5*mean(wells$educ4)+coef(fit_8b)[6]*log(x)*mean(wells$educ4)), from=.5, lwd=.5, add=T)
text(.25, .80, "if dist = 0", adj=0, cex=.8)
text(2, .63, "if dist = 50", adj=0, cex=.8)

br <- binned_resids(wells$arsenic, wells$switch-pred8b, nclass=40)$binned
plot(range(0,br[,1]), range(br[,2],br[,6],-br[,6]),
     xlab="Arsenic level", ylab="Average residual", type="n",
     main="Binned residual plot\nfor model with log (arsenic)", mgp=c(2,.5,0))
abline(0,0, col="gray", lwd=.5)
n.within.bin <- length(wells$switch)/nrow(br)
lines(br[,1], br[,6], col="gray", lwd=.5)
lines(br[,1], -br[,6], col="gray", lwd=.5)
points(br[,1], br[,2], pch=20, cex=.5)

LS0tCnRpdGxlOiAiUmVncmVzc2lvbiBhbmQgT3RoZXIgU3RvcmllczogQXJzZW5pYyBtb2RlbCByZXNpZHVhbHMiCmF1dGhvcjogIkFuZHJldyBHZWxtYW4sIEplbm5pZmVyIEhpbGwsIEFraSBWZWh0YXJpIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy5EYXRlKCkpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogcmVhZGFibGUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAyCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCkJpbm5lZCByZXNpZHVhbCBwbG90cyBmb3IgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsOiB3ZWxscyBpbgpCYW5nbGFkZXNoLiBTZWUgQ2hhcHRlciAxNCBpbiBSZWdyZXNzaW9uIGFuZCBPdGhlciBTdG9yaWVzLgoKLS0tLS0tLS0tLS0tLQoKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQobWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNvbW1lbnQ9TkEpCiMgc3dpdGNoIHRoaXMgdG8gVFJVRSB0byBzYXZlIGZpZ3VyZXMgaW4gc2VwYXJhdGUgZmlsZXMKc2F2ZWZpZ3MgPC0gRkFMU0UKYGBgCgojIyMjIExvYWQgcGFja2FnZXMKCmBgYHtyIH0KbGlicmFyeSgicnByb2pyb290IikKcm9vdDwtaGFzX2ZpbGUoIi5ST1MtRXhhbXBsZXMtcm9vdCIpJG1ha2VfZml4X2ZpbGUoKQpsaWJyYXJ5KCJyc3RhbmFybSIpCmxpYnJhcnkoImxvbyIpCmludmxvZ2l0IDwtIHBsb2dpcwpgYGAKCiMjIyMgTG9hZCBkYXRhCgpgYGB7ciB9CndlbGxzIDwtIHJlYWQuY3N2KHJvb3QoIkFyc2VuaWMvZGF0YSIsIndlbGxzLmNzdiIpKQpoZWFkKHdlbGxzKQpuIDwtIG5yb3cod2VsbHMpCmBgYAoKIyMgUHJlZGljdCBzd2l0Y2hpbmcKd2l0aCBkaXN0YW5jZSwgYXJzZW5pYywgZWR1Y2F0aW9uIGFuZCBpbnRlcmNhdGlvbnMKCmBgYHtyIH0Kd2VsbHMkY19kaXN0MTAwIDwtIHdlbGxzJGRpc3QxMDAgLSBtZWFuKHdlbGxzJGRpc3QxMDApCndlbGxzJGNfYXJzZW5pYyA8LSB3ZWxscyRhcnNlbmljIC0gbWVhbih3ZWxscyRhcnNlbmljKQp3ZWxscyRjX2VkdWM0IDwtIHdlbGxzJGVkdWM0IC0gbWVhbih3ZWxscyRlZHVjNCkKYGBgCmBgYHtyIHJlc3VsdHM9J2hpZGUnfQpmaXRfOCA8LSBzdGFuX2dsbShzd2l0Y2ggfiBjX2Rpc3QxMDAgKyBjX2Fyc2VuaWMgKyBjX2VkdWM0ICsKICAgICAgICAgICAgICAgICAgICAgIGNfZGlzdDEwMDpjX2VkdWM0ICsgY19hcnNlbmljOmNfZWR1YzQsCiAgICAgICAgICAgICAgICAgIGZhbWlseSA9IGJpbm9taWFsKGxpbms9ImxvZ2l0IiksIGRhdGEgPSB3ZWxscykKcHJlZDggPC0gZml0dGVkKGZpdF84KQpgYGAKCiMjIyMgRXJyb3IgcmF0ZXMKCmBgYHtyIH0KZXJyb3JfcmF0ZV9udWxsIDwtIG1lYW4ocm91bmQoYWJzKHdlbGxzJHN3aXRjaC1tZWFuKHByZWQ4KSkpKQpyb3VuZChlcnJvcl9yYXRlX251bGwsIDIpCmVycm9yX3JhdGUgPC0gbWVhbihyb3VuZChhYnMod2VsbHMkc3dpdGNoLXByZWQ4KSkpCnJvdW5kKGVycm9yX3JhdGUsIDIpCmBgYAoKIyMgUmVzaWR1YWwgcGxvdAoKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBwb3N0c2NyaXB0KHJvb3QoIkFyc2VuaWMvZmlncyIsImFyc2VuaWMubG9naXRyZXNpZHNhLnBzIiksCiAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQ9My41LCB3aWR0aD00LCBob3Jpem9udGFsPVRSVUUpCmBgYApgYGB7ciB9CnBsb3QoYygwLDEpLCBjKC0xLDEpLCB4bGFiPSJFc3RpbWF0ZWQgIFByIChzd2l0Y2hpbmcpIiwgeWxhYj0iT2JzZXJ2ZWQgLSBlc3RpbWF0ZWQiLAogICAgIHR5cGU9Im4iLCBtYWluPSJSZXNpZHVhbCBwbG90IiwgbWdwPWMoMiwuNSwwKSkKYWJsaW5lKDAsMCwgY29sPSJncmF5IiwgbHdkPS41KQpwb2ludHMocHJlZDgsIHdlbGxzJHN3aXRjaC1wcmVkOCwgcGNoPTIwLCBjZXg9LjIpCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIGRldi5vZmYoKQpgYGAKCiMjIEJpbm5lZCByZXNpZHVhbCBwbG90cwoKIyMjIyBGdW5jdGlvbiBmb3IgYmlubmluZyByZXNpZHVhbHMKCmBgYHtyIH0KYmlubmVkX3Jlc2lkcyA8LSBmdW5jdGlvbiAoeCwgeSwgbmNsYXNzPXNxcnQobGVuZ3RoKHgpKSl7CiAgYnJlYWtzLmluZGV4IDwtIGZsb29yKGxlbmd0aCh4KSooMToobmNsYXNzLTEpKS9uY2xhc3MpCiAgYnJlYWtzIDwtIGMgKC1JbmYsIHNvcnQoeClbYnJlYWtzLmluZGV4XSwgSW5mKQogIG91dHB1dCA8LSBOVUxMCiAgeGJyZWFrcyA8LSBOVUxMCiAgeC5iaW5uZWQgPC0gYXMubnVtZXJpYyAoY3V0ICh4LCBicmVha3MpKQogIGZvciAoaSBpbiAxOm5jbGFzcyl7CiAgICBpdGVtcyA8LSAoMTpsZW5ndGgoeCkpW3guYmlubmVkPT1pXQogICAgeC5yYW5nZSA8LSByYW5nZSh4W2l0ZW1zXSkKICAgIHhiYXIgPC0gbWVhbih4W2l0ZW1zXSkKICAgIHliYXIgPC0gbWVhbih5W2l0ZW1zXSkKICAgIG4gPC0gbGVuZ3RoKGl0ZW1zKQogICAgc2RldiA8LSBzZCh5W2l0ZW1zXSkKICAgIG91dHB1dCA8LSByYmluZCAob3V0cHV0LCBjKHhiYXIsIHliYXIsIG4sIHgucmFuZ2UsIDIqc2Rldi9zcXJ0KG4pKSkKICB9CiAgY29sbmFtZXMgKG91dHB1dCkgPC0gYyAoInhiYXIiLCAieWJhciIsICJuIiwgIngubG8iLCAieC5oaSIsICIyc2UiKQogIHJldHVybiAobGlzdCAoYmlubmVkPW91dHB1dCwgeGJyZWFrcz14YnJlYWtzKSkKfQpgYGAKCiMjIyMgQmlubmVkIHJlc2lkdWFsIHBsb3Qgd2l0aCByZXNwZWN0IHRvIHByZWRpY3RlZCBwcm9iYWJpbGl0eQoKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBwb3N0c2NyaXB0KHJvb3QoIkFyc2VuaWMvZmlncyIsImFyc2VuaWMubG9naXRyZXNpZHNiLnBzIiksCiAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQ9My41LCB3aWR0aD00LCBob3Jpem9udGFsPVQpCmBgYApgYGB7ciB9CmJyOCA8LSBiaW5uZWRfcmVzaWRzKHByZWQ4LCB3ZWxscyRzd2l0Y2gtcHJlZDgsIG5jbGFzcz00MCkkYmlubmVkCnBsb3QocmFuZ2UoYnI4WywxXSksIHJhbmdlKGJyOFssMl0sYnI4Wyw2XSwtYnI4Wyw2XSksCiAgICAgeGxhYj0iRXN0aW1hdGVkICBQciAoc3dpdGNoaW5nKSIsIHlsYWI9IkF2ZXJhZ2UgcmVzaWR1YWwiLAogICAgIHR5cGU9Im4iLCBtYWluPSJCaW5uZWQgcmVzaWR1YWwgcGxvdCIsIG1ncD1jKDIsLjUsMCkpCmFibGluZSgwLDAsIGNvbD0iZ3JheSIsIGx3ZD0uNSkKbGluZXMoYnI4WywxXSwgYnI4Wyw2XSwgY29sPSJncmF5IiwgbHdkPS41KQpsaW5lcyhicjhbLDFdLCAtYnI4Wyw2XSwgY29sPSJncmF5IiwgbHdkPS41KQpwb2ludHMoYnI4WywxXSwgYnI4WywyXSwgcGNoPTIwLCBjZXg9LjUpCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIGRldi5vZmYoKQpgYGAKCiMjIyMgQmlubmVkIHJlc2lkdWFsIHBsb3RzIHdpdGggcmVzcGVjdCB0byBwcmVkaWN0b3JzCgpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIHBvc3RzY3JpcHQocm9vdCgiQXJzZW5pYy9maWdzIiwibG9naXRyZXNpZHMuMmEucHMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGhlaWdodD0zLjUsIHdpZHRoPTQsIGhvcml6b250YWw9VCkKYGBgCmBgYHtyIH0KYnIgPC0gYmlubmVkX3Jlc2lkcyh3ZWxscyRkaXN0LCB3ZWxscyRzd2l0Y2gtcHJlZDgsIG5jbGFzcz00MCkkYmlubmVkCnBsb3QocmFuZ2UoYnJbLDFdKSwgcmFuZ2UoYnJbLDJdLGJyWyw2XSwtYnJbLDZdKSwKICAgICB4bGFiPSJEaXN0YW5jZSB0byBuZWFyZXN0IHNhZmUgd2VsbCIsIHlsYWI9IkF2ZXJhZ2UgcmVzaWR1YWwiLAogICAgIHR5cGU9Im4iLCBtYWluPSJCaW5uZWQgcmVzaWR1YWwgcGxvdCIsIG1ncD1jKDIsLjUsMCkpCmFibGluZSgwLDAsIGNvbD0iZ3JheSIsIGx3ZD0uNSkKbl93aXRoaW5fYmluIDwtIGxlbmd0aCh3ZWxscyRzd2l0Y2gpL25yb3coYnIpCmxpbmVzKGJyWywxXSwgYnJbLDZdLCBjb2w9ImdyYXkiLCBsd2Q9LjUpCmxpbmVzKGJyWywxXSwgLWJyWyw2XSwgY29sPSJncmF5IiwgbHdkPS41KQpwb2ludHMoYnJbLDFdLCBiclssMl0sIHBjaD0yMCwgY2V4PS41KQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBkZXYub2ZmKCkKYGBgCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmlmIChzYXZlZmlncykgcG9zdHNjcmlwdChyb290KCJBcnNlbmljL2ZpZ3MiLCJhcnNlbmljLmxvZ2l0cmVzaWRzLjJiLnBzIiksCiAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQ9My41LCB3aWR0aD00LCBob3Jpem9udGFsPVQpCmBgYApgYGB7ciB9CmJyIDwtIGJpbm5lZF9yZXNpZHMod2VsbHMkYXJzZW5pYywgd2VsbHMkc3dpdGNoLXByZWQ4LCBuY2xhc3M9NDApJGJpbm5lZApwbG90KHJhbmdlKDAsYnJbLDFdKSwgcmFuZ2UoYnJbLDJdLGJyWyw2XSwtYnJbLDZdKSwKICAgICB4bGFiPSJBcnNlbmljIGxldmVsIiwgeWxhYj0iQXZlcmFnZSByZXNpZHVhbCIsCiAgICAgdHlwZT0ibiIsIG1haW49IkJpbm5lZCByZXNpZHVhbCBwbG90IiwgbWdwPWMoMiwuNSwwKSkKYWJsaW5lICgwLDAsIGNvbD0iZ3JheSIsIGx3ZD0uNSkKbGluZXMgKGJyWywxXSwgYnJbLDZdLCBjb2w9ImdyYXkiLCBsd2Q9LjUpCmxpbmVzIChiclssMV0sIC1iclssNl0sIGNvbD0iZ3JheSIsIGx3ZD0uNSkKcG9pbnRzIChiclssMV0sIGJyWywyXSwgcGNoPTIwLCBjZXg9LjUpCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIGRldi5vZmYoKQpgYGAKCiMjIyMgUHJlZGljdCBzd2l0Y2hpbmcgd2l0aCBkaXN0YW5jZSwgbG9nKGFyc2VuaWMpLCBlZHVjYXRpb24gYW5kIGludGVyY2F0aW9ucwpVc2Ugbm9uLWNlbnRlcmVkIHByZWRpY3RvcnMgZm9yIGVhc2llciBwbG90dGluZwoKYGBge3IgcmVzdWx0cz0naGlkZSd9CndlbGxzJGxvZ19hcnNlbmljIDwtIGxvZyh3ZWxscyRhcnNlbmljKQpmaXRfOGIgPC0gc3Rhbl9nbG0oc3dpdGNoIH4gZGlzdDEwMCArIGxvZ19hcnNlbmljICsgZWR1YzQgKwogICAgICAgICAgICAgICAgICAgICAgZGlzdDEwMDplZHVjNCArIGxvZ19hcnNlbmljOmVkdWM0LAogICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gYmlub21pYWwobGluaz0ibG9naXQiKSwgZGF0YSA9IHdlbGxzKQpgYGAKCiMjIyMgUHJlZGljdCBzd2l0Y2hpbmcgd2l0aCBkaXN0YW5jZSwgbG9nKGFyc2VuaWMpLCBlZHVjYXRpb24gYW5kIGludGVyY2F0aW9ucwpVc2Ugbm9uLWNlbnRlcmVkIHByZWRpY3RvcnMgZm9yIGVhc2llciBwbG90dGluZwoKYGBge3IgcmVzdWx0cz0naGlkZSd9CndlbGxzJGxvZ19hcnNlbmljIDwtIGxvZyh3ZWxscyRhcnNlbmljKQpmaXRfOGIgPC0gc3Rhbl9nbG0oc3dpdGNoIH4gZGlzdDEwMCArIGxvZ19hcnNlbmljICsgZWR1YzQgKwogICAgICAgICAgICAgICAgICAgICAgZGlzdDEwMDplZHVjNCArIGxvZ19hcnNlbmljOmVkdWM0LAogICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gYmlub21pYWwobGluaz0ibG9naXQiKSwgZGF0YSA9IHdlbGxzKQpwcmVkOGIgPC0gZml0dGVkKGZpdF84YikKYGBgCgojIyMjIEVycm9yIHJhdGUKCmBgYHtyIH0KZXJyb3JfcmF0ZSA8LSBtZWFuKHJvdW5kKGFicyh3ZWxscyRzd2l0Y2gtcHJlZDhiKSkpCnJvdW5kKGVycm9yX3JhdGUsIDIpCmBgYAoKIyMjIyBQbG90cyBmb3IgbG9nIG1vZGVsCgpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIHBvc3RzY3JpcHQocm9vdCgiQXJzZW5pYy9maWdzIiwiYXJzZW5pYy5sb2dtb2RlbC5wcyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgaGVpZ2h0PTMuNSwgd2lkdGg9NCwgaG9yaXpvbnRhbD1UUlVFKQpgYGAKYGBge3IgfQpqaXR0ZXJfYmluYXJ5IDwtIGZ1bmN0aW9uKGEsIGppdHQ9LjA1KXsKICBhICsgKDEtMiphKSpydW5pZihsZW5ndGgoYSksMCxqaXR0KQp9CnBsb3QoYygwLG1heCh3ZWxscyRhcnNlbmljLG5hLnJtPVQpKjEuMDIpLCBjKDAsMSksCiAgICAgeGxhYj0iQXJzZW5pYyBjb25jZW50cmF0aW9uIGluIHdlbGwgd2F0ZXIiLCB5bGFiPSJQciAoc3dpdGNoaW5nKSIsCiAgICAgdHlwZT0ibiIsIHhheHM9ImkiLCB5YXhzPSJpIiwgbWdwPWMoMiwuNSwwKSkKcG9pbnRzKHdlbGxzJGFyc2VuaWMsIGppdHRlcl9iaW5hcnkod2VsbHMkc3dpdGNoKSwgcGNoPTIwLCBjZXg9LjEpCmN1cnZlKGludmxvZ2l0KGNvZWYoZml0XzhiKVsxXStjb2VmKGZpdF84YilbMl0qMCtjb2VmKGZpdF84YilbM10qbG9nKHgpK2NvZWYoZml0XzhiKVs0XSptZWFuKHdlbGxzJGVkdWM0KStjb2VmKGZpdF84YilbNV0qMCptZWFuKHdlbGxzJGVkdWM0KStjb2VmKGZpdF84YilbNl0qbG9nKHgpKm1lYW4od2VsbHMkZWR1YzQpKSwgZnJvbT0uNSwgbHdkPS41LCBhZGQ9VCkKY3VydmUoaW52bG9naXQoY29lZihmaXRfOGIpWzFdK2NvZWYoZml0XzhiKVsyXSouNStjb2VmKGZpdF84YilbM10qbG9nKHgpK2NvZWYoZml0XzhiKVs0XSptZWFuKHdlbGxzJGVkdWM0KStjb2VmKGZpdF84YilbNV0qLjUqbWVhbih3ZWxscyRlZHVjNCkrY29lZihmaXRfOGIpWzZdKmxvZyh4KSptZWFuKHdlbGxzJGVkdWM0KSksIGZyb209LjUsIGx3ZD0uNSwgYWRkPVQpCnRleHQoLjI1LCAuODAsICJpZiBkaXN0ID0gMCIsIGFkaj0wLCBjZXg9LjgpCnRleHQoMiwgLjYzLCAiaWYgZGlzdCA9IDUwIiwgYWRqPTAsIGNleD0uOCkKYGBgCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmlmIChzYXZlZmlncykgZGV2Lm9mZigpCgpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBwb3N0c2NyaXB0KHJvb3QoIkFyc2VuaWMvZmlncyIsImFyc2VuaWMubG9naXRyZXNpZHMuM2IucHMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGhlaWdodD0zLjUsIHdpZHRoPTQsIGhvcml6b250YWw9VFJVRSkKYGBgCmBgYHtyIH0KYnIgPC0gYmlubmVkX3Jlc2lkcyh3ZWxscyRhcnNlbmljLCB3ZWxscyRzd2l0Y2gtcHJlZDhiLCBuY2xhc3M9NDApJGJpbm5lZApwbG90KHJhbmdlKDAsYnJbLDFdKSwgcmFuZ2UoYnJbLDJdLGJyWyw2XSwtYnJbLDZdKSwKICAgICB4bGFiPSJBcnNlbmljIGxldmVsIiwgeWxhYj0iQXZlcmFnZSByZXNpZHVhbCIsIHR5cGU9Im4iLAogICAgIG1haW49IkJpbm5lZCByZXNpZHVhbCBwbG90XG5mb3IgbW9kZWwgd2l0aCBsb2cgKGFyc2VuaWMpIiwgbWdwPWMoMiwuNSwwKSkKYWJsaW5lKDAsMCwgY29sPSJncmF5IiwgbHdkPS41KQpuLndpdGhpbi5iaW4gPC0gbGVuZ3RoKHdlbGxzJHN3aXRjaCkvbnJvdyhicikKbGluZXMoYnJbLDFdLCBiclssNl0sIGNvbD0iZ3JheSIsIGx3ZD0uNSkKbGluZXMoYnJbLDFdLCAtYnJbLDZdLCBjb2w9ImdyYXkiLCBsd2Q9LjUpCnBvaW50cyhiclssMV0sIGJyWywyXSwgcGNoPTIwLCBjZXg9LjUpCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIGRldi5vZmYoKQpgYGAKCg==