読み込み中...

PythonのCounterを使って要素の出現回数を簡単にカウントする7つの方法

Counter Python
この記事は約18分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

●Counterとは?

Pythonでプログラミングをしていると、リストや文字列など様々なデータの中で、特定の要素がどれだけ出現しているかをカウントしたいことがありますよね。

そんな時に便利なのが、Pythonの標準ライブラリcollectionsモジュールに含まれるCounterクラスです。

Counterを使えば、データ内の要素の出現回数を簡単にカウントすることができます。

しかも、ただカウントするだけでなく、出現回数順にソートしたり、他のCounterとの演算を行ったりと、色々な操作が可能なんです。

この記事では、そんなCounterの基本的な使い方から、少し応用的な使い方までを、サンプルコードを交えながら詳しく解説していきます。

Counterを使いこなせば、Pythonでのデータ処理の幅が大きく広がるはずですよ。

それでは、まずはCounterの基本的な使い方から見ていきましょう。

○Counterの基本的な使い方

Counterの最も基本的な使い方は、コンストラクタにデータを渡すだけです。

リストや文字列、辞書などを渡すと、その中に含まれる要素の出現回数を数えてくれます。

○サンプルコード1:リストの要素をカウント

例えば、次のようにリストをCounterに渡してみましょう。

from collections import Counter

data = [1, 1, 2, 3, 3, 3, 4, 4, 4, 4]
counter = Counter(data)

print(counter)  # Counter({4: 4, 3: 3, 1: 2, 2: 1})

実行結果を見ると、dataリストに含まれる要素とその出現回数がCounterオブジェクトとして表示されました。

要素をキー、出現回数を値とする辞書のような形になっています。

例えば、1という要素は2回、3という要素は3回、4という要素は4回出現しているということがひと目でわかりますね。

○サンプルコード2:文字列の文字をカウント

同様に、文字列に対してもCounterを使ってカウントできます。

from collections import Counter

text = "abracadabra"
counter = Counter(text)

print(counter)  # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})

この例では、”abracadabra”という文字列の各文字の出現回数を数えています。

‘a’は5回、’b’と’r’はそれぞれ2回、’c’と’d’は1回ずつ出現しているのがわかります。

Counterは渡されたデータの型を問わず、その中に含まれる要素をカウントしてくれる汎用性の高いクラスなのです。

●Counterでカウントアップする方法

さて、先ほどはCounterの基本的な使い方として、データをそのままCounterに渡して要素をカウントする方法を見てきました。

でも、実際のプログラミングではデータが動的に変化することも多いですよね。

例えば、新しい要素が追加されたり、既存の要素が増えたりすることがあります。

そんな時でも大丈夫。Counterにはカウントアップするためのメソッドが用意されています。

それがupdate()メソッドです。

○サンプルコード3:要素を追加してカウント

update()メソッドを使えば、既存のCounterに新しい要素を追加したり、既存の要素のカウントを増やしたりできます。

使い方はとてもシンプルです。

from collections import Counter

counter = Counter(['a', 'b', 'c'])
print(counter)  # Counter({'a': 1, 'b': 1, 'c': 1})

counter.update(['b', 'c', 'd'])
print(counter)  # Counter({'b': 2, 'c': 2, 'a': 1, 'd': 1})

最初にCounterを['a', 'b', 'c']で初期化しています。

この時点では、各要素のカウントは1ずつですね。

次に、update()メソッドに['b', 'c', 'd']を渡しています。

すると、’b’と’c’のカウントが1から2に増え、新しい要素’d’がカウント1で追加されました。

もしupdate()に渡したiterableの中に、Counterに存在しない要素があれば、その要素が新しく追加されるわけです。

update()は、渡されたiterableの要素を1つずつCounterに追加していくイメージですね。

○サンプルコード4:複数の要素を一度にカウント

でも、update()にはもう1つ便利な使い方があります。

それは、別のCounterオブジェクトを渡すことです。

from collections import Counter

counter1 = Counter(['a', 'b', 'c'])
counter2 = Counter(['b', 'c', 'd'])

counter1.update(counter2)
print(counter1)  # Counter({'b': 2, 'c': 2, 'a': 1, 'd': 1})

counter1['a', 'b', 'c']で、counter2['b', 'c', 'd']で初期化しています。

