This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Ctrl+Shift+Enter.

Add a new chunk by clicking the Insert Chunk button on the toolbar or by pressing Ctrl+Alt+I.

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).

The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.

Chapter 3: Classification using Nearest Neighbors

This is an R Notebook with the code from Machine Learning with R, Lantz.

Example: Classifying Cancer Samples

Step 2: Exploring and preparing the data

import the CSV file

wbcd <- read.csv("wisc_bc_data.csv", stringsAsFactors = FALSE)
wbcd

examine the structure of the wbcd data frame

str(wbcd)
'data.frame':   569 obs. of  32 variables:
 $ id               : int  87139402 8910251 905520 868871 9012568 906539 925291 87880 862989 89827 ...
 $ diagnosis        : chr  "B" "B" "B" "B" ...
 $ radius_mean      : num  12.3 10.6 11 11.3 15.2 ...
 $ texture_mean     : num  12.4 18.9 16.8 13.4 13.2 ...
 $ perimeter_mean   : num  78.8 69.3 70.9 73 97.7 ...
 $ area_mean        : num  464 346 373 385 712 ...
 $ smoothness_mean  : num  0.1028 0.0969 0.1077 0.1164 0.0796 ...
 $ compactness_mean : num  0.0698 0.1147 0.078 0.1136 0.0693 ...
 $ concavity_mean   : num  0.0399 0.0639 0.0305 0.0464 0.0339 ...
 $ points_mean      : num  0.037 0.0264 0.0248 0.048 0.0266 ...
 $ symmetry_mean    : num  0.196 0.192 0.171 0.177 0.172 ...
 $ dimension_mean   : num  0.0595 0.0649 0.0634 0.0607 0.0554 ...
 $ radius_se        : num  0.236 0.451 0.197 0.338 0.178 ...
 $ texture_se       : num  0.666 1.197 1.387 1.343 0.412 ...
 $ perimeter_se     : num  1.67 3.43 1.34 1.85 1.34 ...
 $ area_se          : num  17.4 27.1 13.5 26.3 17.7 ...
 $ smoothness_se    : num  0.00805 0.00747 0.00516 0.01127 0.00501 ...
 $ compactness_se   : num  0.0118 0.03581 0.00936 0.03498 0.01485 ...
 $ concavity_se     : num  0.0168 0.0335 0.0106 0.0219 0.0155 ...
 $ points_se        : num  0.01241 0.01365 0.00748 0.01965 0.00915 ...
 $ symmetry_se      : num  0.0192 0.035 0.0172 0.0158 0.0165 ...
 $ dimension_se     : num  0.00225 0.00332 0.0022 0.00344 0.00177 ...
 $ radius_worst     : num  13.5 11.9 12.4 11.9 16.2 ...
 $ texture_worst    : num  15.6 22.9 26.4 15.8 15.7 ...
 $ perimeter_worst  : num  87 78.3 79.9 76.5 104.5 ...
 $ area_worst       : num  549 425 471 434 819 ...
 $ smoothness_worst : num  0.139 0.121 0.137 0.137 0.113 ...
 $ compactness_worst: num  0.127 0.252 0.148 0.182 0.174 ...
 $ concavity_worst  : num  0.1242 0.1916 0.1067 0.0867 0.1362 ...
 $ points_worst     : num  0.0939 0.0793 0.0743 0.0861 0.0818 ...
 $ symmetry_worst   : num  0.283 0.294 0.3 0.21 0.249 ...
 $ dimension_worst  : num  0.0677 0.0759 0.0788 0.0678 0.0677 ...

drop the id feature

wbcd <- wbcd[-1]

table of diagnosis

table(wbcd$diagnosis)

  B   M 
357 212 

recode diagnosis as a factor and table or proportions with more informative labels

wbcd$diagnosis <- factor(wbcd$diagnosis, levels = c("B", "M"),
                         labels = c("Benign", "Malignant"))
round(prop.table(table(wbcd$diagnosis)) * 100, digits = 1)

   Benign Malignant 
     62.7      37.3 

