Pandas 的历史与 2.x 里程碑
Pandas(Panel Data + Python 的组合词)由 Wes McKinney 于 2008 年在 AQR Capital Management 开发,最初用于处理金融时间序列数据。2009 年开源后迅速成为 Python 数据科学生态的核心工具,与 NumPy、Matplotlib、Scikit-learn 共同构成所谓"数据科学四件套"。
在很长一段时间里,Pandas 的版本迭代主要是 API 扩展和性能小幅优化,底层架构变化不大。然而 2023 年 4 月发布的 Pandas 2.0 是一次真正意义上的重大重构,随后的 2.1、2.2 版本持续完善,带来了三项核心变化:
- Copy-on-Write (CoW):彻底解决了困扰 Pandas 用户多年的"视图 vs 拷贝"歧义问题
- PyArrow 后端:提供与 Apache Arrow 内存格式兼容的列式存储,大幅降低内存占用
- 新式 NA 处理:统一了不同数据类型下缺失值的行为,引入
pd.NA作为通用缺失值标记
Copy-on-Write:解决最大的历史遗留问题
理解 Copy-on-Write 需要先知道 Pandas 1.x 中最令人头疼的问题:SettingWithCopyWarning。
在 Pandas 1.x 中,对 DataFrame 进行切片操作时,结果有时是原始数据的"视图"(view,共享内存),有时是"拷贝"(copy,独立内存)。这取决于操作的具体方式和底层 NumPy 数组的内存布局,行为难以预测:
# Pandas 1.x 中的歧义问题示例
import pandas as pd
df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
# 这会触发 SettingWithCopyWarning——修改是否影响 df?不确定!
subset = df[df['A'] > 1]
subset['B'] = 99 # Warning: 可能是视图,也可能是拷贝
CoW 的核心语义
Copy-on-Write 的规则非常简单且一致:任何 Pandas 操作产生的结果都是独立的对象,不会影响原始数据;只有当你尝试修改某个对象时,才会实际创建数据副本。
# Pandas 2.x(CoW 启用后)的行为
import pandas as pd
df = pd.DataFrame({'A': [1, 2, 3], 'B': [4, 5, 6]})
subset = df[df['A'] > 1]
subset['B'] = 99 # 修改 subset,不影响 df
print(df) # df 保持不变
print(subset) # subset.B = [99, 99]
# 正确的修改原 df 的方式
df.loc[df['A'] > 1, 'B'] = 99 # 直接修改 df 本身
df[cond]['col'] = val),在 CoW 下这不再起作用。你需要将其改写为 df.loc[cond, 'col'] = val 的形式。这是迁移到 2.x 时最常见的需要修改的模式。
CoW 的性能影响
很多人担心 CoW 会导致频繁的数据复制,从而降低性能。实际上,CoW 采用了"惰性复制"策略:只有当你真正修改一个共享内存的对象时,才发生数据复制。如果你只是读取数据(大多数分析操作),内存是共享的,没有额外开销。
从实际测试结果来看,CoW 对大多数典型数据分析工作流的性能影响微乎其微,而代码的可预测性和正确性显著提升。
PyArrow 后端:列式存储的力量
Apache Arrow 是一种跨语言的内存数据格式规范,专为列式分析设计,被 Spark、DuckDB、Polars、Snowflake 等主流数据工具广泛采用。Pandas 2.x 通过 PyArrow(Arrow 的 Python 绑定)提供了与这一生态的深度集成。
- NumPy 后端(默认) 传统 Pandas 使用 NumPy 数组存储数据。优点是 NumPy 生态成熟;缺点是某些类型(如字符串、nullable 整数)的处理效率较低,内存占用较大。
- PyArrow 后端 使用 Apache Arrow 的 ChunkedArray 存储数据。优点包括:更紧凑的内存表示、原生支持 null 值(包括整数列)、与其他 Arrow 兼容工具零拷贝交换数据;缺点是部分操作的 API 兼容性还在完善中。
# 使用 PyArrow 后端创建 DataFrame
import pandas as pd
# 方式一:全局使用 ArrowDtype
df = pd.DataFrame({
'name': pd.array(['Alice', 'Bob', None], dtype='string[pyarrow]'),
'age': pd.array([25, 30, None], dtype='int64[pyarrow]'),
'score': pd.array([9.5, 8.2, None], dtype='double[pyarrow]'),
})
print(df.dtypes)
# 方式二:读取时指定后端
df2 = pd.read_csv('data.csv', dtype_backend='pyarrow')
# 查看内存占用对比
df_numpy = pd.read_csv('data.csv')
df_arrow = pd.read_csv('data.csv', dtype_backend='pyarrow')
print(df_numpy.memory_usage(deep=True).sum()) # 通常更大
print(df_arrow.memory_usage(deep=True).sum()) # 通常更小
新式 NA 处理:统一缺失值语义
这是 Pandas 历史遗留问题中最复杂的一个。在旧版 Pandas 中,缺失值的表示方式取决于列的数据类型:
| 旧版行为 | 数据类型 | 缺失值表示 | 问题 |
|---|---|---|---|
| float64 | 浮点型 | NaN(IEEE 754) | NaN 的布尔运算行为特殊 |
| object | 字符串/混合 | NaN 或 None | 两种表示混用,不一致 |
| int64 | 整数 | 无法原生支持,会升级为 float64 | 类型自动变更,精度损失 |
| bool | 布尔 | 无法原生支持 | — |
Pandas 2.x 引入了 Nullable 扩展类型(Int64、Float64、boolean、string)和统一的 pd.NA 标记:
import pandas as pd
import numpy as np
# 旧式:整数列有缺失值会变成 float64
s_old = pd.Series([1, 2, None])
print(s_old.dtype) # float64(整数被升级了!)
print(s_old.isna()) # [False, False, True]
# 新式:使用 nullable 整数类型
s_new = pd.Series([1, 2, pd.NA], dtype='Int64') # 注意大写 I
print(s_new.dtype) # Int64
print(s_new.isna()) # [False, False, True]
# pd.NA 的三值逻辑
print(pd.NA or True) # True(短路求值)
print(pd.NA or False) # <NA>(无法确定)
print(pd.NA and False) # False(短路求值)
安装与环境配置
推荐安装方式
# 方式一:使用 pip(推荐带 PyArrow 支持)
pip install "pandas[pyarrow]>=2.2"
# 方式二:使用 conda(自动处理依赖)
conda install -c conda-forge "pandas>=2.2" pyarrow
# 方式三:使用 uv(现代快速包管理器)
uv pip install "pandas[pyarrow]>=2.2"
# 验证安装
python -c "import pandas as pd; print(pd.__version__)"
推荐的完整数据科学环境
# requirements.txt(推荐版本组合)
pandas[pyarrow]>=2.2
numpy>=1.26
pyarrow>=14.0
jupyterlab>=4.0
matplotlib>=3.8
seaborn>=0.13
scikit-learn>=1.4
# 一键安装
pip install pandas[pyarrow] numpy pyarrow jupyterlab matplotlib seaborn
Jupyter 环境配置建议
# 在 Jupyter Notebook 的开头单元格设置
import pandas as pd
import numpy as np
# 显示设置
pd.set_option('display.max_columns', 50)
pd.set_option('display.max_rows', 100)
pd.set_option('display.float_format', '{:.4f}'.format)
pd.set_option('display.width', 1000)
# Pandas 2.x CoW 提前启用(2.2 中默认已启用 future 警告)
# pd.options.mode.copy_on_write = True # 在 2.2 中可选,3.0 后强制
print(f"Pandas {pd.__version__} 已就绪")
Pandas 2.x 其他重要变化速览
性能提升
- groupby 操作平均加速 2-4x
- 字符串操作通过 PyArrow 加速
read_csv解析速度提升- MultiIndex 操作优化
API 清理
- 移除了大量过时的 API(
append()、swaplevel()旧签名等) DataFrame.groupby()的dropna参数默认值变化- 索引操作的行为更一致
- 类型推断规则统一
df.append()已移除,改用pd.concat([df, new_rows])- 整数索引的切片行为更严格
- 部分 resample 的频率字符串已重命名(如
'M'→'ME',代表月末) DataFrame.swapaxes()已废弃
小结
Pandas 2.x 是一次提升代码正确性和性能的重大版本。Copy-on-Write 解决了最令人困惑的行为不一致问题,PyArrow 后端为大数据场景和跨工具协作提供了新的可能,Nullable 类型则统一了缺失值处理的语义。
对于新项目,建议直接从 Pandas 2.2+ 开始,享受这些改进带来的益处。对于已有的 1.x 代码库,迁移的主要工作是将链式赋值模式改写为 loc 赋值,以及更新已移除的 API。
接下来第 2 章将深入讲解 Pandas 最核心的数据结构——Series 和 DataFrame,理解它们的内部工作原理是掌握所有后续操作的基础。