Polars:超越 Pandas,新一代高性能数据分析框架
在 Python 数据科学生态中,Pandas 长期占据主导地位。但随着数据规模不断增长以及多核处理器的广泛应用,Pandas 在性能和内存管理方面的局限性逐渐暴露。Polars 作为一个基于 Rust 语言从零构建的高效 DataFrame 库,凭借其卓越的速度和现代化的 API 设计,正在成为数据处理领域的新锐力量。本文将通过七个实际案例,全面展示 Polars 的核心优势与使用方式。
为何需要 Pandas 的替代方案?
- 单线程执行限制:Pandas 多数操作依赖单线程,难以发挥现代 CPU 的多核并行能力。
- 高内存消耗:尤其在处理字符串类型或进行某些转换时,Pandas 的内存占用较高。
- API 设计不一致:由于历史原因,Pandas 的接口存在冗余与逻辑混乱的问题,增加学习和维护成本。
- 计算效率低下:数据工程师常需长时间等待结果,严重影响开发迭代效率。
针对上述问题,Polars 应运而生。它以内存高效的 Apache Arrow 列式存储为基础,结合 Rust 语言的安全性与高性能,实现了自动并行化与深度查询优化。其设计目标包括:
- 最大化并行处理能力:自动调度任务至所有可用 CPU 核心。
- 智能查询优化:借助惰性求值机制,对整个操作流程进行分析并选择最优执行路径。
- 声明式表达式 API:提供链式、可组合的操作接口,使代码更简洁、易读且稳定。
copy
准备工作与核心理念
开始使用 Polars 前,首先安装主包:
pip install polars
若需增强功能(如 CSV 解析或多库交互),可安装完整依赖:
pip install polars[all]
核心概念:表达式(Expressions)
Polars 的关键在于其“表达式”系统。不同于 Pandas 中直接执行的命令式操作,Polars 中的表达式并不立即运行,而是构建一个描述计算逻辑的对象。这些对象可以被组合、传递,并最终由底层引擎统一优化后并行执行。
df['A'] * 2
例如,在 Pandas 中调用 df['col'] 会立刻返回数据;而在 Polars 中,pl.col("col") 返回的是一个指向该列的表达式,用于后续参与复杂运算。这种延迟执行模式是实现高性能的关键之一。
pl.col('A') * 2
实战一:初识表达式 API
以下示例演示如何创建数据、选择字段及新增列,展现 Polars 表达式的灵活性。
import polars as pl
# 构造原始数据
data = {
"city": ["New York", "London", "Tokyo", "Paris", "London"],
"country": ["USA", "UK", "Japan", "France", "UK"],
"temperature_c": [22.5, 15.0, 28.3, 25.1, 18.5],
"humidity": [65, 72, 75, 68, 70],
}
df = pl.DataFrame(data)
# 1. 列选择
selected_df = df.select([
pl.col("city"),
pl.col("temperature_c")
])
print("--- Selected DataFrame ---\n", selected_df)
# 2. 添加新列
transformed_df = df.with_columns([
(pl.col("temperature_c") * 9/5 + 32).alias("temperature_f"),
pl.col("city").str.to_uppercase().alias("city_uppercase")
])
print("\n--- Transformed DataFrame ---\n", transformed_df)
结果说明:
- 不再通过字符串索引访问列,而是使用
pl.col()创建列表达式。 - 所有变换均封装在
select()或with_columns()方法内,便于整体优化。 .alias()明确指定输出列名,提升语义清晰度。
df['col']
这种编程范式让 Polars 能够预先分析整个数据流图,合并冗余步骤、重排操作顺序,从而显著提升执行效率。
pl.col("col")
实战二:链式操作与高效过滤
Polars 支持流畅的链式调用风格,使得多步骤的数据清洗和转换过程结构清晰、易于理解。
import polars as pl
df = pl.DataFrame({
"type": ["A", "B", "A", "C", "B", "A"],
"value": [10, 20, 30, 40, 50, 60]
})
假设我们需要完成以下任务:
- 筛选出 type 为 "A" 或 "B" 的记录
- 将 value 加上一个偏移量
- 按 type 分组并计算平均值
- 按均值降序排列
使用 Polars 可以这样写:
result = (
df
.filter(pl.col("type").is_in(["A", "B"]))
.with_columns((pl.col("value") + 5).alias("value_adj"))
.group_by("type")
.agg(pl.col("value_adj").mean())
.sort("value_adj", descending=True)
)
print(result)
整个流程无需中间变量,逻辑连贯,且每个步骤都可能被查询优化器重新组织以获得最佳性能。
select
链式语法不仅提高了可读性,也增强了代码的可维护性,特别适合构建复杂的数据管道。
with_columns
此外,由于 Polars 默认启用多线程处理,像过滤、聚合这类操作会自动分布到多个核心上运行,进一步缩短执行时间。
.alias("new_name")需求:筛选出类型为 'A' 的数据行,并将对应的 value 值扩大两倍。
在 Polars 中,可以通过以下方式实现:
result = (
df.filter(pl.col("type") == "A")
.select(
(pl.col("value") * 2).alias("doubled_value")
)
)
print(result)
结果分析:
通过
filter与select的链式调用结构,整个数据处理流程被清晰地自上而下表达出来。这种声明式的编程风格相比 Pandas 中需要混合使用[]和多种方法调用的方式,在可读性和后期维护方面具有明显优势。
四、案例三:高效的分组聚合操作(group_by
)
group_bygroup_by是数据分析中的核心步骤之一。Polars 的实现充分利用了多核并行计算能力,因此在执行效率上显著优于传统的 Pandas。
import polars as pl
import numpy as np
# 构造一个大规模数据集
num_rows = 1_000_000
data = {
"category": np.random.choice(["A", "B", "C", "D"], num_rows),
"value1": np.random.rand(num_rows) * 100,
"value2": np.random.rand(num_rows) * 50,
}
df = pl.DataFrame(data)
# 需求说明:
# 按照 category 进行分组,并对每个组进行如下统计:
# 1. 统计行数(count)
# 2. 计算 value1 的总和(sum)
# 3. 计算 value2 的平均值(mean)
# 4. 计算 value1 的中位数(median)
aggregated_df = df.group_by("category").agg([
pl.count().alias("num_rows"),
pl.sum("value1").alias("sum_value1"),
pl.mean("value2").alias("mean_value2"),
pl.median("value1").alias("median_value1"),
# 同时支持对同一列应用多个条件聚合
pl.col("value1").filter(pl.col("value1") > 50).count().alias("count_value1_gt_50")
]).sort("category")
print(aggregated_df)
结果分析:
group_by().agg()展现出强大的表达能力。你可以在agg中传入一组表达式,针对不同字段执行多样化的聚合逻辑,甚至嵌套过滤条件。所有这些操作都会被 Polars 查询引擎统一优化,并尽可能以并行方式执行,极大提升处理效率。
五、案例四:利用 Lazy API 实现查询优化
Lazy API 是 Polars 提供的一项高性能工具,常被视为其性能“核武器”。它允许用户先定义完整的数据转换流程,由系统生成并优化整体执行计划(如谓词下推、投影下推等),最后才真正触发计算。
import polars as pl
df = pl.DataFrame({
"type": ["A", "B", "A", "C", "B", "A"],
"value": [10, 20, 30, 40, 50, 60]
})
# 将普通 DataFrame 转换为惰性计算对象
lazy_df = df.lazy()
# 定义一系列操作(此阶段不执行任何实际运算)
query_plan = (
lazy_df.filter(pl.col("value") > 25)
.with_columns(
(pl.col("value") * 10).alias("value_x10")
)
.select(["type", "value_x10"])
)
# 查看经过优化后的执行策略
print("--- Optimized Execution Plan ---")
print(query_plan.describe_optimized_plan())
# 触发实际计算并输出结果
print("\n--- Result ---")
result_df = query_plan.collect()
print(result_df)
结果分析:
.lazy()将原始数据结构转为 LazyFrame,后续所有变换仅用于构建逻辑执行计划。通过调用describe_optimized_plan(),我们可以预览 Polars 对该查询的最终执行方案。通常会发现,系统自动将过滤操作filter前置执行,随后再进行数值计算with_columns,从而有效减少中间数据量——这正是“谓词下推”优化机制的体现。最后通过
.collect()启动真正的数据处理流程。对于大型数据集,尤其是从磁盘文件加载时,Lazy API 能够避免读取无关的行列,大幅节省内存占用并加快运行速度。六、案例五:强大的窗口函数
窗口函数提供了一种在“窗口”范围内(即一组逻辑相关的行)执行计算的能力,而不会像传统聚合那样将多行合并为单行。这种机制保留了原始数据的结构,同时支持复杂的组内分析。
group_by
import polars as pl
df = pl.DataFrame({
"department": ["Sales", "Sales", "Marketing", "Engineering", "Marketing"],
"employee": ["Alice", "Bob", "Charlie", "David", "Eve"],
"salary": [80000, 90000, 75000, 120000, 85000],
})
# 计算每位员工薪资与其所在部门平均薪资的差值
result_df = df.with_columns([
(pl.col("salary") - pl.mean("salary").over("department")).alias("salary_diff_from_dept_avg")
])
print(result_df)
结果分析:
over
pl.mean("salary").over("department")
该操作的核心含义是:“按部门分组计算薪资的平均值,但不进行行折叠,而是将该平均值广播到对应组内的每一行”。这种方式极大简化了组内比较类的计算任务,代码简洁且执行效率高。相比 Pandas 中需借助 groupby().transform() 的实现方式,Polars 的语法更加直观易读。
department
salary
transform
七、案例六:高效的数据连接操作
数据连接(Join)是数据整合中的基础操作之一,Polars 提供了高性能且语义清晰的连接功能,适用于多种业务场景。
import polars as pl
# 用户表
df_users = pl.DataFrame({
"user_id": [1, 2, 3],
"name": ["Alice", "Bob", "Charlie"]
})
# 订单表
df_orders = pl.DataFrame({
"order_id": [101, 102, 103],
"user_id": [2, 3, 1],
"amount": [150, 200, 100]
})
# 执行内连接并按用户ID排序
joined_df = df_users.join(df_orders, on="user_id", how="inner").sort("user_id")
print(joined_df)
结果分析:
join
Polars 的 join() 方法支持多种连接类型,包括内连接、左连接、外连接、半连接和反连接等。
inner
left
outer
semi
anti
其底层采用并行化的哈希连接算法,在处理大规模数据集时表现出优异的性能,远超传统单线程实现方式。
八、案例七:与 Pandas/NumPy 的无缝互操作
Polars 并非试图脱离现有的 Python 数据生态,而是通过 Apache Arrow 内存格式实现与 Pandas 和 NumPy 的高效数据交换,支持零拷贝或低成本转换,确保流程平滑过渡。
import polars as pl
import pandas as pd
import numpy as np
# Polars 转 Pandas
pl_df = pl.DataFrame({"a": [1, 2], "b": [3, 4]})
pd_df = pl_df.to_pandas()
print("--- Converted to Pandas ---\n", pd_df)
print(type(pd_df))
# Pandas 转 Polars
pl_df_from_pd = pl.from_pandas(pd_df)
print("\n--- Converted from Pandas ---\n", pl_df_from_pd)
# Polars 转 NumPy
numpy_array = pl_df.to_numpy()
print("\n--- Converted to NumPy ---\n", numpy_array)
print(type(numpy_array))
结果分析:
这一特性使得开发者可以在不同阶段灵活选用最适合的工具。例如,使用 Polars 完成大规模数据清洗与特征工程后,可将结果快速转为 NumPy 数组,直接输入机器学习模型进行训练,充分发挥各库的优势。
总结与展望
Polars 凭借基于 Rust 构建的高性能计算引擎、表达力强的表达式 API 以及惰性求值机制,显著提升了数据分析的效率与可维护性。它并不旨在取代 Pandas,而是在面对中大型数据集和高性能需求时,提供一个更优的替代方案。
当你仍在为 Pandas 的缓慢运行而困扰时,尝试 Polars 可能会带来耳目一新的体验。
当完成高效的数据处理与特征提取之后,下一步便是将这些高质量特征输入先进的 AI 模型,以解锁更深层次的智能应用。
从高性能数据处理迈向高级人工智能
对于想要探索数据分析成果与语言模型融合方式的开发者而言,
提供了一个理想的实验环境。在这里,你可以将 Polars 生成的分析结果作为上下文输入,免费且无限制地向多种主流模型如 Llama、Qwen、gpt-4o 等发起提问,还能体验每周轮换的 gpt-5 等前沿旗舰模型。
https://0v0.pro
Polars 专注于高效处理,实现“快”,而 AI 模型则承担理解与推理,体现“聪明”。两者的协同,标志着数据驱动应用迈入全新阶段。


雷达卡


京公网安备 11010802022788号







