一覧へ

groupby — スプリット-適用-結合パターン

pandas groupbyの動作原理をスプリット-適用-結合として理解し、実践的な集約パターンを学びます。

中級
|
10
|
検証済み (2026-07)
groupbySplit-Apply-Combine集約グループ別演算pandas
進捗0/17 (0%)

groupby — スプリット-適用-結合パターン

このトピックを修了すると

pandasのgroupby()の内部動作をスプリット-適用-結合パターンとして説明でき、実務でよく使われるグループごとの集計、変換、フィルタリングのパターンを習得します。


グループごとの演算が必要な理由

「全体の平均」はdf["score"].mean()で一行で済みます。しかし、実務上の質問はほとんど次のような形です。

  • 部署別の平均年収は?
  • 月別の売上合計は?
  • カテゴリ別で最も売れた商品は?

「〜別」と付く瞬間、グループごとの演算が必要になります。これがgroupby()の役割です。


スプリット-適用-結合パターン

Hadley Wickhamが2011年に名付けたこのパターンは、データ分析で最も頻繁に繰り返される思考構造です。

text
元のデータ
┌────────┬──────┬───────┐
│ dept   │ name │ score │
├────────┼──────┼───────┤
│ Sales  │ Alice│   85  │
│ Sales  │ Bob  │   90  │
│ Dev    │ Carol│   95  │
│ Dev    │ Dave │   88  │
│ HR     │ Eve  │   78  │
└────────┴──────┴───────┘

1. SPLIT — 部署ごとに分割
   Sales: [85, 90]
   Dev:   [95, 88]
   HR:    [78]

2. APPLY — 各グループに関数を適用 (例: 平均)
   Sales: 87.5
   Dev:   91.5
   HR:    78.0

3. COMBINE — 結果を1つに結合
   ┌────────┬───────┐
   │ dept   │ score │
   ├────────┼───────┤
   │ Dev    │  91.5 │
   │ HR     │  78.0 │
   │ Sales  │  87.5 │
   └────────┴───────┘

pandasでは、この3つのステップが1行で済みます。

python
df.groupby("dept")["score"].mean()

基本的な使い方

python
import pandas as pd
df = pd.DataFrame({
"dept": ["Sales", "Sales", "Dev", "Dev", "HR"],
"name": ["Alice", "Bob", "Carol", "Dave", "Eve"],
"score": [85, 90, 95, 88, 78],
"salary": [50000, 52000, 70000, 68000, 45000]
})
# 部署ごとのスコア平均
df.groupby("dept")["score"].mean()
# dept
# Dev 91.5
# HR 78.0
# Sales 87.5
# 部署ごとの給与合計
df.groupby("dept")["salary"].sum()
# dept
# Dev 138000
# HR 45000
# Sales 102000

GroupByオブジェクトとは

python
grouped = df.groupby("dept")
print(type(grouped)) # <class 'pandas.core.groupby.DataFrameGroupBy'>

groupby()はすぐに計算を実行しません。遅延評価(lazy evaluation) — 何らかの関数(mean、sumなど)を適用するときに初めて計算が実行されます。これは、大量のデータで不要な演算を防ぎます。

グループの内容を確認

python
for name, group in df.groupby("dept"):
print(f"\n--- {name} ---")
print(group)
# --- Dev ---
# dept name score salary
# 2 Dev Carol 95 70000
# 3 Dev Dave 88 68000
# --- HR ---
# ...

複数の集計関数

1つのカラム、複数の関数

python
df.groupby("dept")["score"].agg(["mean", "min", "max", "count"])
# mean min max count
# dept
# Dev 91.5 88 95 2
# HR 78.0 78 78 1
# Sales 87.5 85 90 2

カラムごとに異なる関数

python
df.groupby("dept").agg(
avg_score=("score", "mean"),
total_salary=("salary", "sum"),
headcount=("name", "count")
)
# avg_score total_salary headcount
# dept
# Dev 91.5 138000 2
# HR 78.0 45000 1
# Sales 87.5 102000 2

