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.
Install Package
#Install package
install.packages('dplyr')
Installing package into ‘/cloud/lib/x86_64-pc-linux-gnu-library/4.2’
(as ‘lib’ is unspecified)
trying URL 'http://rspm/default/__linux__/focal/latest/src/contrib/dplyr_1.1.0.tar.gz'
Content type 'application/x-gzip' length 1447085 bytes (1.4 MB)
==================================================
downloaded 1.4 MB
* installing *binary* package ‘dplyr’ ...
* DONE (dplyr)
The downloaded source packages are in
‘/tmp/RtmppEGSWX/downloaded_packages’
install.packages('reshape2')
Installing package into ‘/cloud/lib/x86_64-pc-linux-gnu-library/4.2’
(as ‘lib’ is unspecified)
trying URL 'http://rspm/default/__linux__/focal/latest/src/contrib/reshape2_1.4.4.tar.gz'
Content type 'application/x-gzip' length 116930 bytes (114 KB)
==================================================
downloaded 114 KB
* installing *binary* package ‘reshape2’ ...
* DONE (reshape2)
The downloaded source packages are in
‘/tmp/RtmppEGSWX/downloaded_packages’
library(dplyr)
Attaching package: ‘dplyr’
The following objects are masked from ‘package:stats’:
filter, lag
The following objects are masked from ‘package:base’:
intersect, setdiff, setequal, union
library(reshape2)
library(stats)
#Explore Iris Dataset
iris
data(iris)
#Use head to show top rows from data
head(iris)
head(iris,10)
Bước 1: Thực hành khám phá dữ liệu IRIS
Ở bước đầu tiên chúng ta sẽ tìm hiểu các công thức trong R giúp có
thể xem và khám phá dữ liệu một cách tổng quan. Những tính năng này cho
phép chúng ta nhìn được dữ liệu hoặc cung cấp các thống kê tổng quan về
dữ liệu như những chỉ số cơ bản về Mean, Median, Min, Max, Std,
Variance, … Đồng thời chúng ta cũng có thể xem và xác nhận cấu trúc dữ
liệu và cả định dạng của dữ liệu trước khi phân tích. Chúng ta sẽ cùng
thực hành viết các câu lệnh và đoạn code như dưới đây:
#STEP 1: EXPLORE DATA ----------------------------
#1.1 Function summary to review data
summary(iris)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
Min. :4.300 Min. :2.000 Min. :1.000 Min. :0.100 setosa :50
1st Qu.:5.100 1st Qu.:2.800 1st Qu.:1.600 1st Qu.:0.300 versicolor:50
Median :5.800 Median :3.000 Median :4.350 Median :1.300 virginica :50
Mean :5.843 Mean :3.057 Mean :3.758 Mean :1.199
3rd Qu.:6.400 3rd Qu.:3.300 3rd Qu.:5.100 3rd Qu.:1.800
Max. :7.900 Max. :4.400 Max. :6.900 Max. :2.500
#1.2 Function names to show columns name
names(iris)
[1] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width" "Species"
names(iris) <- tolower(names(iris))
#1.3 Function dim to show row and column counts
dim(iris)
[1] 150 5
#1.4 Function class to show data structure
class(iris)
[1] "data.frame"
#1.5 Function typeof and str to show data type
typeof(iris$sepal.length)
[1] "double"
str(iris$sepal.length)
num [1:150] 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
typeof(iris$species)
[1] "integer"
class(iris$species)
[1] "factor"
str(iris$species)
Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
Bước 2: Thực hành chỉnh sửa và thay đổi cấu trúc dữ liệu Ở bước tiếp
theo, chúng ta sẽ cùng sử dụng những câu lệnh để chỉnh sửa cấu trúc dữ
liệu Iris theo yêu cầu. Điều này bao gồm việc bóc tách dữ liệu thành
những bảng nhỏ hơn như phân thành các bảng theo từng loài hoa. Hoặc
chúng mong muốn thêm các cột, sort bảng theo một tiêu chí nào đó. Và một
yếu tố quan trọng không kém là chuyển đổi cấu trúc giống với tính năng
Unpivot trong PowerBI thì chúng ta sẽ chuyển đổi dữ liệu từ dạng Wide
(cấu trúc giống Pivot Table) sang dạng Long (cấu trúc giống Tabular).
Điều này yêu cầu chúng ta sẽ phải cài đặt thêm Package dplyr là một bộ
ngôn ngữ lập trình trong R giúp thay đổi cấu trúc và chỉnh sửa dữ liệu.
Chúng ta sẽ thêm ở bên trên phần Install Package nội dung câu lệnh để
cài đặt package dplyr và sử dụng chúng trong môi trường hiện tại.
#STEP 2: TRANSFORM DATA --------------------------
#2.1 Split data into subset
virginica <- iris[iris$species == 'virginica',]
virginica2 <- iris[iris$species == 'virginica' & iris$sepal.length > 6,]
head(virginica)
#2.2 Function select to select columns
selected <- select(iris, sepal.length, sepal.width)
head(selected)
#2.3 Function mutate to add column
newcol <- mutate(iris, longer = sepal.length / sepal.width )
newcol <- mutate(newcol, longer.2x = sepal.length > 2*sepal.width )
tail(newcol)
#2.4 Function arrange to sort data
newcol <- arrange(newcol, sepal.width)
newcol <- arrange(newcol, desc(sepal.width))
#2.5 Function melt to unpivot table (wide -> long)
iris.melt <- melt(iris, id = 'species', variable.name = 'size')
head(iris.melt)
Bước 3: Thực hành trực quan hóa dữ liệu và tạo biểu đồ
Sau khi đã có các bảng dữ liệu theo dạng cần thiết, chúng ta có thể
sử dụng các package và code xây dựng các biểu đồ giúp trực quan hóa
thông tin. Ở bước này chúng ta sẽ tạo những biểu đồ có ý nghĩa về mặt
thống kê với dữ liệu để hiểu về tập đối tượng đang phân tích. Chúng bao
gồm biểu đồ Histogram để xem sự phân bổ về các chiều dài của hoa, sử
dụng Box Plot giúp trực quan các con số thống kê. Sử dụng Scatter Plot
để xem phân bổ của dữ liệu theo 2 chiều cụ thể. Để tạo ra các biểu đồ
này, chúng ta có thể sử dụng các câu lệnh có sẵn trong RStudio hoặc cài
đặt thêm Package GGPlot & Ggally để có thể tạo các biểu đồ tương tự
nhưng có nhiều khả năng thay đổi điều chỉnh cũng như dễ dàng tạo
hơn.
#STEP 3: VISUALIZE DATA -----------------------
#3.1 Function hist to show histogram
hist(iris$sepal.length)

