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

如何在Rust Polars中应用自定义函数并返回带命名字段的Struct

两种GroupBy聚合结果的实现方案(基于Rust Polars)

原代码及现有结果

以下是一个GroupBy聚合操作的简单示例,当前代码会将每组的min和max值以数组/向量形式作为单列返回:

#[pyfunction]
fn test_fn(pydf: PyDataFrame, colnm: &str, by_cols: Vec<&str>) -> PyResult<PyDataFrame> {
    let df: DataFrame = pydf.into();
    let res = df
        .lazy()
        .groupby(by_cols)
        .agg([col(colnm).apply(
            |s| {
                let v: Vec<f64> = vec![s.min().unwrap(), s.max().unwrap()];
                Ok(Some(Series::new("s", v)))
            },
            GetOutput::default(),
        )])
        .collect()
        .map_err(PyPolarsErr::from)?;
    Ok(PyDataFrame(res))
}

#[pymodule]
fn test_module(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(test_fn, m)?)?;
    Ok(())
}

执行Python调用代码后,返回的DataFrame中a列是包含两个元素的列表(对应每组的min和max值):

import polars as pl
import test_module

df = pl.DataFrame(
    {"a": [1.0, 2.0, 3.0, 4.0, 5.0], "g1": [1, 1, 2, 2, 2], "g2": [1, 1, 1, 2, 2]}
)

>>> test_module.test_fn(df, "a", ["g1", "g2"])
shape: (3, 3)
┌─────┬─────┬────────────┐
│ g1  ┆ g2  ┆ a          │
│ --- ┆ --- ┆ ---        │
│ i64 ┆ i64 ┆ list[f64]  │
╞═════╪═════╪════════════╡
│ 1   ┆ 1   ┆ [1.0, 2.0] │
│ 2   ┆ 2   ┆ [4.0, 5.0] │
│ 2   ┆ 1   ┆ [3.0, 3.0] │
└─────┴─────┴────────────┘

需求:修改test_fn,使其返回结构体/字典而非向量,从而得到带命名字段的结果,期望两种输出格式之一。


方案一:返回Struct类型单列

利用Polars内置的struct!宏直接组合min和max值,无需使用性能较低的apply操作:

#[pyfunction]
fn test_fn(pydf: PyDataFrame, colnm: &str, by_cols: Vec<&str>) -> PyResult<PyDataFrame> {
    let df: DataFrame = pydf.into();
    let res = df
        .lazy()
        .groupby(by_cols)
        .agg([struct!(
            min = col(colnm).min(),
            max = col(colnm).max()
        ).alias(colnm)])
        .collect()
        .map_err(PyPolarsErr::from)?;
    Ok(PyDataFrame(res))
}

#[pymodule]
fn test_module(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(test_fn, m)?)?;
    Ok(())
}

执行后得到Struct类型的单列结果:

>>> test_module.test_fn(df, "a", ["g1", "g2"])
shape: (3, 3)
┌─────┬─────┬───────────┐
│ g1  ┆ g2  ┆ a         │
│ --- ┆ --- ┆ ---       │
│ i64 ┆ i64 ┆ struct[2] │
╞═════╪═════╪═══════════╡
│ 1   ┆ 1   ┆ {1.0,2.0} │
│ 2   ┆ 2   ┆ {4.0,5.0} │
│ 2   ┆ 1   ┆ {3.0,3.0} │
└─────┴─────┴───────────┘

方案二:拆分为独立的多列

直接在聚合阶段分别计算min和max并指定别名,性能最优且后续数据处理更便捷:

#[pyfunction]
fn test_fn(pydf: PyDataFrame, colnm: &str, by_cols: Vec<&str>) -> PyResult<PyDataFrame> {
    let df: DataFrame = pydf.into();
    let min_col = format!("{}_min", colnm);
    let max_col = format!("{}_max", colnm);
    
    let res = df
        .lazy()
        .groupby(by_cols)
        .agg([
            col(colnm).min().alias(&min_col),
            col(colnm).max().alias(&max_col)
        ])
        .collect()
        .map_err(PyPolarsErr::from)?;
    Ok(PyDataFrame(res))
}

#[pymodule]
fn test_module(_py: Python, m: &PyModule) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(test_fn, m)?)?;
    Ok(())
}

执行后得到拆分后的多列结果:

>>> test_module.test_fn(df, "a", ["g1", "g2"])
shape: (3, 4)
┌─────┬─────┬───────┬───────┐
│ g1  ┆ g2  ┆ a_min ┆ a_max │
│ --- ┆ --- ┆ ---   ┆ ---   │
│ i64 ┆ i64 ┆ f64   ┆ f64   │
╞═════╪═════╪═══════╪═══════╡
│ 2   ┆ 1   ┆ 3.0   ┆ 3.0   │
│ 2   ┆ 2   ┆ 4.0   ┆ 5.0   │
│ 1   ┆ 1   ┆ 1.0   ┆ 2.0   │
└─────┴─────┴───────┴───────┘

说明

  • 优先使用Polars内置的向量化聚合函数,避免apply这类逐组标量操作,后者性能远低于前者。
  • 方案二的可读性和后续数据处理便捷性更强,无需额外解析Struct结构。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 13:17:50