1. Mục tiêu bài học
Bài 2 tiếp nối trực tiếp Bài 1. Dữ liệu đầu vào là wbes2023_renamed.dta, trong đó các biến đã được đổi sang tên dễ đọc hơn nhưng chưa được làm sạch. Mục tiêu của bài này là kiểm tra chất lượng dữ liệu, xử lý các mã trả lời đặc biệt, kiểm tra phạm vi giá trị, tính nhất quán giữa các biến, logic bảng hỏi, missing và ngoại lệ trước khi chuyển sang xây dựng biến phân tích ở Bài 3.
Nguyên tắc xuyên suốt là không ghi đè tệp đầu vào, không tự động xóa ngoại lệ và không winsorize (không thay thế các giá trị ngoại lệ bằng giá trị tại các ngưỡng phân vị đã chọn). Một giá trị bất thường chưa chắc là sai; trước hết cần phát hiện, mô tả và đối chiếu với bảng hỏi hoặc tài liệu dữ liệu.
2. Chuẩn bị dữ liệu
clear all
set more off
cd “D:\Stata\WBES2023”
use “wbes2023_renamed.dta”, clear
clear all xóa dữ liệu và các đối tượng đang có trong bộ nhớ; set more off giúp Stata chạy liên tục. Lệnh cd xác định thư mục làm việc và use mở dữ liệu đã đổi tên từ Bài 1. Bài 2 sẽ lưu kết quả thành một tệp mới, vì vậy dữ liệu của Bài 1 vẫn được giữ nguyên.
3. Kiểm tra dữ liệu đầu vào
describe
describe, short
count
isid id
duplicates report id
misstable summarize
Lệnh describe cho biết cấu trúc dữ liệu; còn lệnh describe, short cho một bản tóm tắt ngắn hơn; lệnh count cho biết số quan sát. Lệnh isid id kiểm tra id có thực sự xác định duy nhất từng doanh nghiệp/cơ sở hay không, còn lệnh duplicates report id giúp phát hiện id trùng. Đây là các kiểm tra nên thực hiện trước mọi thao tác làm sạch.
Lệnh misstable summarize cho cái nhìn tổng quát về missing. Tuy nhiên, ở thời điểm này một số câu trả lời đặc biệt vẫn đang được mã hóa bằng số âm nên chưa được Stata tính là system missing (tức là không phải là các dấu .). Do kết quả sau lệnh này hơi dài (vì rất nhiều biến), nên tôi không trình bày ở đây. Bạn hãy xem do-file.
3.1. Đếm missing theo từng biến
foreach var of varlist _all {
quietly count if missing(`var’)
if r(N) > 0 {
display “`var’: ” r(N) ” missing”
}
}
Vòng lặp đi qua toàn bộ biến và chỉ hiển thị những biến có ít nhất một giá trị missing. quietly làm cho kết quả gọn hơn vì Stata không in kết quả của từng lệnh count trung gian.
Một số kết quả điển hình:
- n_estab: 986 missing
- headquarters: 986 missing
- hq_sepfs: 988 missing
- hq_prodsales: 988 missing
- separate_fs: 1027 missing
- legal_other: 1028 missing
- female_ownpct: 768 missing
- quality_certtxt: 839 missing
- elec_waitday: 949 missing
- elec_bribe: 949 missing
- elec_conncost: 949 missing
- outage_n: 484 missing
- outage_hour: 575 missing
- outage_min: 575 missing
- outage_losspct: 511 missing
- outage_lossval: 1001 missing
- generator_pct: 764 missing
- water_waitday: 978 missing
3.2. Kiểm tra các mã trả lời đặc biệt
ds, has(type numeric)
local numvars `r(varlist)’
foreach v of local numvars {
quietly count if `v’ == -9
if r(N) > 0 {
display as text “`v’: ” as result r(N) as text ” Don’t know (-9)”
}
}
Một số kết quả điển hình:
- n_estab: 1 Don’t know (-9)
- owner_manager: 7 Don’t know (-9)
- own_domestic: 7 Don’t know (-9)
- own_foreign: 7 Don’t know (-9)
- own_state: 7 Don’t know (-9)
- own_other: 7 Don’t know (-9)
- female_owner: 5 Don’t know (-9)
- female_ownpct: 2 Don’t know (-9)
- start_emp: 50 Don’t know (-9)
- manager_exp: 60 Don’t know (-9)
- female_manager: 1 Don’t know (-9)
- quality_cert: 26 Don’t know (-9)
foreach v of local numvars {
quietly count if `v’ < 0 & `v’ != -9
if r(N) > 0 {
display as text “Variable: `v'”
tab `v’ if `v’ < 0 & `v’ != -9
}
}
Một số kết quả điển hình:

ds, has(type numeric) xác định tất cả biến số và lưu danh sách vào local numvars. Hai vòng lặp tiếp theo giúp nhận diện -9 và các mã âm khác. Trong WBES, các mã âm này là mã trả lời đặc biệt chứ không phải giá trị định lượng thực. Ý nghĩa cụ thể của một số mã có thể phụ thuộc câu hỏi, vì vậy không nên gán một ý nghĩa chung cho mọi biến chỉ dựa vào con số âm.
4. Xử lý các mã trả lời đặc biệt
foreach v of local numvars {
quietly count if `v’ < 0 & `v’ != -9
if r(N) > 0 {
display as text “Variable: `v'”
tab `v’ if `v’ < 0 & `v’ != -9, nolabel
}
}
Một số kết quả điển hình:

foreach v of local numvars {
quietly replace `v’ = . if inrange(`v’, -9, -4)
}
misstable summarize
Một số kết quả điển hình:

Tùy chọn nolabel cho phép nhìn trực tiếp mã số gốc. Sau khi đã kiểm tra, các mã từ -9 đến -4 được chuyển thành system missing (.). Cách làm này phù hợp với mục tiêu của bộ dữ liệu sạch dùng cho phân tích định lượng: các mã trả lời đặc biệt không được phép tham gia tính trung bình, tổng, hồi quy hoặc các phép toán số như thể chúng là giá trị thực.
Việc chuyển sang . làm mất sự phân biệt giữa các lý do không trả lời trong tệp sạch. Khi cần nghiên cứu riêng lý do từ chối, không áp dụng, không biết hoặc các trường hợp đặc biệt khác, cần quay lại wbes2023_renamed.dta và đối chiếu value labels/bảng hỏi.
5. Kiểm tra phạm vi giá trị
5.1. Các biến tỷ lệ/phần trăm
local pctvars own_domestic own_foreign own_state own_other female_ownpct ///
main_salespct domestic_salespct indirect_exportpct direct_exportpct ///
export_comppct digitalexp_costpct input_domesticpct input_foreignpct ///
import_comppct largest_comppct generator_pct outage_losspct wshort_losspct ///
internet_losspct wc_internalpct wc_bankpct wc_nonbankpct wc_tradepct ///
wc_otherpct fa_internalpct fa_equitypct fa_bankpct fa_nonbankpct ///
fa_tradepct fa_otherpct epay_receivepct epay_makepct regulation_timepct ///
informal_paypct highschool_pct train_prod_pct train_nonprod_pct ///
train_female_pct income_taxrate consump_taxrate capacity_util
foreach v of local pctvars {
quietly count if (`v’ < 0 | `v’ > 100) & !missing(`v’)
if r(N) > 0 {
display as error “`v’: ” r(N) ” giá trị ngoài khoảng 0-100″
}
}
Danh sách pctvars được xác định từ nội dung câu hỏi và nhãn biến, không phải do Stata tự động nhận diện. Các biến này được kỳ vọng nằm trong khoảng 0–100. Điều kiện !missing(v') rất quan trọng vì trong Stata, system missing được xếp lớn hơn các số thực; nếu chỉ kiểm tra v’ > 100 thì missing có thể bị đếm nhầm là giá trị vượt 100.
5.2. Các biến năm
local yearvars fyend_year start_year register_year first_exportyr credit_year
foreach v of local yearvars {
quietly count if (`v’ < 1900 | `v’ > 2023) & !missing(`v’)
if r(N) > 0 {
display as error “`v’: ” r(N) ” quan sát cần kiểm tra về năm”
}
}
Đây là kiểm tra sơ bộ nhằm phát hiện năm bất hợp lý. Ngưỡng 1900–2023 được dùng như một phạm vi kiểm tra cho bộ WBES 2023, không có nghĩa mọi năm trong khoảng đều chắc chắn đúng. Nếu có quan sát bị cảnh báo, cần đối chiếu nội dung câu hỏi và bối cảnh của doanh nghiệp.
5.3. Các biến số lượng, thời gian, chi phí và giá trị
local nonnegvars screen_emp n_estab start_emp manager_exp elec_waitday elec_conncost ///
elec_kwh outage_n outage_hour outage_min outage_lossval water_waitday water_conncost ///
water_use wshort_n wshort_hour wshort_lossval broadband_waitday broadband_cost ///
internet_hour internet_min internet_lossval hotel_rooms hotel_area sales sales_2y ///
sales_3y expcustom_day expcustom_hour expborder_day expborder_hour digitalexp_day ///
digitalexp_hour impcustom_day impcustom_hour impborder_day impborder_hour competitors_n ///
rd_value constpermit_day occuppermit_day occuppermit_cost selling_area ///
climate_damageval equipment_exp landbuild_exp fa_internalval fa_equityval fa_bankval ///
fa_nonbankval fa_tradeval fa_otherval equipment_exp1y landbuild_exp1y credit_value ///
collat_value credit_n credit_balance loan_applycost loan_waitday epay_recvday ///
epay_recvcost epay_recvfee epay_makecost taxinspect_n taxaudit_week taxcomply_yearhr ///
taxcomply_monthhr vat_refundweek gov_paymentday informal_payval importlic_day operlic_day ///
emp_fulltime emp_fulltime3y emp_production emp_nonprod emp_skilled emp_semiskill ///
emp_lowskill female_prod female_nonprod female_emp highschool_n train_prod_n ///
train_nonprod_n train_female_n workers_needed workers_hired dismiss_week severance_week ///
male_emp temp_emp female_temp temp_months labdispute_week labdispute_cost labor_cost ///
socialsec_tax electricity_cost water_cost broadband_costyr rawmaterial_cost ///
resalegoods_cost labor_cost2y rawmaterial_prev machinery_replace
foreach v of local nonnegvars {
quietly count if `v’ < 0 & !missing(`v’)
if r(N) > 0 {
display as error “`v’: ” r(N) ” giá trị âm cần kiểm tra”
}
}
Sau khi special codes đã được chuyển thành missing, các biến số lượng, thời gian, chi phí và giá trị trong danh sách này không nên âm. Lệnh chỉ phát hiện trường hợp cần xem lại; nó không tự động thay đổi dữ liệu. capacity_util không nằm trong nhóm này vì được kiểm tra chặt hơn như một tỷ lệ từ 0 đến 100.
6. Kiểm tra tính nhất quán giữa các biến
Một biến có thể hợp lệ khi xem riêng lẻ nhưng không nhất quán khi đặt cạnh các biến liên quan. Bài 2 kiểm tra năm nhóm tỷ lệ mà tổng các thành phần được kỳ vọng bằng 100 khi tất cả thành phần đều có dữ liệu.
6.1. Cơ cấu sở hữu
tempvar own_total own_miss
egen own_total' = rowtotal(own_domestic own_foreign own_state own_other) egen own_miss’ = rowmiss(own_domestic own_foreign own_state own_other)
count if own_miss' == 0 & own_total’ != 100
list id own_domestic own_foreign own_state own_other own_total' /// if own_miss’ == 0 & `own_total’ != 100, noobs
drop own_total' own_miss’
rowtotal tính tổng bốn tỷ lệ sở hữu; rowmiss đếm số thành phần bị missing. Chỉ khi own_miss bằng 0 mới yêu cầu tổng bằng 100. tempvar tạo các biến phục vụ kiểm tra, sau đó drop ngay để chúng không đi vào tệp dữ liệu sạch.
6.2. Các nhóm tỷ lệ còn lại
Doanh thu:
tempvar salesmarket_total sales_miss
egen salesmarket_total' = rowtotal(domestic_salespct indirect_exportpct direct_exportpct) egen sales_miss’ = rowmiss(domestic_salespct indirect_exportpct direct_exportpct)
count if sales_miss' == 0 & salesmarket_total’ != 100
list id domestic_salespct indirect_exportpct direct_exportpct salesmarket_total' /// if sales_miss’ == 0 & salesmarket_total' != 100, noobs drop salesmarket_total’ `sales_miss’
Nguồn đầu vào:
tempvar input_total input_miss
egen input_total' = rowtotal(input_domesticpct input_foreignpct) egen input_miss’ = rowmiss(input_domesticpct input_foreignpct)
count if input_miss' == 0 & input_total’ != 100
list id input_domesticpct input_foreignpct input_total' /// if input_miss’ == 0 & input_total' != 100, noobs drop input_total’ `input_miss’
Hai kiểm tra trên áp dụng cùng một nguyên tắc cho cơ cấu doanh thu theo thị trường và nguồn đầu vào. Nếu count bằng 0, không có trường hợp đầy đủ dữ liệu nhưng tổng khác 100.
Nguồn tài trợ vốn lưu động:
tempvar wc_total wc_miss
egen wc_total' = rowtotal(wc_internalpct wc_bankpct wc_nonbankpct wc_tradepct wc_otherpct) egen wc_miss’ = rowmiss(wc_internalpct wc_bankpct wc_nonbankpct wc_tradepct wc_otherpct)
count if wc_miss' == 0 & wc_total’ != 100
list id wc_internalpct wc_bankpct wc_nonbankpct wc_tradepct wc_otherpct wc_total' /// if wc_miss’ == 0 & wc_total' != 100, noobs drop wc_total’ `wc_miss’
Nguồn tài trợ tài sản cố định:
tempvar fa_total fa_miss
egen fa_total' = rowtotal(fa_internalpct fa_equitypct fa_bankpct fa_nonbankpct fa_tradepct fa_otherpct) egen fa_miss’ = rowmiss(fa_internalpct fa_equitypct fa_bankpct fa_nonbankpct fa_tradepct fa_otherpct)
count if fa_miss' == 0 & fa_total’ != 100
list id fa_internalpct fa_equitypct fa_bankpct fa_nonbankpct fa_tradepct fa_otherpct fa_total' /// if fa_miss’ == 0 & fa_total' != 100, noobs drop fa_total’ `fa_miss’
Hai nhóm tài trợ cũng được kiểm tra theo cùng logic. Việc dùng rowmiss tránh sai lầm coi missing như 0 khi đánh giá tổng tỷ lệ.
7. Kiểm tra tính nhất quán theo logic bảng hỏi (skip patterns)
Skip pattern là logic rẽ nhánh của bảng hỏi: câu trả lời ở một câu quyết định doanh nghiệp có được hỏi các câu tiếp theo hay không. Vì vậy missing ở các câu chi tiết đôi khi là hoàn toàn hợp lệ. Ngược lại, nếu doanh nghiệp trả lời “không” ở câu điều kiện nhưng vẫn có dữ liệu ở câu chi tiết, trường hợp đó cần được xem lại.
count if power_outage == 2 & (!missing(outage_n) | !missing(outage_hour) | ///
!missing(outage_min) | !missing(outage_losspct) | !missing(outage_lossval))
count if generator == 2 & !missing(generator_pct)
count if water_shortage == 2 & (!missing(wshort_n) | !missing(wshort_hour) | ///
!missing(wshort_losspct) | !missing(wshort_lossval))
count if internet_disrupt == 2 & (!missing(internet_hour) | !missing(internet_min) | ///
!missing(internet_losspct) | !missing(internet_lossval))
count if fixedasset_buy == 2 & (!missing(equipment_exp) | !missing(landbuild_exp))
count if loan_apply == 4 & !missing(loan_outcome)
count if formal_training == 2 & (!missing(train_prod_pct) | !missing(train_prod_n) | ///
!missing(train_nonprod_pct) | !missing(train_nonprod_n) | ///
!missing(train_female_pct) | !missing(train_female_n))
Các lệnh trên kiểm tra lần lượt mất điện, máy phát điện, thiếu nước, gián đoạn internet, mua tài sản cố định, nộp đơn vay/hạn mức tín dụng và đào tạo chính thức. Đặc biệt, loan_apply có nhiều phương án trả lời: chỉ loan_apply == 4 mới là không nộp đơn. Vì vậy không được suy luận mã 2 là “No” chỉ vì nhiều biến nhị phân khác dùng 1/2 cho Yes/No.
Nếu count bằng 0, dữ liệu không cho thấy vi phạm logic đang kiểm tra. Nếu count lớn hơn 0, chưa nên tự động recode hoặc xóa; cần xem các quan sát cụ thể và đối chiếu bảng hỏi trước khi quyết định.
Kết quả đều có count = 0.
8. Kiểm tra ngoại lệ và phân phối
summarize sales sales_2y sales_3y, detail
summarize emp_fulltime emp_fulltime3y start_emp, detail
summarize labor_cost rawmaterial_cost electricity_cost, detail
summarize equipment_exp landbuild_exp credit_value credit_balance, detail
summarize rd_value climate_damageval, detail
Tùy chọn detail cung cấp thêm percentile, độ lệch, độ nhọn và các giá trị cực trị, giúp nhận diện phân phối lệch và quan sát rất lớn. Trong dữ liệu doanh nghiệp, doanh thu, lao động, chi phí hoặc đầu tư có thể chênh lệch rất mạnh giữa các doanh nghiệp; vì vậy giá trị lớn không đồng nghĩa với lỗi.
Một số kết quả điển hình:

Bài 2 chỉ nhận diện ngoại lệ, không xóa và không winsorize. Winsorize là thay các giá trị ở hai đầu phân phối bằng giá trị tại một ngưỡng percentile được chọn. Nếu kỹ thuật này cần thiết cho một mô hình cụ thể, quyết định đó nên được thực hiện và giải thích ở giai đoạn phân tích sau.
8.1. Histogram và box plot: từ mặc định đến trình bày dễ đọc
histogram sales, frequency ///
title(“Phân phối doanh thu của doanh nghiệp”) ///
xtitle(“Doanh thu”) ///
ytitle(“Tần số”) ///
xlabel(, format(%12.0fc))

graph box sales, ///
title(“Phân phối doanh thu của doanh nghiệp”) ///
ytitle(“Doanh thu”) ///
ylabel(, format(%12.0fc))

8.2. Đổi đơn vị tạm thời để trình bày biến tiền tệ lớn
tempvar sales_bil
gen sales_bil' = sales/1e9 histogram sales_bil’, frequency ///
title(“Phân phối doanh thu của doanh nghiệp”) ///
xtitle(“Doanh thu (tỷ đồng)”) ///
ytitle(“Tần số”) ///
xlabel(, format(%12.0fc))

tempvar sales_bil
graph box sales_bil', /// title("Phân phối doanh thu của doanh nghiệp") /// ytitle("Doanh thu (tỷ đồng)") /// ylabel(, format(%12.0fc))
drop sales_bil’

tempvar labor_cost_bil
gen `labor_cost_bil’ = labor_cost/1000000000
graph box `labor_cost_bil’, ///
title(“Phân phối chi phí lao động của doanh nghiệp”) ///
ytitle(“Chi phí lao động (tỷ đồng)”) ///
ylabel(, format(%12.0fc))

histogram `labor_cost_bil’, frequency ///
title(“Phân phối chi phí lao động của doanh nghiệp”) ///
xtitle(“Chi phí lao động (tỷ đồng)”) ///
ytitle(“Tần số”) ///
xlabel(, format(%12.0fc))
drop `labor_cost_bil’

9. Kiểm tra missing sau khi làm sạch
misstable summarize
codebook sales emp_fulltime female_owner female_manager product_innov /// process_innov bank_account loan_apply climate_damage

Sau khi special codes đã được chuyển thành system missing, misstable summarize phản ánh missing phù hợp hơn cho phân tích. codebook được dùng cho một số biến quan trọng để xem số quan sát, missing, phạm vi và phân phối giá trị.
Không nên tự động coi missing là 0. Trong WBES, missing có thể xuất phát từ skip pattern, câu hỏi không áp dụng, không biết, từ chối trả lời hoặc nguyên nhân khác. Ý nghĩa của missing phải được xem xét trong bối cảnh từng câu hỏi.
10. Kiểm tra cuối cùng và lưu dữ liệu sạch
describe
describe, short
count
duplicates report id
compress
save “wbes2023_clean.dta”, replace
Các lệnh cuối cùng kiểm tra lại cấu trúc, số quan sát và id trùng. compress tối ưu kiểu lưu trữ để giảm dung lượng mà không làm thay đổi giá trị dữ liệu. Cuối cùng, dữ liệu được lưu thành wbes2023_clean.dta. Tệp wbes2023_renamed.dta vẫn được giữ nguyên để có thể quay lại kiểm tra special codes hoặc đối chiếu dữ liệu gốc khi cần.
11. Kết quả đầu ra và nguyên tắc chuyển sang Bài 3
Sau Bài 2, chúng ta có wbes2023_clean.dta: tên biến đã rõ ràng hơn, special codes số âm đã được chuyển thành missing, các phạm vi cơ bản và một số quan hệ nhất quán đã được kiểm tra, skip patterns quan trọng đã được rà soát và ngoại lệ đã được nhận diện nhưng chưa bị can thiệp.
Bài 2 chưa tạo các biến phân tích chính, chưa chuẩn hóa hoặc log-transform các biến, chưa winsorize và chưa xử lý missing bằng imputation. Những quyết định này phụ thuộc câu hỏi nghiên cứu và sẽ được thực hiện ở các bài tiếp theo. Khi phân tích dữ liệu chéo WBES, các mối liên hệ quan sát được cũng không nên được diễn giải tự động như quan hệ nhân quả.
Lời kết
Qua Bài 2, dữ liệu WBES Việt Nam 2023 đã được kiểm tra và làm sạch một cách có hệ thống, từ biến định danh, giá trị thiếu và các mã trả lời đặc biệt đến phạm vi giá trị, tính nhất quán giữa các biến, logic bảng hỏi (skip patterns) và các quan sát ngoại lệ. Một nguyên tắc quan trọng là làm sạch dữ liệu không đồng nghĩa với tự động sửa hoặc loại bỏ những giá trị bất thường; mọi quyết định xử lý cần dựa trên ý nghĩa của biến và cấu trúc bảng hỏi. Kết quả của bài này là tệp wbes2023_clean.dta, tạo nền tảng dữ liệu đáng tin cậy hơn để chuyển sang Bài 3 – Xây dựng và khám phá các biến phân tích, nơi dữ liệu sẽ bắt đầu được chuyển thành các chỉ tiêu phục vụ mô tả, trực quan hóa và nghiên cứu doanh nghiệp.
