Rust Polars:基于现有Series创建新Series并解析字符串时间戳
我有一列格式不规范的时间戳字符串,需要将其解析为时间戳类型。Polars文档指出ChunkedArray是字符串的类型化容器,但我无法完成完整实现。尝试编写了如下代码:
fn with_timestamps(mut df: DataFrame) -> Result<DataFrame, PolarsError> { let column = df.column("myTime")?.clone(); // clone, just for a good measure ... let ca = column.utf8()?; // ChunkedArray // I think I want something like this: let new_time = Series::new("newTime", ca.into_iter().map(|v: &str| 42).collect()); // 42 is not a timestamp, // but maybe I can work on that from an integer df.with_column(new_time); df }
我有几个疑问:
.with_column()是否为原地操作?- 应该遍历
Series还是ChunkedArray? - 如何正确构造新的
Series?
另外,我找到相关答案后写出了可行的lazy API示例,但不理解为什么必须使用.lazy()——如果不用,Expr(比如col("Foo").alias("bar"))无法直接使用,希望了解Polars的惯用实现方式:
let df = df!("Fruit" => &["Apple", "Apple", "Pear"], "Color" => &["Red", "Yellow", "Green"], "Date" => &["02/21/2022 07:51:00 AM", "2/21/2022 07:51:00 AM", "2/21/2022 07:51:00 AM"])?; let options = StrpTimeOptions { fmt: Some("%-m/%-d/%Y %I:%M:%S %p".into()), date_dtype: polars::datatypes::DataType::Datetime(TimeUnit::Milliseconds, None), exact: true, ..Default::default() }; let foo = df .clone() .lazy() .with_columns([ col("Date") .str() .strptime(options) .alias("parsed date") ]) .collect();
1. 关于.with_column()的原地操作问题
.with_column()不是原地操作,它会返回一个包含新列的DataFrame实例,原DataFrame不会被修改。你代码中df.with_column(new_time);没有重新赋值,相当于丢弃了新生成的DataFrame,正确写法是:
df = df.with_column(new_time)?;
注意要处理with_column()返回的Result,用?或match处理错误。
2. 遍历选择:优先用向量化操作,避免手动遍历
Polars的核心优势是向量化计算,手动遍历ChunkedArray迭代器会失去性能优化。解析时间戳不需要手动遍历,直接用Utf8ChunkedArray提供的strptime向量化方法即可,它会批量处理所有元素,效率远高于手动循环。
3. 两种惯用实现方式
方式一:Eager API(立即执行)
直接操作ChunkedArray完成解析,适合简单的列转换场景:
use polars::prelude::*; fn with_timestamps(mut df: DataFrame) -> Result<DataFrame, PolarsError> { // 获取目标列的Utf8类型ChunkedArray let str_ca = df.column("myTime")?.utf8()?; // 定义时间解析规则,根据你的实际时间格式调整fmt参数 let parse_options = StrpTimeOptions { fmt: Some("%-m/%-d/%Y %I:%M:%S %p".into()), date_dtype: DataType::Datetime(TimeUnit::Milliseconds, None), exact: true, ..Default::default() }; // 向量化解析字符串为Datetime类型的ChunkedArray let datetime_ca = str_ca.strptime(parse_options)?; // 将ChunkedArray转为Series并添加到DataFrame let new_series = datetime_ca.into_series().rename("newTime"); df = df.with_column(new_series)?; Ok(df) }
方式二:Lazy API(延迟执行)
先构建查询计划,最后通过collect()执行,适合复杂查询场景(Polars会自动做查询优化,比如列裁剪、谓词下推):
use polars::prelude::*; fn with_timestamps_lazy(df: DataFrame) -> Result<DataFrame, PolarsError> { let parse_options = StrpTimeOptions { fmt: Some("%-m/%-d/%Y %I:%M:%S %p".into()), date_dtype: DataType::Datetime(TimeUnit::Milliseconds, None), exact: true, ..Default::default() }; let result_df = df .lazy() .with_columns([ col("myTime") .str() .strptime(parse_options) .alias("newTime") ]) .collect()?; Ok(result_df) }
关于Lazy vs Eager的区别
- Eager API:操作的是实际存在的
Series/ChunkedArray,调用方法时立即执行计算,适合简单、直观的操作。 - Lazy API:操作的是
Expr表达式,它代表要执行的计算逻辑,但不会立即执行。直到调用collect()时,Polars才会优化查询计划并执行计算,适合复杂查询,性能更优。
你之前困惑的“为什么eager不能用Expr”,是因为Expr是Lazy API的专属抽象,它描述的是要做什么,而非直接操作数据;而Eager API需要直接操作具体的数据实例(Series/ChunkedArray),所以要用对应的方法(比如ChunkedArray::strptime)而非Expr。
内容的提问来源于stack exchange,提问作者user4344

