Groupby 在 Pandas 中改变性能

Groupby Mutate Performance in Pandas

我经常发现自己尝试做 groupby 然后 mutate 的 R 等价物,但正如许多人指出的那样,简单地使用 groupbyapply 会受到影响来自主要的性能问题。所以我的问题是,在 pandas 中对数据帧进行分组的最佳(最高性能)方法是什么,然后根据该组中的某些条件,根据某些计算添加一个新列?

(我搜索了又搜索,但我没有找到任何关于如何使用 numpy 在 pandas 中向量化自定义函数的指南/步骤。类似问题的所有答案总是针对具体情况的,而不是概括得很好。)

示例数据:

df
Out[17]: 
             ID   ID2        col1        col2       col3    value
0             1     J       333.5       333.3      333.4      cat    
1             1     S       333.5       333.3      333.8      
2             2     J       333.7       333.3      333.8      cat
3             2     S       333.7       333.3      333.4      dog
4             3     L       333.7       333.8      333.9      
5             3     D       333.8       333.8      333.9      
6             4     S       333.8       333.6      333.7      cat
7             4     J       333.8       333.2      333.8      
8             4     J       333.8       333.7      333.9      
9             4     L       333.8       333.3      333.4      cat

下面是一些例子,我运行不断地改成:

1) apply 根据条件分组的功能,return 这些结果与原始数据帧一起。

df.groupby(by=['ID']).apply(myfunc)

def myfunc(group):
    group['new_col'] = len(group.query('''ID2=='T' & (col1>=col3 | px<=col2)''').unique())
    return group

2) 与 1) 类似,但仅根据某些条件更新一个现有列,然后 return 将结果与原始数据框一起。

df.groupby(by=['ID']).apply(update_func)

def update_func(group):
     if 'S' in group['ID2'].values:
          group.loc[(group['value']=='cat'), 'other_column'] = False
     return group

对于第一个示例,我通过使用 numpy 运算符改进了函数,并且按照@ScottBoston 的建议,我将函数更改为 return 只是值,然后将它们映射回我的原始数据框:

def my_func(group):
    mask = np.logical_and(group.ID2 == 'J', (np.logical_or((group.col1 >= group.col3), (group.col1 <= group.col2))))
    return len(group[mask].col1.unique())

dict = df.groupby(by=['ID'], sort=False).apply(my_func).to_dict() #This is a bit slow

df['new_col'] = df['ID'].map(dict) #This is fast

最慢的部分仍然是应用numpy条件,必须对每个组进行操作。如果有一种方法可以对每个并行操作,那将是理想的,因为不需要像现在这样按顺序进行操作。

我认为您不需要在 groupby 中进行屏蔽,让我们看看这对您来说是否更快。

d1 = df.assign(mask=np.logical_and(df.ID2 == 'J', (np.logical_or((df.col1 >= df.col3), (df.col1 <= df.col2)))))

dict = d1.groupby('ID').apply(lambda x: x.loc[x['mask'],'col1'].size).to_dict()