【问题标题】:How improve speed/memory of logic when obtaining the unique record of a cross joined table based on the dates of two different columns?根据两个不同列的日期获取交叉连接表的唯一记录时,如何提高逻辑速度/内存?
【发布时间】:2021-08-22 12:36:44
【问题描述】:

我之前问过如何根据两个不同列的日期获取交叉连接表的唯一记录?这里:Previous Question, Context and Answer

虽然提供的答案有效:

import numpy as np

# df - your DataFrame

group = df.groupby(['P_CLIENT_ID', 'P_DATE_ENCOUNTER'])

def foo(df):
    result = df.loc[df.P_DATE_ENCOUNTER>df.R_DATE_TESTED, ['R_DATE_TESTED', 'R_RESULT']].tail(1).reset_index()
    if not result.empty:
        return result
    else:
        return pd.DataFrame([[np.nan, np.nan, np.nan]], columns=['RECORD_ID','R_DATE_TESTED', 'R_RESULT'])


group.apply(foo)

我的实际数据框有 +- 150 万行,这个 .apply 需要两个小时,并且在笔记本上使用了大量内存,最终崩溃。应用相同的逻辑是否有内存效率?我想也许使用dense-ranknp.where

【问题讨论】:

    标签: python pandas apply where-clause rank


    【解决方案1】:

    您可以通过使用所有矢量化的 Pandas 操作(Pandas 内置函数)来加速这个过程,这些操作已经被 Pandas 优化为快速运行,而不是使用 .apply() 和 Pandas 无法优化和运行缓慢的自定义函数。

    我们首先将R_DATE_TESTEDR_RESULT 的值替换为NaN 的值Series.where()。然后,使用groupby() + last() 得到所需的聚合条目,如下:

    1. 如果 R_DATE_TESTED R_DATE_TESTED 和 R_RESULT 的值替换为 NaN,由 Series.where() 使用:
    df['R_DATE_TESTED'] = df['R_DATE_TESTED'].where(df['R_DATE_TESTED'] < df['P_DATE_ENCOUNTER'])
    df['R_RESULT'] = df['R_RESULT'].where(df['R_DATE_TESTED'] < df['P_DATE_ENCOUNTER'])
    
    1. 使用groupby() + last() 获取每个组中的最新(和非NaN)条目:
    df.groupby(['P_CLIENT_ID', 'P_DATE_ENCOUNTER'], as_index=False).last()
    

    输出:

       P_CLIENT_ID P_DATE_ENCOUNTER  RECORD_ID  R_CLIENT_ID R_DATE_TESTED  R_RESULT
    0        25835       2016-12-21     302956      25835.0          None       NaN
    1        25835       2017-02-21     302963      25835.0          None       NaN
    2        25835       2017-04-25     302970      25835.0    2017-03-07      20.0
    3        25835       2017-06-21     302977      25835.0    2017-03-07      20.0
    4        25835       2017-09-04     302984      25835.0    2017-08-03      20.0
    5        25835       2018-01-08     302991      25835.0    2017-08-03      20.0
    6        25835       2018-04-03     302998      25835.0    2018-03-23      20.0
    7        25835       2018-07-25     303005      25835.0    2018-03-23      20.0
    

    执行时间基准测试

    1.样本数据大小:56 行

    旧解决方案:

    %%timeit
    group = df.groupby(['P_CLIENT_ID', 'P_DATE_ENCOUNTER'])
    
    def foo(df):
        result = df.loc[df.P_DATE_ENCOUNTER>df.R_DATE_TESTED, ['R_DATE_TESTED', 'R_RESULT']].tail(1).reset_index()
        if not result.empty:
            return result
        else:
            return pd.DataFrame([[np.nan, np.nan, np.nan]], columns=['RECORD_ID','R_DATE_TESTED', 'R_RESULT'])
    
    
    group.apply(foo)
    
    
    16.5 ms ± 94.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    新解决方案:

    %%timeit
    df['R_DATE_TESTED'] = df['R_DATE_TESTED'].where(df['R_DATE_TESTED'] < df['P_DATE_ENCOUNTER'])
    df['R_RESULT'] = df['R_RESULT'].where(df['R_DATE_TESTED'] < df['P_DATE_ENCOUNTER'])
    df.groupby(['P_CLIENT_ID', 'P_DATE_ENCOUNTER'], as_index=False).last()
    
    4.24 ms ± 146 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    样本数据大小为 56 行,新解决方案的速度提高了近 4 倍(16.5ms 减少到 4.24ms)

    2。扩大后的数据量:168 万行

    df2 = pd.concat([df] * 30000, ignore_index=True)
    
    df2.shape
    (1680000, 6)       # 1.68 million rows
    

    新解决方案:

    %%timeit
    df2['R_DATE_TESTED'] = df2['R_DATE_TESTED'].where(df2['R_DATE_TESTED'] < df2['P_DATE_ENCOUNTER'])
    df2['R_RESULT'] = df2['R_RESULT'].where(df2['R_DATE_TESTED'] < df2['P_DATE_ENCOUNTER'])
    df2.groupby(['P_CLIENT_ID', 'P_DATE_ENCOUNTER'], as_index=False).last()
    
    473 ms ± 7.37 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    新解决方案的处理时间不到 1 秒。

    由于放大的数据集大部分是重复数据,因此运行时间可能与真实数据的情况不成线性比例。无论如何,这表明新解决方案的运行时间对于大型数据集仍然是合理的。

    【讨论】:

      猜你喜欢
      • 2021-10-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-21
      • 1970-01-01
      • 2019-10-08
      • 1970-01-01
      • 2014-06-13
      相关资源
      最近更新 更多