How to Sort a Dict in Descending Order by Value With Python

How to Sort a Dict in Descending Order by Value With Python

Learn the best way to perform a dict sort by value in desc order

In this post, you will learn how to sort a Python dictionary by value descending i.e. in reverse order.

Say that you have the following dictionary containing your grades associated with a subject. You want to sort the values, in this case the grades, in a descending manner - the highest grade will appear first and the lowest last.

For example, you have this:

grades = {"Math": 34, "Science": 12, "English": 89, "Physics": 8}

... And you want this:

{'English': 89, 'Math': 34:, 'Science': 12:, 'Physics': 8}

You can do this is at least 3 different ways.

Sorting a dict by value descending using list comprehension

The quickest way is to iterate over the key-value pairs of your current dict and call sorted passing the dictionary values and setting reversed=True.

If you are using Python 3.7, regular dicts are ordered by default. So let's use it!

>>> grades = {"Math": 34, "Science": 12, "English": 89, "Physics": 8}
>>> grades
{'Math': 34, 'Science': 12, 'English': 89, 'Physics': 8}
>>> value_key_pairs = ((value, key) for (key,value) in grades.items())
>>> sorted_value_key_pairs = sorted(value_key_pairs, reverse=True)
>>> sorted_value_key_pairs
[(89, 'English'), (34, 'Math'), (12, 'Science'), (8, 'Physics')]
>>> {k: v for v, k in sorted_value_key_pairs}
 {'English': 89, 'Math': 34, 'Science': 12, 'Physics': 8}

And Voila! You have your sorted grades dict in a descending fashion.

What if I have Python 3.6 or lower?

In this case, you can use OrderedDict from the collections module.

>>> from collections import OrderedDict

>>> OrderedDict((k, v) for v, k in sorted_value_key_pairs)
OrderedDict([('English', 89), ('Math', 34), ('Science', 12), ('Physics', 8)])

Sorting a dictionary in descending order using the operator module

The operator module provides a functional interface to built-in operators like <, >, == and so on.

This module has many useful functions and one of them is the itemgetter. This function returns a callable object that will fetch the item using the __getitem__() method.

In a nutshell, if you do callable = operator.itemgetter(1), and pass a subscriptable object, say ('Physics', 8) to this callable, it will return the equivalent of ('Physics', 8)[1].

For example,

>>> subject_grade_pair = ('Physics', 8)

>>> get_grade = operator.itemgetter(1)

>>> get_grade(subject_grade_pair)
8

>>> subject_grade_pair[1]
8

Now, the question is, how can we use this to sort the values?

The sorted built-in function expects not only the iterable you want to sort but also a key. This key argument is nothing more than a function of one argument that you can feed each item of the list. And that’s exactly what we need to sort our list!

>>> import operator

# remember, the grade is the second item in the subject - grade pair
>>> sort_by_grade = operator.itemgetter(1)

>>> grades = {"Math": 34, "Science": 12, "English": 89, "Physics": 8}
>>> grades
{'Math': 34, 'Science': 12, 'English': 89, 'Physics': 8}

>>> sorted_value_key_pairs = sorted(grades.items(), key=sort_by_grade, reverse=True)
>>> {k: v for v, k in sorted_value_key_pairs}
 {'English': 89, 'Math': 34, 'Science': 12, 'Physics': 8}

At this point you might wonder:

This sort_by_grade looks like a glorified lambda...

Well, good shout, this brings us to the last section.

Using lambda to sort the dictionary in descending order

So, as we saw in the previous section, operator.itemgetter returns a callable that is equivalent to calling the __getitem__() method on a subscriptable object.

This is remarkably similar to pass a lambda as a key which takes a tuple, say (“Math”, 34), and returns the second item which is the grade.

>>> sort_by_grade_lambda = lambda subject_grade_pair: subject_grade_pair[1]

>>> sorted_value_key_pairs = sorted(grades.items(), key=sort_by_grade_lambda, reverse=True)

>>> {k: v for v, k in sorted_value_key_pairs}
 {'English': 89, 'Math': 34, 'Science': 12, 'Physics': 8}

