代码之家  ›  专栏  ›  技术社区  ›  Programmer

(Python)帮助加速应用lambda行

  •  -1
  • Programmer  · 技术社区  · 6 年前

    我有一些代码可以用,但需要加速

    我有一亿行的数据帧。在它里面,有一个我关心的列,包含浮点数。我还有一个10000行的第二个数据帧,包含两列float。

    >>> df_1
               col_A
    0          12.9900
    1          5.0001
    ...        ...
    100000000  6.0070
    
    
    >>> df_2
               col_B      col_C
    0          5.0000      0.19
    1          6.0080      0.43
    ...        ...         ...
    9999       13.0000     10.95
    

    我的目标是,对于A列中的每个值,在B列中搜索最接近的值,然后在df\u 1的新列中返回C列的相应值。例如,在df_1中取12.99;在col_B中最接近的值是13,因此它在新列中返回10.95。这是我写的:

    def find_nearest1(row,array):
        idx,val = min(enumerate(array['colB']), key=lambda x: abs(x[1]-row['colA']))
        return array['ColC'][idx]
    
    df_1['new_col']=df_1.apply(lambda row: find_nearest1(row,df_2),axis=1)
    
    # The result is:
    >>> df_1
               col_A        new_col
    0          12.9900      10.95
    1          5.0001       0.19
    ...        ...          ...
    100000000  6.0070       0.43
    

    我的代码在小数据集上运行得很好,但在1亿行的数据集上运行速度非常慢。有什么办法能让这更快吗?

    1 回复  |  直到 6 年前
        1
  •  2
  •   Matt Messersmith    6 年前

    正如评论中所暗示的,您需要一个更好的算法。您可以进行的一个优化是排序 col_B ,这样就可以执行二进制搜索来查找最近的元素。我将这两个实现放在一个 test.py 包含1000个随机元素的文件。下面是一些引导程序代码供您自己测试( find_nearest2 "other_new_col" ).

    O(N logn)解决方案保持与原始O(N^2)解决方案相同的行为:

    import bisect
    import numpy as np
    import pandas as pd
    
    # Original                                                                                                                                                                                                  
    def find_nearest1(row,array):
        idx,val = min(enumerate(array['col_B']), key=lambda x: abs(x[1]-row['col_A']))
        return array['col_C'][idx]
    
    # Optimized                                                                                                                                                                                                 
    def find_nearest2(row,array):
        idx = bisect.bisect_left(array['col_B'].values, row['col_A'])
        arr_len = len(array['col_C'])
        if idx == 0:
            return array['col_C'].iloc[0]
        elif idx == arr_len:
            return array['col_C'].iloc[-1]
        else:
            diff1 = abs(array['col_B'].iloc[idx] - row['col_A'])
            diff2 = abs(array['col_B'].iloc[idx-1] - row['col_A'])
            m = min(diff1, diff2)
            if np.isclose(m, diff1):
                return array['col_C'].iloc[idx]
            else:
                return array['col_C'].iloc[idx-1]
    
    np.random.seed(1) # Set seed for reproducability                                                                                                                                                            
    
    size = 1000
    df1_data = np.random.random(size)
    df1_cols = ["col_A"]
    df_1 = pd.DataFrame(df1_data, columns=df1_cols)
    
    df2_data = {"col_B": np.random.random(size), "col_C": np.random.random(size)}
    df_2 = pd.DataFrame.from_dict(df2_data)
    
    if __name__ == "__main__":
        # Original                                                                                                                                                                                              
        df_1['new_col']=df_1.apply(lambda row: find_nearest1(row,df_2),axis=1)
    
        # Optimized                                                                                                                                                                                             
        df_2_sorted = df_2.sort_values('col_B')
        df_1['other_new_col'] = df_1.apply(lambda row: find_nearest2(row, df_2_sorted), axis=1)
    
        print(df_1)
    

    这将输出(因此您可以验证是否保持了原始行为):

            col_A   new_col  other_new_col
    0    0.417022  0.842518       0.842518
    1    0.720324  0.633461       0.633461
    2    0.000114  0.327524       0.327524
    3    0.302333  0.947542       0.947542
    4    0.146756  0.985317       0.985317
    5    0.092339  0.875530       0.875530
    6    0.186260  0.348727       0.348727
    7    0.345561  0.471819       0.471819
    8    0.396767  0.674607       0.674607
    9    0.538817  0.696878       0.696878
    10   0.419195  0.695152       0.695152
    ...
    990  0.812507  0.584883       0.584883
    991  0.283802  0.497448       0.497448
    992  0.527847  0.490089       0.490089
    993  0.339417  0.905808       0.905808
    994  0.554667  0.745292       0.745292
    995  0.974403  0.615726       0.615726
    996  0.311703  0.594144       0.594144
    997  0.668797  0.571321       0.571321
    998  0.325967  0.576978       0.576978
    999  0.774477  0.963032       0.963032
    

    一些性能结果:

    bash-3.2$ ipython
    Python 3.6.4 |Anaconda, Inc.| (default, Jan 16 2018, 12:04:33)
    Type 'copyright', 'credits' or 'license' for more information
    IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help.
    
    In [1]: from test import find_nearest1, find_nearest2, df_1, df_2
    
    In [2]: %timeit df_1['new_col']=df_1.apply(lambda row: find_nearest1(row,df_2),axis=1)
    6.04 s ± 13.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    
    In [3]: %timeit df_2_sorted = df_2.sort_values('col_B')
    201 µs ± 3.25 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    
    In [4]: df_2_sorted = df_2.sort_values('col_B')
    
    In [5]: %timeit df_1.apply(lambda row: find_nearest2(row, df_2_sorted), axis=1)
    111 ms ± 1.67 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

    嗯。