Python Pandas groupby操作の結果を親データフレームの列に割り当てる方法は?


83

IPythonには次のデータフレームがあり、各行は単一の株です。

In [261]: bdata
Out[261]:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 21210 entries, 0 to 21209
Data columns:
BloombergTicker      21206  non-null values
Company              21210  non-null values
Country              21210  non-null values
MarketCap            21210  non-null values
PriceReturn          21210  non-null values
SEDOL                21210  non-null values
yearmonth            21210  non-null values
dtypes: float64(2), int64(1), object(4)

「yearmonth」列の各日付ごとに、すべてのキャップ加重平均リターンを計算するgroupby操作を適用したいと思います。

これは期待どおりに機能します。

In [262]: bdata.groupby("yearmonth").apply(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())
Out[262]:
yearmonth
201204      -0.109444
201205      -0.290546

しかし、次に、これらの値を元のデータフレームのインデックスに「ブロードキャスト」して、日付が一致する定数列として保存したいと思います。

In [263]: dateGrps = bdata.groupby("yearmonth")

In [264]: dateGrps["MarketReturn"] = dateGrps.apply(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/mnt/bos-devrnd04/usr6/home/espears/ws/Research/Projects/python-util/src/util/<ipython-input-264-4a68c8782426> in <module>()
----> 1 dateGrps["MarketReturn"] = dateGrps.apply(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())

TypeError: 'DataFrameGroupBy' object does not support item assignment

この素朴な割り当ては機能しないはずです。しかし、groupby操作の結果を親データフレームの新しい列に割り当てるための「正しい」パンダのイディオムは何ですか?

結局、groupby操作の出力と日付が一致するすべてのインデックスに対して繰り返される定数値になる「MarketReturn」という列が必要です。

これを達成するための1つのハックは、次のとおりです。

marketRetsByDate  = dateGrps.apply(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())

bdata["MarketReturn"] = np.repeat(np.NaN, len(bdata))

for elem in marketRetsByDate.index.values:
    bdata["MarketReturn"][bdata["yearmonth"]==elem] = marketRetsByDate.ix[elem]

しかし、これは遅く、悪く、そしてPythonicではありません。


元のフレームではなく、グループ化されたオブジェクトに割り当て直します。
Wouter Overmeire 2012

2
私はそれを知っていて、エラーのすぐ下でこう言いました。「この素朴な割り当ては機能しないはずです。しかし、groupby操作の結果を親の新しい列に割り当てるための「正しい」パンダのイディオムは何ですか。データフレーム?」LHSで元のデータフレームを使用して割り当てを行うことも機能せず、GroupByオブジェクトレベルで列を追加するよりも直感的ではありません。
ely 2012

回答:


74
In [97]: df = pandas.DataFrame({'month': np.random.randint(0,11, 100), 'A': np.random.randn(100), 'B': np.random.randn(100)})

In [98]: df.join(df.groupby('month')['A'].sum(), on='month', rsuffix='_r')
Out[98]:
           A         B  month       A_r
0  -0.040710  0.182269      0 -0.331816
1  -0.004867  0.642243      1  2.448232
2  -0.162191  0.442338      4  2.045909
3  -0.979875  1.367018      5 -2.736399
4  -1.126198  0.338946      5 -2.736399
5  -0.992209 -1.343258      1  2.448232
6  -1.450310  0.021290      0 -0.331816
7  -0.675345 -1.359915      9  2.722156

これでも、groupby操作を実行する行のLHSに直接割り当てを行うのではなく、groupbyの計算を保存する必要があります。適用は、質問の下部にある私のハックのループよりも少し良いかもしれませんが、基本的に同じ考えです。
ely 2012

結合はこれを行うことができますが、追加された列の名前を変更する必要があります。この場合、A_rはnew_colです。
Wouter Overmeire 2012

下部の結合の例は機能しますが、明確に示されていません。答えの前半部分を削除し、後半部分をもう少し明確にしたい場合は、受け入れることに加えて賛成します。
ely 2012

12
最初のアプローチを削除しました。正直なところ、コードがそれ自体を物語っているように感じます。ドキュメントに説明や参照を追加したい場合は、自由に編集してください。私はそれほど投票システムにはあまり興味がありません。パンダを少しサポートするためにここにいます。
Wouter Overmeire 2012

1
私はこの答えを探すのに長い時間を費やしました。ちょっとしたネクロの投稿ですが、ありがとう!+1
ダンカーター

52

apply与えられた部分を連結する非常にスマートな方法をすべて調べていますが、groupby操作の後に親に新しい列を追加する別の方法があります。

In [236]: df
Out[236]: 
  yearmonth    return
0    201202  0.922132
1    201202  0.220270
2    201202  0.228856
3    201203  0.277170
4    201203  0.747347

In [237]: def add_mkt_return(grp):
   .....:     grp['mkt_return'] = grp['return'].sum()
   .....:     return grp
   .....: 

In [238]: df.groupby('yearmonth').apply(add_mkt_return)
Out[238]: 
  yearmonth    return  mkt_return
0    201202  0.922132    1.371258
1    201202  0.220270    1.371258
2    201202  0.228856    1.371258
3    201203  0.277170    1.024516
4    201203  0.747347    1.024516

1
あなたはまた、ラムダと割り当てを使用して関数を定義せずにこれを行うことができます:df.groupby('yearmonth').apply(lambda grp: grp.assign(mkt_return=grp['return'].sum()))
krassowski

32

groupby()を使用する場合の原則として、.transform()関数を使用すると、pandasは元のテーブルと同じ長さのテーブルを返します。.sum()や.first()などの他の関数を使用すると、パンダは各行がグループであるテーブルを返します。

これがapplyでどのように機能するかはわかりませんが、transformを使用して複雑なラムダ関数を実装するのはかなり難しい場合があるため、必要な変数を作成し、元のデータセットに配置して、そこで操作を行うのが最も役立つ戦略です。

最初にあなたが正しくやろうとしていることを理解していれば、各グループの時価総額を計算できます。

bdata['group_MarketCap'] = bdata.groupby('yearmonth')['MarketCap'].transform('sum')

これにより、「group_MarketCap」という列が元のデータに追加され、各グループの時価総額の合計が含まれます。次に、加重値を直接計算できます。

bdata['weighted_P'] = bdata['PriceReturn'] * (bdata['MarketCap']/bdata['group_MarketCap'])

そして最後に、同じ変換関数を使用して各グループの加重平均を計算します。

bdata['MarketReturn'] = bdata.groupby('yearmonth')['weighted_P'].transform('sum')

私は自分の変数をこのように構築する傾向があります。すべてを1つのコマンドにまとめることができる場合もありますが、ほとんどの場合、パンダは新しいオブジェクトをインスタンス化して完全なデータセットスケールで操作する必要があるため、groupby()で常に機能するとは限りません(つまり、できません)。 1つがまだ存在しない場合は、2つの列を一緒に追加します)。

