Polars 和 Pandas 的区别及 Polars 更快速的原因

1,227 阅读4分钟

Polars 和 Pandas 的区别及 Polars 更快速的原因

polars

pola-rs.github.io/polars-book…

主要区别

1. 数据结构

  • Pandas:使用 DataFrameSeries 数据结构,基于 Python 的原生 listdict 实现,适合小到中等规模的数据集。
  • Polars:也提供 DataFrameSeries,但使用基于 Rust 的多线程并行计算引擎,适合大数据集。

2. 性能差异

  • Pandas:依赖单线程计算,数据结构基于 Python 对象(如 listdict),在大数据集上性能下降。
  • Polars:使用 Rust 语言的并行计算能力,能充分利用现代 CPU 的多核处理,同时采用零拷贝内存处理,减少数据复制,提高性能。

3. 内存管理

  • Pandas:由于其基于 Python 对象,内存消耗较大,尤其在处理大数据集时容易出现内存瓶颈。
  • Polars:优化了内存管理,使用高效的内存布局,能处理更大的数据集,避免内存问题。

4. API 和功能

  • Pandas:功能丰富,成熟,尤其在数据清洗、处理、时间序列分析等方面非常强大。
  • Polars:API 较新,功能逐渐完善,支持常见数据操作,但缺少一些 Pandas 的高级功能。

5. 易用性

  • Pandas:生态系统广泛,使用者多,文档丰富,学习曲线较低。
  • Polars:学习曲线稍高,尤其在高级功能方面,但在处理性能要求高的任务时具有优势。

为什么 Polars 更快?

Polars 的快速性能来自以下几个方面:

  1. Rust 的高性能:Polars 使用 Rust 编写,Rust 是高性能的编程语言,提供低级的内存控制和更少的垃圾回收开销,从而提升了性能。

  2. 并行计算:Polars 默认使用多线程执行数据处理任务,能够充分利用多核 CPU。相比之下,Pandas 大多数操作都是单线程的,限制了处理速度。

  3. 列式存储:Polars 使用列式存储格式(类似于 Apache Arrow),在执行列操作(如筛选、聚合、排序)时比 Pandas 更高效。

  4. 内存管理优化:Polars 的内存布局更高效,避免了不必要的数据拷贝和内存分配,使得其性能更加优化。

总结

  • Pandas:功能强大、易用,适合常规数据分析,但在大数据集上性能有所下降。
  • Polars:更适合处理大数据,性能优越,但功能和生态系统的成熟度相对较低。

如果更注重性能和处理大数据时的效率,Polars。如果需要丰富的功能和更广泛的生态系统支持,Pandas。

附:10000-1000000的数据量测试

image.png

import pandas as pd
import polars as pl
import numpy as np
import random
import string
import time
import matplotlib.pyplot as plt

# 随机数据生成
def generate_data(num_rows, num_columns):
    """生成一个包含 num_rows 行和 num_columns 列的随机数据集"""
    data = {}
    for i in range(num_columns):
        column_name = f"col_{i}"
        # 随机选择数据类型
        if i % 4 == 0:
            data[column_name] = np.random.rand(num_rows)  # 浮动数值
        elif i % 4 == 1:
            data[column_name] = np.random.randint(0, 100, num_rows)  # 整数
        elif i % 4 == 2:
            data[column_name] = [random.choice(string.ascii_uppercase) for _ in range(num_rows)]  # 字符串
        else:
            data[column_name] = np.random.choice([True, False], num_rows)  # 布尔值
    return data

# 生成数据集
num_rows = 10**6  # 1百万行数据
num_columns = 10  # 10列数据
data = generate_data(num_rows, num_columns)

# Pandas 数据集
df_pandas = pd.DataFrame(data)

# Polars 数据集
df_polars = pl.DataFrame(data)



# 定义一个测试函数来执行常见的数据操作
def test_pandas_operations(df):
    start = time.time()
    df_filtered = df[df['col_0'] > 0.5]  # 筛选操作
    filter_time = time.time() - start

    start = time.time()
    df_grouped = df.groupby('col_1').agg({'col_0': 'mean'})  # 分组聚合
    group_time = time.time() - start

    start = time.time()
    df_sorted = df.sort_values(by='col_2')  # 排序操作
    sort_time = time.time() - start

    start = time.time()
    df_concat = pd.concat([df, df], axis=0)  # 数据连接
    concat_time = time.time() - start

    start = time.time()
    df_sum = df['col_0'].sum()  # 数值计算
    sum_time = time.time() - start

    return filter_time, group_time, sort_time, concat_time, sum_time

def test_polars_operations(df):
    start = time.time()
    df_filtered = df.filter(pl.col('col_0') > 0.5)  # 筛选操作
    filter_time = time.time() - start

    start = time.time()
    df_grouped = df.group_by('col_1').agg(pl.col('col_0').mean())  # 分组聚合
    group_time = time.time() - start

    start = time.time()
    df_sorted = df.sort('col_2')  # 排序操作
    sort_time = time.time() - start

    start = time.time()
    df_concat = pl.concat([df, df])  # 数据连接
    concat_time = time.time() - start

    start = time.time()
    df_sum = df.select(pl.col('col_0').sum())  # 数值计算
    sum_time = time.time() - start

    return filter_time, group_time, sort_time, concat_time, sum_time


# 测试不同数据集规模下的执行时间
row_sizes = [10**4, 10**5, 10**6]  # 不同大小的数据集
pandas_times = []
polars_times = []

for rows in row_sizes:
    print(f"Testing with {rows} rows...")
    # 生成数据
    data = generate_data(rows, num_columns)
    df_pandas = pd.DataFrame(data)
    df_polars = pl.DataFrame(data)

    # 测试 Pandas 操作
    pandas_results = test_pandas_operations(df_pandas)
    pandas_times.append(pandas_results)

    # 测试 Polars 操作
    polars_results = test_polars_operations(df_polars)
    polars_times.append(polars_results)

# 将结果转换为 DataFrame 用于可视化
pandas_times_df = pd.DataFrame(pandas_times, columns=['Filter', 'Group', 'Sort', 'Concat', 'Sum'], index=row_sizes)
polars_times_df = pd.DataFrame(polars_times, columns=['Filter', 'Group', 'Sort', 'Concat', 'Sum'], index=row_sizes)


# 可视化对比速度
fig, axes = plt.subplots(3, 2, figsize=(15, 10))
operations = ['Filter', 'Group', 'Sort', 'Concat', 'Sum']
for i, op in enumerate(operations):
    ax = axes[i // 2, i % 2]
    ax.plot(row_sizes, pandas_times_df[op], label='Pandas', marker='o')
    ax.plot(row_sizes, polars_times_df[op], label='Polars', marker='x')
    ax.set_title(f"{op} Time Comparison")
    ax.set_xlabel('Number of Rows')
    ax.set_ylabel('Time (seconds)')
    ax.legend()

plt.tight_layout()
plt.show()