And this is how you use a lambda function to sort a dict items by value in descending order.

Sorting a dictionary with complex objects as values

So far we've only dealt with simple objects such as int. What happens if we have a complex object, such as a custom Grade object as a dictionary value?

Let's see how it works.

class Grade:
    def __init__(self, grade: int, cutoff: int = 70):
        self.grade = grade
        self.cutoff = cutoff
        self.passed = grade >= cutoff

    def __repr__(self):
        return f"<Grade(grade={self.grade}, cutoff={self.cutoff}, passed={self.passed}>"

Let's try to sort it...

>>> grades = {"Math": Grade(grade=34), "Science": Grade(grade=12), "English": Grade(grade=89), "Physics": Grade(grade=8)}
>>> grades
grades = {"Math": Grade(grade=34), "Science": Grade(grade=12), "English": Grade(grade=89), "Physics": Grade(grade=8)}

>>> value_key_pairs = ((value, key) for (key,value) in grades.items())
>>> sorted_value_key_pairs = sorted(value_key_pairs, reverse=True)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-14-0c94e26fda4a> in <module>
----> 1 sorted_value_key_pairs = sorted(value_key_pairs, reverse=True)

TypeError: '<' not supported between instances of 'Grade' and 'Grade'

Oops! It didn't work. The reason is that, as the error message says, Grade doesn't implement the __lt__ operator, which makes it impossible to compare them.

To fix that we can either implement the __lt__ method or use the lambda as we used before with an adaptation. Let's see the lambda approach first.

>>> sort_by_grade_lambda = lambda subject_grade_pair: subject_grade_pair[1].grade

>>> sorted_value_key_pairs = sorted(grades.items(), key=sort_by_grade_lambda, reverse=True)

>>> {k: v for v, k in sorted_value_key_pairs}
{<Grade(grade=89, cutoff=70, passed=True>: 'English',
 <Grade(grade=34, cutoff=70, passed=False>: 'Math',
 <Grade(grade=12, cutoff=70, passed=False>: 'Science',
 <Grade(grade=8, cutoff=70, passed=False>: 'Physics'}

Implementing __lt__

Let's see how it looks when we implement the < operator.

class Grade:
    def __init__(self, grade: int, cutoff: int = 70):
        self.grade = grade
        self.cutoff = cutoff
        self.passed = grade >= cutoff

    def __lt__(self, other: "Grade") -> bool:
        return self.grade < other.grade

    def __repr__(self):
        return f"<Grade(grade={self.grade}, cutoff={self.cutoff}, passed={self.passed}>"

>>> grades = {"Math": Grade(grade=34), "Science": Grade(grade=12), "English": Grade(grade=89), "Physics": Grade(grade=8)}

>>> grades
{'Math': <Grade(grade=34, cutoff=70, passed=False>,
 'Science': <Grade(grade=12, cutoff=70, passed=False>,
 'English': <Grade(grade=89, cutoff=70, passed=True>,
 'Physics': <Grade(grade=8, cutoff=70, passed=False>}

>>> value_key_pairs = ((value, key) for (key,value) in grades.items())

>>> sorted_value_key_pairs = sorted(value_key_pairs, reverse=True)

>>> sorted_value_key_pairs
[(<Grade(grade=89, cutoff=70, passed=True>, 'English'),
 (<Grade(grade=34, cutoff=70, passed=False>, 'Math'),
 (<Grade(grade=12, cutoff=70, passed=False>, 'Science'),
 (<Grade(grade=8, cutoff=70, passed=False>, 'Physics')]

>>> {k: v for v, k in sorted_value_key_pairs}
{'English': <Grade(grade=89, cutoff=70, passed=True>,
 'Math': <Grade(grade=34, cutoff=70, passed=False>,
 'Science': <Grade(grade=12, cutoff=70, passed=False>,
 'Physics': <Grade(grade=8, cutoff=70, passed=False>}

And Voila! You have your grades dictionary sorted by value in a descending way.

Conclusion

In this post we saw 3 different ways of sorting a dictionary in descending order with Python. I hope you find it useful.

Other posts you may like:

See you next time!