summarize three numeric features

summary(wbcd[c("radius_mean", "area_mean", "smoothness_mean")])
  radius_mean       area_mean      smoothness_mean  
 Min.   : 6.981   Min.   : 143.5   Min.   :0.05263  
 1st Qu.:11.700   1st Qu.: 420.3   1st Qu.:0.08637  
 Median :13.370   Median : 551.1   Median :0.09587  
 Mean   :14.127   Mean   : 654.9   Mean   :0.09636  
 3rd Qu.:15.780   3rd Qu.: 782.7   3rd Qu.:0.10530  
 Max.   :28.110   Max.   :2501.0   Max.   :0.16340  

create normalization function and test normalization function - result should be identical

normalize <- function(x) {
  return ((x - min(x)) / (max(x) - min(x)))
}
normalize(c(1, 2, 3, 4, 5))
[1] 0.00 0.25 0.50 0.75 1.00
normalize(c(10, 20, 30, 40, 50))
[1] 0.00 0.25 0.50 0.75 1.00

normalize the wbcd data and confirm that normalization worked

wbcd_n <- as.data.frame(lapply(wbcd[2:31], normalize))
summary(wbcd_n$area_mean)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.0000  0.1174  0.1729  0.2169  0.2711  1.0000 

create training and test data

create labels for training and test data

wbcd_train <- wbcd_n[1:469, ]
wbcd_test <- wbcd_n[470:569, ]
wbcd_train_labels <- wbcd[1:469, 1]
wbcd_test_labels <- wbcd[470:569, 1]

visualize the data using labels

plot(wbcd$radius_mean,wbcd$texture_mean, 
     main = 'Scatterplot',
     xlab = 'randius mean',
     ylab = 'texture mean')

pairs(~radius_mean+texture_mean+perimeter_mean+area_mean+smoothness_mean, 
      data = wbcd,
      main = 'Scaterplot of many variables')

library(car)
scatterplot(texture_mean ~ radius_mean | diagnosis, data = wbcd,
     main = 'Scatterplot',
     xlab = 'randius mean',
     ylab = 'texture mean')

scatterplotMatrix(~radius_mean+texture_mean+perimeter_mean+area_mean+smoothness_mean | diagnosis, data=wbcd)

Step 3: Training a model on the data

library(class)
wbcd_test_pred <- knn(train = wbcd_train, test = wbcd_test,
                      cl = wbcd_train_labels, k = 21)
head(wbcd_test)
head(wbcd_test_pred)
[1] Benign    Benign    Benign    Benign    Malignant Benign   
Levels: Benign Malignant

Step 4: Evaluating model performance

Create the cross tabulation of predicted vs. actual

