library(dplyr)
library(tidyr)
library(writexl)
application <- read.csv("data/application_sample.csv")
head(application)
tail(application, 10)
head(application, 10)
NA
arrange(application, AMT_INCOME_TOTAL)
arrange(application, CODE_GENDER, AMT_CREDIT)

str(data): cấu trúc của dataset, cung cấp thông tin:

str(application)
'data.frame':   20000 obs. of  11 variables:
 $ SK_ID_CURR        : int  411019 360800 238560 130181 216974 128257 205326 160384 317054 369746 ...
 $ TARGET            : int  0 0 0 0 0 0 1 0 0 0 ...
 $ NAME_CONTRACT_TYPE: chr  "Cash loans" "Revolving loans" "Cash loans" "Cash loans" ...
 $ CODE_GENDER       : chr  "F" "F" "M" "M" ...
 $ FLAG_OWN_CAR      : chr  "N" "N" "N" "N" ...
 $ FLAG_OWN_REALTY   : chr  "Y" "Y" "Y" "N" ...
 $ CNT_CHILDREN      : int  0 1 1 1 0 1 3 0 0 0 ...
 $ CNT_FAM_MEMBERS   : int  1 2 2 3 1 3 5 2 1 1 ...
 $ AMT_INCOME_TOTAL  : num  180000 94500 157500 157500 202500 ...
 $ AMT_CREDIT        : num  675000 180000 675000 746280 961146 ...
 $ NAME_FAMILY_STATUS: chr  "Single / not married" "Single / not married" "Separated" "Married" ...

dim(data): Số dòng và số cột của bảng

dim(application)
[1] 20000    11

nrow(data): số dòng (số quan sát) của bảng

nrow(application)
[1] 20000

ncol(data): số cột (số biến) của bảng

ncol(application)
[1] 11

summary(data): thống kê mô tả nhanh các biến

summary(application)
   SK_ID_CURR         TARGET       NAME_CONTRACT_TYPE CODE_GENDER        FLAG_OWN_CAR       FLAG_OWN_REALTY     CNT_CHILDREN    
 Min.   :100031   Min.   :0.0000   Length:20000       Length:20000       Length:20000       Length:20000       Min.   : 0.0000  
 1st Qu.:187757   1st Qu.:0.0000   Class :character   Class :character   Class :character   Class :character   1st Qu.: 0.0000  
 Median :276387   Median :0.0000   Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median : 0.0000  
 Mean   :277116   Mean   :0.0774                                                                               Mean   : 0.4153  
 3rd Qu.:366167   3rd Qu.:0.0000                                                                               3rd Qu.: 1.0000  
 Max.   :456238   Max.   :1.0000                                                                               Max.   :12.0000  
                                                                                                                                
 CNT_FAM_MEMBERS  AMT_INCOME_TOTAL     AMT_CREDIT      NAME_FAMILY_STATUS
 Min.   : 1.000   Min.   :   27000   Min.   :  45000   Length:20000      
 1st Qu.: 2.000   1st Qu.:  112500   1st Qu.: 270000   Class :character  
 Median : 2.000   Median :  148500   Median : 512064   Mode  :character  
 Mean   : 2.152   Mean   :  170469   Mean   : 599440                     
 3rd Qu.: 3.000   3rd Qu.:  202500   3rd Qu.: 808650                     
 Max.   :14.000   Max.   :18000090   Max.   :3375000                     
 NA's   :1                                                               

2. Package dplyr

dplyr là một thư viện có chức năng thao tác và biến đổi dữ liệu một cách trực quan. Package dplyr 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.

2.1. Thao tác với dòng

2.1.1. filter(): lọc dữ liệu

Lọc những khoản vay có thu nhập >= 100.000 USD

head(
  filter(application, AMT_INCOME_TOTAL >= 100000)
)

Đếm số khoản vay mà người đi vay có thu nhập >= 100.000 USD

nrow(
  filter(application, AMT_INCOME_TOTAL >= 100000)
)
[1] 15873

** 2.1.2. distinct(): bỏ quan sát trùng**

Số lượng quan sát ban đầu chưa bỏ trùng là 20000

nrow(application)
[1] 20000

Sau khi thực hiện bỏ quan sát trùng thì số quan sát không thay đổi, do đó dataset này không có quan sát trùng

nrow(distinct(application, SK_ID_CURR))
[1] 20000

Có thể dùng distinct(data, tên cột) để kiểm tra số lượng biểu hiện có trong cột đó, ví dụ cột CODE_GENDER chỉ có 2 biểu hiện là M và F

distinct(application, CODE_GENDER)

2.1.3. arrange(): sắp xếp dữ liệu

Sắp xếp quan sát theo thứ tự tăng dần của thu nhập

head(
  arrange(application, AMT_INCOME_TOTAL),
  10
)

Sắp xếp quan sát theo thứ tự giảm dần của thu nhập

head(
  arrange(application, desc(AMT_INCOME_TOTAL)),
  10
)

Lưu ý: trường hợp dữ liệu bị khuyết (missing data - NA) thì dù sắp xếp tăng dần hay giảm dần thì giá trị khuyết luôn được đưa xuống cuối cùng của bảng

2.2 Thao tác với cột

2.2.1. select()

data1 <- select(application, CODE_GENDER, AMT_INCOME_TOTAL, AMT_CREDIT)
head(data1)
select(application, CODE_GENDER, AMT_INCOME_TOTAL, AMT_CREDIT)

Lấy từ cột A đến cột B: CỘT A : CỘT B

select(application, TARGET : AMT_CREDIT)

Dùng dấu - để bỏ đi cột không cần lấy

select(application, -SK_ID_CURR, -TARGET, -CODE_GENDER)
head(select(application, -SK_ID_CURR, -TARGET, -CODE_GENDER))


Ví dụ đặt tên cột bắt đầu bằng chữ q:

  • q1, q2, …
select(application, starts_with('C'))
select(application, ends_with('DER'))

2.2.2. Rename()

Đổi tên cột CODE_GENDER -> gender

Đổi tên cột SK_ID_CURR -> id

