Data Science

Polars 与 pandas:差异对比

Read this post in other languages:

如果您有关注过去一年中 Python DataFrame 的进展,那么您一定听说过 Polars,专为处理大型数据集而设计的强大 DataFrame 库。

与 SparkDask 和 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。主要原因是 pandas 与整个 Python 数据科学生态系统一起成长,仍然与构成机器学习流水线的其他软件包具有最大的互操作性。

然而,Polars 正在迎头赶上,它与这些软件包的互操作性逐月增长。Polars 现在兼容多种绘图库,包括 plotlymatplotlib(使用 Series 时除外)、seabornaltairhvplot,这意味着它现在作为探索性数据分析库也很好用。

现在还可以将 Polars DataFrame 用作机器学习和深度学习流水线的一部分。从 scikit-learn 版本 1.4.0 开始,可以将 transformers 输出为 Polars DataFrames。现在还可以将 Polars DataFrames 转换为 PyTorch 数据类型,包括 PyTorch Tensor、PolarsDataset(框架专用的 TensorDataset)或 Tensors 字典。这可以在 Polars 中通过对 DataFrame 调用 to_torch 方法来实现。

所有这些都是开源项目,维护者投入了大量时间来使这些包协同工作。非常感谢所有参与这些项目的人——这无疑是数据科学领域激动人心的时刻!(此外,还要感谢 Ritchie Vink 强调这些进步!)

Polars 和 pandas 的工具

读完这些,我相信您一定迫不及待地想要亲自尝试一下 Polars!适用于数据科学的 PyCharm Professional 为在 Jupyter Notebook 中使用 pandas 和 Polars 提供了出色的工具。特别是 pandas 和 Polars DataFrames 具有交互功能,使您能够更快、更舒适地探索数据。

我最喜欢的一些功能包括能够滚动浏览 DataFrame 的所有行和列而不会截断、一键获取 DataFrame 值的聚合和以多种格式(包括 Markdown!)导出 DataFrame。

如果您尚未使用 PyCharm,可以通过以下链接试用 30 天。

开始 PyCharm Pro 免费试用

本博文英文原作者:

image description