【collections.Counter】Pythonでリストや文字列の要素の出現回数を調べる方法
この記事では、collections.Counterの基本的な使い方を紹介しています。特徴を分かりやすく説明するためのコード例もつけているので、参考にしながら機能を確認してください。
本記事は、こちらのドキュメントを参考にしているため、pythonのバージョンによっては挙動が異なる場合があります。バージョンによる違いも説明していますが、最新のバージョンをカバーできていない可能性があります。その場合は、ドキュメントを参照してください。
collections.Counterとは
collections.Counterは、Pythonのモジュールであるcollectionsに含まれるクラスです。Counterクラスを使用することで、リストや文字列、辞書、タプル、セットなどのデータを要素数や出現回数をカウントすることができます。Counterクラスは、カウント処理を効率的に行うことができるため、データの集計や統計処理を行う際によく使用されます。また、Counterクラスを使用することで、複数のデータを統合してカウントすることもできます。
collections.Counterの引数
collections — Counter コンテナデータ型 — Python 3.11.0b5 ドキュメント
collections.Counterの引数は、iterable-or-mapping
を指定することができます。具体的には以下のようなデータ構造を指定することができます。
- リスト:
[1, 2, 3, 1, 2, 1, 2, 3]
- 文字列:
'abcdeabcde'
- 辞書:
{'a': 3, 'b': 2, 'c': 1}
- タプル:
('a', 'b', 'c', 'a', 'b')
- 集合:
set(['a', 'b', 'c', 'a', 'b'])
また、次のような指定の仕方もあります。
from collections import Counter
c = Counter(boys=4, girls=8)
print(c) # Counter({'girls': 8, 'boys': 4})
上記のようなデータを引数として指定することで、Counterクラスを使用して、要素数や出現回数をカウントすることができます。
引数を省略した場合
引数を省略することで、空のCounterを作成することもできます。この場合、要素数や出現回数をカウントするためには、後述するupdate()メソッドや、演算子を使用して、データを追加していく必要があります。
from collections import Counter
# 空のCounterを作成
c = Counter()
print(c) # Counter()
collections.Counterの仕様
collections.Counter
は、存在しない要素を指定した場合の挙動が辞書の場合の挙動と異なります。具体的には、次のような違いがあります。
Counter
の場合:0を返します- 辞書の場合:KeyErrorが発生します
from collections import Counter
c = Counter({'a': 3, 'b': 2, 'c': 1})
# 存在するkeyを指定
print(c['a']) # 3
# 存在しないkeyを指定
print(c['d']) # 0
d = {'a': 1, 'b': 2, 'c': 3}
# 存在するkeyを指定
print(d['a']) # 1
# 存在しないkeyを指定
print(d['d']) # KeyError: 'd'
上記のコードからそれぞれの挙動が分かると思います。
pythonのバージョンによる変更点
pythonのバージョンによって機能や仕様が変更されているものがあります。
要素の順序は保持する?
要素の順序を保持するかどうかはバージョンによって変わるとドキュメントには書いています。
・バージョン3.7~
要素の順番を保持する
・~バージョン3.6
要素の順番を保持するかは分からない
「ドキュメントには書いています」と書いている通り、私が試したところ3.6においても要素の順序を保持しているようでした。
以下は要素の順序を確かめるコード例です。
from collections import Counter
# Counter オブジェクトの生成
c1 = Counter(['a', 'b', 'c', 'b', 'b', 'b'])
c2 = Counter(['e', 'd', 'f'])
# Counter オブジェクトの挿入順序を保持することを確認
print(list(c1 + c2)) # ['a', 'b', 'c', 'e', 'd', 'f']
print(list(c2 + c1)) # ['e', 'd', 'f', 'a', 'b', 'c']
バージョン3.6, 3.10において同じ結果となりました。要素の順序が関係ある処理をする場合は、3.7以降を使うことが無難だと思います。
Counter(a=3)とCounter(a=3, b=0)は等しい?
Counter(a=3) と Counter(a=3, b=0) が等しいものとみなされるかどうかがバージョンによって変わります。
・バージョン3.10~
等しいものとして扱われる
・~バージョン3.9
異なるものとして扱われる
バージョン3.10~
from collections import Counter
# Counter オブジェクトの生成
c1 = Counter(['a', 'b', 'c', 'a', 'b', 'b'])
c2 = Counter(['a', 'b', 'c', 'a', 'b', 'b'])
c3 = Counter(['a', 'b', 'c', 'a', 'b', 'b'])
# c1 のカウントを更新
c1.update({'d': 0})
print(c1) # Counter({'b': 3, 'a': 2, 'c': 1, 'd': 0})
# c2 のカウントを更新
c2.update({'d': 0})
print(c2) # Counter({'b': 3, 'a': 2, 'c': 1, 'd': 0})
# c3 のカウントを更新
c3.update({'d': 0, 'e': 0})
print(c3) # Counter({'b': 3, 'a': 2, 'c': 1, 'd': 0, 'e': 0})
# c1 と c2 の等値性を検定
print(c1 == c2) # True
# c1 と c3 の等値性を検定
print(c1 == c3) # True
c1とc3が等しいものとされていることがわかりますね。
~バージョン3.9
from collections import Counter
# Counter オブジェクトの生成
c1 = Counter(['a', 'b', 'c', 'a', 'b', 'b'])
c2 = Counter(['a', 'b', 'c', 'a', 'b', 'b'])
c3 = Counter(['a', 'b', 'c', 'a', 'b', 'b'])
# c1 のカウントを更新
c1.update({'d': 0})
print(c1) # Counter({'b': 3, 'a': 2, 'c': 1, 'd': 0})
# c2 のカウントを更新
c2.update({'d': 0})
print(c2) # Counter({'b': 3, 'a': 2, 'c': 1, 'd': 0})
# c3 のカウントを更新
c3.update({'d': 0, 'e': 0})
print(c3) # Counter({'b': 3, 'a': 2, 'c': 1, 'd': 0, 'e': 0})
# c1 と c2 の等値性を検定
print(c1 == c2) # True
# c1 と c3 の等値性を検定
print(c1 == c3) # False
c1とc3が異なるものとされていることがわかりますね。
比較演算子を使える?
Counterに対して比較演算子が使えるかどうかが変わります。
・バージョン3.10~
比較演算子が使える
・~バージョン3.9
比較演算子が使えない
バージョン3.10~
from collections import Counter
# Counter オブジェクトを作成する
counter1 = Counter({'apple': 3, 'banana': 2, 'orange': 1})
counter2 = Counter({'apple': 1, 'banana': 2, 'orange': 3})
counter3 = Counter({'apple': 3, 'banana': 2, 'orange': 1, 'grape': 1})
# 比較演算子を使って、Counter オブジェクトを比較する
print(counter1 <= counter2) # False
print(counter1 <= counter3) # True
print(counter2 < counter3) # False
比較演算子を使えることが分かります。
~バージョン3.9
from collections import Counter
# Counter オブジェクトを作成する
counter1 = Counter({'apple': 3, 'banana': 2, 'orange': 1})
counter2 = Counter({'apple': 1, 'banana': 2, 'orange': 3})
counter3 = Counter({'apple': 3, 'banana': 2, 'orange': 1, 'grape': 1})
# 比較演算子を使って、Counter オブジェクトを比較する
print(counter1 == counter2) # False
print(counter1 <= counter3) # TypeError: '<=' not supported between instances of 'Counter' and 'Counter'
==
は使えるみたいですが、その他の比較演算子を使うとエラーが出ます。
dict型とは異なる挙動のメソッド
Counterオブジェクトは、通常の辞書型のメソッドを利用できますが、以下の 2 つのメソッドが例外です。
fromkeys(iterable)
update([iterable-or-mapping])
fromkeys(iterable)
fromkeys
メソッドは、指定されたキーを持つ新しい辞書を作成するために、通常の辞書型 (dict
) で使用されるクラスメソッドです。このメソッドは、 Counter
クラスでは実装されていません。
# dict型の場合
# dict.fromkeys メソッドを使用して辞書を作成
keys = ['a', 'b', 'c']
my_dict = dict.fromkeys(keys, 0)
print(my_dict) # {'a': 0, 'b': 0, 'c': 0}
# Counterの場合
from collections import Counter
c = Counter(['a', 'b', 'c', 'a', 'b', 'b'])
c.fromkeys(['d', 'e', 'f'])
# NotImplementedError: Counter.fromkeys() is undefined. Use Counter(iterable) instead.
Counterの場合は、エラーが出ることが分かります。
update([iterable-or-mapping])
update
メソッドは、要素が iterable からカウントされるか、別の mapping (やカウンタ) が追加されるメソッドです。このメソッドは、通常の辞書型でもCounter
クラスでも使用できますが、挙動には違いがあります。
Counter
クラスでは、update
メソッドは、指定された mapping や iterable から要素を取得し、現在のカウントに追加します。 iterable には (key, value) 対のシーケンスではなく、要素のシーケンスが求められます。- 通常の辞書型 (
dict
) では、update
メソッドは、指定された mapping や iterable からキーと値の組を取得し、現在の辞書に追加または置き換えます。
Counterの場合
# Counterオブジェクトで update メソッドを使用
from collections import Counter
c = Counter(['a', 'b', 'c', 'a', 'b', 'b'])
print(c) # Counter({'b': 3, 'a': 2, 'c': 1})
c.update(['a', 'b', 'd'])
print(c) # Counter({'b': 4, 'a': 3, 'c': 1, 'd': 1})
# Counter オブジェクトでは、update メソッドの引数に iterable を指定できます
c.update(['e', 'f', 'g'])
print(c) # Counter({'b': 4, 'a': 3, 'c': 1, 'd': 1, 'e': 1, 'f': 1, 'g': 1})
dict型の場合
# dict型で update メソッドを使用
my_dict = {'a': 1, 'b': 2, 'c': 3}
my_dict.update({'c': 4, 'd': 5})
print(my_dict) # {'a': 1, 'b': 2, 'c': 4, 'd': 5}
# dict オブジェクトでは、update メソッドの引数に iterable を指定することはできません
try:
my_dict.update(['e', 'f', 'g'])
except ValueError as e:
print(e) # dictionary update sequence element #0 has length 1; 2 is required
collections.Counterの独自のメソッド
Counter
オブジェクトには、独自のメソッドが用意されています。以下のような独自のメソッドが利用できます。
elements()
most_common(n)
subtract([iterable-or-mapping])
total()
elements()
elements()
メソッドは、Counter オブジェクトの要素を、それぞれのカウント分だけ繰り返して、イテレータとして返します。カウントが 1 未満の要素は、無視されます。要素は挿入された順番で返されます。
以下はelements()
を使った一例です。
from collections import Counter
# Counterクラスのインスタンスを作成
c = Counter(a=-1, b=-1, c=2, d=0)
# Counterのインスタンスに格納されている要素を反復処理する
for elem in c.elements():
# 要素を出力する
print(elem)
# c
# c
カウントが0以下の要素は出力されていないことがわかります。
most_common(n)
most_common(n)
メソッドは、Counterオブジェクトの中で、最もカウントが多いn個の要素を、カウントが多いものから少ないものの順番で、リストとして返します。nが省略されるかNone
である場合は、Counter オブジェクトのすべての要素が返されます。カウントが等しい要素は、挿入された順番で並べられます。
以下は、most_common(n)
を使った一例です。
from collections import Counter
# Counterクラスのインスタンスを作成
c = Counter(a=3, b=1, c=2, d=4)
# Counterのインスタンスから、カウントが最も多い3個の要素を取得する
most_common_3 = c.most_common(3)
# 取得した要素を出力する
print(most_common_3) # [('d', 4), ('a', 3), ('c', 2)]
# Counterのインスタンスから、すべての要素を取得する
most_common_all = c.most_common()
# 取得した要素を出力する
print(most_common_all) # [('d', 4), ('a', 3), ('c', 2), ('b', 1)]
subtract([iterable-or-mapping]):バージョン 3.2 で追加
subtract([iterable-or-mapping])
メソッドは、Counter オブジェクトから、指定された counter の要素を引きます。入力も出力も、0 や負の数になることがあります。
以下は、subtract([iterable-or-mapping])
を使用した一例です。
from collections import Counter
# オブジェクトを作成する
counter1 = Counter({'apple': 3, 'banana': 2, 'orange': 1})
counter2 = Counter({'apple': 1, 'banana': 2, 'orange': 3})
# subtract() メソッドを使って、counter1からcounter2を引く
counter1.subtract(counter2)
# 結果を表示する
print(counter1) # Counter({'apple': 2, 'banana': 0, 'orange': -2})
total():バージョン 3.10 で追加
total()
メソッドは、Counterオブジェクトのすべてのカウントを計算して合計を返します。
以下は、total()
を使用した一例です。
from collections import Counter
# オブジェクトを作成する
counter = Counter({'apple': 3, 'banana': 2, 'orange': 1})
# total() メソッドを使って、すべてのカウントを計算する
total = counter.total()
# 結果を表示する
print(total) # 6
コメント