第八章:性能优化与进阶技巧 第八章:Pandas 性能优化与进阶技巧 Pandas 是 Python 中用于数据分析的核心库。 虽然它提供了强大的数据操作功能,但在处理大型数据集时,性能可能会成为瓶颈。 本章将深入探讨 Pandas 性能优化的各种技术和进阶技巧,帮助你编写更高效的代码。 8.1 理解 Pandas 性能瓶颈 Pandas 的性能瓶颈通常源于以下几个方面: 循环迭代: 显式循环( 循环)遍历 Pandas 对象通常很慢。 数据类型: 不合适的数据类型会导致内存占用增加和计算速度降低。 矢量化操作的低效使用: 未充分利用 Pandas 的矢量化操作。 数据副本: 不必要的数据复制会消耗大量时间和内存。 内存管理: 大型数据集的内存管理不当。 8.
Pandas 是 Python 中用于数据分析的核心库。 虽然它提供了强大的数据操作功能,但在处理大型数据集时,性能可能会成为瓶颈。 本章将深入探讨 Pandas 性能优化的各种技术和进阶技巧,帮助你编写更高效的代码。
Pandas 的性能瓶颈通常源于以下几个方面:
循环迭代: 显式循环(for 循环)遍历 Pandas 对象通常很慢。
数据类型: 不合适的数据类型会导致内存占用增加和计算速度降低。
矢量化操作的低效使用: 未充分利用 Pandas 的矢量化操作。
数据副本: 不必要的数据复制会消耗大量时间和内存。
内存管理: 大型数据集的内存管理不当。
矢量化是 Pandas 性能优化的基石。 它利用 NumPy 的底层实现,将操作应用于整个数组,而不是逐个元素进行。
示例:
import pandas as pd import numpy as np # 创建一个示例 DataFrame df = pd.DataFrame({'A': np.random.rand(100000), 'B': np.random.rand(100000)}) # 使用循环 (低效) def add_columns_loop(df): for i in range(len(df)): df.loc[i, 'C'] = df.loc[i, 'A'] + df.loc[i, 'B'] return df # 使用矢量化 (高效) def add_columns_vectorized(df): df['C'] = df['A'] + df['B'] return df # 比较性能 import time start_time = time.time() df_loop = add_columns_loop(df.copy()) # 使用copy()避免修改原始数据 end_time = time.time() loop_time = end_time - start_time print(f"循环耗时: {loop_time:.4f} 秒") start_time = time.time() df_vectorized = add_columns_vectorized(df.copy()) end_time = time.time() vectorized_time = end_time - start_time print(f"矢量化耗时: {vectorized_time:.4f} 秒") print(f"矢量化操作比循环快 {loop_time/vectorized_time:.2f} 倍")
解释:
矢量化操作直接对 A 和 B 列的 NumPy 数组进行加法运算,而循环则逐行访问 DataFrame,效率差异显著。
apply 函数apply 函数可以对 DataFrame 的行或列应用自定义函数。 虽然它比循环更灵活,但性能通常不如矢量化操作。 如果 apply 中使用的函数无法矢量化,它仍然是比显式循环更好的选择。
示例:
def custom_function(row): return row['A'] * 2 + row['B'] df['D'] = df.apply(custom_function, axis=1) # axis=1 表示按行应用
优化 apply:
如果 apply 中使用的函数可以进行 JIT 编译,可以使用 numba 库进行加速。
from numba import jit @jit(nopython=True) def custom_function_numba(a, b): return a * 2 + b df['E'] = df[['A', 'B']].apply(lambda x: custom_function_numba(x['A'], x['B']), axis=1)
eval() 和 query()eval() 和 query() 函数允许使用字符串表达式进行计算和过滤,它们可以利用 NumPy 的底层加速。
示例:
# 使用 eval() 进行计算 df['F'] = df.eval('A + B * 2') # 使用 query() 进行过滤 df_filtered = df.query('A > 0.5 and B < 0.5')
选择合适的数据类型可以显著减少内存占用并提高性能。
category 类型: 用于表示有限数量的重复值,例如性别、国家等。df['category_col'] = df['category_col'].astype('category')
int8、int16、int32、int64、float32、float64)。df['int_col'] = df['int_col'].astype('int32')
示例:
df = pd.DataFrame({'A': np.random.randint(0, 100, 1000000), 'B': ['apple', 'banana', 'orange'] * (1000000 // 3)}) # 原始内存占用 memory_usage_before = df.memory_usage(deep=True).sum() / 1024**2 print(f"优化前内存占用: {memory_usage_before:.2f} MB") # 优化数据类型 df['A'] = df['A'].astype('int8') df['B'] = df['B'].astype('category') # 优化后内存占用 memory_usage_after = df.memory_usage(deep=True).sum() / 1024**2 print(f"优化后内存占用: {memory_usage_after:.2f} MB")
pd.read_csv 优化读取 CSV 文件时,可以使用以下技巧进行优化:
dtype 参数: 指定每列的数据类型,避免 Pandas 自动推断。
usecols 参数: 只读取需要的列,减少内存占用。
chunksize 参数: 分块读取文件,避免一次性加载整个文件。
low_memory=False: 禁用 Pandas 的内存推断,可能提高读取速度。
示例:
df = pd.read_csv('large_file.csv', dtype={'col1': 'int32', 'col2': 'category'}, usecols=['col1', 'col2', 'col3'], chunksize=100000, low_memory=False) for chunk in df: # 处理每个数据块 print(chunk.head())
Pandas 中的某些操作会创建数据的副本,这会消耗时间和内存。 使用 inplace=True 参数可以避免创建副本,直接修改原始 DataFrame。 但是,过度使用 inplace=True 可能会导致代码难以调试。
示例:
# 创建副本 df_copy = df.copy() # 避免创建副本 (谨慎使用) df.rename(columns={'A': 'new_A'}, inplace=True)
Parquet 是一种列式存储格式,可以高效地存储和读取大型数据集。 它支持压缩和数据类型推断,可以显著提高性能。
示例:
# 保存为 Parquet 格式 df.to_parquet('data.parquet', engine='pyarrow') # 读取 Parquet 格式 df = pd.read_parquet('data.parquet', engine='pyarrow')
对于计算密集型任务,可以使用 dask 或 modin 等库进行并行计算。
Dask: 将 Pandas 操作分解为多个任务,并在多个核心上并行执行。
Modin: 使用 Ray 或 Dask 作为底层引擎,加速 Pandas 操作。
示例 (使用 Modin):
import modin.pandas as pd import numpy as np # 创建一个示例 DataFrame df = pd.DataFrame(np.random.randint(0, 100, size=(1000000, 4)), columns=list('ABCD')) def complex_calculation(row): return row['A']**2 + row['B'] * row['C'] - row['D'] df['E'] = df.apply(complex_calculation, axis=1) print(df.head())
性能优化是一个持续的过程,需要根据具体情况选择合适的技巧。 以下是一些常用的优化策略:
优先使用矢量化操作。
谨慎使用 apply 函数,并考虑使用 numba 进行加速。
使用 eval() 和 query() 函数进行计算和过滤。
优化数据类型,减少内存占用。
使用 pd.read_csv 优化读取 CSV 文件。
避免不必要的数据复制。
使用 Parquet 格式存储大型数据集。
考虑使用并行计算库 (Dask, Modin) 加速计算密集型任务。
性能优化流程图 (Mermaid):
通过理解 Pandas 的性能瓶颈并应用这些优化技巧,可以显著提高数据分析的效率。