在数据分析工作中,我们常常需要观察某个数值随记录顺序不断累积的效果,例如每日营业额滚动汇总、用户行为步数累计等。Pandas 作为主流的 Python 数据处理库,提供了非常直接的向量化接口来完成这类需求,不必手写循环即可为数据框添加累加和列。

使用 cumsum 创建基础累加列
最核心的方法是 Series 对象的 cumsum 函数。它会返回一个同样长度的序列,其中每一个位置的值是原始序列从开头到该位置的累加结果。我们将这个序列直接赋值给数据框的一个新列,就完成了新列的创建。
下面的示例构造了一个简单的销售记录表,并为每一步增加了一个名为 total 的累加列:
import pandas as pd
# 构造示例数据
data = {'day': [1, 2, 3, 4], 'amount': [100, 200, 150, 50]}
df = pd.DataFrame(data)
# 创建累加和的新列
df['total'] = df['amount'].cumsum()
print(df)
运行后可以看到,total 列依次变为 100、300、450、500。这种写法完全基于底层的 C 实现,比用 for 循环遍历每一行并不断加总要快得多,而且代码可读性很强。
需要注意的是,如果 amount 列中存在缺失值(NaN),cumsum 默认会忽略 NaN 并继续累加其他有效值。例如某天数据缺失,累加和会沿用上一次的有效和,不会中断。如果你希望缺失值传播到后续累加结果中,可以先使用 fillna 方法做填充策略处理。
在分组内计算累加和
实际业务中更常见的场景是:数据包含多个类别,需要分别在每个类别内部做累加。比如不同门店各自的累计销售额,而不是所有门店混在一起算。此时可以结合 groupby 与 cumsum 实现。
我们扩展前面的例子,加入门店编号,然后按门店分组累加:
import pandas as pd
data = {
'store': ['A', 'A', 'B', 'B', 'A'],
'amount': [100, 200, 150, 50, 80]
}
df = pd.DataFrame(data)
# 每个门店分组内的累加和
df['store_total'] = df.groupby('store')['amount'].cumsum()
print(df)
输出中,store 为 A 的记录累加顺序是 100、300、380,而 B 的记录独立累加为 150、200。groupby 的 cumsum 会保持原数据框的行顺序,仅在数学逻辑上划分组边界,因此新列可以直接对齐原表,不需要再做合并操作。
这种分组累加的方式避免了先拆分再拼接的繁琐过程,也降低了出错概率。如果分组后还需按时间排序累加,建议先使用 sort_values 对时间和分组列排序,否则累加顺序可能不符合业务含义。
处理缺失值与自定义初始值
虽然 cumsum 能自动跳过 NaN,但有时候业务要求空缺当天视为 0,或者希望从一个基础库存开始累加。这时我们可以在累加前显式填充,或者使用算术加法调整起始点。
以下代码演示了用 fillna 将缺失金额视为 0,并从初始库存 500 开始计算可用总量:
import pandas as pd
import numpy as np
data = {'amount': [100, np.nan, 150, 50]}
df = pd.DataFrame(data)
# 缺失视为 0,并从 500 开始累加
base = 500
df['total'] = base + df['amount'].fillna(0).cumsum()
print(df)
经过 fillna(0) 之后,第二行的 NaN 不再影响后续累加逻辑,total 列结果为 600、600、750、800。通过引入 base 变量,我们轻松实现了带初始偏移的累加列。
如果原始数据含有非数值类型,cumsum 会抛出异常,因此确保待累加列是 int 或 float 类型。可用 astype 强制转换,或在读取数据时通过 dtype 参数指定,防止后续计算失败。
性能与适用边界
向量化的 cumsum 在时间复杂度上等同于单次遍历,空间上仅多出一列存储,非常适合中大规模数据框。相比之下,apply 配合自定义函数虽然灵活,但会带来大量 Python 层开销,在千万行数据上差距明显。
当数据已经位于数据库或分布式环境时,也可以将累加下推到 SQL 的窗口函数(如 sum() over (order by))中完成,但在本地分析场景下,Pandas 的 cumsum 仍是最直观且高效的选择。理解其分组与缺失值行为,能让你在构造新列时少走弯路。