[英]General Groupby in Python Pandas: Fast way
有没有办法做一个不依赖 pd.groupby 的通用的、高性能的 groupby 操作?
pd.DataFrame([[1, '2020-02-01', 'a'], [1, '2020-02-10', 'b'], [1, '2020-02-17', 'c'], [2, '2020-02-02', 'd'], [2, '2020-03-06', 'b'], [2, '2020-04-17', 'c']], columns=['id', 'begin_date', 'status'])`
id begin_date status
0 1 2020-02-01 a
1 1 2020-02-10 b
2 1 2020-02-17 c
3 2 2020-02-02 d
4 2 2020-03-06 b
id status count uniquecount
0 1 a 1 1
1 1 b 1 1
2 1 c 1 1
3 2 b 1 1
4 2 c 1 1
现在,在 Python 中有一个简单的方法可以做到这一点,使用 Pandas。
df = df.groupby(["id", "status"]).agg(count=("begin_date", "count"), uniquecount=("begin_date", lambda x: x.nunique())).reset_index()
# As commented, omitting the lambda and replacing it with "begin_date", "nunique" will be faster. Thanks!
对于较大的数据集,此操作很慢,我猜测并说 O(n²)。
现在,经过一番谷歌搜索,StackOverflow 上有一些替代解决方案,或者使用 numpy、iterrows 或其他不同的方式。
还有一个很棒的:
在我的示例中,这些解决方案通常旨在创建“计数”或“唯一计数”,基本上是聚合值。 但是,不幸的是,总是只有一个聚合,而不是多个 groupby 列。 此外,不幸的是,他们从未解释如何将它们合并到分组的 dataframe 中。
Is there a way to use itertools (Like this answer: Faster alternative to perform pandas groupby operation , or even better this answer: Groupby in python pandas: Fast Way ) that do not only return the series "count", but the whole dataframe in分组形式?
有没有办法做一个不依赖 pd.groupby 的通用的、高性能的 groupby 操作?
这看起来像这样:
from typing import List
def fastGroupby(df, groupbyColumns: List[str], aggregateColumns):
# numpy / iterrow magic
return df_grouped
df = fastGroupby(df, ["id", "status"], {'status': 'count',
'status': 'count'}
并返回所需的 output。
在放弃groupby
之前,我建议首先评估您是否真正利用了groupby
所提供的功能。
lambda
以支持内置的pd.DataFrameGroupBy
方法。 许多Series
和DataFrame
方法都实现为pd.DataFrameGroupBy
方法。 您应该直接使用它们,而不是使用groupby
+ apply(lambda x: ...)
调用它们
此外,对于许多计算,您可以将问题重新定义为对整个 DataFrame 进行一些矢量化操作,然后使用在 cython 中实现的 groupby 方法。 这会很快。
一个常见的例子是在一个组中找到'Y'
答案的比例。 一个直接的方法是检查每个组内的条件,然后得到比例:
N = 10**6
df = pd.DataFrame({'grp': np.random.choice(range(10000), N),
'answer': np.random.choice(['Y', 'N'], N)})
df.groupby('grp')['answer'].apply(lambda x: x.eq('Y').mean())
以这种方式思考问题需要lambda
,因为我们在 groupby 内部做了两个操作; 检查条件然后平均。 这个完全相同的计算可以被认为是首先检查整个 DataFrame 的条件,然后计算组内的平均值:
df['answer'].eq('Y').groupby(df['grp']).mean()
这是一个很小的变化,但后果是巨大的,随着组数的增加,收益会越来越大。
%timeit df.groupby('grp')['answer'].apply(lambda x: x.eq('Y').mean())
#2.32 s ± 99.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit df['answer'].eq('Y').groupby(df['grp']).mean()
#82.8 ms ± 995 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
sort=False
作为参数默认情况下, groupby
对键上的 output 进行排序。 如果没有理由对 output 进行排序,则指定sort=False
可以获得轻微的收益
observed=True
作为参数如果分组键是分类的,它将重新索引到所有可能的组合,即使对于从未出现在 DataFrame 中的组也是如此。 如果这些不重要,从 output 中删除它们将大大提高速度。
对于您的示例,我们可以检查差异。 切换到pd.DataFrameGroupBy.nunique
可以获得巨大的收益,并且删除排序会增加一点额外的速度。 两者的结合提供了一个“相同”的解决方案(直到排序),并且对于许多组来说快了近 100 倍。
import perfplot
import pandas as pd
import numpy
def agg_lambda(df):
return df.groupby(['id', 'status']).agg(uniquecount=('Col4', lambda x: x.nunique()))
def agg_nunique(df):
return df.groupby(['id', 'status']).agg(uniquecount=('Col4', 'nunique'))
def agg_nunique_nosort(df):
return df.groupby(['id', 'status'], sort=False).agg(uniquecount=('Col4', 'nunique'))
perfplot.show(
setup=lambda N: pd.DataFrame({'Col1': range(N),
'status': np.random.choice(np.arange(N), N),
'id': np.random.choice(np.arange(N), N),
'Col4': np.random.choice(np.arange(N), N)}),
kernels=[
lambda df: agg_lambda(df),
lambda df: agg_nunique(df),
lambda df: agg_nunique_nosort(df),
],
labels=['Agg Lambda', 'Agg Nunique', 'Agg Nunique, No sort'],
n_range=[2 ** k for k in range(20)],
# Equality check same data, just allow for different sorting
equality_check=lambda x,y: x.sort_index().compare(y.sort_index()).empty,
xlabel="~ Number of Groups"
)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.