transform() 関数を使う場合

DataFrameGroupByのtransform()

次のようにtransform()を使うことで、関数を使った変換処理ができます。そのため、指定する関数次第で柔軟な処理が行えます。

# 指定した列でグループ化
grouped = df.groupby(列名)
# 指定した関数の処理を使って変換
grouped.transform(関数)

変換方法として指定する関数には、「引数でグループごとに各列のSeriesを受け取り、戻り値で変換結果を返す」 ような処理を定義します。戻り値には、引数と同じ要素数のSeries か、スカラー(1つの値) を指定できます。

具体的な例を見てみましょう。次のような試験結果と学習時間のデータについて考えます。


生徒ID
クラス点数学習時間(分)
ST0011-A60232
ST0021-A87345
ST0031-B66180
ST0041-A7222
ST0051-B74120
ST0061-B58215

クラスごとに、「各クラスの最大値と個々のデータの差」 を計算したいとします。

たとえば列点数では、クラス1-Aのデータは「最大値87との差」が、クラス1-Bのデータは「最大値74との差」が計算されることになります。
また列学習時間(分)では、クラス1-Aのデータは「最大値345との差」が、クラス1-Bのデータは「最大値215との差」が計算されることになります。

まずは、次のように各列のSeriesを受け取る関数を作成します。

def calc_max_diff(sr):
    # グループの最大値と各データの差を計算する
    # 引数srには各列のSeriesが渡される
    return sr.max() - sr

上記の関数をtransform()の引数に渡すと、各クラスの各列のSeriescalc_max_diff()が適用されます。

# 列「クラス」でグループ化
grouped = df.groupby("クラス")
# グループごとに変換関数を適用
grouped.transform(calc_max_diff)

生徒ID
点数学習時間(分)
ST00127113
ST00200
ST003835
ST00415323
ST005095
ST006160

結果を確認してみましょう。列点数に着目すると、下記のようになります。

  • 1-Aに属する生徒ST001(60点)は 87 - 60 = 27で27点
  • 1-Aに属する生徒ST002(87点)は 87 - 87 = 0で0点
  • 1-Bに属する生徒ST003(66点)は 74 - 66 = 8で8点

このように、グループごとに最高値との差が計算されていることがわかります。

SeriesGroupByのtransform()

SeriesGroupByでもtransform()は使えます。
DataFrameGroupBy同様、各グループのSeriesを受け取り、同じサイズのSeriesまたはスカラーを返す関数を指定します。

# 指定した列でグループ化
grouped = df.groupby(列名)
# 指定した関数の処理を使って変換
grouped[列名].transform(関数)

演習

import pandas as pd

# 試験結果のデータを読み込み
df = pd.read_csv("dataset/score_study_time.csv", index_col="生徒ID")
# 先頭5行を確認
df.head()
クラス点数学習時間(分)
生徒ID
ST0011-A48.0226
ST0021-A0.024
ST0031-B80.0271
ST0041-ANaN45
ST0051-A68.0271

今回はクラスごとに計算を行うので、groupby()を使ってクラスごとにグループ化します。

# 列「クラス」でグループ化
grouped = df.groupby("クラス")

(1)各グループの最大値とデータの差

グループ内の最大値と個々のデータの差を計算してみましょう。

たとえば、次の結果からわかるように、列点数の最大値はクラス1-Aでは98、クラス1-Bでは83です。そのため、クラス1-Aのデータは98との差分を、クラス1-Bのデータでは83との差分を計算します。列学習時間(分)も同様に、1-Aのデータでは381、1-Bのデータでは334との差分を計算することになります。

# 各グループの最大値
grouped.max()
点数学習時間(分)
クラス
1-A98.0381
1-B83.0334

transform()を使うと、指定した関数をグループごとに適用できます。 次のように 「各列のSeriesを引数で受け取り、Seriesの最大値との個々のデータの差を返す関数」 を定義します。

# transformで指定する関数
def calc_max_diff(sr):
    # 各データとグループの最大値との差を計算する
    # 引数srには各列のSeriesが渡される
    return sr.max() - sr

定義した関数をtarnsform()で指定して実行します。

# グループごとに変換関数を適用
max_diff_df = grouped.transform(calc_max_diff)
# 結果の先頭5行を確認
max_diff_df.head()
点数学習時間(分)
生徒ID
ST00150.0155
ST00298.0357
ST0033.063
ST004NaN336
ST00530.0110

実行結果を新しい列として元のdfに追加し、確認してみましょう。

たとえば、1-Aの列点数の最大値は98点だったので、1-Aに所属する生徒ST001(48点)の列クラス内最大値との差_点数は 98 - 48 = 50 となり、結果は50点になっています。これに対し、ST003(80点)は1-Bに所属しているので、1-Bの最大値である83点との差が計算され、83 - 3 = 3 で結果は3点になっています。

このように、グループごとに異なる処理が適用されていることがわかります。In [6]:

