seleccionar - Python Pandas ¿Cómo asignar los resultados de operación groupby a columnas en el marco de datos padre?
seleccionar dos columnas en python (5)
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)
Deseo aplicar una operación de agrupamiento por grupo que calcula el rendimiento medio ponderado por capítulos en todo, por cada fecha en la columna "año-año".
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 "enviar" estos valores de vuelta 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 tarea ingenua no debería funcionar. Pero, ¿cuál es el modismo pandas "correcto" para asignar el resultado de una operación groupby a una nueva columna en el dataframe padre?
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 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 antiponónico.
¿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'')
¿Puedo sugerir el método de transform
(en lugar de agregar)? Si lo usa en su ejemplo original, debe hacer lo que quiera (la transmisión).
Como regla general al usar groupby (), si usa la función .transform () pandas devolverá una tabla con la misma longitud que su original. Cuando use otras funciones como .sum () o .first (), entonces pandas devolverá una tabla donde 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 considero más útil es crear las variables que necesito, ubicarlas en el conjunto de datos original y luego realizar mis operaciones allí.
Si entiendo lo que intentas hacer correctamente (me disculpo si me equivoco) primero puedes calcular el límite total del mercado 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. Entonces puede calcular los valores ponderados directamente:
bdata[''weighted_P''] = bdata[''PriceReturn''] * (bdata[''MarketCap'']/bdata[''group_MarketCap''])
Y finalmente se 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 puedes hacer 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 operar en la escala de conjunto de datos completa (es decir, no puedes agregue dos columnas juntas si todavía no existe una).
Espero que esto ayude :)
Mientras sigo explorando todas las formas increíblemente inteligentes que se apply
concatenar las piezas que se le otorgan, aquí hay otra forma de agregar una nueva columna en el elemento principal después de una operación 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
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