news 2026/8/19 10:35:42

Polars数据处理核心:行列选择与数据转换实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Polars数据处理核心:行列选择与数据转换实战指南

1. 从“选择”到“转换”:Polars数据处理的核心两步

如果你从Pandas转向Polars,或者刚开始用Polars处理数据,最先要搞明白的不是它的语法有多快,而是怎么把数据“拿过来”和“变过去”。这说的就是行列选择数据转换。很多新手卡住的地方,往往是以为Polars和Pandas用法一样,结果在选列、过滤行或者转换类型时报错。

Polars的核心优势在于惰性执行和并行计算,但前提是你的操作得在它的“语法体系”里。行列选择是定位数据的“坐标”,而转换则是改变数据的“形态”,比如把字符串日期变成时间戳、把分类文本转成数值。这两步做对了,后续的分析、聚合、输出才能顺畅。

我建议先别急着处理复杂逻辑,从最基础的selectwith_columns开始,把单张表玩熟。你会发现,很多在Pandas里需要绕一下的操作,在Polars里有更直接、更高效的做法。这篇文章就围绕这两个核心动作,拆解具体的用法、常见的坑,以及怎么组合起来解决实际问题。

2. 行列选择:用对方法,效率翻倍

行列选择是数据操作的起点。Polars提供了多种方式,但每种都有其适用的场景和性能考量。用错了方法,在数据量大的时候,速度差异会非常明显。

2.1 列选择:select是主力,[]是快捷方式

选择列最常用的方法是select。它接受列名、表达式,或者一个列名列表。

import polars as pl # 假设我们有一个DataFrame `df` df = pl.DataFrame({ “id”: [1, 2, 3], “name”: [“Alice”, “Bob”, “Charlie”], “value”: [100.5, 200.3, 150.0], “category”: [“A”, “B”, “A”] }) # 1. 选择单列或多列 df_selected = df.select(“name”, “value”) # 选择 name 和 value 列 df_selected = df.select([“name”, “value”]) # 用列表形式也一样 # 2. 使用 pl.col 对象进行更灵活的选择 df_selected = df.select(pl.col(“name”), pl.col(“value”) * 2) # 选择name列,并计算value*2作为一个新列

pl.col是构建表达式的核心,它允许你在选择的同时进行计算。这是Polars非常强大的地方,你不需要先选择列,再赋值,可以一步到位。

除了select,你也可以用类似Pandas的[]索引,但这通常只用于非常简单的列名选取,且返回的是一个Series(单列)或新的DataFrame(多列)。对于复杂的表达式,[]就无能为力了。

# 使用 [] 选择单列(返回 Series) name_series = df[“name”] # 使用 [] 选择多列(返回 DataFrame) sub_df = df[[“name”, “value”]]

我的经验是:只要是涉及列的操作,无论是单纯选择还是附带计算,优先使用select配合pl.col[]只在你临时需要查看某一列,或者进行非常简单的切片时用。因为select能更好地融入Polars的表达式API和惰性执行框架。

2.2 行选择:filter是核心,切片要小心

选择行主要靠filter方法,它接受一个布尔表达式。

# 选择 value 大于 150 的行 df_filtered = df.filter(pl.col(“value”) > 150) # 组合条件选择 df_filtered = df.filter((pl.col(“value”) > 150) & (pl.col(“category”) == “A”))

这里的关键是,条件表达式必须用pl.col来构建。直接写df[“value”] > 150在某些简单情况下可能行得通,但在复杂查询或惰性模式下容易出错,不推荐。

对于按位置选择行(切片),Polars提供了slice方法。但要注意,在惰性LazyFrame上直接按整数位置切片是低效的,因为它通常需要先计算或排序。在DataFrame上可以,但要明白其含义。

# 选择前2行 df_head = df.slice(0, 2) # 跳过前1行,取之后的所有行 df_tail = df.slice(1, -1) # -1 表示剩余全部

一个重要的避坑点:不要试图用df[0:2]这样的Python列表切片语法来选择行,这在Polars中通常不是你想要的结果,或者会直接报错。行选择,逻辑条件用filter,位置切片用slice,界限要清晰。

2.3 组合选择:行列同时筛选

实际工作中,我们经常需要同时筛选行和列。Polars鼓励链式调用,顺序很重要。

