You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何高效对Polars DataFrame嵌套列表执行滑动窗口求和取最大值?

Polars嵌套列表滑动窗口求和最大值的高效实现方法

问题背景

给定如下Polars DataFrame:

import polars as pl
from polars import Boolean, List, col

src = pl.DataFrame(
    {
        "c1": ["a", "b", "c", "d"],
        "c2": [
            [0, 0],
            [0, 1, 0, 0],
            [1, 0, 1, 1, 1],
            [1, 1, 0],
        ],
    },
    schema_overrides={"c2": List(Boolean)},
)

需求为对c2列的每个内部列表,计算窗口长度为3的滑动窗口求和的最大值,逻辑示例:

[1, 0, 0, 1, 1, 1]
⇒
[(1, 0, 0), (0, 0, 1), (0, 1, 1),(1, 1, 1)]
⇒
[1, 1, 2, 3]
⇒
3

尝试过展开嵌套列表再执行滚动计算的方法,但该方法会导致滚动窗口跨越原内部列表边界,不符合需求,错误代码及输出如下:

(
    src.with_row_index("outer_index")
    .explode("c2")
    .with_row_index("inner_index")
    .rolling("inner_index", period="3i", offset="0i", closed="left")
    .agg("outer_index", "c1", col("c2").sum())
    .drop("inner_index")
)

错误输出(字典形式):

[{'outer_index': [0, 0, 1], 'c1': ['a', 'a', 'b'], 'c2': 0},
 {'outer_index': [0, 1, 1], 'c1': ['a', 'b', 'b'], 'c2': 1},
 {'outer_index': [1, 1, 1], 'c1': ['b', 'b', 'b'], 'c2': 1},
 {'outer_index': [1, 1, 1], 'c1': ['b', 'b', 'b'], 'c2': 1},
 {'outer_index': [1, 1, 2], 'c1': ['b', 'b', 'c'], 'c2': 1},
 {'outer_index': [1, 2, 2], 'c1': ['b', 'c', 'c'], 'c2': 1},
 {'outer_index': [2, 2, 2], 'c1': ['c', 'c', 'c'], 'c2': 2},
 {'outer_index': [2, 2, 2], 'c1': ['c', 'c', 'c'], 'c2': 2},
 {'outer_index': [2, 2, 2], 'c1': ['c', 'c', 'c'], 'c2': 3},
 {'outer_index': [2, 2, 3], 'c1': ['c', 'c', 'd'], 'c2': 3},
 {'outer_index': [2, 3, 3], 'c1': ['c', 'd', 'd'], 'c2': 3},
 {'outer_index': [3, 3, 3], 'c1': ['d', 'd', 'd'], 'c2': 2},
 {'outer_index': [3, 3], 'c1': ['d', 'd'], 'c2': 1},
 {'outer_index': [3], 'c1': ['d'], 'c2': 0}]

高效解决方案

方法1:map_batches结合numpy向量化计算

利用numpy的sliding_window_view实现高效滑动窗口求和,适合大体积数据场景:

import numpy as np

def rolling_max_sum(arr: np.ndarray, window_size: int) -> int:
    if len(arr) < window_size:
        return arr.sum()  # 可根据需求调整短列表的返回逻辑,比如返回0
    windows = np.lib.stride_tricks.sliding_window_view(arr, window_size)
    return windows.sum(axis=1).max()

result = src.with_columns(
    rolling_max=pl.col("c2").map_batches(
        lambda s: s.struct.arr.to_numpy().apply(rolling_max_sum, window_size=3)
    )
)

print(result)

输出结果:

shape: (4, 3)
┌─────┬───────────────────┬────────────┐
│ c1  ┆ c2                ┆ rolling_max│
│ --- ┆ ---               ┆ ---        │
│ str ┆ list[bool]        ┆ i64        │
╞═════╪═══════════════════╪════════════╡
│ a   ┆ [false, false]    ┆ 0          │
│ b   ┆ [false, true, …]  ┆ 1          │
│ c   ┆ [true, false, …]  ┆ 3          │
│ d   ┆ [true, true, false]┆ 2          │
└─────┴───────────────────┴────────────┘

方法2:纯Polars列表操作(无外部依赖)

通过列表切片生成滑动窗口,直接计算求和最大值,逻辑直观:

window_size = 3

result = src.with_columns(
    rolling_max=pl.col("c2").map_elements(
        lambda lst: max(
            sum(lst[i:i+window_size]) for i in range(len(lst) - window_size + 1)
        ) if len(lst) >= window_size else sum(lst),
        return_dtype=pl.Int64
    )
)

print(result)

该方法无需额外依赖,但对于极长列表,性能略低于numpy向量化方案。

方法3:修正分组滚动逻辑

如果偏好展开-滚动的思路,可通过按原行分组避免窗口跨边界:

window_size = 3

result = (
    src.with_row_index("idx")
    .explode("c2")
    .with_columns(inner_idx=pl.int_range(0, pl.count()).over("idx"))
    .rolling(
        index_column="inner_idx",
        window_size=window_size,
        by="idx",  # 限制窗口仅在原行内部滚动
        closed="left"
    )
    .agg(pl.col("c2").sum())
    .group_by("idx", "c1", maintain_order=True)
    .agg(pl.col("c2").max().alias("rolling_max"))
    .drop("idx")
)

print(result)

该方法通过by="idx"参数确保滚动窗口不会跨越原列表边界,同时保留Polars的向量化性能。


内容的提问来源于stack exchange,提问作者Konstantin Druzhkin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 05:40:59