使用pandas dataframe进行透视和转置

3
假设我有一个如下所示的pandas数据框:
import pandas as pd
df = pd.DataFrame({'fk ID': [1,1,2,2], 
                   'value': [3,3,4,5],
                   'valID': [1,2,1,2]})

上述操作将输出以下内容:
print(df)
   fk ID  value  valID
0      1      3      1
1      1      3      2
2      2      4      1
3      2      5      2

或者

 |fk ID| value | valId |
 |  1  |  3    |   1   |
 |  1  |  3    |   2   |
 |  2  |  4    |   1   |
 |  2  |  5    |   2   |

我希望将其转置并透视,以便获得以下表格和相同的列名顺序:

  fk ID  value  valID  fkID  value   valID
 |  1  |   3  |   1  |  1  |   3   |  2   | 
 |  2  |   4  |   1  |  2  |   5   |  2   |

我需要列的名称与上面显示的顺序相同。 - freak11
3个回答

1
我能想到的最直接的解决方案是:
df = pd.DataFrame({'fk ID': [1,1,2,2], 
                   'value': [3,3,4,5],
                   'valID': [1,2,1,2]})

# concatenate the rows (Series) of each 'fk ID' group side by side 
def flatten_group(g):
    return pd.concat(row for _, row in g.iterrows())

res = df.groupby('fk ID', as_index=False).apply(flatten_group)

然而,使用Series.iterrows并不理想,如果每个分组的大小很大,会非常慢。

此外,如果'fk ID'分组的大小不同,则上述解决方案无法工作。为了验证这一点,我们可以在DataFrame中添加第三个组。

>>> df2 = df.append({'fk ID': 3, 'value':10, 'valID': 4}, 
                    ignore_index=True)
>>> df2

   fk ID  value  valID
0      1      3      1
1      1      3      2
2      2      4      1
3      2      5      2
4      3     10      4

>>> df2.groupby('fk ID', as_index=False).apply(flatten_group)

0  fk ID     1
   value     3
   valID     1
   fk ID     1
   value     3
   valID     2
1  fk ID     2
   value     4
   valID     1
   fk ID     2
   value     5
   valID     2
2  fk ID     3
   value    10
   valID     4
dtype: int64

结果不是像人们预期的那样是一个DataFrame,因为 pandas 无法对齐组的列。


为了解决这个问题,我建议采用以下解决方案。它适用于任何组大小,并且对于大型数据框应该更快。

import numpy as np 

def flatten_group(g):
    # flatten each group data into a single row 
    flat_data = g.to_numpy().reshape(1,-1)
    return pd.DataFrame(flat_data)

# group the rows by 'fk ID'
groups = df.groupby('fk ID', group_keys=False)

# get the maximum group size 
max_group_size = groups.size().max()

# contruct the new columns by repeating the 
# original columns 'max_group_size' times
new_cols = np.tile(df.columns, max_group_size)

# aggregate the flattened rows 
res = groups.apply(flatten_group).reset_index(drop=True) 
# update the columns 
res.columns = new_cols

输出:

# df 
>>> res

   fk ID  value  valID  fk ID  value  valID
0      1      3      1      1      3      2
1      2      4      1      2      5      2

# df2 
>>> res

   fk ID  value  valID  fk ID  value  valID
0      1      3      1    1.0    3.0    2.0
1      2      4      1    2.0    5.0    2.0
2      3     10      4    NaN    NaN    NaN

1
您可以将df转换为numpy数组,重新调整其形状并将其转换回数据框,然后重命名列(0..5)。如果值不是数字而是字符串,则此方法同样适用。
import pandas as pd
df = pd.DataFrame({'fk ID': [1,1,2,2], 
                   'value': [3,3,4,5],
                   'valID': [1,2,1,2]})

nrows = 2
array = df.to_numpy().reshape((nrows, -1))
pd.DataFrame(array).rename(mapper=lambda x: df.columns[x % len(df.columns)], axis=1)

只有当“fk ID”组具有相同的大小时,此方法才有效。 - Rodalm

1
如果你的组大小保证相同,那么你可以将奇数行和偶数行合并:
import pandas as pd
df = pd.DataFrame({'fk ID': [1,1,2,2], 
                   'value': [3,3,4,5],
                   'valID': [1,2,1,2]})
df_even = df[df.index%2==0].reset_index(drop=True)
df_odd = df[df.index%2==1].reset_index(drop=True)
df_odd.join(df_even, rsuffix='_2')

收益率

   fk ID  value  valID  fk ID_2  value_2  valID_2
0      1      3      2        1        3        1
1      2      5      2        2        4        1

我认为这应该表现得相当好,并且可以推广到每个组中的任意数量的行(而不是假设每组有两行奇偶性),但需要确保每个fk ID具有相同数量的行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接