Ngày 45: Giới thiệu Pandas - Bắt đầu làm quen với DataFrames và Series
Chào mừng các bạn đến với Ngày 45 của chuỗi Hướng dẫn Python 365 ngày! Hôm nay, chúng ta sẽ cùng nhau khám phá một thư viện vô cùng mạnh mẽ và quan trọng trong thế giới xử lý dữ liệu với Python: Pandas.
Pandas là thư viện mã nguồn mở cung cấp các cấu trúc dữ liệu hiệu suất cao và dễ sử dụng cùng với các công cụ phân tích dữ liệu mạnh mẽ. Nó là nền tảng cho hầu hết các công việc phân tích dữ liệu, machine learning và khoa học dữ liệu trong Python.
Tại sao lại là Pandas?
Trong các bài trước, chúng ta đã làm việc với dữ liệu dưới dạng list hoặc dictionary. Tuy nhiên, khi làm việc với lượng dữ liệu lớn hơn hoặc dữ liệu có cấu trúc phức tạp hơn (giống như bảng tính Excel hoặc bảng trong cơ sở dữ liệu), việc sử dụng các cấu trúc dữ liệu cơ bản của Python trở nên khó khăn và kém hiệu quả. Pandas ra đời để giải quyết vấn đề này với các cấu trúc dữ liệu tối ưu cho dữ liệu dạng bảng.
Hai Cấu Trúc Dữ Liệu Chính: Series và DataFrame
Pandas giới thiệu hai cấu trúc dữ liệu chính:
- Series: Một mảng một chiều (1D) có nhãn. Nó giống như một cột trong bảng tính hoặc một Series trong toán học. Mỗi phần tử trong Series có một chỉ mục (index) đi kèm.
- DataFrame: Một cấu trúc dữ liệu hai chiều (2D) có nhãn với các cột có kiểu dữ liệu khác nhau. Nó giống như một bảng tính, một bảng trong cơ sở dữ liệu hoặc một dictionary của các đối tượng Series. DataFrame là cấu trúc dữ liệu được sử dụng phổ biến nhất trong Pandas.
Bắt đầu với Series
Để sử dụng Pandas, bạn cần cài đặt và import nó:
pip install pandas
import pandas as pd
Tạo Series từ List
import pandas as pd
data = [10, 20, 30, 40, 50]
my_series = pd.Series(data)
print(my_series)
Kết quả:
0 10
1 20
2 30
3 40
4 50
dtype: int64
Ở đây, Pandas tự động tạo chỉ mục số nguyên từ 0 đến 4. Bạn cũng có thể chỉ định chỉ mục riêng:
import pandas as pd
data = [10, 20, 30, 40, 50]
indices = ['a', 'b', 'c', 'd', 'e']
my_series = pd.Series(data, index=indices)
print(my_series)
Kết quả:
a 10
b 20
c 30
d 40
e 50
dtype: int64
Tạo Series từ Dictionary
Dictionary có thể được sử dụng để tạo Series, trong đó keys của dictionary trở thành index của Series:
import pandas as pd
data_dict = {'apple': 3, 'banana': 5, 'cherry': 6}
my_series = pd.Series(data_dict)
print(my_series)
Kết quả:
apple 3
banana 5
cherry 6
dtype: int64
Bắt đầu với DataFrame
DataFrame là cấu trúc dữ liệu mạnh mẽ nhất trong Pandas. Nó giống như một tập hợp các Series có cùng chỉ mục.
Tạo DataFrame từ Dictionary các List
Cách phổ biến nhất để tạo DataFrame là sử dụng dictionary, trong đó keys là tên cột và values là list dữ liệu cho cột đó:
import pandas as pd
data = {
'Tên': ['An', 'Bình', 'Hoa', 'Dung'],
'Tuổi': [25, 30, 22, 28],
'Thành phố': ['Hà Nội', 'TP.HCM', 'Đà Nẵng', 'Hà Nội']
}
df = pd.DataFrame(data)
print(df)
Kết quả:
Tên Tuổi Thành phố
0 An 25 Hà Nội
1 Bình 30 TP.HCM
2 Hoa 22 Đà Nẵng
3 Dung 28 Hà Nội
Pandas lại tự động tạo chỉ mục số nguyên. Bạn có thể chỉ định chỉ mục riêng:
import pandas as pd
data = {
'Tên': ['An', 'Bình', 'Hoa', 'Dung'],
'Tuổi': [25, 30, 22, 28],
'Thành phố': ['Hà Nội', 'TP.HCM', 'Đà Nẵng', 'Hà Nội']
}
indices = ['sv1', 'sv2', 'sv3', 'sv4']
df = pd.DataFrame(data, index=indices)
print(df)
Kết quả:
Tên Tuổi Thành phố
sv1 An 25 Hà Nội
sv2 Bình 30 TP.HCM
sv3 Hoa 22 Đà Nẵng
sv4 Dung 28 Hà Nội
Xem Dữ Liệu Cơ Bản
Với DataFrame, bạn có thể xem vài dòng đầu hoặc cuối:
print(df.head(2)) # Xem 2 dòng đầu
print(df.tail(1)) # Xem 1 dòng cuối
Kết quả head(2):
Tên Tuổi Thành phố
sv1 An 25 Hà Nội
sv2 Bình 30 TP.HCM
Kết quả tail(1):
Tên Tuổi Thành phố
sv4 Dung 28 Hà Nội
Truy cập Cột và Hàng
Bạn có thể truy cập một cột bằng tên cột (kết quả là một Series):
print(df['Tuổi'])
print(type(df['Tuổi']))
Kết quả:
sv1 25
sv2 30
sv3 22
sv4 28
Name: Tuổi, dtype: int64
<class 'pandas.core.series.Series'>
Truy cập nhiều cột (kết quả là một DataFrame):
print(df[['Tên', 'Thành phố']])
print(type(df[['Tên', 'Thành phố']]))
Kết quả:
Tên Thành phố
sv1 An Hà Nội
sv2 Bình TP.HCM
sv3 Hoa Đà Nẵng
sv4 Dung Hà Nội
<class 'pandas.core.frame.DataFrame'>
Để truy cập hàng bằng index, sử dụng .loc[]:
print(df.loc['sv1'])
Kết quả:
Tên An
Tuổi 25
Thành phố Hà Nội
Name: sv1, dtype: object
Hoặc truy cập hàng bằng vị trí số nguyên, sử dụng .iloc[]:
print(df.iloc[0]) # Hàng đầu tiên (index 0)
Kết quả:
Tên An
Tuổi 25
Thành phố Hà Nội
Name: sv1, dtype: object
📊 Bảng thao tác pandas quan trọng nhất
Đây là những thao tác bạn dùng mỗi ngày khi làm việc với dữ liệu:
| Việc cần làm | Cú pháp |
|---|---|
| Xem hình dạng | df.shape → (số dòng, số cột) |
| Xem kiểu dữ liệu | df.dtypes |
| 5 dòng đầu / cuối | df.head() / df.tail() |
| Thống kê mô tả | df.describe() |
| Đếm giá trị thiếu | df.isna().sum() |
| Đếm dòng trùng | df.duplicated().sum() |
| Chọn nhiều cột | df[["ten", "gia"]] |
| Lọc theo điều kiện | df[df["gia"] > 100000] |
| Sắp xếp | df.sort_values("gia", ascending=False) |
| Tạo cột mới | df["gia_vnd"] = df["gia_usd"] * 25400 |
| Đổi tên cột | df.rename(columns={"cu": "moi"}) |
| Nhóm và tổng hợp | df.groupby("loai")["gia"].sum() |
| Nối dọc | pd.concat([df1, df2], ignore_index=True) |
| Nối ngang | df1.merge(df2, on="id", how="left") |
🧹 Làm sạch dữ liệu — công việc chiếm nhiều thời gian nhất
Trong thực tế, bạn dành 60–80% thời gian để làm sạch dữ liệu, không phải để phân tích.
import pandas as pd
import numpy as np
df = pd.DataFrame({
"ten": ["Minh", "Lan", None, "Minh", "Hoa"],
"tuoi": ["25", "30", "hai mươi", "25", None],
"luong": ["15,000,000", "20,000,000", "18000000", "15,000,000", "25,000,000"],
"ngay": ["2026-01-15", "15/02/2026", "2026-03-20", None, "2026-05-01"],
})
print("TRƯỚC KHI LÀM SẠCH")
print(df)
print(df.dtypes)
Vấn đề: tuoi là chuỗi, luong có dấu phẩy, ngay có hai định dạng, có giá trị thiếu và dòng trùng.
# --- 1. Loại dòng trùng ---
df = df.drop_duplicates()
# --- 2. Chuyển cột số (xử lý dấu phẩy và giá trị không hợp lệ) ---
df["luong"] = (
df["luong"].astype(str)
.str.replace(",", "", regex=False)
.pipe(pd.to_numeric, errors="coerce")
)
df["tuoi"] = pd.to_numeric(df["tuoi"], errors="coerce")
# --- 3. Chuẩn hoá ngày tháng (tự nhận diện nhiều định dạng) ---
df["ngay"] = pd.to_datetime(df["ngay"], errors="coerce", format="mixed")
# --- 4. Xử lý giá trị thiếu ---
df["ten"] = df["ten"].fillna("Không rõ")
df["tuoi"] = df["tuoi"].fillna(df["tuoi"].median())
df = df.dropna(subset=["luong"]) # cột quan trọng thiếu → bỏ dòng
print("SAU KHI LÀM SẠCH")
print(df)
print(df.dtypes)
⚠️
errors="coerce"là tham số cứu bạn khỏi crash.pd.to_numeric(["25", "hai mươi"])sẽ ném lỗi. Vớierrors="coerce", giá trị không chuyển được thànhNaN— bạn xử lý tiếp thay vì chương trình chết.
📈 Ví dụ phân tích thực tế
import pandas as pd
import numpy as np
np.random.seed(42)
df = pd.DataFrame({
"ngay": pd.date_range("2026-01-01", periods=180, freq="D"),
"khu_vuc": np.random.choice(["Hà Nội", "TP.HCM", "Đà Nẵng"], 180),
"doanh_thu": np.random.randint(5_000_000, 80_000_000, 180),
"so_don": np.random.randint(1, 50, 180),
})
df["thang"] = df["ngay"].dt.to_period("M").astype(str)
bao_cao = (
df.groupby(["thang", "khu_vuc"])
.agg(so_don=("so_don", "sum"),
doanh_thu=("doanh_thu", "sum"),
don_tb=("doanh_thu", "mean"))
.round(0)
.reset_index()
)
print(bao_cao.head(9).to_string(index=False))
print("\n🏆 Xếp hạng theo tổng doanh thu:")
top = df.groupby("khu_vuc")["doanh_thu"].sum().sort_values(ascending=False)
for khu_vuc, doanh_thu in top.items():
phan_tram = doanh_thu / top.sum() * 100
print(f" {khu_vuc:<10} {doanh_thu:>16,.0f} đ ({phan_tram:>5.1f}%)")
with pd.ExcelWriter("bao-cao.xlsx", engine="openpyxl") as writer:
bao_cao.to_excel(writer, sheet_name="Theo tháng", index=False)
top.to_frame("doanh_thu").to_excel(writer, sheet_name="Theo khu vực")
⚠️ Bốn lỗi pandas người mới luôn mắc
1. SettingWithCopyWarning
# ❌ SAI — thao tác trên bản sao, không ảnh hưởng df gốc
(df[df["gia"] > 100])["gia"] = 0
# ✅ ĐÚNG — dùng .loc
df.loc[df["gia"] > 100, "gia"] = 0
2. Vòng lặp qua từng dòng — cực chậm
# ❌ CHẬM: 100.000 dòng mất vài chục giây
# for i, r in df.iterrows():
# df.loc[i, "gia_vnd"] = r["gia_usd"] * 25400
# ✅ NHANH: vector hoá, nhanh hơn hàng trăm lần
df["gia_vnd"] = df["gia_usd"] * 25400
3. Đọc file lớn bằng một lần
# ❌ Đọc cả file 5 GB vào RAM → hết bộ nhớ
# df = pd.read_csv("khong-lo.csv")
# ✅ Đọc theo lô 100.000 dòng
ket_qua = []
for lo in pd.read_csv("khong-lo.csv", chunksize=100_000):
ket_qua.append(lo[lo["gia"] > 0])
df = pd.concat(ket_qua, ignore_index=True)
4. Không kiểm tra dữ liệu trước khi phân tích
# LUÔN chạy 6 kiểm tra này trước khi phân tích gì
def kiem_tra_nhanh(df: pd.DataFrame) -> None:
print(f"Kích thước: {df.shape}")
print(f"Giá trị thiếu: {df.isna().sum().sum()}")
print(f"Dòng trùng: {df.duplicated().sum()}")
print(df.dtypes)
print(df.head())
print(df.describe())
kiem_tra_nhanh(df)
📝 Bài tập mở rộng
Bài 1: Làm sạch dữ liệu thật
Tải một bộ dữ liệu từ Kaggle, chạy df.info(), tìm và xử lý toàn bộ vấn đề về kiểu dữ liệu và giá trị thiếu.
Bài 2: Báo cáo doanh thu theo thứ
Tính doanh thu trung bình theo từng thứ trong tuần. Thứ nào cao nhất?
Bài 3: So sánh tăng trưởng
Tính phần trăm tăng trưởng doanh thu tháng này so với tháng trước cho từng khu vực.
Bài 4: Xuất báo cáo đa sheet
Xuất kết quả phân tích ra file Excel có 3 sheet: chi tiết, tổng hợp theo tháng, top 10.
Bài 5: Phát hiện dữ liệu bất thường
Dùng phương pháp IQR (tứ phân vị) tìm các giá trị ngoại lai trong cột doanh thu.
🎯 Tóm tắt ngày 45
✅ Hiểu Series và DataFrame
✅ Dùng thành thạo bảng thao tác pandas
✅ Áp dụng quy trình 4 bước làm sạch dữ liệu
✅ Tránh 4 lỗi người mới hay mắc
✅ Vector hoá thay vì vòng lặp
✅ Xuất báo cáo Excel nhiều sheet
Kết Luận
Bài viết này đã giới thiệu những kiến thức cơ bản nhất về Pandas, bao gồm hai cấu trúc dữ liệu chính là Series và DataFrame, cùng với cách tạo và xem dữ liệu ban đầu. Pandas là một thư viện rất rộng lớn với nhiều chức năng mạnh mẽ khác mà chúng ta sẽ khám phá trong các bài viết tiếp theo.
Ở Ngày 46, chúng ta sẽ tìm hiểu sâu hơn về cách lọc và chọn dữ liệu trong DataFrame.
Hẹn gặp lại các bạn!