pandas PDEP-10 解读:PyArrow 如何成为 pandas 3.0 默认字符串推断实现的基础依赖
【免费下载链接】pandasFlexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more项目地址: https://gitcode.com/gh_mirrors/pa/pandas
导读
本文围绕 pandas 项目中的设计提案 PDEP-10("PyArrow as a required dependency for default string inference implementation")展开,系统梳理 pandas 将 PyArrow 从可选依赖推向默认依赖的完整决策过程:从版本演进时间线、三条即时用户收益、未来收益与代价权衡,到 FAQ 中关于默认推断边界的澄清,并结合当前仓库源码(如 pandas/compat/_optional.py、pyproject.toml、pandas/core/config_init.py)验证提案的最终落地形态。读完本文,你将理解 pandas 3.0 中字符串数据默认推断为str/string[pyarrow]类型背后的设计动机、性能依据与取舍逻辑,以及如何在代码中提前适配这一行为变更。
一、PDEP-10 提案概览
PDEP-10 是 pandas 官方设计提案(Pandas Enhancement Proposal)系列中的第 10 号提案,于2023 年 4 月 17 日创建,状态为Accepted(已接受),由 Matthew Roeschke 与 Patrick Hoefler 共同撰写,完整原文位于 web/pandas/pdeps/0010-required-pyarrow-dependency.md。
该提案的核心诉求可以浓缩为四条:
- PyArrow 自 pandas 3.0 起成为必需(required)的运行时依赖;
- pandas 3.0 支持的 PyArrow 最低版本为 7;
- 当最低版本被提升时,遵循"提升到已发布至少 2 年的最高版本"的版本策略;
- 从 pandas 3.0 起,字符串数据的默认推断类型从
object变为ArrowDtype(底层为pyarrow.string),并同步推断下文列出的其他数据类型,而不再一律存为object。
重要前置说明:硬依赖已被推迟
PDEP-10 文档开篇有一条醒目的 note(需特别关注):虽然本提案最初计划在 pandas 3.0 中将 PyArrow 列为必需依赖,但这一"硬依赖"目标已被推迟到 pandas 3.0 之后(详见 PDEP-14 的摘要)。因此实际状态是:
pandas 3.0 不会对 PyArrow 构成硬性要求,但当环境中安装了 PyArrow 时,会默认使用它(用于新的字符串 dtype)。
也就是说,"必须安装"降级为"装了就用、默认推荐",这一调整主要回应了社区对安装体积与复杂度的反馈。
渐进式迁移时间线
PDEP-10 规划了一条清晰的警告与迁移路径:
| 版本节点 | 动作 |
|---|---|
| pandas 2.1 | 发布说明中给出大字号警告:PyArrow 将在 pandas 3.0 成为必需依赖,并固定一个反馈 issue,注释指向该 issue |
| pandas 2.2 | 当环境中未安装 PyArrow 时,导入 pandas 抛出一次FutureWarning(保证只警告一次、可轻松静默),警告同样指向反馈 issue |
| pandas 3.0 | 字符串数据默认推断为 PyArrow 支撑的string[pyarrow],而非object;同时默认推断下述其他数据类型 |
二、Background:PyArrow 与 pandas 的集成编年史
PDEP-10 用一段清晰的版本时间线说明了 PyArrow 早已深度嵌入 pandas 的方方面面:
- pandas 0.21.0:PyArrow 提供 Parquet 的 I/O 读取功能;
- pandas 1.2.0:pandas 将 PyArrow 集成进
ExtensionArray接口,提供由 PyArrow 支撑的可选字符串数据类型; - pandas 1.4.0:PyArrow 提供 CSV 的 I/O 读取功能;
- pandas 1.5.0:pandas 提供
ArrowExtensionArray与ArrowDtype,在ExtensionArray接口内支持全部 PyArrow 数据类型; - pandas 2.0.0:所有 I/O 读取器都支持返回 PyArrow 支撑的数据类型,大量方法开始利用 PyArrow 的 compute 函数加速 PyArrow 支撑的数据(尤其是字符串与日期时间类型)。
截至 pandas 2.0,用户已经可以切实地把 PyArrow 作为 NumPy 的替代数据表示使用,其优势包括:
- 所有数据类型都有一致的
NA缺失值支持; - 更广泛的数据类型支持,例如
decimal、date以及嵌套类型(list、struct 等); - 与其他基于 Arrow 的 dataframe 库具有更好的互操作性。
这些能力在当时都是"可选"的——用户需要显式指定才会生效。PDEP-10 的核心主张就是:既然集成已如此深入,不如顺势而为。
三、动机:为 Arrow 生态投出信任票
pandas 官方路线图明确写有"更好的 Apache Arrow 互操作性"这一长期目标(参见 doc/source/development/roadmap.rst 相关章节),且 Python 生态内外已有大量项目在采用或交互 Arrow 格式。在此背景下,把 PyArrow 提升为必需依赖,本质上是 pandas对 Arrow 生态的一次表态——既增强了 pandas 与其他 Arrow 系库互操作的信心,也简化了 pandas 内部围绕 Arrow 的开发路径。
四、三大即时用户收益
4.1 收益一:PyArrow 字符串,终结objectdtype 的性能灾难
现状是:当用户向 pandas 构造函数传入字符串数据且不指定 dtype 时,结果类型是object。而object类型相比 PyArrow 字符串,内存占用与性能都差得多:
In [1]: import pandas as pd In [2]: pd.Series(["a"]).dtype # 当前行为 Out[2]: dtype('O') # pandas 3.0 中的未来行为 Out[2]: string[pyarrow]PDEP-10 内附了一个百万级字符串的性能演示脚本:
import string import random import pandas as pd def random_string() -> str: return "".join(random.choices(string.printable, k=random.randint(10, 100))) ser_object = pd.Series([random_string() for _ in range(1_000_000)]) ser_string = ser_object.astype("string[pyarrow]")文档中记录的实测数据(PyArrow 字符串显著快于 NumPy object 字符串):
str.len
In[1]: %timeit ser_object.str.len() 118 ms ± 260 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) In[2]: %timeit ser_string.str.len() 24.2 ms ± 187 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)str.startswith
In[3]: %timeit ser_object.str.startswith("a") 136 ms ± 300 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) In[4]: %timeit ser_string.str.startswith("a") 11 ms ± 19.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)以文档数据为参照,str.len提速约5 倍,str.startswith提速约12 倍。PDEP 中引用了 Dask 开发者对 PyArrow 字符串在性能与内存上的调研结论——相较当前objectdtype 是"显著的改进"。这也是整个提案最直接、最有说服力的用户收益。
4.2 收益二:嵌套数据类型(Nested Datatypes)自动推断
当前若把dict存入Series,得到的同样是低效的objectdtype:
In [6]: pd.Series([{'a': 1, 'b': 2}, {'a': 2, 'b': 99}]) Out[6]: 0 {'a': 1, 'b': 2} 1 {'a': 2, 'b': 99} dtype: object如果 PyArrow 成为必需依赖,这类数据本可被自动推断为pyarrow.struct,同样带来内存与性能的双重改善。
4.3 收益三:与其他 Arrow 系 dataframe 库的互操作性
其他 Arrow 支撑的 dataframe 库(如 polars)正在快速增长。若 pandas 与它们共享同一套内存表示,那么类似下面的转换将可以做到zero-copy(零拷贝):
import pandas as pd import polars as pl df = pd.DataFrame( { "a": ["one", "two"], "b": [{"name": "Billy", "age": 3}, {"name": "Bob", "age": 4}], } ) pl.from_pandas(df)同时使用多个 dataframe 库的用户,将能更轻松地在它们之间切换。
五、未来收益:让每个 dtype 都有 PyArrow 等价物
5.1 用户侧:全面摆脱object
要求 PyArrow 将简化 pandas 内部相关开发,并潜在改善更适合由 PyArrow 承担的功能,包括:
- 在构造函数或索引操作期间,避免运行时检查 PyArrow 是否可用来执行 PyArrow 对象推断;
- 尽可能规避 NumPy
objectdtype:所有存在 PyArrow 等价物的 dtype 都会被自动推断为 Arrow 类型,覆盖范围包括:decimalbinary- 嵌套类型(list 或 dict 数据)
- 字符串(strings)
timedate
5.2 开发者侧:砍掉冗余功能
对 pandas 自身开发而言:
- 简化 PyArrow 支撑数据类型的开发——不再需要遍布各处的可选依赖检查;
- 潜在移除冗余功能,包括:
read_parquet中的 fastparquet 引擎;- 潜在的
read_csv逻辑简化(需更多调研); - factorization;
- datetime/timezone 运算。
六、代价与权衡(Drawbacks)
PDEP-10 对反面意见同样开诚布公:
- 安装体积显著增加:以 pip wheel 安装为例,pandas + NumPy 约需70MB,而加入 PyArrow 需再增加约120MB。对 AWS Lambda 这类空间受限的开发/部署环境会产生负面影响。
- 无 wheel 环境需要从源码构建:在无法通过
pip install或conda install获取 wheel 的环境中,用户安装 pandas 时还需自行构建 Arrow C++ 及其依赖,典型场景包括:- Alpine Linux(常被用作 Docker 容器基础镜像);
- Python 开发版(未发布的 Python 版本)。
- 发布节奏耦合:pandas 的开发和发布需要关注 PyArrow 的发布节奏。例如,pandas 支持某个新发布的 Python 版本时,需要先确认 PyArrow 是否已为该 Python 版本提供 wheel,才能发布新的 pandas 版本。
七、FAQ:默认推断的边界到底在哪
Q1:为什么不用 NumPy 的 string 和 void 数据类型,而非得用 PyArrow?
- NumPy 字符串尚不可用,而 PyArrow 字符串已经就绪;
- NumPy 的
voiddtype 与 PyArrow 的struct语义不同,无法带来与其他 Arrow 系 dataframe 库相同的互操作性收益。
Q2:所有 PyArrow dtype 都准备好了吗?现在设为默认是不是太早?
PDEP-10 明确回应:到 3.0 它们大概率已就绪,但并不会全部设为默认。例如pd.Series([1, 2, 3])仍会被自动推断为np.int64。默认推断只会改变那些当前没有 NumPy 等价物、且以objectdtype 存储的类型——例如字符串和嵌套数据类型。这一边界至关重要:它保证了数值数据的行为不发生任何变化,迁移冲击面被控制在最小范围。
八、源码落地验证:当前仓库中的实现证据
PDEP-10 是设计文档,其结论需要在代码中兑现。对照当前仓库源码,可以从三个层面看到这条演进路线的实际落地情况:
8.1 最低版本策略的演进:7 → 16
PDEP-10 原始提案规定 pandas 3.0 的 PyArrow 最低版本为 7。而在当前仓库中,可选依赖版本清单 pandas/compat/_optional.py 记录的pyarrow最低版本已是"16.0.0",说明随着 PyArrow 自身的迭代,最低版本要求已按"至少发布 2 年"的策略持续抬升。该版本在 pyproject.toml 中同样有约束:pyarrow = ['pyarrow>=16.0.0']作为主 extra 出现,而parquet、feather两个 extra 则要求pyarrow>=13.0.0。
8.2future.infer_string选项:从 opt-in 到默认
PDEP-14 进一步落实了字符串 dtype 的默认化路径:在 pandas 2.x 时代,用户需显式开启pd.options.future.infer_string = True来预览未来行为。而在当前仓库 pandas/core/config_init.py 中,该选项的注册信息已经变为:
legacy=False、default=True、upcoming=True;- 文档字符串明确写着:"自 pandas 3.0 起,将字符串序列推断为 str dtype 而非 object dtype 已成为默认行为";
- 该选项已被
Pandas4Warning标记为弃用,并注明"将在 pandas 4.0 移除,届时 str dtype 将始终被推断"。
这正好印证了 PDEP-10 → PDEP-14 的接力:pandas 3.0 中字符串推断默认开启,且不可回退。
8.3StringDtype的 storage 与 na_value 双维度
当前 pandas/core/arrays/string_.py 中的StringDtype实现了 PDEP-14 提出的命名方案:storage参数("python"或"pyarrow")决定底层存储,na_value参数(np.nan或pd.NA)区分缺失值语义。这保证了"装没装 PyArrow"都能提供行为一致的字符串 dtype——正是 PDEP-10 关于硬依赖被推迟后留下的 fallback 设计。
8.4 构造与 I/O 层面的 PyArrow 推断
在 pandas/core/dtypes/cast.py 中,dtype_backend参数支持"numpy_nullable"与"pyarrow"两种取值:当指定"pyarrow"时,推断逻辑会通过to_pyarrow_type将基础 dtype 映射为对应的 Arrow 类型(如ArrowDtype(pa.string()),见 pandas/_libs/parsers.pyx)。这正是 PDEP-10 所描述"每个有 PyArrow 等价物的 dtype 都被推断为 Arrow 类型"在解析器层面的实现雏形。
九、与 PDEP-14 的演进关系:从"硬依赖"到"默认启用"
了解完 PDEP-10 后,有必要厘清它与后续提案的关系,避免产生版本认知偏差:
- **PDEP-10(本文主题)**主张 PyArrow 成为硬依赖,并默认推断
string[pyarrow]; - **PDEP-14(web/pandas/pdeps/0014-string-dtype.md)**在社区反馈(安装复杂度、体积)与 NumPy 2.0 原生字符串类型出现的新背景下,将方案修正为:pandas 3.0 启用名为
"str"的默认字符串 dtype,优先使用 PyArrow(若已安装),否则回退到基于 NumPy object 的等价实现,缺失值语义与其余默认 dtype 保持一致(使用NaN)。
因此,站在 pandas 3.0 的视角,最准确的表述是:字符串默认推断为专用 str dtype;装了 PyArrow 就用 PyArrow 加速,没装也有功能等价的回退路径。PyArrow 的实际角色从"强制依赖"演进为"默认推荐依赖"。
十、结语
PDEP-10 是一份兼具技术深度与生态视野的设计文档:它以性能实测(字符串方法最高约 12 倍提速)论证了 PyArrow 字符串对用户的即时价值,以嵌套类型推断与零拷贝互操作展望了更长远的收益,同时也坦诚量化了安装体积、源码构建与发布节奏三大代价。虽然"硬依赖"最终被推迟,但它的核心目标——让 pandas 3.0 默认使用 PyArrow 支撑的高效字符串 dtype——已在后续 PDEP-14 与当前仓库源码中兑现。对于 pandas 用户而言,现在就可以通过pd.options.future.infer_string = True提前验证 3.0 行为,并在升级到 3.0 后享受strdtype 带来的性能与互操作性红利。
【免费下载链接】pandasFlexible and powerful data analysis / manipulation library for Python, providing labeled data structures similar to R data.frame objects, statistical functions, and much more项目地址: https://gitcode.com/gh_mirrors/pa/pandas
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考