「単語の出現回数を数えたいだけなのに、if key not in d: を毎回書くのが面倒…」
「カテゴリごとにデータをまとめるコードが、いつの間にか長くなってしまう…」

Pythonで集計のコードを書いていると、こんなモヤモヤを感じること、ありますよね。

そんなときに使えるのが、標準ライブラリの collectionsモジュール にある Counter と defaultdict です。インストール不要で、import するだけで使えます。

この記事は、for文と辞書(dict)の基本がわかるPython初心者〜初級者向けです。「普通の辞書で書いた場合」と比べながら、一緒に見ていきましょう!😊

Counterとdefaultdictってどんなもの?

counting tally
counting tally / Photo by Magda Ehlers via Pexels

ざっくり言うと、どちらも 「ちょっと便利になった辞書」 です。

  • Counter:数を数える専用の辞書。リストを渡すだけで「何が何回出てきたか」を集計してくれる
  • defaultdict:まだ無いキーにアクセスしたとき、自動で初期値を作ってくれる辞書

イメージとしては、Counterは「正の字を自動で書いてくれるメモ帳」、defaultdictは「新しい見出しを勝手に用意してくれるノート」です。

どちらも dict の仲間なので、[] でのアクセスや .items() など、いつもの辞書の書き方がそのまま使えます。

Counterで「何が何回出たか」を一発で数える

まずは、1週間に売れた果物の記録を数えてみます。普通の辞書で書いた場合と、Counterで書いた場合を並べました。

from collections import Counter

# 1週間分の「売れた果物」の記録
sales = ["りんご", "バナナ", "りんご", "みかん", "バナナ", "りんご",
         "ぶどう", "みかん", "りんご", "バナナ"]

# --- 方法1: 普通の辞書で数える ---
counts = {}
for fruit in sales:
    if fruit not in counts:   # 初めて出てきたら0で初期化
        counts[fruit] = 0
    counts[fruit] += 1
print("辞書:", counts)

# --- 方法2: Counterで数える ---
c = Counter(sales)
print("Counter:", c)

# 多い順に上位2つ
print("TOP2:", c.most_common(2))

# 存在しないキーは 0 が返る(KeyErrorにならない)
print("メロン:", c["メロン"])
Counterと普通の辞書で果物の数を集計した実行結果
Counterと普通の辞書で果物の数を集計した実行結果

実行すると、上の画像のように表示されます。辞書版とCounter版で、集計結果はまったく同じですね。

ポイントをまとめるとこんな感じです👇

  • 普通の辞書だと、初めて出たキーを0で初期化する処理が必要(これを忘れて counts[fruit] += 1 だけ書くと KeyError になります)
  • Counterなら Counter(sales) の1行で集計完了
  • most_common(2) で多い順の上位2件が (値, 回数) のタプルのリストで取れる
  • 存在しないキー(メロン)を参照しても、エラーではなく 0 が返る

「ランキングを出したい」「一番多いものを知りたい」という場面では、most_common() がとにかく便利です。自分で sorted() と lambda を組み合わせて並べ替える必要がなくなります。

defaultdictで「グループ分け」と「合計」をスッキリ書く

次は、家計簿のような支出データをカテゴリごとにまとめる例です。

from collections import defaultdict

# (カテゴリ, 金額) の支出データ
expenses = [
    ("食費", 850), ("交通費", 420), ("食費", 1200),
    ("日用品", 380), ("交通費", 420), ("食費", 640),
]

# カテゴリごとに金額をリストでまとめる
by_category = defaultdict(list)
for category, amount in expenses:
    by_category[category].append(amount)   # 初期化なしでいきなりappendできる

# カテゴリごとの合計を集計
totals = defaultdict(int)
for category, amount in expenses:
    totals[category] += amount             # int() の初期値は 0

for category, amounts in by_category.items():
    print(f"{category}: {amounts} 合計 {totals[category]}円")

# 普通の辞書に戻して表示
print(dict(totals))

実行結果です。

食費: [850, 1200, 640] 合計 2690円
交通費: [420, 420] 合計 840円
日用品: [380] 合計 380円
{'食費': 2690, '交通費': 840, '日用品': 380}

ここが重要です👇

  • defaultdict(list):無いキーにアクセスすると空のリスト [] が自動で用意される → いきなり .append() できる
  • defaultdict(int):無いキーの初期値は int() の結果、つまり 0 → いきなり += できる
  • dict(totals) で普通の辞書に戻せるので、JSONに保存したいときも安心

カッコの中に渡すのは「初期値を作る関数」です。list なら空リスト、int なら0、set なら空の集合が初期値になります。list() のようにカッコを付けて呼び出すのではなく、関数そのものを渡す点に注意してください。

⚠️ defaultdictは「見ただけ」でキーが増える

