Pandas · Module 7 of 22 · 4 concepts · about 4 min

Sorting and Ranking Data

Putting rows in an order you can defend, and picking the top of them.

At work: Leaderboards, top-seller lists, priority queues and “who are our best customers?”.

sort_values

What it is

Sorts by one or more columns, with ascending accepting a list to mix directions, and na_position deciding where missing values land.

Why it matters

Sorting on several keys is how you make an order deterministic — the second key breaks ties the first one leaves.

In the real world: Sorting products by units sold, then by revenue, to build the “best sellers” page on an online store.

Visual example

Sorting

A second sort key breaks ties, so the order no longer depends on how the rows arrived.

two rows tied on score
idscore
850
350
590
sort_values(["score", "id"])
id breaks the tie
idscore
590
350
850

Why this matters

A 'top N' that changes between runs on the same data is a bug nobody can reproduce. A tiebreak makes it deterministic.

Common misconception

Wrong idea: sorting always gives the same order.

Correct idea: tied rows keep whatever order they arrived in. Without a tiebreak, the winner depends on the input.

df.sort_values(
    ["customer_id", "ordered_at"],
    ascending=[True, False],
    na_position="last",
)

Sorting by a single key and taking the head, when ties exist. The winner is then whichever row happened to be first, which changes between runs and makes the result impossible to test.

Practise it: Keep Only Each User's Latest Row, Top N Products by Revenue, The Top Three Per Group, Deterministically

sort_index

What it is

Sorts by the index rather than by a column, on either axis.

Why it matters

Time series need a sorted index for slicing and for rolling with a time-based window to mean anything; some operations will refuse or silently mislead on unsorted data.

In the real world: Putting a daily sales table back in date order after a merge shuffled it, so a trend chart draws correctly.

ts = df.set_index("ordered_at").sort_index()
ts.loc["2026-01":"2026-03"]     # only meaningful once sorted
ts.index.is_monotonic_increasing # True

nlargest and nsmallest

What it is

nlargest(n, col) returns the top n rows by a column without sorting the whole frame.

Why it matters

It is both clearer and faster than sort_values(...).head(n) on large frames, because it never materialises a full ordering.

In the real world: Finding the 10 highest-value customers for a VIP programme, or the 5 slowest delivery routes to fix first.

df.nlargest(10, "total")
df.nsmallest(5, ["total", "ordered_at"])   # ties broken by the second key

rank

What it is

Assigns a rank per row, with method controlling ties — min, dense, average, first — and pct=True giving percentiles.

Why it matters

Ranking within a group is a common feature: a customer's position within their cohort matters more than their raw value.

In the real world: Ranking sales reps by quarterly revenue for a leaderboard, and deciding fairly what happens when two people tie.

df["rank_in_cohort"] = (
    df.groupby("cohort")["spend"].rank(method="min", ascending=False)
)
df["spend_pct"] = df.groupby("cohort")["spend"].rank(pct=True)

Leaving method at the default average, which produces fractional ranks like 2.5 and quietly breaks anything expecting integers.