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.
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
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
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).
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 :)
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')