在Numpy中,是否有一种pythonic方法来创建array3,其中自定义范围来自array1和array2而没有循环?迭代范围的直接解决方案有效,但由于我的数组遇到了数百万个项目,我正在寻找更有效的解决方案(也可能是语法糖).
例如,
array1 = np.array([10, 65, 200])
array2 = np.array([14, 70, 204])
array3 = np.concatenate([np.arange(array1[i], array2[i]) for i in
np.arange(0,len(array1))])
print array3
结果:[10,11,12,13,65,66,67,68,69,200,201,202,203].
解决方法:
前瞻性方法
我将回顾如何解决这个问题.
采取问题中列出的样本.我们有 –
array1 = np.array([10, 65, 200])
array2 = np.array([14, 70, 204])
现在,看看想要的结果 –
result: [10,11,12,13,65,66,67,68,69,200,201,202,203]
让我们计算组长度,因为我们需要那些解释下一步的解决方案.
In [58]: lens = array2 - array1
In [59]: lens
Out[59]: array([4, 5, 4])
我们的想法是使用1的初始化数组,当在整个长度上进行累积求和时,可以得到所需的结果.
这个累积总和将是我们解决方案的最后一步.
为什么1的初始化?好吧,因为我们有一个数组以1的步长增加,除了在我们有变化的特定地方
对应新的团体进来.
现在,因为cumsum将是最后一步,所以它之前的步骤应该给我们一些像 –
array([ 10, 1, 1, 1, 52, 1, 1, 1, 1, 131, 1, 1, 1])
正如前面所讨论的那样,在特定的地方,它的1是[10,52,131].那个10似乎是从array1中的第一个元素进来的,但其余的呢?
第二个52以65-13(看结果)进入,其中13个进入了以10开头并且因为长度而跑的组
第一组4.所以,如果我们做65 – 10 – 4,我们将获得51然后加1以适应边界停止,我们将有52,这是
期望的转移价值.同样,我们会得到131.
因此,可以像这样计算那些移位值 –
In [62]: np.diff(array1) - lens[:-1]+1
Out[62]: array([ 52, 131])
接下来,为了获得发生这种转变的那些转移位置,我们可以简单地对组长度进行累积求和 –
In [65]: lens[:-1].cumsum()
Out[65]: array([4, 9])
为了完整性,我们需要为移位值预先附加0,使用array1 [0]作为移位值.
因此,我们将逐步展示我们的方法!
把它们放回去
1]获取每组的长度:
lens = array2 - array1
2]获取发生偏移的索引和要放入1的初始化数组中的值:
shift_idx = np.hstack((0,lens[:-1].cumsum()))
shift_vals = np.hstack((array1[0],np.diff(array1) - lens[:-1]+1))
3] Setup 1的初始化ID数组,用于将这些值插入前面步骤中列出的那些索引:
id_arr = np.ones(lens.sum(),dtype=array1.dtype)
id_arr[shift_idx] = shift_vals
4]最后对ID数组进行累加求和:
output = id_arr.cumsum()
以功能格式列出,我们会 –
def using_ones_cumsum(array1, array2):
lens = array2 - array1
shift_idx = np.hstack((0,lens[:-1].cumsum()))
shift_vals = np.hstack((array1[0],np.diff(array1) - lens[:-1]+1))
id_arr = np.ones(lens.sum(),dtype=array1.dtype)
id_arr[shift_idx] = shift_vals
return id_arr.cumsum()
它也适用于重叠范围!
In [67]: array1 = np.array([10, 11, 200])
...: array2 = np.array([14, 18, 204])
...:
In [68]: using_ones_cumsum(array1, array2)
Out[68]:
array([ 10, 11, 12, 13, 11, 12, 13, 14, 15, 16, 17, 200, 201,
202, 203])
运行时测试
让我们来讨论@unutbu's flatnonzero based solution中针对其他矢量化方法的提议方法,这种方法已经证明比循环方法好得多 –
In [38]: array1, array2 = (np.random.choice(range(1, 11), size=10**4, replace=True)
...: .cumsum().reshape(2, -1, order='F'))
In [39]: %timeit using_flatnonzero(array1, array2)
1000 loops, best of 3: 889 µs per loop
In [40]: %timeit using_ones_cumsum(array1, array2)
1000 loops, best of 3: 235 µs per loop
改进!
现在,代码NumPy不喜欢追加.因此,对于稍微改进的版本,可以避免那些np.hstack调用,如下所示 –
def get_ranges_arr(starts,ends):
counts = ends - starts
counts_csum = counts.cumsum()
id_arr = np.ones(counts_csum[-1],dtype=int)
id_arr[0] = starts[0]
id_arr[counts_csum[:-1]] = starts[1:] - ends[:-1] + 1
return id_arr.cumsum()
让我们反对我们原来的做法 –
In [151]: array1,array2 = (np.random.choice(range(1, 11),size=10**4, replace=True)\
...: .cumsum().reshape(2, -1, order='F'))
In [152]: %timeit using_ones_cumsum(array1, array2)
1000 loops, best of 3: 276 µs per loop
In [153]: %timeit get_ranges_arr(array1, array2)
10000 loops, best of 3: 193 µs per loop
所以,我们的性能提升了30%!