[Tech Blog #02 | Data Science] Quy Trình Làm Sạch Dữ Liệu Thực Chiến: Làm Chủ Missing Data & Chuẩn Hóa Dữ Liệu Với Pandas
![[Tech Blog #02 | Data Science] Quy Trình Làm Sạch Dữ Liệu Thực Chiến: Làm Chủ Missing Data & Chuẩn Hóa Dữ Liệu Với Pandas](/uploads/ai-images/cover-lam-sach-du-lieu-pandas.png)
1. Đặt vấn đề: Quy tắc vàng "Garbage In, Garbage Out"
Trong giới Data Science, có một câu nói nổi tiếng: "80% thời gian của một Data Scientist dành cho việc thu thập và làm sạch dữ liệu; 20% còn lại dùng để than phiền về việc làm sạch dữ liệu".
Dù bạn có sử dụng thuật toán Machine Learning tối tân đến đâu (như XGBoost, Deep Learning hay các mô hình Agentic AI phức tạp), nếu dữ liệu nạp vào chứa đầy giá trị rỗng (NaN), định dạng lộn xộn, hoặc dữ liệu nhiễu, mô hình sẽ cho ra kết quả sai lệch hoàn toàn. Nguyên lý này được gọi là Garbage In, Garbage Out (GIGO).
Bài viết hôm nay sẽ cùng bạn đi qua quy trình làm sạch dữ liệu (Data Cleaning) thực tế nhất, tập trung vào kỹ năng phát hiện, phân loại và xử lý dữ liệu khuyết thiếu (Missing Values) bằng thư viện Pandas.
2. Các dạng khuyết thiếu dữ liệu (Missing Data Mechanisms)
Trước khi vội vàng gõ lệnh xóa hay điền bừa một con số, một Data Scientist thực thụ luôn phải đặt câu hỏi: "Tại sao dữ liệu này lại bị khuyết?"
-
MCAR (Missing Completely at Random - Khuyết ngẫu nhiên hoàn toàn):
- Bản chất: Dữ liệu bị mất không phụ thuộc vào bất kỳ yếu tố nào (ví dụ: đường truyền mạng bị ngắt quãng ngẫu nhiên khi gửi gói tin, cảm biến pin đột ngột hết).
- Tác động: Không gây sai lệch (bias) mẫu, việc xóa hay điền trung bình tương đối an toàn.
-
MAR (Missing at Random - Khuyết ngẫu nhiên có điều kiện):
- Bản chất: Tỷ lệ khuyết phụ thuộc vào một biến quan sát được khác trong dữ liệu.
- Ví dụ: Phụ nữ thường ít điền cân nặng hơn nam giới trong phiếu khảo sát sức khỏe (sự thiếu hụt phụ thuộc vào biến
giới tính).
-
MNAR (Missing Not at Random - Khuyết không ngẫu nhiên):
- Bản chất: Dữ liệu bị khuyết có liên quan trực tiếp đến chính giá trị của nó.
- Ví dụ: Những người có thu nhập cực cao hoặc cực thấp thường từ chối khai báo mức lương.
- Tác động: Nguy hiểm nhất, nếu xóa đi bạn sẽ vô tình làm méo mó toàn bộ bức tranh dữ liệu.
3. Các chiến lược xử lý dữ liệu khuyết (Imputation Strategies)
| Chiến lược | Cách thực hiện | Ưu điểm | Nhược điểm & Rủi ro | Khi nào nên dùng? |
|---|---|---|---|---|
| 1. Deletion (Xóa bỏ) | dropna(subset=...) | Nhanh, giữ cho dữ liệu sạch tự nhiên, không tự bịa số liệu. | Mất mát dữ liệu nghiêm trọng; gây bias nếu dữ liệu thuộc dạng MAR/MNAR. | Cột có > 60-70% giá trị trống, hoặc biến mục tiêu (target label y) bị thiếu. |
| 2. Simple Imputation (Mean/Median/Mode) | Điền mean() (nếu phân phối chuẩn), median() (nếu lệch/có outlier), mode() (dạng chuỗi). | Đơn giản, giữ nguyên kích thước tập dữ liệu. | Làm giảm phương sai tự nhiên (Variance) của dữ liệu, làm méo mó mối quan hệ giữa các biến. | Tỷ lệ khuyết nhỏ (< 5%), dữ liệu thuộc dạng MCAR. |
| 3. Forward/Backward Fill | Lấy giá trị phía trước (ffill) hoặc phía sau (bfill) đắp vào. | Bảo tồn tính liên tục theo thời gian. | Có thể sao chép sai lệch dài hạn nếu chuỗi rỗng kéo dài. | Dữ liệu chuỗi thời gian (Time-series), giá cổ phiếu, nhật ký cảm biến nhiệt độ. |
| 4. Tạo cờ đánh dấu (Missing Indicator) | Điền hằng số (vd: 'Unknown' hoặc -1) kèm cột nhị phân is_missing. | Giúp mô hình Machine Learning học được "tín hiệu từ sự vắng mặt". | Tăng thêm số lượng cột đặc trưng. | Khi nghi ngờ việc thiếu dữ liệu là có chủ đích (MNAR). |
4. Thực hành thực chiến với Python & Pandas
Hãy cùng xử lý một tập dữ liệu hồ sơ khách hàng thương mại điện tử thực tế: có lỗi định dạng số tiền, có dữ liệu trống, và có giá trị trùng lặp.
import numpy as np
import pandas as pd
# 1. Khởi tạo tập dữ liệu mẫu bị "bẩn" (Dirty Dataset)
raw_data = {
"customer_id": [101, 102, 103, 104, 105, 105, 106, 107],
"city": ["HN", "Ha Noi", "HCMC", "TP.HCM", np.nan, "TP.HCM", "Da Nang", "HN"],
"age": [25, 30, np.nan, 22, 45, 45, 19, 85], # Tuổi 85 có thể là outlier
"income_str": ["$1200", "$1500", "$800", np.nan, "$2200", "$2200", "$950", "$3000"],
"signup_date": ["2026-01-10", "2026-01-12", "invalid_date", "2026-02-01", "2026-02-05", "2026-02-05", "2026-02-20", "2026-03-01"]
}
df = pd.DataFrame(raw_data)
print("--- 1. TỔNG QUAN DỮ LIỆU BAN ĐẦU ---")
print(df.info())
print("\nSố lượng giá trị khuyết ở từng cột:")
print(df.isna().sum())
# 2. Xử lý trùng lặp (Duplicates)
# Nhận thấy customer_id 105 xuất hiện 2 lần giống hệt nhau
initial_rows = len(df)
df = df.drop_duplicates(subset=["customer_id"], keep="first")
print(f"\nĐã xóa {initial_rows - len(df)} dòng dữ liệu trùng lặp.")
# 3. Chuẩn hóa chuỗi và ép kiểu dữ liệu (Data Type Casting)
# Xử lý cột income_str: loại bỏ ký tự '$' và chuyển sang dạng số thực float
df["income"] = (
df["income_str"]
.str.replace("$", "", regex=False) # Bỏ dấu dollar
.astype(float) # Ép kiểu thành số thực (NaN vẫn giữ nguyên)
)
df = df.drop(columns=["income_str"]) # Xóa cột chuỗi cũ
# Ép kiểu cột ngày tháng, chuyển các giá trị rác ('invalid_date') thành NaT (Not a Time)
df["signup_date"] = pd.to_datetime(df["signup_date"], errors="coerce")
# 4. Chuẩn hóa biến định tính không nhất quán (Categorical Standardization)
city_mapping = {
"HN": "Ha Noi",
"TP.HCM": "Ho Chi Minh",
"HCMC": "Ho Chi Minh"
}
df["city"] = df["city"].replace(city_mapping)
# 5. Xử lý Missing Values theo từng chiến lược phù hợp
# Với cột city: Điền 'Unknown' để không làm sai lệch thông tin địa lý
df["city"] = df["city"].fillna("Unknown")
# Với cột age: Dùng Median vì cột tuổi có thể bị kéo lệch bởi người 85 tuổi
median_age = df["age"].median()
df["age"] = df["age"].fillna(median_age)
# Với cột income: Điền theo Median thu nhập của từng thành phố (Group-level Imputation)
# Nếu thành phố chưa có thu nhập, fallback về median chung toàn bộ
df["income"] = df.groupby("city")["income"].transform(
lambda group: group.fillna(group.median() if not group.median() is np.nan else df["income"].median())
)
print("\n--- 2. DỮ LIỆU SAU KHI LÀM SẠCH HOÀN CHỈNH ---")
print(df)
print("\nKiểm tra lại missing values sau xử lý:")
print(df.isna().sum())
5. Câu hỏi phỏng vấn thực tế (Interview Q&A)
Câu 1 (Mức độ: Junior / Entry-level)
Câu hỏi: "Khi nào bạn nên dùng Median thay vì Mean để điền giá trị khuyết (Imputation) cho một biến định lượng?"
Gợi ý trả lời:
"Em sẽ ưu tiên dùng Median khi phân phối của biến bị lệch (skewed distribution) hoặc có chứa các giá trị ngoại lệ (outliers) cực đoan — ví dụ như mức lương, chi tiêu đơn hàng hay thời gian xử lý sự cố. Khi có outlier, Mean sẽ bị kéo lệch đáng kể về phía ngoại lệ, khiến việc điền Mean làm sai lệch phân phối thực tế của dữ liệu. Median đại diện chính xác hơn cho phần đông mẫu dữ liệu."
Câu 2 (Mức độ: Junior / Mid-level)
Câu hỏi: "Data Leakage (Rò rỉ dữ liệu) có thể xảy ra như thế nào trong quá trình xử lý Missing Values?"
Gợi ý trả lời:
"Data Leakage xảy ra khi chúng ta tính toán giá trị điền khuyết (ví dụ:df['col'].mean()) trên TOÀN BỘ tập dữ liệu trước khi chia Train/Test Split.
Khi làm vậy, thông tin thống kê của tập kiểm thử (Test set) đã bị 'rò rỉ' vào tập huấn luyện (Train set), làm cho mô hình có vẻ đạt độ chính xác ảo nhưng lại hoạt động kém trên dữ liệu thực tế.
Cách làm chuẩn: Luôn thực hiệntrain_test_split()trước. Sau đó, tính Mean/Median chỉ trên tập Train (train_mean), rồi dùng giá trị đó để điền cho cả tập Train và tập Test."
6. Key Takeaways (Ghi nhớ nhanh)
- Hiểu rõ nguyên nhân mất dữ liệu: Hãy xác định xem dữ liệu khuyết do lỗi ngẫu nhiên (MCAR) hay có lý do đằng sau (MNAR) trước khi chọn giải pháp.
- Quy tắc Train-Test Split: Không bao giờ tính toán Mean/Median/Mode trên toàn bộ dataset trước khi chia tập huấn luyện để tránh Data Leakage.
- Nhóm để điền (Group-level Imputation) luôn tốt hơn điền tổng: Điền lương trung vị theo từng chức danh hoặc từng thành phố sẽ cho độ chính xác cao hơn rất nhiều so với trung vị của toàn công ty.
- Giữ lại dấu vết: Với các bài toán Machine Learning nhạy cảm, hãy tạo thêm một cột cờ
is_missing(0 hoặc 1) trước khi điền khuyết để mô hình không đánh mất thông tin về sự thiếu hụt.