How do I reorder or rename the levels of a MultiIndex in pandas?
Short answer
Use swaplevel to change the level order and rename_axis to rename them, then sort the index again. Forgetting to re-sort after swapping is what makes later selections slow or raise.
The short version
swaplevel() exchanges two index levels, reorder_levels() sets an arbitrary order, and rename_axis() renames them. After any of these, call sort_index() again, because the sort that made the old order efficient does not carry over to the new one.
Swapping levels
import pandas as pd
df = pd.DataFrame({
"city": ["Leeds", "Leeds", "Bath"],
"year": [2020, 2021, 2020],
"value": [1, 2, 3],
}).set_index(["city", "year"]).sort_index()
swapped = df.swaplevel().sort_index()
print(swapped.index.names) # ['year', 'city']
Now year is the outer level, so swapped.loc[2020] selects every city in 2020, which was awkward before.
Why you would swap
Selection is easy on the outer level and fiddly on inner ones. If you constantly reach for xs or IndexSlice to pin an inner level, the index is arranged the wrong way for how you actually use it. Swap it so the level you filter on most sits outermost, and your code simplifies.
Always re-sort
df.swaplevel() # index is now unsorted for the new order
df.swaplevel().sort_index() # correct
A MultiIndex is sorted with respect to a particular level order. Swap the levels and the sortedness is gone, which makes lookups slow and partial slicing raise UnsortedIndexError. The sort_index() is not optional housekeeping; it is what keeps selection working.
More than two levels
reorder_levels takes the full order you want, by name or position.
df.reorder_levels(["year", "city"]).sort_index()
This is clearer than chaining several swaps when there are three or more levels.
Renaming levels
df.rename_axis(index=["town", "period"])
Names matter more in a MultiIndex than in a plain one, because you refer to levels by name in xs, groupby(level=...) and reorder_levels. Unnamed levels force you to use positional numbers, which is fragile and unreadable.
Grouping by a level
Once levels are named, you can group by one directly, without resetting the index.
df.groupby(level="year")["value"].sum()
This is one of the genuine conveniences of a MultiIndex, and it is why naming the levels is worth the small effort.
Or flatten it
If you find yourself doing a lot of level manipulation, ask whether the MultiIndex is earning its place. reset_index() turns the levels back into ordinary columns, after which plain groupby and boolean filtering do the same jobs with much less ceremony. Many experienced pandas users keep MultiIndexes only as a transient result of aggregation, flattening them immediately, and their code is simpler for it.