グループオブジェクトの適用と変換


174

次のデータフレームを検討してください:

     A      B         C         D
0  foo    one  0.162003  0.087469
1  bar    one -1.156319 -1.526272
2  foo    two  0.833892 -1.666304
3  bar  three -2.026673 -0.322057
4  foo    two  0.411452 -0.954371
5  bar    two  0.765878 -0.095968
6  foo    one -0.654890  0.678091
7  foo  three -1.789842 -1.130922

次のコマンドが機能します。

> df.groupby('A').apply(lambda x: (x['C'] - x['D']))
> df.groupby('A').apply(lambda x: (x['C'] - x['D']).mean())

しかし、次の作業はどれも行われません。

> df.groupby('A').transform(lambda x: (x['C'] - x['D']))
ValueError: could not broadcast input array from shape (5) into shape (5,3)

> df.groupby('A').transform(lambda x: (x['C'] - x['D']).mean())
 TypeError: cannot concatenate a non-NDFrame object

どうして? ドキュメントの例は、transformグループを呼び出すと行ごとの演算処理ができることを示唆しているようです:

# Note that the following suggests row-wise operation (x.mean is the column mean)
zscore = lambda x: (x - x.mean()) / x.std()
transformed = ts.groupby(key).transform(zscore)

つまり、変換は本質的に特定のタイプの適用(集約されないもの)だと思いました。どこが間違っているのですか?

参考までに、上記の元のデータフレームの構成を以下に示します。

df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
                          'foo', 'bar', 'foo', 'foo'],
                   'B' : ['one', 'one', 'two', 'three',
                         'two', 'two', 'one', 'three'],
                   'C' : randn(8), 'D' : randn(8)})

1
に渡される関数はtransform、数値、行、または引数と同じ形状を返す必要があります。数値の場合、その数値はグループ内のすべての要素に設定され、行の場合、グループ内のすべての行にブロードキャストされます。コードでは、lambda関数がグループにブロードキャストできない列を返します。
HYRY

1
@HYRYに​​感謝しますが、私は混乱しています。上記で(つまりを使用してzscore)コピーしたドキュメントの例を見ると、transformそれぞれxが内のアイテムであると想定し、グループ内のアイテムごとにgroup値を返すラムダ関数を受け取ります。何が欠けていますか?
Amelio Vazquez-Reina

非常に詳細なソリューションをお探しの方は、以下をご覧ください
Ted Petrou 2017年

@TedPetrou:そのtl; drは、1)applydf全体transformを渡しますが、各列をシリーズとして個別に渡します。2)apply任意の形状出力(スカラー/シリーズ/データフレーム/配列/リスト... )を返すことができますがtransform、グループと同じ長さのシーケンス(1Dシリーズ/配列/リスト)を返す必要があります。OPが必要であるのはこのためですapply()ませんtransform()。このドキュメントは両方の違いを明確に説明していないため、これは良い質問です。(apply/map/applymap、または他のものとの区別に似ています...)
smci

回答:


146

2つの大きな違いapplytransform

transformapplygroupbyメソッドには2つの大きな違いがあります。

  • 入力:
    • apply各グループのすべての列をDataFrameとしてカスタム関数に暗黙的に渡します。
    • while transformは、各グループの各列をシリーズとして個別にカスタム関数に渡します。
  • 出力:
    • に渡されるカスタム関数はapply、スカラー、SeriesまたはDataFrame(またはnumpy配列またはリスト)を返すことができます。
    • に渡されるカスタム関数は、グループと同じ長さのtransformシーケンス(1次元のシリーズ、配列、またはリスト)を返す必要があります。

したがって、transform一度に1つのシリーズでのみapply機能し、一度にDataFrame全体で機能します。

カスタム関数の検査

applyまたはに渡されたカスタム関数への入力を検査するのにかなり役立ちますtransform

いくつかのサンプルデータを作成し、グループを調べて、私が話していることを確認できるようにします。

import pandas as pd
import numpy as np
df = pd.DataFrame({'State':['Texas', 'Texas', 'Florida', 'Florida'], 
                   'a':[4,5,1,3], 'b':[6,10,3,11]})

     State  a   b
0    Texas  4   6
1    Texas  5  10
2  Florida  1   3
3  Florida  3  11

暗黙的に渡されたオブジェクトのタイプを出力し、実行を停止できるようにエラーを発生させる単純なカスタム関数を作成してみましょう。

def inspect(x):
    print(type(x))
    raise

次に、この関数をgroupby applytransformメソッドの両方に渡して、どのオブジェクトが渡されるかを確認します。

df.groupby('State').apply(inspect)

<class 'pandas.core.frame.DataFrame'>
<class 'pandas.core.frame.DataFrame'>
RuntimeError

