python pandas group-by interpolation pandas-groupby

python - rellenar el dataframe con NaN cuando faltan datos de varios días



pandas group-by (4)

¿Es esto lo que quieres?

data0 = """2017-10-01 0.000000 0.112869 2017-10-02 0.017143 0.112869 2017-10-12 0.003750 0.117274 2017-10-14 0.000000 0.161556 2017-10-17 0.000000 0.116264""" data = [row.split('' '') for row in data0.split(''/n'')] df = pd.DataFrame(data, columns = [''date'',''col_1'',''vals'']) df.date = pd.to_datetime(df.date) last_observation = df.assign(last_observation = df.date.diff().dt.days) df.set_index([''date''], inplace = True) all_dates = pd.date_range(start = last_observation.date.min(), end = last_observation.date.max()) df_interpolated = df.reindex(all_dates).astype(np.float64).interpolate() df_interpolated = df_interpolated.join(last_observation.set_index(''date'').last_observation) df_interpolated[''discard''] = (df_interpolated.last_observation.bfill() > 5) & df_interpolated.last_observation.isnull() df_interpolated[[''col_1'',''vals'']] = df_interpolated[[''col_1'',''vals'']].where(~df_interpolated.discard)

El resultado es:

col_1 vals last_observation discard 2017-10-01 0.000000 0.112869 NaN False 2017-10-02 0.017143 0.112869 1.0 False 2017-10-03 NaN NaN NaN True 2017-10-04 NaN NaN NaN True 2017-10-05 NaN NaN NaN True 2017-10-06 NaN NaN NaN True 2017-10-07 NaN NaN NaN True 2017-10-08 NaN NaN NaN True 2017-10-09 NaN NaN NaN True 2017-10-10 NaN NaN NaN True 2017-10-11 NaN NaN NaN True 2017-10-12 0.003750 0.117274 10.0 False 2017-10-13 0.001875 0.139415 NaN False 2017-10-14 0.000000 0.161556 2.0 False 2017-10-15 0.000000 0.146459 NaN False 2017-10-16 0.000000 0.131361 NaN False 2017-10-17 0.000000 0.116264 3.0 False

La idea es que primero generes la interpolación (como lo hiciste) y luego decides qué observaciones soltar. Comience asignando el número de días entre la observación actual y la última. Como desea descartar las entradas donde este número excede de 5, y las anteriores, utilice .bfill para asignar este número a las interpolaciones anteriores antes de comparar 5. Tenga en cuenta, sin embargo, que para las decisiones de descarte positivo, la observación se descartaría que no quieres Por lo tanto, debe incluir la condición de que no descarta las observaciones, que verifica con el método .notnull() en la last_observation columna de last_observation .

Finalmente, use el método .where para mantener las entradas que no cumplan con el criterio de descarte; por defecto, los otros son reemplazados por NA.

Tengo un marco de datos de pandas que interpolar para obtener un marco de datos diario. El marco de datos original se ve así:

col_1 vals 2017-10-01 0.000000 0.112869 2017-10-02 0.017143 0.112869 2017-10-12 0.003750 0.117274 2017-10-14 0.000000 0.161556 2017-10-17 0.000000 0.116264

En el marco de datos interpolados, deseo cambiar los valores de los datos a NaN, donde la brecha en las fechas excede los 5 días. Por ejemplo, en el marco de datos anterior, la brecha entre 2017-10-02 y 2017-10-12 supera los 5 días, por lo tanto, en el marco de datos interpolado, todos los valores entre estas 2 fechas deben eliminarse. No estoy seguro de cómo hacer esto, ¿tal vez combine_first ?

--EDIT: Dataframe interpolado se ve así:

col_1 vals 2017-10-01 0.000000 0.112869 2017-10-02 0.017143 0.112869 2017-10-03 0.015804 0.113309 2017-10-04 0.014464 0.113750 2017-10-05 0.013125 0.114190 2017-10-06 0.011786 0.114631 2017-10-07 0.010446 0.115071 2017-10-08 0.009107 0.115512 2017-10-09 0.007768 0.115953 2017-10-10 0.006429 0.116393 2017-10-11 0.005089 0.116834 2017-10-12 0.003750 0.117274 2017-10-13 0.001875 0.139415 2017-10-14 0.000000 0.161556 2017-10-15 0.000000 0.146459 2017-10-16 0.000000 0.131361 2017-10-17 0.000000 0.116264

