Lập trình
xử lý dữ liệu

Học kì 1, năm học 2026–2027

Viện Trí tuệ nhân tạo, Trường ĐH Công nghệ, ĐHQGHN

Bài 5
Series & DataFrame
chuyên sâu

Bài trước

Đã biết chọn hàng, chọn cột, lọc điều kiện và sửa bảng bằng loc.

Bài này mở rộng sang phép tính giữa các Series, tính theo nhóm và ghép nhiều bảng.

Tổng quan

  1. Khớp dữ liệu theo nhãn
  2. Áp dụng hàm lên dữ liệu
  3. Tính theo nhóm
  4. Ghép bảng
  5. Viết phân tích thành một chuỗi

1.
Khớp dữ liệu
theo nhãn

Tính chênh lệch giá khi hai bảng không cùng thứ tự.

Tính chênh lệch giá

Hai Series lưu giá qua hai lần thu thập (giả lập, USD/đêm).

gia_cu

Chỉ mụcGiá
P1100
P280

gia_moi

Chỉ mụcGiá
P290
P1110

gia_moi - gia_cu                        # Trừ hai Series
gia_moi.to_numpy() - gia_cu.to_numpy()  # Bỏ nhãn, trừ hai mảng

Hai phép trừ có cho cùng giá trị không?

Trừ các giá có cùng nhãn

Series trừ theo nhãn; mảng NumPy trừ theo vị trí.

Khi một bên thiếu giá

Phép trừ lấy hợp nhãn: kết quả có đủ P1, P2, P3.

P3 chưa có giá cũ nên chưa tính được.

Đưa mã chỗ ở vào chỉ mục

set_index("id") dùng cột mã làm nhãn của hàng.

set_index không kiểm mã lặp: tự kiểm bằng phong.index.is_unique.

Gán cột cũng khớp theo nhãn

Phép gán căn theo nhãn sẵn có của bảng, không lấy hợp nhãn như phép trừ.

Bài tập 1: Gán đúng phí

Bảng phong dùng mã chỗ ở làm chỉ mục. Phí dọn dẹp (USD) được cung cấp riêng:


ma_cho_o = ["P3", "P1", "P2"]
phi = [20, 10, 30]

Thêm cột phi vào phong sao cho phí luôn khớp với mã chỗ ở, kể cả khi thứ tự hàng của bảng thay đổi.

2.
Áp dụng hàm
lên dữ liệu

Tra phí theo loại chỗ ở, rồi tính tiền cho mỗi hàng.

Bảng chỗ ở và bảng mã phí

bang

Chỉ mụcloaigia
P1Nguyên căn100
P2Phòng riêng80
P3Khác60

phi_theo_loai

loaiphí
Nguyên căn20
Phòng riêng10

Giá theo USD/đêm, phí dọn dẹp thu một lần. Tra phí cho từng hàng:


bang["phi"] = bang["loai"].map(phi_theo_loai)

Hàng P3 mang loại “Khác”: cột phi nhận gì?

Tra phí theo loại chỗ ở

map tra từng giá trị trong bảng mã. “Khác” chưa có mức phí nên nhận NaN.

Viết hàm tính tiền cho mỗi phòng

Tiền ba đêm = 3 × giá mỗi đêm + phí dọn dẹp.

Chỉ mụcgiaphi
P110020
P28010
P360NaN

def tien_ba_dem(hang):
    return 3 * hang["gia"] + hang["phi"]

Gọi hàm cho từng hàng

Lần gọiGiá trị trả về
P1: 3 × 100 + 20320
P2: 3 × 80 + 10250
P3: 3 × 60 + NaNNaN

axis=1: mỗi lần gọi, hàm nhận một hàng dưới dạng Series.

Tính trực tiếp trên các cột

Công thức này viết được bằng phép toán trên cột nên chưa cần apply.

So sánh tốc độ hai cách

Cùng một phép tính trên 18.534 hàng Santiago: tiền cho số đêm tối thiểu.


def tien_toi_thieu(hang):
    return hang["price"] * hang["minimum_nights"]

apply(axis=1) dựng một Series Python cho mỗi hàng: khoản chi phí Bài 3 đã chỉ ra.

Bài tập 2: Miễn phí dọn dẹp

Bảng dat_cho: mỗi hàng là một lượt ở; gia tính theo USD/đêm; so_dem là số đêm của lượt ở; phi là phí dọn dẹp một lần (USD).

Chỉ mụcgiaso_demphi
P1100220
P280710
P360815

Thêm cột tong tính tổng tiền phòng và phí dọn dẹp cho mỗi lượt ở, trong đó khách ở từ 7 đêm được miễn phí dọn dẹp.

