Schneiden eines NumPy 2d-Arrays oder wie extrahiere ich eine mxm-Submatrix aus einem nxn-Array (n> m)?

174

Ich möchte ein NumPy nxn-Array in Scheiben schneiden. Ich möchte eine beliebige Auswahl von m Zeilen und Spalten dieses Arrays extrahieren (dh ohne Muster in der Anzahl der Zeilen / Spalten), wodurch es zu einem neuen mxm-Array wird. Nehmen wir für dieses Beispiel an, das Array ist 4x4 und ich möchte ein 2x2-Array daraus extrahieren.

Hier ist unser Array:

from numpy import *
x = range(16)
x = reshape(x,(4,4))

print x
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]]

Die zu entfernenden Zeilen und Spalten sind identisch. Der einfachste Fall ist, wenn ich eine 2x2-Submatrix extrahieren möchte, die am Anfang oder am Ende steht, dh:

In [33]: x[0:2,0:2]
Out[33]: 
array([[0, 1],
       [4, 5]])

In [34]: x[2:,2:]
Out[34]: 
array([[10, 11],
       [14, 15]])

Aber was ist, wenn ich eine andere Mischung aus Zeilen / Spalten entfernen muss? Was ist, wenn ich die erste und dritte Zeile / Zeile entfernen muss, um die Submatrix zu extrahieren [[5,7],[13,15]]? Es kann eine beliebige Zusammensetzung von Zeilen / Zeilen geben. Ich habe irgendwo gelesen, dass ich mein Array nur mit Arrays / Indexlisten für Zeilen und Spalten indizieren muss, aber das scheint nicht zu funktionieren:

In [35]: x[[1,3],[1,3]]
Out[35]: array([ 5, 15])

Ich habe einen Weg gefunden, nämlich:

    In [61]: x[[1,3]][:,[1,3]]
Out[61]: 
array([[ 5,  7],
       [13, 15]])

Das erste Problem dabei ist, dass es kaum lesbar ist, obwohl ich damit leben kann. Wenn jemand eine bessere Lösung hat, würde ich sie sicherlich gerne hören.

Eine andere Sache ist, dass ich in einem Forum gelesen habe , dass das Indizieren von Arrays mit Arrays NumPy zwingt, eine Kopie des gewünschten Arrays zu erstellen. Bei der Behandlung mit großen Arrays kann dies daher zu einem Problem werden. Warum ist das so / wie funktioniert dieser Mechanismus?

levesque
quelle

Antworten:

62

Wie Sven erwähnt hat, x[[[0],[2]],[1,3]]werden die Zeilen 0 und 2 zurückgegeben, die mit den Spalten 1 und 3 übereinstimmen, während x[[0,2],[1,3]]die Werte x [0,1] und x [2,3] in einem Array zurückgegeben werden.

Es gibt eine hilfreiche Funktion für das erste Beispiel, das ich gegeben habe numpy.ix_. Sie können das Gleiche tun wie in meinem ersten Beispiel x[numpy.ix_([0,2],[1,3])]. Dies erspart Ihnen die Eingabe all dieser zusätzlichen Klammern.

Justin Peel
quelle
111

Um diese Frage zu beantworten, müssen wir uns ansehen, wie die Indizierung eines mehrdimensionalen Arrays in Numpy funktioniert. Angenommen, Sie haben das Array xaus Ihrer Frage. Der zugewiesene Puffer xenthält 16 aufsteigende Ganzzahlen von 0 bis 15. Wenn Sie beispielsweise auf ein Element zugreifen, x[i,j]muss NumPy den Speicherort dieses Elements relativ zum Pufferanfang ermitteln. Dies erfolgt durch effektive Berechnungi*x.shape[1]+j (und Multiplikation mit der Größe eines Int, um einen tatsächlichen Speicheroffset zu erhalten).

Wenn Sie ein Subarray durch einfaches Schneiden wie extrahieren, teilt y = x[0:2,0:2]das resultierende Objekt den zugrunde liegenden Puffer mit x. Aber was passiert, wenn Sie beitreten y[i,j]? NumPy kann nicht verwendet werdeni*y.shape[1]+j den Offset im Array berechnen, da die dazugehörigen Daten yim Speicher nicht aufeinanderfolgend sind.