ひとつ、つまずきやすいポイントがあります。defaultdictは、値を読むだけのつもりでもキーが追加されてしまうんです。

実際に試したところ、totals = defaultdict(int) に「食費」だけを入れた状態で print(totals["娯楽費"]) を実行すると 0 が表示され、そのあと dict(totals) を見ると {'食費': 1, '娯楽費': 0} のように、「娯楽費」が0円で登録されていました。

一方、Counterは存在しないキーを c["z"] で参照しても0を返すだけで、キーは増えません("z" in c は False のまま)。

defaultdictで「キーがあるか確認したい」ときは、d[key] ではなく key in d や d.get(key) を使うのが安全です。

実践:アクセスログを集計してみよう

最後に、Counterとdefaultdictを1つのスクリプトで組み合わせてみます。簡単なアクセスログから「人気ページのランキング」と「ユーザーごとに何ページ見たか」を出してみましょう。

これ、実行する前に結果を予想できますか? 特に「sato は何ページ見た?」に注目してみてください。

from collections import Counter, defaultdict

# アクセスログ(時刻, ユーザー, ページ)
logs = """
09:01 sato /top
09:03 suzuki /top
09:05 sato /blog
09:10 tanaka /top
09:12 suzuki /blog
09:15 sato /contact
09:20 tanaka /blog
09:22 suzuki /top
""".strip().splitlines()

page_counter = Counter()
pages_by_user = defaultdict(set)   # ユーザーごとに「見たページ」を重複なしで記録

for line in logs:
    time, user, page = line.split()
    page_counter[page] += 1
    pages_by_user[user].add(page)

print("=== 人気ページ ===")
for page, n in page_counter.most_common():
    print(f"{page:<9}{n}回")

print("=== ユーザー別 閲覧ページ数 ===")
for user, pages in sorted(pages_by_user.items()):
    print(f"{user:<7}{len(pages)}ページ {sorted(pages)}")
アクセスログをCounter・defaultdictで集計した実行結果
アクセスログをCounter・defaultdictで集計した実行結果

実行結果は上の画像のとおりです。sato は3回アクセスして、3ページとも別のページだったので「3ページ」でした。

ポイントをまとめるとこんな感じです👇

  • Counter() は空で作って、page_counter[page] += 1 のように1件ずつ足していく使い方もできる
  • defaultdict(set) にすると、ユーザーごとに重複なしでページを記録できる(suzukiは /top を2回見ていますが、2ページと数えられています)
  • most_common() に数を指定しなければ、全件を多い順に返してくれる

このパターンは、CSVの集計やセンサーログの分析など、いろいろな場面にそのまま応用できます。

普通の辞書・Counter・defaultdictの使い分け

やりたいこと おすすめ
出現回数を数える・ランキングを出す Counter
キーごとにリストや集合にまとめる defaultdict(list) / defaultdict(set)
キーごとに金額などを合計する defaultdict(int)(Counterでも可)
キーが決まっている設定値などを持つ 普通の辞書

より詳しい仕様は、Python公式ドキュメントの collections — コンテナデータ型 で確認できます。

まとめ

  • Counter はリストを渡すだけで回数を集計でき、most_common() でランキングも一発
  • defaultdict は無いキーの初期値を自動で作るので、if key not in d: が不要になる
  • defaultdictは参照しただけでキーが増えるので、存在チェックには in や get() を使う
  • どちらも標準ライブラリなので、from collections import Counter, defaultdict ですぐ使える

「集計のコードって長くなりがち…」と感じていた方も、この2つを覚えるだけでグッと短く、読みやすく書けるようになるはずです。

手元にあるデータで、ぜひ試してみてください🚀

📚 関連商品・おすすめ書籍

スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)

もしも

スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)

初心者に定番のPython入門書

Amazonで見る
ラズパイ5対応 カラー図解 最新 Raspberry Piで学ぶ電子工作

もしも

ラズパイ5対応 カラー図解 最新 Raspberry Piで学ぶ電子工作

ラズパイ5対応のカラー図解電子工作本

Amazonで見る
Python Web開発実践入門 ―― FastAPIによるWebAPI開発と非同期処理

もしも

Python Web開発実践入門 ―― FastAPIによるWebAPI開発と非同期処理

FastAPIでWebAPI開発を実践的に学ぶ

Amazonで見る

※本記事にはアフィリエイトリンクが含まれます。

ABOUT ME
やまちゃん
これまで学生と社会人を合わせて5000人以上にプログラミング学習を指導。 ゼロからイチをわかりやすく解説する専門家として活動しており、本業ではArduinoを用いたIoT開発とロボットプログラミングが専門。 Pythonを用いたアプリ開発、ウェブアプリケーションの開発で業務の効率化をサポートしています。