Tengo el siguiente marco de datos en IPython, donde cada fila es una sola acción:
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)
Quiero aplicar una operación groupby que calcule el rendimiento promedio ponderado por capitalización en todo, por cada fecha en la columna "yearmonth".
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 "transmitir" estos valores a los índices en el marco de datos 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/<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
Me doy cuenta de que esta ingenua tarea no debería funcionar. Pero, ¿cuál es el idioma "correcto" de Pandas para asignar el resultado de una operación groupby en una nueva columna en el marco de datos principal?
Al final, quiero una columna llamada "MarketReturn" que será un valor constante repetido para todos los índices que tienen una fecha coincidente con la salida de la operación groupby.
Un truco para lograr esto 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 poco pítico.
Respuestas:
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
fuente
Si bien todavía estoy explorando todas las formas increíblemente inteligentes de
apply
concatenar las piezas que se le dan, aquí hay otra forma de agregar una nueva columna en el padre después de una operación de grupo.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
fuente
df.groupby('yearmonth').apply(lambda grp: grp.assign(mkt_return=grp['return'].sum()))
Como regla general al usar groupby (), si usa la función .transform (), los pandas devolverán una tabla con la misma longitud que la original. Cuando usa otras funciones como .sum () o .first (), los pandas devolverán una tabla en la que cada fila es un grupo.
No estoy seguro de cómo funciona esto con apply, pero implementar funciones lambda elaboradas con transform puede ser bastante complicado, por lo que la estrategia que encuentro más útil es crear las variables que necesito, colocarlas en el conjunto de datos original y luego realizar mis operaciones allí.
Si primero entiendo lo que está tratando de hacer correctamente, puede 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 suma de los límites de mercado para cada grupo. Luego, puede 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 usando 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 lograr ponerlo todo en un solo comando, pero eso no siempre funciona con groupby () porque la mayoría de las veces los pandas necesitan instanciar el nuevo objeto para operar en él a la escala completa del conjunto de datos (es decir, no puede agregue dos columnas juntas si una aún no existe).
Espero que esto ayude :)
fuente
¿Puedo sugerir el
transform
método (en lugar de agregado)? Si lo usa en su ejemplo original, debería hacer lo que quiera (la transmisión).fuente
transform
parece más limpio. No tengo datos de EMS para confirmar esto, pero esto podría funcionar (aunque la función lambda podría tener que modificarse):bdata['mkt_return'] = bdata.groupby("yearmonth").transform(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())
transform
no se deja uno para operar en múltiples columnas despuésgroupby
, por ejemplo,df.groupby('col_3')[['col_1','col_2']].transform(lambda x: ((1-x.col_1.mean()) - x.col_2.std()))
se generará un error quejándose de que 'ningún atributo XXX'No encontré una manera de hacer una asignación al marco de datos original. Así que solo almaceno los resultados de los grupos y los concateno. Luego ordenamos el marco de datos concatenados por índice para obtener el orden original como marco de datos de entrada. Aquí hay un código de muestra:
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
Este método es bastante rápido y extensible. Puede obtener cualquier característica aquí.
fuente