使用groupby获取每组中最大值所在的行。

486

如何查找Pandas DataFrame中在['Sp','Mt']列分组后,具有count列的最大值的所有行?

示例1:以下为DataFrame:

   Sp   Mt Value   count
0  MM1  S1   a     **3**
1  MM1  S1   n       2
2  MM1  S3   cb    **5**
3  MM2  S3   mk    **8**
4  MM2  S4   bg    **10**
5  MM2  S4   dgd     1
6  MM4  S2   rd      2
7  MM4  S2   cb      2
8  MM4  S2   uyi   **7**

预期输出是获取每个分组中行数最大的结果行,就像这样:
   Sp   Mt   Value  count
0  MM1  S1   a      **3**
2  MM1  S3   cb     **5**
3  MM2  S3   mk     **8**
4  MM2  S4   bg     **10** 
8  MM4  S2   uyi    **7**

例子 2:

   Sp   Mt   Value  count
4  MM2  S4   bg     10
5  MM2  S4   dgd    1
6  MM4  S2   rd     2
7  MM4  S2   cb     8
8  MM4  S2   uyi    8

期望输出:

   Sp   Mt   Value  count
4  MM2  S4   bg     10
7  MM4  S2   cb     8
8  MM4  S2   uyi    8

可能会有用 - J_Arthur
1
这个答案是我能找到的最快速的解决方案:https://dev59.com/9Gkw5IYBdhLWcg3wfKrZ#21007047 - tommy.carstensen
15个回答

593

首先,我们可以像这样获得每个组的最大计数:

In [1]: df
Out[1]:
    Sp  Mt Value  count
0  MM1  S1     a      3
1  MM1  S1     n      2
2  MM1  S3    cb      5
3  MM2  S3    mk      8
4  MM2  S4    bg     10
5  MM2  S4   dgd      1
6  MM4  S2    rd      2
7  MM4  S2    cb      2
8  MM4  S2   uyi      7

In [2]: df.groupby(['Sp', 'Mt'])['count'].max()
Out[2]:
Sp   Mt
MM1  S1     3
     S3     5
MM2  S3     8
     S4    10
MM4  S2     7
Name: count, dtype: int64

要获取原始数据框的索引,您可以执行以下操作:

In [3]: idx = df.groupby(['Sp', 'Mt'])['count'].transform(max) == df['count']

In [4]: df[idx]
Out[4]:
    Sp  Mt Value  count
0  MM1  S1     a      3
2  MM1  S3    cb      5
3  MM2  S3    mk      8
4  MM2  S4    bg     10
8  MM4  S2   uyi      7

请注意,如果您在每个组中有多个最大值,则所有最大值都将返回。


更新

在一次孤注一掷的机会中,假设这是OP所请求的内容:

In [5]: df['count_max'] = df.groupby(['Sp', 'Mt'])['count'].transform(max)

In [6]: df
Out[6]:
    Sp  Mt Value  count  count_max
0  MM1  S1     a      3          3
1  MM1  S1     n      2          3
2  MM1  S3    cb      5          5
3  MM2  S3    mk      8          8
4  MM2  S4    bg     10         10
5  MM2  S4   dgd      1         10
6  MM4  S2    rd      2          7
7  MM4  S2    cb      2          7
8  MM4  S2   uyi      7          7

5
我正在使用第二种 idx 方法。但是,每个组只能有一个最大值(而且我的数据有一些重复的最大值)。你的解决方案是否有办法解决这个问题? - 3pitt
1
当数据集足够大时,transform 方法可能会存在池化性能问题,最好先获取最大值,然后合并数据框。 - Woods Chen
正如@3pitt所提到的,这与原问题无关。 - Prakash Vanapalli
@PrakashVanapalli 不是的 - Zelazny7

313

你可以按计数对数据帧进行排序,然后删除重复项。我认为这样更容易:

df.sort_values('count', ascending=False).drop_duplicates(['Sp','Mt'])

