Mam następującą ramkę danych w IPythonie, gdzie każdy wiersz jest pojedynczą akcją:
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)
Chcę zastosować operację grupowania, która oblicza średni zwrot ważony limitem ze wszystkiego, dla każdej daty w kolumnie „yearmonth”.
Działa to zgodnie z oczekiwaniami:
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
Ale potem chcę posortować „rozgłaszanie” tych wartości z powrotem do indeksów w oryginalnej ramce danych i zapisać je jako stałe kolumny, w których daty są zgodne.
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
Zdaję sobie sprawę, że to naiwne zadanie nie powinno działać. Ale jaki jest „właściwy” idiom Pandy do przypisywania wyniku operacji grupowania do nowej kolumny w nadrzędnej ramce danych?
Na koniec chcę, aby kolumna o nazwie „MarketReturn” była powtarzalną wartością stałą dla wszystkich indeksów, które mają pasującą datę do danych wyjściowych operacji grupowania.
Jeden hack, aby to osiągnąć, wyglądałby następująco:
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]
Ale to jest powolne, złe i nietypowe.
Odpowiedzi:
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
źródło
Chociaż wciąż badam wszystkie niewiarygodnie inteligentne sposoby
apply
łączenia podanych elementów, oto inny sposób dodania nowej kolumny w rodzicu po operacji grupowania.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
źródło
df.groupby('yearmonth').apply(lambda grp: grp.assign(mkt_return=grp['return'].sum()))
Zgodnie z ogólną zasadą podczas korzystania z groupby (), jeśli używasz funkcji .transform (), pandy zwrócą tabelę o takiej samej długości jak oryginał. Gdy używasz innych funkcji, takich jak .sum () lub .first (), pandy zwrócą tabelę, w której każdy wiersz jest grupą.
Nie jestem pewien, jak to działa z zastosuj, ale implementowanie skomplikowanych funkcji lambda z transformacją może być dość trudne, więc strategia, która wydaje mi się najbardziej pomocna, polega na utworzeniu potrzebnych zmiennych, umieszczeniu ich w oryginalnym zbiorze danych, a następnie wykonaniu tam moich operacji.
Jeśli najpierw zrozumiem, co starasz się zrobić poprawnie, możesz obliczyć całkowitą kapitalizację rynkową dla każdej grupy:
bdata['group_MarketCap'] = bdata.groupby('yearmonth')['MarketCap'].transform('sum')
Spowoduje to dodanie kolumny o nazwie „group_MarketCap” do Twoich pierwotnych danych, która będzie zawierać sumę limitów rynkowych dla każdej grupy. Następnie możesz bezpośrednio obliczyć wartości ważone:
bdata['weighted_P'] = bdata['PriceReturn'] * (bdata['MarketCap']/bdata['group_MarketCap'])
Na koniec obliczysz średnią ważoną dla każdej grupy przy użyciu tej samej funkcji przekształcającej:
bdata['MarketReturn'] = bdata.groupby('yearmonth')['weighted_P'].transform('sum')
W ten sposób buduję swoje zmienne. Czasami możesz zrobić wszystko jednym poleceniem, ale to nie zawsze działa z groupby (), ponieważ przez większość czasu pandy muszą utworzyć wystąpienie nowego obiektu, aby operować na nim w pełnej skali zbioru danych (tj. Nie możesz dodaj dwie kolumny razem, jeśli jeszcze jedna nie istnieje).
Mam nadzieję że to pomoże :)
źródło
Czy mogę zasugerować
transform
metodę (zamiast agregatu)? Jeśli używasz go w swoim oryginalnym przykładzie, powinno robić to, co chcesz (nadawanie).źródło
transform
wygląda czyściej. Nie mam danych EMS, aby to potwierdzić, ale to może zadziałać (chociaż funkcja lambda może wymagać modyfikacji):bdata['mkt_return'] = bdata.groupby("yearmonth").transform(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())
transform
nie pozwala na operowanie na wielu kolumnach pogroupby
, np.df.groupby('col_3')[['col_1','col_2']].transform(lambda x: ((1-x.col_1.mean()) - x.col_2.std()))
wyrzuci błąd narzekając, że 'brak atrybutu XXX'Nie znalazłem sposobu, aby przypisać do oryginalnej ramki danych. Więc po prostu przechowuję wyniki z grup i łączę je. Następnie sortujemy połączoną ramkę danych według indeksu, aby uzyskać oryginalną kolejność jako wejściową ramkę danych. Oto przykładowy kod:
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
Ta metoda jest dość szybka i rozszerzalna. Tutaj możesz uzyskać dowolną funkcję.
źródło