1 Giới thiệu

Tidyverse là một hộp công cụ lớn chứa nhiều package nhỏ bên trong và các mảnh nhỏ này kết nối với nhau một cách tiện ích để vận hành một quy trình phân tích dữ liệu hoàn chỉnh, từ khâu nhập dữ liệu vào R (readr,readxl,haven), định hình (tibble), hoán chuyển, chọn lọc và dọn dẹp (tidyr, dplyr,purr) đến bước trình bày và thăm dò dữ liệu bằng đồ họa (ggplot2). Package tidyverse được tạo ra vào năm 2016 bởi Hadley Wickham, một nhân vật quan trọng đã góp phần cho sự thành công của R, cũng là tác giả của những package nổi tiếng như reshape, dplyr, tidyr và ggplot2.

Hiện nay tidyverse chứa 19 packages bao gồm:

library(pander)
library(tidyverse)

tidyverse_packages()
##  [1] "broom"     "dplyr"     "forcats"   "ggplot2"   "haven"    
##  [6] "httr"      "hms"       "jsonlite"  "lubridate" "magrittr" 
## [11] "modelr"    "purrr"     "readr"     "readxl"    "stringr"  
## [16] "tibble"    "rvest"     "tidyr"     "xml2"      "tidyverse"

Trong bài thực hành hôm nay Nhi sẽ giới thiệu với các bạn một bộ phận trong tidyverse có tên là dplyr, và một số chức năng tiện lợi của package dplyr này.

Package dplyr là thành phần rất quan trọng trong hệ sinh thái tidyverse, vì nó cung cấp những function hoán chuyển và thao tác trên dữ liệu sau khi nó đã được tải vào R. Gần như bất cứ lần nào làm việc trên R Nhi cũng đều dùng ít nhất là 2 đến 3 hàm của dplyr cho công việc của mình. Package này được phát hành lần đầu tiên vào ngày 17 tháng 1 năm 2014,và nhanh chóng tạo ra phong trào thay thế baseR bằng dplyr trong cộng đồng. Phiên bản mới nhất hiện nay là dplyr 0.7.4 (phát hành ngày 29/9/2017).

Mặc dù có nhiều tutorial đã được viết cho dplyr trên Rpubs,youtube cũng như sách tham khảo về Data wrangling; nhưng tác giả đều dùng dataset kinh tế, và chỉ minh họa từng function đơn lẻ. Một vài function cũ có từ năm 2014 đã bị thay thế bằng function mới, cũng như có một số function mới được bổ sung vào năm 2016. Do đó, Nhi muốn viết lại bản hướng dẫn này, sử dụng một dataset Y khoa và kết hợp các function lại với nhau để đạt được một số mục tiêu nhất định.

Bộ số liệu sử dụng trong bài là “lung”, trích từ package survival, có nguồn gốc từ một nghiên cứu về Ung thư phổi của nhóm tác giả Loprinzi CL. et al (Journal of Clinical Oncology. 12(3):601-7, 1994). 228 bệnh nhân từ 33 trung tâm khác nhau được khảo sát về Tuổi, Giới tính, Karnofsky performance score, ECOG performance score, calories mổi bữa ăn, số cân nặng sụt giảm trong 6 tháng, thời gian Sống còn và Biến cố tử vong hoặc Censored.

Lưu ý: Tất cả thao tác trong bài chỉ nhằm mục đích trình diễn, nên kết quả chưa được lưu lại như 1 object. Ngoài ra Nhi sử dụng toán tử pipe trong tất cả các quy trình, nên cú pháp của các hàm đã được giản lược

df=read.csv("https://raw.github.com/vincentarelbundock/Rdatasets/master/csv/survival/cancer.csv")%>%as_tibble()

df$inst=paste("Center",df$inst,sep="")

df$status<-recode_factor(df$status,`1` = "Censored", `2` = "Dead")
df$sex<-recode_factor(df$sex,`1` = "Male", `2` = "Female")

head(df)%>%knitr::kable()
X inst time status age sex ph.ecog ph.karno pat.karno meal.cal wt.loss
1 Center3 306 Dead 74 Male 1 90 100 1175 NA
2 Center3 455 Dead 68 Male 0 90 90 1225 15
3 Center3 1010 Censored 56 Male 0 90 90 NA 15
4 Center5 210 Dead 57 Male 1 90 60 1150 11
5 Center1 883 Dead 60 Male 0 100 90 NA 0
6 Center12 1022 Censored 74 Male 1 50 80 513 0

2 Xem cấu trúc dữ liệu: hàm glimpse()

Hàm glimpse() thay thế cho hàm str(), nó mô tả cấu trúc data object , cách trình bày của nó dễ nhìn hơn hàm str(). Hiểu cấu trúc object trong R là một việc quan trọng, vì khi mới tải vào R định dạng của biến số thường được quy ước tự độngbởi package tibble. Đa số trường hợp định dạng là phù hợp, tuy nhiên cũng có khi nó sai, nhất là các định dạng integer, numeric và double, cũng như chr và factor.

glimpse(df)
## Observations: 228
## Variables: 11
## $ X         <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1...
## $ inst      <chr> "Center3", "Center3", "Center3", "Center5", "Center1...
## $ time      <int> 306, 455, 1010, 210, 883, 1022, 310, 361, 218, 166, ...
## $ status    <fctr> Dead, Dead, Censored, Dead, Dead, Censored, Dead, D...
## $ age       <int> 74, 68, 56, 57, 60, 74, 68, 71, 53, 61, 57, 68, 68, ...
## $ sex       <fctr> Male, Male, Male, Male, Male, Male, Female, Female,...
## $ ph.ecog   <int> 1, 0, 0, 1, 0, 1, 2, 2, 1, 2, 1, 2, 1, NA, 1, 1, 1, ...
## $ ph.karno  <int> 90, 90, 90, 90, 100, 50, 70, 60, 70, 70, 80, 70, 90,...
## $ pat.karno <int> 100, 90, 90, 60, 90, 80, 60, 80, 80, 70, 80, 70, 90,...
## $ meal.cal  <int> 1175, 1225, NA, 1150, NA, 513, 384, 538, 825, 271, 1...
## $ wt.loss   <int> NA, 15, 15, 11, 0, 0, 10, 1, 16, 34, 27, 23, 5, 32, ...

3 Đổi tên biến: hàm rename()

Hàm rename cho phép đổi tên biến dễ dàng, như trong thí dụ này, Nhi thay tên biến X bằng ID

rename(df,ID=X)%>%head()
## # A tibble: 6 x 11
##      ID     inst  time   status   age    sex ph.ecog ph.karno pat.karno
##   <int>    <chr> <int>   <fctr> <int> <fctr>   <int>    <int>     <int>
## 1     1  Center3   306     Dead    74   Male       1       90       100
## 2     2  Center3   455     Dead    68   Male       0       90        90
## 3     3  Center3  1010 Censored    56   Male       0       90        90
## 4     4  Center5   210     Dead    57   Male       1       90        60
## 5     5  Center1   883     Dead    60   Male       0      100        90
## 6     6 Center12  1022 Censored    74   Male       1       50        80
## # ... with 2 more variables: meal.cal <int>, wt.loss <int>

4 Lọc dữ liệu với hàm filter()

filter() là hàm quan trọng đầu tiên trong dplyr, khi ta chỉ muốn giữ lại một số rows trong data nếu chúng thỏa mãn một điều kiện nào đó. Trước kia ta hay dùng subset() hoặc [] để làm việc này, nhưng từ khi có dplyr thì việc lọc dữ liệu dễ dàng hơn rất nhiều:

