NumPy Auswahl eines bestimmten Spaltenindex pro Zeile mithilfe einer Liste von Indizes

89

Ich habe Probleme, die spezifischen Spalten pro Zeile einer NumPyMatrix auszuwählen .

Angenommen, ich habe die folgende Matrix, die ich nennen würde X:

[1, 2, 3]
[4, 5, 6]
[7, 8, 9]

Ich habe auch einen listSpaltenindex pro Zeile, den ich aufrufen würde Y:

[1, 0, 2]

Ich muss die Werte erhalten:

[2]
[4]
[9]

Anstelle von a listmit Indizes Ykann ich auch eine Matrix mit der gleichen Form erstellen, Xin der jede Spalte ein bool/ intim Bereich von 0 bis 1 ist, was angibt, ob dies die erforderliche Spalte ist.

[0, 1, 0]
[1, 0, 0]
[0, 0, 1]

Ich weiß, dass dies durch Iterieren über das Array und Auswählen der benötigten Spaltenwerte erreicht werden kann. Dies wird jedoch häufig auf großen Datenfeldern ausgeführt und muss daher so schnell wie möglich ausgeführt werden.

Ich habe mich also gefragt, ob es eine bessere Lösung gibt.

Vielen Dank.

Zee
quelle
Ist die Antwort besser für dich? stackoverflow.com/a/17081678/5046896
GoingMyWay

Antworten:

102

Wenn Sie ein boolesches Array haben, können Sie auf dieser Grundlage eine direkte Auswahl treffen:

>>> a = np.array([True, True, True, False, False])
>>> b = np.array([1,2,3,4,5])
>>> b[a]
array([1, 2, 3])

Um Ihrem ersten Beispiel zu folgen, können Sie Folgendes tun:

>>> a = np.array([[1,2,3], [4,5,6], [7,8,9]])
>>> b = np.array([[False,True,False],[True,False,False],[False,False,True]])
>>> a[b]
array([2, 4, 9])

Sie können auch eine hinzufügen arangeund eine direkte Auswahl treffen, je nachdem, wie Sie Ihr boolesches Array generieren und wie Ihr Code wie YMMV aussieht.

>>> a = np.array([[1,2,3], [4,5,6], [7,8,9]])
>>> a[np.arange(len(a)), [1,0,2]]
array([2, 4, 9])

Hoffe das hilft, lass es mich wissen, wenn du weitere Fragen hast.

Slater Victoroff
quelle
11
+1 für das Beispiel mit arange. Dies war besonders nützlich für mich, um verschiedene Blöcke aus mehreren Matrizen abzurufen (also im Grunde der 3D-Fall dieses Beispiels)
Griddo
1
Hallo, kannst du erklären, warum wir arangestatt verwenden müssen :? Ich weiß, dass dein Weg funktioniert und meiner nicht, aber ich würde gerne verstehen, warum.
Marcotama
@tamzord, da es sich um ein Numpy-Array und nicht um eine Vanille-Python-Liste handelt, sodass die :Syntax nicht auf die gleiche Weise funktioniert.
Slater Victoroff
1
@ SlaterTyranus, danke für die Antwort. Nach einigem Lesen :bedeutet mein Verständnis, dass das Mischen mit der erweiterten Indizierung bedeutet: "Wenden Sie für jeden Unterraum :die angegebene erweiterte Indizierung an". Ist mein Verständnis richtig?
Marcotama
@ Tamzord erklären, was Sie mit "Sub-Space" meinen
Slater Victoroff
35

Sie können so etwas tun:

In [7]: a = np.array([[1, 2, 3],
   ...: [4, 5, 6],
   ...: [7, 8, 9]])

In [8]: lst = [1, 0, 2]

In [9]: a[np.arange(len(a)), lst]
Out[9]: array([2, 4, 9])

Weitere Informationen zum Indizieren mehrdimensionaler Arrays: http://docs.scipy.org/doc/numpy/user/basics.indexing.html#indexing-multi-dimensional-arrays

Ashwini Chaudhary
quelle
1
Schwierigkeiten zu verstehen, warum der Bereich benötigt wird, anstatt einfach ':' oder Reichweite.
MadmanLee
@MadmanLee Hallo, bei Verwendung :werden len(a)stattdessen mehrere Ergebnisse ausgegeben , wobei angegeben wird , dass der Index jeder Zeile die erwarteten Ergebnisse druckt.
GoingMyWay
1
Ich denke, dies ist genau der richtige und elegante Weg, um dieses Problem zu lösen.
GoingMyWay
6

