Reshaping: melt, stack, unstack
Reviewed & published by Brayan K
Reshaping in pandas is the act of changing a table's layout without changing its data — converting between wide format (one column per variable) and long format (one observation per row) using melt, stack, and unstack so the data matches what your next analysis step expects.
Part of the free Pandas course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn in This Lesson
- The difference between wide and long (tidy) data
- Unpivoting columns with pd.melt and id_vars
- Naming the output columns with var_name / value_name
- Moving columns into the index with df.stack
- Moving index levels into columns with df.unstack
- Pivoting long data back to wide
1 Wide → Long with pd.melt
A wide table like this — one column per month — is easy to read but awkward to analyse. pd.melt unpivots the month columns into two tidy columns: one holding the variable name, one holding the value. The columns you keep fixed go in id_vars.
import pandas as pd
# WIDE: one column per month
wide = pd.DataFrame({
"name": ["Alice", "Bob"],
"Jan": [100, 200],
"Feb": [110, 190],
"Mar": [120, 210],
})
print(wide)
print("---")
# Melt the month columns into 'month' / 'sales'
long = pd.melt(
wide,
id_vars="name", # keep these as identifiers
value_vars=["Jan", "Feb", "Mar"],
var_name="month", # name for the former column headers
value_name="sales", # name for the values
)
print(long)
# 6 rows: each name now has one row per month.
# name month sales
# 0 Alice Jan 100
# 1 Bob Jan 200
# ... etc.
# ✅ Expected output:
# name Jan Feb Mar
# 0 Alice 100 110 120
# 1 Bob 200 190 210
# ---
# name month sales
# 0 Alice Jan 100
# 1 Bob Jan 200
# 2 Alice Feb 110
# 3 Bob Feb 190
# 4 Alice Mar 120
# 5 Bob Mar 2102 stack & unstack — Moving Between Index and Columns
df.stack() pushes the column labels down into the row index (wide → long), and df.unstack() pulls an index level up into the columns (long → wide). They are inverses of each other.
import pandas as pd
df = pd.DataFrame(
{"Jan": [100, 200], "Feb": [110, 190]},
index=["Alice", "Bob"],
)
print(df)
print("--- stack() ---")
# stack moves the columns (Jan/Feb) into the row index -> a Series
stacked = df.stack()
print(stacked)
# Alice Jan 100
# Feb 110
# Bob Jan 200
# Feb 190
print("--- unstack() ---")
# unstack reverses it: the inner index level becomes columns again
print(stacked.unstack())
# Back to the original wide layout.
# ✅ Expected output:
# Jan Feb
# Alice 100 110
# Bob 200 190
# --- stack() ---
# Alice Jan 100
# Feb 110
# Bob Jan 200
# Feb 190
# dtype: int64
# --- unstack() ---
# Jan Feb
# Alice 100 110
# Bob 200 1903 Long → Wide: Pivoting Back
To go the other direction — from tidy long data back to a wide layout — use df.pivot. It is the inverse of melt: the variable column becomes the new column headers.
import pandas as pd
long = pd.DataFrame({
"name": ["Alice", "Alice", "Bob", "Bob"],
"month": ["Jan", "Feb", "Jan", "Feb"],
"sales": [100, 110, 200, 190],
})
# pivot spreads 'month' back out into columns -> wide again
wide = long.pivot(index="name", columns="month", values="sales")
print(wide)
# month Feb Jan
# name
# Alice 110 100
# Bob 190 200
print("---")
# Tidy it up: drop the columns-axis name and reset the index
wide = wide.reset_index().rename_axis(columns=None)
print(wide)
# ✅ Expected output:
# month Feb Jan
# name
# Alice 110 100
# Bob 190 200
# ---
# name Feb Jan
# 0 Alice 110 100
# 1 Bob 190 200Use melt / stack when…
You need tidy data for grouping, plotting, or modelling.
Use pivot / unstack when…
You want a compact, human-readable report or matrix.
🎯 Your Turn: Melt the Quarters
Unpivot the quarter columns into tidy rows, keeping company as the identifier. Fill in the blank argument.
import pandas as pd
wide = pd.DataFrame({
"company": ["Acme", "Globex"],
"Q1": [10, 40],
"Q2": [20, 50],
})
# 🎯 YOUR TURN: keep 'company' fixed while melting Q1 and Q2
long = pd.melt(wide, ___="company", var_name="quarter", value_name="profit")
print(long)
# EXPECTED OUTPUT:
# company quarter profit
# 0 Acme Q1 10
# 1 Globex Q1 40
# 2 Acme Q2 20
# 3 Globex Q2 50📋 Quick Reference
| Direction | Tool | Code |
|---|---|---|
| Wide → Long | melt | pd.melt(df, id_vars="id") |
| Wide → Long | stack | df.stack() |
| Long → Wide | pivot | df.pivot(index=, columns=, values=) |
| Long → Wide | unstack | df.unstack() |
🏆 Mini Challenge: Round Trip
Take a wide table, melt it to long, compute the average per subject, then pivot a summary back to wide.
import pandas as pd
wide = pd.DataFrame({
"student": ["A", "B"],
"Math": [80, 70],
"Science": [90, 60],
})
# 1) Wide -> long
long = pd.melt(wide, id_vars="student",
var_name="subject", value_name="score")
print(long)
# 2) Average score per subject (works because data is now tidy)
print(long.groupby("subject")["score"].mean())
# EXPECTED: Math = 75.0, Science = 75.0
# 3) Long -> wide again (reconstruct the original layout)
back = long.pivot(index="student", columns="subject", values="score")
print(back)
# ✅ Expected output:
# student subject score
# 0 A Math 80
# 1 B Math 70
# 2 A Science 90
# 3 B Science 60
# subject
# Math 75.0
# Science 75.0
# Name: score, dtype: float64
# subject Math Science
# student
# A 80 90
# B 70 60❓ Frequently Asked Questions
Lesson complete — you can reshape data at will!
You understand wide vs long, can melt and stack to tidy your data, and pivot or unstack to summarise it back into a readable grid. Reshaping is the glue between messy raw data and clean analysis.
Practice quiz
Which function turns wide columns into long rows?
- pd.melt()
- pd.widen()
- pd.spread()
- pd.cast()
Answer: pd.melt(). pd.melt() unpivots columns into variable/value rows (wide to long).
What are the default column names melt() creates?
- 'key' and 'val'
- 'variable' and 'value'
- 'name' and 'data'
- 'col' and 'num'
Answer: 'variable' and 'value'. melt produces a 'variable' column and a 'value' column by default.
Which method reshapes rows back into columns from a MultiIndex?
- df.melt()
- df.stack()
- df.unstack()
- df.flatten()
Answer: df.unstack(). unstack() pivots an index level out into columns.
What does df.stack() return for a simple DataFrame?
- A dict
- A list of rows
- Another DataFrame
- A Series with a MultiIndex
Answer: A Series with a MultiIndex. stack() compresses columns into the index, yielding a Series.
Which method reshapes using index, columns, and values arguments?
- df.pivot(index=..., columns=..., values=...)
- df.reshape()
- df.spread()
- df.transpose()
Answer: df.pivot(index=..., columns=..., values=...). pivot() builds a new table from index/columns/values.
What happens if pivot() finds duplicate index/column pairs?
- It averages them silently
- It raises a ValueError
- It keeps the last one
- It drops the column
Answer: It raises a ValueError. pivot() cannot handle duplicates and raises ValueError; use pivot_table.
Which function aggregates duplicates while reshaping?
- pd.melt()
- df.stack()
- pd.pivot_table()
- df.explode()
Answer: pd.pivot_table(). pivot_table() applies an aggfunc, handling duplicate keys.
In melt(), what does the id_vars argument do?
- Names the value column
- Sets the aggregation
- Sorts the output
- Lists columns to keep fixed as identifiers
Answer: Lists columns to keep fixed as identifiers. id_vars are the columns kept as-is; the rest are unpivoted.
What is the inverse operation of stack()?
- unstack()
- melt()
- pivot()
- transpose()
Answer: unstack(). unstack() reverses stack(), moving an index level back to columns.
Going from a wide table (one column per month) to one row per month is called what?
- Pivoting wider
- Wide to long (melting)
- Transposing only
- Stacking columns
Answer: Wide to long (melting). Collapsing many columns into rows is the wide-to-long transform done by melt.
Continue this course
- Previous: Pivot Tables & Crosstab
- Next: MultiIndex (Hierarchical Indexing) — Work with multiple index levels for richer, hierarchical data
- Quick reference: Pandas cheat sheet › Merging & Reshaping
Frequently asked questions
What does 'wide' versus 'long' format mean?
In wide format each variable has its own column and a row holds many measurements (e.g. columns Jan, Feb, Mar). In long (tidy) format there is one observation per row, with a column naming the variable and a column holding its value (e.g. columns month and sales). Long format is what most pandas grouping, plotting, and modelling tools expect.
When do I use melt versus stack?
pd.melt works on columns: it unpivots selected value columns into two columns (a variable column and a value column) while keeping id_vars as identifiers. df.stack works on the column index: it moves the innermost column level down into the row index, producing a Series (or a frame with a MultiIndex). melt is the friendly choice for ordinary columns; stack/unstack shine when you already have a MultiIndex.
How do I go from long format back to wide?
Use df.pivot (or pivot_table) to spread a variable column back out into separate columns — it is the inverse of melt. For MultiIndex data, df.unstack moves an index level up into the columns, the inverse of stack.