本文转载、翻译自 Ari Lamstein 发表于 2024.9.4 的博客文章 Why I’m Switching to Polars,这篇文章通过比较 Polars 和 Pandas、Tidyverse 和 Base R 在实现一个基本数据分析任务上的差异,展示了促使其由 Pandas 转向 Polars 的原因。正文字数:5467

我为什么放弃 Pandas 而转用 Polars?

Pandas vs Polars

1. Polars 简介与性能

这一部分转载自古明地盆的博客文章《数据处理神器可不止 Pandas 哦,还有 Polars,全方位解析 Polars》

Python 在数据处理领域有如今的地位,和 Pandas 的存在密不可分,然而除了 Pandas 之外,还有一个库也在为 Python 的数据处理添砖加瓦,它就是我们本次要介绍的 Polars。和 Pandas 相比,Polars 的速度更快,执行常见运算的速度是 Pandas 的 5 到 10 倍。另外 Polars 运算的内存需求也明显小于 Pandas,Pandas 需要数据集大小的 5 到 10 倍左右的内存来执行运算,而 Polars 需要 2 到 4 倍。之所以会有这种性能,是因为 Polars 在设计上从一开始就以性能为宗旨,并通过多种方式实现。

以上是 Polars 在性能方面的优点,安装则只需要 pip install polars[all] 即可。33 强烈建议使用 uv 而非 pip 进行环境依赖管理。

2. 正文:我为什么转用 Polars?

我最近决定,在我那些使用 DataFrame (数据框/数据表)的 Python 项目中,从 Pandas 转向 Polars。这个决定是在我上周参加了一个关于 Polars 的研讨会时做出的:我发现它的语法是如此直观,以至于我无法再为继续努力“精进”Pandas 找借口了,尽管 Pandas 是一个更成熟的库。令人惊讶的是,Polars 速度更快这一事实44 这是 Polars 的主要卖点。,并不是我做出决定的原因。

在 R 语言中,最近也发生了类似的转变。在 R 语言的大部分历史中,与数据帧交互只有一种方式:Base R。后来 Tidyverse 出现了,它不仅提供了性能改进,还带来了更简单的语法。最终,Tidyverse 成为了许多人与 DataFrame 交互的主要方式。我相信 Tidyverse 更简单的语法是其被广泛采用的原因,而且我认为类似的事情很可能也会发生在 Polars 身上。55 Tidyverse-R 优雅、流畅的管道流程和语法表达正在影响包括 Polars 在内的多个数据处理工具,不幸的是 Pandas 似乎不在此列。(再次补充,Pandas 在 3.0 版本中引入了列表达式 pd.col(),似乎也在朝着更具表达力的语法前进。)

很多情况下,这可以用布鲁姆分类法(Bloom’s Taxonomy)来解释。该分类法列出了人们从初学者到专家所经历的各个阶段。关键点是:金字塔的基础是“记忆”。如果你记不住如何完成一个基本任务(比如对数据框进行筛选),那么你就无法将其应用于工作中,无法评估他人的代码,或者为你所使用的语言/库贡献自己的扩展。

图 1 Bloom’s Taxonomy

我相信 Polars 和 Tidyverse 都比之前的库拥有更易于记忆、更符合直觉的语法。就 Tidyverse 而言,这很可能促使它成为许多用户首选的 DataFrame 数据库66 也使 R 成为了许多用户首选的数据处理语言。。我预计类似情况也会发生在 Polars 身上。77 我也很乐观!

理解这些语法差异可以帮助我们所有人成为更好的程序员。虽然很少有人开发下载量达数百万的库,但我们大多数人确实会编写供他人使用的代码。弄清楚是什么让某些 API 比其他 API 更容易掌握,可以帮助我们的下一个项目取得更大的成功。为了帮助说明这一点,下面我将用 Polars 和 Tidyverse,以及它们之前的 DataFrame 库(Pandas 和 Base R),解决同一个简单问题。这个问题是:

  1. 读取包含美国各县的 CSV 文件;
  2. 筛选名为“Washington”的县;
  3. 筛选名为“county.name”和“state.name”的列。

之所以选择这个例子,是因为筛选行和列是最基本的数据操作之一,但它仍然能证明我的观点。而且,当我做一个使用美国县数据的项目时,我发现很多州都有一个名为“Washington”的县,这很有趣。

本文使用的代码也可以在 GitHub 上找到。您可以将其作为起点,自行探索这些库。

2.1 Polars vs Pandas

2.1.1 Polars

