pandas按组分组,然后在组内排序

313

我希望能够按照两个列对我的数据框进行分组,并在这些组内排序聚合结果。

In [167]: df

Out[167]:
   count     job source
0      2   sales      A
1      4   sales      B
2      6   sales      C
3      3   sales      D
4      7   sales      E
5      5  market      A
6      3  market      B
7      2  market      C
8      4  market      D
9      1  market      E


In [168]: df.groupby(['job','source']).agg({'count':sum})

Out[168]:
               count
job    source       
market A           5
       B           3
       C           2
       D           4
       E           1
sales  A           2
       B           4
       C           6
       D           3
       E           7

现在我想对每个组内的“count”列按降序排序,然后只取前三行。得到类似下面的结果:

                count
job     source
market  A           5
        D           4
        B           3
sales   E           7
        C           6
        B           4

在pandas中,这个问题很棘手的原因是当你对多个组进行groupby时,中间(分组器)对象会得到一个包含这些组的多级索引,并且原始索引会被删除。除非你覆盖默认的groupby(... as_index=True) - smci
9个回答

319

你也可以一次性完成,先进行排序,然后使用head取每个组的前3个。

In[34]: df.sort_values(['job','count'],ascending=False).groupby('job').head(3)

Out[35]: 
   count     job source
4      7   sales      E
2      6   sales      C
1      4   sales      B
5      5  market      A
8      4  market      D
6      3  market      B

37
groupby保证原始顺序不变吗? - toto_tico
88
看起来是这样的;根据 groupby 的文档:__groupby 保留每个组内行的顺序__。 - toto_tico
27
toto_tico- 那是正确的,但在解释这个语句时需要小心。在单个组内行的顺序是保留的,但是groupby默认有一个sort=True的语句,这意味着组本身可能已按键进行排序。换句话说,如果我的数据框中的键(在输入时)为3 2 2 1,则分组对象将按1 2 3(排序)的顺序显示3个组。使用sort=False确保组顺序和行顺序被保留。 - brian_ds
6
head(3)给出的结果超过3个? - Nabin
2
我不明白为什么这个得到了大部分的投票,因为它没有处理 'count' 的 sum()。如果添加一行额外的值 ('sales', 'A', 6),就可以看到这个解决方案没有将 ('sales', 'A') 的 2 + 6 相加,而这应该是结果的第一行,总和为8。 - Zvi
显示剩余2条评论

227
你想要做的实际上又是一次分组(基于第一次分组的结果):按组进行排序并取每组的前三个元素。
从第一次分组的结果开始:
In [60]: df_agg = df.groupby(['job','source']).agg({'count':sum})

我们按照索引的第一级进行分组:
In [63]: g = df_agg['count'].groupby('job', group_keys=False)

接下来,我们想对每个分组进行排序,并取前三个元素:

In [64]: res = g.apply(lambda x: x.sort_values(ascending=False).head(3))

不过,有一个快捷函数可以实现这一点,nlargest

In [65]: g.nlargest(3)
Out[65]:
job     source
market  A         5
        D         4
        B         3
sales   E         7
        C         6
        B         4
dtype: int64

因此,一次性完成的情况如下:

df_agg['count'].groupby('job', group_keys=False).nlargest(3)

有没有办法将每个工作的前三个结果之外的所有内容汇总,并将它们添加到名为“其他”的源组中? - JoeDanger
感谢您的出色回答。进一步地,是否有一种方法可以根据groupby列中的值来分配排序顺序?例如,如果值为“Buy”,则按升序排序;如果值为“Sell”,则按降序排序。 - Bowen Liu
@joris as_index 是一个 groupby 参数。我们是否理解一致? - young_souvlaki
当然,但是使用as_index与否并不改变您需要第二个groupby的事实,也不会改变使用nlargest(无需手动排序)是我认为最好的解决方案。 - joris
好的,谢谢你澄清,我明白你的观点了!如果使用as_index=False返回普通数据框,nlargest()函数是否可用? - young_souvlaki
显示剩余2条评论

36

下面是另一个例子,按照排序顺序取前三个,并在组内进行排序:

In [43]: import pandas as pd                                                                                                                                                       

In [44]:  df = pd.DataFrame({"name":["Foo", "Foo", "Baar", "Foo", "Baar", "Foo", "Baar", "Baar"], "count_1":[5,10,12,15,20,25,30,35], "count_2" :[100,150,100,25,250,300,400,500]})

In [45]: df                                                                                                                                                                        
Out[45]: 
   count_1  count_2  name
0        5      100   Foo
1       10      150   Foo
2       12      100  Baar
3       15       25   Foo
4       20      250  Baar
5       25      300   Foo
6       30      400  Baar
7       35      500  Baar


### Top 3 on sorted order:
In [46]: df.groupby(["name"])["count_1"].nlargest(3)                                                                                                                               
Out[46]: 
name   
Baar  7    35
      6    30
      4    20
Foo   5    25
      3    15
      1    10
dtype: int64


### Sorting within groups based on column "count_1":
In [48]: df.groupby(["name"]).apply(lambda x: x.sort_values(["count_1"], ascending = False)).reset_index(drop=True)
Out[48]: 
   count_1  count_2  name
0       35      500  Baar
1       30      400  Baar
2       20      250  Baar
3       12      100  Baar
4       25      300   Foo
5       15       25   Foo
6       10      150   Foo
7        5      100   Foo

31

试试这个替代方案,它是一种简单的方式来进行分组和降序排序:

尝试以下替代方案,用于对数据进行分组并按降序排序:

df.groupby(['companyName'])['overallRating'].sum().sort_values(ascending=False).head(20)

17

如果您不需要对列求和,则可以使用@tvashtar的答案。如果您需要求和,则可以使用@joris的答案或与其非常相似的这个答案。

df.groupby(['job']).apply(lambda x: (x.groupby('source')
                                      .sum()
                                      .sort_values('count', ascending=False))
                                     .head(3))

3

当分组的数据框包含多个分组列(“多级索引”)时,使用其他方法会删除其他列:

edf = pd.DataFrame({"job":["sales", "sales", "sales", "sales", "sales",
                           "market", "market", "market", "market", "market"],
                    "source":["A", "B", "C", "D", "E", "A", "B", "C", "D", "E"],
                    "count":[2, 4,6,3,7,5,3,2,4,1],
                    "other_col":[1,2,3,4,56,6,3,4,6,11]})

gdf = edf.groupby(["job", "source"]).agg({"count":sum, "other_col":np.mean})
gdf.groupby(level=0, group_keys=False).apply(lambda g:g.sort_values("count", ascending=False))

这会保留count列中每个分组内的other_col 并按顺序排列。

有没有一种方法可以不获取计数列的总和而保留计数列? - dspractician

1

我在不使用"by"的情况下遇到了这个错误:

TypeError: sort_values()缺少1个必需的位置参数:'by'

所以,我将其更改为以下内容,现在它可以正常工作:

df.groupby(['job','source']).agg({'count':sum}).sort_values(by='count',ascending=False).head(20)


1

@joris的回答帮了我很多。这就是对我有用的。

df.groupby(['job'])['count'].nlargest(3)

0

你可以用一行代码实现 -

df.groupby(['job']).apply(lambda x: x.sort_values(['count'], ascending=False).head(3)
.drop('job', axis=1))

apply() 的作用是将 groupby 中的每个组分配给 lambda 函数中的 x。

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