2

Good morning, please I have a table that I'm trying to return the positions (ranks) of students scores. I have searched everywhere and I'm not getting anything better. I thought I could do it with sorting but that's not the case. This is the table:

Name       Score    Position 

David      89        3rd
James      56        5th
Matt       72        4th
John       91        1st
Iva        56        5th
Anna       91        1st

I tried writing this code but it's not taking care of the duplicates

score = [89, 56, 72, 91, 56,91]
order = sorted(list(set(score)), reverse=True)
position = []

for n in score:
     position.append(order.index(n) + 1)

print(position)
AKX
  • 152,115
  • 15
  • 115
  • 172
  • What's the output you expect? Have you tried printing `order` and see what you get before that for loop? – solarc Mar 11 '20 at 13:30
  • Printing order prints the sorted list but I only want the position(rank). – billstone09 Mar 11 '20 at 13:35
  • So what you want is `[1, 3, 2, 0, 3, 0]`? – solarc Mar 11 '20 at 13:37
  • More likely `[2,4,3,0,4,0]` given the positions in the question. – Holloway Mar 11 '20 at 13:39
  • If two people are tied for 1st position, is the next person in the 2nd position or 3rd? – solarc Mar 11 '20 at 13:42
  • From the question `David 89 3rd` I'm guessing 3rd. – Holloway Mar 11 '20 at 13:43
  • @billstone09 What you describe in your question is called *Competition Ranking*, not *Ordinal Ranking* (see [Wikipedia](https://en.wikipedia.org/wiki/Ranking)). Ordinal ranking requires each element to have a distinct rank, i.e. no shared ranks. – a_guest Mar 11 '20 at 14:25
  • Almost duplicate of [Efficient method to calculate the rank vector of a list in Python - Stack Overflow](https://stackoverflow.com/questions/3071415/efficient-method-to-calculate-the-rank-vector-of-a-list-in-python) -- except that the method here is `min` instead of `average`. – user202729 Jan 11 '21 at 10:41

4 Answers4

2

If you're willing to use an external library, I can only recommend ranking:

import ranking

# Each player and their scores.
score_list = [
    ('David', 89),
    ('James', 56),
    ('Matt', 72),
    ('John', 91),
    ('Iva', 56),
    ('Anna', 91),
]

# Helper for accessing a score record's score, 
# defined here as we need it twice.
get_score = lambda pair: pair[1]

# Sort the scores in place; required by `ranking`.
score_list.sort(key=get_score, reverse=True)

# Define a format string; handy for formatting both the header and the scores.
format_str = "{player:10}   {score:<10}   {rank}"

# Print header.
print(format_str.format(player="Player", score="Score", rank="Position"))

# Rank and print.
for rank, (player, score) in ranking.Ranking(score_list, key=get_score, start=1):
    print(format_str.format(rank=rank, player=player, score=score))

outputs

Player       Score        Position
John         91           1
Anna         91           1
David        89           3
Matt         72           4
James        56           5
Iva          56           5
AKX
  • 152,115
  • 15
  • 115
  • 172
  • I thought python does not have a library for ranking. I have learned something new from you. I'm going to read about it and thanks to you – billstone09 Mar 11 '20 at 17:19
1

Not the pretiest code, but I think this done what you ask for:

from collections import Counter

score = [89, 56, 72, 91, 56, 91]

score_set = sorted(list(set(score)), reverse=True) # unique score
dico = dict((v, i + 1) for i, v in enumerate(score_set)) # position of unique score
count = Counter(score) # occurence of each score
total = 1 # indice to get the current position in the score

# update the dico by counting the number of duplicate.
for k, v in sorted(dico.items(), key=lambda x: x[1]): # sort the dico by the value (rank without duplicate)
        count_ = count[k]
        dico[k] = total
        total += count_ # take into account potential duplicate

position = [dico[e] for e in score]  # apply the dict to our original list

print(position) # et voilà
RomainL.
  • 997
  • 1
  • 10
  • 24
1

If you are looking for a simple solution without using any libraries:

score = [89, 56, 72, 91, 56, 91]

sorted_score = sorted(score, reverse=True)
position = [1]
pos = 1
for i in range(1, len(score)):
    if sorted_score[i] < sorted_score[i-1]:
        pos = i+1
    position.append(pos)

print(position)
[1, 1, 3, 4, 5, 5]
ywbaek
  • 2,971
  • 3
  • 9
  • 28
  • All the answers given, yours is the one I really understand at my level for now. Very simple and self-explanatory to me. Thank you – billstone09 Mar 11 '20 at 17:22
1

You can use a defaultdict for storing the scores and indices in the original list in order to deal with the duplicates:

from collections import defaultdict                                                   

scores_dict = defaultdict(list)                                                      
for i, s in enumerate(scores): 
    scores_dict[s].append(i)

positions = [None] * len(scores)
rank = 1                                                                             
for s in sorted(scores_dict, reverse=True): 
    indices = scores_dict[s] 
    for i in indices: 
        positions[i] = rank 
    rank += len(indices)
a_guest
  • 34,165
  • 12
  • 64
  • 118
  • I just finished reading about types of ranking from the link you provided. I now know the difference between ordinal ranking and standard competition ranking (1224 ranking). Thank you and thanks for your code too. – billstone09 Mar 11 '20 at 17:31