Titanic Trees

Work through the kaggle Titanic competition found here with bagging, random forest, and GBM boosting.

Libraries I might Use

library(tidyverse)
library(class)
library(ISLR)
library(tidyverse)
library(randomForest)
library(gbm)
library(MASS)
library(rpart)
library(caTools)

Loading in the Data

train_data = read.csv("train.csv") # Train data Is really my sample overall
test_data = read.csv("test.csv") # New predictions will be performed on this data set

Encoding Factors and forcing NA for char columns

train_data[train_data==""] <- NA
train_data$Sex=factor(train_data$Sex, levels=c("male","female") ,labels = c(0,1))
train_data$Pclass=factor(train_data$Pclass, levels = c(1,2,3), labels = c(0,1,2))
train_data$Embarked=factor(train_data$Embarked, levels = c("C","Q","S"), labels = c(0,1,2))
train_data$Survived=factor(train_data$Survived)
train_data

Exploring the data

train_data
summary(train_data)
  PassengerId    Survived Pclass      Name           Sex          Age            SibSp           Parch           Ticket               Fare
 Min.   :  1.0   0:549    0:216   Length:891         0:577   Min.   : 0.42   Min.   :0.000   Min.   :0.0000   Length:891         Min.   :  0.00
 1st Qu.:223.5   1:342    1:184   Class :character   1:314   1st Qu.:20.12   1st Qu.:0.000   1st Qu.:0.0000   Class :character   1st Qu.:  7.91
 Median :446.0            2:491   Mode  :character           Median :28.00   Median :0.000   Median :0.0000   Mode  :character   Median : 14.45
 Mean   :446.0                                               Mean   :29.70   Mean   :0.523   Mean   :0.3816                      Mean   : 32.20
 3rd Qu.:668.5                                               3rd Qu.:38.00   3rd Qu.:1.000   3rd Qu.:0.0000                      3rd Qu.: 31.00
 Max.   :891.0                                               Max.   :80.00   Max.   :8.000   Max.   :6.0000                      Max.   :512.33
                                                             NA's   :177
    Cabin           Embarked
 Length:891         0   :168
 Class :character   1   : 77
 Mode  :character   2   :644
                    NA's:  2


                              
  1. I’m predicting Survived

  2. Intuitively, I’d expect Pclass to be a significant predictor

  3. Likewise, don’t women and children always go first? So age and sex should be valuable predictors.

  4. SibSp represents the number of siblings/spouses aboard. Maybe important? If there are people with you they might help you escape, but you might also stay behind for them.

  5. Parch is the number of parents or children abboard. If you are young I’d expect higher number of parents increases your chance, while if you are older the number of children might cause you to hold back trying to help them.

  6. Information in fare is probably contained in class

  7. Cabin/Ticket seem unlikely to provide much meaningful information

  8. embarked is the port of embarkation. Unlikely to help much, but I’m not that confident about it.

All told our big factors are pclass, age, sex, sibsp, parch. Maybe embarked.

ggplot(train_data)+
  geom_violin(aes(x=Survived, y = Age, fill = Survived))

Right away we are reminded of 177 missing age values seen inthe summary.

We can also see that the didnt survive group has has a heavier weight around the age of 25.

ggplot(train_data[which(is.na(train_data$Age)),])+
  geom_bar(aes(x=Survived, fill = Survived))

Most of our missing values died

train_data[which(is.na(train_data$Age)),]
ggplot(train_data)+
  geom_bar(aes(x=Pclass, fill = Survived))

Ticket class breakdown is as you would expect.

ggplot(train_data)+
  geom_bar(aes(x=Embarked, fill = Survived))

There is more infromation that you would expect here. the final port of embarkation (southhampton) had the most people, but it also has the worst percentage of survival.

ggplot(train_data)+
  geom_bar(aes(x=SibSp, fill = Survived))

Most people had 0 spouses or siblings aboard, the better percentage at 1 could be attributed directly to spouse, but that doesn’t hold up for the better percentage at 2

ggplot(train_data)+
  geom_bar(aes(x=Parch, fill = Survived))

Increased survival at 1, 2, and 3. 1 and 2 are most likely being weighted by parents. 3 is interesting.

ggplot(train_data)+
  geom_bar(aes(x=Sex, fill = Survived))

Women survived at a MUCH higher rate.

train_data$agegroup = ifelse(train_data$Age < 16, 0, ifelse(train_data$Age < 50, 1, 2))
ggplot(train_data)+
 geom_bar(aes(x=agegroup, fill = Survived))

Children survive, those > 50 hardly do.

Dealing with Missing Values

Now that we have explored the data, lets deal with the missing values in embarked and age. We will be using survival, pclass, sex, age, sibsp, parch, and embarked to predict the others

clean_data = train_data[,c(2,3,5,6,7,8, 12)]
clean_data

Embarked

clean_data_embarked = clean_data[-which(is.na(train_data$Embarked)),]
clean_data_embarked

Test/Train Split

set.seed(1234)
split = sample.split(clean_data_embarked$Survived, SplitRatio = 0.8)
train = subset(clean_data_embarked, split == TRUE)
test = subset(clean_data_embarked, split == FALSE)

