Mục lục
- Mục tiêu bài học
- Vì sao cần
.locvà.iloc .loc— label-based.iloc— position-based- So sánh
.locvs.iloc df["col"]vsdf.loc[:, "col"]vsdf[0:5]- Chained indexing pitfall
.atvà.iat— truy cập scalar- Modify giá trị qua
.loc reset_indexsau khi filter- MultiIndex — preview
- Use case AI / ML
- Code Python tổng hợp
- Bài tập
- Tóm tắt
Mục tiêu bài học
- Hiểu khác biệt label-based (
.loc) và position-based (.iloc). - Nhớ quy tắc slice:
.locINCLUSIVE,.ilocEXCLUSIVE. - Tránh chained indexing — gán giá trị qua
.loc. - Biết
.at/.iatcho 1 ô,reset_indexsau khi filter.
Vì sao cần .loc và .iloc
DataFrame có 2 trục đều có thể được "đánh nhãn": index của dòng và tên cột. Khi index là RangeIndex(0..n-1) thì label và position trùng nhau, nên người mới hay nhầm. Khi index là chuỗi ("HN", "HCM") hoặc đã bị "lủng" sau khi filter, label và position khác nhau hẳn — viết df[5] không còn rõ là "dòng có label 5" hay "dòng vị trí thứ 6".
Pandas tách rõ 2 API:
.loc[label]— luôn theo label (giá trị index, tên cột)..iloc[position]— luôn theo vị trí integer như list Python.
Dùng đúng accessor giúp code đọc rõ ý đồ và tránh bug khi index không phải số nguyên liên tục.
.loc — label-based
Cú pháp: df.loc[row_label, col_label]. Hai phần phân tách bởi dấu phẩy; mỗi phần có thể là 1 label, list label, slice label, hoặc boolean mask.
import pandas as pd
df = pd.DataFrame(
{
"name": ["An", "Binh", "Cuong", "Dung", "Hoa"],
"age": [22, 17, 30, 25, 19],
"salary": [1200, 0, 2500, 1800, 900],
},
index=["u1", "u2", "u3", "u4", "u5"],
)
# 1 dòng theo label
df.loc["u3"]
# Slice label — INCLUSIVE cả 2 đầu: lấy u2, u3, u4
df.loc["u2":"u4"]
# Chọn cột
df.loc[:, "name"] # 1 cột → Series
df.loc[:, ["name", "age"]] # nhiều cột → DataFrame
# Row + col
df.loc["u3", "name"] # 1 ô
df.loc["u2":"u4", ["name", "age"]]
# Boolean mask — chỉ dòng age > 18, lấy cột name
df.loc[df["age"] > 18, "name"]
Điểm hay quên: df.loc["u2":"u4"] lấy cả u4. Slice label inclusive vì label không nhất thiết là số liên tục, không có khái niệm "vị trí kết thúc trừ 1".
.iloc — position-based
Cú pháp: df.iloc[row_pos, col_pos]. Vị trí là integer từ 0, hoạt động đúng như slicing list Python — EXCLUSIVE đầu phải.
import pandas as pd
df = pd.DataFrame(
{
"name": ["An", "Binh", "Cuong", "Dung", "Hoa"],
"age": [22, 17, 30, 25, 19],
"salary": [1200, 0, 2500, 1800, 900],
},
index=["u1", "u2", "u3", "u4", "u5"],
)
# Dòng đầu (luôn là dòng vị trí 0, bất kể label)
df.iloc[0] # → label u1
# Slice — EXCLUSIVE: lấy vị trí 0,1,2,3,4 (5 dòng đầu)
df.iloc[0:5]
# Cột đầu tiên
df.iloc[:, 0]
# Row + col theo position
df.iloc[0, 1] # 1 ô (dòng 0, cột 1)
df.iloc[1:5, 0:3] # 4 dòng × 3 cột
# Negative — đếm ngược như list
df.iloc[-1] # dòng cuối
df.iloc[:, -1] # cột cuối
.iloc không quan tâm index hiện tại là gì — luôn dùng vị trí integer. Sau khi filter làm index "lủng" thì .iloc[0] vẫn là dòng đầu của DataFrame mới.
So sánh .loc vs .iloc
Bảng tóm tắt:
| Đặc tính | .loc | .iloc |
|---|---|---|
| Dựa vào | label (index, tên cột) | position (integer) |
| Slice | INCLUSIVE cả 2 đầu | EXCLUSIVE đầu phải (như list) |
| Boolean mask | Hỗ trợ | Chỉ nhận ndarray boolean, không nhận Series mask theo label |
| Negative index | Không (label "-1" sẽ tra label, không phải vị trí) | Có (-1 = dòng cuối) |
| Khi index là RangeIndex 0..n-1 | Trùng giá trị với position nhưng vẫn inclusive | Như cũ — exclusive |
Ví dụ trực tiếp khi index là RangeIndex:
import pandas as pd
df = pd.DataFrame({"x": [10, 20, 30, 40, 50]}) # index 0..4
df.loc[1:3] # label 1,2,3 → 3 dòng (INCLUSIVE)
df.iloc[1:3] # vị trí 1,2 → 2 dòng (EXCLUSIVE)
df["col"] vs df.loc[:, "col"] vs df[0:5]
Một số shorthand Pandas hay gặp:
df["col"]— lấy 1 cột thành Series. Tương đươngdf.loc[:, "col"].df[["col1", "col2"]]— lấy nhiều cột thành DataFrame. Tương đươngdf.loc[:, ["col1", "col2"]].df[0:5]— slice dòng theo position (EXCLUSIVE), giốngdf.iloc[0:5]. Đây là exception duy nhất khi[...]không nói về cột.
Khi đọc lại code, viết rõ .loc / .iloc dễ hiểu hơn df[0:5]; shorthand chỉ tiện khi gõ nhanh trong notebook.
Chained indexing pitfall
Chained indexing là khi bạn truy cập 2 lần liên tiếp: df["col"][0] hoặc df[df["age"] > 18]["name"] = .... Pandas không đảm bảo kết quả của bước thứ nhất là view hay copy, nên gán giá trị qua chain sẽ ra cảnh báo:
df["col"][0] = 99
# SettingWithCopyWarning: A value is trying to be set on
# a copy of a slice from a DataFrame.
Đôi khi gán "thành công" nhưng thực ra ghi vào copy tạm, DataFrame gốc không đổi. Cách an toàn — luôn dùng .loc trong 1 lần truy cập:
df.loc[0, "col"] = 99 # gán 1 ô
df.loc[df["age"] > 18, "name"] = "ADULT" # gán theo mask
Quy tắc đơn giản: khi đọc, chain có thể chấp nhận; khi ghi (assign), luôn 1 lần .loc / .iloc.
.at và .iat — truy cập scalar
Khi chỉ cần 1 ô (scalar), .at / .iat nhanh hơn .loc / .iloc do bỏ qua bước parse selector phức tạp:
df.at[row_label, col_label]— label-based.df.iat[row_pos, col_pos]— position-based.
df.at["u3", "name"] # nhanh hơn df.loc["u3", "name"]
df.iat[2, 0] # nhanh hơn df.iloc[2, 0]
df.at["u3", "age"] = 31 # gán cũng OK
Lưu ý: .at / .iat chỉ nhận đúng 1 label / 1 position cho mỗi trục — không chấp nhận list, slice, mask. Dùng khi loop từng dòng (hiếm, vì vectorization gần như luôn nhanh hơn loop) hoặc khi truy cập 1 ô trong thuật toán bước-bước.
Modify giá trị qua .loc
.loc không chỉ đọc — đây là cách chuẩn để gán giá trị có điều kiện:
import pandas as pd
df = pd.DataFrame({
"name": ["An", "Binh", "Cuong", "Dung", "Hoa"],
"age": [22, 17, 30, 25, 19],
})
# Gán 1 ô
df.loc[0, "name"] = "Alice"
# Thêm cột mới theo điều kiện
df.loc[df["age"] >= 18, "category"] = "adult"
df.loc[df["age"] < 18, "category"] = "minor"
# Gán nhiều cột cùng lúc cho 1 dòng
df.loc[2, ["name", "age"]] = ["Charlie", 31]
print(df)
Đây là pattern an toàn nhất khi cần modify, không gặp SettingWithCopyWarning.
reset_index sau khi filter
Sau khi df[mask] hoặc df.dropna(), index giữ nguyên label cũ — bị "lủng":
df_adult = df[df["age"] >= 18]
df_adult.index # Int64Index([0, 2, 3, 4]) — thiếu 1
df_adult.iloc[0] # dòng đầu của df_adult — vẫn OK
df_adult.loc[1] # KeyError! label 1 không còn trong df_adult
Nếu sau filter bạn muốn index liên tục 0..k-1 (ví dụ trước khi nối sang code dùng .loc[i] để xử lý batch):
df_adult = df[df["age"] >= 18].reset_index(drop=True)
df_adult.index # RangeIndex(0..n-1)
Tham số drop=True bỏ luôn cột index cũ; bỏ qua nó pandas sẽ thêm 1 cột tên "index" chứa label cũ.
MultiIndex — preview
Khi DataFrame có index nhiều cấp (MultiIndex), .loc nhận tuple key:
import pandas as pd
idx = pd.MultiIndex.from_tuples(
[("HN", 1), ("HN", 2), ("HCM", 1), ("HCM", 2)],
names=["city", "store"],
)
df = pd.DataFrame({"revenue": [100, 120, 200, 180]}, index=idx)
df.loc[("HN", 1), "revenue"] # 100
df.loc["HN"] # tất cả dòng có cấp 1 = "HN"
df.loc[("HCM", 2), "revenue"] = 195
MultiIndex dùng khi group by 2+ key (city × store, user × date...). Series sẽ có bài deep dive riêng khi cần.
Use case AI / ML
- Chọn feature cho training:
X = df.loc[:, feature_cols],y = df["target"]. Truyền thẳng vàosklearn. - Train/test split nhanh theo thứ tự (không random — ví dụ time-series):
df_train = df.iloc[:800],df_test = df.iloc[800:]. - Gán nhãn có điều kiện:
df.loc[df["score"] >= 0.5, "pred"] = 1. - Lấy ra ví dụ sai khi đánh giá model:
errors = df.loc[df["y_true"] != df["y_pred"], ["text", "y_true", "y_pred"]].
Code Python tổng hợp
Demo phân biệt label vs position khi index không phải số liên tục:
import pandas as pd
df = pd.DataFrame(
{
"name": ["An", "Binh", "Cuong", "Dung", "Hoa"],
"age": [22, 17, 30, 25, 19],
"salary": [1200, 0, 2500, 1800, 900],
},
index=["u1", "u2", "u3", "u4", "u5"],
)
# .loc — label
print(df.loc["u3"]) # 1 dòng
print(df.loc["u2":"u4"]) # 3 dòng (INCLUSIVE)
print(df.loc[df["age"] >= 18, "name"]) # boolean mask
# .iloc — position
print(df.iloc[0]) # dòng đầu (label u1)
print(df.iloc[0:3]) # 3 dòng đầu (EXCLUSIVE)
print(df.iloc[-1]) # dòng cuối
# Modify an toàn
df.loc[df["age"] >= 18, "category"] = "adult"
df.loc[df["age"] < 18, "category"] = "minor"
df.loc["u2", "salary"] = 500
# Filter rồi reset_index để index liên tục 0..n-1
adults = df.loc[df["category"] == "adult"].reset_index(drop=True)
print(adults)
# Scalar nhanh — .at / .iat
print(df.at["u3", "name"]) # 'Cuong'
print(df.iat[2, 0]) # 'Cuong'
Bài tập
- Tạo DataFrame
citiesvới 5 dòng (cộtpopulation,gdp), index là tên thành phố["HN","HCM","DN","HP","CT"]. Dùng.loc["HCM"]in dòng HCM. - Lấy 3 dòng đầu của
citiesbằng.iloc[:3]. So sánh vớicities.loc["HN":"DN"]— kết quả có giống nhau không, vì sao? - Tạo DataFrame
employees10 dòng (cộtname,age,salary, indexRangeIndex). Lấy cộtagevàsalarycho 5 nhân viên đầu bằng.locvà bằng.iloc. - Với
employeesở RangeIndex 0..9, indf.loc[1:5]vàdf.iloc[1:5]. Đếm số dòng trả về của mỗi cái — vì sao khác nhau? - Trên
employees, gán cộttier = "high"cho mọi dòng cósalary > 1500, ngược lại"low", dùng.loc2 lần. Verify không có cảnh báoSettingWithCopyWarning.
Tóm tắt
.loc[label]— label-based, slice INCLUSIVE cả 2 đầu, nhận boolean mask..iloc[position]— position-based, slice EXCLUSIVE đầu phải, nhận-1là dòng cuối.df["col"],df[["c1","c2"]]là shorthand chọn cột;df[0:5]là shorthand slice dòng theo position.- Khi gán giá trị, luôn 1 lần
.loc/.iloc— không chained (df["c"][0] = ...) để tránhSettingWithCopyWarning. .at/.iatchỉ cho scalar 1 ô, nhanh hơn khi gọi nhiều lần.- Sau khi filter, gọi
.reset_index(drop=True)nếu cần index liên tục. - MultiIndex truy cập bằng tuple:
df.loc[("HN", 1), "col"].
