大量のデータから同じ値がいくつ出現したかを数える処理は、ログ解析や集計に欠かせません。
Pythonでは標準ライブラリのcollections.Counter
を使うだけで、リストの要素ごとの個数を簡潔かつ高速に数えられます。
本記事では、基本から実用的な整形、初心者がつまずきやすい点まで段階的に解説します。
collections.Counterの基本(リストの要素数を数える)
Counterの概要とメリット
Counterは要素の出現回数を数えるための辞書風オブジェクトです。
キーに要素、値に個数を持つ点は辞書と同じですが、存在しないキーにアクセスしたときに0を返す、複数データからの合算(update
)が簡単、上位N件の抽出(most_common
)が高速など、集計に特化した便利機能を備えています。
使いどころ
リストやタプル、文字列などのハッシュ可能な要素から、カテゴリ別の件数
や頻出単語
、IDの登場回数
をまとめたいときに最適です。
特に、大規模データでは一度で全件集計できるのが利点です。
importと基本構文
使い方は非常にシンプルです。
from collections import Counter
で読み込み、リストを渡すだけで個数が得られます。
# 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()
が使えます。
リストの要素数を数える実例
文字列リストの出現回数を数える
ログのステータスやカテゴリ名の頻度集計などでよく使います。
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
単語頻度(簡易版)
テキストから空白区切りで単語頻度を数える例です。
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)も同様です。
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を返すため、条件分岐が簡潔になります。
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を省略すると全件が降順で返ります。
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
で値をキーにします。
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化する場合は辞書へ変換すると扱いやすくなります。
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
が堅牢です。
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
で十分です。
ネストしたリストはフラット化して数える
ネスト構造はそのままでは渡せません。
フラット化してからカウントします。
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のキーはハッシュ可能(イミュータブル)である必要があります。
例えばリストはハッシュ不可のため、そのままではエラーになります。
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 など豊富 |
簡単なベンチマーク例です(環境により変動します)。
# 性能目安のデモ(参考値)
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
はイテラブルまたは辞書(マッピング)のどちらも受け取れます。
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未満を除去)。
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との性能差といった落とし穴も理解しておくと安心です。
まずは小さなデータから試し、慣れてきたら大規模データやパイプラインへ組み込んでいきましょう。