根据起始和结束位置的列表构建Numpy索引

7
我有两个大小相同的numpy.array对象(都是一维数组),其中一个包含起始索引位置的列表,另一个包含结束索引位置的列表(或者你可以说我有一个起始位置列表和窗口长度列表)。如果有必要,可以保证由起始和结束位置形成的切片不重叠。我正在尝试弄清楚如何使用这些起始和结束位置来为另一个数组对象形成索引,而不必使用循环。
例如:
import numpy as np
start = np.array([1,7,20])
end = np.array([3,10,25])

希望引用

somearray[1,2,7,8,9,20,21,22,23,24])
4个回答

7
我会使用:
np.r_[tuple(slice(s, e) for s, e in zip(start, end))]

编辑:这里有一个不使用Python循环的解决方案:

def indices(start, end):
    lens = end - start
    np.cumsum(lens, out=lens)
    i = np.ones(lens[-1], dtype=int)
    i[0] = start[0]
    i[lens[:-1]] += start[1:]
    i[lens[:-1]] -= end[:-1]
    np.cumsum(i, out=i)
    return i

这种方法只创建一个临时的NumPy数组(lens),比其他解决方案都要快。


谢谢Sven,不过我发现这个解决方案仍然比Joe Kington提供的解决方案慢一些。 - Abiel
@Abiel:昨天没有时间计时。这是另一次尝试 :) - Sven Marnach
Sven - 感谢您的更新解决方案。这个方案提供了最佳的性能。 - Abiel
@Abiel:这个解决方案也不像其他的那么显而易见。但是如果你真的需要性能,你就必须使用它 :) - Sven Marnach

2
Numpy的arange函数可以创建单独的序列,把它们串在一起即可。这样怎么样?
In [11]: idx = np.hstack([np.arange(s,e) for s,e in  zip(start, end)])

In [12]: idx
Out[12]: array([ 1,  2,  7,  8,  9, 20, 21, 22, 23, 24])

然后你可以访问somearray[idx]


1
等价地,您可以只需执行 np.hstack([np.r_[s:e] for s,e in zip(start, end)])。不过,如果您不熟悉 numpy.r_,这可能会稍微难以阅读。 - Joe Kington
感谢Andrew和Joe。我的一个问题是,有没有一种方法可以避免使用列表推导式来完成这个任务?当然,这样做很快,但我仍然发现我的函数大部分运行时间都用在了构建idx的那一行上(函数的其余部分使用numpy例程来识别与特定长度标准匹配的窗口的起始和结束位置)。 - Abiel
这两种方法都会生成中间数组,而不仅仅是生成数组的整数索引。如果你处理大型数组,这样做有点浪费。 - the wolf
NumPy只能处理固定长度的数组,因此很难想象一个(简单的)算法版本不经过中间列表。 - Andrew Jaffe
True: numpy数组必须在创建时定义和固定。但是,[np.arange(s,e) for s,e in zip(start, end)]部分生成3个numpy数组,然后使用hstack堆叠。因此,您要创建4个numpy数组才能得到一个结果。如果您改为使用r_的切片语法或创建一个Python列表,则总共只需创建1或2个列表... - the wolf

0
这样怎么样:
>>> import numpy as np
>>> start = np.array([1,7,20])
>>> end = np.array([3,10,25])
>>> na=np.fromiter(sum([range(s,e) for s,e in zip(start,end)],[]),np.int)
>>> na
array([ 1,  2,  7,  8,  9, 20, 21, 22, 23, 24])

优点是:1)没有中间的numpy浮点数组;2)结果数组是整数,以最高效地处理其他numpy数组。

谢谢drewk,不幸的是我发现这个解决方案比Andrew/Joe/Sven的解决方案慢了一个数量级。在处理有100,000个元素和许多起始和结束点的数组时,Joe的解决方案执行速度略高于40倍。 - Abiel
@Abiel:非常感谢您的反馈!有时候写这些 Stack Overflow 的帖子是很费力的。知道至少有人看了它,让这个努力变得值得。 - dawg

-1
你说:“或者你可以说我有一组起始位置和窗口长度”,但这与你的示例数组不符。
如果 `start` 表示起始位置,`end` 是“长度”,那么你可以按照以下方式获取元素:
>>> [i for iter in [range(s,s+e) for s,e in zip(start,end)] for i in iter]
[1, 2, 3, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 21, 22, 23, 24, 25, 26, 
27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44]

如果你想匹配你的示例数组,并且end确实是结束元素-1,你可以通过以下方式获取你的元素:

>>> [i for iter in [range(*t) for t in zip(start,end)] for i in iter]
[1, 2, 7, 8, 9, 20, 21, 22, 23, 24]
>>> somearray=np.array(_)
>>> somearray
array([1, 2, 7, 8, 9, 20, 21, 22, 23, 24])

替代方案:

>>> sum([range(*t) for t in zip(start,end)],[])
[1, 2, 7, 8, 9, 20, 21, 22, 23, 24]

请记住,您只是生成一个整数列表,将其描述为numpy数组的索引。如果在您的情况下使用xrangerange快/好, 则可以使用其中任何一个。


@Andrew Jaffe:他提出了两个不同的问题,我都回答了。 - the wolf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接