Python Pandas comment affecter les résultats des opérations groupby aux colonnes dans la dataframe mère?

j'ai les données suivantes cadre dans IPython, où chaque ligne est un stock unique:

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)

je veux appliquer une opération de groupe par opération qui calcule le rendement moyen pondéré par cap sur tout, pour chaque date dans la colonne "yearmonth".

Cela fonctionne comme prévu:

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

mais ensuite je veux en quelque sorte" diffuser " ces valeurs de nouveau aux indices dans la base de données originale, et les sauver comme des colonnes constantes où les dates correspondre.

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

je me rends compte que cette tâche naïve ne devrait pas marcher. Mais quel est le " bon " idiome de Pandas pour affecter le résultat d'une opération groupby dans une nouvelle colonne sur la dataframe parent?

En fin de compte, je veux une colonne appelée "MarketReturn" que d'être une répétition constante de la valeur pour tous les indices qui ont des date avec la sortie de la groupby opération.

Un hack pour atteindre ce serait le suivant:

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]

mais c'est lent, mauvais, et unPythonic.

46
demandé sur ely 2012-08-30 19:45:26

5 réponses

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
41
répondu Wouter Overmeire 2012-09-08 20:26:16

pendant que j'explore encore Toutes les façons incroyablement intelligentes que apply concaténate les pièces qui lui sont données, Voici une autre façon d'ajouter une nouvelle colonne dans le parent après une opération 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
30
répondu Garrett 2012-09-08 17:33:00

puis-je suggérer le transform méthode (au lieu d'agréger)? Si vous l'utilisez dans votre exemple, il devrait faire ce que vous voulez (la diffusion).

15
répondu Wes McKinney 2012-09-08 22:35:52

en règle générale lorsque vous utilisez groupby(), si vous utilisez le .transform () fonction pandas retournera une table avec la même longueur que votre original. Lorsque vous utilisez d'autres fonctions comme .sum() ou .premièrement () puis pandas retournera une table où chaque ligne est un groupe.

Je ne suis pas sûr de savoir comment cela fonctionne avec apply mais la mise en œuvre de fonctions lambda élaborées avec transform peut être assez délicate de sorte que la stratégie que je trouve le plus utile est de créer les variables dont j'ai besoin, les placer dans le jeu de données d'origine et ensuite faire mes opérations.

si je comprends ce que vous essayez de faire correctement (je m'excuse si je me trompe) d'abord, vous pouvez calculer le total du plafond du marché pour chaque groupe:

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

ceci ajoutera une colonne appelée "group_MarketCap" à vos données originales qui contiendront la somme des plafonds du marché pour chaque groupe. Vous pouvez alors calculer les valeurs pondérées directement:

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

et finalement vous calculez les pondérés moyenne pour chaque groupe utilisant la même fonction de transformation:

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

j'ai tendance à construire mes variables de cette façon. Parfois, vous pouvez tout désactiver en une seule commande, mais cela ne fonctionne pas toujours avec groupby() parce que la plupart du temps pandas a besoin d'instancier le nouvel objet pour l'utiliser à l'échelle complète de l'ensemble de données (c'est-à-dire que vous ne pouvez pas ajouter deux colonnes ensemble si l'une n'existe pas encore).

Espérons que cela aide :)

8
répondu seeiespi 2016-08-19 00:08:08

cela fonctionne?

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

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

j'utilise reindex_like pour ceci:

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
répondu Def_Os 2012-08-31 15:04:46