Python | データ構造強化:itertools.groupby

Python Python
スポンサーリンク

概要(groupby は「連続する同じキーでまとまりを作る」ための基本ツール)

itertools.groupby は、並んだデータを「キーが同じものが連続している区間」ごとにグループ化するイテレータです。重要なのは「連続している要素だけが同じグループになる」こと。つまり、同じキーが散らばっていると一つにまとまらず、通常は「グループ化したいキーであらかじめソートしておく」必要があります。結果は (key, group_iterator) のペアを順に返し、group_iterator は一度きりの消費型(必要なら list(…) で取り出す)です。

from itertools import groupby

data = [1, 1, 2, 3, 3, 3, 2]
for key, grp in groupby(data):
    print(key, list(grp))
# 1 [1, 1]
# 2 [2]
# 3 [3, 3, 3]
# 2 [2]  ← “バラバラ”に現れる 2 は別グループとして分かれる
Python

基本の使い方(ここが重要)

まず「グループ化したいキー」でソートしてから groupby する

groupby は“連続する同じキー”を束ねるので、グループ化の前に並べ替えるのが定石です。辞書やオブジェクトのフィールドでグループ化するなら、同じキー関数で sort と groupby を揃えます。

from itertools import groupby
from operator import itemgetter

rows = [
    {"team": "B", "user": "taro"},
    {"team": "A", "user": "hanako"},
    {"team": "A", "user": "mika"},
    {"team": "B", "user": "jiro"},
]

rows.sort(key=itemgetter("team"))                 # 重要:同じキー関数でソート
for team, grp in groupby(rows, key=itemgetter("team")):
    users = [r["user"] for r in grp]             # grp は一度きりのイテレータ
    print(team, users)
# A ['hanako', 'mika']
# B ['taro', 'jiro']
Python

key 引数で「何を基準にまとめるか」を決める

key=None(省略)なら要素自体で連続区間をまとめます。複雑な構造なら key=lambda r: r[“col”] のように関数を渡します。

from itertools import groupby

words = ["apple", "apricot", "banana", "blueberry", "cherry"]
words.sort(key=lambda w: w[0])                   # 先頭文字で並べる
for initial, grp in groupby(words, key=lambda w: w[0]):
    print(initial, list(grp))
# a ['apple', 'apricot']
# b ['banana', 'blueberry']
# c ['cherry']
Python

group は「一度きりの消費型」。必要分だけ取り出す

groupby が返す grp はイテレータなので、反復すると消費されます。後で再利用したいなら list(grp) で素材化、またはすぐ集計・出力に使います。

重要ポイントの深掘り(区間グループ化の本質・ソートと整合・落とし穴)

groupby は「連続区間」を作る。散在データの“集合”化ではない

同じキーが散在している生データにそのまま groupby をかけると、同じキーでも複数グループに分かれます。全てを一つに“集合”化したいなら、defaultdict(list) などでキー別に集めるアプローチが適切です(groupby は「順序付きの区間処理」に強い)。

# 集合化したいなら defaultdict を使う
from collections import defaultdict

rows = [{"team": "B", "user": "taro"}, {"team": "A", "user": "hanako"}, {"team": "B", "user": "jiro"}]
box = defaultdict(list)
for r in rows:
    box[r["team"]].append(r["user"])
print(box)  # {'B': ['taro', 'jiro'], 'A': ['hanako']}
Python

sort の key と groupby の key を“必ず一致”させる

この整合が崩れると、意図しない分割になります。itemgetter や同じ lambda を使い回し、並べ替えとグループ化の基準を揃えましょう。

大量データは「流しながら処理」が基本

groupby は遅延生成で、1グループずつ手元に来ます。グループごとに集計・出力を行えば、全体をメモリに載せずに処理できます。ログや時系列の「状態が続く区間」を検出する用途に特に強いです。

実務での使いどころ(区間検出・グループ出力・ファイル分割)