ご覧のとおり、DataFrameがinspect関数に渡されます。タイプDataFrameが2度出力された理由を疑問に思うかもしれません。パンダは最初のグループを2回実行します。これは、計算を完了するための高速な方法があるかどうかを判断するために行われます。これは、気にする必要のない細かい部分です。

今、同じことをしてみましょう transform

df.groupby('State').transform(inspect)
<class 'pandas.core.series.Series'>
<class 'pandas.core.series.Series'>
RuntimeError

シリーズ-まったく異なるPandasオブジェクト-が渡されます。

そのためtransform、一度に1つのシリーズでのみ作業できます。2つのカラムを同時に操作することは不可能ではありません。したがって、カスタム関数の内部aから列を減算しようとbすると、でエラーが発生しtransformます。下記参照:

def subtract_two(x):
    return x['a'] - x['b']

df.groupby('State').transform(subtract_two)
KeyError: ('a', 'occurred at index a')

パンダがa存在しないSeriesインデックスを見つけようとしているため、KeyErrorが発生します。applyDataFrame全体が含まれているため、この操作を完了することができます。

df.groupby('State').apply(subtract_two)

State     
Florida  2   -2
         3   -8
Texas    0   -2
         1   -5
dtype: int64

出力はSeriesであり、元のインデックスが保持されているため少し混乱しますが、すべての列にアクセスできます。


渡されたパンダオブジェクトの表示

カスタム関数内にパンダオブジェクト全体を表示することでさらに効果的になるため、操作対象を正確に確認できます。データフレームがジュピターノートブックのHTMLでうまく出力されるように、モジュールから関数printを使用したいのでステートメントを使用できます:displayIPython.display

from IPython.display import display
def subtract_two(x):
    display(x)
    return x['a'] - x['b']

スクリーンショット: ここに画像の説明を入力してください


変換は、グループと同じサイズの1次元シーケンスを返す必要があります

他の違いはtransform、グループと同じサイズの1次元シーケンスを返す必要があることです。この特定のインスタンスでは、各グループには2つの行があるためtransform、2つの行のシーケンスを返す必要があります。そうでない場合、エラーが発生します。

def return_three(x):
    return np.array([1, 2, 3])

df.groupby('State').transform(return_three)
ValueError: transform must return a scalar value for each group

エラーメッセージは、実際には問題を説明するものではありません。グループと同じ長さのシーケンスを返す必要があります。したがって、次のような関数が機能します。

def rand_group_len(x):
    return np.random.rand(len(x))

df.groupby('State').transform(rand_group_len)

          a         b
0  0.962070  0.151440
1  0.440956  0.782176
2  0.642218  0.483257
3  0.056047  0.238208

単一のスカラーオブジェクトを返すことも機能します transform

カスタム関数から単一のスカラーだけを返す場合transform、グループの各行にそれを使用します。

def group_sum(x):
    return x.sum()

df.groupby('State').transform(group_sum)

   a   b
0  9  16
1  9  16
2  4  14
3  4  14

3
np定義されてない。初心者の方はimport numpy as np、回答に含めていただければ幸いです。
Qaswed '25

187

.transform操作と同様に混乱しているように感じたので.apply、いくつかの答えが問題に光を投げかけていました。たとえば、この回答は非常に役に立ちました。

これまでの私の持ち帰りは、それが互いに分離.transformしてSeries(列を)処理する(または処理する)ことです。つまり、最後の2つの呼び出しでは、

df.groupby('A').transform(lambda x: (x['C'] - x['D']))
df.groupby('A').transform(lambda x: (x['C'] - x['D']).mean())

.transform2つの列から値を取得するように要求しましたが、実際には(いわば)両方を同時に「見る」ことはできません。transformデータフレームの列を1つずつ確認し、繰り返し行われるスカラーで作られたシリーズ(またはシリーズのグループ)を返しますlen(input_column)

使用されるべきこのスカラー、そう.transformするためにSeries入力に適用されるいくつかの減少関数の結果であるSeries(そして唯一の直列/時間でカラムに)は

(データフレーム上の)この例を考えてみましょう:

zscore = lambda x: (x - x.mean()) / x.std() # Note that it does not reference anything outside of 'x' and for transform 'x' is one column.
df.groupby('A').transform(zscore)

生成されます:

       C      D
0  0.989  0.128
1 -0.478  0.489
2  0.889 -0.589
3 -0.671 -1.150
4  0.034 -0.285
5  1.149  0.662
6 -1.404 -0.907
7 -0.509  1.653

これは、一度に1つの列でのみ使用する場合とまったく同じです。

df.groupby('A')['C'].transform(zscore)

降伏:

0    0.989
1   -0.478
2    0.889
3   -0.671
4    0.034
5    1.149
6   -1.404
7   -0.509