hist(iris$sepal.length,
col='light blue',
main='Histogram',
xlab='Sepal.Length',
ylab='Frequency')

hist(iris$sepal.length, col='red', breaks=20, main='Histogram', xlab='Size')
hist(iris$petal.length, col='green',breaks=30, add=TRUE)
legend('topright',
c('Sepal Length', 'Petal Length'),
fill=c('red', 'green'))

#3.2 Use ggplot to create charts
ggplot(iris.melt, aes(x=value, fill=size)) +
geom_histogram(color ='#e9ecef', alpha = 0.6, position = 'identity')
Error in ggplot(iris.melt, aes(x = value, fill = size)) :
could not find function "ggplot"
Bước 4: Kiểm chứng giả định với T-test
Ở buổi trước chúng ta đã học cách sử dụng T-test để xác định giả
thuyết có chính xác hay không. Tương tự với RStudio, chúng ta có thể dễ
dàng sử dụng nhiều loại T-test, ở phần thực hành này chúng ta sẽ sử dụng
loại T-test để xác định giả thuyết cơ bản về các loại hoa như sau. Câu
hỏi: liệu loài setosa và versicolor có chiều dài cánh hoa khác nhau hay
không? Giả định: • Null Hypothesis (Ho): Trung bình chiều dài cánh hoa 2
loài giống nhau (khác biệt mean các loài = 0) • Alternative Hypothesis
(Ha): Trung bình chiều dài cánh hoa 2 loài khác nhau
#STEP 4: HYPOTHESIS TESTING WITH T-test ---------------
setosa <- iris[iris$species == 'setosa',]
versicolor <- iris[iris$species == 'versicolor',]
t.test(x= setosa$petal.length, y = versicolor$petal.length)
Welch Two Sample t-test
data: setosa$petal.length and versicolor$petal.length
t = -39.493, df = 62.14, p-value < 2.2e-16
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
-2.939618 -2.656382
sample estimates:
mean of x mean of y
1.462 4.260
Bước 5: Thực hành phân tích phương sai ANOVA
Tương tự chúng ta cũng có thể chạy các mô hình phân tích ANOVA trong
RStudio. Trong phần thực hành này, chúng ta sẽ cùng phân tích ANOVA để
xác định giả thuyết so sánh về chiều dài cánh hoa giữa cả 3 loài hoa.
Câu hỏi: liệu 3 loài hoa khác nhau có chiều dài cánh hoa khác nhau hay
không? Giả định: • Null Hypothesis (Ho): Trung bình chiều dài cánh hoa 3
loài giống nhau (khác biệt mean các loài = 0) • Alternative Hypothesis
(Ha): Trung bình chiều dài cánh hoa 3 loài khác nhau
#STEP 5: ANALYSIS WITH ANOVA -----------------------------
petal.length.aov <- aov(formula = petal.length ~ species, data = iris)
summary(object = petal.length.aov)
Df Sum Sq Mean Sq F value Pr(>F)
species 2 437.1 218.55 1180 <2e-16 ***
Residuals 147 27.2 0.19
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
TukeyHSD(petal.length.aov)
Tukey multiple comparisons of means
95% family-wise confidence level
Fit: aov(formula = petal.length ~ species, data = iris)
$species
diff lwr upr p adj
versicolor-setosa 2.798 2.59422 3.00178 0
virginica-setosa 4.090 3.88622 4.29378 0
virginica-versicolor 1.292 1.08822 1.49578 0
Bước 6:
#STEP 6: CATEGORIZE SPECIES WITH K-MEAN CLUSTERING -------------
iris.test <- iris
iris.test$species <- NULL
head(iris.test)
kmeans.result <- kmeans(iris.test, 3)
table(iris$species, kmeans.result$cluster)
1 2 3
setosa 33 17 0
versicolor 0 4 46
virginica 0 0 50
plot(iris.test[c('sepal.length', 'sepal.width')], col = kmeans.result$cluster)

