Boolesche Maskierung und Indizierung

Boolean Maskes, as Venetian Mask

In diesem Kapitel geht es um die Boolesche Maskierung (englisch: Boolean Masking) und Booleschen 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. Außerdem können damit auch sehr leicht einfache Binarisierungen vorgenommen werden, 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 Komponenten auch mittels der Vergleichsoperatoren "<", "<=", ">" und ">=" bearbeiten. Die Arbeitsweise ist analog zu dem vorigen Fall:

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

Dies lässt 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)
Der obige Code führt zu folgendem Ergebnis:
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 boolesche Maske eines Arrays, um die entsprechenden Elemente eines anderen Arrays auszuwählen, d.h. wir indizieren das Array C mit einer Booleschen 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, bei denen im Array A der Test A <= 5 True liefert.

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

Indizieren lässt sich auch beispielsweise mit einem Integer-Array oder mit einer Integer-Liste. Letzteres tun wir im nächsten Beispiel:

lst = [0, 2, 3, 1, 4, 1] 
C[lst]
Wir erhalten die folgende Ergebnisse:
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 boolescher 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 Boolesches 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-Elements in der Form ```[Zeile, Spalte]
np.transpose(A.nonzero()) 
Führt man obigen Code aus, erhält man folgende Ausgabe:
array([[0, 1],
       [0, 2],
       [0, 4],
       [1, 0],
       [1, 3],
       [2, 0],
       [2, 3]])

Die Funktion nonzero kann dazu verwendet werden, um die Indizes aus einem Array zu holen, bei denen die Bedingung True ist. Im folgenden Skript erstellen wir das boolesche 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)
Führt man obigen Code aus, erhält man folgendes Ergebnis:
(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 booleschen 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: