在 Polars 中在 df.with_column 和 df['new_col'] = func(df['old_col']) 之间创建新列有什么区别

What's the difference of creating new column between df.with_column and df['new_col'] = func(df['old_col']) in Polars

抱歉,如果以前有人问过这个问题,但我还没见过。

我意识到 Polars 可以使用

创建一个新列
def func(series):
   """sample function """
   return series

df.with_column(
   pl.lit(func(df['old_col'])).alias('new_col')
)

然而,它也可以写成:

df['new_col'] = func(df['old_col'])

这个性能相关吗?

干杯!

提前原谅我的长篇大论。但我想展示为什么编码“Polars 方式”值得学习曲线的好处和原因 - 实现我们 Polars 用户所期望的令人震惊的快速性能。

音译Pandas-style编码

以下代码,找不到更好的词,是“音译Pandas”代码。

df['new_col'] = func(df['old_col'])

也就是说,它类似于 Pandas-style 代码。在 Polars 中肯定 运行s。在某些情况下,它甚至可以产生可接受的性能。

但它不包含 Polars 表达计算的方式,因此 Polars 引擎可以轻松地跨多个内核并行计算。此代码也不允许在 Lazy 模式下进行查询优化。 (实际上,Python 函数的使用可能会使代码服从 Python GIL,迫使代码以 single-threaded 的方式 运行。)

大多数使用 Polars 的人都期望 比 single-threaded Python 字节码性能好得多。 (确实,这就是 Polars 的全部意义所在。)

一个例子

让我们仔细看看。让我们使用以下数据集。将 nbr_obs 设置得足够高以挑战您的系统。

import polars as pl

nbr_obs = 1_000_000
df = pl.DataFrame(
    {
        'date1': pl.repeat('2001-01-01', nbr_obs, eager=True),
        'date2': pl.repeat('2002-01-01', nbr_obs, eager=True),
        'date3': pl.repeat('2003-01-01', nbr_obs, eager=True),
        'date4': pl.repeat('2004-01-01', nbr_obs, eager=True),
        'date5': pl.repeat('2005-01-01', nbr_obs, eager=True),
    }
)
shape: (1000000, 5)
┌────────────┬────────────┬────────────┬────────────┬────────────┐
│ date1      ┆ date2      ┆ date3      ┆ date4      ┆ date5      │
│ ---        ┆ ---        ┆ ---        ┆ ---        ┆ ---        │
│ str        ┆ str        ┆ str        ┆ str        ┆ str        │
╞════════════╪════════════╪════════════╪════════════╪════════════╡
│ 2001-01-01 ┆ 2002-01-01 ┆ 2003-01-01 ┆ 2004-01-01 ┆ 2005-01-01 │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2001-01-01 ┆ 2002-01-01 ┆ 2003-01-01 ┆ 2004-01-01 ┆ 2005-01-01 │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2001-01-01 ┆ 2002-01-01 ┆ 2003-01-01 ┆ 2004-01-01 ┆ 2005-01-01 │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2001-01-01 ┆ 2002-01-01 ┆ 2003-01-01 ┆ 2004-01-01 ┆ 2005-01-01 │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ ...        ┆ ...        ┆ ...        ┆ ...        ┆ ...        │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2001-01-01 ┆ 2002-01-01 ┆ 2003-01-01 ┆ 2004-01-01 ┆ 2005-01-01 │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2001-01-01 ┆ 2002-01-01 ┆ 2003-01-01 ┆ 2004-01-01 ┆ 2005-01-01 │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2001-01-01 ┆ 2002-01-01 ┆ 2003-01-01 ┆ 2004-01-01 ┆ 2005-01-01 │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2001-01-01 ┆ 2002-01-01 ┆ 2003-01-01 ┆ 2004-01-01 ┆ 2005-01-01 │
└────────────┴────────────┴────────────┴────────────┴────────────┘

音译Pandas-style编码

现在让我们创建一个 Python 函数来解析这些日期。

from datetime import datetime
def parse_it(series):
    return [datetime.strptime(date_str, "%Y-%m-%d") for date_str in series]