Hai cách chia giá thành ba nhóm

Ngưỡng tuyệt đối (30.000 và 100.000 CLP) và ngưỡng phân vị:

qcut tự chọn mốc 46.720 và 75.872 để ba nhóm bằng nhau.

Nếu cả thị trường tăng giá 20%

Ngưỡng tuyệt đối thấy được thị trường lên giá; ngưỡng phân vị khoá cỡ nhóm nên không thấy.

Chọn ranh giới là câu hỏi phân tích.

3.
Tính theo nhóm

Từ phép tính trên từng hàng chuyển sang phép tính trên một tập hàng: so sánh giá giữa các khu, rồi so từng chỗ ở với khu của nó.

So sánh giá của hai khu vực

Bảng giả lập, USD/đêm. Mỗi hàng là một chỗ ở.

Tính trung bình từng khu

khuCác giá được nhómTrung bình
A100, 200, NaN150
B80, 120100

Chia → tính → gộp (split–apply–combine): groupby chia theo khu, mean tính từng nhóm, pandas gộp thành một kết quả mỗi khu.

Đếm số giá dùng để tính

size đếm mọi hàng; count chỉ đếm giá không thiếu.

Đưa tên khu về cột

📖 Tự học

Bảng tk đang dùng tên khu làm chỉ mục. Đưa tên khu thành cột để lưu hoặc xử lý tiếp:

Giá nguyên căn ở Santiago

Dùng bảng listings của Bài 4 (CLP/đêm). So sánh ba khu sau:

Chỉ xét nguyên căn để tránh gộp chung với phòng riêng.

Bài tập 3: Trung bình của cả hai khu

Bảng dưới tổng hợp các giá đã biết (USD/đêm).

KhuGiá trung bìnhSố giá đã biết
A1002
B2008

Từ bảng trên, tính giá trung bình của toàn bộ 10 giá đã biết.

Tách thêm theo loại chỗ ở

Trong mỗi khu, tính riêng giá nguyên căn và phòng riêng.

Mỗi cặp (khu, loai) là một nhóm.

Bảng dạng dài và dạng rộng

Dạng rộng dễ đọc; dạng dài tiện để vẽ biểu đồ và ghép bảng.

Trừ giá cho trung bình của khu

Muốn biết mỗi chỗ ở cao hay thấp hơn trung bình khu của nó. Nhưng df["gia"] có chỉ mục 0, 1, 2, 3, 4 còn tb_khu có chỉ mục A, B.

Hợp nhãn gồm cả 0…4 và A, B; không nhãn nào có mặt ở cả hai bên nên mọi giá trị đều NaN.

Gắn trung bình khu vào từng hàng

Cùng mean, khác ở bước gộp: tổng hợp cho hai kết quả (mỗi nhóm một), transform cho năm kết quả (mỗi hàng một).

Giữ lại các khu đủ lớn

Chỉ phân tích các khu có từ 300 chỗ ở trở lên:

Từ 18.534 hàng, 31 khu còn 16.850 hàng, 8 khu. Cách khác, cùng kết quả:


mask = listings.groupby("neighbourhood")["id"].transform("size") >= 300
khu_dong = listings.loc[mask]

So giá từng chỗ ở với khu của nó

Chỉ mục giữ nguyên từ bảng gốc nên trừ được trực tiếp: mỗi chỗ ở so với trung vị khu của nó.

Đổi đơn vị quan sát sang host

Quan trọng18.534 chỗ ở gom thành 9.712 host: bảng mới có 9.712 hàng, mỗi hàng là một host. Mọi con số tính trên bảng này phải nói rõ đơn vị đếm.

Dựng lại một cột của nhà cung cấp

Santiago có sẵn cột calculated_host_listings_count. Tự tính lại:

Khớp cả 18.534 hàng. Số người khác đưa, dựng lại từ dữ liệu gốc trước khi dùng.

4.
Ghép bảng

Từ một bảng chuyển sang nhiều bảng: ghép bảng có thể làm mất hoặc nhân hàng, nên phải kiểm số hàng trước và sau.

Gộp hai lần thu thập


dot_1 = pd.DataFrame({"id": ["P1", "P2"], "gia": [100, 80], "dot": 1})
dot_2 = pd.DataFrame({"id": ["P1", "P3"], "gia": [110, 70], "dot": 2})

concat nối thêm hàng. P1 có hai lần thu thập nên xuất hiện hai lần.

Bổ sung thông tin vùng

cho_o

idkhu
P1A
P2B
P3C

vung

khuvung
ABắc
BNam

Ghép theo cột khu. P3 thuộc C nhưng bảng vùng chưa có C.

Giữ lại các chỗ ở chưa có vùng