rename(application, gender = CODE_GENDER, 
       id = SK_ID_CURR, 
       car = FLAG_OWN_CAR, 
       income= AMT_INCOME_TOTAL, 
       n_children= CNT_CHILDREN, 
       credit=AMT_CREDIT, 
       n_fam_members= CNT_FAM_MEMBERS, 
       fam_status = NAME_FAMILY_STATUS, 
       contract_type = NAME_CONTRACT_TYPE
       )


2.2.3. Mutate()

Đổi đơn vị

mutate(application,
       income2 = AMT_INCOME_TOTAL / 1000)

Muốn sửa đổi cột có trong bảng (AMT_INCOME_TOTAL)

#TARGET = as.character(TARGET)
mutate(application,
       AMT_INCOME_TOTAL = AMT_INCOME_TOTAL/1000,
       AMT_CREDIT = AMT_CREDIT/1000)

Chia khách hàng thành ba nhóm, thu nhập cao, trung bình, thấp

min(application$AMT_INCOME_TOTAL)
[1] 27000
max(application$AMT_INCOME_TOTAL)
[1] 18000090
mean(application$AMT_INCOME_TOTAL) #tính trung bình
[1] 170468.9
median(application$AMT_INCOME_TOTAL) #trung vị
[1] 148500
quantile(application$AMT_INCOME_TOTAL, 0.25) #phân vị mức 25
   25% 
112500 
var(application$AMT_INCOME_TOTAL) #phương sai
[1] 35193056111
sd(application$AMT_INCOME_TOTAL) # độ lệch chuẩn
[1] 187598.1

Chia làm 3 nhóm

Thấp: < 100.000

Trung bình: 100.000 - 500.000

Cao: > 500.000

mutate(
  application, 
  income_class = case_when(
    AMT_INCOME_TOTAL < 100000 ~ "Thấp",
    AMT_INCOME_TOTAL >=100000 & AMT_INCOME_TOTAL < 500000 ~ "Trung bình",
    AMT_INCOME_TOTAL >= 500000 ~ "Cao",
    TRUE ~ "Không xác định" #những trường hợp còn lại, phải để dòng TRUE ở cuối
  )
)
mutate(
  application, 
  AMT_INCOME_TOTAL =  AMT_INCOME_TOTAL/1000,
  have_child= case_when (
    CNT_CHILDREN > 0 ~ TRUE,
    CNT_CHILDREN == 0 ~ FALSE
  )
)


Chia thu nhập

  • 0 - 100%

    • 25%: Thấp

    • 25%-75%: Trung bình

    • 75%: Cao

mutate(application,
       income_class = case_when(
         AMT_INCOME_TOTAL < quantile (application$AMT_INCOME_TOTAL, 0.25) ~ "Thấp",
         AMT_INCOME_TOTAL < quantile (application$AMT_INCOME_TOTAL, 0.75) ~ "Trung bình",
         AMT_INCOME_TOTAL >= quantile (application$AMT_INCOME_TOTAL, 0.75) ~ "Cao"
       ))


2.2.4. Summarize()

Tổng hợp dữ liệu, tạo bảng thống kê mô tả

summarize(
  application,
  obs = n(), #tính số quan sát
  mean_income = mean(AMT_INCOME_TOTAL),
  var_income = var(AMT_INCOME_TOTAL),
  sd_income = sd(AMT_INCOME_TOTAL),
  q1 = quantile(AMT_INCOME_TOTAL, 0.25),
  q3 = quantile(AMT_INCOME_TOTAL, 0.75),
  median = median(AMT_INCOME_TOTAL)
  )
as.data.frame(
  t(  summarize( application,
  obs = n(), #tính số quan sát
  mean_income = mean(AMT_INCOME_TOTAL),
  var_income = var(AMT_INCOME_TOTAL),
  sd_income = sd(AMT_INCOME_TOTAL),
  q1 = quantile(AMT_INCOME_TOTAL, 0.25),
  q3 = quantile(AMT_INCOME_TOTAL, 0.75),
  median = median(AMT_INCOME_TOTAL)
  )
  )
)
NA

Toán tử vipe: #%>%


options(scipen = 999) #tắt hiển thị science number
summarize(
  application,
  obs = n(), #tính số quan sát
  mean_income = mean(AMT_INCOME_TOTAL),
  var_income = var(AMT_INCOME_TOTAL),
  sd_income = sd(AMT_INCOME_TOTAL),
  q1 = quantile(AMT_INCOME_TOTAL, 0.25),
  q3 = quantile(AMT_INCOME_TOTAL, 0.75),
  median = median(AMT_INCOME_TOTAL)
  ) %>%
  t() %>%
  as.data.frame() %>%
  rename(income = V1) %>%
  write_xlsx('desc.xlsx')

options(scipen = 999) #tắt hiển thị science number
summarize(
  application,
  obs = n(), #tính số quan sát
  mean_income = mean(AMT_INCOME_TOTAL),
  var_income = var(AMT_INCOME_TOTAL),
  sd_income = sd(AMT_INCOME_TOTAL),
  q1 = quantile(AMT_INCOME_TOTAL, 0.25),
  q3 = quantile(AMT_INCOME_TOTAL, 0.75),
  median = median(AMT_INCOME_TOTAL)
  ) %>%
  t() %>%
  as.data.frame() %>%
  rename(income = V1) %>%
  mutate( 
    info = rownames(.)
    )%>%
  select(info, income) %>%
  write_xlsx('desc2.xlsx')

Group by()

group_by(application, CODE_GENDER) %>%
  summarize(
    obs = n(),
    mean_income = mean (AMT_INCOME_TOTAL),
    sd_income = sd(AMT_INCOME_TOTAL)
  ) %>%
  rename(
    gender = CODE_GENDER
  )

3. Package tidyr

3.1. Hàm gather(): Sử dụng để chuyển dữ liệu dạng wide sang long

head(application, 10) %>%
  select(SK_ID_CURR, CODE_GENDER, AMT_CREDIT, AMT_INCOME_TOTAL) %>%
  gather(CODE_GENDER, AMT_CREDIT, AMT_INCOME_TOTAL,
         key = "var",
         value = "value"
         )%>%