Model

set.seed(1234)
rf.Embarked = randomForest(Embarked~Survived + Pclass+Sex+SibSp+Parch, data = clean_data_embarked,importance =TRUE, mtry = 3)
importance(rf.Embarked)
                  0         1          2 MeanDecreaseAccuracy MeanDecreaseGini
Survived  0.1761412 23.926388 -0.7525674             10.47326         9.088543
Pclass   29.7102029 20.917772 17.0280234             33.91091        28.531706
Sex       1.1353457 25.138247  3.6891944             14.49255         9.606021
SibSp     5.0916546  3.144096  9.8194733             12.15916        18.989624
Parch     4.9348200 19.201793 16.3263343             21.33507        18.814807
yhat.Embarked = predict(rf.Embarked,newdata=test, type="response")
confusion_matrix = table(test$Embarked,yhat.Embarked)
confusion_matrix
   yhat.Embarked
      0   1   2
  0  15   0  23
  1   0   7   9
  2   6   1 117
124/(121+16+30+2+3)
[1] 0.7209302

Not great predictive power, but basically we only are replacing two values. (with 2 most likely) I could do more here by looking at other predictors that ARE probably predictive of location like fare and ticket, but for the sake of time I’m just going to leave it at embarkation = 2

train_data$Embarked[which(is.na(train_data$Embarked))] = 2
clean_data$Embarked[which(is.na(clean_data$Embarked))] = 2

Age

clean_data_age = clean_data[-which(is.na(train_data$Age)),]
clean_data_age = clean_data_age[,-1] #Can't use survived here because OG test set doesn't tell me survived.
clean_data_age

Test/Train Split

set.seed(1234)
split = sample.split(clean_data_age$Age, SplitRatio = 0.8)
train = subset(clean_data_age, split == TRUE)
test = subset(clean_data_age, split == FALSE)

Model

set.seed(1234)
rf.Age = randomForest(Age~., data = clean_data_age,importance =TRUE, mtry = 4)
importance(rf.Age)
           %IncMSE IncNodePurity
Pclass   87.248524     21086.175
Sex      16.198176      5410.003
SibSp    34.869585     15898.640
Parch    53.452846     21110.849
Embarked  9.446469      5809.567
yhat.Age = predict(rf.Age,newdata=test)
mean((yhat.Age-test$Age)^2)
[1] 110.6331

ooooph that is a a not great mean squared error, but lets keep rolling with it for now.

clean_data$Age[which(is.na(clean_data$Age))] = predict(rf.Age, newdata = clean_data[which(is.na(clean_data$Age)),])
train_data$Age[which(is.na(train_data$Age))] = predict(rf.Age, newdata = train_data[which(is.na(train_data$Age)),])

Modedl Building

clean_data
set.seed(1234)
split = sample.split(clean_data$Survived, SplitRatio = 0.8)
train = subset(clean_data, split == TRUE)
test = subset(clean_data, split == FALSE)
rf.Survived = randomForest(Survived~., data = train,importance =TRUE)
yhat.Survived = predict(rf.Survived,newdata=test, type="response")
confusion_matrix = table(test$Survived,yhat.Survived)
confusion_matrix
   yhat.Survived
     0  1
  0 92 18
  1 20 48
140/178
[1] 0.7865169

not bad, but how does it compare to gender?

yhat.gender = ifelse(test$Sex == 1, 1, 0)
confusion_matrix = table(test$Survived,yhat.gender)
confusion_matrix
   yhat.gender
     0  1
  0 89 21
  1 21 47
136/178
[1] 0.7640449

Better, but not THAT much better. Let’s go ahead and clean up the test data and submit

Test Data Cleanup

test_data
test_data
test_data$Sex=factor(test_data$Sex, levels=c("male","female") ,labels = c(0,1))
test_data$Pclass=factor(test_data$Pclass, levels = c(1,2,3), labels = c(0,1,2))
test_data$Embarked=factor(test_data$Embarked, levels = c("C","Q","S"), labels = c(0,1,2))
test_data_clean = test_data[,c(2,4,5,6,7,11)]
test_data_clean
summary(test_data_clean)
 Pclass  Sex          Age            SibSp            Parch        Embarked
 0:107   0:266   Min.   : 0.17   Min.   :0.0000   Min.   :0.0000   0:102
 1: 93   1:152   1st Qu.:21.00   1st Qu.:0.0000   1st Qu.:0.0000   1: 46
 2:218           Median :27.00   Median :0.0000   Median :0.0000   2:270
                 Mean   :30.27   Mean   :0.4474   Mean   :0.3923
                 3rd Qu.:39.00   3rd Qu.:1.0000   3rd Qu.:0.0000
                 Max.   :76.00   Max.   :8.0000   Max.   :9.0000
                 NA's   :86                                                

Only missing values in Age

test_data_clean$Age[which(is.na(test_data_clean$Age))]=predict(rf.Age,test_data_clean[which(is.na(test_data_clean$Age)),])
test_data_clean
Rfguess = data.frame(test_data$PassengerId, predict(rf.Survived,test_data_clean))
colnames(Rfguess) <- c("PassengerId","Survived")
Rfguess
write.csv(Rfguess, "rfguess.csv",row.names = FALSE)

