1. 从“选择”到“转换”Polars数据处理的核心两步如果你从Pandas转向Polars或者刚开始用Polars处理数据最先要搞明白的不是它的语法有多快而是怎么把数据“拿过来”和“变过去”。这说的就是行列选择和数据转换。很多新手卡住的地方往往是以为Polars和Pandas用法一样结果在选列、过滤行或者转换类型时报错。Polars的核心优势在于惰性执行和并行计算但前提是你的操作得在它的“语法体系”里。行列选择是定位数据的“坐标”而转换则是改变数据的“形态”比如把字符串日期变成时间戳、把分类文本转成数值。这两步做对了后续的分析、聚合、输出才能顺畅。我建议先别急着处理复杂逻辑从最基础的select和with_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_columns和cast改变数据形态选出了需要的数据下一步就是转换。转换的目的通常有两个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), # 浮点转整数会截断小数 ) # 更安全的转换使用 strictFalse 避免因转换失败而报错无法转换的会变成 null df_safe df.with_columns(pl.col(“some_column”).cast(pl.Int64, strictFalse))类型转换的常见坑精度丢失Float64转Int64会直接去掉小数部分不是四舍五入。需要四舍五入的话要先round再cast。转换失败比如把“hello”转成整数会失败。在批处理中一个失败可能导致整个任务停止。务必使用strictFalse或提前清洗数据。日期时间字符串转日期时间要用pl.col(“date_str”).str.strptime(pl.Date, “%Y-%m-%d”)而不是简单的cast。cast通常用于同大类下的类型转换如不同整数之间。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存在以下问题amount列是字符串类型且有货币符号如“$100.5”。sale_date列是“YYYYMMDD”格式的字符串。我们只需要2023年以后的数据。我们想计算一个“折扣后金额”列假设打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)这个流程的关键点顺序通常是清洗/转换类型-过滤行-计算新列/选择列。先处理脏数据和非标类型能避免后续过滤和计算出错。链式调用上述步骤可以写成一行链式调用sales_df.with_columns(...).filter(...).with_columns(...).select(...)。Polars的惰性执行会优化整个链条。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。避免物化中间结果除非调用collect、fetch或sink否则数据不会真正加载到内存中计算。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. 使用 strictFalse 在 cast 时容忍错误产生 null df df.with_columns(pl.col(“dirty_int”).cast(pl.Int64, strictFalse)) # 2. 使用 fill_null 为转换产生的 null 提供默认值 df df.with_columns( pl.col(“dirty_int”).cast(pl.Int64, strictFalse).fill_null(-1) ) # 3. 使用 replace 在转换前清洗数据 df df.with_columns( pl.col(“amount_str”).str.replace(“[^0-9.]”, “”, literalFalse).cast(pl.Float64) ) # 这个正则表达式移除了所有非数字和小数点的字符5.4 选择与转换的性能陷阱避免在select或with_columns中重复计算相同表达式。如果多个新列依赖同一个复杂计算先计算并保存到一个临时列或者使用alias后在其他表达式中引用pl.col(“temp_col”)。谨慎使用apply函数。apply会将操作切换到Python的慢速循环中失去Polars的向量化性能优势。绝大多数标准操作字符串处理、数学运算、日期计算都有内置的表达式方法应优先使用。filter条件尽量简单。复杂的filter条件可能阻碍查询优化。如果条件太复杂考虑分步过滤或者先用with_columns计算出一个布尔列再基于此列过滤。6. 总结从知道到熟练Polars的行列选择与转换核心是掌握select、filter、with_columns和cast这几个方法并理解其背后的表达式哲学pl.col。一个高效的Polars数据处理流程通常是这样的读入数据为LazyFrame如果数据量大。清洗与类型转换使用with_columns配合str.*方法、cast和strptime将数据整理成干净、类型正确的状态。行过滤使用filter基于业务逻辑筛选数据尽早减少数据量。特征工程/列转换使用with_columns计算衍生列、聚合指标等。列选择使用select精准选择最终需要的列减少内存占用和I/O。执行与输出调用collect()惰性模式或直接得到结果然后写入文件或进行下一步分析。刚开始你可能会觉得Polars的表达式写法有点绕不如Pandas的赋值直观。但一旦习惯你会发现这种声明式的风格让复杂的数据管道更清晰、更易维护并且能实实在在地享受到性能提升。最关键的是先确保单条数据转换的逻辑正确再通过链式调用组合成完整管道这是用好Polars最踏实的一条路径。