Necesito analizar los últimos 60 días hasta la última fecha en que cada usuario estuvo activo.

Mi marco de datos contiene las fechas ('CalendarDate') que cada usuario ('DataSourceId') estuvo activo ('Activity' un número entero) - una fila por fecha. He agrupado el marco de datos por DataSourceId, así que tengo fechas en las columnas y he tomado el último día que cada usuario estuvo activo 'max_date':

df['max_date'] = df.groupby('DataSourceId')['CalendarDate'].transform('max')

Los datos se ven más o menos así, aunque 'CalendarDate' y 'max_date' son en realidad formato datetime64[ns] (los valores de la actividad son float64):

ID    Jan1    Jan2    Jan3    Jan4    Jan5...  max_date
1               8              15      10        Jan5
2       2              13                        Jan3
3       6      11                                Jan2

Ahora, quiero realinear las columnas desde las fechas del calendario hasta los "últimos x días" para cada fila. Me gusta esto:

ID    Last    Last-1    Last-2    Last-3  ...  Last-x
1      10       15                   8  
2      13                  2           
3      11        6

No he podido encontrar ningún ejemplo de transformaciones similares y estoy realmente varado aquí.

EDITADO: Después de adaptar la solución de Israel, noté que fallaba en ocasiones.

Creo que el problema está relacionado con este código en la solución de jezrael: r = data_wide.bfill().isna().sum(axis=1).values

Ejemplo: estos datos fallan (y r = [0 3]):

CalendarDate                         2017-07-02 2017-07-03 2017-07-06 2017-07-07 2017-07-08 2017-07-09
DataSourceId                                                                                          
1000648                                     NaN     188.37     178.37        NaN     128.37      18.37
1004507                                   51.19        NaN      52.19      53.19        NaN        NaN

Específicamente, el marco de datos realineado se ve así:

              Last-0  Last-1  Last-2  Last-3  Last-4  Last-5
DataSourceId                                                
1000648        18.37  128.37     NaN  178.37  188.37     NaN
1004507        52.19     NaN   51.19     NaN     NaN   53.19

Si cambio el orden en el marco de datos cambiando ID 1000648 a 1100648 (para que se convierta en la segunda fila) este es el resultado (r = [0 2]):

              Last-0  Last-1  Last-2  Last-3  Last-4  Last-5
DataSourceId                                                
1004507          NaN     NaN   53.19   52.19     NaN   51.19
1100648          NaN  178.37  188.37     NaN   18.37  128.37
1
Mads Stenbjerre 13 sep. 2018 a las 13:24

3 respuestas

La mejor respuesta

Si el rendimiento es importante, use un poco cambiado numpy solution:

#select all columns without last
A = df.iloc[:, 1:-1].values
print (A)
[[nan  8. nan 15. 10.]
 [ 2. nan 13. nan nan]
 [ 6. 11. nan nan nan]]

#count NaNs values
r = df.bfill(axis=1).isna().sum(axis=1).values
#oldier pandas versions
#r = df.bfill(axis=1).isnull().sum(axis=1).values
#boost solution by https://stackoverflow.com/a/30428192
#r = A.shape[1] - (~np.isnan(A)).cumsum(axis=1).argmax(axis=1) - 1
print (r)
[0 2 3]

rows, column_indices = np.ogrid[:A.shape[0], :A.shape[1]]

# Use always a negative shift, so that column_indices are valid.
# (could also use module operation)
r[r < 0] += A.shape[1]
column_indices = np.flip(column_indices - r[:,np.newaxis], axis=1)
print (column_indices)
[[ 4  3  2  1  0]
 [ 2  1  0 -1 -2]
 [ 1  0 -1 -2 -3]]

result = A[rows, column_indices]
#https://stackoverflow.com/a/51613442
#result = strided_indexing_roll(A,r)
print (result)
[[10. 15. nan  8. nan]
 [13. nan  2. nan nan]
 [11.  6. nan nan nan]]

c = [f'Last-{x}' for x in np.arange(result.shape[1])]
df1 = pd.DataFrame(result, columns=c)
df1.insert(0, 'ID', df['ID'])
print (df1)
   ID  Last-0  Last-1  Last-2  Last-3  Last-4
0   1    10.0    15.0     NaN     8.0     NaN
1   2    13.0     NaN     2.0     NaN     NaN
2   3    11.0     6.0     NaN     NaN     NaN

Editar:

Si ID es el índice, la solución cambia un poco; no elimine la primera columna con .iloc[:, :-1] y el último uso del constructor DataFrame solamente:

A = df.iloc[:, :-1].values
print (A)
[[nan  8. nan 15. 10.]
 [ 2. nan 13. nan nan]
 [ 6. 11. nan nan nan]]

r = df.bfill(axis=1).isna().sum(axis=1).values
print (r)
[0 2 3]

rows, column_indices = np.ogrid[:A.shape[0], :A.shape[1]]

# Use always a negative shift, so that column_indices are valid.
# (could also use module operation)
r[r < 0] += A.shape[1]
column_indices = np.flip(column_indices - r[:,np.newaxis], axis=1)
print (column_indices)
[[ 4  3  2  1  0]
 [ 2  1  0 -1 -2]
 [ 1  0 -1 -2 -3]]

result = A[rows, column_indices]
print (result)
[[10. 15. nan  8. nan]
 [13. nan  2. nan nan]
 [11.  6. nan nan nan]]

c = [f'Last-{x}' for x in np.arange(result.shape[1])]
#use DataFrame constructor
df1 = pd.DataFrame(result, columns=c, index=df.index)
print (df1)
    Last-0  Last-1  Last-2  Last-3  Last-4
ID                                        
1     10.0    15.0     NaN     8.0     NaN
2     13.0     NaN     2.0     NaN     NaN
3     11.0     6.0     NaN     NaN     NaN
0
jezrael 20 sep. 2018 a las 12:32

Por favor, intente el siguiente código y avíseme si esto ayuda.

df = df.iloc[:,list(range(len(df.columns)-1,0,-1))]
print(df)
0
Adrish 13 sep. 2018 a las 10:48

Puede usar este código primero para encontrar los últimos valores nulos continuos y con el cambio de conteo de cada serie, funcionará.

df1 = df[df.columns.difference(['ID'])]
df1 = df1.apply(lambda x:x.shift(x[::-1].isnull().cumprod().sum())[::-1],axis=1)
df1.columns = ['Last-'+str(i) for i in range(df1.columns.shape[0])]
df1['ID'] = df['ID']

Fuera:

   Last-0   Last-1  Last-2  Last-3  Last-4  ID
0   10.0    15.0    NaN     8.0     NaN     1
1   13.0    NaN     2.0     NaN     NaN     2
2   11.0    6.0     NaN     NaN     NaN     3
0
Naga Kiran 13 sep. 2018 a las 11:15