10
非常好!速度很快,可以处理大型数据集(25,000行)。 - Nolan Conaway
3
对于那些对Python还比较新的人,您需要将它分配给一个新变量,它不会改变当前的df变量。 - Tyler
4
@Samirوˆ–ن½؟用inplace=Trueن½œن¸؛drop_duplicatesçڑ„هڈ‚و•°و‌¥ه°±هœ°هˆ é™¤é‡چه¤چé،¹م€‚ - TMrtSmith
13
如果只需要具有相同最大值的行中的一个,则此答案非常好。但是,如果我需要具有最大值的所有行,则它将无法按预期工作。 - Woods Chen
3
如果数据框是 pd.DataFrame({'sp':[1, 1, 2], 'mt':[1, 1, 2], 'value':[2, 2, 3]},那么在满足 sp==1 且 mt==2 的组中,将会有两行数据的最大值为 2。 - Woods Chen
显示剩余4条评论

120

使用idxmax()函数可以轻松解决问题,它可以获取最大值所在行的索引。 这将过滤掉所有组中具有最大值的行。

In [367]: df
Out[367]: 
    sp  mt  val  count
0  MM1  S1    a      3
1  MM1  S1    n      2
2  MM1  S3   cb      5
3  MM2  S3   mk      8
4  MM2  S4   bg     10
5  MM2  S4  dgb      1
6  MM4  S2   rd      2
7  MM4  S2   cb      2
8  MM4  S2  uyi      7


# Apply idxmax() and use .loc() on dataframe to filter the rows with max values:
In [368]: df.loc[df.groupby(["sp", "mt"])["count"].idxmax()]
Out[368]: 
    sp  mt  val  count
0  MM1  S1    a      3
2  MM1  S3   cb      5
3  MM2  S3   mk      8
4  MM2  S4   bg     10
8  MM4  S2  uyi      7


# Just to show what values are returned by .idxmax() above:
In [369]: df.groupby(["sp", "mt"])["count"].idxmax().values
Out[369]: array([0, 2, 3, 4, 8])

18
这里的提问者指定了“我想获取每个组中计数等于最大值的所有行”,而根据文档(0.21),idxmax“返回所请求轴上最大值的第一次出现的索引”。 - Max Power
11
这是一个很好的解决方案,但是针对不同的问题。 - Carlos Souza

77

你可能不需要使用groupby(),而是使用sort_values + drop_duplicates两个方法。

df.sort_values('count').drop_duplicates(['Sp', 'Mt'], keep='last')
Out[190]: 
    Sp  Mt Value  count
0  MM1  S1     a      3
2  MM1  S3    cb      5
8  MM4  S2   uyi      7
3  MM2  S3    mk      8
4  MM2  S4    bg     10

同样的逻辑也可以使用tail来实现

df.sort_values('count').groupby(['Sp', 'Mt']).tail(1)
Out[52]: 
    Sp  Mt Value  count
0  MM1  S1     a      3
2  MM1  S3    cb      5
8  MM4  S2   uyi      7
3  MM2  S3    mk      8
4  MM2  S4    bg     10

2
这不仅比其他解决方案快一个数量级(至少对于我的用例而言),而且它还具有作为原始数据框构建的一部分简单链接的附加好处。 - Clay
1
当您看到这个答案时,您会意识到所有其他答案都是错误的。这显然是正确的方法。谢谢。 - Hunaphu
应该在 sort_values 中添加 na_position="first" 以忽略 NaN - Antoine
我发现这对于我几百万行的DF来说非常快。 - John Stud
2
这似乎不能处理并列的情况。 - Benjamin Ziepert

41

我尝试了Zelazny建议的解决方案,但在一个相对较大的数据帧(约400k行)上,我发现它非常慢。这里是我在我的数据集上找到的一种替代方法,速度快了几个数量级。

df = pd.DataFrame({
    'sp' : ['MM1', 'MM1', 'MM1', 'MM2', 'MM2', 'MM2', 'MM4', 'MM4', 'MM4'],
    'mt' : ['S1', 'S1', 'S3', 'S3', 'S4', 'S4', 'S2', 'S2', 'S2'],
    'val' : ['a', 'n', 'cb', 'mk', 'bg', 'dgb', 'rd', 'cb', 'uyi'],
    'count' : [3,2,5,8,10,1,2,2,7]
    })

df_grouped = df.groupby(['sp', 'mt']).agg({'count':'max'})

df_grouped = df_grouped.reset_index()

df_grouped = df_grouped.rename(columns={'count':'count_max'})

df = pd.merge(df, df_grouped, how='left', on=['sp', 'mt'])

df = df[df['count'] == df['count_max']]

2
确实,这样会快得多。对于大型数据集,transform似乎很慢。 - goh
2
你能否添加注释来解释每一行的作用? - tommy.carstensen
1
就我个人而言:我发现来自@Zelazny7的看起来更优雅的解决方案在我的约100K行数据集上执行时间很长,但这个解决方案运行得非常快。 (我正在运行一个已经过时的0.13.0版本,这可能是导致速度慢的原因)。 - Roland
3
但是使用 df[df['count'] == df['count_max']] 的方法会丢失 NaN 行,以及以上的答案。请注意保持原意,并使翻译通俗易懂。 - Qy Zuo
我强烈建议使用这种方法,对于更大的数据框,使用.apply()或.agg()会更快。 - Gerard
显示剩余2条评论

17

使用groupbyidxmax方法:

  1. transfer col date to datetime:

    df['date'] = pd.to_datetime(df['date'])
    
  2. get the index of max of column date, after groupyby ad_id:

    idx = df.groupby(by='ad_id')['date'].idxmax()
    
  3. get the wanted data:

    df_max = df.loc[idx,]
    
   ad_id  price       date
7     22      2 2018-06-11
6     23      2 2018-06-22
2     24      2 2018-06-30
3     28      5 2018-06-22

1
“date”列???这似乎是对另一个问题的回答。否则,它就是Surya的答案的重复,并且存在相同的问题:在出现平局的情况下,只保留第一次出现的值。 - wjandrea

14

对我来说,最简单的解决方案是当计数等于最大值时保留价值。因此,以下一行命令就足够了:

df[df['count'] == df.groupby(['Mt'])['count'].transform(max)]

这是与Zelazny7的答案相同的解决方案。请不要发布重复的答案。 - wjandrea

9
总结一下,有很多方法,但哪一个更快?
import pandas as pd
import numpy as np
import time

df = pd.DataFrame(np.random.randint(1,10,size=(1000000, 2)), columns=list('AB'))

start_time = time.time()
df1idx = df.groupby(['A'])['B'].transform(max) == df['B']
df1 = df[df1idx]
print("---1 ) %s seconds ---" % (time.time() - start_time))

start_time = time.time()
df2 = df.sort_values('B').groupby(['A']).tail(1)
print("---2 ) %s seconds ---" % (time.time() - start_time))

start_time = time.time()
df3 = df.sort_values('B').drop_duplicates(['A'],keep='last')
print("---3 ) %s seconds ---" % (time.time() - start_time))

start_time = time.time()
df3b = df.sort_values('B', ascending=False).drop_duplicates(['A'])
print("---3b) %s seconds ---" % (time.time() - start_time))

start_time = time.time()
df4 = df[df['B'] == df.groupby(['A'])['B'].transform(max)]
print("---4 ) %s seconds ---" % (time.time() - start_time))

start_time = time.time()
d = df.groupby('A')['B'].nlargest(1)
df5 = df.iloc[[i[1] for i in d.index], :]
print("---5 ) %s seconds ---" % (time.time() - start_time))

胜者是...

  • --1 ) 0.03337574005126953 秒 ---
  • --2 ) 0.1346898078918457 秒 ---
  • --3 ) 0.10243558883666992 秒 ---
  • --3b) 0.1004343032836914 秒 ---
  • --4 ) 0.028397560119628906 秒 ---
  • --5 ) 0.07552886009216309 秒 ---

