2025年了,还在用 Pandas 那就OUT 了!
Python生态系统以其易用性著称,Pandas作为其中最受欢迎的数据处理库,为初学者提供了友好的入门体验。然而,随着数据规模从MB级跃升至GB甚至TB级,Pandas的单线程执行模式、内存低效利用以及缺乏原生并行处理能力等问题日益凸显。三个关键概念在Pandas中几乎不存在却对大规模生产环境至关重要:首先是并行处理能力,现代CPU多核架构的潜力在Pandas中无法充分发挥;其次是查询优化,Pandas缺乏对执行计划的重写和优化;最后是惰性评估策略,导致不必要的中间结果计算和内存占用。
Polars从根本上重新设计了数据处理引擎的架构,其核心优势源自几个关键设计决策。该库完全采用Rust编写,这种内存安全且无垃圾回收机制的语言为高性能提供了基础保障。多线程查询引擎能够自动将工作负载分布到所有可用的CPU核心,实现真正的并行计算。Polars还实现了基于表达式的查询优化器,可以重新排列操作顺序以减少内存占用和提高执行效率。值得一提的是,Polars提供了两种执行模式:即时执行的eager模式(类似Pandas体验)和构建完整查询计划后再执行的lazy模式,后者通过优化整个操作管道可进一步提升性能。
今天,你将看到 Polars 与 Pandas 在一个有 1,100 万行的 CSV 文件上进行的 4 个系列基准测试中的比较。
首先,来看看为什么要考虑用 Polars 来替代 Pandas。
Pandas 与 Polars 之争
为什么数据专家应该拥抱 Polars?
作为数据从业者,我们都经历过这样的时刻:看着Pandas处理数据时进度条缓慢前进,心里盘算着要不要先去喝杯咖啡。这就像开着一辆老式汽车在高速公路上行驶 - 它能带你到达目的地,但过程实在不够优雅。
Pandas确实是Python数据分析的绝佳入门选择,就像训练轮对学骑自行车的人一样有用。它的API直观易懂,文档丰富,社区支持强大。但当你的数据量从练习用的"玩具数据集"变成真实业务中的庞然大物时,问题就开始显现了。
Pandas的三大硬伤
单线程困境
在这个多核处理器普及的时代,Pandas依然固执地使用单线程。就像让一个工人搬完整个仓库的货物,而其他工人却在旁边闲着。当数据集超过百万行时,这种设计决策的代价就变得极其明显。内存暴食症
Pandas要求将整个DataFrame加载到内存中。处理几个GB的数据?准备好你的内存条预算。更大的数据集?直接"内存不足"警告。这就像试图用家用冰箱存储超市的所有库存 - 根本不现实。机械式执行
Pandas会严格按照你写的代码顺序执行操作,即使这不是最优路径。就像严格按照食谱步骤做菜,明明可以同时煮面和炒酱,却非要等面煮好才开始炒酱。
Polars的四大超能力
并行处理怪兽
Polars能自动利用所有可用的CPU核心,就像雇佣了整个工人团队而不是单打独斗。一个简单的groupby操作就能快上10倍?内存管理大师
借助Apache Arrow的内存格式和延迟执行,Polars的内存效率高得惊人。处理同样规模的数据,内存占用经常只有Pandas的一半。智能查询优化器
Polars会像经验丰富的厨师一样重新安排操作顺序。当你写出低效的查询时,它会悄悄帮你优化,就像有个数据工程师在幕后为你重写代码。超越内存限制
通过流式处理API,Polars能处理远超内存大小的数据集。想象一下不用升级硬件就能分析TB级数据的感觉。
迁移成本比你想的低
最令人欣慰的是,从Pandas迁移到Polars的学习曲线非常平缓。大多数常见操作都有对应的Polars实现,而且语法相似度很高。就像从iPhone换到Android旗舰机 - 核心体验相似,但性能提升立竿见影。
语法相似度70%:很多操作直接把
.df改成.pl就能用# Pandas
df.groupby('城市')['销售额'].mean()
# Polars
df.groupby('城市').agg(pl.col('销售额').mean())无缝转换:可以和Pandas互相转化,不影响现有代码
pandas_df = polars_df.to_pandas()
polars_df = pl.from_pandas(pandas_df)支持SQL:直接用SQL查询数据
df.sql("SELECT 城市, AVG(销售额) FROM self GROUP BY 城市")
真实案例:某电商平台将他们的RFM客户分析从Pandas迁移到Polars后,处理2000万用户数据的时间从47分钟缩短到4分钟 - 这不是理论上的性能提升,而是实实在在的生产力飞跃。
作为数据专家,选择工具不仅要考虑当下的便利性,更要着眼未来的可扩展性。当你的数据规模呈指数级增长时,Polars提供的性能优势可能就意味着能否在deadline前交付分析结果,或者能否实时响应业务需求。这不再是一个简单的技术选型问题,而是关乎工作效率和职业发展的战略决策。
基准前提--如何用 Python 创建大型数据集
首先,你需要一些数据。下面的代码段创建了一个随机数据框,其中包含 125 年(每小时间隔)和 10 个办公地点的虚构销售额。
这将为你提供大约 1100 万行供你使用:
import uuid
import numpy as np
import pandas as pd
import polars as pl
from datetime import datetime
date_ranges = pd.date_range(start=datetime(1900, 1, 1), end=datetime(2025, 12, 31), freq="h")
locations = ["Italy", "France", "Spain", "Portugal", "Greece", "Germany", "Austria", "Norway", "Sweeden", "Poland"]
location_multiplier = [0.7, 1.2, 0.9, 0.5, 0.3, 1.0, 1.4, 1.9, 2.1, 1.4]
data = []
print("Started constructing data...")
for dt in date_ranges:
for i, loc in enumerate(locations):
data.append({
"id": str(uuid.uuid4()),
"date": dt,
"location": loc,
"sales": np.random.randint(low=1, high=50) * location_multiplier[i]
})
print("Finished constructing data!")
print("Converting to dataframe...")
data = pd.DataFrame(data)
print("Finished converting to dataframe!")
print("Saving to CSV...")
data.to_csv("data.csv", index=False)
print("Saved to CSV!")
print("Done!")
如果你想知道,UUID 字符串列的存在只是为了让数据集更大,读取速度更慢。
下面打印形状和数据样本,以验证一切看起来都很好:
print(data.shape)
print()
data.sample(10)
结果:
看起来不错!你的数据集有 1104 万行,占用了大约 800MB 的磁盘空间。虽然不是很多,但足以看出 Pandas 和 Polars 之间是否存在显著差异。
如果需要更多数据,只需调整日期聚合频率即可。将小时换成分钟,就能获得超过 6.6 亿行的数据。
Pandas 与 Polars 数据处理基准对比
每个基准测试我都运行了3次,并取平均值。选择的硬件是一台 M2 MacBook Pro,16GB 内存和 12 个 CPU 内核。库版本如下
pandas-2.2.1polars-0.20.18
Benchmark #1--读取 CSV 文件
几乎所有数据管道的首要任务都是获取数据。现在的文件格式是 CSV,Pandas 和 Polars 都有一个相同的 read_csv() 函数可供使用。
# PANDAS
df_pd = pd.read_csv("data.csv")
# POLARS
df_pl = pl.read_csv("data.csv")
应用程序接口可能是一样的,但幕后执行肯定不一样:
从磁盘读取 800MB 的 CSV 文件时,Polars 的速度几乎是 Pandas 的 10 倍。
Benchmark #2 - 读取和预处理 CSV 文件
数据集读取后通常会立即进行数据预处理,如数据类型转换和过滤。这就是我要测试的用例。
我们的目标是再次读取 CSV 文件,将字符串日期列转换为实际的日期时间对象,只保留意大利办公室位置的行,并删除不必要的列。
Pandas 和 Polars 之间的实现初看起来有很多不同,但经验丰富的 Python 用户在两者之间应该没有问题:
# PANDAS
df_pd_italy = pd.read_csv("data.csv")
df_pd_italy["date"] = pd.to_datetime(df_pd_italy["date"])
df_pd_italy = df_pd_italy[df_pd_italy["location"] == "Italy"]
df_pd_italy = df_pd_italy[["date", "sales"]]
# POLARS
df_pl_italy = (
pl.read_csv("data.csv")
.with_columns(pl.col("date").str.to_date("%Y-%m-%d %H:%M:%S"))
.filter(pl.col("location") == "Italy")
.select(pl.col(["date", "sales"]))
)
Polars 的速度再次加快:
现在只快了 5 倍,但仍然意义重大。
Benchmark #3 — Group By
分组是最常见的数据操作之一。其目的是浓缩信息,并显示不同分组的汇总统计数据,如总数和平均值。
下面的代码片段按年份和地点对数据进行分组,然后计算总销售额和平均销售额。生成的数据框按日期升序排序,按销售总额降序排序:
# PANDAS
(df_pd
.groupby([df_pd["date"].dt.year, df_pd["location"]])
.agg(
total_sales=("sales", "sum"),
avg_sales=("sales", "mean")
)
.reset_index()
.sort_values(by=["date", "total_sales"], ascending=[True, False])
)
# POLARS
(df_pl
.group_by([pl.col("date").dt.year(), pl.col("location")])
.agg(
pl.sum("sales").alias("total_sales"),
pl.mean("sales").alias("avg_sales")
)
.sort(by=["date", "total_sales"], descending=[False, True])
)
Pandas 和 Polars API 之间存在细微差别,主要体现在函数和参数名称上。但实际上,这并不是决定性因素。
Polars 又快了一步,但差距并不大。分组操作本来就不需要太多时间,这就是主要原因。
Benchmark #4 - 完整管道
比较 Pandas 和 Polars 的单个操作是可以的,但大多数数据专家只关心数据管道完成所需的总时间。
这正是 Polars 的优势所在。它随附了一个快速评估数据帧版本,称为LazyFrames。它们背后的理念是建立一个计算图,让 Polars 优化执行顺序。
你不必动一根手指,但请注意你现在使用的是 scan_csv()函数,而不是 read_csv()。长话短说,这个函数创建了一个 LazyFrame,它会引用文件数据,而不是从磁盘读取数据。需要注意的是,你必须手动调用 collect()函数,因为在调用之前不会进行任何数据处理--因此是lazy:
# PANDAS
df_pd = pd.read_csv("data.csv")
df_pd["date"] = pd.to_datetime(df_pd["date"])
df_pd_res = (
df_pd
.groupby([df_pd["date"].dt.year, df_pd["location"]])
.agg(
total_sales=("sales", "sum"),
avg_sales=("sales", "mean")
)
.reset_index()
.sort_values(by=["date", "total_sales"], ascending=[True, False])
)
# POLARS
df_pl_res = (
pl.read_csv("data.csv")
.with_columns(pl.col("date").str.to_date("%Y-%m-%d %H:%M:%S"))
.group_by([pl.col("date").dt.year(), pl.col("location")])
.agg(
pl.sum("sales").alias("total_sales"),
pl.mean("sales").alias("avg_sales")
)
.sort(by=["date", "total_sales"], descending=[False, True])
)
# POLARS WITH LAZY EVALUATION
df_pl_lazy_query = (
pl.scan_csv("data.csv")
.with_columns(pl.col("date").str.to_date("%Y-%m-%d %H:%M:%S"))
.group_by([pl.col("date").dt.year(), pl.col("location")])
.agg(
pl.sum("sales").alias("total_sales"),
pl.mean("sales").alias("avg_sales")
)
.sort(by=["date", "total_sales"], descending=[False, True])
)
df_pl_lazy_res = df_pl_lazy_query.collect()
但额外的功能调用是值得的:
与 Pandas 相比,Polars 在整个流程中的速度几乎快了 5 倍。如果使用其懒惰应用程序接口(lazy API),两者之间的差距将跃升至近 12 倍。
只需修改极少的代码,就能获得如此大的性能提升,令人印象深刻。
写在最后
与 Pandas 相比,使用 Polars 的速度快如闪电。今天展示的每项操作都更快,而且事实证明,从 Pandas 切换到懒散评估的 Polars,可以将整个数据处理管道的速度提高 12 倍。
既然如此,为什么大多数数据专家仍在使用Pandas呢?Pandas 可能是大多数人学会的第一个数据处理框架,而且人们对它的认知度也更高。随着时间的推移,改变只会越来越难。
Pandas 也是中小型数据集的最佳选择。但当数据量超过内存时,两者就不分伯仲了。Polars 胜出一筹。
你是否在工作区用 Polars 替代过 Pandas?请在评论区分享。
最后,推荐阅读:Polars (最强Pandas平替)
🏴☠️宝藏级🏴☠️ 原创公众号『数据STUDIO』内容超级硬核。公众号以Python为核心语言,垂直于数据科学领域,包括可戳👉Python|MySQL|数据分析|数据可视化|机器学习与数据挖掘|爬虫等,从入门到进阶!
长按👇关注- 数据STUDIO -设为星标,干货速递