You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pandas中SeriesGroupBy的diff操作性能瓶颈问题及优化问询

分组后diff操作的性能瓶颈分析与优化方案

性能分析结果

Total time: 1.01876 s Function: prepare at line 91 
Line # Hits Time Per Hit % Time Line Contents
==============================================================
91 @profile
92 def prepare():
93
94 1 5681.0 5681.0 0.6
95 1 2416.0 2416.0 0.2
96
97
98 1 536.0 536.0 0.1 tss = df.groupby('user_id').timestamp
99 1 949643.0 949643.0 93.2 delta = tss.diff()
100 1 1822.0 1822.0 0.2
101 1 13030.0 13030.0 1.3
102 1 5193.0 5193.0 0.5
103 1 1251.0 1251.0 0.1
104
105 1 2038.0 2038.0 0.2
106
107 1 1851.0 1851.0 0.2
108
109 1 282.0 282.0 0.0
110
111 1 3088.0 3088.0 0.3
112 1 2943.0 2943.0 0.3
113 1 438.0 438.0 0.0
114 1 4658.0 4658.0 0.5
115 1 17083.0 17083.0 1.7
116 1 3115.0 3115.0 0.3
117 1 3691.0 3691.0 0.4
118
119 1 2.0 2.0 0.0

问题解答

1. 现象是否符合预期?

完全符合预期。Pandas中groupby后调用diff的机制是:为每个分组单独生成子Series,执行diff计算后再将结果拼接回原DataFrame的结构。这个过程涉及大量的分组拆分、临时对象创建和数据拼接操作——如果你的业务场景中用户(分组键)数量较多,或者每个用户的行为数据量不小,这种开销会被显著放大,直接导致diff步骤成为性能瓶颈,和你看到的profile结果完全一致。

2. 更快的替代方案

结合你的业务场景(用户行为时间已排序、各用户行为完全独立),推荐以下两种高效方案:

方案一:排序+全局diff+掩码修正(最优选择)

利用"同用户行为已排序"的特性,先将数据按user排序,让同用户的行连续排列,然后直接对ts列执行全局diff,最后修正跨用户的diff结果为NaN(每个用户的第一个行为没有前置时间,应该为NaN)。这种方式完全规避了groupby的分组开销,速度提升非常明显。

代码示例:

import pandas as pd
import numpy as np

# 按user排序,确保同用户的行连续
df_sorted = df.sort_values('user')
# 执行全局diff
deltas = df_sorted['ts'].diff()
# 找到每个用户的第一行,将对应的diff结果设为NaN
user_boundaries = df_sorted['user'] != df_sorted['user'].shift()
deltas[user_boundaries] = np.nan
# 还原回原DataFrame的索引顺序
deltas = deltas.reindex(df.index)

方案二:Numba加速分组diff(无需排序的场景)

如果你的数据无法提前按user排序,或者需要保留原数据顺序且不想额外排序,可以用Numba的JIT编译直接操作numpy数组,减少Pandas的对象开销。

代码示例:

import pandas as pd
import numpy as np
from numba import jit

@jit(nopython=True)
def fast_group_diff(values, groups):
    result = np.empty(len(values), dtype=np.float64)
    result[0] = np.nan
    for i in range(1, len(values)):
        if groups[i] == groups[i-1]:
            result[i] = values[i] - values[i-1]
        else:
            result[i] = np.nan
    return result

# 将user转换为数值编码(Numba对字符串处理效率较低)
df['user_code'] = df['user'].astype('category').cat.codes
# 执行加速后的分组diff
deltas = pd.Series(
    fast_group_diff(df['ts'].values, df['user_code'].values),
    index=df.index
)

效果验证

你可以用%timeit对比原生方法和优化方法的速度:

# 原生方法
%timeit df.groupby('user').ts.transform(pd.Series.diff)
# 优化方案一
%timeit df.sort_values('user')['ts'].diff().where(~df.sort_values('user')['user'].ne(df.sort_values('user')['user'].shift())).reindex(df.index)

在数据量较大的场景下,优化后的方法速度通常能提升10~100倍不等。

内容的提问来源于stack exchange,提问作者mkmostafa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 10:17:26