Chapter 01

Pandas 2.x 新特性概述

Copy-on-Write、PyArrow 后端、新式 NA 处理——理解 2.x 的设计哲学转变

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 版本持续完善,带来了三项核心变化:

ℹ️
版本说明 本教程基于 Pandas 2.2.x 编写。Pandas 2.0 引入了 Copy-on-Write 作为可选特性,2.1 完善了 PyArrow 后端,2.2 将 CoW 设为默认行为(通过 future 警告提示),并计划在 Pandas 3.0 中彻底强制启用 CoW。建议使用 Pandas 2.2+ 以跟随本教程。

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 本身
⚠️
迁移注意事项 如果你的代码依赖 1.x 的"链式赋值"模式(如 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 绑定)提供了与这一生态的深度集成。

# 使用 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())   # 通常更小
💡
何时使用 PyArrow 后端? 处理包含大量字符串列或可空整数列的数据集时,PyArrow 后端能带来显著的内存节省(通常 30%-60%)。如果你的数据需要与 Parquet、Arrow IPC、DuckDB 等工具交互,PyArrow 后端还可以避免格式转换的开销。

新式 NA 处理:统一缺失值语义

这是 Pandas 历史遗留问题中最复杂的一个。在旧版 Pandas 中,缺失值的表示方式取决于列的数据类型:

旧版行为数据类型缺失值表示问题
float64浮点型NaN(IEEE 754)NaN 的布尔运算行为特殊
object字符串/混合NaN 或 None两种表示混用,不一致
int64整数无法原生支持,会升级为 float64类型自动变更,精度损失
bool布尔无法原生支持

Pandas 2.x 引入了 Nullable 扩展类型Int64Float64booleanstring)和统一的 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 参数默认值变化
  • 索引操作的行为更一致
  • 类型推断规则统一
🚫
从 1.x 迁移时的重大 Breaking Changes
  • 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,理解它们的内部工作原理是掌握所有后续操作的基础。