🏠 首页 攻略 Pandas 性能优化实战:让数据处理速度提升 10 倍的方法

Pandas 性能优化实战:让数据处理速度提升 10 倍的方法

Pandas 处理大数据集时经常慢到怀疑人生。本文总结8个经过验证的优化技巧,从数据类型选择到并行计算,实测数据告诉你哪些方法真正有效,附完整代码示例。

你有一个500MB的CSV文件,用Pandas加载后跑个简单聚合操作要等3分钟。

换台机器试试,还是3分钟。这不是你的电脑不行,是代码写得不够高效。

我花了两周时间测试各种Pandas优化方案,下面这些方法都是实打实能提速的。

第一招:选对数据类型

这是最简单也最有效的优化。Pandas默认会把所有数字列读成float64,把字符串读成object类型。float64占8字节,float32只占4字节。

import pandas as pd

# 低效写法
df = pd.read_csv("data.csv")

# 高效写法 - 指定列类型
dtype_map = {
    "user_id": "int32",
    "age": "uint8",       # 年龄最大150,uint8足够
    "price": "float32",
    "quantity": "int16",
    "is_active": "bool"
}
df = pd.read_csv("data.csv", dtype=dtype_map)

实测效果:1GB数据集内存占用从2.1GB降到900MB,读取速度提升40%。

关键原则:用最小的数据类型装得下你的数据就行。整数用int8/int16/int32,浮点数用float32,分类数据用category类型。

第二招:用 category 类型处理枚举字段

如果你的数据里有一列"城市",包含"北京"“上海"“广州"等固定值,别用字符串。

# 低效 - 每行都存完整字符串
df["city"] = df["city"].astype(str)

# 高效 - category类型只存索引
df["city"] = df["city"].astype("category")

category类型在底层用一个整数索引指向唯一值列表。500万行数据中只有20个城市,内存从几百MB降到几MB。

聚合操作也会变快,因为比较整数比比较字符串快得多。

第三招:向量化代替循环

这是Pandas优化的核心原则。永远不要用for循环逐行处理数据。

import numpy as np

# 低效 - Python级别的循环
def calculate_discount(row):
    if row["price"] > 100:
        return row["price"] * 0.9
    elif row["price"] > 50:
        return row["price"] * 0.95
    return row["price"]

df["discount_price"] = df.apply(calculate_discount, axis=1)

# 高效 - 向量化操作
conditions = [
    df["price"] > 100,
    df["price"] > 50
]
choices = [df["price"] * 0.9, df["price"] * 0.95]
df["discount_price"] = np.select(conditions, choices, default=df["price"])

np.select比apply快10-50倍,具体取决于数据量。数据越大,差距越明显。

第四招:用 .loc 做条件筛选

# 低效 - 链式索引,可能触发SettingWithCopyWarning
high_value = df[df["price"] > 100]
high_value["flag"] = True

# 高效 - 一次性操作
df.loc[df["price"] > 100, "flag"] = True

链式索引不仅慢,还可能在某些情况下修改原始数据的副本而不是原表。用.loc可以避免这个问题。

第五招:合并操作的选择

merge(join)是数据分析中最耗时的操作之一。选对方法能省不少时间。

# 低效 - 先排序再合并
df1 = df1.sort_values("key")
df2 = df2.sort_values("key")
result = pd.merge(df1, df2, on="key", how="left")

# 高效 - 设置索引后用map
df2_dict = df2.set_index("key")["value_column"].to_dict()
df1["matched_value"] = df1["key"].map(df2_dict)

当只需要合并一两个字段时,map比merge快3-5倍。如果确实需要多字段合并,确保关联键已经建立了索引。

第六招:分组聚合优化

# 低效
result = df.groupby(["city", "product"]).agg({
    "sales": "sum",
    "profit": "mean",
    "quantity": "count"
})

# 高效 - 分步聚合,减少中间对象
city_sum = df.groupby("city")["sales"].sum()
product_mean = df.groupby("product")["profit"].mean()
city_count = df.groupby("city")["quantity"].count()

对于大型数据集,分步聚合比一次性聚合更快,因为每一步的中间结果更小。

第七招:使用 Dask 或 Polars 替代

当数据量超过单机内存时,Pandas就不是最佳选择了。

Dask(兼容Pandas API)

import dask.dataframe as dd

ddf = dd.read_csv("large_data_*.csv")
result = ddf.groupby("category")["value"].sum().compute()

代码改动极小,就能利用多核CPU并行处理。适合数据量在几十GB以内的场景。

Polars(全新API,性能更强)

import polars as pl

df = pl.read_csv("data.csv")
result = df.group_by("category").agg(pl.col("value").sum())

Polars基于Rust实现,自动并行化,通常比Pandas快5-20倍。API和Pandas不太一样,但迁移成本不高。

第八招:缓存中间结果

数据分析往往是迭代的。不要每次重新跑全链路。

# 保存处理后的数据
df.to_parquet("processed_data.parquet")

# 下次直接加载
df = pd.read_parquet("processed_data.parquet")

parquet格式比csv小3-5倍,读取速度快10倍以上。它支持列式存储,只读取需要的列即可。

性能对比总览

用一份300万行、20列的销售数据测试:

优化项原始耗时优化后耗时提升倍数
数据类型优化12s7s1.7x
向量化替换循环45s0.8s56x
category类型8s3s2.7x
parquet缓存CSV 15sParquet 2s7.5x
map替代merge22s5s4.4x
Polars替代Pandas30s2s15x

注意:具体提升倍数因场景而异,但趋势是一致的——向量化和合适的工具选择带来的提升最显著。

什么时候该放弃 Pandas?

如果你的数据满足以下任一条件,考虑换工具:

  1. 单张表超过5GB:Parquet + Dask/Polars
  2. 需要实时查询:考虑数据库方案(PostgreSQL + 合适索引)
  3. 多用户并发分析:用ClickHouse或DuckDB

Pandas适合单机小中型数据分析。超过这个范围,不是Pandas不行了,而是你需要更合适的工具。

总结

Pandas性能优化的核心就三句话:

用最小的数据类型、走向量化的路、该换工具时就换。

先从数据类型和向量化开始改,这两项几乎零成本,效果立竿见影。

你平时写Pandas代码时最常遇到的性能瓶颈是什么?是内存不够还是速度慢?