对于许多不同的开始/结束日期,我想计算开始日期和结束日期之间的S& P开放天数。
一个虚假的例子
SPopen = pd.bdate_range(start = '1950-01-01', end = '2020-01-01')
startdates = pd.bdate_range(start = '1970-01-01', end = '2000-01-01')
enddates = startdates + pd.Timedelta(1, 'Y')
对于开始/结束日期中的每一对,我可以
np.sum( (SPopen > start) & (SPopen <= end) )
获取SP开放天数,但循环数千次是缓慢的。有没有一种有效的方法呢?
注意:SP并非在所有工作日都开放,np.busday_count
无效。
答案 0 :(得分:1)
非常有趣的问题,在处理数据帧的日期时间时可能非常有用,或者只涉及计算满足间隔限制的元素的任何问题。
要解决此问题,我们可以滥用数据的排序和间隔,使用np.searchsorted
及其可选的'left'
和{{1}参数。我开始考虑使用NumPy样本,这也很好地概括了日期时间。
涉及的步骤
让我回顾一下我的历史来解决这个问题:
1]给定输入 -
'right'
2]获取左,右索引位置 -
In [618]: a # Data array
Out[618]: array([ 0, 2, 4, 14, 15, 27, 29])
In [619]: s0 # Interval start
Out[619]: array([ 2, 6, 9, 15, 25])
In [620]: s1 # Interval stop
Out[620]: array([ 7, 10, 11, 19, 29])
3]获取通常案例的不同之处 -
In [621]: search_stop = np.searchsorted(a,s1,'right')
...: search_start = np.searchsorted(a,s0,'left')
...:
4]对于a中已经存在起始位置的情况,In [622]: out = search_stop - search_start
会给我们一个较小的索引,所以偏移它 -
np.searchsorted(a,s0,'left')
5]对于案例,如果间隔没有捕获任何元素,我们可能会因为最后一步而产生负数。因此,将它们剪切为零,从而得到所需的输出 -
In [623]: out -= a[search_start] == s0
此外,对于从数据数组中任何元素之外开始的区间,In [624]: out.clip(min=0)
Out[624]: array([1, 0, 0, 0, 2])
将超出数组长度,因此请使用掩码来限制这些计算。
总结一切,我们最终会得到一个像这样的实现 -
search_start
大量改进
事实证明,正如comments by OP
中所述,我们可以简单地查找def vectorized_interval_count(a, s0, s1):
search_stop = np.searchsorted(a,s1,'right')
search_start = np.searchsorted(a,s0,'left')
L = np.searchsorted(search_start, a.size)
out = search_stop - search_start
out[:L] -= (a[search_start[:L]] == s0[:L])
out.clip(min=0, out = out)
return out
个索引,而各自的差异在功能上意味着'right'
和{{{{}}}中的元素数量1}}间隔。
因此,单线解决方案将是 -
left-open
在问题中给定的大样本数据集上测试它,我得到了 -
right-closed
因此,看到接近 np.searchsorted(a,s1,'right') - np.searchsorted(a,s0,'right')
加速循环理解! (感谢OP!)
答案 1 :(得分:0)
如果您将startdates
和enddates
放入DataFrame,那么您可以使用apply
方法迭代计算您需要的内容。
SPopen = pd.bdate_range(start = '1950-01-01', end = '2020-01-01')
df = pd.DataFrame({'start':startdates, 'end':enddates})
df.apply(lambda x: ((SPopen > x.start) & (SPopen <= x.end)).sum(), axis=1)
返回以下第一行。我的机器需要3秒钟
0 261
1 260
2 261
3 261
4 261
5 261
6 260