The other answers are working fine. However, depending on the structure of the multi-index, it can be considerably faster to apply a map directly on the levels instead of constructing a new multi-index.
I use the following function to modify a particular index level. It works also on single-level indices.
def map_index_level(index, mapper, level=0):
"""
Returns a new Index or MultiIndex, with the level values being mapped.
"""
assert(isinstance(index, pd.Index))
if isinstance(index, pd.MultiIndex):
new_level = index.levels[level].map(mapper)
new_index = index.set_levels(new_level, level=level)
else:
# Single level index.
assert(level==0)
new_index = index.map(mapper)
return new_index
Usage:
df = pd.DataFrame([[1,2],[3,4]])
df.index = pd.MultiIndex.from_product([["a"],["i","ii"]])
df.columns = ["x","y"]
df.index = map_index_level(index=df.index, mapper=str.upper, level=1)
df.columns = map_index_level(index=df.columns, mapper={"x":"foo", "y":"bar"})
# Result:
# foo bar
# a I 1 2
# II 3 4
Note: The above works only if mapper
preserves the uniqueness of level values! In the above example, mapper = {"i": "new", "ii": "new"}
will fail in set_index()
with a ValueError: Level values must be unique
. One could disable the integrity check by modifying above code to:
new_index = index.set_levels(new_level, level=level,
verify_integrity=False)
But better don't! See the docs of set_levels
.