そして、counter1.update(counter2)とすることで、counter2の要素とそのカウントをcounter1に一度に追加しています。

結果を見ると、counter1counter2の要素を取り込んで、’b’と’c’のカウントが2になり、’d’が新しく追加されていますね。

●Counterで要素をソートする

Counterを使えば、要素の出現回数を簡単にカウントできることはわかりましたね。

でも、実際のデータ分析では、単にカウントするだけでなく、出現回数の多い順や少ない順に要素をソートしたいこともあります。

そんな時に便利なのが、Counterのmost_common()メソッドです。

most_common()を使えば、Counterの要素を出現回数順にソートすることができるんです。

○most_common()メソッドの使い方

most_common()メソッドは、Counterの要素を出現回数の多い順にソートしてリストで返してくれます。

使い方はとてもシンプルで、引数には取得したい要素の数を指定します。

例えば、次のようなCounterがあるとします。

from collections import Counter

counter = Counter(['a', 'a', 'b', 'b', 'b', 'c', 'd', 'd'])

このcounterに対してmost_common()を呼び出してみましょう。

○サンプルコード5:出現回数順にソート

print(counter.most_common())
# [('b', 3), ('a', 2), ('d', 2), ('c', 1)]

print(counter.most_common(2))
# [('b', 3), ('a', 2)]

print(counter.most_common(1))
# [('b', 3)]

most_common()を引数なしで呼び出すと、全ての要素を出現回数の多い順にソートしたリストが返ってきます。

結果を見ると、’b’が3回、’a’と’d’が2回、’c’が1回の順になっていますね。

引数に数を指定すると、その数だけ上位の要素が返ってきます。

例えば、most_common(2)とすれば、上位2つの要素が返ってきますし、most_common(1)とすれば、最も出現回数の多い要素だけが返ってきます。

ちなみに、most_common()が返すリストの各要素は、(要素, 出現回数)というタプルになっています。

○サンプルコード6:出現回数の少ない順にソート

most_common()は、出現回数の多い順でソートしてくれるのはわかりました。

では、逆に出現回数の少ない順にソートしたい場合はどうすればいいでしょうか。

実は、most_common()の引数に負の数を指定することで、出現回数の少ない順にソートすることができるんです。

print(counter.most_common()[::-1])
# [('c', 1), ('d', 2), ('a', 2), ('b', 3)]

print(counter.most_common()[-2:])
# [('d', 2), ('c', 1)]

print(counter.most_common()[-1:])
# [('c', 1)]

most_common()の結果に対してスライスを使うことで、出現回数の少ない順に要素を取得できます。

[::-1]とすれば、リスト全体を逆順にできますし、[-2:]とすれば、下位2つの要素が、[-1:]とすれば、最も出現回数の少ない要素だけが取得できます。

●Counterと辞書の変換

Counterはとても便利なクラスですが、実はCounterと辞書の間には密接な関係があるんです。

Counterは辞書のサブクラスとして実装されているので、辞書の機能を継承しています。

つまり、CounterからPythonの標準的な辞書に変換したり、その逆をしたりすることができるんですね。

Pythonでデータを扱う際は、辞書はとてもよく使われるデータ構造です。

Counterと辞書を相互に変換できれば、より柔軟なデータ処理が可能になります。

それでは、実際にCounterと辞書の変換方法を見ていきましょう。

○Counterから辞書への変換

まずは、Counterから辞書への変換です。

とても簡単で、単にdictコンストラクタにCounterオブジェクトを渡すだけでOKです。

from collections import Counter

counter = Counter(a=3, b=2, c=1)
dictionary = dict(counter)

print(dictionary)  # {'a': 3, 'b': 2, 'c': 1}

この例では、最初にCounterをa=3, b=2, c=1として初期化しています。

そして、dict(counter)とすることで、Counterから辞書に変換しています。

結果を見ると、Counterの要素がキー、その出現回数が値となった辞書が得られていますね。

○辞書からCounterへの変換

次に、辞書からCounterへの変換も見てみましょう。

これも同様に、Counterコンストラクタに辞書を渡すだけです。

dictionary = {'a': 3, 'b': 2, 'c': 1}
counter = Counter(dictionary)

print(counter)  # Counter({'a': 3, 'b': 2, 'c': 1})