在 Polars 中,你可以使用函数 filter 来筛选行,使用函数 select 来筛选列。这两个函数都是 Polars DataFrame 类的方法。Python 用户习惯将方法链放入括号 () 中,因此,读取数据后,代码看起来像这样:

import polars as pl

(
pl.read_csv("data/counties.csv")
.filter(pl.col("county.name") == "washington")
.select("county.name", "state.name")
)

阅读这段代码时,我首先注意到的是,对数据框执行的每个操作都有一个描述性的函数名。虽然这听起来很浅显,但 Pandas 和 Base R 都经常使用运算符/符号而不是函数,而且这些运算符会根据输入执行不同的操作。这会使记住如何使用这个库和这些方法变得困难。

R 语言使用者会注意到,filterselect 这两个函数与 dplyr 包中用于相同任务的函数名完全一致。当我看到这一点时,我以为 Polars 的创始人 Ritchie Vink 只是简单借用了 Tidyverse 的做法。但当我在领英上问他时,他说他并不使用 R 语言,实际上对此一无所知!他幽默地称之为“趋同进化”88 优秀的设计总是心有灵犀!

我第一次看到这种语法时非常高兴,因为我觉得它很容易记住:每个任务(根据条件筛选行、按名称选择列)都有一个与之关联的函数,而且这个函数的命名方式也很容易记忆。正如我们将在下文看到的,这与 Tidyverse 非常相似,但与 Pandas 和 Base R 则截然不同。

2.1.2 Pandas

当我尝试用 Pandas 编写这段代码时,我心想:“啊,这里我应该用 .loc(而不是 .iloc)吧?但这个函数是接受 [] 还是 () 呢?”我总是忘记,让我问问 AI。AI 的回复让我很惊讶,它既没有提到 .loc 也没有提到 .iloc,它说直接使用普通的 []

df[df["county.name"] == "washington"]

这种语法优势在于简洁。所以如果你清楚自己在做什么,那么编写和阅读都很快。但对于新手来说,这可能会让人感到困惑,一个困惑之处是,你使用的是运算符/符号,而不是一个命名清晰的函数(因此你必须记住 [] 的作用)。其次,在同一行代码中,[] 实际上做了两件不同的事情。在内部表达式 df["county.name"] 中,你给了它一个列名,它返回了该列的值;但在外部表达式 df[...] 中,你给了它一个逻辑序列,它返回了数据框中相应的行。

既然我想看 .loc 版本,我便告诉 AI:“给我看看另一种方式。”它返回了以下内容:

df.query('`county.name` == "washington"')

刚开始学习 Pandas 的时候,我曾对 query API 感到兴奋,因为它看起来很简单易用99 我实在不明白哪里看起来“简单易用”了🤔,我本人确实并不精通 Pandas,但我认为从这行代码就可以看出 Pandas (至少是这个 query) 的表达多么糟糕。。但后来一位我所敬重的“高级” Pandas 用户告诉我他从不用这个 API,因此我也决定不再使用它。于是我又一次告诉 AI:“再给我展示另一种方式。”它返回了:

df.loc[df["county.name"] == "washington"]

这正是我想要的。所以我在这里使用 .loc 的直觉是正确的。尽管我不确定它是否应该使用 [] 而非 (),但我还是记得这一点需要多加注意。加入用于选择列的代码后,我们得到了这个解决方案:

df.loc[df["county.name"] == "washington"][["county.name", "state.name"]]

有趣的是1010 并非有趣😇,筛选列的代码又增加了两对 []。而且它们在这里的意思又不同:内部的 [] 表示“一个 Python 列表”,外部的表示“选择列”操作。

对我来说,要攀登关于 Pandas 的布鲁姆分类法,有两个明显的障碍。首先,我需要知道在众多可能的方法中,我应该使用哪一种来完成任务。这让我想起了《Python 之禅》中的那句话:“There should be one – and preferably only one – obvious way to do it.”1111 “任何问题应有一种——且最好只有一种——显而易见的解决方法。”其次,记住复杂而抽象的语法细节本身也令人困扰。

2.2 Tidyverse vs. Base R

2.2.1 Tidyverse
Programs must be written for people to read, and only incidentally for machines to execute.
—— Hal Abelson 1212 这是一个引用块脚注