ことを注意.apply最後の例では(df.groupby('A')['C'].apply(zscore))まったく同じように動作しますが、あなたがデータフレームでそれを使用しようとした場合、それは失敗するでしょう:

df.groupby('A').apply(zscore)

エラーを出します:

ValueError: operands could not be broadcast together with shapes (6,) (2,)

それで、他にどこが.transform便利ですか?最も単純なケースは、リダクション関数の結果を元のデータフレームに割り当てようとすることです。

df['sum_C'] = df.groupby('A')['C'].transform(sum)
df.sort('A') # to clearly see the scalar ('sum') applies to the whole column of the group

降伏:

     A      B      C      D  sum_C
1  bar    one  1.998  0.593  3.973
3  bar  three  1.287 -0.639  3.973
5  bar    two  0.687 -1.027  3.973
4  foo    two  0.205  1.274  4.373
2  foo    two  0.128  0.924  4.373
6  foo    one  2.113 -0.516  4.373
7  foo  three  0.657 -1.179  4.373
0  foo    one  1.270  0.201  4.373

で同じことを試みると、.applyが得NaNsられsum_Cます。ので.apply減少戻ってくるSeries、それが戻って放送する方法を知りません、:

df.groupby('A')['C'].apply(sum)

与える:

A
bar    3.973
foo    4.373

.transform使用してデータをフィルタリングする場合もあります。

df[df.groupby(['B'])['D'].transform(sum) < -1]

     A      B      C      D
3  bar  three  1.287 -0.639
7  foo  three  0.657 -1.179

これがもう少し明確になることを願っています。


4
ああ、神様。違いはとても微妙です。
Dawei、2018

3
.transform()欠損値を埋めるためにも使用できます。特に、グループ平均またはグループ統計をNaNそのグループの値にブロードキャストする場合。残念ながら、パンダのドキュメントも私には役に立ちませんでした。
サイバー数学

前者の場合.groupby().filter()も同じことをすると思います。あなたの説明をありがとう、.apply()そして.transform()私もとても混乱させます。

これdf.groupby().transform()が、サブグループdfで機能しない理由を説明しています。列が1つずつ表示されるValueError: transform must return a scalar value for each groupため、常にエラーが発生しますtransform
jerrytim

データのフィルタリングに使用した最後の.transformの例が本当に気に入りました。超いいね!
rishi jain

13

非常に単純なスニペットを使用して、違いを説明します。

test = pd.DataFrame({'id':[1,2,3,1,2,3,1,2,3], 'price':[1,2,3,2,3,1,3,1,2]})
grouping = test.groupby('id')['price']

DataFrameは次のようになります。

    id  price   
0   1   1   
1   2   2   
2   3   3   
3   1   2   
4   2   3   
5   3   1   
6   1   3   
7   2   1   
8   3   2   

この表には3つの顧客IDがあり、各顧客は3つのトランザクションを行い、毎回1,2,3ドルを支払いました。

次に、各顧客の最低支払額を調べたいと思います。それには2つの方法があります。

  1. 使用apply

    grouping.min()

戻りは次のようになります。

id
1    1
2    1
3    1
Name: price, dtype: int64

pandas.core.series.Series # return type
Int64Index([1, 2, 3], dtype='int64', name='id') #The returned Series' index
# lenght is 3
  1. 使用transform

    grouping.transform(min)

戻りは次のようになります。

0    1
1    1
2    1
3    1
4    1
5    1
6    1
7    1
8    1
Name: price, dtype: int64

pandas.core.series.Series # return type
RangeIndex(start=0, stop=9, step=1) # The returned Series' index
# length is 9    

どちらのメソッドもSeriesオブジェクトを返しますlengthが、最初のメソッドのは3 lengthで、2番目のメソッドのは9です。

に答えたい場合は、What is the minimum price paid by each customerこのapply方法の方が適しています。

回答したい場合What is the difference between the amount paid for each transaction vs the minimum paymenttransform、を使用したいと考えます。理由は次のとおりです。

test['minimum'] = grouping.transform(min) # ceates an extra column filled with minimum payment
test.price - test.minimum # returns the difference for each row

Apply サイズ3のシリーズを返すため、ここでは機能しませんが、元のdfの長さは9です。簡単に元のdfに統合することはできません。


3
これは素晴らしい答えだと思います!質問してから4年以上経過して回答にご協力いただき、ありがとうございます。
Benjamin Dubreu

4
tmp = df.groupby(['A'])['c'].transform('mean')

のようなものです

tmp1 = df.groupby(['A']).agg({'c':'mean'})
tmp = df['A'].map(tmp1['c'])

または

tmp1 = df.groupby(['A'])['c'].mean()
tmp = df['A'].map(tmp1)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.