Four Examples
Next we’ll go through the 4 examples mentioned in chap 1 of ESL. Data
can be found here.
1. Prostate Cancer Data
We divide the samples into training and test data, and fit a linear
regression model on the training set to predict the outcome
lpsa
. Then use the fitted regression model to do prediction
on both the training and test data. The performance is measured by the
mean squared error.
The command predict
takes the fitted regression model
(the output from lm
) and a new dataset as input and returns
the prediction on this new dataset. R “remembers” features/covariates
through their names, not their location in the data matrix. So to form
prediction you need to provide the new dataset as a dataframe with the
same column names (for the features, i.e., x’s) as the dataframe used in
the command lm
.
url = "https://hastie.su.domains/ElemStatLearn/datasets/prostate.data"
prostate = read.table(url)
names(prostate)
## "lpsa" the 2nd last var is the response variable
## "train" is the indicator for training data
prostate$train
table(prostate$train) # 67 training vs 30 testing
## Fit a linear regression model to predict lpsa
traindata = prostate[prostate$train==TRUE,]
testdata = prostate[prostate$train==FALSE,]
## Remove the "train" indicator column
traindata = within(traindata, rm(train))
testdata = within(testdata, rm(train))
myfit = lm(lpsa ~ . , data=traindata)
myfit ## display the estimated LS coefficients
summary(myfit) ## more output
mypredict = predict(myfit, newdata=testdata)
## mean squared error on the training and test sets.
sum((traindata$lpsa - myfit$fitted)^2)/nrow(traindata)
sum((testdata$lpsa - mypredict)^2)/nrow(testdata)
2. Spam Email
The column names for the spam data is not informative, except the
last column. We will assign meaningful column names for the spam data
via file spam_name.txt
.
We will also rename the last column (the response variable) to be
Y
: Y = 1 if spam, and Y = 0 if non-spam.
spam = read.table("https://liangfgithub.github.io/Data/spam.txt")
dim(spam)
names(spam)
spam.name=read.table("https://liangfgithub.github.io/Data/spam_name.txt")
for(i in 1:(ncol(spam)-1)) names(spam)[i]=as.character(spam.name[i,]);
names(spam)[ncol(spam)]="Y"
head(spam) # display the first 5 obs/rows.
spam[1:2,] # display the first 2 obs/rows
attach(spam)
table(Y) # counts for Y=1(spam) and 0 (email)
The distribution of each feature differs between spam an email?
To answer this question, we can compute summary statistics (e.g.,
mean, sd, median) within spam and within email for each feature.
spam.sum = matrix(0, 57, 6)
colnames(spam.sum) = c("spam.mean", "email.mean", "spam.sd", "email.sd", "spam.med", "email.med")
rownames(spam.sum) = colnames(spam)[1:57]
for(i in 1:57){
spam.sum[i,1] = mean(spam[Y==1,i])
spam.sum[i,2] = mean(spam[Y==0,i])
spam.sum[i,3] = sd(spam[Y==1,i])
spam.sum[i,4] = sd(spam[Y==0,i])
spam.sum[i,5] = median(spam[Y==1,i])
spam.sum[i,6] = median(spam[Y==0,i])
}
spam.sum ## too many digits after the decimal point
round(spam.sum, dig=2)
spam.sum[1,]
library(ggplot2)
tmp = as.data.frame(spam[, c(1,58)])
ggplot(tmp, aes(x=Wmake, fill=as.factor(Y))) +
geom_density(alpha=0.2)
Next we use linear regression to classy emails as spam or non-spam:
if the prediction is bigger than 0.5, classify this email as 1, and 0
otherwise.
## Extract a subset from the training data to form a test set.
## If you want to make the sampling step reproducible,
## use set.seed function.
set.seed(150)
n=nrow(spam)
test.id=rep(0,n);
test.id[sample(1:n, 500)]=1;
## I would ignore the p-values/t-stat/F-stat since
## the response Y apparently doesn't follow a normal dist
mylm=lm(Y~., data=spam[test.id !=1,]);
summary(mylm)
pred.train=ifelse(mylm$fitted>0.5,1,0)
sum(pred.train == Y[test.id==0])
train.err = 1- sum(pred.train == Y[test.id==0])/(n-500)
train.err
pred.test=predict(mylm, spam[test.id==1,]);
pred.test=ifelse(pred.test>0.5,1,0)
test.err = 1- sum(pred.test == Y[test.id==1])/500
test.err
3. Handwritten Digits
zip.train = read.table("https://liangfgithub.github.io/Data/zip.train")
zip.train = as.matrix(zip.train)
dim(zip.train)
## The first column indicate the digit
table(zip.train[,1])
Show a random sample of 12 images (arranged in a 3x4 table).
n=nrow(zip.train)
par(mfrow=c(3,4), pty='s', mar=c(1,1,1,1), xaxt='n', yaxt='n')
for(i in 1:12){
tmp=zip.train[sample(1:n, 1), -1];
tmp = tmp/max(tmp)
tmp=matrix(tmp, 16, 16)
image(tmp[, 16:1])
}
Make the images black-and-white. Define custom color using
RColorBrewer1
.
install.packages("RColorBrewer")
library(RColorBrewer)
##Color ramp def.
colors = c('white','black')
cus_col = colorRampPalette(colors=colors)
par(mfrow=c(3,4), pty='s', mar=c(1,1,1,1), xaxt='n', yaxt='n')
for(i in 1:12){
tmp=zip.train[sample(1:n, 1), -1];
tmp = tmp/max(tmp)*255
tmp=matrix(tmp, 16, 16)
image(tmp[, 16:1], col=cus_col(256))
}
Show the averaged image for each digit.
par(mfrow=c(3,4), pty='s', mar=c(1,1,1,1), xaxt='n', yaxt='n')
for(i in 0:9){
tmp=zip.train[zip.train[,1]==i, -1];
## calculate the average image and rescale the value in each
## pixel to be between 0 to 255
tmp=apply(tmp, 2, mean)
tmp = tmp/max(tmp)*255
tmp=matrix(tmp, 16, 16)
image(tmp[, 16:1], col=cus_col(256))
}
Try “k-nearest-neighbor” in the library class
to
classify images in the test set.
library(class)
?knn ## check the help file for command "knn"
zip.test = read.table("https://liangfgithub.github.io/Data/zip.test")
zip.test = as.matrix(zip.test)
## For knn, no need to spend extra time on trianing.
## Training is done on the fly when a test point is given
## The command below may take some time to run
pred=knn(zip.train[,-1], zip.test[,-1], zip.train[,1])
## display the classification result in a 10x10 table known as the confusion table
table(zip.test[,1], pred)
## Compute the 0/1 test error
mytable = table(zip.test[,1], pred)
1-sum(diag(mytable))/nrow(zip.test)
4. NCI Microarray Data
http://genome-www.stanford.edu/nci60/
url = "https://hastie.su.domains/ElemStatLearn/datasets/nci.data.csv"
nci = read.table(url, sep=",",row.names=1,header=TRUE)
dim(nci)
## Create a custom color palette from green to red
cus_col = colorRampPalette(colors=c("green", "black", "red"))
## Show just the first 500 genes
image(as.matrix(nci[1:500,]), col=cus_col(64))
LS0tCnRpdGxlOiAiKFBTTCkgV2VlayAxOiBFeGFtcGxlcyBmcm9tIEVTTCIKZGF0ZTogIkZhbGwgMjAyMyIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0aGVtZTogcmVhZGFibGUKICAgIHRvYzogVFJVRQogICAgdG9jX2Zsb2F0OiBUUlVFCi0tLQoKIyMgR2V0IFN0YXJ0ZWQgd2l0aCBSCgoqIEluc3RhbGwgW1JdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnLykgYW5kIFtSU3R1ZGlvXShodHRwczovL3d3dy5yc3R1ZGlvLmNvbS8pIG9uIHlvdXIgY29tcHV0ZXIuCiogQ3JlYXRlIGEgZGlyZWN0b3J5IGZvciBhbGwgeW91ciBSIHdvcmsgZm9yIFBTTC4gSW4gdGhlIGZ1dHVyZSwgeW91IGNhbiBzYXZlIGFsbCBQU0wtcmVsYXRlZCBSIHNjcmlwdC9kYXRhIGluIHRoaXMgZm9sZGVyCiogT3BlbiBSU3R1ZGlvLCB1c2UgdGhlIGNvbW1hbmQgYHNldHdkYCB0byBlbnRlciB0aGlzIGRpcmVjdG9yeSwgZS5nLiwgSSB0eXBlIHRoZSBmb2xsb3dpbmc6CgpgYGB7ciwgZXZhbD1GQUxTRX0Kc2V0d2QoIn4vbXlkb2MvQ291cnNlL1BTTC9SY29kZS8iKQpgYGAKCiMjIEZvdXIgRXhhbXBsZXMKCk5leHQgd2UnbGwgZ28gdGhyb3VnaCB0aGUgNCBleGFtcGxlcyBtZW50aW9uZWQgaW4gY2hhcCAxIG9mIFtFU0xdKGh0dHA6Ly93d3ctc3RhdC5zdGFuZm9yZC5lZHUvfnRpYnMvRWxlbVN0YXRMZWFybi8pLiBEYXRhIGNhbiBiZSBmb3VuZCBbaGVyZV0oaHR0cHM6Ly9oYXN0aWUuc3UuZG9tYWlucy9FbGVtU3RhdExlYXJuL2RhdGEuaHRtbCkuIAoKIyMjIDEuIFByb3N0YXRlIENhbmNlciBEYXRhCgoKV2UgZGl2aWRlIHRoZSBzYW1wbGVzIGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSwgYW5kIGZpdCBhIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIG9uIHRoZSB0cmFpbmluZyBzZXQgdG8gcHJlZGljdCB0aGUgb3V0Y29tZSBgbHBzYWAuIFRoZW4gdXNlIHRoZSBmaXR0ZWQgcmVncmVzc2lvbiBtb2RlbCB0byBkbyBwcmVkaWN0aW9uIG9uIGJvdGggdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEuIFRoZSBwZXJmb3JtYW5jZSBpcyBtZWFzdXJlZCBieSB0aGUgbWVhbiBzcXVhcmVkIGVycm9yLiAKClRoZSBjb21tYW5kIGBwcmVkaWN0YCB0YWtlcyB0aGUgZml0dGVkIHJlZ3Jlc3Npb24gbW9kZWwgKHRoZSBvdXRwdXQgZnJvbSBgbG1gKSBhbmQgYSBuZXcgZGF0YXNldCBhcyBpbnB1dCBhbmQgcmV0dXJucyB0aGUgcHJlZGljdGlvbiBvbiB0aGlzIG5ldyBkYXRhc2V0LiBSICJyZW1lbWJlcnMiICBmZWF0dXJlcy9jb3ZhcmlhdGVzIHRocm91Z2ggdGhlaXIgbmFtZXMsIG5vdCB0aGVpciBsb2NhdGlvbiBpbiB0aGUgZGF0YSBtYXRyaXguIFNvIHRvIGZvcm0gcHJlZGljdGlvbiB5b3UgbmVlZCB0byBwcm92aWRlIHRoZSBuZXcgZGF0YXNldCBhcyBhIGRhdGFmcmFtZSB3aXRoIHRoZSBzYW1lIGNvbHVtbiBuYW1lcyAoZm9yIHRoZSBmZWF0dXJlcywgaS5lLiwgeCdzKSBhcyB0aGUgZGF0YWZyYW1lIHVzZWQgaW4gdGhlIGNvbW1hbmQgYGxtYC4KCmBgYHtyLCBldmFsPUZBTFNFfQp1cmwgPSAiaHR0cHM6Ly9oYXN0aWUuc3UuZG9tYWlucy9FbGVtU3RhdExlYXJuL2RhdGFzZXRzL3Byb3N0YXRlLmRhdGEiCnByb3N0YXRlID0gcmVhZC50YWJsZSh1cmwpCm5hbWVzKHByb3N0YXRlKSAgCiMjICJscHNhIiB0aGUgMm5kIGxhc3QgdmFyIGlzIHRoZSByZXNwb25zZSB2YXJpYWJsZQojIyAidHJhaW4iIGlzIHRoZSBpbmRpY2F0b3IgZm9yIHRyYWluaW5nIGRhdGEKcHJvc3RhdGUkdHJhaW4KdGFibGUocHJvc3RhdGUkdHJhaW4pICAjIDY3IHRyYWluaW5nIHZzIDMwIHRlc3RpbmcKCiMjIEZpdCBhIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIHRvIHByZWRpY3QgbHBzYQp0cmFpbmRhdGEgPSBwcm9zdGF0ZVtwcm9zdGF0ZSR0cmFpbj09VFJVRSxdCnRlc3RkYXRhID0gcHJvc3RhdGVbcHJvc3RhdGUkdHJhaW49PUZBTFNFLF0KCiMjIFJlbW92ZSB0aGUgInRyYWluIiBpbmRpY2F0b3IgY29sdW1uCnRyYWluZGF0YSA9IHdpdGhpbih0cmFpbmRhdGEsIHJtKHRyYWluKSkKdGVzdGRhdGEgPSB3aXRoaW4odGVzdGRhdGEsIHJtKHRyYWluKSkKCm15Zml0ID0gbG0obHBzYSB+IC4gLCBkYXRhPXRyYWluZGF0YSkKbXlmaXQgICMjIGRpc3BsYXkgdGhlIGVzdGltYXRlZCBMUyBjb2VmZmljaWVudHMKc3VtbWFyeShteWZpdCkgICMjIG1vcmUgb3V0cHV0CgpteXByZWRpY3QgPSBwcmVkaWN0KG15Zml0LCBuZXdkYXRhPXRlc3RkYXRhKQoKIyMgbWVhbiBzcXVhcmVkIGVycm9yIG9uIHRoZSB0cmFpbmluZyBhbmQgdGVzdCBzZXRzLiAKc3VtKCh0cmFpbmRhdGEkbHBzYSAtIG15Zml0JGZpdHRlZCleMikvbnJvdyh0cmFpbmRhdGEpICAKc3VtKCh0ZXN0ZGF0YSRscHNhIC0gbXlwcmVkaWN0KV4yKS9ucm93KHRlc3RkYXRhKSAgICAgICAKYGBgCgojIyMgMi4gU3BhbSBFbWFpbAoKClRoZSBjb2x1bW4gbmFtZXMgZm9yIHRoZSBzcGFtIGRhdGEgaXMgbm90IGluZm9ybWF0aXZlLCBleGNlcHQgdGhlIGxhc3QgY29sdW1uLiBXZSB3aWxsIGFzc2lnbiBtZWFuaW5nZnVsIGNvbHVtbiBuYW1lcyBmb3IgdGhlIHNwYW0gZGF0YSB2aWEgZmlsZSBgc3BhbV9uYW1lLnR4dGAuCgpXZSB3aWxsIGFsc28gcmVuYW1lIHRoZSBsYXN0IGNvbHVtbiAgKHRoZSByZXNwb25zZSB2YXJpYWJsZSkgdG8gYmUgYFlgOiAgWSA9IDEgaWYgc3BhbSwgYW5kIFkgPSAwIGlmIG5vbi1zcGFtLiAKCmBgYHtyfQoKc3BhbSA9IHJlYWQudGFibGUoImh0dHBzOi8vbGlhbmdmZ2l0aHViLmdpdGh1Yi5pby9EYXRhL3NwYW0udHh0IikKZGltKHNwYW0pCm5hbWVzKHNwYW0pCgpzcGFtLm5hbWU9cmVhZC50YWJsZSgiaHR0cHM6Ly9saWFuZ2ZnaXRodWIuZ2l0aHViLmlvL0RhdGEvc3BhbV9uYW1lLnR4dCIpCmZvcihpIGluIDE6KG5jb2woc3BhbSktMSkpIG5hbWVzKHNwYW0pW2ldPWFzLmNoYXJhY3RlcihzcGFtLm5hbWVbaSxdKTsKCm5hbWVzKHNwYW0pW25jb2woc3BhbSldPSJZIgoKaGVhZChzcGFtKSAjIGRpc3BsYXkgdGhlIGZpcnN0IDUgb2JzL3Jvd3MuCnNwYW1bMToyLF0gIyBkaXNwbGF5IHRoZSBmaXJzdCAyIG9icy9yb3dzCgphdHRhY2goc3BhbSkKdGFibGUoWSkgICMgY291bnRzIGZvciBZPTEoc3BhbSkgYW5kIDAgKGVtYWlsKQpgYGAKCgo+IFRoZSBkaXN0cmlidXRpb24gb2YgZWFjaCBmZWF0dXJlIGRpZmZlcnMgYmV0d2VlbiBzcGFtIGFuIGVtYWlsPyAKClRvIGFuc3dlciB0aGlzIHF1ZXN0aW9uLCB3ZSBjYW4gY29tcHV0ZSBzdW1tYXJ5IHN0YXRpc3RpY3MgKGUuZy4sIG1lYW4sIHNkLCBtZWRpYW4pIHdpdGhpbiBzcGFtIGFuZCB3aXRoaW4gZW1haWwgZm9yIGVhY2ggZmVhdHVyZS4gCgpgYGB7cn0Kc3BhbS5zdW0gPSBtYXRyaXgoMCwgNTcsIDYpCmNvbG5hbWVzKHNwYW0uc3VtKSA9IGMoInNwYW0ubWVhbiIsICJlbWFpbC5tZWFuIiwgInNwYW0uc2QiLCAiZW1haWwuc2QiLCAic3BhbS5tZWQiLCAiZW1haWwubWVkIikKcm93bmFtZXMoc3BhbS5zdW0pID0gY29sbmFtZXMoc3BhbSlbMTo1N10KCmZvcihpIGluIDE6NTcpewogIHNwYW0uc3VtW2ksMV0gPSBtZWFuKHNwYW1bWT09MSxpXSkKICBzcGFtLnN1bVtpLDJdID0gbWVhbihzcGFtW1k9PTAsaV0pCgogIHNwYW0uc3VtW2ksM10gPSBzZChzcGFtW1k9PTEsaV0pCiAgc3BhbS5zdW1baSw0XSA9IHNkKHNwYW1bWT09MCxpXSkKCiAgc3BhbS5zdW1baSw1XSA9IG1lZGlhbihzcGFtW1k9PTEsaV0pCiAgc3BhbS5zdW1baSw2XSA9IG1lZGlhbihzcGFtW1k9PTAsaV0pICAKfQoKc3BhbS5zdW0gICMjIHRvbyBtYW55IGRpZ2l0cyBhZnRlciB0aGUgZGVjaW1hbCBwb2ludApyb3VuZChzcGFtLnN1bSwgZGlnPTIpCgpzcGFtLnN1bVsxLF0KbGlicmFyeShnZ3Bsb3QyKQp0bXAgPSBhcy5kYXRhLmZyYW1lKHNwYW1bLCBjKDEsNTgpXSkKZ2dwbG90KHRtcCwgYWVzKHg9V21ha2UsIGZpbGw9YXMuZmFjdG9yKFkpKSkgKyAKICBnZW9tX2RlbnNpdHkoYWxwaGE9MC4yKQpgYGAKCk5leHQgd2UgdXNlIGxpbmVhciByZWdyZXNzaW9uIHRvIGNsYXNzeSBlbWFpbHMgYXMgc3BhbSBvciBub24tc3BhbTogaWYgdGhlIHByZWRpY3Rpb24gaXMgYmlnZ2VyIHRoYW4gMC41LCBjbGFzc2lmeSB0aGlzIGVtYWlsIGFzIDEsIGFuZCAwIG90aGVyd2lzZS4gCgpgYGB7cn0KIyMgRXh0cmFjdCBhIHN1YnNldCBmcm9tIHRoZSB0cmFpbmluZyBkYXRhIHRvIGZvcm0gYSB0ZXN0IHNldC4KIyMgSWYgeW91IHdhbnQgdG8gbWFrZSB0aGUgc2FtcGxpbmcgc3RlcCByZXByb2R1Y2libGUsIAojIyB1c2Ugc2V0LnNlZWQgZnVuY3Rpb24uIAoKc2V0LnNlZWQoMTUwKSAgCm49bnJvdyhzcGFtKQp0ZXN0LmlkPXJlcCgwLG4pOyAKdGVzdC5pZFtzYW1wbGUoMTpuLCA1MDApXT0xOwoKIyMgSSB3b3VsZCBpZ25vcmUgdGhlIHAtdmFsdWVzL3Qtc3RhdC9GLXN0YXQgc2luY2UKIyMgdGhlIHJlc3BvbnNlIFkgYXBwYXJlbnRseSBkb2Vzbid0IGZvbGxvdyBhIG5vcm1hbCBkaXN0CgpteWxtPWxtKFl+LiwgZGF0YT1zcGFtW3Rlc3QuaWQgIT0xLF0pOwpzdW1tYXJ5KG15bG0pCgpwcmVkLnRyYWluPWlmZWxzZShteWxtJGZpdHRlZD4wLjUsMSwwKQpzdW0ocHJlZC50cmFpbiA9PSBZW3Rlc3QuaWQ9PTBdKQp0cmFpbi5lcnIgPSAxLSBzdW0ocHJlZC50cmFpbiA9PSBZW3Rlc3QuaWQ9PTBdKS8obi01MDApCnRyYWluLmVycgoKcHJlZC50ZXN0PXByZWRpY3QobXlsbSwgc3BhbVt0ZXN0LmlkPT0xLF0pOwpwcmVkLnRlc3Q9aWZlbHNlKHByZWQudGVzdD4wLjUsMSwwKQp0ZXN0LmVyciA9IDEtIHN1bShwcmVkLnRlc3QgPT0gWVt0ZXN0LmlkPT0xXSkvNTAwCnRlc3QuZXJyCmBgYAoKCiMjIyAzLiBIYW5kd3JpdHRlbiBEaWdpdHMKCgpgYGB7cn0KemlwLnRyYWluID0gcmVhZC50YWJsZSgiaHR0cHM6Ly9saWFuZ2ZnaXRodWIuZ2l0aHViLmlvL0RhdGEvemlwLnRyYWluIikKemlwLnRyYWluID0gYXMubWF0cml4KHppcC50cmFpbikKZGltKHppcC50cmFpbikKCiMjIFRoZSBmaXJzdCBjb2x1bW4gaW5kaWNhdGUgdGhlIGRpZ2l0CnRhYmxlKHppcC50cmFpblssMV0pCmBgYAoKU2hvdyBhIHJhbmRvbSBzYW1wbGUgb2YgMTIgaW1hZ2VzIChhcnJhbmdlZCBpbiBhIDN4NCB0YWJsZSkuCgpgYGB7cn0Kbj1ucm93KHppcC50cmFpbikKcGFyKG1mcm93PWMoMyw0KSwgcHR5PSdzJywgbWFyPWMoMSwxLDEsMSksIHhheHQ9J24nLCB5YXh0PSduJykKZm9yKGkgaW4gMToxMil7CiAgdG1wPXppcC50cmFpbltzYW1wbGUoMTpuLCAxKSwgLTFdOyAKICB0bXAgPSB0bXAvbWF4KHRtcCkKICB0bXA9bWF0cml4KHRtcCwgMTYsIDE2KQogIGltYWdlKHRtcFssIDE2OjFdKQp9CmBgYAoKTWFrZSB0aGUgaW1hZ2VzIGJsYWNrLWFuZC13aGl0ZS4gRGVmaW5lIGN1c3RvbSBjb2xvciB1c2luZyBgUkNvbG9yQnJld2VyMWAuCgpgYGB7cn0KaW5zdGFsbC5wYWNrYWdlcygiUkNvbG9yQnJld2VyIikKbGlicmFyeShSQ29sb3JCcmV3ZXIpCgojI0NvbG9yIHJhbXAgZGVmLgpjb2xvcnMgPSBjKCd3aGl0ZScsJ2JsYWNrJykKY3VzX2NvbCA9IGNvbG9yUmFtcFBhbGV0dGUoY29sb3JzPWNvbG9ycykKCnBhcihtZnJvdz1jKDMsNCksIHB0eT0ncycsIG1hcj1jKDEsMSwxLDEpLCB4YXh0PSduJywgeWF4dD0nbicpCmZvcihpIGluIDE6MTIpewogIHRtcD16aXAudHJhaW5bc2FtcGxlKDE6biwgMSksIC0xXTsgCiAgdG1wID0gdG1wL21heCh0bXApKjI1NQogIHRtcD1tYXRyaXgodG1wLCAxNiwgMTYpCiAgaW1hZ2UodG1wWywgMTY6MV0sIGNvbD1jdXNfY29sKDI1NikpCn0KYGBgCgpTaG93IHRoZSBhdmVyYWdlZCBpbWFnZSBmb3IgZWFjaCBkaWdpdC4gCgpgYGB7cn0KcGFyKG1mcm93PWMoMyw0KSwgcHR5PSdzJywgbWFyPWMoMSwxLDEsMSksIHhheHQ9J24nLCB5YXh0PSduJykKZm9yKGkgaW4gMDo5KXsKICB0bXA9emlwLnRyYWluW3ppcC50cmFpblssMV09PWksIC0xXTsgCiAgCiAgIyMgY2FsY3VsYXRlIHRoZSBhdmVyYWdlIGltYWdlIGFuZCByZXNjYWxlIHRoZSB2YWx1ZSBpbiBlYWNoCiAgIyMgcGl4ZWwgdG8gYmUgYmV0d2VlbiAwIHRvIDI1NQogIHRtcD1hcHBseSh0bXAsIDIsIG1lYW4pCiAgdG1wID0gdG1wL21heCh0bXApKjI1NQogIAogIHRtcD1tYXRyaXgodG1wLCAxNiwgMTYpCiAgaW1hZ2UodG1wWywgMTY6MV0sIGNvbD1jdXNfY29sKDI1NikpCn0KYGBgCgpUcnkgImstbmVhcmVzdC1uZWlnaGJvciIgIGluIHRoZSBsaWJyYXJ5IGBjbGFzc2AgdG8gY2xhc3NpZnkgaW1hZ2VzIGluIHRoZSB0ZXN0IHNldC4gCgpgYGB7cn0KbGlicmFyeShjbGFzcykgIAo/a25uICAgIyMgY2hlY2sgdGhlIGhlbHAgZmlsZSBmb3IgY29tbWFuZCAia25uIgoKemlwLnRlc3QgPSByZWFkLnRhYmxlKCJodHRwczovL2xpYW5nZmdpdGh1Yi5naXRodWIuaW8vRGF0YS96aXAudGVzdCIpCnppcC50ZXN0ID0gYXMubWF0cml4KHppcC50ZXN0KQoKIyMgRm9yIGtubiwgbm8gbmVlZCB0byBzcGVuZCBleHRyYSB0aW1lIG9uIHRyaWFuaW5nLgojIyBUcmFpbmluZyBpcyBkb25lIG9uIHRoZSBmbHkgd2hlbiBhIHRlc3QgcG9pbnQgaXMgZ2l2ZW4KIyMgVGhlIGNvbW1hbmQgYmVsb3cgbWF5IHRha2Ugc29tZSB0aW1lIHRvIHJ1bgpwcmVkPWtubih6aXAudHJhaW5bLC0xXSwgemlwLnRlc3RbLC0xXSwgemlwLnRyYWluWywxXSkgIAoKIyMgZGlzcGxheSB0aGUgY2xhc3NpZmljYXRpb24gcmVzdWx0IGluIGEgMTB4MTAgdGFibGUga25vd24gYXMgdGhlIGNvbmZ1c2lvbiB0YWJsZQp0YWJsZSh6aXAudGVzdFssMV0sIHByZWQpICAKCiMjIENvbXB1dGUgdGhlIDAvMSB0ZXN0IGVycm9yCm15dGFibGUgPSB0YWJsZSh6aXAudGVzdFssMV0sIHByZWQpCjEtc3VtKGRpYWcobXl0YWJsZSkpL25yb3coemlwLnRlc3QpCmBgYAoKCgojIyMgNC4gTkNJIE1pY3JvYXJyYXkgRGF0YQoKCltodHRwOi8vZ2Vub21lLXd3dy5zdGFuZm9yZC5lZHUvbmNpNjAvXShbaHR0cDovL2dlbm9tZS13d3cuc3RhbmZvcmQuZWR1L25jaTYwL10pCgoKYGBge3J9CnVybCA9ICJodHRwczovL2hhc3RpZS5zdS5kb21haW5zL0VsZW1TdGF0TGVhcm4vZGF0YXNldHMvbmNpLmRhdGEuY3N2IgpuY2kgPSByZWFkLnRhYmxlKHVybCwgc2VwPSIsIixyb3cubmFtZXM9MSxoZWFkZXI9VFJVRSkKZGltKG5jaSkKCiMjIENyZWF0ZSBhIGN1c3RvbSBjb2xvciBwYWxldHRlIGZyb20gZ3JlZW4gdG8gcmVkCmN1c19jb2wgPSBjb2xvclJhbXBQYWxldHRlKGNvbG9ycz1jKCJncmVlbiIsICJibGFjayIsICJyZWQiKSkKCiMjIFNob3cganVzdCB0aGUgZmlyc3QgNTAwIGdlbmVzCmltYWdlKGFzLm1hdHJpeChuY2lbMTo1MDAsXSksIGNvbD1jdXNfY29sKDY0KSkKYGBgCg==