订阅更新

Pandas的apply比向量化慢100倍——Pandas性能调优

Python手册 第 18 题 约 8 分钟读完

本文目录
  1. 先说结论
  2. 向量化 vs apply vs iterrows
  3. 复杂条件的向量化
  4. category类型——字符串内存优化
  5. query/eval——复杂条件筛选加速
  6. 实战经验:200万行从15分钟到8秒
  7. 面试高频追问
  8. 总结

先说结论

Pandas性能调优四大原则:① 用向量化操作替代apply/iterrows(快100倍);② 用category类型减少字符串内存(省80%);③ 用query/eval替代复杂条件筛选(快2-3倍);④ 选对数据类型(int32代替int64省50%内存)。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

向量化 vs apply vs iterrows

方法原理速度推荐度
向量化(df['a']+df['b'])NumPy C操作整列最快(基准)首选
apply(func, axis=1)Python逐行调用函数慢10-100倍尽量避免
iterrows()Python逐行迭代最慢(比apply还慢)绝对不用
itertuples()命名元组迭代比iterrows快5倍必须迭代时用
# 场景:计算每行的折扣金额 = amount * discount_rate
 
# iterrows(最慢——15分钟/200万行)
results = []
for idx, row in df.iterrows():
    results.append(row['amount'] * row['discount_rate'])
df['discount_amount'] = results
 
# apply(慢——2分钟/200万行)
df['discount_amount'] = df.apply(
    lambda row: row['amount'] * row['discount_rate'], axis=1)
 
# 向量化(最快——0.01秒/200万行!)
df['discount_amount'] = df['amount'] * df['discount_rate']
 
# 差距:15分钟 vs 0.01秒 = 90000倍!
# 向量化底层是NumPy的C代码直接操作连续内存
# apply/iterrows是Python逐行调用,每行都有Python开销

复杂条件的向量化

# 场景:根据金额分级
# apply写法(慢)
def get_level(amount):
    if amount > 1000: return 'high'
    elif amount > 100: return 'medium'
    else: return 'low'
df['level'] = df['amount'].apply(get_level)
 
# 向量化写法(快50倍)——np.select
import numpy as np
conditions = [df['amount'] > 1000, df['amount'] > 100]
choices = ['high', 'medium']
df['level'] = np.select(conditions, choices, default='low')
 
# 或者用pd.cut(更适合分箱场景)
df['level'] = pd.cut(df['amount'],
    bins=[0, 100, 1000, float('inf')],
    labels=['low', 'medium', 'high'])
 
# 字符串操作的向量化
# apply写法
df['name_upper'] = df['name'].apply(lambda x: x.upper())
# 向量化写法(Pandas str方法)
df['name_upper'] = df['name'].str.upper()  # 快3-5倍

category类型——字符串内存优化

# country列:50个不同值,200万行
# object类型(默认)
df = pd.read_csv('data.csv')
df['country'].memory_usage(deep=True)  # 约120MB
 
# category类型
df['country'] = df['country'].astype('category')
df['country'].memory_usage(deep=True)  # 约2MB(省98%!)
 
# 原理:category把字符串映射为整数编码
# 50个不同值只存50个字符串 + 200万个int8
# 而不是200万个字符串对象
 
# 读CSV时直接指定(更快)
df = pd.read_csv('data.csv', dtype={'country': 'category',
    'event_name': 'category', 'device_type': 'category'})

query/eval——复杂条件筛选加速

# 普通筛选
df_filtered = df[(df['amount'] > 100) & (df['country'] == 'US') & (df['status'] == 'paid')]
 
# query筛选(快2-3倍,可读性也更好)
df_filtered = df.query('amount > 100 and country == "US" and status == "paid"')
 
# eval计算新列(避免创建中间临时DataFrame)
df['profit'] = df.eval('amount * margin - cost')
# 比 df['profit'] = df['amount'] * df['margin'] - df['cost'] 快30%
# 因为eval在底层一次完成计算,不创建中间数组

实战经验:200万行从15分钟到8秒

在某知名出海硬件品牌的数据分析脚本优化:

步骤方法时间提升
原始iterrows逐行计算15分钟基准
步骤一iterrows改apply2分钟7.5倍
步骤二apply改向量化0.5秒240倍
步骤三category减少内存0.3秒读取加速
步骤四query替代复杂条件0.2秒1.5倍
最终全部优化8秒(含IO)112倍

核心发现:iterrows改向量化贡献了99%的提升。其他优化是锦上添花。记住一条规则就够了:永远不要用iterrows/apply处理大数据量的DataFrame。

面试高频追问

追问一:什么时候不得不用apply?

当逻辑太复杂无法用向量化表达时——比如调用外部API、复杂的状态机逻辑、依赖多列的复杂条件。但这种情况下应该考虑:① 能不能拆成多个向量化步骤组合;② 能不能用np.vectorize(比apply快2-3倍但还是比不过真正的向量化);③ 能不能用Cython/Numba加速。

追问二:Pandas和PySpark处理同样数据谁快?

数据量小(几百MB以内)Pandas更快——因为Pandas直接操作内存中的NumPy数组,PySpark有Catalyst优化器+序列化的额外开销。数据量大(几GB以上)PySpark更快——分布式并行+不受单机内存限制。交叉点大约在1-2GB左右。

追问三:Pandas 2.0的PyArrow后端有什么好处?

PyArrow后端用Arrow格式替代NumPy数组存储数据。好处:① 字符串列内存省50%+(Arrow的字符串存储比Python object紧凑得多);② 缺失值处理更好(原生NA类型,不需要用float NaN表示整数的缺失);③ 和PySpark/Polars的Arrow生态互通。pd.read_csv('data.csv', dtype_backend='pyarrow')开启。

总结

Pandas调优讲四原则:① 向量化替代apply/iterrows(快100倍,最重要);② category减少字符串内存(省80-98%);③ query/eval加速复杂条件(快2-3倍);④ 选对dtype(int32/float32省一半)。核心就一句话:永远不要逐行操作DataFrame。能讲出200万行从15分钟到8秒的优化过程,面试官觉得你注意过Pandas性能细节。

下一篇聊大数据开发者的Python学习路线:哪些必须掌握哪些可以跳过?

你们Pandas代码用过iterrows吗?改过向量化吗?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,只讲大数据面试干货

刷题进度保存在当前浏览器