mcpField
P
pandas-performance

pandas-performance

github.com/tondevrel数据与数据库2472 次安装38 周前更新
代码仓库

关于 pandas-performance

pandas-performance 是一项 pandas 性能与内存优化技能,面向处理大型 DataFrame、加载缓慢或遇到运算瓶颈的数据工作者。它涵盖数据类型优化、向量化、Arrow 后端、分块处理及高效读写模式。

主要功能

  1. 01通过降精度减少内存占用
  2. 02使用向量化替代逐行操作
  3. 03采用 PyArrow 后端加载数据
  4. 04用 Parquet 筛选列与行
  5. 05以分块方式处理大型文件
  6. 06优化查找、连接与去重

适用场景

  1. 01降低大型 DataFrame 的内存压力
  2. 02加快大型 CSV 的读取
  3. 03优化数百万行数据的计算与查找
  4. 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 模型准备数据。

参考文档

核心原则

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