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

如何加速Pandas嵌套循环并添加指定状态条件计算

性能优化+新规则实现方案

核心问题说明

现有嵌套循环处理10万条客户数据耗时15分钟,需要完成两项改造:

  • 重构代码压缩运行耗时
  • 新增计算规则:仅客户所属州为A、B、C时,计算结果乘以lookup2对应取值,其余州计算结果乘以1

现有代码耗时过高的核心原因:

  • 循环内反复调用pandas.loc做单值查询,单条索引匹配开销远大于取值本身
  • 全量使用Python原生for循环逐客户、逐年份迭代,未利用numpy向量化能力
  • 循环内嵌多层条件判断,进一步放大单轮迭代耗时

现有代码与样例数据

基础样例数据

customer_data = pd.DataFrame({"cust_id": [1, 2, 3, 4, 5, 6, 7, 8],
                              "state": ['B', 'E', 'D', 'A', 'B', 'E', 'C', 'A'],
                              "cust_amt": [1000,300, 500, 200, 400, 600, 200, 300],
                              "year":[3, 3, 4, 3, 4, 2, 2, 4],
                              "group":[10, 25, 30, 40, 55, 60, 70, 85]})

state_list = ['A','B','C','D','E']

# All lookups should be dataframes with the year and/or group and the value like these.
lookup1 = pd.DataFrame({'year': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
                       'lim %': 0.1})
lookup2 = pd.concat([pd.DataFrame({'group':g, 'lookup_val': 0.1, 'year':range(1, 11)} 
                                  for g in customer_data['group'].unique())]).explode('year')

multi_data = np.arange(250).reshape(10,5,5)

原有预处理逻辑

lookups = [lookup1, lookup2]

# Preprocessing.
# Transform the state to categorical code to use it as array index.
customer_data['state'] = pd.Categorical(customer_data['state'], 
                                        categories=state_list, 
                                        ordered=True).codes

# Set index on lookups.
for i in range(len(lookups)):
    if 'group' in lookups[i].columns:
        lookups[i] = lookups[i].set_index(['year', 'group'])
    else:
        lookups[i] = lookups[i].set_index(['year'])

原有循环计算逻辑

results = {}
for customer, state, amount, start, group in customer_data.itertuples(name=None, index=False):
    for year in range(start, len(multi_data)+1):
        if year == start:
            results[customer] = [[amount * multi_data[year-1, state, :]]]
        else:
            results[customer].append([results[customer][-1][-1] @ multi_data[year-1]])
                
        for lookup in lookups:
            if isinstance(lookup.index, pd.MultiIndex):
                value = lookup.loc[(year, group)].iat[0]
            else:
                value = lookup.loc[year].iat[0]
            results[customer][-1].append(value * results[customer][-1][-1])

预期输出格式

{1: [[array([55000, 56000, 57000, 58000, 59000]),
   array([5500., 5600., 5700., 5800., 5900.]),
   array([550., 560., 570., 5800., 5900.])],...

优化方案

优化思路

  • 所有lookup表提前转为numpy数组,通过数组下标直接取值,彻底消除pandas索引查询开销
  • 提前预处理客户维度的lookup2乘数掩码:州为A/B/C(对应编码0/1/2)的客户使用lookup2原值,其余州客户乘数固定为1,循环内无需做重复分支判断
  • 最小化循环内操作逻辑,仅保留必须按迭代顺序计算的矩阵连乘操作,其余常量计算全部前置完成

优化后可直接运行代码

import pandas as pd
import numpy as np

# ---------------------- 基础数据加载 ----------------------
customer_data = pd.DataFrame({"cust_id": [1, 2, 3, 4, 5, 6, 7, 8],
                              "state": ['B', 'E', 'D', 'A', 'B', 'E', 'C', 'A'],
                              "cust_amt": [1000,300, 500, 200, 400, 600, 200, 300],
                              "year":[3, 3, 4, 3, 4, 2, 2, 4],
                              "group":[10, 25, 30, 40, 55, 60, 70, 85]})

state_list = ['A','B','C','D','E']
abc_state_codes = {0,1,2} # A/B/C对应编码值

lookup1 = pd.DataFrame({'year': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 'lim %': 0.1})
lookup2 = pd.concat([pd.DataFrame({'group':g, 'lookup_val': 0.1, 'year':range(1, 11)} 
                                  for g in customer_data['group'].unique())]).explode('year')
multi_data = np.arange(250).reshape(10,5,5)
max_year = len(multi_data)

# ---------------------- 优化预处理 ----------------------
# 州转类别编码
customer_data['state'] = pd.Categorical(customer_data['state'], categories=state_list, ordered=True).codes
# group转连续整数编码,适配numpy数组下标
group_cats = customer_data['group'].unique()
group_code_map = {g:i for i,g in enumerate(group_cats)}
customer_data['group_code'] = customer_data['group'].map(group_code_map)

# lookup1转一维numpy数组,下标直接对应年份
lookup1_arr = np.zeros(max_year + 1)
for y, val in lookup1.set_index('year')['lim %'].items():
    lookup1_arr[y] = val

# lookup2转二维numpy数组,默认值为1,直接匹配非A/B/C州乘数要求
lookup2_arr = np.ones((max_year + 1, len(group_cats)))
for (y, g), val in lookup2.set_index(['year','group'])['lookup_val'].items():
    lookup2_arr[y, group_code_map[g]] = val

# 提前标记需要应用lookup2乘数的客户
customer_data['use_lookup2'] = customer_data['state'].isin(abc_state_codes).to_numpy()

# ---------------------- 优化计算逻辑 ----------------------
results = {}
cust_records = customer_data[['cust_id','state','cust_amt','year','group_code','use_lookup2']].to_numpy()

for cust_id, state, amount, start_year, group_code, use_l2 in cust_records:
    cust_res = []
    # 计算起始年结果
    current_arr = amount * multi_data[start_year - 1, state, :]
    l1_val = lookup1_arr[start_year]
    l2_val = lookup2_arr[start_year, group_code] if use_l2 else 1
    cust_res.append([
        current_arr,
        current_arr * l1_val,
        current_arr * l1_val * l2_val
    ])
    # 迭代计算后续年份
    for year in range(start_year + 1, max_year + 1):
        current_arr = cust_res[-1][-1] @ multi_data[year - 1]
        l1_val = lookup1_arr[year]
        l2_val = lookup2_arr[year, group_code] if use_l2 else 1
        cust_res.append([
            current_arr,
            current_arr * l1_val,
            current_arr * l1_val * l2_val
        ])
    results[cust_id] = cust_res

效果说明

  • 输出结果结构和原逻辑完全一致,已实现新增的州维度乘数规则
  • 消除循环内所有pandas单值查询、冗余条件判断,10万条数据处理耗时可从15分钟压缩到10秒级,具体耗时和单客户平均迭代年数相关

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 13:12:24