Skip to content
advanced Phase 13 · pandas Aggregation and Reshaping

Melt and Pivot Reshaping

Switch between wide and tidy layouts using melt, pivot_table, and stack operations.

50m
0 problems
Topic Progress 0%

Wide vs Long Format

Wide vs Long Format

Data comes in two fundamental shapes, and knowing which one you need is half the battle. Wide format stores each variable in its own column. Long format (also called tidy or stacked format) stores variable names in one column and values in another.

Consider monthly sales data. In wide format, each month is a column:

import pandas as pd

wide = pd.DataFrame({
    'product': ['A', 'B', 'C'],
    'jan': [100, 150, 200],
    'feb': [120, 160, 210],
    'mar': [110, 170, 220]
})
print(wide)
#   product  jan  feb  mar
# 0       A  100  120  110
# 1       B  150  160  170
# 2       C  200  210  220

In long format, month names and values are stacked into two columns:

long = pd.DataFrame({
    'product': ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C'],
    'month': ['jan', 'feb', 'mar', 'jan', 'feb', 'mar', 'jan', 'feb', 'mar'],
    'sales': [100, 120, 110, 150, 160, 170, 200, 210, 220]
})
print(long)
#   product month  sales
# 0       A   jan    100
# 1       A   feb    120
# 2       A   mar    110
# 3       B   jan    150
# 4       B   feb    160
# 5       B   mar    170
# 6       C   jan    200
# 7       C   feb    210
# 8       C   mar    220

Neither format is universally better. Wide format is compact and human-readable — it is what you would put in a report or a slide. Long format is structured for computation — it is what groupby, merge, and most plotting libraries expect.

The principle that guides the choice: long format is the default for analysis. When each row is an observation and each column is a variable, operations like filtering, grouping, and joining become straightforward. Wide format is a presentation concern — convert to it only when you need to display results.

This distinction matters because most real-world data cleaning is a series of conversions between these two shapes. Data arrives in wide format from Excel exports, database dumps, and API responses. You melt it to long format for analysis. You pivot it back to wide format for reporting. Fluency in both directions is a core data manipulation skill.

Melting: Wide to Long

Melting: Wide to Long

pd.melt() converts wide-format data to long-format by unpivoting columns into rows. The function takes identifier columns (which stay as columns) and value columns (which get stacked into a variable-value pair).

import pandas as pd

wide = pd.DataFrame({
    'product': ['A', 'B', 'C'],
    'jan': [100, 150, 200],
    'feb': [120, 160, 210],
    'mar': [110, 170, 220]
})

long = pd.melt(
    wide,
    id_vars=['product'],
    value_vars=['jan', 'feb', 'mar'],
    var_name='month',
    value_name='sales'
)
print(long)
#   product month  sales
# 0       A   jan    100
# 1       B   jan    150
# 2       C   jan    200
# 3       A   feb    120
# 4       B   feb    160
# 5       C   feb    210
# 6       A   mar    110
# 7       B   mar    170
# 8       C   mar    220

The id_vars parameter lists columns that identify each observation and should remain as columns. The value_vars parameter lists columns to unpivot. If you omit value_vars, all columns not in id_vars are melted. The var_name and value_name parameters let you name the resulting variable and value columns instead of using the defaults variable and value.

A practical example: survey data where each respondent's answers to multiple questions are stored in separate columns.

survey = pd.DataFrame({
    'respondent_id': [1, 2, 3],
    'age': [25, 34, 45],
    'q1_satisfaction': [4, 5, 3],
    'q2_recommend': [3, 4, 2],
    'q3_return': [5, 4, 3]
})

long_survey = pd.melt(
    survey,
    id_vars=['respondent_id', 'age'],
    value_vars=['q1_satisfaction', 'q2_recommend', 'q3_return'],
    var_name='question',
    value_name='score'
)
print(long_survey)

After melting, you can group by question to compute average scores per question, or group by age to see how responses vary by demographic. These operations are awkward or impossible in the wide format.

