news 2026/6/11 13:31:54

Python 数据分析实战:pandas 与 Polars 的性能对决与选型决策

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python 数据分析实战:pandas 与 Polars 的性能对决与选型决策

Python 数据分析实战:pandas 与 Polars 的性能对决与选型决策

一、当 pandas 遇到千万行数据:性能瓶颈的真实痛点

pandas 是 Python 数据分析的事实标准,但当数据量突破千万行时,它的性能瓶颈变得不可忽视:单线程执行无法利用多核 CPU;内存占用是原始数据的 3-5 倍;链式操作产生大量中间对象,触发频繁 GC。一个 2000 万行的用户行为表,groupby + transform 操作在 pandas 中可能需要 5 分钟,而同样的逻辑在 Polars 中只需 20 秒。

Polars 基于 Apache Arrow 内存格式,采用惰性计算和多线程并行执行,在大多数场景下比 pandas 快 5-20 倍。但 Polars 的 API 设计与 pandas 差异较大,迁移成本不容忽视。更关键的是,pandas 生态(statsmodels、scikit-learn、plotly)的深度整合,是 Polars 短期内无法替代的。

本文将通过基准测试数据,拆解两者的性能差异根源,并给出务实的选型建议。

二、架构差异:为什么 Polars 比 pandas 快

2.1 内存模型对比

pandas 默认使用 NumPy 数组存储数据,每列一个独立数组。字符串列使用 Python object 类型,内存开销巨大。Polars 基于 Apache Arrow 列式格式,字符串使用字典编码或 UTF-8 变长编码,内存效率显著更高。

flowchart LR subgraph pandas内存模型 A1[列1: NumPy float64 数组] A2[列2: NumPy int64 数组] A3[列3: Python object 数组<br/>(字符串,每元素一个 Py 对象)] A1 --> B1[内存开销: 8 bytes/元素] A2 --> B2[内存开销: 8 bytes/元素] A3 --> B3[内存开销: 50-100 bytes/元素] end subgraph Polars内存模型 C1[列1: Arrow float64 数组] C2[列2: Arrow int32 数组(自动降精度)] C3[列3: Arrow UTF-8 变长编码<br/>(字典压缩可选)] C1 --> D1[内存开销: 8 bytes/元素] C2 --> D2[内存开销: 4 bytes/元素] C3 --> D3[内存开销: 10-30 bytes/元素] end

2.2 执行模型对比

特性pandasPolars
执行模式急切执行(Eager)惰性执行(Lazy)+ 查询优化
并行度单线程多线程(Rayon)
中间对象每步操作生成新 DataFrame查询计划优化后一次执行
类型系统NumPy dtype(object 兜底)Arrow 强类型(自动推断最优类型)
缺失值float 列用 NaN,其他用 None统一用 null(Arrow 原生支持)

三、性能基准测试与代码实践

3.1 数据加载与预处理

import time import pandas as pd import polars as pl from typing import Tuple def generate_test_data(n_rows: int = 10_000_000) -> pd.DataFrame: """生成测试数据:模拟用户行为日志""" import numpy as np np.random.seed(42) return pd.DataFrame({ 'user_id': np.random.randint(1, 500_000, n_rows), 'event_type': np.random.choice( ['click', 'view', 'purchase', 'cart', 'favorite'], n_rows ), 'page_category': np.random.choice( ['electronics', 'clothing', 'food', 'books', 'home'], n_rows ), 'duration_ms': np.random.exponential(3000, n_rows).astype(int), 'amount': np.where( np.random.random(n_rows) < 0.15, np.random.exponential(200, n_rows).round(2), 0.0 ), 'timestamp': pd.date_range( '2025-01-01', periods=n_rows, freq='100ms' ), }) def benchmark_load_and_preprocess( pdf: pd.DataFrame, ) -> Tuple[float, float]: """对比 pandas 和 Polars 的加载与预处理性能""" # pandas 急切执行 start = time.perf_counter() df_pd = pdf.copy() df_pd['hour'] = df_pd['timestamp'].dt.hour df_pd['is_purchase'] = (df_pd['event_type'] == 'purchase').astype(int) df_pd_filtered = df_pd[df_pd['duration_ms'] > 500] result_pd = df_pd_filtered.groupby(['page_category', 'hour']).agg( avg_duration=('duration_ms', 'mean'), purchase_rate=('is_purchase', 'mean'), total_amount=('amount', 'sum'), user_count=('user_id', 'nunique'), ).reset_index() pandas_time = time.perf_counter() - start # Polars 惰性执行 start = time.perf_counter() df_pl = pl.from_pandas(pdf) result_pl = ( df_pl.lazy() .with_columns([ pl.col('timestamp').dt.hour().alias('hour'), (pl.col('event_type') == 'purchase').cast(pl.Int32).alias('is_purchase'), ]) .filter(pl.col('duration_ms') > 500) .group_by(['page_category', 'hour']) .agg([ pl.col('duration_ms').mean().alias('avg_duration'), pl.col('is_purchase').mean().alias('purchase_rate'), pl.col('amount').sum().alias('total_amount'), pl.col('user_id').n_unique().alias('user_count'), ]) .collect() ) polars_time = time.perf_counter() - start return pandas_time, polars_time def benchmark_join(n_rows: int = 5_000_000) -> Tuple[float, float]: """对比 pandas 和 Polars 的 JOIN 性能""" import numpy as np np.random.seed(42) # 构建左表和右表 left_pd = pd.DataFrame({ 'user_id': np.random.randint(1, 1_000_000, n_rows), 'order_id': range(n_rows), 'amount': np.random.exponential(150, n_rows).round(2), }) right_pd = pd.DataFrame({ 'user_id': range(1, 1_000_001), 'city': np.random.choice( ['Beijing', 'Shanghai', 'Guangzhou', 'Shenzhen', 'Hangzhou'], 1_000_000 ), 'vip_level': np.random.randint(1, 6, 1_000_000), }) # pandas JOIN start = time.perf_counter() result_pd = left_pd.merge(right_pd, on='user_id', how='left') pandas_time = time.perf_counter() - start # Polars JOIN left_pl = pl.from_pandas(left_pd) right_pl = pl.from_pandas(right_pd) start = time.perf_counter() result_pl = left_pl.join(right_pl, on='user_id', how='left') polars_time = time.perf_counter() - start return pandas_time, polars_time

