当值在另一个系列的切片中使用时,如何通过熊猫系列对循环进行矢量化

M H*_*ley 7 python time-series vectorization series pandas

假设我有两个时间戳记,分别是5小时范围内的开始/结束时间对。它们不一定是顺序的,也不能量化为小时。

import pandas as pd

start = pd.Series(pd.date_range('20190412',freq='H',periods=25))

# Drop a few indexes to make the series not sequential
start.drop([4,5,10,14]).reset_index(drop=True,inplace=True)

# Add some random minutes to the start as it's not necessarily quantized
start = start + pd.to_timedelta(np.random.randint(59,size=len(start)),unit='T')

end = start + pd.Timedelta('5H')
Run Code Online (Sandbox Code Playgroud)

现在假设我们有一些以分钟为单位的时间戳数据,其范围涵盖了所有开始/结束对。

data_series = pd.Series(data=np.random.randint(20, size=(75*60)), 
                        index=pd.date_range('20190411',freq='T',periods=(75*60)))
Run Code Online (Sandbox Code Playgroud)

我们希望从和时间data_series范围内获得的值。可以在循环中天真地完成此操作startend

frm = []
for s,e in zip(start,end):
    frm.append(data_series.loc[s:e].values)
Run Code Online (Sandbox Code Playgroud)

如我们所见,这种幼稚的方法遍历每对startend日期,并从数据中获取值。

但是,如果实现len(start)较大,则此实现速度很慢。有没有办法利用pandas矢量功能执行这种逻辑?

我觉得这几乎就像我想应用.loc矢量,pd.Series而不是单个应用pd.Timestamp

编辑

使用.apply天真for循环比使用天真循环更有效。我希望能指出纯向量解决方案的方向

Div*_*kar 4

基本思想

\n\n

像往常一样,pandas 会花时间在 处搜索一个特定索引data_series.loc[s:e],其中se是日期时间索引。这在循环时成本很高,而这正是我们需要改进的地方。我们可以用向量化的方式找到所有这些索引searchsorted。然后,我们将提取值data_series作为数组并使用从searchsorted简单的基于整数的索引获得的索引。因此,将存在一个循环,只需最少的简单切片数组工作。

\n\n

一般口头禅是 - 以矢量化方式进行大部分预处理工作,并在循环时进行最少处理。

\n\n

实现看起来像这样 -

\n\n
def select_slices_by_index(data_series, start, end):\n    idx = data_series.index.values\n    S = np.searchsorted(idx,start.values)\n    E = np.searchsorted(idx,end.values)\n    ar = data_series.values\n    return [ar[i:j] for (i,j) in zip(S,E+1)]\n
Run Code Online (Sandbox Code Playgroud)\n\n

使用NumPy-striding

\n\n

starts对于所有条目的和之间的时间段ends相同并且所有切片都被该长度覆盖的特定情况,即没有越界情况,我们可以使用NumPy\'s sliding window trick

\n\n

我们可以利用np.lib.stride_tricks.as_stridedbasedscikit-image\'s view_as_windows来获得滑动窗口。有关使用as_strided基于view_as_windows的更多信息。

\n\n
from skimage.util.shape import view_as_windows\n\ndef select_slices_by_index_strided(data_series, start, end):\n    idx = data_series.index.values\n    L = np.searchsorted(idx,end.values[0])-np.searchsorted(idx,start.values[0])+1\n    S = np.searchsorted(idx,start.values)\n    ar = data_series.values\n    w = view_as_windows(ar,L)\n    return w[S]\n
Run Code Online (Sandbox Code Playgroud)\n\n

this post如果您无权访问,请使用scikit-image.

\n\n
\n\n

标杆管理

\n\n

让我们根据100x给定的示例数据扩展所有内容并进行测试。

\n\n

设置 -

\n\n
np.random.seed(0)\nstart = pd.Series(pd.date_range(\'20190412\',freq=\'H\',periods=2500))\n\n# Drop a few indexes to make the series not sequential\nstart.drop([4,5,10,14]).reset_index(drop=True,inplace=True)\n\n# Add some random minutes to the start as it\'s not necessarily quantized\nstart = start + pd.to_timedelta(np.random.randint(59,size=len(start)),unit=\'T\')\n\nend = start + pd.Timedelta(\'5H\')\ndata_series = pd.Series(data=np.random.randint(20, size=(750*600)), \n                        index=pd.date_range(\'20190411\',freq=\'T\',periods=(750*600)))\n
Run Code Online (Sandbox Code Playgroud)\n\n

时间安排 -

\n\n
In [156]: %%timeit\n     ...: frm = []\n     ...: for s,e in zip(start,end):\n     ...:     frm.append(data_series.loc[s:e].values)\n1 loop, best of 3: 172 ms per loop\n\nIn [157]: %timeit select_slices_by_index(data_series, start, end)\n1000 loops, best of 3: 1.23 ms per loop\n\nIn [158]: %timeit select_slices_by_index_strided(data_series, start, end)\n1000 loops, best of 3: 994 \xc2\xb5s per loop\n\nIn [161]: frm = []\n     ...: for s,e in zip(start,end):\n     ...:     frm.append(data_series.loc[s:e].values)\n\nIn [162]: np.allclose(select_slices_by_index(data_series, start, end),frm)\nOut[162]: True\n\nIn [163]: np.allclose(select_slices_by_index_strided(data_series, start, end),frm)\nOut[163]: True\n
Run Code Online (Sandbox Code Playgroud)\n\n

140x+170x用这些加速!

\n