# 先筛选行,再选择列(更高效) result = df.filter(pl.col(“category”) == “A”).select(“id”, “name”, “value”) # 也可以先选择列,再筛选行。但在惰性执行下,优化器会尝试重排操作,最终效率可能差不多。 # 对于人类阅读来说,“先过滤行,减少数据量,再选列”的逻辑更直观。

在惰性执行(LazyFrame)中,Polars的查询优化器会自动对操作进行重新排序(如谓词下推、投影下推)以获得最佳性能。所以你可以按照最自然的逻辑去写,Polars会在后台帮你优化。

3. 数据转换:用with_columnscast改变数据形态

选出了需要的数据,下一步就是转换。转换的目的通常有两个:1) 改变数据类型;2) 基于现有列计算新列。

3.1 类型转换:cast方法

这是最直接的“转换”。比如把字符串转成整数、浮点数转成整数、或者转成日期类型。

# 假设 value 列是字符串,我们想转成浮点数 df_converted = df.with_columns(pl.col(“value”).cast(pl.Float64)) # 一次转换多列类型 df_converted = df.with_columns( pl.col(“id”).cast(pl.Int32), pl.col(“value”).cast(pl.Int64), # 浮点转整数会截断小数 ) # 更安全的转换:使用 `strict=False` 避免因转换失败而报错,无法转换的会变成 null df_safe = df.with_columns(pl.col(“some_column”).cast(pl.Int64, strict=False))

类型转换的常见坑:

  1. 精度丢失Float64Int64会直接去掉小数部分,不是四舍五入。需要四舍五入的话,要先roundcast
  2. 转换失败:比如把“hello”转成整数会失败。在批处理中,一个失败可能导致整个任务停止。务必使用strict=False或提前清洗数据。
  3. 日期时间:字符串转日期时间要用pl.col(“date_str”).str.strptime(pl.Date, “%Y-%m-%d”),而不是简单的castcast通常用于同大类下的类型转换(如不同整数之间)。

3.2 计算新列与列变换:with_columns的舞台

with_columns是Polars中添加新列或替换现有列的主要方法。它可以在保留原有列的同时,添加由表达式计算出的新列。

# 1. 添加新列 df_new = df.with_columns( (pl.col(“value”) * 1.1).alias(“value_with_tax”), # 计算含税价,新列名为 value_with_tax pl.lit(“processed”).alias(“status”) # 添加一个常量列 ) # 2. 替换(覆盖)现有列 df_replaced = df.with_columns( (pl.col(“value”) * 2).alias(“value”) # 列名与现有列相同,则覆盖 ) # 3. 复杂的表达式转换 df_complex = df.with_columns( pl.when(pl.col(“value”) > 150) .then(pl.lit(“High”)) .otherwise(pl.lit(“Low”)) .alias(“value_level”), # 条件赋值,类似 SQL CASE WHEN pl.col(“name”).str.to_uppercase().alias(“name_upper”) # 字符串操作 )

with_columns的核心优势:

  • 并行计算:每个表达式独立计算,Polars会自动并行化。
  • 表达式API:可以与pl.col、字符串函数、时间函数等无缝结合。
  • 惰性友好:在LazyFrame上使用,可以极大优化整个查询计划。

与Pandas的对比:在Pandas里,你可能会写df[‘new_col’] = df[‘old_col’] * 2。在Polars里,对应的就是df.with_columns((pl.col(‘old_col’) * 2).alias(‘new_col’))。虽然写法不同,但Polars的方式更符合其函数式、表达式的设计哲学,也更容易被优化。

3.3 使用select进行“转换式选择”

有时候,转换的目的就是为了选出新的结果集,这时可以直接用select

# 选择原有列的同时,直接计算并选择新列 result = df.select( “id”, “name”, (pl.col(“value”) * 1.1).alias(“value_taxed”), pl.col(“category”).cast(pl.Categorical).alias(“category_cat”) )

这相当于先with_columns添加新列,再select选择它们。对于一次性转换并输出的场景,直接用select更简洁。

4. 实战串联:一个完整的数据清洗与转换流程

现在我们把行列选择和转换组合起来,看一个更接近真实场景的例子:处理一份销售数据。

假设我们有一个原始的销售记录DataFramesales_df,存在以下问题:

  1. amount列是字符串类型,且有货币符号(如“$100.5”)。
  2. sale_date列是“YYYYMMDD”格式的字符串。
  3. 我们只需要2023年以后的数据。
  4. 我们想计算一个“折扣后金额”列(假设打9折),并只保留相关列输出。
