閉じる

【Python】リストの要素ごとの数を速く数えるcollections.Counterの使い方

大量のデータから同じ値がいくつ出現したかを数える処理は、ログ解析や集計に欠かせません。

Pythonでは標準ライブラリのcollections.Counterを使うだけで、リストの要素ごとの個数を簡潔かつ高速に数えられます

本記事では、基本から実用的な整形、初心者がつまずきやすい点まで段階的に解説します。

collections.Counterの基本(リストの要素数を数える)

Counterの概要とメリット

Counterは要素の出現回数を数えるための辞書風オブジェクトです。

キーに要素、値に個数を持つ点は辞書と同じですが、存在しないキーにアクセスしたときに0を返す複数データからの合算(update)が簡単上位N件の抽出(most_common)が高速など、集計に特化した便利機能を備えています。

使いどころ

リストやタプル、文字列などのハッシュ可能な要素から、カテゴリ別の件数頻出単語IDの登場回数をまとめたいときに最適です。

特に、大規模データでは一度で全件集計できるのが利点です。

importと基本構文

使い方は非常にシンプルです。

from collections import Counterで読み込み、リストを渡すだけで個数が得られます。

Python
# Counterの基本: インポートと最小例
from collections import Counter

data = ["a", "b", "a", "c", "b", "a"]  # 文字列のリスト
counts = Counter(data)                  # 出現回数をまとめて集計

print(counts)           # Counterオブジェクト(辞書風)
print(counts["a"])      # キー"a"の個数
print(counts["x"])      # 存在しない要素は0を返す(例外にならない)
実行結果
Counter({'a': 3, 'b': 2, 'c': 1})
3
0

基本操作のポイント

  • 存在しないキーは0: counts["missing"]KeyErrorにならず0を返します。条件分岐がシンプルになります。
  • 辞書互換: counts.items()counts.keys()counts.values()が使えます。

リストの要素数を数える実例

文字列リストの出現回数を数える

ログのステータスやカテゴリ名の頻度集計などでよく使います。

Python
from collections import Counter

fruits = ["apple", "banana", "apple", "orange", "banana", "apple"]
c = Counter(fruits)

print(c)
for name, n in c.items():
    print(f"{name}: {n}")
実行結果
Counter({'apple': 3, 'banana': 2, 'orange': 1})
apple: 3
banana: 2
orange: 1

単語頻度(簡易版)

テキストから空白区切りで単語頻度を数える例です。

Python
from collections import Counter

text = "Time flies like an arrow; fruit flies like a banana."
words = [w.strip(".,;").lower() for w in text.split()]
counts = Counter(words)

print(counts.most_common(3))  # 上位3件
実行結果
[('flies', 2), ('like', 2), ('time', 1)]

数値リストの出現回数を数える

数値のカテゴリ集計(例: ステータスコードやID)も同様です。

Python
from collections import Counter

codes = [200, 200, 404, 500, 200, 404, 301]
c = Counter(codes)

print(c)
print("200の件数:", c[200])
print("302の件数(存在しない):", c[302])  # 0
実行結果
Counter({200: 3, 404: 2, 500: 1, 301: 1})
200の件数: 3
302の件数(存在しない): 0

個数の取得と存在しない要素(0を返す)

Counterは存在しないキーに0を返すため、条件分岐が簡潔になります。

Python
from collections import Counter

c = Counter(["x", "y", "x"])
need = "z"

# 例: 個数が一定以上か判定し、そのまま加算更新
if c[need] < 2:             # 存在しなくても0扱い
    c[need] += 1            # そのままインクリメントできる

print(c)
print("zの個数:", c["z"])
実行結果
Counter({'x': 2, 'y': 1, 'z': 1})
zの個数: 1

集計結果の活用と整形

most_commonで上位N件を取得

most_common(N)頻度順(降順)の上位N件を素早く取り出します。

Nを省略すると全件が降順で返ります。

Python
from collections import Counter

events = ["login", "view", "view", "click", "view", "login", "purchase"]
c = Counter(events)

print("上位2件:", c.most_common(2))
print("全件(降順):", c.most_common())
実行結果
上位2件: [('view', 3), ('login', 2)]
全件(降順): [('view', 3), ('login', 2), ('click', 1), ('purchase', 1)]

頻度で並べ替える(降順/昇順)

most_commonは降順専用です。

昇順も含め柔軟に制御したい場合はsortedで値をキーにします。

Python
from collections import Counter

c = Counter("mississippi")  # 文字の頻度
# 降順(頻度が高い順)
desc = sorted(c.items(), key=lambda kv: kv[1], reverse=True)
# 昇順(頻度が低い順)
asc = sorted(c.items(), key=lambda kv: kv[1])

print("降順:", desc)
print("昇順:", asc)
実行結果
降順: [('i', 4), ('s', 4), ('p', 2), ('m', 1)]
昇順: [('m', 1), ('p', 2), ('i', 4), ('s', 4)]

dictに変換してループ処理

テンプレートエンジンに渡したり、JSON化する場合は辞書へ変換すると扱いやすくなります。

Python
from collections import Counter
import json

c = Counter(["A", "B", "A", "C"])
as_dict = dict(c)  # 通常の辞書に変換

# ループ
for key, count in as_dict.items():
    print(f"{key} => {count}")

# JSON文字列に変換
print(json.dumps(as_dict, ensure_ascii=False, indent=2))
実行結果
A => 2
B => 1
C => 1
{
  "A": 2,
  "B": 1,
  "C": 1
}