Ein einfacher Weg könnte aussehen wie:

In [1]: a = np.array([[1, 2, 3],
   ...: [4, 5, 6],
   ...: [7, 8, 9]])

In [2]: y = [1, 0, 2]  #list of indices we want to select from matrix 'a'

range(a.shape[0]) wird zurückkehren array([0, 1, 2])

In [3]: a[range(a.shape[0]), y] #we're selecting y indices from every row
Out[3]: array([2, 4, 9])
Dhaval Mayatra
quelle
1
Bitte erwägen Sie, Erklärungen hinzuzufügen.
Souki
@ Souki Ich habe jetzt eine Erklärung hinzugefügt. Vielen Dank
Dhaval Mayatra
6

Neuere numpyVersionen haben ein take_along_axis(und put_along_axis) hinzugefügt , das diese Indizierung sauber durchführt.

In [101]: a = np.arange(1,10).reshape(3,3)                                                             
In [102]: b = np.array([1,0,2])                                                                        
In [103]: np.take_along_axis(a, b[:,None], axis=1)                                                     
Out[103]: 
array([[2],
       [4],
       [9]])

Es funktioniert wie folgt:

In [104]: a[np.arange(3), b]                                                                           
Out[104]: array([2, 4, 9])

aber mit unterschiedlicher Achshandhabung. Es zielt insbesondere darauf ab, die Ergebnisse von argsortund anzuwenden argmax.

hpaulj
quelle
3

Sie können dies mit dem Iterator tun. So was:

np.fromiter((row[index] for row, index in zip(X, Y)), dtype=int)

Zeit:

N = 1000
X = np.zeros(shape=(N, N))
Y = np.arange(N)

#@Aशwini चhaudhary
%timeit X[np.arange(len(X)), Y]
10000 loops, best of 3: 30.7 us per loop

#mine
%timeit np.fromiter((row[index] for row, index in zip(X, Y)), dtype=int)
1000 loops, best of 3: 1.15 ms per loop

#mine
%timeit np.diag(X.T[Y])
10 loops, best of 3: 20.8 ms per loop
Kei Minagawa
quelle
1
OP erwähnte, dass es auf großen Arrays schnell laufen sollte , daher sind Ihre Benchmarks nicht sehr repräsentativ. Ich bin gespannt, wie sich Ihre letzte Methode für (viel) größere Arrays verhält!
@moarningsun: Aktualisiert. np.diag(X.T[Y])ist so langsam ... aber np.diag(X.T)ist so schnell (10us). Ich weiß nicht warum.
Kei Minagawa
0

Eine andere clevere Möglichkeit besteht darin, das Array zuerst zu transponieren und anschließend zu indizieren. Nehmen Sie zum Schluss die Diagonale, es ist immer die richtige Antwort.

X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])
Y = np.array([1, 0, 2, 2])

np.diag(X.T[Y])

Schritt für Schritt:

Original-Arrays:

>>> X
array([[ 1,  2,  3],
       [ 4,  5,  6],
       [ 7,  8,  9],
       [10, 11, 12]])

>>> Y
array([1, 0, 2, 2])

Transponieren, um es richtig zu indizieren.

>>> X.T
array([[ 1,  4,  7, 10],
       [ 2,  5,  8, 11],
       [ 3,  6,  9, 12]])

Holen Sie sich Zeilen in der Y-Reihenfolge.

>>> X.T[Y]
array([[ 2,  5,  8, 11],
       [ 1,  4,  7, 10],
       [ 3,  6,  9, 12],
       [ 3,  6,  9, 12]])

Die Diagonale sollte jetzt klar werden.

>>> np.diag(X.T[Y])
array([ 2,  4,  9, 12]
Thomas Devoogdt
quelle
1
Das funktioniert technisch und sieht sehr elegant aus. Ich finde jedoch, dass dieser Ansatz vollständig explodiert, wenn Sie mit großen Arrays arbeiten. In meinem Fall hat NumPy 30 GB Swap verschluckt und meine SSD gefüllt. Ich empfehle stattdessen den erweiterten Indizierungsansatz.
5. schändlicher