Pandas 根据两列分组并展开第三列。

8
我有一个 Pandas 数据框,其结构如下:
A       B       C
a       b       1
a       b       2
a       b       3
c       d       7
c       d       8
c       d       5
c       d       6
c       d       3
e       b       4
e       b       3
e       b       2
e       b       1

我希望你能将它转化为这样:

A       B       C1      C2      C3      C4      C5
a       b       1       2       3       NAN     NAN
c       d       7       8       5       6       3
e       b       4       3       2       1       NAN

换言之,就像按A和B进行分组并将C扩展到不同的列中。 每个分组的长度是不同的。 C已经被排序。 较短的分组可能具有NAN或NULL值(为空),这并不重要。

请勿在问题中提供解决方案。我已经为您删除了它。通常情况下,您应该发布自己的答案,但是由于这个更改非常小,最好在答案上建议进行编辑 - wjandrea
@wjandrea,以后请不要删除最正确的解决方案。这样做浪费了大家的时间,包括你自己的时间。 - mirix
在我看来,这与已接受的解决方案基本相同,因此不需要自己的答案。(而且我认为任何收到警告的人都可以自行解决。)无论如何,由于该网站的格式:问题在上面,解决方案在下面,因此将问题和答案分开非常重要。其他人也说过同样的话,比如这里是模糊类似情况的模式 - wjandrea
现在,它基本上是一样的。但是弃用警告意味着将来不再接受已接受的解决方案。因此,通过删除正确的解决方案,您会让未来的编码人员浪费时间。 - mirix
它的基本原理仍然是相同的,因为只有一个小部分发生了变化。我们在这个网站上也有针对Python 2编写的答案,仍然使用print作为语句而不是函数,你只需要在Python 3中添加括号就可以让它们正常工作。虽然编辑它们以添加括号是完全可以的,这就像我在这里建议的那样。 - wjandrea
3个回答

12

使用 GroupBy.cumcountpandas.Series.add 加 1,从 1 开始命名新列,然后将其传递给 DataFrame.pivot,并添加 DataFrame.add_prefix 以重命名列(C1、C2、C3等...)。最后,使用 DataFrame.rename_axis 删除索引原名称 ("g") 并使用 DataFrame.reset_indexMultiIndex 转换为列 A,B

df['g'] = df.groupby(['A','B']).cumcount().add(1)

df = df.pivot(['A','B'], 'g', 'C').add_prefix('C').rename_axis(columns=None).reset_index()
print (df)
   A  B   C1   C2   C3   C4   C5
0  a  b  1.0  2.0  3.0  NaN  NaN
1  c  d  7.0  8.0  5.0  6.0  3.0
2  e  b  4.0  3.0  2.0  1.0  NaN

由于 NaN 默认为浮点类型,如果需要列的数据类型为整数,则需使用 DataFrame.astypeInt64

df['g'] = df.groupby(['A','B']).cumcount().add(1)

df = (df.pivot(['A','B'], 'g', 'C')
        .add_prefix('C')
        .astype('Int64')
        .rename_axis(columns=None)
        .reset_index())
print (df)
   A  B  C1  C2  C3    C4    C5
0  a  b   1   2   3  <NA>  <NA>
1  c  d   7   8   5     6     3
2  e  b   4   3   2     1  <NA>

编辑:如果最多可以添加N列,则意味着A,B是重复的。因此,需要添加帮助组g1,g2,并使用整数和模除法,在索引中添加新级别:

N = 4
g  = df.groupby(['A','B']).cumcount()
df['g1'], df['g2'] = g // N, (g % N) + 1
df = (df.pivot(['A','B','g1'], 'g2', 'C')
        .add_prefix('C')
        .droplevel(-1)
        .rename_axis(columns=None)
        .reset_index())
print (df)
   A  B   C1   C2   C3   C4
0  a  b  1.0  2.0  3.0  NaN
1  c  d  7.0  8.0  5.0  6.0
2  c  d  3.0  NaN  NaN  NaN
3  e  b  4.0  3.0  2.0  1.0 

1
你能否再解释一下这段代码背后的思路? - seralouk
@jezrael 你需要 .rename_axis(columns=None) 吗?我把它移除了,我认为它可以正常工作。让我知道,因为我想稍微编辑一下答案。它很棒。 - Celius Stingher
@CeliusStingher - 如果删除.rename_axis(columns=None),则第一个解决方案的输出为g,EDIT解决方案的输出为g2 - jezrael
1
好的,但是在 reset_index() 后也可以添加。谢谢。如果你不介意,我会很快编辑答案。 - Celius Stingher
1
它运行了!非常感谢!只是一个小的弃用警告:FutureWarning: 在未来版本的pandas中,DataFrame.pivot的所有参数都将是关键字参数。 - mirix
显示剩余2条评论

0
df1.astype({'C':str}).groupby([*'AB'])\
    .agg(','.join).C.str.split(',',expand=True)\
    .add_prefix('C').reset_index()


 A  B C0 C1 C2    C3    C4
0  a  b  1  2  3  None  None
1  c  d  7  8  5     6     3
2  e  b  4  3  2     1  None

0

已接受的解决方案,但避免弃用警告:

N = 3
g  = df_grouped.groupby(['A','B']).cumcount()
df_grouped['g1'], df_grouped['g2'] = g // N, (g % N) + 1
df_grouped = (df_grouped.pivot(index=['A','B','g1'], columns='g2', values='C')
        .add_prefix('C_')
        .astype('Int64')
        .droplevel(-1)
        .rename_axis(columns=None)
        .reset_index())

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