Đôi khi người phân tích dữ liệu đã đi đến kết quả cuối cùng với những bảng báo cáo tóm tắt, biểu đồ đã được vẽ, sau đó phát hiện ra những lỗi dữ liệu không thể chấp nhận được, chẳng hạn có vài đối tượng nghiên cứu có tuổi “- 15” hoặc thân nhiệt 82oC. Kết quả phân tích phải bị hủy, chúng ta lại phải làm lại từ đầu bằng việc làm sạch dữ liệu trước tiên (data cleaning).
Việc làm sạch dữ liệu rất quan trọng, mất nhiều thời gian và đôi khi rất khó khăn trong việc phát hiện và thay đổi những dữ liệu bị lỗi theo cách phù hợp nhất.
Đơn giản nhất là missing (NA), tức là dữ liệu không đầy đủ của một số đối tượng làm ảnh hưởng không nhỏ đến kết quả phân tích. Lỗi phổ biến là những dữ liệu bị lỗi do quá trình nhập dữ liệu bị sai sót, như vừa kể trên.
Trong bài viết ngắn này tôi muốn trình bày một vài cách đơn giản, nhanh chóng để kiểm tra dữ liệu của mình trước khi bước vào phân tích. Một vài công cụ, packages được sử dụng cho công đoạn data cleaning.
library(readr)
df <- read_csv("D:/AZ Projects/DataCleanProject.csv")
head(df)
## # A tibble: 6 x 15
## ID Age Gender V11 Heig~ Weig~ Temp~ V15 SBP DBP V20 V21
## <int> <int> <int> <dbl> <int> <int> <dbl> <int> <int> <int> <chr> <dbl>
## 1 1 69 1 52.0 166 78 36.8 18 120 80 68 1.00
## 2 2 62 1 50.0 165 72 36.8 17 120 80 61 1.50
## 3 3 67 1 52.0 148 68 36.5 16 130 70 67 0.500
## 4 4 64 1 55.0 169 72 37.0 15 110 70 64 0.500
## 5 5 67 1 72.0 165 88 36.8 18 130 70 65 1.92
## 6 6 78 1 44.0 156 86 81.0 21 120 80 75 3.00
## # ... with 3 more variables: Disease <int>, Married <int>, Center <int>
dim(df)
## [1] 640 15
Bộ dữ liệu của chúng ta có 640 rows và 15 variables.
Trước tiên là missing data (NA).
library(VIM)
aggr(df[2:14], sortComb=TRUE, sortVar=TRUE, only.miss=TRUE)
##
## Variables sorted by number of missings:
## Variable Count
## Temperature 0.0968750
## V15 0.0796875
## V21 0.0765625
## Weight 0.0234375
## V20 0.0171875
## SBP 0.0093750
## DBP 0.0078125
## Age 0.0000000
## Gender 0.0000000
## V11 0.0000000
## Height 0.0000000
## Disease 0.0000000
## Married 0.0000000
Biểu đồ cho thấy tình trạng missing data đối với từng biến số trong dataset qua phần trăm bị missing và dạng kết hợp missing data giữa các biến số. Những biến số như Temperature, V15, V21 và Weight có tình trạng missing data đáng chú ý hơn.
Một số functions căn bản giúp chúng ta có cái nhìn tổng quát về bộ dữ liệu mà chúng ta chuẩn bị xử lí là:
summary(df)
## ID Age Gender V11
## Min. : 1.0 Min. :-15.00 Min. :1.000 Min. : 33.00
## 1st Qu.:160.8 1st Qu.: 61.00 1st Qu.:1.000 1st Qu.: 52.00
## Median :320.5 Median : 68.00 Median :1.000 Median : 60.00
## Mean :320.5 Mean : 68.03 Mean :1.069 Mean : 60.10
## 3rd Qu.:480.2 3rd Qu.: 76.00 3rd Qu.:1.000 3rd Qu.: 67.35
## Max. :640.0 Max. : 96.00 Max. :2.000 Max. :110.00
##
## Height Weight Temperature V15
## Min. : 85.0 Min. : 0.0 Min. : 0.0 Min. :10.00
## 1st Qu.:158.0 1st Qu.: 74.0 1st Qu.:36.3 1st Qu.:17.00
## Median :162.0 Median : 81.0 Median :36.8 Median :20.00
## Mean :163.4 Mean : 83.8 Mean :36.5 Mean :18.73
## 3rd Qu.:167.0 3rd Qu.: 91.0 3rd Qu.:37.0 3rd Qu.:20.00
## Max. :861.0 Max. :178.0 Max. :92.0 Max. :72.00
## NA's :15 NA's :62 NA's :51
## SBP DBP V20 V21
## Min. : 70.0 Min. : 47.0 Length:640 Min. : 0.000
## 1st Qu.:120.0 1st Qu.: 70.0 Class :character 1st Qu.: 0.500
## Median :130.0 Median : 79.0 Mode :character Median : 2.000
## Mean :132.1 Mean : 77.5 Mean : 3.351
## 3rd Qu.:143.0 3rd Qu.: 83.0 3rd Qu.: 5.000
## Max. :193.0 Max. :172.0 Max. :20.000
## NA's :6 NA's :5 NA's :49
## Disease Married Center
## Min. :0.0000 Min. :0.0000 Min. :1.000
## 1st Qu.:0.0000 1st Qu.:0.0000 1st Qu.:1.000
## Median :0.0000 Median :0.0000 Median :2.000
## Mean :0.2891 Mean :0.2109 Mean :1.531
## 3rd Qu.:1.0000 3rd Qu.:0.0000 3rd Qu.:2.000
## Max. :1.0000 Max. :1.0000 Max. :2.000
##
Nhìn vào các giá trị Min, Max, NA’s của các variables chúng ta có thể phát hiện ra một số lỗi dữ liệu dễ thấy.
Biến số Age có giá trị nhỏ nhất là -15, biến số Height có giá trị lớn nhất là 861 (cm), biến số Weight có 15 đối tượng missing (NA), và giá trị nhỏ nhất là 0, biến số Temperature có giá trị lớn nhất là 92 và nhỏ nhất là 0 độ C.
Đó là những giá trị rất bất thường, không thể chấp nhận được. Chúng ta cần biết thêm là có bao nhiêu đối tượng, tương ứng với các biến số trên, có giá trị dữ liệu bất thường và đó là những đối tượng nào để chúng ta tìm cách thay thế phù hợp.
Từ lỗi được phát hiện với Age, chúng ta tìm xem, ngoài giá trị đó, còn có bao nhiêu lỗi như thế:
head(sort(df$Age), 5)
## [1] -15 -10 10 12 13
tail(sort(df$Age), 5)
## [1] 91 91 92 95 96
Đó là top 5 những giá trị nhỏ nhất và lớn nhất của Age. Ngoài một đối tượng có giá trị Age là -15 còn có một đối tượng khác với -10. Các bạn đừng quên sort(df$Age) vì nếu không thì R cho ra các giá trị theo 5 vị trí đầu tiên và sau cùng trong dataset, chứ không phải top 5 lớn nhất, nhỏ nhất như chúng ta mong muốn.
Nếu chỉ có hai giá trị âm bất thường (không có nhiều lỗi như thế), thì chúng ta có thể tìm đến những rows mang lỗi này để chỉnh sửa.
which(df$Age < 0)
## [1] 16 32
R cho chúng ta biết 2 đối tượng mang ID là 16 và 32 có lỗi Age < 0. Đến đây bạn có thể xác định chính xác đối tượng cụ thể rồi, kiểm tra lại bệnh án nghiên cứu của hai đối tượng có ID trên để xác minh tuổi đúng của họ để nhập vào dataset.
Trong trường hợp dataset của chúng ta đủ lớn thì việc tìm ra để chỉnh sửa từng đối tượng một sẽ rất khó khăn. Khi đó chúng ta cẩn đến những thủ thuật khác để chỉnh sửa dữ liệu. Phương pháp này sẽ được đề cập đến sau đây.
Validate package giúp chúng ta tìm kiếm errors một cách chuyên sâu hơn bằng cách đặt ra các qui tắc dữ liệu (rules).
Tìm kiếm nhanh với Validate:
library(validate)
ct <- check_that(df, Age > 0)
summary(ct)
## name items passes fails nNA error warning expression
## 1 V1 640 638 2 0 FALSE FALSE Age > 0
Với rule có tên V1 = “Age > 0”, hàm check_that cho chúng ta biết có 2 trường hợp không thỏa mãn, tức là Age <= 0 trong 640 rows được check.
Chúng ta có thể kiểm tra đồng thời những rules khác, không có liên quan với nhau theo cách liệt kê như sau
library(validate)
ct <- check_that(df, Age > 0, Temperature < 41, Temperature > 30)
summary(ct)
## name items passes fails nNA error warning expression
## 1 V1 640 638 2 0 FALSE FALSE Age > 0
## 2 V2 640 574 4 62 FALSE FALSE Temperature < 41
## 3 V3 640 571 7 62 FALSE FALSE Temperature > 30
Tương tự kết quả cho thấy có 4 trường hợp có Temperature lớn hơn 41 độ C, 7 trường hợp Temperature < 30 độ C trong 640 rows, và có 62 missing values.
barplot(ct[1:3], main="Errors in Age and Temperaturs")
Biểu đồ cho ta sự hình dung trực quan số lượng những errors và NA’s trong các biến số.
Sử dụng hàm which để xác định những rows chứa errors
which(df$Age < 0)
## [1] 16 32
which(df$Temperature > 41)
## [1] 6 33 132 167
which(df$Temperature < 30)
## [1] 69 71 83 84 90 91 93
Đó là ID của những rows có Age và Temperature bất thường nêu trên. Nếu số lượng rows có lỗi không nhiều lắm thì chúng ta có thể chỉnh sửa thủ công trong dataset cho từng rows cụ thể khi đã biết ID của chúng.
Trên đây là những lỗi không liên quan giữa các variables. Có những rows khi check riêng rẽ thì không bị errors, nhưng khi xét trong các qui luật liên quan thì chúng là các rows bị lỗi.
Một đối tượng có Age = 10 thì không phải error. Tuy nhiên, nếu đối tượng này có Married = 1, tức là đã kết hôn, thì đây là một trường hợp lỗi dữ liệu.
Package errorlocate được sử dụng để phát hiện các loại lỗi như thế này.
Chúng ta đặt ra hai qui luật là Age < 16 và nếu đã kết hôn thì tuổi (Age) phải lớn hơn 16.
Bây giờ chúng ta áp dụng qui luật rules1 này vào dataset để xem tình trạng dữ liệu như thế nào:
library(errorlocate)
rules1 <- validator(
Age < 16,
if (Married == TRUE) Age >= 16
)
err <- locate_errors(df, rules1)
summary(err)
## Variable:
## name errors missing
## 2 Age 635 0
## 14 Married 4 0
## 1 ID 0 0
## 3 Gender 0 0
## 4 V11 0 0
## 5 Height 0 0
## 6 Weight 0 15
## 7 Temperature 0 62
## 8 V15 0 51
## 9 SBP 0 6
## 10 DBP 0 5
## 11 V20 0 11
## 12 V21 0 49
## 13 Disease 0 0
## 15 Center 0 0
## Errors per record:
## errors records
## 1 0 1
## 2 1 639
Kết quả cho thấy có 5 trường hợp (640-635) có tuổi < 16 và trong số này có 4 người đã kết hôn (Married = 1).
Chúng ta vẽ biểu đồ để minh họa tình trạng lỗi này.
cf2 <- confront(df, rules1)
barplot(cf2[1:2], main = "Errors in Age of Marriage")
Biểu đồ cho thấy hầu hết (635) các đối tượng có tuổi >= 16 và chì có số ít kết hôn ở tuổi < 16 (màu hồng).
Sau khi biết được đã có 4 lỗi xuất hiện chúng ta xác định ID của chúng bằng cách sau:
which(df$Age < 16 & df$Married == 1)
## [1] 16 35 141 153
Tra cứu lại bệnh án nghiên cứu để xác minh vì sao các lỗi này đã xảy ra, sai tuổi hai sai tình trạng Married.
Chỉ khi bạn hiểu về lĩnh vực này bạn mới lưu ý huyết áp tâm thu (SBP) luôn luôn phải lớn hơn huyết áp tâm trương (DBP) để đặt ra qui luật SBP > DBP như sau:
rules2 <- validator(
SBP > DBP
)
err2 <- locate_errors(df, rules2)
summary(err2)
## Variable:
## name errors missing
## 10 DBP 6 5
## 9 SBP 3 6
## 1 ID 0 0
## 2 Age 0 0
## 3 Gender 0 0
## 4 V11 0 0
## 5 Height 0 0
## 6 Weight 0 15
## 7 Temperature 0 62
## 8 V15 0 51
## 11 V20 0 11
## 12 V21 0 49
## 13 Disease 0 0
## 14 Married 0 0
## 15 Center 0 0
## Errors per record:
## errors records
## 1 0 631
## 2 1 9
which(df$SBP < df$DBP)
## [1] 26 27 28 29 30 31 310 537 555
Kết quả cho thấy có 9 trường hợp lỗi nhập dữ liệu đã xảy ra khi SBP < DBP. Kiểm tra lại dataset, khả năng xảy ra lỗi là do người nhập dữ liệu đã nhầm lẫn hai field này với nhau vì các giá trị đã hoán đổi vị trí. Từ đó chúng ta sửa lại bằng cách hoán đổi vị trí của chúng.
Nếu bạn là người am hiểu lĩnh vực mà bạn đang phân tích, thì bạn có thể đặt ra nhiều rules để thẩm định dataset của bạn.
Khi đã phát hiện được các loại errors và định vị chúng (ID) thì coi như chúng ta đã trả lời được câu hỏi quan trọng rồi. Đến đây, công việc của chúng ta là sửa lỗi dữ liệu sai đó.
Nếu có số ít errors thì chúng ta chỉnh sửa thủ công trên datdaset. Tuy nhiên, khi làm việc mới dataset khá lớn, với số lượng errors nhiều chúng ta không thể chỉnh sửa thủ công mà phải có công cụ (functions) để làm điều đó.
Tôi nêu ra đây vài thủ thuật chỉnh sửa đơn giản cho dữ liệu bị lỗi.
Với trường hợp thân nhiệt (Temperature) bằng 0, chúng ta có thể phát hiện và thay thế bằng giá trị thân nhiệt phổ biến nhất (median).
summary(df$Temperature)
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 0.0 36.3 36.8 36.5 37.0 92.0 62
head(sort(df$Temperature),10)
## [1] 0 0 0 0 0 0 0 35 35 35
Nhìn vào kết quả ta thấy median = 36.8. Ta thay thế 0 bằng 36.8 cho 7 đối tượng có thân nhiệt bằng 0.
library(car)
df$Temperature <- recode(df$Temperature, "0=36.8")
head(sort(df$Temperature), 10)
## [1] 35.0 35.0 35.0 35.0 35.0 35.0 35.0 35.1 35.2 35.2
Có rất nhiều công cụ giúp chúng ta chỉnh sửa dữ liệu bị lỗi sao cho phù hợp, giảm thiểu những sai lệch trong dataset mà chúng ta đang có.
Tuy nhiên, vấn đề này còn nhiều việc để bàn luận. Bạn có thể tham khảo thêm bài viết về cách xử lí NAs ở đây http://rpubs.com/nnthieu/301064
Statistical Data Cleaning with Applications in R là một cuốn sách tốt về vấn đề này mà bạn có thể tham khảo.
Cám ơn các bạn đã đọc bài viết.