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.

plot(cars)

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.

CASE STUDY

Dữ liệu mẫu IRIS Trong phần thực hành buổi 10, chúng ta sẽ sử dụng ngay dữ liệu mẫu tích hợp sẵn trong RStudio đó là dữ liệu Iris. Đây là tập dữ liệu nhỏ và đơn giản giúp dễ hình dung về cách sử dụng RStudio cũng như cách áp dụng các mô hình thống kê cho người bắt đầu. Bộ dữ liệu này được tạo ra khi nhà sinh vật học người Anh Ronald Fisher xuất bản bài báo năm 1936 của ông "Việc sử dụng nhiều phép đo trong các vấn đề phân loại" như một ví dụ về phân tích phân biệt tuyến tính. Đôi khi nó cũng được gọi là tập dữ liệu Anderson's Iris vì Edgar Anderson là người đã thu thập dữ liệu để định lượng sự biến đổi hình thái của hoa Iris theo ba loài có liên quan. Về cơ bản, hai nhà khoa học lâu đời này đã khai sinh ra tập dữ liệu, tập dữ liệu vẫn được sử dụng cho đến ngày nay để học các kiến thức cơ bản về RStudio. Bộ dữ liệu iris đưa ra các phép đo tính bằng cm của các biến chiều dài và chiều rộng của lá đài "Sepal" và chiều dài và chiều rộng của cánh hoa "Petal", tương ứng, đối với 50 bông hoa từ mỗi trong số 3 loài iris. Các loài là Iris setosa, versicolor, và virginica. Trong hình này, bạn có thể thấy chúng ta đang xử lý những gì và hình dạng chính xác của các biến (chiều dài và chiều rộng của đài hoa "Sepal" và chiều dài và chiều rộng của cánh hoa "Petal") mà chúng ta đang đo và bản thân đối tượng:

Ảnh loài hoa

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