web-dev-qa-db-ja.com

pandas過去5分間のローリング合計

以下のデータフレームがあると仮定します

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

最初の列は日時オブジェクトで、2番目の列は整数です。私が欲しいのは、各行の最後の5分間の列「A」の合計を計算することです。

2014-11-21 11:12:00, 1の例として、列 'A'の合計は2(1 + 1)になり、行2014-11-21 11:05:00, 2の列 'A'の合計は7(2 + 1)になります。 +4)。重要なことは、時間枠(5分)の過去の行数が各行で同じではないことです(時系列が不規則であるため)。

パンダでrolling_sumメソッドを使用して列「A」の最後の5分間の合計を取得するにはどうすればよいですか?前もって感謝します。

12
Sajith Dilshan

一般に、日付が完全に任意の場合、行に対して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
_
16
unutbu