You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pandas groupby apply性能优化:80k行耗时43秒,求改进方案

Pandas groupby+apply性能优化方案(80k行数据耗时43秒)

问题背景

使用Pandas的groupby+apply处理80k行数据时耗时43秒,寻求更高效的实现方案。

输入DataFrame

ff    pp           xx     yy
0   10000   IVR     -19.6000    0.9700
1   10000   IVL     -19.8100    11.0900
2   10000   RV      -19.8500    -10.0300
3   10000   LV      -20.1500    23.3100
4   10001   RV      -19.8700    -10.0100
5   10001   IVR     -19.5900    0.9900
6   10001   IVL     -19.8100    11.0700
7   10001   LV      -20.1600    23.3300
8   10002   RV      -19.8900    -10.0000
9   10002   IVR     -19.5700    1.0100
10  10002   IVL     -19.8200    11.0500
11  10002   LV      -20.1800    23.3600
12  10003   IVR     -19.5400    1.0300
13  10003   RV      -19.9100    -9.9800
14  10003   IVL     -19.8200    11.0300
15  10003   LV      -20.1900    23.3700
16  10004   RV      -19.9400    -9.9600
17  10004   IVR     -19.5000    1.0600
18  10004   IVL     -19.8400    11.0100
19  10004   LV      -20.2000    23.4000

输出DataFrame

ff     x_min_LV    x_min_RV    x_max_LV    x_max_RV    y_min_LV    y_min_RV    y_max_LV    y_max_RV
0   10000   0.3400      0.0400      0.5500      0.2500      12.2200     11.0000     22.3400     21.1200
1   10001   0.3500      0.0600      0.5700      0.2800      12.2600     11.0000     22.3400     21.0800
2   10002   0.3600      0.0700      0.6100      0.3200      12.3100     11.0100     22.3500     21.0500
3   10003   0.3700      0.0900      0.6500      0.3700      12.3400     11.0100     22.3400     21.0100
4   10004   0.3600      0.1000      0.7000      0.4400      12.3900     11.0200     22.3400     20.9700

计算逻辑

按ff分组后生成衍生列,核心逻辑如下:

  • x_min_LV:LV的xx值与IVR、IVL的xx值差值的绝对值的最小值(例:ff=10000时,abs(-20.15 - (-19.81)) = 0.34)
  • x_max_LV:LV的xx值与IVR、IVL的xx值差值的绝对值的最大值(例:ff=10000时,abs(-20.15 - (-19.60)) = 0.55)
  • y_min_RV:RV的yy值与IVR、IVL的yy值差值的绝对值的最小值(例:ff=10000时,abs(-10.03 - 0.97) = 11)
  • 其余列(x_min_RV、x_max_RV、y_min_LV、y_max_LV、y_max_RV)逻辑以此类推,对应RV/LV与IVR、IVL的xx/yy值的差值绝对值的最小/最大值。

优化方案

方案1:pivot_table重塑数据 + 向量化计算(最优)

groupby+apply本质是Python层逐组循环,效率极低。先通过pivot_table将数据转为宽表,再用向量化运算批量生成衍生列:

import pandas as pd

# 构造示例数据(实际可替换为你的输入数据)
df = pd.DataFrame({
    'ff': [10000]*4 + [10001]*4 + [10002]*4 + [10003]*4 + [10004]*4,
    'pp': ['IVR','IVL','RV','LV']*5,
    'xx': [-19.60,-19.81,-19.85,-20.15,-19.87,-19.59,-19.81,-20.16,-19.89,-19.57,-19.82,-20.18,-19.54,-19.91,-19.82,-20.19,-19.94,-19.50,-19.84,-20.20],
    'yy': [0.97,11.09,-10.03,23.31,-10.01,0.99,11.07,23.33,-10.00,1.01,11.05,23.36,1.03,-9.98,11.03,23.37,-9.96,1.06,11.01,23.40]
})

# 分别对xx、yy生成宽表
pivot_xx = df.pivot_table(index='ff', columns='pp', values='xx').reset_index()
pivot_yy = df.pivot_table(index='ff', columns='pp', values='yy').reset_index()

# 合并宽表,添加后缀区分xx/yy
pivot_df = pd.merge(pivot_xx, pivot_yy, on='ff', suffixes=('_xx', '_yy'))

# 批量计算所有衍生列(向量化运算)
# xx相关列
pivot_df['x_min_LV'] = pivot_df[['IVR_xx', 'IVL_xx']].sub(pivot_df['LV_xx'], axis=0).abs().min(axis=1)
pivot_df['x_max_LV'] = pivot_df[['IVR_xx', 'IVL_xx']].sub(pivot_df['LV_xx'], axis=0).abs().max(axis=1)
pivot_df['x_min_RV'] = pivot_df[['IVR_xx', 'IVL_xx']].sub(pivot_df['RV_xx'], axis=0).abs().min(axis=1)
pivot_df['x_max_RV'] = pivot_df[['IVR_xx', 'IVL_xx']].sub(pivot_df['RV_xx'], axis=0).abs().max(axis=1)