連続状態の区間抽出(状態が続いた塊を得る)

例えば、連続する同じエラーレベルの区間を抽出します。出力側で区間の開始・終了を扱うと、継続時間の分析に直結します。

from itertools import groupby

logs = [
    ("INFO", 1), ("INFO", 2),
    ("WARN", 3),
    ("ERROR", 4), ("ERROR", 5), ("ERROR", 6),
    ("INFO", 7)
]

# すでに時系列順に並んでいる前提で、連続区間をまとめる
for level, grp in groupby(logs, key=lambda x: x[0]):
    times = [t for _, t in grp]
    print(level, (times[0], times[-1]))  # 開始・終了
# INFO (1, 2)
# WARN (3, 3)
# ERROR (4, 6)
# INFO (7, 7)
Python

ソート済みデータを「キー別ファイル」に安全に分割

グループごとにまとめて書き出すことで、バッファの切り替えや最後の残りの処理を簡潔にできます。

from itertools import groupby
from operator import itemgetter

rows = [
    {"dept": 10, "name": "scott"},
    {"dept": 10, "name": "john"},
    {"dept": 20, "name": "bob"},
    {"dept": 20, "name": "maria"},
]
rows.sort(key=itemgetter("dept"))
for dept, grp in groupby(rows, key=itemgetter("dept")):
    with open(f"dept-{dept}.txt", "w", encoding="utf-8", newline="\n") as f:
        for r in grp:
            f.write(r["name"] + "\n")
Python

見出しで区切られたレポートを「セクションごと」に処理

連続する同じヘッダ種別でグループ化して、セクション単位の集計やレンダリングに使えます。

例題で身につける(定番から一歩先まで)

例題1:先頭文字ごとに単語をまとめて表示(要ソート)

from itertools import groupby

words = ["blueberry", "banana", "apple", "apricot", "cherry"]
words.sort(key=lambda w: w[0])
for initial, grp in groupby(words, key=lambda w: w[0]):
    print(initial, list(grp))
Python

例題2:CSV の列でグループ化して、各グループの合計を出す

import csv
from itertools import groupby
from operator import itemgetter

def sum_by_col(path: str, key_col: int, val_col: int):
    with open(path, "r", encoding="utf-8", newline="") as f:
        rows = list(csv.reader(f))
    rows.sort(key=itemgetter(key_col))                 # グループ化列でソート
    for key, grp in groupby(rows, key=itemgetter(key_col)):
        total = sum(int(r[val_col]) for r in grp)
        print(key, total)
Python

例題3:時系列の「同じ状態が続く区間」の長さを測る

from itertools import groupby

series = [("idle", 0), ("idle", 1), ("busy", 2), ("busy", 3), ("idle", 4)]
for state, grp in groupby(series, key=lambda x: x[0]):
    ts = [t for _, t in grp]
    duration = ts[-1] - ts[0] + 1
    print(state, duration)
Python

例題4:グループごとの先頭・末尾だけを効率よく取得

from itertools import groupby

nums = [1, 1, 1, 2, 2, 3, 1]
for key, grp in groupby(nums):
    g = list(grp)
    print(key, g[0], g[-1])  # 各区間の先頭・末尾
Python

まとめ

groupby は「並んだデータの連続区間をキーごとに束ねる」ためのイテレータです。散在する同キーを“1か所に集める”機能ではないため、グループ化前のソートと、sort の key と groupby の key を一致させる設計が最重要ポイント。返されるグループは消費型イテレータなので、その場で集計・出力するか、必要に応じて list へ素材化します。ログの区間検出、ソート済みデータのキー別出力、時系列の状態区間の分析など、区間を意識した処理で強力に機能します。まずは「ソート→groupby→グループ単位で処理」という基本線を体に覚えさせると、現場のコードが短く、読みやすく、堅牢になります。

タイトルとURLをコピーしました