Mengiris array NumPy 2d, atau bagaimana cara mengekstrak submatrix mxm dari array nxn (n> m)?

174

Saya ingin mengiris array NumPy nxn. Saya ingin mengekstraksi pemilihan acak baris dan kolom array itu (yaitu tanpa pola dalam jumlah baris / kolom), menjadikannya array, mxm baru. Untuk contoh ini, katakanlah arraynya 4x4 dan saya ingin mengekstrak array 2x2 darinya.

Inilah susunan kami:

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

Baris dan kolom yang akan dihapus sama. Kasus termudah adalah ketika saya ingin mengekstrak submatrix 2x2 yang ada di awal atau di akhir, yaitu:

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

Tetapi bagaimana jika saya harus menghapus campuran baris / kolom? Bagaimana jika saya harus menghapus baris / baris pertama dan ketiga, sehingga mengekstraksi submatrix [[5,7],[13,15]]? Mungkin ada komposisi baris / garis. Saya membaca di suatu tempat bahwa saya hanya perlu mengindeks array saya menggunakan array / daftar indeks untuk baris dan kolom, tetapi sepertinya itu tidak berfungsi:

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

Saya menemukan satu cara, yaitu:

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

Masalah pertama dengan ini adalah bahwa itu hampir tidak dapat dibaca, meskipun saya bisa hidup dengan itu. Jika seseorang memiliki solusi yang lebih baik, saya pasti ingin mendengarnya.

Hal lain yang saya baca di forum bahwa pengindeksan array dengan array memaksa NumPy untuk membuat salinan array yang diinginkan, sehingga ketika memperlakukan dengan array besar ini bisa menjadi masalah. Mengapa begitu / bagaimana mekanisme ini bekerja?

levesque
sumber

Jawaban:

62

Seperti yang disebutkan Sven, x[[[0],[2]],[1,3]]akan mengembalikan baris 0 dan 2 yang cocok dengan kolom 1 dan 3 sementara x[[0,2],[1,3]]akan mengembalikan nilai x [0,1] dan x [2,3] dalam array.

Ada fungsi yang membantu untuk melakukan contoh pertama yang saya berikan numpy.ix_,. Anda dapat melakukan hal yang sama dengan contoh pertama saya x[numpy.ix_([0,2],[1,3])]. Ini dapat menyelamatkan Anda dari keharusan masuk dalam semua tanda kurung tambahan itu.

Justin Peel
sumber
111

Untuk menjawab pertanyaan ini, kita harus melihat bagaimana pengindeksan array multidimensi bekerja di Numpy. Pertama-tama katakanlah Anda memiliki array xdari pertanyaan Anda. Buffer yang ditugaskan xakan berisi 16 bilangan bulat naik dari 0 hingga 15. Jika Anda mengakses satu elemen, katakanlah x[i,j], NumPy harus mencari tahu lokasi memori elemen ini relatif terhadap awal buffer. Ini dilakukan dengan menghitung efek i*x.shape[1]+j(dan mengalikan dengan ukuran int untuk mendapatkan offset memori yang sebenarnya).

Jika Anda mengekstrak subarray dengan dasar slicing like y = x[0:2,0:2], objek yang dihasilkan akan berbagi buffer yang mendasarinya x. Tetapi apa yang terjadi jika Anda mengakses y[i,j]? NumPy tidak dapat digunakan i*y.shape[1]+juntuk menghitung offset ke dalam array, karena data yang dimiliki ytidak berurutan dalam memori.

NumPy memecahkan masalah ini dengan memperkenalkan langkah-langkah . Saat menghitung offset memori untuk mengakses x[i,j], apa yang sebenarnya dihitung adalah i*x.strides[0]+j*x.strides[1](dan ini sudah termasuk faktor ukuran int):

x.strides
(16, 4)

Ketika ydiekstrak seperti di atas, NumPy tidak membuat buffer baru, tetapi tidak membuat objek array baru referensi buffer yang sama (jika tidak yhanya akan sama dengan x.) The objek array baru akan memiliki bentuk yang berbeda kemudian xdan mungkin awal yang berbeda mengimbangi ke dalam buffer, tetapi akan berbagi langkah dengan x(setidaknya dalam hal ini):

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

Dengan cara ini, menghitung offset memori y[i,j]akan menghasilkan hasil yang benar.

Tapi apa yang harus dilakukan NumPy untuk sesuatu seperti z=x[[1,3]]? Mekanisme langkah tidak akan memungkinkan pengindeksan yang benar jika buffer asli digunakan untuk z. NumPy secara teoritis dapat menambahkan beberapa mekanisme yang lebih canggih daripada langkah-langkahnya, tetapi ini akan membuat akses elemen relatif mahal, entah bagaimana menentang seluruh gagasan array. Selain itu, tampilan tidak akan menjadi objek yang sangat ringan lagi.

Ini dibahas secara mendalam dalam dokumentasi NumPy tentang pengindeksan .

Oh, dan hampir lupa tentang pertanyaan Anda yang sebenarnya: Inilah cara membuat pengindeksan dengan beberapa daftar berfungsi seperti yang diharapkan:

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