NumPy löst dieses Problem durch Einführung von Schritten . Bei der Berechnung des Speicherversatzes für den Zugriff x[i,j]wird tatsächlich Folgendes berechnet i*x.strides[0]+j*x.strides[1](und dies schließt bereits den Faktor für die Größe eines Int ein):

x.strides
(16, 4)

Wenn ywie oben extrahiert wird, wird NumPy keinen neuen Puffer schaffen, aber es tut Objekt ein neues Array erstellen den gleichen Puffer Referenzierung (sonst ywürde nur gleich sein x.) Das neue Array - Objekt wird eine andere Form haben dann xund vielleicht ein anderer Ausgangs Versatz in den Puffer, teilt aber die Schritte mit x(zumindest in diesem Fall):

y.shape
(2,2)
y.strides
(16, 4)

Auf diese Weise wird der Speicheroffset für berechnet y[i,j] liefert die das richtige Ergebnis.

Aber was soll NumPy für so etwas tun z=x[[1,3]]? Der Schrittmechanismus erlaubt keine korrekte Indizierung, wenn der ursprüngliche Puffer für verwendet wird z. NumPy könnte theoretisch einen ausgefeilteren Mechanismus als die Schritte hinzufügen, aber dies würde den Elementzugriff relativ teuer machen und irgendwie der ganzen Idee eines Arrays widersprechen. Außerdem wäre eine Ansicht kein wirklich leichtes Objekt mehr.

Dies wird in der NumPy-Dokumentation zur Indizierung ausführlich behandelt .

Oh, und fast Ihre eigentliche Frage vergessen: So funktioniert die Indizierung mit mehreren Listen wie erwartet:

x[[[1],[3]],[1,3]]

Dies liegt daran, dass die Index-Arrays in eine gemeinsame Form gesendet werden . In diesem Beispiel können Sie natürlich auch mit dem einfachen Schneiden auskommen:

x[1::2, 1::2]
Sven Marnach
quelle
Es sollte möglich sein, Arrays in Unterklassen zu unterteilen, so dass man ein "slcie-view" -Objekt haben könnte, das Indizes dem ursprünglichen Array neu zuordnen würde. Das könnte möglicherweise die Bedürfnisse des OP erfüllen
jsbueno
@jsbueno: Das funktioniert für Python-Code, aber nicht für C / Fortran-Routinen, die Scipy / Numpy umschließt. In diesen verpackten Routinen liegt die Kraft von Numpy.
Dat Chu
Also ... was ist der Unterschied zwischen x [[[1], [3]], [1,3]] und x [[1,3] ,:] [:, [1,3]]? Ich meine, gibt es eine Variante, die besser zu bedienen ist als die andere?
Levesque
1
@JC: Erstellt x[[[1],[3]],[1,3]]nur ein neues Array, während es x[[1,3],:][:,[1,3]]zweimal kopiert wird. Verwenden Sie also das erste.
Sven Marnach
@JC: Oder verwenden Sie die Methode aus Justins Antwort.
Sven Marnach
13

Ich denke nicht, dass x[[1,3]][:,[1,3]]das kaum lesbar ist. Wenn Sie Ihre Absichten klarer formulieren möchten, können Sie Folgendes tun:

a[[1,3],:][:,[1,3]]

Ich bin kein Experte für das Schneiden, aber wenn Sie versuchen, in ein Array zu schneiden und die Werte kontinuierlich sind, erhalten Sie normalerweise eine Ansicht zurück, in der der Schrittwert geändert wird.

Beispiel: In Ihren Eingaben 33 und 34 beträgt der Schritt 4, obwohl Sie ein 2x2-Array erhalten. Wenn Sie also die nächste Zeile indizieren, bewegt sich der Zeiger an die richtige Position im Speicher.

Es ist klar, dass dieser Mechanismus bei einer Reihe von Indizes nicht gut funktioniert. Daher muss numpy die Kopie erstellen. Schließlich hängen viele andere mathematische Matrixfunktionen von Größe, Schritt und kontinuierlicher Speicherzuweisung ab.

Dat Chu
quelle
10

Wenn Sie jede zweite Zeile und jede zweite Spalte überspringen möchten, können Sie dies mit einem einfachen Slicing tun:

