Python provides the Counter class for counting objects. The Counter class
is part of the collections module and a subclass of the dict class (we
can check the same by using the issubclass function, try running issubclass(Counter, dict)
).
Counter helps us to determine frequency of objects. The following code counts the frequency of character in a string:
word = 'banana'
frequency = {}
for char in word:
# we could have also used frequency[char] = frequency.get(char, 0) + 1
if char in frequency:
frequency[char] += 1
else:
frequency[char] = 1
print(frequency)
The above code prints:
{'b': 1, 'a': 3, 'n': 2}
Excercise for readers: Can we have used defaultdict to simplify the code?
The Counter class helps us to simplify this counting operation.
Creating a Counter Object
Counter can be created:
- from an iterable (for example, list, string, tuple)
- from another dictionary
- using keyword arguments
Examples
From an iterable:
>>> from collections import Counter
>>> counter_from_string = Counter('banana')
>>> counter_from_string
Counter({'a': 3, 'n': 2, 'b': 1})
>>> counter_from_list = Counter([1, 2, 3, 3, 2])
Counter({2: 2, 3: 2, 1: 1})
From another dictionary:
>>> from collections import Counter
>>> custom_counter = Counter({'letters': 26, 'digits': 10})
>>> custom_counter
Counter({'letters': 26, 'digits': 10})
Using keyword arguments:
>>> from collections import Counter
>>> custom_counter = Counter(letters=26, digits=10)
>>> custom_counter
Counter({'letters': 26, 'digits': 10})
elements() Method
The .elements() method returns an iterator over elements repeating each as many times as its frequency. Elements are returned in the order they were encountered. If an element’s count is less than 1, it is ignored.
>>> counter_apple = Counter('apple')
>>> for c in counter_apple.elements():
... print(c)
...
a
p
p
l
e
>>> counter_amazon = Counter('amazon')
>>> for c in counter_amazon.elements():
... print(c)
...
a
a
m
z
o
n
Accessing a Frequency
Since Counter is a subclass of dict, we can access the keys as we do in a
dict (value = my_dict[key]
).
Like a dictionary, we can also use the methods .keys(), .values(), and .items().
Example
>>> from collections import Counter
>>> frequencies = Counter('banana')
>>> frequencies['a']
3
>>> frequencies.get('a')
3
>>> for letter in frequencies:
... print(letter, frequencies[letter])
...
b 1
a 3
n 2
>>> for letter, frequency in frequencies.items():
... print(letter, frequency)
...
b 1
a 3
n 2
>>> print(frequencies.keys())
dict_keys(['b', 'a', 'n'])
>>> print(frequencies.values())
dict_values([1, 3, 2])
Note that, unlike a dict, Counter doesn’t raise a KeyError if a key is not found. Instead, it returns 0.
>>> from collections import Counter
>>> frequencies = Counter('banana')
>>> frequencies['x'] # note that, 'x' is not a character of 'banana'
0
Updating Frequency of the Objects
Counter provides a method .update() to update the count/frequency of a Counter.
Example
>>> from collections import Counter
>>> frequencies = Counter('banana')
>>> frequencies
Counter({'a': 3, 'n': 2, 'b': 1})
>> frequencies.update(Counter('apple'))
>>> frequencies
Counter({'a': 4, 'n': 2, 'p': 2, 'b': 1, 'l': 1, 'e': 1})
The method update adds the frequency from a new Counter. In the above example, frequencies of letters from “apple” gets added to the frequencies of “banana”.
We can simply assign a new counter if we want it to reflect frequecies from a different Counter:
>>> from collections import Counter
>>> frequencies = Counter('banana')
>>> frequencies
Counter({'a': 3, 'n': 2, 'b': 1})
>> frequencies = Counter('apple') # Simply assign a new Counter
>>> frequencies
Counter({'p': 2, 'a': 1, 'l': 1, 'e': 1})
Most Common Objects
The most_common method returns a list of n most common elements and their counts from the most common to least.
>>> Counter('mississippi').most_common(3)
[('i', 4), ('s', 4), ('p', 2)]
If we do not pass n or use None, it returns all elements in the counter. Elements with equal counts are printed in the order they were encountered (introduced in Python 3.7).
>>> Counter('mississippi').most_common()
[('i', 4), ('s', 4), ('p', 2), ('m', 1)]
To get the least common objects, we can slice the result of most_common.
>>> Counter('mississippi').most_common()[:-3:-1]
[('m', 1), ('p', 2)]
Counter Arithmetics
The Counter class provides various arithmetic operations. In the following examples, we will use two strings “amazon” and “amaze”. Here are their counts for easy reference.
letter | Count in “amazon” | Count in “amaze” |
---|---|---|
‘a’ | 2 | 2 |
‘m’ | 1 | 1 |
‘z’ | 1 | 1 |
‘o’ | 1 | 0 |
‘n’ | 1 | 0 |
‘e’ | 0 | 1 |
Addition
Addition adds two counter together. This is similar as the .update() method.
>>> counter_amazon = Counter('amazon')
>>> counter_amaze = Counter('amaze')
>>> counter_amazon + counter_amaze
Counter({'a': 4, 'm': 2, 'z': 2, 'o': 1, 'n': 1, 'e': 1})
>>> counter_amazon.update(counter_amaze)
>>> counter_amazon
Counter({'a': 4, 'm': 2, 'z': 2, 'o': 1, 'n': 1, 'e': 1}) # note the result is same
One difference with the .update() is that update can return negative numbers which the addition operator doesn’t.
>>> c1 = Counter(x=2, y=-3)
>>> c2 = Counter(x=1, y=-2)
>>> c1 + c2 # note that, there is no count of y
Counter({'x': 3})
>>> c1.update(c2)
>>> c1 # count of y is also returned
Counter({'x': 3, 'y': -5})
Uniary addition adds the corresponding counter to an empty counter.
>>> c = Counter(x=3, y=-2)
>>> +c
Counter({'x': 3})
Subtraction
Subtraction subtracts two counters keeping only the positive counts (>=0).
>>> counter_amazon = Counter('amazon')
>>> counter_amaze = Counter('amaze')
>>> counter_amazon - counter_amaze
Counter({'o': 1, 'n': 1})
Note that in the above example, the count ‘e’: -1 and the zero count of ‘a’, ‘m’, and ‘z’ are omitted. If we want to keep the negative count as well, we can use the .subtract() method.
>>> counter_amazon = Counter('amazon')
>>> counter_amaze = Counter('amaze')
>>> counter_amazon.subtract(counter_amaze)
>>> counter_amazon
Counter({'o': 1, 'n': 1, 'a': 0, 'm': 0, 'z': 0, 'e': -1})
Uniary subtraction subtracts the corresponding counter to an empty counter.
>>> c = Counter(x=3, y=-2)
>>> -c
Counter({'y': 2})
Intersection
Intersection returns the minimum of the corresponding counts.
>>> counter_amazon = Counter('amazon')
>>> counter_maze = Counter('maze')
>>> counter_amazon & counter_maze
Counter({'a': 1, 'm': 1, 'z': 1})
Union
Union returns the maximum of the corresponding counts.
>>> counter_amazon = Counter('amazon')
>>> counter_maze = Counter('maze')
>>> counter_amazon | counter_maze
Counter({'a': 2, 'm': 1, 'z': 1, 'o': 1, 'n': 1, 'e': 1})