# 変換結果を新しい列として追加
df[["クラス内最大値との差_点数", "クラス内最大値との差_学習時間"]] = max_diff_df
# 結果の先頭5行を確認
df.head()

2つの値を入れる時は、df[[a,b]]とリストで代入するようにする。

クラス点数学習時間(分)クラス内最大値との差_点数クラス内最大値との差_学習時間
生徒ID
ST0011-A48.022650.0155
ST0021-A0.02498.0357
ST0031-B80.02713.063
ST0041-ANaN45NaN336
ST0051-A68.027130.0110

transform()で適用する関数でスカラーを返すと、同じグループでは同じ結果になります。

たとえば、次のコードでは各列のSeriesの最大値を返しています。結果を確認すると、1-Aに所属する生徒(ST001ST002ST004ST005 など)の列点数は、すべて98.0になっていることがわかります。

def calc_group_max(sr):
    # スカラーを返す関数の例
    # 各グループの最大値を返す(同じグループでは同じ値になる)
    return sr.max()  # スカラー(1つの値)


# スカラーを返す関数を適用する
max_df = grouped.transform(calc_group_max)
max_df.head()
点数学習時間(分)
生徒ID
ST00198.0381.0
ST00298.0381.0
ST00383.0334.0
ST00498.0381.0
ST00598.0381.0

(2)クラス内の偏差値

次に、列点数の値を使って、クラス内の偏差値を出してみましょう。ここでの偏差値は、次の式で計算します。

((データの点数 - グループ内の平均) / グループ内の標準偏差)  * 10 + 50

まずは、偏差値を計算する関数を定義しましょう。In [8]:

def calc_dev(sr):
    # 偏差値を計算する
    # 引数srには各列のSeriesが渡される
    # ((個人の点数 - クラスの平均)/ クラスの標準偏差) * 10 + 50
    return (sr - sr.mean()) / sr.std() * 10 + 50

agg()と違い、DataFrameGroupByのtransform()では指定した関数がすべての列に適用されます。特定の列だけを指定することはできません。

そのため、今回は次のようにSeriesGroupByオブジェクトのtransform()を使います。

# クラス内の偏差値を計算する
# 列「点数」だけに適用
dev_sr = grouped["点数"].transform(calc_dev)
# 結果の先頭5行を確認
dev_sr.head()
生徒ID
ST001    43.953391
ST002    28.025736
ST003    59.328339
ST004          NaN
ST005    50.589913
Name: 点数, dtype: float64

実行した結果を、新しい列として追加しましょう。In [10]:

# クラス内偏差値を新しい列として追加
df["クラス内偏差値"] = dev_sr
# 結果の先頭5行を確認
df.head()
クラス点数学習時間(分)クラス内最大値との差_点数クラス内最大値との差_学習時間クラス内偏差値
生徒ID
ST0011-A48.022650.015543.953391
ST0021-A0.02498.035728.025736
ST0031-B80.02713.06359.328339
ST0041-ANaN45NaN336NaN
ST0051-A68.027130.011050.589913

では、引数を持つ関数を適用したい場合、どうすればよいでしょうか。

たとえば次の関数では、各データのグループの最大値との差を計算した後、引数xを掛けています。
(なお、これはあくまで説明用の例であり、この処理自体に何か統計的に意味があるわけではありません)

def calc_max_diff_with_x(sr, x):
    # 「各データとグループの最大値との差」のx倍を計算する
    # 引数srには各列のSeriesが渡される
    return (sr.max() - sr) * x

上記のxのように追加の引数を指定したい場合は、下記のようにtansform()呼び出し時にキーワード引数で指定します。前クエスト「グループ化して集約しよう」で学んだagg()と同じ要領です。

import pandas as pd

# 試験結果のデータを読み込み
df = pd.read_csv("dataset/score_study_time.csv", index_col="生徒ID")
# 列「クラス」でグループ化
grouped = df.groupby("クラス")
# x=1倍に指定
grouped.transform(calc_max_diff_with_x, x=1)

生徒ID
点数学習時間(分)
ST00150.0155
ST00298.0357
ST0033.063
ST004NaN336

次のように、引数に渡す値を変えることで処理を変更できることがわかります。

# x=2倍に指定
grouped.transform(calc_max_diff_with_x, x=2)
生徒ID点数学習時間(分)
ST001100.0310
ST002196.0714
ST0036.0126
ST004NaN672

位置引数で指定したい場合は、次のように第2引数にタプルを使って指定します。

# DataFrameGroupByの場合
grouped.transform(関数, (第2引数に渡す値, 第3引数に渡す値, ... , 第N引数に渡す値))

# SeriesGroupByの場合
grouped[列名].transform(関数, (第2引数に渡す値, 第3引数に渡す値, ... , 第N引数に渡す値)) 

たとえば、先ほどのcalc_max_diff_with_x()の第2引数(x)に2を指定する場合、次のようになります。

# 位置引数で指定する場合
grouped.transform(calc_max_diff_with_x, (2,))

コメント

タイトルとURLをコピーしました