目次
🌟 Python集計でよくある「迷い」
Pythonでデータ集計をしていると、ふとこんな疑問が湧いてきます。
「この処理、
groupby
とpivot_table
どっち使えばいいんだろう…?」
短くスマートに書きたい一方で、 「処理を追加したい」「欠損にも対応したい」となってくると、 見た目の短さだけでは済まされない現実が立ちはだかります。
この記事では、実際のコードとともに、 **“スマートな集計処理とは何か?”**を探り下げていきます。
✅ 3ステップ構成のgroupby集計(件数+点数合計)
まずは柔軟性重視のgroupby構成をご紹介します。
この構成では、以下の3つのステップで処理を行います。 それぞれの意図と期待する効果を簡単に整理すると:
🪜 ステップの目的
- ベースの一覧を作る:ID × 項目名のユニークな組み合わせを抽出し、結果表の骨組みを準備します。
- ➤ 欠損や未出現の項目も「必ず表示」するための準備段階です。
- 集計処理を行う:内部コードが存在するデータだけを対象に、
- 「内部コードの件数(≒出現数)」
- 「点数列の合計(≒定量的評価)」 をgroupby+aggで一括集計します。
- 結合・補完して整える:最初に用意した骨組みに集計結果を左結合。
- 欠損(=集計に該当しなかった)箇所は0で補完し、
- 最終的に整形された一覧表として出力します。
この流れは、**「完全な出力表が欲しい」「集計後も追加加工したい」**という目的に非常に適しています。
# ステップ1: ID × 項目名のユニークな組み合わせをベースに
base_df = merged_df[["ID", "項目名"]].drop_duplicates()
# ステップ2: 内部コードが存在する行のみ対象に、件数+点数合計を集計
agg_df = (
merged_df[merged_df["内部コード"].notna()]
.groupby(["ID", "項目名"])
.agg(
件数=("内部コード", "count"),
点数合計=("点数", "sum")
)
.reset_index()
)
# ステップ3: 欠損値を0で補筆して、ベースと結合
result_df = pd.merge(base_df, agg_df, on=["ID", "項目名"], how="left")
result_df["件数"] = result_df["件数"].fillna(0).astype(int)
result_df["点数合計"] = result_df["点数合計"].fillna(0).astype(float)
🔍 集計結果のイメージ
ID | 項目名 | 件数 | 点数合計 |
---|---|---|---|
1 | 薬剤A | 10 | 450.0 |
2 | 薬剤B | 0 | 0.0 |
3 | 薬剤C | 3 | 120.0 |
☑ ️ 「薬剤B」のように内部コードが欠損していた項目も、件数0&点数0.0で出力される のがこの構成の強み。
💡 ワトソンの集計Tips!
count
は NaNをカウントしないので、想定する件数とのズレに注意sum
は NaNがあると合計もNaNになりがち。fillna(0)
を習慣にastype(int)
は NaNを含むとエラーになるので、fillna後に型変換するのが安全.agg()
の構文は、{"新しい列名": (元列名, 関数)}
とすると合理的なアウトプットに
⛓️ 分析で使える集計関数まとめ
データの集計では、基本的な統計処理が求められます。 以下は groupby()
などと一緒に使用できる代表的な集計関数です。
関数名 | 説明 | 使用例 |
count() | 要素の件数(NaN除く) | トランザクション回数の集計 |
size() | 要素の件数(NaN含む) | ログ件数や欠損も含めた計数 |
sum() | 合計値 | 送金額の合計 |
mean() | 平均値 | 1件あたりの平均送金額 |
max() | 最大値 | 最大送金額 |
min() | 最小値 | 最小送金額 |
nunique() | ユニークな要素の数 | アドレス数、コントラクト数 |
✅ データは量が膨大なった場合、集計処理はできるだけ軽く・効率よく書くことが重要になります。
🚀 groupby 高速化テクニック3選
Pandasのgroupby
は便利ですが、大規模データでは遅くなることも。 以下の方法で処理速度を向上できます。
1. 列を必要なものだけに絞る
df = df[["ID", "項目名", "点数"]] # 事前に必要な列だけ抽出
2. カテゴリ型に変換して処理軽量化
df["項目名"] = df["項目名"].astype("category")
3. 計算対象の行数を絞る(事前フィルタ)
filtered = df[df["内部コード"].notna()] # 欠損除外を最初に
💡 高速化のポイントは、「使う列・行だけに限定して、無駄な処理を避ける」ことです。
🔁 pivot_table で書く場合(コンパクト重視系)
pivot_df = pd.pivot_table(
data=merged_df[merged_df["内部コード"].notna()],
index=["ID", "項目名"],
values=["内部コード", "点数"],
aggfunc={"内部コード": "count", "点数": "sum"},
fill_value=0
).reset_index().rename(columns={"内部コード": "件数", "点数": "点数合計"})
📅 pivot_table の出力例
ID | 項目名 | 件数 | 点数合計 |
1 | 薬剤A | 10 | 450.0 |
3 | 薬剤C | 3 | 120.0 |
⚠️
pivot_table
は、元データに内部コードが欠けている行は出力されない ので注意。
🔍 結局「スマート」とは何なのか?
目的 | 最適な構文 |
缺損も含めて網羅的に管理 | ✅ groupby + merge |
簡易にまとめて出力 | ✅ pivot_table |
分布の傾向を確認 | ✅ crosstab |
スマートさは**「コードが短い」より、「拡張しやすい」「壊れにくい」**の方が重要な場面もあります。
🧪 おまけ:crosstab
という選択肢も
pd.crosstab(
index=[merged_df["ID"], merged_df["項目名"]],
columns=merged_df["内部コード"].notna(),
margins=False
)
- 分布の確認やカテゴリの可視化に便利
- ただし、柔軟な動程化には向かない
💟 結論:目的に合った“スマートさ”が最強
集計の目的 | ベストな方法 |
拡張性・保守性が大事 | ✅ groupbyで構造的に書く |
集計だけ済ませたい | ✅ pivot_tableで一発 |
分布の傾向を確認したい | ✅ crosstabでざっくり見る |
✍️ 最後に:未来の自分が喜ぶコードを書こう
📌 スマートさは見た目だけじゃない。
「あとから見てもわかる」「ちょっと修正しやすい」
そんなコードこそ、現場で一番有難がられる。