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

如何有效地将argsort pandas数据帧分列

  •  7
  • jpp  · 技术社区  · 6 年前

    我想根据每行最大的3个值,用列标签替换值。让我们假设这个输入:

       p1  p2  p3  p4
    0   0   9   1   4
    1   0   2   3   4
    2   1   3  10   7
    3   1   5   3   1
    4   2   3   7  10
    

    鉴于 n = 3 ,我在找:

      Top1 Top2 Top3
    0   p2   p4   p3
    1   p4   p3   p2
    2   p3   p4   p2
    3   p2   p3   p1
    4   p4   p3   p2
    

    我不关心重复,例如索引 3 , Top3 可以是 'p1' 'p4' .

    尝试1

    我的第一次尝试是使用 np.ndarray.argsort :

    res = pd.DataFrame(df.columns[df.values.argsort(1)]).iloc[:, len(df.index): 0: -1]
    

    但实际上,我有4个以上的专栏,这将是低效的。

    尝试2

    接下来我尝试 np.argpartition . 但是,由于每个分区中的值没有排序,因此需要 后来的 排序:

    n = 3
    parts = np.argpartition(-df.values, n, axis=1)[:, :-1]
    args = (-df.values[np.arange(df.shape[0])[:, None], parts]).argsort(1)
    
    res = pd.DataFrame(df.columns[parts[np.arange(df.shape[0])[:, None], args]],
                       columns=[f'Top{i}' for i in range(1, n+1)])
    

    事实上,这是可行的 更慢的 比第一次尝试更大的数据帧。有没有更有效的方法可以利用部分排序?您可以使用以下代码进行基准测试。

    标杆管理

    # Python 3.6.0, NumPy 1.11.3, Pandas 0.19.2
    
    import pandas as pd, numpy as np
    
    df = pd.DataFrame({'p1': [0, 0, 1, 1, 2],
                       'p2': [9, 2, 3, 5, 3],
                       'p3': [1, 3, 10, 3, 7],
                       'p4': [4, 4, 7, 1, 10]})
    
    def full_sort(df):
        return pd.DataFrame(df.columns[df.values.argsort(1)]).iloc[:, len(df.index): 0: -1]
    
    def partial_sort(df):
        n = 3
        parts = np.argpartition(-df.values, n, axis=1)[:, :-1]
        args = (-df.values[np.arange(df.shape[0])[:, None], parts]).argsort(1)
        return pd.DataFrame(df.columns[parts[np.arange(df.shape[0])[:, None], args]])
    
    df = pd.concat([df]*10**5)
    
    %timeit full_sort(df)     # 86.3 ms per loop
    %timeit partial_sort(df)  # 158 ms per loop
    
    1 回复  |  直到 6 年前
        1
  •  4
  •   Divakar    6 年前

    有了相当数量的列,我们可以使用 np.argpartition 与一些 slicing indexing 像这样——

    def topN_perrow_colsindexed(df, N):
        # Extract array data
        a = df.values
    
        # Get top N indices per row with not necessarily sorted order
        idxtopNpart = np.argpartition(a,-N,axis=1)[:,-1:-N-1:-1]
    
        # Index into input data with those and use argsort to force sorted order
        sidx = np.take_along_axis(a,idxtopNpart,axis=1).argsort(1)
        idxtopN = np.take_along_axis(idxtopNpart,sidx[:,::-1],axis=1)    
    
        # Index into column values with those for final output
        c = df.columns.values
        return pd.DataFrame(c[idxtopN], columns=[['Top'+str(i+1) for i in range(N)]])
    

    样品运行

    In [65]: df
    Out[65]: 
       p1  p2  p3  p4
    0   0   9   1   4
    1   0   2   3   4
    2   1   3  10   7
    3   1   5   3   1
    4   2   3   7  10
    
    In [66]: topN_perrow_colsindexed(df, N=3)
    Out[66]: 
      Top1 Top2 Top3
    0   p2   p4   p3
    1   p4   p3   p2
    2   p3   p4   p2
    3   p2   p3   p4
    4   p4   p3   p2
    

    计时-

    In [143]: np.random.seed(0)
    
    In [144]: df = pd.DataFrame(np.random.rand(10000,30))
    
    In [145]: %timeit full_sort(df)
         ...: %timeit partial_sort(df)
         ...: %timeit topN_perrow_colsindexed(df,N=3)
    100 loops, best of 3: 7.96 ms per loop
    100 loops, best of 3: 13.9 ms per loop
    100 loops, best of 3: 5.47 ms per loop
    
    In [146]: df = pd.DataFrame(np.random.rand(10000,100))
    
    In [147]: %timeit full_sort(df)
         ...: %timeit partial_sort(df)
         ...: %timeit topN_perrow_colsindexed(df,N=3)
    10 loops, best of 3: 34 ms per loop
    10 loops, best of 3: 56.1 ms per loop
    100 loops, best of 3: 13.6 ms per loop