Rendimiento esperado:

col_1 vals 2017-10-01 0.000000 0.112869 2017-10-02 0.017143 0.112869 2017-10-12 0.003750 0.117274 2017-10-13 0.001875 0.139415 2017-10-14 0.000000 0.161556 2017-10-15 0.000000 0.146459 2017-10-16 0.000000 0.131361 2017-10-17 0.000000 0.116264


Primero identificaría dónde las brechas excedieron los 5 días. A partir de ahí, genero una matriz que identificó grupos entre tales brechas. Finalmente, utilizaría groupby para activar la frecuencia diaria e interpolar.

# convenience: assign string to variable for easier access daytype = ''timedelta64[D]'' # define five days for use when evaluating size of gaps five = np.array(5, dtype=daytype) # get the size of gaps deltas = np.diff(df.index.values).astype(daytype) # identify groups between gaps groups = np.append(False, deltas > five).cumsum() # handy function to turn to daily frequency and interpolate to_daily = lambda x: x.asfreq(''D'').interpolate() # and finally... df.groupby(groups, group_keys=False).apply(to_daily) col_1 vals 2017-10-01 0.000000 0.112869 2017-10-02 0.017143 0.112869 2017-10-12 0.003750 0.117274 2017-10-13 0.001875 0.139415 2017-10-14 0.000000 0.161556 2017-10-15 0.000000 0.146459 2017-10-16 0.000000 0.131361 2017-10-17 0.000000 0.116264

En caso de que quiera proporcionar su propio método de interpolación. Puede modificar lo anterior de esta manera:

daytype = ''timedelta64[D]'' five = np.array(5, dtype=daytype) deltas = np.diff(df.index.values).astype(daytype) groups = np.append(False, deltas > five).cumsum() # custom interpolation function that takes a dataframe def my_interpolate(df): """This can be whatever you want. I just provided what will result in the same thing as before.""" return df.interpolate() to_daily = lambda x: x.asfreq(''D'').pipe(my_interpolate) df.groupby(groups, group_keys=False).apply(to_daily) col_1 vals 2017-10-01 0.000000 0.112869 2017-10-02 0.017143 0.112869 2017-10-12 0.003750 0.117274 2017-10-13 0.001875 0.139415 2017-10-14 0.000000 0.161556 2017-10-15 0.000000 0.146459 2017-10-16 0.000000 0.131361 2017-10-17 0.000000 0.116264


Si entendí que es correcto, puede eliminar las filas innecesarias por indexación booleana. Suponiendo que tiene la diferencia en días en una columna llamada diff , puede usar df.loc[df[''diff''].dt.days < 5]

Aquí hay una demostración

df = pd.read_clipboard() col_1 vals 2017-10-01 0.000000 0.112869 2017-10-02 0.017143 0.112869 2017-10-12 0.003750 0.117274 2017-10-14 0.000000 0.161556 2017-10-17 0.000000 0.116264

Convertir a una columna de tiempo y obtener una nueva columna para la diferencia con el siguiente valor en días

df = df.reset_index() df[''index'']=pd.to_datetime(df[''index'']) df[''diff''] = df[''index''] - df[''index''].shift(1) index col_1 vals diff 0 2017-10-01 0.000000 0.112869 NaT 1 2017-10-02 0.017143 0.112869 1 days 2 2017-10-12 0.003750 0.117274 10 days 3 2017-10-14 0.000000 0.161556 2 days 4 2017-10-17 0.000000 0.116264 3 days

Agregar un filtro boolian

new_df = df.loc[df[''diff''].dt.days < 5] new_df = new_df.drop(''diff'', axis=1) new_df.set_index(''index'', inplace=True) new_df col_1 vals index 2017-10-02 0.017143 0.112869 2017-10-14 0.000000 0.161556 2017-10-17 0.000000 0.116264


