9

I have a python dictionary:

x = {'a':10.1,'b':2,'c':5}

How do I go about ranking and returning the rank value? Like getting back:

res = {'a':1,c':2,'b':3}

Thanks

Edit:

I am not trying to sort as that can be done via sorted function in python. I was more thinking about getting the rank values from highest to smallest...so replacing the dictionary values by their position after sorting. 1 means highest and 3 means lowest.

user1234440
  • 22,521
  • 18
  • 61
  • 103

8 Answers8

19

If I understand correctly, you can simply use sorted to get the ordering, and then enumerate to number them:

>>> x = {'a':10.1, 'b':2, 'c':5}
>>> sorted(x, key=x.get, reverse=True)
['a', 'c', 'b']
>>> {key: rank for rank, key in enumerate(sorted(x, key=x.get, reverse=True), 1)}
{'b': 3, 'c': 2, 'a': 1}

Note that this assumes that the ranks are unambiguous. If you have ties, the rank order among the tied keys will be arbitrary. It's easy to handle that too using similar methods, for example if you wanted all the tied keys to have the same rank. We have

>>> x = {'a':10.1, 'b':2, 'c': 5, 'd': 5}
>>> {key: rank for rank, key in enumerate(sorted(x, key=x.get, reverse=True), 1)}
{'a': 1, 'b': 4, 'd': 3, 'c': 2}

but

>>> r = {key: rank for rank, key in enumerate(sorted(set(x.values()), reverse=True), 1)}
>>> {k: r[v] for k,v in x.items()}
{'a': 1, 'b': 3, 'd': 2, 'c': 2}
DSM
  • 342,061
  • 65
  • 592
  • 494
  • 1
    in the last case, `b` should be `4` in my opinion because `d` and `c` are tied at position `2`. to keep the number correct `b` should be `4`. how can this be achieved? – beta Oct 02 '15 at 13:36
  • @beta: that's not really an opinion about what's right, just a different thing that you want. :-) But if you have a new question, please open a new question -- no one's going to see it in an old comment. – DSM Oct 02 '15 at 13:38
  • opinion or not, it is usually done that way. look to all sport rankings. if there are 2 people tied at the same position, the next assigned position is +1. anyhow, there may be use cases, where this is not so useful. thanks, i will open a new question, if i dont find the answer somewhere else. see also: http://stackoverflow.com/questions/23641054/adding-a-rank-to-a-dict-in-python#comment36307322_23641054 – beta Oct 02 '15 at 13:39
  • @beta: this is getting silly, but there are three different ranking schemes described in the above. You want a fourth, which is perfectly fine, but not actually a problem. – DSM Oct 02 '15 at 13:40
  • solution here: http://stackoverflow.com/questions/32908613/ranking-values-in-a-dictionary-and-taking-care-of-ex-aequos-correctly – beta Oct 02 '15 at 14:18
  • @beta - posted an answer using scipy.stats.rankdata which does what you are asking. – keithpjolley Sep 17 '19 at 13:02
2

Using scipy.stats.rankdata:

[ins] In [55]: from scipy.stats import rankdata                                                                                                                                                        

[ins] In [56]: x = {'a':10.1, 'b':2, 'c': 5, 'd': 5}                                                                                                                                                   

[ins] In [57]: dict(zip(x.keys(), rankdata([-i for i in x.values()], method='min')))                                                                                                                   
Out[57]: {'a': 1, 'b': 4, 'c': 2, 'd': 2}

[ins] In [58]: dict(zip(x.keys(), rankdata([-i for i in x.values()], method='max')))                                                                                                                   
Out[58]: {'a': 1, 'b': 4, 'c': 3, 'd': 3}

@beta, @DSM scipy.stats.rankdata has some other 'methods' for ties also that may be more appropriate to what you are wanting to do with ties.

keithpjolley
  • 2,089
  • 1
  • 17
  • 20
0

First sort by value in the dict, then assign ranks. Make sure you sort reversed, and then recreate the dict with the ranks.

from the previous answer :

import operator
x={'a':10.1,'b':2,'c':5}
sorted_x = sorted(x.items(), key=operator.itemgetter(1), reversed=True)
out_dict = {}
for idx, (key, _) in enumerate(sorted_x):
    out_dict[key] = idx + 1
print out_dict
Rcynic
  • 392
  • 3
  • 10
  • Doesn't the OP want it back in a dict? – Rcynic May 17 '15 at 02:25
  • There's really no point to that. The dictionary itself is unsorted, so putting sorted data into an unsorted data structure makes no sense to me. – Makoto May 17 '15 at 02:25
  • Yea - not for use down stream. My guess is/was that it maybe used to print to a file or JSON or something. But you're point is well taken. Unsorted => sorted => unsorted makes little sense. – Rcynic May 17 '15 at 02:27
  • Not bad overall, but DSM's answer is arguably better. You can use `enumerate(…, 1)` instead of `idx+1`. You can also easily use a dictionary comprehension: `out_dict = {idx… for }`. Keeping the original values around in `sorted_x` is a little wasteful, too (see DSM's answer for how to remove them). – Eric O. Lebigot May 17 '15 at 03:39
0

You could do like this,

