web-dev-qa-db-ja.com

Python Pandas groupby操作の結果を親データフレームの列に戻す方法は?

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操作の結果を親データフレームの新しい列に割り当てるための「正しい」Pandas=イディオムは何ですか?

最後に、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]

しかし、これは遅く、悪く、Pythonに反しています。

66
ely
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
61

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
44
Garrett

(集約の代わりに)transformメソッドを提案できますか?元の例で使用する場合は、必要なこと(ブロードキャスト)を行う必要があります。

20
Wes McKinney

Groupby()を使用する際の一般的なルールとして、.transform()関数を使用する場合、pandasは元のテーブルと同じ長さのテーブルを返します。 )または.first()then pandasは、各行がグループであるテーブルを返します。

これが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')

この方法で変数を作成する傾向があります。時々、それをすべて単一のコマンドに入れることができますが、ほとんどの場合pandasはそれで操作するために新しいオブジェクトをインスタンス化する必要があるため、groupby()で常に動作するとは限りません完全なデータセットスケール(つまり、1つがまだ存在しない場合、2つの列を一緒に追加することはできません)。

お役に立てれば :)

19
seeiespi

これは機能しますか?

capWeighting = lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum()

bdata["MarketReturn"] = bdata.groupby("yearmonth").transform(capWeighting)

私が使う reindex_like このため:

summedbdata = bdata.groupby("yearmonth").apply(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())
summedbdata.set_index('yearmonth').reindex_like(bdata.set_index('yearmonth').sort_index(), method='ffill')
0
Def_Os