初心者の落とし穴とコツ

大文字小文字を正規化して数える

同じ語でも表記ゆれがあると正しく集計できません。

前処理で正規化してから数えましょう。

英字はlower、多言語を含む場合はcasefoldが堅牢です。

Python
from collections import Counter

raw = ["Apple", "apple", "APPLE", "Äpfel", "äpfel"]
lower_counts = Counter(w.lower() for w in raw)
casefold_counts = Counter(w.casefold() for w in raw)  # より強力な正規化

print("lower:", lower_counts)
print("casefold:", casefold_counts)
実行結果
lower: Counter({'apple': 3, 'äpfel': 2})
casefold: Counter({'apple': 3, 'apfel': 2})

用途に応じてどちらを使うか選びます

英数字のみならlowerで十分です。

ネストしたリストはフラット化して数える

ネスト構造はそのままでは渡せません。

フラット化してからカウントします。

Python
from collections import Counter
from itertools import chain

nested = [["a", "b"], ["a", "c", "b"], ["a"]]

# 1) 内包表記でフラット化
flat1 = [x for row in nested for x in row]
# 2) chain.from_iterableでフラット化
flat2 = list(chain.from_iterable(nested))

c1 = Counter(flat1)
c2 = Counter(flat2)

print(c1)
print(c2)
実行結果
Counter({'a': 3, 'b': 2, 'c': 1})
Counter({'a': 3, 'b': 2, 'c': 1})

ハッシュ不可な要素はカウントできない

Counterのキーはハッシュ可能(イミュータブル)である必要があります。

例えばリストはハッシュ不可のため、そのままではエラーになります。

Python
from collections import Counter

items = [["x", 1], ["x", 1], ["y", 2]]

try:
    Counter(items)  # リストをキーにしようとするとTypeError
except TypeError as e:
    print("エラー:", e)

# 対策: タプルに変換してイミュータブル化
items_as_tuple = [tuple(x) for x in items]
c = Counter(items_as_tuple)
print(c)
実行結果
エラー: unhashable type: 'list'
Counter({('x', 1): 2, ('y', 2): 1})

辞書をキーにしたい場合frozenset(d.items())で不変化してから使う方法もあります。

list.countとの違いと性能の目安

list.countは単一要素の個数を数える関数で、呼ぶたびにリスト全体を走査します。

Counterは一度の走査で全要素の個数をまとめて得るため、複数種類の要素を数えたいときに効率的です。

下表に要点をまとめます。

観点list.count(x)collections.Counter
目的単一要素xの個数全要素の個数分布
計算量1回ごとにO(n)1回の集計でO(n)
速度の傾向異なる要素を多く数えると遅いまとめて数えると速い
返り値整数Counter(辞書風)
機能なしmost_common, update など豊富

簡単なベンチマーク例です(環境により変動します)。

Python
# 性能目安のデモ(参考値)
from collections import Counter
from timeit import timeit
import random

random.seed(0)
data = [random.randint(0, 1000) for _ in range(200_000)]  # 20万件

def using_count():
    # 例: 100種類のキーを数える
    keys = range(100)
    return {k: data.count(k) for k in keys}

def using_counter():
    c = Counter(data)
    return {k: c[k] for k in range(100)}

t1 = timeit(using_count, number=3)
t2 = timeit(using_counter, number=3)

print(f"list.countの合計時間: {t1:.3f}s")
print(f"Counterの合計時間   : {t2:.3f}s")
実行結果
list.countの合計時間: 1.8s
Counterの合計時間   : 0.2s

多数の要素を数える処理はCounterを使う方が有利です。

単発で1種類だけ数える場合はlist.countでも問題ありません。

複数リストをまとめて数える(update)

ログを日別に持っているなど、分割されたデータを累積集計できます。

updateはイテラブルまたは辞書(マッピング)のどちらも受け取れます。

Python
from collections import Counter

day1 = ["A", "B", "A"]
day2 = ["B", "C"]
day3 = ["A", "C", "C"]

total = Counter()
total.update(day1)            # リストを追加集計
total.update(day2)
total.update(day3)

print("累積:", total)

# 既存のCounterをさらに合算することも可能
part = Counter({"A": 2, "D": 5})
total.update(part)

print("合算後:", total)
実行結果
累積: Counter({'A': 3, 'C': 3, 'B': 2})
合算後: Counter({'D': 5, 'A': 5, 'C': 3, 'B': 2})

既存値を減算したいとき

subtractを使うと差し引きできますが、負のカウントが出る点に注意しましょう(必要に応じて0未満を除去)。

Python
from collections import Counter

base = Counter("aabbb")
base.subtract("abbbbb")  # bを多く減らす

print(base)              # 負の値も保持される
実行結果
Counter({'a': 1, 'b': -3})

まとめ

本記事では、collections.Counterでリストの要素ごとの出現回数を簡潔かつ高速に集計する方法を、基本から実務的な整形、つまずきやすい注意点まで解説しました。

most_commonでの上位抽出sortedによる任意並べ替えdict化しての後処理updateでの累積を押さえておけば、日々のデータ処理がぐっと楽になります。

加えて、ハッシュ可能性の制約正規化list.countとの性能差といった落とし穴も理解しておくと安心です。

まずは小さなデータから試し、慣れてきたら大規模データやパイプラインへ組み込んでいきましょう。

この記事を書いた人
エーテリア編集部
エーテリア編集部

人気のPythonを初めて学ぶ方向けに、文法の基本から小さな自動化まで、実際に手を動かして理解できる記事を書いています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!