We still use the lalonde data from the MatchIt package and use the propensity score model that we found out in R example 7.
library("MatchIt")
data("lalonde")
model <- glm(treat ~ . , data = lalonde[, -9], family = "binomial")
eps <- predict(model, type = "response")
lalonde
1. Esimate the inverse probability weights
The figures are not very informative as there are units with extremely large weights. We can perfrom log transformation to make the histogram more informative.
## Calculate the raw weights (before normalization)
## Weights to estimate ATE
n.treated <- sum(lalonde$treat == 1)
n.control <- sum(lalonde$treat == 0)
weights <- ifelse(lalonde$treat == 1, 1/eps, 1/(1 - eps))
## Check the weights histogram
library(ggplot2)
temp.data <- data.frame(weights = weights, treated = as.factor(lalonde$treat))
ggplot(temp.data, aes(x = weights, fill = treated, color = treated)) +
geom_histogram(alpha = 0.5, position = "identity") +
xlab("Weights")
## We can perform log transformation to make the histogram more informative
ggplot(temp.data, aes(x = weights, fill = treated, color = treated)) +
geom_histogram(alpha = 0.5, position = "identity") + scale_x_log10() +
xlab("Weights")
- Visualize the estimated propensity scores
temp.data.eps <- data.frame(eps = eps, treated = as.factor(lalonde$treat))
ggplot(temp.data.eps, aes(x = eps, fill = treated, color = treated)) +
geom_histogram(alpha = 0.5, position = "identity") + xlim(c(0, 1)) +
ggtitle("Histogram of eps before trimming")
We likely will do some trimming to make sure overlapping assumption is satisfied and probably will also improve covariates balancing.
- Check covariate balancing.
We can check covariate balancing from the love plot after inverse probability weighting. Here, we need to write our own function to draw the love plot.
## Need to change race (categorical) into indicators (numerical)
lalonde$black <- lalonde$race == "black"
lalonde$hispan <- lalonde$race == "hispan"
lalonde$white <- lalonde$race == "white"
## Draw love plot
love.plot = function(cov, treat, ## cov is the matrix of covariates and treat is a vector of treatment assignment
weights = rep(1, length(treat)),
plot = F)
{
## mean with normalized weights \sum w_i x_i / (\sum w_i)
treat.means <- colSums(cov[treat == 1,] * weights[treat == 1])/sum(weights[treat == 1])
treat.var <- colSums(t(t(cov[treat == 1,]) - treat.means)^2 *
weights[treat == 1])/sum(weights[treat == 1])
control.means <- colSums(cov[treat == 0,] * weights[treat == 0])/sum(weights[treat == 0])
control.var <- colSums(t(t(cov[treat == 0,]) - control.means)^2 *
weights[treat == 0])/sum(weights[treat == 0])
## the standardized mean differences for every covariate
smd <- (treat.means - control.means)/sqrt((treat.var + control.var)/2)
names(smd) <- colnames(cov)
if (plot == T) {
plot.data <- data.frame(smd = smd, covariates = names(smd))
range <- max(abs(smd))
ggplot(plot.data) + geom_point(aes(x = as.numeric(smd), y = covariates)) +
geom_vline(xintercept = 0) + xlim(-range, range) +
labs(x = 'Standardized Difference in Means')
}
return(smd)
}
raw.smd <- love.plot(lalonde[, c(2:3, 5:8, 10:12)], lalonde$treat)
weighted.smd <- love.plot(lalonde[, c(2:3, 5:8, 10:12)], lalonde$treat, weights = weights)
plot.data <- data.frame(smd = c(raw.smd, weighted.smd),
covariates = c(names(raw.smd), names(weighted.smd)),
category = c(rep("Original", length(raw.smd)), rep("IPW", length(weighted.smd))))
range <- max(abs(plot.data$smd))
ggplot(plot.data) + geom_point(aes(x = as.numeric(smd), y = covariates, color = category)) +
geom_vline(xintercept = c(-0.1, -0.05, 0, 0.05, 0.1),
linetype = c("solid", "dashed", "solid", "dashed", "solid")) +
xlim(-range, range) +
labs(x = 'Standardized Difference in Means')
Covariate balancing is greatly improved after IPW. There is still room for further improvement.
2. Trimming
2.1 Check weights and covariates balancing after trimming
- Perform trimming and check the histogram of weights
rm.idx <- which(eps < 0.1 | eps > 0.9)
temp.data.trimmed <- temp.data[-rm.idx, ]
lalonde.trimmed <- lalonde[-rm.idx, ]
## Check the histogram of weights again
ggplot(temp.data.trimmed, aes(x = weights, fill = treated, color = treated)) +
geom_histogram(alpha = 0.5, position = "identity") + scale_x_log10() +
xlab("Weights") + ggtitle("Histogram of weights after trimming")
- Check covariate balancing after trimming.
raw.smd <- love.plot(lalonde.trimmed[, c(2:3, 5:8, 10:12)], lalonde.trimmed$treat)
weighted.smd <- love.plot(lalonde.trimmed[, c(2:3, 5:8, 10:12)], lalonde.trimmed$treat,
weights = weights[-rm.idx])
plot.data <- data.frame(smd = c(raw.smd, weighted.smd),
covariates = c(names(raw.smd), names(weighted.smd)),
category = c(rep("Original", length(raw.smd)), rep("IPW", length(weighted.smd))))
range <- max(abs(plot.data$smd))
ggplot(plot.data) + geom_point(aes(x = as.numeric(smd), y = covariates, color = category)) +
geom_vline(xintercept = c(-0.1, -0.05, 0, 0.05, 0.1),
linetype = c("solid", "dashed", "solid", "dashed", "solid")) +
xlim(-range, range) +
labs(x = 'Standardized Difference in Means')
Though covariate balanicng of the raw data gets better, covariates balancing with IPW does not improve.
2.2 Refit the propensity score model after trimming
We do this because the covariate balancing the previous step is not satisfying.
## Refit the propensity score model
model.trimmed <- glm(treat ~ . , data = lalonde.trimmed[, -(9:12)], family = "binomial")
eps.trimmed <- predict(model.trimmed, type = "response")
weights.trimmed <- ifelse(lalonde.trimmed$treat == 1, 1/eps.trimmed, 1/(1 - eps.trimmed))
temp.data.trimmed$weights <- weights.trimmed
## Check the histogram of weights again
ggplot(temp.data.trimmed, aes(x = weights, fill = treated, color = treated)) +
geom_histogram(alpha = 0.5, position = "identity") + scale_x_log10() +
xlab("Weights") + ggtitle("Histogram of weights after trimming")
- Check covariate balancing again.
raw.smd <- love.plot(lalonde.trimmed[, c(2:3, 5:8, 10:12)], lalonde.trimmed$treat)
weighted.smd <- love.plot(lalonde.trimmed[, c(2:3, 5:8, 10:12)], lalonde.trimmed$treat,
weights = weights.trimmed)
plot.data <- data.frame(smd = c(raw.smd, weighted.smd),
covariates = c(names(raw.smd), names(weighted.smd)),
category = c(rep("Original", length(raw.smd)), rep("IPW", length(weighted.smd))))
range <- max(abs(plot.data$smd))
ggplot(plot.data) + geom_point(aes(x = as.numeric(smd), y = covariates, color = category)) +
geom_vline(xintercept = c(-0.1, -0.05, 0, 0.05, 0.1),
linetype = c("solid", "dashed", "solid", "dashed", "solid")) +
xlim(-range, range) +
labs(x = 'Standardized Difference in Means')
3. Estimate the ATE using IPW
- Estimate ATE by weighted least square regression
lm.result <- lm(re78 ~ treat, weights = weights.trimmed, data = lalonde.trimmed)
summary(lm.result)
Call:
lm(formula = re78 ~ treat, data = lalonde.trimmed, weights = weights.trimmed)
Weighted Residuals:
Min 1Q Median 3Q Max
-16478 -7345 -2741 3852 61182
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 5402.7 537.4 10.053 <2e-16 ***
treat 1155.1 756.9 1.526 0.128
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 9888 on 339 degrees of freedom
Multiple R-squared: 0.006823, Adjusted R-squared: 0.003894
F-statistic: 2.329 on 1 and 339 DF, p-value: 0.1279
- Statistical inference treating the weights as fixed.
We use the Sandwich estimator allowing heteroscedastic noise levels between the treated and control grousp
library(sandwich)
tau_hat <- lm.result$coefficients[2]
SE <- sqrt(diag(vcovHC(lm.result, type = "HC2")))[2]
## get the 95% CI
result <- c(tau_hat, SE, c(tau_hat- 1.96 * SE, tau_hat + 1.96 * SE))
names(result) <- c("est", "sd", "CI_lower", "CI_upper")
result
est sd CI_lower CI_upper
1155.0913 827.0484 -465.9235 2776.1062
- Statistical inference using bootstrap
IPW_estimator <- function(W, Y, X) {
## Estimate propensity score
model <- glm(W ~ X , family = "binomial")
eps <- predict(model, type = "response")
## Calculate the weights
weights <- ifelse(W == 1, 1/eps, 1/(1 - eps))
## Calculate weighted mean difference between treated and control group
est <- lm(Y ~ W, weights = weights)$coef[2]
return(est)
}
X <- model.matrix(model.trimmed)
Y <- lalonde.trimmed$re78
W <- lalonde.trimmed$treat
IPW_bootstrap <- function(W, Y, X, n.boot = 200){
est <- IPW_estimator(W, Y, X)
IPWboot <- sapply(1:n.boot, function(i) {
id.boot <- sample(1:length(W), replace = T)
IPW_estimator(W[id.boot], Y[id.boot], X[id.boot, ])
})
return(c(est, sd(IPWboot)))
}
SE_boostrap <- IPW_bootstrap(W, Y, X, 5000)[2]
result_bootstrap <- c(tau_hat, SE_boostrap,
c(tau_hat- 1.96 * SE_boostrap, tau_hat + 1.96 * SE_boostrap))
names(result_bootstrap) <- c("est", "sd (bootstrap)", "CI_lower (bootstrap)", "CI_upper (bootstrap)")
result_bootstrap
est sd (bootstrap) CI_lower (bootstrap) CI_upper (bootstrap)
1155.0913 869.8960 -549.9049 2860.0875
LS0tCnRpdGxlOiAiUiBFeGFtcGxlIDk6IEludmVyc2UgcHJvcGVuc2l0eSBzY29yZSB3ZWlnaHRpbmciCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCldlIHN0aWxsIHVzZSB0aGUgbGFsb25kZSBkYXRhIGZyb20gdGhlIE1hdGNoSXQgcGFja2FnZSBhbmQgdXNlIHRoZSBwcm9wZW5zaXR5IHNjb3JlIG1vZGVsIHRoYXQgd2UgZm91bmQgb3V0IGluIFIgZXhhbXBsZSA3LgoKYGBge3J9CmxpYnJhcnkoIk1hdGNoSXQiKQpkYXRhKCJsYWxvbmRlIikKCm1vZGVsIDwtIGdsbSh0cmVhdCB+IC4gLCBkYXRhID0gbGFsb25kZVssIC05XSwgZmFtaWx5ID0gImJpbm9taWFsIikKZXBzIDwtIHByZWRpY3QobW9kZWwsIHR5cGUgPSAicmVzcG9uc2UiKQoKbGFsb25kZQpgYGAKCiMgMS4gRXNpbWF0ZSB0aGUgaW52ZXJzZSBwcm9iYWJpbGl0eSB3ZWlnaHRzIAoKLSBWaXN1YWxpemUgdGhlIHdlaWdodHMKClRoZSBmaWd1cmVzIGFyZSBub3QgdmVyeSBpbmZvcm1hdGl2ZSBhcyB0aGVyZSBhcmUgdW5pdHMgd2l0aCBleHRyZW1lbHkgbGFyZ2Ugd2VpZ2h0cy4gV2UgY2FuIHBlcmZyb20gbG9nIHRyYW5zZm9ybWF0aW9uIHRvIG1ha2UgdGhlIGhpc3RvZ3JhbSBtb3JlIGluZm9ybWF0aXZlLgpgYGB7cn0KIyMgQ2FsY3VsYXRlIHRoZSByYXcgd2VpZ2h0cyAoYmVmb3JlIG5vcm1hbGl6YXRpb24pCiMjIFdlaWdodHMgdG8gZXN0aW1hdGUgQVRFCm4udHJlYXRlZCA8LSBzdW0obGFsb25kZSR0cmVhdCA9PSAxKQpuLmNvbnRyb2wgPC0gc3VtKGxhbG9uZGUkdHJlYXQgPT0gMCkKd2VpZ2h0cyA8LSBpZmVsc2UobGFsb25kZSR0cmVhdCA9PSAxLCAxL2VwcywgMS8oMSAtIGVwcykpCgojIyBDaGVjayB0aGUgd2VpZ2h0cyBoaXN0b2dyYW0KbGlicmFyeShnZ3Bsb3QyKQp0ZW1wLmRhdGEgPC0gZGF0YS5mcmFtZSh3ZWlnaHRzID0gd2VpZ2h0cywgdHJlYXRlZCA9IGFzLmZhY3RvcihsYWxvbmRlJHRyZWF0KSkKZ2dwbG90KHRlbXAuZGF0YSwgYWVzKHggPSB3ZWlnaHRzLCBmaWxsID0gdHJlYXRlZCwgY29sb3IgPSB0cmVhdGVkKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShhbHBoYSA9IDAuNSwgcG9zaXRpb24gPSAiaWRlbnRpdHkiKSArIAogIHhsYWIoIldlaWdodHMiKSAKCiMjIFdlIGNhbiBwZXJmb3JtIGxvZyB0cmFuc2Zvcm1hdGlvbiB0byBtYWtlIHRoZSBoaXN0b2dyYW0gbW9yZSBpbmZvcm1hdGl2ZQpnZ3Bsb3QodGVtcC5kYXRhLCBhZXMoeCA9IHdlaWdodHMsIGZpbGwgPSB0cmVhdGVkLCBjb2xvciA9IHRyZWF0ZWQpKSArIAogIGdlb21faGlzdG9ncmFtKGFscGhhID0gMC41LCBwb3NpdGlvbiA9ICJpZGVudGl0eSIpICsgIHNjYWxlX3hfbG9nMTAoKSArIAogIHhsYWIoIldlaWdodHMiKSAKYGBgCgotIFZpc3VhbGl6ZSB0aGUgZXN0aW1hdGVkIHByb3BlbnNpdHkgc2NvcmVzCmBgYHtyfQp0ZW1wLmRhdGEuZXBzIDwtIGRhdGEuZnJhbWUoZXBzID0gZXBzLCB0cmVhdGVkID0gYXMuZmFjdG9yKGxhbG9uZGUkdHJlYXQpKQpnZ3Bsb3QodGVtcC5kYXRhLmVwcywgYWVzKHggPSBlcHMsIGZpbGwgPSB0cmVhdGVkLCBjb2xvciA9IHRyZWF0ZWQpKSArIAogIGdlb21faGlzdG9ncmFtKGFscGhhID0gMC41LCBwb3NpdGlvbiA9ICJpZGVudGl0eSIpICsgeGxpbShjKDAsIDEpKSArCiAgZ2d0aXRsZSgiSGlzdG9ncmFtIG9mIGVwcyBiZWZvcmUgdHJpbW1pbmciKQpgYGAKCldlIGxpa2VseSB3aWxsIGRvIHNvbWUgdHJpbW1pbmcgdG8gbWFrZSBzdXJlIG92ZXJsYXBwaW5nIGFzc3VtcHRpb24gaXMgc2F0aXNmaWVkIGFuZCBwcm9iYWJseSB3aWxsIGFsc28gaW1wcm92ZSBjb3ZhcmlhdGVzIGJhbGFuY2luZy4KCi0gQ2hlY2sgY292YXJpYXRlIGJhbGFuY2luZy4KCldlIGNhbiBjaGVjayBjb3ZhcmlhdGUgYmFsYW5jaW5nIGZyb20gdGhlIGxvdmUgcGxvdCBhZnRlciBpbnZlcnNlIHByb2JhYmlsaXR5IHdlaWdodGluZy4gSGVyZSwgd2UgbmVlZCB0byB3cml0ZSBvdXIgb3duIGZ1bmN0aW9uIHRvIGRyYXcgdGhlIGxvdmUgcGxvdC4gCmBgYHtyfQojIyBOZWVkIHRvIGNoYW5nZSByYWNlIChjYXRlZ29yaWNhbCkgaW50byBpbmRpY2F0b3JzIChudW1lcmljYWwpCmxhbG9uZGUkYmxhY2sgPC0gbGFsb25kZSRyYWNlID09ICJibGFjayIKbGFsb25kZSRoaXNwYW4gPC0gbGFsb25kZSRyYWNlID09ICJoaXNwYW4iCmxhbG9uZGUkd2hpdGUgPC0gbGFsb25kZSRyYWNlID09ICJ3aGl0ZSIKCiMjIERyYXcgbG92ZSBwbG90CmxvdmUucGxvdCA9IGZ1bmN0aW9uKGNvdiwgdHJlYXQsICAjIyBjb3YgaXMgdGhlIG1hdHJpeCBvZiBjb3ZhcmlhdGVzIGFuZCB0cmVhdCBpcyBhIHZlY3RvciBvZiB0cmVhdG1lbnQgYXNzaWdubWVudAogICAgICAgICAgICAgICAgICAgICB3ZWlnaHRzID0gcmVwKDEsIGxlbmd0aCh0cmVhdCkpLAogICAgICAgICAgICAgICAgICAgICBwbG90ID0gRikgCnsKICAgIAogICAgIyMgbWVhbiB3aXRoIG5vcm1hbGl6ZWQgd2VpZ2h0cyBcc3VtIHdfaSB4X2kgLyAoXHN1bSB3X2kpCiAgdHJlYXQubWVhbnMgPC0gY29sU3Vtcyhjb3ZbdHJlYXQgPT0gMSxdICogd2VpZ2h0c1t0cmVhdCA9PSAxXSkvc3VtKHdlaWdodHNbdHJlYXQgPT0gMV0pCiAgdHJlYXQudmFyIDwtIGNvbFN1bXModCh0KGNvdlt0cmVhdCA9PSAxLF0pIC0gdHJlYXQubWVhbnMpXjIgKgogICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodHNbdHJlYXQgPT0gMV0pL3N1bSh3ZWlnaHRzW3RyZWF0ID09IDFdKQogIAogIGNvbnRyb2wubWVhbnMgPC0gY29sU3Vtcyhjb3ZbdHJlYXQgPT0gMCxdICogd2VpZ2h0c1t0cmVhdCA9PSAwXSkvc3VtKHdlaWdodHNbdHJlYXQgPT0gMF0pCiAgY29udHJvbC52YXIgPC0gY29sU3Vtcyh0KHQoY292W3RyZWF0ID09IDAsXSkgLSBjb250cm9sLm1lYW5zKV4yICoKICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHRzW3RyZWF0ID09IDBdKS9zdW0od2VpZ2h0c1t0cmVhdCA9PSAwXSkKICAKICAjIyB0aGUgc3RhbmRhcmRpemVkIG1lYW4gZGlmZmVyZW5jZXMgZm9yIGV2ZXJ5IGNvdmFyaWF0ZQogIHNtZCA8LSAodHJlYXQubWVhbnMgLSBjb250cm9sLm1lYW5zKS9zcXJ0KCh0cmVhdC52YXIgKyBjb250cm9sLnZhcikvMikKICBuYW1lcyhzbWQpIDwtIGNvbG5hbWVzKGNvdikKICAKICBpZiAocGxvdCA9PSBUKSB7CiAgICBwbG90LmRhdGEgPC0gZGF0YS5mcmFtZShzbWQgPSBzbWQsIGNvdmFyaWF0ZXMgPSBuYW1lcyhzbWQpKQogICAgcmFuZ2UgPC0gbWF4KGFicyhzbWQpKQogICAgZ2dwbG90KHBsb3QuZGF0YSkgKyBnZW9tX3BvaW50KGFlcyh4ID0gYXMubnVtZXJpYyhzbWQpLCB5ID0gY292YXJpYXRlcykpICsKICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCkgKyB4bGltKC1yYW5nZSwgcmFuZ2UpICsKICAgICAgbGFicyh4ID0gJ1N0YW5kYXJkaXplZCBEaWZmZXJlbmNlIGluIE1lYW5zJykKICB9CiAgcmV0dXJuKHNtZCkKfQoKcmF3LnNtZCA8LSBsb3ZlLnBsb3QobGFsb25kZVssIGMoMjozLCA1OjgsIDEwOjEyKV0sIGxhbG9uZGUkdHJlYXQpCndlaWdodGVkLnNtZCA8LSBsb3ZlLnBsb3QobGFsb25kZVssIGMoMjozLCA1OjgsIDEwOjEyKV0sIGxhbG9uZGUkdHJlYXQsIHdlaWdodHMgPSB3ZWlnaHRzKQoKCnBsb3QuZGF0YSA8LSBkYXRhLmZyYW1lKHNtZCA9IGMocmF3LnNtZCwgd2VpZ2h0ZWQuc21kKSwgCiAgICAgICAgICAgICAgICAgICAgICAgIGNvdmFyaWF0ZXMgPSBjKG5hbWVzKHJhdy5zbWQpLCBuYW1lcyh3ZWlnaHRlZC5zbWQpKSwKICAgICAgICAgICAgICAgICAgICAgICAgY2F0ZWdvcnkgPSBjKHJlcCgiT3JpZ2luYWwiLCBsZW5ndGgocmF3LnNtZCkpLCByZXAoIklQVyIsIGxlbmd0aCh3ZWlnaHRlZC5zbWQpKSkpCnJhbmdlIDwtIG1heChhYnMocGxvdC5kYXRhJHNtZCkpCgpnZ3Bsb3QocGxvdC5kYXRhKSArIGdlb21fcG9pbnQoYWVzKHggPSBhcy5udW1lcmljKHNtZCksIHkgPSBjb3ZhcmlhdGVzLCBjb2xvciA9IGNhdGVnb3J5KSkgKwogICAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBjKC0wLjEsIC0wLjA1LCAwLCAwLjA1LCAwLjEpLAogICAgICAgICAgICAgICAgIGxpbmV0eXBlID0gYygic29saWQiLCAiZGFzaGVkIiwgInNvbGlkIiwgImRhc2hlZCIsICJzb2xpZCIpKSArIAogICAgICB4bGltKC1yYW5nZSwgcmFuZ2UpICsKICAgICAgbGFicyh4ID0gJ1N0YW5kYXJkaXplZCBEaWZmZXJlbmNlIGluIE1lYW5zJykKYGBgCgpDb3ZhcmlhdGUgYmFsYW5jaW5nIGlzIGdyZWF0bHkgaW1wcm92ZWQgYWZ0ZXIgSVBXLiBUaGVyZSBpcyBzdGlsbCByb29tIGZvciBmdXJ0aGVyIGltcHJvdmVtZW50LgoKIyAyLiBUcmltbWluZwoKIyMgMi4xIENoZWNrIHdlaWdodHMgYW5kIGNvdmFyaWF0ZXMgYmFsYW5jaW5nIGFmdGVyIHRyaW1taW5nCgotIFBlcmZvcm0gdHJpbW1pbmcgYW5kIGNoZWNrIHRoZSBoaXN0b2dyYW0gb2Ygd2VpZ2h0cwoKYGBge3J9CnJtLmlkeCA8LSB3aGljaChlcHMgPCAwLjEgfCBlcHMgPiAwLjkpCgp0ZW1wLmRhdGEudHJpbW1lZCA8LSB0ZW1wLmRhdGFbLXJtLmlkeCwgXQpsYWxvbmRlLnRyaW1tZWQgPC0gbGFsb25kZVstcm0uaWR4LCBdCgojIyBDaGVjayB0aGUgaGlzdG9ncmFtIG9mIHdlaWdodHMgYWdhaW4KCmdncGxvdCh0ZW1wLmRhdGEudHJpbW1lZCwgYWVzKHggPSB3ZWlnaHRzLCBmaWxsID0gdHJlYXRlZCwgY29sb3IgPSB0cmVhdGVkKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShhbHBoYSA9IDAuNSwgcG9zaXRpb24gPSAiaWRlbnRpdHkiKSArICBzY2FsZV94X2xvZzEwKCkgKyAKICB4bGFiKCJXZWlnaHRzIikgKyBnZ3RpdGxlKCJIaXN0b2dyYW0gb2Ygd2VpZ2h0cyBhZnRlciB0cmltbWluZyIpCmBgYAoKLSBDaGVjayBjb3ZhcmlhdGUgYmFsYW5jaW5nIGFmdGVyIHRyaW1taW5nLgoKYGBge3J9CnJhdy5zbWQgPC0gbG92ZS5wbG90KGxhbG9uZGUudHJpbW1lZFssIGMoMjozLCA1OjgsIDEwOjEyKV0sIGxhbG9uZGUudHJpbW1lZCR0cmVhdCkKd2VpZ2h0ZWQuc21kIDwtIGxvdmUucGxvdChsYWxvbmRlLnRyaW1tZWRbLCBjKDI6MywgNTo4LCAxMDoxMildLCBsYWxvbmRlLnRyaW1tZWQkdHJlYXQsIAogICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodHMgPSB3ZWlnaHRzWy1ybS5pZHhdKQpwbG90LmRhdGEgPC0gZGF0YS5mcmFtZShzbWQgPSBjKHJhdy5zbWQsIHdlaWdodGVkLnNtZCksIAogICAgICAgICAgICAgICAgICAgICAgICBjb3ZhcmlhdGVzID0gYyhuYW1lcyhyYXcuc21kKSwgbmFtZXMod2VpZ2h0ZWQuc21kKSksCiAgICAgICAgICAgICAgICAgICAgICAgIGNhdGVnb3J5ID0gYyhyZXAoIk9yaWdpbmFsIiwgbGVuZ3RoKHJhdy5zbWQpKSwgcmVwKCJJUFciLCBsZW5ndGgod2VpZ2h0ZWQuc21kKSkpKQpyYW5nZSA8LSBtYXgoYWJzKHBsb3QuZGF0YSRzbWQpKQpnZ3Bsb3QocGxvdC5kYXRhKSArIGdlb21fcG9pbnQoYWVzKHggPSBhcy5udW1lcmljKHNtZCksIHkgPSBjb3ZhcmlhdGVzLCBjb2xvciA9IGNhdGVnb3J5KSkgKwogICAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBjKC0wLjEsIC0wLjA1LCAwLCAwLjA1LCAwLjEpLAogICAgICAgICAgICAgICAgIGxpbmV0eXBlID0gYygic29saWQiLCAiZGFzaGVkIiwgInNvbGlkIiwgImRhc2hlZCIsICJzb2xpZCIpKSArIAogICAgICB4bGltKC1yYW5nZSwgcmFuZ2UpICsKICAgICAgbGFicyh4ID0gJ1N0YW5kYXJkaXplZCBEaWZmZXJlbmNlIGluIE1lYW5zJykKYGBgCgpUaG91Z2ggY292YXJpYXRlIGJhbGFuaWNuZyBvZiB0aGUgcmF3IGRhdGEgZ2V0cyBiZXR0ZXIsIGNvdmFyaWF0ZXMgYmFsYW5jaW5nIHdpdGggSVBXIGRvZXMgbm90IGltcHJvdmUuCgojIyAyLjIgUmVmaXQgdGhlIHByb3BlbnNpdHkgc2NvcmUgbW9kZWwgYWZ0ZXIgdHJpbW1pbmcKCldlIGRvIHRoaXMgYmVjYXVzZSB0aGUgY292YXJpYXRlIGJhbGFuY2luZyB0aGUgcHJldmlvdXMgc3RlcCBpcyBub3Qgc2F0aXNmeWluZy4KCmBgYHtyfQojIyBSZWZpdCB0aGUgcHJvcGVuc2l0eSBzY29yZSBtb2RlbAptb2RlbC50cmltbWVkIDwtIGdsbSh0cmVhdCB+IC4gLCBkYXRhID0gbGFsb25kZS50cmltbWVkWywgLSg5OjEyKV0sIGZhbWlseSA9ICJiaW5vbWlhbCIpCmVwcy50cmltbWVkIDwtIHByZWRpY3QobW9kZWwudHJpbW1lZCwgdHlwZSA9ICJyZXNwb25zZSIpCndlaWdodHMudHJpbW1lZCA8LSBpZmVsc2UobGFsb25kZS50cmltbWVkJHRyZWF0ID09IDEsIDEvZXBzLnRyaW1tZWQsIDEvKDEgLSBlcHMudHJpbW1lZCkpCnRlbXAuZGF0YS50cmltbWVkJHdlaWdodHMgPC0gd2VpZ2h0cy50cmltbWVkCgojIyBDaGVjayB0aGUgaGlzdG9ncmFtIG9mIHdlaWdodHMgYWdhaW4KCmdncGxvdCh0ZW1wLmRhdGEudHJpbW1lZCwgYWVzKHggPSB3ZWlnaHRzLCBmaWxsID0gdHJlYXRlZCwgY29sb3IgPSB0cmVhdGVkKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShhbHBoYSA9IDAuNSwgcG9zaXRpb24gPSAiaWRlbnRpdHkiKSArICBzY2FsZV94X2xvZzEwKCkgKyAKICB4bGFiKCJXZWlnaHRzIikgKyBnZ3RpdGxlKCJIaXN0b2dyYW0gb2Ygd2VpZ2h0cyBhZnRlciB0cmltbWluZyIpCgpgYGAKCgotIENoZWNrIGNvdmFyaWF0ZSBiYWxhbmNpbmcgYWdhaW4uCgpgYGB7cn0KcmF3LnNtZCA8LSBsb3ZlLnBsb3QobGFsb25kZS50cmltbWVkWywgYygyOjMsIDU6OCwgMTA6MTIpXSwgbGFsb25kZS50cmltbWVkJHRyZWF0KQp3ZWlnaHRlZC5zbWQgPC0gbG92ZS5wbG90KGxhbG9uZGUudHJpbW1lZFssIGMoMjozLCA1OjgsIDEwOjEyKV0sIGxhbG9uZGUudHJpbW1lZCR0cmVhdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0cyA9IHdlaWdodHMudHJpbW1lZCkKcGxvdC5kYXRhIDwtIGRhdGEuZnJhbWUoc21kID0gYyhyYXcuc21kLCB3ZWlnaHRlZC5zbWQpLCAKICAgICAgICAgICAgICAgICAgICAgICAgY292YXJpYXRlcyA9IGMobmFtZXMocmF3LnNtZCksIG5hbWVzKHdlaWdodGVkLnNtZCkpLAogICAgICAgICAgICAgICAgICAgICAgICBjYXRlZ29yeSA9IGMocmVwKCJPcmlnaW5hbCIsIGxlbmd0aChyYXcuc21kKSksIHJlcCgiSVBXIiwgbGVuZ3RoKHdlaWdodGVkLnNtZCkpKSkKcmFuZ2UgPC0gbWF4KGFicyhwbG90LmRhdGEkc21kKSkKZ2dwbG90KHBsb3QuZGF0YSkgKyBnZW9tX3BvaW50KGFlcyh4ID0gYXMubnVtZXJpYyhzbWQpLCB5ID0gY292YXJpYXRlcywgY29sb3IgPSBjYXRlZ29yeSkpICsKICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYygtMC4xLCAtMC4wNSwgMCwgMC4wNSwgMC4xKSwKICAgICAgICAgICAgICAgICBsaW5ldHlwZSA9IGMoInNvbGlkIiwgImRhc2hlZCIsICJzb2xpZCIsICJkYXNoZWQiLCAic29saWQiKSkgKyAKICAgICAgeGxpbSgtcmFuZ2UsIHJhbmdlKSArCiAgICAgIGxhYnMoeCA9ICdTdGFuZGFyZGl6ZWQgRGlmZmVyZW5jZSBpbiBNZWFucycpCmBgYAoKIyAzLiBFc3RpbWF0ZSB0aGUgQVRFIHVzaW5nIElQVwoKLSBFc3RpbWF0ZSBBVEUgYnkgd2VpZ2h0ZWQgbGVhc3Qgc3F1YXJlIHJlZ3Jlc3Npb24gCgpgYGB7cn0KbG0ucmVzdWx0IDwtIGxtKHJlNzggfiB0cmVhdCwgd2VpZ2h0cyA9IHdlaWdodHMudHJpbW1lZCwgZGF0YSA9IGxhbG9uZGUudHJpbW1lZCkKc3VtbWFyeShsbS5yZXN1bHQpCgpgYGAKCi0gU3RhdGlzdGljYWwgaW5mZXJlbmNlIHRyZWF0aW5nIHRoZSB3ZWlnaHRzIGFzIGZpeGVkLgoKV2UgdXNlIHRoZSBTYW5kd2ljaCBlc3RpbWF0b3IgYWxsb3dpbmcgaGV0ZXJvc2NlZGFzdGljIG5vaXNlIGxldmVscyBiZXR3ZWVuIHRoZSB0cmVhdGVkIGFuZCBjb250cm9sIGdyb3VzcAoKYGBge3J9CmxpYnJhcnkoc2FuZHdpY2gpCnRhdV9oYXQgPC0gbG0ucmVzdWx0JGNvZWZmaWNpZW50c1syXQpTRSA8LSBzcXJ0KGRpYWcodmNvdkhDKGxtLnJlc3VsdCwgdHlwZSA9ICJIQzIiKSkpWzJdCgojIyBnZXQgdGhlIDk1JSBDSQpyZXN1bHQgPC0gYyh0YXVfaGF0LCBTRSwgYyh0YXVfaGF0LSAxLjk2ICogU0UsIHRhdV9oYXQgKyAxLjk2ICogU0UpKQpuYW1lcyhyZXN1bHQpIDwtIGMoImVzdCIsICJzZCIsICJDSV9sb3dlciIsICJDSV91cHBlciIpCnJlc3VsdApgYGAKCi0gU3RhdGlzdGljYWwgaW5mZXJlbmNlIHVzaW5nIGJvb3RzdHJhcAoKYGBge3J9CgpJUFdfZXN0aW1hdG9yIDwtIGZ1bmN0aW9uKFcsIFksIFgpIHsKICAjIyBFc3RpbWF0ZSBwcm9wZW5zaXR5IHNjb3JlCiAgbW9kZWwgPC0gZ2xtKFcgfiBYICwgZmFtaWx5ID0gImJpbm9taWFsIikKICBlcHMgPC0gcHJlZGljdChtb2RlbCwgdHlwZSA9ICJyZXNwb25zZSIpCiAgCiAgIyMgQ2FsY3VsYXRlIHRoZSB3ZWlnaHRzCiAgd2VpZ2h0cyA8LSBpZmVsc2UoVyA9PSAxLCAxL2VwcywgMS8oMSAtIGVwcykpCiAgCiAgIyMgQ2FsY3VsYXRlIHdlaWdodGVkIG1lYW4gZGlmZmVyZW5jZSBiZXR3ZWVuIHRyZWF0ZWQgYW5kIGNvbnRyb2wgZ3JvdXAKICBlc3QgPC0gbG0oWSB+IFcsIHdlaWdodHMgPSB3ZWlnaHRzKSRjb2VmWzJdCiAgcmV0dXJuKGVzdCkKfQoKWCA8LSBtb2RlbC5tYXRyaXgobW9kZWwudHJpbW1lZCkKWSA8LSBsYWxvbmRlLnRyaW1tZWQkcmU3OApXIDwtIGxhbG9uZGUudHJpbW1lZCR0cmVhdAoKSVBXX2Jvb3RzdHJhcCA8LSBmdW5jdGlvbihXLCBZLCBYLCBuLmJvb3QgPSAyMDApewogIGVzdCA8LSBJUFdfZXN0aW1hdG9yKFcsIFksIFgpCiAgSVBXYm9vdCA8LSBzYXBwbHkoMTpuLmJvb3QsIGZ1bmN0aW9uKGkpIHsKICAgIGlkLmJvb3QgPC0gc2FtcGxlKDE6bGVuZ3RoKFcpLCByZXBsYWNlID0gVCkKICAgIElQV19lc3RpbWF0b3IoV1tpZC5ib290XSwgWVtpZC5ib290XSwgWFtpZC5ib290LCBdKQogIH0pCiAgcmV0dXJuKGMoZXN0LCBzZChJUFdib290KSkpCn0KClNFX2Jvb3N0cmFwIDwtIElQV19ib290c3RyYXAoVywgWSwgWCwgNTAwMClbMl0KcmVzdWx0X2Jvb3RzdHJhcCA8LSBjKHRhdV9oYXQsIFNFX2Jvb3N0cmFwLCAKICAgICAgICAgICAgICAgICAgICAgIGModGF1X2hhdC0gMS45NiAqIFNFX2Jvb3N0cmFwLCB0YXVfaGF0ICsgMS45NiAqIFNFX2Jvb3N0cmFwKSkKbmFtZXMocmVzdWx0X2Jvb3RzdHJhcCkgPC0gYygiZXN0IiwgInNkIChib290c3RyYXApIiwgIkNJX2xvd2VyIChib290c3RyYXApIiwgIkNJX3VwcGVyIChib290c3RyYXApIikKcmVzdWx0X2Jvb3RzdHJhcApgYGAKCgo=