Fatiar uma matriz NumPy 2d ou como extrair uma submatriz mxm de uma matriz nxn (n> m)?

174

Eu quero cortar uma matriz NumPy nxn. Eu quero extrair uma seleção arbitrária de m linhas e colunas dessa matriz (ou seja, sem nenhum padrão no número de linhas / colunas), tornando-a uma nova matriz mxm. Neste exemplo, digamos que a matriz é 4x4 e quero extrair uma matriz 2x2 dela.

Aqui está a nossa matriz:

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

A linha e as colunas a serem removidas são as mesmas. O caso mais fácil é quando eu quero extrair uma submatriz 2x2 que está no começo ou no fim, ou seja:

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

Mas e se eu precisar remover outra mistura de linhas / colunas? E se eu precisar remover a primeira e a terceira linha / linha, extraindo a submatriz [[5,7],[13,15]]? Pode haver qualquer composição de linhas / linhas. Eu li em algum lugar que eu só preciso indexar minha matriz usando matrizes / listas de índices para linhas e colunas, mas isso não parece funcionar:

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

Eu encontrei uma maneira, que é:

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

A primeira questão é que é dificilmente legível, embora eu possa viver com isso. Se alguém tiver uma solução melhor, eu certamente gostaria de ouvi-la.

Outra coisa é que li em um fórum que a indexação de matrizes com matrizes força o NumPy a fazer uma cópia da matriz desejada, portanto, ao tratar com matrizes grandes, isso pode se tornar um problema. Por que é assim / como esse mecanismo funciona?

leve
fonte

Respostas:

62

Como Sven mencionou, x[[[0],[2]],[1,3]]retornará as linhas 0 e 2 que correspondem às colunas 1 e 3, enquanto x[[0,2],[1,3]]retornará os valores x [0,1] e x [2,3] em uma matriz.

Existe uma função útil para fazer o primeiro exemplo que eu dei numpy.ix_,. Você pode fazer o mesmo que no meu primeiro exemplo x[numpy.ix_([0,2],[1,3])]. Isso pode evitar que você entre em todos esses colchetes extras.

Justin Peel
fonte
111

Para responder a essa pergunta, precisamos examinar como a indexação de uma matriz multidimensional funciona no Numpy. Vamos primeiro dizer que você tem a matriz xda sua pergunta. O buffer atribuído a xconterá 16 números inteiros ascendentes de 0 a 15. Se você acessar um elemento, digamos x[i,j], NumPy precisa descobrir a localização da memória desse elemento em relação ao início do buffer. Isso é feito calculando com efeito i*x.shape[1]+j(e multiplicando pelo tamanho de um int para obter um deslocamento de memória real).

Se você extrair uma sub-matriz por fatias básicas como y = x[0:2,0:2], o objeto resultante compartilhará o buffer subjacente com x. Mas o que acontece se você acessar y[i,j]? O NumPy não pode ser usado i*y.shape[1]+jpara calcular o deslocamento na matriz, porque os dados pertencentes a ynão são consecutivos na memória.

O NumPy resolve esse problema introduzindo avanços . Ao calcular o deslocamento da memória para acessar x[i,j], o que é realmente calculado é i*x.strides[0]+j*x.strides[1](e isso já inclui o fator para o tamanho de um int):

x.strides
(16, 4)

Quando yé extraído como acima, NumPy não cria um novo tampão, mas faz criar um novo objecto matriz referenciar o mesmo tampão (caso contrário, yseria apenas igual a x.) O novo objecto matriz irá ter uma forma diferente, em seguida, xe talvez uma partida diferente compensado no buffer, mas compartilhará os progressos com x(neste caso, pelo menos):

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

Dessa forma, calcular o deslocamento da memória para y[i,j]produzirá o resultado correto.

Mas o que o NumPy deve fazer por algo assim z=x[[1,3]]? O mecanismo de passada não permitirá a indexação correta se o buffer original for usado z. Teoricamente, o NumPy poderia adicionar algum mecanismo mais sofisticado do que os avanços, mas isso tornaria o acesso ao elemento relativamente caro, desafiando de alguma forma toda a idéia de uma matriz. Além disso, uma visualização não seria mais um objeto realmente leve.

Isso é abordado em profundidade na documentação do NumPy sobre indexação .

Ah, e quase esqueci sua pergunta real: veja como fazer a indexação com várias listas funcionar como esperado:

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

Isso ocorre porque as matrizes de índice são transmitidas para um formato comum. Obviamente, neste exemplo em particular, você também pode se contentar com o fatiamento básico:

