Our Problem
Tiền xử lí số liệu (Data Pre-processing) là một khâu mất nhiều thời gian. Post này sử dụng bộ dữ liệu VES - Vietnam Enterprise Survey (download tại đây). Giải nén và đọc bộ dữ liệu này:
#===================================================================
# Data Processing Project with real-world data: VES 2015 data set
#===================================================================
# Clear work space:
rm(list = ls())
# Import data:
haven::read_dta("E:/dn2015.dta") -> dn2015
# Load dplyr gackage:
library(dplyr)
# Number of columns/rows:
dim(dn2015)
## [1] 455300 222
Bộ dữ liệu này là lớn với 455300 quan sát và 222 cột biến. Theo quy định của các cơ quan quản lí tại Việt Nam thì:
- Mỗi một DN chỉ có một mã số thuế duy nhất và mã số thuế này là độc nhất.
- Một mã số thuế hợp lệ dùng 10 chữ số.
Tuy nhiên một số MST (mã số thuế) trong bộ dữ liệu chỉ có 5 chữ số. Mặt khác, một số DN có MST trùng nhau. Chúng ta sẽ loại bỏ các quan sát này ra khỏi bộ dữ liệu ban đầu như sau:
#---------- Remove firms with duplicated tax code ---------------
dn2015 %>%
group_by(ma_thue) %>%
count() %>%
ungroup() %>%
filter(n > 1) %>%
pull(ma_thue) -> tax_codes_duplicated
dn2015 %>% filter(!ma_thue %in% tax_codes_duplicated) -> dn2015
#--------- Remove firms with tax code != 10 digits ----------------
library(stringr)
dn2015 %>% filter(str_count(ma_thue) == 10) -> dn2015
Theo Quyết định số 27/2018/QĐ-TTg thì mã ngành cấp 5 của doanh nghiệp sẽ phải có 5 chữ số. Tuy nhiên một số quan sát thì mã ngành chỉ có 4 chữ số. Ta có thể suy luận rằng nguyên nhân là do một số ngành có mã với số 0 đứng ở vị trí đầu tiên đã bị “biến mất” trong quá trình vào-lưu-gửi dữ liệu. Vì vậy chúng ta có thể khôi phục chứ số 0 này cho các quan sát với mã ngành chỉ có 4 chữ số như sau:
# Add 0 for 4-digits sector codes:
dn2015 %>%
mutate(nganh_kd = as.character(nganh_kd)) %>%
mutate(nganh_kd = case_when(str_count(nganh_kd) == 4 ~ str_c("0", nganh_kd), TRUE ~ nganh_kd)) -> dn2015
Chúng ta có thể xem qua dữ liệu sau khi xử lí:
library(knitr)
dn2015 %>%
head() %>%
select(1:7, contains("nganh")) %>%
kable(caption = "Table 1: Some Observations")
Table 1: Some Observations
01 |
1 |
152 |
0100110528 |
001 |
00028 |
1 |
X©y Dùng |
42200 |
01 |
1 |
465 |
0100100583 |
007 |
00283 |
1 |
DÖt Kim |
14100 |
01 |
1 |
565 |
0100100752 |
020 |
00640 |
1 |
Sx Pin |
27200 |
01 |
1 |
1825 |
0102071754 |
009 |
00355 |
1 |
Sx C¸c Sp Gç |
16291 |
01 |
1 |
104752 |
0100886857 |
019 |
00625 |
1 |
X©y Dùng |
42200 |
01 |
1 |
277 |
0100103785 |
006 |
00232 |
1 |
X©y Dùng |
42200 |
Lấy dữ liệu về mã ngành của Quyết định số 27/2018/QĐ-TTg bằng đoạn R codes dưới đây:
# --------------------------------------------------------------------------------
# Extract sector code from https://dangkykinhdoanh.gov.vn/vn/Pages/NganhNghe.aspx
#---------------------------------------------------------------------------------
library(rvest)
link <- "https://dangkykinhdoanh.gov.vn/vn/Pages/NganhNghe.aspx"
link %>%
read_html() %>%
html_nodes(xpath = '//*[@id="ctl00_SPWebPartManager1_g_15a30b99_50d8_49e5_8f11_de265cb949e8"]/div/div[2]/table') %>%
html_table() %>%
.[[1]] -> df_sector_code
# Rename for columns:
names(df_sector_code) <- c(str_c("code_level", 1:5), "sector_name")
# Remove/ filter data:
df_sector_code %>%
slice(-1) %>%
filter(code_level1 != "21") %>%
mutate(sector_name_latin = stringi::stri_trans_general(sector_name, "Latin-ASCII")) -> df_sector_code
Có 21 ngành lớn (Cấp 1) theo quy định của chính phủ như ta thấy (chỉ hiển thị 6 ngành cấp 1):
df_sector_code %>%
filter(str_count(code_level1) != 0) %>%
select(code_level1, sector_name_latin) %>%
head() %>%
kable(caption = "Table 2: Some Level-1 Sectors")
Table 2: Some Level-1 Sectors
A |
NONG NGHIEP, LAM NGHIEP VA THUY SAN |
B |
KHAI KHOANG |
C |
CONG NGHIEP CHE BIEN, CHE TAO |
D |
SAN XUAT VA PHAN PHOI DIEN, KHI DOT, NUOC NONG, HOI NUOC VA DIEU HOA KHONG KHI |
E |
CUNG CAP NUOC; HOAT DONG QUAN LY VA XU LY RAC THAI, NUOC THAI |
F |
XAY DUNG |
Để lấp đầy dữ liệu trống cho cột code_level1 chúng ta có thể sử dụng tidyr::fill()
như sau:
df_sector_code %>%
mutate(code_level1 = case_when(str_count(code_level1) == 0 ~ NA_character_, TRUE ~ code_level1)) %>%
tidyr::fill(code_level1, .direction = "down") %>%
filter(str_detect(sector_name_latin, "[a-z]")) -> df_sector_code_filled
So sánh:
# Original data:
df_sector_code %>%
select(1:5) %>%
head() %>%
kable(caption = "Table 3: Original Data")
Table 3: Original Data
A |
|
|
|
|
|
01 |
|
|
|
|
|
011 |
|
|
|
|
|
0111 |
01110 |
|
|
|
0112 |
01120 |
|
|
|
0113 |
01130 |
# Filled data:
df_sector_code_filled %>%
select(1:5) %>%
head() %>%
kable(caption = "Table 4: Filled Data")
Table 4: Filled Data
A |
01 |
|
|
|
A |
|
011 |
|
|
A |
|
|
0111 |
01110 |
A |
|
|
0112 |
01120 |
A |
|
|
0113 |
01130 |
A |
|
|
0114 |
01140 |
Theo Quyết định số 27/2018/QĐ-TTg thì có tất cả 734 ngành cấp 5:
df_sector_code_filled %>% filter(str_count(code_level5) == 5) -> df_sector_code_level5
df_sector_code_level5 %>% pull(code_level5) -> code_level5
length(code_level5)
## [1] 734
Nhưng số lượng mã ngành cấp 5 của VES 2015 chỉ là 621:
dn2015 %>% filter(!is.na(nganh_kd)) -> dn2015
dn2015 %>%
pull(nganh_kd) %>%
unique() -> code_level5_ves
length(code_level5_ves)
## [1] 621
Dưới đây là 60 mã ngành ở VES 2015 không có mặt trong danh sách mã ngành của năm 2018:
condition_in2018 <- code_level5_ves %in% code_level5
code_level5_ves[!condition_in2018] -> not_in_2018
not_in_2018
## [1] "42200" "42102" "41000" "22120" "42101" "42900" "10750" "13220" "35101"
## [10] "35102" "20110" "68100" "13290" "68200" "24200" "23100" "52219" "61200"
## [19] "61100" "79200" "10409" "58190" "85311" "85100" "58110" "85322" "72100"
## [28] "63290" "52211" "49200" "85200" "01190" "65129" "02210" "01290" "01300"
## [37] "01410" "02220" "01450" "03230" "03122" "13210" "02109" "47110" "63210"
## [46] "10204" "13240" "03210" "72200" "58130" "85312" "13230" "51100" "02300"
## [55] "85321" "35200" "01440" "01420" "58120" "03121"
Như vậy một số tên ngành của VES 2015 không có mặt trong Quyết định số 27/2018/QĐ-TTg mới nhất. Việc này có thể là do: một số mã ngành của năm 2015 sau này được phân loại chi tiết hơn thành nhiều hơn thành 2, 3 ngành nhỏ hơn hoặc một số mã ngành ở 2015 đã được recode lại theo một cách thức nào đó mà ta chưa rõ. Trước khi đi đến một giải pháp tinh tế hơn, thì tạm thời chúng ta phân loại những ngành với mã ngành không có mặt trong danh sách của Quyết định số 27/2018/QĐ-TTg thành một nhóm chung có tên Unclassified. Trước hết join các bộ dữ liệu lại:
# join the two data sets:
dn2015 %>% full_join(
df_sector_code_level5 %>% select(sector_name_latin, code_level5),
by = c("nganh_kd" = "code_level5")
) -> dn2015_joined
# Relabel:
dn2015_joined %>% mutate(sector_name_latin = case_when(is.na(sector_name_latin) ~ "Unclassified", TRUE ~ sector_name_latin)) -> dn2015_joined
Hàm đánh giá tỉ lệ missing của dữ liệu:
missing_rate <- function(x) {sum(is.na(x)) / length(x)}
Hàm đánh giá tị lệ số âm:
negative_rate <- function(x) {sum(x < 0, na.rm = TRUE) / length(x)}
Tỉ lệ missing và số âm của cột biến kqkd1:
dn2015_joined$kqkd1 %>% missing_rate()
## [1] 0.03396914
dn2015_joined$kqkd1 %>% negative_rate()
## [1] 6.676325e-06
Giải pháp ngây thơ là loại bỏ các quan sát là số âm, missing ở cột biến kqkd1:
dn2015_joined %>%
filter(!is.na(kqkd1)) %>%
filter(kqkd1 >= 0) -> dn2015_joined1
Đến đây chúng ta có thể, ví dụ, tính tổng doanh thu theo ngành cấp 5:
dn2015_joined1 %>%
group_by(sector_name_latin) %>%
summarise(sales_by_sector = sum(kqkd1)) %>%
ungroup() %>%
arrange(-sales_by_sector) %>%
head() %>%
kable(caption = "Table 5: Total Revenue by Sector")
Table 5: Total Revenue by Sector
Unclassified |
1476867424 |
San xuat thiet bi truyen thong |
928331927 |
Ban buon xang dau va cac san pham lien quan |
484522160 |
Ban buon sat, thep |
329661497 |
May trang phuc (tru trang phuc tu da long thu) |
255777993 |
San xuat giay, dep |
219492935 |
Missing Data
Dữ liệu trống (Missing Data) là vấn đề luôn gặp ở gần như mọi dự án phân tích dữ liệu. Trước hết đánh giá tỉ lệ missing cho tất cả các cột biến của VES 2015:
sapply(dn2015_joined1, missing_rate) -> missing_columns
df_miss_rate <- tibble(var = names(dn2015_joined1), missing_rate_col = missing_columns)
df_miss_rate %>% arrange(-missing_rate_col) -> df_miss_rate
df_miss_rate %>%
head() %>%
kable(caption = "Table 5: Missing Rate by Column")
Table 5: Missing Rate by Column
kqkdn13 |
0.9873964 |
nkqkd13 |
0.9873872 |
nkd13 |
0.9853461 |
ldn131 |
0.9853115 |
nld13 |
0.9853069 |
ldn132 |
0.9853000 |
Từ table 5 có thể thấy tỉ lệ missing có thể là rất lớn: gần 100%. Dưới đây là danh sách (một số) cột biến có tỉ lệ missing bé hơn 50%:
df_miss_rate %>%
filter(missing_rate_col < 0.5) -> mising_under50
mising_under50 %>%
head() %>%
kable(caption = "Table 6: Columns with Missing Rate <= 50%")
Table 6: Columns with Missing Rate <= 50%
von_nn |
0.4991638 |
khucn |
0.4990601 |
tgnk_tt |
0.4895711 |
kqkdn1 |
0.4794002 |
nkd1 |
0.4789095 |
ldn11 |
0.4787874 |
Chúng ta cũng có thể sử dụng gói naniar để đánh giá tỉ lệ missing của data một cách nhanh chóng:
library(naniar)
# Missing rate for 25 columns:
gg_miss_var(dn2015_joined1 %>% select(1:25), show_pct = TRUE)

