Cálculo de diferencias dentro de grupos de un dataframe

Digamos que tengo un dataframe con 3 columnas: Fecha, Marcador, Valor (no hay índice, al menos para empezar). Tengo muchas fechas y muchos tickers, pero cada tupla (ticker, date) es única. (Pero, obviamente, la misma fecha se mostrará en muchas filas ya que estará allí para varios tickers, y el mismo ticker se mostrará en varias filas ya que estará allí para muchas fechas).

Inicialmente, mis filas en un orden específico, pero no ordenadas por ninguna de las columnas.

Me gustaría calcular las primeras diferencias (cambios diarios) de cada ticker (ordenadas por fecha) y colocarlas en una nueva columna en mi dataframe. Dado este contexto, no puedo simplemente hacer

 df['diffs'] = df['value'].diff() 

porque las filas adyacentes no provienen del mismo ticker. Ordenar de esta manera:

 df = df.sort(['ticker', 'date']) df['diffs'] = df['value'].diff() 

No resuelve el problema porque habrá “fronteras”. Es decir, después de esa clasificación, el último valor para un ticker estará por encima del primer valor para el siguiente ticker. Y computar las diferencias tomaría una diferencia entre dos tickers. No quiero esto Quiero que la fecha más temprana para cada ticker termine con un NaN en su columna de diferencia.

Este parece ser un momento obvio para usar groupby pero por alguna razón, parece que no puedo hacerlo funcionar correctamente. Para ser claros, me gustaría realizar el siguiente proceso:

  1. Grupo de filas en función de su ticker
  2. Dentro de cada grupo, ordena las filas por su date
  3. Dentro de cada grupo ordenado, calcular las diferencias de la columna de value
  4. Coloque estas diferencias en el dataframe original en una nueva columna de diffs (idealmente, dejando el orden del dataframe original intacto).

Tengo que imaginar que esto es de una sola línea. Pero ¿qué me estoy perdiendo?


Editar a las 9:00 pm 2013-12-17

Ok … un poco de progreso. Puedo hacer lo siguiente para obtener un nuevo dataframe:

 result = df.set_index(['ticker', 'date'])\ .groupby(level='ticker')\ .transform(lambda x: x.sort_index().diff())\ .reset_index() 