让我们 运行 使用“音译 Pandas”代码。根据您的 OS,观察 CPU 在您机器上的使用情况,因为此代码 运行s.

df['date1_parse'] = parse_it(df['date1'])
df['date2_parse'] = parse_it(df['date2'])
df['date3_parse'] = parse_it(df['date3'])
df['date4_parse'] = parse_it(df['date4'])
df['date5_parse'] = parse_it(df['date5'])

你观察到什么?你应该痛苦地single-threaded观察表现。 (Polars 用户 不惜一切代价 避免这种情况,尤其是因为我们中的许多人都在使用大型数据集和 multi-core 计算平台。)

惰性模式的消失

现在,让我们尝试切换到 Polars Lazy 模式,以便我们可以利用 Polars 强大的查询优化引擎。

df = df.lazy()
df['date1_parse'] = parse_it(df['date1'])
>>> df['date1_parse'] = parse_it(df['date1'])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'LazyFrame' object is not subscriptable

糟糕,我们不能在 Lazy 模式下使用音译的 Pandas-style 代码。总的来说,这是潜在性能的巨大损失。

极地之路

利用 Polars 强大引擎的方法是掌握 Expression 语法。我们用表达式来编码我们的目标。如果做得好,Polars 可以并行化我们的代码(并避免 Python GIL)。结果是令人震惊更好的表现。

现在,让我们重新创建数据集,并使用 Polars 表达式解析日期。请注意,我已经完全放弃使用 Python 函数,而是留在“Polars 内部 API”。也就是说,我已经使用表达式表达了我需要的一切。

df = pl.DataFrame(
    {
        'date1': pl.repeat('2001-01-01', nbr_obs, eager=True),
        'date2': pl.repeat('2002-01-01', nbr_obs, eager=True),
        'date3': pl.repeat('2003-01-01', nbr_obs, eager=True),
        'date4': pl.repeat('2004-01-01', nbr_obs, eager=True),
        'date5': pl.repeat('2005-01-01', nbr_obs, eager=True),
    }
)

df.with_columns([
    pl.col("^date.*$").str.strptime(pl.Date, "%Y-%m-%d")
])
shape: (1000000, 5)
┌────────────┬────────────┬────────────┬────────────┬────────────┐
│ date1      ┆ date2      ┆ date3      ┆ date4      ┆ date5      │
│ ---        ┆ ---        ┆ ---        ┆ ---        ┆ ---        │
│ date       ┆ date       ┆ date       ┆ date       ┆ date       │
╞════════════╪════════════╪════════════╪════════════╪════════════╡
│ 2001-01-01 ┆ 2002-01-01 ┆ 2003-01-01 ┆ 2004-01-01 ┆ 2005-01-01 │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2001-01-01 ┆ 2002-01-01 ┆ 2003-01-01 ┆ 2004-01-01 ┆ 2005-01-01 │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2001-01-01 ┆ 2002-01-01 ┆ 2003-01-01 ┆ 2004-01-01 ┆ 2005-01-01 │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2001-01-01 ┆ 2002-01-01 ┆ 2003-01-01 ┆ 2004-01-01 ┆ 2005-01-01 │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ ...        ┆ ...        ┆ ...        ┆ ...        ┆ ...        │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2001-01-01 ┆ 2002-01-01 ┆ 2003-01-01 ┆ 2004-01-01 ┆ 2005-01-01 │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2001-01-01 ┆ 2002-01-01 ┆ 2003-01-01 ┆ 2004-01-01 ┆ 2005-01-01 │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2001-01-01 ┆ 2002-01-01 ┆ 2003-01-01 ┆ 2004-01-01 ┆ 2005-01-01 │
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2001-01-01 ┆ 2002-01-01 ┆ 2003-01-01 ┆ 2004-01-01 ┆ 2005-01-01 │
└────────────┴────────────┴────────────┴────────────┴────────────┘

这次的表演,你注意到了什么? 大规模并行令人震惊的表现。

这个 (long-winded) 答案是否提供了一些关于为什么最好掌握表达式语法并避免“音译 Pandas-style”代码的见解?

您可以在 Polars Cookbook 中找到更多信息。有很多使用表达式和“Polars 方式”编码的好例子。