Скользящая сумма панд за последние пять минут

Предположим, у меня есть ниже фрейма данных

Date, A
2014-11-21 11:00:00, 1
2014-11-21 11:03:00, 4
2014-11-21 11:04:00, 1
2014-11-21 11:05:00, 2
2014-11-21 11:07:00, 4
2014-11-21 11:08:00, 1
2014-11-21 11:12:00, 1
2014-11-21 11:13:00, 2

Первый столбец - это объект даты и времени, а второй - целое число. Я хочу вычислить сумму столбца "А" за последние пять минут для каждой строки.

В качестве примера для строки 2014-11-21 11:12:00, 1сумма столбца "А" будет 2(1+1), а сумма столбца "А" для строки 2014-11-21 11:05:00, 2 будет 7(2+1+4). Важно то, что количество прошедших строк для временного окна (5 минут) не одинаково для каждой строки (поскольку временные ряды нерегулярны).

Как я могу получить сумму за последние пять минут для столбца "А", используя метод roll_sum в пандах? Заранее спасибо.

1 ответ

Решение

В общем, если даты абсолютно произвольны, я думаю, что вы будете вынуждены использовать Python for-loop по строкам или использоватьdf.apply, (который под капотом, также использует цикл Python.)

Однако, если ваши даты имеют общую частоту, как в случае выше, есть хитрость, которая должна быть намного быстрее, чем при использовании df.apply: Расширьте временные ряды в соответствии с общей частотой - в этом случае 1 минута - заполните NaN нулями, а затем вызовите 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

Конечно, любой временной ряд имеет общую частоту, если вы готовы принять достаточно малую гранулярность, но требуемый размер df.asfreq(...) может сделать этот трюк непрактичным.


Вот пример более общего подхода с использованием df.apply, Обратите внимание, что вызов searchsorted опирается на df['Date'] находясь в отсортированном порядке.

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']])

доходность

                 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

Вот эталон сравнения df.asfreq трюк против вызова 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
Другие вопросы по тегам