wake-up-neo.net

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

Ich möchte ein NumPy-nxn-Array in Scheiben schneiden. Ich möchte eine beliebige Auswahl von m Zeilen und Spalten dieses Arrays extrahieren (d. H. Ohne ein Muster in der Anzahl der Zeilen/Spalten), so dass es ein neues mxm-Array ist. Nehmen wir für dieses Beispiel an, dass das Array 4x4 ist und ich ein 2x2-Array daraus extrahieren möchte.

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 gleich. Der einfachste Fall ist, wenn ich eine 2x2-Submatrix extrahieren möchte, die sich am Anfang oder am Ende befindet, d. H.

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

Was aber, wenn ich eine andere Mischung von Zeilen/Spalten entfernen muss? Was ist, wenn ich die erste und dritte Zeile/Zeile entfernen muss, um die Submatrix [[5,7],[13,15]] zu extrahieren? Es kann eine beliebige Zusammenstellung von Zeilen/Zeilen geben. Ich habe irgendwo gelesen, dass ich mein Array nur mithilfe von 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 es sicherlich gerne hören.

Eine andere Sache ist, ich lese in einem Forum dass das Indizieren von Arrays mit Arrays NumPy dazu zwingt, eine Kopie des gewünschten Arrays zu erstellen. Wenn Sie also mit großen Arrays arbeiten, könnte dies zu einem Problem werden. Warum ist das so/wie funktioniert dieser Mechanismus?

155
levesque

Wie Sven erwähnt, gibt x[[[0],[2]],[1,3]] die 0 und 2 Zeilen zurück, 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ückgibt. 

Es gibt eine hilfreiche Funktion für das erste Beispiel, numpy.ix_. Mit x[numpy.ix_([0,2],[1,3])] können Sie dasselbe wie mein erstes Beispiel machen. Dies kann Sie davon abhalten, diese zusätzlichen Klammern einzugeben. 

54
Justin Peel

Um diese Frage zu beantworten, müssen wir uns ansehen, wie die Indexierung eines mehrdimensionalen Arrays in Numpy funktioniert. Lassen Sie uns zunächst sagen, Sie haben das Array x aus Ihrer Frage. Der Puffer, der x zugewiesen ist, enthält 16 aufsteigende Ganzzahlen von 0 bis 15. Wenn Sie auf ein Element zugreifen, beispielsweise x[i,j], muss NumPy den Speicherplatz dieses Elements relativ zum Anfang des Puffers ermitteln. Dies geschieht durch Berechnung von i*x.shape[1]+j (und Multiplizieren mit der Größe eines int, um einen tatsächlichen Speicheroffset zu erhalten).

Wenn Sie ein Subarray durch einfaches Slicing wie y = x[0:2,0:2] extrahieren, wird das zugrunde liegende Objekt den zugrunde liegenden Puffer mit x gemeinsam nutzen. Was passiert aber, wenn Sie auf y[i,j] zugreifen? NumPy kann i*y.shape[1]+j nicht zur Berechnung des Offsets in das Array verwenden, da die zu y gehörenden Daten im Speicher nicht fortlaufend sind.

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

x.strides
(16, 4)

Wenn y wie oben extrahiert wird, erstellt NumPy keinen neuen Puffer, aber do erstellt ein neues Array-Objekt, das auf denselben Puffer verweist (andernfalls wäre y nur gleich x.) Das neue Array-Objekt wird vorhanden sein eine andere Form als x und möglicherweise ein anderer Startversatz in den Puffer, teilt jedoch die Schritte mit x (in diesem Fall mindestens):

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

Auf diese Weise führt die Berechnung des Speicheroffsets für y[i,j] zum korrekten Ergebnis.

Aber was soll NumPy für so etwas wie z=x[[1,3]] tun? Der Strides-Mechanismus erlaubt keine korrekte Indizierung, wenn der Originalpuffer für z verwendet wird. NumPy fügt theoretisch könnte einige komplexere Mechanismen hinzu als die Schritte, aber dies würde den Elementzugriff relativ teuer machen und die ganze Idee eines Arrays irgendwie in Frage stellen. Darüber hinaus wäre eine Ansicht nicht mehr ein wirklich leichtes Objekt.

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

Oh, und fast vergessen Sie Ihre eigentliche Frage: So können Sie die Indexierung mit mehreren Listen wie erwartet funktionieren lassen:

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

Dies liegt daran, dass die Index-Arrays Broadcasting in einer gemeinsamen Form sind . In diesem speziellen Beispiel können Sie natürlich auch mit der grundlegenden Aufteilung zurechtkommen:

x[1::2, 1::2]
107
Sven Marnach

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

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

Ich bin kein Experte beim Schneiden, aber wenn Sie versuchen, in ein Array zu schneiden, und die Werte kontinuierlich sind, erhalten Sie eine Ansicht, in der der Schrittwert geändert wird.

z.B. Obwohl Sie ein 2x2-Array erhalten, ist der Schritt in den Eingaben 33 und 34 4. Wenn Sie die nächste Zeile indizieren, bewegt sich der Zeiger an die richtige Position im Speicher.

Offensichtlich ist dieser Mechanismus bei einem Array von Indizes nicht gut. Daher muss numpy die Kopie erstellen. Viele andere mathematische Matrixfunktionen sind auf Größe, Schritt und kontinuierliche Speicherzuordnung angewiesen.

11
Dat Chu

Wenn Sie jede zweite Zeile und jede zweite Spalte überspringen möchten, können Sie dies mit der grundlegenden Aufteilung 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 und keine Kopie Ihres Arrays zurück.

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 daher 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 x unverä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 willkürliche Zeilen und Spalten auswählen möchten, können Sie kein einfaches Slicing verwenden. Sie müssen die erweiterte Indizierung verwenden, beispielsweise x[rows,:][:,columns], wobei rows und columns Sequenzen sind. Dies gibt Ihnen natürlich eine Kopie Ihres ursprünglichen Arrays und keine Ansicht. 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).

10
unutbu

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

Wenn Sie nur Spalten oder Zeilen gleichmäßig überspringen möchten, können Sie Slices mit drei Komponenten übergeben. __ (d. H. Start, stop, step).

Nochmals für Ihr Beispiel oben:

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

Grundsätzlich gilt: Slice in der ersten Dimension mit Start bei Index 1, stoppen, wenn der Index gleich oder größer als 4 ist, und dem Index in jedem Durchgang 2 hinzufügen. Gleiches gilt für die zweite Dimension. Nochmals: Dies funktioniert nur für konstante Schritte.

Die Syntax, die Sie machen müssen, um etwas ganz anderes zu tun - was x[[1,3]][:,[1,3]] tatsächlich tut, ist das Erstellen eines neuen Arrays, das nur die Zeilen 1 und 3 des ursprünglichen Arrays enthält (getan mit dem x[[1,3]]-Teil). nur die Spalten 1 und 3 des vorherigen Arrays.

5
jsbueno

Ich habe hier eine ähnliche Frage: Schreiben in Sub-ndarray eines ndarrays 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]

Eine Verwendung von ix_:

x[np.ix_(rows_to_keep, columns_to_keep)] 

Welches ist:

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

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)] 
0
Valery Marcel