Pandas 性能优化(保姆级教程)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观

在数据分析领域,Pandas 是一个不可或缺的工具,它以简洁的语法和强大的数据处理能力赢得了广泛认可。然而,随着数据规模的扩大,许多开发者会发现,原本流畅的代码开始出现性能瓶颈。例如,简单的数据筛选或聚合操作可能需要等待数分钟甚至更长时间。这种情况下,如何优化 Pandas 的性能,就成为提升工作效率的关键。本文将从基础概念到实战技巧,系统性地讲解如何通过合理的方法和工具,显著提升 Pandas 的运行效率。


理解性能瓶颈:从根源解决问题

在优化性能之前,我们需要明确 Pandas 的性能问题通常来源于三个核心环节:内存使用效率计算逻辑设计I/O操作效率

内存使用效率

Pandas 的 DataFrame 是基于 NumPy 的二维数组实现的,因此其内存占用与数据类型(dtypes)和数据规模密切相关。例如,存储一个包含 100 万个整数的列,使用 int64 类型需要 8MB 内存,而若转换为 int32 则只需 4MB。

比喻:这就像整理书架——如果将每本书都塞进特制的防震箱(类似 int64),虽然安全但空间浪费大;而换成标准尺寸的书套(类似 int32),既能节省空间,又不影响使用。

计算逻辑设计

Pandas 的设计哲学是“避免显式循环”,提倡通过向量化(Vectorization)操作提升效率。然而,许多开发者仍会不自觉地使用 for 循环或 apply 函数,导致性能下降。例如,遍历 100 万行数据的循环可能需要数秒甚至更久,而向量化操作可能只需毫秒级时间。

I/O 操作效率

数据读取(如 pd.read_csv)和写入(如 to_csv)的效率也直接影响整体流程。如果原始数据文件格式不佳(如包含大量冗余列),或读取时未进行参数优化,可能会显著拖慢进程。


内存优化:精简数据结构

1. 数据类型转换

通过 dtypes 属性查看当前列的数据类型,并尝试使用更紧凑的类型。例如:

import pandas as pd  

df = pd.DataFrame({  
    "ID": [1, 2, 3],  
    "Sales": [100.5, 200.3, 300.1],  
    "Category": ["A", "B", "A"]  
})  

print(df.memory_usage(deep=True))  

df["ID"] = df["ID"].astype("int32")  
df["Sales"] = df["Sales"].astype("float32")  
df["Category"] = df["Category"].astype("category")  

print("优化后内存占用:", df.memory_usage(deep=True))  

关键点

  • 对于分类数据(如性别、省份),使用 category 类型可显著减少内存。
  • 避免使用 object 类型存储数值数据(如字符串表示的数字)。

2. 稀疏数据的处理

当数据中存在大量重复值或 NaN 时,可以考虑使用 稀疏 DataFrame

df_sparse = pd.DataFrame({  
    "Col1": [np.nan] * 1000000 + [1],  
    "Col2": [0] * 1000000 + [2]  
})  

df_sparse = df_sparse.to_sparse()  

3. 避免不必要的数据复制

Pandas 的许多操作(如 df.copy()、切片)会默认生成新对象,导致内存浪费。例如:

df_new = df.copy()  
df_new["New_Column"] = df_new["Sales"] * 0.8  

df["New_Column"] = df["Sales"] * 0.8  

计算优化:从循环到向量化

1. 向量化操作的威力

向量化操作利用底层 C 语言加速计算,而 Python 循环则因解释器开销效率低下。例如:

result = []  
for value in df["Sales"]:  
    if value > 100:  
        result.append(value * 0.9)  
    else:  
        result.append(value)  

df["Discounted"] = df["Sales"].where(df["Sales"] <= 100, df["Sales"] * 0.9)  

比喻:这如同用扫地机器人(向量化)清理房间,而非手动逐块擦拭(循环),效率差异立现。

2. 避免滥用 apply

虽然 apply 函数灵活,但其本质仍是隐式循环。例如:

df["Total"] = df.apply(lambda row: row["Sales"] * row["Quantity"], axis=1)  

df["Total"] = df["Sales"] * df["Quantity"]  

3. 利用查询优化(Query Optimization)

通过 query 方法或布尔索引替代多重条件判断,减少中间对象的创建:

filtered = df[(df["Category"] == "A") & (df["Sales"] > 200)]  

filtered = df.query("Category == 'A' and Sales > 200")  

I/O 优化:高效读写数据

1. 按需读取数据

使用 usecolsdtype 参数减少读取数据的范围和类型:

df = pd.read_csv("large_data.csv",  
                usecols=["ID", "Sales", "Category"],  
                dtype={"ID": "int32", "Sales": "float32"})  

2. 并行化读取

对于超大数据集,可尝试分块读取或并行处理:

chunk_size = 100000  
for chunk in pd.read_csv("large_data.csv", chunksize=chunk_size):  
    process(chunk)  # 自定义处理函数  

3. 写入时压缩数据

使用 to_parquetto_hdf 替代 to_csv,并启用压缩:

df.to_parquet("optimized_data.parquet", compression="gzip")  

高级技巧:更进一步的优化策略

1. 利用 Categorical 和 Datetime 类型

将频繁出现的类别或日期列转换为 categorydatetime64 类型,提升查询速度:

df["Category"] = df["Category"].astype("category")  

df["Date"] = pd.to_datetime(df["Date"], format="%Y-%m-%d")  

2. 使用 NumPy 的底层操作

直接操作 DataFrame 的 values 属性,绕过 Pandas 的封装开销:

df["New_Column"] = (df["Sales"].values * 0.9).astype("float32")  

3. 查询表达式(Query Expressions)

通过 @ 符号引用外部变量,避免动态字符串拼接:

threshold = 200  
filtered = df.query("Sales > @threshold")  # 安全引用外部变量  

总结

Pandas 性能优化是一个系统性工程,需要从内存管理、计算逻辑设计和 I/O 操作三个维度入手。通过数据类型转换、向量化操作、分块处理等方法,开发者可以显著减少计算时间和内存占用。例如,在处理千万级数据时,合理优化后可能将运行时间从数分钟缩短至数秒。

对于初学者,建议从以下步骤开始实践:

  1. 使用 memory_usage 定位内存瓶颈;
  2. for 循环改写为向量化操作;
  3. 优先选择 Parquet 或 Feather 等高效存储格式。

记住,性能优化并非一蹴而就,而是需要结合具体场景持续迭代。通过本文提供的方法和案例,读者可以逐步掌握 Pandas 的核心优化技巧,并在实际项目中实现效率的飞跃。

最新发布