【问题标题】:Pandas get topmost n records within each group熊猫在每组中获得前 n 条记录
【发布时间】:2018-12-12 02:10:02
【问题描述】:

假设我有这样的 pandas DataFrame:

>>> df = pd.DataFrame({'id':[1,1,1,2,2,2,2,3,4],'value':[1,2,3,1,2,3,4,1,1]})
>>> df
   id  value
0   1      1
1   1      2
2   1      3
3   2      1
4   2      2
5   2      3
6   2      4
7   3      1
8   4      1

我想获得一个新的 DataFrame,每个 id 都有前 2 条记录,如下所示:

   id  value
0   1      1
1   1      2
3   2      1
4   2      2
7   3      1
8   4      1

我可以用一个又一个的分组来编号记录:

>>> dfN = df.groupby('id').apply(lambda x:x['value'].reset_index()).reset_index()
>>> dfN
   id  level_1  index  value
0   1        0      0      1
1   1        1      1      2
2   1        2      2      3
3   2        0      3      1
4   2        1      4      2
5   2        2      5      3
6   2        3      6      4
7   3        0      7      1
8   4        0      8      1
>>> dfN[dfN['level_1'] <= 1][['id', 'value']]
   id  value
0   1      1
1   1      2
3   2      1
4   2      2
7   3      1
8   4      1

但是有没有更有效/优雅的方法来做到这一点?还有更优雅的方法来对每个组内的记录进行编号(如 SQL 窗口函数row_number())。

【问题讨论】:

标签: python pandas greatest-n-per-group window-functions top-n


【解决方案1】:

你试过df.groupby('id').head(2)

生成的输出:

>>> df.groupby('id').head(2)
       id  value
id             
1  0   1      1
   1   1      2 
2  3   2      1
   4   2      2
3  7   3      1
4  8   4      1

(请记住,您可能需要先订购/排序,具体取决于您的数据)

编辑:正如提问者所说,使用df.groupby('id').head(2).reset_index(drop=True) 删除多索引并展平结果。

>>> df.groupby('id').head(2).reset_index(drop=True)
    id  value
0   1      1
1   1      2
2   2      1
3   2      2
4   3      1
5   4      1

【讨论】:

  • 是的,我想就是这样。不知何故忽略了这一点。你知道给组内记录编号的好方法吗?
  • 为了得到我需要的输出,我还添加了.reset_index(drop=True)
  • github.com/pydata/pandas/pull/5510 刚刚被合并;将在 0.13 中,执行此操作的新方法称为 cumcount(为每组中的记录编号)
  • 为了使@dorvak 他的答案更完整,如果您想要每个id 的2 个最小值,请执行df.sort_values(['id', 'value'], axis=0).groupby('id').head(2)。另一个例子,每个id 的最大值由df.sort_values(['id', 'value'], axis=0).groupby('id').tail(1) 给出。
  • 从 0.23.4 开始,df.groupby('id').head(2) 之后不需要展平。 Source
【解决方案2】:

Since 0.14.1,您现在可以在groupby 对象上执行nlargestnsmallest

In [23]: df.groupby('id')['value'].nlargest(2)
Out[23]: 
id   
1   2    3
    1    2
2   6    4
    5    3
3   7    1
4   8    1
dtype: int64

您也可以在其中获得原始索引,这有点奇怪,但这可能非常有用,具体取决于您的原始索引是什么

如果你对它不感兴趣,你可以通过.reset_index(level=1, drop=True) 彻底摆脱它。

(注意:From 0.17.1 您也可以在 DataFrameGroupBy 上执行此操作,但目前它仅适用于 SeriesSeriesGroupBy。)

【讨论】:

  • 有没有办法获得unique_limit(n)?就像我想要前 n 个唯一值一样?如果我要求nlargest,它将对整个 df 进行排序,这可能很昂贵
  • 这不适用于对 groupby 进行聚合的情况?例如,df.groupby([pd.Grouper(freq='M'), 'A'])['B'].count().nlargest(5, 'B') 这只是返回整个系列中的整体前 5 名,而不是每个组
  • 关于DataFrameGroupBys 现在也可以这样做的声明似乎是错误的,链接的拉取请求似乎仅将nlargest 添加到简单的DataFrames。这是相当不幸的,因为如果您想选择多个列怎么办?
【解决方案3】:

有时提前对整个数据进行排序非常耗时。 我们可以先groupby,对每个组做topk:

g = df.groupby(['id']).apply(lambda x: x.nlargest(topk,['value'])).reset_index(drop=True)

【讨论】:

    【解决方案4】:
    df.groupby('id').apply(lambda x : x.sort_values(by = 'value', ascending = False).head(2).reset_index(drop = True))
    
    • 此处排序值升序 false 给出类似于 nlargest 而 True 给出类似于 nsmallest。
    • head 中的值与我们在 nlargest 中给出的值相同,以获取每个组要显示的值的数量。
    • reset_index 是可选的,不是必需的。

    【讨论】:

    • 缺少一个括号,用于应用
    【解决方案5】:

    这适用于重复值

    如果您在前 n 个值中有重复值,并且只需要唯一值,您可以这样做:

    import pandas as pd
    
    ifile = "https://raw.githubusercontent.com/bhishanpdl/Shared/master/data/twitter_employee.tsv"
    df = pd.read_csv(ifile,delimiter='\t')
    print(df.query("department == 'Audit'")[['id','first_name','last_name','department','salary']])
    
        id first_name last_name department  salary
    24  12   Shandler      Bing      Audit  110000
    25  14      Jason       Tom      Audit  100000
    26  16     Celine    Anston      Audit  100000
    27  15    Michale   Jackson      Audit   70000
    
    If we do not remove duplicates, for the audit department we get top 3 salaries as 110k,100k and 100k.
    If we want to have not-duplicated salaries per each department, we can do this:
    
    (df.groupby('department')['salary']
     .apply(lambda ser: ser.drop_duplicates().nlargest(3))
     .droplevel(level=1)
     .sort_index()
     .reset_index()
    )
    
    This gives
    
    department  salary
    0   Audit   110000
    1   Audit   100000
    2   Audit   70000
    3   Management  250000
    4   Management  200000
    5   Management  150000
    6   Sales   220000
    7   Sales   200000
    8   Sales   150000
    
    
    
    
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-09-01
      • 1970-01-01
      相关资源
      最近更新 更多