Ich habe den folgenden Datenrahmen in IPython, wobei jede Zeile ein einzelner Bestand ist:
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)
Ich möchte eine Groupby-Operation anwenden, die die kapitalgewichtete durchschnittliche Rendite für jedes Datum in der Spalte "Jahrmonat" berechnet.
Dies funktioniert wie erwartet:
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
Aber dann möchte ich diese Werte zurück zu den Indizes im ursprünglichen Datenrahmen "senden" und sie als konstante Spalten speichern, in denen die Daten übereinstimmen.
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
Mir ist klar, dass diese naive Aufgabe nicht funktionieren sollte. Aber was ist die "richtige" Pandas-Sprache, um das Ergebnis einer Groupby-Operation einer neuen Spalte im übergeordneten Datenrahmen zuzuweisen?
Am Ende möchte ich eine Spalte mit dem Namen "MarketReturn", die ein wiederholter konstanter Wert für alle Indizes ist, deren Datum mit der Ausgabe der groupby-Operation übereinstimmt.
Ein Hack, um dies zu erreichen, wäre der folgende:
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]
Aber das ist langsam, schlecht und unpythonisch.
Antworten:
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
quelle
Während ich noch alle unglaublich intelligenten Methoden untersuche, mit denen
apply
die angegebenen Teile verkettet werden, gibt es hier eine weitere Möglichkeit, nach einer Groupby-Operation eine neue Spalte im übergeordneten Element hinzuzufügen.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
quelle
df.groupby('yearmonth').apply(lambda grp: grp.assign(mkt_return=grp['return'].sum()))
Wenn Sie bei Verwendung von groupby () die Funktion .transform () verwenden, geben Pandas in der Regel eine Tabelle mit der gleichen Länge wie Ihr Original zurück. Wenn Sie andere Funktionen wie .sum () oder .first () verwenden, geben Pandas eine Tabelle zurück, in der jede Zeile eine Gruppe ist.
Ich bin mir nicht sicher, wie dies mit apply funktioniert, aber das Implementieren ausgefeilter Lambda-Funktionen mit Transformation kann ziemlich schwierig sein. Die Strategie, die ich am hilfreichsten finde, besteht darin, die benötigten Variablen zu erstellen, sie in den ursprünglichen Datensatz zu platzieren und dort meine Operationen auszuführen.
Wenn ich verstehe, was Sie zuerst richtig machen wollen, können Sie die Gesamtmarktkapitalisierung für jede Gruppe berechnen:
bdata['group_MarketCap'] = bdata.groupby('yearmonth')['MarketCap'].transform('sum')
Dadurch wird Ihren Originaldaten eine Spalte mit dem Namen "group_MarketCap" hinzugefügt, die die Summe der Marktkapitalisierungen für jede Gruppe enthält. Dann können Sie die gewichteten Werte direkt berechnen:
bdata['weighted_P'] = bdata['PriceReturn'] * (bdata['MarketCap']/bdata['group_MarketCap'])
Und schließlich würden Sie den gewichteten Durchschnitt für jede Gruppe mit derselben Transformationsfunktion berechnen:
bdata['MarketReturn'] = bdata.groupby('yearmonth')['weighted_P'].transform('sum')
Ich neige dazu, meine Variablen auf diese Weise zu erstellen. Manchmal können Sie alles in einem einzigen Befehl zusammenfassen, aber das funktioniert nicht immer mit groupby (), da Pandas das neue Objekt die meiste Zeit instanziieren müssen, um es im vollen Datensatzmaßstab zu bearbeiten (dh Sie können es nicht addiere zwei Spalten, falls noch keine existiert).
Hoffe das hilft :)
quelle
Darf ich die
transform
Methode vorschlagen (anstelle von Aggregat)? Wenn Sie es in Ihrem ursprünglichen Beispiel verwenden, sollte es tun, was Sie wollen (die Übertragung).quelle
transform
sieht sauberer aus. Ich habe keine EMS-Daten, um dies zu bestätigen, aber dies könnte funktionieren (obwohl die Lambda-Funktion möglicherweise geändert werden muss):bdata['mkt_return'] = bdata.groupby("yearmonth").transform(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())
transform
liege. Erlaubt einem nicht, mehrere Spalten zu bearbeitengroupby
, z. B.df.groupby('col_3')[['col_1','col_2']].transform(lambda x: ((1-x.col_1.mean()) - x.col_2.std()))
wird ein Fehler ausgegeben, der sich beschwert, dass 'kein Attribut XXX'Ich habe keine Möglichkeit gefunden, eine Zuordnung zum ursprünglichen Datenrahmen vorzunehmen. Also speichere ich einfach die Ergebnisse aus den Gruppen und verkette sie. Dann sortieren wir den verketteten Datenrahmen nach Index, um die ursprüngliche Reihenfolge als Eingabedatenrahmen zu erhalten. Hier ist ein Beispielcode:
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
Diese Methode ist ziemlich schnell und erweiterbar. Sie können hier jede Funktion ableiten.
quelle