verifique se um array numpy tem 0 em todas as suas bordas [fechado]

13

Qual seria a maneira mais rápida de verificar se uma matriz numpy multidimensional tem 0 em todos os lados.

Então, para um exemplo 2D simples, tenho:

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

Embora isso seja bom para casos 2D corretos, escrever para dimensões mais altas é um pouco entediante e eu queria saber se existe algum truque inteligente e inteligente que eu possa usar aqui para torná-lo eficiente e também mais sustentável.

Luca
fonte
8
Não np.all (x[:, 0] == 0)seria mais seguro que a soma? O teste de soma está correto apenas se todos os números forem positivos.
Demi-Lune
11
@ Demi-Lume Faz sentido. No meu caso, tudo será> = 0, mas seu comentário será apreciado :)
Luca
11
Em um caso 3D, você quer dizer faces (existem seis) ou arestas (existem 12) do cubo?
Riccardo Bucco
@RiccardoBucco Sim, 6 faces. mas meu problema é que ele pode ter uma dimensão maior que 3.
Luca

Respostas:

7

Veja como você pode fazer isso:

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

np.take faz o mesmo que indexação "sofisticada".

Riccardo Bucco
fonte
11
@Luca: A documentação não deixa claro, mas numpy.takefaz uma cópia. Isso pode causar um desempenho pior que o código com base em uma exibição. (O tempo seria necessário para ter certeza - a eficiência da exibição do NumPy às vezes é estranha.)
user2357112 suporta Monica em 23/03
11
@RiccardoBucco: len(x.shape)pode ser escrito de forma mais simples como x.ndim.
user2357112 suporta Monica em 23/03
11
@ user2357112supportsMonica obrigado, eu fixa-lo :)
Riccardo Bucco
5
Além disso, o uso de uma compreensão de lista evita allcurto-circuitos. Você pode remover os colchetes para usar uma expressão de gerador, permitindo allretornar assim que uma única numpy.allchamada retornar False.
user2357112 suporta Monica em 23/03
11
@ user2357112supportsMonica True !!
Riccardo Bucco 23/03
5

Aqui está uma resposta que realmente examina as partes da matriz em que você está interessado e não perde tempo construindo uma máscara do tamanho de toda a matriz. Há um loop no nível do Python, mas é curto, com iterações proporcionais ao número de dimensões em vez do tamanho da matriz.

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
user2357112 suporta Monica
fonte
Existem circunstâncias em que not (view[0] == 0).all()não é equivalente view[0].any()?
Paul Panzer
@ PaulPanzer: Suponho view[0].any()que funcionaria também. Não tenho muita certeza das implicações de eficiência da conversão e do buffer envolvidas nas duas opções - view[0].any()teoricamente poderiam ser implementadas mais rapidamente, mas já vi resultados estranhos antes e não compreendo completamente o buffer envolvido.
user2357112 suporta Monica em 24/03
Suponho view[0].view(bool).any()que seria a solução de alta velocidade.
Paul Panzer
@ PaulPanzer: argmaxpode realmente superar anya visão booleana . Esse material fica estranho.
user2357112 suporta Monica
(Além disso, se argmaxou any, usar uma visualização booleana significa manipular o zero negativo como desigual ao zero regular.)
user2357112 suporta Monica em
2

Alterei a forma da matriz e iteramos através dela. Infelizmente, minha resposta pressupõe que você tenha pelo menos três dimensões e irá errar em matrizes normais; você teria que adicionar uma cláusula especial para matrizes de 1 e 2 dimensões. Além disso, isso será lento, portanto provavelmente haverá melhores soluções.

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

Qual produzirá

>>> False
>>> True

Basicamente, empilho todas as dimensões umas sobre as outras e olho através delas para verificar suas bordas.

lwileczek
fonte
Isso examina as partes erradas da matriz. Para uma matriz tridimensional, queremos examinar as faces de toda a matriz, não as arestas de cada sub-matriz bidimensional.
user2357112 suporta Monica em 23/03
Ah, isso faz mais sentido. Eu
entendi errado
1

talvez o operador de reticências seja o que você está procurando, que funcionará em várias dimensões:

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)
Daveg
fonte
Isso não colorirá todos os rostos. Por exemplo, tente com um cubo (4, 4, 4).
Luca
Não sei o que você quer dizer com colorir rostos, mas funciona se você fizer x (4, 4, 4)
daveg 23/03
1

Você pode usar o slicemascaramento booleano para realizar o trabalho:

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])

Essa função primeiro molda o "núcleo" da matriz na tupla se depois cria uma máscara que é exibida Trueapenas para os pontos limítrofes. A indexação booleana entrega os pontos de borda.

Exemplo de trabalho:

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])

Então, np.all(borders==0)você fornecerá as informações desejadas.


Nota: isso quebra para matrizes unidimensionais, embora eu as considere um caso extremo. Você provavelmente está melhor apenas verificando os dois pontos em questão

Lukas Thaler
fonte
Isso leva um tempo proporcional ao número total de elementos na matriz, em vez de apenas a borda. Além disso, matrizes unidimensionais não são um caso de borda irrelevante.
user2357112 suporta Monica em 23/03
11
Além disso, np.arange(15)não inclui 15.
user2357112 suporta Monica em 23/03
Eu concordo que "irrelevante" é uma expressão forte, embora eu sinta que é melhor verificar os dois pontos relativos a uma matriz 1d. Os 15 são um erro de digitação, boa captura
Lukas Thaler