Slicing di un array NumPy 2d o come estrarre un sottomatrix mxm da un array nxn (n> m)?


174

Voglio tagliare un array NumPy nxn. Voglio estrarre una selezione arbitraria di m righe e colonne di quell'array (cioè senza alcun modello nei numeri di righe / colonne), rendendolo un nuovo array mxm. Per questo esempio diciamo che l'array è 4x4 e voglio estrarre un array 2x2 da esso.

Ecco il nostro 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]]

La linea e le colonne da rimuovere sono uguali. Il caso più semplice è quando voglio estrarre una submatrix 2x2 che è all'inizio o alla fine, cioè:

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

Ma cosa succede se devo rimuovere un'altra combinazione di righe / colonne? Cosa devo fare se devo rimuovere la prima e la terza riga / riga, estraendo così la sottotrix [[5,7],[13,15]]? Può esserci qualsiasi composizione di righe / linee. Ho letto da qualche parte che ho solo bisogno di indicizzare il mio array usando array / elenchi di indici sia per righe che per colonne, ma ciò non sembra funzionare:

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

Ho trovato un modo, che è:

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

Il primo problema è che è appena leggibile, anche se posso conviverci. Se qualcuno ha una soluzione migliore, mi piacerebbe sicuramente ascoltarla.

Un'altra cosa è che ho letto su un forum che indicizzare matrici con matrici costringe NumPy a fare una copia dell'array desiderato, quindi quando si tratta con matrici di grandi dimensioni questo potrebbe diventare un problema. Perché è così / come funziona questo meccanismo?

Risposte:


62

Come accennato da Sven, x[[[0],[2]],[1,3]]restituirà le righe 0 e 2 che corrispondono alle colonne 1 e 3 mentre x[[0,2],[1,3]]restituirà i valori x [0,1] e x [2,3] in un array.

C'è una funzione utile per fare il primo esempio che ho dato, numpy.ix_. Puoi fare la stessa cosa del mio primo esempio con x[numpy.ix_([0,2],[1,3])]. Questo può evitarti di dover inserire tutte quelle parentesi extra.


111

Per rispondere a questa domanda, dobbiamo vedere come funziona l'indicizzazione di un array multidimensionale in Numpy. Diciamo innanzitutto che hai l'array xdalla tua domanda. Il buffer assegnato xconterrà 16 numeri interi crescenti da 0 a 15. Se si accede a un elemento, diciamo x[i,j], NumPy deve capire la posizione di memoria di questo elemento rispetto all'inizio del buffer. Questo viene fatto calcolando in effettii*x.shape[1]+j (e moltiplicando per la dimensione di un int per ottenere un effettivo offset di memoria).

Se si estrae un subarray mediante lo slicing di base come y = x[0:2,0:2], l'oggetto risultante condividerà il buffer sottostante x. Ma cosa succede se accedi y[i,j]? NumPy non può utilizzare i*y.shape[1]+jper calcolare l'offset nell'array, poiché i dati appartenenti a ynon sono consecutivi in ​​memoria.

NumPy risolve questo problema introducendo passi da gigante . Quando si calcola l'offset di memoria per l'accesso x[i,j], ciò che viene effettivamente calcolato è i*x.strides[0]+j*x.strides[1](e questo include già il fattore per la dimensione di un int):

x.strides
(16, 4)

Quando yviene estratto come sopra, NumPy non crea un nuovo buffer, ma non creare un nuovo oggetto array riferimento lo stesso tampone (altrimenti ysarebbe solo pari a x.) Il nuovo oggetto matrice avrà una forma diversa xe forse una partenza diversa offset nel buffer, ma condividerà i passi con x(almeno in questo caso):

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

In questo modo, il calcolo dell'offset di memoria per y[i,j]produrrà il risultato corretto.

Ma cosa dovrebbe fare NumPy per qualcosa del genere z=x[[1,3]]? Il meccanismo strides non consente l'indicizzazione corretta se viene utilizzato il buffer originale z. NumPy teoricamente potrebbe aggiungere un meccanismo più sofisticato rispetto ai passi, ma ciò renderebbe l'accesso agli elementi relativamente costoso, sfidando in qualche modo l'idea di un array. Inoltre, una vista non sarebbe più un oggetto davvero leggero.

Questo è approfondito nella documentazione di NumPy sull'indicizzazione .

Oh, e quasi dimenticato la tua vera domanda: ecco come far funzionare l'indicizzazione con più liste come previsto:

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

