在数据分析的日常工作中,我们经常需要处理随时间变化的数据,比如计算累计销售额、追踪库存变化,或者分析用户增长趋势。这时候,单纯的求和往往无法反映事物的动态过程,而“累积求和”才是理解数据背后故事的关键。
Python 的 Pandas 库为我们提供了一个非常强大且高效的方法——dataframe.cumsum()。今天,我们将一起深入探索这个函数的奥秘,不仅学习它的基本用法,还会探讨它如何处理缺失值,以及如何在真实的业务场景中发挥巨大作用。无论你是数据分析的初学者,还是希望巩固基础的开发者,这篇文章都将帮助你更透彻地掌握累积求和的技巧。
什么是累积求和?
在开始写代码之前,让我们先统一一下概念。所谓的“累积求和”,就是序列中从第一个元素开始,逐个将当前元素加到之前的总和上。
举个例子:
- 原始数据:
[10, 20, 30, 40] - 普通求和:
100(所有数字加在一起) - 累积求和:
[10, 30, 60, 100](第一个数是10,第二个是10+20,第三个是10+20+30…)
在 Pandas 中,我们使用 cumsum() 方法来实现这一过程。它会返回一个与原 DataFrame 形状相同的新对象,但其中的每一个单元格都包含了该位置之前所有数值的累计总和。
语法与参数详解
让我们先来看看 DataFrame.cumsum() 的标准语法,了解我们能控制哪些参数。
> 语法: DataFrame.cumsum(axis=None, skipna=True, *args, **kwargs)
为了让你用起来更得心应手,我们详细解读一下这些参数:
-
axis(轴方向)
* 这是我们在处理多维数据时最常用的参数。
* INLINECODEf3d5ed25 或 INLINECODEf04741c0:这是默认值。表示沿着行(从上往下)进行计算。想象一下,你在按时间顺序记录每一天的数据,这个参数就是让你算“截至到这一天”的总和。
* INLINECODE5c9112b1 或 INLINECODEbf77ba78:表示沿着列(从左往右)进行计算。这常用于横向统计,比如算出每个学生的总分。
-
skipna(排除缺失值)
* 布尔值,默认为 True。这体现了 Pandas 处理脏数据的智慧。
* 当设为 INLINECODEef408ea2 时,计算会自动跳过 INLINECODE25598850(空值)。比如 INLINECODEe9eef233 的 cumsum 会视为 INLINECODE674986ff,即把空值当成不存在,继续累加。
* 当设为 INLINECODE64eae720 时,一旦遇到 INLINECODE197f5ce3,之后的所有结果都会变成 NaN。
args, kwargs
* 这些通常是为了兼容 NumPy 的函数而保留的,在实际使用中我们很少直接手动传递这些参数。
准备工作:引入 Pandas
在我们的所有示例开始之前,别忘了先导入 Pandas 库并准备好数据。如果你还没有安装,可以使用 pip install pandas 进行安装。
# 导入 pandas 并简写为 pd,这是业界的标准惯例
import pandas as pd
import numpy as np # 为了生成 NaN 数据,我们也顺便导入 numpy
示例 #1:沿 Index 轴(行)计算累积和
这是最常见的场景。想象我们有一张记录了四个季度不同部门(A、B、C、D)业绩的表格。我们想知道每个部门随着时间推移,业绩是如何累积增长的。
让我们创建一个 DataFrame:
# 创建示例数据框
df = pd.DataFrame({
"A": [5, 3, 6, 4],
"B": [11, 2, 4, 3],
"C": [4, 3, 8, 5],
"D": [5, 4, 2, 8]
}, index=["Q1", "Q2", "Q3", "Q4"]) # 为了直观,我们给行加了季度标签
print("原始业绩数据:")
print(df)
输出结果:
A B C D
Q1 5 11 4 5
Q2 3 2 3 4
Q3 6 4 8 2
Q4 4 3 5 8
现在,我们要计算每个列(部门)的累积业绩。这里我们显式地指定 axis=0(其实这也是默认值,写出来是为了让大家看清楚)。
# 沿着 index 轴(向下)计算累积和
cumsum_df = df.cumsum(axis=0)
print("
累积业绩数据 (axis=0):")
print(cumsum_df)
输出结果:
A B C D
Q1 5 11 4 5
Q2 8 13 7 9
Q3 14 17 15 11
Q4 18 20 20 19
发生了什么?
让我们看 "A" 列:
- Q1 是 5。
- Q2 是 5 (上一行) + 3 (当前行) = 8。
- Q3 是 8 (上一行累积) + 6 (当前行) = 14。
- Q4 是 14 + 4 = 18。
这就是累积求和的直观展示。每一行都代表了“从过去到现在”的总和。
示例 #2:沿 Column 轴(列)计算累积和
有时候,数据是横向排列的。假设上面的表格每一行代表一个项目,A、B、C、D 代表该项目在不同阶段的投入。我们想知道每个项目(每一行)的总投入进度。
这次我们使用 axis=1:
# 沿着 column 轴(向右)计算累积和
row_cumsum = df.cumsum(axis=1)
print("
项目阶段累积投入 (axis=1):")
print(row_cumsum)
输出结果:
A B C D
Q1 5 16 20 25
Q2 3 5 8 12
Q3 6 10 18 20
Q4 4 7 12 20
让我们分析一下 Q1 这一行:
- A 列:保持原样 5。
- B 列:5 (A) + 11 (B) = 16。
- C 列:16 (之前累积) + 4 (C) = 20。
- D 列:20 + 5 = 25。
实战提示: 这种用法在计算学生成绩(平时分+期中+期末)或者财务报表(季度1+季度2+…)时非常有用,能让你一眼看出每一步的总量。
示例 #3:处理缺失值
现实世界的数据往往是不完美的。NaN (Not a Number) 到处都是。如果不正确处理,它们会打断你的计算链。
让我们创建一个包含 None 值的新 DataFrame:
# 包含缺失值的数据框
df_nan = pd.DataFrame({
"A": [5, 3, None, 4],
"B": [None, 2, 4, 3],
"C": [4, 3, 8, 5],
"D": [5, 4, 2, None]
})
print("
包含 NaN 的原始数据:")
print(df_nan)
默认情况:
# 使用 skipna=True (默认值)
result_skip = df_nan.cumsum(axis=0)
print("
默认跳过 NaN 的累积结果:")
print(result_skip)
输出结果:
A B C D
0 5.0 NaN 4.0 5.0
1 8.0 2.0 7.0 9.0 <-- 注意 B 列:忽略了上面的 NaN,从 2 开始,上一行视为0参与运算(如果是第一行则保留NaN)
2 NaN 6.0 15.0 11.0 <-- 注意 A 列:因为第一行是 5,第二行是 8,但第三行是 NaN,所以结果直接是 NaN
3 12.0 9.0 20.0 NaN <-- D 列最后也是 NaN
重要细节: 请注意 skipna=True 的行为。
- 第0行的
B是 NaN,结果保留 NaN。 - 第1行的 INLINECODEd2cbcdff 是 2。计算时,Pandas 实际上是将 INLINECODEc907bc25 视为 0 参与了当次累加(或者理解为忽略空值,只累加有效值),得到 2.0。然后第2行是 4,累加得到 6.0。
- 但是,对于 A 列的第2行(值为 NaN),因为它之前有值(8.0),遇到 NaN 时,默认的 INLINECODE38f6e90a 行为通常不会简单地忽略它而延续旧值,而是直接返回 NaN。这就是 INLINECODE6ea9d20f 在累积运算中的特性:只要有 NaN 参与运算的那一步,结果通常就是 NaN(除非它作为序列的开头被跳过)。
如果不跳过 NaN (skipna=False):
# 不跳过 NaN
result_noskip = df_nan.cumsum(axis=0, skipna=False)
print("
保留 NaN (不跳过) 的累积结果:")
print(result_noskip)
输出结果:
A B C D
0 5.0 NaN 4.0 5.0
1 8.0 NaN 7.0 9.0 <-- B 列依然是 NaN,因为起始就是 NaN
2 NaN NaN 15.0 11.0 <-- A 列变成 NaN,并且之后一直是 NaN
3 NaN NaN 20.0 NaN
可以看到,一旦某列出现了 NaN,整条链就会断裂,之后的结果全变成了 NaN。这种模式下,数据非常“干净”地告诉我们:这里有问题数据。
进阶应用:复利增长与百分比变化
cumsum 最迷人的地方在于它能和其他 Pandas 操作无缝结合。比如,我们不仅能求和,还能模拟复利增长。
假设我们要计算一个投资组合在连续 4 天内的累计收益。
# 每日收益率数据 (例如 0.01 代表 1%)
returns = pd.DataFrame({
"Stock": [0.01, 0.02, -0.01, 0.03],
"Bond": [0.005, 0.01, 0.01, 0.005]
})
print("每日收益率:")
print(returns)
# 我们想要计算“累计收益率”。在金融领域,这通常是 (1+r1)*(1+r2)... - 1
# 但 cumsum 直接加通常用于简单的利息累加。
# 这里演示如何结合 apply 进行复杂的累加
# 假设我们想看简单的收益累加(非复利)
print("
简单收益累加:")
print(returns.cumsum())
# 如果想看复利效果 (通常不直接用 cumsum,而是 cumprod,但我们可以演示逻辑)
# 在此我们仅展示 cumsum 在“增长额”上的应用
# 假设本金是 10000,每日盈亏是 Stock * 10000
principal = 10000
daily_pnl = returns * principal
print("
每日盈亏额:")
print(daily_pnl)
print("
账户余额变化 (cumsum):")
print(daily_pnl.cumsum())
常见错误与性能优化建议
在使用 cumsum() 时,有几个坑是你可能会遇到的,作为经验之谈,我分享给大家:
- 原地修改的误区
cumsum() 默认返回一个新的对象,它不会直接修改原始的 DataFrame。这意味着你需要把结果赋值给一个变量,否则你的计算就“丢失”了。
错误做法:* df.cumsum() 然后什么都不做。
正确做法:* df_cumsum = df.cumsum()
- 数据类型溢出
虽然在 Pandas 中 INLINECODE21f7f02f 很大,但在累积非常大的数值时,依然要注意溢出问题。如果你的数据是标准的 INLINECODE56e565d8,累加可能会溢出。Pandas 通常会自动将其提升为 INLINECODEe2f3fbb4 或 INLINECODEa619afe5,但如果你在使用 NumPy 数组或其他底层类型,需要留意这一点。
- 处理大数据集
如果你有一个超大的 DataFrame(比如数百万行),INLINECODE1ebffb3b 操作是非常快的(它是 C 级优化的)。但是,如果你的内存有限,尝试在循环中反复对切片进行 INLINECODEe56b3b0b 会非常慢。尽量向量化操作,一次性对整个 DataFrame 调用 INLINECODE2318a81e,而不是使用 INLINECODE7838b1e2 循环逐行处理。
关键要点与总结
在这篇文章中,我们通过几个具体的例子,从零开始掌握了 Pandas 的 cumsum() 函数。让我们快速回顾一下核心要点:
- 核心功能:它用于计算沿指定轴的累积和。
- 方向控制:记住 INLINECODEe6042b56 是“向下”算(行累积),INLINECODE1af19272 是“向右”算(列累积)。
- 缺失值处理:默认情况下 Pandas 很聪明,会尝试跳过 INLINECODE11ec512f,但要注意这可能会导致结果中出现 INLINECODEc564d629。如果你需要严格的连续性,可能需要先用
fillna()填充数据。 - 实战价值:从计算销售KPI到分析用户留存路径,累积求和是理解数据随时间演变的最基础工具。
接下来你可以尝试:
不要只停留在练习数据上。找一份你自己的业务数据——无论是你的个人记账记录,还是网站访问日志——试着用 cumsum() 绘制一张累积增长曲线图。你会发现,这种视角往往比单纯看当月数据更能洞察长期趋势。
祝你在数据分析的旅程中收获满满!