在数据分析的日常工作中,经常遇到这样一个场景:需要根据特定的分组条件——比如相同的时间点加上相同的类别标签——去计算某个数值字段相对于前一条记录的变化量,也就是一阶差分。很多人第一反应是写好几层 for 循环,挨个遍历不同的组合,再手动算差值。这种方法在小规模数据上还能应付,可一旦数据量上来,性能问题立刻就暴露了,代码也容易写得又长又乱,调试起来相当头疼。
其实,Pandas 提供了一套非常简洁高效的向量化方案,核心就是一句话:groupby(...)[col].diff()。它的逻辑很清晰:先按你指定的列(比如 ['Time', 'Prop1'])进行分组,然后在每个组内,对目标列(比如 Prop2)按照原始行顺序计算当前行与上一行的差值,也就是 current - previous。对于每个组的第一条记录,因为前面没有数据,所以直接返回 NaN。
直接看一个完整的例子,会更直观:
import pandas as pd
# 构造示例数据
df = pd.DataFrame({
"Time": [1, 2, 3, 1, 2, 3],
"Prop1": ["A", "B", "A", "A", "B", "B"],
"Prop2": [4, 5, 1, 5, 4, 3]
})
# 关键操作:按 Time 和 Prop1 分组,对 Prop2 计算组内差分
df["Prop2Diff"] = df.groupby(['Time', 'Prop1'])['Prop2'].diff()
print(df)
运行结果如下:
Time Prop1 Prop2 Prop2Diff 0 1 A 4 NaN 1 2 B 5 NaN 2 3 A 1 NaN 3 1 A 5 1.0 4 2 B 4 -1.0 5 3 B 3 NaN
这里面的逻辑其实一目了然:
- 第 0 行,分组
Time=1, Prop1='A'第一次出现,所以Prop2Diff是NaN。 - 第 3 行,同样是这个分组,
Prop2从 4 变成了 5,差值就是 5 - 4 = 1.0。 - 第 1 行,分组
Time=2, Prop1='B'的首行,同样是NaN。 - 第 4 行,属于同一分组,
Prop2从 5 降到 4,差值就是 4 - 5 = -1.0。 - 至于第 2 行和第 5 行,它们在
Time=3这个时间点上,每个分组都只有一条记录,没有前一行可比较,所以结果都是NaN。
不过,使用这个技巧时,有几个细节值得留意:
diff()默认是按原始 DataFrame 的行顺序来计算组内差分的,并不会自动排序。如果你的业务逻辑要求必须按时间升序来算(比如分析时间序列数据),最好先对数据排序:
df = df.sort_values(['Time', 'Prop1']).reset_index(drop=True) df['Prop2Diff'] = df.groupby(['Time', 'Prop1'])['Prop2'].diff()
- 如果你的需求是“跨组差分”——比如想计算同一个 Prop1 下,相邻 Time 之间的差值,那就不能用上面这个简单的分组了。这时需要改用
groupby('Prop1').apply(lambda g: g.sort_values('Time')['Prop2'].diff())。 diff(periods=n)这个参数可以让你指定跨几行差分,比如diff(2)就是计算隔一行的差值。- 最核心的一点是,这个操作是纯向量化的,性能远超 Python 循环,数据量越大,优势越明显,百万级数据也完全不在话下。
说到底,groupby([...])[col].diff() 就是处理“条件差分”这类问题的标准范式。逻辑清晰、代码简短、性能卓越,算是 Pandas 高效数据处理中一个非常实用的核心技巧。