import polars as pl # 模拟原始数据 sales_df = pl.DataFrame({ “order_id”: [“A001”, “A002”, “A003”, “A004”], “amount”: [“$100.50”, “$200.00”, “$150.75”, “$80.30”], “sale_date”: [“20221215”, “20230120”, “20230510”, “20240105”], “customer”: [“Alice”, “Bob”, “Alice”, “Charlie”] }) print(“原始数据:”) print(sales_df) print(sales_df.schema) # 查看数据类型 # 第一步:类型转换与清洗 # 1. 清洗 amount 列:去掉`$`,转成浮点数 # 2. 转换 sale_date 列:字符串转日期 df_cleaned = sales_df.with_columns( pl.col(“amount”).str.replace_all(“\$”, “”).cast(pl.Float64).alias(“amount”), pl.col(“sale_date”).str.strptime(pl.Date, “%Y%m%d”).alias(“sale_date”) ) print(“\n清洗转换后:”) print(df_cleaned) print(df_cleaned.schema) # 第二步:行筛选(过滤) # 只保留2023年及以后的销售记录 df_filtered = df_cleaned.filter(pl.col(“sale_date”) >= pl.Date(2023, 1, 1)) print(“\n过滤后(2023年以后):”) print(df_filtered) # 第三步:列转换与选择 # 1. 计算折扣后金额(9折) # 2. 选择最终需要的列 final_df = df_filtered.with_columns( (pl.col(“amount”) * 0.9).alias(“amount_after_discount”) ).select( “order_id”, “customer”, “sale_date”, “amount”, “amount_after_discount” ) print(“\n最终结果:”) print(final_df)

这个流程的关键点:

  1. 顺序:通常是清洗/转换类型->过滤行->计算新列/选择列。先处理脏数据和非标类型,能避免后续过滤和计算出错。
  2. 链式调用:上述步骤可以写成一行链式调用:sales_df.with_columns(...).filter(...).with_columns(...).select(...)。Polars的惰性执行会优化整个链条。
  3. alias的使用:在with_columns中,如果新列名与旧列名相同(如amount),会覆盖旧列。如果想保留旧列,需要指定不同的alias

5. 性能考量与进阶技巧

当你熟悉基础操作后,这些进阶技巧能帮你写出更高效、更优雅的Polars代码。

5.1 惰性执行(LazyFrame)下的选择与转换

所有上述操作在LazyFrame上同样适用,而且强烈建议在数据处理管道中使用惰性模式。

# 创建 LazyFrame lazy_sales = sales_df.lazy() # 构建完整的惰性查询计划 query = (lazy_sales .with_columns( pl.col(“amount”).str.replace_all(“\$”, “”).cast(pl.Float64).alias(“amount”), pl.col(“sale_date”).str.strptime(pl.Date, “%Y%m%d”) ) .filter(pl.col(“sale_date”) >= pl.Date(2023, 1, 1)) .with_columns((pl.col(“amount”) * 0.9).alias(“amount_after_discount”)) .select(“order_id”, “customer”, “sale_date”, “amount”, “amount_after_discount”) ) # 查看优化后的查询计划(可选,用于调试) print(query.explain()) # 真正执行计算,得到结果 DataFrame final_df_lazy = query.collect()

惰性执行的优势在于:

  • 查询优化:Polars会合并操作、下推谓词和投影,减少中间数据移动。
  • 流水线并行:整个计划可以更高效地利用多核CPU。
  • 避免物化中间结果:除非调用collectfetchsink,否则数据不会真正加载到内存中计算。

5.2 多列批量操作

当需要对很多列进行相同操作时(比如将所有数值列转为Float32),手动写每一列很麻烦。Polars提供了便捷的方式。

# 选择所有数值列进行转换 df = df.with_columns(pl.col(pl.NUMERIC_DTYPES).cast(pl.Float32)) # 选择所有列名以“_temp”结尾的列并删除 df = df.select(pl.all().exclude(pl.col(“^.*_temp$”))) # 使用正则表达式排除 # 对多列应用同一个字符串操作 df = df.with_columns(pl.col([“col1”, “col2”, “col3”]).str.strip_chars())

5.3 处理转换中的错误与空值

数据转换时,脏数据或边界情况可能导致错误。Polars的表达式API提供了稳健的处理方式。