Ini karena array indeks disiarkan ke bentuk umum. Tentu saja, untuk contoh khusus ini, Anda juga bisa puas dengan irisan dasar:

x[1::2, 1::2]
Sven Marnach
sumber
Harus dimungkinkan untuk subclass array sehingga orang dapat memiliki objek "slcie-view" yang akan memetakan kembali indeks ke array asli. Itu mungkin bisa memenuhi kebutuhan OP
jsbueno
@ jsbueno: itu akan bekerja untuk kode Python tetapi tidak untuk rutinitas C / Fortran yang dibungkus Scipy / Numpy. Rutinitas yang dibungkus itu adalah tempat kekuatan Numpy terletak.
Dat Chu
Soo .. apa perbedaan antara x [[[1], [3]], [1,3]] dan x [[1,3],:] [:, [1,3]]? Maksud saya apakah ada varian yang lebih baik digunakan daripada yang lain?
levesque
1
@JC: x[[[1],[3]],[1,3]]hanya membuat satu array baru, sementara x[[1,3],:][:,[1,3]]menyalin dua kali, jadi gunakan yang pertama.
Sven Marnach
@ JK: Atau gunakan metode dari jawaban Justin.
Sven Marnach
13

Saya tidak berpikir itu x[[1,3]][:,[1,3]]sulit dibaca. Jika Anda ingin lebih jelas tentang niat Anda, Anda dapat melakukan:

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

Saya bukan ahli dalam mengiris tetapi biasanya, jika Anda mencoba mengiris menjadi array dan nilainya kontinu, Anda mendapatkan kembali tampilan di mana nilai langkahnya diubah.

mis. Dalam input Anda 33 dan 34, meskipun Anda mendapatkan array 2x2, langkahnya adalah 4. Jadi, ketika Anda mengindeks baris berikutnya, pointer bergerak ke posisi yang benar dalam memori.

Jelas, mekanisme ini tidak membawa kasus array indeks. Oleh karena itu, numpy harus membuat salinannya. Bagaimanapun, banyak fungsi matrik matematika lainnya bergantung pada ukuran, langkah, dan alokasi memori kontinu.

Dat Chu
sumber
10

Jika Anda ingin melewati setiap baris lain dan setiap kolom lainnya, maka Anda bisa melakukannya dengan mengiris dasar:

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

Ini mengembalikan tampilan, bukan salinan array Anda.

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

saat z=x[(1,3),:][:,(1,3)]menggunakan pengindeksan tingkat lanjut dan dengan demikian mengembalikan salinan:

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

Catatan yang xtidak berubah:

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

Jika Anda ingin memilih baris dan kolom sewenang-wenang, maka Anda tidak dapat menggunakan pengirisan dasar. Anda harus menggunakan pengindeksan lanjutan, menggunakan sesuatu seperti x[rows,:][:,columns], di mana, rowsdan columnsurutan. Ini tentu saja akan memberi Anda salinan, bukan tampilan, dari array asli Anda. Ini seperti yang diharapkan, karena array numpy menggunakan memori yang berdekatan (dengan langkah konstan), dan tidak akan ada cara untuk menghasilkan tampilan dengan baris dan kolom sewenang-wenang (karena itu akan membutuhkan langkah tidak konstan).

unutbu
sumber
5

Dengan numpy, Anda bisa memberikan irisan untuk setiap komponen indeks - jadi, x[0:2,0:2]contoh Anda di atas berfungsi.

Jika Anda hanya ingin melompati kolom atau baris secara merata, Anda dapat memberikan irisan dengan tiga komponen (yaitu mulai, berhenti, langkah).

Sekali lagi, untuk contoh Anda di atas:

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

Yang pada dasarnya adalah: iris dalam dimensi pertama, dengan mulai dari indeks 1, berhenti ketika indeks sama atau lebih besar dari 4, dan tambahkan 2 ke indeks di setiap pass. Hal yang sama untuk dimensi kedua. Sekali lagi: ini hanya berfungsi untuk langkah konstan.

Sintaks Anda harus melakukan sesuatu yang sangat berbeda secara internal - apa yang x[[1,3]][:,[1,3]]sebenarnya dilakukan adalah membuat array baru termasuk hanya baris 1 dan 3 dari array asli (dilakukan dengan x[[1,3]]bagian), dan kemudian mengiris ulang itu - membuat array ketiga - termasuk hanya kolom 1 dan 3 dari array sebelumnya.

jsbueno
sumber
1
Solusi ini tidak berfungsi karena khusus untuk baris / kolom yang saya coba ekstrak. Bayangkan hal yang sama dalam matriks 50x50, ketika saya ingin mengekstrak baris / kolom 5,11,12,32,39,45, tidak ada cara untuk melakukannya dengan irisan sederhana. Maaf jika pertanyaan saya tidak jelas.
levesque
0

Saya tidak yakin seberapa efisien ini tetapi Anda dapat menggunakan range () untuk memotong di kedua sumbu

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