write_xlsx('ID')


Sử dụng data GDP

gdp <- read.csv("data/gdp.csv")
pop <- read.csv("data/pop.csv")
Error in exists(cacheKey, where = .rs.WorkingDataEnv, inherits = FALSE) : 
  invalid first argument
Error in assign(cacheKey, frame, .rs.CachedDataEnv) : 
  attempt to use zero-length variable name
head(gdp)
#lấy từ năm (cột) x1960 tới x2021 
gdp <-gather(gdp, X1960 : X2021, key="Year", value="GDP")%>%
  mutate(
    Year= as.numeric(gsub("X", "", Year))
  )
#không dùng định danh khi reshape
head(pop)
pop1<- gather(pop, X1960:X2021, key= "Year", value = "Population")%>%
mutate(
  Year = as.numeric(gsub("X","",Year))
)

3.2. Spread() Chuyển từ long sang wide

spread(pop1, key= Year, value= Population) %>%
 head()

3.3. Ghép các cột của 2 đối tượng

full_join, inner_join, left_join, right_join

select(gdp, -Country.Name) %>%
  full_join(pop1, by= c("Country.Code", "Year")) %>% 
  select(Country.Name, everything())
#đưa cột Country Name lên đầu
# everything là những cột còn lại

4. Merge data

4.1 Nối theo cột

cbind()

#income
summarise(
  application,
  mean = mean (AMT_INCOME_TOTAL),
  sd = sd (AMT_INCOME_TOTAL),
  q1= quantile(AMT_INCOME_TOTAL, 0.25),
  q3= quantile(AMT_INCOME_TOTAL, 0.75),
  median= median(AMT_INCOME_TOTAL),
  min = min(AMT_INCOME_TOTAL),
  max = max(AMT_INCOME_TOTAL)
)
#income
desc.income <- summarise(
  application,
  mean = mean (AMT_INCOME_TOTAL),
  sd = sd (AMT_INCOME_TOTAL),
  q1= quantile(AMT_INCOME_TOTAL, 0.25),
  q3= quantile(AMT_INCOME_TOTAL, 0.75),
  median= median(AMT_INCOME_TOTAL),
  min = min(AMT_INCOME_TOTAL),
  max = max(AMT_INCOME_TOTAL)
) %>%
  t() %>%
  as.data.frame() %>%
  rename(income = V1)
desc.income
#income
desc.credit <- summarise(
  application,
  mean = mean (AMT_CREDIT),
  sd = sd (AMT_CREDIT),
  q1= quantile(AMT_CREDIT, 0.25),
  q3= quantile(AMT_CREDIT, 0.75),
  median= median(AMT_CREDIT),
  min = min(AMT_CREDIT),
  max = max(AMT_CREDIT)
) %>%
  t() %>%
  as.data.frame() %>%
  rename(credit = V1)
desc.credit
cbind(desc.income, desc.credit)

4.2 Nối theo dòng

rbind()

data1 <- data.frame(
  x = c(1,2),
  y = c(3,4)
)

data1

data2 <- data.frame(
  x = c(5,6),
  y = c(7,8)
)
data2
rbind(data1, data2)

