Pandas 滑动窗口滚动计算(不等间距)

13

假设你拥有一些不均匀的时间序列数据:

import pandas as pd
import random as randy
ts = pd.Series(range(1000),index=randy.sample(pd.date_range('2013-02-01 09:00:00.000000',periods=1e6,freq='U'),1000)).sort_index()
print ts.head()


2013-02-01 09:00:00.002895    995
2013-02-01 09:00:00.003765    499
2013-02-01 09:00:00.003838    797
2013-02-01 09:00:00.004727    295
2013-02-01 09:00:00.006287    253

假设我想要对1毫秒的时间窗口进行滚动求和,以得到如下结果:

2013-02-01 09:00:00.002895    995
2013-02-01 09:00:00.003765    499 + 995
2013-02-01 09:00:00.003838    797 + 499 + 995
2013-02-01 09:00:00.004727    295 + 797 + 499
2013-02-01 09:00:00.006287    253

目前,我将所有的数据转换为长整型并在cython中执行此操作,但纯pandas是否可以实现?我知道你可以像 .asfreq('U') 这样做,然后填充和使用传统的函数,但是一旦你有超过玩具般数量的行,这种方法就不可扩展了。

作为参考,以下是一个hackish、不太快的Cython版本:

%%cython
import numpy as np
cimport cython
cimport numpy as np

ctypedef np.double_t DTYPE_t

def rolling_sum_cython(np.ndarray[long,ndim=1] times, np.ndarray[double,ndim=1] to_add, long window_size):
    cdef long t_len = times.shape[0], s_len = to_add.shape[0], i =0, win_size = window_size, t_diff, j, window_start
    cdef np.ndarray[DTYPE_t, ndim=1] res = np.zeros(t_len, dtype=np.double)
    assert(t_len==s_len)
    for i in range(0,t_len):
        window_start = times[i] - win_size
        j = i
        while times[j]>= window_start and j>=0:
            res[i] += to_add[j]
            j-=1
    return res   

在稍微大一点的系列中进行演示:

ts = pd.Series(range(100000),index=randy.sample(pd.date_range('2013-02-01 09:00:00.000000',periods=1e8,freq='U'),100000)).sort_index()

%%timeit
res2 = rolling_sum_cython(ts.index.astype(int64),ts.values.astype(double),long(1e6))
1000 loops, best of 3: 1.56 ms per loop

添加一个毫秒舍入的时间列,并按此列分组,对每个组应用累加求和。 - lowtech
ts_df['millis_rounded'] = ts.index.astype(int64) / 1000000 * 1000000res3 = ts_df.groupby("millis_rounded")[0].agg(np.sum)这就是你所说的吗?它只是对特定毫秒桶中的条目求和,而不是滑动窗口。它也不是非常快。 (我认为大约比上面的cython慢10倍) - radikalus
4个回答

12

你可以使用cumsum和二分查找来解决大部分这类问题。

from datetime import timedelta

def msum(s, lag_in_ms):
    lag = s.index - timedelta(milliseconds=lag_in_ms)
    inds = np.searchsorted(s.index.astype(np.int64), lag.astype(np.int64))
    cs = s.cumsum()
    return pd.Series(cs.values - cs[inds].values + s[inds].values, index=s.index)

res = msum(ts, 100)
print pd.DataFrame({'a': ts, 'a_msum_100': res})


                            a  a_msum_100
2013-02-01 09:00:00.073479  5           5
2013-02-01 09:00:00.083717  8          13
2013-02-01 09:00:00.162707  1          14
2013-02-01 09:00:00.171809  6          20
2013-02-01 09:00:00.240111  7          14
2013-02-01 09:00:00.258455  0          14
2013-02-01 09:00:00.336564  2           9
2013-02-01 09:00:00.536416  3           3
2013-02-01 09:00:00.632439  4           7
2013-02-01 09:00:00.789746  9           9

[10 rows x 2 columns]

你需要一种处理NaN的方法,具体根据你的应用需求而定,你可能需要在滞后时间中使用当前值或者不需要(即使用kdb+ bin与np.searchsorted之间的差异)。

希望这可以帮到你。


12

1
也许更合理的做法是使用rolling_sum函数:
pd.rolling_sum(ts, window=1, freq='1ms')

这实际上并不起作用--它将数据采样到1毫秒频率,然后只是对桶进行求和,得到:2013-02-01 09:00:00.002000 995 2013-02-01 09:00:00.003000 648 2013-02-01 09:00:00.004000 295 2013-02-01 09:00:00.005000 NaN 2013-02-01 09:00:00.006000 617 - radikalus

0

这样怎么样:

创建一个1毫秒的偏移量:

In [1]: ms = tseries.offsets.Milli()

创建一系列与您的时间序列相同长度的索引位置:

In [2]: s = Series(range(len(ts)))

应用一个 lambda 函数,从 ts 系列中索引当前时间。该函数返回 x - msx 之间所有 ts 条目的总和。
In [3]: s.apply(lambda x: ts.between_time(start_time=ts.index[x]-ms, end_time=ts.index[x]).sum())

In [4]: ts.head()
Out[4]:
2013-02-01 09:00:00.000558    348
2013-02-01 09:00:00.000647    361
2013-02-01 09:00:00.000726    312
2013-02-01 09:00:00.001012    550
2013-02-01 09:00:00.002208    758

以上函数的结果:

0     348
1     709
2    1021
3    1571
4     758

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接