【问题标题】:Groupby value counts on the dataframe pandasGroupby 值对数据框 pandas 的计数
【发布时间】:2017-01-01 03:29:52
【问题描述】:

我有以下数据框:

df = pd.DataFrame([
    (1, 1, 'term1'),
    (1, 2, 'term2'),
    (1, 1, 'term1'),
    (1, 1, 'term2'),
    (2, 2, 'term3'),
    (2, 3, 'term1'),
    (2, 2, 'term1')
], columns=['id', 'group', 'term'])

我想按idgroup 对其进行分组,并计算此id、组对的每个术语的数量。

所以最后我会得到这样的东西:

我能够通过使用df.iterrows() 遍历所有行并创建一个新数据框来实现我想要的,但这显然效率低下。 (如果有帮助,我事先知道所有术语的列表,其中大约有 10 个)。

看起来我必须先分组,然后计算值,所以我尝试了 df.groupby(['id', 'group']).value_counts(),但它不起作用,因为 value_counts 在 groupby 系列而不是数据帧上运行。

无论如何我可以在不循环的情况下实现这一点?

【问题讨论】:

    标签: python pandas dataframe crosstab pandas-groupby


    【解决方案1】:

    我使用groupbysize

    df.groupby(['id', 'group', 'term']).size().unstack(fill_value=0)
    


    时间

    1,000,000 行

    df = pd.DataFrame(dict(id=np.random.choice(100, 1000000),
                           group=np.random.choice(20, 1000000),
                           term=np.random.choice(10, 1000000)))
    

    【讨论】:

    • @jezrael thx, size 也更快。 crosstab 效率低得奇怪
    • 我很惊讶crosstab 如此懒惰;)
    • @jezrael, crosstab 在内部使用 pivot_table... ;)
    • @piRSquared - 你可以添加到计时df.groupby(['id', 'group', 'term'])['term'].size().unstack(fill_value=0) 吗?对我来说似乎更快。谢谢。
    • @piRSquared - 我在更大的 df 中尝试它并且更快一点(0.2ms,也许它是相同的;))
    【解决方案2】:

    使用pivot_table()方法:

    In [22]: df.pivot_table(index=['id','group'], columns='term', aggfunc='size', fill_value=0)
    Out[22]:
    term      term1  term2  term3
    id group
    1  1          2      1      0
       2          0      1      0
    2  2          1      0      1
       3          1      0      0
    

    针对 700K 行 DF 计时:

    In [24]: df = pd.concat([df] * 10**5, ignore_index=True)
    
    In [25]: df.shape
    Out[25]: (700000, 3)
    
    In [3]: %timeit df.groupby(['id', 'group', 'term'])['term'].size().unstack(fill_value=0)
    1 loop, best of 3: 226 ms per loop
    
    In [4]: %timeit df.pivot_table(index=['id','group'], columns='term', aggfunc='size', fill_value=0)
    1 loop, best of 3: 236 ms per loop
    
    In [5]: %timeit pd.crosstab([df.id, df.group], df.term)
    1 loop, best of 3: 355 ms per loop
    
    In [6]: %timeit df.groupby(['id','group','term'])['term'].size().unstack().fillna(0).astype(int)
    1 loop, best of 3: 232 ms per loop
    
    In [7]: %timeit df.groupby(['id', 'group', 'term']).size().unstack(fill_value=0)
    1 loop, best of 3: 231 ms per loop
    

    针对 7M 行 DF 的计时:

    In [9]: df = pd.concat([df] * 10, ignore_index=True)
    
    In [10]: df.shape
    Out[10]: (7000000, 3)
    
    In [11]: %timeit df.groupby(['id', 'group', 'term'])['term'].size().unstack(fill_value=0)
    1 loop, best of 3: 2.27 s per loop
    
    In [12]: %timeit df.pivot_table(index=['id','group'], columns='term', aggfunc='size', fill_value=0)
    1 loop, best of 3: 2.3 s per loop
    
    In [13]: %timeit pd.crosstab([df.id, df.group], df.term)
    1 loop, best of 3: 3.37 s per loop
    
    In [14]: %timeit df.groupby(['id','group','term'])['term'].size().unstack().fillna(0).astype(int)
    1 loop, best of 3: 2.28 s per loop
    
    In [15]: %timeit df.groupby(['id', 'group', 'term']).size().unstack(fill_value=0)
    1 loop, best of 3: 1.89 s per loop
    

    【讨论】:

    • 我只是想用更大的样本更新时间:-)
    • 哇!在更大的范围内,pivot 似乎同样有效。我必须记住这一点。我会给你 +1,但我前段时间已经这样做了。
    • 所以size 是我们忘记的别名here :)
    • @ayhan,很奇怪 - 这次df.assign(ones = np.ones(len(df))).pivot_table(index=['id','group'], columns='term', values = 'ones', aggfunc=np.sum, fill_value=0) 的解决方案有点慢 - 1 loop, best of 3: 2.55 s per loop
    • 我认为这是因为您在那里使用了len,而不是“大小”。 len 是一个 Python 函数,但我们作为字符串传递的函数是优化的 C 函数的别名。
    【解决方案3】:

    与其记住冗长的解决方案,不如使用 pandas 为您内置的解决方案:

    df.groupby(['id', 'group', 'term']).count()
    

    【讨论】:

      【解决方案4】:

      你可以使用crosstab:

      print (pd.crosstab([df.id, df.group], df.term))
      term      term1  term2  term3
      id group                     
      1  1          2      1      0
         2          0      1      0
      2  2          1      0      1
         3          1      0      0
      

      另一个解决方案是groupby 聚合size,通过unstack 重塑:

      df.groupby(['id', 'group', 'term'])['term'].size().unstack(fill_value=0)
      
      term      term1  term2  term3
      id group                     
      1  1          2      1      0
         2          0      1      0
      2  2          1      0      1
         3          1      0      0
      

      时间安排

      df = pd.concat([df]*10000).reset_index(drop=True)
      
      In [48]: %timeit (df.groupby(['id', 'group', 'term']).size().unstack(fill_value=0))
      100 loops, best of 3: 12.4 ms per loop
      
      In [49]: %timeit (df.groupby(['id', 'group', 'term'])['term'].size().unstack(fill_value=0))
      100 loops, best of 3: 12.2 ms per loop
      

      【讨论】:

      • 哇哇哇,你太棒了。你只用了 3 分钟(与我写循环所用的时间相同,而我写这个问题所用的时间更少)。如果你能写一些解释为什么会这样,我真的很感激,但很可能我能在几分钟内自己理解。
      • 在你的情况下crosstabpivot_table 更好,因为默认聚合函数是len(它与size 相同),我认为它也是更快的解决方案。 Crosstab 使用第一个参数作为列的 indexsecond。给我一点时间,我试试添加时间。
      • 但我认为最好在docs 中解释。
      【解决方案5】:

      如果你想使用value_counts,你可以在给定的系列上使用它,并采用以下方法:

      df.groupby(["id", "group"])["term"].value_counts().unstack(fill_value=0)
      

      或以等效方式,使用.agg 方法:

      df.groupby(["id", "group"]).agg({"term": "value_counts"}).unstack(fill_value=0)
      

      另一种选择是直接在 DataFrame 本身上使用value_counts,而不使用groupby

      df.value_counts().unstack(fill_value=0)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-01-29
        • 2020-07-12
        • 2018-10-27
        • 2021-01-19
        相关资源
        最近更新 更多