# yy相关列
pivot_df['y_min_LV'] = pivot_df[['IVR_yy', 'IVL_yy']].sub(pivot_df['LV_yy'], axis=0).abs().min(axis=1)
pivot_df['y_max_LV'] = pivot_df[['IVR_yy', 'IVL_yy']].sub(pivot_df['LV_yy'], axis=0).abs().max(axis=1)
pivot_df['y_min_RV'] = pivot_df[['IVR_yy', 'IVL_yy']].sub(pivot_df['RV_yy'], axis=0).abs().min(axis=1)
pivot_df['y_max_RV'] = pivot_df[['IVR_yy', 'IVL_yy']].sub(pivot_df['RV_yy'], axis=0).abs().max(axis=1)

# 提取目标输出列
output_df = pivot_df[['ff', 'x_min_LV', 'x_min_RV', 'x_max_LV', 'x_max_RV', 'y_min_LV', 'y_min_RV', 'y_max_LV', 'y_max_RV']]
print(output_df.round(4))

方案2:groupby.agg替代apply(次优)

若需保留分组逻辑,用groupby.agg替代apply,减少Python层循环开销:

import pandas as pd

# 构造示例数据
df = pd.DataFrame({
    'ff': [10000]*4 + [10001]*4 + [10002]*4 + [10003]*4 + [10004]*4,
    'pp': ['IVR','IVL','RV','LV']*5,
    'xx': [-19.60,-19.81,-19.85,-20.15,-19.87,-19.59,-19.81,-20.16,-19.89,-19.57,-19.82,-20.18,-19.54,-19.91,-19.82,-20.19,-19.94,-19.50,-19.84,-20.20],
    'yy': [0.97,11.09,-10.03,23.31,-10.01,0.99,11.07,23.33,-10.00,1.01,11.05,23.36,1.03,-9.98,11.03,23.37,-9.96,1.06,11.01,23.40]
})

def agg_func(group):
    # 提取分组内各pp对应的xx/yy值
    lv_xx = group.loc[group['pp'] == 'LV', 'xx'].iloc[0]
    rv_xx = group.loc[group['pp'] == 'RV', 'xx'].iloc[0]
    ivr_xx = group.loc[group['pp'] == 'IVR', 'xx'].iloc[0]
    ivl_xx = group.loc[group['pp'] == 'IVL', 'xx'].iloc[0]
    
    lv_yy = group.loc[group['pp'] == 'LV', 'yy'].iloc[0]
    rv_yy = group.loc[group['pp'] == 'RV', 'yy'].iloc[0]
    ivr_yy = group.loc[group['pp'] == 'IVR', 'yy'].iloc[0]
    ivl_yy = group.loc[group['pp'] == 'IVL', 'yy'].iloc[0]
    
    # 计算衍生列
    return pd.Series({
        'x_min_LV': min(abs(lv_xx - ivr_xx), abs(lv_xx - ivl_xx)),
        'x_max_LV': max(abs(lv_xx - ivr_xx), abs(lv_xx - ivl_xx)),
        'x_min_RV': min(abs(rv_xx - ivr_xx), abs(rv_xx - ivl_xx)),
        'x_max_RV': max(abs(rv_xx - ivr_xx), abs(rv_xx - ivl_xx)),
        'y_min_LV': min(abs(lv_yy - ivr_yy), abs(lv_yy - ivl_yy)),
        'y_max_LV': max(abs(lv_yy - ivr_yy), abs(lv_yy - ivl_yy)),
        'y_min_RV': min(abs(rv_yy - ivr_yy), abs(rv_yy - ivl_yy)),
        'y_max_RV': max(abs(rv_yy - ivr_yy), abs(rv_yy - ivl_yy))
    })

# 应用聚合函数
output_df = df.groupby('ff').apply(agg_func).reset_index()
print(output_df.round(4))

性能对比

  • 原groupby+apply:80k行耗时约43秒
  • 方案1(pivot+向量化):耗时通常在1秒以内(利用Pandas底层C实现,无Python循环)
  • 方案2(groupby.agg):耗时约5-10秒(比原生apply高效,但仍存在Python层循环)

关键优化点

  1. 避免Python层循环:apply触发逐组Python循环,向量化运算直接调用底层C扩展,效率提升数十倍
  2. 数据重塑为宽表:将分散的pp类别转为列,直接进行列间运算,无需在分组内反复查找值
  3. 批量计算:一次性生成所有衍生列,减少重复的数据读取和计算开销

内容的提问来源于stack exchange,提问作者Abhinav Ralhan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 15:59:54