Tidyverse 有一个原则:代码应该为人设计 1313 设计 API 时,首先要考虑的是用户能否轻松使用。计算机效率是次要考虑因素,因为大多数数据分析的瓶颈在于思考时间,而非计算时间。这包括:1) 花些时间为你的函数命名。易于理解的函数名能让你的 API 更易于使用和记忆;2) 尽量使用明确、冗长的名称,而不是简短、隐含的名称。将最短的名称留给最重要的操作;3) 确保函数族使用共同的前缀,而不是共同的后缀。这样,自动补全功能就能更好地帮助你回忆起函数名称。对于较小的包,这意味着每个函数都可以使用共同的前缀。 。在实践中,这意味着创建名称清晰的函数,并且每个函数只做一件事。这也意味着可以使用管道操作符 |>%>% 来组合这些函数。进而,我们简单的分析可以这样完成,这段代码与等效的 Polars 代码非常相似:

df |>
filter(county.name == "washington") |>
select(county.name, state.name)

今年七月我举办了一个“R 语言入门”研讨会。我们同时讲解了 Tidyverse 和 Base R。学生们使用 Tidyverse 解决简单问题的速度,远快于使用 Base R 解决类似问题。我将这归因于 Tidyverse 使用具有明确名称且只做一件事的函数来进行数据操作。

这段代码的另一个特点是它使用了非标准求值(Non-Standard Evaluation, NSE)。在使用 filter 函数时,我们可以直接写入列名(例如 county.name)来引用列的内容,而无需使用引号。在 Polars 中,我们需要写 pl.col("county.name");而在 Pandas 中,我们则需要写 df["county.name"] 之类的表达。NSE 非常有用,并且能写出非常简洁的代码,我不明白为什么 Pandas 和 Polars 都没有采用它。1414 Polars 作者 Ritchie Vink 对此进行了回复:“NSE 在 Python 中无法实现。这意味着某些领域特定语言无法在 Python 中表达,需要 pl.col(..) 这样的工具对象。” 1515 在 Polars 中,如果列名没有空格,则可以使用 pl.col.foo 来选择 foo 列,如果提前导入 from polars import col as c,则可以使用更简洁的 c.foo

2.2.2 Base R

相同功能的 Base R 版本代码则大相径庭。与 Pandas 类似,Base R 不使用显式的函数调用,相反,你需要使用运算符/符号 []$

df[df$county.name == "washington", c("county.name", "state.name")]

作为一名 R 语言的长期用户,我个人感觉这样的代码既容易阅读也容易编写,但研讨会上的学生们写起来却很费劲。他们能够轻松地编写向量化的逻辑判断(df$county.name == "washington"),但却难以将这个判断表达式放进 [] 中。

这段代码暴露的另一个问题是,运算符常常会被重载,这会让新手更加困惑。例如,在上面的代码中,df$county.name == "washington" 被用作下标索引。但在 R 语言中有五种下标,新手需要全部学习。但如果使用显式函数,这就不再是个问题了,事实上,Tidyverse 有很多针对特定需求的、符合直觉和命名规律的显式函数(例如 starts_withends_with)。

2.3 结论

去年十二月,当我第一次开始学习如何在 Python 中处理表格数据时,我选择了 Pandas,因为它是 Python 中最流行的数据框库。我现在认为,对于新手来说,学习 Polars 更好,所以我把精力都投入其中。主要原因是,我发现 Polars 的语法更流畅、更容易记忆。根据布鲁姆分类法,我认为一个更容易记住如何使用的库,反过来能让我更快地做出重要贡献。此外,Polars 的性能也远优于 Pandas。

“Polars vs. Pandas” 之争让我想起了大约十年前开始的 “Tidyverse vs. Base R” 之争。那时,像我这样经验丰富的 R 用户会嘲笑那些认为 “学习 Tidyverse” 就等同于 “学习 R 语言” 的人。事后来看,我们错了。我们低估了人们采纳更简单、更明确的 DataFrame API 的速度。我认为,如今那些对 Polars 持类似立场的资深 Pandas 用户,很可能也会因为类似的原因被证明是错误的。当然,只有时间才能证明一切。

Polars 和 Tidyverse 的诞生让我想起了我第一份工作时,一位首席工程师告诉我的话:“第一次构建某物时,专注于让它能工作;第二次构建某物时,专注于让它更优雅。” Pandas 和 Base R 都对统计计算领域做出了巨大贡献。它们能很好地工作。而 Polars 和 Tidyverse 作为后继者则更胜一筹,拥有了专注于“优雅”和“流畅”的优势。

正如俗话所说:“历史不会重演,但总会似曾相识。”1616 “History doesn’t repeat itself, but it sure does rhyme.”