Skip to content

Pandas GroupBy:Aggregation・Transform・Apply 完全ガイド

Updated on

split-apply-combine は Pandas の得意分野ですが、aggtransformapply の使い分けや、ソート・欠損グループ・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=FalseNaN グループを保持。
  • 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 DataFrameas_index=False を設定、または後で reset_index()
マージ後の列名重複Named aggregation で列名を制御。
apply で遅いagg/transform またはベクトル化メソッドに置き換える。
出力からカテゴリーが消えるobserved=False(デフォルト)を維持し、dropna=FalseNaN を保持。

エクスポートと可視化のヒント

  • 集計後の並び替え: summary.sort_values("avg_score", ascending=False)
  • グラフ用にピボット: summary.pivot(index="team", values="avg_score")

関連ガイド


まとめ

  • 要約は agg、行ごとの特徴量は transformapply は最小限。
  • as_indexreset_index で形をコントロール。
  • sortdropna で順序と欠損ラベルを扱う。
  • まずベクトル化メソッドを選び、パイプラインを高速に保つ。