在Pandas数据处理过程中,经常会遇到需要对满足特定条件的数据进行滚动累加的需求,比如统计近7天内销售额大于1000的订单累计金额,或者计算最近5次实验中成功实验的累计次数。如果使用循环逐个遍历数据,不仅代码冗长,在处理大规模数据时还会带来严重的性能问题,而向量化实现可以充分利用Pandas的底层优化,大幅提升执行效率。

基础条件滚动累加实现
最基础的条件滚动累加可以通过布尔掩码和cumsum方法组合实现,先筛选出满足条件的数据,再将不满足条件的值置为0,最后进行累加。下面的示例演示了如何对销售额大于1000的订单进行累计求和:
import pandas as pd
import numpy as np
# 构造示例数据
data = {
'order_id': range(1, 11),
'sales': [800, 1200, 900, 1500, 1100, 700, 1300, 1000, 1400, 850]
}
df = pd.DataFrame(data)
print("原始数据:")
print(df)
# 条件:销售额大于1000
condition = df['sales'] > 1000
# 满足条件的值保留,不满足的置为0,再累加
df['cumsum_gt_1000'] = (df['sales'] * condition).cumsum()
print("n条件滚动累加结果:")
print(df)
结合rolling的窗口条件累加
如果需要限制累加的窗口范围,比如只累加最近3个满足条件的值,可以结合rolling方法实现。首先生成满足条件的标记值,再对标记值进行窗口内的累加操作:
import pandas as pd
# 构造示例数据
data = {
'date': pd.date_range('2024-01-01', periods=10),
'value': [2, 5, 3, 7, 1, 8, 4, 9, 2, 6]
}
df = pd.DataFrame(data)
print("原始数据:")
print(df)
# 条件:值大于4
condition = df['value'] > 4
# 满足条件的记为当前值,不满足记为0
masked_value = df['value'] * condition
# 窗口大小为3的滚动累加
df['rolling_cumsum'] = masked_value.rolling(window=3, min_periods=1).sum()
print("n窗口为3的条件滚动累加结果:")
print(df)
复杂条件的自定义向量化实现
当条件涉及多列判断或者更复杂的逻辑时,可以使用numpy的where方法构造向量化的条件判断,再结合累加逻辑实现需求。下面的示例演示了当类别为A且数值大于5时,进行滚动累加的需求:
import pandas as pd
import numpy as np
# 构造示例数据
data = {
'category': ['A', 'B', 'A', 'A', 'B', 'A', 'B', 'A', 'A', 'B'],
'num': [3, 6, 7, 4, 8, 9, 5, 2, 10, 7]
}
df = pd.DataFrame(data)
print("原始数据:")
print(df)
# 多条件判断:类别为A且数值大于5
condition = (df['category'] == 'A') & (df['num'] > 5)
# 构造累加值:满足条件取num,否则取0
accum_value = np.where(condition, df['num'], 0)
# 计算滚动累加
df['custom_cumsum'] = np.cumsum(accum_value)
print("n复杂条件滚动累加结果:")
print(df)
性能对比与注意事项
为了验证向量化实现的性能优势,我们可以对比循环方式和向量化方式的执行时间。在处理10万行数据时,向量化实现的耗时通常仅为循环方式的几十分之一。需要注意的是,向量化实现依赖于Pandas和Numpy的内置方法,当条件逻辑过于复杂无法用内置方法表达时,才考虑使用循环或者apply方法,同时尽量避免在向量化逻辑中混入Python层面的循环操作,否则会失去向量化的性能优势。
| 实现方式 | 10万行数据耗时 | 适用场景 |
|---|---|---|
| 循环遍历 | 约2.5秒 | 逻辑极复杂无法向量化 |
| 基础向量化 | 约0.03秒 | 简单单条件累加 |
| 窗口+向量化 | 约0.05秒 | 带窗口限制的条件累加 |
| 自定义numpy向量化 | 约0.04秒 | 多条件复杂判断累加 |