【问题标题】:Get the row(s) which have the max value in groups using groupby使用 groupby 获取组中具有最大值的行
【发布时间】:2018-12-31 20:32:12
【问题描述】:

在按['Sp','Mt'] 列分组后,如何在 pandas 数据框中找到 count 列的最大值的所有行?

示例1:以下dataFrame,我按['Sp','Mt']分组:

   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**

预期输出:获取组间计数最大的结果行,如:

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'] 分组:

   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

对于上面的示例,我想在每个组中获取 allcount 等于 max 的行,例如:

MM2  S4   bg     10
MM4  S2   cb     8
MM4  S2   uyi    8

【问题讨论】:

标签: python pandas max pandas-groupby


【解决方案1】:
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(['Mt'], sort=False)['count'].max()
Out[2]:
Mt
S1     3
S3     8
S4    10
S2     7
Name: count

要获取原始 DF 的索引,您可以这样做:

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

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

请注意,如果每个组有多个最大值,则将全部返回。

更新

万幸这就是 OP 所要求的:

In [5]: df['count_max'] = df.groupby(['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          8
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

【讨论】:

  • @Zelazny7,有没有办法采用这个答案来按列分组,然后查看 2 列并做最大的两列以获得更大的两列?我不能让它工作。我目前拥有的是:def Greater(Merge, maximumA, maximumB): a = Merge[maximumA] b = Merge[maximumB] return max(a, b) Merger.groupby("Search_Term").apply(Greater,"Ratio_x ","Ratio_y")
  • @Zelazny7 我正在使用第二种方法,idx。但是,我只能为每个组设置一个最大值(而且我的数据有几个重复最大值)。有没有办法通过您的解决方案解决这个问题?
  • 实际上,这对我不起作用。我无法跟踪问题,因为数据框如果退出很大,但@Rani 的解决方案效果很好
  • 嗨 Zealzny,如果我想取前 3 个最大行而不是一个最大值,我该如何调整您的代码?
  • transform 方法在数据集足够大的情况下可能会有池化性能,先取最大值再合并数据帧会更好。
【解决方案2】:

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

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

【讨论】:

  • 非常好!大帧(25k 行)速度快
  • 对于那些对Python有些陌生的人,您需要将它分配给一个新变量,它不会改变当前的df变量。
  • @Samir 或使用inplace = True 作为drop_duplicates 的参数
  • 当只需要一个具有相同最大值的行时,这是一个很好的答案,但是如果我需要所有具有最大值的行,它就不会按预期工作。
  • 我的意思是如果数据框是 pd.DataFrame({'sp':[1, 1, 2], 'mt':[1, 1, 2], 'value':[2, 2, 3]},那么在 sp==1 和 mt==2 的组中将有 2 行具有相同的最大值 2。@Rani
【解决方案3】:

简单的解决方案是应用idxmax() 函数来获取具有最大值的行的索引。 这将过滤掉组中具有最大值的所有行。

In [365]: import pandas as pd

In [366]: 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]
})

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


### 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]: 
   count  mt   sp  val
0      3  S1  MM1    a
2      5  S3  MM1   cb
3      8  S3  MM2   mk
4     10  S4  MM2   bg
8      7  S2  MM4  uyi

### 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])

【讨论】:

  • 这里的提问者指定"I want to get ALL the rows where count equals max in each group",而根据文档(0.21)idxmax Return[s] index of first occurrence of maximum over requested axis"
  • 这是一个很好的解决方案,但针对不同的问题
【解决方案4】:

您可能不需要 group by ,使用 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

【讨论】:

  • 这不仅比其他解决方案快一个数量级(至少对于我的用例而言),它还具有将链接作为原始数据帧构造的一部分的额外好处。跨度>
  • 当您看到这个答案时,您会意识到所有其他人都错了。这显然是实现它的方法。谢谢。
  • 应将na_position="first" 添加到sort_values 以忽略NaNs。
  • 我发现这对于我的几百万行的 DF 来说很快。
【解决方案5】:

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

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']]

【讨论】:

  • 确实这样更快。对于大型数据集,转换似乎很慢。
  • 你能添加 cmets 来解释每一行的作用吗?
  • fwiw:我发现@Zelazny7 提供的外观更优雅的解决方案需要很长时间才能为我的约 100K 行执行,但这个运行得非常快。 (我正在运行一个现在已经过时的 0.13.0,这可能会导致速度缓慢)。
  • 但是这样做df[df['count'] == df['count_max']]会丢失NaN行,以及上面的答案。
  • 我强烈建议使用这种方法,对于更大的数据帧,使用 .appy() 或 .agg() 会快得多。
【解决方案6】:

使用groupbyidxmax 方法:

  1. 将coldate转移到datetime

    df['date']=pd.to_datetime(df['date'])
    
  2. 得到datemax的索引,在groupyby ad_id之后:

    idx=df.groupby(by='ad_id')['date'].idxmax()
    
  3. 得到想要的数据:

    df_max=df.loc[idx,]
    

输出[54]:

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

【讨论】:

    【解决方案7】:

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

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

    【讨论】:

      【解决方案8】:

      尝试在 groupby 对象上使用“nlargest”。使用 nlargest 的优点是它返回从中获取“nlargest item(s)”的行的索引。 注意:我们对索引的第二(1)个元素进行切片,因为在这种情况下,我们的索引由元组组成(例如(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('mt')['count'].nlargest(1) # pass 1 since we want the max
      
      df.iloc[[i[1] for i in d.index], :] # pass the index of d as list comprehension
      

      enter image description here

      【讨论】:

        【解决方案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 秒 ---

        【讨论】:

          【解决方案10】:

          意识到 "applying" "nlargest"groupby 对象 也同样有效:

          其他优势 - 如果需要,还可以获取 前 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
          

          【讨论】:

            【解决方案11】:
            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.groupby(['sp', 'mt']).apply(lambda grp: grp.nlargest(1, 'count'))
            

            【讨论】:

              【解决方案12】:

              如果您对 DataFrame 进行排序,则排序将保留在 groupby 中。然后,您可以抓取第一个或最后一个元素并重置索引。

              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.sort_values("count", ascending=False).groupby(["sp", "mt"]).first().reset_index()
              

              【讨论】:

                【解决方案13】:

                我在很多组操作中都使用了这种函数式风格:

                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.groupby('Mt')\
                  .apply(lambda group: group[group.Count == group.Count.max()])\
                  .reset_index(drop=True)
                
                    sp  mt  val  count
                0  MM1  S1    a      3
                1  MM4  S2  uyi      7
                2  MM2  S3   mk      8
                3  MM2  S4   bg     10
                

                .reset_index(drop=True) 通过删除组索引让您回到原始索引。

                【讨论】:

                  猜你喜欢
                  相关资源
                  最近更新 更多