LS0tDQp0aXRsZTogIkNoxrDGoW5nIDI6IGRwbHlyLCB0aWR5ciINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCmBgYHtyfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KHdyaXRleGwpDQpgYGANCg0KYGBge3J9DQphcHBsaWNhdGlvbiA8LSByZWFkLmNzdigiZGF0YS9hcHBsaWNhdGlvbl9zYW1wbGUuY3N2IikNCmBgYA0KDQpgYGB7cn0NCmhlYWQoYXBwbGljYXRpb24pDQpgYGANCg0KYGBge3J9DQp0YWlsKGFwcGxpY2F0aW9uLCAxMCkNCmBgYA0KDQpgYGB7cn0NCmhlYWQoYXBwbGljYXRpb24sIDEwKQ0KDQpgYGANCg0KYGBge3J9DQphcnJhbmdlKGFwcGxpY2F0aW9uLCBBTVRfSU5DT01FX1RPVEFMKQ0KYGBgDQoNCmBgYHtyfQ0KYXJyYW5nZShhcHBsaWNhdGlvbiwgQ09ERV9HRU5ERVIsIEFNVF9DUkVESVQpDQpgYGANCg0KKipgc3RyKGRhdGEpYDogY+G6pXUgdHLDumMgY+G7p2EgZGF0YXNldCwgY3VuZyBj4bqlcCB0aMO0bmcgdGluOioqDQoNCi0gICBT4buRIGTDsm5nIHPhu5EgY+G7mXQNCg0KLSAgIFTDqm4gY8OhYyBj4buZdCwga2nhu4N1IGThu68gbGnhu4d1IGPhu6dhIHThu6tuZyBj4buZdA0KDQotICAgTeG7mXQgc+G7kSBkYXRhIMSR4bqndSB0acOqbiBj4bunYSBj4buZdA0KDQpgYGB7cn0NCnN0cihhcHBsaWNhdGlvbikNCmBgYA0KDQoqKmBkaW0oZGF0YSlgOiBT4buRIGTDsm5nIHbDoCBz4buRIGPhu5l0IGPhu6dhIGLhuqNuZ1wNCioqDQoNCmBgYHtyfQ0KZGltKGFwcGxpY2F0aW9uKQ0KYGBgDQoNCioqYG5yb3coZGF0YSlgOiBz4buRIGTDsm5nIChz4buRIHF1YW4gc8OhdCkgY+G7p2EgYuG6o25nKioNCg0KYGBge3J9DQpucm93KGFwcGxpY2F0aW9uKQ0KYGBgDQoNCioqYG5jb2woZGF0YSlgOiBz4buRIGPhu5l0IChz4buRIGJp4bq/bikgY+G7p2EgYuG6o25nKioNCg0KYGBge3J9DQpuY29sKGFwcGxpY2F0aW9uKQ0KYGBgDQoNCioqYHN1bW1hcnkoZGF0YSlgOiB0aOG7kW5nIGvDqiBtw7QgdOG6oyBuaGFuaCBjw6FjIGJp4bq/bioqDQoNCmBgYHtyfQ0Kc3VtbWFyeShhcHBsaWNhdGlvbikNCmBgYA0KDQojIyAqKjIuIFBhY2thZ2UgYGRwbHlyYCoqDQoNCioqYGRwbHlyYCoqIGzDoCBt4buZdCB0aMawIHZp4buHbiBjw7MgY2jhu6ljIG7Eg25nIHRoYW8gdMOhYyB2w6AgYmnhur9uIMSR4buVaSBk4buvIGxp4buHdSBt4buZdCBjw6FjaCB0cuG7sWMgcXVhbi4gUGFja2FnZSBkcGx5ciBjdW5nIGPhuqVwIG5o4buvbmcgZnVuY3Rpb24gaG/DoW4gY2h1eeG7g24gdsOgIHRoYW8gdMOhYyB0csOqbiBk4buvIGxp4buHdSBzYXUga2hpIG7DsyDEkcOjIMSRxrDhu6NjIHThuqNpIHbDoG8gUi4NCg0KIyMgKioyLjEuIFRoYW8gdMOhYyB24bubaSBkw7JuZyoqDQoNCiMjIyMgKioyLjEuMS4gYGZpbHRlcigpYDogbOG7jWMgZOG7ryBsaeG7h3UqKg0KDQpM4buNYyBuaOG7r25nIGtob+G6o24gdmF5IGPDsyB0aHUgbmjhuq1wIFw+PSAxMDAuMDAwIFVTRA0KDQpgYGB7cn0NCmhlYWQoDQogIGZpbHRlcihhcHBsaWNhdGlvbiwgQU1UX0lOQ09NRV9UT1RBTCA+PSAxMDAwMDApDQopDQpgYGANCg0KxJDhur9tIHPhu5Ega2hv4bqjbiB2YXkgbcOgIG5nxrDhu51pIMSRaSB2YXkgY8OzIHRodSBuaOG6rXAgXD49IDEwMC4wMDAgVVNEDQoNCmBgYHtyfQ0KbnJvdygNCiAgZmlsdGVyKGFwcGxpY2F0aW9uLCBBTVRfSU5DT01FX1RPVEFMID49IDEwMDAwMCkNCikNCg0KYGBgDQoNCiMjIyMgXCpcKiAyLjEuMi4gYGRpc3RpbmN0KClgOiBi4buPIHF1YW4gc8OhdCB0csO5bmdcKlwqDQoNClPhu5EgbMaw4bujbmcgcXVhbiBzw6F0IGJhbiDEkeG6p3UgY2jGsGEgYuG7jyB0csO5bmcgbMOgIDIwMDAwDQoNCmBgYHtyfQ0KbnJvdyhhcHBsaWNhdGlvbikNCmBgYA0KDQpTYXUga2hpIHRo4buxYyBoaeG7h24gYuG7jyBxdWFuIHPDoXQgdHLDuW5nIHRow6wgc+G7kSBxdWFuIHPDoXQga2jDtG5nIHRoYXkgxJHhu5VpLCBkbyDEkcOzIGRhdGFzZXQgbsOgeSBraMO0bmcgY8OzIHF1YW4gc8OhdCB0csO5bmcNCg0KYGBge3J9DQpucm93KGRpc3RpbmN0KGFwcGxpY2F0aW9uLCBTS19JRF9DVVJSKSkNCmBgYA0KDQpDw7MgdGjhu4MgZMO5bmfCoGBkaXN0aW5jdChkYXRhLCB0w6puIGPhu5l0KWDCoMSR4buDIGtp4buDbSB0cmEgc+G7kSBsxrDhu6NuZyBiaeG7g3UgaGnhu4duIGPDsyB0cm9uZyBj4buZdCDEkcOzLCB2w60gZOG7pSBj4buZdMKgYENPREVfR0VOREVSYMKgY2jhu4kgY8OzIDIgYmnhu4N1IGhp4buHbiBsw6DCoGBNYMKgdsOgwqBgRmANCg0KYGBge3J9DQpkaXN0aW5jdChhcHBsaWNhdGlvbiwgQ09ERV9HRU5ERVIpDQpgYGANCg0KIyMjIyAqKjIuMS4zLiBgYXJyYW5nZSgpYDogc+G6r3AgeOG6v3AgZOG7ryBsaeG7h3UqKg0KDQpT4bqvcCB44bq/cCBxdWFuIHPDoXQgdGhlbyB0aOG7qSB04buxIHTEg25nIGThuqduIGPhu6dhIHRodSBuaOG6rXANCg0KYGBge3J9DQpoZWFkKA0KICBhcnJhbmdlKGFwcGxpY2F0aW9uLCBBTVRfSU5DT01FX1RPVEFMKSwNCiAgMTANCikNCmBgYA0KDQpT4bqvcCB44bq/cCBxdWFuIHPDoXQgdGhlbyB0aOG7qSB04buxIGdp4bqjbSBk4bqnbiBj4bunYSB0aHUgbmjhuq1wDQoNCmBgYHtyfQ0KaGVhZCgNCiAgYXJyYW5nZShhcHBsaWNhdGlvbiwgZGVzYyhBTVRfSU5DT01FX1RPVEFMKSksDQogIDEwDQopDQpgYGANCg0KKipMxrB1IMO9Kio6IHRyxrDhu51uZyBo4bujcCBk4buvIGxp4buHdSBi4buLIGtodXnhur90IChtaXNzaW5nIGRhdGEgLcKgYE5BYCkgdGjDrCBkw7kgc+G6r3AgeOG6v3AgdMSDbmcgZOG6p24gaGF5IGdp4bqjbSBk4bqnbiB0aMOsIGdpw6EgdHLhu4sga2h1eeG6v3QgbHXDtG4gxJHGsOG7o2MgxJHGsGEgeHXhu5FuZyBjdeG7kWkgY8O5bmcgY+G7p2EgYuG6o25nDQoNCiMjICoqMi4yIFRoYW8gdMOhYyB24bubaSBj4buZdCoqDQoNCiMjIyMgKioyLjIuMS4qKiBgc2VsZWN0KClgDQoNCmBgYHtyfQ0KZGF0YTEgPC0gc2VsZWN0KGFwcGxpY2F0aW9uLCBDT0RFX0dFTkRFUiwgQU1UX0lOQ09NRV9UT1RBTCwgQU1UX0NSRURJVCkNCmhlYWQoZGF0YTEpDQpgYGANCg0KYGBge3J9DQpzZWxlY3QoYXBwbGljYXRpb24sIENPREVfR0VOREVSLCBBTVRfSU5DT01FX1RPVEFMLCBBTVRfQ1JFRElUKQ0KYGBgDQoNCkzhuqV5IHThu6sgY+G7mXQgQSDEkeG6v24gY+G7mXQgQjogQ+G7mFQgQSA6IEPhu5hUIEINCg0KYGBge3J9DQpzZWxlY3QoYXBwbGljYXRpb24sIFRBUkdFVCA6IEFNVF9DUkVESVQpDQpgYGANCg0KRMO5bmcgZOG6pXUgYC1gIMSR4buDIGLhu48gxJFpIGPhu5l0IGtow7RuZyBj4bqnbiBs4bqleQ0KDQpgYGB7cn0NCnNlbGVjdChhcHBsaWNhdGlvbiwgLVNLX0lEX0NVUlIsIC1UQVJHRVQsIC1DT0RFX0dFTkRFUikNCmBgYA0KDQpgYGB7cn0NCmhlYWQoc2VsZWN0KGFwcGxpY2F0aW9uLCAtU0tfSURfQ1VSUiwgLVRBUkdFVCwgLUNPREVfR0VOREVSKSkNCmBgYA0KDQo8YnI+DQoNClbDrSBk4bulIMSR4bq3dCB0w6puIGPhu5l0IGLhuq90IMSR4bqndSBi4bqxbmcgY2jhu68gcToNCg0KLSAgIHExLCBxMiwgLi4uDQoNCmBgYHtyfQ0Kc2VsZWN0KGFwcGxpY2F0aW9uLCBzdGFydHNfd2l0aCgnQycpKQ0KYGBgDQoNCmBgYHtyfQ0Kc2VsZWN0KGFwcGxpY2F0aW9uLCBlbmRzX3dpdGgoJ0RFUicpKQ0KYGBgDQoNCiMjIyMgKioyLjIuMi4gUmVuYW1lKCkqKg0KDQrEkOG7lWkgdMOqbiBj4buZdCBDT0RFX0dFTkRFUiAtXD4gZ2VuZGVyDQoNCsSQ4buVaSB0w6puIGPhu5l0IFNLX0lEX0NVUlIgLVw+IGlkDQoNCmBgYHtyfQ0KcmVuYW1lKGFwcGxpY2F0aW9uLCBnZW5kZXIgPSBDT0RFX0dFTkRFUiwgDQogICAgICAgaWQgPSBTS19JRF9DVVJSLCANCiAgICAgICBjYXIgPSBGTEFHX09XTl9DQVIsIA0KICAgICAgIGluY29tZT0gQU1UX0lOQ09NRV9UT1RBTCwgDQogICAgICAgbl9jaGlsZHJlbj0gQ05UX0NISUxEUkVOLCANCiAgICAgICBjcmVkaXQ9QU1UX0NSRURJVCwgDQogICAgICAgbl9mYW1fbWVtYmVycz0gQ05UX0ZBTV9NRU1CRVJTLCANCiAgICAgICBmYW1fc3RhdHVzID0gTkFNRV9GQU1JTFlfU1RBVFVTLCANCiAgICAgICBjb250cmFjdF90eXBlID0gTkFNRV9DT05UUkFDVF9UWVBFDQogICAgICAgKQ0KYGBgDQoNCjxicj4NCg0KIyMjIyAyLjIuMy4gTXV0YXRlKCkNCg0KxJDhu5VpIMSRxqFuIHbhu4sNCg0KYGBge3J9DQptdXRhdGUoYXBwbGljYXRpb24sDQogICAgICAgaW5jb21lMiA9IEFNVF9JTkNPTUVfVE9UQUwgLyAxMDAwKQ0KYGBgDQoNCk114buRbiBz4butYSDEkeG7lWkgY+G7mXQgY8OzIHRyb25nIGLhuqNuZyAoQU1UX0lOQ09NRV9UT1RBTCkNCg0KYGBge3J9DQojVEFSR0VUID0gYXMuY2hhcmFjdGVyKFRBUkdFVCkNCm11dGF0ZShhcHBsaWNhdGlvbiwNCiAgICAgICBBTVRfSU5DT01FX1RPVEFMID0gQU1UX0lOQ09NRV9UT1RBTC8xMDAwLA0KICAgICAgIEFNVF9DUkVESVQgPSBBTVRfQ1JFRElULzEwMDApDQpgYGANCg0KQ2hpYSBraMOhY2ggaMOgbmcgdGjDoG5oIGJhIG5ow7NtLCB0aHUgbmjhuq1wIGNhbywgdHJ1bmcgYsOsbmgsIHRo4bqlcA0KDQpgYGB7cn0NCm1pbihhcHBsaWNhdGlvbiRBTVRfSU5DT01FX1RPVEFMKQ0KbWF4KGFwcGxpY2F0aW9uJEFNVF9JTkNPTUVfVE9UQUwpDQptZWFuKGFwcGxpY2F0aW9uJEFNVF9JTkNPTUVfVE9UQUwpICN0w61uaCB0cnVuZyBiw6xuaA0KbWVkaWFuKGFwcGxpY2F0aW9uJEFNVF9JTkNPTUVfVE9UQUwpICN0cnVuZyB24buLDQpxdWFudGlsZShhcHBsaWNhdGlvbiRBTVRfSU5DT01FX1RPVEFMLCAwLjI1KSAjcGjDom4gduG7iyBt4bupYyAyNQ0KdmFyKGFwcGxpY2F0aW9uJEFNVF9JTkNPTUVfVE9UQUwpICNwaMawxqFuZyBzYWkNCnNkKGFwcGxpY2F0aW9uJEFNVF9JTkNPTUVfVE9UQUwpICMgxJHhu5kgbOG7h2NoIGNodeG6qW4NCmBgYA0KDQpDaGlhIGzDoG0gMyBuaMOzbQ0KDQpUaOG6pXA6IFw8IDEwMC4wMDANCg0KVHJ1bmcgYsOsbmg6IDEwMC4wMDAgLSA1MDAuMDAwDQoNCkNhbzogXD4gNTAwLjAwMA0KDQpgYGB7cn0NCm11dGF0ZSgNCiAgYXBwbGljYXRpb24sIA0KICBpbmNvbWVfY2xhc3MgPSBjYXNlX3doZW4oDQogICAgQU1UX0lOQ09NRV9UT1RBTCA8IDEwMDAwMCB+ICJUaOG6pXAiLA0KICAgIEFNVF9JTkNPTUVfVE9UQUwgPj0xMDAwMDAgJiBBTVRfSU5DT01FX1RPVEFMIDwgNTAwMDAwIH4gIlRydW5nIGLDrG5oIiwNCiAgICBBTVRfSU5DT01FX1RPVEFMID49IDUwMDAwMCB+ICJDYW8iLA0KICAgIFRSVUUgfiAiS2jDtG5nIHjDoWMgxJHhu4tuaCIgI25o4buvbmcgdHLGsOG7nW5nIGjhu6NwIGPDsm4gbOG6oWksIHBo4bqjaSDEkeG7gyBkw7JuZyBUUlVFIOG7nyBjdeG7kWkNCiAgKQ0KKQ0KYGBgDQoNCmBgYHtyfQ0KbXV0YXRlKA0KICBhcHBsaWNhdGlvbiwgDQogIEFNVF9JTkNPTUVfVE9UQUwgPSAgQU1UX0lOQ09NRV9UT1RBTC8xMDAwLA0KICBoYXZlX2NoaWxkPSBjYXNlX3doZW4gKA0KICAgIENOVF9DSElMRFJFTiA+IDAgfiBUUlVFLA0KICAgIENOVF9DSElMRFJFTiA9PSAwIH4gRkFMU0UNCiAgKQ0KKQ0KYGBgDQoNCjxicj4NCg0KQ2hpYSB0aHUgbmjhuq1wDQoNCi0gICAwIC0gMTAwJQ0KDQogICAgLSAgIDI1JTogVGjhuqVwDQoNCiAgICAtICAgMjUlLTc1JTogVHJ1bmcgYsOsbmgNCg0KICAgIC0gICA3NSU6IENhbw0KDQpgYGB7cn0NCm11dGF0ZShhcHBsaWNhdGlvbiwNCiAgICAgICBpbmNvbWVfY2xhc3MgPSBjYXNlX3doZW4oDQogICAgICAgICBBTVRfSU5DT01FX1RPVEFMIDwgcXVhbnRpbGUgKGFwcGxpY2F0aW9uJEFNVF9JTkNPTUVfVE9UQUwsIDAuMjUpIH4gIlRo4bqlcCIsDQogICAgICAgICBBTVRfSU5DT01FX1RPVEFMIDwgcXVhbnRpbGUgKGFwcGxpY2F0aW9uJEFNVF9JTkNPTUVfVE9UQUwsIDAuNzUpIH4gIlRydW5nIGLDrG5oIiwNCiAgICAgICAgIEFNVF9JTkNPTUVfVE9UQUwgPj0gcXVhbnRpbGUgKGFwcGxpY2F0aW9uJEFNVF9JTkNPTUVfVE9UQUwsIDAuNzUpIH4gIkNhbyINCiAgICAgICApKQ0KYGBgDQoNCjxicj4NCg0KIyMjIyAyLjIuNC4gU3VtbWFyaXplKCkNCg0KVOG7lW5nIGjhu6NwIGThu68gbGnhu4d1LCB04bqhbyBi4bqjbmcgdGjhu5FuZyBrw6ogbcO0IHThuqMNCg0KYGBge3J9DQpzdW1tYXJpemUoDQogIGFwcGxpY2F0aW9uLA0KICBvYnMgPSBuKCksICN0w61uaCBz4buRIHF1YW4gc8OhdA0KICBtZWFuX2luY29tZSA9IG1lYW4oQU1UX0lOQ09NRV9UT1RBTCksDQogIHZhcl9pbmNvbWUgPSB2YXIoQU1UX0lOQ09NRV9UT1RBTCksDQogIHNkX2luY29tZSA9IHNkKEFNVF9JTkNPTUVfVE9UQUwpLA0KICBxMSA9IHF1YW50aWxlKEFNVF9JTkNPTUVfVE9UQUwsIDAuMjUpLA0KICBxMyA9IHF1YW50aWxlKEFNVF9JTkNPTUVfVE9UQUwsIDAuNzUpLA0KICBtZWRpYW4gPSBtZWRpYW4oQU1UX0lOQ09NRV9UT1RBTCkNCiAgKQ0KYGBgDQoNCmBgYHtyfQ0KYXMuZGF0YS5mcmFtZSgNCiAgdCggIHN1bW1hcml6ZSggYXBwbGljYXRpb24sDQogIG9icyA9IG4oKSwgI3TDrW5oIHPhu5EgcXVhbiBzw6F0DQogIG1lYW5faW5jb21lID0gbWVhbihBTVRfSU5DT01FX1RPVEFMKSwNCiAgdmFyX2luY29tZSA9IHZhcihBTVRfSU5DT01FX1RPVEFMKSwNCiAgc2RfaW5jb21lID0gc2QoQU1UX0lOQ09NRV9UT1RBTCksDQogIHExID0gcXVhbnRpbGUoQU1UX0lOQ09NRV9UT1RBTCwgMC4yNSksDQogIHEzID0gcXVhbnRpbGUoQU1UX0lOQ09NRV9UT1RBTCwgMC43NSksDQogIG1lZGlhbiA9IG1lZGlhbihBTVRfSU5DT01FX1RPVEFMKQ0KICApDQogICkNCikNCg0KYGBgDQoNClRvw6FuIHThu60gdmlwZTogIyVcPiUNCg0KYGBge3J9DQoNCm9wdGlvbnMoc2NpcGVuID0gOTk5KSAjdOG6r3QgaGnhu4NuIHRo4buLIHNjaWVuY2UgbnVtYmVyDQpzdW1tYXJpemUoDQogIGFwcGxpY2F0aW9uLA0KICBvYnMgPSBuKCksICN0w61uaCBz4buRIHF1YW4gc8OhdA0KICBtZWFuX2luY29tZSA9IG1lYW4oQU1UX0lOQ09NRV9UT1RBTCksDQogIHZhcl9pbmNvbWUgPSB2YXIoQU1UX0lOQ09NRV9UT1RBTCksDQogIHNkX2luY29tZSA9IHNkKEFNVF9JTkNPTUVfVE9UQUwpLA0KICBxMSA9IHF1YW50aWxlKEFNVF9JTkNPTUVfVE9UQUwsIDAuMjUpLA0KICBxMyA9IHF1YW50aWxlKEFNVF9JTkNPTUVfVE9UQUwsIDAuNzUpLA0KICBtZWRpYW4gPSBtZWRpYW4oQU1UX0lOQ09NRV9UT1RBTCkNCiAgKSAlPiUNCiAgdCgpICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIHJlbmFtZShpbmNvbWUgPSBWMSkgJT4lDQogIHdyaXRlX3hsc3goJ2Rlc2MueGxzeCcpDQpgYGANCg0KYGBge3J9DQoNCm9wdGlvbnMoc2NpcGVuID0gOTk5KSAjdOG6r3QgaGnhu4NuIHRo4buLIHNjaWVuY2UgbnVtYmVyDQpzdW1tYXJpemUoDQogIGFwcGxpY2F0aW9uLA0KICBvYnMgPSBuKCksICN0w61uaCBz4buRIHF1YW4gc8OhdA0KICBtZWFuX2luY29tZSA9IG1lYW4oQU1UX0lOQ09NRV9UT1RBTCksDQogIHZhcl9pbmNvbWUgPSB2YXIoQU1UX0lOQ09NRV9UT1RBTCksDQogIHNkX2luY29tZSA9IHNkKEFNVF9JTkNPTUVfVE9UQUwpLA0KICBxMSA9IHF1YW50aWxlKEFNVF9JTkNPTUVfVE9UQUwsIDAuMjUpLA0KICBxMyA9IHF1YW50aWxlKEFNVF9JTkNPTUVfVE9UQUwsIDAuNzUpLA0KICBtZWRpYW4gPSBtZWRpYW4oQU1UX0lOQ09NRV9UT1RBTCkNCiAgKSAlPiUNCiAgdCgpICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIHJlbmFtZShpbmNvbWUgPSBWMSkgJT4lDQogIG11dGF0ZSggDQogICAgaW5mbyA9IHJvd25hbWVzKC4pDQogICAgKSU+JQ0KICBzZWxlY3QoaW5mbywgaW5jb21lKSAlPiUNCiAgd3JpdGVfeGxzeCgnZGVzYzIueGxzeCcpDQpgYGANCg0KR3JvdXAgYnkoKQ0KDQpgYGB7cn0NCmdyb3VwX2J5KGFwcGxpY2F0aW9uLCBDT0RFX0dFTkRFUikgJT4lDQogIHN1bW1hcml6ZSgNCiAgICBvYnMgPSBuKCksDQogICAgbWVhbl9pbmNvbWUgPSBtZWFuIChBTVRfSU5DT01FX1RPVEFMKSwNCiAgICBzZF9pbmNvbWUgPSBzZChBTVRfSU5DT01FX1RPVEFMKQ0KICApICU+JQ0KICByZW5hbWUoDQogICAgZ2VuZGVyID0gQ09ERV9HRU5ERVINCiAgKQ0KYGBgDQoNCiMjIDMuIFBhY2thZ2UgYHRpZHlyYA0KDQojIyMgMy4xLiBIw6BtIGdhdGhlcigpOiBT4butIGThu6VuZyDEkeG7gyBjaHV54buDbiBk4buvIGxp4buHdSBk4bqhbmcgd2lkZSBzYW5nIGxvbmcNCg0KYGBge3J9DQpoZWFkKGFwcGxpY2F0aW9uLCAxMCkgJT4lDQogIHNlbGVjdChTS19JRF9DVVJSLCBDT0RFX0dFTkRFUiwgQU1UX0NSRURJVCwgQU1UX0lOQ09NRV9UT1RBTCkgJT4lDQogIGdhdGhlcihDT0RFX0dFTkRFUiwgQU1UX0NSRURJVCwgQU1UX0lOQ09NRV9UT1RBTCwNCiAgICAgICAgIGtleSA9ICJ2YXIiLA0KICAgICAgICAgdmFsdWUgPSAidmFsdWUiDQogICAgICAgICApJT4lDQp3cml0ZV94bHN4KCdJRCcpDQpgYGANCg0KPGJyPg0KDQpT4butIGThu6VuZyBkYXRhIEdEUA0KDQpgYGB7cn0NCmdkcCA8LSByZWFkLmNzdigiZGF0YS9nZHAuY3N2IikNCnBvcCA8LSByZWFkLmNzdigiZGF0YS9wb3AuY3N2IikNCmBgYA0KDQpgYGB7cn0NCmhlYWQoZ2RwKQ0KI2zhuqV5IHThu6sgbsSDbSAoY+G7mXQpIHgxOTYwIHThu5tpIHgyMDIxIA0KYGBgDQoNCmBgYHtyfQ0KZ2RwIDwtZ2F0aGVyKGdkcCwgWDE5NjAgOiBYMjAyMSwga2V5PSJZZWFyIiwgdmFsdWU9IkdEUCIpJT4lDQogIG11dGF0ZSgNCiAgICBZZWFyPSBhcy5udW1lcmljKGdzdWIoIlgiLCAiIiwgWWVhcikpDQogICkNCiNraMO0bmcgZMO5bmcgxJHhu4tuaCBkYW5oIGtoaSByZXNoYXBlDQpgYGANCg0KYGBge3J9DQpoZWFkKHBvcCkNCmBgYA0KDQpgYGB7cn0NCnBvcDE8LSBnYXRoZXIocG9wLCBYMTk2MDpYMjAyMSwga2V5PSAiWWVhciIsIHZhbHVlID0gIlBvcHVsYXRpb24iKSU+JQ0KbXV0YXRlKA0KICBZZWFyID0gYXMubnVtZXJpYyhnc3ViKCJYIiwiIixZZWFyKSkNCikNCmBgYA0KDQojIyMgMy4yLiBTcHJlYWQoKSBDaHV54buDbiB04burIGxvbmcgc2FuZyB3aWRlDQoNCmBgYHtyfQ0Kc3ByZWFkKHBvcDEsIGtleT0gWWVhciwgdmFsdWU9IFBvcHVsYXRpb24pICU+JQ0KIGhlYWQoKQ0KYGBgDQoNCiMjIyAgMy4zLiBHaMOpcCBjw6FjIGPhu5l0IGPhu6dhIDIgxJHhu5FpIHTGsOG7o25nDQoNCmZ1bGxfam9pbiwgaW5uZXJfam9pbiwgbGVmdF9qb2luLCByaWdodF9qb2luDQoNCmBgYHtyfQ0Kc2VsZWN0KGdkcCwgLUNvdW50cnkuTmFtZSkgJT4lDQogIGZ1bGxfam9pbihwb3AxLCBieT0gYygiQ291bnRyeS5Db2RlIiwgIlllYXIiKSkgJT4lIA0KICBzZWxlY3QoQ291bnRyeS5OYW1lLCBldmVyeXRoaW5nKCkpDQojxJHGsGEgY+G7mXQgQ291bnRyeSBOYW1lIGzDqm4gxJHhuqd1DQojIGV2ZXJ5dGhpbmcgbMOgIG5o4buvbmcgY+G7mXQgY8OybiBs4bqhaQ0KYGBgDQoNCiMjIyA0LiBNZXJnZSBkYXRhDQoNCiMjIyMgNC4xIE7hu5FpIHRoZW8gY+G7mXQgDQoNCmNiaW5kKCkNCg0KYGBge3J9DQojaW5jb21lDQpzdW1tYXJpc2UoDQogIGFwcGxpY2F0aW9uLA0KICBtZWFuID0gbWVhbiAoQU1UX0lOQ09NRV9UT1RBTCksDQogIHNkID0gc2QgKEFNVF9JTkNPTUVfVE9UQUwpLA0KICBxMT0gcXVhbnRpbGUoQU1UX0lOQ09NRV9UT1RBTCwgMC4yNSksDQogIHEzPSBxdWFudGlsZShBTVRfSU5DT01FX1RPVEFMLCAwLjc1KSwNCiAgbWVkaWFuPSBtZWRpYW4oQU1UX0lOQ09NRV9UT1RBTCksDQogIG1pbiA9IG1pbihBTVRfSU5DT01FX1RPVEFMKSwNCiAgbWF4ID0gbWF4KEFNVF9JTkNPTUVfVE9UQUwpDQopIA0KYGBgDQoNCmBgYHtyfQ0KI2luY29tZQ0KZGVzYy5pbmNvbWUgPC0gc3VtbWFyaXNlKA0KICBhcHBsaWNhdGlvbiwNCiAgbWVhbiA9IG1lYW4gKEFNVF9JTkNPTUVfVE9UQUwpLA0KICBzZCA9IHNkIChBTVRfSU5DT01FX1RPVEFMKSwNCiAgcTE9IHF1YW50aWxlKEFNVF9JTkNPTUVfVE9UQUwsIDAuMjUpLA0KICBxMz0gcXVhbnRpbGUoQU1UX0lOQ09NRV9UT1RBTCwgMC43NSksDQogIG1lZGlhbj0gbWVkaWFuKEFNVF9JTkNPTUVfVE9UQUwpLA0KICBtaW4gPSBtaW4oQU1UX0lOQ09NRV9UT1RBTCksDQogIG1heCA9IG1heChBTVRfSU5DT01FX1RPVEFMKQ0KKSAlPiUNCiAgdCgpICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIHJlbmFtZShpbmNvbWUgPSBWMSkNCmRlc2MuaW5jb21lDQpgYGANCg0KYGBge3J9DQojaW5jb21lDQpkZXNjLmNyZWRpdCA8LSBzdW1tYXJpc2UoDQogIGFwcGxpY2F0aW9uLA0KICBtZWFuID0gbWVhbiAoQU1UX0NSRURJVCksDQogIHNkID0gc2QgKEFNVF9DUkVESVQpLA0KICBxMT0gcXVhbnRpbGUoQU1UX0NSRURJVCwgMC4yNSksDQogIHEzPSBxdWFudGlsZShBTVRfQ1JFRElULCAwLjc1KSwNCiAgbWVkaWFuPSBtZWRpYW4oQU1UX0NSRURJVCksDQogIG1pbiA9IG1pbihBTVRfQ1JFRElUKSwNCiAgbWF4ID0gbWF4KEFNVF9DUkVESVQpDQopICU+JQ0KICB0KCkgJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgcmVuYW1lKGNyZWRpdCA9IFYxKQ0KZGVzYy5jcmVkaXQNCmBgYA0KDQpgYGB7cn0NCmNiaW5kKGRlc2MuaW5jb21lLCBkZXNjLmNyZWRpdCkNCmBgYA0KDQojIyMjIDQuMiBO4buRaSB0aGVvIGTDsm5nDQoNCnJiaW5kKCkNCg0KYGBge3J9DQpkYXRhMSA8LSBkYXRhLmZyYW1lKA0KICB4ID0gYygxLDIpLA0KICB5ID0gYygzLDQpDQopDQoNCmRhdGExDQoNCmRhdGEyIDwtIGRhdGEuZnJhbWUoDQogIHggPSBjKDUsNiksDQogIHkgPSBjKDcsOCkNCikNCmRhdGEyDQpgYGANCg0KYGBge3J9DQpyYmluZChkYXRhMSwgZGF0YTIpDQpgYGANCg0KIyMjIyAgDQo=