Pero si entiendo la mecánica de groupby, mis filas ahora se ordenarán primero por ticker y luego por date . ¿Es eso correcto? Si es así, ¿necesitaría hacer una combinación para agregar la columna de diferencias (actualmente en el result['current'] al df datos original?

no sería más fácil hacer lo que uno mismo describe, a saber

 df.sort(['ticker', 'date'], inplace=True) df['diffs'] = df['value'].diff() 

y luego corregir para las fronteras:

 mask = df.ticker != df.ticker.shift(1) df['diffs'][mask] = np.nan 

para mantener el índice original puede hacer idx = df.index al principio, y luego al final puede hacer df.reindex(idx) , o si es un gran dataframe, realice las operaciones en

 df.filter(['ticker', 'date', 'value']) 

y luego join los dos marcos de datos al final.

edición : alternativamente, (aunque todavía no use groupby )

 df.set_index(['ticker','date'], inplace=True) df.sort_index(inplace=True) df['diffs'] = np.nan for idx in df.index.levels[0]: df.diffs[idx] = df.value[idx].diff() 

para

  date ticker value 0 63 C 1.65 1 88 C -1.93 2 22 C -1.29 3 76 A -0.79 4 72 B -1.24 5 34 A -0.23 6 92 B 2.43 7 22 A 0.55 8 32 A -2.50 9 59 B -1.01 

esto producirá:

  value diffs ticker date A 22 0.55 NaN 32 -2.50 -3.05 34 -0.23 2.27 76 -0.79 -0.56 B 59 -1.01 NaN 72 -1.24 -0.23 92 2.43 3.67 C 22 -1.29 NaN 63 1.65 2.94 88 -1.93 -3.58 

De acuerdo. Estoy pensando mucho en esto, y creo que esta es mi combinación favorita de las soluciones anteriores y un poco de juego. Los datos originales viven en df :

 df.sort(['ticker', 'date'], inplace=True) # for this example, with diff, I think this syntax is a bit clunky # but for more general examples, this should be good. But can we do better? df['diffs'] = df.groupby(['ticker'])['value'].transform(lambda x: x.diff()) df.sort_index(inplace=True) 

Esto logrará todo lo que quiero. Y lo que realmente me gusta es que puede generalizarse a los casos en los que desea aplicar una función más intrincada que diff . En particular, podría hacer cosas como lambda x: pd.rolling_mean(x, 20, 20) para hacer una columna de medios rodantes donde no tenga que preocuparse de que los datos de cada ticker se corrompan por los de cualquier otro ticker ( groupby se encarga de eso por ti …).

Así que aquí está la pregunta que me queda … ¿por qué no funciona lo siguiente para la línea que comienza con df['diffs'] :

 df['diffs'] = df.groupby[('ticker')]['value'].transform(np.diff) 

Cuando hago eso, obtengo una columna de diffs llena de 0. ¿Alguna idea sobre eso?

Aquí hay una solución que se basa en lo que @ behzad.nouri escribió, pero usando pd.IndexSlice :

 df = df.set_index(['ticker', 'date']).sort_index()[['value']] df['diff'] = np.nan idx = pd.IndexSlice for ix in df.index.levels[0]: df.loc[ idx[ix,:], 'diff'] = df.loc[idx[ix,:], 'value' ].diff() 

Por:

 > df date ticker value 0 63 C 1.65 1 88 C -1.93 2 22 C -1.29 3 76 A -0.79 4 72 B -1.24 5 34 A -0.23 6 92 B 2.43 7 22 A 0.55 8 32 A -2.50 9 59 B -1.01 

Vuelve:

 > df value diff ticker date A 22 0.55 NaN 32 -2.50 -3.05 34 -0.23 2.27 76 -0.79 -0.56 B 59 -1.01 NaN 72 -1.24 -0.23 92 2.43 3.67 C 22 -1.29 NaN 63 1.65 2.94 88 -1.93 -3.58 

Puede usar pivot para convertir el dataframe en una tabla de fecha-marcador, aquí hay un ejemplo:

crear los datos de prueba primero:

 import pandas as pd import numpy as np import random from itertools import product dates = pd.date_range(start="2013-12-01", periods=10).to_native_types() ticks = "ABCDEF" pairs = list(product(dates, ticks)) random.shuffle(pairs) pairs = pairs[:-5] values = np.random.rand(len(pairs)) dates, ticks = zip(*pairs) df = pd.DataFrame({"date":dates, "tick":ticks, "value":values}) 

convertir el dataframe por formato de pivot :

 df2 = df.pivot(index="date", columns="tick", values="value") 

llenar NaN:

 df2 = df2.fillna(method="ffill") 

llamar al método diff() :

 df2.diff() 

df2 es lo que parece df2 :

 tick ABCDEF date 2013-12-01 0.077260 0.084008 0.711626 0.071267 0.811979 0.429552 2013-12-02 0.106349 0.141972 0.457850 0.338869 0.721703 0.217295 2013-12-03 0.330300 0.893997 0.648687 0.628502 0.543710 0.217295 2013-12-04 0.640902 0.827559 0.243816 0.819218 0.543710 0.190338 2013-12-05 0.263300 0.604084 0.655723 0.299913 0.756980 0.135087 2013-12-06 0.278123 0.243264 0.907513 0.723819 0.506553 0.717509 2013-12-07 0.960452 0.243264 0.357450 0.160799 0.506553 0.194619 2013-12-08 0.670322 0.256874 0.637153 0.582727 0.628581 0.159636 2013-12-09 0.226519 0.284157 0.388755 0.325461 0.957234 0.810376 2013-12-10 0.958412 0.852611 0.472012 0.832173 0.957234 0.723234