One thing to watch: melting increases row count. A DataFrame with 1000 rows and 12 month columns becomes 12,000 rows after melting. This is expected, but be aware of it when working with large datasets. The long format trades row count for structural clarity.

Pivoting: Long to Wide

Pivoting: Long to Wide

pivot() is the inverse of melt(). It converts long-format data to wide-format by spreading the values of one column into multiple columns. The key difference from melt() is that pivot() requires a unique combination of index and column values — it cannot handle duplicates.

import pandas as pd

long = pd.DataFrame({
    'product': ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C'],
    'month': ['jan', 'feb', 'mar', 'jan', 'feb', 'mar', 'jan', 'feb', 'mar'],
    'sales': [100, 120, 110, 150, 160, 170, 200, 210, 220]
})

wide = long.pivot(index='product', columns='month', values='sales')
print(wide)
# month  jan  feb  mar
# product
# A      100  120  110
# B      150  160  170
# C      200  210  220

The index parameter specifies which column becomes the row index. The columns parameter specifies whose values become column headers. The values parameter specifies which column provides the cell values.

The result has a MultiIndex on the columns if you pass a list to columns. Call .reset_index() to flatten the index back into a column.

wide = long.pivot(index='product', columns='month', values='sales').reset_index()
print(wide)
# month product  jan  feb  mar
# 0           A  100  120  110
# 1           B  150  160  170
# 2           C  200  210  220

pivot() raises a ValueError if there are duplicate entries for the same (index, columns) combination. This is a feature, not a bug — it prevents silent data loss. If you have duplicates, you need pivot_table() instead.

# This raises ValueError if (product, month) is not unique
# ValueError: Index contains duplicate entries, cannot reshape

When to use pivot(): when you have a clean long-format dataset with unique keys and you want to reshape it for display or for feeding into an algorithm that expects wide input. When to use pivot_table(): when you need to aggregate duplicates during the reshape.

Pivot Table: Aggregation During Reshape

Pivot Table: Aggregation During Reshape

pivot_table() is pivot() with a built-in aggregation step. It handles duplicate (index, column) combinations by applying an aggregation function, defaulting to the mean.

import pandas as pd

# Long-format sales data with duplicate (product, month) entries
long = pd.DataFrame({
    'product': ['A', 'A', 'A', 'A', 'B', 'B', 'B', 'B'],
    'month': ['jan', 'jan', 'feb', 'feb', 'jan', 'jan', 'feb', 'feb'],
    'store': ['X', 'Y', 'X', 'Y', 'X', 'Y', 'X', 'Y'],
    'sales': [100, 120, 110, 130, 150, 160, 170, 180]
})

# pivot_table aggregates duplicates (mean by default)
wide = long.pivot_table(
    index='product',
    columns='month',
    values='sales',
    aggfunc='mean'
)
print(wide)
# month   jan  feb
# product
# A       110  120
# B       155  175

The aggfunc parameter accepts any aggregation function: 'sum', 'mean', 'count', 'min', 'max', or a custom function. You can also pass a list of functions to compute multiple aggregations at once.

wide = long.pivot_table(
    index='product',
    columns='month',
    values='sales',
    aggfunc=['mean', 'sum', 'count']
)
print(wide)

pivot_table() also supports multiple index and column levels, creating a MultiIndex on both axes. This is useful for cross-tabulations.

# Cross-tabulation: product vs month, broken down by store
wide = long.pivot_table(
    index=['product', 'store'],
    columns='month',
    values='sales',
    aggfunc='sum'
)
print(wide)

The margins parameter adds row and column totals, which is useful for summary reports.

wide = long.pivot_table(
    index='product',
    columns='month',
    values='sales',
    aggfunc='sum',
    margins=True,
    margins_name='Total'
)
print(wide)
# month   jan  feb  Total
# product
# A       220  240    460
# B       310  350    660
# Total   530  590   1120

Choosing between pivot() and pivot_table() is simple: if your data has unique keys and you want a pure reshape, use pivot(). If you need to aggregate during the reshape, use pivot_table(). Using pivot_table() when you expect unique keys is also fine — it will just apply the aggregation function to single values, which is a no-op.

