使用 pandas 数据框旋转和转置

Pivoting and transposing using pandas dataframe

假设我有一个如下所示的 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   |

我想以这样的方式转置和旋转它,以便得到以下 table 和相同的列名顺序:

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

我能想到的最直接的解决方案是

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

您可以将 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)

如果保证您的组大小相同,您可以合并奇数行和偶数行:

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

我希望这会非常高效,并且这可以推广到每组中的任意数量的行(与假设每组两行 odd/even 相比),但是需要你有每个 fk ID 的行数相同。