Mười thao tác pandas dùng hằng ngày trong nghiên cứu là: đọc tệp, xem nhanh cấu trúc, lọc dòng, tạo cột, nhóm và tóm tắt, gộp bảng có kiểm khoá, xử lý giá trị thiếu, đổi ngày tháng, chuyển dạng dài rộng và lưu kết quả. Nắm chắc mười thao tác này là làm được phần lớn khâu chuẩn bị dữ liệu.
Ví dụ trong bài dùng một bảng giả định gồm mã sinh viên, trường, điểm, ngày khảo sát và giới tính gõ tay, cùng một bảng tra trường theo vùng. Mã đã chạy với pandas 3.0. Bản 3 đổi một số hành vi mặc định so với bản 2, phần cuối bài nói những chỗ ảnh hưởng tới mã cũ.
Mười thao tác và lệnh tương ứng
Bảng tra nhanh, chi tiết từng nhóm ở các mục sau:
| Thao tác | Lệnh chính | Ghi nhớ |
|---|---|---|
| 1. Đọc tệp | pd.read_csv(), pd.read_excel() | Khai kiểu cột mã, bảng mã ký tự |
| 2. Xem nhanh | df.info(), df.head(), df.describe() | Chạy ngay sau khi đọc |
| 3. Lọc dòng | df.loc[điều_kiện], df.query() | Điều kiện ghép dùng &, | và ngoặc |
| 4. Tạo, sửa cột | df.assign(), df.loc[mask, 'cot'] = ... | Không gán kiểu dây chuyền |
| 5. Nhóm, tóm tắt | groupby().agg() | Thêm dropna=False để thấy nhóm thiếu |
| 6. Gộp bảng | merge(validate=..., indicator=True) | Kiểm khoá trùng và dòng không khớp |
| 7. Giá trị thiếu | isna().sum(), dropna(subset=), fillna() | Đếm trước khi quyết định |
| 8. Ngày tháng | pd.to_datetime(format=, errors='coerce') | Khai định dạng ngày Việt Nam |
| 9. Dài và rộng | melt(), pivot_table() | Dạng dài để vẽ, dạng rộng để mô hình |
| 10. Lưu | to_csv(), to_parquet() | Lưu bản sạch riêng, không đè tệp gốc |
Đọc tệp và xem nhanh
Đọc đúng ngay từ đầu
df = pd.read_csv('du-lieu/khaosat.csv', dtype={'ma_tinh': str}). Khai cột mã là chuỗi để giữ số 0 ở đầu. Tệp lưu từ Excel bằng tuỳ chọn CSV UTF-8 thường có ký tự đánh dấu thứ tự byte ở đầu; đọc với encoding='utf-8-sig' để tên cột đầu tiên không bị dính ký tự lạ. Excel thì pd.read_excel(tep, sheet_name='Phieu', skiprows=2), cần cài thêm openpyxl.
Xem trước khi làm gì khác
df.info() cho số dòng, kiểu từng cột, số ô không thiếu. Một cột điểm hiện kiểu chuỗi là dấu hiệu có ô lạ như “8,5” hay “vắng”. df['gioi'].value_counts(dropna=False) cho thấy ngay “Nữ”, “nữ ” và “NU” đang là ba nhóm khác nhau.
Đọc nhiều tệp cùng cấu trúc
Khảo sát thu theo từng trường, mỗi trường một tệp: gom lại bằng pd.concat([pd.read_csv(p).assign(nguon=p.name) for p in sorted(Path('du-lieu/tho').glob('*.csv'))], ignore_index=True), với from pathlib import Path. Cột nguon ghi tên tệp gốc của từng dòng, giúp lần lại khi một trường gửi dữ liệu lỗi. Trước khi gộp, kiểm các tệp có cùng tên cột: một tệp đặt tên “Diem” còn tệp khác “diem” sẽ cho hai cột nửa trống.
Lọc, tạo cột và tránh gán dây chuyền
Lọc
df.loc[(df['diem'] >= 7) & (df['gioi'] == 'Nữ')] hoặc gọn hơn gt = 'Nữ' rồi df.query('diem >= 7 and gioi == @gt'); dấu @ cho phép query dùng biến Python, tránh phải lồng nháy trong chuỗi. Mỗi điều kiện trong loc phải đặt trong ngoặc vì thứ tự ưu tiên của &.
Chuẩn hoá cột chữ
df['gioi'] = df['gioi'].str.strip().str.capitalize() gọt khoảng trắng và đưa về một kiểu chữ hoa. Với nhiều cách gõ khác nhau, dùng một từ điển: df['gioi'] = df['gioi'].replace({'Nu': 'Nữ', 'F': 'Nữ'}).
Sửa giá trị theo điều kiện
Viết df.loc[df['diem'] > 10, 'diem'] = None, một bước, chỉ rõ dòng và cột. Kiểu viết df['diem'][df['diem'] > 10] = None là gán dây chuyền: từ pandas 3, cách này không bao giờ sửa được bảng gốc và pandas phát cảnh báo ChainedAssignmentError. Khi chạy thử, bảng giữ nguyên giá trị cũ.
Nhóm, tóm tắt và gộp bảng có kiểm khoá
Tóm tắt theo trường: df.groupby('truong', dropna=False).agg(so_sv=('ma_sv', 'count'), diem_tb=('diem', 'mean')). Cú pháp đặt tên kiểu ten_moi=('cot', 'ham') cho bảng kết quả có tên cột rõ ràng. Mặc định groupby bỏ các dòng có khoá nhóm thiếu; dropna=False giữ chúng thành một nhóm riêng, tránh việc tổng số sinh viên trong bảng tóm tắt ít hơn cỡ mẫu mà không ai hay.
Gộp với bảng tra vùng: g = df.merge(truong, on='truong', how='left', validate='m:1', indicator=True). validate='m:1' báo lỗi ngay nếu bảng tra có một trường xuất hiện hai lần, thay vì lặng lẽ nhân đôi dòng. indicator=True thêm cột _merge; g['_merge'].value_counts() cho biết bao nhiêu dòng không tìm thấy trường trong bảng tra, thường do gõ tên khác nhau.
Giá trị thiếu và ngày tháng
Đếm trước, quyết sau: df.isna().sum() cho số ô thiếu từng cột. df.dropna(subset=['diem']) chỉ bỏ dòng thiếu ở cột cần thiết, không bỏ cả dòng chỉ vì một cột phụ trống. fillna() chỉ nên dùng khi có lý do rõ ràng, ví dụ ô trống trong câu nhiều lựa chọn nghĩa là “không chọn” và đổi thành 0. Thay giá trị thiếu bằng trung bình là quyết định phương pháp, không phải thao tác làm sạch. Nếu phân tích chính sẽ dùng phương pháp xử lý thiếu như nhập liệu bội, làm bước đó trong kịch bản phân tích và giữ bảng sạch còn nguyên các ô thiếu.
Ngày gõ kiểu Việt Nam: df['ngay'] = pd.to_datetime(df['ngay'], format='%d/%m/%Y', errors='coerce'). Khai format để pandas không đoán nhầm ngày với tháng. errors='coerce' biến ô không hợp lệ, như ngày 31 tháng 13, thành NaT; đếm lại số ô thiếu sau bước này để biết bao nhiêu ô hỏng. Sau đó lấy năm, tháng bằng df['ngay'].dt.year, hay gom theo tháng bằng df.set_index('ngay').resample('MS')['diem'].mean().
Chuyển dạng và lưu kết quả
Dạng dài để vẽ hình theo nhóm: dai = df.melt(id_vars='ma_sv', value_vars=['q1', 'q2', 'q3'], var_name='cau', value_name='diem'). Dạng rộng từ dài: dai.pivot_table(index='ma_sv', columns='cau', values='diem'). Bảng chéo đếm tần số: pd.crosstab(df['truong'], df['gioi']), thêm normalize='index' để ra tỷ lệ theo dòng.
Lưu bản sạch ra thư mục riêng: df.to_csv('du-lieu/xu-ly/khaosat-sach.csv', index=False). Định dạng Parquet (to_parquet, cần pyarrow) giữ đúng kiểu cột kể cả ngày tháng, đọc nhanh hơn CSV với bảng lớn.
Viết chuỗi làm sạch đọc được
Nhiều sổ tay làm sạch dữ liệu là một loạt dòng gán lại df = ... rải rác qua hai chục ô. pandas cho phép viết cả quá trình thành một chuỗi phương thức liền mạch, đọc từ trên xuống như một danh sách bước: sach = (tho.loc[lambda d: d['dong_y'] == 1].drop_duplicates('ma_sv').assign(gioi=lambda d: d['gioi'].str.strip().str.capitalize(), ngay=lambda d: pd.to_datetime(d['ngay'], format='%d/%m/%Y', errors='coerce')).dropna(subset=['diem'])).
assign với lambda d: tham chiếu tới bảng ở đúng bước đó, không phải bảng gốc. Đặt mỗi phương thức trên một dòng trong ngoặc tròn cho dễ đọc. Khi một bước phức tạp, viết thành hàm riêng nhận và trả một DataFrame, rồi gọi bằng .pipe(ten_ham). Ưu điểm lớn nhất: bảng thô tho không bị sửa, chạy lại chuỗi bao nhiêu lần cũng ra cùng một kết quả.
Kiểm tra tự động sau khi làm sạch
Mắt người bỏ sót, nên đặt vài câu lệnh kiểm tra cuối kịch bản. assert sach['ma_sv'].is_unique bảo đảm không còn sinh viên trùng. assert sach['diem'].between(0, 10).all() bảo đảm điểm nằm trong thang. assert len(sach) == len(tho) - so_phieu_loai bảo đảm số dòng khớp với số bạn đã ghi trong nhật ký làm sạch. Điều kiện sai thì Python dừng với AssertionError, số sai không đi tiếp được vào bảng kết quả.
Ghi các con số chính của quá trình làm sạch ra một tệp nhật ký ngắn: số phiếu thô, số phiếu không đồng ý, số phiếu trùng, số phiếu thiếu điểm, số còn lại. Đây chính là sơ đồ chọn mẫu mà phản biện hay hỏi.
pandas 3 thay đổi gì với mã cũ?
Mã viết cho pandas 1 hoặc 2 phần lớn vẫn chạy, nhưng có mấy chỗ cần biết:
- Cơ chế chép khi ghi (Copy-on-Write) thành mặc định: một bảng lấy ra từ bảng khác luôn hành xử như bản sao. Gán dây chuyền không còn tác dụng, phải dùng
loc. - Cột chữ đọc vào có kiểu chuỗi riêng, hiện là
strtrongdf.dtypes, thay vìobjectchung chung. Mã kiểm kiểu bằngdtype == objectcó thể cần sửa. - Nếu dự án cũ cần giữ nguyên hành vi, ghi rõ phiên bản pandas trong tệp
requirements.txtvà cài đúng bản đó trong môi trường của dự án.
Việc KHÔNG cần làm: nâng cấp pandas giữa chừng một luận văn đang chạy ổn. Nâng cấp khi bắt đầu đề tài mới, hoặc khi có thời gian chạy lại và so toàn bộ kết quả.
Câu hỏi thường gặp
pandas đọc được tệp SPSS không?
Có hàm pd.read_spss, cần cài thêm thư viện pyreadstat. Nhãn giá trị có thể được chuyển thành chữ; nếu cần giữ đầy đủ nhãn biến và mã thiếu, dùng trực tiếp pyreadstat để nhận cả siêu dữ liệu.
Bảng quá lớn, pandas chạy chậm thì làm sao?
Chỉ đọc các cột cần bằng usecols, khai kiểu cột cho gọn, lưu sang Parquet. Với dữ liệu lớn hơn bộ nhớ máy, cân nhắc Polars hoặc DuckDB, đều đọc được Parquet.
Vì sao tổng theo nhóm ít hơn số dòng?
Thường do groupby mặc định bỏ các dòng có khoá nhóm thiếu. Thêm dropna=False để thấy nhóm thiếu, rồi quyết định giữ hay bỏ có chủ đích.
Nên dùng apply hay phép toán trên cả cột?
Ưu tiên phép toán trên cả cột và các hàm có sẵn như str, dt, vì nhanh và dễ đọc. apply với hàm tự viết chạy từng dòng, chậm hơn nhiều với bảng lớn.
Làm sao biết phiên bản pandas đang dùng?
Chạy pd.__version__ trong sổ tay hoặc pip show pandas trong cửa sổ lệnh. Ghi số này vào tệp phiên bản của dự án.