NumPy memilih indeks kolom tertentu per baris dengan menggunakan daftar indeks

90

Saya berjuang untuk memilih kolom spesifik per baris dari sebuah NumPymatriks.

Misalkan saya memiliki matriks berikut yang akan saya sebut X:

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

Saya juga memiliki listindeks kolom per setiap baris yang akan saya sebut Y:

[1, 0, 2]

Saya perlu mendapatkan nilainya:

[2]
[4]
[9]

Alih-alih a listdengan indeks Y, saya juga dapat menghasilkan matriks dengan bentuk yang sama seperti di Xmana setiap kolom adalah a bool/ intdalam rentang nilai 0-1, yang menunjukkan apakah ini kolom yang diperlukan.

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

Saya tahu ini dapat dilakukan dengan iterasi pada array dan memilih nilai kolom yang saya butuhkan. Namun, ini akan sering dieksekusi pada array data yang besar dan itulah mengapa harus dijalankan secepat mungkin.

Saya jadi bertanya-tanya apakah ada solusi yang lebih baik?

Terima kasih.

Zee
sumber
Apakah jawabannya lebih baik untuk Anda? stackoverflow.com/a/17081678/5046896
GoingMyWay

Jawaban:

102

Jika Anda memiliki array boolean, Anda dapat melakukan seleksi langsung berdasarkan itu seperti:

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

Untuk mengikuti contoh awal Anda, Anda dapat melakukan hal berikut:

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

Anda juga dapat menambahkan arangedan melakukan seleksi langsung, meskipun tergantung pada bagaimana Anda membuat array boolean dan seperti apa kode Anda 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])

Semoga dapat membantu, beri tahu saya jika Anda memiliki pertanyaan lain.

Slater Victoroff
sumber
11
+1 untuk contoh penggunaan arange. Ini sangat berguna bagi saya untuk mengambil blok yang berbeda dari beberapa matriks (jadi pada dasarnya kasus 3D dari contoh ini)
Griddo
1
Hi, bisa Anda menjelaskan mengapa kita harus menggunakan arangebukan :? Saya tahu cara Anda berhasil dan cara saya tidak, tetapi saya ingin memahami alasannya.
marcotama
@tamzord karena ini adalah array numpy dan bukan daftar python vanilla, jadi :sintaksnya tidak berfungsi dengan cara yang sama.
Slater Victoroff
1
@SlaterTyranus, terima kasih telah menanggapi. Pemahaman saya, setelah membaca beberapa, adalah bahwa pencampuran :dengan pengindeksan lanjutan berarti: "untuk setiap sub-ruang bersama :, terapkan pengindeksan lanjutan yang diberikan". Apakah pemahaman saya benar?
marcotama
@tamzord jelaskan apa yang Anda maksud dengan "sub-space"
Slater Victoroff
35

Anda bisa melakukan sesuatu seperti ini:

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

Selengkapnya tentang pengindeksan array multi-dimensi: http://docs.scipy.org/doc/numpy/user/basics.indexing.html#indexing-multi-dimensional-arrays

Ashwini Chaudhary
sumber
1
berjuang untuk memahami mengapa arange dibutuhkan, bukan hanya ':' atau range.
MadmanLee
@MadmanLee Hai, menggunakan :akan menampilkan hasil beberapa len(a)kali, sebaliknya, menunjukkan indeks setiap baris akan mencetak hasil yang diantisipasi.
GoingMyWay
1
Saya rasa inilah cara yang tepat dan elegan untuk mengatasi masalah ini.
GoingMyWay
6

Cara sederhana mungkin terlihat seperti:

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]) akan kembali 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
sumber
1
Tolong, pertimbangkan untuk menambahkan penjelasan.
souki
@souki Saya telah menambahkan penjelasan sekarang. Terima kasih
Dhaval Mayatra
6

numpyVersi terbaru telah menambahkan take_along_axis(dan put_along_axis) yang melakukan pengindeksan ini dengan bersih.

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

Ini beroperasi dengan cara yang sama seperti:

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

tetapi dengan penanganan sumbu yang berbeda. Ini secara khusus ditujukan untuk menerapkan hasil dari argsortdan argmax.

hpaulj
sumber
3

Anda dapat melakukannya dengan menggunakan iterator. Seperti ini:

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

Waktu:

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
sumber
1
OP menyebutkan itu harus berjalan cepat pada array besar , jadi tolok ukur Anda tidak terlalu representatif. Saya ingin tahu bagaimana metode terakhir Anda bekerja untuk array (yang jauh) lebih besar!
@moarningsun: Diperbarui. np.diag(X.T[Y])sangat lambat ... Tapi np.diag(X.T)sangat cepat (10us). Saya tidak tahu kenapa.
Kei Minagawa
0

Cara cerdas lainnya adalah dengan terlebih dahulu mengubah urutan array dan mengindeksnya setelahnya. Akhirnya, ambil diagonal, selalu jawaban yang benar.

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

Selangkah demi selangkah:

Array asli:

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

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

Ubah urutan agar memungkinkan untuk mengindeksnya dengan benar.

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

Dapatkan baris dalam urutan Y.

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

Diagonal sekarang akan menjadi jelas.

>>> np.diag(X.T[Y])
array([ 2,  4,  9, 12]
Thomas Devoogdt
sumber
1
Ini secara teknis berfungsi dan terlihat sangat elegan. Namun, saya menemukan bahwa pendekatan ini benar-benar meledak saat Anda berurusan dengan array besar. Dalam kasus saya, NumPy menelan 30GB swap dan mengisi SSD saya. Saya merekomendasikan menggunakan pendekatan pengindeksan lanjutan sebagai gantinya.
5nefarious