Q&A library Python

What is the difference between nlargest on a groupby and sorting with head?

Python Advanced groupbynlargesttop-n

Short answer

groupby.nlargest returns a Series with a MultiIndex of group and original position, which is awkward, while sort then groupby.head returns a normal frame. Use whichever output shape suits your next step.

The short version

groupby(key)[col].nlargest(n) gives you the top values per group, but returns a Series with a two-level index (the group, and the original row label), which is awkward to use. Sorting the frame and calling groupby(key).head(n) returns an ordinary DataFrame with all its columns, which is usually what you want.

The two approaches

import pandas as pd

orders = pd.DataFrame({
    "city": ["Leeds", "Leeds", "Bath", "Bath"],
    "amount": [5, 9, 7, 3],
})

# approach one: nlargest on a groupby
orders.groupby("city")["amount"].nlargest(1)
city
Bath   2    7
Leeds  1    9
Name: amount, dtype: int64

The result is a Series indexed by (city, original row label). You have the values, but you have lost the other columns, and the index is fiddly.

# approach two: sort, then head per group
orders.sort_values("amount", ascending=False).groupby("city").head(1)
    city  amount
1  Leeds       9
2   Bath       7

This returns real rows, with every column intact and a normal index. For most purposes this is the better output.

Getting a usable frame from nlargest

If you prefer nlargest, flatten the result with reset_index.

top = orders.groupby("city")["amount"].nlargest(1).reset_index(level=0)

level=0 pushes the city back into a column while keeping the original row label as the index, which lets you rejoin to the source frame if you need the other columns.

Which to use

Use the sort-then-head pattern when you want whole rows, which is the common case for "show me the biggest order in each city". Use nlargest on a groupby when you only need the values themselves and are happy to work with the MultiIndex, for example when feeding the numbers straight into another calculation.

The ties caveat, in both cases

Both approaches return exactly n rows per group, arbitrarily breaking ties. If tied rows should all be kept, neither is right. Rank instead and filter, which keeps every row sharing the boundary rank.

ranked = orders.groupby("city")["amount"].rank(method="min", ascending=False)
orders[ranked <= 1]

Do not forget the sort

The head approach depends entirely on sorting first. groupby(...).head(n) takes the first n rows of each group as they currently sit, so without the sort you get an arbitrary selection, not the top. This is the single most common mistake with this pattern, and it fails silently, producing plausible-looking but wrong results.

Need this solved on your own data?

The library is free and always will be. When the problem is bigger than an answer, we take on the work.

Start a project