名前付き集計(カラム名=("元のカラム", "関数"))は、結果の列名まで一度に指定できるため、最もきれいな方法です。


複数のカラムでグループ化

python
sales = pd.DataFrame({
"region": ["East", "East", "West", "West", "East", "West"],
"category": ["A", "B", "A", "B", "A", "A"],
"revenue": [100, 200, 150, 250, 120, 180]
})
sales.groupby(["region", "category"])["revenue"].sum()
# region category
# East A 220
# B 200
# West A 330
# B 250

リストで複数のカラムを渡すと、**階層的インデックス(MultiIndex)**が生成されます。

python
# MultiIndexを通常のカラムにする
result = sales.groupby(["region", "category"])["revenue"].sum().reset_index()
# region category revenue
# 0 East A 220
# 1 East B 200
# 2 West A 330
# 3 West B 250

reset_index()は、グループキーをインデックスから通常のカラムに戻します。後続の作業(可視化、保存など)では、この形式の方が便利です。


transform — グループごとの変換

agg()はグループごとに1つの値を返します(5行 → 3行)。transform()は元のものと同じサイズの返します(5行 → 5行)。

python
# 部署ごとの平均を元のデータに付加
df["dept_avg"] = df.groupby("dept")["score"].transform("mean")
print(df)
# dept name score salary dept_avg
# 0 Sales Alice 85 50000 87.5
# 1 Sales Bob 90 52000 87.5
# 2 Dev Carol 95 70000 91.5
# 3 Dev Dave 88 68000 91.5
# 4 HR Eve 78 45000 78.0

各行に、その部署の平均が付加されます。これで、部署平均とのスコアの差を計算できます。

python
df["vs_avg"] = df["score"] - df["dept_avg"]
# Alice: 85 - 87.5 = -2.5 (部署平均以下)
# Carol: 95 - 91.5 = +3.5 (部署平均以上)

filter — グループごとのフィルタリング

条件を満たすグループ全体を残すか、削除します。

python
# 人数が2人以上の部署のみ
df.groupby("dept").filter(lambda g: len(g) >= 2)
# dept name score salary
# 0 Sales Alice 85 50000
# 1 Sales Bob 90 52000
# 2 Dev Carol 95 70000
# 3 Dev Dave 88 68000

HR(1人)がまとめて削除されました。個々の行ではなく、グループ単位でフィルタリングするのがfilter()の核心です。


実践パターン — 月別売上分析

python
orders = pd.DataFrame({
"date": pd.to_datetime([
"2026-01-05", "2026-01-15", "2026-02-03",
"2026-02-20", "2026-03-10", "2026-03-25"
]),
"product": ["Widget", "Gadget", "Widget", "Gadget", "Widget", "Widget"],
"amount": [1200, 800, 1500, 900, 1100, 1300]
})
# 月別売上合計
monthly = orders.groupby(orders["date"].dt.to_period("M"))["amount"].sum()
print(monthly)
# date
# 2026-01 2000
# 2026-02 2400
# 2026-03 2400
# 商品ごとの月平均売上
orders.groupby("product").agg(
total=("amount", "sum"),
avg=("amount", "mean"),
count=("amount", "count")
)
# total avg count
# Gadget 1700 850.0 2
# Widget 5100 1275.0 4

日付データから.dt.to_period("M")で月単位のグループを作成することは、非常に頻繁に使用されるパターンです。


主要なまとめ

メソッド動作結果のサイズ
agg()グループごとの集計 (sum, mean, count...)グループの数だけ (減少)
transform()グループごとの変換 (元のサイズを維持)元と同じ
filter()条件を満たすグループ全体を維持元以下

groupby()は、pandasで最も強力で、最も頻繁に使用される機能です。スプリット-適用-結合パターンを頭の中で描くことができれば、複雑な集計も「どのカラムで分割し(スプリット)、どのような関数を適用し(適用)、結果をどのような形式で結合するか(結合するか)」という3つのステップに自然に分解できます。


💬 質問・コメント

0件のコメント

ログインせずに投稿できます。ゲスト投稿は投稿者自身で編集・削除できません。

0/2000

読み込み中...