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
Ôn nhanh Python theo nhu cầu xử lý dữ liệu.
Bạn đã học Python ở Tư duy tính toán — bài này không học lại, mà xem phần nào của Python phục vụ trực tiếp việc xử lý dữ liệu.
Mỗi ô trong một bảng dữ liệu là một giá trị Python — kiểu của nó quyết định bạn làm được gì.
Người đọc báo cáo cần số đọc được — f-string là công cụ tiêu chuẩn:
gia_tb = 1834567.891
ty_le = 0.0734
f"Giá TB: {gia_tb:,.0f} đ — tỷ lệ trống: {ty_le:.1%}"
'Giá TB: 1,834,568 đ — tỷ lệ trống: 7.3%'
Hai đuôi định dạng đáng nhớ: :,.0f (phân cách nghìn) và :.1% (phần trăm).
gia = [1200000, None, 950000] # phòng thứ 2 không khai giá
sum(x for x in gia if x is not None) / 2
1075000.0
None ≠ 0 ≠ "".
doanh_thu = [5, 8, 6, 9, 12, 11, 7] # 7 ngày
doanh_thu[0], doanh_thu[-1] # đầu, cuối
(5, 7)
doanh_thu[1:4] # lát cắt: phần tử 1,2,3
[8, 6, 9]
Cú pháp lát cắt [bắt_đầu:kết_thúc] cũng được dùng trong NumPy và pandas.
phong = {"id": 52811, "gia": 1200000, "loai": "Entire home"}
phong["gia"] # tra theo khoá
1200000
phong.get("diem", "chưa có") # khoá không tồn tại? có phương án B
'chưa có'
Trong Python thuần, một bảng dữ liệu có thể được biểu diễn bằng một list các dict. Từ bài 4, DataFrame sẽ cung cấp các thao tác thuận tiện hơn.
Hai snapshot dữ liệu — phòng nào biến mất?
thang_9 = {52811, 78230, 99182, 10021}
thang_12 = {52811, 99182, 33307}
thang_9 - thang_12 # có ở T9, mất ở T12
{10021, 78230}
Phép trừ, giao (&), hợp (|) trên set là cách trực tiếp để so sánh hai danh sách ID.
| Bạn cần… | Dùng | Ví dụ trong môn |
|---|---|---|
| dãy có thứ tự, lát cắt | list | giá 7 ngày liên tiếp |
| tra cứu theo tên/khoá | dict | một dòng dữ liệu, cấu hình pipeline |
| khử trùng, so thành viên | set | so ID giữa hai snapshot |
| bộ giá trị không đổi | tuple | toạ độ (lat, lon) |
Tư duy "biến đổi cả dãy" — bước đệm sang NumPy.
gia_tho = [1200000, -5, 950000, 0, 2400000]
gia_sach = []
for g in gia_tho:
if g > 0: # quy tắc QA (quality assurance) đầu tiên!
gia_sach.append(g)
gia_sach
[1200000, 950000, 2400000]
gia_sach = [g for g in gia_tho if g > 0]
gia_trieu = [g / 1e6 for g in gia_sach] # biến đổi cả dãy
gia_trieu
[1.2, 0.95, 2.4]
Cách đọc: "lấy g cho mỗi g trong dãy, nếu…". Câu lệnh ngắn hơn, ít chỗ sai hơn — và đúng cách tư duy trong NumPy: thao tác trên cả dãy, không trên từng phần tử.
ten = ["Adelie", "Gentoo", "Chinstrap"]
so_luong = [152, 124, 68]
{t: n for t, n in zip(ten, so_luong)}
{'Adelie': 152, 'Gentoo': 124, 'Chinstrap': 68}
zip ghép hai dãy song song thành từng cặp; thao tác này thường được dùng khi ghép cột.
goc = [1, 2, 3]
ban_sao = goc # ⚠️ không copy — chỉ thêm tên mới!
ban_sao.append(99)
goc
[1, 2, 3, 99]
ban_sao = goc.copy(). Lỗi một biến thay đổi
ngoài ý muốn có cùng nguyên nhân với vấn đề view vs copy trong pandas.
for qua từng dòng dữ liệu là tín hiệu để dừng lại
và hỏi: "có công cụ cả-dãy nào làm được việc này không?"
Mỗi bước xử lý là một hàm nhỏ, gọi được, thử được.
Còn nhớ cột giá "$45,647.00" của Airbnb? Ta viết một hàm để làm sạch chuỗi này:
def clean_price(s):
"""Đổi chuỗi giá '$45,647.00' thành float; hỏng thì trả None."""
try:
return float(s.replace("$", "").replace(",", ""))
except (ValueError, AttributeError):
return None
clean_price("$45,647.00"), clean_price(None)
(45647.0, None)
clean_price,
không phải xu_ly).None hay phát sinh lỗi).Docstring một dòng + type hint nhẹ (def clean_price(s) -> float | None) là đủ.
float("N/A")
ValueError: could not convert string to float: 'N/A'
except ValueError). Kiểu
except Exception: pass che khuất mọi loại lỗi, kể cả lỗi lập trình,
nên pipeline có thể trả kết quả sai mà không báo lỗi.
gia_tho = ["$1,200.00", "N/A", "$950.00"]
list(map(clean_price, gia_tho))
[1200.0, None, 950.0]
Truyền hàm vào hàm khác — mẫu này sẽ xuất hiện lại ở bài 5: df["price"].map(clean_price).
phong = [{"id": 1, "gia": 1200}, {"id": 2, "gia": 950},
{"id": 3, "gia": 2400}]
sorted(phong, key=lambda p: p["gia"], reverse=True)[0]
{'id': 3, 'gia': 2400}
Quy ước chung: lambda chỉ nên dài một dòng; dài hơn thì viết hàm có tên bằng def.
Cách tự viết lớp (class/OOP) xem ở mục Tự học cuối bài.
src/ của bài tập lớn sẽ giống vậy.Dữ liệu thường nằm trong file và pipeline phải tự đọc dữ liệu đầu vào.
with open("ghi_chu.txt", encoding="utf-8") as f:
noi_dung = f.read()
import csv
with open("phong.csv", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
rows[0]
{'id': '52811', 'gia': '$1,200.00', 'loai': 'Entire home'}
Để ý: mọi giá trị đều là chuỗi, kể cả id và giá. CSV không lưu kiểu dữ liệu; chương trình đọc phải tự chuyển kiểu. Từ bài 4, pandas sẽ tự suy luận kiểu dữ liệu và đôi khi có thể suy luận sai.
import json
ket_qua = {"thanh_pho": "Santiago", "gia_tb": 45647.0}
with open("kq.json", "w", encoding="utf-8") as f:
json.dump(ket_qua, f, ensure_ascii=False, indent=2)
json.loads('{"gia_tb": 45647.0}')["gia_tb"]
45647.0
JSON và dict/list có cấu trúc tương ứng gần như trực tiếp. JSON là định dạng thường dùng cho API (bài 6) và dữ liệu lưu tạm của LLM (bài 11).
from pathlib import Path
DATA = Path("data") / "raw" / "santiago"
DATA.mkdir(parents=True, exist_ok=True)
sorted(DATA.parent.glob("*"))
[PosixPath('data/raw/santiago')]
Đường dẫn được nối bằng / và hoạt động nhất quán trên
các hệ điều hành phổ biến. Có thể dùng công cụ này để tạo cấu trúc
data/raw/<city>/.
None là dữ liệu thiếu; list/dict/set — mỗi thứ một việc.with + encoding="utf-8"; CSV không mang kiểu, JSON ↔ dict.clean_price.
except Exception: pass để code "chạy được"; hay quên encoding;
code đúng với ví dụ bạn đưa nhưng sai với ca lạ (giá âm, chuỗi rỗng…).
None, âm, sai định dạng.