假设我有两个时间戳记,分别是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')
现在假设我们有一些以分钟为单位加时间戳的数据,其范围涵盖了所有开始/结束对。
data_series = pd.Series(data=np.random.randint(20, size=(75*60)),
index=pd.date_range('20190411',freq='T',periods=(75*60)))
我们希望在每个data_series
和start
时间范围内从end
获得值。可以在循环内天真地完成
frm = []
for s,e in zip(start,end):
frm.append(data_series.loc[s:e].values)
我们可以看到,这种幼稚的方法在start
和end
这对日期中循环,从数据中获取值。
但是,如果len(start)
大,则此实现速度很慢。有没有一种方法可以利用pandas
向量函数来执行这种逻辑?
我感觉几乎就像我想将.loc
应用于矢量或pd.Series
而不是单个pd.Timestamp
一样?
EDIT
使用.apply
不会比使用简单的for
循环有效/略微更高。我希望能指出纯向量解的方向
答案 0 :(得分:4)
和往常一样,大熊猫会花时间在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
对于特定情况,当所有条目的starts
和ends
之间的时间间隔都相同并且所有条带都覆盖该长度时,即没有越界情况,我们可以使用NumPy's sliding window trick
。
我们可以利用基于np.lib.stride_tricks.as_strided
的scikit-image's view_as_windows
来获取滑动窗口。 More info on use of as_strided
based 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
,请使用this post
。
让我们在给定的样本数据上按100x
扩展所有内容并进行测试。
设置-
np.random.seed(0)
start = pd.Series(pd.date_range('20190412',freq='H',periods=2500))
# 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')
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
140x+
和 170x
可以加快这些速度!
答案 1 :(得分:1)
如果将系列移到数据框,则可以利用apply函数:
pdf = pd.DataFrame({'s': start,'e':end})
pdf.apply(lambda x: data_series.loc[x['s']:x['e']].values, axis=1)
Dask可以帮助您并行处理大数据量的计算。
http://docs.dask.org/en/latest/dataframe-api.html#dask.dataframe.DataFrame.apply https://github.com/dask/dask
答案 2 :(得分:1)
您可以使用start
找到end
和data_series
的元素在index.get_loc
中的索引
ind_start = [data_series.index.get_loc(i) for i in start]
ind_end = [data_series.index.get_loc(i) for i in end]
然后使用np.take_along_axis
和np.r_
来执行切片。
frm = [np.take_along_axis(data_series.values, np.r_[s,e],axis=0) for s,e in zip(ind_start,ind_end)]
使用%timeit
%timeit [np.take_along_axis(data_series.values, np.r_[s,e],axis=0) for s,e in zip(ind_start,ind_end)]
425 µs ± 4.28 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
与使用.loc
的for循环方法相比
def timeme(start,end):
frm = []
for s,e in zip(start,end):
frm.append(data_series.loc[s:e].values)
%timeit timeme(start,end)
2.99 ms ± 65.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)