基本思路
通常情况下,Pandas 在搜索具有特定索引的数据时,会使用 data_series.loc[s:e]
,其中 s
和 e
是日期时间索引。当进行循环时,这样做代价高昂,这正是我们需要改进的地方。我们将使用 searchsorted
以向量化的方式找到所有这些索引。接着,我们将从 data_series
中提取值作为数组,并使用从 searchsorted
获得的这些索引进行简单的整数索引。因此,只需通过简单地切片一个数组即可完成循环最小化的工作。
总体口号是 - 在向量化的预处理中完成大部分工作,在循环中尽量减少工作量。
实现代码将类似于以下内容 -
def select_slices_by_index(data_series, start, end):
idx = data_series.index.values
S = np.searchsorted(idx,start.values)
E = np.searchsorted(idx,end.values)
ar = data_series.values
return [ar[i:j] for (i,j) in zip(S,E+1)]
使用 NumPy-striding
对于特定情况,当所有条目之间的时间段相同且所有切片都被该长度覆盖时,即无任何越界情况,我们可以使用NumPy的滑动窗口技巧
。
我们可以利用np.lib.stride_tricks.as_strided
和scikit-image的view_as_windows
获取滑动窗口。更多关于基于as_strided
的view_as_windows
的信息。
from skimage.util.shape import view_as_windows
def select_slices_by_index_strided(data_series, start, end):
idx = data_series.index.values
L = np.searchsorted(idx,end.values[0])-np.searchsorted(idx,start.values[0])+1
S = np.searchsorted(idx,start.values)
ar = data_series.values
w = view_as_windows(ar,L)
return w[S]
如果您无法访问 scikit-image
,请使用此帖子。
基准测试
我们将在给定的示例数据上将所有内容按比例放大 100 倍
并进行测试。
设置 -
np.random.seed(0)
start = pd.Series(pd.date_range('20190412',freq='H',periods=2500))
start.drop([4,5,10,14]).reset_index(drop=True,inplace=True)
start = start + pd.to_timedelta(np.random.randint(59,size=len(start)),unit='T')
end = start + pd.Timedelta('5H')
data_series = pd.Series(data=np.random.randint(20, size=(750*600)),
index=pd.date_range('20190411',freq='T',periods=(750*600)))
时间 -
In [156]: %%timeit
...: frm = []
...: for s,e in zip(start,end):
...: frm.append(data_series.loc[s:e].values)
1 loop, best of 3: 172 ms per loop
In [157]: %timeit select_slices_by_index(data_series, start, end)
1000 loops, best of 3: 1.23 ms per loop
In [158]: %timeit select_slices_by_index_strided(data_series, start, end)
1000 loops, best of 3: 994 µs per loop
In [161]: frm = []
...: for s,e in zip(start,end):
...: frm.append(data_series.loc[s:e].values)
In [162]: np.allclose(select_slices_by_index(data_series, start, end),frm)
Out[162]: True
In [163]: np.allclose(select_slices_by_index_strided(data_series, start, end),frm)
Out[163]: True
使用这些技术可提高
140倍及以上
和
170倍
的速度!
len(data_series)
大约在 10^5 到 10^6 之间,而len(start)
大约在 10^4 到 10^5 之间。循环执行该操作大约需要 5 秒钟,然而,对于各种start
向量可能需要执行该操作 10^5 次,这使得该方法不可行。 - mch56end
条目是否与相应的start
条目在固定时间间隔内?就像给定样本中的5H
一样。 - Divakar