Search code examples
pythonpandassortingranktop-n

How to find column-index of top-n values within each row of huge dataframe


I have a dataframe of format: (example data)

      Metric1  Metric2  Metric3  Metric4  Metric5
ID    
1     0.5      0.3      0.2      0.8      0.7    
2     0.1      0.8      0.5      0.2      0.4    
3     0.3      0.1      0.7      0.4      0.2    
4     0.9      0.4      0.8      0.5      0.2    

where score range between [0,1] and I wish to generate a function that, for each id (row), calculates the top n metrics, where n is an input of the function along with the original dataframe.

My ideal output would be:(for eg. n = 3)

      Top_1     Top_2     Top_3
ID    
1     Metric4   Metric5   Metric1    
2     Metric2   Metric3   Metric5    
3     Metric3   Metric4   Metric1    
4     Metric1   Metric3   Metric4  

Now I have written a function that does work:

def top_n_partners(scores,top_n=3):
metrics = np.array(scores.columns)
records=[]
for rec in scores.to_records():
    rec = list(rec)
    ID = rec[0]
    score_vals = rec[1:]
    inds = np.argsort(score_vals)
    top_metrics = metrics[inds][::-1]
    dic = {
        'top_score_%s' % (i+1):top_metrics[i]
        for i in range(top_n)
    }
    dic['ID'] = ID
    records.append(dic)
top_n_df = pd.DataFrame(records)
top_n_df.set_index('ID',inplace=True)
return top_n_df

However it seems rather inefficient/slow especially for the volume of data I'd be running this over (dataframe with millions of rows) and I was wondering if there was a smarter way to go about this?


Solution

  • You can use numpy.argsort:

    print (np.argsort(-df.values, axis=1)[:,:3])
    [[3 4 0]
     [1 2 4]
     [2 3 0]
     [0 2 3]]
    
    print (df.columns[np.argsort(-df.values, axis=1)[:,:3]])
    
    Index([['Metric4', 'Metric5', 'Metric1'], ['Metric2', 'Metric3', 'Metric5'],
           ['Metric3', 'Metric4', 'Metric1'], ['Metric1', 'Metric3', 'Metric4']],
          dtype='object')
    
    df = pd.DataFrame(df.columns[np.argsort(-df.values, axis=1)[:,:3]], 
                                   index=df.index)
    df = df.rename(columns = lambda x: 'Top_{}'.format(x + 1))
    print (df)
          Top_1    Top_2    Top_3
    ID                           
    1   Metric4  Metric5  Metric1
    2   Metric2  Metric3  Metric5
    3   Metric3  Metric4  Metric1
    4   Metric1  Metric3  Metric4 
    

    Thank you Divakar for improving:

    n = 3
    df = pd.DataFrame(df.columns[df.values.argsort(1)[:,-n+2:1:-1]], 
                                   index=df.index)
    
    df = df.rename(columns = lambda x: 'Top_{}'.format(x + 1))
    print (df)
          Top_1    Top_2    Top_3
    ID                           
    1   Metric4  Metric5  Metric1
    2   Metric2  Metric3  Metric5
    3   Metric3  Metric4  Metric1
    4   Metric1  Metric3  Metric4