Python Pandas Как назначить результаты операции groupby обратно в столбцы в родительском фрейме?
У меня есть следующий фрейм данных в IPython, где каждая строка является единственным запасом:
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)
Я хочу применить операцию groupby, которая вычисляет средневзвешенный возврат по каплям во всем, за каждую дату в столбце "yearmonth".
Это работает как ожидалось:
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
Но потом я хочу сортировать эти значения обратно в индексы в исходном фрейме данных и сохранять их как постоянные столбцы, в которых соответствуют даты.
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
Я понимаю, что это наивное задание не должно работать. Но что такое "правильная" Pandas идиома для назначения результата операции groupby в новый столбец на родительском фрейме?
В конце концов, мне нужен столбец с названием "MarketReturn", чем будет повторяющееся постоянное значение для всех индексов, которые имеют совпадающую дату с выходом операции groupby.
Один взлом для достижения этого будет следующим:
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]
Но это медленный, плохой и непитонический.
Ответы
Ответ 1
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
Ответ 2
Пока я все еще изучаю все невероятно умные способы, которые apply
объединяет части, которые он дал, здесь другой способ добавить новый столбец родителя после операции 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
Ответ 3
Могу ли я предложить метод transform
(вместо агрегата)? Если вы используете его в своем первоначальном примере, он должен делать то, что вы хотите (трансляция).
Ответ 4
Как правило, при использовании groupby(), если вы используете функцию .transform(), pandas вернет таблицу с той же длиной, что и ваш оригинал. Когда вы используете другие функции, такие как .sum() или .first(), тогда pandas возвращает таблицу, в которой каждая строка является группой.
Я не уверен, как это работает с применением, но реализация сложных лямбда-функций с преобразованием может быть довольно сложной, поэтому стратегия, которую я считаю наиболее полезной, - это создать нужные мне переменные, поместить их в исходный набор данных, а затем сделать там.
Если я понимаю, что вы пытаетесь сделать правильно (я извиняюсь, если ошибаюсь), сначала вы можете рассчитать общую рыночную капитализацию для каждой группы:
bdata['group_MarketCap'] = bdata.groupby('yearmonth')['MarketCap'].transform('sum')
Это добавит столбец с именем "group_MarketCap" к вашим исходным данным, который будет содержать сумму рыночных ограничений для каждой группы. Затем вы можете рассчитать взвешенные значения напрямую:
bdata['weighted_P'] = bdata['PriceReturn'] * (bdata['MarketCap']/bdata['group_MarketCap'])
И, наконец, вы бы вычислили средневзвешенное значение для каждой группы, используя одну и ту же функцию преобразования:
bdata['MarketReturn'] = bdata.groupby('yearmonth')['weighted_P'].transform('sum')
Я стараюсь строить свои переменные таким образом. Иногда вы можете выделить все в одной команде, но это не всегда работает с groupby(), потому что большую часть времени pandas необходимо создать экземпляр нового объекта для работы с ним на полной шкале набора данных (т.е. Вы можете 't добавить два столбца вместе, если их еще нет).
Надеюсь, что это поможет:)
Ответ 5
Это работает?
capWeighting = lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum()
bdata["MarketReturn"] = bdata.groupby("yearmonth").transform(capWeighting)
Я использую reindex_like
для этого:
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')