Tengo dataframe

    site1   time1   site2   time2   site3   time3   site4   time4   site5   time5   ... time6   site7   time7   site8   time8   site9   time9   site10  time10  target
session_id                                                                                  

21669   56  2013-01-12 08:05:57 55.0    2013-01-12 08:05:57 NaN NaT NaN NaT NaN NaT ... NaT NaN NaT NaN NaT NaN NaT NaN NaT 0
54843   56  2013-01-12 08:37:23 55.0    2013-01-12 08:37:23 56.0    2013-01-12 09:07:07 55.0    2013-01-12 09:07:09 NaN NaT ... NaT NaN NaT NaN NaT NaN NaT NaN NaT 0
77292   946 2013-01-12 08:50:13 946.0   2013-01-12 08:50:14 951.0   2013-01-12 08:50:15 946.0   2013-01-12 08:50:15 946.0   2013-01-12 08:50:16 ... 2013-01-12 08:50:16 948.0   2013-01-12 08:50:16 784.0   2013-01-12 08:50:16 949.0   2013-01-12 08:50:17 946.0   2013-01-12 08:50:17 0

Necesito contar la diferencia entre la última vez que no sea NaN y la primera vez.

Salida deseada (convertir a segunda)

session_id    diff
 21669         0
 54843        2013-01-12 09:07:09 - 2013-01-12 08:37:23 55.0
 77292        4

Puedo hacerlo por cada par y luego fusionar eso

df['diff1'] = df['time1'] - df['time2']
...

¿Pero hay alguna forma de hacerlo más rápido?

1
Petr Petrov 30 oct. 2017 a las 09:40

3 respuestas

La mejor respuesta

Use .ffill() en el marco de datos con solo las columnas time:

df['diff1'] = df.filter(like='time').ffill(axis = 1).time10 - df.time1
1
Ken Wei 30 oct. 2017 a las 06:46

Uso:

  • filter columna con { {X1}}
  • obtenga los nombres de las columnas por último notnull con idxmax
  • obtener valores lookup a Series
  • último sub con < a href = "http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.dt.total_seconds.html" rel = "nofollow noreferrer"> total_seconds

a = df.filter(like='time').notnull().iloc[:, ::-1].idxmax(1)
print (a)
0    time2
1    time4
2    time5
dtype: object

df['diff']= pd.Series(df.lookup(df.index,a),index=df.index)
              .sub(df['time1'])
              .dt.total_seconds()
print (df['diff'])
0       0.0
1    1786.0
2       4.0
Name: diff, dtype: float64

numpy alternative:

A = df.filter(like='time')
b =  len(A.columns) - A.notnull().values[:, ::-1].argmax(1) - 1

df['diff'] = pd.Series(A.values[np.arange(len(A)),b]).sub(df['time1']).dt.total_seconds()
print (df['diff'])
0       0.0
1    1786.0
2       4.0
Name: diff, dtype: float64

Solución más general de Ken Wei: seleccione la primera y la última columna con iloc:

df1 = df.filter(like='time')
df['diff']= df1.ffill(1).iloc[:, -1].sub(df1.iloc[:, 0]).dt.total_seconds()
print (df['diff'])
0       0.0
1    1786.0
2       4.0
Name: diff, dtype: float64
2
jezrael 30 oct. 2017 a las 07:31
  • Se me cayó target
  • Dividí tus columnas en un pd.MultiIndex
  • Se aseguró de que las marcas de tiempo fueran en realidad marcas de tiempo (no es necesario que haga esto a menos que lo haga)
  • groupby 'session_id' luego usó 'first' y 'last' para obtener el primer y el último valor no nulo.
  • pipe para pasar convenientemente el resultado a una función que me resta

d = df.drop('target', 1)
a = d.columns.str.extract('([a-z]+)(\d+)', expand=True).values.T
mux = pd.MultiIndex.from_arrays([a[0], a[1].astype(int)])
d.columns = mux

for (c0, c1), col in d.iteritems():
    if c0 == 'time':
        d[(c0, c1)] = pd.to_datetime(col, errors='coerce')

f = lambda d: d['last'].sub(d['first']).dt.total_seconds()
d.time.stack().groupby('session_id').agg(['last', 'first']).pipe(f)

session_id
21669       0.0
54843    1786.0
77292       4.0
dtype: float64
2
piRSquared 30 oct. 2017 a las 07:16