In [49]: x=np.arange(16).reshape((4,4))
In [50]: x[1:4:2,1:4:2]
Out[50]: 
array([[ 5,  7],
       [13, 15]])

Dies gibt eine Ansicht zurück, keine Kopie Ihres Arrays.

In [51]: y=x[1:4:2,1:4:2]

In [52]: y[0,0]=100

In [53]: x   # <---- Notice x[1,1] has changed
Out[53]: 
array([[  0,   1,   2,   3],
       [  4, 100,   6,   7],
       [  8,   9,  10,  11],
       [ 12,  13,  14,  15]])

while z=x[(1,3),:][:,(1,3)]verwendet die erweiterte Indizierung und gibt somit eine Kopie zurück:

In [58]: x=np.arange(16).reshape((4,4))
In [59]: z=x[(1,3),:][:,(1,3)]

In [60]: z
Out[60]: 
array([[ 5,  7],
       [13, 15]])

In [61]: z[0,0]=0

Beachten Sie, dass dies xunverändert bleibt:

In [62]: x
Out[62]: 
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11],
       [12, 13, 14, 15]])

Wenn Sie beliebige Zeilen und Spalten auswählen möchten, können Sie kein einfaches Slicing verwenden. Sie müssen die erweiterte Indizierung verwenden und dabei Sequenzen wie x[rows,:][:,columns], wo rowsund columnssind verwenden. Dadurch erhalten Sie natürlich eine Kopie und keine Ansicht Ihres ursprünglichen Arrays. Dies ist zu erwarten, da ein Numpy-Array zusammenhängenden Speicher (mit konstanten Schritten) verwendet und es keine Möglichkeit gibt, eine Ansicht mit beliebigen Zeilen und Spalten zu generieren (da dies nicht konstante Schritte erfordern würde).

unutbu
quelle
5

Mit numpy können Sie für jede Komponente des Index ein Slice übergeben - also Ihr x[0:2,0:2] obige Beispiel funktioniert also.

Wenn Sie nur Spalten oder Zeilen gleichmäßig überspringen möchten, können Sie Slices mit drei Komponenten übergeben (z. B. Start, Stopp, Schritt).

Nochmals für Ihr Beispiel oben:

>>> x[1:4:2, 1:4:2]
array([[ 5,  7],
       [13, 15]])

Das heißt im Grunde: Slice in der ersten Dimension mit Start bei Index 1, Stop, wenn der Index gleich oder größer als 4 ist, und addiere 2 zum Index in jedem Durchgang. Gleiches gilt für die zweite Dimension. Nochmals: Dies funktioniert nur für konstante Schritte.

Die Syntax, mit der Sie intern etwas ganz anderes tun müssen - x[[1,3]][:,[1,3]]tatsächlich wird ein neues Array erstellt, das nur die Zeilen 1 und 3 des ursprünglichen Arrays enthält (mit dem x[[1,3]]Teil fertig ), und das dann neu geschnitten - ein drittes Array erstellt - nur eingeschlossen Spalten 1 und 3 des vorherigen Arrays.

jsbueno
quelle
1
Diese Lösung funktioniert nicht, da sie spezifisch für die Zeilen / Spalten ist, die ich extrahieren wollte. Stellen Sie sich dasselbe in einer 50x50-Matrix vor, wenn ich Zeilen / Spalten 5,11,12,32,39,45 extrahieren möchte, gibt es keine Möglichkeit, dies mit einfachen Slices zu tun. Entschuldigung, wenn ich in meiner Frage nicht klar war.
Levesque
3

Ich habe hier eine ähnliche Frage: Schreiben in Sub-Ndarray eines Ndarray auf pythonischste Weise. Python 2 .

Nach der Lösung des vorherigen Beitrags für Ihren Fall sieht die Lösung folgendermaßen aus:

columns_to_keep = [1,3] 
rows_to_keep = [1,3]

Ein using ix_:

x[np.ix_(rows_to_keep, columns_to_keep)] 

Welches ist:

array([[ 5,  7],
       [13, 15]])
Rafael Valero
quelle
0

Ich bin nicht sicher, wie effizient dies ist, aber Sie können range () verwenden, um in beide Achsen zu schneiden

 x=np.arange(16).reshape((4,4))
 x[range(1,3), :][:,range(1,3)] 
Valery Marcel
quelle