Pandas按两个变量从长格式转换为宽格式
我有一些数据是长格式的,现在想把它变成宽格式,但用 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