Pandas groupby-transform() 関数

Pandas 常用函数Pandas よく使う関数


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()最も基本的な使い方は、各グループの平均を計算し、その結果を各行にブロードキャストすることです。

実例

import pandas as pd

# 学生の成績データを作成
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

コードの解説:

  1. transform('mean')各クラスの数学の平均点を計算します。
  2. 張三、李四、王五、鄭十はすべてAクラスなので、彼らが得る「クラス数学平均点」はAクラスの数学の平均点になります。
  3. 戻り値の長さは元のデータの行数と同じです。これが transform と agg の重要な違いです。

例2:グループ内標準化(Z-Score)

transform()グループ内の標準化値の計算によく使われ、これはグループ内での個人の相対位置を比較する際に非常に役立ちます。

実例

import pandas as pd

# 従業員の給与データを作成
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 pandas as pd
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 pandas as pd
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 常用函数Pandas よく使う関数

その他の拡張