Pandas groupby-transform() 関数
groupby.transform()は、Pandas でグループ化した後にデータ変換を行うための関数です。集計関数とは異なり、集計はグループごとの要約結果を返しますが、変換は元のデータと同じ長さの結果を返します。。
簡単に言えば、transform()はグループ計算の結果を元のデータの各行に「ブロードキャスト」します。これは、グループ内での各メンバーの相対位置の計算、グループ内標準化、グループ平均との差の計算などの場面で非常に役立ちます。
基本構文とパラメータ
transform()は GroupBy オブジェクトのメンバー関数であり、最初にgroupby()でグループ化してから呼び出す必要があります。
構文形式
GroupBy.transform(func, axis=0, *args, engine=None, engine_kwargs=None, **kwargs)
パラメータの説明
| パラメータ | 型 | 説明 | デフォルト値 |
|---|---|---|---|
| func | str、list、dict または callable | 変換関数。組み込みの関数名('mean', 'sum', 'std' など)、関数のリスト、列ごとに異なる関数を指定する辞書、またはカスタム関数を指定できます。 | None |
| axis | int | 変換を適用する軸の方向。0 は行方向(グループ化の次元)、1 は列方向を表します。 | 0 |
| args | tuple | 変換関数に渡す追加の位置引数。 | () |
| engine | str | 計算エンジンを指定します。'cython' または 'numba' を指定できます。None は Pandas が自動選択することを示します。 | None |
| engine_kwargs | dict | 基盤となるエンジンに渡す追加の引数の辞書。 | None |
戻り値
- 戻り値の型:
Series或DataFrame - 説明:元のデータと同じ行数の結果を返します。元の各行は、その行が属するグループの計算結果を取得します。
実例
簡単なものから複雑なものまでの一連の例を通して、groupby.transform()の使い方を完全にマスターしましょう。
例1:基本の使い方 - グループ内平均の計算
transform()最も基本的な使い方は、各グループの平均を計算し、その結果を各行にブロードキャストすることです。
実例
# 学生の成績データを作成
data = {
'氏名': ['張三', '李四', '王五', '趙六', '孫七', '周八', '呉九', '鄭十'],
'クラス': ['A', 'A', 'A', 'B', 'B', 'B', 'B', 'A'],
'国語': [85, 92, 78, 88, 95, 82, 90, 87],
'数学': [90, 85, 92, 78, 88, 91, 85, 89]
}
# DataFrameを作成
df = pd.DataFrame(data)
print("学生の成績データ:")
print(df)
print()
# 各クラスの数学の平均点を計算し、各行にブロードキャスト
df['クラス数学平均点'] = df.groupby('クラス')['数学'].transform('mean')
# 各学生の成績とクラス平均の差を計算
df['平均との差'] = df['数学'] - df['クラス数学平均点']
print("クラス平均点と差を追加したデータ:")
print(df)
print()
# 複数列のクラス平均を同時に計算
df[['クラス国語平均点', 'クラス数学平均点2']] = df.groupby('クラス')[['国語', '数学']].transform('mean')
print("複数列の平均を追加したデータ:")
print(df)
実行結果:
学生成绩数据: 姓名 班级 语文 数学 0 张三 A 85 90 1 李四 A 92 85 2 王五 A 78 92 3 赵六 B 88 78 4 孙七 B 95 88 5 周八 B 82 91 6 吴九 B 90 85 7 郑十 A 87 89 添加班级平均分和差值后的数据: 姓名 班级 语文 数学 班级数学平均分 与平均分差值 0 张三 A 85 90 89.000000 1.000000 1 李四 A 92 85 89.000000 -4.000000 2 王五 A 78 92 89.000000 3.000000 3 赵六 B 88 78 85.666667 -7.666667 4 孙七 B 95 88 85.666667 2.333333 5 周八 B 82 91 85.666667 5.333333 6 吴九 B 90 85 85.666667 -0.666667 7 郑十 A 87 89 89.000000 0.000000 添加多列平均值后的数据: 姓名 班级 语文 数学 班级语文平均分 班级数学平均分2 0 张三 A 85 90 85.500000 89.0 1 李四 A 92 85 85.500000 89.0 2 王五 A 78 92 85.500000 89.0 3 赵六 B 88 78 88.750000 85.5 4 孙七 B 95 88 88.750000 85.5 5 周八 B 82 91 88.750000 85.5 6 吴九 B 90 85 88.750000 85.5 7 郑十 A 87 89 85.500000 89.0
コードの解説:
transform('mean')各クラスの数学の平均点を計算します。- 張三、李四、王五、鄭十はすべてAクラスなので、彼らが得る「クラス数学平均点」はAクラスの数学の平均点になります。
- 戻り値の長さは元のデータの行数と同じです。これが transform と agg の重要な違いです。
例2:グループ内標準化(Z-Score)
transform()グループ内の標準化値の計算によく使われ、これはグループ内での個人の相対位置を比較する際に非常に役立ちます。
実例
# 従業員の給与データを作成
data = {
'氏名': ['張三', '李四', '王五', '趙六', '孫七', '周八', '呉九', '鄭十'],
'部門': ['営業', '営業', '営業', '技術', '技術', '技術', '総務', '総務'],
'給与': [5000, 8000, 6000, 9000, 12000, 10000, 4500, 7000]
}
df = pd.DataFrame(data)
print(従業員の給与データ:)
print(df)
print()
# 各従業員の給与の部門内Z-Score(標準化スコア)を計算
# Z-Score = (元の値 - グループ内平均) / グループ内標準偏差
df['部門平均給与'] = df.groupby('部門')['給与'].transform('mean')
df['部門給与標準偏差'] = df.groupby('部門')['給与'].transform('std')
df['Z-Score'] = (df['給与'] - df['部門平均給与']) / df['部門給与標準偏差']
print("部門統計とZ-Scoreを追加したデータ:")
print(df)
print()
# カスタム関数を使ってZ-Scoreを一度に計算
def z_score(x):
"""Z-Scoreを計算"""
return (x - x.mean()) / x.std()
df['Z-Score2'] = df.groupby('部門')['給与'].transform(z_score)
print("カスタム関数で計算したZ-Score:")
print(df[['氏名', '部門', '給与', 'Z-Score2']])
実行結果:
员工工资数据: 姓名 部门 工资 0 张三 销售 5000 1 李四 销售 8000 2 王五 销售 6000 3 赵六 技术 9000 4 孙七 技术 12000 5 周八 技术 10000 6 吴九 行政 4500 7 郑十 行政 7000 添加部门统计和Z-Score后的数据: 姓名 部门 工资 部门平均工资 部门工资标准差 Z-Score 0 张三 销售 5000 6333.333333 1525.755 -0.872871 1 李四 销售 8000 6333.333333 1525.755 1.092091 2 王五 销售 6000 6333.333333 1525.755 -0.218218 3 赵六 技术 9000 10333.333333 1525.755 -0.872871 4 孙七 技术 12000 10333.333333 1525.755 1.092091 5 周八 技术 10000 10333.333333 1525.755 -0.218218 6 吴九 行政 4500 5750.000000 1767.767 -0.707107 7 郑十 行政 7000 5750.000000 1767.767 0.707107 使用自定义函数计算的 Z-Score: 姓名 部门 工资 Z-Score2 0 张三 销售 5000 -0.872871 1 李四 销售 8000 1.092091 2 王五 销售 6000 -0.218218 3 赵六 技术 9000 -0.872871 4 孙七 技术 12000 1.092091 5 周八 技术 10000 -0.218218 6 吴九 行政 4500 -0.707107 7 郑十 行政 7000 0.707107
コードの解説:
- Z-Score は元の値が平均から標準偏差いくつ分離れているかを表し、正の値は平均より高いこと、負の値は平均より低いことを示します。
- Z-Score を使用することで、部門間の給与水準の違いの影響を排除し、従業員の部門内での相対的な位置を比較できます。
- 張三の Z-Score は -0.87 で、彼の給与が営業部門の平均より約0.87標準偏差低いことを示しています。
例3:グループ内ランキングとパーセンタイル
transform()グループ内の順位付けの計算にも使用でき、これは順位付けや競争分析で非常に役立ちます。
実例
import numpy as np
# 学生の成績データを作成
data = {
'氏名': ['張三', '李四', '王五', '趙六', '孫七', '周八', '呉九', '鄭十'],
'クラス': ['A', 'A', 'A', 'B', 'B', 'B', 'B', 'A'],
'数学': [90, 85, 92, 78, 88, 91, 85, 89]
}
df = pd.DataFrame(data)
print("学生の成績データ:")
print(df)
print()
# 各学生のクラス内順位を計算(高い順)
df['クラス内順位'] = df.groupby('クラス')['数学'].transform(lambda x: x.rank(ascending=False))
# 各学生のクラス内パーセンタイル(順位の百分率)を計算
df['クラス内パーセンタイル'] = df.groupby('クラス')['数学'].transform(lambda x: x.rank(pct=True))
print("クラス内順位とパーセンタイルを追加したデータ:")
print(df)
print()
# 欠損値の補完におけるもう一つの使い方:グループ内平均で補完
data_with_na = {
'氏名': ['張三', '李四', '王五', '趙六', '孫七', '周八'],
'クラス': ['A', 'A', 'A', 'B', 'B', 'B'],
'成績': [85, np.nan, 78, 88, np.nan, 82]
}
df_na = pd.DataFrame(data_with_na)
print("欠損値を含むデータ:")
print(df_na)
print()
# グループ内平均を使用して欠損値を補完
df_na['補完後の成績'] = df_na.groupby('クラス')['成績'].transform(lambda x: x.fillna(x.mean()))
print("グループ内平均で補完した結果:")
print(df_na)
実行結果:
学生成绩数据: 姓名 班级 数学 0 张三 A 90 1 李四 A 85 2 王五 A 92 3 赵六 B 78 4 孙七 B 88 5 周八 B 91 6 吴九 B 85 7 郑十 A 89 添加班级内排名和百分位后的数据: 姓名 班级 数学 班级内排名 班级内百分位 0 张三 A 90 2.0 0.666667 1 李四 A 85 3.0 1.000000 2 王五 A 92 1.0 1.000000 3 赵六 B 78 4.0 0.250000 4 孙七 B 88 2.0 0.500000 5 周八 B 91 1.0 0.750000 6 吴九 B 85 3.0 0.500000 7 郑十 A 89 4.0 0.333333 包含缺失值的数据: 姓名 班级 成绩 0 张三 A 85.0 1 李四 A NaN 2 王五 A 78.0 3 赵六 B 88.0 4 孙七 B NaN 5 周八 B 82.0 使用组内均值填充后的结果: 姓名 班级 成绩 填充后的成绩 0 张三 A 85.0 85.0 1 李四 A NaN 81.5 2 王五 A 78.0 78.0 3 赵六 B 88.0 88.0 4 孙七 B NaN 85.0 5 周八 B 82.0 82.0
コードの解説:
rank(ascending=False)順位を計算し、数値が大きいものが前になります。rank(pct=True)パーセンタイル順位を計算し、結果は0から1の間になります。transform(lambda x: x.fillna(x.mean()))は欠損値を処理する一般的なパターンで、各グループの平均値を使ってそのグループ内の欠損値を補完します。
例4:カスタム変換関数を使用
transform()カスタム関数をサポートしており、より複雑なグループ内データ変換ロジックを実装できます。
実例
import numpy as np
# 株価データを作成
data = {
'株式コード': ['AAPL', 'AAPL', 'AAPL', 'AAPL', 'GOOG', 'GOOG', 'GOOG', 'GOOG'],
'日付': ['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04'] * 2,
'終値': [100, 105, 102, 110, 80, 85, 82, 90]
}
df = pd.DataFrame(data)
print("株価データ:")
print(df)
print()
# カスタム関数:各銘柄の日次収益率を計算
def calc_daily_return(prices):
"""日次収益率を計算:(今日の価格 - 昨日の価格) / 昨日の価格"""
return prices.pct_change()
# 各銘柄の日次収益率を計算
df['日次収益率'] = df.groupby('株式コード')['終値'].transform(calc_daily_return)
print("日次収益率を追加した後のデータ:")
print(df)
print()
# カスタム関数:グループ内の最大値と最小値による正規化値を計算
def normalize(x):
"""正規化:(x - min) / (max - min)"""
return (x - x.min()) / (x.max() - x.min())
# 各銘柄の株価の正規化値を計算
df['正規化価格'] = df.groupby('株式コード')['終値'].transform(normalize)
print("正規化価格を追加した後のデータ:")
print(df)
実行結果:
股票价格数据: 股票代码 日期 收盘价 0 AAPL 2024-01-01 100 1 AAPL 2024-01-02 105 2 AAPL 2024-01-03 102 3 AAPL 2024-01-04 110 4 GOOG 2024-01-01 80 5 GOOG 2024-01-02 85 6 GOOG 2024-01-03 82 7 GOOG 2024-01-04 90 添加日收益率后的数据: 股票代码 日期 收盘价 日收益率 0 AAPL 2024-01-01 100 NaN 1 AAPL 2024-01-02 105 0.050000 2 AAPL 2024-01-03 102 -0.028571 3 AAPL 2024-01-04 110 0.078431 4 GOOG 2024-01-01 80 NaN 5 GOOG 2024-01-02 85 0.062500 6 GOOG 2024-01-03 82 -0.035294 7 GOOG 2024-01-04 90 0.097561 添加归一化价格后的数据: 股票代码 日期 收盘价 日收益率 归一化价格 0 AAPL 2024-01-01 100 NaN 0.000000 1 AAPL 2024-01-02 105 0.050000 0.500000 2 AAPL 2024-01-03 102 -0.028571 0.200000 3 AAPL 2024-01-04 110 0.078431 1.000000 4 GOOG 2024-01-01 80 NaN 0.000000 5 GOOG 2024-01-02 85 0.062500 0.500000 6 GOOG 2024-01-03 82 -0.035294 0.200000 7 GOOG 2024-01-04 90 0.097561 1.000000
コード解説:
- 変換関数が受け取るのはSeries(各グループ内のデータ)です。
- 返されるSeriesの長さは、入力されたSeriesの長さと同じである必要があります。
- 最初の値がNaNである理由は、
pct_change()相対変化を計算しているため、初日には前日のデータがないからです。
重要な違い:
agg()とtransform()の違いは次のとおりです:agg()各グループの集計結果を返します(行数はグループ数に等しい)。一方、transform()元のデータと同じ長さの結果を返します。簡単に覚えるには:aggはデータを圧縮し、transformはデータの形状を維持します。
その他の拡張
Pandas よく使う関数