群ごとの一覧表が欲しいならagg、群の統計量を各測定行へ戻したいならtransformを使う。transformは元の行に対応する形で値を返すので、並び順の違う集計表を位置で代入してしまう問題を避けられる。件数はsizeとcountで意味が違うことも確認しておこう。
最小例で確かめる
以下は説明用に作った小さなデータである。実測データや実行速度の測定結果ではない。コード全体をexample.pyとして保存すれば、入力ファイルを別途用意せずに実行できる。assertは、この例で成り立つべき形や値を確認するために入れてある。
import numpy as np
import pandas as pd
df = pd.DataFrame({"group": ["B", "A", "B", "A", None],
"value": [10., 1., np.nan, 3., 7.]},
index=["r4", "r1", "r3", "r2", "r5"])
g = df.groupby("group", dropna=False, sort=False, observed=True)["value"]
summary = g.agg(rows="size", valid="count", mean="mean")
row_mean = g.transform("mean")
df["group_mean"] = row_mean
df["centered"] = df["value"] - df["group_mean"]
print("group summary:")
print(summary.to_string())
print("original rows:")
print(df.to_string())
assert row_mean.index.equals(df.index)
assert len(row_mean) == len(df) == 5
assert summary.loc["B", "rows"] == 2
assert summary.loc["B", "valid"] == 1
np.testing.assert_allclose(row_mean, [10, 2, 10, 2, 7])
assert pd.isna(df.loc["r3", "centered"])
np.testing.assert_allclose(df.loc[["r1", "r2"], "centered"], [-1, 1])
実行結果
group summary:
rows valid mean
group
B 2 1 10.0
A 2 2 2.0
NaN 1 1 7.0
original rows:
group value group_mean centered
r4 B 10.0 10.0 0.0
r1 A 1.0 2.0 -1.0
r3 B NaN 10.0 NaN
r2 A 3.0 2.0 1.0
r5 None 7.0 7.0 0.0
aggは群を一行にまとめる
summaryにはB、A、欠測キーの3群が並ぶ。各群の行数rows、値が欠測でない件数valid、平均meanを名前付きで計算している。入力は5行だが、出力は群の数だけになる。元の測定行とは粒度が違うので、summaryの列をそのまま位置で測定表へ貼り付けることはできない。
sizeは群に含まれる行数、countは対象列の欠測を除いた件数である。B群には2行あるが、一方のvalueがNaNなのでvalidは1になる。平均10が「2回測った平均」のように誤読されないよう、この二種類の件数を分けると、入力品質を伝えやすい。
transformは元の行へ対応させる
g.transform(“mean”)は、各行が属する群の平均を、その行に対応するSeriesとして返す。Bの2行にはどちらも10、Aの2行にはどちらも2が入る。元のvalueが欠測だった行にも群平均は付くが、その行の観測値を自動で補完したわけではない。
df[“group_mean”]へ代入すると、Seriesのindexで対応する。例では入力indexをあえて不規則な順序にしており、戻り値が同じindexを保つことを確かめた。centeredは元の値から群平均を引いた列で、元からNaNだった行はNaNのままになる。補完と中心化を混同しないようにしたい。
欠測キーと並び順を明示する
groupbyのdropna=Falseは、群を表すキーが欠測の行も一群として含める指定である。キー不明のデータを集計から外すべき場面もあるが、その場合は除外した行数を把握する。測定値の欠測と群キーの欠測は別の問題であり、平均の分母と対象の両方へ影響する。
sort=Falseは群の表示順に関する指定であり、transformが元の行へ対応する契約とは別である。またカテゴリ型のキーを使う場合には、観測されなかったカテゴリを出すかどうかがobservedで変わる。版による既定値の違いに依存しないよう、ここでは観測された群だけに限定して明示した。
集計値を戻す別の方法との使い分け
複数列の台帳情報や、別データから作った群の統計量を戻すならmergeやmapが適していることもある。その場合は群キーの一意性と未対応キーを検査する。同じDataFrame内で、平均や件数などを各行へ付けたいだけなら、transformで対応付けまでまとめられるので読みやすい。
群平均を使う解析では、群の定義が目的に合っているかも考える。予測モデルの評価でテストデータを含めた平均を作ると、将来の情報が前処理へ混ざることがある。単に行数とindexが正しいだけでなく、どのデータから統計量を計算してよいかを決める必要がある。
全欠測の群では平均も欠測になる。無条件に0で置き換えず、有効件数を確認して除外・保留・補完の方針を分けよう。小例では平均、件数、元の行順、欠測の保存を同時に確認することで、集計が成功したというだけの検査よりも取り違えを見つけやすくしている。
動作確認環境と参考資料
Linux・CPython 3.12.14、NumPy 2.3.5、pandas 2.2.3、SciPy 1.17.0、Matplotlib 3.10.8の環境で掲載コードを実行した。使用するライブラリはコード冒頭のimportを参照してほしい。公式資料の最新版と、この実行確認版は区別している。数値の末尾や表の表示幅は環境によって変わることがある。