library(gmodels)
CrossTable(x = wbcd_test_labels, y = wbcd_test_pred,
           prop.chisq = FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  100 

 
                 | wbcd_test_pred 
wbcd_test_labels |    Benign | Malignant | Row Total | 
-----------------|-----------|-----------|-----------|
          Benign |        61 |         0 |        61 | 
                 |     1.000 |     0.000 |     0.610 | 
                 |     0.968 |     0.000 |           | 
                 |     0.610 |     0.000 |           | 
-----------------|-----------|-----------|-----------|
       Malignant |         2 |        37 |        39 | 
                 |     0.051 |     0.949 |     0.390 | 
                 |     0.032 |     1.000 |           | 
                 |     0.020 |     0.370 |           | 
-----------------|-----------|-----------|-----------|
    Column Total |        63 |        37 |       100 | 
                 |     0.630 |     0.370 |           | 
-----------------|-----------|-----------|-----------|

 

Step 5: Improving model performance

use the scale() function to z-score standardize a data frame and confirm that the transformation was applied correctly

wbcd_z <- as.data.frame(scale(wbcd[-1]))
summary(wbcd_z$area_mean)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-1.4532 -0.6666 -0.2949  0.0000  0.3632  5.2459 

create training and test datasets

wbcd_train <- wbcd_z[1:469, ]
wbcd_test <- wbcd_z[470:569, ]

re-classify test cases

wbcd_test_pred <- knn(train = wbcd_train, test = wbcd_test,
                      cl = wbcd_train_labels, k = 21)
head(wbcd_test)
head(wbcd_test_pred)
[1] Benign    Benign    Benign    Benign    Malignant Benign   
Levels: Benign Malignant

Create the cross tabulation of predicted vs. actual

CrossTable(x = wbcd_test_labels, y = wbcd_test_pred,
           prop.chisq = FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  100 

 
                 | wbcd_test_pred 
wbcd_test_labels |    Benign | Malignant | Row Total | 
-----------------|-----------|-----------|-----------|
          Benign |        61 |         0 |        61 | 
                 |     1.000 |     0.000 |     0.610 | 
                 |     0.924 |     0.000 |           | 
                 |     0.610 |     0.000 |           | 
-----------------|-----------|-----------|-----------|
       Malignant |         5 |        34 |        39 | 
                 |     0.128 |     0.872 |     0.390 | 
                 |     0.076 |     1.000 |           | 
                 |     0.050 |     0.340 |           | 
-----------------|-----------|-----------|-----------|
    Column Total |        66 |        34 |       100 | 
                 |     0.660 |     0.340 |           | 
-----------------|-----------|-----------|-----------|

 

try several different values of k

wbcd_train <- wbcd_n[1:469, ]
wbcd_test <- wbcd_n[470:569, ]
#start time
strt<-Sys.time()
wbcd_test_pred <- knn(train = wbcd_train, test = wbcd_test, cl = wbcd_train_labels, k=1)
CrossTable(x = wbcd_test_labels, y = wbcd_test_pred, prop.chisq=FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  100 

 
                 | wbcd_test_pred 
wbcd_test_labels |    Benign | Malignant | Row Total | 
-----------------|-----------|-----------|-----------|
          Benign |        58 |         3 |        61 | 
                 |     0.951 |     0.049 |     0.610 | 
                 |     0.983 |     0.073 |           | 
                 |     0.580 |     0.030 |           | 
-----------------|-----------|-----------|-----------|
       Malignant |         1 |        38 |        39 | 
                 |     0.026 |     0.974 |     0.390 | 
                 |     0.017 |     0.927 |           | 
                 |     0.010 |     0.380 |           | 
-----------------|-----------|-----------|-----------|
    Column Total |        59 |        41 |       100 | 
                 |     0.590 |     0.410 |           | 
-----------------|-----------|-----------|-----------|

 
wbcd_test_pred <- knn(train = wbcd_train, test = wbcd_test, cl = wbcd_train_labels, k=5)
CrossTable(x = wbcd_test_labels, y = wbcd_test_pred, prop.chisq=FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  100 

 
                 | wbcd_test_pred 
wbcd_test_labels |    Benign | Malignant | Row Total | 
-----------------|-----------|-----------|-----------|
          Benign |        61 |         0 |        61 | 
                 |     1.000 |     0.000 |     0.610 | 
                 |     0.968 |     0.000 |           | 
                 |     0.610 |     0.000 |           | 
-----------------|-----------|-----------|-----------|
       Malignant |         2 |        37 |        39 | 
                 |     0.051 |     0.949 |     0.390 | 
                 |     0.032 |     1.000 |           | 
                 |     0.020 |     0.370 |           | 
-----------------|-----------|-----------|-----------|
    Column Total |        63 |        37 |       100 | 
                 |     0.630 |     0.370 |           | 
-----------------|-----------|-----------|-----------|

 
wbcd_test_pred <- knn(train = wbcd_train, test = wbcd_test, cl = wbcd_train_labels, k=11)
CrossTable(x = wbcd_test_labels, y = wbcd_test_pred, prop.chisq=FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  100 

 
                 | wbcd_test_pred 
wbcd_test_labels |    Benign | Malignant | Row Total | 
-----------------|-----------|-----------|-----------|
          Benign |        61 |         0 |        61 | 
                 |     1.000 |     0.000 |     0.610 | 
                 |     0.953 |     0.000 |           | 
                 |     0.610 |     0.000 |           | 
-----------------|-----------|-----------|-----------|
       Malignant |         3 |        36 |        39 | 
                 |     0.077 |     0.923 |     0.390 | 
                 |     0.047 |     1.000 |           | 
                 |     0.030 |     0.360 |           | 
-----------------|-----------|-----------|-----------|
    Column Total |        64 |        36 |       100 | 
                 |     0.640 |     0.360 |           | 
-----------------|-----------|-----------|-----------|

 
wbcd_test_pred <- knn(train = wbcd_train, test = wbcd_test, cl = wbcd_train_labels, k=15)
CrossTable(x = wbcd_test_labels, y = wbcd_test_pred, prop.chisq=FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  100 

 
                 | wbcd_test_pred 
wbcd_test_labels |    Benign | Malignant | Row Total | 
-----------------|-----------|-----------|-----------|
          Benign |        61 |         0 |        61 | 
                 |     1.000 |     0.000 |     0.610 | 
                 |     0.953 |     0.000 |           | 
                 |     0.610 |     0.000 |           | 
-----------------|-----------|-----------|-----------|
       Malignant |         3 |        36 |        39 | 
                 |     0.077 |     0.923 |     0.390 | 
                 |     0.047 |     1.000 |           | 
                 |     0.030 |     0.360 |           | 
-----------------|-----------|-----------|-----------|
    Column Total |        64 |        36 |       100 | 
                 |     0.640 |     0.360 |           | 
-----------------|-----------|-----------|-----------|

 
wbcd_test_pred <- knn(train = wbcd_train, test = wbcd_test, cl = wbcd_train_labels, k=21)
CrossTable(x = wbcd_test_labels, y = wbcd_test_pred, prop.chisq=FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  100 

 
                 | wbcd_test_pred 
wbcd_test_labels |    Benign | Malignant | Row Total | 
-----------------|-----------|-----------|-----------|
          Benign |        61 |         0 |        61 | 
                 |     1.000 |     0.000 |     0.610 | 
                 |     0.968 |     0.000 |           | 
                 |     0.610 |     0.000 |           | 
-----------------|-----------|-----------|-----------|
       Malignant |         2 |        37 |        39 | 
                 |     0.051 |     0.949 |     0.390 | 
                 |     0.032 |     1.000 |           | 
                 |     0.020 |     0.370 |           | 
-----------------|-----------|-----------|-----------|
    Column Total |        63 |        37 |       100 | 
                 |     0.630 |     0.370 |           | 
-----------------|-----------|-----------|-----------|

 
wbcd_test_pred <- knn(train = wbcd_train, test = wbcd_test, cl = wbcd_train_labels, k=27)
CrossTable(x = wbcd_test_labels, y = wbcd_test_pred, prop.chisq=FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  100 

 
                 | wbcd_test_pred 
wbcd_test_labels |    Benign | Malignant | Row Total | 
-----------------|-----------|-----------|-----------|
          Benign |        61 |         0 |        61 | 
                 |     1.000 |     0.000 |     0.610 | 
                 |     0.938 |     0.000 |           | 
                 |     0.610 |     0.000 |           | 
-----------------|-----------|-----------|-----------|
       Malignant |         4 |        35 |        39 | 
                 |     0.103 |     0.897 |     0.390 | 
                 |     0.062 |     1.000 |           | 
                 |     0.040 |     0.350 |           | 
-----------------|-----------|-----------|-----------|
    Column Total |        65 |        35 |       100 | 
                 |     0.650 |     0.350 |           | 
-----------------|-----------|-----------|-----------|

 
#end time
print(Sys.time()-strt)
Time difference of 0.3308871 secs
LS0tCnRpdGxlOiAiQ2hhcHRlciAzIFIgTm90ZWJvb2siCm91dHB1dDoKICB3b3JkX2RvY3VtZW50OiBkZWZhdWx0CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAotLS0KClRoaXMgaXMgYW4gW1IgTWFya2Rvd25dKGh0dHA6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20pIE5vdGVib29rLiBXaGVuIHlvdSBleGVjdXRlIGNvZGUgd2l0aGluIHRoZSBub3RlYm9vaywgdGhlIHJlc3VsdHMgYXBwZWFyIGJlbmVhdGggdGhlIGNvZGUuIAoKVHJ5IGV4ZWN1dGluZyB0aGlzIGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqUnVuKiBidXR0b24gd2l0aGluIHRoZSBjaHVuayBvciBieSBwbGFjaW5nIHlvdXIgY3Vyc29yIGluc2lkZSBpdCBhbmQgcHJlc3NpbmcgKkN0cmwrU2hpZnQrRW50ZXIqLiAKCkFkZCBhIG5ldyBjaHVuayBieSBjbGlja2luZyB0aGUgKkluc2VydCBDaHVuayogYnV0dG9uIG9uIHRoZSB0b29sYmFyIG9yIGJ5IHByZXNzaW5nICpDdHJsK0FsdCtJKi4KCldoZW4geW91IHNhdmUgdGhlIG5vdGVib29rLCBhbiBIVE1MIGZpbGUgY29udGFpbmluZyB0aGUgY29kZSBhbmQgb3V0cHV0IHdpbGwgYmUgc2F2ZWQgYWxvbmdzaWRlIGl0IChjbGljayB0aGUgKlByZXZpZXcqIGJ1dHRvbiBvciBwcmVzcyAqQ3RybCtTaGlmdCtLKiB0byBwcmV2aWV3IHRoZSBIVE1MIGZpbGUpLgoKVGhlIHByZXZpZXcgc2hvd3MgeW91IGEgcmVuZGVyZWQgSFRNTCBjb3B5IG9mIHRoZSBjb250ZW50cyBvZiB0aGUgZWRpdG9yLiBDb25zZXF1ZW50bHksIHVubGlrZSAqS25pdCosICpQcmV2aWV3KiBkb2VzIG5vdCBydW4gYW55IFIgY29kZSBjaHVua3MuIEluc3RlYWQsIHRoZSBvdXRwdXQgb2YgdGhlIGNodW5rIHdoZW4gaXQgd2FzIGxhc3QgcnVuIGluIHRoZSBlZGl0b3IgaXMgZGlzcGxheWVkLgoKCiMgQ2hhcHRlciAzOiBDbGFzc2lmaWNhdGlvbiB1c2luZyBOZWFyZXN0IE5laWdoYm9ycwoKVGhpcyBpcyBhbiBSIE5vdGVib29rIHdpdGggdGhlIGNvZGUgZnJvbSBNYWNoaW5lIExlYXJuaW5nIHdpdGggUiwgTGFudHouCgojIyBFeGFtcGxlOiBDbGFzc2lmeWluZyBDYW5jZXIgU2FtcGxlcwoKIyMgU3RlcCAyOiBFeHBsb3JpbmcgYW5kIHByZXBhcmluZyB0aGUgZGF0YQoKaW1wb3J0IHRoZSBDU1YgZmlsZQoKCmBgYHtyfQp3YmNkIDwtIHJlYWQuY3N2KCJ3aXNjX2JjX2RhdGEuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQp3YmNkCmBgYAoKZXhhbWluZSB0aGUgc3RydWN0dXJlIG9mIHRoZSB3YmNkIGRhdGEgZnJhbWUKCmBgYHtyfQpzdHIod2JjZCkKYGBgCgpkcm9wIHRoZSBpZCBmZWF0dXJlCgpgYGB7cn0Kd2JjZCA8LSB3YmNkWy0xXQpgYGAKCnRhYmxlIG9mIGRpYWdub3NpcwoKYGBge3J9CnRhYmxlKHdiY2QkZGlhZ25vc2lzKQpgYGAKCnJlY29kZSBkaWFnbm9zaXMgYXMgYSBmYWN0b3IgYW5kIHRhYmxlIG9yIHByb3BvcnRpb25zIHdpdGggbW9yZSBpbmZvcm1hdGl2ZSBsYWJlbHMKCmBgYHtyfQp3YmNkJGRpYWdub3NpcyA8LSBmYWN0b3Iod2JjZCRkaWFnbm9zaXMsIGxldmVscyA9IGMoIkIiLCAiTSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiQmVuaWduIiwgIk1hbGlnbmFudCIpKQoKcm91bmQocHJvcC50YWJsZSh0YWJsZSh3YmNkJGRpYWdub3NpcykpICogMTAwLCBkaWdpdHMgPSAxKQpgYGAKCnN1bW1hcml6ZSB0aHJlZSBudW1lcmljIGZlYXR1cmVzCgpgYGB7cn0Kc3VtbWFyeSh3YmNkW2MoInJhZGl1c19tZWFuIiwgImFyZWFfbWVhbiIsICJzbW9vdGhuZXNzX21lYW4iKV0pCmBgYAoKY3JlYXRlIG5vcm1hbGl6YXRpb24gZnVuY3Rpb24gYW5kIHRlc3Qgbm9ybWFsaXphdGlvbiBmdW5jdGlvbiAtIHJlc3VsdCBzaG91bGQgYmUgaWRlbnRpY2FsCgpgYGB7cn0Kbm9ybWFsaXplIDwtIGZ1bmN0aW9uKHgpIHsKICByZXR1cm4gKCh4IC0gbWluKHgpKSAvIChtYXgoeCkgLSBtaW4oeCkpKQp9Cgpub3JtYWxpemUoYygxLCAyLCAzLCA0LCA1KSkKbm9ybWFsaXplKGMoMTAsIDIwLCAzMCwgNDAsIDUwKSkKCmBgYAoKbm9ybWFsaXplIHRoZSB3YmNkIGRhdGEgYW5kIGNvbmZpcm0gdGhhdCBub3JtYWxpemF0aW9uIHdvcmtlZAoKYGBge3J9CndiY2RfbiA8LSBhcy5kYXRhLmZyYW1lKGxhcHBseSh3YmNkWzI6MzFdLCBub3JtYWxpemUpKQoKc3VtbWFyeSh3YmNkX24kYXJlYV9tZWFuKQpgYGAKCmNyZWF0ZSB0cmFpbmluZyBhbmQgdGVzdCBkYXRhCgpjcmVhdGUgbGFiZWxzIGZvciB0cmFpbmluZyBhbmQgdGVzdCBkYXRhCgpgYGB7cn0Kd2JjZF90cmFpbiA8LSB3YmNkX25bMTo0NjksIF0Kd2JjZF90ZXN0IDwtIHdiY2Rfbls0NzA6NTY5LCBdCgp3YmNkX3RyYWluX2xhYmVscyA8LSB3YmNkWzE6NDY5LCAxXQp3YmNkX3Rlc3RfbGFiZWxzIDwtIHdiY2RbNDcwOjU2OSwgMV0KYGBgCgp2aXN1YWxpemUgdGhlIGRhdGEgdXNpbmcgbGFiZWxzCgpgYGB7cn0KcGxvdCh3YmNkJHJhZGl1c19tZWFuLHdiY2QkdGV4dHVyZV9tZWFuLCAKICAgICBtYWluID0gJ1NjYXR0ZXJwbG90JywKICAgICB4bGFiID0gJ3JhbmRpdXMgbWVhbicsCiAgICAgeWxhYiA9ICd0ZXh0dXJlIG1lYW4nKQoKcGFpcnMofnJhZGl1c19tZWFuK3RleHR1cmVfbWVhbitwZXJpbWV0ZXJfbWVhbithcmVhX21lYW4rc21vb3RobmVzc19tZWFuLCAKICAgICAgZGF0YSA9IHdiY2QsCiAgICAgIG1haW4gPSAnU2NhdGVycGxvdCBvZiBtYW55IHZhcmlhYmxlcycpCmBgYAoKYGBge3J9CmxpYnJhcnkoY2FyKQoKc2NhdHRlcnBsb3QodGV4dHVyZV9tZWFuIH4gcmFkaXVzX21lYW4gfCBkaWFnbm9zaXMsIGRhdGEgPSB3YmNkLAogICAgIG1haW4gPSAnU2NhdHRlcnBsb3QnLAogICAgIHhsYWIgPSAncmFuZGl1cyBtZWFuJywKICAgICB5bGFiID0gJ3RleHR1cmUgbWVhbicpCgpzY2F0dGVycGxvdE1hdHJpeCh+cmFkaXVzX21lYW4rdGV4dHVyZV9tZWFuK3BlcmltZXRlcl9tZWFuK2FyZWFfbWVhbitzbW9vdGhuZXNzX21lYW4gfCBkaWFnbm9zaXMsIGRhdGE9d2JjZCkKCmBgYAoKIyMgU3RlcCAzOiBUcmFpbmluZyBhIG1vZGVsIG9uIHRoZSBkYXRhCgoKYGBge3J9CmxpYnJhcnkoY2xhc3MpCgp3YmNkX3Rlc3RfcHJlZCA8LSBrbm4odHJhaW4gPSB3YmNkX3RyYWluLCB0ZXN0ID0gd2JjZF90ZXN0LAogICAgICAgICAgICAgICAgICAgICAgY2wgPSB3YmNkX3RyYWluX2xhYmVscywgayA9IDIxKQoKaGVhZCh3YmNkX3Rlc3QpCmhlYWQod2JjZF90ZXN0X3ByZWQpCgpgYGAKCiMjIFN0ZXAgNDogRXZhbHVhdGluZyBtb2RlbCBwZXJmb3JtYW5jZQoKQ3JlYXRlIHRoZSBjcm9zcyB0YWJ1bGF0aW9uIG9mIHByZWRpY3RlZCB2cy4gYWN0dWFsCgpgYGB7cn0KbGlicmFyeShnbW9kZWxzKQoKQ3Jvc3NUYWJsZSh4ID0gd2JjZF90ZXN0X2xhYmVscywgeSA9IHdiY2RfdGVzdF9wcmVkLAogICAgICAgICAgIHByb3AuY2hpc3EgPSBGQUxTRSkKYGBgCgojIyBTdGVwIDU6IEltcHJvdmluZyBtb2RlbCBwZXJmb3JtYW5jZQoKdXNlIHRoZSBzY2FsZSgpIGZ1bmN0aW9uIHRvIHotc2NvcmUgc3RhbmRhcmRpemUgYSBkYXRhIGZyYW1lIGFuZCBjb25maXJtIHRoYXQgdGhlIHRyYW5zZm9ybWF0aW9uIHdhcyBhcHBsaWVkIGNvcnJlY3RseQoKYGBge3J9CndiY2RfeiA8LSBhcy5kYXRhLmZyYW1lKHNjYWxlKHdiY2RbLTFdKSkKCnN1bW1hcnkod2JjZF96JGFyZWFfbWVhbikKYGBgCgpjcmVhdGUgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YXNldHMKCmBgYHtyfQp3YmNkX3RyYWluIDwtIHdiY2RfelsxOjQ2OSwgXQp3YmNkX3Rlc3QgPC0gd2JjZF96WzQ3MDo1NjksIF0KYGBgCgpyZS1jbGFzc2lmeSB0ZXN0IGNhc2VzCgpgYGB7cn0Kd2JjZF90ZXN0X3ByZWQgPC0ga25uKHRyYWluID0gd2JjZF90cmFpbiwgdGVzdCA9IHdiY2RfdGVzdCwKICAgICAgICAgICAgICAgICAgICAgIGNsID0gd2JjZF90cmFpbl9sYWJlbHMsIGsgPSAyMSkKCmhlYWQod2JjZF90ZXN0KQpoZWFkKHdiY2RfdGVzdF9wcmVkKQpgYGAKCkNyZWF0ZSB0aGUgY3Jvc3MgdGFidWxhdGlvbiBvZiBwcmVkaWN0ZWQgdnMuIGFjdHVhbAoKYGBge3J9CkNyb3NzVGFibGUoeCA9IHdiY2RfdGVzdF9sYWJlbHMsIHkgPSB3YmNkX3Rlc3RfcHJlZCwKICAgICAgICAgICBwcm9wLmNoaXNxID0gRkFMU0UpCmBgYAoKdHJ5IHNldmVyYWwgZGlmZmVyZW50IHZhbHVlcyBvZiBrCgpgYGB7cn0Kd2JjZF90cmFpbiA8LSB3YmNkX25bMTo0NjksIF0Kd2JjZF90ZXN0IDwtIHdiY2Rfbls0NzA6NTY5LCBdCgojc3RhcnQgdGltZQpzdHJ0PC1TeXMudGltZSgpCgp3YmNkX3Rlc3RfcHJlZCA8LSBrbm4odHJhaW4gPSB3YmNkX3RyYWluLCB0ZXN0ID0gd2JjZF90ZXN0LCBjbCA9IHdiY2RfdHJhaW5fbGFiZWxzLCBrPTEpCkNyb3NzVGFibGUoeCA9IHdiY2RfdGVzdF9sYWJlbHMsIHkgPSB3YmNkX3Rlc3RfcHJlZCwgcHJvcC5jaGlzcT1GQUxTRSkKCndiY2RfdGVzdF9wcmVkIDwtIGtubih0cmFpbiA9IHdiY2RfdHJhaW4sIHRlc3QgPSB3YmNkX3Rlc3QsIGNsID0gd2JjZF90cmFpbl9sYWJlbHMsIGs9NSkKQ3Jvc3NUYWJsZSh4ID0gd2JjZF90ZXN0X2xhYmVscywgeSA9IHdiY2RfdGVzdF9wcmVkLCBwcm9wLmNoaXNxPUZBTFNFKQoKd2JjZF90ZXN0X3ByZWQgPC0ga25uKHRyYWluID0gd2JjZF90cmFpbiwgdGVzdCA9IHdiY2RfdGVzdCwgY2wgPSB3YmNkX3RyYWluX2xhYmVscywgaz0xMSkKQ3Jvc3NUYWJsZSh4ID0gd2JjZF90ZXN0X2xhYmVscywgeSA9IHdiY2RfdGVzdF9wcmVkLCBwcm9wLmNoaXNxPUZBTFNFKQoKd2JjZF90ZXN0X3ByZWQgPC0ga25uKHRyYWluID0gd2JjZF90cmFpbiwgdGVzdCA9IHdiY2RfdGVzdCwgY2wgPSB3YmNkX3RyYWluX2xhYmVscywgaz0xNSkKQ3Jvc3NUYWJsZSh4ID0gd2JjZF90ZXN0X2xhYmVscywgeSA9IHdiY2RfdGVzdF9wcmVkLCBwcm9wLmNoaXNxPUZBTFNFKQoKd2JjZF90ZXN0X3ByZWQgPC0ga25uKHRyYWluID0gd2JjZF90cmFpbiwgdGVzdCA9IHdiY2RfdGVzdCwgY2wgPSB3YmNkX3RyYWluX2xhYmVscywgaz0yMSkKQ3Jvc3NUYWJsZSh4ID0gd2JjZF90ZXN0X2xhYmVscywgeSA9IHdiY2RfdGVzdF9wcmVkLCBwcm9wLmNoaXNxPUZBTFNFKQoKd2JjZF90ZXN0X3ByZWQgPC0ga25uKHRyYWluID0gd2JjZF90cmFpbiwgdGVzdCA9IHdiY2RfdGVzdCwgY2wgPSB3YmNkX3RyYWluX2xhYmVscywgaz0yNykKQ3Jvc3NUYWJsZSh4ID0gd2JjZF90ZXN0X2xhYmVscywgeSA9IHdiY2RfdGVzdF9wcmVkLCBwcm9wLmNoaXNxPUZBTFNFKQoKI2VuZCB0aW1lCnByaW50KFN5cy50aW1lKCktc3RydCkKCmBgYAoK