107

I have data in long format and am trying to reshape to wide, but there doesn't seem to be a straightforward way to do this using melt/stack/unstack:

Salesman  Height   product      price
  Knut      6        bat          5
  Knut      6        ball         1
  Knut      6        wand         3
  Steve     5        pen          2

Becomes:

Salesman  Height    product_1  price_1  product_2 price_2 product_3 price_3  
  Knut      6        bat          5       ball      1        wand      3
  Steve     5        pen          2        NA       NA        NA       NA

I think Stata can do something like this with the reshape command.

Luke
  • 6,699
  • 13
  • 50
  • 88
  • 1
    Do you really care that the two pivoted variables be interlaced: `product_1 price_1 product_2 price_2 product_3 price_3`? Can they just be `product_1 product_2 ... price_1 price_2 ...`? – smci May 24 '18 at 11:26
  • Yeah that doesn't matter. – Luke May 24 '18 at 17:25
  • This is a similar long-to-wide question that uses `pivot` and `join`: https://stackoverflow.com/a/65839968/7434285. – Toto Lele Jan 20 '22 at 08:55

6 Answers6

85

Here's another solution more fleshed out, taken from Chris Albon's site.

Create "long" dataframe

raw_data = {'patient': [1, 1, 1, 2, 2],
                'obs': [1, 2, 3, 1, 2],
          'treatment': [0, 1, 0, 1, 0],
              'score': [6252, 24243, 2345, 2342, 23525]}

df = pd.DataFrame(raw_data, columns = ['patient', 'obs', 'treatment', 'score'])

Make a "wide" data

df.pivot(index='patient', columns='obs', values='score')

Charles Clayton
  • 17,005
  • 11
  • 87
  • 120
61

A simple pivot might be sufficient for your needs but this is what I did to reproduce your desired output:

df['idx'] = df.groupby('Salesman').cumcount()

Just adding a within group counter/index will get you most of the way there but the column labels will not be as you desired:

print df.pivot(index='Salesman',columns='idx')[['product','price']]

        product              price        
idx            0     1     2      0   1   2
Salesman                                   
Knut         bat  ball  wand      5   1   3
Steve        pen   NaN   NaN      2 NaN NaN

To get closer to your desired output I added the following:

df['prod_idx'] = 'product_' + df.idx.astype(str)
df['prc_idx'] = 'price_' + df.idx.astype(str)

product = df.pivot(index='Salesman',columns='prod_idx',values='product')
prc = df.pivot(index='Salesman',columns='prc_idx',values='price')

reshape = pd.concat([product,prc],axis=1)
reshape['Height'] = df.set_index('Salesman')['Height'].drop_duplicates()
print reshape

         product_0 product_1 product_2  price_0  price_1  price_2  Height
Salesman                                                                 
Knut           bat      ball      wand        5        1        3       6
Steve          pen       NaN       NaN        2      NaN      NaN       5

Edit: if you want to generalize the procedure to more variables I think you could do something like the following (although it might not be efficient enough):

df['idx'] = df.groupby('Salesman').cumcount()

tmp = []
for var in ['product','price']:
    df['tmp_idx'] = var + '_' + df.idx.astype(str)
    tmp.append(df.pivot(index='Salesman',columns='tmp_idx',values=var))

reshape = pd.concat(tmp,axis=1)

@Luke said:

I think Stata can do something like this with the reshape command.

You can but I think you also need a within group counter to get the reshape in stata to get your desired output:

     +-------------------------------------------+
     | salesman   idx   height   product   price |
     |-------------------------------------------|
  1. |     Knut     0        6       bat       5 |
  2. |     Knut     1        6      ball       1 |
  3. |     Knut     2        6      wand       3 |
  4. |    Steve     0        5       pen       2 |
     +-------------------------------------------+

If you add idx then you could do reshape in stata:

reshape wide product price, i(salesman) j(idx)
Karl D.
  • 13,332
  • 5
  • 56
  • 38
  • 11
    Works well. This would be a nice feature for pandas. There's already wide_to_long, why not the other direction. – Luke Apr 02 '14 at 23:28
  • 1
    Agreed ... that kind of reshape is one of the more useful tools in stata. – Karl D. Apr 02 '14 at 23:42
  • Yeah, that's basically what I ended up doing, although you also have to separate out the columns that don't change, like height, drop duplicates and then concat those later. – Luke Apr 03 '14 at 17:23
37

