The Index: set_index, reset_index & reindex
Reviewed & published by Brayan K
The index is a pandas DataFrame's row-label axis — the column of labels on the left that pandas uses to align, look up, and join rows quickly.
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.
Learn to promote a column to the index with set_index, push it back with reset_index, conform to new labels with reindex, and why a meaningful index makes .loc lookups dramatically faster.
What You'll Learn in This Lesson
- What the Index object is and why it matters
- Promote a column with df.set_index("id")
- Flatten it back with df.reset_index()
- Conform to new labels with df.reindex([...])
- Fast label lookups with .loc on an indexed frame
- drop=False and inplace pitfalls to avoid
1 Promoting a Column — df.set_index("id")
When you build a DataFrame, pandas hands you a default RangeIndex: the plain integers 0, 1, 2, 3... down the left side. That auto-number rarely means anything. If one of your columns is a natural identifier — a product code, an employee ID, a date — you can promote it to become the index with df.set_index("id").
Once a column is the index it stops being a regular column (it moves to the label axis), and you can address rows by that label using .loc. This is the difference between "row number 2" and "the row for product P-100."
import pandas as pd
df = pd.DataFrame({
"id": ["P-100", "P-101", "P-102"],
"name": ["Pen", "Notebook", "Eraser"],
"price": [1.50, 3.00, 0.75],
})
df = df.set_index("id") # "id" becomes the row-label axis
df.loc["P-101"] # look up a row by its labelimport pandas as pd
df = pd.DataFrame({
"id": ["P-100", "P-101", "P-102"],
"name": ["Pen", "Notebook", "Eraser"],
"price": [1.50, 3.00, 0.75],
})
df = df.set_index("id")
print(df)
# Now look a row up by its label:
print(df.loc["P-101"])
# name Notebook
# price 3.0
# Name: P-101, dtype: object
# ✅ Expected output:
# name price
# id
# P-100 Pen 1.50
# P-101 Notebook 3.00
# P-102 Eraser 0.75
# name Notebook
# price 3.0
# Name: P-101, dtype: object2 Flattening It Back — df.reset_index()
reset_index() is the inverse of set_index. It pops the current index back out into an ordinary column and gives you a fresh default RangeIndex (0, 1, 2...). You will reach for this constantly: after a groupby, after filtering rows away (which leaves gaps in the integer index), or whenever you want the index labels to live in a real column you can save to CSV.
df = df.reset_index() # "id" goes back to being a column
df = df.reset_index(drop=True) # throw the old index away entirelyimport pandas as pd
df = pd.DataFrame({
"id": ["P-100", "P-101", "P-102", "P-103"],
"name": ["Pen", "Notebook", "Eraser", "Marker"],
"keep": [True, False, True, True],
}).set_index("id")
# Filtering leaves a gappy index... reset_index cleans it up.
kept = df[df["keep"]]
print(kept.index.tolist())
# ['P-100', 'P-102', 'P-103']
flat = kept.reset_index()
print(flat)
# ✅ Expected output:
# ['P-100', 'P-102', 'P-103']
# id name keep
# 0 P-100 Pen True
# 1 P-102 Eraser True
# 2 P-103 Marker True3 Conforming to New Labels — df.reindex([...])
reindex() is different from both. You hand it the exact list of labels you want the result to have. Pandas keeps and reorders the rows whose labels you asked for, drops any you left out, and creates NaN-filled rows for labels that do not yet exist. It is the tool for forcing data onto a known shape — for example, making sure every day of the week appears even if some had no sales.
# Force the rows into this exact order; "Wed" is missing -> NaN
sales.reindex(["Mon", "Tue", "Wed", "Thu"])
# Fill missing labels with 0 instead of NaN
sales.reindex(["Mon", "Tue", "Wed", "Thu"], fill_value=0)import pandas as pd
sales = pd.Series([10, 25, 40], index=["Mon", "Tue", "Thu"])
# Ask for all four weekdays; "Wed" did not exist -> NaN
print(sales.reindex(["Mon", "Tue", "Wed", "Thu"]))
# Fill the gap with 0 instead:
print(sales.reindex(["Mon", "Tue", "Wed", "Thu"], fill_value=0))
# Mon 10
# Tue 25
# Wed 0
# Thu 40
# dtype: int64
# ✅ Expected output:
# Mon 10.0
# Tue 25.0
# Wed NaN
# Thu 40.0
# dtype: float64
# Mon 10
# Tue 25
# Wed 0
# Thu 40
# dtype: int64import pandas as pd
df = pd.DataFrame({
"sku": ["A1", "A2", "A3"],
"stock": [12, 0, 7],
})
# 🎯 YOUR TURN:
# Make the "sku" column the index so you can do df.loc["A2"].
# Fill in the blank with the right method name.
df = df.___("sku")
print(df.loc["A2"])
# Expected output:
# stock 0
# Name: A2, dtype: int64! Common Errors (And How to Fix Them)
❌ The index change "did not work"
You called set_index but the original frame looks unchanged:
df.set_index("id") # ❌ result thrown away, df unchanged✅ Fix: assign the result back:
df = df.set_index("id") # ✅❌ KeyError when using .loc on a default index
You tried df.loc["P-101"] but never set an index, so the labels are still 0, 1, 2:
df.loc["P-101"] # ❌ KeyError: 'P-101'✅ Fix: set the index first (or use a boolean mask):
df = df.set_index("id")
df.loc["P-101"] # ✅🎯 Mini Challenge: Round-trip the Index
Take a small catalogue through the full lifecycle of the index.
- Promote code to the index
- Look up the row for "B" with .loc
- Reindex onto ["A", "B", "C", "D"] so the missing D appears
- Flatten it all back with reset_index
import pandas as pd
cat = pd.DataFrame({
"code": ["A", "B", "C"],
"title": ["Alpha", "Beta", "Gamma"],
})
# 1) promote "code" to the index
cat = cat.set_index("code")
print(cat.loc["B"]) # 2) look up row B
# title Beta
# Name: B, dtype: object
# 3) reindex to include a missing "D"
wide = cat.reindex(["A", "B", "C", "D"])
print(wide)
# title
# code
# A Alpha
# B Beta
# C Gamma
# D NaN
# 4) flatten back to columns
print(wide.reset_index())
# code title
# 0 A Alpha
# 1 B Beta
# 2 C Gamma
# 3 D NaN
# ✅ Expected output:
# title Beta
# Name: B, dtype: object
# title
# code
# A Alpha
# B Beta
# C Gamma
# D NaN
# code title
# 0 A Alpha
# 1 B Beta
# 2 C Gamma
# 3 D NaN❓ Frequently Asked Questions
Lesson complete — you own the index!
You can promote a column with set_index, flatten it with reset_index, force data onto target labels with reindex, and you understand why a meaningful, unique index makes .loc lookups fast.
Practice quiz
What is the index of a DataFrame?
- A hidden cache
- The first data column
- The row-label axis on the left that pandas uses to align rows
- The list of column names
Answer: The row-label axis on the left that pandas uses to align rows. The index is the row-label axis used for alignment, lookups, and joins.
What does df.set_index('id') do?
- Sorts by id
- Deletes the id column
- Counts unique ids
- Promotes the id column to become the row-label index
Answer: Promotes the id column to become the row-label index. set_index promotes a column to the index so you can look rows up by that label.
What default index does a new DataFrame get?
- A RangeIndex of 0, 1, 2, ...
- The first column
- An empty index
- Random IDs
Answer: A RangeIndex of 0, 1, 2, .... Pandas gives a default RangeIndex (0, 1, 2, ...).
What does reset_index() do?
- Sorts the index
- Deletes all rows
- Pops the index back into a column and restores a default RangeIndex
- Renames the index
Answer: Pops the index back into a column and restores a default RangeIndex. reset_index moves the index labels back into an ordinary column with a fresh RangeIndex.
After df = df.set_index('id'), how do you look up the 'P-101' row?
- df['P-101']
- df.loc['P-101']
- df.iloc['P-101']
- df.get('P-101')
Answer: df.loc['P-101']. With a label index, df.loc['P-101'] selects that row by label.
What does reset_index(drop=True) do with the old index?
- Keeps it as a new column
- Throws it away entirely
- Moves it to the end
- Sorts it
Answer: Throws it away entirely. drop=True discards the old index rather than keeping it as a column.
What does reindex(['Mon','Tue','Wed','Thu']) do for a label that does not exist?
- Raises a KeyError
- Drops the request
- Fills it with 0
- Creates a row filled with NaN
Answer: Creates a row filled with NaN. Missing labels get NaN-filled rows (unless you pass fill_value).
How do you make reindex fill missing labels with 0 instead of NaN?
- Pass fill_value=0
- Pass na=0
- Call fillna first
- It is impossible
Answer: Pass fill_value=0. reindex(..., fill_value=0) substitutes 0 for missing labels.
Why is a meaningful, unique index faster for lookups?
- It compresses the data
- It runs on the GPU
- It can use a hash or binary search instead of scanning every row
- It caches the whole table
Answer: It can use a hash or binary search instead of scanning every row. A sorted, unique index allows hash or binary-search lookups instead of a full scan.
Why might set_index appear to 'not work'?
- It only works on numbers
- The result was not assigned back to df
- It needs inplace=True always
- The column must be sorted first
Answer: The result was not assigned back to df. set_index returns a new DataFrame; reassign it (df = df.set_index('id')).
Continue this course
- Previous: Performance: vectorization & memory
- Next: Data Types & astype — Inspect dtypes and convert columns with astype and to_numeric for speed and correctness
- Quick reference: Pandas cheat sheet
Frequently asked questions
What is the difference between a column and the index?
A column is regular data you select with df['col']. The index is the row-label axis sitting to the left of your data — pandas uses it to align rows during merges, joins, and arithmetic, and to power fast .loc lookups. set_index() promotes a column to become that label axis; reset_index() pushes it back to being an ordinary column.
Does set_index modify the DataFrame in place?
No. By default set_index(), reset_index(), and reindex() all return a NEW DataFrame and leave the original unchanged. Assign the result back (df = df.set_index('id')) or pass inplace=True. Most experienced users avoid inplace=True and prefer reassignment because it reads more clearly and chains nicely.
Why is a meaningful index faster for lookups?
When the index is unique and sorted, pandas can find a label with a hash or binary search instead of scanning every row with a boolean mask. df.loc['P-100'] on an indexed DataFrame is far cheaper than df[df['id'] == 'P-100'] on a large table, because the latter must compare every single row.
What does reindex do that set_index does not?
reindex() conforms the data to a brand-new set of labels you supply. Labels that exist are kept and reordered to match; labels you ask for that are missing get filled with NaN (or a fill_value); existing labels you omit are dropped. It is for reshaping to a target index, not for promoting a column.