この例では、{'a': 3, 'b': 2, 'c': 1}という辞書を作り、それをCounterコンストラクタに渡すことで、辞書からCounterへ変換しています。

結果は、辞書のキーが要素、値が出現回数となったCounterオブジェクトになっています。

○サンプルコード7:Counterと辞書の相互変換

では、Counterと辞書の相互変換を実際に使ってみましょう。

from collections import Counter

# Counterから辞書への変換
counter = Counter(['apple', 'banana', 'apple', 'orange', 'banana', 'apple'])
dictionary = dict(counter)

print(dictionary)  # {'apple': 3, 'banana': 2, 'orange': 1}

# 辞書からCounterへの変換
dictionary = {'cat': 4, 'dog': 3, 'bird': 2, 'fish': 1}
counter = Counter(dictionary)

print(counter)  # Counter({'cat': 4, 'dog': 3, 'bird': 2, 'fish': 1})

この例では、最初にリストからCounterを作り、それを辞書に変換しています。

{'apple': 3, 'banana': 2, 'orange': 1}という辞書が得られていますね。

次に、{'cat': 4, 'dog': 3, 'bird': 2, 'fish': 1}という辞書を作り、それをCounterに変換しています。

結果は、辞書のキーと値がそのままCounterの要素と出現回数になっています。

●Counterのその他の使い方

さて、ここまででCounterの基本的な使い方から、カウントアップ、ソート、辞書との相互変換まで見てきました。

でも、Counterの機能はそれだけではありません。ここからは、Counterのその他の便利な使い方を見ていきましょう。

○要素の削除と更新

Counterは辞書のサブクラスなので、辞書と同じように要素の削除や更新ができます。

要素を削除するには、del文を使います。

from collections import Counter

counter = Counter(a=3, b=2, c=1)
del counter['b']

print(counter)  # Counter({'a': 3, 'c': 1})

この例では、del counter['b']として、Counterから’b’要素を削除しています。

結果を見ると、’b’が削除され、Counter({'a': 3, 'c': 1})になっていますね。

要素を更新するには、辞書と同様に添字アクセスを使います。

counter['a'] = 5
print(counter)  # Counter({'a': 5, 'c': 1})

ここでは、counter['a'] = 5として、’a’の出現回数を3から5に更新しています。

○Counterどうしの演算

Counterどうしの演算も便利な機能の1つです。

Counterは、+, -, &, |などの演算子をサポートしています。

from collections import Counter

counter1 = Counter(a=3, b=2, c=1)
counter2 = Counter(b=4, c=2, d=1)

print(counter1 + counter2)  # Counter({'b': 6, 'a': 3, 'c': 3, 'd': 1})
print(counter1 - counter2)  # Counter({'a': 3})
print(counter1 & counter2)  # Counter({'b': 2, 'c': 1})
print(counter1 | counter2)  # Counter({'b': 4, 'a': 3, 'c': 2, 'd': 1})

この例では、counter1counter2という2つのCounterを用意しています。

counter1 + counter2は、2つのCounterの要素の出現回数を足し合わせます。

重複する要素は出現回数が加算されます。

counter1 - counter2は、counter1からcounter2の要素の出現回数を引きます。

出現回数が0以下になる要素は削除されます。

counter1 & counter2は、2つのCounterの共通要素の最小の出現回数を取ります。

counter1 | counter2は、2つのCounterの全要素の最大の出現回数を取ります。

このように、Counterどうしの演算を使えば、複数のCounterを組み合わせて新しいCounterを作ることができます。データ分析の場面などで役立ちそうですね。

○Counterのメソッド一覧

最後に、Counterのメソッドを簡単にまとめておきましょう。

  • most_common([n]): 出現回数の多い順にn個の要素とその出現回数を返す
  • elements(): 要素を出現回数の数だけ繰り返すイテレータを返す
  • update([iterable-or-mapping]): 要素の出現回数を更新する
  • subtract([iterable-or-mapping]): 要素の出現回数を引く
  • copy(): Counterのコピーを返す
  • clear(): 全ての要素を削除する

most_common()update()は既に見てきましたね。

他のメソッドも、データ処理のニーズに応じて使い分けると良いでしょう。

特に、elements()は面白いメソッドです。

