CHAPTER 2 ・ データ構造を使いこなす / Lesson 06
辞書を使いこなす(集計・Counter)
「ないキーを聞いても止まらない」get、部署別の合計、出現回数の数え上げ。辞書は集計の道具です。
このレッスンが終わったら
「辞書は、キーごとの集計表として使える。get や defaultdict、Counter を使うと、KeyError なしで数えられる」と言える。
集計しようとすると、止まってしまう
「商品ごとに、何件売れたか」「得意先ごとの売上は、いくらか」。このように、キーごとに数える・足す作業を、集計と呼びます。辞書は「名前(キー)と値の対応表」なので、集計表にぴったりの入れ物です。
sales_detail.csv(4〜9月の売上明細、128行)で、商品ごとの件数を数えてみましょう。まず、基礎編の知識だけで、素直に書いてみます。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
KeyError: 'P05' と出て止まりました。KeyErrorは「その名前(キー)は、辞書にありません」という意味です。最初の明細の商品は P05 ですが、空の辞書には、まだ P05 の項目がありません。足し算(+= 1)をする前に、まず「今の値」を読もうとして、失敗しています。
基礎編の知識で直すなら、「なければ、先に 0 を入れる」と書きます(rows は上の Run と同じ、CSVを読んだ結果です)。
counts = {}
for row in rows:
code = row["商品コード"]
if code not in counts:
counts[code] = 0
counts[code] += 1これで動きます。ただ、「なければ作る」の3行を、集計のたびに書くのは大変です。辞書には、この手間を減らす道具が用意されています。
3つの道具で、数えてみる
同じ「商品ごとの件数」を、3つの道具で数えます。結果は、すべて同じになります。
get:counts.get(code, 0)は、「codeがあればその値、なければ 0」を返します。KeyError は出ません。defaultdict:ないキーを読んだとき、自動で初期値を作る辞書です。最初に初期値を決めておくと、+= 1と書くだけで済みます。Counter:数えるための専用の辞書です。数える対象を渡すだけで、数えてくれます。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
3つとも、P02 が43件、P03 が34件、…と同じ数字になりましたか。最後の行の most_common(2) は、Counter だけの機能で、多い順に2件を返します。(dict(by_default) と書いているのは、defaultdict という名札を外して、ふつうの辞書の形で表示するためです。)
仕組みを読む
まず、いちばん基本の get を、部品に分けて見ます。
counts[code]書き込む先(code の項目)counts.get(辞書から読む(なくても止まらない)code探すキー0ないときに返す値) + 1読んだ値に 1 を足すfor row in rows:明細を1行ずつ取り出して code = row["商品コード"]その行の商品コードを取り出し counts[code] = counts.get(code, 0) + 1今の件数(なければ 0)に 1 を足して、書き戻します日本語にすると
Step 1 の if code not in counts: と見比べてください。3行が1行になりました。in は「そのキーが辞書にあるか」を調べる書き方で、値ではなくキーを調べます。
変えて、試す
① 件数ではなく、金額を足して、上位を出す。 得意先ごとの売上は「数量 × 単価」の合計です。CSVの値は文字なので、int で数字にしてから掛けます。items() は、辞書を「(キー, 値)」の組として取り出します。これを sorted で金額順に並べます(key=lambda は Lesson 05 の書き方です)。最後に、明細の合計と一致するかの検算もします。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
最後の2つの数字(辞書の合計と、明細から直接求めた合計)が同じなら、取りこぼしはありません。
② 名前を引く。 コードから名前を引く辞書は、実務で毎日のように使います。登録のないコードを [ ] で読むと KeyError です。get なら、代わりの値を返せます。
▶ 実行を押すと、ここに結果が出ます
▶ 実行を押すと、ここに結果が出ます
③ 辞書をまとめる。 | で、2つの辞書を1つにできます。同じキーがあれば、右側が優先されます。
▶ 実行を押すと、ここに結果が出ます
自分で書く
お題① sales_detail.csv で、商品コード P03 が何件あるかを、表示してください。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
Counter(row["商品コード"] for row in rows) で数えます。作った Counter から counts["P03"] で取り出します。
答えの例を見る
import csv
from collections import Counter
with open("sales_detail.csv", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
counts = Counter(row["商品コード"] for row in rows)
print(counts["P03"])お題② 得意先ごとの売上合計(数量 × 単価)を求め、上位3社を [(得意先コード, 合計), ...] の形で表示してください。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
defaultdict(int) に、数量 × 単価を足していきます。sorted(totals.items(), key=lambda kv: kv[1], reverse=True) で並べ、[:3] で上位3つです。
答えの例を見る
import csv
from collections import defaultdict
with open("sales_detail.csv", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
totals = defaultdict(int)
for row in rows:
totals[row["得意先コード"]] += int(row["数量"]) * int(row["単価"])
ranking = sorted(totals.items(), key=lambda kv: kv[1], reverse=True)
print(ranking[:3])お題③ customers.csv から「得意先コード → 得意先名」の辞書を作り、C003 と、登録のない C999 の名前を、1行に並べて表示してください。C999 は「(未登録)」と表示します。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
print(names.get("C003", "(未登録)"), names.get("C999", "(未登録)")) の形です。print に2つ渡すと、間に空白が入ります。
答えの例を見る
import csv
with open("customers.csv", encoding="utf-8") as f:
names = {row["得意先コード"]: row["得意先名"] for row in csv.DictReader(f)}
print(names.get("C003", "(未登録)"), names.get("C999", "(未登録)"))仕事ならどう使う?
理解チェックd = {"A": 1} のとき、d.get("B", 0) + 1 の結果は?
理解チェックfrom collections import defaultdict して d = defaultdict(int) とし、d["X"] を print したあと、len(d) は?