辞書でグループ化と集計

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

辞書でグループ化と集計 とは

辞書を使ってリストをキー別にグループ化し、件数や合計を算出する方法を学びます。本レッスンでは、辞書でグループ化と集計 の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

辞書でグループ化と集計

実務の Python で本当に多いのが 「カテゴリ別に件数を数える」「キー別に合計を出す」「タグごとにまとめる」 といった集計処理です。SQL なら GROUP BY、Excel なら ピボットテーブル、JavaScript なら reducePython では 辞書 がその役割を引き受けます。for ループで 1 件ずつ走査し、辞書 のキーに集計値を貯めていく、これがグループ化の基本パターンです。集計が手早く書けると、データ分析やレポート出力が一気に楽になります。

グループ化の鉄則は 「キーをハッシュ可能な値にする」。文字列・整数・タプルなら OK、リストはダメ、と覚えておきましょう。

グループ化処理は 「初回はキーを作る、2 回目以降は更新する」 という 2 段構えを覚えるだけ。これさえ身に付けば応用が無限に広がります。

カウント (件数集計)

単語のリストから「出現回数」を数えたいときの典型です。下記のとおりです。

Python

words = ["apple", "banana", "apple", "cherry", "banana", "apple"] count = {} for w in words: count[w] = count.get(w, 0) + 1 print(count) # {'apple': 3, 'banana': 2, 'cherry': 1}

ポイントは count.get(w, 0) + 1 の部分。「キーがあれば値を取得、なければ 0」+ 1 で、既存キーは加算、新規キーは 1 になります。if w in count で分岐するより短く、Python らしい書き方です。

合計集計

金額やスコアの合計を計算するパターンも、考え方は同じです。下記のとおりです。

Python

orders = [ {"category": "food", "amount": 500}, {"category": "book", "amount": 1200}, {"category": "food", "amount": 800}, ] totals = {} for o in orders: c = o["category"] totals[c] = totals.get(c, 0) + o["amount"] print(totals) # {'food': 1300, 'book': 1200}

ここでも dict.get(key, 0) でデフォルト 0 を取り出し、足し算して書き戻す、というイディオムが活躍します。

リストにまとめる

「同じカテゴリの要素をリストとして集めたい」場合は、値を リスト にして append します。下記のとおりです。

Python

people = [("Tokyo", "Taro"), ("Osaka", "Hanako"), ("Tokyo", "Jiro")] grouped = {} for city, name in people: grouped.setdefault(city, []).append(name) print(grouped) # {'Tokyo': ['Taro', 'Jiro'], 'Osaka': ['Hanako']}

d.setdefault(key, [])「キーが無ければ空リストを作って返す」 という便利メソッド。grouped.setdefault(city, []).append(name) で「グループのリストに追加」が 1 行で書けます。defaultdict を使えばさらにスッキリ書けます。

collections.defaultdict を使う

標準ライブラリの defaultdict は、存在しないキーへの初回アクセス時にデフォルト値を自動生成 してくれる辞書です。下記のとおりです。

Python

from collections import defaultdict count = defaultdict(int) for w in ["a", "b", "a"]: count[w] += 1 print(dict(count)) # {'a': 2, 'b': 1}

defaultdict(int) なら初期値 0defaultdict(list) なら初期値 [].getsetdefault を書かずに済むので、コードがぐっとシンプルになります。

動きを追ってみる

diagram (will load when visible)

グループ化の流れを図にすると上のとおり。「キーで分岐」 → 「初期値か既存値」 → 「更新」 の 3 ステップで集計が完了します。

よくある間違い

  1. count[w] += 1 をいきなり書いて KeyError。新規キーは存在しないので、初回は値を初期化する必要があります。get(key, 0) + 1defaultdict(int) で解決しましょう。
  2. キーに リスト を渡してしまう。リストは可変なのでハッシュ化できず TypeError。代わりに tuple を使ってください。
  3. 同じキーに append するつもりが、値が文字列のまま。最初に setdefault(key, [])必ずリストに初期化 するのを忘れずに。

集計の難しさはロジックではなく 初期化と分岐の漏れdefaultdict get setdefault の 3 つを使い分ければ、9 割の集計は綺麗に書けます。

やってみよう

単語のリスト words を受け取り、{単語: 出現回数} の辞書を返す関数 count_words を書いてみましょう。dict.get を使う書き方でも、defaultdict(int) を使う書き方でもどちらでも OK。集計の基本パターンを体に覚え込ませる練習です。

よくある質問

Q. 辞書の存在しないキーを参照するとどうなりますか?

A. d['missing'] は KeyError になります。安全に取り出すには d.get('missing', default) を使い、複雑な処理を書くなら collections.defaultdict を使うとキー初期化が省けます。in 演算子(if 'k' in d)で事前確認するのも定石です。

Q. 辞書のキーには何でも使えますか?

A. ハッシュ可能(イミュータブル)な値だけが使えます。文字列、数値、タプルは OK ですが、リストや辞書はキーにできません。タプルにすれば座標 (x, y) や複合キーとして使えるので、辞書を多重キーにしたいときに便利です。

Q. 辞書を for で回すと何が取れますか?

A. デフォルトはキーだけです。値も欲しい場合は d.values()、両方なら d.items() を使い for k, v in d.items(): と書きます。順序は Python 3.7 以降は挿入順が保証されているため、追加順に処理されます。

次のレッスン

次は タプルのアンパックで複数値をまとめて受け取る で、辞書を使ってリストをキー別にグループ化し、件数や合計を算出する方法を学びます を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. グループ化 の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. グループ化 とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. dict.get(key, 0) + 1 で集計する
  2. 新規キーで KeyError を起こさない
  3. 空リストでは空辞書を返す

入出力例

test-cases.txt

count_words(["apple","banana","apple","cherry","banana","apple"]){"apple":3,"banana":2,"cherry":1} count_words(["a","b","c"]){"a":1,"b":1,"c":1} count_words(["x","x","x"]){"x":3} count_words([]){}

ヒント

main.py
main.py
学習モード

メモ

辞書でグループ化と集計

⌘S で保存