Have you ever tried to pivot a data frame, only to have Pandas refuse?
readings = pd.DataFrame({
'date': ['2026-03-01', '2026-03-01', '2026-03-01', '2026-03-02', '2026-03-02'],
'station': ['Haifa', 'Eilat', 'Haifa', 'Haifa', 'Eilat'],
'reading': ['morning', 'morning', 'evening', 'morning', 'morning'],
'temp': [14.0, 22.5, 11.0, 15.5, 23.0]})
readings.pivot(index='date', columns='station', values='temp')
ValueError: Index contains duplicate entries, cannot reshape
The short answer is that at least two rows share the same combination of index and columns, so Pandas has two values competing for a single cell. What you should do about that depends on why those rows exist, and that is worth finding out before you pick a fix.
pivot reshapes, it does not summarize
pivot takes long data and rearranges it into a grid. Each value in index becomes a row, each value in columns becomes a column, and each original row supplies exactly one number for exactly one cell.
That "exactly one" is the whole contract. Here, Haifa on March 1st has two readings, 14.0 in the morning and 11.0 in the evening. There is one cell for Haifa on March 1st, and pivot has no way to decide which of the two belongs there. It could average them, take the first, or take the largest, but each of those would be a decision about your data, and pivot is not in the business of making those. So it stops.
First, find the rows that collide
Before choosing a fix, look at the offenders. duplicated with keep=False marks every row involved in a collision, not just the second one:
readings[readings.duplicated(['date', 'station'], keep=False)]
date station reading temp
0 2026-03-01 Haifa morning 14.0
2 2026-03-01 Haifa evening 11.0
Pass the same columns you gave to index and columns. What comes back tells you which of the three situations you are in.
Situation 1: a column you left out is doing real work
These two rows are not duplicates at all. They differ in reading, which is exactly what distinguishes them. Put it in the index, and every cell is unique again:
readings.pivot(index=['date', 'reading'], columns='station', values='temp')
station Eilat Haifa
date reading
2026-03-01 evening NaN 11.0
morning 22.5 14.0
2026-03-02 morning 23.0 15.5
In my experience, this is the most common case, and it is the one to check first. The error is often Pandas pointing out that your mental model of the data was a little too simple.
Situation 2: you actually want one number per cell
Sometimes you really do want one temperature per station per day, and you need to say how to combine them. That is what pivot_table is for. It is pivot plus an aggregation function, which defaults to the mean:
readings.pivot_table(index='date', columns='station', values='temp')
station Eilat Haifa
date
2026-03-01 22.5 12.5
2026-03-02 23.0 15.5
Be careful here, because that default is quiet. Haifa's 12.5 is a temperature nobody recorded. If the question is "how hot did it get," you wanted the maximum:
readings.pivot_table(index='date', columns='station', values='temp', aggfunc='max')
station Eilat Haifa
date
2026-03-01 22.5 14.0
2026-03-02 23.0 15.5
Switching from pivot to pivot_table makes the error go away, but it does so by averaging, whether or not averaging makes sense. Choose aggfunc on purpose.
Situation 3: the rows really are identical
If the colliding rows match in every column, they are true duplicates, typically from a file that was appended twice or a join that multiplied rows. Remove them and pivot:
dupes = pd.concat([readings, readings.iloc[[1]]])
dupes.drop_duplicates().pivot(index=['date', 'reading'], columns='station', values='temp')
station Eilat Haifa
date reading
2026-03-01 evening NaN 11.0
morning 22.5 14.0
2026-03-02 morning 23.0 15.5
But do find out where they came from. Duplicate rows that break a pivot are also inflating every sum and count you computed earlier.
unstack says the same thing
pivot is built on set_index and unstack, so the same data fails the same way through that route:
readings.set_index(['date', 'station'])['temp'].unstack()
ValueError: Index contains duplicate entries, cannot reshape
The fixes carry over directly. Either add the missing level to the index, or aggregate first with groupby, which is exactly what pivot_table does for you:
readings.groupby(['date', 'station'])['temp'].mean().unstack()
station Eilat Haifa
date
2026-03-01 22.5 12.5
2026-03-02 23.0 15.5
A quick way to see how many rows are fighting over each cell, before deciding anything, is to pivot with aggfunc='size':
readings.pivot_table(index='date', columns='station', values='temp', aggfunc='size')
station Eilat Haifa
date
2026-03-01 1 2
2026-03-02 1 1
Anything above 1 is a collision.
Practice
See pivot in Pandas and pivot_table in Pandas for both methods on real data, and try the pivot and pivot_table exercises. For a longer look at choosing between the two, I wrote about pivot vs. pivot_table and duplicate values on LernerPython.