The purpose of this assignment is to predict the number of rings on an abalone. Rings are a way of determining the maturity of abalones, but the instructor altered the data set to set some number of rings values equal to zero (rude). This post is broken into two main sections: first we’ll account for missing data, outliers, and our instructor’s tinkering then we’ll develop a Poisson regression model to predict the number of rings in our test set of abalone.
Data Exploration
The abalone
data set contains information on about 3,300 abalones. Gender and height do not appear to correlate with number of rings, so those are dropped prior to modeling. Length, diameter, and measures of weight do correlate with number of rings, but the weight measurements may pose multicollinearity issues.
ggpairs(abalone%>%select(-index))+yaztheme::theme_yaz()

Digging further into multicollinearity around weight measures, there is strong correlation between all four weight measures. We’ll go ahead and pick wholeweight
for use in models since the measures shuckedweight
, visceraweight
, and shellweight
involve human interaction with the abalone and therefore potentially risk measurement error.
The resulting data set has four columns and 3,310 observations. Although there are no null values, there may be outliers and we know the data is inflated with zero-values.
abalone_train <- abalone%>%
select(target_rings, length, diameter, wholeweight)
head(abalone_train)
Rather than train a zeroo-inflated poisson regression model, we can delete all outliers and reimpute the missing values using regression trees.

Missing values can be reimputed using the mice
package.
library(mice)
temp_df <- mice(abalone_sans_outliers, method = 'cart', maxit = 1)
abalone_train_clean <- complete(temp_df)
GGally::ggpairs(abalone_train_clean)

