¿Hay alguna manera de cambiar una columna de marco de datos dependiendo de la condición en otras dos columnas? algo como:
df["cumulated_closed_value"] = df.groupby("user").['close_cumsum'].shiftWhile(df['close_time']>df['open_time])He descubierto una manera de hacer esto, pero es ineficiente:
1) Cargue datos y cree la columna para cambiar
df=pd.read_csv('data.csv') df.sort_values(['user','close_time'],inplace=True) df['close_cumsum']=df.groupby('user')['value'].cumsum() df.sort_values(['user','open_time'],inplace=True) print(df)producción:
user open_time close_time value close_cumsum 0 1 2017-01-01 2017-03-01 5 18 1 1 2017-01-02 2017-02-01 6 6 2 1 2017-02-03 2017-02-05 7 13 3 1 2017-02-07 2017-04-01 3 21 4 1 2017-09-07 2017-09-11 1 22 5 2 2018-01-01 2018-02-01 15 15 6 2 2018-03-01 2018-04-01 3 182) cambiar la columna con una autounión y algunos filtros
Unión automática (esto es memoria ineficiente) df2=pd.merge(df[['user','open_time']],df[['user','close_time','close_cumsum']], on='user')
filtrar por 'hora_de_cierre' < 'hora_de_apertura'. Luego obtenga la fila con el tiempo de cierre máximo
df2=df2[df2['close_time']<df2['open_time']] idx = df2.groupby(['user','open_time'])['close_time'].transform(max) == df2['close_time'] df2=df2[idx]3) fusionar con el conjunto de datos original:
df3=pd.merge(df[['user','open_time','close_time','value']],df2[['user','open_time','close_cumsum']],how='left') print(df3)producción:
user open_time close_time value close_cumsum 0 1 2017-01-01 2017-03-01 5 NaN 1 1 2017-01-02 2017-02-01 6 NaN 2 1 2017-02-03 2017-02-05 7 6.0 3 1 2017-02-07 2017-04-01 3 13.0 4 1 2017-09-07 2017-09-11 1 21.0 5 2 2018-01-01 2018-02-01 15 NaN 6 2 2018-03-01 2018-04-01 3 15.0¿Hay una forma más pandas de obtener el mismo resultado?
Editar: he agregado una línea de datos para que el caso sea más claro. Mi objetivo es obtener la suma de todas las transacciones cerradas antes de la hora de apertura de la nueva transacción
Estoy usando un nuevo párrafo aquí para registrar la condición df2['close_time']<df2['open_time']
df['New']=((df.open_time-df.close_time.shift()).dt.days>0).shift(-1) s=df.groupby('user').apply(lambda x : (x['value']*x['New']).cumsum().shift()).reset_index(level=0,drop=True) s.loc[~(df.New.shift()==True)]=np.nan df['Cumsum']=s df Out[1043]: user open_time close_time value New Cumsum 0 1 2017-01-01 2017-03-01 5 False NaN 1 1 2017-01-02 2017-02-01 6 True NaN 2 1 2017-02-03 2017-02-05 7 True 6 3 1 2017-02-07 2017-04-01 3 False 13 4 2 2017-01-01 2017-02-01 15 True NaN 5 2 2017-03-01 2017-04-01 3 NaN 15Actualización: desde op actualice la pregunta (Datos de Gabriel A)
df['New']=df.user.map(df.groupby('user').close_time.apply(lambda x: np.array(x))) df['New1']=df.user.map(df.groupby('user').value.apply(lambda x: np.array(x))) df['New2']=[[x>m for m in y] for x,y in zip(df['open_time'],df['New']) ] df['Yourtarget']=list(map(sum,df['New2']*df['New1'].values)) df.drop(['New','New1','New2'],1) Out[1376]: user open_time close_time value Yourtarget 0 1 2016-12-30 2016-12-31 1 0 1 1 2017-01-01 2017-03-01 5 1 2 1 2017-01-02 2017-02-01 6 1 3 1 2017-02-03 2017-02-05 7 7 4 1 2017-02-07 2017-04-01 3 14 5 1 2017-09-07 2017-09-11 1 22 6 2 2018-01-01 2018-02-01 15 0 7 2 2018-03-01 2018-04-01 3 15Hice una modificación a su caso de prueba que creo que debería incluir. Esta solución maneja su edición.
import pandas as pd import numpy as np df = pd.read_csv("cond_shift.csv") dfaporte:
user open_time close_time value 0 1 12/30/2016 12/31/2016 1 1 1 1/1/2017 3/1/2017 5 2 1 1/2/2017 2/1/2017 6 3 1 2/3/2017 2/5/2017 7 4 1 2/7/2017 4/1/2017 3 5 1 9/7/2017 9/11/2017 1 6 2 1/1/2018 2/1/2018 15 7 2 3/1/2018 4/1/2018 3crear columnas para cambiar:
df["open_time"] = pd.to_datetime(df["open_time"]) df["close_time"] = pd.to_datetime(df["close_time"]) df.sort_values(['user','close_time'],inplace=True) df['close_cumsum']=df.groupby('user')['value'].cumsum() df.sort_values(['user','open_time'],inplace=True) df user open_time close_time value close_cumsum 0 1 2016-12-30 2016-12-31 1 1 1 1 2017-01-01 2017-03-01 5 19 2 1 2017-01-02 2017-02-01 6 7 3 1 2017-02-03 2017-02-05 7 14 4 1 2017-02-07 2017-04-01 3 22 5 1 2017-09-07 2017-09-11 1 23 6 2 2018-01-01 2018-02-01 15 15 7 2 2018-03-01 2018-04-01 3 18Columnas de cambio (explicación a continuación):
df["cumulated_closed_value"] = df.groupby("user")["close_cumsum"].transform("shift") condition = ~(df.groupby("user")['close_time'].transform("shift") < df["open_time"]) df.loc[ condition,"cumulated_closed_value" ] = None df["cumulated_closed_value"] =df.groupby("user")["cumulated_closed_value"].fillna(method="ffill").fillna(0) df user open_time close_time value close_cumsum cumulated_closed_value 0 1 2016-12-30 2016-12-31 1 1 0.0 1 1 2017-01-01 2017-03-01 5 19 1.0 2 1 2017-01-02 2017-02-01 6 7 1.0 3 1 2017-02-03 2017-02-05 7 14 7.0 4 1 2017-02-07 2017-04-01 3 22 14.0 5 1 2017-09-07 2017-09-11 1 23 22.0 6 2 2018-01-01 2018-02-01 15 15 0.0 7 2 2018-03-01 2018-04-01 3 18 15.0Todo esto se ha escrito de tal manera que se hace en todos los usuarios. Creo que la lógica es más fácil si solo te enfocas en un usuario a la vez.
Todavía probaría esto a fondo antes de usarlo. Los intervalos de tiempo son extraños y hay muchos casos extremos.
(Nota: la respuesta de @wen me parece bien, así que no estoy seguro de si el OP está buscando algo más o algo diferente. En cualquier caso, aquí hay un enfoque alternativo que usa merge_asof que también debería funcionar bien).
Primero remodele los marcos de datos de la siguiente manera:
lookup = ( df[['close_time','value','user']].set_index(['user','close_time']) .sort_index().groupby('user').cumsum().reset_index(0) ) df = df.set_index('open_time').sort_index()La idea con "búsqueda" es simplemente ordenar por "hora_de_cierre" y luego tomar una suma acumulativa (agrupada):
user value close_time 2017-02-01 1 6 2017-02-05 1 13 2017-03-01 1 18 2017-04-01 1 21 2017-09-11 1 22 2018-02-01 2 15 2018-04-01 2 18Para "df" simplemente tomamos un subconjunto del marco de datos original:
user close_time value open_time 2017-01-01 1 2017-03-01 5 2017-01-02 1 2017-02-01 6 2017-02-03 1 2017-02-05 7 2017-02-07 1 2017-04-01 3 2017-09-07 1 2017-09-11 1 2018-01-01 2 2018-02-01 15 2018-03-01 2 2018-04-01 3A partir de aquí, solo desea fusionar conceptualmente los dos conjuntos de datos en "usuario" y "tiempo_abierto"/"tiempo_cerrado", pero el factor complicado es que no queremos hacer una coincidencia exacta en el tiempo, sino más bien una especie de coincidencia "más cercana".
Para estos tipos de fusiones, puede usar merge_asof que es una gran herramienta para varias coincidencias no exactas (incluidas 'más cercana', 'hacia atrás' y 'hacia adelante'). Desafortunadamente, debido a la inclusión de groupby, también es necesario recorrer a los usuarios, pero sigue siendo un código bastante simple de leer:
df_merged = pd.DataFrame() for u in df['user'].unique(): df_merged = df_merged.append( pd.merge_asof( df[df.user==u], lookup[lookup.user==u], left_index=True, right_index=True, direction='backward' ) ) df_merged.drop('user_y',axis=1).rename({'value_y':'close_cumsum'},axis=1)Resultados:
user_x close_time value_x close_cumsum open_time 2017-01-01 1 2017-03-01 5 NaN 2017-01-02 1 2017-02-01 6 NaN 2017-02-03 1 2017-02-05 7 6.0 2017-02-07 1 2017-04-01 3 13.0 2017-09-07 1 2017-09-11 1 21.0 2018-01-01 2 2018-02-01 15 NaN 2018-03-01 2 2018-04-01 3 15.0