Questo perché gli array di indici vengono trasmessi a una forma comune. Ovviamente, per questo esempio particolare, puoi anche accontentarti di un taglio di base:

x[1::2, 1::2]

Sarebbe possibile sottoclassare le matrici in modo che si possa avere un oggetto "slcie-view" che rimappa gli indici alla matrice originale. Ciò potrebbe soddisfare le esigenze dell'OP
jsbueno,

@jsbueno: funzionerà con il codice Python ma non con le routine C / Fortran che Scipy / Numpy ha in mente. Quelle routine avvolte sono dove sta il potere di Numpy.
Dat Chu,

Quindi .. qual è la differenza tra x [[[1], [3]], [1,3]] e x [[1,3],:] [:, [1,3]]? Voglio dire, c'è una variante che è meglio usare rispetto all'altra?
levesque il

1
@JC: x[[[1],[3]],[1,3]]crea solo un nuovo array, mentre x[[1,3],:][:,[1,3]]copia due volte, quindi usa il primo.
Sven Marnach,

@JC: Oppure usa il metodo dalla risposta di Justin.
Sven Marnach,

13

Non credo x[[1,3]][:,[1,3]]sia difficilmente leggibile. Se vuoi essere più chiaro sulle tue intenzioni, puoi fare:

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

Non sono un esperto di suddivisione, ma in genere, se si tenta di suddividere in un array e i valori sono continui, si ottiene una visualizzazione in cui viene modificato il valore del passo.

ad es. nei tuoi ingressi 33 e 34, anche se ottieni un array 2x2, il passo è 4. Quindi, quando indicizzi la riga successiva, il puntatore si sposta nella posizione corretta in memoria.

Chiaramente, questo meccanismo non si adatta bene al caso di una serie di indici. Quindi, numpy dovrà fare la copia. Dopotutto, molte altre funzioni matematiche di matrice si basano su dimensioni, falcata e allocazione di memoria continua.


10

Se vuoi saltare ogni altra riga e ogni altra colonna, puoi farlo con il slicing di base:

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

Ciò restituisce una vista, non una copia dell'array.

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

mentre z=x[(1,3),:][:,(1,3)]utilizza l'indicizzazione avanzata e quindi restituisce una copia:

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

Si noti che xè invariato:

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

Se si desidera selezionare righe e colonne arbitrarie, non è possibile utilizzare lo slicing di base. Dovrai utilizzare l'indicizzazione avanzata, usando qualcosa di simile x[rows,:][:,columns], dove rowse columnssono sequenze. Questo ovviamente ti darà una copia, non una vista, dell'array originale. Questo è come ci si dovrebbe aspettare, dal momento che un array intorpidito utilizza memoria contigua (con passi costanti) e non ci sarebbe modo di generare una vista con righe e colonne arbitrarie (poiché ciò richiederebbe passi non costanti).


5

Con numpy, puoi passare una sezione per ogni componente dell'indice, quindi l' x[0:2,0:2]esempio sopra funziona.

Se vuoi semplicemente saltare uniformemente colonne o righe, puoi passare sezioni con tre componenti (es. Start, stop, step).

Ancora una volta, per il tuo esempio sopra:

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

Che è fondamentalmente: suddivisione nella prima dimensione, con inizio all'indice 1, interrompi quando l'indice è uguale o maggiore di 4 e aggiungi 2 all'indice in ogni passaggio. Lo stesso per la seconda dimensione. Ancora una volta: questo funziona solo per passi costanti.

La sintassi che devi fare internamente è molto diversa: ciò che x[[1,3]][:,[1,3]]effettivamente fa è creare un nuovo array includendo solo le righe 1 e 3 dell'array originale (fatto con la x[[1,3]]parte), e quindi suddividere nuovamente - creando un terzo array - includendo solo colonne 1 e 3 dell'array precedente.


1
Questa soluzione non funziona in quanto è specifica per le righe / colonne che stavo cercando di estrarre. Immagina lo stesso in una matrice 50x50, quando voglio estrarre righe / colonne 5,11,12,32,39,45, non c'è modo di farlo con semplici sezioni. Scusa se non ero chiaro nella mia domanda.
levesque il


0

Non sono sicuro di quanto sia efficiente, ma puoi usare range () per tagliare in entrambi gli assi

 x=np.arange(16).reshape((4,4))
 x[range(1,3), :][:,range(1,3)] 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.