首页 > 解决方案 > 慢双 for 循环

问题描述

我有一个双循环,它在一个数据集上运行,在多个匹配条件下将第一行与下一行进行比较。当满足条件时,匹配的对被添加到列表中。该代码适用于小型数据集,但对于超过 5k 行的任何内容都非常慢。

如果有人对提高性能有任何想法,我将不胜感激。

以下是代码和基础数据。

基本上我从excel导入数据。

result = []
                                
def is_match(base_row, next_row):
    return base_row['NOTIONAL'] == next_row['NOTIONAL'] and base_row['LEG'] != next_row['LEG'] and \
    base_row['REF_RATE'] == next_row['REF_RATE'] and abs(base_row['RATE'] - next_row['RATE']) < 0.15 and \
    abs((base_row['MATURITY_DATE'] - next_row['MATURITY_DATE']).days) <= 30

df_len = len(df.index)
for base_index, base_row in df.iterrows():
    for i in range(base_index + 1, df_len):
        if is_match(base_row, df.loc[i]):
            result.append((base_index, i))
    
BUSN_DATE TRADE_ID 类型 REF_RATE 速度 名义上的 成人礼
2020 年 9 月 12 日 12345_P 支付 固定的 固定的 1.50 10000000 2021 年 12 月 31 日
2020 年 9 月 12 日 12345_R 录音 漂浮 BBSW 1.25 10000000 2021 年 12 月 31 日
2020 年 9 月 12 日 12346_R 录音 固定的 固定的 1.55 10000000 27/12/2021
2020 年 9 月 12 日 12346_P 支付 漂浮 BBSW 1.30 10000000 27/12/2021
df = pd.DataFrame({'BUSN_DATE': {0: '9/12/2020', 1: '9/12/2020', 2: '9/12/2020', 3: '9/12/2020'},
 'TRADE_ID': {0: '12345_P', 1: '12345_R', 2: '12346_R', 3: '12346_P'},
 'LEG': {0: 'PAY', 1: 'REC', 2: 'REC', 3: 'PAY'},
 'TYPE': {0: 'FIXED', 1: 'FLOAT', 2: 'FIXED', 3: 'FLOAT'},
 'REF_RATE': {0: 'FIXED', 1: 'BBSW', 2: 'FIXED', 3: 'BBSW'},
 'RATE': {0: '1.50', 1: '1.25', 2: '1.55', 3: '1.30'},
 'NOTIONAL': {0: '10000000', 1: '10000000', 2: '10000000', 3: '10000000'},
 'MATURITY_DATE': {0: '31/12/2021',
  1: '31/12/2021',
  2: '27/12/2021',
  3: '27/12/2021'}})

标签: pythonpython-3.xpandas

解决方案


创建一个函数create_cross_join以使用原始 df 创建组合交叉连接数据框。

import itertools
def create_cross_join(dfn):
    dfn = dfn.reset_index()
    # create a combinations with 2 elemens
    # -> [0,1,2,3] 
    # -> [(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]
    df_combine_idx = pd.DataFrame(itertools.combinations(dfn.index, 2), columns = ['a', 'b'])
    dfa = dfn.loc[df_combine_idx['a']].reset_index(drop=True)
    dfb = dfn.loc[df_combine_idx['b']].reset_index(drop=True).add_suffix('_2')
    dfab = pd.concat([dfa, dfb], axis = 1)
    return dfab

# convert float type and datetime type
df['RATE'] = df['RATE'].astype(float)
for col in ['BUSN_DATE', 'MATURITY_DATE']:
    df[col] = pd.to_datetime(df[col], format='%d/%m/%Y')

# refer columns   
cols = ['NOTIONAL', 'REF_RATE', 'LEG', 'RATE', 'MATURITY_DATE']        