1
做得好,包括计时器,这是所有建议中缺少的。还有一些重要的事情,更重要的是,将其添加到更大的数据集上也会很好。使用具有不同数量重复项的280万行显示了一些惊人的数字。特别是在大型数据上使用nlargest失败得非常惨(比慢100倍以上)。对于我的数据,最快的方法是按排序然后删除重复项(除了最后一个之外全部删除,比降序排序和删除第一个稍微快一点)。 - Jon

9

尝试在groupby对象上使用nlargest。优点是它返回"最大的nlargest项"所取自的行,我们可以获取它们的索引。

在这种情况下,我们想要n=1表示最大值,keep='all'表示包含重复的最大值。

注意: 我们在此将索引的最后一个(-1)元素进行切片,因为在这种情况下,我们的索引由元组组成(例如('MM1', 'S1', 0))。

df = pd.DataFrame({
    'Sp': ['MM1', 'MM1', 'MM1', 'MM2', 'MM2', 'MM2', 'MM4', 'MM4','MM4'],
    'Mt': ['S1', 'S1', 'S3', 'S3', 'S4', 'S4', 'S2', 'S2', 'S2'],
    'Val': ['a', 'n', 'cb', 'mk', 'bg', 'dgb', 'rd', 'cb', 'uyi'],
    'count': [3, 2, 5, 8, 10, 1, 2, 2, 7]
})

d = df.groupby(['Sp', 'Mt'])['count'].nlargest(1, keep='all')

df.loc[[i[-1] for i in d.index]]

    Sp  Mt  Val  count
0  MM1  S1    a      3
2  MM1  S3   cb      5
3  MM2  S3   mk      8
4  MM2  S4   bg     10
8  MM4  S2  uyi      7

如果输入具有MultiIndex,则最好执行类似于df.loc[d.droplevel(['Sp', 'Mt']).index]的操作。我不确定。 - wjandrea
你可以使用 df.loc[d.index.get_level_values(-1)] 更加符合惯用法。 - wjandrea
这个代码是正确的,但在大约10万行数据集上非常非常慢。 - Prakash Vanapalli

6

需要注意的是,对于 groupby对象"应用" "nlargest" 的效果同样出色:

另一个优点是,如果需要,也可以方便地获取 前n个值:

In [85]: import pandas as pd

In [86]: df = pd.DataFrame({
    ...: 'sp' : ['MM1', 'MM1', 'MM1', 'MM2', 'MM2', 'MM2', 'MM4', 'MM4','MM4'],
    ...: 'mt' : ['S1', 'S1', 'S3', 'S3', 'S4', 'S4', 'S2', 'S2', 'S2'],
    ...: 'val' : ['a', 'n', 'cb', 'mk', 'bg', 'dgb', 'rd', 'cb', 'uyi'],
    ...: 'count' : [3,2,5,8,10,1,2,2,7]
    ...: })

## Apply nlargest(1) to find the max val df, and nlargest(n) gives top n values for df:
In [87]: df.groupby(["sp", "mt"]).apply(lambda x: x.nlargest(1, "count")).reset_index(drop=True)
Out[87]:
   count  mt   sp  val
0      3  S1  MM1    a
1      5  S3  MM1   cb
2      8  S3  MM2   mk
3     10  S4  MM2   bg
4      7  S2  MM4  uyi

这似乎不能处理并列情况。 - Benjamin Ziepert

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