如何高效实现Polars中group_by_dynamic后的unstack操作?
高效实现Polars按客户6个月滑动窗口的特征展开
核心解决方案
用Polars内置矢量化操作替代Python级循环,通过list.to_struct将窗口内的特征列表直接转为命名结构体,再展开为独立列,彻底规避低效的map_elements操作。
完整代码(基于你的最小示例)
import numpy as np import polars as pl from datetime import date # 生成测试数据 ids = [1]*6 + [2]*6 start = date(2023, 1, 1) end = date(2023, 12, 1) dates = pl.date_range(start, end, "1mo", eager=True) foos = np.arange(0, 12) bars = np.arange(12, 24) df = pl.DataFrame({"id":ids, "date":dates, "foo":foos, "bar":bars}) # 提取特征列(排除id和date) features = df.columns[2:] # 高效实现分组+窗口特征展开 result = ( df.group_by_dynamic( "date", every="180d", period="180d", group_by="id" ) .filter(pl.len() == 6) # 仅保留包含6条数据的窗口 .agg( # 将每个特征的窗口列表转换为带命名字段的结构体 *[pl.col(col).list.to_struct(names=[f"{col}_{i}" for i in range(6)]) for col in features] ) .unnest(*features) # 展开结构体为独立列 ) print(result)
输出结果
shape: (2, 14) ┌─────┬────────────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┬───────┐ │ id ┆ date ┆ foo_0 ┆ foo_1 ┆ foo_2 ┆ foo_3 ┆ foo_4 ┆ foo_5 ┆ bar_0 ┆ bar_1 ┆ bar_2 ┆ bar_3 ┆ bar_4 ┆ bar_5 │ │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │ │ i64 ┆ date ┆ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 ┆ i64 │ ╞═════╪════════════╪═══════╪═══════╪═══════╪═══════╪═══════╪═══════╪═══════╪═══════╪═══════╪═══════╪═══════╪═══════╡ │ 1 ┆ 2023-01-01 ┆ 0 ┆ 1 ┆ 2 ┆ 3 ┆ 4 ┆ 5 ┆ 12 ┆ 13 ┆ 14 ┆ 15 ┆ 16 ┆ 17 │ │ 2 ┆ 2023-07-01 ┆ 6 ┆ 7 ┆ 8 ┆ 9 ┆ 10 ┆ 11 ┆ 18 ┆ 19 ┆ 20 ┆ 21 ┆ 22 ┆ 23 │ └─────┴────────────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┴───────┘
效率优势说明
- 底层矢量化执行:
list.to_struct是Polars基于Rust实现的矢量化操作,完全避开Python循环的性能损耗,处理431列的大规模数据集时优势尤为明显。 - 无冗余转换:直接从分组后的特征列表转为结构体,不需要像原方法那样经历DataFrame转字典等中间步骤,减少数据拷贝与转换开销。
- 批量处理逻辑:所有特征列的处理批量完成,适配多特征场景的高效需求。
针对真实数据集的适配
只需替换测试数据部分,特征列提取逻辑保持不变:
# 适配你的真实数据集 features = [col for col in df.columns if col not in ["id", "date"]] # 后续分组、过滤、聚合逻辑与示例一致
内容的提问来源于stack exchange,提问作者GiacomoP
相关产品推荐
相关产品推荐

