web-dev-qa-db-fra.com

Python Pandas Comment attribuer les résultats de l'opération groupby à des colonnes du cadre de données parent?

J'ai le cadre de données suivant 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 souhaite appliquer une opération groupby qui calcule le rendement moyen pondéré en fonction des plafonds pour chaque date, dans chaque colonne de la colonne "année".

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 souhaite "rediffuser" ces valeurs dans les index du bloc de données d'origine et les enregistrer sous forme de colonnes constantes où les dates correspondent.

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 fonctionner. Mais quel est le "droit" Pandas idiome pour affecter le résultat d’une opération groupby dans une nouvelle colonne de la base de données parent?

En fin de compte, je veux une colonne appelée "MarketReturn" qui sera une valeur constante répétée pour tous les index dont la date correspond à la sortie de l'opération groupby.

Un hack pour y parvenir 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 ceci est lent, mauvais et non-Pythonique.

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
Wouter Overmeire

Bien que j'explore toujours toutes les méthodes extrêmement intelligentes permettant à apply de concaténer les éléments fournis, voici un autre moyen 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
44
Garrett

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

20
Wes McKinney

En règle générale, lorsque vous utilisez groupby (), si vous utilisez la fonction .transform () pandas renverra un tableau de la même longueur que votre original. Lorsque vous utilisez d'autres fonctions telles que .sum ( ) ou .first () then pandas renverra une table où chaque ligne est un groupe.

Je ne sais pas comment cela fonctionne avec apply, mais implémenter des fonctions lambda élaborées avec transform peut être assez délicat. La stratégie que je trouve la plus utile est donc de créer les variables dont j'ai besoin, de les placer dans le jeu de données d'origine, puis d'y effectuer mes opérations.

Si je comprends bien ce que vous essayez de faire correctement (je m'excuse si je me trompe), vous pouvez d'abord calculer la capitalisation boursière totale de chaque groupe:

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

Ceci ajoutera une colonne appelée "group_MarketCap" à vos données d'origine, qui contiendrait la somme des capitalisations boursières de chaque groupe. Ensuite, vous pouvez calculer directement les valeurs pondérées:

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

Et enfin, vous calculeriez la moyenne pondérée pour chaque groupe en 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 mettre tout en une seule commande, mais cela ne fonctionne pas toujours avec groupby () car la plupart du temps pandas doit instancier le nouvel objet pour pouvoir être utilisé à la échelle complète du jeu de données (vous ne pouvez pas ajouter deux colonnes si l'une d'elles n'existe pas encore).

J'espère que cela t'aides :)

19
seeiespi

Est-ce que ça marche?

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

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

J'utilise reindex_like pour ça:

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