Загрузка данных

url <- "https://raw.githubusercontent.com/forvis/Labs-2021/main/Datasets/boston.csv"
data <- read.csv(url)

The Boston data frame has 506 rows and 14 columns (predictors). We have descriptions and summaries of predictors as follow:

Основные характеристики датасета

# первые 5 строк датасета
head(data)
# Удаление колонки data$X
data$X <- NULL
head(data)
# размер датасета - 506 строк, 13 колонок признаков, 1 колонка целевой величины
dim(data)
[1] 506  14
# Список колонок
colnames(data)
 [1] "crim"    "zn"      "indus"   "chas"    "nox"     "rm"      "age"     "dis"     "rad"    
[10] "tax"     "ptratio" "black"   "lstat"   "medv"   
# Список колнок с типами данных
str(data)
'data.frame':   506 obs. of  14 variables:
 $ crim   : num  0.00632 0.02731 0.02729 0.03237 0.06905 ...
 $ zn     : num  18 0 0 0 0 0 12.5 12.5 12.5 12.5 ...
 $ indus  : num  2.31 7.07 7.07 2.18 2.18 2.18 7.87 7.87 7.87 7.87 ...
 $ chas   : int  0 0 0 0 0 0 0 0 0 0 ...
 $ nox    : num  0.538 0.469 0.469 0.458 0.458 0.458 0.524 0.524 0.524 0.524 ...
 $ rm     : num  6.58 6.42 7.18 7 7.15 ...
 $ age    : num  65.2 78.9 61.1 45.8 54.2 58.7 66.6 96.1 100 85.9 ...
 $ dis    : num  4.09 4.97 4.97 6.06 6.06 ...
 $ rad    : int  1 2 2 3 3 3 5 5 5 5 ...
 $ tax    : int  296 242 242 222 222 222 311 311 311 311 ...
 $ ptratio: num  15.3 17.8 17.8 18.7 18.7 18.7 15.2 15.2 15.2 15.2 ...
 $ black  : num  397 397 393 395 397 ...
 $ lstat  : num  4.98 9.14 4.03 2.94 5.33 ...
 $ medv   : num  24 21.6 34.7 33.4 36.2 28.7 22.9 27.1 16.5 18.9 ...
# Или
sapply(data, class)
     crim        zn     indus      chas       nox        rm       age       dis       rad       tax 
"numeric" "numeric" "numeric" "integer" "numeric" "numeric" "numeric" "numeric" "integer" "integer" 
  ptratio     black     lstat      medv 
"numeric" "numeric" "numeric" "numeric" 
# # Проверка на наличие пропущенных значений
any(is.na(data))
[1] FALSE
colSums(is.na(data))
   crim      zn   indus    chas     nox      rm     age     dis     rad     tax ptratio   black 
      0       0       0       0       0       0       0       0       0       0       0       0 
  lstat    medv 
      0       0 
# Основные статистические характеристики набора данных
summary(data)
      crim                zn             indus            chas              nox        
 Min.   : 0.00632   Min.   :  0.00   Min.   : 0.46   Min.   :0.00000   Min.   :0.3850  
 1st Qu.: 0.08205   1st Qu.:  0.00   1st Qu.: 5.19   1st Qu.:0.00000   1st Qu.:0.4490  
 Median : 0.25651   Median :  0.00   Median : 9.69   Median :0.00000   Median :0.5380  
 Mean   : 3.61352   Mean   : 11.36   Mean   :11.14   Mean   :0.06917   Mean   :0.5547  
 3rd Qu.: 3.67708   3rd Qu.: 12.50   3rd Qu.:18.10   3rd Qu.:0.00000   3rd Qu.:0.6240  
 Max.   :88.97620   Max.   :100.00   Max.   :27.74   Max.   :1.00000   Max.   :0.8710  
       rm             age              dis              rad              tax           ptratio     
 Min.   :3.561   Min.   :  2.90   Min.   : 1.130   Min.   : 1.000   Min.   :187.0   Min.   :12.60  
 1st Qu.:5.886   1st Qu.: 45.02   1st Qu.: 2.100   1st Qu.: 4.000   1st Qu.:279.0   1st Qu.:17.40  
 Median :6.208   Median : 77.50   Median : 3.207   Median : 5.000   Median :330.0   Median :19.05  
 Mean   :6.285   Mean   : 68.57   Mean   : 3.795   Mean   : 9.549   Mean   :408.2   Mean   :18.46  
 3rd Qu.:6.623   3rd Qu.: 94.08   3rd Qu.: 5.188   3rd Qu.:24.000   3rd Qu.:666.0   3rd Qu.:20.20  
 Max.   :8.780   Max.   :100.00   Max.   :12.127   Max.   :24.000   Max.   :711.0   Max.   :22.00  
     black            lstat            medv      
 Min.   :  0.32   Min.   : 1.73   Min.   : 5.00  
 1st Qu.:375.38   1st Qu.: 6.95   1st Qu.:17.02  
 Median :391.44   Median :11.36   Median :21.20  
 Mean   :356.67   Mean   :12.65   Mean   :22.53  
 3rd Qu.:396.23   3rd Qu.:16.95   3rd Qu.:25.00  
 Max.   :396.90   Max.   :37.97   Max.   :50.00  