# 1. 使用 `strict=False` 在 cast 时容忍错误,产生 null df = df.with_columns(pl.col(“dirty_int”).cast(pl.Int64, strict=False)) # 2. 使用 `fill_null` 为转换产生的 null 提供默认值 df = df.with_columns( pl.col(“dirty_int”).cast(pl.Int64, strict=False).fill_null(-1) ) # 3. 使用 `replace` 在转换前清洗数据 df = df.with_columns( pl.col(“amount_str”).str.replace(“[^0-9.]”, “”, literal=False).cast(pl.Float64) ) # 这个正则表达式移除了所有非数字和小数点的字符

5.4 选择与转换的性能陷阱

  1. 避免在selectwith_columns中重复计算相同表达式。如果多个新列依赖同一个复杂计算,先计算并保存到一个临时列,或者使用alias后在其他表达式中引用pl.col(“temp_col”)
  2. 谨慎使用apply函数apply会将操作切换到Python的慢速循环中,失去Polars的向量化性能优势。绝大多数标准操作(字符串处理、数学运算、日期计算)都有内置的表达式方法,应优先使用。
  3. filter条件尽量简单。复杂的filter条件可能阻碍查询优化。如果条件太复杂,考虑分步过滤,或者先用with_columns计算出一个布尔列,再基于此列过滤。

6. 总结:从知道到熟练

Polars的行列选择与转换,核心是掌握selectfilterwith_columnscast这几个方法,并理解其背后的表达式哲学(pl.col)。

一个高效的Polars数据处理流程通常是这样的:

  1. 读入数据LazyFrame(如果数据量大)。
  2. 清洗与类型转换:使用with_columns配合str.*方法、caststrptime,将数据整理成干净、类型正确的状态。
  3. 行过滤:使用filter基于业务逻辑筛选数据,尽早减少数据量。
  4. 特征工程/列转换:使用with_columns计算衍生列、聚合指标等。
  5. 列选择:使用select精准选择最终需要的列,减少内存占用和I/O。
  6. 执行与输出:调用collect()(惰性模式)或直接得到结果,然后写入文件或进行下一步分析。

刚开始你可能会觉得Polars的表达式写法有点绕,不如Pandas的赋值直观。但一旦习惯,你会发现这种声明式的风格让复杂的数据管道更清晰、更易维护,并且能实实在在地享受到性能提升。最关键的是,先确保单条数据转换的逻辑正确,再通过链式调用组合成完整管道,这是用好Polars最踏实的一条路径。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/19 10:35:27

如何一键给安卓APK应用加卡密验证?Ars验证系统防破解授权保护方案

在安卓软件防破解领域,将卡密验证模块注入到目标 APK 中,是一种常见的防破解保护手段。通过 APK 注入技术,可以把卡密验证逻辑嵌入到目标应用内部,使应用启动时先弹出卡密验证界面,未通过验证则无法进入主功能。Ars验证…

作者头像 李华
网站建设 2026/8/19 10:34:05

受够了散乱的文件?用OpenKM文档管理系统搭建统一文档平台

受够了散乱的文件?用OpenKM文档管理系统搭建统一文档平台 【免费下载链接】document-management-system OpenKM is a Open Source Document Management System 项目地址: https://gitcode.com/gh_mirrors/do/document-management-system 如果你的团队还在靠Q…

作者头像 李华
网站建设 2026/8/19 10:28:49

SAP Gateway 后端错误响应控制,如何让 OData 错误既能给用户看,也能让开发人员快速定位

在 SAP Gateway 项目里,最令人头疼的接口问题往往并不是 OData 请求直接返回 500 Internal Server Error,而是接口明明已经发现了业务问题,却不知道应该把什么信息返回给前端。 销售订单保存失败了,后台 BAPI 返回十几条消息。到底哪一条应该显示在 SAP Fiori 页面顶部,哪…

作者头像 李华
网站建设 2026/8/19 10:25:13

基于Qt与QCustomPlot的串口绘图仪开发:从架构设计到工程实践

1. 项目概述:串口绘图仪是什么,以及为什么你需要它 如果你玩过Arduino、ESP32或者树莓派Pico这类微控制器,那你肯定对“串口打印调试”不陌生。每次想看看传感器数据,比如温度、加速度或者一个自定义的变量值,最常见的…

作者头像 李华