Xử lý dữ liệu bị khuyết

Với dữ liệu bảng, việc các trường thông tin bị khuyết (giá trị nan) là thường xuyên xảy ra. Việc này đến từ quá trình thu thập dữ liệu. Người dùng có thể thực sự không có thông tin đó hoặc họ không muốn tiết lộ vì lý do riên tư cá nhân.

Vậy, chúng ta cần làm gì với dữ liệu bị khuyết?

Một cách đơn giản là bỏ cả cột thông tin có dữ liệu bị khuyết ra khỏi quá trình xây dựng mô hình. Cách làm này đơn giản nhưng có những hạn chế nhất định. Nếu có quá nhiều cột có dữ liệu bị khuyết và ta đều bỏ các cột này đi thì sẽ không còn thông tin gì cho việc xây dựng mô hình.

Cách thứ hai là bỏ đi hàng có giá trị bị khuyết đó khỏi tập huấn luyện. Việc này cũng có hạn chế tương tự như trên nên mỗi hàng mất một chút dữ liệu. Hạn chế thứ hai là khi gặp một điểm dữ liệu mới mà mô hình cần dự đoán, ta không thể đơn giản bỏ điểm dữ liệu đó đi mà vẫn phải dự đoán ra một giá trị nào đó.

Có một quy tắc chung là nếu một cột có quá nhiều dữ liệu bị khuyết thì ta có thể bỏ cột đó. Việc tìm ngưỡng thế nào là quá nhiều tùy thuộc vào tính chất của dữ liệu và kinh nghiệm của các kỹ sư. Nếu dữ liệu có quá ít thông tin và lại bỏ bớt đi có thể làm giảm chất lượng mô hình.

Nếu muốn giữ một cột có thông tin bị khuyết, có hai hướng chính.

Thứ nhất là tạo một cột mới is_nan mang thông tin dữ liệu có bị khuyết hay không. Đôi khi việc khuyết thông tin cũng chính là một thông tin quý giá. Một người không khai báo số điện thoại có thể vì họ không có điện thoại, một người giấu địa chỉ IP của máy chứng tỏ họ đề cao quyền riêng tư và có kỹ năng nhất định về máy tính. Như vậy việc thiếu thông tin cũng chính là một thông tin có thể khai thác.

Cách thứ hai giúp ta có thể giải quyết vấn đề dữ liệu bị khuyết là “điền” (impute) các giá trị bị khuyết một giá trị nào đó rồi dùng giá trị đó để xây dựng mô hình.

Dữ liệu dạng số

Với dữ liệu dạng số, hai cách phổ biến và đơn giản nhất là điền các giá trị bị khuyết bằng trung bình cộng hoặc trung vị của các giá trị không bị khuyết. Đây là các lựa chọn an toàn vì trung bình cộng hoặc trung vị là các giá trị có khả năng cao xảy ra. Một điểm đáng lưu ý là việc lấy trung bình hay trung vị này nên được cân nhắc dựa trên dữ liệu trước hoặc sau khi xử lý các điểm ngoại lệ.

Thư viện scikit-learn với lớp sklearn.impute.SimpleImputer thường được sử dụng cho tác vụ này. Lấy ví dụ với cột Age trong dữ liệu Titanic. Trong bộ dữ liệu này, tập train.csv\(891 - 714 = 177\) điểm bị khuyết, tập test.csv\(418 - 332 = 86\) điểm bị khuyết.

import pandas as pd

titanic_path = "https://media.githubusercontent.com/media/tiepvupsu/tabml_data/master/titanic/"

df_train = pd.read_csv(titanic_path + "train.csv")
df_test = pd.read_csv(titanic_path + "test.csv")
df_train[["Age"]].info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 1 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   Age     714 non-null    float64
dtypes: float64(1)
memory usage: 7.1 KB
df_test[["Age"]].info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 418 entries, 0 to 417
Data columns (total 1 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   Age     332 non-null    float64
dtypes: float64(1)
memory usage: 3.4 KB

Một điểm đáng lưu ý khác là việc tính toán giá trị để điền chỉ được dựa trên dữ liệu huấn luyện, trong trường hợp này là tập train.csv. Khi điền các giá trị bị khuyết trên tập test.csv ta cần sử dụng kết quả thu được ở tập train.csv. Dưới đây là ví dụ cụ thể với việc sử dụng sklearn.impute.SimpleImputer và cách điền là 'median' (trung vị).

from sklearn.impute import SimpleImputer

imputer = SimpleImputer(strategy="median")
imputer.fit(df_train[["Age"]])
df_train[["ImputedAge"]] = imputer.transform(df_train[["Age"]])
df_test[["ImputedAge"]] = imputer.transform(df_test[["Age"]])

df_train[["Age", "ImputedAge"]].tail(3)
Age ImputedAge
888 NaN 28.0
889 26.0 26.0
890 32.0 32.0
df_test[["Age", "ImputedAge"]].tail(3)
Age ImputedAge
415 38.5 38.5
416 NaN 28.0
417 NaN 28.0

Đoạn code trên đây tính toán giá trị trung vị (strategy='median') dựa trên các điểm không bị khuyết ở tập huấn luyện rồi điền vào cả hai tập đó. Ta thấy rằng các giá trị NaN ở cột Age đã được điền một giá trị gần bằng \(28.0\) ở cột ImputedAge. Bạn cũng có thể thử với các strategy khác để xem cách nào mang lại kết quả tốt nhất. Nên nhớ rằng không có một cách điền giá trị nào đúng cho mọi loại dữ liệu, bạn cần hiểu dữ liệu để đề ra phương án mà bạn nghĩ là có kết quả tốt nhất.

Nếu có thêm thời gian, bạn có thể điền các giá trị một cách tỉ mỉ hơn. Ví dụ, điền các giá trị về tuổi bị khuyết khác nhau cho mỗi loại giới tính.

Dữ liệu hạng mục

Với dữ liệu hạng mục, vì ta không tính được giá trị trung bình nên cách thường dùng là điền vào giá trị xuất hiện nhiều nhất (strategy='mode') hoặc coi chính việc bị khuyết là một giá trị đặc biệt (strategy='constant') với giá trị đặc biệt được truyền qua biến fill_value (Xem thêm tại sklearn.impute.SimpleImputer).