检查多维numpy数组的所有边缘是否为零数组

15

一个n维数组有2^n个面(一个一维数组有2个端点; 一个二维数组有4条边或边缘; 一个三维数组有6个二维面; 一个四维数组有8个边; 以此类推)。这类似于抽象的n维立方体。

我想检查n维数组的所有面是否由零组成。以下是三个面由零组成的数组示例:

# 1D
np.array([0,1,2,3,0])
# 2D
np.array([[0, 0, 0, 0],
          [0, 1, 0, 0],
          [0, 2, 3, 0],
          [0, 0, 1, 0],
          [0, 0, 0, 0]])
# 3D
np.array([[[0, 0, 0, 0],
           [0, 0, 0, 0],
           [0, 0, 0, 0]],
          [[0, 0, 0, 0],
           [0, 1, 2, 0],
           [0, 0, 0, 0]],
          [[0, 0, 0, 0],
           [0, 0, 0, 0],
           [0, 0, 0, 0]]])

我怎样才能检查一个多维numpy数组的所有边缘是否都是由零组成的数组?例如,对于一个简单的2维数组,我可以这样做:

x = np.random.rand(5, 5)
assert np.sum(x[0:,  0]) == 0
assert np.sum(x[0,  0:]) == 0
assert np.sum(x[0:, -1]) == 0
assert np.sum(x[-1, 0:]) == 0

虽然这种方法适用于二维情况,但它不适用于更高的维度。我想知道是否有一些巧妙的numpy技巧可以在此处使用,使其更有效率和可维护性。


9
“np.all(x[:, 0] == 0)”比使用求和更安全吗?只有所有的数都为正数时,求和测试才正确。 - Demi-Lune
3
相关问题:如何获取数组的所有边缘? - Georgy
1
@Demi-Lume 有道理。在我的情况下,一切都将是>=0,但感谢您的评论 :) - Luca
1
在三维情况下,您是指立方体的面(共六个)还是棱(共十二个)? - Riccardo Bucco
@RiccardoBucco 是的,有6个面。但我的问题是它可以比3维更高。 - Luca
是的,我明白了,只是想澄清一下你的问题。 - Riccardo Bucco
5个回答

10

以下是您可以操作的步骤:

assert(all(np.all(np.take(x, index, axis=axis) == 0)
           for axis in range(x.ndim)
           for index in (0, -1)))

np.take实现的功能与“fancy”索引相同。


这太棒了。我不确定如何循环遍历轴索引并在0和-1之间切换。之前不知道np.take - Luca
1
@Luca:文档并不清楚,但 numpy.take 会进行复制。这可能导致其性能不如基于视图的代码。(需要进行计时才能确定 - NumPy 视图效率有时很奇怪。) - user2357112
5
此外,使用列表推导式会阻止all进行短路求值。你可以去掉方括号并使用生成器表达式,这样一旦单个numpy.all调用返回Falseall就可以立即返回。 - user2357112
1
@user2357112supportsMonica 真的没错!! - Riccardo Bucco

5
这是一个实际检查你感兴趣的数组部分,而不浪费时间构建整个数组大小掩码的答案。有一个Python循环,但它很短,迭代次数与数组大小无关,而与维度数量成比例。
def all_borders_zero(array):
    if not array.ndim:
        raise ValueError("0-dimensional arrays not supported")
    for dim in range(array.ndim):
        view = numpy.moveaxis(array, dim, 0)
        if not (view[0] == 0).all():
            return False
        if not (view[-1] == 0).all():
            return False
    return True

在什么情况下 not (view[0] == 0).all() 不等同于 view[0].any() - Paul Panzer
@PaulPanzer:我想view[0].any()也可以。我不完全确定这两个选项中涉及的转换和缓冲的效率影响 - view[0].any()在理论上可能会更快地实现,但我以前见过奇怪的结果,并且我不完全理解所涉及的缓冲。 - user2357112
我想 view[0].view(bool).any() 可能是高速解决方案。 - Paul Panzer
@PaulPanzer:argmax 可能会在布尔视图上击败 any。这些东西变得很奇怪。 - user2357112
实际上,等等 - view(bool) 不安全,因为我们无法保证任意非零位模式被视为等同于通常用于真布尔值的位模式。 - user2357112
显示剩余2条评论