# Определим эмпирическое среднее и дисперсию целевого признака
mean(data$medv)
[1] 22.53281
sd(data$medv)
[1] 9.197104

Визуальное исследование датасета

ggplot(data, aes(x = crim)) + 
  geom_histogram()

ggplot(data, aes(x = lstat, y = crim)) +geom_point()

library(tidyverse)
data %>%
  gather(key, val, -medv) %>%
  ggplot(aes(x = val, y = medv)) +
  geom_point() +
  stat_smooth(method = "lm", se = TRUE, col = "blue") +
  facet_wrap(~key, scales = "free") +
  theme_gray() +
  ggtitle("Scatter plot of dependent variables vs Median Value (medv)") 

# Парные диаграммы
pairs(~ medv + ptratio + black + lstat + dis + rm + crim, data = data, main = "Boston Data")

par(mfrow = c(3,5))
mapply(boxplot, data[-4], main=paste("Boxplot of",colnames(data[-4])), xlab=colnames(data[-4]))
      X         crim       zn         chas       nox       rm         age       dis       rad       tax       ptratio   
stats Numeric,5 Numeric,5  Numeric,5  Numeric,5  Numeric,5 Numeric,5  Numeric,5 Numeric,5 Numeric,5 Numeric,5 Numeric,5 
n     506       506        506        506        506       506        506       506       506       506       506       
conf  Numeric,2 Numeric,2  Numeric,2  Numeric,2  Numeric,2 Numeric,2  Numeric,2 Numeric,2 Numeric,2 Numeric,2 Numeric,2 
out   Numeric,0 Numeric,66 Numeric,68 Numeric,35 Numeric,0 Numeric,30 Numeric,0 Numeric,5 Numeric,0 Numeric,0 Numeric,15
group Numeric,0 Numeric,66 Numeric,68 Numeric,35 Numeric,0 Numeric,30 Numeric,0 Numeric,5 Numeric,0 Numeric,0 Numeric,15
names ""        ""         ""         ""         ""        ""         ""        ""        ""        ""        ""        
      black      lstat     medv      
stats Numeric,5  Numeric,5 Numeric,5 
n     506        506       506       
conf  Numeric,2  Numeric,2 Numeric,2 
out   Numeric,76 Numeric,6 Numeric,37
group Numeric,76 Numeric,6 Numeric,37
names ""         ""        ""        

plot(medv~lstat, data)

ggplot2

ggplot(data, aes(x = medv, y = crim, colour = as.factor(chas))) +
  geom_point() +
  ggtitle("Fig 1.Crime, Property Value and River Proximity of Boston Towns") +
  theme_bw()

library(ggthemes)
ggplot(data, aes(x = medv, y = crim, colour = as.factor(chas))) +
  geom_point() +
  ggtitle("Fig 1.Crime, Property Value and River Proximity of Boston Towns") +
  theme_economist()

ggplot(data, aes(x = medv, y = crim, colour = as.factor(chas))) +
  geom_point() +
  ggtitle("Fig 1.Crime, Property Value and River Proximity of Boston Towns") +
  labs(x = "Median Property Value (in US Dollars x 1000)",
       y = "Per capita crime rate",
       colour = "Borders the river") +
  scale_colour_discrete(labels = c("No", "Yes"))

