Data Cleaning

Đô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.

Chuẩn bị dữ liệu

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.

Đánh giá sơ khởi

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.

Tìm kiếm nâng cao với package Validate

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.

Các rules có liên quan giữa các biến số với nhau

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.

Bạn phải là người am hiểu lĩnh vực mà mình đang phân tích dữ liệu

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.

Sửa lỗi dữ liệu

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.