Polars 如何装箱日期时间类型列

Polars how to bin a datetime type column

提问人:arman 提问时间:11/16/2023 最后编辑:Arpita Shrivastavaarman 更新时间:11/20/2023 访问量:113

问:

我正在尝试装箱日期列。我能够使用 Pandas 库相当轻松地做到这一点。但是我在使用 Polars 时遇到以下错误:

Traceback (most recent call last):
  File "\workspace\polars.py", line 4, in <module>
    pl.col("ts").cut(
  File "\venvs\polars\Lib\site-packages\polars\utils\deprecation.py", line 192, in wrapper
    return function(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "\venvs\polars\Lib\site-packages\polars\expr\expr.py", line 3716, in cut
    self._pyexpr.cut(breaks, labels, left_closed, include_breaks)
TypeError: argument 'breaks': must be real number, not datetime.datetime

我的代码是:

>>> df
┌────────────┐
│ ts         │
│ ---        │
│ date       │
╞════════════╡
│ 2021-10-01 │
│ 2021-11-01 │
│ 2022-03-01 │
└────────────┘
>>> start_year = df.select("ts").min().item()
>>> end_year = df.select("ts").max().item()
>>> df = df.with_columns(
        pl.col("ts").cut(
            pl.datetime_range(start_year, end_year, interval="3mo", eager=True),
        ).alias("period_bins")
    )
日期时间 python-polars

评论


答:

1赞 ignoring_gravity 11/16/2023 #1

这看起来像一个错误 - 你想把它报告给 https://github.com/pola-rs/polars/issues 吗?

目前,作为一种解决方法,您可以强制转换为 int:

breaks = pl.date_range(start_year, end_year, interval="3mo", eager=True)

df.with_columns(
    pl.col("ts").cast(pl.Int64).cut(
        breaks.cast(pl.Int64),
    ).alias("period_bins")
)

评论

0赞 Dean MacGregor 11/17/2023
我不认为这是一个错误,只是不支持按时态列装箱,这当然是一个很好的功能请求。
0赞 arman 11/19/2023
谢谢!我已经接受了这个答案,因为我扩展了这个答案来获得我的解决方案
2赞 Hericks 11/17/2023 #2

如果中断是等距的,则另一种方法是使用 pl。Expr.dt.round,如下所示。

df.with_columns(
    pl.col("ts").dt.round(every="3mo").alias("rounded_ts")
)

输出。

┌────────────┬────────────┐
│ ts         ┆ rounded_ts │
│ ---        ┆ ---        │
│ date       ┆ date       │
╞════════════╪════════════╡
│ 2021-10-01 ┆ 2021-10-01 │
│ 2021-11-01 ┆ 2021-10-01 │
│ 2022-03-01 ┆ 2022-04-01 │
└────────────┴────────────┘

评论

1赞 arman 11/19/2023
这是一个非常好的解决方案。但是,我不能使用它,因为我的存储桶边界的月份应该与值无关。ts
0赞 Hericks 11/19/2023
@arman 感谢 - 也感谢您提供您最终使用的完整解决方案。我会对未来的读者有用。点赞!
1赞 FObersteiner 11/17/2023 #3

如果您还想将值放入箱中(例如平均值或总和),polars 提供group_by_dynamic。前任:

from datetime import datetime
import polars as pl

df = pl.DataFrame(
    {
     "datetime": pl.datetime_range(datetime(2022, 1, 1), datetime(2022, 12, 31), "1d", eager=True),
     "values": pl.Series([1 for _ in range(365)]),
     }
)

period = "3mo"
df_out = df.group_by_dynamic("datetime", every=period).agg(pl.col("values").sum())

# summing up the 1's in 'values' over 3mo effectively gives us the days in a quarter;
# Q1: 90d, Q2: 91d, Q3: 92d, Q4: 92d
print(df_out.head())
┌─────────────────────┬────────┐
│ datetime            ┆ values │
│ ---                 ┆ ---    │
│ datetime[μs]        ┆ i64    │
╞═════════════════════╪════════╡
│ 2022-01-01 00:00:00 ┆ 90     │
│ 2022-04-01 00:00:00 ┆ 91     │
│ 2022-07-01 00:00:00 ┆ 92     │
│ 2022-10-01 00:00:00 ┆ 92     │
└─────────────────────┴────────┘
2赞 arman 11/19/2023 #4

我已经扩展了ignoring_gravity的答案以获得所需的结果。

>>> df = df.with_columns(
        pl.col("ts").dt.epoch(time_unit="us")
        .cut(breaks.dt.epoch(time_unit="us"))
        .str.replace_all("\(|]|\s+", "")
        .str.split(',')
        .cast(pl.List(pl.Int64))
        .cast(pl.List(pl.Datetime))
        .cast(pl.List(pl.Date))
        .alias("bins")
    )

┌────────────┬──────────────────────────┐
│ ts         ┆ bins                     │
│ ---        ┆ ---                      │
│ date       ┆ list[date]               │
╞════════════╪══════════════════════════╡
│ 2021-10-01 ┆ [2021-07-01, 2021-10-01] │
│ 2021-11-01 ┆ [2021-10-01, 2022-01-01] │
│ 2022-03-01 ┆ [2022-01-01, 2022-04-01] │
└────────────┴──────────────────────────┘