#I create a new data frame that only contains 4 variables included in the Boston dataset and I am calling this new data frame object Boston_spm
Boston_spm <- dplyr::select(data, crim, medv, lstat)
# run the scatterplot matrix using the ggpairs function from GGally:
library(GGally)
ggpairs(Boston_spm)

Информация о корреляции признаков

# матрица корреляций
corr_matrix<-cor(data)
corr_matrix
               crim          zn       indus         chas         nox          rm         age
crim     1.00000000 -0.20046922  0.40658341 -0.055891582  0.42097171 -0.21924670  0.35273425
zn      -0.20046922  1.00000000 -0.53382819 -0.042696719 -0.51660371  0.31199059 -0.56953734
indus    0.40658341 -0.53382819  1.00000000  0.062938027  0.76365145 -0.39167585  0.64477851
chas    -0.05589158 -0.04269672  0.06293803  1.000000000  0.09120281  0.09125123  0.08651777
nox      0.42097171 -0.51660371  0.76365145  0.091202807  1.00000000 -0.30218819  0.73147010
rm      -0.21924670  0.31199059 -0.39167585  0.091251225 -0.30218819  1.00000000 -0.24026493
age      0.35273425 -0.56953734  0.64477851  0.086517774  0.73147010 -0.24026493  1.00000000
dis     -0.37967009  0.66440822 -0.70802699 -0.099175780 -0.76923011  0.20524621 -0.74788054
rad      0.62550515 -0.31194783  0.59512927 -0.007368241  0.61144056 -0.20984667  0.45602245
tax      0.58276431 -0.31456332  0.72076018 -0.035586518  0.66802320 -0.29204783  0.50645559
ptratio  0.28994558 -0.39167855  0.38324756 -0.121515174  0.18893268 -0.35550149  0.26151501
black   -0.38506394  0.17552032 -0.35697654  0.048788485 -0.38005064  0.12806864 -0.27353398
lstat    0.45562148 -0.41299457  0.60379972 -0.053929298  0.59087892 -0.61380827  0.60233853
medv    -0.38830461  0.36044534 -0.48372516  0.175260177 -0.42732077  0.69535995 -0.37695457
                dis          rad         tax    ptratio       black      lstat       medv
crim    -0.37967009  0.625505145  0.58276431  0.2899456 -0.38506394  0.4556215 -0.3883046
zn       0.66440822 -0.311947826 -0.31456332 -0.3916785  0.17552032 -0.4129946  0.3604453
indus   -0.70802699  0.595129275  0.72076018  0.3832476 -0.35697654  0.6037997 -0.4837252
chas    -0.09917578 -0.007368241 -0.03558652 -0.1215152  0.04878848 -0.0539293  0.1752602
nox     -0.76923011  0.611440563  0.66802320  0.1889327 -0.38005064  0.5908789 -0.4273208
rm       0.20524621 -0.209846668 -0.29204783 -0.3555015  0.12806864 -0.6138083  0.6953599
age     -0.74788054  0.456022452  0.50645559  0.2615150 -0.27353398  0.6023385 -0.3769546
dis      1.00000000 -0.494587930 -0.53443158 -0.2324705  0.29151167 -0.4969958  0.2499287
rad     -0.49458793  1.000000000  0.91022819  0.4647412 -0.44441282  0.4886763 -0.3816262
tax     -0.53443158  0.910228189  1.00000000  0.4608530 -0.44180801  0.5439934 -0.4685359
ptratio -0.23247054  0.464741179  0.46085304  1.0000000 -0.17738330  0.3740443 -0.5077867
black    0.29151167 -0.444412816 -0.44180801 -0.1773833  1.00000000 -0.3660869  0.3334608
lstat   -0.49699583  0.488676335  0.54399341  0.3740443 -0.36608690  1.0000000 -0.7376627
medv     0.24992873 -0.381626231 -0.46853593 -0.5077867  0.33346082 -0.7376627  1.0000000
library(corrplot)
corrplot(corr_matrix, type="upper")

