Pandas按两个变量从长格式转换为宽格式

115 投票
6 回答
209308 浏览
提问于 2025-04-18 00:56

我有一些数据是长格式的,现在想把它变成宽格式,但用 melt/stack/unstack 这些方法似乎没有简单的办法来做到这一点:

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

变成:

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

我觉得 Stata 这个软件可以用 reshape 命令来做到类似的事情。

6 个回答

13
pivoted = df.pivot('salesman', 'product', 'price')

第192页 《Python数据分析》

25

这虽然有点老旧,但我还是想分享给其他人。

你想要的东西是可以实现的,不过你可能不应该太想要它;)Pandas支持对行和列使用层次索引。

在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')

这样做可能会产生比你想要的更方便的表示方式。

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

使用set_index和unstack的好处在于,它比单一的pivot函数更容易理解,因为你可以把操作分解成几个简单的小步骤,这样调试起来也更简单。

37

Karl D的解决方案抓住了问题的核心。不过我觉得用.pivot_table来处理所有内容会简单得多,因为有两个索引列。然后再用sort来排序,并给列赋值,这样就可以把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()

输出结果:

  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
64

一个简单的透视表可能就能满足你的需求,但这是我为你复现想要的输出所做的:

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

只需在组内添加一个计数器或索引,你就能接近目标,但列标签可能不会完全符合你的期望:

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

为了更接近你想要的输出,我添加了以下内容:

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

补充:如果你想把这个过程推广到更多变量,我觉得你可以尝试以下方法(虽然可能效率不高):

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说:

我觉得Stata可以用reshape命令做类似的事情。

确实可以,但我认为你还需要一个组内计数器,才能在Stata中使用reshape命令得到你想要的输出:

     +-------------------------------------------+
     | 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 |
     +-------------------------------------------+

如果你添加了 idx,那么你就可以在 stata 中使用reshape了:

reshape wide product price, i(salesman) j(idx)
98

这里有一个更详细的解决方案,来自于Chris Albon的网站

创建“长”格式的数据表

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'])
   patient  obs  treatment  score
0        1    1          0   6252
1        1    2          1  24243
2        1    3          0   2345
3        2    1          1   2342
4        2    2          0  23525

制作“宽”格式的数据

df.pivot(index='patient', columns='obs', values='score')
obs           1        2       3
patient
1        6252.0  24243.0  2345.0
2        2342.0  23525.0     NaN

撰写回答