Groupby 值对数据框 pandas 的计数

Posted

技术标签:

【中文标题】Groupby 值对数据框 pandas 的计数【英文标题】:Groupby value counts on the dataframe pandas 【发布时间】:2021-11-05 05:13:08 【问题描述】:

我有以下数据框:

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 系列而不是数据帧上运行。

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

【问题讨论】:

【参考方案1】:

你可以使用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 中解释。【参考方案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】:

我使用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,也许它是相同的;))【参考方案4】:

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

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

【讨论】:

【参考方案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)

【讨论】:

以上是关于Groupby 值对数据框 pandas 的计数的主要内容,如果未能解决你的问题,请参考以下文章

如何在 pandas 数据框中从 groupby 的结果生成所有值对

Pandas Groupby:计数和平均值相结合

使用 pandas 在数据帧上执行 groupby,按计数排序并获取 python 中的前 2 个计数

Groupby并根据Pandas中的多个条件计算计数和均值

Pandas DataFrame Groupby 两列并获取计数

Python、pandas 数据框、groupby 列和预先知道的值