Python Central HubSign in

Data Analytics with Python / Phase 3: Data Manipulation with Pandas

Hierarchical Indexing (MultiIndex)

Work with multiple index levels on one axis using MultiIndex — partial indexing, swaplevel, sort_index by level, stats by level, and set_index/reset_index.

Why stack more than one index level

Sometimes one label isn't enough to describe a row — "city" alone doesn't tell you which year; "year" alone doesn't tell you which city. A MultiIndex lets a single axis carry two or more levels at once, so you can keep working with a Series or DataFrame instead of juggling nested dictionaries.

Python
import pandas as pd
import numpy as np
 
data = pd.Series(
    np.arange(9),
    index=[
        ["a", "a", "a", "b", "b", "c", "c", "d", "d"],
        [1, 2, 3, 1, 3, 1, 2, 2, 3],
    ],
)
print(data)

The blank-looking gaps in the printed output mean "same as the label directly above" — it's just a display convenience, the index itself has no gaps.

Partial indexing

With a hierarchical index, you can select using just the outer level:

Python
print(data["b"])         # every row where the outer level is "b"
print(data.loc["b":"c"]) # a slice across outer-level labels

You can also reach into the inner level directly:

Python
print(data.loc[:, 2])  # every row where the inner level equals 2

Reshaping with unstack

A hierarchically indexed Series can be pivoted into a DataFrame with unstack — the inner level becomes columns:

Python
print(data.unstack())

Missing combinations (like "b" + 2, which never existed) become NaN.

Building a MultiIndex from DataFrame columns: set_index

More often, you'll have plain columns that you want to promote into a hierarchical row index:

Python
frame = pd.DataFrame({
    "city": ["pune", "pune", "delhi", "delhi"],
    "year": [2023, 2024, 2023, 2024],
    "sales": [100, 120, 200, 220],
})
 
frame2 = frame.set_index(["city", "year"])
print(frame2)

reset_index does the reverse — it moves the index levels back into ordinary columns:

Python
print(frame2.reset_index())

Reordering and sorting levels

Python
print(frame2.swaplevel("city", "year"))       # levels interchanged, data untouched
print(frame2.sort_index(level="year"))        # sort using just one level

Aggregating by level

Many summary methods accept a level argument, so you can aggregate across just one part of the index without unstacking first:

Python
print(frame2.groupby(level="city").sum())

Common pitfalls

  • Partial indexing performance is much better when the index is sorted starting from the outermost level — call sort_index() after building a MultiIndex if you'll be slicing it a lot.
  • set_index drops the columns you passed by default; pass drop=False if you still want them available as regular columns too.
  • Selecting an inner level directly (data.loc[:, 2]) uses a different syntax than selecting the outer level (data["b"]) — easy to mix up at first.

Visualize it

diagram From flat columns to a MultiIndex tree mermaid
set_index promotes ordinary columns into hierarchical row-index levels; reset_index moves them back to being plain columns.
sketch A MultiIndex as a nested tree p5.js
Each city branches into its years; sales values live at the leaves — this is what a two-level MultiIndex looks like structurally.

🧪 Try It Yourself

Exercise 1 – Select From the Outer Level

exerciseYour turn

Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.

Output
Press Run to see what your code prints.

Exercise 2 – Build a MultiIndex With set_index

exerciseYour turn

Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.

Output
Press Run to see what your code prints.

Exercise 3 – Aggregate by One Index Level

exerciseYour turn

Tab indents. Press Escape, then Tab, to leave the editor. Control plus Enter runs the code; Control plus Shift plus Enter submits it.

Output
Press Run to see what your code prints.

Check your understanding

quizCheck yourself
  1. For a Series with outer labels a to d and inner labels 1 to 3, what does data.loc[:, 2] select?

    Show answer

    A — Every row whose inner level equals 2 — The first slot of loc covers the outer level and the second reaches the inner level. In the lesson data, that selects the rows (a, 2), (c, 2) and (d, 2).

  2. What does data.unstack() do to a Series with a two-level index?

    Show answer

    A — Moves the inner level into columns, producing a DataFrame — unstack pivots the inner level into columns. Combinations that never existed, like b and 2, become NaN.

  3. What does frame.set_index(["city", "year"]) do by default?

    Show answer

    A — Builds a MultiIndex from the two columns and drops them from the regular columns — set_index promotes columns into a hierarchical row index and drops them unless you pass drop=False. reset_index does the reverse.

  4. How do you sum sales for each city when city and year form the index?

    Show answer

    A — frame2.groupby(level="city").sum() — groupby(level=...) aggregates across just one part of the index without unstacking first.

Next

A MultiIndex is one way to reshape data into a different structure — continue to Reshaping Data (pivot, pivot_table, melt) for more ways to move between wide and long formats.

Was this page helpful?

Let us know how we did