在Numpy中,是否有一种pythonic方法来创建array3,其中自定义范围来自array1和array2而没有循环?迭代范围的直接解决方案有效,但由于我的数组遇到了数百万个项目,我正在寻找更有效的解决方案(也可能是语法糖)。
例如,
File
结果: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
。
答案 0 :(得分:5)
假设范围不重叠,您可以构建一个非零的掩码,其中索引介于array1
和array2
指定的范围之间,然后使用np.flatnonzero
获取数组索引 - 期望的array3
:
import numpy as np
array1 = np.array([10, 65, 200])
array2 = np.array([14, 70, 204])
first, last = array1.min(), array2.max()
array3 = np.zeros(last-first+1, dtype='i1')
array3[array1-first] = 1
array3[array2-first] = -1
array3 = np.flatnonzero(array3.cumsum())+first
print(array3)
产量
[ 10 11 12 13 65 66 67 68 69 200 201 202 203]
对于大型len(array1)
,using_flatnonzero
可能明显快于using_loop
:
def using_flatnonzero(array1, array2):
first, last = array1.min(), array2.max()
array3 = np.zeros(last-first+1, dtype='i1')
array3[array1-first] = 1
array3[array2-first] = -1
return np.flatnonzero(array3.cumsum())+first
def using_loop(array1, array2):
return np.concatenate([np.arange(array1[i], array2[i]) for i in
np.arange(0,len(array1))])
array1, array2 = (np.random.choice(range(1, 11), size=10**4, replace=True)
.cumsum().reshape(2, -1, order='F'))
assert np.allclose(using_flatnonzero(array1, array2), using_loop(array1, array2))
In [260]: %timeit using_loop(array1, array2)
100 loops, best of 3: 9.36 ms per loop
In [261]: %timeit using_flatnonzero(array1, array2)
1000 loops, best of 3: 564 µs per loop
如果范围重叠,则using_loop
将返回包含重复项的array3
。 using_flatnonzero
返回一个没有重复项的数组。
解释:让我们看一下
的小例子array1 = np.array([10, 65, 200])
array2 = np.array([14, 70, 204])
目标是在下面构建一个类似goal
的数组。 1位于索引值[ 10, 11, 12, 13, 65, 66, 67, 68, 69, 200, 201, 202, 203]
(即array3
):
In [306]: goal
Out[306]:
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1], dtype=int8)
获得goal
数组后,可以通过调用array3
获取np.flatnonzero
:
In [307]: np.flatnonzero(goal)
Out[307]: array([ 10, 11, 12, 13, 65, 66, 67, 68, 69, 200, 201, 202, 203])
goal
的长度与array2.max()
相同:
In [308]: array2.max()
Out[308]: 204
In [309]: goal.shape
Out[309]: (204,)
所以我们可以从分配
开始goal = np.zeros(array2.max()+1, dtype='i1')
然后在由array1
给出的索引处array2
和-1&#39处给出的索引位置填写1:
In [311]: goal[array1] = 1
In [312]: goal[array2] = -1
In [313]: goal
Out[313]:
array([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
-1], dtype=int8)
现在应用cumsum
(累积和)会生成所需的goal
数组:
In [314]: goal = goal.cumsum(); goal
Out[314]:
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0])
In [315]: np.flatnonzero(goal)
Out[315]: array([ 10, 11, 12, 13, 65, 66, 67, 68, 69, 200, 201, 202, 203])
这是using_flatnonzero
背后的主要理念。减去first
只是为了节省一点内存。
答案 1 :(得分:2)
我将回顾如何解决这个问题。
采取问题中列出的样本。我们有 -
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
(查看result
)进入,其中13
来自以10
开头并因为的长度
第一组4
。因此,如果我们执行65 - 10 - 4
,我们将获得51
,然后添加1
以适应边界停止,我们将52
,这是
期望的转移价值。同样,我们会得到131
。
因此,可以计算那些shifting-values
,如此 -
In [62]: np.diff(array1) - lens[:-1]+1
Out[62]: array([ 52, 131])
接下来,为了让那些发生这种转变的shifting-places
,我们可以简单地对组长度进行累积求和 -
In [65]: lens[:-1].cumsum()
Out[65]: array([4, 9])
为了完整起见,我们需要为0
预先附加shifting-places
数组array1[0]
和shifting-values
。
因此,我们将逐步展示我们的方法!
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]设置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%
性能提升!
答案 2 :(得分:0)
这是我结合vectorize和concatenate的方法:
<强>实施强>:
import numpy as np
array1, array2 = np.array([10, 65, 200]), np.array([14, 70, 204])
ranges = np.vectorize(lambda a, b: np.arange(a, b), otypes=[np.ndarray])
result = np.concatenate(ranges(array1, array2), axis=0)
print result
# [ 10 11 12 13 65 66 67 68 69 200 201 202 203]
<强>性能强>:
%timeit np.concatenate(ranges(array1, array2), axis=0)
100000个循环,最佳3:每循环13.9μs
答案 3 :(得分:0)
你是说这个吗?
In [440]: np.r_[10:14,65:70,200:204]
Out[440]: array([ 10, 11, 12, 13, 65, 66, 67, 68, 69, 200, 201, 202, 203])
或概括:
In [454]: np.r_[tuple([slice(i,j) for i,j in zip(array1,array2)])]
Out[454]: array([ 10, 11, 12, 13, 65, 66, 67, 68, 69, 200, 201, 202, 203])
虽然这确实涉及双循环,但是生成切片的显式循环和r_
内的切换将切片转换为arange
。
for k in range(len(key)):
scalar = False
if isinstance(key[k], slice):
step = key[k].step
start = key[k].start
...
newobj = _nx.arange(start, stop, step)
我之所以提到这一点,是因为它表明numpy
开发人员认为你的迭代是正常的。
我希望@ unutbu的切肉刀,如果有点迟钝(我还没弄清楚它到底在做什么),解决方案是你最好的速度机会。 cumsum
是一个很好的工具,当您需要使用范围而不是长度不同时。当使用许多小范围时,它可能获得最多。我不认为它适用于重叠范围。
=====
np.vectorize
使用np.frompyfunc
。所以这个迭代也可以表示为:
In [467]: f=np.frompyfunc(lambda x,y: np.arange(x,y), 2,1)
In [468]: f(array1,array2)
Out[468]:
array([array([10, 11, 12, 13]), array([65, 66, 67, 68, 69]),
array([200, 201, 202, 203])], dtype=object)
In [469]: timeit np.concatenate(f(array1,array2))
100000 loops, best of 3: 17 µs per loop
In [470]: timeit np.r_[tuple([slice(i,j) for i,j in zip(array1,array2)])]
10000 loops, best of 3: 65.7 µs per loop
使用@ Darius的vectorize
解决方案:
In [474]: timeit result = np.concatenate(ranges(array1, array2), axis=0)
10000 loops, best of 3: 52 µs per loop
vectorize
必须做一些额外的工作才能更有效地使用广播。如果array1
更大,相对速度可能会发生变化。
@ unutbu的解决方案对于这个小array1
并不特别。
In [478]: timeit using_flatnonzero(array1,array2)
10000 loops, best of 3: 57.3 µs per loop
OP解决方案,迭代没有我的r_
中间人是好的
In [483]: timeit array3 = np.concatenate([np.arange(array1[i], array2[i]) for i in np.arange(0,len(array1))])
10000 loops, best of 3: 24.8 µs per loop
通常情况下,使用少量循环,列表理解比较高级numpy
操作更快。
对于@ unutbu更大的测试用例,我的时间与他的时间一致 - 速度提高了17倍。
===================
对于小样本阵列,@ Divakar的解决方案速度较慢,但对于大型解决方案,速度比@ unutbu快3倍。因此它有更多的设置成本,但缩放速度较慢。