Boolsche Maskierung und Indizierung

Boolean Maskes, as Venetian Mask

In diesem Kapitel geht es um die Boolsche Maskierung (englisch: Boolean Masking) und Boolschen Masken. Wir zeigen, wie man damit die Werte von NumPy-Arrays verändern kann.
Maskierung ist hilfreich, um Daten mit bestimmten Eigenschaften zu extrahieren, zu verändern, zu zählen und so weiter. Amn kann damit auch sehr leicht einfache Binarisierungen vornommen, also alle Werte die über einer bestimmten Schwelle liegen auf einen Wert setzen und alle darunter liegenden Werte auf einen anderen. Die Benutzung von Maskierungen gestaltet sich meistens nicht nur sehr einfach sondern dabei handelt es sich meistens auch um die effizienteste Art diese Operationen durchzuführen.

Im ersten Beispiel werden alle Komponenten des Arrays A mit der Zahl verglichen. Das Ergebnis der Maskierung besteht in einem neuen Array mit der gleichen Shape, in dem ein True steht, falls an der entsprechenden Position in A eine 4 stand, ansonsten wird der Wert auf False gesetzt.

import numpy as np
A = np.array([4, 7, 3, 4, 2, 8])
print(A == 4)
[ True False False  True False False]

Analog kann man die einzelnen Komenenten auf mittels der Vergleichsoperatoren "<", "<=", ">" und ">=". Die Arbeitsweise ist analog zu dem vorigen Fall:

print(A < 5)
[ True False  True  True  True False]

Dies lässte sich auch auf Arrays mit höherer Dimension anwenden:

B = np.array([[42, 56, 89, 65],
              [99, 88, 42, 12],
              [55, 42, 17, 18]])
print(B >= 42)
[[ True  True  True  True]
 [ True  True  True False]
 [ True  True False False]]

Damit lassen sich auch Arrays binarisieren. Betrachten wir das folgende Array A als ein Grauwertbild, so können wir dieses mit der Schwelle 15 binarisieren:

import numpy as np
A = np.array([
[12, 13, 14, 12, 16, 14, 11, 10,  9],
[11, 14, 12, 15, 15, 16, 10, 12, 11],
[10, 12, 12, 15, 14, 16, 10, 12, 12],
[ 9, 11, 16, 15, 14, 16, 15, 12, 10],
[12, 11, 16, 14, 10, 12, 16, 12, 13],
[10, 15, 16, 14, 14, 14, 16, 15, 12],
[13, 17, 14, 10, 14, 11, 14, 15, 10],
[10, 16, 12, 14, 11, 12, 14, 18, 11],
[10, 19, 12, 14, 11, 12, 14, 18, 10],
[14, 22, 17, 19, 16, 17, 18, 17, 13],
[10, 16, 12, 14, 11, 12, 14, 18, 11],
[10, 16, 12, 14, 11, 12, 14, 18, 11],
[10, 19, 12, 14, 11, 12, 14, 18, 10],
[14, 22, 12, 14, 11, 12, 14, 17, 13],
[10, 16, 12, 14, 11, 12, 14, 18, 11]])
B = A < 15
B.astype(np.int)
Wir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
array([[1, 1, 1, 1, 0, 1, 1, 1, 1],
       [1, 1, 1, 0, 0, 0, 1, 1, 1],
       [1, 1, 1, 0, 1, 0, 1, 1, 1],
       [1, 1, 0, 0, 1, 0, 0, 1, 1],
       [1, 1, 0, 1, 1, 1, 0, 1, 1],
       [1, 0, 0, 1, 1, 1, 0, 0, 1],
       [1, 0, 1, 1, 1, 1, 1, 0, 1],
       [1, 0, 1, 1, 1, 1, 1, 0, 1],
       [1, 0, 1, 1, 1, 1, 1, 0, 1],
       [1, 0, 0, 0, 0, 0, 0, 0, 1],
       [1, 0, 1, 1, 1, 1, 1, 0, 1],
       [1, 0, 1, 1, 1, 1, 1, 0, 1],
       [1, 0, 1, 1, 1, 1, 1, 0, 1],
       [1, 0, 1, 1, 1, 1, 1, 0, 1],
       [1, 0, 1, 1, 1, 1, 1, 0, 1]])

Alle Werte des Originalarrays A wurden durch 0 bzw. 1 ersetzt. Im Bild kann man übrigens auch ein großes A erkennen.


Fancy Indizierung

Das Prinzip der "Fancy Indizierung" ist recht einfach: Statt eines einzelnen Indexes benutzt man ein Array mit Indizes. Dadurch kann man mehrere Elemente auf einen Schlag ansprechen.

A = np.array([34, 8, 99, 12, 1, 102, 44])
# umständlich:
B = np.array([A[1], A[3], A[5]])
print(B)
# mit fancy Indizierung:
B2 = A[[1, 3, 5]]
print(B2)
[  8  12 102]
[  8  12 102]

