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

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    210

2 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  190

3 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  200

Use 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

DirectionToolCode
Wide → Longmeltpd.melt(df, id_vars="id")
Wide → Longstackdf.stack()
Long → Widepivotdf.pivot(index=, columns=, values=)
Long → Wideunstackdf.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

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.