
本文介绍如何在 Polars 中高效过滤 DataFrame 行——基于另一 DataFrame 中多个子串进行批量、非循环的模糊匹配(如 SQL 的 LIKE '%pattern%'),避免逐行迭代,充分利用 Polars 的向量化能力和表达式优化。
本文介绍如何在 polars 中高效过滤 dataframe 行——基于另一 dataframe 中多个子串进行批量、非循环的模糊匹配(如 sql 的 `like '%pattern%'`),避免逐行迭代,充分利用 polars 的向量化能力和表达式优化。
在数据清洗与 ETL 场景中,常需从主表中剔除“文件名含关键词”的行(例如跳过所有以 skip 或 discard 开头的文件)。SQL 可通过 LIKE + 子查询高效完成,但若用传统 Pandas 或错误使用 Polars 循环(如 iter_rows()),不仅代码冗长,更会严重损害性能——失去 Polars 原生并行与查询优化优势。
幸运的是,Polars 自 0.20.0 起提供了专为批量子串匹配设计的 .str.contains_any() 方法,它能将目标列与一个字符串序列(如 Series)进行向量化模糊匹配,语义等价于「该字符串包含任意一个关键词」,天然适配此类过滤需求。
以下以实际数据为例演示标准做法:
假设你有两份 CSV 文件:
-
data.csv(主数据):
filename,col2 keep.txt,bar skip.txt,foo keep2.txt,zoom skip3.txt,custom1 discard.txt,custom2 file3.txt,custom3 discard2.txt,custom4 file4.txt,custom5
-
filter.csv(过滤关键词):
skip discard
✅ 推荐方案:使用 .str.contains_any()(简洁、高效、原生支持)
import polars as pl
df_data = pl.read_csv("data.csv")
df_filter = pl.read_csv("filter.csv")
# 构建过滤表达式:保留 filename 不包含 filter 中任一关键词的行
result = df_data.filter(
~pl.col("filename").str.contains_any(df_filter["skip"])
)
print(result)输出:
shape: (4, 2) ┌───────────┬─────────┐ │ filename ┆ col2 │ │ --- ┆ --- │ │ str ┆ str │ ╞═══════════╪═════════╡ │ keep.txt ┆ bar │ │ keep2.txt ┆ zoom │ │ file3.txt ┆ custom3 │ │ file4.txt ┆ custom5 │ └───────────┴─────────┘
? 原理说明:
contains_any() 接收一个 Series[str](此处为 df_filter["skip"]),内部自动编译为高效正则或哈希查找逻辑(取决于引擎),对 filename 列单次全量扫描即可完成全部关键词匹配,时间复杂度接近 O(n),远优于循环调用 contains() 的 O(n×m)。
⚠️ 注意事项与进阶建议:
-
大小写敏感性:默认区分大小写。如需忽略大小写,链式调用 .str.to_lowercase() 或启用 strict=False(部分版本需配合正则):
~pl.col("filename").str.to_lowercase().str.contains_any( df_filter["skip"].str.to_lowercase() ) -
转义特殊字符:若 filter.csv 中含正则元字符(如 .、*、?),需提前转义,否则可能误匹配:
import re escaped_keywords = df_filter["skip"].map_elements(lambda s: re.escape(s)) df_data.filter(~pl.col("filename").str.contains_any(escaped_keywords)) -
性能对比关键点:
❌ 错误示范(低效循环):# 每次 filter 都触发一次执行计划重编译 + 全表扫描 → O(m) 次 I/O 和计算 for keyword in df_filter["skip"]: df_data = df_data.filter(~pl.col("filename").str.contains(keyword))✅ 正确范式(单次执行):
# 一次性构建完整布尔表达式,由 Polars 优化器统一调度 mask = pl.col("filename").str.contains_any(df_filter["skip"]) result = df_data.filter(~mask) -
替代方案(兼容旧版 Polars):
若环境版本 < 0.20.0,可手动构造逻辑或(OR)表达式,再取反:from functools import reduce import operator patterns = df_filter["skip"].to_list() contains_expr = reduce( operator.or_, (pl.col("filename").str.contains(p) for p in patterns) ) result = df_data.filter(~contains_expr)
? 总结:
str.contains_any() 是 Polars 对 SQL LIKE 批量模式匹配的优雅映射——它消除了显式循环,让「关键词黑名单过滤」回归声明式、高性能的数据操作本质。在处理百万级文件名、日志路径或 URL 清洗任务时,该方法可带来数倍至数十倍的性能提升,同时保持代码清晰可维护。务必优先选用此原生接口,而非模拟 SQL 逻辑的手动循环或嵌套表达式。