Final score: 0.77751, not great but I’m in the top quarter and improved over the gender guesses at least!

bagged.Survived = randomForest(Survived~., data = train,importance =TRUE, mtry = 6)
yhat.Survived = predict(bagged.Survived,newdata=test, type="response")
confusion_matrix = table(test$Survived,yhat.Survived)
confusion_matrix
   yhat.Survived
     0  1
  0 87 23
  1 19 49
110/188
[1] 0.5851064

a lot worse

Baggedguess = data.frame(test_data$PassengerId, predict(bagged.Survived,test_data_clean))
colnames(Baggedguess) <- c("PassengerId","Survived")
write.csv(Baggedguess, "rfguess.csv",row.names = FALSE)
boost.Survived = gbm(as.character(Survived)~., data = train, distribution="bernoulli", n.tree = 5000)
best.iter <- gbm.perf(boost.Survived, method = "OOB", plot = FALSE)
OOB generally underestimates the optimal number of iterations although predictive performance is reasonably competitive. Using cv_folds>1 when calling gbm usually results in improved predictive performance.
best.iter
[1] 92
attr(,"smoother")
Call:
loess(formula = object$oobag.improve ~ x, enp.target = min(max(4,
    length(x)/10), 50))

Number of Observations: 5000
Equivalent Number of Parameters: 39.99
Residual Standard Error: 0.000677 
prob = predict(boost.Survived, newdata=test, best.iter,type="response")
yhat.Survived = ifelse(prob>.5, 1, 0)
confusion_matrix = table(test$Survived,yhat.Survived)
confusion_matrix
   yhat.Survived
     0  1
  0 92 18
  1 16 52
140/178
[1] 0.7865169

Same as RF

