Search code examples
pythonarrayspython-3.xranking

Python ranking without skipping number (dense rank)


Using python 3.x I would like to achieve dense rank(do not skip number if the ranks are repeated ). I have the below array sorted according to score

rank_list = [{'Id': 236966, 'score': 91.0}, {'Id': 237241, 'score': 82.0}, {'Id': 237077, 'score': 79.0}, {'Id': 237084, 'score': 78.0}, {'Id': 237080, 'score': 72.0}, {'Id': 237236, 'score': 71.0}, {'Id': 236979, 'score': 71.0}, {'Id': 236909, 'score': 67.0}, {'Id': 237174, 'score': 67.0}, {'Id': 237035, 'score': 66.0}] 

I used the below code to calculate and assign the 'rank' field but the code skips a rank if there is a rank repeated

def rankFunc(e):
    return e['score']

rank_list.sort(key = rankFunc, reverse=True)
sorted_scores = [obj['score'] for obj in rank_list]
ranks = [sorted_scores.index(x) for x in sorted_scores]
for index, obj in enumerate(rank_list):
    obj['rank'] = ranks[index]+1

the current output:

rank = [1, 2, 3, 4, 5, 6, 6, 8, 8, 10]

I would like ranks to be assigned without skipping any number like below,

rank = [1, 2, 3, 4, 5, 6, 6, 7, 7, 8]


Solution

  • This little helper function should solve your problem:

    def rank_unique(x, **kwargs):
        sx = sorted(set(x), **kwargs)
        invsx = {s: i for i, s in enumerate(sx)}
        return [1 + invsx[v] for v in x]
    
    >>> rank_unique([r["score"] for r in rank_list], reverse=True)
    [1, 2, 3, 4, 5, 6, 6, 7, 7, 8]