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

如何在Polars中高效处理DataFrame的多行切片(时间序列场景)

问题描述

我有如下时间序列数据集(Rust + Polars):

let series_one = Series::new(
    "a",
    (0..4).into_iter().map(|v| v as f64).collect::<Vec<_>>(),
);
let series_two = Series::new(
    "b",
    (4..8).into_iter().map(|v| v as f64).collect::<Vec<_>>(),
);
let series_three = Series::new(
    "c",
    (8..12).into_iter().map(|v| v as f64).collect::<Vec<_>>(),
);

let series_dates = Series::new(
    "date",
    (0..4)
        .into_iter()
        .map(|v| NaiveDate::default() + Duration::days(2 * v))
        .collect::<Vec<_>>(),
);
let df = DataFrame::new(vec![series_one, series_two, series_three, series_dates]).unwrap();

对应的DataFrame结构:

shape: (4, 4)
┌─────┬─────┬──────┬────────────┐
│ a   ┆ b   ┆ c    ┆ date       │
│ --- ┆ --- ┆ ---  ┆ ---        │
│ f64 ┆ f64 ┆ f64  ┆ date       │
╞═════╪═════╪══════╪════════════╡
│ 0.0 ┆ 4.0 ┆ 8.0  ┆ 1970-01-01 │
├╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 1.0 ┆ 5.0 ┆ 9.0  ┆ 1970-01-02 │
├╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 2.0 ┆ 6.0 ┆ 10.0 ┆ 1970-01-03 │
├╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
│ 3.0 ┆ 7.0 ┆ 11.0 ┆ 1970-01-04 │
└─────┴─────┴──────┴────────────┘

我需要对每一行,调用some_fn处理包含当前行及所有前置行的DataFrame切片,函数定义如下:

fn some_fn(_df: DataFrame) -> DataFrame {
    // 对输入的DataFrame切片执行操作,返回包含结果的新DataFrame
    DataFrame::new(vec![
        Series::new("a_result", vec![1.0, 2.0, 3.0, 4.0]),
        Series::new("b_result", vec![5.0, 6.0, 7.0, 8.0]),
        Series::new("c_result", vec![9.0, 10.0, 11.0, 12.0]),
    ])
    .unwrap()
}

我最初的实现方式是循环每一行,每次取前i+1行切片传入函数,再拼接结果:

let size = df.column("a").unwrap().len();
let results = (0..size)
    .into_iter()
    .map(|i| {
        let t = df.head((i + 1).into());
        some_fn(t)
    })
    .reduce(|acc, b| acc.vstack(&b).unwrap())
    .unwrap();

但这种方法效率极低,仅处理3000行就耗时约1ms(空函数基准测试,耗时主要来自切片操作)。请问如何利用Polars的特性实现高效处理?

高效解决方案

1. 改用状态累积,避免重复切片与拼接

当前实现的核心问题是每次循环都要生成新的DataFrame切片,还要反复执行vstack拼接,带来大量不必要的内存分配和拷贝。可以改为逐行累积状态的方式:

// 先修改some_fn为接受引用参数,减少克隆开销
fn some_fn(_df: &DataFrame) -> DataFrame {
    // ... 原有逻辑不变
    DataFrame::new(vec![
        Series::new("a_result", vec![1.0, 2.0, 3.0, 4.0]),
        Series::new("b_result", vec![5.0, 6.0, 7.0, 8.0]),
        Series::new("c_result", vec![9.0, 10.0, 11.0, 12.0]),
    ])
    .unwrap()
}

// 累积处理逻辑
let mut accumulated_df = df.head(0).unwrap();
let mut results = Vec::with_capacity(df.height());

for row in df.iter_rows() {
    // 将当前行转为单行DataFrame并添加到累积集合
    let row_df = DataFrame::new(row.into_iter().map(|(name, val)| {
        Series::new(name, vec![val.clone()])
    }).collect()).unwrap();
    accumulated_df = accumulated_df.vstack(&row_df).unwrap();
    
    // 传入累积的DataFrame引用处理
    let res = some_fn(&accumulated_df);
    results.push(res);
}

// 最终拼接所有结果
let final_results = results.into_iter().reduce(|acc, b| acc.vstack(&b).unwrap()).unwrap();

这种方式只在每次循环中添加一行数据到累积集合,避免了重复生成完整切片,同时通过传递引用减少了DataFrame克隆的开销。

2. 优先使用Polars内置的窗口/累积函数(向量化优化)

如果some_fn中的操作可以拆解为逐列的滚动累积计算(比如求和、均值、最大值等),直接使用Polars内置的向量化函数会比手动循环高效得多,因为Polars会利用底层的优化执行逻辑:

例如,如果a_result是a列从起始行到当前行的累积和,可以直接写:

let df_with_results = df
    .with_columns([
        col("a").cum_sum().alias("a_result"),
        col("b").cum_sum().alias("b_result"),
        col("c").cum_sum().alias("c_result"),
    ])
    .unwrap();

如果需要更复杂的滚动窗口逻辑,可以用rolling方法自定义窗口范围:

let df_with_results = df
    .with_columns([
        col("a").rolling_sum(None).alias("a_result"),
        col("b").rolling_sum(None).alias("b_result"),
        col("c").rolling_sum(None).alias("c_result"),
    ])
    .unwrap();

其中rolling_sum(None)表示窗口从数据集起始位置到当前行。

3. 大数据集用惰性求值API优化执行计划

如果处理的是超大数据集,推荐使用Polars的惰性求值API,它会自动优化执行计划,减少中间数据的内存占用:

let lazy_df = df.lazy();

let results = lazy_df
    .with_columns([
        col("a").cum_sum().alias("a_result"),
        col("b").cum_sum().alias("b_result"),
        col("c").cum_sum().alias("c_result"),
    ])
    .collect()
    .unwrap();

关键优化总结

  • 避免频繁创建DataFrame切片和拼接,改用累积状态减少内存操作
  • 尽可能将操作向量化,优先使用Polars内置的窗口/累积函数,避免手动循环
  • 若必须处理完整切片,尽量传递DataFrame引用而非克隆整个对象
  • 大数据集优先使用惰性求值API,让Polars自动优化执行流程

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 02:20:27