測定レコードを装置名や試料IDごとにまとめるなら、collections.defaultdict(list)を使うと、最初の一件に必要な空リストの準備を省ける。未登録キーを[]で参照したときに、新しいリストを作って保存してくれる。ただし、読むだけのつもりで未登録キーへアクセスしても辞書が増えるため、確認にはgetやinを使い分けよう。
初回だけ必要なリストを自動で作る
普通の辞書でグループ分けする場合、その装置名が初めて現れたら空リストを作り、そこへレコードを追加する。defaultdictは、この最初の準備をファクトリーへ任せる。listという呼び出し可能なオブジェクトを渡すことで、キーごとに新しい空リストが作られる。
defaultdict([])のように、作成済みのリストそのものを渡すのではない。必要になったときに引数なしで呼び出せるものを指定する。listの代わりにsetやintを使えば、集合や数値を初期値にした集計にも利用できる。
装置ごとにレコードをまとめる
example.py
from collections import defaultdict
records = [
{"device": "A", "value": 10},
{"device": "B", "value": 20},
{"device": "A", "value": 12},
]
groups = defaultdict(list)
for record in records:
groups[record["device"]].append(record)
summary = {key: [r["value"] for r in rows] for key, rows in groups.items()}
print(summary)
assert summary == {"A": [10, 12], "B": [20]}
assert groups["A"][0] is records[0]
assert groups["A"] is not groups["B"]
assert groups.get("C", []) == []
print("after get:", "C" in groups)
assert "C" not in groups
assert groups["C"] == []
print("after indexing:", "C" in groups)
assert "C" in groups
plain = dict(groups)
assert isinstance(plain, dict) and not isinstance(plain, defaultdict)
assert plain["A"] is groups["A"]
実行結果
{'A': [10, 12], 'B': [20]}
after get: False
after indexing: True
各グループにはレコードそのものを追加し、表示するときだけvalueを取り出している。この形なら、測定時刻や試料名など別の項目を後から利用できる。グループ内のレコードは入力された順に並ぶため、元の測定順を保ったまま分類できる。
getはグループを新しく作らない
groups.get(“C”, [])は、Cがなくても辞書へ追加しない。一方、groups[“C”]はファクトリーを呼び、空リストを保存する。例ではこの違いを、キーが存在するかどうかで確認している。読み取り処理が辞書の状態を変えていないかを意識すると、空グループが増える原因を見つけやすい。
getの既定値として渡した空リストへ追加しても、キーがない場合にはgroupsへ登録されない。グループを作って記録したい操作は[]、存在確認はin、変更しない取得はgetと役割を分けると分かりやすい。
レコードをコピーするかどうかを決める
groupsには元レコードへの参照が入っているため、グループ内の辞書を編集すると入力側からも同じ変更が見える。分類そのものはレコードを複製しない。分類後に自由に加工したいなら、追加時にrecord.copy()などを使う方法があるが、入れ子の共有には別途注意する。
測定値の集計だけが必要なら、レコード全体をためず、groups[key]へ数値を足す構成も選べる。平均を出すなら合計と件数だけで足りる場合がある。何を後で使うかに応じて、保存するデータを決めることがメモリ使用にも関係する。
欠損した分類キーをどう扱うか
deviceという項目が入力レコードにない場合、defaultdictはその不足を補ってくれるわけではない。record[“device”]の部分でKeyErrorになる。未知の装置名を自動で新規グループにすることと、分類項目が欠落した入力を許すことは別である。
欠損を”unknown”へまとめる設計もできるが、その文字列が実在の装置名と衝突しないかを考える。None、空文字列、前後の空白をどう扱うかも、分類する前に決めておく。勝手な正規化で別の装置を一つにまとめないようにしたい。
外へ渡すときは通常の辞書にする方法もある
集計が終わったらdict(groups)で普通の辞書へ変換できる。未登録キーを[]で読んでも新しいグループが増えなくなるため、後段を読み取り中心にしたい場合に分かりやすい。ただし、内側のリストは共有されたままであり、読み取り専用になるわけではない。
辞書のグループ順は最初にそのキーが登場した順になる。装置名順に表示したいなら、表示時にsorted(groups)でキーを並べる。分類の処理とレポートの順序を分けることで、入力順を保持したい用途にも対応しやすい。
確認では、一件だけのグループ、複数件のグループ、未登録キー、空入力を試す。特にgetと[]の違いを知っておくと、集計が終わった後に「なぜ空の装置が増えたのか」という疑問を解きやすい。
動作確認と参考資料
掲載例はLinux・CPython 3.12.14で動作確認した。OS固有のコマンドや環境ごとに変わるパスは、本文中の条件を確認して使ってほしい。