Karl D's solution gets at the heart of the problem. But I find it's far easier to pivot everything (with .pivot_table because of the two index columns) and then sort and assign the columns to collapse the MultiIndex:

df['idx'] = df.groupby('Salesman').cumcount()+1
df = df.pivot_table(index=['Salesman', 'Height'], columns='idx', 
                    values=['product', 'price'], aggfunc='first')

df = df.sort_index(axis=1, level=1)
df.columns = [f'{x}_{y}' for x,y in df.columns]
df = df.reset_index()

Output:

  Salesman  Height  price_1 product_1  price_2 product_2  price_3 product_3
0     Knut       6      5.0       bat      1.0      ball      3.0      wand
1    Steve       5      2.0       pen      NaN       NaN      NaN       NaN
ALollz
  • 57,915
  • 7
  • 66
  • 89
  • 3
    Thank you so much. Though I already had the idx col in my dataframe, with your solution is was able to bring repeated measures from long to wide format. Pandas has this for [wide_to_long()](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.wide_to_long.html) but doesn't offer it for long_to_wide. Sad. – jlplenio May 08 '20 at 21:29
  • 1
    Hello , could you please help me with https://stackoverflow.com/questions/66964780/reshape-function-in-pyspark-transpose-a-column?noredirect=1#comment118425286_66964780 – Harshit Kakkar Apr 08 '21 at 10:57
  • 1
    Quite relatable from Stata background. – Moses Apr 19 '21 at 08:00
25

A bit old but I will post this for other people.

What you want can be achieved, but you probably shouldn't want it ;) Pandas supports hierarchical indexes for both rows and columns. In Python 2.7.x ...

from StringIO import StringIO

raw = '''Salesman  Height   product      price
  Knut      6        bat          5
  Knut      6        ball         1
  Knut      6        wand         3
  Steve     5        pen          2'''
dff = pd.read_csv(StringIO(raw), sep='\s+')

print dff.set_index(['Salesman', 'Height', 'product']).unstack('product')

Produces a probably more convenient representation than what you were looking for

                price             
product          ball bat pen wand
Salesman Height                   
Knut     6          1   5 NaN    3
Steve    5        NaN NaN   2  NaN

The advantage of using set_index and unstacking vs a single function as pivot is that you can break the operations down into clear small steps, which simplifies debugging.

Gecko
  • 1,379
  • 11
  • 14
  • 3
    Why are you still using Python 2.7? How about in Python 3? – devinbost Jan 25 '18 at 04:16
  • 1
    For python3, you do `from io import StringIO` and use print as a function and all is well. Basic idea of setting the index and unstacking works identically. – Nate Mar 19 '18 at 20:16
  • I find this the only working solution. `pivot` fails because it is unable to carry over columns that are neither index, variable or value and is unable to make use of a multi index. `pivot_table` fails due to its low performance, it can't handle anything larger than a few thousands rows. – deeenes Jan 18 '22 at 18:14
13
pivoted = df.pivot('salesman', 'product', 'price')

pg. 192 Python for Data Analysis

chucklukowski
  • 1,996
  • 2
  • 13
  • 13
  • 10
    When using this method (from the book) I get "ValueError: Index contains duplicate entries, cannot reshape" even after I've used df.drop_duplicates() – d8aninja Oct 25 '14 at 16:09
  • @d8aninja I have the same issue, can you tell me how you fixed this ? – Salih Nov 25 '22 at 05:52
  • 1
    @Salih 8 years later, unfortunately, and I cannot. Certainly with another solution from this post, mixed-in? I'm assuming the issue may have been some form of NaN (etc) in my pivot? – d8aninja Jan 02 '23 at 13:54
  • I have the same issue with all the methods I try to convert from long to wide, it simply does not work. Did any of you manage to fix it? – baskcat Feb 06 '23 at 07:43
5

An old question; this is an addition to the already excellent answers. pivot_wider from pyjanitor may be helpful as an abstraction for reshaping from long to wide (it is a wrapper around pd.pivot):

# pip install pyjanitor
import pandas as pd
import janitor

idx = df.groupby(['Salesman', 'Height']).cumcount().add(1)

(df.assign(idx = idx)
   .pivot_wider(index = ['Salesman', 'Height'], names_from = 'idx')
)
 
  Salesman  Height product_1 product_2 product_3  price_1  price_2  price_3
0     Knut       6       bat      ball      wand      5.0      1.0      3.0
1    Steve       5       pen       NaN       NaN      2.0      NaN      NaN

sammywemmy
  • 27,093
  • 4
  • 17
  • 31