on="khu" đối chiếu theo khu; how="left" giữ mọi hàng bảng trái (left join).

Tìm hàng không ghép được

Thêm indicator=True để biết mỗi chỗ ở có tìm được khu tương ứng trong bảng vung hay không.

both: tìm thấy khu trong bảng vùng.
left_only: P3 thuộc khu C → bảng vùng không có dòng nào khớp.

Khớp khoá mà thiếu thông tin

Bảng vùng có khu C nhưng ô vung của C trống:

Cả ba hàng đều both: khoá C khớp, chỉ thiếu giá trị. NaN bên phải không có nghĩa là không khớp khoá.

Chỉ lấy các chỗ ở ghép được vùng

how="inner" (inner join) chỉ giữ cặp khớp khoá: P3 biến mất. inner là mặc định, quên how là mất hàng.

Khi bảng tra cứu lặp khoá

P1 khớp hai hàng mang khoá A nên xuất hiện hai lần.

Báo lỗi nếu bảng vùng lặp khu

many_to_one: nhiều chỗ ở một khu; bảng vùng mỗi khu một dòng.

Khu A lặp nên pandas dừng, thay vì lặng lẽ nhân đôi P1.

Ghép bảng host trở lại bảng chỗ ở

validate: mỗi chỗ ở khớp đúng một host. assert: số hàng phải không đổi, lần chạy sau tự báo lỗi.

Bài tập 4: Ghép hai bảng

trai

idkhu
P1A
P2A
P3B

phai

khuvung
ABắc
ATrung tâm

Viết bảng kết quả của lệnh sau:


trai.merge(phai, on="khu", how="left")

5.
Viết phân tích
thành một chuỗi

Một chuỗi trả bảng mới: chạy lại bao nhiêu lần cũng ra một kết quả.

Chạy lại một ô có đổi kết quả không?

Đổi giá sang nghìn CLP cho dễ đọc, gán đè lên cột price:

Gán đè lên cột cũ: mỗi lần chạy lại, giá bị chia thêm cho 1000.

Thêm cột mà không sửa bảng gốc

assign(gia_nghin=...) trả về một bảng mới có thêm cột gia_nghin; bảng gốc giữ nguyên.

co_gia vẫn giữ giá CLP, nên chạy lại ô trên bao nhiêu lần thì moi vẫn như vậy.

Đặt tên cho từng bước

Mỗi bước là một hàm: nhận bảng, trả một cột.


def chia_phan_khuc(d):
    return pd.cut(d["price"], nguong_gia, right=False, labels=nhan)

def dem_host(d):
    return d.groupby("host_id")["id"].transform("size")

Thêm cột, trả bảng mới

Giá trị là hàm thì assign tự gọi hàm với bảng: cột phan_khuc là kết quả của chia_phan_khuc(co_gia).

Bảng mới có thêm hai cột. listings không đổi.

Ghép thành một chuỗi

Lọc → thêm cột → nhóm → tổng hợp. Không sửa listings, nên chạy lại vẫn ra một kết quả.

Tổng kết

  1. Phép tính và phép gán Series khớp theo nhãn.
  2. Dùng hàm hoặc phép toán cột để thực hiện đúng quy tắc tính.
  3. Tổng hợp trả kết quả cho mỗi nhóm; transform trả kết quả cho từng hàng.
  4. Ghép bảng có thể làm mất hoặc nhân hàng, nên phải kiểm số hàng trước và sau.
  5. Viết phân tích thành một chuỗi trả bảng mới để chạy lại luôn ra cùng kết quả.

Làm việc với AI thì sao?

🤖 Làm việc với AI
AI làm tốtViết code tính toán, nhóm và ghép từ yêu cầu cụ thể.
AI hay saiBỏ nhãn khi tính, bỏ sót điều kiện, chọn sai khoá nhóm, không kiểm tra các hàng bị lặp sau ghép, và trả bảng đã đổi đơn vị đếm mà không nói.

Kiểm chứng thế nào?

  1. Thử dữ liệu đảo thứ tự, thiếu nhãn và giá trị đúng biên điều kiện.
  2. Tính tay một nhóm; kiểm tra số giá tham gia phép tính.
  3. Dự đoán số hàng sau ghép rồi kiểm tra khoá và các hàng chưa khớp.
  4. Hỏi mỗi hàng của bảng kết quả là gì: một chỗ ở, một host hay một lượt thu thập?

Đọc thêm

  1. McKinney, §5.2: khớp nhãn và áp dụng hàm
  2. McKinney, chương 10: tính theo nhóm
  3. McKinney, §8.2: ghép dữ liệu; §8.3: đổi hình dạng bảng
  4. pandas: kiểm tra phép ghép