Danh sách bài viết

Bài 39: Sắp xếp dữ liệu với sort_values

Sắp xếp DataFrame trong Pandas với sort_values theo một hoặc nhiều cột, ascending mix per column, na_position, kind, key. So sánh sort_index, reset_index, rank, nlargest / nsmallest và ứng dụng vào top-K, leaderboard, time series, feature importance.

24/05/2026
10 phút đọc
0 lượt xem
1

Mục tiêu bài học

  • Sort DataFrame theo 1 hoặc nhiều cột với sort_values, mix ascending per 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ị) vs sort_index (theo index / column labels).
  • Biết khi nào dùng nlargest / nsmallest thay cho sort_values(...).head(n).
  • Dùng rank để tính hạng và percentile cho feature engineering.
2

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 timestamp trướ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).

3

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

4

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.

5

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.

6

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.

7

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

8

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.

9

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
10

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.

11

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)
12

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.

13

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ôn sort_values("timestamp") hoặc sort_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.
14

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)
15

Bài tập

  1. Tạo DataFrame students gồm name, class, score với 8 dòng. Sort theo score giảm dần, tiebreak bằng name tăng dần (alphabet). In ra 5 dòng đầu sau khi reset_index(drop=True).
  2. Tạo DataFrame products với name, qty_sold. Lấy top 3 sản phẩm bán chạy nhất bằng nlargest, so sánh kết quả với sort_values(...).head(3) — xác nhận giống nhau.
  3. Cho cột students.score, tính percentile rank (pct=True) và thêm cột mới score_pct. Kiểm tra: học sinh điểm cao nhất phải có score_pct = 1.0.
  4. 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.
  5. (Mở rộng) Tạo DataFrame time series trades với cột timestamp bị xáo trộn. set_index("timestamp"), sort_index(), sau đó tính cột mới price_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.
16

Tóm tắt

  • sort_values(by, ascending) sort theo 1 hoặc nhiều cột; ascending có 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_index sort theo nhãn dòng / cột; bắt buộc cho time series trước khi rolling / shift.
  • Sau sort, reset_index(drop=True) để có index 0..n-1 sạch sẽ.
  • rank cho hạng / percentile; method chọn cách xử lý tie; pct=True ra giá trị 0..1.
  • nlargest / nsmallest nhanh hơn sort-toàn-bộ-rồi-head khi chỉ cần top-N.