Thí dụ: Lọc ra những bệnh nhân có thời gian sống dài hơn 1 năm, và nhỏ hơn 60 tuổi:

filter(df,time>=365 & age < 60)
## # A tibble: 26 x 11
##        X     inst  time   status   age    sex ph.ecog ph.karno pat.karno
##    <int>    <chr> <int>   <fctr> <int> <fctr>   <int>    <int>     <int>
##  1     3  Center3  1010 Censored    56   Male       0       90        90
##  2    15 Center12   567     Dead    57   Male       1       80        70
##  3    23 Center11   624     Dead    50   Male       1       70        80
##  4    24 Center15   371     Dead    58   Male       0       90       100
##  5    29 Center13   390     Dead    53   Male       1       80        70
##  6    33  Center7   533     Dead    48   Male       2       60        80
##  7    43 Center10   433     Dead    59 Female       0       90        90
##  8    50 Center22   765     Dead    50 Female       1       90       100
##  9    55  Center6   689     Dead    59   Male       1       90        80
## 10    59  Center3   687     Dead    58 Female       1       80        80
## # ... with 16 more rows, and 2 more variables: meal.cal <int>,
## #   wt.loss <int>

Hay lọc ra những bệnh nhân có điểm Karno tự đánh giá từ 0 đến 50:

filter(df, pat.karno %in% c(0:50) )
## # A tibble: 8 x 11
##       X     inst  time   status   age    sex ph.ecog ph.karno pat.karno
##   <int>    <chr> <int>   <fctr> <int> <fctr>   <int>    <int>     <int>
## 1    30  Center1    12     Dead    74   Male       2       70        50
## 2    36  Center1   122     Dead    62 Female       2       50        50
## 3    39  Center1    93     Dead    74   Male       2       50        40
## 4    66  Center3    65     Dead    68   Male       2       70        50
## 5   110  Center3   351     Dead    75 Female       2       60        50
## 6   136 Center22   511 Censored    74 Female       2       60        40
## 7   155 Center32   156     Dead    55   Male       2       70        30
## 8   219 Center11   211 Censored    70 Female       2       70        30
## # ... with 2 more variables: meal.cal <int>, wt.loss <int>

5 Chọn biến số: Hàm select()

Hàm select() cho phép ta trích một phần dữ liệu chỉ chứa những biến số mà ta quan tâm (hay ngược lại, loại bỏ những biến số không cần thiết). Khi kết hợp với hàm contains, ta còn có thể chọn hàng loạt biến có tên chứa 1 chuỗi kí tự nào đó

Thí dụ:

Chọn tất cả biến số có chứa chữ “karno”:

select(df,contains("karno")) %>%head()
## # A tibble: 6 x 2
##   ph.karno pat.karno
##      <int>     <int>
## 1       90       100
## 2       90        90
## 3       90        90
## 4       90        60
## 5      100        90
## 6       50        80

Loại bỏ biến ECOG và tất cả biến có chứa chữ “karno”:

select(df,-contains("karno"),-ph.ecog)
## # A tibble: 228 x 8
##        X     inst  time   status   age    sex meal.cal wt.loss
##    <int>    <chr> <int>   <fctr> <int> <fctr>    <int>   <int>
##  1     1  Center3   306     Dead    74   Male     1175      NA
##  2     2  Center3   455     Dead    68   Male     1225      15
##  3     3  Center3  1010 Censored    56   Male       NA      15
##  4     4  Center5   210     Dead    57   Male     1150      11
##  5     5  Center1   883     Dead    60   Male       NA       0
##  6     6 Center12  1022 Censored    74   Male      513       0
##  7     7  Center7   310     Dead    68 Female      384      10
##  8     8 Center11   361     Dead    71 Female      538       1
##  9     9  Center1   218     Dead    53   Male      825      16
## 10    10  Center7   166     Dead    61   Male      271      34
## # ... with 218 more rows

Chọn riêng 4 biến từ time cho đến sex:

select(df,time:sex)%>%head()
## # A tibble: 6 x 4
##    time   status   age    sex
##   <int>   <fctr> <int> <fctr>
## 1   306     Dead    74   Male
## 2   455     Dead    68   Male
## 3  1010 Censored    56   Male
## 4   210     Dead    57   Male
## 5   883     Dead    60   Male
## 6  1022 Censored    74   Male

6 Xếp thứ tự : hàm range()

Thí dụ: Ta muốn lọc riêng những bệnh nhân sống lâu hơn 1 năm, dưới 50 tuổi, sau đó xếp thứ tự thời gian sống còn giảm dần.

df%>%filter(time>=365 & age < 50)%>%
  select(X,time)%>%
  arrange(desc(time))
## # A tibble: 5 x 2
##       X  time
##   <int> <int>
## 1    85   806
## 2    89   740
## 3   130   543
## 4    33   533
## 5   174   382

Hoặc tăng dần:

df%>%filter(time>=365 & age < 50)%>%
  select(X,time)%>%
  arrange(time)
## # A tibble: 5 x 2
##       X  time
##   <int> <int>
## 1   174   382
## 2    33   533
## 3   130   543
## 4    89   740
## 5    85   806

Như bạn thấy, Nhi vừa kết hợp 3 hàm filter, select và range với nhau.

7 Tạo biến số mới: Hàm mutate()

mutate() là một hàm cực kì tiện ích trong khi phân tích dữ liệu, vì nó cho phép bạn tạo ra biến số mới tùy thích trong dataframe.

Bạn có thể dùng mutate cho 2 việc: 1) Hoán chuyển biến số hiện có và 2) tạo ra biến số mới hoàn toàn.

Thí dụ: Chuyển thời gian sống còn từ ngày sang tháng:

df%>%mutate(timeMonth=round(time/30,2))%>%select(time,timeMonth)%>%head()
## # A tibble: 6 x 2
##    time timeMonth
##   <int>     <dbl>
## 1   306     10.20
## 2   455     15.17
## 3  1010     33.67
## 4   210      7.00
## 5   883     29.43
## 6  1022     34.07

Hoặc tạo ra 1 biến mới có tên là karnomean, là trung bình của pat.karno và ph.karno, sau đó tạo ra 1 biến nhị phân cho biết bệnh nhân sống lâu hơn 2 năm, và 1 biến khác là Dead mã hóa nhị phân 1/0

df%>%mutate(karnomean=(ph.karno+pat.karno)/2,
            Surv2yr=if_else(time>365*2,"Yes","No"),
            Dead=if_else(status=="Censored",0,1))%>%
  select(contains("karno"),karnomean,time,status,Surv2yr,Dead)%>%
  glimpse()
## Observations: 228
## Variables: 7
## $ ph.karno  <int> 90, 90, 90, 90, 100, 50, 70, 60, 70, 70, 80, 70, 90,...
## $ pat.karno <int> 100, 90, 90, 60, 90, 80, 60, 80, 80, 70, 80, 70, 90,...
## $ karnomean <dbl> 95, 90, 90, 75, 95, 65, 65, 70, 75, 70, 80, 70, 90, ...
## $ time      <int> 306, 455, 1010, 210, 883, 1022, 310, 361, 218, 166, ...
## $ status    <fctr> Dead, Dead, Censored, Dead, Dead, Censored, Dead, D...
## $ Surv2yr   <chr> "No", "No", "Yes", "No", "Yes", "Yes", "No", "No", "...
## $ Dead      <dbl> 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1...

8 Thống kê mô tả theo phân nhóm với hàm summarise()

