学习笔记
2026-07-11 · 约8分钟阅读

Pandas数据处理技巧:性能优化与常见陷阱

Python Pandas 数据处理

性能优化技巧

1. 避免使用Python循环

Pandas最常见的性能问题是使用Python循环遍历DataFrame。以下是几种替代方案:

import pandas as pd
import numpy as np

df = pd.DataFrame({'a': range(100000), 'b': range(100000, 200000)})

# ❌ 慢:Python循环(约10秒)
result = []
for i in range(len(df)):
    result.append(df['a'].iloc[i] + df['b'].iloc[i])

# ✅ 快:向量化操作(约0.001秒)
result = df['a'] + df['b']

# ✅ 快:apply(比循环快,但不如向量化)
result = df.apply(lambda row: row['a'] + row['b'], axis=1)

# ✅ 最快:numpy向量化
result = df['a'].values + df['b'].values
✅ 性能对比:向量化操作比Python循环快约10000倍!能用向量化解决的问题,绝对不要用循环。

2. 使用合适的数据类型

默认的数据类型可能会浪费大量内存,尤其是对于大数据集。

df = pd.DataFrame({
    'category': ['A', 'B', 'C', 'A', 'B'] * 20000,
    'value': np.random.randn(100000),
    'flag': [True, False] * 50000
})

# 查看当前内存使用
print(df.memory_usage(deep=True))

# ✅ 优化:将字符串转为category类型
df['category'] = df['category'].astype('category')

# ✅ 优化:将float64转为float32(如果精度允许)
df['value'] = df['value'].astype('float32')

# ✅ 优化:将bool转为int8(节省75%空间)
df['flag'] = df['flag'].astype('int8')

print(df.memory_usage(deep=True))
✅ 优化效果:category类型可以将字符串列的内存占用降低90%以上。

3. 选择合适的文件格式

不同文件格式的读写速度和压缩率差异很大:

# ❌ CSV:通用但慢、体积大
df.to_csv('data.csv', index=False)
df = pd.read_csv('data.csv')

# ✅ Parquet:推荐用于数据分析,压缩率高、读取快
df.to_parquet('data.parquet', index=False)
df = pd.read_parquet('data.parquet')

# ✅ Feather:读取速度最快,适合临时文件
df.to_feather('data.feather')
df = pd.read_feather('data.feather')

# ✅ Pickle:Python专用,支持复杂对象
df.to_pickle('data.pkl')
df = pd.read_pickle('data.pkl')

4. 使用query替代布尔索引

# ❌ 普通布尔索引
result = df[(df['a'] > 100) & (df['b'] < 200) & (df['c'] == 'test')]

# ✅ 使用query,代码更简洁,大数据集更快
result = df.query('a > 100 and b < 200 and c == "test"')

5. 分块处理大文件

当文件太大无法一次性加载到内存时,使用分块处理:

# 分块读取CSV
chunk_size = 100000
chunks = []

for chunk in pd.read_csv('large_file.csv', chunksize=chunk_size):
    processed = chunk[chunk['value'] > 0]
    chunks.append(processed)

df = pd.concat(chunks, ignore_index=True)

常见陷阱

陷阱一:链式赋值警告(SettingWithCopyWarning)

这是Pandas最常见的警告之一,容易导致意外的数据修改。

df = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]})

# ❌ 链式赋值,可能导致SettingWithCopyWarning
df[df['a'] > 1]['b'] = 100

# ✅ 正确方式:使用.loc
df.loc[df['a'] > 1, 'b'] = 100

# ✅ 或先创建副本
subset = df[df['a'] > 1].copy()
subset['b'] = 100
⚠️ 为什么会警告?链式赋值可能返回原始数据的视图或副本,修改行为不确定。始终使用.loc进行条件赋值。

陷阱二:浮点数精度问题

# ❌ 直接比较浮点数
df = pd.DataFrame({'value': [0.1 + 0.2]})
print(df['value'] == 0.3)  # False!

# ✅ 使用round或math.isclose
print(round(df['value'], 10) == 0.3)  # True

import math
print(df['value'].apply(lambda x: math.isclose(x, 0.3, rel_tol=1e-9)))  # True

