comprobar si una matriz numpy tiene 0 en todos sus bordes [cerrado]

13

¿Cuál sería la forma más rápida de verificar si una matriz numpy multidimensional tiene 0 en todos los lados?

Entonces, para un ejemplo 2D simple, tengo:

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

Si bien esto está bien para casos 2D a la derecha, escribir para dimensiones más altas es un poco tedioso y me preguntaba si hay algún truco ingenioso y numpy que pueda usar aquí para hacerlo más eficiente y también más fácil de mantener.

Luca
fuente
8
¿No np.all (x[:, 0] == 0)sería más seguro que la suma? La prueba de suma es correcta solo si todos los números son positivos.
Demi-Lune
1
@ Demi-Lume tiene sentido. En mi caso, todo será> = 0 pero tu comentario es apreciado :)
Luca
1
En un caso 3D, ¿te refieres a caras (hay seis de ellas) o bordes (hay 12 de ellas) del cubo?
Riccardo Bucco
@RiccardoBucco Sí, 6 caras. pero mi problema es que puede ir dimensión superior a 3.
Luca

Respuestas:

7

Así es como puedes hacerlo:

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

np.take hace lo mismo que la indexación "elegante".

Riccardo Bucco
fuente
1
@Luca: la documentación no lo deja claro, pero numpy.takehace una copia. Esto puede hacer que funcione peor que el código basado en una vista. (Momento sería necesario estar seguro - NumPy eficiencia de vista es a veces extraño.)
user2357112 apoya Mónica
1
@RiccardoBucco: len(x.shape)se puede escribir más simplemente como x.ndim.
user2357112 es compatible con Monica
1
@ user2357112supportsMonica gracias, lo arreglé :)
Riccardo Bucco
55
Además, el uso de una lista de comprensión evita allcortocircuitos. Puede eliminar los corchetes para usar una expresión generadora, lo allque permite regresar tan pronto como numpy.allregrese una sola llamada False.
user2357112 es compatible con Monica
1
@ user2357112supportsMonica True !!
Riccardo Bucco
5

Aquí hay una respuesta que realmente examina las partes de la matriz que le interesan y no pierde el tiempo construyendo una máscara del tamaño de toda la matriz. Hay un bucle a nivel de Python, pero es corto, con iteraciones proporcionales al número de dimensiones en lugar del tamaño de la 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 es compatible con Monica
fuente
¿Hay alguna circunstancia en la not (view[0] == 0).all()que no sea equivalente view[0].any()?
Paul Panzer
@PaulPanzer: Supongo view[0].any()que también funcionaría. No estoy completamente seguro de las implicaciones de eficiencia de la conversión y el almacenamiento en búfer involucrados en las dos opciones: en view[0].any()teoría podría implementarse más rápido, pero he visto resultados extraños antes, y no entiendo completamente el almacenamiento en búfer involucrado.
user2357112 es compatible con Monica
Supongo view[0].view(bool).any()que sería la solución de alta velocidad.
Paul Panzer
@PaulPanzer: en argmaxrealidad podría superar anyla vista booleana . Esto se pone raro.
user2357112 es compatible con Monica
(Además, si argmaxo no any, usar una vista booleana significa manejar el cero negativo como desigual al cero regular.)
user2357112 es compatible con Monica el
2

Reformé la matriz y luego la iteré. Desafortunadamente, mi respuesta asume que tiene al menos tres dimensiones y generará un error para las matrices normales, tendría que agregar una cláusula especial para las matrices con forma de 1 y 2 dimensiones. Además, esto será lento, por lo que probablemente haya mejores soluciones.

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

Que producirá

>>> False
>>> True

Básicamente apilo todas las dimensiones una encima de la otra y luego miro a través de ellas para verificar sus bordes.

lwileczek
fuente
Esto examina las partes incorrectas de la matriz. Para una matriz tridimensional, queremos examinar las caras de toda la matriz, no los bordes de cada submatriz bidimensional.
user2357112 admite Monica
Ahh, eso tiene mas sentido. No
entendí bien
1

tal vez el operador de puntos suspensivos es lo que está buscando, que funcionará para muchas dimensiones:

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
fuente
Esto no coloreará todas las caras. Por ejemplo, pruébelo con un cubo (4, 4, 4).
Luca
No estoy seguro de lo que quieres decir con colorear caras, pero funciona si haces x (4, 4, 4)
daveg
1

Puede utilizar el sliceenmascaramiento booleano para hacer el trabajo:

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

Esta función primero da forma al "núcleo" de la matriz en la tupla s, y luego crea una máscara que se muestra Truesolo para los puntos limítrofes. La indexación booleana entrega los puntos de borde.

Ejemplo de trabajo:

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

Luego, np.all(borders==0)te dará la información deseada.


Nota: esto se rompe para las matrices unidimensionales, aunque considero que son un caso extremo. Probablemente sea mejor que solo verifique los dos puntos en cuestión allí

Lukas Thaler
fuente
Esto lleva un tiempo proporcional al número total de elementos en la matriz, en lugar de solo el borde. Además, las matrices unidimensionales no son un caso marginal irrelevante.
user2357112 es compatible con Monica el
1
Además, np.arange(15)no incluye 15.
user2357112 es compatible con Monica el
Estoy de acuerdo en que "irrelevante" es una redacción sólida, aunque creo que es mejor que solo verifique los dos puntos relevantes para una matriz 1d. El 15 es un error tipográfico, buena captura
Lukas Thaler