[DS & DA Blog #60] Làm Chủ Thư Viện Pandas & Quy Trình Làm Sạch Dữ Liệu Thực Chiến (Data Cleaning 101): Xử Lý Missing Values, Duplicates, loc vs. iloc & Mẹo Giảm 70% RAM
![[DS & DA Blog #60] Làm Chủ Thư Viện Pandas & Quy Trình Làm Sạch Dữ Liệu Thực Chiến (Data Cleaning 101): Xử Lý Missing Values, Duplicates, loc vs. iloc & Mẹo Giảm 70% RAM](/uploads/ai-images/cover-lam-sach-du-lieu-pandas.png)
1. Bối cảnh thực tế (Context & Problem Statement)
Trong thực tế doanh nghiệp, dữ liệu không bao giờ sạch sẽ, đẹp đẽ và sẵn sàng sử dụng như trên các cuộc thi học thuật:
- Dữ liệu xuất từ file Excel của phòng kinh doanh thường chứa đầy các ô trống vô nghĩa, cột ngày tháng bị gõ sai định dạng, giá tiền bị dính ký tự chữ cái (ví dụ:
"150,000 VND"hoặc"$1,200"). - Dữ liệu tải từ hệ thống web về thường bị nhân đôi bản ghi (Duplicates) do mạng chập chờn khiến khách hàng bấm nút gửi 2 lần.
- Khoảng thời gian làm việc hàng ngày của một Data Analyst, Data Engineer hay Data Scientist là dành cho việc Làm sạch và tiền xử lý dữ liệu (Data Cleaning & Wrangling).
Nếu bạn không làm chủ thư viện Pandas trong Python, bạn sẽ tốn hàng giờ đồng hồ viết các vòng lặp for chậm chạp, dễ gây lỗi sai lệch số liệu và thường xuyên gặp lỗi tràn bộ nhớ MemoryError khi xử lý các file dữ liệu lớn.
2. Các Khái Niệm & Cơ Chế Cốt Lõi
2.1. Cấu Trúc Dữ Liệu Cốt Lõi: Series vs. DataFrame
Pandas tổ chức dữ liệu dựa trên 2 cấu trúc nền tảng:
- Pandas Series (Mảng 1 chiều): Là một cột dữ liệu duy nhất kèm theo nhãn chỉ mục (Index).
- Pandas DataFrame (Bảng 2 chiều): Là một bảng gồm nhiều hàng và nhiều cột (tương đương một trang tính Excel hoặc một bảng trong SQL). Mỗi cột trong DataFrame thực chất là một Series!
PANDAS SERIES (1 Chiều): PANDAS DATAFRAME (2 Chiều):
Index Value Index Name Age Salary
0 ---> "An" 0 ---> "An" 25 15000000
1 ---> "Bình" 1 ---> "Bình" 30 22000000
2 ---> "Cường" 2 ---> "Cường" 28 18000000
^ ^ ^
|-----------+--------|
Mỗi cột là một Series!
2.2. Phân Biệt Tuyệt Đối: df.loc[] vs. df.iloc[]
Đây là câu hỏi phỏng vấn kinh điển nhất về Pandas mà rất nhiều ứng viên trả lời nhầm lẫn:
df.loc[](Label-based Selection - Truy xuất theo TÊN/NHÃN):- Dựa vào Tên của dòng (Index Label) và Tên của cột (Column Name).
- Cú pháp:
df.loc[row_label, column_label]. - Lưu ý: Khi cắt lát (
slice),loclấy chặn cả hai đầu mút (Inclusive) (ví dụ:df.loc['a':'c']lấy cả dòngc).
df.iloc[](Integer Index-based Selection - Truy xuất theo VỊ TRÍ SỐ NGUYÊN):- Dựa vào Vị trí chỉ số số nguyên (0, 1, 2, ...) của dòng và cột, bất kể nhãn tên của nó là gì.
- Cú pháp:
df.iloc[row_index, column_index]. - Lưu ý: Khi cắt lát (
slice),ilochoạt động giống như Python thông thường, không lấy phần tử cuối (Exclusive) (ví dụ:df.iloc[0:3]chỉ lấy dòng 0, 1, 2).
2.3. Xử Lý Dữ Liệu Khuyết Thiếu (Missing Values / NaN)
Trong Pandas, giá trị bị trống được đại diện bằng NaN (Not a Number) hoặc None.
- Phát hiện dữ liệu trống:
df.isna().sum(): Đếm tổng số lượng ô trống trên từng cột.
- Chiến lược Xóa bỏ (
df.dropna()):df.dropna(): Xóa sạch toàn bộ dòng nếu dòng đó có ít nhất 1 ô bị trống.df.dropna(subset=['user_id', 'order_date']): Chỉ xóa dòng nếu cột quan trọng bị trống.- Khi nào nên xóa? Khi số lượng dòng bị khuyết rất nhỏ ( tổng dữ liệu) hoặc khi cột bị thiếu là khóa chính định danh bắt buộc.
- Chiến lược Điền khuyết (
df.fillna()):- Điền giá trị thống kê: Điền bằng
df['age'].median()(nếu dữ liệu lệch/có outlier) hoặcdf['age'].mean()(nếu dữ liệu chuẩn). - Điền giá trị lân cận:
df.ffill()(Forward Fill): Lấy giá trị của dòng phía trên điền cho dòng phía dưới (rất tối ưu cho dữ liệu chuỗi thời gian).df.bfill()(Backward Fill): Lấy giá trị dòng dưới điền ngược lên trên.
- Điền nhãn riêng biệt:
df['category'].fillna('Unknown')(cho biến phân loại).
- Điền giá trị thống kê: Điền bằng
2.4. Xử Lý Dữ Liệu Trùng Lặp (Duplicate Records)
- Kiểm tra trùng lặp:
df.duplicated(subset=['user_id', 'order_id']). - Xóa trùng lặp:
df.drop_duplicates(subset=['user_id'], keep='last'):keep='first'(Mặc định): Giữ lại bản ghi xuất hiện đầu tiên, xóa các bản ghi sau.keep='last': Giữ lại bản ghi xuất hiện cuối cùng (thường là bản cập nhật mới nhất).
2.5. Các Phép Gộp Bảng: pd.merge() vs. pd.concat()
pd.merge()(Tương đương câu lệnh SQLJOIN):- Ghép 2 bảng lại với nhau dựa trên một hoặc nhiều Cột khóa chung (Common Key / Foreign Key).
- Hỗ trợ các kiểu kết nối:
how='inner',how='left',how='right',how='outer'.
pd.concat()(Tương đương câu lệnh SQLUNION ALLhoặc ghép cột vật lý):- Nối các bảng lại với nhau bằng cách xếp chồng lên nhau theo chiều dọc (
axis=0) hoặc ghép ngang cạnh nhau theo chiều ngang (axis=1).
- Nối các bảng lại với nhau bằng cách xếp chồng lên nhau theo chiều dọc (
3. Các Bảng Markdown So Sánh Chi Tiết
📊 Bảng 1: So Sánh Toàn Diện df.loc[] vs. df.iloc[]
| Tiêu chí kỹ thuật | df.loc[] | df.iloc[] |
|---|---|---|
| Cơ chế truy xuất | Dựa trên Tên / Nhãn (Label-based) | Dựa trên Vị trí số nguyên (Position-based: 0, 1, 2...) |
| Truy xuất dòng | df.loc['row_name'] | df.iloc[0] |
| Truy xuất cột | df.loc[:, 'column_name'] | df.iloc[:, 1] |
Cơ chế cắt lát (slice) | Lấy cả điểm cuối (Inclusive: 'a':'c' lấy cả 'c') | Bỏ điểm cuối (Exclusive: 0:3 chỉ lấy 0, 1, 2) |
| Lọc theo điều kiện logic | df.loc[df['age'] > 20, ['name', 'age']] | Phức tạp hơn (Phải đổi điều kiện thành danh sách boolean index) |
| Lỗi thường gặp | Bị lỗi nếu gõ sai chính tả tên cột | Bị lỗi IndexError nếu chỉ số vượt quá số dòng/cột |
📊 Bảng 2: So Sánh Các Chiến Lược Điền Dữ Liệu Khuyết Thiếu (fillna)
| Chiến lược điền | Câu lệnh Pandas mẫu | Ưu điểm | Nhược điểm / Rủi ro | Khi nào nên sử dụng? |
|---|---|---|---|---|
| Điền Mean (Trung bình) | df['col'].fillna(df['col'].mean()) | Đơn giản, giữ nguyên giá trị trung bình tổng thể | Bị méo mó nếu dữ liệu có Outliers lớn | Dữ liệu số phân phối chuẩn đối xứng |
| Điền Median (Trung vị) | df['col'].fillna(df['col'].median()) | An toàn, không bị ảnh hưởng bởi Outliers | Làm phẳng đỉnh nhọn của phân phối | Dữ liệu số bị lệch (Thu nhập, Giá nhà, Tuổi) |
Forward Fill (ffill) | df['col'].ffill() | Tự nhiên, bảo toàn tính liên tục | Gây sai lệch nếu chuỗi bị khuyết một đoạn quá dài | Dữ liệu chuỗi thời gian, Tỷ giá hối đoái, Giá chứng khoán |
| Gán nhãn riêng | df['col'].fillna('Missing') | Không đoán mò thông tin, giữ tính trung thực | Tạo thêm 1 nhóm danh mục mới | Dữ liệu chữ/danh mục (Categorical data) |
📊 Bảng 3: So Sánh Các Phép Nối Bảng Trong Pandas
| Thao tác | Cú pháp chính | Bản chất cơ chế | Tương đương câu lệnh SQL |
|---|---|---|---|
pd.merge() | pd.merge(df1, df2, on='id', how='left') | Ghép dữ liệu dựa trên Giá trị của Cột khóa chung | SELECT * FROM df1 LEFT JOIN df2 ON df1.id = df2.id |
pd.concat(axis=0) | pd.concat([df_jan, df_feb], axis=0) | Xếp chồng các dòng lên nhau theo chiều dọc | SELECT * FROM df_jan UNION ALL SELECT * FROM df_feb |
pd.concat(axis=1) | pd.concat([df1, df2], axis=1) | Dán 2 bảng nằm cạnh nhau theo chỉ số Index | Không có trong SQL chuẩn (Tương tự nối ngang) |
df.join() | df1.join(df2, on='id') | Phím tắt ghép bảng dựa trên Chỉ số Index | Tương tự SQL JOIN trên Primary Key Index |
4. Thực Hành Thực Chiến: Quy Trình Làm Sạch Dữ Liệu Bẩn Bằng Python (Walkthrough)
📌 Dữ liệu bẩn ban đầu (Messy Data):
Giả sử bạn nhận được một tập dữ liệu đơn hàng bị lỗi: Cột price bị dính ký tự $ và dấu phẩy, cột customer_name có khoảng trắng thừa, có các dòng bị trùng lặp và chứa giá trị rỗng NaN:
import pandas as pd
import numpy as np
# 1. Tạo tập dữ liệu bẩn mô phỏng:
raw_data = {
'order_id': [101, 102, 103, 103, 104, 105],
'customer_name': [' Nguyen Van An ', 'Le Thi Binh', 'TRAN VAN CUONG', 'TRAN VAN CUONG', np.nan, 'Pham Thi Dung'],
'price': ['$1,200.50', '$450.00', '$25.99', '$25.99', '$80.00', 'MISSING'],
'age': [25, np.nan, 32, 32, 45, 120], # Có 1 người 120 tuổi (Outlier)
'order_date': ['2026-09-01', '2026/09/02', '03-09-2026', '03-09-2026', '2026-09-04', '2026-09-05']
}
df = pd.DataFrame(raw_data)
print("=== DỮ LIỆU BAN ĐẦU ===")
print(df)
🛠️ Script Làm Sạch Dữ Liệu Chuẩn Mực Từng Dòng (Data Cleaning Pipeline):
# BƯỚC 1: Xóa các bản ghi bị trùng lặp hoàn toàn:
df = df.drop_duplicates()
# BƯỚC 2: Chuẩn hóa cột họ tên (Xóa khoảng trắng thừa 2 đầu và viết hoa chữ cái đầu):
df['customer_name'] = df['customer_name'].astype(str).str.strip().str.title()
# Thay thế chuỗi 'Nan' vừa bị convert thành giá trị khuyết thiếu thực sự:
df['customer_name'] = df['customer_name'].replace('Nan', np.nan)
# BƯỚC 3: Làm sạch cột giá tiền (Bỏ dấu '$', bỏ dấu phẩy, ép kiểu về float):
df['price'] = df['price'].astype(str).str.replace('$', '', regex=False).str.replace(',', '', regex=False)
# Chuyển chuỗi không hợp lệ 'MISSING' thành NaN bằng errors='coerce':
df['price'] = pd.to_numeric(df['price'], errors='coerce')
# Điền giá trị giá tiền bị thiếu bằng Median của cột:
df['price'] = df['price'].fillna(df['price'].median())
# BƯỚC 4: Chuẩn hóa cột ngày tháng về đúng định dạng chuẩn YYYY-MM-DD:
df['order_date'] = pd.to_datetime(df['order_date'], format='mixed').dt.strftime('%Y-%m-%d')
# BƯỚC 5: Xử lý Outlier và Missing Value ở cột Tuổi (Age):
# Thay thế tuổi bất hợp lý (> 100 tuổi) thành NaN:
df.loc[df['age'] > 100, 'age'] = np.nan
# Điền tuổi bị thiếu bằng Median:
df['age'] = df['age'].fillna(df['age'].median()).astype(int)
# BƯỚC 6: Xóa bỏ dòng không có tên khách hàng (vì customer_name là trường quan trọng):
df = df.dropna(subset=['customer_name'])
print("\n=== DỮ LIỆU SAU KHI ĐÃ ĐƯỢC LÀM SẠCH HOÀN HẢO ===")
print(df)
💡 Mẹo Đỉnh Cao: Giảm Tới 70% Bộ Nhớ RAM Cho DataFrame Bằng Cách Ép Kiểu
Khi đọc file dữ liệu lớn, Pandas mặc định gán kiểu int64 () cho số nguyên và kiểu object (chuỗi văn bản tốn rất nhiều RAM):
- Chuyển các cột số nguyên nhỏ sang
int8,int16hoặcint32:- Nếu cột
agechỉ từ đến , chỉ cần kiểuint8(lưu được từ đến , tốn đúng thay vì ).
- Nếu cột
- Chuyển các cột danh mục lặp đi lặp lại sang kiểu
category:- Cột
gender('Nam','Nu') hoặc cộtcity('Hà Nội','TP.HCM') bị lặp lại hàng triệu lần. Chuyển sangdf['city'] = df['city'].astype('category')sẽ giúp Pandas lưu trữ dưới dạng mã số nguyên nội bộ, tiết kiệm ngay lập tức RAM của cột đó!
- Cột
- Đọc theo từng phần nhỏ (
chunksize):# Đọc file 10GB trên máy tính 8GB RAM mà không bị sập: chunk_size = 50000 # Đọc mỗi lần 50,000 dòng for chunk in pd.read_csv("giant_file.csv", chunksize=chunk_size): process_clean_data(chunk) # Xử lý xong dòng nào giải phóng dòng đó
5. Góc Ôn Luyện Phỏng Vấn (Interview Corner)
❓ Câu hỏi 1: Phân biệt sự khác nhau giữa df.loc[] và df.iloc[] trong Pandas. Nếu tập dữ liệu có chỉ số Index là một dãy số ngẫu nhiên không liên tục (ví dụ: Index = [10, 50, 99]), thì df.loc[0] và df.iloc[0] sẽ trả về kết quả gì?
- Gợi ý trả lời ngắn gọn, chuẩn xác:
- Sự khác biệt cốt lõi:
df.loc[]truy xuất dựa trên Nhãn tên thực tế (Label) được hiển thị trên bảng.df.iloc[]truy xuất dựa trên Vị trí số nguyên vật lý (Integer Position) từ trên xuống dưới bắt đầu từ vị trí số 0.
- Trường hợp Index = [10, 50, 99]:
df.iloc[0]: Luôn luôn chạy thành công và trả về Dòng đầu tiên của bảng (tương ứng với dòng có index nhãn là 10).df.loc[0]: Sẽ bị báo lỗiKeyError: 0, bởi vìlocsẽ đi tìm dòng nào có dán nhãn tên chính xác là số0. Do trong danh sách index chỉ có nhãn10, 50, 99mà không có nhãn0, lệnh này bị thất bại! Để lấy dòng đầu tiên bằngloc, bạn phải viết làdf.loc[10].
- Sự khác biệt cốt lõi:
❓ Câu hỏi 2: Khi làm sạch dữ liệu, khi nào bạn nên xóa các dòng chứa giá trị NaN (dropna) và khi nào bạn nên điền giá trị thay thế (fillna)? Nếu điền giá trị số, bạn sẽ ưu tiên chọn Mean hay Median?
- Gợi ý trả lời:
- Nên dùng
dropna()khi:- Số lượng dòng chứa
NaNlà rất nhỏ (dưới tổng dữ liệu), việc xóa bỏ không làm ảnh hưởng đến tính toàn vẹn và phân phối của mẫu. - Giá trị bị khuyết nằm ở các cột định danh quan trọng không thể tự suy đoán (ví dụ:
user_id,transaction_id, hoặc biến mục tiêu cần dự đoán).
- Số lượng dòng chứa
- Nên dùng
fillna()khi:- Tỷ lệ dữ liệu bị khuyết đáng kể (ví dụ ). Nếu xóa hết sẽ làm mất đi một lượng lớn thông tin giá trị của các cột khác trong cùng dòng đó.
- Lựa chọn Mean hay Median khi điền số:
- Ưu tiên chọn Median (Trung vị): Vì trong thực tế, hầu hết các biến số kinh doanh (Thu nhập, Chi tiêu, Giá sản phẩm, Tuổi tác) đều bị lệch và có chứa các giá trị ngoại lai (Outliers). Median là giá trị trung tâm ổn định nhất, không bị kéo lệch bởi các giá trị bất thường.
- Chỉ chọn Mean (Trung bình cộng): Khi biết chắc chắn dữ liệu có phân phối chuẩn đối xứng hoàn hảo (hình chuông cân bằng) và hoàn toàn không có Outliers.
- Nên dùng
❓ Câu hỏi 3: Khi bạn cần đọc một file CSV dung lượng 5GB vào Pandas trên một máy tính cá nhân chỉ có 8GB RAM, chương trình Python lập tức bị crash với lỗi MemoryError. Hãy trình bày 3 kỹ thuật cụ thể trong Pandas để giải quyết triệt để vấn đề này.
- Gợi ý trả lời (3 kỹ thuật tối ưu hóa bộ nhớ chuyên nghiệp):
- Chỉ tải các cột thực sự cần thiết bằng tham số
usecols:- File CSV có thể có 50 cột, nhưng bài toán phân tích chỉ cần 5 cột.
- Dùng
pd.read_csv('file.csv', usecols=['user_id', 'order_date', 'amount'])giúp giảm ngay dung lượng RAM nạp vào ban đầu.
- Chỉ định trước kiểu dữ liệu tối ưu bằng tham số
dtype:- Mặc định Pandas gán
int64() vàfloat64(). - Chủ động ép kiểu dữ liệu nhỏ hơn: Cột số nguyên nhỏ dùng
int16(), số thực dùngfloat32(), và đặc biệt các cột chữ bị lặp lại nhiều dùng kiểu'category'Cắt giảm từ dung lượng RAM của DataFrame.
- Mặc định Pandas gán
- Đọc và xử lý dữ liệu theo từng khối nhỏ với tham số
chunksize:- Không nạp toàn bộ 5GB cùng một lúc. Thiết lập
pd.read_csv('file.csv', chunksize=100000). - Sử dụng vòng lặp duyệt qua từng chunk 100,000 dòng: Tính toán hoặc làm sạch xong dòng nào thì ghi kết quả tóm tắt ra file mới hoặc nạp vào Database, sau đó bộ nhớ RAM tự động giải phóng trước khi nạp chunk tiếp theo.
- Không nạp toàn bộ 5GB cùng một lúc. Thiết lập
- Chỉ tải các cột thực sự cần thiết bằng tham số
6. Tóm Tắt Ghi Nhớ Nhanh (Key Takeaways)
loc= Tìm theo Tên/Nhãn (Lấy cả điểm mút cuối);iloc= Tìm theo Vị trí số nguyên (Bỏ điểm mút cuối).- Xử lý
NaN: Dùng Median cho dữ liệu số bị lệch; dùng Mode hoặc nhãn'Unknown'cho dữ liệu danh mục; chỉ dropna khi lượng thiếu rất nhỏ. - Dùng
pd.merge()khi cần ghép bảng theo cột khóa chung (tương đương SQLJOIN); dùngpd.concat()khi cần xếp chồng các dòng lên nhau (SQLUNION ALL). - Tối ưu RAM: Luôn chuyển cột text lặp lại sang kiểu
category, ép số nguyên sang kiểu bit nhỏ hơn và đọc file lớn quachunksize. - Luôn làm sạch dữ liệu theo luồng: Xóa trùng lặp Xóa khoảng trắng Chuẩn hóa định dạng số/ngày Xử lý ô trống Lọc ngoại lai.