In generale, se sono del tutto arbitrarie le date, penso che si sarebbe costretti ad usare un Python for-loop
sopra le righe o le use df.apply
, (che sotto il cofano, utilizza anche un ciclo di Python.)
Tuttavia, se le tue date condividono una frequenza comune, come sopra, allora c'è un trucco che dovrebbe essere molto più veloce dell'uso di df.apply
: Espandi gli orari in base alla frequenza comune - in questo caso, 1 minuto - compila i NaN con zeri, e quindi chiamare rolling_sum
:
In [279]: pd.rolling_sum(df.set_index(['Date']).asfreq('1T').fillna(0), window=5, min_periods=1).reindex(df['Date'])
Out[279]:
A
Date
2014-11-21 11:00:00 1
2014-11-21 11:03:00 5
2014-11-21 11:04:00 6
2014-11-21 11:05:00 7
2014-11-21 11:07:00 11
2014-11-21 11:08:00 8
2014-11-21 11:12:00 2
2014-11-21 11:13:00 3
Naturalmente, qualsiasi serie temporale ha una frequenza comune se si è disposti ad accettare una granularità sufficientemente piccola, ma la dimensione richiesta di df.asfreq(...)
potrebbe rendere questo trucco poco pratico.
Ecco un esempio dell'approccio più generale utilizzando df.apply
. Si noti che la chiamata searchsorted
si basa su df['Date']
in ordine.
import numpy as np
import pandas as pd
df = pd.read_csv('data', parse_dates=[0], sep=',\s*')
start_dates = df['Date'] - pd.Timedelta(minutes=5)
df['start_index'] = df['Date'].values.searchsorted(start_dates, side='right')
df['end_index'] = np.arange(len(df))
def sum_window(row):
return df['A'].iloc[row['start_index']:row['end_index']+1].sum()
df['rolling_sum'] = df.apply(sum_window, axis=1)
print(df[['Date', 'A', 'rolling_sum']])
cede
Date A rolling_sum
0 2014-11-21 11:00:00 1 1
1 2014-11-21 11:03:00 4 5
2 2014-11-21 11:04:00 1 6
3 2014-11-21 11:05:00 2 7
4 2014-11-21 11:07:00 4 11
5 2014-11-21 11:08:00 1 8
6 2014-11-21 11:12:00 1 2
7 2014-11-21 11:13:00 2 3
Ecco un punto di riferimento confrontando il df.asfreq
trucco contro chiamando df.apply
:
import numpy as np
import pandas as pd
df = pd.read_csv('data', parse_dates=[0], sep=',\s*')
def big_df(df):
df = df.copy()
for i in range(7):
dates = df['Date'] + pd.Timedelta(df.iloc[-1]['Date']-df.iloc[0]['Date']) + pd.Timedelta('1 minute')
df2 = pd.DataFrame({'Date': dates, 'A': df['A']})
df = pd.concat([df, df2])
df = df.reset_index(drop=True)
return df
def using_apply():
start_dates = df['Date'] - pd.Timedelta(minutes=5)
df['start_index'] = df['Date'].values.searchsorted(start_dates, side='right')
df['end_index'] = np.arange(len(df))
def sum_window(row):
return df['A'].iloc[row['start_index']:row['end_index']+1].sum()
df['rolling_sum'] = df.apply(sum_window, axis=1)
return df[['Date', 'rolling_sum']]
def using_asfreq():
result = (pd.rolling_sum(
df.set_index(['Date']).asfreq('1T').fillna(0),
window=5, min_periods=1).reindex(df['Date']))
return result
In [364]: df = big_df(df)
In [367]: %timeit using_asfreq()
1000 loops, best of 3: 1.21 ms per loop
In [368]: %timeit using_apply()
1 loops, best of 3: 208 ms per loop
Grazie mille per la rapida risposta. Non riesco a utilizzare il metodo df.asfreq (...) dal momento che la più piccola granularità nel mio set di dati è secondi e ho milioni di righe. Ma il metodo df.apply fa il trucco. –
L'approccio generale funziona, solo una cosa da tenere a mente se viene usata nel codice lungo: la funzione 'sum_window' non usa l'input' df' come esplicitamente, quindi bisogna stare attenti. – nilesh