CHAPTER 6 ・ pandas を一歩進める / Lesson 24
ピボットと集計の応用
pivot_table でクロス集計、agg で複数の指標、cumsum で累計。ピボットテーブルをコードで再現します。
このレッスンが終わったら
「pivot_table で、行と列を指定したクロス集計ができる。agg で合計・平均・件数を一度に出せる」と言える。
こんな場面、ありませんか?
上司から、「商品ごと・地域ごとの売上が、一覧で見たい」と頼まれました。基礎編の groupby なら、地域別、商品別と、1つの切り口ずつは出せます。
まず、準備をします。Lesson 23 で学んだ結合で、売上明細に得意先の地域と商品名を付け、金額(数量×単価)の列と、月の列(日付の先頭7文字、2025-04 の形)を作ります。日付を日付として扱う方法は、Lesson 25 で学びます。この準備のコードは、各コード欄が単独で動くように、必要な行を毎回書き直して使います。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
それぞれは出ましたが、「モニターは、東京でいくら売れたのか」は、この2つの表からは分かりません。商品を行に、地域を列に並べた表が必要です。このような表をクロス集計と呼び、Excelでは、ピボットテーブルで作ります。pandas では、pivot_table で作れます。
pivot_table で、クロス集計表を作る
商品を行に、地域を列に並べて、金額を合計します。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
商品ごと・地域ごとの合計が並び、右端と下端に「合計」の行と列が付きました。右下の 14687500 は、全体の売上合計で、df["金額"].sum() と一致します。
仕組みを読む
pivot_table は、Excelのピボットテーブルで「行」「列」「値」のボックスに入れる項目を、そのまま文字で書いたものです。
index="商品名"行に並べる列columns="地域"列に並べる列values="金額"集計する値aggfunc="sum"集計方法(合計)fill_value=0該当なしを 0 にするmargins=True合計の行と列を付けるindex="商品名"商品名の種類ごとに、1行ずつ作るcolumns="地域"地域の種類ごとに、1列ずつ作るvalues="金額", aggfunc="sum"行と列の組み合わせごとに、金額を合計するfill_value=0その組み合わせの売上がないところは、空欄ではなく 0 にするmargins=True, margins_name="合計"いちばん下に合計の行、いちばん右に合計の列を足し、名前を「合計」にする日本語にすると
集計方法(aggfunc)には、"sum"(合計)のほか、"count"(件数)、"mean"(平均)、"max"(最大)、"min"(最小)が使えます。Excelのピボットテーブルで、「合計」を「平均」に切り替えるのと同じです。
変えて、試す
① 複数の指標を、一度に出す。 得意先ごとに、合計・件数・平均を並べます。agg の中で、列名=("対象の列", "集計方法") と書くと、出てくる列に好きな名前を付けられます。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
round(0) は、平均の小数を丸めて見やすくしました。合計は大きいのに、平均は小さい得意先は、「少額の取引が多い」と読めます。
② 得意先 × 月 のクロス集計。 列に「月」を使うと、月ごとの推移が横に並びます。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
アオイ電機の4月や、スミレ運輸の9月が 0 なのは、その月に取引がなかったからです。fill_value=0 を付けないと、NaN(欠損値)になります。
③ 累計を出す。 月ごとの合計を出し、cumsum() で、それまでの合計を足し上げていきます。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
最後の行の累計 14687500 は、全体の合計と同じです。年度の目標に対して、いくらまで進んだかを見るときに使います。
④ 順位と構成比。 上位の得意先を取り出すには nlargest、順位を付けるには rank、全体に占める割合は「個別 ÷ 全体の合計」で出します。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
構成比の合計は、四捨五入のため、ちょうど 100 にならないことがあります。報告書に載せるときは、「端数処理の関係で、合計が 100 にならない場合があります」と注記します。
自分で書く
お題① 商品名を行に、地域を列にして、数量の合計のクロス集計を作り、「モニター」の「東京」の数量を表示してください。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
table = df.pivot_table(index="商品名", columns="地域", values="数量", aggfunc="sum") で作ります。行と列の名前で取り出すには、table.loc["モニター", "東京"] と書きます。
答えの例を見る
import pandas as pd
df = pd.read_csv("sales_detail.csv")
df = df.merge(pd.read_csv("customers.csv"), on="得意先コード", how="left")
df = df.merge(pd.read_csv("products.csv")[["商品コード", "商品名"]], on="商品コード", how="left")
table = df.pivot_table(index="商品名", columns="地域", values="数量", aggfunc="sum")
print(table.loc["モニター", "東京"])お題② 得意先ごとの伝票の件数を数え、件数がいちばん多い得意先名を表示してください。(ヒント:idxmax() は、最大の値がある行の名前を返します)
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
df.groupby("得意先名")["伝票番号"].count() で件数が出ます。その結果に .idxmax() を付けると、最大の得意先名が取り出せます。
答えの例を見る
import pandas as pd
df = pd.read_csv("sales_detail.csv")
df = df.merge(pd.read_csv("customers.csv"), on="得意先コード", how="left")
counts = df.groupby("得意先名")["伝票番号"].count()
print(counts.idxmax())お題③ 商品名ごとの金額(数量×単価)の合計から、「モニター」の構成比(%)を、小数第1位まで表示してください。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
total = df.groupby("商品名")["金額"].sum() で合計を出し、total["モニター"] / total.sum() * 100 を計算します。round(..., 1) で小数第1位にします。
答えの例を見る
import pandas as pd
df = pd.read_csv("sales_detail.csv")
df = df.merge(pd.read_csv("products.csv")[["商品コード", "商品名"]], on="商品コード", how="left")
df["金額"] = df["数量"] * df["単価"]
total = df.groupby("商品名")["金額"].sum()
print(round(total["モニター"] / total.sum() * 100, 1))仕事ならどう使う?
理解チェックpivot_table で、「行に得意先、列に月、中身は金額の合計」にしたいとき、正しい組み合わせは?
理解チェック月ごとの売上が [100, 200, 150] のとき、cumsum()(累計)の結果は、どれでしょう?