Python 在数据帧中高效地获取可变长度的时间片

Python 在数据帧中高效地获取可变长度的时间片,python,pandas,Python,Pandas,我希望使用DatetimeIndex(类似于重采样或groupby操作)有效地分割数据帧,但所需的时间片长度不同 通过循环实现这一点相对容易(请参见下面的代码),但对于大时间序列,多个切片很快就会变慢。有没有关于矢量化/提高速度的建议 import pandas as pd, datetime as dt, numpy as np #Example DataFrame with a DatetimeIndex idx = pd.DatetimeIndex(start=dt.datetime(2

我希望使用DatetimeIndex(类似于重采样或groupby操作)有效地分割数据帧,但所需的时间片长度不同

通过循环实现这一点相对容易(请参见下面的代码),但对于大时间序列,多个切片很快就会变慢。有没有关于矢量化/提高速度的建议

import pandas as pd, datetime as dt, numpy as np

#Example DataFrame with a DatetimeIndex
idx = pd.DatetimeIndex(start=dt.datetime(2017,1,1), end=dt.datetime(2017,1,31), freq='h')
df = pd.Series(index = idx, data = np.random.rand(len(idx)))

#The slicer dataframe contains a series of start and end windows
slicer_df = pd.DataFrame(index = [1,2])
slicer_df['start_window'] = [dt.datetime(2017,1,2,2), dt.datetime(2017,1,6,12)]
slicer_df['end_window'] = [dt.datetime(2017,1,6,12), dt.datetime(2017,1,15,2)]

#The results should be stored to a dataframe, indexed by the index of the slicer dataframe
#This is the loop that I would like to vectorise
slice_results = pd.DataFrame()
slice_results['total'] = None
for index, row in slicer_df.iterrows():
    slice_results.loc[index,'total'] = df[(df.index >= row.start_window) &
                                          (df.index <= row.end_window)].sum()
导入pandas作为pd,datetime作为dt,numpy作为np
#带有DatetimeIndex的数据帧示例
idx=pd.DatetimeIndex(开始=dt.datetime(2017,1,1),结束=dt.datetime(2017,1,31),频率=h')
df=pd.Series(index=idx,data=np.rand.rand(len(idx)))
#切片器数据帧包含一系列开始和结束窗口
切片器_df=pd.DataFrame(索引=[1,2])
切片器_df['start_window']=[dt.datetime(2017,1,2,2),dt.datetime(2017,1,6,12)]
切片器_df['end_window']=[dt.datetime(2017,1,6,12),dt.datetime(2017,1,15,2)]
#结果应存储到数据帧中,并由切片器数据帧的索引索引
#这是我想矢量化的循环
slice_results=pd.DataFrame()
切片结果['total']=无
对于索引,切片器_df.iterrows()中的行:
slice_results.loc[索引,'total']=df[(df.index>=row.start_窗口)&

(df.index您可以作为应用程序来执行此操作,它将显示结果,而不是迭代更新数据帧:

In [11]: slicer_df.apply((lambda row: \
              df[(df.index >= row.start_window) 
               & (df.index <= row.end_window)].sum()), axis=1)
Out[11]:
1     36.381155
2    111.521803
dtype: float64
[11]中的
:切片器应用((lambda行:\
df[(df.index>=行开始\u窗口)

&(df.index我提出了一种矢量化方法,它依赖于可变长度的“窗口”总是彼此相邻,即窗口的开始与之前窗口的结束相同

# Ensure that the join will be successful by rounding to a specific frequency
round_freq = '1h'
df.index = df.index.round(round_freq)
slicer_df.start_window= slicer_df.start_window.dt.round(round_freq)

# Give the index of the slicer a useful name
slicer_df.index.name = 'event_number'

#Perform a join to the start of the window, forward fill to the next window, then groupby to get the totals for each time window
df = df.to_frame('orig_data').join(slicer_df.reset_index().set_index('start_window')[['event_number']])
df.event_number = df.event_number.ffill()
df.groupby('event_number').sum()

当然,这只适用于窗口相邻的情况,即它们不能重叠或有任何间隙。如果有人有更通用的方法适用于上述情况,我很乐意看到它!

您可以使用searchsorted将其矢量化(假设datetime索引已排序,否则为第一排序):

那里还有一个环路,但现在便宜多了


这让我们找到了一个完全矢量化的解决方案(有点神秘):


注意:当开始日期早于第一个日期时,我们希望避免开始索引从0回滚到-1(这意味着数组结束,即下溢).

我不确定循环或数据帧创建是否会成为瓶颈,但这至少缓解了后者。嗨,安迪,这是避免iterrows循环的一个非常巧妙的技巧(+1),但不幸的是lambda函数仍然需要为每次迭代执行切片。一旦df非常大,每个切片都需要很长时间,因此我遇到了速度问题。正在寻找一种方法来矢量化,以便所有切片都可以同时执行。@DaveB是的,这只节省了数据帧构造(避免多次切片/构建)。一般来说,我认为解决方案可能涉及对日期时间索引进行排序(或者我们是否可以假设它已排序?),在每个开始/结束时到达,然后使用它进行切片/求和。将此作为单独的答案添加…因为这是一种完全不同的方法!searchsorted正是我所寻找的。最后的矢量化解决方案也非常聪明。谢谢!
In [11]: inds = np.searchsorted(df.index.values, slicer_df.values)

In [12]: s = df.cumsum()  # only sum once!

In [13]: pd.Series([s[end] - s[start-1] if start else s[end] for start, end in inds], slicer_df.index)
Out[13]:
1     36.381155
2    111.521803
dtype: float64
In [21]: inds2 = np.maximum(1, inds)  # see note

In [22]: inds2[:, 0] -= 1

In [23]: inds2
Out[23]:
array([[ 23,  96],
       [119, 336]])

In [24]: x = s[inds2]

In [25]: x
Out[25]:
array([[  11.4596498 ,   47.84080472],
       [  55.94941276,  167.47121538]])

In [26]: x[:, 1] - x[:, 0]
Out[26]: array([  36.38115493,  111.52180263])