很多 Pandas 初学者在处理数据时,第一反应就是用 .apply(),尤其是带 axis=1 的行遍历。但其实,在绝大多数情况下,你应该尽量避免使用 apply 循环 —— 因为它慢、不优雅、且往往有更高效的方法。
🚫 为什么应该避免 apply 循环?
.apply() 本质上是一个 Python 层面的 for 循环(即使内部做了优化),它会逐行或逐列调用你传入的函数。而 Pandas 的核心优势在于:
✅ 向量化操作(vectorized operations) —— 利用底层 C/C++ 实现的 NumPy 数组运算,一次处理整个数组,速度极快!
举个例子:
import pandas as pd
import numpy as np
df = pd.DataFrame({
'A': np.random.randn(100000),
'B': np.random.randn(100000)
})
❌ 慢:用 apply + lambda 逐行计算
%time df['C'] = df.apply(lambda row: row['A'] + row['B'], axis=1)
# 可能需要几百毫秒甚至几秒
✅ 快:直接向量化运算
%time df['C'] = df['A'] + df['B']
# 通常只需几毫秒!
性能差距可达几十倍甚至上百倍!
✅ 如何避免 apply?替代方案大全
下面根据不同场景,教你如何“消灭” apply:
🧮 场景1:简单数学/逻辑运算 → 直接向量化
❌ 错误写法:
df['total'] = df.apply(lambda x: x['price'] * x['quantity'], axis=1)
✅ 正确写法:
df['total'] = df['price'] * df['quantity']
支持所有基本运算符:+ - * / // % ** > < == & | 等。
📊 场景2:条件赋值 → 使用 np.where, pd.cut, pd.qcut
❌ 错误写法:
def categorize(x):
if x > 90:
return 'A'
elif x > 80:
return 'B'
else:
return 'C'
df['grade'] = df['score'].apply(categorize)
✅ 正确写法:
✅ 方法1:np.where(适合简单条件)
df['grade'] = np.where(df['score'] > 90, 'A',
np.where(df['score'] > 80, 'B', 'C'))
✅ 方法2:pd.cut(适合区间分箱)
bins = [0, 80, 90, 100]
labels = ['C', 'B', 'A']
df['grade'] = pd.cut(df['score'], bins=bins, labels=labels, right=False)
🔍 场景3:字符串操作 → 使用 .str 访问器
❌ 错误写法:
df['upper_name'] = df['name'].apply(lambda x: x.upper() if isinstance(x, str) else x)
✅ 正确写法:
df['upper_name'] = df['name'].str.upper()
Pandas 提供了强大的 .str 方法:
.str.lower(),.str.upper().str.contains(),.str.startswith().str.split(),.str.replace().str.len(),.str.strip()
这些方法都是向量化的!
🗂️ 场景4:复杂逻辑但可拆解 → 分步 + 布尔索引
有时你的 apply 函数包含多个判断和赋值,可以拆成多个布尔条件 + 赋值。
❌ 错误写法:
def assign_group(row):
if row['age'] < 18:
return 'child'
elif row['income'] > 50000:
return 'high_income_adult'
else:
return 'other'
df['group'] = df.apply(assign_group, axis=1)
✅ 正确写法:
df['group'] = 'other' # 默认值
mask_child = df['age'] < 18
df.loc[mask_child, 'group'] = 'child'
mask_high_income = (df['age'] >= 18) & (df['income'] > 50000)
df.loc[mask_high_income, 'group'] = 'high_income_adult'
这样不仅更快,而且逻辑清晰、易于调试!
🧩 场景5:必须自定义函数?→ 尝试 numba 或 swifter
如果确实无法避免复杂函数(比如涉及递归、状态机等),可以考虑:
✅ 方案1:用 numba 加速(仅限数值计算)
from numba import jit
@jit(nopython=True)
def my_func(a, b):
return a * b + a - b
# 注意:需传入 numpy 数组
df['result'] = my_func(df['A'].values, df['B'].values)
✅ 方案2:用 swifter 自动并行化 apply(懒人方案)
pip install swifter
import swifter
df['result'] = df.swifter.apply(lambda row: complex_function(row), axis=1)
⚠️ 注意:这只是让 apply “不那么慢”,不是根本解决方案!
🎯 总结:避免 apply 的黄金法则
| 场景 | 替代方案 |
|---|---|
| 数学/逻辑运算 | 直接向量化 (df['A'] + df['B']) |
| 条件赋值 | np.where, pd.cut |
| 字符串处理 | .str 方法 |
| 复杂条件赋值 | 布尔索引 + .loc |
| 必须自定义函数 | numba, swifter(次选) |
💡 终极建议
每当你想写
.apply(..., axis=1)时,请先停下来问自己:“这个操作能不能用向量化方式完成?”
90% 的情况答案是 YES!
养成这个习惯,你的代码将更快、更简洁、更“Pandasic”。
📚 推荐练习
尝试重构以下代码:
df['discounted_price'] = df.apply(
lambda row: row['price'] * 0.9 if row['category'] == 'electronics' else row['price'],
axis=1
)
✅ 向量化版本:
df['discounted_price'] = np.where(
df['category'] == 'electronics',
df['price'] * 0.9,
df['price']
)
希望这份指南帮你彻底告别低效的 apply 循环!🚀