print(create_cross_join(df[cols]))

    |    |   index |   NOTIONAL | REF_RATE   | LEG   |   RATE | MATURITY_DATE       |   index_2 |   NOTIONAL_2 | REF_RATE_2   | LEG_2   |   RATE_2 | MATURITY_DATE_2     |
    |---:|--------:|-----------:|:-----------|:------|-------:|:--------------------|----------:|-------------:|:-------------|:--------|---------:|:--------------------|
    |  0 |       0 |   10000000 | FIXED      | PAY   |   1.5  | 2021-12-31 00:00:00 |         1 |     10000000 | BBSW         | REC     |     1.25 | 2021-12-31 00:00:00 |
    |  1 |       0 |   10000000 | FIXED      | PAY   |   1.5  | 2021-12-31 00:00:00 |         2 |     10000000 | FIXED        | REC     |     1.55 | 2021-12-27 00:00:00 |
    |  2 |       0 |   10000000 | FIXED      | PAY   |   1.5  | 2021-12-31 00:00:00 |         3 |     10000000 | BBSW         | PAY     |     1.3  | 2021-12-27 00:00:00 |
    |  3 |       1 |   10000000 | BBSW       | REC   |   1.25 | 2021-12-31 00:00:00 |         2 |     10000000 | FIXED        | REC     |     1.55 | 2021-12-27 00:00:00 |
    |  4 |       1 |   10000000 | BBSW       | REC   |   1.25 | 2021-12-31 00:00:00 |         3 |     10000000 | BBSW         | PAY     |     1.3  | 2021-12-27 00:00:00 |
    |  5 |       2 |   10000000 | FIXED      | REC   |   1.55 | 2021-12-27 00:00:00 |         3 |     10000000 | BBSW         | PAY     |     1.3  | 2021-12-27 00:00:00 |

对相同的 NOTIONAL 和 REF_RATE 使用 group by 'NOTIONAL', 'REF_RATE',并减少交叉连接大小。

def main(df):
    result_list = []
    for (NOTIONAL, REF_RATE), group in df.groupby(['NOTIONAL', 'REF_RATE']):
        print(f'handle <NOTIONAL:{NOTIONAL}, REF_RATE:{REF_RATE}>')
        col_cond = ['LEG', 'RATE', 'MATURITY_DATE']
        dfm = create_cross_join(group[col_cond])
        cond = True
        cond &= dfm['LEG'] != dfm['LEG_2']
        cond &= abs(dfm['RATE'] - dfm['RATE_2']) < 0.15    
        cond &= abs((dfm['MATURITY_DATE'] - dfm['MATURITY_DATE_2']).dt.days) <= 30
        match_list = dfm.loc[cond, ['index', 'index_2']].values.tolist()
        if len(match_list) > 0:
            result_list.extend(match_list)
    return (result_list) 
result_list = main(df)

速度测试:

data = [{'BUSN_DATE': '9/12/2020', 'TRADE_ID': '12345_P', 'LEG': 'PAY', 'TYPE': 'FIXED', 'REF_RATE': 'FIXED', 'RATE': 1.5, 'NOTIONAL': 10000000, 'MATURITY_DATE': '31/12/2021'},
        {'BUSN_DATE': '9/12/2020', 'TRADE_ID': '12345_R', 'LEG': 'REC', 'TYPE': 'FLOAT', 'REF_RATE': 'BBSW', 'RATE': 1.25, 'NOTIONAL': 10000000, 'MATURITY_DATE': '31/12/2021'},
        {'BUSN_DATE': '9/12/2020', 'TRADE_ID': '12346_R', 'LEG': 'REC', 'TYPE': 'FIXED', 'REF_RATE': 'FIXED', 'RATE': 1.55, 'NOTIONAL': 10000000, 'MATURITY_DATE': '27/12/2021'}, 
        {'BUSN_DATE': '9/12/2020', 'TRADE_ID': '12346_P', 'LEG': 'PAY', 'TYPE': 'FLOAT', 'REF_RATE': 'BBSW', 'RATE': 1.3, 'NOTIONAL': 10000000, 'MATURITY_DATE': '27/12/2021'}]

df = pd.DataFrame(data*2500)

df['RATE'] = df['RATE'].astype(float)
for col in ['BUSN_DATE', 'MATURITY_DATE']:
    df[col] = pd.to_datetime(df[col], format='%d/%m/%Y')
    
cols = ['NOTIONAL', 'REF_RATE', 'LEG', 'RATE', 'MATURITY_DATE']    
print(df.shape)
result_list = main(df)
# 10k rows -> 46s -> 12500000

结果:

result_list[:10]

    [[1, 3],
     [1, 7],
     [1, 11],
     [1, 15],
     [1, 19],
     [1, 23],
     [1, 27],
     [1, 31],
     [1, 35],
     [1, 39]]

推荐阅读