Practice Problems

0 / 3 solved
Melt Survey Data

You have a wide-format survey DataFrame with columns [respondent_id, age, q1_satisfaction, q2_recommend, q3_return]. Melt it to long format with columns [respondent_id, age, question, score].

Solution
long_survey = pd.melt(
    survey,
    id_vars=['respondent_id', 'age'],
    value_vars=['q1_satisfaction', 'q2_recommend', 'q3_return'],
    var_name='question',
    value_name='score'
)
print(long_survey.head())
Pivot Sales Data

Given a long-format DataFrame with columns [product, month, sales], pivot it to wide format where products are rows, months are columns, and sales are values. Then reset the index so product becomes a column.

Solution
wide = long.pivot(index='product', columns='month', values='sales').reset_index()
print(wide)
Pivot Table with Aggregation

You have a long-format DataFrame with columns [product, month, store, sales] where the same (product, month) combination appears multiple times (once per store). Use pivot_table to compute the mean sales per product per month.

Solution
wide = long.pivot_table(
    index='product',
    columns='month',
    values='sales',
    aggfunc='mean',
    margins=True,
    margins_name='Total'
)
print(wide)

Quiz

1. What is the main difference between wide and long data formats?

Question 1 options

2. When should you use pivot_table() instead of pivot()?

Question 2 options

3. What does the id_vars parameter specify in pd.melt()?

Question 3 options

Flashcards

Question

What is the difference between wide and long format?

Answer

Wide format stores each variable in its own column (good for display). Long format stacks variable names and values into separate columns (good for computation). Long format is the default for analysis.

Question

When should you use pivot_table() instead of pivot()?

Answer

Use pivot_table() when your data has duplicate (index, column) combinations that need aggregation. pivot() raises an error on duplicates. pivot_table() applies an aggregation function (mean by default) to handle them.

Question

What does pd.melt() do?

Answer

It converts wide-format data to long-format by unpivoting specified columns into rows. id_vars stay as columns; value_vars get stacked into a variable-value pair.

Question

What does the margins parameter do in pivot_table()?

Answer

It adds row and column totals to the pivot table output. The margins_name parameter controls the label for the totals row/column (default is 'All').

Revision Notes

Key Takeaways

  • 1. Long format (one row per observation) is the default for analysis; wide format is for display
  • 2. pd.melt() converts wide to long by unpivoting columns into variable-value pairs
  • 3. pivot() converts long to wide but requires unique (index, column) combinations
  • 4. pivot_table() handles duplicates by aggregating them during the reshape
  • 5. Choose pivot() for pure reshapes and pivot_table() when aggregation is needed

Interview Tips

  • • Explain why long format is preferred for analysis — it makes groupby, merge, and plotting straightforward
  • • Know when to use pivot_table() over pivot() — the duplicate key handling is a common interview follow-up
  • • Be able to describe a real scenario where you converted between formats and why

Cheat Sheet

Cheat Sheet: Melt and Pivot

Format Decision

  • Long format: one row per observation, one column per variable → use for analysis
  • Wide format: one column per variable value → use for display/reporting

Melt (Wide → Long)

  • pd.melt(df, id_vars=['id'], value_vars=['c1', 'c2'],
    var_name='variable', value_name='value')
  • id_vars: columns that stay as columns
  • value_vars: columns to unpivot (default: all non-id columns)

Pivot (Long → Wide)

  • df.pivot(index='row', columns='col', values='val')
  • Requires unique (index, column) combinations
  • Raises ValueError on duplicates

Pivot Table (Long → Wide + Aggregation)

  • df.pivot_table(index='row', columns='col', values='val',
    aggfunc='mean', margins=True)
  • Handles duplicates via aggregation
  • margins=True adds totals

Key Distinction

  • pivot(): pure reshape, no aggregation, fails on duplicates
  • pivot_table(): reshape + aggregation, handles duplicates

Continue Learning