对于许多不同的开始/结束值计算已排序数组中项目数的有效方法

时间:2017-01-14 19:04:04

标签: python pandas numpy

对于许多不同的开始/结束日期,我想计算开始日期和结束日期之间的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无效。

2 个答案:

答案 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)

如果您将startdatesenddates放入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