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ì:

  1. 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.
  2. 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
tinh capso macs ma_thue huyen xa tthd tennganhkd nganh_kd
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
code_level1 sector_name_latin
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
code_level1 code_level2 code_level3 code_level4 code_level5
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
code_level1 code_level2 code_level3 code_level4 code_level5
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
sector_name_latin sales_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
var missing_rate_col
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%
var missing_rate_col
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) 

Conclusion

Data Pre-processing/Data Wrangling là khâu mất nhiều thời gian của một dự án phân tích dữ liệu. Post này không thể cover hết mọi khía cạnh của quá trình này.

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