Pandas的apply比向量化慢100倍——Pandas性能调优
本文目录
先说结论
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改apply | 2分钟 | 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年大数据老兵,只讲大数据面试干货