1

我有以下带有熊猫的 Python 代码

df['EVENT_DATE'] = df.apply(
        lambda row: datetime.date(year=row.iyear, month=row.imonth, day=row.iday).strftime("%Y-%m-%d"), axis=1)

并希望将其转换为有效的 Polars 代码。有没有人有任何想法来解决这个问题?

4

2 回答 2

2

我还将回答您的一般问题,而不仅仅是您的具体用例。

对于您的具体情况,从 开始polars version >= 0.10.18,推荐的创建您想要的方法是使用pl.dateorpl.datetime表达式。

给定此数据框,pl.date用于根据要求格式化日期。

import polars as pl

df = pl.DataFrame({
    "iyear": [2001, 2001],
    "imonth": [1, 2],
    "iday": [1, 1]
})


df.with_columns([
    pl.date("iyear", "imonth", "iday").dt.strftime("%Y-%m-%d").alias("fmt")

])

这输出:

shape: (2, 4)
┌───────┬────────┬──────┬────────────┐
│ iyear ┆ imonth ┆ iday ┆ fmt        │
│ ---   ┆ ---    ┆ ---  ┆ ---        │
│ i64   ┆ i64    ┆ i64  ┆ str        │
╞═══════╪════════╪══════╪════════════╡
│ 2001  ┆ 1      ┆ 1    ┆ 2001-01-01 │
├╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2001  ┆ 2      ┆ 1    ┆ 2001-02-01 │
└───────┴────────┴──────┴────────────┘

在单个表达式中收集其他列的其他方法

以下是关于主要问题的更通用的答案。我们可以使用 amap来获取多个列Series,或者如果我们知道我们想要格式化一个字符串列,我们可以使用pl.format. map提供最实用的功能。

df.with_columns([
    # string fmt over multiple expressions
    pl.format("{}-{}-{}", "iyear", "imonth", "iday").alias("date"),
    # columnar lambda over multiple expressions
    pl.map(["iyear", "imonth", "iday"], lambda s: s[0] + "-" + s[1] + "-" + s[2]).alias("date2"),
])

这输出

shape: (2, 5)
┌───────┬────────┬──────┬──────────┬──────────┐
│ iyear ┆ imonth ┆ iday ┆ date     ┆ date2    │
│ ---   ┆ ---    ┆ ---  ┆ ---      ┆ ---      │
│ i64   ┆ i64    ┆ i64  ┆ str      ┆ str      │
╞═══════╪════════╪══════╪══════════╪══════════╡
│ 2001  ┆ 1      ┆ 1    ┆ 2001-1-1 ┆ 2001-1-1 │
├╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┤
│ 2001  ┆ 2      ┆ 1    ┆ 2001-2-1 ┆ 2001-2-1 │
└───────┴────────┴──────┴──────────┴──────────┘

避免逐行操作

虽然,结果中接受的答案是正确的。这不是对极坐标中的多列应用操作的推荐方法。访问行非常慢。导致大量缓存未命中,需要运行缓慢的 python 字节码并终止所有并行化/查询优化。

笔记

在这种特定情况下,不建议使用地图创建字符串数据:

pl.map(["iyear", "imonth", "iday"], lambda s: s[0] + "-" + s[1] + "-" + s[2]).alias("date2"),. 因为内存的布局方式以及我们为每个字符串操作创建一个新列,这实际上非常昂贵(仅限字符串数据)。因此有pl.formatand pl.concat_str

于 2021-11-09T18:14:59.210 回答
1

Polarsapply会将行数据作为元组返回,因此您需要使用数字索引。例子:

import datetime
import polars as pl

df = pl.DataFrame({"iyear": [2020, 2021],
                   "imonth": [1, 2],
                   "iday": [3, 4]})

df['EVENT_DATE'] = df.apply(
        lambda row: datetime.date(year=row[0], month=row[1], day=row[2]).strftime("%Y-%m-%d"))

如果df包含更多列或以不同的顺序,您可以使用 apply ondf[["iyear", "imonth", "iday"]]而不是df确保索引引用正确的元素。

可能有更好的方法来实现这一点,但这最接近 Pandas 代码。

在单独的说明中,我的猜测是您不想将日期存储为字符串,而是作为适当的pl.Date. 您可以通过这种方式修改代码:

def days_since_epoch(dt):
    return (dt - datetime.date(1970, 1, 1)).days


df['EVENT_DATE_dt'] = df.apply(
        lambda row: days_since_epoch(datetime.date(year=row[0], month=row[1], day=row[2])), return_dtype=pl.Date)

我们首先将 Python 转换为date自 1970 年 1 月 1 日以来的天数,然后转换为pl.Dateusingapplyreturn_dtype参数。转换为pl.Date需要一个 int 而不是 Python 日期时间,因为它最终将数据存储为一个 int。通过简单地访问日期最容易看到这一点:

print(type(df["EVENT_DATE_dt"][0]))  # >>> <class 'int'>
print(type(df["EVENT_DATE_dt"].dt[0]))  # >>> <class 'datetime.date'>

如果演员确实直接对 Python 日期时间进行操作,那就太好了。

编辑:关于性能与熊猫的对话。对于 Pandas 和 Polars,如果您有许多重复的行(年/月/日),您可以通过使用缓存来加快应用速度,从而进一步加快速度。IE

from functools import lru_cache

@lru_cache
def row_to_date(row):
    return days_since_epoch(datetime.date(year=row[0], month=row[1], day=row[2]))

df['EVENT_DATE_dt'] = df.apply(row_to_date, return_dtype=pl.Date)

当有许多重复条目时,这将提高运行时间,但会消耗一些内存。如果没有重复,它可能会减慢你的速度。

于 2021-11-09T06:46:35.500 回答