Überprüfen Sie, ob ein Numpy-Array an allen Rändern 0 hat [geschlossen]

13

Was wäre der schnellste Weg, um zu überprüfen, ob ein mehrdimensionales Numpy-Array auf allen Seiten 0 hat?

Für ein einfaches 2D-Beispiel habe ich also:

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

Während dies für 2D-Fälle in Ordnung ist, ist das Schreiben für höhere Dimensionen etwas mühsam und ich habe mich gefragt, ob es einen cleveren Numpy-Trick gibt, den ich hier verwenden kann, um es effizienter und auch wartbarer zu machen.

Luca
quelle
8
Wäre nicht np.all (x[:, 0] == 0)sicherer als die Summe? Der Summentest ist nur dann korrekt, wenn alle Zahlen positiv sind.
Demi-Lune
1
@ Demi-Lume macht Sinn. In meinem Fall wird alles> = 0 sein, aber Ihr Kommentar wird geschätzt :)
Luca
1
Meinen Sie in einem 3D-Fall Flächen (es gibt sechs davon) oder Kanten (es gibt 12 davon) des Würfels?
Riccardo Bucco
@ RiccardoBucco Ja, 6 Gesichter. aber mein Problem ist, dass es eine höhere Dimension als 3 haben kann.
Luca

Antworten:

7

So geht's:

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

np.take macht das gleiche wie "ausgefallene" Indizierung.

Riccardo Bucco
quelle
1
@ Luca: Die Dokumentation macht es nicht klar, sondern numpy.takemacht eine Kopie. Dies kann dazu führen, dass die Leistung schlechter ist als bei Code, der auf einer Ansicht basiert. (Das Timing wäre notwendig, um sicherzugehen - die Effizienz der NumPy-Ansicht ist manchmal seltsam.)
user2357112 unterstützt Monica
1
@ RiccardoBucco: len(x.shape)kann einfacher geschrieben werden als x.ndim.
user2357112 unterstützt Monica
1
@ user2357112supportsMonica danke, ich habe es behoben :)
Riccardo Bucco
5
Auch die Verwendung eines Listenverständnisses verhindert einen allKurzschluss. Sie können die Klammern entfernen, um einen Generatorausdruck zu verwenden, der die allRückgabe ermöglicht , sobald ein einzelner numpy.allAufruf zurückgegeben wird False.
user2357112 unterstützt Monica
1
@ user2357112supportsMonica True !!
Riccardo Bucco
5

Hier ist eine Antwort, die tatsächlich die Teile des Arrays untersucht, an denen Sie interessiert sind, und keine Zeit damit verschwendet, eine Maske von der Größe des gesamten Arrays zu erstellen. Es gibt eine Python-Level-Schleife, die jedoch kurz ist und deren Iterationen proportional zur Anzahl der Dimensionen anstelle der Größe des Arrays sind.

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 unterstützt Monica
quelle
Gibt es Umstände, denen dies not (view[0] == 0).all()nicht entspricht view[0].any()?
Paul Panzer
@ PaulPanzer: Ich denke, view[0].any()würde auch funktionieren. Ich bin mir nicht ganz sicher, welche Auswirkungen das Casting und die Pufferung auf die Effizienz der beiden Optionen haben - view[0].any()könnte theoretisch schneller implementiert werden, aber ich habe schon seltsame Ergebnisse gesehen und verstehe die damit verbundene Pufferung nicht vollständig.
user2357112 unterstützt Monica
Ich nehme an, das view[0].view(bool).any()wäre die Hochgeschwindigkeitslösung.
Paul Panzer
@PaulPanzer: argmaxkönnte tatsächlich anydie boolesche Ansicht übertreffen . Dieses Zeug wird komisch.
user2357112 unterstützt Monica
(Unabhängig davon, ob argmaxoder ob die anyVerwendung einer booleschen Ansicht bedeutet, dass die negative Null als ungleich der regulären Null behandelt wird.)
user2357112 unterstützt Monica
2

Ich habe das Array umgeformt und es dann durchlaufen. Leider geht meine Antwort davon aus, dass Sie mindestens drei Dimensionen haben und bei normalen Matrizen Fehler auftreten. Sie müssten eine spezielle Klausel für 1 & 2-dimensional geformte Arrays hinzufügen. Darüber hinaus wird dies langsam sein, sodass es wahrscheinlich bessere Lösungen gibt.

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

Welches wird produzieren

>>> False
>>> True

Grundsätzlich staple ich alle Maße übereinander und schaue sie dann durch, um ihre Kanten zu überprüfen.

lwileczek
quelle
Dies untersucht die falschen Teile des Arrays. Für ein dreidimensionales Array möchten wir die Flächen des gesamten Arrays untersuchen, nicht die Kanten jedes zweidimensionalen Subarrays.
user2357112 unterstützt Monica
Ah, das macht mehr Sinn. Ich habe es falsch verstanden
lwileczek
1

Vielleicht ist der Ellipsenoperator genau das, wonach Sie suchen, was für viele Dimensionen funktioniert:

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
quelle
Dadurch werden nicht alle Gesichter gefärbt. Versuchen Sie es beispielsweise mit einem (4, 4, 4) Würfel.
Luca
Ich bin mir nicht sicher, was du mit Färben von Gesichtern meinst, aber es funktioniert, wenn du x (4, 4, 4)
machst
1

Sie können eine sliceboolesche Maskierung verwenden, um die Aufgabe zu erledigen:

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

Diese Funktion formt zuerst den "Kern" des Arrays in das Tupel sund erstellt dann eine Maske, die Truenur für die angrenzenden Punkte angezeigt wird. Die boolesche Indizierung liefert dann die Grenzpunkte.

Arbeitsbeispiel:

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

Dann erhalten np.all(borders==0)Sie die gewünschten Informationen.


Hinweis: Dies wird für eindimensionale Arrays unterbrochen, obwohl ich diese als Randfall betrachte. Sie sind wahrscheinlich besser dran, wenn Sie nur die beiden fraglichen Punkte dort überprüfen

Lukas Thaler
quelle
Dies dauert proportional zur Gesamtzahl der Elemente im Array und nicht nur zum Rand. Auch eindimensionale Arrays sind kein irrelevanter Randfall.
user2357112 unterstützt Monica
1
Auch np.arange(15)nicht enthalten 15.
user2357112 unterstützt Monica
Ich bin damit einverstanden, dass "irrelevant" eine starke Formulierung ist, obwohl ich der Meinung bin, dass Sie besser dran sind, wenn Sie nur die beiden Punkte für ein 1d-Array überprüfen. Die 15 ist ein Tippfehler, ein guter Fang
Lukas Thaler