代码之家  ›  专栏  ›  技术社区  ›  Martin Thoma

为什么熊猫比我的慢?

  •  6
  • Martin Thoma  · 技术社区  · 7 年前

    我有一个数据框

                ID  CAT    SCORE
    0            0    0  8325804
    1            0    1  1484405
    ...        ...  ...      ...
    1999980  99999    0  4614037
    1999981  99999    1  1818470
    

    我将数据分组的位置 ID 想知道每个ID中得分最高的两个类别。我可以看到两种解决方案:

    df2 = df.groupby('ID').apply(lambda g: g.nlargest(2, columns='SCORE'))
    

    或者手动将其转换为元组列表,对元组进行排序,删除除两个之外的每个ID,然后转换回数据帧。第一个应该比第二个快得多,但我观察到手动解决方案要快得多。

    为什么手动比熊猫解决方案更快?

    MVCE

    import numpy as np
    import pandas as pd
    import time
    
    
    def create_df(n=10**5, categories=20):
        np.random.seed(0)
        df = pd.DataFrame({'ID': [id_ for id_ in range(n) for c in range(categories)],
                           'CAT': [c for id_ in range(n) for c in range(categories)],
                           'SCORE': np.random.randint(10**7, size=n * categories)})
        return df
    
    
    def are_dfs_equal(df1, df2):
        columns = sorted(df1.columns)
        if len(df1.columns) != len(df2.columns):
            return False
        elif not all(el1 == el2 for el1, el2 in zip(columns, sorted(df2.columns))):
            return False
        df1_list = [tuple(x) for x in df1[columns].values]
        df1_list = sorted(df1_list, reverse=True)
        df2_list = [tuple(x) for x in df2[columns].values]
        df2_list = sorted(df2_list, reverse=True)
        is_same = df1_list == df2_list
        return is_same
    
    
    def manual_nlargest(df, n=2):
        df_list = [tuple(x) for x in df[['ID', 'SCORE', 'CAT']].values]
        df_list = sorted(df_list, reverse=True)
        l = []
        current_id = None
        current_id_count = 0
        for el in df_list:
            if el[0] != current_id:
                current_id = el[0]
                current_id_count = 1
            else:
                current_id_count += 1
            if current_id_count <= n:
                l.append(el)
        df = pd.DataFrame(l, columns=['ID', 'SCORE', 'CAT'])
        return df
    
    df = create_df()
    
    t0 = time.time()
    df2 = df.groupby('ID').apply(lambda g: g.nlargest(2, columns='SCORE'))
    t1 = time.time()
    print('nlargest solution: {:0.2f}s'.format(t1 - t0))
    
    t0 = time.time()
    df3 = manual_nlargest(df, n=2)
    t1 = time.time()
    print('manual nlargest solution: {:0.2f}s'.format(t1 - t0))
    print('is_same: {}'.format(are_dfs_equal(df2, df3)))
    

    给予

    nlargest solution: 97.76s
    manual nlargest solution: 4.62s
    is_same: True
    
    2 回复  |  直到 7 年前
        1
  •  4
  •   Lamine    7 年前

    我想你可以用这个:

    df.sort_values(by=['SCORE'],ascending=False).groupby('ID').head(2)
    

    这与在Pandas Groupby上使用Sort/Head功能的手动解决方案相同。

    t0 = time.time()
    df4 = df.sort_values(by=['SCORE'],ascending=False).groupby('ID').head(2)
    t1 = time.time()
    df4_list = [tuple(x) for x in df4[['ID', 'SCORE', 'CAT']].values]
    df4_list = sorted(df4_list, reverse=True)
    is_same = df3_list == df4_list
    print('SORT/HEAD solution: {:0.2f}s'.format(t1 - t0))
    print(is_same)
    

    给予

    SORT/HEAD solution: 0.08s
    True
    

    时计

    77.9 ms ± 7.91 ms per loop (mean ± std. dev. of 7 runs, 10 loops each).
    

    至于为什么 nlargest 是否比其他解决方案慢?,我想为每个组调用它会产生一个开销( %prun 在30.293秒内显示15764409个函数调用(15464352个基元调用)。

    对于此解决方案(1533个函数调用(1513个基元调用),0.078秒)

        2
  •  1
  •   Sander van den Oord    7 年前

    这里有一个比你的手动解决方案更快的解决方案,除非我犯了一个错误;)我想nlargest()不是解决这个问题的最快方法,如果速度是你需要的,但它是更可读的解决方案。

    t0 = time.time()
    df4 = df.sort_values(by=['ID', 'SCORE'], ascending=[True, False])
    df4['cumcount'] = df4.groupby('ID')['SCORE'].cumcount()
    df4 = df4[df4['cumcount'] < 2]
    t1 = time.time()
    print('cumcount solution: {:0.2f}s'.format(t1 - t0))