dplyr cung cấp một số hàm tiện ích khác là summarise,summarise_at() và summarise_if(); khi kết hợp chúng với hàm group_by(), bạn có thể tạo ra những bảng thống kê mô tả cho từng phân nhóm tùy thích:

Hàm summarise dùng được hầu hết các hàm thống kê đơn giản trong R, với điều kiện mỗi hàm chỉ xuất ra 1 kết quả, thí dụ nếu ta muốn tính trung bình cân nặng sụt giảm trong 6 tháng cho 2 phân nhóm Censored và Dead:

df %>%
  group_by(status) %>%
  summarise(Mean_Weight_loss = mean(wt.loss, na.rm=TRUE))%>%pander()
status Mean_Weight_loss
Censored 9.113
Dead 10.12

Hoặc trung vị thời gian sống còn cho từng bệnh viện:

df %>%
  group_by(inst) %>%
  summarise_at("time",funs(medTime=median),na.rm=T)%>%pander()
inst medTime
Center1 221.5
Center10 275.5
Center11 216.5
Center12 285
Center13 298
Center15 274
Center16 279
Center2 197
Center21 183
Center22 428
Center26 295.5
Center3 305
Center32 240
Center33 139
Center4 433.5
Center5 224
Center6 225.5
Center7 289.5
CenterNA 329

Hàm summarise_if() cho phép thực hiện cùng 1 hàm trên hàng loạt biến nếu chúng thỏa điều kiện, thí dụ là biến kiểu số.

Thí dụ ta tính thời gian trung bình cho tất cả biến numeric và cho riêng mỗi phân nhóm censored và dead

df %>%select(-X)%>%
  group_by(status) %>%
  summarise_if(is.numeric,mean,na.rm=T)%>%pander()
status time age ph.ecog ph.karno pat.karno meal.cal wt.loss
Censored 363.5 60.25 0.6825 85.56 83.97 912.8 9.113
Dead 283 63.28 1.055 80.55 78.4 934.4 10.12

Nhi có thể làm cầu kì hơn khi kết hợp thêm nhiều hàm , thí dụ Mean, Sd, quantile…

df %>%
  group_by(status) %>%
  summarise_at("time",funs(Mean=mean,
                           Median=median,
                           SD=sd,
                    LL=quantile(.,probs=0.05),
                    UL=quantile(.,probs=0.95))
               )%>%pander()
status Mean Median SD LL UL
Censored 363.5 284 221.1 174.1 838.1
Dead 283 226 202.8 26.8 701.8

Bạn có thể tạo bảng chéo nữa:

df %>%
  group_by(status) %>%
  select(inst)%>%table()
##           inst
## status     Center1 Center10 Center11 Center12 Center13 Center15 Center16
##   Censored       9        0        7        5        8        2        4
##   Dead          27        4       11       18       12        4       12
##           inst
## status     Center2 Center21 Center22 Center26 Center3 Center32 Center33
##   Censored       1        2        4        4       4        5        1
##   Dead           4       11       13        2      15        2        1
##           inst
## status     Center4 Center5 Center6 Center7 CenterNA
##   Censored       0       3       2       2        0
##   Dead           4       6      12       6        1

dplyr còn cung cấp hàm n() để đếm tần số của những biến categorical. Khi kết hợp với hàm summarise, group_by và mutate bạn có thể làm 1 thống kê mô tả đơn giản cho một biến số trong dữ liệu:

df%>%group_by(inst)%>%
  summarise(Frequency=n())%>%
  mutate(Proportion=round(100*Frequency/sum(Frequency),2))%>%pander()
inst Frequency Proportion
Center1 36 15.79
Center10 4 1.75
Center11 18 7.89
Center12 23 10.09
Center13 20 8.77
Center15 6 2.63
Center16 16 7.02
Center2 5 2.19
Center21 13 5.7
Center22 17 7.46
Center26 6 2.63
Center3 19 8.33
Center32 7 3.07
Center33 2 0.88
Center4 4 1.75
Center5 9 3.95
Center6 14 6.14
Center7 8 3.51
CenterNA 1 0.44

Một bảng chéo khác được tạo ra bằng cách kết hợp hàm group_by, summarise và spread

df%>%
  group_by(inst,status) %>%
  summarise(frequency = n()) %>% 
  spread(status,frequency) %>%
  pander
inst Censored Dead
Center1 9 27
Center10 NA 4
Center11 7 11
Center12 5 18
Center13 8 12
Center15 2 4
Center16 4 12
Center2 1 4
Center21 2 11
Center22 4 13
Center26 4 2
Center3 4 15
Center32 5 2
Center33 1 1
Center4 NA 4
Center5 3 6
Center6 2 12
Center7 2 6
CenterNA NA 1

9 Chọn mẫu ngẫu nhiên và cắt dữ liệu

dplyr cung cấp hàm sample_frac() và sample_n(), cho phép thực hiện việc chọn mẫu ngẫu nhiên trên dữ liệu theo tỉ lệ và theo kích thước tùy chọn:

Thí dụ: Chọn mẫu ngẫu nhiên 10% dữ liệu

sample_frac(df,0.1)
## # A tibble: 23 x 11
##        X     inst  time   status   age    sex ph.ecog ph.karno pat.karno
##    <int>    <chr> <int>   <fctr> <int> <fctr>   <int>    <int>     <int>
##  1    17 Center22   613     Dead    70   Male       1       90       100
##  2    87 Center22   641     Dead    62 Female       1       80        80
##  3   192 Center16    81     Dead    52   Male       2       60        70
##  4   107 Center26   529 Censored    54 Female       1       80       100
##  5    90  Center1   163     Dead    72   Male       2       70        70
##  6   226 Center32   105 Censored    75 Female       2       60        70
##  7   201 Center15    79     Dead    64 Female       1       90        90
##  8    22  Center6    81     Dead    49 Female       0      100        70
##  9    86  Center6   284     Dead    71   Male       1       80        90
## 10   102  Center6   450     Dead    69 Female       1       80        90
## # ... with 13 more rows, and 2 more variables: meal.cal <int>,
## #   wt.loss <int>

Chọn 10 cases ngẫu nhiên:

sample_n(df,10)
## # A tibble: 10 x 11
##        X     inst  time   status   age    sex ph.ecog ph.karno pat.karno
##    <int>    <chr> <int>   <fctr> <int> <fctr>   <int>    <int>     <int>
##  1   107 Center26   529 Censored    54 Female       1       80       100
##  2    31 Center12   473     Dead    69 Female       1       90        90
##  3    98 Center12   310     Dead    71   Male       1       90       100
##  4    72  Center3   305     Dead    48 Female       1       80        90
##  5   159 Center12   179     Dead    63   Male       1       80        70
##  6   108  Center1    11     Dead    67   Male       1       90        90
##  7    11  Center6   170     Dead    57   Male       1       80        80
##  8   156 CenterNA   329     Dead    69   Male       2       70        80
##  9   122 Center26   199     Dead    60 Female       2       70        80
## 10   133  Center6   353     Dead    47   Male       0      100        90
## # ... with 2 more variables: meal.cal <int>, wt.loss <int>

Chọn 50 cases từ hàng 100 đến 150:

