使用polars的滚动函数得到滚动中所有值的列表windows

Use the rolling function of polars to get a list of all values in the rolling windows

我想使用滚动函数来获取滚动中所有值的列表 windows。

我用下面的代码片段试了一下:

import polars as pl
df = pl.DataFrame(
    {
        "A": [1.0, 2.0, 9.0, 2.0, 13.0],
    }
)

df.select(
    [
        pl.col("A").rolling_apply(3, lambda s: s),
    ]
)

这个输出

┌──────┐
│ A    │
│ ---  │
│ f64  │
╞══════╡
│ null │
├╌╌╌╌╌╌┤
│ null │
├╌╌╌╌╌╌┤
│ 1    │
├╌╌╌╌╌╌┤
│ 2    │
├╌╌╌╌╌╌┤
│ 9    │
└──────┘

但我需要的是:

┌─────────────────┐
│ A               │
│ ---             │
│ list [f64]      │
╞═════════════════╡
│ [null, null, 1] │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ [null, 1, 2]    │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ [1, 2, 9]       │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ [2, 9, 2]       │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ [9, 2, 13]      │
└─────────────────┘

有没有人知道如何在极地中以简单的方式做到这一点?

您看到的行为是 lambda s: s 导致 rolling_apply 选择最后一个元素。目前似乎没有办法让它 return 多个元素。例如,当我尝试使用 lambda s: list(s) 将其强制添加到列表时,它会引发一个错误,即列表无法 returned,表明此功能不存在。

下面是涉及 Python 循环(太慢)的解决方法,但可能对您有所帮助:

定义自定义 slice 函数:

def _slice(s: pl.Series, offset: int, l: int) -> pl.Series:
    # like s.slice(offset, l), but prepadding with null when offset <0
    if offset < 0:
        prepad = pl.Series([None] * abs(offset))
        return pl.concat((prepad, s.slice(0, l+offset)))
    else:
        return s.slice(offset, l)

并用它来循环 df["A"]:

l = 3
pl.Series([_slice(df["A"], n-l+1, l) for n in range(len(df))])

这导致:

shape: (5,)
Series: '' [list]
[
    [null, null, 1]
    [null, 1, 2]
    [1, 2, 9]
    [2, 9, 2]
    [9, 2, 13]
]

这绝不是一个高效的解决方案,所以这里的问题是您将如何处理数据框中的列表?以这种格式存储数据对于进一步处理通常是一个挑战。

您可以创建滞后列并将它们收集到列表中。

(df
    .with_columns([pl.col("A").shift(i).alias(f"A_lag_{i}") for i in range(3)])
    .select(
        [pl.concat_list([f"A_lag_{i}" for i in range(3)][::-1]).alias("A_rolling")]
))

输出:

shape: (5, 1)
┌─────────────────┐
│ A_rolling       │
│ ---             │
│ list [f64]      │
╞═════════════════╡
│ [null, null, 1] │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ [null, 1, 2]    │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ [1, 2, 9]       │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ [2, 9, 2]       │
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
│ [9, 2, 13]      │
└─────────────────┘

让我们分解一下:

reshape(-1, 1)"A" 转换为列表。

pl.col(..).shift(i) for i in range(3)] 创建新的滞后列。

这导致了这个中间 DataFrame:

shape: (5, 4)
┌─────┬────────────┬────────────┬────────────┐
│ A   ┆ A_lag_0    ┆ A_lag_1    ┆ A_lag_2    │
│ --- ┆ ---        ┆ ---        ┆ ---        │
│ f64 ┆ list [f64] ┆ list [f64] ┆ list [f64] │
╞═════╪════════════╪════════════╪════════════╡
│ 1   ┆ [1]        ┆ [null]     ┆ [null]     │
├╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2   ┆ [2]        ┆ [1]        ┆ [null]     │
├╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 9   ┆ [9]        ┆ [2]        ┆ [1]        │
├╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2   ┆ [2]        ┆ [9]        ┆ [2]        │
├╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 13  ┆ [13]       ┆ [2]        ┆ [9]        │
└─────┴────────────┴────────────┴────────────┘

最后我们以相反的顺序连接它们并将输出命名为“A_rolling”:

pl.concat_list([f"A_lag_{i}" for i in range(3)][::-1]).alias("A_rolling")