关于 pandas-performance
pandas-performance 是一项 pandas 性能与内存优化技能,面向处理大型 DataFrame、加载缓慢或遇到运算瓶颈的数据工作者。它涵盖数据类型优化、向量化、Arrow 后端、分块处理及高效读写模式。
主要功能
- 01通过降精度减少内存占用
- 02使用向量化替代逐行操作
- 03采用 PyArrow 后端加载数据
- 04用 Parquet 筛选列与行
- 05以分块方式处理大型文件
- 06优化查找、连接与去重
适用场景
- 01降低大型 DataFrame 的内存压力
- 02加快大型 CSV 的读取
- 03优化数百万行数据的计算与查找
- 04为机器学习准备高效数据
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ tondevrel/ scientific-agent-skills --skill pandas-performance安装后在 Claude Code 中输入/pandas-performance确认已加载。
详细介绍
pandas - 性能与内存管理
标准 pandas 代码通常非常占内存且运行缓慢。本子技能介绍如何通过了解 pandas 的内部架构(BlockManager 和 Arrow 后端),让 pandas 快 10 倍、内存占用减少 5 倍。
适用场景
- 你的 DataFrame 大于 1GB,导致内存压力。
pd.read_csv加载数据耗时过长。- 按行操作(
apply、iterrows)造成性能瓶颈。 - 你需要对数百万行数据执行复杂的连接或查找。
- 为高性能 ML 模型准备数据。
参考文档
- 官方性能指南:https://pandas.pydata.org/docs/user_guide/enhancingperf.html
- 扩展至大型数据:https://pandas.pydata.org/docs/user_guide/scale.html
- 搜索模式:
df.memory_usage、pd.to_numeric(downcast=...)、pd.Categorical、DataFrame.eval()
核心原则
RAM 是瓶颈
pandas 通常会在操作过程中复制数据。处理大型数据时,必须尽量减少复制,并为数据类型选择最高效的位宽。
向量化与循环
- 第 1 级(最佳):NumPy/Pandas 内置的向量化函数。
- 第 2 级(良好):使用
df.eval()或df.query()进行复杂计算。 - 第 3 级(一般):使用
np.vectorize或df.apply()(仅限逻辑复杂的情况)。 - 第 4 级(最差):使用
iterrows()或itertuples()。
内存优化模式
1. “降位宽”工作流
标准整数列和浮点数列默认使用 64 位。大多数科学数据用 16 位或 32 位就能表示。
import pandas as pd
import numpy as np
def optimize_memory(df):
start_mem = df.memory_usage().sum() / 1024**2
for col in df.columns:
col_type = df[col].dtype
if col_type != object:
c_min = df[col].min()
c_max = df[col].max()
if str(col_type)[:3] == 'int':
if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
df[col] = df[col].astype(np.int8)
elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
df[col] = df[col].astype(np.int16)
else:
if c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
df[col] = df[col].astype(np.float32)
else:
# Convert low-cardinality strings to Categorical
num_unique = df[col].nunique()
if num_unique / len(df) < 0.5:
df[col] = df[col].astype('category')
end_mem = df.memory_usage().sum() / 1024**2
print(f'Memory reduced by {100 * (start_mem - end_mem) / start_mem:.1f}%')
return df
2. 现代 PyArrow 后端(Pandas 2.0+)
使用 Arrow 后端可大幅提升字符串操作的速度,并加快数据加载。
# Load with Arrow engine for 2-3x speedup
df = pd.read_csv("data.csv", engine="pyarrow", dtype_backend="pyarrow")
速度优化模式
1. 复杂“if-else”逻辑的向量化(替代 .apply)
不要对每一行都调用 Python 函数:
# ❌ SLOW:
# df['status'] = df.apply(lambda x: 'High' if x['val'] > 100 else 'Low', axis=1)
# ✅ FAST:
df['status'] = np.where(df['val'] > 100, 'High', 'Low')
# ✅ FAST (Multiple conditions):
conditions = [
(df['val'] > 100),
(df['val'] > 50) & (df['val'] <= 100),
(df['val'] <= 50)
]
choices = ['High', 'Medium', 'Low']
df['status'] = np.select(conditions, choices, default='Unknown')
2. 高速查找
如果你需要数百万次从字典或其他表中映射值:
# ❌ SLOW: df.merge() or df['id'].map(large_dict)
# ✅ FAST: Use a Series as a lookup table with index
lookup_table = pd.Series(data=values, index=keys)
result = lookup_table.reindex(df['target_ids']).values
高效 I/O
1. 使用带筛选的 Parquet(谓词下推)
大型数据存储绝不要使用 CSV。请使用 Parquet。
# Save as partitioned parquet
df.to_parquet('data_dir', partition_cols=['year', 'month'])
# Load only specific columns and rows (Fast)
df_subset = pd.read_parquet('data_dir', columns=['price', 'id'],
filters=[('year', '==', 2023)])
2. 内存受限系统中的分块处理
如果文件有 50GB,而你的 RAM 只有 16GB:
# Process in chunks of 100k rows
chunk_size = 100_000
for chunk in pd.read_csv("massive.csv", chunksize=chunk_size):
# Perform aggregation
summary = chunk.groupby('id')['value'].sum()
# Save or update a running total
性能关键规则
✅ 应做
- 谨慎使用原地操作 - 与常见说法相反,
inplace=True通常仍会在内部创建副本。应重点关注数据类型。 - 对索引排序后再切片 - 如果按索引切片大型 DataFrame,请确保索引已排序:
df.sort_index(inplace=True)。这能将 O(N) 操作变为 O(log N)。 - 使用带 format 的 pd.to_datetime - 指定格式(
%Y-%m-%d)比自动解析快得多。 - 善用 .eval() - 对于
(A + B) / (C * D)这类复杂算术,df.eval()使用 numexpr,速度更快且内存效率更高。
❌ 不要做
- 绝不要使用 iterrows() 迭代 - 它会将每一行转换为 Series 对象,速度极慢。
- 避免使用 object 数据类型 - 任何 object 类型的列(通常是字符串)都只是指向 Python 对象的指针,非常占内存。请使用
category或string[pyarrow]。 - 不要在循环中使用 append() - 每次调用都会完整复制 DataFrame。请先将数据收集到列表中,再使用
pd.concat()。
反模式(绝不要使用)
# ❌ BAD: Growing a DataFrame row by row
df = pd.DataFrame()
for data in large_source:
df = pd.concat([df, pd.DataFrame([data])]) # ❌ Disaster for performance!
# ✅ GOOD: List of dicts to DataFrame
data_list = []
for data in large_source:
data_list.append(data)
df = pd.DataFrame(data_list)
# ❌ BAD: Manual string formatting
# df['name'].apply(lambda x: f"USER_{x}")
# ✅ GOOD: Vectorized string accessor
df['name'] = "USER_" + df['name'].astype(str)
实用工作流
1. 找出内存占用大户
# Get detailed memory breakdown (including object overhead)
print(df.memory_usage(deep=True))
# Identify columns with too many unique strings (bad for 'category')
for col in df.select_dtypes(include=['object']):
print(f"{col}: {df[col].nunique() / len(df):.2%}")
2. 快速去重 1000 万行以上的数据
# Using sorting + shift is often faster than drop_duplicates
df = df.sort_values(['id', 'timestamp'])
mask = (df['id'] != df['id'].shift())
df_unique = df[mask]
3. 使用多重索引合并
# If you join on multiple columns, setting them as an index
# and using join() can be 5x faster than merge()
df1.set_index(['key1', 'key2'], inplace=True)
df2.set_index(['key1', 'key2'], inplace=True)
result = df1.join(df2, how='inner')
这项子技能能将 pandas 从原型开发工具转变为高性能引擎,使其能够处理工业规模的科学数据。
评论
支持 Markdown