>>> x = {'a':10.1,'b':2,'c':5}
>>> m = {}
>>> k = 0
>>> for i in dict(sorted(x.items(), key=lambda k: k[1], reverse=True)):
        k += 1
        m[i] = k


>>> m
{'a': 1, 'c': 2, 'b': 3}
Avinash Raj
  • 172,303
  • 28
  • 230
  • 274
  • This works, but is unnecessarily complicated in many places (see DSM's answer): (1) With your variable `k`, you are redoing manually what Python provides with `enumerate`. (2) There is no need for the explicit `int` conversion: Python knows how to compare objects directly. (3) Your `update` is always written as `m[i] = k`. – Eric O. Lebigot May 17 '15 at 03:17
0

Pretty simple sort-of simple but kind of complex one-liner.

{key[0]:1 + value for value, key in enumerate(
                       sorted(d.iteritems(),
                              key=lambda x: x[1],
                              reverse=True))}

Let me walk you through it.

  • We use enumerate to give us a natural ordering of elements, which is zero-based. Simply using enumerate(d.iteritems()) will generate a list of tuples that contain an integer, then the tuple which contains a key:value pair from the original dictionary.
  • We sort the list so that it appears in order from highest to lowest.
  • We want to treat the value as the enumerated value (that is, we want 0 to be a value for 'a' if there's only one occurrence (and I'll get to normalizing that in a bit), and so forth), and we want the key to be the actual key from the dictionary. So here, we swap the order in which we're binding the two values.
  • When it comes time to extract the actual key, it's still in tuple form - it appears as ('a', 0), so we want to only get the first element from that. key[0] accomplishes that.
  • When we want to get the actual value, we normalize the ranking of it so that it's 1-based instead of zero-based, so we add 1 to value.
Makoto
  • 104,088
  • 27
  • 192
  • 230
  • "Did Bob come in first, second, or third?" is a reasonable question to ask, and d["Bob"] == 3 provides the answer. That dictionaries aren't ordered doesn't mean that a map between key and rank couldn't be useful. – DSM May 17 '15 at 02:27
  • That's fair. I'll rethink this a bit. – Makoto May 17 '15 at 02:31
0
In [23]: from collections import OrderedDict

In [24]: mydict=dict([(j,i) for i, j in enumerate(x.keys(),1)])

In [28]: sorted_dict = sorted(mydict.items(), key=itemgetter(1))

In [29]: sorted_dict
Out[29]: [('a', 1), ('c', 2), ('b', 3)]
In [35]: OrderedDict(sorted_dict)
Out[35]: OrderedDict([('a', 1), ('c', 2), ('b', 3)])
Ajay
  • 5,267
  • 2
  • 23
  • 30
  • Isn't this a near exact copy of my answer? – Zizouz212 May 17 '15 at 02:20
  • @Zizouz212 You didn't use enumerate ,you gave an example using numbers – Ajay May 17 '15 at 02:23
  • Your 28th line is an **exact** copy of my answer, I find something strange about that. Variable names, methods, everything is the exact same. – Zizouz212 May 17 '15 at 02:24
  • This definitely does not work, except sometimes by chance: the result depends on the order in which the keys of `x` are returned, when you calculate `mydict`. – Eric O. Lebigot May 17 '15 at 03:14
  • @EOL x= {'b':10,'z':2,'d':5} I tried for this and it worked.BTW ans is based on values not keys,and i'm sorting(list of tuples) .May be you should give it one more look – Ajay May 17 '15 at 03:23
  • 1
    Again, it works _by chance_. Your answer is _not_ based on the values _in `x`_ (`x` only appears in `x.keys()`), which is required by the question, so it _cannot_ work (except, again, by chance). – Eric O. Lebigot May 17 '15 at 03:41
0

One way would be to examine the dictionary for the largest value, then remove it, while building a new dictionary:

my_dict = x = {'a':10.1,'b':2,'c':5}
i = 1
new_dict ={}
while len(my_dict) > 0:
    my_biggest_key = max(my_dict, key=my_dict.get)
    new_dict[my_biggest_key] = i
    my_dict.pop(my_biggest_key)
    i += 1
print new_dict
Abd Azrad
  • 301
  • 2
  • 10
  • This solution feels un-Pythonic and cumbersome. There are more elegant ways to accomplish what it is you're trying for without the need to resort to a while statement. – Makoto May 17 '15 at 02:24
  • 1
    So, what. Not a reason to down vote. If it works, then it's useful. – Zizouz212 May 17 '15 at 02:25
  • (I am not the original downvoter) A solution that works is useful in that it can solve the problem. However, it can even be _counterproductive_, by showing a kind of method, or a style that should be _avoided_ by any programmer if they don't want to waste 80 % of their time: DSM's answer takes two lines, so it is faster to write, it is also more legible. I would argue that it is _counterproductive_ to do things like in this answer, so I want to discourage people from using it, or they will have many, many more questions in the future, with code that is long to read and harder to understand. – Eric O. Lebigot May 17 '15 at 03:20
0

Using pandas:

import pandas as pd

x = {'a':10.1,'b':2,'c':5}
res = dict(zip(x.keys(), pd.Series(x.values()).rank().tolist()))
Mark Z.
  • 2,127
  • 16
  • 33