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 dict
s 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:
- Design Patterns That Make Sense in Python: Simple Factory
- How to Pass Multiple Arguments to a map Function in Python
- 73 Examples to Help You Master Python's f-strings
- 3 Ways to Test API Client Applications in Python
- Everything You Need to Know About Python's Namedtuples
- The Best Way to Compare Two Dictionaries in Python
- 5 Hidden Python Features You Probably Never Heard Of
See you next time!