220

Other than doing list comprehensions of reversed list comprehension, is there a pythonic way to sort Counter by value? If so, it is faster than this:

>>> from collections import Counter
>>> x = Counter({'a':5, 'b':3, 'c':7})
>>> sorted(x)
['a', 'b', 'c']
>>> sorted(x.items())
[('a', 5), ('b', 3), ('c', 7)]
>>> [(l,k) for k,l in sorted([(j,i) for i,j in x.items()])]
[('b', 3), ('a', 5), ('c', 7)]
>>> [(l,k) for k,l in sorted([(j,i) for i,j in x.items()], reverse=True)]
[('c', 7), ('a', 5), ('b', 3)
alvas
  • 115,346
  • 109
  • 446
  • 738

4 Answers4

406

Use the Counter.most_common() method, it'll sort the items for you:

>>> from collections import Counter
>>> x = Counter({'a':5, 'b':3, 'c':7})
>>> x.most_common()
[('c', 7), ('a', 5), ('b', 3)]

It'll do so in the most efficient manner possible; if you ask for a Top N instead of all values, a heapq is used instead of a straight sort:

>>> x.most_common(1)
[('c', 7)]

Outside of counters, sorting can always be adjusted based on a key function; .sort() and sorted() both take callable that lets you specify a value on which to sort the input sequence; sorted(x, key=x.get, reverse=True) would give you the same sorting as x.most_common(), but only return the keys, for example:

>>> sorted(x, key=x.get, reverse=True)
['c', 'a', 'b']

or you can sort on only the value given (key, value) pairs:

>>> sorted(x.items(), key=lambda pair: pair[1], reverse=True)
[('c', 7), ('a', 5), ('b', 3)]

See the Python sorting howto for more information.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
53

A rather nice addition to @MartijnPieters answer is to get back a dictionary sorted by occurrence since Collections.most_common only returns a tuple. I often couple this with a json output for handy log files:

from collections import Counter, OrderedDict

x = Counter({'a':5, 'b':3, 'c':7})
y = OrderedDict(x.most_common())

With the output:

OrderedDict([('c', 7), ('a', 5), ('b', 3)])
{
  "c": 7, 
  "a": 5, 
  "b": 3
}
Community
  • 1
  • 1
Hooked
  • 84,485
  • 43
  • 192
  • 261
  • 17
    Starting from Python 3.7 (3.6 for CPython) there is no need for `OrderedDict` any more, because `dict` now keeps the insertion order. So it's simply `y = dict(x.most_common())` – Walter Tross Dec 30 '20 at 22:41
  • 1
    @WalterTross Just to be clear, even in Python 3.7+, `OrderedDict` offers functionality that `dict` doesn't, especially regarding comparisons. For example, `OrderedDict([('a', 1), ('b', 2)]) == OrderedDict([('b', 2), ('a', 1)])` evaluates to False, whereas `{'a': 1, 'b': 2} == {'b': 2, 'a': 1}` evaluates to True. – Flimm Mar 04 '22 at 20:52
  • OrderedDict isn't the slightest bit more useful here compared to a plain dict. That includes the == check. Here the non-ordered dict does the right thing. Insertion order is that's desired in this instance. Sometimes yes, you want the order to change the result of the comparison. I don't think the comment you replied to suggested OrderedDict wasn't needed for *anything* just that it wasn't needed here, and it's not. BTW JSON is effectively insertion-ordered even though it isn't in the spec. – Benjamin Atkin Apr 13 '23 at 19:20
24

Yes:

>>> from collections import Counter
>>> x = Counter({'a':5, 'b':3, 'c':7})

Using the sorted keyword key and a lambda function:

>>> sorted(x.items(), key=lambda i: i[1])
[('b', 3), ('a', 5), ('c', 7)]
>>> sorted(x.items(), key=lambda i: i[1], reverse=True)
[('c', 7), ('a', 5), ('b', 3)]

This works for all dictionaries. However Counter has a special function which already gives you the sorted items (from most frequent, to least frequent). It's called most_common():

>>> x.most_common()
[('c', 7), ('a', 5), ('b', 3)]
>>> list(reversed(x.most_common()))  # in order of least to most
[('b', 3), ('a', 5), ('c', 7)]

You can also specify how many items you want to see:

>>> x.most_common(2)  # specify number you want
[('c', 7), ('a', 5)]
Inbar Rose
  • 41,843
  • 24
  • 85
  • 131
  • Another way to reverse sort is to set the key function to `lamda i: -i[1]` – Steinar Lima Jan 06 '14 at 14:09
  • I had forgotten the `.items()` which then gave me `TypeError: bad operand type for unary -: 'str'`. It is just that you need the `items()` to make it read as a pair so that `k[1]` is found as the second item of each pair which can be reverse sorted with `-k[1]` because it is a number. You would not be able to do `-k[0]` since `k[0]` is a string. – questionto42 Oct 01 '21 at 22:04
10

More general sorted, where the key keyword defines the sorting method, minus before numerical type indicates descending:

>>> x = Counter({'a':5, 'b':3, 'c':7})
>>> sorted(x.items(), key=lambda k: -k[1])  # Ascending
[('c', 7), ('a', 5), ('b', 3)]
Artem
  • 3,304
  • 3
  • 18
  • 41
Alex Seam
  • 101
  • 1
  • 3