What do stack and unstack do in pandas?
Short answer
stack moves column labels down into the index, making the frame longer and narrower, and unstack does the reverse. They reshape around the index, unlike melt and pivot which work on columns.
The short version
stack pushes the column labels down into the index, turning a wide frame into a long one. unstack does the opposite, lifting an index level up into columns. They are the index-based counterparts of melt and pivot, and they are the natural tools once your data already has a meaningful index or a MultiIndex.
stack: wide to long
import pandas as pd
wide = pd.DataFrame(
{"jan": [1, 3], "feb": [2, 4]},
index=["Leeds", "Bath"],
)
long = wide.stack()
print(long)
Leeds jan 1
feb 2
Bath jan 3
feb 4
dtype: int64
The month names, which were columns, are now the inner level of the index, and the result is a single column of values. The frame went from two columns to one, and from two rows to four.
unstack: long back to wide
unstack reverses it, turning the innermost index level back into columns.
long.unstack() # returns the original wide frame
By default unstack lifts the innermost index level. Pass a level name or number to choose a different one, which is how you pivot a specific level of a MultiIndex up into columns.
How this differs from melt and pivot
melt and pivot operate on columns you name explicitly, so they suit a plain frame with a default index. stack and unstack operate on the index, so they suit data that already carries meaning in its index, especially a MultiIndex produced by groupby. The results can be equivalent; choose based on whether your reshape is about columns (melt/pivot) or about index levels (stack/unstack).
The missing-data behaviour
By default stack drops combinations that are missing, so a wide frame with gaps produces a shorter long frame. If you need every combination represented, pass dropna=False. This matters when the absence of a row is itself information you want to keep.
A common use after groupby
A frequent pattern is grouping by two keys, then unstacking one of them to get a readable grid.
df.groupby(["city", "month"])["amount"].sum().unstack("month")
This produces a table with cities down the side and months across the top, which is often exactly the shape you want for a summary, all without leaving the index-based tools.