ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Polars快速入门:高性能DataFrame库与数据处理实践

Polars快速入门:高性能DataFrame库与数据处理实践 不用着急咱们先把这个冷门角落照亮。Polars 在刚出现那几年确实属于小众玩具毕竟 Pandas 已经占据了数据处理半壁江山。但如果你是那种要高频处理几千万行、上亿行数据的人迟早会撞上内存爆炸、跑批慢到怀疑人生的坎。Polars 就在这种背景下冲了出来而且大部分写过几年数据处理代码的人第一次上手就会有一种“原来 DataFrame 还能这么痛快”的感觉。这套简明基础教程定位就是给两类人看一类是在 Pandas 里挣扎已久、希望提速换挡的数据开发者另一类是刚准备入行数据分析/数据工程、想直接选择一个更现代工具的新手。通篇围绕一个核心主题怎么用 Polars 快速完成日常数据读取、筛选、分组、聚合、连接等高频操作并且理解它背后那些让你“用得爽”的设计思路。不堆砌文档也不做源码考古只讲实际操作中有价值的部分。1. Polars 是什么为什么这两年大家都在聊它先花三分钟把定位搞清楚后面动手才不会总觉得别扭。Polars 是一个基于Apache Arrow内存列式格式、使用Rust编写的 DataFrame 库。语法上你会有天然的熟悉感因为它的接口设计吸取了 Pandas 的便利性同时又借鉴了 SQL 的表达习惯所以整体学习曲线并不陡峭。核心卖点有三个我展开聊聊单机场景下惊人的多核利用能力。大家遇到大数据集第一反应是上 Spark。但 Spark 有调度开销而且小数据集上反而不如单机工具快。Polars 可以直接使用机器所有 CPU 核心进行并行计算因为 Rust 本身没有 GIL 锁的限制底层又多利用 Arrow 列式存储做向量化操作所以它在几 GB 到几十 GB 的数据集上常常能做到比 Pandas 快数倍到数十倍。注意Polars 不是要替代 Spark而是解决“单机范围内最极致性能”这个需求。LazyFrame 延迟计算带来的查询优化。这是它和 Pandas 最本质的区别。Pandas 是即时计算Eager每写一步就执行一步。Polars 给了你一个 Lazy 模式所有操作先构建成一个逻辑查询计划不真正运行直到你主动collect()才一次性执行。这个过程中 Polars 的优化器会自动做谓词下推、投影下推、连接重排等优化很多情况下你不需要手写调优技巧它帮你做了。类 SQL 的表达一致性。你可以把select、filter、group_by、join这些操作按照几乎固定的语序串联起来代码在可读性上比 Pandas 的方法链更加规整。1.1 它和 Pandas / Spark 的定位差异先直接给一张对比表有助于你想清楚什么场景该用谁对比维度PandasPolarsSpark计算模式Eager即时计算Eager Lazy延迟计算主要是 Lazy 调度加 DAG 执行核心语言Python/C/CythonRustScala/Java内存格式基于 NumPy 的内存块类型不够统一Apache Arrow 列式格式零拷贝共享JVM 内部二进制格式多核支持部分操作多线程但受 GIL 影响明显所有算子设计为并行分布式、集群并行适用数据规模单机、内存放得下但开始卡的级别单机/单节点可超出内存流式集群级别海量数据学习曲线低中低高这个表格不是贬低 PandasPandas 在数据处理生态中的深度时间序列、各种统计模型协同、大量第三方库接口目前仍然无法被完全取代。但问题是当数据量大到 Pandas 卡顿、内存爆掉时你用 Python 生态的其他库也很难救。Polars 就正好卡在“Pandas 有点带不动、Spark 又太重”这个甜点区间。1.2 真正让我决定切换的实际场景分享一下我个人的经历。之前处理一份某零售业务的订单明细表单表 7000 万行左右字段大概 40 个存储为 Parquet 文件原始占用接近 8GB。用 Pandas 做一次按月聚合、然后和前一年同期 join 对比的操作跑完大概需要 11 分钟内存峰值跑到 36GB 左右那台 64GB 的开发机风扇直接起飞其他任务基本没法做了。换成 Polars 后同一台机器同一份数据做同样的逻辑首次冷缓存跑下来大约 1 分 20 秒内存峰值大概 12GB。这个差异给我触动挺深的那种感觉就像以前开车走国道堵到怀疑人生突然换上了高速路。所以这篇快速入门核心不是要你立刻放弃 Pandas而是让你尽早知道有一个更好的选项并且在实际项目中能快速上手。2. 安装与环境准备比想象中更平滑的接入体验安装 Polars 不需要预装 Rust 工具链直接使用 Python 包安装工具从 PyPI 拉取即可。pip install polars如果你需要额外支持 Excel、Cloud 存储等扩展功能可以安装带 extra 依赖的版本比如pip install polars[all]这里我建议常规使用直接pip install polars就够了等真的需要某些扩展功能时再按需安装避免一开始就把环境搞得很臃肿。安装完成后可以通过 Python 交互式环境验证一下import polars as pl print(pl.__version__)目前 Polars 的版本号已经稳定在 1.x 之后1.0 版本的 API 经过了大范围的整理整体稳定性比 0.x 时代高很多。如果你之前用过 0.20 以下的版本升级到 1.x 之后会发现很多函数参数被重新命名一些旧写法会报 FutureWarning 甚至直接报错建议保持依赖相对较新的版本。2.1 基础数据结构速览Polars 中最核心的数据结构有两种Series一维数据数组类似 Pandas 的 Series但底层就是 Arrow 列。DataFrame二维表格数据结构由多个 Series 组成每一列都是相同数据类型的。空手创建一个 DataFrame 是入门第一步下面这几种方式都很常用import polars as pl # 从字典创建 df pl.DataFrame({ name: [Alice, Bob, Charlie], age: [25, 30, 35], city: [Beijing, Shanghai, Shenzhen] }) print(df)输出效果shape: (3, 3) ┌─────────┬─────┬───────────┐ │ name ┆ age ┆ city │ │ --- ┆ --- ┆ --- │ │ str ┆ i64 ┆ str │ ╞═════════╪═════╪═══════════╡ │ Alice ┆ 25 ┆ Beijing │ │ Bob ┆ 30 ┆ Shanghai │ │ Charlie ┆ 35 ┆ Shenzhen │ └─────────┴─────┴───────────┘注意表格中输出的每一列都列出了数据类型这是 Polars 的一个特点它非常强调显式类型不会像 Pandas 那样搞出全列 object 类型的情况。从列表、NumPy 数组、甚至多个 Series 组合也都是支持的import numpy as np df2 pl.DataFrame( np.random.randn(5, 3), schema[a, b, c], ) series_from_list pl.Series(scores, [88, 92, 75, 66])有一点需要特别提醒Polars 的 DataFrame 是不区分行索引的。Pandas 里面df.iloc[0]那种“第 0 行”的概念在 Polars 中仍然能用.row(0)来取但没有传统的 Index 对象一说。这一点对于老 Pandas 用户来说是最需要调整的心智模型后面还会专门讲到。2.2 Eager 与 Lazy理解 Polars 性能的钥匙在快速入门阶段我认为最先要建立的不是写一堆操作而是理解计算模式因为这是 Polars 整个设计的分水岭。Eager API和 Pandas 类似调用的每一步都会立即计算出结果返回一个新的 DataFrame。Lazy API你输入的是 DataFrame 或扫描器产生的 LazyFrame所有操作都只是记录在逻辑计划里直到调用.collect()才真正执行。实际使用中我强烈建议凡是超过几千万行的数据处理任务一律以 Lazy API 为主。不仅因为性能更优而且代码逻辑上先描述转换步骤、最后一次性收集结果本身也更好读。lazy_df pl.scan_csv(large_data.csv) # scan 开头就是 Lazy API result ( lazy_df .filter(pl.col(age) 20) .group_by(city) .agg(pl.col(score).mean()) .collect() # 触发计算 )pl.scan_csv不会真的把数据全部读入内存而是生成一个 LazyFrame除了必要的元数据数据还是躺在磁盘上的。后面的 filter 会推送到文件扫描层只读取和最终结果有关的行和列。这就是谓词下推和投影下推的实际效果。3. 快速上手创建 DataFrame 与读写文件真正进入业务前先把读写数据的能力练熟这是所有数据处理项目的起点。3.1 CSV 读取的细节与参数常识读 CSV 最直观的方式是pl.read_csv它一次性加载数据到内存df pl.read_csv(sales_data.csv)如果说要读取的文件很大或者你根本不需要所有列使用pl.scan_csv配合 Lazy 模式会更合适lazy_df pl.scan_csv( sales_data.csv, separator,, has_headerTrue, try_parse_datesTrue, infer_schema_length10000, # 增加推断行数避免类型误判 )几个参数我在实战中的经验infer_schema_length默认是 100 行如果某些列前面 100 行都是空值或相同值后面才有不同内容容易导致类型推断错误。我一般调高到 10000 行以上。try_parse_datesTrue会自动识别一些常见日期格式建议保留。如果文件里有脏数据偶尔会有解析报错可以设置ignore_errorsTrue临时跳过坏行但要注意这样可能造成数据缺失需要自查风险。3.2 ParquetPolars 的“最佳搭档”使用 Polars 的人会很快意识到 Parquet 格式的价值。原因在于 Parquet 列式存储加上压缩和 Arrow 内存列式格式之间的转换开销极小读大文件时 I/O 大量减少。# 读取 df pl.read_parquet(data.parquet) # 写入compression 可选 zstd / lz4 / snappy df.write_parquet(output.parquet, compressionzstd)这里我提到了 zstd实际使用中它是我最常用的压缩算法压缩率高且速度也快。一次实际测试中一个 5GB CSV 文件转为 zstd 压缩的 Parquet 后大约 1.2GB读取时间从 40 秒降到 6 秒一次效果非常显著。3.3 DataFrame 的基础查看与操作创建好 DataFrame 后第一件事当然是“看看这个表长什么样”# 前几行 df.head() # 后几行 df.tail() # 形状 df.shape # 所有列名与类型 df.schema # 整体描述统计 df.describe()这里要提醒一个坑df.head()在 Polars 和 Pandas 里都返回新对象不会影响原对象本身这一点没问题。但如果你想在查看前几行之前先做一次行列裁剪避免大表打印慢可以使用df.select(pl.first()).head() # 取第一列 df.select(pl.col(name, age)).head(10)或者更直接地把 Lazy 和 Eager 结合使用result pl.scan_parquet(big.parquet).select([name, age]).head(10).collect()这个模式在实际探索数据时非常实用可以快速在超大文件上进行试错而不是每次都全量加载。3.4 写入与导出你需要注意的数据类型问题Polars 导出到 Pandas、Arrow 等格式也非常方便# 转为 Pandas DataFrame df_pd df.to_pandas() # 转为 Arrow Table df_arrow df.to_arrow() # 从 Pandas 导入 df_round pl.from_pandas(df_pd)但在from_pandas时有个常见的坑如果 Pandas DataFrame 的某些列是object类型比如混合类型 或者 字符串字典转换过来的列类型可能不是你预期的 str 或 int需要手动 cast。例如df_clean df_round.with_columns( pl.col(age).cast(pl.Int64) )Polars 的列名中如果有空格或特殊字符用pl.col()引用时要用字符串包裹。比如pl.col(user name)是合法的这种规则和 SQL 的加引号列名有些类似值得注意。4. 高频操作实战筛选、聚合、分组、排序与连接现在进入整篇教程最核心的部分。我会用一套模拟的订单销售数据带你走一遍最常用的数据处理操作流程。这套代码你在自己机器上跑完基本就具备独立处理日常数据分析任务的能力了。先构建示例数据import polars as pl df pl.DataFrame( { order_id: [A001, A002, A003, A004, A005, A006, A007, A008], user_id: [u1, u2, u1, u3, u4, u2, u5, u1], category: [Electronics, Books, Electronics, Books, Clothing, Clothing, Electronics, Books], amount: [1200, 88, 2599, 45, 399, 120, 899, 76], quantity: [1, 2, 1, 1, 2, 1, 1, 3], order_date: [2024-01-01, 2024-01-02, 2024-01-03, 2024-01-05, 2024-01-07, 2024-01-08, 2024-01-10, 2024-01-12], } ).with_columns( pl.col(order_date).str.strptime(pl.Date, %Y-%m-%d) ) print(df)4.1 筛选行filter 而不是 boolean indexing在 Pandas 里你可能熟悉df[df[amount] 100]这样的布尔索引写法。Polars 中统一使用filter# 单条件 df_filtered df.filter(pl.col(amount) 200) # 多条件与 df_multi df.filter( (pl.col(amount) 100) (pl.col(category) Books) ) # 多条件或 | df_or df.filter( (pl.col(category) Electronics) | (pl.col(quantity) 2) ) # 取反~ df_not df.filter( ~pl.col(category).is_in([Clothing, Books]) )多说一句is_in是个非常好用的操作替代 SQL 里的IN子句尤其当筛选集合来自另一个列表时。4.2 选择列select 和 with_columns 的配合select用于从 DataFrame 中选择列并可在选择过程中创建新列。# 只选择两列 df.select([user_id, amount]) # 选择列并重命名 df.select([ pl.col(user_id).alias(uid), pl.col(amount).alias(order_amount), ]) # 选择列并创建新表达式 df.select([ pl.col(amount) * pl.col(quantity), ])但如果你想在保留原始所有列的基础上新增一列应该用with_columnsdf df.with_columns( (pl.col(amount) * pl.col(quantity)).alias(total_amount) )这里有一个容易踩的坑Polars 中的with_columns可以一次加多列而且这些新列的表达式可以引用此前在同一with_columns中新创建的列。这个特性很灵活但依赖顺序。比如df.with_columns( pl.col(quantity).sum().alias(global_quantity_sum), (pl.col(amount) / pl.col(global_quantity_sum)).alias(avg_amount_per_total_quantity), )我第一次使用时以为第二列表达式会引用第一列的结果实际上 Polars 在同一个with_columns内确实会按顺序解析但为了避免混淆我还是建议把有依赖关系的新列拆成多个with_columns逻辑更清晰。4.3 分组聚合group_by agg 是绝对主力Polars 的分组聚合语法非常接近 SQL 的GROUP BY加各种聚合函数grouped df.group_by(category).agg( pl.col(amount).sum().alias(total_amount), pl.col(order_id).count().alias(order_count), pl.col(amount).mean().alias(avg_amount), )注意一个重要差异在 Pandas 中groupby.agg的聚合结果通常会以分组列为索引需要reset_index()才能变成普通列。而 Polars 的group_by().agg()默认就直接把分组列保留为普通列不会有索引问题。多列分组也直接支持df.group_by([category, user_id]).agg( pl.col(amount).sum().alias(total_amount), ).sort(total_amount, descendingTrue)4.4 排序sort 方法与 descending 参数Polars 排序很简单df.sort(amount) # 默认升序 df.sort(amount, descendingTrue) # 降序 df.sort([category, amount], descending[False, True]) # 多列类别升序金额降序如果你在group_by().agg()之后排序可以用sort_by表达式或者直接.sort(聚合列名)效果一样。排序环节有一个实际经验在大数据量下对整列做排序开销比较大。如果只是需要每组内部排序建议用sort_by表达式结合over窗口函数避免对全表排一遍。4.5 连接操作join 的参数与性能优势Polars 用join做表连接语法比 Pandas 清晰而且性能极好。df_orders pl.DataFrame({ order_id: [A001, A002, A003], user_id: [u1, u2, u1], amount: [1200, 88, 2599], }) df_users pl.DataFrame({ user_id: [u1, u2, u3], city: [Beijing, Shanghai, Guangzhou], }) joined df_orders.join(df_users, onuser_id, howleft) print(joined)how参数支持inner、left、outer、cross、semi、anti等其中semi和anti在过滤场景中非常有用semi join保留左表在右表中有匹配的行但不会把右表的列拼进来。anti join保留左表在右表中没有匹配的行。# 只保留有用户信息的订单 df_semi df_orders.join(df_users, onuser_id, howsemi) # 找出没有用户信息的订单 df_anti df_orders.join(df_users, onuser_id, howanti)这种写法比先left join再筛选空值更加可读性能也更好如果你之前只用过inner/left/outer值得在真实项目中尝试一下。连接性能方面Polars 本身做了哈希连接和排序连接等多种策略优化常规数据规模下几乎不需要手动指定连接算法。但有一点可以留意连接键的类型必须一致比如一个是字符串一个是整数会报错或匹配不上这点和数据库约束类似。4.6 窗口函数over 的灵活应用Polars 的over可以说是最令人惊喜的功能之一它可以实现GROUP BY与保留原明细行同时存在的优雅方式。举一个例子我们希望计算每一行订单金额占该用户总订单金额的比例。result df.with_columns( (pl.col(amount) / pl.col(amount).sum().over(user_id)).alias(amount_ratio) )这段代码的解释是对每个user_id分组计算组内金额总和然后用当前行的金额除以组内总和。这个操作如果放到 Pandas 里面你需要groupbytransform或者 merge 一个聚合结果步骤繁琐且容易出错。再看更复杂的场景给每个订单按照金额排名取每组前 N 名。df.with_columns( pl.col(amount).rank(descendingTrue).over(category).alias(rank_in_category) ).filter(pl.col(rank_in_category) 2)4.7 复杂聚合与长宽表变换pivot 和 melt实际业务里经常遇到宽表和长表互转的需求。pivot长表转宽表类似 Excel 数据透视表。pivot_df df.group_by(user_id).pivot( indexuser_id, columnscategory, valuesamount, aggregate_functionsum, )melt宽表转长表即“取消透视”。long_df df.melt( id_vars[user_id], value_vars[Electronics, Books, Clothing], var_namecategory, value_nameamount, )这两个操作在报表场景中很常见Polars 的语法总体来说比 Pandas 的pivot_table和melt更简洁也更贴近 SQL 的语义。5. 从 Pandas 迁移过来需要注意的几个习惯变化很多朋友刚接触 Polars 时最大的困惑不是“不会写”而是“写着写着就用回了 Pandas 的思维”。这里我把最典型的几个思维转换点整理出来帮助你少走弯路。5.1 没有行索引filter 不再是 df[条件]这是最容易踩的第一个坑。Pandas 里df[df[col] 0]等效于 Polars 的df.filter(pl.col(col) 0)。但要特别注意Polars 的 DataFrame 没有隐式整数索引不能直接用df[0]取第一行也不能用df[df[col] 0]做布尔索引。需要用.row(0)或者.filter()。# 错误写法如果你从 Pandas 过来 # df[df[amount] 100] # 正确写法 df.filter(pl.col(amount) 100)5.2 类型更加激进自动推断可能不是你想要的Polars 的类型系统比 Pandas 严格得多。如果你想把一列object类型转成整数或浮点数必须显式cast否则后续的算术操作会直接报错。df.with_columns( pl.col(amount).cast(pl.Float64) )Pandas 中那种“自动把 object 列转成 float 再计算”的行为Polars 不会模仿。遇到类型不对它会明确告诉你哪里出了问题而不是悄悄做隐式转换。刚开始可能觉得不习惯但它能帮你提前发现很多数据质量问题。5.3 null 处理逻辑不同缺失值传播的差异Pandas 中整列是 float 类型时缺失值习惯填np.nan字符串缺失是None。Polars 则统一使用null表示缺失值同时支持NaN浮点数专用。默认情况下Polars 的算术操作遇到 null 会传播聚合函数则会跳过 null。常用填充方法df.with_columns( pl.col(amount).fill_null(0), pl.col(category).fill_null(Unknown), )如果想把某列的 null 填充为该列的平均值也可以直接df.with_columns( pl.col(amount).fill_null(pl.col(amount).mean()) )5.4 apply 的替代方案尽量使用 expression 而不是自定义函数Pandas 用户习惯用apply或lambda做逐行处理。Polars 的apply虽然也存在但它的性能远不如原生表达式而且会丧失很多并行优势。最佳实践是把自定义逻辑拆解为原生表达式组合。比如你想根据金额大小打标签# 不推荐的写法 # df.with_columns(pl.col(amount).apply(lambda x: High if x 500 else Low).alias(level)) # 推荐的写法 df.with_columns( pl.when(pl.col(amount) 500) .then(pl.lit(High)) .otherwise(pl.lit(Low)) .alias(level) )5.5 自动缓存与惰性求值的配合Lazy API 中如果你在group_by和join之间多次引用了同一个 LazyFramePolars 优化器可能重复扫描。lazy_df pl.scan_parquet(big.parquet) result lazy_df.join(lazy_df.group_by(key).agg(pl.len()), onkey).collect()虽然 Polars 尽力做了缓存但某些情况下重复计算不可避免。遇到这种情况可以先把子查询 collect 成 DataFrame再在事务中复用。5.6 实际项目中的一个效率提升案例之前做用户行为分析时需要按用户维度计算“首单时间”“末单时间”“总订单数”“平均订单间隔”等指标并且要在每个订单行上拼接对应的用户归属城市。Pandas 的写法往往涉及多个groupby().agg()然后merge两三次代码长度常在 40 行以上。Polars 的 Lazy API 一次写完result ( orders_lazy .join(users_lazy, onuser_id, howleft) .with_columns([ pl.col(order_date).min().over(user_id).alias(first_order_date), pl.col(order_date).max().over(user_id).alias(last_order_date), pl.col(order_id).count().over(user_id).alias(order_count), ]) .filter(pl.col(order_date) pl.col(first_order_date)) .collect() )简单直接并且由于窗口操作在太多个组内并行执行速度提升非常明显。6. 临时档案处理复杂类型与性能调优预设这一节是偏进阶内容但既然题目叫“快速入门”我认为至少要让你知道有哪些更强大的能力方便之后按图索骥。6.1 List 和 Struct现代数据处理的高级结构Polars 原生支持复杂嵌套类型比如一个单元格中存放一组列表。df_complex pl.DataFrame({ id: [1, 2], scores: [[85, 90, 95], [70, 80]], info: [ {age: 25, city: Beijing}, {age: 30, city: Shanghai}, ], })这类数据在 JSON 日志分析中非常常见。针对结构体字段可以通过表达式直接访问df_complex.with_columns( pl.col(info).struct.field(age).alias(age), pl.col(scores).list.len().alias(score_count), )这种能力让你在处理半结构化数据时完全不需要先展开成多行再操作。6.2 数据大小与内存预分配什么时候选择流式模式Polars 在默认情况下会将数据全部载入内存。当数据文件大于可用内存时可以使用scan_*接口并开启流式模式q pl.scan_csv(huge_file.csv) q q.filter(pl.col(amount) 0) q q.group_by(category).agg(pl.col(amount).sum()) # 触发流式处理reduce memory result q.collect(streamingTrue)流式模式下Polars 会尽量分块处理而不是一次性全部加载。我的个人经验是streamingTrue在文件很大但最终聚合结果很小时特别有效它能让你的单机处理上限拉高不少。但注意某些复杂查询可能不支持流式Polars 在遇到不支持的操作时要么回退到非流式要么直接报错提示你调整查询计划。6.3 并行线程数的设置Polars 默认会使用 CPU 的所有核一般不需要手动干预。但在多任务共享 CPU 的服务器上可以限制线程数pl.Config.set_tbl_rows(100) pl.Config.set_thread_pool_size(8) # 限制为 8 个线程另外两个和显示相关的常用配置pl.Config.set_tbl_cols(80) # 控制台显示列数 pl.Config.set_fmt_str_lengths(50) # 截断字符串显示长度6.4 数据切片与抽样调试大型 DataFrame 时不需要全量打印。可以使用# 数据抽样 n 行 sample_df df.sample(n5) # 按比例抽样 sample_frac df.sample(frac0.01) # 跳过前 100 行取 10 行 slice_df df.slice(offset100, length10)在 Lazy API 中甚至可以直接在scan_parquet(*.parquet)后跟上.slice()这样只读取部分行范围进一步减少 I/O。7. 进阶资源与最佳实践补遗讲完了基础这里补充一些真正有价值的“路线图”帮助你把 Polars 用到更深的层次。7.1 善用官方文档与 polars 生态Polars 的官方文档质量很高尤其是 “User Guide” 里的 “Expressions” 部分值得反复翻看。它没有简单停留在函数列表而是用一个个可运行示例展示表达式之间的组合方式。入门阶段可以通读一遍。Polars 生态中还有一些不错的配套工具比如polars-xdt提供一些扩展表达式如日期偏移、字符串快捷处理等。polars[time]时间序列功能增强。polars[plot]和绘图库的集成。7.2 项目中迁移的节奏建议如果你想把现有 Pandas 项目迁移到 Polars我不建议一次性重写全部代码。比较稳妥的方式是“局部引入、逐步替换”先找到数据量最大、跑批时间最长的数据处理步骤。把这些步骤封装成独立函数用 Polars 重写并和原 Pandas 结果做逐字段比对。验证通过后将生成的 DataFrame 转为 Pandas如果需要交给下游业务逻辑。之后再逐步把更多步骤迁移进 Polars。这种方式风险低、收益快因为你总是可以从最容易提升性能的地方尝到甜头。7.3 常见性能杀手的规避方式操作问题建议频繁 Eager collect每个步骤都全量计算浪费 I/O 与计算资源尽量在最后再 collect在超大列上做正则表达式处理正则耗 CPU 且难以并行尽量用内置字符串方法如str.contains高频使用 apply丧失并行和向量化优势用原生表达式重构不必要的数据复制某些操作可能触发内存复制使用 Lazy API 并让优化器处理中间结果多列自动推断成超大字符串影响内存占用与编码性能读取时显式指定 schema 或使用 Parquet7.4 单元测试与数据验证数据处理代码最怕结果错误但流程不报错。在迁移或者用 Polars 写核心链路时我会额外做一步对结果做断言。assert result.height expected_rows assert result[total_amount].sum() expected_sum assert result[date].min() date(2024, 1, 1)这些简单的断言在开发阶段经常能提前暴露 schema 变化、过滤条件写错等问题。最后关于学习路径我个人的体感是Polars 的上手速度比当年学 Pandas 快很多因为大部分概念你已经在 SQL 或 Excel 数据透视表中接触过。关键是要主动切换心智模型不再用“行索引”和“apply 逐行处理”那一套而是用“表达式组合”和“查询计划”的视角去写数据管道。你花几天时间适应后续在数据处理任务上的时间投入会成倍省回来。
返回列表