boostedguess = data.frame(test_data$PassengerId, predict(boost.Survived,test_data_clean))
Using 5000 trees...
colnames(boostedguess) <- c("PassengerId","Survived")
write.csv(boostedguess, "boguess.csv",row.names = FALSE)
LS0tDQp0aXRsZTogIlRpdGFuaWMgVHJlZXMiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpXb3JrIHRocm91Z2ggdGhlIGthZ2dsZSBUaXRhbmljIGNvbXBldGl0aW9uIGZvdW5kIFtoZXJlXShodHRwczovL3d3dy5rYWdnbGUuY29tL2MvdGl0YW5pYykgd2l0aCBiYWdnaW5nLCByYW5kb20gZm9yZXN0LCBhbmQgR0JNIGJvb3N0aW5nLg0KDQojIyBMaWJyYXJpZXMgSSBtaWdodCBVc2UNCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoY2xhc3MpDQpsaWJyYXJ5KElTTFIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KbGlicmFyeShnYm0pDQpsaWJyYXJ5KE1BU1MpDQpsaWJyYXJ5KHJwYXJ0KQ0KbGlicmFyeShjYVRvb2xzKQ0KYGBgDQoNCg0KDQojIyBMb2FkaW5nIGluIHRoZSBEYXRhDQoNCmBgYHtyfQ0KdHJhaW5fZGF0YSA9IHJlYWQuY3N2KCJ0cmFpbi5jc3YiKSAjIFRyYWluIGRhdGEgSXMgcmVhbGx5IG15IHNhbXBsZSBvdmVyYWxsDQp0ZXN0X2RhdGEgPSByZWFkLmNzdigidGVzdC5jc3YiKSAjIE5ldyBwcmVkaWN0aW9ucyB3aWxsIGJlIHBlcmZvcm1lZCBvbiB0aGlzIGRhdGEgc2V0DQpgYGANCg0KDQojIyBFbmNvZGluZyBGYWN0b3JzIGFuZCBmb3JjaW5nIE5BIGZvciBjaGFyIGNvbHVtbnMNCg0KYGBge3J9DQp0cmFpbl9kYXRhW3RyYWluX2RhdGE9PSIiXSA8LSBOQQ0KdHJhaW5fZGF0YSRTZXg9ZmFjdG9yKHRyYWluX2RhdGEkU2V4LCBsZXZlbHM9YygibWFsZSIsImZlbWFsZSIpICxsYWJlbHMgPSBjKDAsMSkpDQp0cmFpbl9kYXRhJFBjbGFzcz1mYWN0b3IodHJhaW5fZGF0YSRQY2xhc3MsIGxldmVscyA9IGMoMSwyLDMpLCBsYWJlbHMgPSBjKDAsMSwyKSkNCnRyYWluX2RhdGEkRW1iYXJrZWQ9ZmFjdG9yKHRyYWluX2RhdGEkRW1iYXJrZWQsIGxldmVscyA9IGMoIkMiLCJRIiwiUyIpLCBsYWJlbHMgPSBjKDAsMSwyKSkNCnRyYWluX2RhdGEkU3Vydml2ZWQ9ZmFjdG9yKHRyYWluX2RhdGEkU3Vydml2ZWQpDQp0cmFpbl9kYXRhDQpgYGANCg0KDQoNCg0KDQojIEV4cGxvcmluZyB0aGUgZGF0YQ0KDQpgYGB7cn0NCnRyYWluX2RhdGENCmBgYA0KDQpgYGB7cn0NCnN1bW1hcnkodHJhaW5fZGF0YSkNCmBgYA0KDQoNCjEuIEknbSBwcmVkaWN0aW5nIFN1cnZpdmVkDQoNCjIuIEludHVpdGl2ZWx5LCBJJ2QgZXhwZWN0IFBjbGFzcyB0byBiZSBhIHNpZ25pZmljYW50IHByZWRpY3Rvcg0KDQozLiBMaWtld2lzZSwgZG9uJ3Qgd29tZW4gYW5kIGNoaWxkcmVuIGFsd2F5cyBnbyBmaXJzdD8gU28gYWdlIGFuZCBzZXggc2hvdWxkIGJlIHZhbHVhYmxlIHByZWRpY3RvcnMuDQoNCjQuIFNpYlNwIHJlcHJlc2VudHMgdGhlIG51bWJlciBvZiBzaWJsaW5ncy9zcG91c2VzIGFib2FyZC4gTWF5YmUgaW1wb3J0YW50PyBJZiB0aGVyZSBhcmUgcGVvcGxlIHdpdGggeW91IHRoZXkgbWlnaHQgaGVscCB5b3UgZXNjYXBlLCBidXQgeW91IG1pZ2h0IGFsc28gc3RheSBiZWhpbmQgZm9yIHRoZW0uDQoNCjUuIFBhcmNoIGlzIHRoZSBudW1iZXIgb2YgcGFyZW50cyBvciBjaGlsZHJlbiBhYmJvYXJkLiBJZiB5b3UgYXJlIHlvdW5nIEknZCBleHBlY3QgaGlnaGVyIG51bWJlciBvZiBwYXJlbnRzIGluY3JlYXNlcyB5b3VyIGNoYW5jZSwgd2hpbGUgaWYgeW91IGFyZSBvbGRlciB0aGUgbnVtYmVyIG9mIGNoaWxkcmVuIG1pZ2h0IGNhdXNlIHlvdSB0byBob2xkIGJhY2sgdHJ5aW5nIHRvIGhlbHAgdGhlbS4NCg0KNi4gSW5mb3JtYXRpb24gaW4gZmFyZSBpcyBwcm9iYWJseSBjb250YWluZWQgaW4gY2xhc3MNCg0KNy4gQ2FiaW4vVGlja2V0IHNlZW0gdW5saWtlbHkgdG8gcHJvdmlkZSBtdWNoIG1lYW5pbmdmdWwgaW5mb3JtYXRpb24NCg0KOC4gZW1iYXJrZWQgaXMgdGhlIHBvcnQgb2YgZW1iYXJrYXRpb24uIFVubGlrZWx5IHRvIGhlbHAgbXVjaCwgYnV0IEknbSBub3QgdGhhdCBjb25maWRlbnQgYWJvdXQgaXQuDQoNCkFsbCB0b2xkIG91ciBiaWcgZmFjdG9ycyBhcmUgcGNsYXNzLCBhZ2UsIHNleCwgc2lic3AsIHBhcmNoLiBNYXliZSBlbWJhcmtlZC4NCg0KYGBge3J9DQpnZ3Bsb3QodHJhaW5fZGF0YSkrDQogIGdlb21fdmlvbGluKGFlcyh4PVN1cnZpdmVkLCB5ID0gQWdlLCBmaWxsID0gU3Vydml2ZWQpKQ0KYGBgDQpSaWdodCBhd2F5IHdlIGFyZSByZW1pbmRlZCBvZiAxNzcgbWlzc2luZyBhZ2UgdmFsdWVzIHNlZW4gaW50aGUgc3VtbWFyeS4NCg0KV2UgY2FuIGFsc28gc2VlIHRoYXQgdGhlIGRpZG50IHN1cnZpdmUgZ3JvdXAgaGFzIGhhcyBhIGhlYXZpZXIgd2VpZ2h0IGFyb3VuZCB0aGUgYWdlIG9mIDI1Lg0KDQpgYGB7cn0NCmdncGxvdCh0cmFpbl9kYXRhW3doaWNoKGlzLm5hKHRyYWluX2RhdGEkQWdlKSksXSkrDQogIGdlb21fYmFyKGFlcyh4PVN1cnZpdmVkLCBmaWxsID0gU3Vydml2ZWQpKQ0KYGBgDQoNCk1vc3Qgb2Ygb3VyIG1pc3NpbmcgdmFsdWVzIGRpZWQNCg0KYGBge3J9DQp0cmFpbl9kYXRhW3doaWNoKGlzLm5hKHRyYWluX2RhdGEkQWdlKSksXQ0KYGBgDQoNCg0KYGBge3J9DQpnZ3Bsb3QodHJhaW5fZGF0YSkrDQogIGdlb21fYmFyKGFlcyh4PVBjbGFzcywgZmlsbCA9IFN1cnZpdmVkKSkNCmBgYA0KVGlja2V0IGNsYXNzIGJyZWFrZG93biBpcyBhcyB5b3Ugd291bGQgZXhwZWN0Lg0KDQpgYGB7cn0NCmdncGxvdCh0cmFpbl9kYXRhKSsNCiAgZ2VvbV9iYXIoYWVzKHg9RW1iYXJrZWQsIGZpbGwgPSBTdXJ2aXZlZCkpDQpgYGANClRoZXJlIGlzIG1vcmUgaW5mcm9tYXRpb24gdGhhdCB5b3Ugd291bGQgZXhwZWN0IGhlcmUuIHRoZSBmaW5hbCBwb3J0IG9mIGVtYmFya2F0aW9uIChzb3V0aGhhbXB0b24pIGhhZCB0aGUgbW9zdCBwZW9wbGUsIGJ1dCBpdCBhbHNvIGhhcyB0aGUgd29yc3QgcGVyY2VudGFnZSBvZiBzdXJ2aXZhbC4NCg0KYGBge3J9DQpnZ3Bsb3QodHJhaW5fZGF0YSkrDQogIGdlb21fYmFyKGFlcyh4PVNpYlNwLCBmaWxsID0gU3Vydml2ZWQpKQ0KYGBgDQoNCk1vc3QgcGVvcGxlIGhhZCAwIHNwb3VzZXMgb3Igc2libGluZ3MgYWJvYXJkLCB0aGUgYmV0dGVyIHBlcmNlbnRhZ2UgYXQgMSBjb3VsZCBiZSBhdHRyaWJ1dGVkIGRpcmVjdGx5IHRvIHNwb3VzZSwgYnV0IHRoYXQgZG9lc24ndCBob2xkIHVwIGZvciB0aGUgYmV0dGVyIHBlcmNlbnRhZ2UgYXQgMg0KDQpgYGB7cn0NCmdncGxvdCh0cmFpbl9kYXRhKSsNCiAgZ2VvbV9iYXIoYWVzKHg9UGFyY2gsIGZpbGwgPSBTdXJ2aXZlZCkpDQpgYGANCkluY3JlYXNlZCBzdXJ2aXZhbCBhdCAxLCAyLCBhbmQgMy4gMSBhbmQgMiBhcmUgbW9zdCBsaWtlbHkgYmVpbmcgd2VpZ2h0ZWQgYnkgcGFyZW50cy4gMyBpcyBpbnRlcmVzdGluZy4NCg0KYGBge3J9DQpnZ3Bsb3QodHJhaW5fZGF0YSkrDQogIGdlb21fYmFyKGFlcyh4PVNleCwgZmlsbCA9IFN1cnZpdmVkKSkNCmBgYA0KDQpXb21lbiBzdXJ2aXZlZCBhdCBhIE1VQ0ggaGlnaGVyIHJhdGUuDQoNCmBgYHtyfQ0KdHJhaW5fZGF0YSRhZ2Vncm91cCA9IGlmZWxzZSh0cmFpbl9kYXRhJEFnZSA8IDE2LCAwLCBpZmVsc2UodHJhaW5fZGF0YSRBZ2UgPCA1MCwgMSwgMikpDQpnZ3Bsb3QodHJhaW5fZGF0YSkrDQogZ2VvbV9iYXIoYWVzKHg9YWdlZ3JvdXAsIGZpbGwgPSBTdXJ2aXZlZCkpDQpgYGANCg0KDQpDaGlsZHJlbiBzdXJ2aXZlLCB0aG9zZSA+IDUwIGhhcmRseSBkby4NCg0KDQojIERlYWxpbmcgd2l0aCBNaXNzaW5nIFZhbHVlcw0KDQpOb3cgdGhhdCB3ZSBoYXZlIGV4cGxvcmVkIHRoZSBkYXRhLCBsZXRzIGRlYWwgd2l0aCB0aGUgbWlzc2luZyB2YWx1ZXMgaW4gZW1iYXJrZWQgYW5kIGFnZS4gV2Ugd2lsbCBiZSB1c2luZyBzdXJ2aXZhbCwgcGNsYXNzLCBzZXgsIGFnZSwgc2lic3AsIHBhcmNoLCBhbmQgZW1iYXJrZWQgdG8gcHJlZGljdCB0aGUgb3RoZXJzDQoNCmBgYHtyfQ0KY2xlYW5fZGF0YSA9IHRyYWluX2RhdGFbLGMoMiwzLDUsNiw3LDgsIDEyKV0NCmNsZWFuX2RhdGENCmBgYA0KDQojIyBFbWJhcmtlZA0KYGBge3J9DQpjbGVhbl9kYXRhX2VtYmFya2VkID0gY2xlYW5fZGF0YVstd2hpY2goaXMubmEodHJhaW5fZGF0YSRFbWJhcmtlZCkpLF0NCmNsZWFuX2RhdGFfZW1iYXJrZWQNCmBgYA0KDQojIyMgVGVzdC9UcmFpbiBTcGxpdA0KDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzNCkNCnNwbGl0ID0gc2FtcGxlLnNwbGl0KGNsZWFuX2RhdGFfZW1iYXJrZWQkU3Vydml2ZWQsIFNwbGl0UmF0aW8gPSAwLjgpDQp0cmFpbiA9IHN1YnNldChjbGVhbl9kYXRhX2VtYmFya2VkLCBzcGxpdCA9PSBUUlVFKSANCnRlc3QgPSBzdWJzZXQoY2xlYW5fZGF0YV9lbWJhcmtlZCwgc3BsaXQgPT0gRkFMU0UpDQpgYGANCg0KDQojIyMgTW9kZWwNCg0KYGBge3J9DQpzZXQuc2VlZCgxMjM0KQ0KcmYuRW1iYXJrZWQgPSByYW5kb21Gb3Jlc3QoRW1iYXJrZWR+U3Vydml2ZWQgKyBQY2xhc3MrU2V4K1NpYlNwK1BhcmNoLCBkYXRhID0gY2xlYW5fZGF0YV9lbWJhcmtlZCxpbXBvcnRhbmNlID1UUlVFLCBtdHJ5ID0gMykNCmltcG9ydGFuY2UocmYuRW1iYXJrZWQpDQpgYGANCg0KYGBge3J9DQp5aGF0LkVtYmFya2VkID0gcHJlZGljdChyZi5FbWJhcmtlZCxuZXdkYXRhPXRlc3QsIHR5cGU9InJlc3BvbnNlIikNCmNvbmZ1c2lvbl9tYXRyaXggPSB0YWJsZSh0ZXN0JEVtYmFya2VkLHloYXQuRW1iYXJrZWQpDQpjb25mdXNpb25fbWF0cml4DQpgYGANCmBgYHtyfQ0KMTI0LygxMjErMTYrMzArMiszKQ0KYGBgDQpOb3QgZ3JlYXQgcHJlZGljdGl2ZSBwb3dlciwgYnV0IGJhc2ljYWxseSB3ZSBvbmx5IGFyZSByZXBsYWNpbmcgdHdvIHZhbHVlcy4gKHdpdGggMiBtb3N0IGxpa2VseSkgSSBjb3VsZCBkbyBtb3JlIGhlcmUgYnkgbG9va2luZyBhdCBvdGhlciBwcmVkaWN0b3JzIHRoYXQgQVJFIHByb2JhYmx5IHByZWRpY3RpdmUgb2YgbG9jYXRpb24gbGlrZSBmYXJlIGFuZCB0aWNrZXQsIGJ1dCBmb3IgdGhlIHNha2Ugb2YgdGltZSBJJ20ganVzdCBnb2luZyB0byBsZWF2ZSBpdCBhdCBlbWJhcmthdGlvbiA9IDINCg0KYGBge3J9DQp0cmFpbl9kYXRhJEVtYmFya2VkW3doaWNoKGlzLm5hKHRyYWluX2RhdGEkRW1iYXJrZWQpKV0gPSAyDQpjbGVhbl9kYXRhJEVtYmFya2VkW3doaWNoKGlzLm5hKGNsZWFuX2RhdGEkRW1iYXJrZWQpKV0gPSAyDQpgYGANCg0KDQojIyBBZ2UNCmBgYHtyfQ0KY2xlYW5fZGF0YV9hZ2UgPSBjbGVhbl9kYXRhWy13aGljaChpcy5uYSh0cmFpbl9kYXRhJEFnZSkpLF0NCmNsZWFuX2RhdGFfYWdlID0gY2xlYW5fZGF0YV9hZ2VbLC0xXSAjQ2FuJ3QgdXNlIHN1cnZpdmVkIGhlcmUgYmVjYXVzZSBPRyB0ZXN0IHNldCBkb2Vzbid0IHRlbGwgbWUgc3Vydml2ZWQuDQpjbGVhbl9kYXRhX2FnZQ0KYGBgDQoNCg0KIyMjIFRlc3QvVHJhaW4gU3BsaXQNCg0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMzQpDQpzcGxpdCA9IHNhbXBsZS5zcGxpdChjbGVhbl9kYXRhX2FnZSRBZ2UsIFNwbGl0UmF0aW8gPSAwLjgpDQp0cmFpbiA9IHN1YnNldChjbGVhbl9kYXRhX2FnZSwgc3BsaXQgPT0gVFJVRSkgDQp0ZXN0ID0gc3Vic2V0KGNsZWFuX2RhdGFfYWdlLCBzcGxpdCA9PSBGQUxTRSkNCmBgYA0KDQoNCiMjIyBNb2RlbA0KDQoNCg0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMzQpDQpyZi5BZ2UgPSByYW5kb21Gb3Jlc3QoQWdlfi4sIGRhdGEgPSBjbGVhbl9kYXRhX2FnZSxpbXBvcnRhbmNlID1UUlVFLCBtdHJ5ID0gNCkNCmltcG9ydGFuY2UocmYuQWdlKQ0KYGBgDQoNCmBgYHtyfQ0KeWhhdC5BZ2UgPSBwcmVkaWN0KHJmLkFnZSxuZXdkYXRhPXRlc3QpDQptZWFuKCh5aGF0LkFnZS10ZXN0JEFnZSleMikNCmBgYA0KDQpvb29vcGggdGhhdCBpcyBhIGEgbm90IGdyZWF0IG1lYW4gc3F1YXJlZCBlcnJvciwgYnV0IGxldHMga2VlcCByb2xsaW5nIHdpdGggaXQgZm9yIG5vdy4NCg0KYGBge3J9DQpjbGVhbl9kYXRhJEFnZVt3aGljaChpcy5uYShjbGVhbl9kYXRhJEFnZSkpXSA9IHByZWRpY3QocmYuQWdlLCBuZXdkYXRhID0gY2xlYW5fZGF0YVt3aGljaChpcy5uYShjbGVhbl9kYXRhJEFnZSkpLF0pDQp0cmFpbl9kYXRhJEFnZVt3aGljaChpcy5uYSh0cmFpbl9kYXRhJEFnZSkpXSA9IHByZWRpY3QocmYuQWdlLCBuZXdkYXRhID0gdHJhaW5fZGF0YVt3aGljaChpcy5uYSh0cmFpbl9kYXRhJEFnZSkpLF0pDQpgYGANCg0KDQojIE1vZGVkbCBCdWlsZGluZw0KDQpgYGB7cn0NCmNsZWFuX2RhdGENCmBgYA0KDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzNCkNCnNwbGl0ID0gc2FtcGxlLnNwbGl0KGNsZWFuX2RhdGEkU3Vydml2ZWQsIFNwbGl0UmF0aW8gPSAwLjgpDQp0cmFpbiA9IHN1YnNldChjbGVhbl9kYXRhLCBzcGxpdCA9PSBUUlVFKSANCnRlc3QgPSBzdWJzZXQoY2xlYW5fZGF0YSwgc3BsaXQgPT0gRkFMU0UpDQpgYGANCg0KDQpgYGB7cn0NCnJmLlN1cnZpdmVkID0gcmFuZG9tRm9yZXN0KFN1cnZpdmVkfi4sIGRhdGEgPSB0cmFpbixpbXBvcnRhbmNlID1UUlVFKQ0KeWhhdC5TdXJ2aXZlZCA9IHByZWRpY3QocmYuU3Vydml2ZWQsbmV3ZGF0YT10ZXN0LCB0eXBlPSJyZXNwb25zZSIpDQpjb25mdXNpb25fbWF0cml4ID0gdGFibGUodGVzdCRTdXJ2aXZlZCx5aGF0LlN1cnZpdmVkKQ0KY29uZnVzaW9uX21hdHJpeA0KYGBgDQpgYGB7cn0NCjE0MC8xNzgNCmBgYA0Kbm90IGJhZCwgYnV0IGhvdyBkb2VzIGl0IGNvbXBhcmUgdG8gZ2VuZGVyPw0KDQpgYGB7cn0NCnloYXQuZ2VuZGVyID0gaWZlbHNlKHRlc3QkU2V4ID09IDEsIDEsIDApDQpjb25mdXNpb25fbWF0cml4ID0gdGFibGUodGVzdCRTdXJ2aXZlZCx5aGF0LmdlbmRlcikNCmNvbmZ1c2lvbl9tYXRyaXgNCmBgYA0KYGBge3J9DQoxMzYvMTc4DQpgYGANCkJldHRlciwgYnV0IG5vdCBUSEFUIG11Y2ggYmV0dGVyLiBMZXQncyBnbyBhaGVhZCBhbmQgY2xlYW4gdXAgdGhlIHRlc3QgZGF0YSBhbmQgc3VibWl0DQoNCiMgVGVzdCBEYXRhIENsZWFudXANCmBgYHtyfQ0KdGVzdF9kYXRhDQpgYGANCg0KYGBge3J9DQp0ZXN0X2RhdGENCmBgYA0KDQoNCmBgYHtyfQ0KdGVzdF9kYXRhJFNleD1mYWN0b3IodGVzdF9kYXRhJFNleCwgbGV2ZWxzPWMoIm1hbGUiLCJmZW1hbGUiKSAsbGFiZWxzID0gYygwLDEpKQ0KdGVzdF9kYXRhJFBjbGFzcz1mYWN0b3IodGVzdF9kYXRhJFBjbGFzcywgbGV2ZWxzID0gYygxLDIsMyksIGxhYmVscyA9IGMoMCwxLDIpKQ0KdGVzdF9kYXRhJEVtYmFya2VkPWZhY3Rvcih0ZXN0X2RhdGEkRW1iYXJrZWQsIGxldmVscyA9IGMoIkMiLCJRIiwiUyIpLCBsYWJlbHMgPSBjKDAsMSwyKSkNCnRlc3RfZGF0YV9jbGVhbiA9IHRlc3RfZGF0YVssYygyLDQsNSw2LDcsMTEpXQ0KdGVzdF9kYXRhX2NsZWFuDQpgYGANCg0KYGBge3J9DQpzdW1tYXJ5KHRlc3RfZGF0YV9jbGVhbikNCmBgYA0KT25seSBtaXNzaW5nIHZhbHVlcyBpbiBBZ2UNCg0KDQpgYGB7cn0NCnRlc3RfZGF0YV9jbGVhbiRBZ2Vbd2hpY2goaXMubmEodGVzdF9kYXRhX2NsZWFuJEFnZSkpXT1wcmVkaWN0KHJmLkFnZSx0ZXN0X2RhdGFfY2xlYW5bd2hpY2goaXMubmEodGVzdF9kYXRhX2NsZWFuJEFnZSkpLF0pDQpgYGANCg0KDQpgYGB7cn0NCnRlc3RfZGF0YV9jbGVhbg0KYGBgDQoNCg0KDQpgYGB7cn0NClJmZ3Vlc3MgPSBkYXRhLmZyYW1lKHRlc3RfZGF0YSRQYXNzZW5nZXJJZCwgcHJlZGljdChyZi5TdXJ2aXZlZCx0ZXN0X2RhdGFfY2xlYW4pKQ0KY29sbmFtZXMoUmZndWVzcykgPC0gYygiUGFzc2VuZ2VySWQiLCJTdXJ2aXZlZCIpDQpSZmd1ZXNzDQpgYGANCg0KYGBge3J9DQp3cml0ZS5jc3YoUmZndWVzcywgInJmZ3Vlc3MuY3N2Iixyb3cubmFtZXMgPSBGQUxTRSkNCmBgYA0KDQpGaW5hbCBzY29yZTogMC43Nzc1MSwgbm90IGdyZWF0IGJ1dCBJJ20gaW4gdGhlIHRvcCBxdWFydGVyIGFuZCBpbXByb3ZlZCBvdmVyIHRoZSBnZW5kZXIgZ3Vlc3NlcyBhdCBsZWFzdCEgDQoNCmBgYHtyfQ0KYmFnZ2VkLlN1cnZpdmVkID0gcmFuZG9tRm9yZXN0KFN1cnZpdmVkfi4sIGRhdGEgPSB0cmFpbixpbXBvcnRhbmNlID1UUlVFLCBtdHJ5ID0gNikNCnloYXQuU3Vydml2ZWQgPSBwcmVkaWN0KGJhZ2dlZC5TdXJ2aXZlZCxuZXdkYXRhPXRlc3QsIHR5cGU9InJlc3BvbnNlIikNCmNvbmZ1c2lvbl9tYXRyaXggPSB0YWJsZSh0ZXN0JFN1cnZpdmVkLHloYXQuU3Vydml2ZWQpDQpjb25mdXNpb25fbWF0cml4DQpgYGANCmBgYHtyfQ0KMTEwLzE4OA0KYGBgDQphIGxvdCB3b3JzZQ0KYGBge3J9DQpCYWdnZWRndWVzcyA9IGRhdGEuZnJhbWUodGVzdF9kYXRhJFBhc3NlbmdlcklkLCBwcmVkaWN0KGJhZ2dlZC5TdXJ2aXZlZCx0ZXN0X2RhdGFfY2xlYW4pKQ0KY29sbmFtZXMoQmFnZ2VkZ3Vlc3MpIDwtIGMoIlBhc3NlbmdlcklkIiwiU3Vydml2ZWQiKQ0Kd3JpdGUuY3N2KEJhZ2dlZGd1ZXNzLCAiYmFndWVzcy5jc3YiLHJvdy5uYW1lcyA9IEZBTFNFKQ0KYGBgDQoNCg0KYGBge3J9DQpib29zdC5TdXJ2aXZlZCA9IGdibShhcy5jaGFyYWN0ZXIoU3Vydml2ZWQpfi4sIGRhdGEgPSB0cmFpbiwgZGlzdHJpYnV0aW9uPSJiZXJub3VsbGkiLCBuLnRyZWUgPSA1MDAwKQ0KYmVzdC5pdGVyIDwtIGdibS5wZXJmKGJvb3N0LlN1cnZpdmVkLCBtZXRob2QgPSAiT09CIiwgcGxvdCA9IEZBTFNFKQ0KYmVzdC5pdGVyDQpgYGANCmBgYHtyfQ0KcHJvYiA9IHByZWRpY3QoYm9vc3QuU3Vydml2ZWQsIG5ld2RhdGE9dGVzdCwgYmVzdC5pdGVyLHR5cGU9InJlc3BvbnNlIikNCnloYXQuU3Vydml2ZWQgPSBpZmVsc2UocHJvYj4uNSwgMSwgMCkNCmNvbmZ1c2lvbl9tYXRyaXggPSB0YWJsZSh0ZXN0JFN1cnZpdmVkLHloYXQuU3Vydml2ZWQpDQpjb25mdXNpb25fbWF0cml4DQpgYGANCmBgYHtyfQ0KMTQwLzE3OA0KYGBgDQpTYW1lIGFzIFJGDQoNCmBgYHtyfQ0KYm9vc3RlZGd1ZXNzID0gZGF0YS5mcmFtZSh0ZXN0X2RhdGEkUGFzc2VuZ2VySWQsIHByZWRpY3QoYm9vc3QuU3Vydml2ZWQsdGVzdF9kYXRhX2NsZWFuKSkNCmNvbG5hbWVzKGJvb3N0ZWRndWVzcykgPC0gYygiUGFzc2VuZ2VySWQiLCJTdXJ2aXZlZCIpDQp3cml0ZS5jc3YoYm9vc3RlZGd1ZXNzLCAiYm9ndWVzcy5jc3YiLHJvdy5uYW1lcyA9IEZBTFNFKQ0KYGBgDQoNCg0KDQoNCg0K