LS0tDQp0aXRsZTogIkRhdGEgUHJlLXByb2Nlc3Npbmc6IENhc2Ugb2YgVkVTIChWaWV0bmFtIEVudGVycHJpc2UgU3VydmV5KSINCmF1dGhvcjogJ0F1dGhvcjogTmd1eWVuIENoaSBEdW5nJw0Kc3VidGl0bGU6IERhdGEgUHJlLXByb2Nlc3NpbmcgU2VyaWVzDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgaGlnaGxpZ2h0OiB6ZW5idXJuDQogICAgdGhlbWU6IGZsYXRseQ0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgd29yZF9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCi0tLQ0KDQpgYGB7ciBzZXR1cCxpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgY2FjaGUgPSBUUlVFKQ0KDQpgYGANCg0KDQoNCiFbXShDOlxcVXNlcnNcXEFkbWluXFxEb2N1bWVudHNcXHZlcy5wbmcpDQoNCg0KIyBPdXIgUHJvYmxlbQ0KDQpUaeG7gW4geOG7rSBsw60gc+G7kSBsaeG7h3UgKERhdGEgUHJlLXByb2Nlc3NpbmcpIGzDoCBt4buZdCBraMOidSBt4bqldCBuaGnhu4F1IHRo4budaSBnaWFuLiBQb3N0IG7DoHkgc+G7rSBk4bulbmcgYuG7mSBk4buvIGxp4buHdSBWRVMgLSBWaWV0bmFtIEVudGVycHJpc2UgU3VydmV5IChkb3dubG9hZCBbdOG6oWkgxJHDonldKGh0dHBzOi8vd3d3Lm1lZGlhZmlyZS5jb20vZmlsZS9sa3p3OTl5N2szbzFhYTEvZG4yMDE1LnJhci9maWxlKSkuIEdp4bqjaSBuw6luIHbDoCDEkeG7jWMgYuG7mSBk4buvIGxp4buHdSBuw6B5OiANCg0KDQpgYGB7cn0NCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQojICBEYXRhIFByb2Nlc3NpbmcgUHJvamVjdCB3aXRoIHJlYWwtd29ybGQgZGF0YTogVkVTIDIwMTUgZGF0YSBzZXQNCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCiMgQ2xlYXIgd29yayBzcGFjZTogDQpybShsaXN0ID0gbHMoKSkNCg0KIyBJbXBvcnQgZGF0YTogDQoNCmhhdmVuOjpyZWFkX2R0YSgiRTovZG4yMDE1LmR0YSIpIC0+IGRuMjAxNQ0KDQojIExvYWQgZHBseXIgZ2Fja2FnZTogDQpsaWJyYXJ5KGRwbHlyKQ0KDQojIE51bWJlciBvZiBjb2x1bW5zL3Jvd3M6IA0KZGltKGRuMjAxNSkNCmBgYA0KDQoNCkLhu5kgZOG7ryBsaeG7h3UgbsOgeSBsw6AgbOG7m24gduG7m2kgNDU1MzAwIHF1YW4gc8OhdCB2w6AgMjIyIGPhu5l0IGJp4bq/bi4gVGhlbyBxdXkgxJHhu4tuaCBj4bunYSBjw6FjIGPGoSBxdWFuIHF14bqjbiBsw60gdOG6oWkgVmnhu4d0IE5hbSB0aMOsOiANCg0KMS4gTeG7l2kgbeG7mXQgRE4gY2jhu4kgY8OzIG3hu5l0IG3DoyBz4buRIHRodeG6vyBkdXkgbmjhuqV0IHbDoCBtw6Mgc+G7kSB0aHXhur8gbsOgeSBsw6AgxJHhu5ljIG5o4bqldC4gDQoyLiBN4buZdCBtw6Mgc+G7kSB0aHXhur8gaOG7o3AgbOG7hyBkw7luZyAxMCBjaOG7ryBz4buRLiANCg0KVHV5IG5oacOqbiBt4buZdCBz4buRIE1TVCAobcOjIHPhu5EgdGh14bq/KSB0cm9uZyBi4buZIGThu68gbGnhu4d1IGNo4buJIGPDsyA1IGNo4buvIHPhu5EuIE3hurd0IGtow6FjLCBt4buZdCBz4buRIEROIGPDsyBNU1QgdHLDuW5nIG5oYXUuIENow7puZyB0YSBz4bq9IGxv4bqhaSBi4buPIGPDoWMgcXVhbiBzw6F0IG7DoHkgcmEga2jhu49pIGLhu5kgZOG7ryBsaeG7h3UgYmFuIMSR4bqndSBuaMawIHNhdTogDQoNCg0KYGBge3J9DQojLS0tLS0tLS0tLSBSZW1vdmUgZmlybXMgd2l0aCBkdXBsaWNhdGVkIHRheCBjb2RlIC0tLS0tLS0tLS0tLS0tLQ0KDQpkbjIwMTUgJT4lIA0KICBncm91cF9ieShtYV90aHVlKSAlPiUgDQogIGNvdW50KCkgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0KICBmaWx0ZXIobiA+IDEpICU+JSANCiAgcHVsbChtYV90aHVlKSAtPiB0YXhfY29kZXNfZHVwbGljYXRlZA0KDQpkbjIwMTUgJT4lIGZpbHRlcighbWFfdGh1ZSAlaW4lIHRheF9jb2Rlc19kdXBsaWNhdGVkKSAtPiBkbjIwMTUNCg0KIy0tLS0tLS0tLSBSZW1vdmUgZmlybXMgd2l0aCB0YXggY29kZSAhPSAxMCBkaWdpdHMgLS0tLS0tLS0tLS0tLS0tLQ0KDQpsaWJyYXJ5KHN0cmluZ3IpDQoNCmRuMjAxNSAlPiUgZmlsdGVyKHN0cl9jb3VudChtYV90aHVlKSA9PSAxMCkgLT4gZG4yMDE1DQpgYGANCg0KVGhlbyBRdXnhur90IMSR4buLbmggc+G7kSAyNy8yMDE4L1HEkC1UVGcgdGjDrCBtw6MgbmfDoG5oIGPhuqVwIDUgY+G7p2EgZG9hbmggbmdoaeG7h3Agc+G6vSBwaOG6o2kgY8OzIDUgY2jhu68gc+G7kS4gVHV5IG5oacOqbiBt4buZdCBz4buRIHF1YW4gc8OhdCB0aMOsIG3DoyBuZ8OgbmggY2jhu4kgY8OzIDQgY2jhu68gc+G7kS4gVGEgY8OzIHRo4buDIHN1eSBsdeG6rW4gcuG6sW5nIG5ndXnDqm4gbmjDom4gbMOgIGRvIG3hu5l0IHPhu5EgbmfDoG5oIGPDsyBtw6MgduG7m2kgc+G7kSAwIMSR4bupbmcg4bufIHbhu4sgdHLDrSDEkeG6p3UgdGnDqm4gxJHDoyBi4buLICJiaeG6v24gbeG6pXQiIHRyb25nIHF1w6EgdHLDrG5oIHbDoG8tbMawdS1n4butaSBk4buvIGxp4buHdS4gVsOsIHbhuq15IGNow7puZyB0YSBjw7MgdGjhu4Mga2jDtGkgcGjhu6VjIGNo4bupIHPhu5EgMCBuw6B5IGNobyBjw6FjIHF1YW4gc8OhdCB24bubaSBtw6MgbmfDoG5oIGNo4buJIGPDsyA0IGNo4buvIHPhu5EgbmjGsCBzYXU6IA0KDQpgYGB7cn0NCiMgQWRkIDAgZm9yIDQtZGlnaXRzIHNlY3RvciBjb2RlczogDQoNCmRuMjAxNSAlPiUgDQogIG11dGF0ZShuZ2FuaF9rZCA9IGFzLmNoYXJhY3RlcihuZ2FuaF9rZCkpICU+JSANCiAgbXV0YXRlKG5nYW5oX2tkICA9IGNhc2Vfd2hlbihzdHJfY291bnQobmdhbmhfa2QpID09IDQgfiBzdHJfYygiMCIsIG5nYW5oX2tkKSwgVFJVRSB+IG5nYW5oX2tkKSkgLT4gZG4yMDE1DQpgYGANCg0KQ2jDum5nIHRhIGPDsyB0aOG7gyB4ZW0gcXVhIGThu68gbGnhu4d1IHNhdSBraGkgeOG7rSBsw606IA0KDQpgYGB7cn0NCmxpYnJhcnkoa25pdHIpDQoNCmRuMjAxNSAlPiUgDQogIGhlYWQoKSAlPiUgDQogIHNlbGVjdCgxOjcsIGNvbnRhaW5zKCJuZ2FuaCIpKSAlPiUgDQogIGthYmxlKGNhcHRpb24gPSAiVGFibGUgMTogU29tZSBPYnNlcnZhdGlvbnMiKQ0KYGBgDQoNCkzhuqV5IGThu68gbGnhu4d1IHbhu4EgbcOjIG5nw6BuaCBj4bunYSBRdXnhur90IMSR4buLbmggc+G7kSAyNy8yMDE4L1HEkC1UVGcgYuG6sW5nIMSRb+G6oW4gUiBjb2RlcyBkxrDhu5tpIMSRw6J5OiANCg0KYGBge3J9DQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojIEV4dHJhY3Qgc2VjdG9yIGNvZGUgZnJvbSBodHRwczovL2RhbmdreWtpbmhkb2FuaC5nb3Yudm4vdm4vUGFnZXMvTmdhbmhOZ2hlLmFzcHgNCiMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KbGlicmFyeShydmVzdCkNCg0KbGluayA8LSAiaHR0cHM6Ly9kYW5na3lraW5oZG9hbmguZ292LnZuL3ZuL1BhZ2VzL05nYW5oTmdoZS5hc3B4Ig0KDQpsaW5rICU+JSANCiAgcmVhZF9odG1sKCkgJT4lIA0KICBodG1sX25vZGVzKHhwYXRoID0gJy8vKltAaWQ9ImN0bDAwX1NQV2ViUGFydE1hbmFnZXIxX2dfMTVhMzBiOTlfNTBkOF80OWU1XzhmMTFfZGUyNjVjYjk0OWU4Il0vZGl2L2RpdlsyXS90YWJsZScpICU+JSANCiAgaHRtbF90YWJsZSgpICU+JSANCiAgLltbMV1dIC0+IGRmX3NlY3Rvcl9jb2RlDQoNCiMgUmVuYW1lIGZvciBjb2x1bW5zOiANCg0KbmFtZXMoZGZfc2VjdG9yX2NvZGUpIDwtIGMoc3RyX2MoImNvZGVfbGV2ZWwiLCAxOjUpLCAic2VjdG9yX25hbWUiKQ0KDQojIFJlbW92ZS8gZmlsdGVyIGRhdGE6IA0KDQpkZl9zZWN0b3JfY29kZSAlPiUgDQogIHNsaWNlKC0xKSAlPiUgDQogIGZpbHRlcihjb2RlX2xldmVsMSAhPSAiMjEiKSAlPiUgDQogIG11dGF0ZShzZWN0b3JfbmFtZV9sYXRpbiA9IHN0cmluZ2k6OnN0cmlfdHJhbnNfZ2VuZXJhbChzZWN0b3JfbmFtZSwgIkxhdGluLUFTQ0lJIikpIC0+IGRmX3NlY3Rvcl9jb2RlDQpgYGANCg0KQ8OzIDIxIG5nw6BuaCBs4bubbiAoQ+G6pXAgMSkgdGhlbyBxdXkgxJHhu4tuaCBj4bunYSBjaMOtbmggcGjhu6cgbmjGsCB0YSB0aOG6pXkgKGNo4buJIGhp4buDbiB0aOG7iyA2IG5nw6BuaCBj4bqlcCAxKTogDQoNCmBgYHtyfQ0KZGZfc2VjdG9yX2NvZGUgJT4lIA0KICBmaWx0ZXIoc3RyX2NvdW50KGNvZGVfbGV2ZWwxKSAhPSAwKSAlPiUgDQogIHNlbGVjdChjb2RlX2xldmVsMSwgc2VjdG9yX25hbWVfbGF0aW4pICU+JSANCiAgaGVhZCgpICU+JSANCiAga2FibGUoY2FwdGlvbiA9ICJUYWJsZSAyOiBTb21lIExldmVsLTEgU2VjdG9ycyIpDQpgYGANCg0KDQrEkOG7gyBs4bqlcCDEkeG6p3kgZOG7ryBsaeG7h3UgdHLhu5FuZyBjaG8gY+G7mXQgY29kZV9sZXZlbDEgY2jDum5nIHRhIGPDsyB0aOG7gyBz4butIGThu6VuZyBgdGlkeXI6OmZpbGwoKWAgbmjGsCBzYXU6IA0KDQpgYGB7cn0NCg0KZGZfc2VjdG9yX2NvZGUgJT4lIA0KICBtdXRhdGUoY29kZV9sZXZlbDEgPSBjYXNlX3doZW4oc3RyX2NvdW50KGNvZGVfbGV2ZWwxKSA9PSAwIH4gTkFfY2hhcmFjdGVyXywgVFJVRSB+IGNvZGVfbGV2ZWwxKSkgJT4lIA0KICB0aWR5cjo6ZmlsbChjb2RlX2xldmVsMSwgLmRpcmVjdGlvbiA9ICJkb3duIikgJT4lIA0KICBmaWx0ZXIoc3RyX2RldGVjdChzZWN0b3JfbmFtZV9sYXRpbiwgIlthLXpdIikpIC0+IGRmX3NlY3Rvcl9jb2RlX2ZpbGxlZA0KDQpgYGANCg0KU28gc8Ohbmg6IA0KDQpgYGB7cn0NCiMgT3JpZ2luYWwgZGF0YTogDQoNCmRmX3NlY3Rvcl9jb2RlICU+JSANCiAgc2VsZWN0KDE6NSkgJT4lIA0KICBoZWFkKCkgJT4lIA0KICBrYWJsZShjYXB0aW9uID0gIlRhYmxlIDM6IE9yaWdpbmFsIERhdGEiKQ0KDQojIEZpbGxlZCBkYXRhOiANCmRmX3NlY3Rvcl9jb2RlX2ZpbGxlZCAlPiUgDQogIHNlbGVjdCgxOjUpICU+JSANCiAgaGVhZCgpICU+JSANCiAga2FibGUoY2FwdGlvbiA9ICJUYWJsZSA0OiBGaWxsZWQgRGF0YSIpDQpgYGANCg0KVGhlbyBRdXnhur90IMSR4buLbmggc+G7kSAyNy8yMDE4L1HEkC1UVGcgdGjDrCBjw7MgdOG6pXQgY+G6oyA3MzQgbmfDoG5oIGPhuqVwIDU6IA0KYGBge3J9DQpkZl9zZWN0b3JfY29kZV9maWxsZWQgJT4lIGZpbHRlcihzdHJfY291bnQoY29kZV9sZXZlbDUpID09IDUpIC0+IGRmX3NlY3Rvcl9jb2RlX2xldmVsNQ0KDQpkZl9zZWN0b3JfY29kZV9sZXZlbDUgJT4lIHB1bGwoY29kZV9sZXZlbDUpIC0+IGNvZGVfbGV2ZWw1DQoNCmxlbmd0aChjb2RlX2xldmVsNSkNCmBgYA0KDQpOaMawbmcgc+G7kSBsxrDhu6NuZyBtw6MgbmfDoG5oIGPhuqVwIDUgY+G7p2EgVkVTIDIwMTUgY2jhu4kgbMOgIDYyMTogDQoNCmBgYHtyfQ0KZG4yMDE1ICU+JSBmaWx0ZXIoIWlzLm5hKG5nYW5oX2tkKSkgLT4gZG4yMDE1DQoNCmRuMjAxNSAlPiUgDQogIHB1bGwobmdhbmhfa2QpICU+JSANCiAgdW5pcXVlKCkgLT4gY29kZV9sZXZlbDVfdmVzDQoNCmxlbmd0aChjb2RlX2xldmVsNV92ZXMpDQpgYGANCg0KRMaw4bubaSDEkcOieSBsw6AgNjAgbcOjIG5nw6BuaCDhu58gVkVTIDIwMTUga2jDtG5nIGPDsyBt4bq3dCB0cm9uZyBkYW5oIHPDoWNoIG3DoyBuZ8OgbmggY+G7p2EgbsSDbSAyMDE4OiANCg0KYGBge3J9DQpjb25kaXRpb25faW4yMDE4IDwtIGNvZGVfbGV2ZWw1X3ZlcyAlaW4lIGNvZGVfbGV2ZWw1DQoNCmNvZGVfbGV2ZWw1X3Zlc1shY29uZGl0aW9uX2luMjAxOF0gLT4gbm90X2luXzIwMTgNCg0Kbm90X2luXzIwMTgNCmBgYA0KDQpOaMawIHbhuq15IG3hu5l0IHPhu5EgdMOqbiBuZ8OgbmggY+G7p2EgVkVTIDIwMTUga2jDtG5nIGPDsyBt4bq3dCB0cm9uZyBRdXnhur90IMSR4buLbmggc+G7kSAyNy8yMDE4L1HEkC1UVGcgbeG7m2kgbmjhuqV0LiBWaeG7h2MgbsOgeSBjw7MgdGjhu4MgbMOgIGRvOiBt4buZdCBz4buRIG3DoyBuZ8OgbmggY+G7p2EgbsSDbSAyMDE1IHNhdSBuw6B5IMSRxrDhu6NjIHBow6JuIGxv4bqhaSBjaGkgdGnhur90IGjGoW4gdGjDoG5oIG5oaeG7gXUgaMahbiB0aMOgbmggMiwgMyBuZ8Ogbmggbmjhu48gaMahbiBob+G6t2MgbeG7mXQgc+G7kSBtw6MgbmfDoG5oIOG7nyAyMDE1IMSRw6MgxJHGsOG7o2MgcmVjb2RlIGzhuqFpIHRoZW8gbeG7mXQgY8OhY2ggdGjhu6ljIG7DoG8gxJHDsyBtw6AgdGEgY2jGsGEgcsO1LiBUcsaw4bubYyBraGkgxJFpIMSR4bq/biBt4buZdCBnaeG6o2kgcGjDoXAgdGluaCB04bq/IGjGoW4sIHRow6wgdOG6oW0gdGjhu51pIGNow7puZyB0YSBwaMOibiBsb+G6oWkgbmjhu69uZyBuZ8OgbmggduG7m2kgbcOjIG5nw6BuaCBraMO0bmcgY8OzIG3hurd0IHRyb25nIGRhbmggc8OhY2ggY+G7p2EgUXV54bq/dCDEkeG7i25oIHPhu5EgMjcvMjAxOC9RxJAtVFRnIHRow6BuaCBt4buZdCBuaMOzbSBjaHVuZyBjw7MgdMOqbiAqVW5jbGFzc2lmaWVkKi4gVHLGsOG7m2MgaOG6v3Qgam9pbiBjw6FjIGLhu5kgZOG7ryBsaeG7h3UgbOG6oWk6IA0KDQpgYGB7cn0NCiMgam9pbiB0aGUgdHdvIGRhdGEgc2V0czogDQpkbjIwMTUgJT4lIGZ1bGxfam9pbigNCiAgZGZfc2VjdG9yX2NvZGVfbGV2ZWw1ICU+JSBzZWxlY3Qoc2VjdG9yX25hbWVfbGF0aW4sIGNvZGVfbGV2ZWw1KSwgDQogIGJ5ID0gYygibmdhbmhfa2QiID0gImNvZGVfbGV2ZWw1IikNCiAgKSAtPiBkbjIwMTVfam9pbmVkDQoNCiMgUmVsYWJlbDogDQoNCmRuMjAxNV9qb2luZWQgJT4lIG11dGF0ZShzZWN0b3JfbmFtZV9sYXRpbiA9IGNhc2Vfd2hlbihpcy5uYShzZWN0b3JfbmFtZV9sYXRpbikgfiAiVW5jbGFzc2lmaWVkIiwgVFJVRSB+IHNlY3Rvcl9uYW1lX2xhdGluKSkgLT4gZG4yMDE1X2pvaW5lZA0KYGBgDQoNCkjDoG0gxJHDoW5oIGdpw6EgdOG7iSBs4buHIG1pc3NpbmcgY+G7p2EgZOG7ryBsaeG7h3U6IA0KDQpgYGB7cn0NCm1pc3NpbmdfcmF0ZSA8LSBmdW5jdGlvbih4KSB7c3VtKGlzLm5hKHgpKSAvIGxlbmd0aCh4KX0NCmBgYA0KDQpIw6BtIMSRw6FuaCBnacOhIHThu4sgbOG7hyBz4buRIMOibTogDQoNCmBgYHtyfQ0KbmVnYXRpdmVfcmF0ZSA8LSBmdW5jdGlvbih4KSB7c3VtKHggPCAwLCBuYS5ybSA9IFRSVUUpIC8gbGVuZ3RoKHgpfQ0KYGBgDQoNClThu4kgbOG7hyBtaXNzaW5nIHbDoCBz4buRIMOibSBj4bunYSBj4buZdCBiaeG6v24ga3FrZDE6IA0KDQpgYGB7cn0NCmRuMjAxNV9qb2luZWQka3FrZDEgJT4lIG1pc3NpbmdfcmF0ZSgpDQoNCmRuMjAxNV9qb2luZWQka3FrZDEgJT4lIG5lZ2F0aXZlX3JhdGUoKQ0KYGBgDQoNCkdp4bqjaSBwaMOhcCBuZ8OieSB0aMahIGzDoCBsb+G6oWkgYuG7jyBjw6FjIHF1YW4gc8OhdCBsw6Agc+G7kSDDom0sIG1pc3Npbmcg4bufIGPhu5l0IGJp4bq/biBrcWtkMTogDQoNCmBgYHtyfQ0KZG4yMDE1X2pvaW5lZCAlPiUgDQogIGZpbHRlcighaXMubmEoa3FrZDEpKSAlPiUgDQogIGZpbHRlcihrcWtkMSA+PSAwKSAtPiBkbjIwMTVfam9pbmVkMQ0KYGBgDQoNCsSQ4bq/biDEkcOieSBjaMO6bmcgdGEgY8OzIHRo4buDLCB2w60gZOG7pSwgdMOtbmggdOG7lW5nIGRvYW5oIHRodSB0aGVvIG5nw6BuaCBj4bqlcCA1OiANCg0KYGBge3J9DQpkbjIwMTVfam9pbmVkMSAlPiUgDQogIGdyb3VwX2J5KHNlY3Rvcl9uYW1lX2xhdGluKSAlPiUgDQogIHN1bW1hcmlzZShzYWxlc19ieV9zZWN0b3IgPSBzdW0oa3FrZDEpKSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQogIGFycmFuZ2UoLXNhbGVzX2J5X3NlY3RvcikgJT4lIA0KICBoZWFkKCkgJT4lIA0KICBrYWJsZShjYXB0aW9uID0gIlRhYmxlIDU6IFRvdGFsIFJldmVudWUgYnkgU2VjdG9yIikNCmBgYA0KDQojIE1pc3NpbmcgRGF0YQ0KDQpE4buvIGxp4buHdSB0cuG7kW5nIChNaXNzaW5nIERhdGEpIGzDoCB24bqlbiDEkeG7gSBsdcO0biBn4bq3cCDhu58gZ+G6p24gbmjGsCBt4buNaSBk4buxIMOhbiBwaMOibiB0w61jaCBk4buvIGxp4buHdS4gVHLGsOG7m2MgaOG6v3QgxJHDoW5oIGdpw6EgdOG7iSBs4buHIG1pc3NpbmcgY2hvIHThuqV0IGPhuqMgY8OhYyBj4buZdCBiaeG6v24gY+G7p2EgVkVTIDIwMTU6IA0KDQpgYGB7cn0NCnNhcHBseShkbjIwMTVfam9pbmVkMSwgbWlzc2luZ19yYXRlKSAtPiBtaXNzaW5nX2NvbHVtbnMNCg0KZGZfbWlzc19yYXRlIDwtIHRpYmJsZSh2YXIgPSBuYW1lcyhkbjIwMTVfam9pbmVkMSksIG1pc3NpbmdfcmF0ZV9jb2wgPSBtaXNzaW5nX2NvbHVtbnMpDQoNCmRmX21pc3NfcmF0ZSAlPiUgYXJyYW5nZSgtbWlzc2luZ19yYXRlX2NvbCkgLT4gZGZfbWlzc19yYXRlDQoNCmRmX21pc3NfcmF0ZSAlPiUgDQogIGhlYWQoKSAlPiUgDQogIGthYmxlKGNhcHRpb24gPSAiVGFibGUgNTogTWlzc2luZyBSYXRlIGJ5IENvbHVtbiIpDQoNCmBgYA0KDQpU4burIHRhYmxlIDUgY8OzIHRo4buDIHRo4bqleSB04buJIGzhu4cgbWlzc2luZyBjw7MgdGjhu4MgbMOgIHLhuqV0IGzhu5tuOiBn4bqnbiAxMDAlLiBExrDhu5tpIMSRw6J5IGzDoCBkYW5oIHPDoWNoICht4buZdCBz4buRKSBj4buZdCBiaeG6v24gY8OzIHThu4kgbOG7hyBtaXNzaW5nIGLDqSBoxqFuIDUwJTogDQoNCmBgYHtyfQ0KZGZfbWlzc19yYXRlICU+JSANCiAgZmlsdGVyKG1pc3NpbmdfcmF0ZV9jb2wgPCAwLjUpIC0+IG1pc2luZ191bmRlcjUwDQoNCm1pc2luZ191bmRlcjUwICU+JSANCiAgaGVhZCgpICU+JSANCiAga2FibGUoY2FwdGlvbiA9ICJUYWJsZSA2OiBDb2x1bW5zIHdpdGggTWlzc2luZyBSYXRlIDw9IDUwJSIpDQpgYGANCg0KQ2jDum5nIHRhIGPFqW5nIGPDsyB0aOG7gyBz4butIGThu6VuZyBnw7NpICoqbmFuaWFyKiogxJHhu4MgxJHDoW5oIGdpw6EgdOG7iSBs4buHIG1pc3NpbmcgY+G7p2EgZGF0YSBt4buZdCBjw6FjaCBuaGFuaCBjaMOzbmc6IA0KDQpgYGB7cn0NCmxpYnJhcnkobmFuaWFyKQ0KDQojIE1pc3NpbmcgcmF0ZSBmb3IgMjUgY29sdW1uczogDQpnZ19taXNzX3ZhcihkbjIwMTVfam9pbmVkMSAlPiUgc2VsZWN0KDE6MjUpLCBzaG93X3BjdCA9IFRSVUUpIA0KDQpgYGANCg0KDQojIENvbmNsdXNpb24NCg0KRGF0YSBQcmUtcHJvY2Vzc2luZy9EYXRhIFdyYW5nbGluZyBsw6Aga2jDonUgbeG6pXQgbmhp4buBdSB0aOG7nWkgZ2lhbiBj4bunYSBt4buZdCBk4buxIMOhbiBwaMOibiB0w61jaCBk4buvIGxp4buHdS4gUG9zdCBuw6B5IGtow7RuZyB0aOG7gyBjb3ZlciBo4bq/dCBt4buNaSBraMOtYSBj4bqhbmggY+G7p2EgcXXDoSB0csOsbmggbsOgeS4gDQoNCg0KDQoNCg0KDQoNCg0K