如果您有关注过去一年中 Python DataFrame 的进展,那么您一定听说过 Polars,专为处理大型数据集而设计的强大 DataFrame 库。
与 Spark、Dask 和 Ray 等处理大型数据集的其他库有所不同,Polars 在单台机器上使用,也因此引起许多与 pandas 的比较。事实上,Polars 在许多重要方面都与 pandas 存在差异,包括数据处理方式以及最佳应用。下文将探讨这两种 DataFrame 库的技术细节区别,并分析其各自优点和局限。
如果您想听 Polars 的缔造者 Ritchie Vink 亲口讲述,您可以收看我们对他的采访!
为什么使用 Polars
而不是 pandas?
两个字:性能。Polars 从一开始就速度极快,执行常见运算的速度是 pandas 的 5 到 10 倍。另外,Polars 运算的内存需求明显小于 pandas:pandas 需要数据集大小的 5 到 10 倍左右的 RAM 来执行运算,而 Polars 需要 2 到 4 倍。
您可以在这里了解 Polars 与其他 DataFrame 库的性能对比。对于常见运算,Polars 的速度是 pandas 的 10 到 100 倍,也是最快的 DataFrame 库之一。此外,在内存不足错误之前,它可以处理比 pandas 更大的数据集。
Polars 为什么这么快?
这些结果非常惊人,您可能会想:Polars 是怎么在单台机器上运行时获得这种性能的?因为,库在设计上从一开始就以性能为宗旨,并通过多种方式实现。
以 Rust 编写
关于 Polars 最著名的事实之一是它以 Rust 编写,一种几乎与 C 和 C++ 一样快的低级语言。而 pandas 在 Python 库之上构建,其中之一是 NumPy。虽然 NumPy 的核心是以 C 编写,但它仍然受到 Python 处理内存中某些类型(例如分类数据的字符串)时的固有问题的影响,导致处理这些类型时性能不佳(请参阅 Wes McKinney 的这篇精彩博文,了解更多详细信息)。
使用 Rust 的另一个优点是它允许安全并发,使并行性尽可能可预测。这意味着 Polars 可以安全使用所有机器核心执行涉及多个列的复杂查询,甚至让 Ritchie Vink 将 Polar 的性能描述为“过分并行”。Polars 的性能因此远高于 pandas,pandas 只使用一个核心执行运算。观看今年 PyCon DE 上 Nico Kreiling 的精彩演讲,其中详细说明了 Polars 如何实现这一目标。
基于 Arrow
Polars 惊人性能的另一个因素是Apache Arrow,一种独立于语言的内存格式。Arrow 实际上由 Wes McKinney 参与创建,为了解决他在数据量爆炸式增长时在 pandas 上看到的问题。它也是今年 3 月发布的性能更高的 pandas 版本 pandas 2.0 的后端。不过,库的 Arrow 后端略有不同:虽然 pandas 2.0 基于 PyArrow 构建,但 Polars 团队构建了自己的 Arrow 实现。
在 Arrow 上构建数据库的主要优点之一是互操作性。Arrow 的设计目的是标准化跨库使用的内存数据格式,且已经用于许多重要的库和数据库,如下图所示。
这种互操作性可以提高性能,因为它避开了将数据转换为不同格式以在数据管道的不同步骤之间传递的需要(换句话说,它避免了对数据进行序列化和反序列化的需要)。它还具有更高的内存效率,因为两个进程可以共享相同的数据,无需创建副本。据估计,序列化/反序列化占数据工作流中 80-90% 的计算开销,Arrow 的通用数据格式为 Polars 带来了显著性能提升。
Arrow 还具有比 pandas 更广泛的数据类型内置支持。由于 pandas 基于 NumPy,它在处理整数和浮点列方面非常出色,但难以应对其他数据类型。 相比之下,Arrow 对日期时间、布尔值、二进制甚至复杂的列类型(例如包含列表的列类型)提供了复杂的支持。另外,Arrow 能够原生处理缺失数据,这在 NumPy 中需要额外步骤。
最后,Arrow 使用列式数据存储,无论数据类型如何,所有列都存储在连续内存块中。这不仅使并行更容易,也使数据检索更快。
查询优化
Polars 性能的另一个核心是评估代码的方式。Pandas 默认使用 Eager 执行,按照编写的顺序执行运算。 相比之下,Polars 能够同时执行 Eager 和惰性执行,查询优化器将对所有必需运算求值并制定最有效的代码执行方式。这可能包括重写运算的执行顺序或删除冗余计算。例如,使用以下表达式获取Category
中每个类别“A”和“B”的Number1
列的平均值。
(
df
.groupby(by = "Category").agg(pl.col("Number1").mean())
.filter(pl.col("Category").is_in(["A", "B"]))
)
如果表达式 Eager 执行,则会多余地对整个 DataFrame 执行groupby
运算,然后按Category
筛选。通过惰性执行,DataFrame 会经过筛选,并仅对所需数据执行groupby
。
表达性 API
最后,Polars 拥有一个极具表达性的 API,基本上您想执行的任何运算都可以用 Polars 方法表达。相比之下,pandas 中更复杂的运算通常需要作为 lambda 表达式传递给apply
方法。apply
方法的问题是它循环遍历 DataFrame 的行,对每一行按顺序执行运算。内置方法能够让您在列级别上工作并利用另一种称为 SIMD 的并行形式。
什么时候应该继续使用 pandas?
这一切看起来都棒极了,您可能现在就想抛弃 pandas。先别急!虽然 Polars 非常适合进行极其高效的数据转换,但它目前并不是数据探索或机器学习管道的最佳选择。 这些都是 pandas 可以继续崭露头角的领域。
其中一个原因是,虽然 Polars 与其他使用 Arrow 的软件包具有良好的互操作性,但它尚不兼容大多数 Python 数据可视化软件包或机器学习库,例如 scikit-learn 和 PyTorch。 唯一的例外是 Plotly,它允许您直接从 Polars DataFrame 创建图表。
当前讨论较多的解决方案是在这些软件包中使用 Python DataFrame 交换协议,以允许其支持一系列 DataFrame 库,让数据科学和机器学习工作流不再遭遇 pandas 的瓶颈。不过,这个想法相对较新,项目距离实现还有一段时间。
Polars 和 pandas 的工具
想亲自尝试 Polars 了吗?DataSpell 和 PyCharm Professional 2023.2 都提供了出色的工具,可在 Jupyter Notebook 中处理 pandas 和 Polars。特别是,pandas 和 Polars DataFrame 以交互功能显示,使数据探索更快、更舒适。
我最喜欢的功能包括 DataFrame 所有行和列的不截断滚动浏览、DataFrame 值聚合的一键获取以及 DataFrame 的多种格式导出(包括 Markdown!)。
如果您还没有使用 DataSpell,可以立即开始 30 天试用。
本博文英文原作者:Jodie Burchell
DataSpell 相关阅读
⏬ 戳「阅读原文」了解更多
本文分享自微信公众号 - JetBrains(JetBrainsChina)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。