In unserem nächsten Beispiel benutzen wir die boolsche Maske eines Arrays, um die entsprechenden Elemente eines anderen Arrays auszuwählen, d.h. indizieren das Array C mit einer Boolschen Maske, die wir mittels Maskierung des Arrays A erzeugen. Das Ergebnis ist dann eine Kopie und keine Sicht (View).

Das neue Array R beinhaltet all die Elemente aus C deren Indizes in A <= 5 True sind.

C = np.array([123, 188, 190, 99, 77, 88, 100])
A = np.array([4, 7, 2,8, 6, 9, 5])
print(A <= 5)
R = C[A <= 5]
print(R)
[ True False  True False False False  True]
[123 190 100]


Indizierung mit einem Integer-Array

Im folgenden Beispiel indizieren wir mit Hilfe einer Integer-Liste:

C[[0, 2, 3, 1, 4, 1]]
Wir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
array([123, 190,  99, 188,  77, 188])

Wir wir sehen, können Indizes mehrfach und in beliebiger Reihenfolge auftreten!


Übung

Extrahieren Sie aus dem Array np.array([3, 4, 6, 10, 24, 89, 45, 43, 46, 99, 100]), anhand von boolscher Indizierung, die Werte, die:


Lösung

import numpy as np
A = np.array([3, 4, 6, 10, 24, 89, 45, 43, 46, 99, 100])
div3 = A[A % 3 != 0]
print("Elemente von A, die nicht durch 3 teilbar sind:")
print(div3)
div5 = A[A % 5 == 0]
print("Elemente von A, die durch 5 teilbar sind:")
print(div5)
print("Elemente von A, die durch 3 und 5 teilbar sind:")
print(A[(A % 3 == 0) & (A % 5 == 0)])
A[ A % 3 == 0] = 42
print("Alle durch 3 teilbaren Werte von A wurden auf 42 gesetzt:")
print(A)
Elemente von A, die nicht durch 3 teilbar sind:
[  4  10  89  43  46 100]
Elemente von A, die durch 5 teilbar sind:
[ 10  45 100]
Elemente von A, die durch 3 und 5 teilbar sind:
[45]
Alle durch 3 teilbaren Werte von A wurden auf 42 gesetzt:
[ 42   4  42  10  42  89  42  43  46  42 100]


nonzero und where


Die Methode nonzero liefert die Indizes der Elemente aus einem Array zurück, die nicht 0 (non-zero) sind. Die Indizes werden als Tupel von eindimensionalen Arrays zurückgeliefert, eins für jede Dimension. Die entsprechenden non-zero Werte eines Arrays A kann man dann durch Boolsche Indizieren erhalten:

A[numpy.nonzero(A)]

import numpy as np
A = np.array([[0, 2, 3, 0, 1],
              [1, 0, 0, 7, 0],
              [5, 0, 0, 1, 0]])
print(A.nonzero())
print(A[A.nonzero()])
(array([0, 0, 0, 1, 1, 2, 2]), array([1, 2, 4, 0, 3, 0, 3]))
[2 3 1 1 7 5 1]

Möchte man die Elemente als Pärchen von Zeilen und Spalten haben, so kann man transpose benutzen:

transpose(nonzero(A))```
Es wird ein zweidimensionales Array erzeugt. Jede Zeile entspricht den Indizes eines non-zero-Element in der Form ```[Zeile, Spalte]
np.transpose(A.nonzero()) 
Wir erhalten die folgende Ausgabe:
array([[0, 1],
       [0, 2],
       [0, 4],
       [1, 0],
       [1, 3],
       [2, 0],
       [2, 3]])

Die Funktion nonzero kann dazu verwendet werde um die Indizes aus einem Array zu holen, bei denen die Bedingung True ist. Im folgenden Skript erstellen wir das boolsche Array B >= 42:

B = np.array([[42, 56, 89, 65],
              [99, 88, 42, 12],
              [55, 42, 17, 18]])
print(B >= 42)
[[ True  True  True  True]
 [ True  True  True False]
 [ True  True False False]]

np.nonzero(B >= 42) produziert die Indizes aus B, auf die die Bedingung zutrifft.

B = np.array([[42, 56, 89, 65],
              [99, 88, 42, 12],
              [55, 42, 17, 18]])
np.nonzero(B >= 42)
Wir können die folgende Ausgabe erwarten, wenn wir den obigen Python-Code ausführen:
(array([0, 0, 0, 0, 1, 1, 1, 2, 2]), array([0, 1, 2, 3, 0, 1, 2, 0, 1]))



Übung

Berechnen Sie die Primzahlen zwischen 0 und 100 mit Hilfe eines boolschen Arrays.



Lösung

import numpy as np
is_prime = np.ones((100,), dtype=bool)
# Cross out 0 and 1 which are not primes:
is_prime[:2] = 0
# cross out its higher multiples (sieve of Eratosthenes):
nmax = int(np.sqrt(len(is_prime)))
for i in range(2, nmax):
    is_prime[2*i::i] = False
print(np.nonzero(is_prime))
(array([ 2,  3,  5,  7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
       61, 67, 71, 73, 79, 83, 89, 97]),)



Flatnonzero und count_nonzero

ähnliche Funktionen: