Mục lục
- Mục tiêu bài học
- Vì sao cần sort
sort_valuestheo một cột- Sort nhiều cột và mix
ascending inplacevs trả về DataFrame mớina_position— NaN ở đâu- Tham số
kindvà stable sort - Tham số
key— biến đổi trước khi sort sort_index— sort theo index- Sort xong, index có lộn xộn? —
reset_index rank— đánh hạng và percentilenlargest/nsmallest— top-N nhanh- 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
- Sort DataFrame theo 1 hoặc nhiều cột với
sort_values, mixascendingper column. - Hiểu
na_position, tham sốkind(stable vs không), vàkeyđể biến đổi trước khi so sánh. - Phân biệt
sort_values(theo giá trị) vssort_index(theo index / column labels). - Biết khi nào dùng
nlargest/nsmallestthay chosort_values(...).head(n). - Dùng
rankđể tính hạng và percentile cho feature engineering.
Vì sao cần sort
Sau khi đã chọn được dòng / cột cần làm việc (xem bài 38 về loc / iloc), bước tiếp theo thường gặp là sắp xếp. Vài tình huống điển hình:
- Top-N analysis: nhân viên lương cao nhất, sản phẩm bán chạy nhất, học sinh điểm cao nhất.
- Time series: dữ liệu giao dịch về theo nhiều batch, cần sort theo
timestamptrước khi tính rolling mean hoặc shift. - Leaderboard: sắp xếp theo metric (accuracy, F1, RMSE) để so sánh model.
- Restore order sau merge / groupby: Pandas có thể đảo thứ tự sau khi merge nhiều bảng, sort lại theo key để tái lập trật tự gốc.
- Feature importance: sau khi train model, sort feature theo độ quan trọng để chọn top-K cho lần train sau.
Sort không thay đổi nội dung dữ liệu, nhưng quyết định thứ tự bạn nhìn thấy — quan trọng cho cả khi báo cáo lẫn khi feed vào hàm tiếp theo (ví dụ shift, rolling, cumsum).
sort_values theo một cột
Cú pháp tối thiểu: truyền tên cột vào tham số by. Mặc định sort tăng dần (ascending=True).
import pandas as pd
df = pd.DataFrame({
"name": ["An", "Binh", "Chi", "Dung", "Em"],
"dept": ["IT", "HR", "IT", "HR", "IT"],
"age": [29, 41, 24, 35, 31],
"salary": [18, 22, 14, 25, 19], # triệu đồng
})
# Sort theo age tăng dần
df.sort_values("age")
# Output (cột salary, dept giữ nguyên đi cùng dòng):
# name dept age salary
# 2 Chi IT 24 14
# 0 An IT 29 18
# 1 Binh HR 41 22 ← cuối vì lớn nhất
Để giảm dần, truyền ascending=False:
df.sort_values("salary", ascending=False)
# Binh đứng đầu (salary=25 → đính chính: dòng Dung salary 25)
Một điểm thường bị quên: sort_values không sửa df gốc — kết quả là một DataFrame mới. Phải gán lại hoặc dùng inplace (xem mục 5).
Sort nhiều cột và mix ascending
Truyền list vào by để sort theo nhiều cột. Pandas sort theo cột đầu tiên, các dòng cùng giá trị sẽ tiếp tục được sort theo cột thứ hai, v.v.
# Sort theo dept (A→Z), trong cùng dept sort theo salary (tăng dần)
df.sort_values(by=["dept", "salary"])
# name dept age salary
# 1 Binh HR 41 22
# 3 Dung HR 35 25
# 2 Chi IT 24 14
# 0 An IT 29 18
# 4 Em IT 31 19
Để mỗi cột một hướng khác nhau, truyền list cùng độ dài cho ascending:
# dept tăng dần, nhưng salary giảm dần trong từng dept
df.sort_values(by=["dept", "salary"], ascending=[True, False])
# name dept age salary
# 3 Dung HR 35 25 ← lương cao nhất HR
# 1 Binh HR 41 22
# 0 An IT 29 18 ← lương cao nhất IT
# 4 Em IT 31 19
# 2 Chi IT 24 14
Nếu ascending là một boolean duy nhất, nó áp dụng cho tất cả các cột trong by.
inplace vs trả về DataFrame mới
Mặc định sort_values trả về DataFrame mới; df gốc không đổi. Đây là behavior nên dùng — pipeline rõ ràng, dễ debug, dễ chain.
df_sorted = df.sort_values("salary", ascending=False)
# df vẫn nguyên thứ tự ban đầu
inplace=True sửa trực tiếp df, trả về None:
df.sort_values("salary", ascending=False, inplace=True)
# df bị thay đổi tại chỗ; không thể chain phép tiếp theo từ kết quả trả về
Đa số style guide Pandas khuyến cáo tránh inplace: nó chặn chain method, hiệu năng không thực sự nhanh hơn, và làm reasoning về dataflow trong notebook khó hơn (vì state ẩn đi). Pandas 2.x cũng đang loại bỏ dần inplace ở một số API.
na_position — NaN ở đâu
Khi cột sort chứa NaN, mặc định Pandas đặt chúng ở cuối (na_position="last") bất kể ascending là gì. Có thể đổi sang "first":
import numpy as np
df2 = pd.DataFrame({"score": [7.5, np.nan, 9.0, 6.0, np.nan]})
df2.sort_values("score") # NaN ở cuối
df2.sort_values("score", na_position="first") # NaN ở đầu
df2.sort_values("score", ascending=False, na_position="first") # NaN đầu, score giảm dần phần còn lại
Lưu ý: chỉ áp dụng cho sort_values trên cột số / object. Với time series, NaT cũng được coi như NaN.
Tham số kind và stable sort
kind chọn thuật toán sort:
"quicksort"— mặc định, nhanh nhưng không stable."mergesort","stable"— stable: các dòng có cùng key giữ nguyên thứ tự gốc."heapsort"— ít dùng.
Bình thường không cần đổi. Đổi khi cần sort nhiều bước:
# Mục tiêu: sort theo dept (chính), trong cùng dept giữ thứ tự salary giảm dần
# Cách 1: làm 1 phát
df.sort_values(by=["dept", "salary"], ascending=[True, False])
# Cách 2: 2 bước — phải dùng stable cho lần sau
step1 = df.sort_values("salary", ascending=False)
step2 = step1.sort_values("dept", kind="stable")
# step2 == cách 1
Mẹo: với sort nhiều cột phức tạp, đa số trường hợp dùng by=[...] 1 phát là đủ. kind="stable" chỉ thực sự cần khi mỗi bước sort được tách ra vì lý do logic (ví dụ giữa các bước có filter).
Tham số key — biến đổi trước khi sort
Từ Pandas 1.1+, sort_values có tham số key nhận một hàm vector hóa áp lên Series trước khi so sánh — tương tự sorted(..., key=...) trong Python.
df_name = pd.DataFrame({"name": ["binh", "An", "chi", "Dung"]})
# Sort thông thường — chữ in hoa đứng trước (theo code-point)
df_name.sort_values("name")
# name
# 1 An
# 3 Dung
# 0 binh
# 2 chi
# Case-insensitive: dùng key
df_name.sort_values("name", key=lambda s: s.str.lower())
# name
# 1 An
# 0 binh
# 2 chi
# 3 Dung
Một số ứng dụng khác:
- Sort theo độ dài chuỗi:
key=lambda s: s.str.len(). - Sort theo giá trị tuyệt đối (cột residual, error):
key=lambda s: s.abs(). - Sort cột ngày chỉ theo tháng:
key=lambda s: s.dt.month(sau khi đãparse_dates).
Hàm trong key phải nhận và trả về một Series cùng độ dài — không dùng cho phép biến đổi từng phần tử dạng apply chậm.
sort_index — sort theo index
sort_values sort theo giá trị của cột. sort_index sort theo nhãn index (mặc định trục dòng) hoặc nhãn cột:
df3 = pd.DataFrame(
{"a": [3, 1, 2], "c": [9, 8, 7], "b": [6, 5, 4]},
index=["row3", "row1", "row2"],
)
df3.sort_index() # sort theo row label: row1, row2, row3
df3.sort_index(axis=1) # sort cột: a, b, c (alphabetical)
df3.sort_index(ascending=False) # row3, row2, row1
Use case quan trọng nhất của sort_index: sau khi set_index("timestamp") với DataFrame time series, gọi sort_index() để chắc chắn dòng theo đúng thứ tự thời gian trước khi tính rolling, shift, hay resample.
ts = ts.set_index("timestamp").sort_index()
ts["price"].rolling("7D").mean() # giờ mới đúng nghĩa
Sort xong, index có lộn xộn? — reset_index
Sau khi sort_values, index gốc đi cùng các dòng — không phải 0, 1, 2, ... nữa:
df.sort_values("salary", ascending=False)
# name dept age salary
# 3 Dung HR 35 25 ← index 3 đứng đầu
# 1 Binh HR 41 22
# 0 An IT 29 18
# 4 Em IT 31 19
# 2 Chi IT 24 14
Khi cần index sạch 0, 1, 2, ... (ví dụ trước khi export hoặc dùng iloc):
df_top = df.sort_values("salary", ascending=False).reset_index(drop=True)
# name dept age salary
# 0 Dung HR 35 25
# 1 Binh HR 41 22
# 2 An IT 29 18
# ...
drop=True bỏ luôn index cũ; bỏ tham số này (mặc định False) sẽ giữ index cũ thành một cột tên index — đôi khi hữu ích nếu cần truy nguyên dòng gốc.
rank — đánh hạng và percentile
Sort cho ta thứ tự, còn rank cho ta số hạng của từng phần tử trong cột (1 = nhỏ nhất theo mặc định, hoặc lớn nhất nếu ascending=False):
s = pd.Series([60, 90, 75, 90, 50])
s.rank()
# 0 2.0
# 1 4.5 ← cùng giá trị 90 → trung bình của hạng 4 và 5
# 2 3.0
# 3 4.5
# 4 1.0
Tham số method quyết định cách xử lý tie (giá trị bằng nhau):
"average"(mặc định) — trung bình các hạng trùng."min"— gán hạng nhỏ nhất cho cả nhóm tie (kiểu xếp hạng thể thao)."max"— gán hạng lớn nhất."first"— gán theo thứ tự xuất hiện trong dữ liệu."dense"— như"min"nhưng hạng kế tiếp tăng đúng 1 (không nhảy số).
s.rank(method="min") # 90 → hạng 4, 4
s.rank(method="dense") # 50,60,75,90 → 1,2,3,4 (không nhảy)
s.rank(pct=True) # trả về percentile (0..1)
pct=True rất tiện để tạo feature percentile cho mô hình — ví dụ chuyển cột income thành income_percentile:
df["income_pct"] = df["income"].rank(pct=True)
nlargest / nsmallest — top-N nhanh
Khi chỉ cần top-N hoặc bottom-N, không nên sort toàn bộ DataFrame rồi .head(n). Pandas có sẵn cặp nlargest / nsmallest với độ phức tạp khoảng O(N log K) thay vì O(N log N):
# Top 3 lương cao nhất
df.nlargest(3, "salary")
# Top 5 sản phẩm bán ít nhất
df_sales.nsmallest(5, "qty")
# Top theo nhiều tiêu chí: tiebreak bằng cột thứ hai
df.nlargest(5, ["salary", "age"])
Trên DataFrame nhỏ (vài nghìn dòng), khác biệt hiệu năng không đáng kể, nhưng với hàng triệu dòng, nlargest nhanh hơn rõ rệt và đỡ tốn bộ nhớ.
Lưu ý: nlargest bỏ qua NaN; nếu cột sort có NaN thì các dòng NaN không lọt vào top.
Use case AI / ML
- Top-K predictions (recommender, search): với mỗi user, sort score giảm dần lấy top-K item:
top10 = scores_df.nlargest(10, "score") - Time series feature engineering: trước khi
shift,diff,rolling, luônsort_values("timestamp")hoặcsort_index(). - Leaderboard model: sort theo metric chính (giảm dần) và tiebreak bằng metric phụ.
- Feature importance: sau khi train tree-based model,
importancesở dạng Series →importances.sort_values(ascending=False).head(20)để chọn top-K feature. - Percentile feature: với cột số có distribution lệch,
rank(pct=True)tạo feature 0..1 robust hơn so với raw value. - Stratified sampling check: sort theo nhãn / group để xem class imbalance bằng mắt trước khi quyết định resampling.
Code Python tổng hợp
import pandas as pd
import numpy as np
# Bảng nhân viên: name, dept, age, salary (triệu), score (KPI)
df = pd.DataFrame({
"name": ["An", "Binh", "Chi", "Dung", "Em", "Phong", "Giang"],
"dept": ["IT", "HR", "IT", "HR", "IT", "IT", "HR"],
"age": [29, 41, 24, 35, 31, 28, np.nan],
"salary": [18, 22, 14, 25, 19, 21, 17],
"score": [88, 75, 92, 75, 88, 95, 80],
})
# 1) Sort nhiều cột với hướng khác nhau
sorted_df = df.sort_values(
by=["dept", "salary"],
ascending=[True, False],
)
# 2) NaN trong cột age đặt lên đầu để dễ phát hiện
df.sort_values("age", na_position="first")
# 3) Top 3 nhân viên lương cao nhất (đỡ phải sort toàn bộ)
top3 = df.nlargest(3, "salary")
# 4) Rank theo KPI score giảm dần (cao = hạng 1)
df["score_rank"] = df["score"].rank(ascending=False, method="min")
# 5) Feature percentile cho salary (robust với outlier)
df["salary_pct"] = df["salary"].rank(pct=True)
# 6) Sort case-insensitive theo tên
df.sort_values("name", key=lambda s: s.str.lower())
# 7) Sort xong, đánh lại index 0..n-1 để export
result = (
df.sort_values(["dept", "score"], ascending=[True, False])
.reset_index(drop=True)
)
print(result)
Bài tập
- Tạo DataFrame
studentsgồmname,class,scorevới 8 dòng. Sort theoscoregiảm dần, tiebreak bằngnametăng dần (alphabet). In ra 5 dòng đầu sau khireset_index(drop=True). - Tạo DataFrame
productsvớiname,qty_sold. Lấy top 3 sản phẩm bán chạy nhất bằngnlargest, so sánh kết quả vớisort_values(...).head(3)— xác nhận giống nhau. - Cho cột
students.score, tính percentile rank (pct=True) và thêm cột mớiscore_pct. Kiểm tra: học sinh điểm cao nhất phải cóscore_pct = 1.0. - Cho cột tên hỗn hợp chữ hoa / thường (
["binh", "An", "chi", "Dung"]), sort theo bảng chữ cái không phân biệt hoa thường bằng tham sốkey. - (Mở rộng) Tạo DataFrame time series
tradesvới cộttimestampbị xáo trộn.set_index("timestamp"),sort_index(), sau đó tính cột mớiprice_shift = trades["price"].shift(1). So sánh với trường hợp không sort trước khi shift — chỉ ra sự khác biệt.
Tóm tắt
sort_values(by, ascending)sort theo 1 hoặc nhiều cột;ascendingcó thể là list để mix mỗi cột một hướng.- Mặc định trả DataFrame mới — tránh
inplace=Trueđể pipeline rõ ràng. na_position="first"/"last"quyết định vị trí NaN; mặc định"last".kind="stable"cần khi sort tách thành nhiều bước và muốn giữ thứ tự cũ giữa các tie.key=lambda s: ...để sort theo dạng biến đổi: lowercase, abs, độ dài, dt.month, v.v.sort_indexsort theo nhãn dòng / cột; bắt buộc cho time series trước khirolling/shift.- Sau sort,
reset_index(drop=True)để có index 0..n-1 sạch sẽ. rankcho hạng / percentile;methodchọn cách xử lý tie;pct=Truera giá trị 0..1.nlargest/nsmallestnhanh hơn sort-toàn-bộ-rồi-head khi chỉ cần top-N.