これを使えば、Counterを要素の出現回数だけ繰り返すイテレータに変換できます。

from collections import Counter

counter = Counter(a=3, b=2, c=1)

for element in counter.elements():
    print(element)

この例では、counter.elements()で要素を繰り返すイテレータを取得し、それをfor文で回しています。

実行結果は’a’が3回、’b’が2回、’c’が1回出力されるはずです。

●Counterを使う上での注意点

Counterは非常に便利なクラスで、要素のカウントや出現回数に基づくデータ処理を簡単に行うことができます。

でも、使う上でいくつか注意点があるんです。

ここでは、Counterを使う際に知っておくべき2つの注意点、「計算量とメモリ消費」と「要素の型」について説明します。

○計算量とメモリ消費について

Counterは、内部的には要素をキー、出現回数を値とする辞書を使って実装されています。

そのため、Counterの計算量やメモリ消費は、辞書と同じ特性を持っています。

具体的には、要素の数をnとすると、Counterの主要な操作(要素の追加、削除、参照など)の計算量はO(1)になります。

これは、要素の数に関わらず、一定の時間で操作が完了することを意味します。

from collections import Counter
import time

counter = Counter()

start_time = time.time()

for i in range(1000000):
    counter[i] = i

end_time = time.time()

print(f"Elapsed time: {end_time - start_time:.2f} seconds")

この例では、100万個の要素をCounterに追加しています。

実行時間を測定すると、要素の数が多くても一定の時間で処理が完了することがわかります。

ただし、メモリ消費については注意が必要です。

Counterは、要素をキーとして保持するため、要素の数が増えるほどメモリを消費します。

特に、要素が大量にある場合や、要素のサイズが大きい場合は、メモリ不足になる可能性があります。

from collections import Counter
import sys

counter = Counter(range(1000000))

print(f"Memory usage: {sys.getsizeof(counter) / 1024 / 1024:.2f} MB")

この例では、100万個の要素をCounterに追加し、そのメモリ使用量を確認しています。

実行結果を見ると、かなりのメモリを消費していることがわかります。

したがって、大量のデータを扱う際は、Counterのメモリ消費に注意が必要です。

場合によっては、Counterを使わずに、他のデータ構造や アルゴリズムを検討する必要があるかもしれません。

○要素の型について

Counterは、ハッシュ可能な型ならば、あらゆる型の要素をカウントできます。

数値、文字列、タプルなどがよく使われます。

from collections import Counter

counter = Counter([1, '2', (3, 4), 1, '2', (3, 4), (3, 4)])

print(counter)  # Counter({(3, 4): 3, 1: 2, '2': 2})

この例では、整数、文字列、タプルが混在したリストをCounterで処理しています。

結果を見ると、それぞれの型の要素がきちんとカウントされていますね。

ただし、要素の型が異なると、同じ値でも別の要素として扱われます。

例えば、整数の1と文字列の’1’は、値は同じでも型が異なるため、別々にカウントされます。

counter = Counter([1, '1', 1, '1', 1])

print(counter)  # Counter({1: 3, '1': 2})

この例では、整数の1と文字列の’1’が別々にカウントされています。

また、要素の型が異なると、Counterどうしの演算結果も影響を受けます。

例えば、&演算子は共通の要素を取りますが、型が異なると共通とはみなされません。

counter1 = Counter([1, 1, 2])
counter2 = Counter(['1', '1', '2'])

print(counter1 & counter2)  # Counter()

この例では、counter1counter2に共通の値がありますが、型が異なるため、&演算の結果は空のCounterになっています。

したがって、Counterを使う際は、要素の型にも注意が必要です。

特に、異なる型の要素を混在させる場合は、意図しない結果になることがあるので注意しましょう。

まとめ

さて、ここまでPythonのCounterについて、基本的な使い方から、カウントアップ、ソート、辞書との相互変換、その他の使い方、注意点まで詳しく見てきました。

この記事で紹介したCounterの使い方を参考に、ぜひ実際のプログラミングの場面で活用してみてください。

きっと、Pythonでのデータ処理の幅が大きく広がるはずです。

もし、この記事を読んでもまだCounterの使い方に不安があるという方は、ぜひサンプルコードを実際に動かしてみてください。

実際にコードを書いて動かすことが、理解を深めるための一番の近道です。