pandas处理复杂逻辑时,如何减少apply的使用以提高代码运行效率?

5 阅读3分钟

🧭 核心思路:为什么要减少 apply

apply 的问题在于它绕过了 pandas 的向量化引擎,退化为逐行 Python 循环。替代策略的核心优先级如下:

优先级方法适用场景
⭐⭐⭐⭐⭐merge / join多表关联查询
⭐⭐⭐⭐⭐向量化运算(+, *, np.where列间计算
⭐⭐⭐⭐map / isin单列映射/过滤
⭐⭐⭐⭐query()条件筛选
⭐⭐⭐groupby + 聚合函数分组统计
⭐⭐np.vectorize复杂逻辑兜底
apply最后手段

🔍 实战案例与示例代码

案例 1:多表关联后的条件计算(merge 替代 apply

场景:订单表关联用户表、商品表,计算折后价格。

import pandas as pd
import numpy as np

# 三张源表
orders = pd.DataFrame({
    'order_id': [1, 2, 3, 4],
    'user_id':  [101, 102, 101, 103],
    'product_id': [10, 20, 30, 10],
    'quantity': [2, 1, 3, 1]
})

users = pd.DataFrame({
    'user_id': [101, 102, 103],
    'level':   ['vip', 'normal', 'vip']
})

products = pd.DataFrame({
    'product_id': [10, 20, 30],
    'price':      [100.0, 200.0, 150.0]
})

# ❌ 慢方法:apply 逐行查找
def calc_price_slow(row):
    price = products.loc[products['product_id'] == row['product_id'], 'price'].values[0]
    level = users.loc[users['user_id'] == row['user_id'], 'level'].values[0]
    discount = 0.8 if level == 'vip' else 1.0
    return price * row['quantity'] * discount

orders['total_slow'] = orders.apply(calc_price_slow, axis=1)

# ✅ 快方法:merge 向量化
df = orders \
    .merge(users, on='user_id') \
    .merge(products, on='product_id')

df['discount'] = np.where(df['level'] == 'vip', 0.8, 1.0)
df['total_fast'] = df['price'] * df['quantity'] * df['discount']

print(df[['order_id', 'total_slow', 'total_fast']])

性能对比(10万行数据):apply ≈ 8s,merge+np.where ≈ 0.05s [1]


案例 2:多表 query() 链式过滤(替代 apply 条件判断)

场景:从销售记录中筛选"华东区 + 金额 > 500 + 产品在白名单"的订单。

sales = pd.DataFrame({
    'region':  ['华东', '华北', '华东', '华南', '华东'],
    'amount':  [600, 300, 800, 450, 520],
    'product': ['A', 'B', 'A', 'C', 'B'],
    'rep_id':  [1, 2, 1, 3, 2]
})

whitelist = pd.DataFrame({
    'product': ['A', 'B'],
    'category': ['电子', '服装']
})

# ❌ 慢方法:apply 判断
def filter_slow(row):
    return (row['region'] == '华东' and
            row['amount'] > 500 and
            row['product'] in ['A', 'B'])

mask_slow = sales.apply(filter_slow, axis=1)

# ✅ 快方法:query + isin + merge
valid_products = whitelist['product'].tolist()

result = (sales
    .query("region == '华东' and amount > 500")   # 向量化过滤
    .loc[sales['product'].isin(valid_products)]    # isin 替代 in 判断
    .merge(whitelist, on='product')                # 关联白名单表
)

print(result)

案例 3:groupby + transform 替代分组内 apply

场景:多表 join 后,计算每个用户的订单金额占该用户总金额的比例。

orders_ext = pd.DataFrame({
    'user_id':  [101, 101, 102, 103, 103, 103],
    'order_id': [1, 2, 3, 4, 5, 6],
    'amount':   [200, 300, 150, 400, 100, 250]
})

users_info = pd.DataFrame({
    'user_id': [101, 102, 103],
    'city':    ['上海', '北京', '广州']
})

# merge 联表
df = orders_ext.merge(users_info, on='user_id')

# ❌ 慢方法:apply 分组计算
df['ratio_slow'] = df.groupby('user_id')['amount'].apply(
    lambda x: x / x.sum()
).reset_index(level=0, drop=True)

# ✅ 快方法:transform 向量化(无需 reset_index)
df['user_total'] = df.groupby('user_id')['amount'].transform('sum')
df['ratio_fast'] = df['amount'] / df['user_total']

print(df[['user_id', 'city', 'amount', 'ratio_slow', 'ratio_fast']])

transform 保持索引对齐,比 apply 快约 3–5 倍,且代码更简洁。[4]


案例 4:map 字典映射替代 apply 单列转换

场景:多表关联后,用字典映射替代逐行 apply 做编码转换。

df_main = pd.DataFrame({
    'product_id': [10, 20, 30, 10, 20],
    'sales': [5, 3, 8, 2, 6]
})

category_map = {10: '电子', 20: '服装', 30: '食品'}

# ❌ 慢方法
df_main['category_slow'] = df_main['product_id'].apply(lambda x: category_map.get(x, '未知'))

# ✅ 快方法:map 直接接受字典
df_main['category_fast'] = df_main['product_id'].map(category_map).fillna('未知')

print(df_main)

💡 总结:替代 apply 的决策树

需要多表关联?
  └─ 是 → merge / join

需要条件筛选?
  └─ 是 → query() + isin()

需要列间计算?
  └─ 是 → 向量化运算 + np.where

需要分组统计?
  └─ 是 → groupby + transform / agg

需要单列映射?
  └─ 是 → map(dict)

以上都不满足?
  └─ np.vectorize → 实在不行再用 apply

核心原则:让数据操作尽量留在 pandas/NumPy 的 C 层,而不是回到 Python 解释器层。这一原则在大数据量场景下,性能差距会从"秒级"拉开到"分钟级"。