Llene los vacíos de datos con el promedio de datos de días adyacentes

Imagine un dataframe con múltiples variables medidas cada 30 minutos. Cada serie de tiempo dentro de este dataframe tiene huecos en posiciones posiblemente diferentes. Estas brechas deben ser reemplazadas por algún tipo de media stream, digamos +/- 2 días. Por ejemplo, si al día 4 07:30 me faltan datos, quiero reemplazar una entrada NaN con el promedio de las mediciones a las 07:30 el día 2, 3, 5 y 6. Tenga en cuenta que también es posible que, por ejemplo, el día 5, 07:30 también es NaN ; en este caso, debe excluirse del promedio que reemplaza la medición faltante en el día 4 (¿debería ser posible con np.nanmean ?)

No estoy seguro de cómo hacer esto. En este momento, probablemente pasaría por cada fila y columna en el dataframe y escribiría un hack muy malo en la línea de np.mean(df.ix[[i-48, i, i+48], "A"]) , pero siento que debe haber una forma más pythonica / pandas-y?

Conjunto de datos de muestra:

 import numpy as np import pandas as pd # generate a 1-week time series dates = pd.date_range(start="2014-01-01 00:00", end="2014-01-07 00:00", freq="30min") df = pd.DataFrame(np.random.randn(len(dates),3), index=dates, columns=("A", "B", "C")) # generate some artificial gaps df.ix["2014-01-04 10:00":"2014-01-04 11:00", "A"] = np.nan df.ix["2014-01-04 12:30":"2014-01-04 14:00", "B"] = np.nan df.ix["2014-01-04 09:30":"2014-01-04 15:00", "C"] = np.nan print df["2014-01-04 08:00":"2014-01-04 16:00"] ABC 2014-01-04 08:00:00 0.675720 2.186484 -0.033969 2014-01-04 08:30:00 -0.897217 1.332437 -2.618197 2014-01-04 09:00:00 0.299395 0.837023 1.346117 2014-01-04 09:30:00 0.223051 0.913047 NaN 2014-01-04 10:00:00 NaN 1.395480 NaN 2014-01-04 10:30:00 NaN -0.800921 NaN 2014-01-04 11:00:00 NaN -0.932760 NaN 2014-01-04 11:30:00 0.057219 -0.071280 NaN 2014-01-04 12:00:00 0.215810 -1.099531 NaN 2014-01-04 12:30:00 -0.532563 NaN NaN 2014-01-04 13:00:00 -0.697872 NaN NaN 2014-01-04 13:30:00 -0.028541 NaN NaN 2014-01-04 14:00:00 -0.073426 NaN NaN 2014-01-04 14:30:00 -1.187419 0.221636 NaN 2014-01-04 15:00:00 1.802449 0.144715 NaN 2014-01-04 15:30:00 0.446615 1.013915 -1.813272 2014-01-04 16:00:00 -0.410670 1.265309 -0.198607 [17 rows x 3 columns] 

(Una herramienta aún más sofisticada también excluiría las mediciones del procedimiento de promediado que ellos mismos crearon promediando, pero eso no necesariamente tiene que ser incluido en una respuesta, ya que creo que esto puede hacer las cosas demasiado complicadas por ahora).

/ edit: una solución de ejemplo con la que no estoy realmente contento:

 # specify the columns of df where gaps should be filled cols = ["A", "B", "C"] for col in cols: for idx, rows in df.iterrows(): if np.isnan(df.ix[idx, col]): # replace with mean of adjacent days df.ix[idx, col] = np.nanmean(df.ix[[idx-48, idx+48], col]) 

Hay dos cosas que no me gustan de esta solución:

  1. Si falta una sola línea o está duplicada en alguna parte, esto falla. En la última línea, me gustaría restar “un día” todo el tiempo, no importa si están a 47, 48 o 49 filas de distancia. Además, sería bueno que pudiera extender el rango (por ejemplo, -3 días a +3 días) sin escribir manualmente una lista para el índice.
  2. Me gustaría deshacerme de los bucles, si eso es posible.

Esta debería ser una forma más rápida y concisa de hacerlo. Lo principal es usar la función shift () en lugar del bucle. La versión simple sería esta:

 df[ df.isnull() ] = np.nanmean( [ df.shift(-48), df.shift(48) ] ) 

Resultó ser muy difícil generalizar esto, pero esto parece funcionar:

 df[ df.isnull() ] = np.nanmean( [ df.shift(x).values for x in range(-48*window,48*(window+1),48) ], axis=0 ) 

No estoy seguro, pero sospecho que puede haber un error con Nanmean y también es la misma razón por la que te faltan valores. Me parece que nanmean no puede manejar nans si le das un dataframe. Pero si me convierto a una matriz (con .values) y uso axis = 0, entonces parece que funciona.

Compruebe los resultados para window = 1:

 print df.ix["2014-01-04 12:30":"2014-01-04 14:00", "B"] print df.ix["2014-01-03 12:30":"2014-01-03 14:00", "B"] print df.ix["2014-01-05 12:30":"2014-01-05 14:00", "B"] 2014-01-04 12:30:00 0.940193 # was nan, now filled 2014-01-04 13:00:00 0.078160 2014-01-04 13:30:00 -0.662918 2014-01-04 14:00:00 -0.967121 2014-01-03 12:30:00 0.947915 # day before 2014-01-03 13:00:00 0.167218 2014-01-03 13:30:00 -0.391444 2014-01-03 14:00:00 -1.157040 2014-01-05 12:30:00 0.932471 # day after 2014-01-05 13:00:00 -0.010899 2014-01-05 13:30:00 -0.934391 2014-01-05 14:00:00 -0.777203 

Con respecto al problema # 2, dependerá de sus datos, pero si usted precede al anterior con

df = df.resample('30min')

eso te dará una fila de nans para todas las filas que faltan y luego podrás rellenarlas del mismo modo que todas las demás nans. Esa es probablemente la forma más simple y rápida si funciona.

Alternativamente, podrías hacer algo con groupby. Mi groupby-fu es débil pero para darte el sabor, algo como:

df.groupby( df.index.hour ).fillna(method='pad')

trataría correctamente el problema de las filas faltantes, pero no las otras cosas.