お役に立てれば :)


24

transform(集計ではなく)方法を提案してもいいですか?元の例で使用する場合は、必要な処理(ブロードキャスト)を実行する必要があります。


私の理解では、変換は渡されたもののように見えるオブジェクトを生成します。したがって、DataFrameを変換すると、列が返されるだけでなく、DataFrameが返されます。私の場合、元のデータフレームに新しい結果を追加したいと思います。それとも、私は、データフレームを受け取り、新しい列を計算し、新しい列を追加する別の関数を記述し、必要があることを言っているそしてその機能を変換しますか?
ely 2012

2
同意します。変換の方が適しています。df['A-month-sum'] = df.groupby( 'month')['A']。transform(sum)
Wouter Overmeire 2012

しかし、なぜそれが良いのでしょうか?同じことをしますね 速いですか?
K.-Michael Aye 2013

1
私見、transformきれいに見えます。これを確認するためのEMSデータはありませんが、これは機能する可能性があります(ただし、ラムダ関数を変更する必要がある場合があります):bdata['mkt_return'] = bdata.groupby("yearmonth").transform(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())
cd98 2013

1
私が間違っている場合は訂正してください。transform後に複数の列を操作することを許可しませんgroupby。たとえばdf.groupby('col_3')[['col_1','col_2']].transform(lambda x: ((1-x.col_1.mean()) - x.col_2.std()))、「属性XXXがありません」というエラーがスローされます
Jason

0

元のデータフレームに割り当てる方法が見つかりませんでした。したがって、グループからの結果を保存し、それらを連結するだけです。次に、連結されたデータフレームをインデックスで並べ替えて、元の順序を入力データフレームとして取得します。サンプルコードは次のとおりです。

In [10]: df = pd.DataFrame({'month': np.random.randint(0,11, 100), 'A': np.random.randn(100), 'B': np.random.randn(100)})

In [11]: df.head()
Out[11]:
   month         A         B
0      4 -0.029106 -0.904648
1      2 -2.724073  0.492751
2      7  0.732403  0.689530
3      2  0.487685 -1.017337
4      1  1.160858 -0.025232

In [12]: res = []

In [13]: for month, group in df.groupby('month'):
    ...:     new_df = pd.DataFrame({
    ...:         'A^2+B': group.A ** 2 + group.B,
    ...:         'A+B^2': group.A + group.B**2
    ...:     })
    ...:     res.append(new_df)
    ...:

In [14]: res = pd.concat(res).sort_index()

In [15]: res.head()
Out[15]:
      A^2+B     A+B^2
0 -0.903801  0.789282
1  7.913327 -2.481270
2  1.225944  1.207855
3 -0.779501  1.522660
4  1.322360  1.161495

この方法は非常に高速で拡張可能です。ここで任意の機能を導出できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.