Pandas idxmax & idxmin

Reviewed & published by Brayan K

max() tells you the biggest number in a column. It does not tell you who it belongs to. idxmax() answers that second question — it returns the index label of the row holding that maximum, which you can hand straight to .loc to pull back the whole record.

Part of the free Pandas course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

This is the everyday "which product sold most?" question, and getting it wrong — by sorting the entire frame, or by confusing idxmax with argmax — is one of the most common beginner mistakes in pandas.

What You'll Learn in This Lesson

1 The Value vs. the Location

These two methods answer different questions, and mixing them up is where the confusion starts:

On its own that label is not very interesting. Its power is that .loc accepts it, so the two together give you the complete winning row:

idxmin() is the exact mirror image and takes all the same arguments, so everything below applies to finding minimums too.

import pandas as pd

df = pd.DataFrame({
    "product": ["Laptop", "Mouse", "Monitor", "Keyboard"],
    "sales":   [420, 155, 980, 310],
})

print("Biggest number :", df["sales"].max())
print("Its row label  :", df["sales"].idxmax())
print()
print("The whole row:")
print(df.loc[df["sales"].idxmax()])

# ✅ Expected output:
# Biggest number : 980
# Its row label  : 2
#
# The whole row:
# product    Monitor
# sales          980
# Name: 2, dtype: object

2 idxmax vs. argmax — the Bug That Hides

argmax() returns the position (0, 1, 2 …). idxmax() returns the label. On a fresh DataFrame these are the same number, so code using the wrong one appears to work — until you filter the rows or set a custom index, and the labels stop matching the positions.

After a filter, the surviving rows keep their original labels — so a frame of three rows might have labels 1, 2 and 5. That is exactly when position and label part company.

import pandas as pd

df = pd.DataFrame({
    "product": ["Laptop", "Mouse", "Monitor", "Keyboard"],
    "sales":   [420, 155, 980, 310],
})

# Keep only the pricier items — labels 0, 2, 3 survive
big = df[df["sales"] > 300]
print(big)
print()

print("idxmax (label)   :", big["sales"].idxmax())
print("argmax (position):", big["sales"].argmax())
print()
print("Correct row with .loc :", df.loc[big["sales"].idxmax(), "product"])
print("Correct row with .iloc:", big.iloc[big["sales"].argmax()]["product"])

# ✅ Expected output:
#     product  sales
# 0    Laptop    420
# 2   Monitor    980
# 3  Keyboard    310
#
# idxmax (label)   : 2
# argmax (position): 1
#
# Correct row with .loc : Monitor
# Correct row with .iloc: Monitor

3 The Best Row in Every Group

This is where idxmax really earns its place. Suppose you want the top-selling product per region. A plain groupby("region").max() will not do it — that aggregates every column independently, so you can end up with one region's highest sales sitting next to a different product's name. A row that never existed.

groupby(...)["sales"].idxmax() instead returns one label per group. Feed those labels to .loc and you get real, intact rows:

import pandas as pd

df = pd.DataFrame({
    "region":  ["North", "North", "South", "South", "South"],
    "product": ["Laptop", "Mouse", "Monitor", "Keyboard", "Webcam"],
    "sales":   [420, 155, 980, 310, 640],
})

# One index label per region, then fetch those rows
best = df.loc[df.groupby("region")["sales"].idxmax()]
print(best)
print()

# The misleading alternative — note the mismatched product names
print(df.groupby("region").max())

# ✅ Expected output:
#   region  product  sales
# 0  North   Laptop    420
# 2  South  Monitor    980
#
#        product  sales
# region               
# North    Mouse    420
# South   Webcam    980

4 Edge Cases: Ties, NaN, Empty & axis=1

import pandas as pd
import numpy as np

s = pd.Series([10, np.nan, 30, 30])
print("skipna=True (default):", s.idxmax())      # NaN ignored, first 30 wins
print("all tied labels      :", list(s[s == s.max()].index))
print()

# axis=1: which month was each shop's best?
sales = pd.DataFrame({
    "jan": [120, 300],
    "feb": [450, 210],
    "mar": [380, 260],
}, index=["Shop A", "Shop B"])
print(sales.idxmax(axis=1))
print()

# Guard against the empty case
empty = s[s > 999]
print("safe:", empty.idxmax() if empty.notna().any() else "no values to compare")

# ✅ Expected output:
# skipna=True (default): 2
# all tied labels      : [2, 3]
#
# Shop A    feb
# Shop B    jan
# dtype: object
#
# safe: no values to compare

5 🎯 Your Turn

Given a table of city temperature readings, find the single hottest reading, the single coldest, and the hottest reading in each country — keeping every column of those rows intact.

import pandas as pd

df = pd.DataFrame({
    "country": ["UK", "UK", "Spain", "Spain", "Spain"],
    "city":    ["London", "Leeds", "Madrid", "Seville", "Bilbao"],
    "temp_c":  [24, 21, 35, 39, 27],
})

