vérifier si un tableau numpy a 0 sur toutes ses bordures [fermé]

13

Quel serait le moyen le plus rapide de vérifier si un tableau numpy multidimensionnel a 0 de tous les côtés.

Donc, pour un exemple 2D simple, j'ai:

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

Bien que cela soit correct pour les cas 2D à droite, l'écriture pour des dimensions plus élevées est un peu fastidieuse et je me demandais s'il y avait une astuce numpy intelligente que je peux utiliser ici pour la rendre efficace et plus facile à maintenir.

Luca
la source
8
Ne serait pas np.all (x[:, 0] == 0)plus sûr que la somme? Le test de somme n'est correct que si tous les nombres sont positifs.
Demi-Lune
1
@ Demi-Lume est logique. Dans mon cas, tout sera> = 0 mais votre commentaire est apprécié :)
Luca
1
Dans un cas 3D, voulez-vous dire les faces (il y en a six) ou les bords (il y en a 12) du cube?
Riccardo Bucco
@RiccardoBucco Oui, 6 visages. mais mon problème est qu'il peut aller à une dimension supérieure à 3.
Luca

Réponses:

7

Voici comment procéder:

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

np.take fait la même chose que l'indexation "fantaisie".

Riccardo Bucco
la source
1
@Luca: La documentation n'est pas claire, mais en numpy.takefait une copie. Cela peut entraîner des performances inférieures à celles du code basé sur une vue. (Le timing serait nécessaire pour être sûr - L'efficacité de la vue NumPy est parfois bizarre.)
user2357112 prend en charge Monica
1
@RiccardoBucco: len(x.shape)peut être écrit plus simplement comme x.ndim.
user2357112 prend en charge Monica
1
@ user2357112supportsMonica merci, je l'ai corrigé :)
Riccardo Bucco
5
En outre, l'utilisation d'une compréhension de liste empêche les allcourts-circuits. Vous pouvez supprimer les crochets pour utiliser une expression de générateur, permettant allde retourner dès qu'un seul numpy.allappel revient False.
user2357112 prend en charge Monica
1
@ user2357112supportsMonica True !!
Riccardo Bucco
5

Voici une réponse qui examine réellement les parties du tableau qui vous intéressent et ne perd pas de temps à construire un masque de la taille de l'ensemble du tableau. Il y a une boucle au niveau Python, mais elle est courte, avec des itérations proportionnelles au nombre de dimensions au lieu de la taille du tableau.

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 prend en charge Monica
la source
Y a-t-il des circonstances où not (view[0] == 0).all()n'est pas équivalent à view[0].any()?
Paul Panzer
@PaulPanzer: Je suppose que view[0].any()cela fonctionnerait aussi. Je ne suis pas entièrement sûr des implications en termes d'efficacité de la conversion et de la mise en mémoire tampon impliquées dans les deux options - view[0].any()pourraient théoriquement être mises en œuvre plus rapidement, mais j'ai déjà vu des résultats étranges auparavant, et je ne comprends pas complètement la mise en mémoire tampon impliquée.
user2357112 prend en charge Monica
Je suppose que ce view[0].view(bool).any()serait la solution à grande vitesse.
Paul Panzer
@PaulPanzer: argmaxpourrait en fait battre anyla vue booléenne . Ce truc devient bizarre.
user2357112 prend en charge Monica
(En outre, si argmaxou any, l'utilisation d'une vue booléenne signifie que le zéro négatif est différent du zéro normal.)
user2357112 prend en charge Monica
2

J'ai remodelé le tableau, puis itéré à travers lui. Malheureusement, ma réponse suppose que vous avez au moins trois dimensions et que vous vous tromperez pour les matrices normales, vous devrez ajouter une clause spéciale pour les tableaux en forme 1 et 2 dimensions. De plus, cela sera lent donc il y a probablement de meilleures solutions.

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

Qui produira

>>> False
>>> True

Fondamentalement, j'empile toutes les dimensions les unes sur les autres, puis je les regarde pour vérifier leurs bords.

lwileczek
la source
Cela examine les mauvaises parties du tableau. Pour un tableau à 3 dimensions, nous voulons examiner les faces de l'ensemble du tableau, pas les bords de chaque sous-tableau à 2 dimensions.
user2357112 prend en charge Monica
Ah, ça a plus de sens. J'ai mal compris
lwileczek
1

peut-être que l'opérateur des points de suspension est ce que vous recherchez, qui fonctionnera pour de nombreuses dimensions:

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
la source
Cela ne colorera pas tous les visages. Par exemple, essayez-le avec un cube (4, 4, 4).
Luca
Je ne suis pas sûr de ce que vous entendez par coloration des visages, mais cela fonctionne si vous faites x (4, 4, 4)
daveg
1

Vous pouvez utiliser le slicemasquage booléen pour faire le travail:

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

Cette fonction façonne d'abord le «noyau» du tableau dans le tuple s, puis crée un masque qui s'affiche Trueuniquement pour les points limitrophes. L'indexation booléenne délivre ensuite les points frontières.

Exemple de travail:

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

Ensuite, np.all(borders==0)vous donnera les informations souhaitées.


Remarque: cela se casse pour les tableaux unidimensionnels, bien que je considère ceux-ci comme un cas de bord. Vous feriez probablement mieux de vérifier les deux points en question

Lukas Thaler
la source
Cela prend du temps proportionnel au nombre total d'éléments dans le tableau, au lieu de simplement la bordure. En outre, les tableaux unidimensionnels ne sont pas un cas de bord non pertinent.
user2357112 prend en charge Monica
1
En outre, np.arange(15)ne comprend pas 15.
user2357112 prend en charge Monica
Je suis d'accord que "non pertinent" est une formulation forte, bien que je pense que vous feriez mieux de vérifier les deux points concernant un tableau 1d. Le 15 est une faute de frappe, bonne prise
Lukas Thaler