Agregué algunas filas más a su ejemplo para tener dos bloques con más de 5 días de intervalo entre filas.
Guardé las dos tablas localmente como archivos .csv y agregué la date como el primer nombre de columna para completar la fusión a continuación:

Preparar

import pandas as pd import numpy as np df_1=pd.read_csv(''df_1.csv'', delimiter=r"/s+") df_2=pd.read_csv(''df_2.csv'', delimiter=r"/s+")

fusionar (unir) los dos conjuntos de datos y renombrar las columnas:
observe dos grupos con más de 5 días de diferencia.

df=df_2.merge(df_1, how=''left'', on=''Date'').reset_index(drop=True) df.columns=[''date'',''col'',''val'',''col_na'',''val_na''] #purely aesthetic df date col val col_na val_na 0 2017-10-01 0.000000 0.112869 0.000000 0.112869 1 2017-10-02 0.017143 0.112869 0.017143 0.112869 2 2017-10-03 0.015804 0.113309 NaN NaN 3 2017-10-04 0.014464 0.113750 NaN NaN 4 2017-10-05 0.013125 0.114190 NaN NaN 5 2017-10-06 0.011786 0.114631 NaN NaN 6 2017-10-07 0.010446 0.115071 NaN NaN 7 2017-10-08 0.009107 0.115512 NaN NaN 8 2017-10-09 0.007768 0.115953 NaN NaN 9 2017-10-10 0.006429 0.116393 NaN NaN 10 2017-10-11 0.005089 0.116834 NaN NaN 11 2017-10-12 0.003750 0.117274 0.003750 0.117274 12 2017-10-13 0.001875 0.139415 NaN NaN 13 2017-10-14 0.000000 0.161556 0.000000 0.161556 14 2017-10-15 0.000000 0.146459 NaN NaN 15 2017-10-16 0.000000 0.131361 NaN NaN 16 2017-10-17 0.000000 0.989999 0.000000 0.116264 17 2017-10-18 0.000000 0.412311 NaN NaN 18 2017-10-19 0.000000 0.166264 NaN NaN 19 2017-10-20 0.000000 0.123464 NaN NaN 20 2017-10-21 0.000000 0.149767 NaN NaN 21 2017-10-22 0.000000 0.376455 NaN NaN 22 2017-10-23 0.000000 0.000215 NaN NaN 23 2017-10-24 0.000000 0.940219 NaN NaN 24 2017-10-25 0.000000 0.030352 0.000000 0.030352 25 2017-10-26 0.000000 0.111112 NaN NaN 26 2017-10-27 0.000000 0.002500 NaN NaN

Método para llevar a cabo la tarea

def my_func(my_df): non_na_index=[] #define empty list for i in range(len(my_df.iloc[:,[1]])): if not pd.isnull(my_df.iloc[i,[3]][0]): non_na_index.append(i) #add indexes of rows that that have non NaN value sub=np.roll(non_na_index, shift=-1)-non_na_index #subract column in indexes to find row count of NaN sub=sub[:-1] #get rid of last element (calculation artifact) for i in reversed(range(len(sub))): if sub[i]>=5: #identidy indexes with more than 5 NaN in between b=non_na_index[i+1] #assign end index a=non_na_index[i]+1 #assign start index my_df=my_df.drop(my_df.index[[range(a,b)]]) #drop the rows within the range return(my_df)

ejecutar la función usando df

new_df=my_func(df) new_df=df.drop([''col_na'',''val_na''],1) # drop the two extra columns new_df date col val 0 2017-10-01 0.000000 0.112869 1 2017-10-02 0.017143 0.112869 11 2017-10-12 0.003750 0.117274 12 2017-10-13 0.001875 0.139415 13 2017-10-14 0.000000 0.161556 14 2017-10-15 0.000000 0.146459 15 2017-10-16 0.000000 0.131361 16 2017-10-17 0.000000 0.989999 24 2017-10-25 0.000000 0.030352 25 2017-10-26 0.000000 0.111112 26 2017-10-27 0.000000 0.002500