print("Hottest overall:")
print(df.loc[df["temp_c"].idxmax()])
print()
print("Coldest overall:")
print(df.loc[df["temp_c"].idxmin()])
print()
print("Hottest per country:")
print(df.loc[df.groupby("country")["temp_c"].idxmax()])

# ✅ Expected output:
# Hottest overall:
# country      Spain
# city       Seville
# temp_c          39
# Name: 3, dtype: object
#
# Coldest overall:
# country       UK
# city       Leeds
# temp_c        21
# Name: 1, dtype: object
#
# Hottest per country:
#   country     city  temp_c
# 3   Spain  Seville      39
# 0      UK   London      24

❓ Frequently Asked Questions

Lesson complete — you can now name the winner, not just the number!

You know that idxmax returns a label, that .loc turns that label into a full row, and that groupby(...).idxmax() is the honest way to get the best record per group.

Practice quiz

What does df["sales"].idxmax() return?

  • The largest sales value
  • The index label of the largest value
  • The row as a Series
  • The column name

Answer: The index label of the largest value. idxmax returns the index LABEL where the maximum sits — not the value itself.

How do you get the whole row containing the largest value?

  • df.max()
  • df.loc[df["sales"].idxmax()]
  • df["sales"].max()
  • df.iloc[df["sales"].max()]

Answer: df.loc[df["sales"].idxmax()]. idxmax gives you the label; feed that label to .loc to pull back the entire row.

What is the difference between .max() and .idxmax()?

  • They are identical
  • max returns the value, idxmax returns where it is
  • max is faster
  • idxmax only works on integers

Answer: max returns the value, idxmax returns where it is. max answers 'what is the biggest number', idxmax answers 'which row holds it'.

If two rows tie for the maximum, what does idxmax return?

  • Both labels
  • The last one
  • The first one it meets
  • NaN

Answer: The first one it meets. Ties are broken by position — idxmax returns the first occurrence only.

By default, how does idxmax treat NaN values?

  • Returns NaN
  • Raises an error
  • Skips them (skipna=True)
  • Treats them as zero

Answer: Skips them (skipna=True). skipna defaults to True, so missing values are ignored when finding the maximum.

What happens if you call idxmax() on an empty Series?

  • Returns None
  • Returns 0
  • Returns NaN
  • Raises a ValueError

Answer: Raises a ValueError. There is no label to return, so pandas raises ValueError: attempt to get argmax of an empty sequence.

Which argument makes idxmax scan across columns instead of down rows?

  • axis=1
  • across=True
  • rows=False
  • orient='columns'

Answer: axis=1. axis=1 works row-wise, returning the column NAME holding each row's maximum.

How do you find the best-selling row within every region?

  • df.groupby('region').max()
  • df.loc[df.groupby('region')['sales'].idxmax()]
  • df.groupby('region').idxmax()
  • df.sort_values('sales')

Answer: df.loc[df.groupby('region')['sales'].idxmax()]. groupby(...).idxmax() gives one label per group; .loc turns those labels back into full rows.

How does argmax differ from idxmax?

  • They are the same method
  • argmax returns a position, idxmax returns a label
  • argmax works on strings only
  • argmax is deprecated

Answer: argmax returns a position, idxmax returns a label. argmax gives the integer POSITION (use with .iloc); idxmax gives the index LABEL (use with .loc).

Why is df.loc[df['x'].idxmax()] better than sorting the whole frame?

  • It is not, sorting is always better
  • It reads more clearly and avoids sorting every row
  • It handles NaN differently
  • Sorting cannot find a maximum

Answer: It reads more clearly and avoids sorting every row. Sorting rearranges every row just to read the top one; idxmax goes straight to it and states your intent.

Continue this course

Frequently asked questions

What does idxmax() return in pandas?

idxmax() returns the index label of the first occurrence of the maximum value — not the maximum value itself. If your DataFrame uses the default integer index it looks like a position, but it is genuinely a label, which is why you pair it with .loc rather than .iloc.

How do I get the entire row with the maximum value?

Pass the label idxmax gives you straight into .loc: df.loc[df['sales'].idxmax()]. That returns the whole row as a Series. To keep it as a one-row DataFrame instead, wrap the label in a list: df.loc[[df['sales'].idxmax()]].

What is the difference between idxmax and argmax?

idxmax returns the index LABEL and is used with .loc. argmax returns the integer POSITION and is used with .iloc. On a default RangeIndex they happen to be the same number, which hides bugs — after filtering or setting a custom index they diverge, and only idxmax still points at the right row.

Why does idxmax raise 'attempt to get argmax of an empty sequence'?

The Series has no values left to compare — usually because a filter matched nothing, or because every value was NaN and skipna removed them all. Guard it with a check such as: if not s.empty and s.notna().any(): s.idxmax().

How do I find the maximum row for each group?

Use groupby with idxmax to get one label per group, then feed those labels to .loc: df.loc[df.groupby('region')['sales'].idxmax()]. This keeps every column of the winning row, which a plain groupby().max() cannot do because it aggregates each column independently.