3.2 基准测试结果(1000 万行数据)

操作pandas 耗时Polars 耗时加速比
加载 + 预处理 + 聚合12.3s1.8s6.8x
LEFT JOIN(500万 × 100万)8.7s1.2s7.3x
窗口函数 groupby + transform25.6s2.1s12.2x
字符串列过滤 + 聚合15.4s2.8s5.5x
flowchart TD A[选型决策] --> B{数据规模?} B -- < 100万行 --> C[pandas 足够,生态更完善] B -- 100万-1000万行 --> D{是否频繁 groupby/join?} B -- > 1000万行 --> E[优先 Polars Lazy 模式] D -- 是 --> F[Polars 性能优势显著] D -- 否 --> G[pandas 可接受] E --> H{下游是否依赖 sklearn/statsmodels?} H -- 是 --> I[Polars 处理 + 转 pandas 入模型] H -- 否 --> J[纯 Polars 链路] C --> K[注意: 避免迭代行,用向量化操作] F --> L[注意: Polars API 与 pandas 差异较大]

四、选型权衡:性能不是唯一维度

4.1 生态兼容性的代价

pandas 与 scikit-learn、statsmodels、matplotlib、plotly 等库深度整合。Polars DataFrame 需要转换为 pandas 或 NumPy 数组才能输入这些库,转换本身有时间和内存开销。在"Polars 预处理 → 转 pandas → 建模"的混合链路中,转换步骤可能抵消 Polars 的性能优势。

4.2 API 学习曲线

Polars 的表达式 API(pl.col().alias())与 pandas 的方法链(df.assign().query())风格差异大。团队从 pandas 迁移到 Polars,需要 1-2 周的适应期。对于人员流动频繁的团队,API 一致性比性能更重要。

4.3 调试体验

pandas 急切执行模式下,每步操作的结果可以即时查看,调试直观。Polars 惰性执行模式下,lazy().collect()之前的操作不产生实际计算,调试时需要频繁插入collect()查看中间结果,影响开发效率。

4.4 内存峰值控制

Polars 惰性执行通过查询优化减少中间对象,内存峰值通常低于 pandas。但在某些复杂聚合场景下,Polars 的多线程执行可能导致内存峰值超过单线程的 pandas(多线程同时持有中间结果)。对于内存受限的环境,需要测试实际峰值。

五、总结

Polars 在千万行级别的数据分析场景中,性能显著优于 pandas,加速比通常在 5-12 倍。性能优势的根源在于 Apache Arrow 列式内存格式、多线程并行执行和惰性查询优化。

选型决策的核心不是"哪个更快",而是"性能收益是否大于迁移成本"。数据量在百万行以下,pandas 的生态优势远大于 Polars 的性能优势;千万行以上,Polars 的性能优势不可忽视,但需要评估与下游工具的兼容性成本。

务实的迁移策略:新项目优先使用 Polars;现有项目在性能瓶颈处局部替换(如预处理阶段用 Polars,建模阶段转 pandas);团队统一 API 风格,避免混用导致维护困难。pandas 不会消失,但 Polars 代表了 Python 数据分析的性能演进方向。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/11 13:20:51

jQuery小体积进度条组件,带实时百分比数字和可换肤样式

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;直接引入就能用的jQuery进度条工具&#xff0c;加载后自动在指定容器里画出进度条&#xff0c;同时在条上方或旁边同步显示当前完成百分比&#xff08;比如35%、78%&#xff09;。依赖jquery-1.4.2.min.js和jqu…

作者头像 李华
网站建设 2026/6/11 13:19:53

你的机械键盘键帽坏了?用3D打印轻松修复!

你的机械键盘键帽坏了&#xff1f;用3D打印轻松修复&#xff01; 【免费下载链接】cherry-mx-keycaps 3D models of Chery MX keycaps 项目地址: https://gitcode.com/gh_mirrors/ch/cherry-mx-keycaps 想象一下&#xff1a;你心爱的机械键盘上&#xff0c;最常用的空格…

作者头像 李华
网站建设 2026/6/11 13:18:50

wvp-GB28181-pro:构建企业级视频监控平台的终极指南

wvp-GB28181-pro&#xff1a;构建企业级视频监控平台的终极指南 【免费下载链接】wvp-GB28181-pro 基于GB28181-2016、部标808、部标1078标准实现的开箱即用的网络视频平台。自带管理页面&#xff0c;支持NAT穿透&#xff0c;支持海康、大华、宇视等品牌的IPC、NVR接入。支持国…

作者头像 李华