Pandas GroupBy:Aggregation・Transform・Apply 完全ガイド
Updated on
split-apply-combine は Pandas の得意分野ですが、agg と transform、apply の使い分けや、ソート・欠損グループ・SettingWithCopy に悩むことが多いです。
PASの流れ:
- 問題: 集計結果や行ごとの指標が欲しいだけなのに、GroupBy が分かりづらく遅い。
- あおり:
applyの乱用やas_index=Falseを忘れると、意図しない形状や重複列、遅い処理につながる。 - 解決:
aggでまとめ、transformで行単位の特徴量、applyは最小限に。ソートや欠損ラベルの扱いも明示する。
クイックリファレンス:どのメソッドを使うか
| メソッド | 戻りの形 | 適した用途 | 例 |
|---|---|---|---|
agg / aggregate | グループにつき1行 | 要約・複数メトリクス | df.groupby("team").agg(avg_score=("score","mean")) |
transform | 元と同じ長さ | 行ごとのグループ指標付与 | df["z"] = df.groupby("team")["score"].transform("zscore") |
apply | 柔軟 | agg/transformで足りない複雑処理 | df.groupby("team").apply(custom_fn) |
サンプルデータ
import pandas as pd
data = {
"team": ["A", "A", "B", "B", "B", "C"],
"player": ["x1", "x2", "y1", "y2", "y3", "z1"],
"score": [9, 7, 8, 6, 10, 3],
"minutes": [30, 25, 28, 32, 20, 15],
}
df = pd.DataFrame(data)集計パターン (agg)
summary = (
df.groupby("team", as_index=False)
.agg(
avg_score=("score", "mean"),
max_score=("score", "max"),
minutes_played=("minutes", "sum"),
)
)- Named aggregation で列名を明確に。
as_index=Falseならteamを列として保持(結合や可視化に便利)。sort=Falseで元の順番を保つ。
1列に複数集計
df.groupby("team", as_index=False).agg(
score_mean=("score", "mean"),
score_std=("score", "std"),
score_count=("score", "size"),
)行ごとの特徴量 (transform)
transform は元の行数を保ち、グループ指標を行に戻します。
df["score_pct_of_team"] = (
df["score"] / df.groupby("team")["score"].transform("sum")
)チーム内Zスコア:
df["score_z"] = (
df.groupby("team")["score"]
.transform(lambda s: (s - s.mean()) / s.std(ddof=0))
)transform を使う場面:
- グループ合計に対する比率
- 正規化スコア
- グループフラグ(
rank,cumcountなど)
apply が有効なケース
apply は柔軟だが遅くなりがち。agg/transform でできないときだけ。
def top_n(group, n=2):
return group.nlargest(n, "score")
top_players = df.groupby("team").apply(top_n, n=1).reset_index(drop=True)apply を使うのは:
- グループごとの行数が可変になるとき
- 非ベクトル化の Python ロジックが必要なとき
欠損グループとソート
result = (
df.groupby("team", dropna=False, sort=False)
.agg(avg_score=("score", "mean"))
)dropna=FalseでNaNグループを保持。sort=Falseで順序の変化を防止(順序に意味がある場合)。
複数キーとインデックスでの GroupBy
multi = (
df.groupby(["team", "player"], as_index=False)
.agg(score_mean=("score", "mean"))
)インデックスレベルでのグルーピング:
df2 = df.set_index(["team", "player"])
df2.groupby(level="team")["score"].mean()よくある落とし穴
| 問題 | 対応 |
|---|---|
想定外の形(Series vs DataFrame) | as_index=False を設定、または後で reset_index()。 |
| マージ後の列名重複 | Named aggregation で列名を制御。 |
apply で遅い | agg/transform またはベクトル化メソッドに置き換える。 |
| 出力からカテゴリーが消える | observed=False(デフォルト)を維持し、dropna=False で NaN を保持。 |
エクスポートと可視化のヒント
- 集計後の並び替え:
summary.sort_values("avg_score", ascending=False)。 - グラフ用にピボット:
summary.pivot(index="team", values="avg_score")。
関連ガイド
まとめ
- 要約は
agg、行ごとの特徴量はtransform、applyは最小限。 as_indexとreset_indexで形をコントロール。sortとdropnaで順序と欠損ラベルを扱う。- まずベクトル化メソッドを選び、パイプラインを高速に保つ。