NumPy wybierając określony indeks kolumny na wiersz przy użyciu listy indeksów

90

Usiłuję wybrać określone kolumny na wiersz NumPymacierzy.

Załóżmy, że mam następującą macierz, którą nazwałbym X:

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

Mam też listindeksy kolumn w każdym wierszu, które nazwałbym Y:

[1, 0, 2]

Muszę uzyskać wartości:

[2]
[4]
[9]

Zamiast a listz indeksami Ymogę również utworzyć macierz o tym samym kształcie, w Xktórym każda kolumna ma wartość bool/ intw zakresie 0-1, wskazując, czy jest to wymagana kolumna.

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

Wiem, że można to zrobić, wykonując iterację po tablicy i wybierając potrzebne wartości kolumn. Jednak będzie to wykonywane często na dużych tablicach danych i dlatego musi działać tak szybko, jak to tylko możliwe.

Zastanawiałem się więc, czy jest lepsze rozwiązanie?

Dziękuję Ci.

Zee
źródło
Czy odpowiedź jest lepsza dla Ciebie? stackoverflow.com/a/17081678/5046896
GoingMyWay

Odpowiedzi:

102

Jeśli masz tablicę logiczną, możesz dokonać bezpośredniego wyboru na podstawie tego:

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

Aby postępować zgodnie z początkowym przykładem, możesz wykonać następujące czynności:

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

Możesz również dodać arangei dokonać bezpośredniego wyboru, ale w zależności od tego, jak generujesz tablicę boolowską i jak wygląda Twój kod YMMV.

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

Mam nadzieję, że to pomoże. Daj mi znać, jeśli masz więcej pytań.

Slater Victoroff
źródło
11
+1 na przykład za pomocą arange. Było to dla mnie szczególnie przydatne do pobierania różnych bloków z wielu macierzy (czyli w zasadzie przypadek 3D w tym przykładzie)
Griddo
1
Cześć, czy mógłbyś wyjaśnić, dlaczego musimy używać arangezamiast :? Wiem, że twój sposób działa, a mój nie, ale chciałbym zrozumieć, dlaczego.
marcotama
@tamzord, ponieważ jest to tablica numpy, a nie zwykła lista w języku Python, więc :składnia nie działa w ten sam sposób.
Slater Victoroff
1
@SlaterTyranus, dzięki za odpowiedź. Po lekturze rozumiem, że mieszanie :z zaawansowanym indeksowaniem oznacza: „dla każdej podprzestrzeni :zastosuj podane zaawansowane indeksowanie”. Czy moje rozumienie jest prawidłowe?
marcotama
@tamzord wyjaśnij, co rozumiesz przez „
podprzestrzeń
35

Możesz zrobić coś takiego:

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

Więcej o indeksowaniu tablic wielowymiarowych: http://docs.scipy.org/doc/numpy/user/basics.indexing.html#indexing-multi-dimensional-arrays

Ashwini Chaudhary
źródło
1
starając się zrozumieć, dlaczego potrzebny jest zakres, a nie po prostu „:” lub zakres.
MadmanLee,
@MadmanLee Hi, użycie :spowoduje wyświetlenie wielu len(a)razy wyników, zamiast tego wskazanie indeksu każdego wiersza spowoduje wydrukowanie oczekiwanych wyników.
GoingMyWay
1
Myślę, że jest to dokładnie właściwy i elegancki sposób rozwiązania tego problemu.
GoingMyWay
6

Prosty sposób może wyglądać następująco:

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]) wróci 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
źródło
1
Prosimy o rozważenie dodania wyjaśnień.
souki
@souki Dodałem teraz wyjaśnienie. Dzięki
Dhaval Mayatra
6

W najnowszych numpywersjach dodano take_along_axis(i put_along_axis), który czyści to indeksowanie w sposób przejrzysty.

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

Działa tak samo jak:

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

ale z inną obsługą osi. Jest szczególnie ukierunkowany na zastosowanie wyników argsorti argmax.

hpaulj
źródło
3

Możesz to zrobić za pomocą iteratora. Lubię to:

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

Czas:

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
źródło
1
OP wspomniał, że powinien działać szybko na dużych macierzach, więc testy porównawcze nie są zbyt reprezentatywne. Jestem ciekaw, jak twoja ostatnia metoda sprawdza się w przypadku (znacznie) większych tablic!
@moarningsun: Zaktualizowano. np.diag(X.T[Y])jest taki wolny ... Ale np.diag(X.T)jest taki szybki (10us). Nie wiem dlaczego.
Kei Minagawa
0

Innym sprytnym sposobem jest najpierw transpozycja tablicy, a następnie indeksowanie. Na koniec wybierz przekątną, to zawsze właściwa odpowiedź.

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

Krok po kroku:

Oryginalne tablice:

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

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

Transpozycja, aby umożliwić prawidłowe indeksowanie.

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

Uzyskaj wiersze w kolejności Y.

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

Przekątna powinna teraz stać się wyraźna.

>>> np.diag(X.T[Y])
array([ 2,  4,  9, 12]
Thomas Devoogdt
źródło
1
To technicznie działa i wygląda bardzo elegancko. Jednak uważam, że to podejście całkowicie wybucha, gdy masz do czynienia z dużymi tablicami. W moim przypadku NumPy połknął 30 GB wymiany i zapełnił mój dysk SSD. Zamiast tego zalecam korzystanie z zaawansowanego podejścia do indeksowania.
5nefarious