slice(df,100:150)
## # A tibble: 51 x 11
##        X     inst  time   status   age    sex ph.ecog ph.karno pat.karno
##    <int>    <chr> <int>   <fctr> <int> <fctr>   <int>    <int>     <int>
##  1   100  Center3   166     Dead    70 Female       0       90        70
##  2   101  Center1   559 Censored    58 Female       0      100       100
##  3   102  Center6   450     Dead    69 Female       1       80        90
##  4   103 Center13   364     Dead    56   Male       1       70        80
##  5   104  Center6   107     Dead    63   Male       1       90        70
##  6   105 Center13   177     Dead    59   Male       2       50        NA
##  7   106 Center12   156     Dead    66   Male       1       80        90
##  8   107 Center26   529 Censored    54 Female       1       80       100
##  9   108  Center1    11     Dead    67   Male       1       90        90
## 10   109 Center21   429     Dead    55   Male       1      100        80
## # ... with 41 more rows, and 2 more variables: meal.cal <int>,
## #   wt.loss <int>

Ta làm phức tạp hơn 1 chút: Chọn 3 case đầu tiên từ mỗi phân nhóm Censore hay Dead

df%>%group_by(status)%>%slice(1:3)
## # A tibble: 6 x 11
## # Groups:   status [2]
##       X     inst  time   status   age    sex ph.ecog ph.karno pat.karno
##   <int>    <chr> <int>   <fctr> <int> <fctr>   <int>    <int>     <int>
## 1     3  Center3  1010 Censored    56   Male       0       90        90
## 2     6 Center12  1022 Censored    74   Male       1       50        80
## 3    38 Center15   965 Censored    66 Female       1       70        90
## 4     1  Center3   306     Dead    74   Male       1       90       100
## 5     2  Center3   455     Dead    68   Male       0       90        90
## 6     4  Center5   210     Dead    57   Male       1       90        60
## # ... with 2 more variables: meal.cal <int>, wt.loss <int>

Hoặc chọn 10% từ mỗi phân nhóm Censore/Dead

df%>%group_by(status)%>%sample_frac(0.1)
## # A tibble: 22 x 11
## # Groups:   status [2]
##        X     inst  time   status   age    sex ph.ecog ph.karno pat.karno
##    <int>    <chr> <int>   <fctr> <int> <fctr>   <int>    <int>     <int>
##  1   157 Center26   364 Censored    68 Female       1       90        90
##  2   134 Center13   511 Censored    55 Female       1       80        70
##  3   219 Center11   211 Censored    70 Female       2       70        30
##  4   217 Center13   192 Censored    41 Female       1       90        80
##  5   130 Center12   543 Censored    48 Female       0       90        60
##  6   174 Center16   382 Censored    43 Female       0      100        90
##  7   192 Center16    81     Dead    52   Male       2       60        70
##  8   110  Center3   351     Dead    75 Female       2       60        50
##  9   195 Center22   269     Dead    71   Male       1       90        90
## 10    29 Center13   390     Dead    53   Male       1       80        70
## # ... with 12 more rows, and 2 more variables: meal.cal <int>,
## #   wt.loss <int>

10 Tổng kết

Nhi vừa trình bày một số tính năng mà dplyr package thực hiện được. Như ta thấy, những công việc này có thể cần bất cứ khi nào trong quá trình làm việc trên dữ liệu, đặc biệt là việc lọc biến số và tạo biến mới. Những hàm trong dplyr còn cực kì hữu ích khi bạn muốn vẽ biểu đồ từ dữ liệu gốc hay từ output object của một package khác, như bootstrap, mô hình… chẳng hạn. Sử dụng thông thạo dplyr có nghĩa là bạn đã có thể từ bỏ hoàn toàn những software dạng bảng tính như Excel, SPSS, Medcalc và hoàn toàn dùng R cho công việc của mình.