plotcluster(iris.test, kmeans.result$cluster)
Error in plotcluster(iris.test, kmeans.result$cluster) :
could not find function "plotcluster"
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2suIFdoZW4geW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCB0aGUgcmVzdWx0cyBhcHBlYXIgYmVuZWF0aCB0aGUgY29kZS4KClRyeSBleGVjdXRpbmcgdGhpcyBjaHVuayBieSBjbGlja2luZyB0aGUgKlJ1biogYnV0dG9uIHdpdGhpbiB0aGUgY2h1bmsgb3IgYnkgcGxhY2luZyB5b3VyIGN1cnNvciBpbnNpZGUgaXQgYW5kIHByZXNzaW5nICpDdHJsK1NoaWZ0K0VudGVyKi4KCmBgYHtyfQpwbG90KGNhcnMpCmBgYAoKQWRkIGEgbmV3IGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqSW5zZXJ0IENodW5rKiBidXR0b24gb24gdGhlIHRvb2xiYXIgb3IgYnkgcHJlc3NpbmcgKkN0cmwrQWx0K0kqLgoKV2hlbiB5b3Ugc2F2ZSB0aGUgbm90ZWJvb2ssIGFuIEhUTUwgZmlsZSBjb250YWluaW5nIHRoZSBjb2RlIGFuZCBvdXRwdXQgd2lsbCBiZSBzYXZlZCBhbG9uZ3NpZGUgaXQgKGNsaWNrIHRoZSAqUHJldmlldyogYnV0dG9uIG9yIHByZXNzICpDdHJsK1NoaWZ0K0sqIHRvIHByZXZpZXcgdGhlIEhUTUwgZmlsZSkuCgpUaGUgcHJldmlldyBzaG93cyB5b3UgYSByZW5kZXJlZCBIVE1MIGNvcHkgb2YgdGhlIGNvbnRlbnRzIG9mIHRoZSBlZGl0b3IuIENvbnNlcXVlbnRseSwgdW5saWtlICpLbml0KiwgKlByZXZpZXcqIGRvZXMgbm90IHJ1biBhbnkgUiBjb2RlIGNodW5rcy4gSW5zdGVhZCwgdGhlIG91dHB1dCBvZiB0aGUgY2h1bmsgd2hlbiBpdCB3YXMgbGFzdCBydW4gaW4gdGhlIGVkaXRvciBpcyBkaXNwbGF5ZWQuCgojIENBU0UgU1RVRFkKCkThu68gbGnhu4d1IG3huqt1IElSSVMKVHJvbmcgcGjhuqduIHRo4buxYyBow6BuaCBideG7lWkgMTAsIGNow7puZyB0YSBz4bq9IHPhu60gZOG7pW5nIG5nYXkgZOG7ryBsaeG7h3UgbeG6q3UgdMOtY2ggaOG7o3Agc+G6tW4gdHJvbmcgUlN0dWRpbyDEkcOzIGzDoCBk4buvIGxp4buHdSBJcmlzLiDEkMOieSBsw6AgdOG6rXAgZOG7ryBsaeG7h3Ugbmjhu48gdsOgIMSRxqFuCmdp4bqjbiBnacO6cCBk4buFIGjDrG5oIGR1bmcgduG7gSBjw6FjaCBz4butIGThu6VuZyBSU3R1ZGlvIGPFqW5nIG5oxrAgY8OhY2ggw6FwIGThu6VuZyBjw6FjIG3DtCBow6xuaCB0aOG7kW5nIGvDqiBjaG8gbmfGsOG7nWkgYuG6r3QgxJHhuqd1LgpC4buZIGThu68gbGnhu4d1IG7DoHkgxJHGsOG7o2MgdOG6oW8gcmEga2hpIG5ow6Agc2luaCB24bqtdCBo4buNYyBuZ8aw4budaSBBbmggUm9uYWxkIEZpc2hlciB4deG6pXQgYuG6o24gYsOgaSBiw6FvIG7Eg20gMTkzNiBj4bunYSDDtG5nIFwiVmnhu4djIHPhu60gZOG7pW5nIG5oaeG7gXUgcGjDqXAgxJFvIHRyb25nCmPDoWMgduG6pW4gxJHhu4EgcGjDom4gbG/huqFpXCIgbmjGsCBt4buZdCB2w60gZOG7pSB24buBIHBow6JuIHTDrWNoIHBow6JuIGJp4buHdCB0dXnhur9uIHTDrW5oLiDEkMO0aSBraGkgbsOzIGPFqW5nIMSRxrDhu6NjIGfhu41pIGzDoCB04bqtcCBk4buvIGxp4buHdSBBbmRlcnNvblwncyBJcmlzIHbDrCBFZGdhciBBbmRlcnNvbgpsw6AgbmfGsOG7nWkgxJHDoyB0aHUgdGjhuq1wIGThu68gbGnhu4d1IMSR4buDIMSR4buLbmggbMaw4bujbmcgc+G7sSBiaeG6v24gxJHhu5VpIGjDrG5oIHRow6FpIGPhu6dhIGhvYSBJcmlzIHRoZW8gYmEgbG/DoGkgY8OzIGxpw6puIHF1YW4uIFbhu4EgY8ahIGLhuqNuLCBoYWkgbmjDoCBraG9hIGjhu41jIGzDonUgxJHhu51pIG7DoHkgxJHDowpraGFpIHNpbmggcmEgdOG6rXAgZOG7ryBsaeG7h3UsIHThuq1wIGThu68gbGnhu4d1IHbhuqtuIMSRxrDhu6NjIHPhu60gZOG7pW5nIGNobyDEkeG6v24gbmfDoHkgbmF5IMSR4buDIGjhu41jIGPDoWMga2nhur9uIHRo4bupYyBjxqEgYuG6o24gduG7gSBSU3R1ZGlvLgpC4buZIGThu68gbGnhu4d1IGlyaXMgxJHGsGEgcmEgY8OhYyBwaMOpcCDEkW8gdMOtbmggYuG6sW5nIGNtIGPhu6dhIGPDoWMgYmnhur9uIGNoaeG7gXUgZMOgaSB2w6AgY2hp4buBdSBy4buZbmcgY+G7p2EgbMOhIMSRw6BpIFwiU2VwYWxcIiB2w6AgY2hp4buBdSBkw6BpIHbDoCBjaGnhu4F1IHLhu5luZyBj4bunYSBjw6FuaCBob2EKXCJQZXRhbFwiLCB0xrDGoW5nIOG7qW5nLCDEkeG7kWkgduG7m2kgNTAgYsO0bmcgaG9hIHThu6sgbeG7l2kgdHJvbmcgc+G7kSAzIGxvw6BpIGlyaXMuIEPDoWMgbG/DoGkgbMOgIElyaXMgc2V0b3NhLCB2ZXJzaWNvbG9yLCB2w6AgdmlyZ2luaWNhLiBUcm9uZyBow6xuaCBuw6B5LCBi4bqhbiBjw7MgdGjhu4MKdGjhuqV5IGNow7puZyB0YSDEkWFuZyB44butIGzDvSBuaOG7r25nIGfDrCB2w6AgaMOsbmggZOG6oW5nIGNow61uaCB4w6FjIGPhu6dhIGPDoWMgYmnhur9uIChjaGnhu4F1IGTDoGkgdsOgIGNoaeG7gXUgcuG7mW5nIGPhu6dhIMSRw6BpIGhvYSBcIlNlcGFsXCIgdsOgIGNoaeG7gXUgZMOgaSB2w6AgY2hp4buBdSBy4buZbmcgY+G7p2EKY8OhbmggaG9hIFwiUGV0YWxcIikgbcOgIGNow7puZyB0YSDEkWFuZyDEkW8gdsOgIGLhuqNuIHRow6JuIMSR4buRaSB0xrDhu6NuZzoKCiFb4bqibmggbG/DoGkgaG9hXSgxX25mSzN2R1prVGE0R3JPN3lXcGNTLVEucG5nKQoKIyBJbnN0YWxsIFBhY2thZ2UgCgpgYGB7cn0KI0luc3RhbGwgcGFja2FnZQppbnN0YWxsLnBhY2thZ2VzKCdkcGx5cicpCmluc3RhbGwucGFja2FnZXMoJ3Jlc2hhcGUyJykKCgpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHJlc2hhcGUyKQpsaWJyYXJ5KHN0YXRzKQoKYGBgCgpgYGB7cn0KI0V4cGxvcmUgSXJpcyBEYXRhc2V0CmlyaXMKZGF0YShpcmlzKSAKI1VzZSBoZWFkIHRvIHNob3cgdG9wIHJvd3MgZnJvbSBkYXRhCmhlYWQoaXJpcykKaGVhZChpcmlzLDEwKQpgYGAKCiMjIApCxrDhu5tjIDE6IFRo4buxYyBow6BuaCBraMOhbSBwaMOhIGThu68gbGnhu4d1IElSSVMgCgrhu54gYsaw4bubYyDEkeG6p3UgdGnDqm4gY2jDum5nIHRhIHPhur0gdMOsbSBoaeG7g3UgY8OhYyBjw7RuZyB0aOG7qWMgdHJvbmcgUiBnacO6cCBjw7MgdGjhu4MgeGVtIHbDoCBraMOhbSBwaMOhIGThu68gbGnhu4d1IG3hu5l0IGPDoWNoIHThu5VuZyBxdWFuLiBOaOG7r25nIHTDrW5oIG7Eg25nIG7DoHkgY2hvCnBow6lwIGNow7puZyB0YSBuaMOsbiDEkcaw4bujYyBk4buvIGxp4buHdSBob+G6t2MgY3VuZyBj4bqlcCBjw6FjIHRo4buRbmcga8OqIHThu5VuZyBxdWFuIHbhu4EgZOG7ryBsaeG7h3UgbmjGsCBuaOG7r25nIGNo4buJIHPhu5EgY8ahIGLhuqNuIHbhu4EgTWVhbiwgTWVkaWFuLCBNaW4sIE1heCwgU3RkLApWYXJpYW5jZSwgLi4uIMSQ4buTbmcgdGjhu51pIGNow7puZyB0YSBjxaluZyBjw7MgdGjhu4MgeGVtIHbDoCB4w6FjIG5o4bqtbiBj4bqldSB0csO6YyBk4buvIGxp4buHdSB2w6AgY+G6oyDEkeG7i25oIGThuqFuZyBj4bunYSBk4buvIGxp4buHdSB0csaw4bubYyBraGkgcGjDom4gdMOtY2guCkNow7puZyB0YSBz4bq9IGPDuW5nIHRo4buxYyBow6BuaCB2aeG6v3QgY8OhYyBjw6J1IGzhu4duaCB2w6AgxJFv4bqhbiBjb2RlIG5oxrAgZMaw4bubaSDEkcOieToKCmBgYHtyfQoKI1NURVAgMTogRVhQTE9SRSBEQVRBIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIzEuMSBGdW5jdGlvbiBzdW1tYXJ5IHRvIHJldmlldyBkYXRhCnN1bW1hcnkoaXJpcykKYGBgCgpgYGB7cn0KIzEuMiBGdW5jdGlvbiBuYW1lcyB0byBzaG93IGNvbHVtbnMgbmFtZQpuYW1lcyhpcmlzKQpuYW1lcyhpcmlzKSA8LSB0b2xvd2VyKG5hbWVzKGlyaXMpKQpgYGAKCmBgYHtyfQoKIzEuMyBGdW5jdGlvbiBkaW0gdG8gc2hvdyByb3cgYW5kIGNvbHVtbiBjb3VudHMKZGltKGlyaXMpCmBgYAoKYGBge3J9CiMxLjQgRnVuY3Rpb24gY2xhc3MgdG8gc2hvdyBkYXRhIHN0cnVjdHVyZQpjbGFzcyhpcmlzKQpgYGAKCmBgYHtyfQojMS41IEZ1bmN0aW9uIHR5cGVvZiBhbmQgc3RyIHRvIHNob3cgZGF0YSB0eXBlCnR5cGVvZihpcmlzJHNlcGFsLmxlbmd0aCkKc3RyKGlyaXMkc2VwYWwubGVuZ3RoKQp0eXBlb2YoaXJpcyRzcGVjaWVzKQpjbGFzcyhpcmlzJHNwZWNpZXMpCnN0cihpcmlzJHNwZWNpZXMpCgpgYGAKCkLGsOG7m2MgMjogVGjhu7FjIGjDoG5oIGNo4buJbmggc+G7rWEgdsOgIHRoYXkgxJHhu5VpIGPhuqV1IHRyw7pjIGThu68gbGnhu4d1CuG7niBixrDhu5tjIHRp4bq/cCB0aGVvLCBjaMO6bmcgdGEgc+G6vSBjw7luZyBz4butIGThu6VuZyBuaOG7r25nIGPDonUgbOG7h25oIMSR4buDIGNo4buJbmggc+G7rWEgY+G6pXUgdHLDumMgZOG7ryBsaeG7h3UgSXJpcyB0aGVvIHnDqnUgY+G6p3UuIMSQaeG7gXUgbsOgeSBiYW8gZ+G7k20gdmnhu4djIGLDs2MgdMOhY2ggZOG7rwpsaeG7h3UgdGjDoG5oIG5o4buvbmcgYuG6o25nIG5o4buPIGjGoW4gbmjGsCBwaMOibiB0aMOgbmggY8OhYyBi4bqjbmcgdGhlbyB04burbmcgbG/DoGkgaG9hLiBIb+G6t2MgY2jDum5nIG1vbmcgbXXhu5FuIHRow6ptIGPDoWMgY+G7mXQsIHNvcnQgYuG6o25nIHRoZW8gbeG7mXQgdGnDqnUgY2jDrQpuw6BvIMSRw7MuIFbDoCBt4buZdCB54bq/dSB04buRIHF1YW4gdHLhu41uZyBraMO0bmcga8OpbSBsw6AgY2h1eeG7g24gxJHhu5VpIGPhuqV1IHRyw7pjIGdp4buRbmcgduG7m2kgdMOtbmggbsSDbmcgVW5waXZvdCB0cm9uZyBQb3dlckJJIHRow6wgY2jDum5nIHRhIHPhur0gY2h1eeG7g24gxJHhu5VpIGThu68KbGnhu4d1IHThu6sgZOG6oW5nIFdpZGUgKGPhuqV1IHRyw7pjIGdp4buRbmcgUGl2b3QgVGFibGUpIHNhbmcgZOG6oW5nIExvbmcgKGPhuqV1IHRyw7pjIGdp4buRbmcgVGFidWxhcikuCsSQaeG7gXUgbsOgeSB5w6p1IGPhuqd1IGNow7puZyB0YSBz4bq9IHBo4bqjaSBjw6BpIMSR4bq3dCB0aMOqbSBQYWNrYWdlIGRwbHlyIGzDoCBt4buZdCBi4buZIG5nw7RuIG5n4buvIGzhuq1wIHRyw6xuaCB0cm9uZyBSIGdpw7pwIHRoYXkgxJHhu5VpIGPhuqV1IHRyw7pjIHbDoCBjaOG7iW5oIHPhu61hIGThu68gbGnhu4d1LgpDaMO6bmcgdGEgc+G6vSB0aMOqbSDhu58gYsOqbiB0csOqbiBwaOG6p24gSW5zdGFsbCBQYWNrYWdlIG7hu5lpIGR1bmcgY8OidSBs4buHbmggxJHhu4MgY8OgaSDEkeG6t3QgcGFja2FnZSBkcGx5ciB2w6Agc+G7rSBk4bulbmcgY2jDum5nIHRyb25nIG3DtGkgdHLGsOG7nW5nIGhp4buHbiB04bqhaS4KCmBgYHtyfQojU1RFUCAyOiBUUkFOU0ZPUk0gREFUQSAgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIzIuMSBTcGxpdCBkYXRhIGludG8gc3Vic2V0CnZpcmdpbmljYSA8LSBpcmlzW2lyaXMkc3BlY2llcyA9PSAndmlyZ2luaWNhJyxdCnZpcmdpbmljYTIgPC0gaXJpc1tpcmlzJHNwZWNpZXMgPT0gJ3ZpcmdpbmljYScgJiBpcmlzJHNlcGFsLmxlbmd0aCA+IDYsXQpoZWFkKHZpcmdpbmljYSkKCiMyLjIgRnVuY3Rpb24gc2VsZWN0IHRvIHNlbGVjdCBjb2x1bW5zCnNlbGVjdGVkIDwtIHNlbGVjdChpcmlzLCBzZXBhbC5sZW5ndGgsIHNlcGFsLndpZHRoKQpoZWFkKHNlbGVjdGVkKQoKIzIuMyBGdW5jdGlvbiBtdXRhdGUgdG8gYWRkIGNvbHVtbgpuZXdjb2wgPC0gbXV0YXRlKGlyaXMsIGxvbmdlciA9IHNlcGFsLmxlbmd0aCAvIHNlcGFsLndpZHRoICkKbmV3Y29sIDwtIG11dGF0ZShuZXdjb2wsIGxvbmdlci4yeCA9IHNlcGFsLmxlbmd0aCA+IDIqc2VwYWwud2lkdGggKQp0YWlsKG5ld2NvbCkKCiMyLjQgRnVuY3Rpb24gYXJyYW5nZSB0byBzb3J0IGRhdGEKbmV3Y29sIDwtIGFycmFuZ2UobmV3Y29sLCBzZXBhbC53aWR0aCkKbmV3Y29sIDwtIGFycmFuZ2UobmV3Y29sLCBkZXNjKHNlcGFsLndpZHRoKSkKCgojMi41IEZ1bmN0aW9uIG1lbHQgdG8gdW5waXZvdCB0YWJsZSAod2lkZSAtPiBsb25nKQppcmlzLm1lbHQgPC0gbWVsdChpcmlzLCBpZCA9ICdzcGVjaWVzJywgdmFyaWFibGUubmFtZSA9ICdzaXplJykKaGVhZChpcmlzLm1lbHQpCmBgYAoKIyMgCgoKQsaw4bubYyAzOiBUaOG7sWMgaMOgbmggdHLhu7FjIHF1YW4gaMOzYSBk4buvIGxp4buHdSB2w6AgdOG6oW8gYmnhu4N1IMSR4buTCgpTYXUga2hpIMSRw6MgY8OzIGPDoWMgYuG6o25nIGThu68gbGnhu4d1IHRoZW8gZOG6oW5nIGPhuqduIHRoaeG6v3QsIGNow7puZyB0YSBjw7MgdGjhu4Mgc+G7rSBk4bulbmcgY8OhYyBwYWNrYWdlIHbDoCBjb2RlIHjDonkgZOG7sW5nIGPDoWMgYmnhu4N1IMSR4buTIGdpw7pwIHRy4buxYyBxdWFuIGjDs2EgdGjDtG5nCnRpbi4g4bueIGLGsOG7m2MgbsOgeSBjaMO6bmcgdGEgc+G6vSB04bqhbyBuaOG7r25nIGJp4buDdSDEkeG7kyBjw7Mgw70gbmdoxKlhIHbhu4EgbeG6t3QgdGjhu5FuZyBrw6ogduG7m2kgZOG7ryBsaeG7h3UgxJHhu4MgaGnhu4N1IHbhu4EgdOG6rXAgxJHhu5FpIHTGsOG7o25nIMSRYW5nIHBow6JuIHTDrWNoLiBDaMO6bmcgYmFvIGfhu5NtCmJp4buDdSDEkeG7kyBIaXN0b2dyYW0gxJHhu4MgeGVtIHPhu7EgcGjDom4gYuG7lSB24buBIGPDoWMgY2hp4buBdSBkw6BpIGPhu6dhIGhvYSwgc+G7rSBk4bulbmcgQm94IFBsb3QgZ2nDunAgdHLhu7FjIHF1YW4gY8OhYyBjb24gc+G7kSB0aOG7kW5nIGvDqi4gU+G7rSBk4bulbmcgU2NhdHRlciBQbG90IMSR4buDCnhlbSBwaMOibiBi4buVIGPhu6dhIGThu68gbGnhu4d1IHRoZW8gMiBjaGnhu4F1IGPhu6UgdGjhu4MuCsSQ4buDIHThuqFvIHJhIGPDoWMgYmnhu4N1IMSR4buTIG7DoHksIGNow7puZyB0YSBjw7MgdGjhu4Mgc+G7rSBk4bulbmcgY8OhYyBjw6J1IGzhu4duaCBjw7Mgc+G6tW4gdHJvbmcgUlN0dWRpbyBob+G6t2MgY8OgaSDEkeG6t3QgdGjDqm0gUGFja2FnZSBHR1Bsb3QgJiBHZ2FsbHkgxJHhu4MgY8OzIHRo4buDIHThuqFvCmPDoWMgYmnhu4N1IMSR4buTIHTGsMahbmcgdOG7sSBuaMawbmcgY8OzIG5oaeG7gXUga2jhuqMgbsSDbmcgdGhheSDEkeG7lWkgxJFp4buBdSBjaOG7iW5oIGPFqW5nIG5oxrAgZOG7hSBkw6BuZyB04bqhbyBoxqFuLgoKYGBge3J9CiNTVEVQIDM6IFZJU1VBTElaRSBEQVRBIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMzLjEgRnVuY3Rpb24gaGlzdCB0byBzaG93IGhpc3RvZ3JhbQpoaXN0KGlyaXMkc2VwYWwubGVuZ3RoKQoKaGlzdChpcmlzJHNlcGFsLmxlbmd0aCwKICAgICBjb2w9J2xpZ2h0IGJsdWUnLAogICAgIG1haW49J0hpc3RvZ3JhbScsCiAgICAgeGxhYj0nU2VwYWwuTGVuZ3RoJywKICAgICB5bGFiPSdGcmVxdWVuY3knKQoKaGlzdChpcmlzJHNlcGFsLmxlbmd0aCwgY29sPSdyZWQnLCBicmVha3M9MjAsIG1haW49J0hpc3RvZ3JhbScsIHhsYWI9J1NpemUnKQpoaXN0KGlyaXMkcGV0YWwubGVuZ3RoLCBjb2w9J2dyZWVuJyxicmVha3M9MzAsIGFkZD1UUlVFKQpsZWdlbmQoJ3RvcHJpZ2h0JywgCiAgICAgICBjKCdTZXBhbCBMZW5ndGgnLCAnUGV0YWwgTGVuZ3RoJyksIAogICAgICAgZmlsbD1jKCdyZWQnLCAnZ3JlZW4nKSkKCiMzLjIgVXNlIGdncGxvdCB0byBjcmVhdGUgY2hhcnRzCmdncGxvdChpcmlzLm1lbHQsIGFlcyh4PXZhbHVlLCBmaWxsPXNpemUpKSArCiAgZ2VvbV9oaXN0b2dyYW0oY29sb3IgPScjZTllY2VmJywgYWxwaGEgPSAwLjYsIHBvc2l0aW9uID0gJ2lkZW50aXR5JykKCmdncGxvdChpcmlzLm1lbHQsIGFlcyh4PXZhbHVlLCBmaWxsPXNpemUpKSArCiAgZ2VvbV9oaXN0b2dyYW0oY29sb3IgPScjZTllY2VmJywgYWxwaGEgPSAwLjYsIHBvc2l0aW9uID0gJ2lkZW50aXR5JykgKwogIGZhY2V0X3dyYXAofnNpemUpCgoKIzMuMyBGdW5jdGlvbiBib3hwbG90IHRvIGNyZWF0ZSBib3hwbG90CmJveHBsb3Qoc2VwYWwubGVuZ3RoIH4gc3BlY2llcywKICAgICAgICBkYXRhID0gaXJpcywKICAgICAgICBtYWluID0gJ1NlcGFsIExlbmd0aCBieSBTcGVjaWVzJywKICAgICAgICB4bGFiID0gJ1NwZWNpZXMnLAogICAgICAgIHlsYWIgPSAnU2VwYWwgTGVuZ3RoJywKICAgICAgICBjb2wgPSAnbGlnaHQgYmx1ZScsCiAgICAgICAgYm9yZGVyID0gJ2JsYWNrJykKCmJveHBsb3QodmFsdWUgfiBzaXplLAogICAgICAgIGRhdGEgPSBpcmlzLm1lbHQsCiAgICAgICAgbWFpbiA9ICdDb21wYXJlIGRpZmZlcmVudCBzaXplJywKICAgICAgICB4bGFiID0gJ1NpemUnLAogICAgICAgIHlsYWIgPSAnVmFsdWUnLAogICAgICAgIGNvbCA9ICdsaWdodCBibHVlJywKICAgICAgICBib3JkZXIgPSAnYmxhY2snKQoKCmdncGxvdChpcmlzLm1lbHQsIGFlcyh4PXNpemUsIHk9dmFsdWUsIGZpbGw9c2l6ZSkpICsKICBnZW9tX2JveHBsb3QoKSsKICBnZW9tX2ppdHRlcihjb2xvciA9ICdibGFjaycsIHNpemUgPSAwLjQsIGFscGhhID0gMC45KQoKIzMuNCBGdW5jdGlvbiBwbG90IHRvIGNyZWF0ZSBzY2F0dGVyIHBsb3QKcGxvdChpcmlzKQoKcGxvdChpcmlzWywxOjRdKQoKcGxvdChpcmlzJHNlcGFsLndpZHRoLCBpcmlzJHNlcGFsLmxlbmd0aCwKICAgICBjb2wgPSAnc3RlZWxibHVlJywKICAgICBtYWluID0gJ1NjYXR0ZXJwbG90JywKICAgICB4bGFiID0gJ1NlcGFsIFdpZHRoJywKICAgICB5bGFiID0gJ1NlcGFsIExlbmd0aCcsCiAgICAgcGNoID0gMTkpCgpwYWlycyhpcmlzWywxOjRdLGNvbD1pcmlzWyw1XSxvbWE9Yyg0LDQsNiwxMikpCnBhcih4cGQ9VFJVRSkKCmdncGxvdChpcmlzLCBhZXMoeD1zZXBhbC5sZW5ndGgsIHk9c2VwYWwud2lkdGgsIGNvbG9yPXNwZWNpZXMpKSArIAogIGdlb21fcG9pbnQoc2l6ZT01KSAKCmdncGFpcnMoaXJpcywKICAgICAgICBjb2x1bW5zID0gMTo0LAogICAgICAgIGFlcyhjb2xvciA9IHNwZWNpZXMsIGFscGhhID0gMC41KSkKCmBgYAoKIyMgCgoKQsaw4bubYyA0OiBLaeG7g20gY2jhu6luZyBnaeG6oyDEkeG7i25oIHbhu5tpIFQtdGVzdAoK4bueIGJ14buVaSB0csaw4bubYyBjaMO6bmcgdGEgxJHDoyBo4buNYyBjw6FjaCBz4butIGThu6VuZyBULXRlc3QgxJHhu4MgeMOhYyDEkeG7i25oIGdp4bqjIHRodXnhur90IGPDsyBjaMOtbmggeMOhYyBoYXkga2jDtG5nLiBUxrDGoW5nIHThu7EgduG7m2kgUlN0dWRpbywgY2jDum5nIHRhIGPDsyB0aOG7gyBk4buFIGTDoG5nCnPhu60gZOG7pW5nIG5oaeG7gXUgbG/huqFpIFQtdGVzdCwg4bufIHBo4bqnbiB0aOG7sWMgaMOgbmggbsOgeSBjaMO6bmcgdGEgc+G6vSBz4butIGThu6VuZyBsb+G6oWkgVC10ZXN0IMSR4buDIHjDoWMgxJHhu4tuaCBnaeG6oyB0aHV54bq/dCBjxqEgYuG6o24gduG7gSBjw6FjIGxv4bqhaSBob2EgbmjGsCBzYXUuCkPDonUgaOG7j2k6IGxp4buHdSBsb8OgaSBzZXRvc2EgdsOgIHZlcnNpY29sb3IgY8OzIGNoaeG7gXUgZMOgaSBjw6FuaCBob2Ega2jDoWMgbmhhdSBoYXkga2jDtG5nPwpHaeG6oyDEkeG7i25oOgrigKIgTnVsbCBIeXBvdGhlc2lzIChIbyk6IFRydW5nIGLDrG5oIGNoaeG7gXUgZMOgaSBjw6FuaCBob2EgMiBsb8OgaSBnaeG7kW5nIG5oYXUgKGtow6FjIGJp4buHdCBtZWFuIGPDoWMgbG/DoGkgPSAwKQrigKIgQWx0ZXJuYXRpdmUgSHlwb3RoZXNpcyAoSGEpOiBUcnVuZyBiw6xuaCBjaGnhu4F1IGTDoGkgY8OhbmggaG9hIDIgbG/DoGkga2jDoWMgbmhhdQoKYGBge3J9CiNTVEVQIDQ6IEhZUE9USEVTSVMgVEVTVElORyBXSVRIIFQtdGVzdCAtLS0tLS0tLS0tLS0tLS0Kc2V0b3NhIDwtIGlyaXNbaXJpcyRzcGVjaWVzID09ICdzZXRvc2EnLF0KdmVyc2ljb2xvciA8LSBpcmlzW2lyaXMkc3BlY2llcyA9PSAndmVyc2ljb2xvcicsXQoKdC50ZXN0KHg9IHNldG9zYSRwZXRhbC5sZW5ndGgsIHkgPSB2ZXJzaWNvbG9yJHBldGFsLmxlbmd0aCkKYGBgCgojIyAKCkLGsOG7m2MgNTogVGjhu7FjIGjDoG5oIHBow6JuIHTDrWNoIHBoxrDGoW5nIHNhaSBBTk9WQQoKVMawxqFuZyB04buxIGNow7puZyB0YSBjxaluZyBjw7MgdGjhu4MgY2jhuqF5IGPDoWMgbcO0IGjDrG5oIHBow6JuIHTDrWNoIEFOT1ZBIHRyb25nIFJTdHVkaW8uIFRyb25nIHBo4bqnbiB0aOG7sWMgaMOgbmggbsOgeSwgY2jDum5nIHRhIHPhur0gY8O5bmcgcGjDom4gdMOtY2ggQU5PVkEKxJHhu4MgeMOhYyDEkeG7i25oIGdp4bqjIHRodXnhur90IHNvIHPDoW5oIHbhu4EgY2hp4buBdSBkw6BpIGPDoW5oIGhvYSBnaeG7r2EgY+G6oyAzIGxvw6BpIGhvYS4KQ8OidSBo4buPaTogbGnhu4d1IDMgbG/DoGkgaG9hIGtow6FjIG5oYXUgY8OzIGNoaeG7gXUgZMOgaSBjw6FuaCBob2Ega2jDoWMgbmhhdSBoYXkga2jDtG5nPwpHaeG6oyDEkeG7i25oOgrigKIgTnVsbCBIeXBvdGhlc2lzIChIbyk6IFRydW5nIGLDrG5oIGNoaeG7gXUgZMOgaSBjw6FuaCBob2EgMyBsb8OgaSBnaeG7kW5nIG5oYXUgKGtow6FjIGJp4buHdCBtZWFuIGPDoWMgbG/DoGkgPSAwKQrigKIgQWx0ZXJuYXRpdmUgSHlwb3RoZXNpcyAoSGEpOiBUcnVuZyBiw6xuaCBjaGnhu4F1IGTDoGkgY8OhbmggaG9hIDMgbG/DoGkga2jDoWMgbmhhdQoKYGBge3J9CiNTVEVQIDU6IEFOQUxZU0lTIFdJVEggQU5PVkEgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCnBldGFsLmxlbmd0aC5hb3YgPC0gYW92KGZvcm11bGEgPSBwZXRhbC5sZW5ndGggfiBzcGVjaWVzLCBkYXRhID0gaXJpcykKCnN1bW1hcnkob2JqZWN0ID0gcGV0YWwubGVuZ3RoLmFvdikKClR1a2V5SFNEKHBldGFsLmxlbmd0aC5hb3YpCmBgYAoKIyMgQsaw4bubYyA2OgoKYGBge3J9CiNTVEVQIDY6IENBVEVHT1JJWkUgU1BFQ0lFUyBXSVRIIEstTUVBTiBDTFVTVEVSSU5HIC0tLS0tLS0tLS0tLS0KaXJpcy50ZXN0IDwtIGlyaXMKCmlyaXMudGVzdCRzcGVjaWVzIDwtIE5VTEwKCmhlYWQoaXJpcy50ZXN0KQoKa21lYW5zLnJlc3VsdCA8LSBrbWVhbnMoaXJpcy50ZXN0LCAzKQoKdGFibGUoaXJpcyRzcGVjaWVzLCBrbWVhbnMucmVzdWx0JGNsdXN0ZXIpCgpwbG90KGlyaXMudGVzdFtjKCdzZXBhbC5sZW5ndGgnLCAnc2VwYWwud2lkdGgnKV0sIGNvbCA9IGttZWFucy5yZXN1bHQkY2x1c3RlcikKCnBsb3RjbHVzdGVyKGlyaXMudGVzdCwga21lYW5zLnJlc3VsdCRjbHVzdGVyKQoKY2x1c3Bsb3QoaXJpcy50ZXN0LCBrbWVhbnMucmVzdWx0JGNsdXN0ZXIsIGNvbG9yID0gVFJVRSwgc2hhZGUgPSBUUlVFKQpgYGAK