如何修复Rust Polars自定义gmean表达式,实现无别名聚合?
问题描述
在Rust Polars中实现自定义几何均值(gmean)表达式,希望复刻.mean()的行为:无需手动添加.alias()即可执行聚合操作并保留原列名。但当前实现运行时会报重复列名错误,必须使用.alias()才能正常执行。
期望代码逻辑:
let df = self .lazy() .select([cols(apply_to).gmean()]) .collect() .expect("Error implementing mean returns df"); Ok(df)
当前自定义实现代码:
fn geometric_mean(values: &[f64]) -> f64 { let adjusted_values: Vec<f64> = values.iter().map(|&v| 1.0 + v).collect(); let product: f64 = adjusted_values.iter().product(); let count = adjusted_values.len() as f64; product.powf(1.0 / count) } fn gmean(series: &[Series]) -> PolarsResult<Series> { let _series = &series[0]; let _chunk_array = _series.f64()?; let values: Vec<f64> = _chunk_array.into_iter().filter_map(|opt| opt).collect(); let geo_mean= geometric_mean(&values) - 1.0; Ok(Series::from_iter(vec![geo_mean])) } fn gmean_column(series: &Column) -> Result<Column,PolarsError> { let materialized_series_slice = std::slice::from_ref(series.as_materialized_series()); Ok(gmean(materialized_series_slice)?.into_column()) } pub trait GeoMean { fn gmean(self) -> Expr; } impl GeoMean for Expr { fn gmean(self) -> Expr { self.map( |column| { let geom_mean_series = gmean_column(&column)?; Ok(Some(geom_mean_series.into_column())) }, GetOutput::same_type() ) } }
报错信息:
Error implementing mean returns df: Duplicate(ErrString("the name 'literal' is duplicate It's possible that multiple expressions are returning the same default column name. If this is the case, try renaming the columns with `.alias(\"new_name\")` to avoid duplicate column names."))
预期结果:
shape: (1, 2) ┌──────────┬──────────┐ │ eer ┆ pegy │ │ --- ┆ --- │ │ f64 ┆ f64 │ ╞══════════╪══════════╡ │ 0.229572 ┆ -0.11088 │ └──────────┴──────────┘
问题原因
当前实现中,Series::from_iter(vec![geo_mean])会默认生成名为literal的Series。当对多列调用.gmean()时,所有结果列都会使用这个默认名称,导致Polars检测到重复列名报错。而.mean()这类内置聚合函数会自动继承原列的名称,所以不会有这个问题。
修复方案
核心是让自定义聚合后的Series继承原列的名称,具体修改如下:
- 给
gmean函数新增参数接收目标列名,创建Series时指定该名称 - 在
gmean_column中获取原列的名称,传递给gmean函数 - 确保表达式逻辑中保留原列的元信息
修改后的完整代码
use polars::prelude::*; fn geometric_mean(values: &[f64]) -> f64 { let adjusted_values: Vec<f64> = values.iter().map(|&v| 1.0 + v).collect(); let product: f64 = adjusted_values.iter().product(); let count = adjusted_values.len() as f64; product.powf(1.0 / count) } // 新增name参数,用于指定结果Series的名称 fn gmean(series: &[Series], name: &str) -> PolarsResult<Series> { let _series = &series[0]; let _chunk_array = _series.f64()?; let values: Vec<f64> = _chunk_array.into_iter().filter_map(|opt| opt).collect(); let geo_mean = geometric_mean(&values) - 1.0; // 使用指定的列名创建Series,而非默认的"literal" Ok(Series::new(name, vec![geo_mean])) } fn gmean_column(series: &Column) -> Result<Column, PolarsError> { let materialized_series = series.as_materialized_series(); // 获取原列的名称 let column_name = materialized_series.name(); let result_series = gmean(std::slice::from_ref(&materialized_series), column_name)?; Ok(result_series.into_column()) } pub trait GeoMean { fn gmean(self) -> Expr; } impl GeoMean for Expr { fn gmean(self) -> Expr { self.map( |column| { let geom_mean_column = gmean_column(&column)?; Ok(Some(geom_mean_column)) }, GetOutput::same_type() ) } }
验证效果
修改后运行期望的代码逻辑,即可得到保留原列名的聚合结果,无需手动添加.alias(),与内置.mean()的行为一致。
内容的提问来源于stack exchange,提问作者Trevor Seibert
相关产品推荐
相关产品推荐

