【问题标题】:Find index positions where 3D-array meets MULTIPLE conditions查找 3D-array 满足 MULTIPLE 条件的索引位置
【发布时间】:2013-11-15 05:27:29
【问题描述】:

我有一个 3D 数组,由每个波段内的多个数字组成。有没有返回数组满足MULTIPLE条件的索引位置的函数?

我尝试了以下方法:

index_pos = numpy.where(
    array[:,:,0]==10 and array[:,:,1]==15 and array[:,:,2]==30)

返回错误:

ValueError: The truth value of an array with more than one element is ambiguous.
Use a.any() or a.all()

【问题讨论】:

    标签: python arrays numpy


    【解决方案1】:

    您实际上有一种特殊情况,执行以下操作会更简单、更有效:

    创建数据:

    >>> arr
    array([[[ 6,  9,  4],
            [ 5,  2,  1],
            [10, 15, 30]],
    
           [[ 9,  0,  1],
            [ 4,  6,  4],
            [ 8,  3,  9]],
    
           [[ 6,  7,  4],
            [ 0,  1,  6],
            [ 4,  0,  1]]])
    

    期望值:

    >>> index_pos = np.where((arr[:,:,0]==10) & (arr[:,:,1]==15) & (arr[:,:,2]==30))
    >>> index_pos
    (array([0]), array([2]))
    

    同时使用广播:

    >>> arr == np.array([10,15,30])
    array([[[False, False, False],
            [False, False, False],
            [ True,  True,  True]],
    
           [[False, False, False],
            [False, False, False],
            [False, False, False]],
    
           [[False, False, False],
            [False, False, False],
            [False, False, False]]], dtype=bool)
    
    >>> np.where( np.all(arr == np.array([10,15,30]), axis=-1) )
    (array([0]), array([2]))
    

    如果您想要的索引不连续,您可以执行以下操作:

    ind_vals = np.array([0,2])
    where_mask = (arr[:,:,ind_vals] == values)
    

    尽可能广播。

    受到@Jamie 评论的启发,需要考虑一些有趣的事情:

    arr = np.random.randint(0,100,(5000,5000,3))
    
    %timeit np.all(arr == np.array([10,15,30]), axis=-1)
    1 loops, best of 3: 614 ms per loop
    
    %timeit ((arr[:,:,0]==10) & (arr[:,:,1]==15) & (arr[:,:,2]==30))
    1 loops, best of 3: 217 ms per loop
    
    %timeit tmp = (arr == np.array([10,15,30])); (tmp[:,:,0] & tmp[:,:,1] & tmp[:,:,2])
    1 loops, best of 3: 368 ms per loop
    

    问题变成了,为什么会这样?:

    首先检查:

    %timeit (arr[:,:,0]==10)
    10 loops, best of 3: 51.2 ms per loop
    
    %timeit (arr == np.array([10,15,30]))
    1 loops, best of 3: 300 ms per loop
    

    人们会期望arr == np.array([10,15,30]) 在最坏的情况下是arr[:,:,0]==10 的速度的1/3。任何人都知道为什么不是这种情况?

    那么在组合最终轴时,有很多方法可以实现这一点。

    tmp = (arr == np.array([10,15,30]))
    
    method1 = np.all(tmp,axis=-1)
    method2 = (tmp[:,:,0] & tmp[:,:,1] & tmp[:,:,2])
    method3 = np.einsum('ij,ij,ij->ij',tmp[:,:,0] , tmp[:,:,1] , tmp[:,:,2])
    
    np.allclose(method1,method2)
    True
    np.allclose(method1,method3)
    True
    
    %timeit np.all(tmp,axis=-1)
    1 loops, best of 3: 318 ms per loop
    
    %timeit (tmp[:,:,0] & tmp[:,:,1] & tmp[:,:,2])
    10 loops, best of 3: 68.2 ms per loop
    
    %timeit np.einsum('ij,ij,ij->ij',tmp[:,:,0] , tmp[:,:,1] , tmp[:,:,2])
    10 loops, best of 3: 38 ms per loop
    

    einsum 加速是明确定义的elsewhere,但我觉得all 和连续& 之间存在如此大的差异似乎很奇怪。

    【讨论】:

    • 对于非常大的数组,创建单个布尔数组可能会更慢,而不是三分之一的大小,但它看起来肯定更干净。顺便说一句,您可以让 ufunc 将列表转换为数组:arr = [10, 15, 30] 的工作原理相同。
    • @Jamie 请查看我的编辑,您的评论很有趣,我不太明白为什么它这么慢。 np.array(...) 来自习惯——有几个地方 ufunc 不调用 np.asarray,这让我很紧张。
    • 我认为构建布尔数组的速度是内存缓存的事情。您提到的 3 比 1 速度比仅适用于像 (10, 10, 3) 这样的小型阵列,我猜这里它们都适合缓存。对于像(100, 100, 3) 这样的中间数组,它上升到 10 比 1,可能是因为一个适合缓存而另一个不适合。对于像 (5000, 5000, 3) 这样的较大数组,显然又开始下降,可能是因为现在两者都需要缓存读取。
    • @EMS 我同意这是一个值得发表的评论,而且是一个很好的观点(这就是我投票赞成该评论的原因)。然而,说按位部分无关紧要让我觉得有点笨拙。特别是当按位运算在提供的示例中明确有用时。
    • 我想我认为它与您的评论相反。按位方法可以作为一次性注释,就像“嘿,你也可以用这个按位符号来做,但要小心一些陷阱。”如果将其视为主要或首选答案,则按位方法在这里会产生误导。按位方法很有用,但不是适合这项工作的工具。
    【解决方案2】:

    and 运算符在这种情况下不起作用。

    index_pos = numpy.where(array[:,:,0]==10 and array[:,:,1]==15 and array[:,:,2]==30)
    

    试试这个:

    index_pos = numpy.where((array[:,:,0]==10) & (array[:,:,1]==15) & (array[:,:,2]==30))
    

    【讨论】:

    • 请注意,使用& 等同于使用np.bitwise_and,它会为布尔数组做你所期望的:一个元素,如示例中所示,因为 bools是单个位。但是,它仍然是按位,所以np.array([1,2]) & np.array([0,1]) 不会给出与np.logical_and(np.array([1,2]), np.array([0,1])) 相同的值。
    【解决方案3】:

    问题在于使用了原生 Python and 关键字,它在数组上的行为方式与您不希望的那样。

    请尝试使用numpy.logical_and 函数。

    cond1 = np.logical_and(array[:,:,0]==10, array[:,:,1]==15)
    cond2 = np.logical_and(cond1, array[:,:,2]==30)
    index_pos = numpy.where(cond2)
    

    您甚至可以创建自己的logical_and 版本,接受任意数量的条件:

    def my_logical_and(*args):
        return reduce(np.logical_and, args)
    
    condition_locs_and_vals = [(0, 10), (1, 15), (2, 30)]
    conditions = [array[:,:,x] == y for x,y in conditition_locs_and_vals]
    my_logical_and(*conditions)
    

    使用按位与 (&) 有效,但这只是巧合。按位与用于比较位或bool 类型。使用它来比较数值数组的真值并不可靠(例如,如果您突然需要对条目计算为 True 的位置进行索引,而不是实际首先转换为 bool 数组)。确实应该使用logical_and 而不是&(即使它会带来速度损失)。

    此外,将任意条件列表与& 链接在一起可能会让人难以阅读和输入。并且为了代码的可重用性,以便以后的程序员不必将一堆从属子句更改为 & 运算符,最好将各个条件单独存储,然后使用类似的函数将它们组合起来。

    【讨论】:

    • 不错的答案。顺便说一句,np.logical_and 有一个reduce 方法,据我所知,它与您的my_logical_and 相同:np.logical_and.reduce(*conditions)。您也可以使用 np.all(conditions, axis=0) 来执行此操作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-07
    • 2018-05-26
    • 1970-01-01
    • 1970-01-01
    • 2019-11-27
    • 2015-04-30
    • 2019-01-22
    相关资源
    最近更新 更多