Chuyển tới nội dung chính

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 Logo

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 Data Structures

Pandas giới thiệu hai cấu trúc dữ liệu chính:

  1. 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.
  2. 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)

Series Example

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.

DataFrame Structure

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àmCú pháp
Xem hình dạngdf.shape → (số dòng, số cột)
Xem kiểu dữ liệudf.dtypes
5 dòng đầu / cuốidf.head() / df.tail()
Thống kê mô tảdf.describe()
Đếm giá trị thiếudf.isna().sum()
Đếm dòng trùngdf.duplicated().sum()
Chọn nhiều cộtdf[["ten", "gia"]]
Lọc theo điều kiệndf[df["gia"] > 100000]
Sắp xếpdf.sort_values("gia", ascending=False)
Tạo cột mớidf["gia_vnd"] = df["gia_usd"] * 25400
Đổi tên cộtdf.rename(columns={"cu": "moi"})
Nhóm và tổng hợpdf.groupby("loai")["gia"].sum()
Nối dọcpd.concat([df1, df2], ignore_index=True)
Nối ngangdf1.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ới errors="coerce", giá trị không chuyển được thành NaN — 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!