Finally, we can train a poisson regression model on the remaining data. Interaction terms were tested but eventally discarded in the backwards step-wise variable selection process, so the final poisson regression model is \(rings ~ length + diameter + wholeweight\).
library(MASS)
step <- stepAIC(glm(target_rings~ .,data = abalone_train_clean, family = poisson),
scope = . ~ .^2, direction = 'backward')
summary(step)
We can now build the perscribed model and bootstrap confidence intervals for its estimates by iteratively resampling the data and calculating the root mean squared error (RMSE). The average bootstrapped prediction is 2.23 rings! That can’t possibly be right. Re-examining the exploratory plots
for(i in seq(1,250)){
df <- abalone_train_clean_index%>%sample_frac(.9, replace = TRUE)
pois.fit <- glm(formula = target_rings ~ length + diameter + wholeweight,
family = poisson, data = df)
oos <- setdiff(abalone_train_clean_index, df)
pred_df <- data.frame(predicted_value = predict.glm(pois.fit, oos),
actual = oos$target_rings,
index = oos$index)%>%
mutate(error = actual - predicted_value,
iter = i)
full_dfs[[i]] <- pred_df
}
for(i in seq(1,250)){
df <- abalone_train_clean_index%>%sample_frac(.9, replace = TRUE)
pois.fit <- glm(formula = target_rings ~ length + diameter + wholeweight,
family = poisson, data = df)
oos <- setdiff(abalone_train_clean_index, df)
pred_df <- data.frame(predicted_value = predict.glm(pois.fit, oos),
actual = oos$target_rings,
index = oos$index)%>%
mutate(error = actual - predicted_value,
iter = i)
full_dfs[[i]] <- pred_df
}
cv_preds <- bind_rows(full_dfs)
cv_preds%>%
group_by(index)%>%
summarise(mean_error = mean(error, na.rm = T),
mean_pred = mean(predicted_value, na.rm = T))%>%
.$mean_pred%>%
summary()
The first model attempted clearly underestimates ring count. But before giving up on this line of inquiry, I want to try one more model with the data as we have it. After removing outliers (including our inflated number of zero values!) the data looked almost normalized, so maybe an OLS regression model will do just fine here.
summary(lm.fit)
This is much better! But we’re still not getting much beyond just taking the average value for target_rings
and applying it to the whole data set.
Lastly, we can try a zero-inflated poisson regression model without removing and reimputing outliers. The zero-inflated model has much larger coefficients than the traditional poisson model tested previously. Perhaps that will remedy the problem of under-predicting ring counts.
As should have been intuitive all along, the zero-inflated model outperforms the rest. The result isn’t perfect, but the pattern of the fit appears to follow the pattern of actual values better than OLS or simple poisson models.
LS0tDQp0aXRsZTogIlBSRUQgNDExIC0gQWJhbG9uZSBFeHRyYSBDcmVkaXQiDQphdXRob3I6ICdKb3NoIFlhem1hbicNCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNClRoZSBwdXJwb3NlIG9mIHRoaXMgYXNzaWdubWVudCBpcyB0byBwcmVkaWN0IHRoZSBudW1iZXIgb2YgcmluZ3Mgb24gYW4gYWJhbG9uZS4gUmluZ3MgYXJlIGEgd2F5IG9mIGRldGVybWluaW5nIHRoZSBtYXR1cml0eSBvZiBhYmFsb25lcywgYnV0IHRoZSBpbnN0cnVjdG9yIGFsdGVyZWQgdGhlIGRhdGEgc2V0IHRvIHNldCBzb21lIG51bWJlciBvZiByaW5ncyB2YWx1ZXMgZXF1YWwgdG8gemVybyAocnVkZSkuIFRoaXMgcG9zdCBpcyBicm9rZW4gaW50byB0d28gbWFpbiBzZWN0aW9uczogZmlyc3Qgd2UnbGwgYWNjb3VudCBmb3IgbWlzc2luZyBkYXRhLCBvdXRsaWVycywgYW5kIG91ciBpbnN0cnVjdG9yJ3MgdGlua2VyaW5nIHRoZW4gd2UnbGwgZGV2ZWxvcCBhIFBvaXNzb24gcmVncmVzc2lvbiBtb2RlbCB0byBwcmVkaWN0IHRoZSBudW1iZXIgb2YgcmluZ3MgaW4gb3VyIHRlc3Qgc2V0IG9mIGFiYWxvbmVbXjFdLiANCg0KIyMgRGF0YSBFeHBsb3JhdGlvbg0KVGhlIGBhYmFsb25lYCBkYXRhIHNldCBjb250YWlucyBpbmZvcm1hdGlvbiBvbiBhYm91dCAzLDMwMCBhYmFsb25lcy4gR2VuZGVyIGFuZCBoZWlnaHQgZG8gbm90IGFwcGVhciB0byBjb3JyZWxhdGUgd2l0aCBudW1iZXIgb2YgcmluZ3MsIHNvIHRob3NlIGFyZSBkcm9wcGVkIHByaW9yIHRvIG1vZGVsaW5nLiBMZW5ndGgsIGRpYW1ldGVyLCBhbmQgbWVhc3VyZXMgb2Ygd2VpZ2h0IGRvIGNvcnJlbGF0ZSB3aXRoIG51bWJlciBvZiByaW5ncywgYnV0IHRoZSB3ZWlnaHQgbWVhc3VyZW1lbnRzIG1heSBwb3NlIG11bHRpY29sbGluZWFyaXR5IGlzc3Vlcy4gDQoNCmBgYHtyLCBlY2hvPVRSVUUsIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgZmlnLmhlaWdodD0xMH0NCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh5YXp0aGVtZSkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoR0dhbGx5KQ0KIyBzZXR3ZCgnRGVza3RvcC9hYmFsb25lLycpDQphYmFsb25lIDwtIHJlYWRfY3N2KCd6aXBfYWJhbG9uZS0xLmNzdicpDQpjb2xuYW1lcyhhYmFsb25lKSA8LSB0b2xvd2VyKGNvbG5hbWVzKGFiYWxvbmUpKQ0KZ2dwYWlycyhhYmFsb25lJT4lc2VsZWN0KC1pbmRleCkpK3lhenRoZW1lOjp0aGVtZV95YXooKQ0KYGBgDQoNCkRpZ2dpbmcgZnVydGhlciBpbnRvIG11bHRpY29sbGluZWFyaXR5IGFyb3VuZCB3ZWlnaHQgbWVhc3VyZXMsIHRoZXJlIGlzIHN0cm9uZyBjb3JyZWxhdGlvbiBiZXR3ZWVuIGFsbCBmb3VyIHdlaWdodCBtZWFzdXJlcy4gV2UnbGwgZ28gYWhlYWQgYW5kIHBpY2sgYHdob2xld2VpZ2h0YCBmb3IgdXNlIGluIG1vZGVscyBzaW5jZSB0aGUgbWVhc3VyZXMgYHNodWNrZWR3ZWlnaHRgLCBgdmlzY2VyYXdlaWdodGAsIGFuZCBgc2hlbGx3ZWlnaHRgIGludm9sdmUgaHVtYW4gaW50ZXJhY3Rpb24gd2l0aCB0aGUgYWJhbG9uZSBhbmQgdGhlcmVmb3JlIHBvdGVudGlhbGx5IHJpc2sgbWVhc3VyZW1lbnQgZXJyb3IuIA0KDQpgYGB7ciwgZWNobz1UUlVFLHJlc3VsdHM9J2hpZGUnfQ0KZ2dwYWlycyhhYmFsb25lJT4lc2VsZWN0KGNvbnRhaW5zKCd3ZWlnaHQnKSkpK3RoZW1lX3lheigpDQpgYGANCg0KVGhlIHJlc3VsdGluZyBkYXRhIHNldCBoYXMgZm91ciBjb2x1bW5zIGFuZCAzLDMxMCBvYnNlcnZhdGlvbnMuIEFsdGhvdWdoIHRoZXJlIGFyZSBubyBudWxsIHZhbHVlcywgdGhlcmUgbWF5IGJlIG91dGxpZXJzIGFuZCB3ZSBrbm93IHRoZSBkYXRhIGlzIGluZmxhdGVkIHdpdGggemVyby12YWx1ZXMuIA0KYGBge3IsIGVjaG8gPSBUUlVFfQ0KYWJhbG9uZV90cmFpbiA8LSBhYmFsb25lJT4lDQogIHNlbGVjdCh0YXJnZXRfcmluZ3MsIGxlbmd0aCwgZGlhbWV0ZXIsIHdob2xld2VpZ2h0KQ0KaGVhZChhYmFsb25lX3RyYWluKQ0KYGBgDQoNClJhdGhlciB0aGFuIHRyYWluIGEgemVyb28taW5mbGF0ZWQgcG9pc3NvbiByZWdyZXNzaW9uIG1vZGVsLCB3ZSBjYW4gZGVsZXRlIGFsbCBvdXRsaWVycyBhbmQgcmVpbXB1dGUgdGhlIG1pc3NpbmcgdmFsdWVzIHVzaW5nIHJlZ3Jlc3Npb24gdHJlZXMuIA0KYGBge3IsIGVjaG89VFJVRSxyZXN1bHRzPSdoaWRlJywgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9M30NCmNvbXBsZXRlX2NvbHMgPC0gbGlzdCgpDQpmb3IoaSBpbiBjb2xuYW1lcyhhYmFsb25lX3RyYWluKSl7DQogIGNvbHZlYyA8LSBhYmFsb25lX3RyYWluJT4lc2VsZWN0Xyhjb2wgPSBpKSU+JS4kY29sDQogIGNvbF9xIDwtIHF1YW50aWxlKGNvbHZlYykNCiAgbG93IDwtIGNvbF9xWzJdIC0gKGNvbF9xWzRdIC0gY29sX3FbMl0pDQogIGhpZ2ggPC0gY29sX3FbNF0gKyAoY29sX3FbNF0gLSBjb2xfcVsyXSkNCiAgZm9yKGogaW4gc2VxKDEsbGVuZ3RoKGNvbHZlYykpKXsNCiAgICBpZihiZXR3ZWVuKGNvbHZlY1tqXSwgbG93LCBoaWdoKSl7DQogICAgICBjb2x2ZWNbal0gPC0gY29sdmVjW2pdDQogICAgfSBlbHNlIHsNCiAgICAgIGNvbHZlY1tqXSA8LSBOQQ0KICAgIH0NCiAgfQ0KICBjb21wbGV0ZV9jb2xzW1tpXV0gPC0gY29sdmVjDQp9DQoNCmFiYWxvbmVfc2Fuc19vdXRsaWVycyA8LSBkYXRhLmZyYW1lKGNvbXBsZXRlX2NvbHMpDQoNCmRlbGV0ZWQgPC0gbWljZTo6ZmljbyhhYmFsb25lX3NhbnNfb3V0bGllcnMpDQoNCmJpbmRfY29scyhkYXRhLmZyYW1lKHZhcmlhYmxlID0gbmFtZXMoZGVsZXRlZCkpLA0KICAgICAgICAgIGRhdGEuZnJhbWUocGVyY2VudCA9IGFzLnZlY3RvcihkZWxldGVkKSkpJT4lDQogIGdncGxvdChhZXModmFyaWFibGUsIHBlcmNlbnQqMTAwKSkrDQogIGdlb21fY29sKGZpbGwgPSB5YXpfY29sc1sxXSkrDQogIGNvb3JkX2ZsaXAoKSsNCiAgbGFicyh0aXRsZSA9ICdQZXJjZW50YWdlIG9mIENvbHVtbiBEZWxldGVkIGFzIE91dGxpZXJzJywNCiAgICAgICB5ID0gJ1BlcmNlbnQnLCB4ID0gZWxlbWVudF9ibGFuaygpKSsNCiAgdGhlbWVfeWF6KCkNCmBgYA0KDQpNaXNzaW5nIHZhbHVlcyBjYW4gYmUgcmVpbXB1dGVkIHVzaW5nIHRoZSBgbWljZWAgcGFja2FnZS4gDQpgYGB7ciwgZWNobz1UUlVFLHJlc3VsdHM9J2hpZGUnfQ0KbGlicmFyeShtaWNlKQ0KdGVtcF9kZiA8LSBtaWNlKGFiYWxvbmVfc2Fuc19vdXRsaWVycywgbWV0aG9kID0gJ2NhcnQnLCBtYXhpdCA9IDEpDQphYmFsb25lX3RyYWluX2NsZWFuIDwtIGNvbXBsZXRlKHRlbXBfZGYpDQpHR2FsbHk6OmdncGFpcnMoYWJhbG9uZV90cmFpbl9jbGVhbikNCmBgYA0KDQpGaW5hbGx5LCB3ZSBjYW4gdHJhaW4gYSBwb2lzc29uIHJlZ3Jlc3Npb24gbW9kZWwgb24gdGhlIHJlbWFpbmluZyBkYXRhLiBJbnRlcmFjdGlvbiB0ZXJtcyB3ZXJlIHRlc3RlZCBidXQgZXZlbnRhbGx5IGRpc2NhcmRlZCBpbiB0aGUgYmFja3dhcmRzIHN0ZXAtd2lzZSB2YXJpYWJsZSBzZWxlY3Rpb24gcHJvY2Vzcywgc28gdGhlIGZpbmFsIHBvaXNzb24gcmVncmVzc2lvbiBtb2RlbCBpcyAkcmluZ3MgfiBsZW5ndGggKyBkaWFtZXRlciArIHdob2xld2VpZ2h0JC4gDQpgYGB7ciwgZWNobz1UUlVFLHJlc3VsdHM9J2hpZGUnfQ0KbGlicmFyeShNQVNTKQ0Kc3RlcCA8LSBzdGVwQUlDKGdsbSh0YXJnZXRfcmluZ3N+IC4sZGF0YSA9IGFiYWxvbmVfdHJhaW5fY2xlYW4sIGZhbWlseSA9IHBvaXNzb24pLA0KICAgICAgICAgICAgICAgIHNjb3BlID0gLiB+IC5eMiwgZGlyZWN0aW9uID0gJ2JhY2t3YXJkJykNCnN1bW1hcnkoc3RlcCkNCmBgYA0KDQpXZSBjYW4gbm93IGJ1aWxkIHRoZSBwZXJzY3JpYmVkIG1vZGVsIGFuZCBib290c3RyYXAgY29uZmlkZW5jZSBpbnRlcnZhbHMgZm9yIGl0cyBlc3RpbWF0ZXMgYnkgaXRlcmF0aXZlbHkgcmVzYW1wbGluZyB0aGUgZGF0YSBhbmQgY2FsY3VsYXRpbmcgdGhlIHJvb3QgbWVhbiBzcXVhcmVkIGVycm9yIChSTVNFKS4gVGhlIGF2ZXJhZ2UgYm9vdHN0cmFwcGVkIHByZWRpY3Rpb24gaXMgMi4yMyByaW5ncyEgVGhhdCBjYW4ndCBwb3NzaWJseSBiZSByaWdodC4gUmUtZXhhbWluaW5nIHRoZSBleHBsb3JhdG9yeSBwbG90cw0KDQpgYGB7ciwgZWNobz1UUlVFLHJlc3VsdHM9J2hpZGUnfQ0KYWJhbG9uZV90cmFpbl9jbGVhbl9pbmRleCA8LSBhYmFsb25lX3RyYWluX2NsZWFuJT4lDQogIGJpbmRfY29scyhkYXRhLmZyYW1lKGluZGV4ID0gc2VxKDEsIG5yb3coYWJhbG9uZV90cmFpbl9jbGVhbikpKSkNCmZ1bGxfZGZzIDwtIGxpc3QoKQ0KZm9yKGkgaW4gc2VxKDEsMjUwKSl7DQogIGRmIDwtIGFiYWxvbmVfdHJhaW5fY2xlYW5faW5kZXglPiVzYW1wbGVfZnJhYyguOSwgcmVwbGFjZSA9IFRSVUUpDQogIHBvaXMuZml0IDwtIGdsbShmb3JtdWxhID0gdGFyZ2V0X3JpbmdzIH4gbGVuZ3RoICsgZGlhbWV0ZXIgKyB3aG9sZXdlaWdodCwgDQogICAgICAgICAgICAgICAgICBmYW1pbHkgPSBwb2lzc29uLCBkYXRhID0gZGYpDQogIG9vcyA8LSBzZXRkaWZmKGFiYWxvbmVfdHJhaW5fY2xlYW5faW5kZXgsIGRmKQ0KICBwcmVkX2RmIDwtIGRhdGEuZnJhbWUocHJlZGljdGVkX3ZhbHVlID0gcHJlZGljdC5nbG0ocG9pcy5maXQsIG9vcyksDQogICAgICAgICAgICAgICAgICAgICAgICBhY3R1YWwgPSBvb3MkdGFyZ2V0X3JpbmdzLA0KICAgICAgICAgICAgICAgICAgICAgICAgaW5kZXggPSBvb3MkaW5kZXgpJT4lDQogICAgbXV0YXRlKGVycm9yID0gYWN0dWFsIC0gcHJlZGljdGVkX3ZhbHVlLA0KICAgICAgICAgICBpdGVyID0gaSkNCiAgZnVsbF9kZnNbW2ldXSA8LSBwcmVkX2RmDQp9DQoNCmN2X3ByZWRzIDwtIGJpbmRfcm93cyhmdWxsX2RmcykNCmN2X3ByZWRzJT4lDQogIGdyb3VwX2J5KGluZGV4KSU+JQ0KICBzdW1tYXJpc2UobWVhbl9lcnJvciA9IG1lYW4oZXJyb3IsIG5hLnJtID0gVCksDQogICAgICAgICAgICBtZWFuX3ByZWQgPSBtZWFuKHByZWRpY3RlZF92YWx1ZSwgbmEucm0gPSBUKSklPiUNCiAgLiRtZWFuX3ByZWQlPiUNCiAgc3VtbWFyeSgpDQpgYGANCg0KVGhlIGZpcnN0IG1vZGVsIGF0dGVtcHRlZCBjbGVhcmx5IHVuZGVyZXN0aW1hdGVzIHJpbmcgY291bnQuIEJ1dCBiZWZvcmUgZ2l2aW5nIHVwIG9uIHRoaXMgbGluZSBvZiBpbnF1aXJ5LCBJIHdhbnQgdG8gdHJ5IG9uZSBtb3JlIG1vZGVsIHdpdGggdGhlIGRhdGEgYXMgd2UgaGF2ZSBpdC4gQWZ0ZXIgcmVtb3Zpbmcgb3V0bGllcnMgKGluY2x1ZGluZyBvdXIgaW5mbGF0ZWQgbnVtYmVyIG9mIHplcm8gdmFsdWVzISkgdGhlIGRhdGEgbG9va2VkIGFsbW9zdCBub3JtYWxpemVkLCBzbyBtYXliZSBhbiBPTFMgcmVncmVzc2lvbiBtb2RlbCB3aWxsIGRvIGp1c3QgZmluZSBoZXJlLg0KDQpgYGB7ciwgZWNobz1UUlVFLHJlc3VsdHM9J2hpZGUnfQ0KbG0uZml0IDwtIGxtKHRhcmdldF9yaW5ncyB+IGxlbmd0aCArIGRpYW1ldGVyICsgd2hvbGV3ZWlnaHQsIGRhdGEgPSBhYmFsb25lX3RyYWluX2NsZWFuKQ0Kc3VtbWFyeShsbS5maXQpDQpgYGANCg0KVGhpcyBpcyBtdWNoIGJldHRlciEgQnV0IHdlJ3JlIHN0aWxsIG5vdCBnZXR0aW5nIG11Y2ggYmV5b25kIGp1c3QgdGFraW5nIHRoZSBhdmVyYWdlIHZhbHVlIGZvciBgdGFyZ2V0X3JpbmdzYCBhbmQgYXBwbHlpbmcgaXQgdG8gdGhlIHdob2xlIGRhdGEgc2V0LiAgDQpgYGB7ciwgZWNobz1UUlVFLHJlc3VsdHM9J2hpZGUnfQ0Kb2xzX2RmcyA8LSBsaXN0KCkNCmZvcihpIGluIHNlcSgxLDI1MCkpew0KICBkZiA8LSBhYmFsb25lX3RyYWluX2NsZWFuX2luZGV4JT4lc2FtcGxlX2ZyYWMoLjksIHJlcGxhY2UgPSBUUlVFKQ0KICBsbS5maXQgPC0gbG0oZm9ybXVsYSA9IHRhcmdldF9yaW5ncyB+IGxlbmd0aCArIGRpYW1ldGVyICsgd2hvbGV3ZWlnaHQsIGRhdGEgPSBkZikNCiAgb29zIDwtIHNldGRpZmYoYWJhbG9uZV90cmFpbl9jbGVhbl9pbmRleCwgZGYpDQogIHByZWRfZGYgPC0gZGF0YS5mcmFtZShwcmVkaWN0ZWRfdmFsdWUgPSBwcmVkaWN0KGxtLmZpdCwgb29zKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGFjdHVhbCA9IG9vcyR0YXJnZXRfcmluZ3MsDQogICAgICAgICAgICAgICAgICAgICAgICBpbmRleCA9IG9vcyRpbmRleCklPiUNCiAgICBtdXRhdGUoZXJyb3IgPSBhY3R1YWwgLSBwcmVkaWN0ZWRfdmFsdWUsDQogICAgICAgICAgIGl0ZXIgPSBpKQ0KICBvbHNfZGZzW1tpXV0gPC0gcHJlZF9kZg0KfQ0KDQpjdl9wcmVkcyA8LSBiaW5kX3Jvd3Mob2xzX2RmcykNCmN2X3ByZWRzJT4lDQogIGdyb3VwX2J5KGFjdHVhbCklPiUNCiAgc3VtbWFyaXNlKG1lYW5fZXJyb3IgPSBtZWFuKGVycm9yLCBuYS5ybSA9IFQpLA0KICAgICAgICAgICAgbWVhbl9wcmVkID0gbWVhbihwcmVkaWN0ZWRfdmFsdWUsIG5hLnJtID0gVCksDQogICAgICAgICAgICBoaWdoID0gcXVhbnRpbGUocHJlZGljdGVkX3ZhbHVlLCAuOTUpLA0KICAgICAgICAgICAgbG93ID0gcXVhbnRpbGUocHJlZGljdGVkX3ZhbHVlLCAuMDUpKSU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBhY3R1YWwsIHkgPSBtZWFuX3ByZWQpKSsNCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbiA9IGxvdywgeW1heCA9IGhpZ2gpLCB3aWR0aCA9IC4yNSkrDQogIGdlb21fcG9pbnQoc2hvdy5sZWdlbmQgPSBGLCBjb2xvciA9IHlhel9jb2xzWzFdKSsNCiAgbGFicyh0aXRsZSA9ICdPTFMgTW9kZWwgUHJlZGljdGlvbnMnLA0KICAgICAgIHkgPSAnUHJlZGljdGVkIFZhbHVlJywgeCA9ICdBY3R1YWwgVmFsdWUnKSsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cCA9ICdkYXNoZWQnKSsNCiAgdGhlbWVfeWF6KCkNCmBgYA0KDQpMYXN0bHksIHdlIGNhbiB0cnkgYSB6ZXJvLWluZmxhdGVkIHBvaXNzb24gcmVncmVzc2lvbiBtb2RlbCB3aXRob3V0IHJlbW92aW5nIGFuZCByZWltcHV0aW5nIG91dGxpZXJzLiBUaGUgemVyby1pbmZsYXRlZCBtb2RlbCBoYXMgbXVjaCBsYXJnZXIgY29lZmZpY2llbnRzIHRoYW4gdGhlIHRyYWRpdGlvbmFsIHBvaXNzb24gbW9kZWwgdGVzdGVkIHByZXZpb3VzbHkuIFBlcmhhcHMgdGhhdCB3aWxsIHJlbWVkeSB0aGUgcHJvYmxlbSBvZiB1bmRlci1wcmVkaWN0aW5nIHJpbmcgY291bnRzLiANCmBgYHtyLCBlY2hvPVRSVUUscmVzdWx0cz0naGlkZSd9DQpsaWJyYXJ5KHBzY2wpDQp6aW5mLmZpdCA8LSB6ZXJvaW5mbCh0YXJnZXRfcmluZ3MgfiBsZW5ndGggKyBkaWFtZXRlciArIHdob2xld2VpZ2h0LCBkYXRhID0gYWJhbG9uZV90cmFpbikNCnN1bW1hcnkoemluZi5maXQpDQpgYGANCg0KQXMgc2hvdWxkIGhhdmUgYmVlbiBpbnR1aXRpdmUgYWxsIGFsb25nLCB0aGUgemVyby1pbmZsYXRlZCBtb2RlbCBvdXRwZXJmb3JtcyB0aGUgcmVzdC4gVGhlIHJlc3VsdCBpc24ndCBwZXJmZWN0LCBidXQgdGhlIHBhdHRlcm4gb2YgdGhlIGZpdCBhcHBlYXJzIHRvIGZvbGxvdyB0aGUgcGF0dGVybiBvZiBhY3R1YWwgdmFsdWVzIGJldHRlciB0aGFuIE9MUyBvciBzaW1wbGUgcG9pc3NvbiBtb2RlbHMuICANCmBgYHtyLCBlY2hvPVRSVUUscmVzdWx0cz0naGlkZSd9DQphYmFsb25lX2luZGV4X3plcm9lcyA8LSBhYmFsb25lX3RyYWluJT4lDQogIGJpbmRfY29scyhkYXRhLmZyYW1lKGluZGV4ID0gc2VxKDEsIG5yb3coYWJhbG9uZV90cmFpbikpKSkNCnppbmZfZGZzIDwtIGxpc3QoKQ0KZm9yKGkgaW4gc2VxKDEsMjUwKSl7DQogIGRmIDwtIGFiYWxvbmVfaW5kZXhfemVyb2VzJT4lc2FtcGxlX2ZyYWMoLjksIHJlcGxhY2UgPSBUUlVFKQ0KICB6aW5mLmZpdCA8LSB6aW5mLmZpdCA8LSB6ZXJvaW5mbCh0YXJnZXRfcmluZ3MgfiBsZW5ndGggKyBkaWFtZXRlciArIHdob2xld2VpZ2h0LCBkYXRhID0gZGYpDQogIG9vcyA8LSBzZXRkaWZmKGFiYWxvbmVfaW5kZXhfemVyb2VzLCBkZikNCiAgcHJlZF9kZiA8LSBkYXRhLmZyYW1lKHByZWRpY3RlZF92YWx1ZSA9IHByZWRpY3QoemluZi5maXQsIG9vcyksDQogICAgICAgICAgICAgICAgICAgICAgICBhY3R1YWwgPSBvb3MkdGFyZ2V0X3JpbmdzLA0KICAgICAgICAgICAgICAgICAgICAgICAgaW5kZXggPSBvb3MkaW5kZXgpJT4lDQogICAgbXV0YXRlKGVycm9yID0gYWN0dWFsIC0gcHJlZGljdGVkX3ZhbHVlLA0KICAgICAgICAgICBpdGVyID0gaSkNCiAgemluZl9kZnNbW2ldXSA8LSBwcmVkX2RmDQp9DQoNCmN2X3ByZWRzIDwtIGJpbmRfcm93cyh6aW5mX2RmcykNCmN2X3ByZWRzJT4lDQogIGdyb3VwX2J5KGFjdHVhbCklPiUNCiAgc3VtbWFyaXNlKG1lYW5fZXJyb3IgPSBtZWFuKGVycm9yLCBuYS5ybSA9IFQpLA0KICAgICAgICAgICAgbWVhbl9wcmVkID0gbWVhbihwcmVkaWN0ZWRfdmFsdWUsIG5hLnJtID0gVCksDQogICAgICAgICAgICBoaWdoID0gcXVhbnRpbGUocHJlZGljdGVkX3ZhbHVlLCAuOTUpLA0KICAgICAgICAgICAgbG93ID0gcXVhbnRpbGUocHJlZGljdGVkX3ZhbHVlLCAuMDUpKSU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBhY3R1YWwsIHkgPSBtZWFuX3ByZWQpKSsNCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbiA9IGxvdywgeW1heCA9IGhpZ2gpLCB3aWR0aCA9IC4yNSkrDQogIGdlb21fcG9pbnQoc2hvdy5sZWdlbmQgPSBGLCBjb2xvciA9IHlhel9jb2xzWzFdKSsNCiAgbGFicyh0aXRsZSA9ICdaZXJvLUluZmxhdGVkIE1vZGVsIFByZWRpY3Rpb25zJywNCiAgICAgICB5ID0gJ1ByZWRpY3RlZCBWYWx1ZScsIHggPSAnQWN0dWFsIFZhbHVlJykrDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGxpbmV0eXAgPSAnZGFzaGVkJykrDQogIHRoZW1lX3lheigpDQpgYGANCg0KW14xXTogQWxsIG1lYXN1cmVzIGFyZSBib290c3RyYXBwZWQgdG8gcHJlc2VudCBhIHJhbmdlIG9mIG1vZGVsIG91dGNvbWVzIHdpdGhpbiBhIHJlYXNvbmFibGUgY29uZmlkZW5jZSBpbnRlcnZhbC4g