Python Pandas ¿Cómo asignar los resultados de la operación grupal a las columnas en el dataframe principal?

Tengo el siguiente dataframe en IPython, donde cada fila es un stock único:

In [261]: bdata Out[261]:  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) 

Quiero aplicar una operación groupby que calcula el rendimiento promedio ponderado por límite en todo, por cada fecha en la columna “año mes”.

Esto funciona como se esperaba:

 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 

Pero luego quiero ordenar “transmitir” estos valores a los índices en el dataframe original, y guardarlos como columnas constantes donde coinciden las fechas.

 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/ in () ----> 1 dateGrps["MarketReturn"] = dateGrps.apply(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum()) TypeError: 'DataFrameGroupBy' object does not support item assignment 

Me doy cuenta de que esta ingenua tarea no debería funcionar. Pero, ¿cuál es el lenguaje Pandas “correcto” para asignar el resultado de una operación groupby a una nueva columna en el dataframe principal?

Al final, quiero una columna llamada “MarketReturn” que será un valor constante repetido para todos los índices que tengan fecha coincidente con la salida de la operación groupby.

Un truco para lograrlo sería el siguiente:

 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] 

Pero esto es lento, malo y antipónico.

 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]: AB 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 

Mientras sigo explorando todas las formas increíblemente inteligentes que apply concatenaciones de las piezas que se le han dado, aquí hay otra manera de agregar una nueva columna en la matriz después de una operación grupal.

 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 

¿Puedo sugerir el método de transform (en lugar de agregado)? Si lo usa en su ejemplo original, debería hacer lo que quiera (la transmisión).

Como regla general, cuando use groupby (), si usa la función .transform (), los pandas devolverán una tabla con la misma longitud que su original. Cuando usas otras funciones como .sum () o .first (), entonces los pandas devolverán una tabla donde cada fila es un grupo.

No estoy seguro de cómo funciona esto con aplicar, pero la implementación de funciones lambda elaboradas con transformación puede ser bastante complicada, por lo que la estrategia que me resulta más útil es crear las variables que necesito, colocarlas en el conjunto de datos original y luego realizar mis operaciones allí.

Si entiendo lo que intentas hacer correctamente (pido disculpas si me equivoco), primero puedes calcular la capitalización de mercado total para cada grupo:

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

Esto agregará una columna llamada “group_MarketCap” a sus datos originales que contendría la sum de los límites de mercado para cada grupo. Entonces puedes calcular los valores ponderados directamente:

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

Y finalmente, calcularía el promedio ponderado para cada grupo utilizando la misma función de transformación:

 bdata['MarketReturn'] = bdata.groupby('yearmonth')['weighted_P'].transform('sum') 

Tiendo a construir mis variables de esta manera. A veces puede quitarlo todo en un solo comando, pero eso no siempre funciona con groupby () porque la mayoría de las veces los pandas necesitan crear una instancia del nuevo objeto para operarlo en la escala del conjunto de datos completo (es decir, no puede agregue dos columnas juntas si una no existe todavía).

Espero que esto ayude 🙂

¿Esto funciona?

 capWeighting = lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum() bdata["MarketReturn"] = bdata.groupby("yearmonth").transform(capWeighting) 

Yo uso reindex_like para esto:

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