Reshape long data into wide — with no aggregation, and no forgiveness.
Have you ever called pivot and been told ValueError: Index contains duplicate entries, cannot reshape? Most people read that as a complaint about dirty data and go looking for a bug. It is not. It is pivot telling you, correctly, that you asked it a question it is not allowed to answer.
pivot is a pure rearrangement. Each value in the frame moves to the cell named by its index label and its column label, and nothing is combined along the way. That works only if every index/column pair occurs at most once. When a pair repeats, two values want the same cell, and pivot has no rule for choosing between them — so it refuses. Its sibling pivot_table has such a rule, aggfunc, which is the entire difference between them.
Official documentation: DataFrame.pivot.
The arguments that earn their keep
df.pivot(index='date', # one row per unique value here
columns='country', # one column per unique value here
values='amount') # the values to place in the cells
Only columns is required. Omit index and Pandas uses the frame's current index; omit values and every remaining column is pivoted, giving you a two-level column MultiIndex. index and columns both accept a list, which turns out to be the fix for the error above more often than people expect.
All three are keyword-only in Pandas 3.
A worked example, on real data
The OECD consumer price index behind Bamboo Weekly #175: one row per country, per month, per expenditure category.
import pandas as pd
url = 'https://www.bambooweekly.com/content/files/2026/06/bw-175-oecd.csv'
df = pd.read_csv(url, usecols=['Reference area', 'Expenditure',
'TIME_PERIOD', 'OBS_VALUE'])
df.pivot(index='TIME_PERIOD', columns='Reference area', values='OBS_VALUE')
ValueError: Index contains duplicate entries, cannot reshape
That is the whole lesson, so let us diagnose it rather than route around it. The error names no column and shows no row, which makes it feel opaque. One line finds the culprit:
df.value_counts(['TIME_PERIOD', 'Reference area']).head(3)
TIME_PERIOD Reference area
2006-06 Türkiye 7
2006-07 Türkiye 7
2006-08 Türkiye 7
Name: count, dtype: int64
Seven rows per country per month, because the file breaks inflation out by expenditure category — nine of them across the file, seven for Türkiye — and I asked for a table with no room to put them. Now the fix is obvious, and there are two honest ones.
Filter to the category you meant:
(
df
.loc[df['Expenditure'] == 'Total']
.pivot(index='TIME_PERIOD', columns='Reference area', values='OBS_VALUE')
.iloc[-3:, :5]
.round(2)
)
Reference area Austria Belgium Bulgaria Canada Chile
TIME_PERIOD
2026-03 3.2 1.65 4.07 2.39 2.83
2026-04 3.4 4.00 6.80 2.82 3.96
2026-05 NaN 4.08 6.85 NaN 3.93
240 months by 34 countries, every cell a single reading. Or keep the category as a dimension, by adding it to index:
df.pivot(index=['TIME_PERIOD', 'Expenditure'],
columns='Reference area', values='OBS_VALUE')
Which works, because a date and a category together are unique. That is the one to reach for first: a duplicate-pair error usually means a column you left out is doing real work, not that anything is wrong with the file.
The third option is pivot_table with aggfunc='mean'. It will run, and it will silently average nine unrelated inflation measures into one number. When duplicates are real repeated measurements, that is exactly right. When they are a dimension you forgot, it is a wrong answer that never complains.
Three mistakes people make
Passing the arguments positionally. Every tutorial written before Pandas 2.0 shows df.pivot('date', 'country', 'amount'). Today that gives TypeError: DataFrame.pivot() takes 1 positional argument but 4 were given. Name all three.
Reading the duplicate error as a data-quality problem. It is a specification problem. The file is fine; the request was ambiguous. Run value_counts on the two key columns before you start cleaning anything.
Reaching for pivot when the job is counting. If your cells should hold "how many," you need an aggregation, so you need pivot_table or crosstab. pivot can only move values that already exist.
Where it shows up in Bamboo Weekly
Here is the honest answer, and it is more useful than a list: no Pandas solution on the site calls pivot. Real-world data almost always has duplicate pairs, so pivot_table wins nearly every time.
The two solutions where a bare pivot does appear are written in Polars, where the same name means something different — Bamboo Weekly #80: Inflation, which is free to read, and Bamboo Weekly #156: Winter Olympics. Polars' pivot takes an aggregate_function, so it is the equivalent of Pandas' pivot_table, not of this method. Worth knowing before you translate code between the two.
For the Pandas side of the comparison, Bamboo Weekly #91: Roller coasters pivots type against design with aggfunc='count' — 74 coasters land in one cell, so pivot could not have done it. Whereas the pivot_table call in Bamboo Weekly #175: Inflation runs after a filter that leaves every date-and-country pair unique, and pivot would have returned exactly the same table.
Practice it
Work through a pivot exercise, with instant feedback and no signup required: practice.lernerpython.com/bamboo-weekly/pivot/
Go deeper
pivot_table is where you will end up most of the time, and its page covers the aggfunc default that catches everyone. melt is this method run backwards, and unstack does the same reshape starting from an index level rather than a column.
More Pandas videos on Python and Pandas with Reuven Lerner.
Bamboo Weekly is the practice. If you want the structured version — full courses with downloadable Jupyter notebooks, plus live Pandas office hours when you get stuck — that is LernerPython+Data. A paid Bamboo Weekly subscription is included with it.
Related methods
.pivot_table()— when the same index and column pair appears twice and needs aggregating.melt()— for the opposite direction, wide back to long
Part of the Pandas Methods Index. See also practice by skill.