Groupby 在 Pandas 中改变性能
Groupby Mutate Performance in Pandas
我经常发现自己尝试做 groupby
然后 mutate
的 R 等价物,但正如许多人指出的那样,简单地使用 groupby
和 apply
会受到影响来自主要的性能问题。所以我的问题是,在 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()
我经常发现自己尝试做 groupby
然后 mutate
的 R 等价物,但正如许多人指出的那样,简单地使用 groupby
和 apply
会受到影响来自主要的性能问题。所以我的问题是,在 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()