corrplot(corr_matrix, method = "number", type = "upper", diag = FALSE)

corrplot(corr_matrix, method="number", diag=FALSE)

corrplot(cor(data[,c(1, 3, 4, 6)]), method="number", diag=FALSE)

corrplot(cor(data[,c(1, 3, 4, 6)]), method="color", diag=FALSE)

Можно наблюдать сильную корреляцию между фичей DIS и фичами AGE, NOX, INDUS; имеет смысл оставить только одну из четырех.

Normalization

normalize <- function(x)
{
    return((x- min(x)) /(max(x)-min(x)))
}

# To get a vector, use apply instead of lapply
as.data.frame(apply(df$name, normalize))
LS0tDQp0aXRsZTogIkxhYiAxIC0gRXhhbXBsZSINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCg0KDQojIyDQl9Cw0LPRgNGD0LfQutCwINC00LDQvdC90YvRhQ0KYGBge3J9DQp1cmwgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9mb3J2aXMvTGFicy0yMDIxL21haW4vRGF0YXNldHMvYm9zdG9uLmNzdiINCmRhdGEgPC0gcmVhZC5jc3YodXJsKQ0KYGBgDQoNCg0KVGhlIEJvc3RvbiBkYXRhIGZyYW1lIGhhcyA1MDYgcm93cyBhbmQgMTQgY29sdW1ucyAocHJlZGljdG9ycykuIFdlIGhhdmUgZGVzY3JpcHRpb25zIGFuZCBzdW1tYXJpZXMgb2YgcHJlZGljdG9ycyBhcyBmb2xsb3c6DQoNCi0gYGNyaW1gOiBwZXIgY2FwaXRhIGNyaW1lIHJhdGUgYnkgdG93bi4NCi0gYHpuYDogcHJvcG9ydGlvbiBvZiByZXNpZGVudGlhbCBsYW5kIHpvbmVkIGZvciBsb3RzIG92ZXIgMjUsMDAwIHNxLmZ0Lg0KLSBgaW5kdXNgOiBwcm9wb3J0aW9uIG9mIG5vbi1yZXRhaWwgYnVzaW5lc3MgYWNyZXMgcGVyIHRvd24uDQotIGBjaGFzYDogQ2hhcmxlcyBSaXZlciBkdW1teSB2YXJpYWJsZSAoPSAxIGlmIHRyYWN0IGJvdW5kcyByaXZlcjsgMCBvdGhlcndpc2UpLg0KLSBgbm94YDogbml0cm9nZW4gb3hpZGVzIGNvbmNlbnRyYXRpb24gKHBhcnRzIHBlciAxMCBtaWxsaW9uKS4NCi0gYHJtYDogYXZlcmFnZSBudW1iZXIgb2Ygcm9vbXMgcGVyIGR3ZWxsaW5nLg0KLSBgYWdlYDogcHJvcG9ydGlvbiBvZiBvd25lci1vY2N1cGllZCB1bml0cyBidWlsdCBwcmlvciB0byAxOTQwLg0KLSBgZGlzYDogd2VpZ2h0ZWQgbWVhbiBvZiBkaXN0YW5jZXMgdG8gZml2ZSBCb3N0b24gZW1wbG95bWVudCBjZW50cmVzLg0KLSBgcmFkYDogaW5kZXggb2YgYWNjZXNzaWJpbGl0eSB0byByYWRpYWwgaGlnaHdheXMuDQotIGB0YXhgOiBmdWxsLXZhbHVlIHByb3BlcnR5LXRheCByYXRlIHBlciAkMTAsMDAwLg0KLSBgcHRyYXRpb2A6IHB1cGlsLXRlYWNoZXIgcmF0aW8gYnkgdG93bi4NCi0gYGJsYWNrYDogMTAwMChCayAtIDAuNjMpXjIgd2hlcmUgQmsgaXMgdGhlIHByb3BvcnRpb24gb2YgYmxhY2tzIGJ5IHRvd24uDQotIGBsc3RhdGA6IGxvd2VyIHN0YXR1cyBvZiB0aGUgcG9wdWxhdGlvbiAocGVyY2VudCkuDQotIGBtZWR2YDogbWVkaWFuIHZhbHVlIG9mIG93bmVyLW9jY3VwaWVkIGhvbWVzIGluICQxMDAwcy4NCg0KIyMg0J7RgdC90L7QstC90YvQtSDRhdCw0YDQsNC60YLQtdGA0LjRgdGC0LjQutC4INC00LDRgtCw0YHQtdGC0LANCmBgYHtyfQ0KIyDQv9C10YDQstGL0LUgNSDRgdGC0YDQvtC6INC00LDRgtCw0YHQtdGC0LANCmhlYWQoZGF0YSkNCmBgYA0KDQoNCg0KDQpgYGB7cn0NCiMg0KPQtNCw0LvQtdC90LjQtSDQutC+0LvQvtC90LrQuCBkYXRhJFgNCmRhdGEkWCA8LSBOVUxMDQpoZWFkKGRhdGEpDQpgYGANCmBgYHtyfQ0KIyDRgNCw0LfQvNC10YAg0LTQsNGC0LDRgdC10YLQsCAtIDUwNiDRgdGC0YDQvtC6LCAxMyDQutC+0LvQvtC90L7QuiDQv9GA0LjQt9C90LDQutC+0LIsIDEg0LrQvtC70L7QvdC60LAg0YbQtdC70LXQstC+0Lkg0LLQtdC70LjRh9C40L3Riw0KZGltKGRhdGEpDQpgYGANCg0KDQpgYGB7cn0NCiMg0KHQv9C40YHQvtC6INC60L7Qu9C+0L3QvtC6DQpjb2xuYW1lcyhkYXRhKQ0KYGBgDQoNCg0KYGBge3J9DQojINCh0L/QuNGB0L7QuiDQutC+0LvQvdC+0Log0YEg0YLQuNC/0LDQvNC4INC00LDQvdC90YvRhQ0Kc3RyKGRhdGEpDQpgYGANCmBgYHtyfQ0KIyDQmNC70LgNCnNhcHBseShkYXRhLCBjbGFzcykNCmBgYA0KYGBge3J9DQojICMg0J/RgNC+0LLQtdGA0LrQsCDQvdCwINC90LDQu9C40YfQuNC1INC/0YDQvtC/0YPRidC10L3QvdGL0YUg0LfQvdCw0YfQtdC90LjQuQ0KYW55KGlzLm5hKGRhdGEpKQ0KYGBgDQpgYGB7cn0NCmNvbFN1bXMoaXMubmEoZGF0YSkpDQpgYGANCg0KDQpgYGB7cn0NCiMg0J7RgdC90L7QstC90YvQtSDRgdGC0LDRgtC40YHRgtC40YfQtdGB0LrQuNC1INGF0LDRgNCw0LrRgtC10YDQuNGB0YLQuNC60Lgg0L3QsNCx0L7RgNCwINC00LDQvdC90YvRhQ0Kc3VtbWFyeShkYXRhKQ0KYGBgDQoNCmBgYHtyfQ0KIyDQntC/0YDQtdC00LXQu9C40Lwg0Y3QvNC/0LjRgNC40YfQtdGB0LrQvtC1INGB0YDQtdC00L3QtdC1INC4INC00LjRgdC/0LXRgNGB0LjRjiDRhtC10LvQtdCy0L7Qs9C+INC/0YDQuNC30L3QsNC60LANCm1lYW4oZGF0YSRtZWR2KQ0Kc2QoZGF0YSRtZWR2KQ0KYGBgDQoNCg0KDQoNCiMjINCS0LjQt9GD0LDQu9GM0L3QvtC1INC40YHRgdC70LXQtNC+0LLQsNC90LjQtSDQtNCw0YLQsNGB0LXRgtCwDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KZ2dwbG90KGRhdGEsIGFlcyh4ID0gY3JpbSkpICsgDQogIGdlb21faGlzdG9ncmFtKCkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChkYXRhLCBhZXMoeCA9IGxzdGF0LCB5ID0gY3JpbSkpICtnZW9tX3BvaW50KCkNCmBgYA0KDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpkYXRhICU+JQ0KICBnYXRoZXIoa2V5LCB2YWwsIC1tZWR2KSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gdmFsLCB5ID0gbWVkdikpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgc3RhdF9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBUUlVFLCBjb2wgPSAiYmx1ZSIpICsNCiAgZmFjZXRfd3JhcCh+a2V5LCBzY2FsZXMgPSAiZnJlZSIpICsNCiAgdGhlbWVfZ3JheSgpICsNCiAgZ2d0aXRsZSgiU2NhdHRlciBwbG90IG9mIGRlcGVuZGVudCB2YXJpYWJsZXMgdnMgTWVkaWFuIFZhbHVlIChtZWR2KSIpIA0KYGBgDQoNCg0KDQpgYGB7ciwgZmlnLndpZHRoPSAxMn0NCiMg0J/QsNGA0L3Ri9C1INC00LjQsNCz0YDQsNC80LzRiw0KcGFpcnMofiBtZWR2ICsgcHRyYXRpbyArIGJsYWNrICsgbHN0YXQgKyBkaXMgKyBybSArIGNyaW0sIGRhdGEgPSBkYXRhLCBtYWluID0gIkJvc3RvbiBEYXRhIikNCmBgYA0KYGBge3J9DQpwYXIobWZyb3cgPSBjKDMsNSkpDQptYXBwbHkoYm94cGxvdCwgZGF0YVstNF0sIG1haW49cGFzdGUoIkJveHBsb3Qgb2YiLGNvbG5hbWVzKGRhdGFbLTRdKSksIHhsYWI9Y29sbmFtZXMoZGF0YVstNF0pKQ0KYGBgDQoNCg0KYGBge3J9DQpwbG90KG1lZHZ+bHN0YXQsIGRhdGEpDQpgYGANCg0KIyMgZ2dwbG90Mg0KDQpgYGB7cn0NCmdncGxvdChkYXRhLCBhZXMoeCA9IG1lZHYsIHkgPSBjcmltLCBjb2xvdXIgPSBhcy5mYWN0b3IoY2hhcykpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdndGl0bGUoIkZpZyAxLkNyaW1lLCBQcm9wZXJ0eSBWYWx1ZSBhbmQgUml2ZXIgUHJveGltaXR5IG9mIEJvc3RvbiBUb3ducyIpICsNCiAgdGhlbWVfYncoKQ0KYGBgDQpgYGB7cn0NCmxpYnJhcnkoZ2d0aGVtZXMpDQpnZ3Bsb3QoZGF0YSwgYWVzKHggPSBtZWR2LCB5ID0gY3JpbSwgY29sb3VyID0gYXMuZmFjdG9yKGNoYXMpKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZ3RpdGxlKCJGaWcgMS5DcmltZSwgUHJvcGVydHkgVmFsdWUgYW5kIFJpdmVyIFByb3hpbWl0eSBvZiBCb3N0b24gVG93bnMiKSArDQogIHRoZW1lX2Vjb25vbWlzdCgpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSwgYWVzKHggPSBtZWR2LCB5ID0gY3JpbSwgY29sb3VyID0gYXMuZmFjdG9yKGNoYXMpKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZ3RpdGxlKCJGaWcgMS5DcmltZSwgUHJvcGVydHkgVmFsdWUgYW5kIFJpdmVyIFByb3hpbWl0eSBvZiBCb3N0b24gVG93bnMiKSArDQogIGxhYnMoeCA9ICJNZWRpYW4gUHJvcGVydHkgVmFsdWUgKGluIFVTIERvbGxhcnMgeCAxMDAwKSIsDQogICAgICAgeSA9ICJQZXIgY2FwaXRhIGNyaW1lIHJhdGUiLA0KICAgICAgIGNvbG91ciA9ICJCb3JkZXJzIHRoZSByaXZlciIpICsNCiAgc2NhbGVfY29sb3VyX2Rpc2NyZXRlKGxhYmVscyA9IGMoIk5vIiwgIlllcyIpKQ0KYGBgDQoNCmBgYHtyfQ0KI0kgY3JlYXRlIGEgbmV3IGRhdGEgZnJhbWUgdGhhdCBvbmx5IGNvbnRhaW5zIDQgdmFyaWFibGVzIGluY2x1ZGVkIGluIHRoZSBCb3N0b24gZGF0YXNldCBhbmQgSSBhbSBjYWxsaW5nIHRoaXMgbmV3IGRhdGEgZnJhbWUgb2JqZWN0IEJvc3Rvbl9zcG0NCkJvc3Rvbl9zcG0gPC0gZHBseXI6OnNlbGVjdChkYXRhLCBjcmltLCBtZWR2LCBsc3RhdCkNCmBgYA0KDQpgYGB7cn0NCiMgIyMgU2NhdHRlcnBsb3QgbWF0cml4IHVzaW5nIA0KIyBydW4gdGhlIHNjYXR0ZXJwbG90IG1hdHJpeCB1c2luZyB0aGUgZ2dwYWlycyBmdW5jdGlvbiBmcm9tIEdHYWxseToNCmxpYnJhcnkoR0dhbGx5KQ0KZ2dwYWlycyhCb3N0b25fc3BtKQ0KYGBgDQoNCg0KDQojIyDQmNC90YTQvtGA0LzQsNGG0LjRjyDQviDQutC+0YDRgNC10LvRj9GG0LjQuCDQv9GA0LjQt9C90LDQutC+0LINCg0KYGBge3J9DQojINC80LDRgtGA0LjRhtCwINC60L7RgNGA0LXQu9GP0YbQuNC5DQpjb3JyX21hdHJpeDwtY29yKGRhdGEpDQpjb3JyX21hdHJpeA0KYGBgDQoNCmBgYHtyfQ0KIyDQmtC+0YDRgNC10LvQvtCz0YDQsNC80LzQsA0KbGlicmFyeShjb3JycGxvdCkNCmNvcnJwbG90KGNvcnJfbWF0cml4LCB0eXBlPSJ1cHBlciIpDQpgYGANCmBgYHtyfQ0KY29ycnBsb3QoY29ycl9tYXRyaXgsIG1ldGhvZCA9ICJudW1iZXIiLCB0eXBlID0gInVwcGVyIiwgZGlhZyA9IEZBTFNFKQ0KYGBgDQoNCg0KYGBge3IsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMn0NCmNvcnJwbG90KGNvcnJfbWF0cml4LCBtZXRob2Q9Im51bWJlciIsIGRpYWc9RkFMU0UpDQpgYGANCg0KDQpgYGB7cn0NCmNvcnJwbG90KGNvcihkYXRhWyxjKDEsIDMsIDQsIDYpXSksIG1ldGhvZD0ibnVtYmVyIiwgZGlhZz1GQUxTRSkNCmBgYA0KYGBge3J9DQpjb3JycGxvdChjb3IoZGF0YVssYygxLCAzLCA0LCA2KV0pLCBtZXRob2Q9ImNvbG9yIiwgZGlhZz1GQUxTRSkNCmBgYA0KDQoNCtCc0L7QttC90L4g0L3QsNCx0LvRjtC00LDRgtGMINGB0LjQu9GM0L3Rg9GOINC60L7RgNGA0LXQu9GP0YbQuNGOINC80LXQttC00YMg0YTQuNGH0LXQuSBESVMg0Lgg0YTQuNGH0LDQvNC4IEFHRSwgTk9YLCBJTkRVUzsg0LjQvNC10LXRgiDRgdC80YvRgdC7INC+0YHRgtCw0LLQuNGC0Ywg0YLQvtC70YzQutC+INC+0LTQvdGDINC40Lcg0YfQtdGC0YvRgNC10YUuDQoNCg0KIyMgTm9ybWFsaXphdGlvbg0KYGBge3J9DQpub3JtYWxpemUgPC0gZnVuY3Rpb24oeCkNCnsNCiAgICByZXR1cm4oKHgtIG1pbih4KSkgLyhtYXgoeCktbWluKHgpKSkNCn0NCg0KIyBUbyBnZXQgYSB2ZWN0b3IsIHVzZSBhcHBseSBpbnN0ZWFkIG9mIGxhcHBseQ0KYXMuZGF0YS5mcmFtZShhcHBseShkZiRuYW1lLCBub3JtYWxpemUpKQ0KYGBgDQo=