2

我重新整理了数组,然后对其进行了迭代。不幸的是,我的答案假定您至少有三个维度,并且对于普通矩阵会出错,您需要为1和2维形状的数组添加特殊子句。此外,这将很慢,因此可能有更好的解决方案。

x = np.array(
        [
            [
                [0 , 1, 1, 0],
                [0 , 2, 3, 0],
                [0 , 4, 5, 0]
            ],
            [
                [0 , 6, 7, 0],
                [0 , 7, 8, 0],
                [0 , 9, 5, 0]
            ]
        ])

xx = np.array(
        [
            [
                [0 , 0, 0, 0],
                [0 , 2, 3, 0],
                [0 , 0, 0, 0]
            ],
            [
                [0 , 0, 0, 0],
                [0 , 7, 8, 0],
                [0 , 0, 0, 0]
            ]
        ])

def check_edges(x):

    idx = x.shape
    chunk = np.prod(idx[:-2])
    x = x.reshape((chunk*idx[-2], idx[-1]))
    for block in range(chunk):
        z = x[block*idx[-2]:(block+1)*idx[-2], :]
        if not np.all(z[:, 0] == 0):
            return False
        if not np.all(z[:, -1] == 0):
            return False
        if not np.all(z[0, :] == 0):
            return False
        if not np.all(z[-1, :] == 0):
            return False

    return True

这将产生

>>> False
>>> True

基本上,我将所有维度叠放在一起,然后查看它们的边缘。

这个程序检查了数组的错误部分。对于一个三维数组,我们想要检查整个数组的面,而不是每个二维子数组的边缘。 - user2357112
啊,这样就更有意义了。我误解了。 - lwileczek

1
也许省略号运算符是您正在寻找的内容,它适用于许多维度:
import numpy as np

# data
x = np.random.rand(2, 5, 5)
x[..., 0:, 0] = 0
x[..., 0, 0:] = 0
x[..., 0:, -1] = 0
x[..., -1, 0:] = 0

test = np.all(
    [
        np.all(x[..., 0:, 0] == 0),
        np.all(x[..., 0, 0:] == 0),
        np.all(x[..., 0:, -1] == 0),
        np.all(x[..., -1, 0:] == 0),
    ]
)

print(test)

这不会给所有的面都上色。例如,尝试使用一个(4, 4, 4)的立方体。 - Luca
我不确定您所说的"着色面孔"是什么意思,但如果您将x (4, 4, 4)设置好,它确实有效。 - daveg

1
你可以利用 slice 和布尔掩码来完成任务:
def get_borders(arr):
    s=tuple(slice(1,i-1) for i in a.shape)
    mask = np.ones(arr.shape, dtype=bool)
    mask[s] = False
    return(arr[mask])

此函数首先将数组的“核心”变成元组s,然后建立一个掩码,只显示边缘点的True。布尔索引随后提供了边界点。
工作示例:
a = np.arange(16).reshape((4,4))

print(a)
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])

borders = get_borders(a)
print(borders)
array([ 0,  1,  2,  3,  4,  7,  8, 11, 12, 13, 14, 15])

然后,np.all(borders==0)将会给你所需的信息。

注意:这对于一维数组来说会出现问题,虽然我认为这是一个边缘情况。你最好只检查相关的两个点。


这需要与数组中元素总数成比例的时间,而不仅仅是边界。此外,一维数组并不是一个无关紧要的边缘情况。 - user2357112
1
另外,np.arange(15)不包括15。 - user2357112
我同意“不相关”是一个强烈的措辞,但我认为你最好只检查一维数组中的两个相关点。15是一个打字错误,发现得好。 - Lukas Thaler

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