LS0tDQp0aXRsZTogIkdp4bubaSB0aGnhu4d1IHBhY2thZ2UgZHBseXIiDQpzdWJ0aXRsZTogIlRoYW8gdMOhYyB0csOqbiBk4buvIGxp4buHdSINCmF1dGhvcjogIkzDqiBOZ+G7jWMgS2jhuqMgTmhpIg0KZGF0ZTogIjI5IFRow6FuZyAxMSAyMDE3Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogImRlZmF1bHQiDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQotLS0NCg0KYGBge3Igc2V0dXAsaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShwYW5kZXIpDQpgYGANCg0KIVtdKGRwbHlydHV0LnBuZykNCg0KIyBHaeG7m2kgdGhp4buHdQ0KDQpUaWR5dmVyc2UgbMOgIG3hu5l0IGjhu5lwIGPDtG5nIGPhu6UgbOG7m24gY2jhu6lhIG5oaeG7gXUgcGFja2FnZSBuaOG7jyBiw6puIHRyb25nIHbDoCBjw6FjIG3huqNuaCBuaOG7jyBuw6B5IGvhur90IG7hu5FpIHbhu5tpIG5oYXUgbeG7mXQgY8OhY2ggdGnhu4duIMOtY2ggxJHhu4MgduG6rW4gaMOgbmggbeG7mXQgcXV5IHRyw6xuaCBwaMOibiB0w61jaCBk4buvIGxp4buHdSBob8OgbiBjaOG7iW5oLCB04burIGtow6J1IG5o4bqtcCBk4buvIGxp4buHdSB2w6BvIFIgKHJlYWRyLHJlYWR4bCxoYXZlbiksIMSR4buLbmggaMOsbmggKHRpYmJsZSksIGhvw6FuIGNodXnhu4NuLCBjaOG7jW4gbOG7jWMgdsOgIGThu41uIGThurlwICh0aWR5ciwgZHBseXIscHVycikgxJHhur9uIGLGsOG7m2MgdHLDrG5oIGLDoHkgdsOgIHRoxINtIGTDsiBk4buvIGxp4buHdSBi4bqxbmcgxJHhu5MgaOG7jWEgKGdncGxvdDIpLiBQYWNrYWdlIHRpZHl2ZXJzZSDEkcaw4bujYyB04bqhbyByYSB2w6BvIG7Eg20gMjAxNiBi4bufaSBIYWRsZXkgV2lja2hhbSwgbeG7mXQgbmjDom4gduG6rXQgcXVhbiB0cuG7jW5nIMSRw6MgZ8OzcCBwaOG6p24gY2hvIHPhu7EgdGjDoG5oIGPDtG5nIGPhu6dhIFIsIGPFqW5nIGzDoCB0w6FjIGdp4bqjIGPhu6dhIG5o4buvbmcgcGFja2FnZSBu4buVaSB0aeG6v25nIG5oxrAgcmVzaGFwZSwgZHBseXIsIHRpZHlyIHbDoCBnZ3Bsb3QyLiANCg0KSGnhu4duIG5heSB0aWR5dmVyc2UgY2jhu6lhIDE5IHBhY2thZ2VzIGJhbyBn4buTbToNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHBhbmRlcikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KDQp0aWR5dmVyc2VfcGFja2FnZXMoKQ0KYGBgDQoNClRyb25nIGLDoGkgdGjhu7FjIGjDoG5oIGjDtG0gbmF5IE5oaSBz4bq9IGdp4bubaSB0aGnhu4d1IHbhu5tpIGPDoWMgYuG6oW4gbeG7mXQgYuG7mSBwaOG6rW4gdHJvbmcgdGlkeXZlcnNlIGPDsyB0w6puIGzDoCBkcGx5ciwgdsOgIG3hu5l0IHPhu5EgY2jhu6ljIG7Eg25nIHRp4buHbiBs4bujaSBj4bunYSBwYWNrYWdlIGRwbHlyIG7DoHkuIA0KDQpQYWNrYWdlIGRwbHlyIGzDoCB0aMOgbmggcGjhuqduIHLhuqV0IHF1YW4gdHLhu41uZyB0cm9uZyBo4buHIHNpbmggdGjDoWkgdGlkeXZlcnNlLCB2w6wgbsOzIGN1bmcgY+G6pXAgbmjhu69uZyBmdW5jdGlvbiBob8OhbiBjaHV54buDbiB2w6AgdGhhbyB0w6FjIHRyw6puIGThu68gbGnhu4d1IHNhdSBraGkgbsOzIMSRw6MgxJHGsOG7o2MgdOG6o2kgdsOgbyBSLiBH4bqnbiBuaMawIGLhuqV0IGPhu6kgbOG6p24gbsOgbyBsw6BtIHZp4buHYyB0csOqbiBSIE5oaSBjxaluZyDEkeG7gXUgZMO5bmcgw610IG5o4bqldCBsw6AgMiDEkeG6v24gMyBow6BtIGPhu6dhIGRwbHlyIGNobyBjw7RuZyB2aeG7h2MgY+G7p2EgbcOsbmguIFBhY2thZ2UgbsOgeSDEkcaw4bujYyBwaMOhdCBow6BuaCBs4bqnbiDEkeG6p3UgdGnDqm4gdsOgbyBuZ8OgeSAxNyB0aMOhbmcgMSBuxINtIDIwMTQsdsOgIG5oYW5oIGNow7NuZyB04bqhbyByYSBwaG9uZyB0csOgbyB0aGF5IHRo4bq/IGJhc2VSIGLhurFuZyBkcGx5ciB0cm9uZyBj4buZbmcgxJHhu5NuZy4gUGhpw6puIGLhuqNuIG3hu5tpIG5o4bqldCBoaeG7h24gbmF5IGzDoCBkcGx5ciAwLjcuNCAocGjDoXQgaMOgbmggbmfDoHkgMjkvOS8yMDE3KS4gDQoNCk3hurdjIGTDuSBjw7Mgbmhp4buBdSB0dXRvcmlhbCDEkcOjIMSRxrDhu6NjIHZp4bq/dCBjaG8gZHBseXIgdHLDqm4gUnB1YnMseW91dHViZSBjxaluZyBuaMawIHPDoWNoIHRoYW0ga2jhuqNvIHbhu4EgRGF0YSB3cmFuZ2xpbmc7IG5oxrBuZyB0w6FjIGdp4bqjIMSR4buBdSBkw7luZyBkYXRhc2V0IGtpbmggdOG6vywgdsOgIGNo4buJIG1pbmggaOG7jWEgdOG7q25nIGZ1bmN0aW9uIMSRxqFuIGzhursuIE3hu5l0IHbDoGkgZnVuY3Rpb24gY8WpIGPDsyB04burIG7Eg20gMjAxNCDEkcOjIGLhu4sgdGhheSB0aOG6vyBi4bqxbmcgZnVuY3Rpb24gbeG7m2ksIGPFqW5nIG5oxrAgY8OzIG3hu5l0IHPhu5EgZnVuY3Rpb24gbeG7m2kgxJHGsOG7o2MgYuG7lSBzdW5nIHbDoG8gbsSDbSAyMDE2LiBEbyDEkcOzLCBOaGkgbXXhu5FuIHZp4bq/dCBs4bqhaSBi4bqjbiBoxrDhu5tuZyBk4bqrbiBuw6B5LCBz4butIGThu6VuZyBt4buZdCBkYXRhc2V0IFkga2hvYSB2w6Aga+G6v3QgaOG7o3AgY8OhYyBmdW5jdGlvbiBs4bqhaSB24bubaSBuaGF1IMSR4buDIMSR4bqhdCDEkcaw4bujYyBt4buZdCBz4buRIG3hu6VjIHRpw6p1IG5o4bqldCDEkeG7i25oLg0KDQpC4buZIHPhu5EgbGnhu4d1IHPhu60gZOG7pW5nIHRyb25nIGLDoGkgbMOgICJsdW5nIiwgdHLDrWNoIHThu6sgcGFja2FnZSBzdXJ2aXZhbCwgY8OzIG5ndeG7k24gZ+G7kWMgdOG7qyBt4buZdCBuZ2hpw6puIGPhu6l1IHbhu4EgVW5nIHRoxrAgcGjhu5VpIGPhu6dhIG5ow7NtIHTDoWMgZ2nhuqMgTG9wcmluemkgQ0wuIGV0IGFsIChKb3VybmFsIG9mIENsaW5pY2FsIE9uY29sb2d5LiAxMigzKTo2MDEtNywgMTk5NCkuIDIyOCBi4buHbmggbmjDom4gdOG7qyAzMyB0cnVuZyB0w6JtIGtow6FjIG5oYXUgxJHGsOG7o2Mga2jhuqNvIHPDoXQgduG7gSBUdeG7lWksIEdp4bubaSB0w61uaCwgS2Fybm9mc2t5IHBlcmZvcm1hbmNlIHNjb3JlLCBFQ09HIHBlcmZvcm1hbmNlIHNjb3JlLCBjYWxvcmllcyBt4buVaSBi4buvYSDEg24sIHPhu5EgY8OibiBu4bq3bmcgc+G7pXQgZ2nhuqNtIHRyb25nIDYgdGjDoW5nLCB0aOG7nWkgZ2lhbiBT4buRbmcgY8OybiB2w6AgQmnhur9uIGPhu5EgdOG7rSB2b25nIGhv4bq3YyBDZW5zb3JlZC4NCg0KTMawdSDDvTogVOG6pXQgY+G6oyB0aGFvIHTDoWMgdHJvbmcgYsOgaSBjaOG7iSBuaOG6sW0gbeG7pWMgxJHDrWNoIHRyw6xuaCBkaeG7hW4sIG7Dqm4ga+G6v3QgcXXhuqMgY2jGsGEgxJHGsOG7o2MgbMawdSBs4bqhaSBuaMawIDEgb2JqZWN0LiBOZ2/DoGkgcmEgTmhpIHPhu60gZOG7pW5nIHRvw6FuIHThu60gcGlwZSB0cm9uZyB04bqldCBj4bqjIGPDoWMgcXV5IHRyw6xuaCwgbsOqbiBjw7ogcGjDoXAgY+G7p2EgY8OhYyBow6BtIMSRw6MgxJHGsOG7o2MgZ2nhuqNuIGzGsOG7o2MNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpkZj1yZWFkLmNzdigiaHR0cHM6Ly9yYXcuZ2l0aHViLmNvbS92aW5jZW50YXJlbGJ1bmRvY2svUmRhdGFzZXRzL21hc3Rlci9jc3Yvc3Vydml2YWwvY2FuY2VyLmNzdiIpJT4lYXNfdGliYmxlKCkNCg0KZGYkaW5zdD1wYXN0ZSgiQ2VudGVyIixkZiRpbnN0LHNlcD0iIikNCg0KZGYkc3RhdHVzPC1yZWNvZGVfZmFjdG9yKGRmJHN0YXR1cyxgMWAgPSAiQ2Vuc29yZWQiLCBgMmAgPSAiRGVhZCIpDQpkZiRzZXg8LXJlY29kZV9mYWN0b3IoZGYkc2V4LGAxYCA9ICJNYWxlIiwgYDJgID0gIkZlbWFsZSIpDQoNCmhlYWQoZGYpJT4la25pdHI6OmthYmxlKCkNCmBgYA0KDQojIFhlbSBj4bqldSB0csO6YyBk4buvIGxp4buHdTogaMOgbSBnbGltcHNlKCkNCg0KSMOgbSBnbGltcHNlKCkgdGhheSB0aOG6vyBjaG8gaMOgbSBzdHIoKSwgbsOzIG3DtCB04bqjIGPhuqV1IHRyw7pjIGRhdGEgb2JqZWN0ICwgY8OhY2ggdHLDrG5oIGLDoHkgY+G7p2EgbsOzIGThu4UgbmjDrG4gaMahbiBow6BtIHN0cigpLiBIaeG7g3UgY+G6pXUgdHLDumMgb2JqZWN0IHRyb25nIFIgbMOgIG3hu5l0IHZp4buHYyBxdWFuIHRy4buNbmcsIHbDrCBraGkgbeG7m2kgdOG6o2kgdsOgbyBSIMSR4buLbmggZOG6oW5nIGPhu6dhIGJp4bq/biBz4buRIHRoxrDhu51uZyDEkcaw4bujYyBxdXkgxrDhu5tjIHThu7EgxJHhu5luZ2Lhu59pIHBhY2thZ2UgdGliYmxlLiDEkGEgc+G7kSB0csaw4budbmcgaOG7o3AgxJHhu4tuaCBk4bqhbmcgbMOgIHBow7kgaOG7o3AsIHR1eSBuaGnDqm4gY8WpbmcgY8OzIGtoaSBuw7Mgc2FpLCBuaOG6pXQgbMOgIGPDoWMgxJHhu4tuaCBk4bqhbmcgaW50ZWdlciwgbnVtZXJpYyB2w6AgZG91YmxlLCBjxaluZyBuaMawIGNociB2w6AgZmFjdG9yLg0KDQpgYGB7cn0NCmdsaW1wc2UoZGYpDQpgYGANCg0KIyDEkOG7lWkgdMOqbiBiaeG6v246IGjDoG0gcmVuYW1lKCkNCg0KSMOgbSByZW5hbWUgY2hvIHBow6lwIMSR4buVaSB0w6puIGJp4bq/biBk4buFIGTDoG5nLCBuaMawIHRyb25nIHRow60gZOG7pSBuw6B5LCBOaGkgdGhheSB0w6puIGJp4bq/biBYIGLhurFuZyBJRA0KDQpgYGB7cn0NCnJlbmFtZShkZixJRD1YKSU+JWhlYWQoKQ0KYGBgDQoNCiMgTOG7jWMgZOG7ryBsaeG7h3UgduG7m2kgaMOgbSBmaWx0ZXIoKQ0KDQpmaWx0ZXIoKSBsw6AgaMOgbSBxdWFuIHRy4buNbmcgxJHhuqd1IHRpw6puIHRyb25nIGRwbHlyLCBraGkgdGEgY2jhu4kgbXXhu5FuIGdp4buvIGzhuqFpIG3hu5l0IHPhu5Egcm93cyB0cm9uZyBkYXRhIG7hur91IGNow7puZyB0aOG7j2EgbcOjbiBt4buZdCDEkWnhu4F1IGtp4buHbiBuw6BvIMSRw7MuIFRyxrDhu5tjIGtpYSB0YSBoYXkgZMO5bmcgc3Vic2V0KCkgaG/hurdjIFtdIMSR4buDIGzDoG0gdmnhu4djIG7DoHksIG5oxrBuZyB04burIGtoaSBjw7MgZHBseXIgdGjDrCB2aeG7h2MgbOG7jWMgZOG7ryBsaeG7h3UgZOG7hSBkw6BuZyBoxqFuIHLhuqV0IG5oaeG7gXU6DQoNClRow60gZOG7pTogTOG7jWMgcmEgbmjhu69uZyBi4buHbmggbmjDom4gY8OzIHRo4budaSBnaWFuIHPhu5FuZyBkw6BpIGjGoW4gMSBuxINtLCB2w6Agbmjhu48gaMahbiA2MCB0deG7lWk6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZmlsdGVyKGRmLHRpbWU+PTM2NSAmIGFnZSA8IDYwKQ0KYGBgDQoNCkhheSBs4buNYyByYSBuaOG7r25nIGLhu4duaCBuaMOibiBjw7MgxJFp4buDbSBLYXJubyB04buxIMSRw6FuaCBnacOhIHThu6sgMCDEkeG6v24gNTA6DQoNCmBgYHtyfQ0KZmlsdGVyKGRmLCBwYXQua2Fybm8gJWluJSBjKDA6NTApICkNCg0KYGBgDQoNCiMgQ2jhu41uIGJp4bq/biBz4buROiBIw6BtIHNlbGVjdCgpDQoNCkjDoG0gc2VsZWN0KCkgY2hvIHBow6lwIHRhIHRyw61jaCBt4buZdCBwaOG6p24gZOG7ryBsaeG7h3UgY2jhu4kgY2jhu6lhIG5o4buvbmcgYmnhur9uIHPhu5EgbcOgIHRhIHF1YW4gdMOibSAoaGF5IG5nxrDhu6NjIGzhuqFpLCBsb+G6oWkgYuG7jyBuaOG7r25nIGJp4bq/biBz4buRIGtow7RuZyBj4bqnbiB0aGnhur90KS4gS2hpIGvhur90IGjhu6NwIHbhu5tpIGjDoG0gY29udGFpbnMsIHRhIGPDsm4gY8OzIHRo4buDIGNo4buNbiBow6BuZyBsb+G6oXQgYmnhur9uIGPDsyB0w6puIGNo4bupYSAxIGNodeG7l2kga8OtIHThu7EgbsOgbyDEkcOzDQoNClRow60gZOG7pToNCg0KQ2jhu41uIHThuqV0IGPhuqMgYmnhur9uIHPhu5EgY8OzIGNo4bupYSBjaOG7ryAia2Fybm8iOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnNlbGVjdChkZixjb250YWlucygia2Fybm8iKSkgJT4laGVhZCgpDQoNCmBgYA0KDQpMb+G6oWkgYuG7jyBiaeG6v24gRUNPRyB2w6AgdOG6pXQgY+G6oyBiaeG6v24gY8OzIGNo4bupYSBjaOG7ryAia2Fybm8iOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnNlbGVjdChkZiwtY29udGFpbnMoImthcm5vIiksLXBoLmVjb2cpDQpgYGANCg0KQ2jhu41uIHJpw6puZyA0IGJp4bq/biB04burIHRpbWUgY2hvIMSR4bq/biBzZXg6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0Kc2VsZWN0KGRmLHRpbWU6c2V4KSU+JWhlYWQoKQ0KYGBgDQoNCiMgWOG6v3AgdGjhu6kgdOG7sSA6IGjDoG0gcmFuZ2UoKQ0KDQpUaMOtIGThu6U6IFRhIG114buRbiBs4buNYyByacOqbmcgbmjhu69uZyBi4buHbmggbmjDom4gc+G7kW5nIGzDonUgaMahbiAxIG7Eg20sIGTGsOG7m2kgNTAgdHXhu5VpLCBzYXUgxJHDsyB44bq/cCB0aOG7qSB04buxIHRo4budaSBnaWFuIHPhu5FuZyBjw7JuIGdp4bqjbSBk4bqnbi4NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpkZiU+JWZpbHRlcih0aW1lPj0zNjUgJiBhZ2UgPCA1MCklPiUNCiAgc2VsZWN0KFgsdGltZSklPiUNCiAgYXJyYW5nZShkZXNjKHRpbWUpKQ0KDQpgYGANCg0KSG/hurdjIHTEg25nIGThuqduOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmRmJT4lZmlsdGVyKHRpbWU+PTM2NSAmIGFnZSA8IDUwKSU+JQ0KICBzZWxlY3QoWCx0aW1lKSU+JQ0KICBhcnJhbmdlKHRpbWUpDQpgYGANCg0KTmjGsCBi4bqhbiB0aOG6pXksIE5oaSB24burYSBr4bq/dCBo4bujcCAzIGjDoG0gZmlsdGVyLCBzZWxlY3QgdsOgIHJhbmdlIHbhu5tpIG5oYXUuDQoNCiMgVOG6oW8gYmnhur9uIHPhu5EgbeG7m2k6IEjDoG0gbXV0YXRlKCkNCg0KbXV0YXRlKCkgbMOgIG3hu5l0IGjDoG0gY+G7sWMga8OsIHRp4buHbiDDrWNoIHRyb25nIGtoaSBwaMOibiB0w61jaCBk4buvIGxp4buHdSwgdsOsIG7DsyBjaG8gcGjDqXAgYuG6oW4gdOG6oW8gcmEgYmnhur9uIHPhu5EgbeG7m2kgdMO5eSB0aMOtY2ggdHJvbmcgZGF0YWZyYW1lLiANCg0KQuG6oW4gY8OzIHRo4buDIGTDuW5nIG11dGF0ZSBjaG8gMiB2aeG7h2M6IDEpIEhvw6FuIGNodXnhu4NuIGJp4bq/biBz4buRIGhp4buHbiBjw7MgdsOgIDIpIHThuqFvIHJhIGJp4bq/biBz4buRIG3hu5tpIGhvw6BuIHRvw6BuLg0KDQpUaMOtIGThu6U6IENodXnhu4NuIHRo4budaSBnaWFuIHPhu5FuZyBjw7JuIHThu6sgbmfDoHkgc2FuZyB0aMOhbmc6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZGYlPiVtdXRhdGUodGltZU1vbnRoPXJvdW5kKHRpbWUvMzAsMikpJT4lc2VsZWN0KHRpbWUsdGltZU1vbnRoKSU+JWhlYWQoKQ0KYGBgDQoNCkhv4bq3YyB04bqhbyByYSAxIGJp4bq/biBt4bubaSBjw7MgdMOqbiBsw6Aga2Fybm9tZWFuLCBsw6AgdHJ1bmcgYsOsbmggY+G7p2EgcGF0Lmthcm5vIHbDoCBwaC5rYXJubywgc2F1IMSRw7MgdOG6oW8gcmEgMSBiaeG6v24gbmjhu4sgcGjDom4gY2hvIGJp4bq/dCBi4buHbmggbmjDom4gc+G7kW5nIGzDonUgaMahbiAyIG7Eg20sIHbDoCAxIGJp4bq/biBraMOhYyBsw6AgRGVhZCBtw6MgaMOzYSBuaOG7iyBwaMOibiAxLzANCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpkZiU+JW11dGF0ZShrYXJub21lYW49KHBoLmthcm5vK3BhdC5rYXJubykvMiwNCiAgICAgICAgICAgIFN1cnYyeXI9aWZfZWxzZSh0aW1lPjM2NSoyLCJZZXMiLCJObyIpLA0KICAgICAgICAgICAgRGVhZD1pZl9lbHNlKHN0YXR1cz09IkNlbnNvcmVkIiwwLDEpKSU+JQ0KICBzZWxlY3QoY29udGFpbnMoImthcm5vIiksa2Fybm9tZWFuLHRpbWUsc3RhdHVzLFN1cnYyeXIsRGVhZCklPiUNCiAgZ2xpbXBzZSgpDQpgYGANCg0KIyBUaOG7kW5nIGvDqiBtw7QgdOG6oyB0aGVvIHBow6JuIG5ow7NtIHbhu5tpIGjDoG0gc3VtbWFyaXNlKCkgDQoNCmRwbHlyIGN1bmcgY+G6pXAgbeG7mXQgc+G7kSBow6BtIHRp4buHbiDDrWNoIGtow6FjIGzDoCBzdW1tYXJpc2Usc3VtbWFyaXNlX2F0KCkgdsOgIHN1bW1hcmlzZV9pZigpOyBraGkga+G6v3QgaOG7o3AgY2jDum5nIHbhu5tpIGjDoG0gZ3JvdXBfYnkoKSwgYuG6oW4gY8OzIHRo4buDIHThuqFvIHJhIG5o4buvbmcgYuG6o25nIHRo4buRbmcga8OqIG3DtCB04bqjIGNobyB04burbmcgcGjDom4gbmjDs20gdMO5eSB0aMOtY2g6DQoNCkjDoG0gc3VtbWFyaXNlIGTDuW5nIMSRxrDhu6NjIGjhuqd1IGjhur90IGPDoWMgaMOgbSB0aOG7kW5nIGvDqiDEkcahbiBnaeG6o24gdHJvbmcgUiwgduG7m2kgxJFp4buBdSBraeG7h24gbeG7l2kgaMOgbSBjaOG7iSB4deG6pXQgcmEgMSBr4bq/dCBxdeG6oywgdGjDrSBk4bulIG7hur91IHRhIG114buRbiB0w61uaCB0cnVuZyBiw6xuaCBjw6JuIG7hurduZyBz4buldCBnaeG6o20gdHJvbmcgNiB0aMOhbmcgY2hvIDIgcGjDom4gbmjDs20gQ2Vuc29yZWQgdsOgIERlYWQ6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZGYgJT4lDQogIGdyb3VwX2J5KHN0YXR1cykgJT4lDQogIHN1bW1hcmlzZShNZWFuX1dlaWdodF9sb3NzID0gbWVhbih3dC5sb3NzLCBuYS5ybT1UUlVFKSklPiVwYW5kZXIoKQ0KYGBgDQoNCkhv4bq3YyB0cnVuZyB24buLIHRo4budaSBnaWFuIHPhu5FuZyBjw7JuIGNobyB04burbmcgYuG7h25oIHZp4buHbjoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpkZiAlPiUNCiAgZ3JvdXBfYnkoaW5zdCkgJT4lDQogIHN1bW1hcmlzZV9hdCgidGltZSIsZnVucyhtZWRUaW1lPW1lZGlhbiksbmEucm09VCklPiVwYW5kZXIoKQ0KYGBgDQoNCkjDoG0gc3VtbWFyaXNlX2lmKCkgY2hvIHBow6lwIHRo4buxYyBoaeG7h24gY8O5bmcgMSBow6BtIHRyw6puIGjDoG5nIGxv4bqhdCBiaeG6v24gbuG6v3UgY2jDum5nIHRo4buPYSDEkWnhu4F1IGtp4buHbiwgdGjDrSBk4bulIGzDoCBiaeG6v24ga2nhu4N1IHPhu5EuDQoNClRow60gZOG7pSB0YSB0w61uaCB0aOG7nWkgZ2lhbiB0cnVuZyBiw6xuaCBjaG8gdOG6pXQgY+G6oyBiaeG6v24gbnVtZXJpYyB2w6AgY2hvIHJpw6puZyBt4buXaSBwaMOibiBuaMOzbSBjZW5zb3JlZCB2w6AgZGVhZA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmRmICU+JXNlbGVjdCgtWCklPiUNCiAgZ3JvdXBfYnkoc3RhdHVzKSAlPiUNCiAgc3VtbWFyaXNlX2lmKGlzLm51bWVyaWMsbWVhbixuYS5ybT1UKSU+JXBhbmRlcigpDQpgYGANCg0KTmhpIGPDsyB0aOG7gyBsw6BtIGPhuqd1IGvDrCBoxqFuIGtoaSBr4bq/dCBo4bujcCB0aMOqbSBuaGnhu4F1IGjDoG0gLCB0aMOtIGThu6UgTWVhbiwgU2QsIHF1YW50aWxlLi4uDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZGYgJT4lDQogIGdyb3VwX2J5KHN0YXR1cykgJT4lDQogIHN1bW1hcmlzZV9hdCgidGltZSIsZnVucyhNZWFuPW1lYW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICBNZWRpYW49bWVkaWFuLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgU0Q9c2QsDQogICAgICAgICAgICAgICAgICAgIExMPXF1YW50aWxlKC4scHJvYnM9MC4wNSksDQogICAgICAgICAgICAgICAgICAgIFVMPXF1YW50aWxlKC4scHJvYnM9MC45NSkpDQogICAgICAgICAgICAgICApJT4lcGFuZGVyKCkNCg0KYGBgDQoNCkLhuqFuIGPDsyB0aOG7gyB04bqhbyBi4bqjbmcgY2jDqW8gbuG7r2E6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZGYgJT4lDQogIGdyb3VwX2J5KHN0YXR1cykgJT4lDQogIHNlbGVjdChpbnN0KSU+JXRhYmxlKCkNCmBgYA0KDQpkcGx5ciBjw7JuIGN1bmcgY+G6pXAgaMOgbSBuKCkgxJHhu4MgxJHhur9tIHThuqduIHPhu5EgY+G7p2Egbmjhu69uZyBiaeG6v24gY2F0ZWdvcmljYWwuIEtoaSBr4bq/dCBo4bujcCB24bubaSBow6BtIHN1bW1hcmlzZSwgZ3JvdXBfYnkgdsOgIG11dGF0ZSBi4bqhbiBjw7MgdGjhu4MgbMOgbSAxIHRo4buRbmcga8OqIG3DtCB04bqjIMSRxqFuIGdp4bqjbiBjaG8gbeG7mXQgYmnhur9uIHPhu5EgdHJvbmcgZOG7ryBsaeG7h3U6DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZGYlPiVncm91cF9ieShpbnN0KSU+JQ0KICBzdW1tYXJpc2UoRnJlcXVlbmN5PW4oKSklPiUNCiAgbXV0YXRlKFByb3BvcnRpb249cm91bmQoMTAwKkZyZXF1ZW5jeS9zdW0oRnJlcXVlbmN5KSwyKSklPiVwYW5kZXIoKQ0KYGBgDQoNCk3hu5l0IGLhuqNuZyBjaMOpbyBraMOhYyDEkcaw4bujYyB04bqhbyByYSBi4bqxbmcgY8OhY2gga+G6v3QgaOG7o3AgaMOgbSBncm91cF9ieSwgc3VtbWFyaXNlIHbDoCBzcHJlYWQNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpkZiU+JQ0KICBncm91cF9ieShpbnN0LHN0YXR1cykgJT4lDQogIHN1bW1hcmlzZShmcmVxdWVuY3kgPSBuKCkpICU+JSANCiAgc3ByZWFkKHN0YXR1cyxmcmVxdWVuY3kpICU+JQ0KICBwYW5kZXINCg0KYGBgDQoNCiMgQ2jhu41uIG3huqt1IG5n4bqrdSBuaGnDqm4gdsOgIGPhuq90IGThu68gbGnhu4d1DQoNCmRwbHlyIGN1bmcgY+G6pXAgaMOgbSBzYW1wbGVfZnJhYygpIHbDoCBzYW1wbGVfbigpLCBjaG8gcGjDqXAgdGjhu7FjIGhp4buHbiB2aeG7h2MgY2jhu41uIG3huqt1IG5n4bqrdSBuaGnDqm4gdHLDqm4gZOG7ryBsaeG7h3UgdGhlbyB04buJIGzhu4cgdsOgIHRoZW8ga8OtY2ggdGjGsOG7m2MgdMO5eSBjaOG7jW46DQoNClRow60gZOG7pTogQ2jhu41uIG3huqt1IG5n4bqrdSBuaGnDqm4gMTAlIGThu68gbGnhu4d1DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0Kc2FtcGxlX2ZyYWMoZGYsMC4xKQ0KYGBgDQoNCkNo4buNbiAxMCBjYXNlcyBuZ+G6q3Ugbmhpw6puOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCnNhbXBsZV9uKGRmLDEwKQ0KYGBgDQoNCkNo4buNbiA1MCBjYXNlcyB04burIGjDoG5nIDEwMCDEkeG6v24gMTUwOg0KDQpgYGB7cn0NCnNsaWNlKGRmLDEwMDoxNTApDQoNCmBgYA0KDQpUYSBsw6BtIHBo4bupYyB04bqhcCBoxqFuIDEgY2jDunQ6IENo4buNbiAzIGNhc2UgxJHhuqd1IHRpw6puIHThu6sgbeG7l2kgcGjDom4gbmjDs20gQ2Vuc29yZSBoYXkgRGVhZA0KDQpgYGB7cn0NCmRmJT4lZ3JvdXBfYnkoc3RhdHVzKSU+JXNsaWNlKDE6MykNCmBgYA0KDQpIb+G6t2MgY2jhu41uIDEwJSB04burIG3hu5dpIHBow6JuIG5ow7NtIENlbnNvcmUvRGVhZA0KDQpgYGB7cn0NCmRmJT4lZ3JvdXBfYnkoc3RhdHVzKSU+JXNhbXBsZV9mcmFjKDAuMSkNCmBgYA0KDQojIFThu5VuZyBr4bq/dA0KDQpOaGkgduG7q2EgdHLDrG5oIGLDoHkgbeG7mXQgc+G7kSB0w61uaCBuxINuZyBtw6AgZHBseXIgcGFja2FnZSB0aOG7sWMgaGnhu4duIMSRxrDhu6NjLiBOaMawIHRhIHRo4bqleSwgbmjhu69uZyBjw7RuZyB2aeG7h2MgbsOgeSBjw7MgdGjhu4MgY+G6p24gYuG6pXQgY+G7qSBraGkgbsOgbyB0cm9uZyBxdcOhIHRyw6xuaCBsw6BtIHZp4buHYyB0csOqbiBk4buvIGxp4buHdSwgxJHhurdjIGJp4buHdCBsw6Agdmnhu4djIGzhu41jIGJp4bq/biBz4buRIHbDoCB04bqhbyBiaeG6v24gbeG7m2kuIE5o4buvbmcgaMOgbSB0cm9uZyBkcGx5ciBjw7JuIGPhu7FjIGvDrCBo4buvdSDDrWNoIGtoaSBi4bqhbiBtdeG7kW4gduG6vSBiaeG7g3UgxJHhu5MgdOG7qyBk4buvIGxp4buHdSBn4buRYyBoYXkgdOG7qyBvdXRwdXQgb2JqZWN0IGPhu6dhIG3hu5l0IHBhY2thZ2Uga2jDoWMsIG5oxrAgYm9vdHN0cmFwLCBtw7QgaMOsbmguLi4gY2jhurNuZyBo4bqhbi4gU+G7rSBk4bulbmcgdGjDtG5nIHRo4bqhbyBkcGx5ciBjw7MgbmdoxKlhIGzDoCBi4bqhbiDEkcOjIGPDsyB0aOG7gyB04burIGLhu48gaG/DoG4gdG/DoG4gbmjhu69uZyBzb2Z0d2FyZSBk4bqhbmcgYuG6o25nIHTDrW5oIG5oxrAgRXhjZWwsIFNQU1MsIE1lZGNhbGMgdsOgIGhvw6BuIHRvw6BuIGTDuW5nIFIgY2hvIGPDtG5nIHZp4buHYyBj4bunYSBtw6xuaC4NCg==