How do I rename index labels in pandas?
Short answer
Use rename with a dictionary to change specific index labels, and rename_axis to change the name of the index itself. The two are easy to confuse and do different things.
The short version
rename() changes the index labels (the values). rename_axis() changes the name of the index (its heading). These sound similar and are constantly confused, but they operate on different things.
Renaming labels
import pandas as pd
s = pd.Series([1], index=["a"])
s.rename({"a": "b"}).index.tolist() # ['b']
The label a became b. Labels not mentioned are left alone, which makes this safe on a large index.
Renaming the index itself
df = df.rename_axis(index="city")
Now the index has the name city, which appears as a heading above it and is the name that reset_index() will give the resulting column. The labels themselves are untouched.
The confusing overlap
On a DataFrame, rename handles both axes through keyword arguments, which is where the confusion sets in.
df.rename(index={"a": "b"}) # changes an index LABEL
df.rename(columns={"x": "y"}) # changes a COLUMN NAME
df.rename_axis(index="city") # changes the INDEX's NAME
A useful way to hold it: rename deals with the values in the axis, rename_axis deals with the axis's own title.
Why the index name matters
It is not merely cosmetic. The name is what reset_index() uses for the new column, and what groupby(level=...) and merge use to refer to the index.
df.rename_axis(index="city").reset_index() # gives a column called 'city'
Without a name, reset_index() produces a column called index, which is meaningless and will confuse anyone reading your output.
Renaming with a function
rename accepts a callable, applied to every label, which is useful for bulk tidying.
df.rename(index=str.lower) # lowercase every label
df.rename(columns=lambda c: c.strip().lower()) # tidy every column name
Unlike a dictionary, a function applies to everything, so use it when the rule is uniform.
Reassign the result
Like most pandas methods, rename returns a new object and leaves the original alone.
df.rename(columns={"a": "b"}) # result discarded!
df = df.rename(columns={"a": "b"}) # correct
Missing labels are ignored silently
A label in your dictionary that does not exist in the index is simply ignored, with no error. That is convenient but means a typo in your rename dictionary fails silently, leaving the column unchanged. If a rename appears not to have worked, check the spelling of the key. To make it raise instead, pass errors="raise".