在 pandas 中使用 groupby 并并行应用过滤器的最有效方法是什么?
基本上我要求的是 SQL 中的等效项
select *
...
group by col_name
having condition
我认为有很多用例,从条件平均值、总和、条件概率等,这将使这样的命令非常强大。
我需要非常好的性能,所以理想情况下这样的命令不会是在 python 中完成多个分层操作的结果。
正如unutbu的评论中提到的,groupby的过滤器相当于SQL的HAVING:
In [11]: df = pd.DataFrame([[1, 2], [1, 3], [5, 6]], columns=['A', 'B'])
In [12]: df
Out[12]:
A B
0 1 2
1 1 3
2 5 6
In [13]: g = df.groupby('A') # GROUP BY A
In [14]: g.filter(lambda x: len(x) > 1) # HAVING COUNT(*) > 1
Out[14]:
A B
0 1 2
1 1 3
您可以编写更复杂的函数(这些函数适用于每个组),前提是它们返回一个简单的布尔值:
In [15]: g.filter(lambda x: x['B'].sum() == 5)
Out[15]:
A B
0 1 2
1 1 3
注意:可能存在一个错误,您无法编写函数来对用于分组的列进行操作...解决方法是手动对列进行分组,即
g = df.groupby(df['A']))
。
我按 max 大于 20 的州和县进行分组,然后使用数据帧 loc 子查询 True 的结果值
counties=df.groupby(['state','county'])['field1'].max()>20
counties=counties.loc[counties.values==True]
groupby.transform
+ 布尔索引尽管 pandas 中的等效语法是
groupby.filter
,但速度却慢得令人痛苦。如果性能很重要,那么最好是稍后执行 groupby 并过滤数据帧,而不是在 groupby 操作期间进行过滤。因为 groupby.filter
为每个组调用 Python 函数(例如 lambda),而 groupby.transform
在整个数据帧上调用 Cython 优化的函数,所以如果有很多组,后者会快得多。使用 groupby.transform
的要点是它返回一个与填充聚合值的原始数据帧具有相同索引的数据帧。由于它的输出具有相同的索引,因此可以用来过滤原始数据帧。
所以相当于SELECT * FROM df GROUP BY colA HAVING COUNT(*) > 1
是
df[df.groupby('colA').transform('size') > 1]
以及相当于
SELECT * FROM df GROUP BY colA HAVING SUM(colB) > 5
是
df[df.groupby('colA')['colB'].transform('sum') > 5]
无论如何,正如下面的性能图所示,随着组数量的增加,
groupby.transform
+布尔索引的执行速度比
groupby.filter
快得多;例如,如果有 10k 组,则速度快 1000 倍。事实上,如果您的数据框有数百万个组,groupby.filter
甚至可能无法运行,而groupby.transform
+布尔索引将在合理的时间内完成工作。
import perfplot
import pandas as pd
import numpy as np
def groupby_filter(df):
g = df.groupby('A')
return g.filter(lambda x: x['B'].sum() > 5)
def groupby_transform(df):
g = df.groupby('A')
return df[g['B'].transform('sum') > 5]
perfplot.plot(
kernels=[groupby_filter, groupby_transform],
n_range=[2**k for k in range(16)],
setup=lambda n: pd.DataFrame({
'A': np.random.choice(n, size=n, replace=False),
'B': np.random.randint(n, size=n)}),
xlabel='Number of groups'
)