x[1::2, 1::2]
Sven Marnach
fonte
Deveria ser possível subclassar matrizes para que se pudesse ter um objeto "slcie-view" que remapeava os índices para a matriz original. Isso possivelmente poderia atender às necessidades do OP
jsbueno
@jsbueno: isso funcionará no código Python, mas não nas rotinas C / Fortran que o Scipy / Numpy adota. Essas rotinas são onde está o poder do Numpy.
Dat Chu
Então, qual é a diferença entre x [[[1], [3]], [1,3]] e x [[1,3],:] [:, [1,3]]? Quero dizer, existe uma variante que é melhor usar do que a outra?
Levesque
1
@JC: x[[[1],[3]],[1,3]]cria apenas uma nova matriz, enquanto x[[1,3],:][:,[1,3]]copia duas vezes, então use a primeira.
Sven Marnach
@JC: Ou use o método da resposta de Justin.
Sven Marnach
13

Eu não acho isso x[[1,3]][:,[1,3]]dificilmente legível. Se você quiser ser mais claro sobre sua intenção, pode:

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

Eu não sou especialista em fatiar, mas normalmente, se você tentar dividir em uma matriz e os valores forem contínuos, você voltará a ver onde o valor da passada é alterado.

Por exemplo, nas entradas 33 e 34, embora você obtenha uma matriz 2x2, o passo é 4. Assim, quando você indexa a próxima linha, o ponteiro se move para a posição correta na memória.

Claramente, esse mecanismo não se encaixa bem no caso de uma matriz de índices. Portanto, numpy terá que fazer a cópia. Afinal, muitas outras funções matemáticas da matriz dependem do tamanho, avanço e alocação de memória contínua.

Dat Chu
fonte
10

Se você quiser pular todas as outras linhas e outras colunas, poderá fazê-lo com o fatiamento básico:

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

Isso retorna uma visualização, não uma cópia da sua matriz.

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

while z=x[(1,3),:][:,(1,3)]usa indexação avançada e, portanto, retorna uma cópia:

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

Observe que xnão é alterado:

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

Se você deseja selecionar linhas e colunas arbitrárias, não pode usar o fatiamento básico. Você precisará usar a indexação avançada, usando algo como x[rows,:][:,columns], onde rowse columnssão sequências. Obviamente, isso fornecerá uma cópia, não uma visualização, da sua matriz original. É o que se espera, uma vez que uma matriz numpy usa memória contígua (com avanços constantes), e não haveria maneira de gerar uma exibição com linhas e colunas arbitrárias (já que isso exigiria avanços não constantes).

unutbu
fonte
5

Com numpy, você pode passar uma fatia para cada componente do índice - assim, seu x[0:2,0:2]exemplo acima funciona.

Se você quiser pular uniformemente colunas ou linhas, pode passar fatias com três componentes (ou seja, iniciar, parar, etapa).

Novamente, para o seu exemplo acima:

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

O que é basicamente: fatia na primeira dimensão, com início no índice 1, pare quando o índice for igual ou superior a 4 e adicione 2 ao índice em cada passagem. O mesmo para a segunda dimensão. Novamente: isso funciona apenas para etapas constantes.

A sintaxe que você precisa fazer é algo completamente diferente internamente - o que x[[1,3]][:,[1,3]]realmente faz é criar uma nova matriz, incluindo apenas as linhas 1 e 3 da matriz original (feita com a x[[1,3]]parte) e, em seguida, cortar novamente isso - criando uma terceira matriz - incluindo apenas colunas 1 e 3 da matriz anterior.

jsbueno
fonte
1
Esta solução não funciona, pois é específica para as linhas / colunas que eu estava tentando extrair. Imagine o mesmo em uma matriz 50x50, quando eu quero extrair linhas / colunas 5,11,12,32,39,45, não há como fazer isso com fatias simples. Desculpe se eu não estava claro na minha pergunta.
Levesque
3

Eu tenho uma pergunta semelhante aqui: Escrevendo no subarray de um ndarray da maneira mais pythoniana. Python 2 .

Seguindo a solução da postagem anterior para o seu caso, a solução se parece com:

columns_to_keep = [1,3] 
rows_to_keep = [1,3]

Um usando ix_:

x[np.ix_(rows_to_keep, columns_to_keep)] 

Qual é:

array([[ 5,  7],
       [13, 15]])
Rafael Valero
fonte
0

Não sei ao certo quão eficiente isso é, mas você pode usar range () para cortar nos dois eixos

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