陷阱三:groupby后的索引问题

df = pd.DataFrame({'group': ['A', 'A', 'B'], 'value': [1, 2, 3]})

# ❌ groupby后默认保留group列作为索引
result = df.groupby('group')['value'].sum()
print(result.index)  # Index(['A', 'B'], dtype='object', name='group')

# ✅ 使用as_index=False保留列
result = df.groupby('group', as_index=False)['value'].sum()
print(result.columns)  # Index(['group', 'value'], dtype='object')

陷阱四:datetime格式处理

# ❌ 字符串比较日期
df = pd.DataFrame({'date': ['2024-01-01', '2024-01-02']})
print(df['date'] > '2024-01-01')  # 可能工作,但不推荐

# ✅ 转为datetime类型
df['date'] = pd.to_datetime(df['date'])
print(df['date'] > '2024-01-01')  # 正确的日期比较

# ✅ 指定格式加速转换
df['date'] = pd.to_datetime(df['date'], format='%Y-%m-%d')

陷阱五:dropna的默认行为

df = pd.DataFrame({
    'a': [1, 2, None, 4],
    'b': [None, 5, 6, 7],
    'c': [8, 9, 10, 11]
})

# ❌ 默认dropna删除任何包含NaN的行
result = df.dropna()  # 只剩第4行

# ✅ 指定subset只检查特定列
result = df.dropna(subset=['a'])  # 删除第3行

# ✅ 使用thresh指定至少需要多少非NaN值
result = df.dropna(thresh=2)  # 删除第1行和第3行

实用技巧

1. 快速查看数据概况

df = pd.read_csv('data.csv')

# 查看基本信息
df.info()

# 查看统计摘要
df.describe()

# 查看前几行
df.head()

# 查看缺失值情况
df.isnull().sum()

# 查看唯一值数量
df.nunique()

2. 批量重命名列

df = pd.DataFrame({
    'UserID': [1, 2, 3],
    'UserName': ['Alice', 'Bob', 'Charlie'],
    'UserAge': [25, 30, 35]
})

# ✅ 使用str方法批量重命名
df.columns = df.columns.str.lower()
df.columns = df.columns.str.replace('user', '')
# 结果:['id', 'name', 'age']

3. 透视表快速统计

df = pd.DataFrame({
    'category': ['A', 'A', 'B', 'B'],
    'region': ['East', 'West', 'East', 'West'],
    'sales': [100, 150, 200, 250]
})

# 创建透视表
pivot = df.pivot_table(
    index='category',
    columns='region',
    values='sales',
    aggfunc='sum',
    margins=True  # 添加合计行/列
)

print(pivot)

4. 数据分箱

df = pd.DataFrame({'score': [55, 68, 72, 85, 92, 98]})

# ✅ 使用cut进行等距分箱
df['grade'] = pd.cut(
    df['score'],
    bins=[0, 60, 70, 80, 90, 100],
    labels=['F', 'D', 'C', 'B', 'A']
)

# ✅ 使用qcut进行等频分箱(每个区间数量相等)
df['quantile'] = pd.qcut(df['score'], q=4, labels=['Q1', 'Q2', 'Q3', 'Q4'])

5. 处理重复数据

df = pd.DataFrame({
    'a': [1, 1, 2, 2, 3],
    'b': [10, 10, 20, 20, 30]
})

# 查看重复行
print(df.duplicated())

# 删除重复行
df = df.drop_duplicates()

# 根据特定列判断重复
df = df.drop_duplicates(subset=['a'])

总结

Pandas是数据分析的利器,但要发挥其最大效能,需要注意:

  1. 优先使用向量化操作,避免Python循环
  2. 选择合适的数据类型,减少内存占用
  3. 使用高效的文件格式(Parquet > Feather > CSV)
  4. 注意链式赋值,始终使用.loc进行条件修改
  5. 处理浮点数精度问题,使用round或isclose

记住:Pandas的性能瓶颈通常不在Pandas本身,而在使用方式。掌握上述技巧,可以让你的数据处理速度提升10倍甚至100倍。

🤖
AI助手
ONLINE