Polars:超越 Pandas,下一代高性能数据分析框架

在 Python 数据科学领域,Pandas 长期以来是无可争议的王者。然而,随着数据集的日益庞大和多核 CPU 的普及,Pandas 在性能和内存效率上的瓶颈也愈发凸显。Polars,一个从零开始用 Rust 编写的高性能 DataFrame 库,正以其闪电般的速度和富有表现力的 API,成为数据分析领域的下一代挑战者。本文将通过 7 个实战案例,带你领略 Polars 的魅力。


在这里插入图片描述

导语

为什么我们需要 Pandas 的替代品?

  • 单线程限制:Pandas 的大部分操作是单线程的,无法充分利用现代 CPU 的多核优势。
  • 内存开销:Pandas 的内存使用效率不高,尤其是在处理大量字符串或进行某些操作(如 copy)时。
  • API 不一致:Pandas 的 API 历史悠久,存在一些不一致和令人困惑的地方。
  • 渴望性能:数据科学家和工程师们花费了大量时间等待 Pandas 完成计算,这直接影响了迭代速度。

Polars 正是为解决这些痛点而生。它基于 Apache Arrow 列存格式,并利用 Rust 的能力实现了大规模的并行计算和查询优化。其核心设计理念是:

  1. 最大化并行计算:自动利用所有可用的 CPU 核心。
  2. 查询优化:通过惰性求值(Lazy Evaluation)分析整个查询计划,寻找最优执行路径。
  3. 富有表现力的 API:提供一种链式、声明式的“表达式” API,代码更易读、更稳定。

一、环境准备与核心思想

首先,安装 Polars。

pip install polars

(可选)为了更好地支持 CSV 读取和与其他库的交互,可以安装更多依赖:

pip install polars[all]

核心思想:表达式 (Expressions)

Polars 的核心是表达式 API。与 Pandas 中 df['A'] * 2 这样立即执行的操作不同,Polars 的 pl.col('A') * 2 创建了一个描述“如何计算”的表达式对象。这些表达式可以被组合、传递,并由 Polars 的查询引擎在后台进行优化和并行执行。


二、案例一:拥抱表达式 API

让我们从基础的读数据、选择和创建新列开始,感受 Polars 表达式的魅力。

import polars as pl

# 创建一个 DataFrame
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. 选择列 (select)
# Pandas: df[['city', 'temperature_c']]
selected_df = df.select([
    pl.col("city"),
    pl.col("temperature_c")
])
print("--- Selected DataFrame ---\n", selected_df)

# 2. 创建新列 (with_columns)
# Pandas: df['temperature_f'] = df['temperature_c'] * 9/5 + 32
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)

结果分析

  • 我们不再使用 df['col'] 的方式,而是用 pl.col("col") 创建一个代表列的表达式。
  • 所有操作都封装在 selectwith_columns 的上下文中,这让 Polars 可以整体看待这些操作。
  • .alias("new_name") 用于给新创建的表达式(列)命名,清晰直观。

三、案例二:链式操作与高效过滤

Polars 的 API 设计非常适合链式调用,使得复杂的数据处理流程一目了然。

import polars as pl

df = pl.DataFrame({
    "type": ["A", "B", "A", "C", "B", "A"],
    "value": [10, 20, 30, 40, 50, 60]
})

# 需求:筛选出 type 为 'A' 的行,并将 value 乘以 2
# Pandas: df[df['type'] == 'A']['value'] * 2

# Polars 写法
result = (
    df.filter(pl.col("type") == "A")
      .select(
          (pl.col("value") * 2).alias("doubled_value")
      )
)
print(result)

结果分析:通过 filterselect 的链式调用,代码从上到下清晰地描述了数据处理的每一步。这种声明式的风格比 Pandas 中混合使用 [] 和方法调用更容易阅读和维护。


四、案例三:闪电般的分组聚合 (group_by)

group_by 是数据分析的核心操作。Polars 的 group_by 利用了多核能力,执行速度远超 Pandas。

import polars as pl
import numpy as np

# 创建一个更大的 DataFrame
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 的性能“核武器”。它允许你先构建查询计划,然后让 Polars 对整个计划进行优化(如谓词下推、投影下推),最后再执行计算。

import polars as pl

df = pl.DataFrame({
    "type": ["A", "B", "A", "C", "B", "A"],
    "value": [10, 20, 30, 40, 50, 60]
})

# 创建一个 LazyFrame
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() 将 DataFrame 转换为 LazyFrame,后续所有操作都只构建查询计划。
  • describe_optimized_plan() 可以让你看到 Polars 打算如何执行你的查询。你会发现,它会先执行过滤(filter),再进行计算(with_columns),从而减少需要处理的数据量——这就是谓词下推优化。
  • .collect() 触发实际的计算。对于大型数据集,特别是从文件中读取时,Lazy API 可以通过避免读取不必要的列和行,极大地提升性能和降低内存消耗。

六、案例五:强大的窗口函数 (over)

窗口函数允许你在一个“窗口”(一组相关的行)内进行计算,而无需像 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)

结果分析pl.mean("salary").over("department") 这一行代码的含义是:“计算按 department 分组的 salary 的平均值,但不要折叠行,而是将结果广播回每一行”。这使得计算组内相对值变得异常简单和高效,其可读性远超 Pandas 中使用 transform 的等效操作。


七、案例六:高效连接数据 (join)

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]
})

# 执行内连接
joined_df = df_users.join(df_orders, on="user_id", how="inner").sort("user_id")

print(joined_df)

结果分析join 的语法清晰明了,支持 inner, left, outer, semi, anti 等多种连接策略。由于其并行的哈希连接算法,在大数据集上的性能同样出色。


八、案例七:与 Pandas/NumPy 无缝切换

Polars 并非要完全孤立于现有的 PyData 生态。得益于 Apache Arrow,它可以与 Pandas 和 NumPy 进行零成本(zero-copy)或低成本的数据交换。

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 和强大的惰性求值能力,为 Python 数据分析领域注入了新的活力。它并非要完全取代 Pandas,而是在性能和处理中大型数据集方面提供了一个更优越的选择。当你还在为 Pandas 的缓慢执行而苦恼时,不妨试试 Polars,它可能会给你带来惊喜。

当你用 Polars 高效地处理和分析完数据,提取出有价值的特征后,下一步便是将这些洞察和特征输入给强大的 AI 模型,去完成更复杂的任务。

从高性能分析到高级智能

  • 😳 0v0 AI 助手推荐:对于希望探索如何将数据分析结果与语言模型结合的开发者,https://0v0.pro 是一个理想的试验平台。你可以将 Polars 的分析结论作为上下文,免费、无限制地向 Llama、Qwen、gpt-4o 等多种模型提问,甚至可以体验每周轮换的 gpt-5 等旗舰模型。

Polars 负责“快”,而 AI 负责“聪明”。二者结合,将开启数据应用的新篇章。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