Indexação estranha usando numpy

27

Eu tenho uma variável, x, que é da forma (2,2,50,100).

Eu também tenho uma matriz, y, que é igual a np.array ([0,10,20]). Uma coisa estranha acontece quando eu indexo x [0,:,:, y].

x = np.full((2,2,50,100),np.nan)
y = np.array([0,10,20])
print(x.shape)
(2,2,50,100)
print(x[:,:,:,y].shape)
(2,2,50,3)
print(x[0,:,:,:].shape)
(2,50,100)
print(x[0,:,:,y].shape)
(3,2,50)

Por que o último produz (3,2,50) e não (2,50,3)?

Paul Scotti
fonte
Eu sou novo em entorpecido, então não tenho uma resposta para sua pergunta. Para investigar isso mais, sugiro encontrar um exemplo menor que seja apenas 2D ou 3D e seja apenas no máximo 10 elementos em qualquer eixo.
Code-Apprentice

Respostas:

21

É assim que o numpy usa a indexação avançada para transmitir formas de array. Quando você passa a 0para o primeiro índice e yo último, numpy transmitirá 0a mesma forma que y. A seguir equivalência detém: x[0,:,:,y] == x[(0, 0, 0),:,:,y]. aqui está um exemplo

import numpy as np

x = np.arange(120).reshape(2,3,4,5)
y = np.array([0,2,4])

np.equal(x[0,:,:,y], x[(0, 0, 0),:,:,y]).all()
# returns:
True

Agora, como você efetivamente passa dois conjuntos de índices, está usando a API de indexação avançada para formar (nesse caso) pares de índices.

x[(0, 0, 0),:,:,y])

# equivalent to
[
  x[0,:,:,y[0]], 
  x[0,:,:,y[1]], 
  x[0,:,:,y[2]]
]

# equivalent to
rows = np.array([0, 0, 0])
cols = y
x[rows,:,:,cols]

# equivalent to
[
  x[r,:,:,c] for r, c in zip(rows, columns)
]

Qual tem uma primeira dimensão igual ao comprimento de y. Isto é o que você está vendo.

Como exemplo, observe uma matriz com 4 dimensões, descritas no próximo bloco:

x = np.arange(120).reshape(2,3,4,5)
y = np.array([0,2,4])

# x looks like:
array([[[[  0,   1,   2,   3,   4],    -+      =+
         [  5,   6,   7,   8,   9],     Sheet1  |
         [ 10,  11,  12,  13,  14],     |       |
         [ 15,  16,  17,  18,  19]],   -+       |
                                                Workbook1
        [[ 20,  21,  22,  23,  24],    -+       |
         [ 25,  26,  27,  28,  29],     Sheet2  |
         [ 30,  31,  32,  33,  34],     |       |
         [ 35,  36,  37,  38,  39]],   -+       |
                                                |
        [[ 40,  41,  42,  43,  44],    -+       |
         [ 45,  46,  47,  48,  49],     Sheet3  |
         [ 50,  51,  52,  53,  54],     |       |
         [ 55,  56,  57,  58,  59]]],  -+      =+


       [[[ 60,  61,  62,  63,  64],
         [ 65,  66,  67,  68,  69],
         [ 70,  71,  72,  73,  74],
         [ 75,  76,  77,  78,  79]],

        [[ 80,  81,  82,  83,  84],
         [ 85,  86,  87,  88,  89],
         [ 90,  91,  92,  93,  94],
         [ 95,  96,  97,  98,  99]],

        [[100, 101, 102, 103, 104],
         [105, 106, 107, 108, 109],
         [110, 111, 112, 113, 114],
         [115, 116, 117, 118, 119]]]])

x tem uma forma seqüencial realmente fácil de entender que agora podemos usar para mostrar o que está acontecendo ...

A primeira dimensão é como ter 2 pastas de trabalho do Excel, a segunda dimensão é como ter 3 folhas em cada pasta de trabalho, a terceira dimensão é como ter 4 linhas por folha e a última dimensão é de 5 valores para cada linha (ou colunas por folha).

Olhando dessa maneira, solicitando x[0,:,:,0], está o ditado: "na primeira pasta de trabalho, para cada planilha, para cada linha, me dê o primeiro valor / coluna".

x[0,:,:,y[0]]
# returns:
array([[ 0,  5, 10, 15],
       [20, 25, 30, 35],
       [40, 45, 50, 55]])

# this is in the same as the first element in:
x[(0,0,0),:,:,y]

Mas agora, com a indexação avançada, podemos pensar x[(0,0,0),:,:,y]como "na primeira pasta de trabalho, para cada planilha, para cada linha, me dê o yvalor / coluna. Ok, agora faça isso para cada valor de y"

x[(0,0,0),:,:,y]
# returns:
array([[[ 0,  5, 10, 15],
        [20, 25, 30, 35],
        [40, 45, 50, 55]],

       [[ 2,  7, 12, 17],
        [22, 27, 32, 37],
        [42, 47, 52, 57]],

       [[ 4,  9, 14, 19],
        [24, 29, 34, 39],
        [44, 49, 54, 59]]])

Onde fica louco é que numpy será transmitido para corresponder às dimensões externas da matriz de índice. Portanto, se você quiser fazer a mesma operação acima, mas para as "pastas de trabalho do Excel", não precisará fazer loop e concatenar. Você pode simplesmente passar uma matriz para a primeira dimensão, mas DEVE ter uma forma compatível.

Passar um número inteiro é transmitido para y.shape == (3,). Se você deseja passar uma matriz como o primeiro índice, apenas a última dimensão da matriz deve ser compatível y.shape. Ou seja, a última dimensão do primeiro índice deve ser 3 ou 1.

ix = np.array([[0], [1]])
x[ix,:,:,y].shape
# each row of ix is broadcast to length 3:
(2, 3, 3, 4)

ix = np.array([[0,0,0], [1,1,1]])
x[ix,:,:,y].shape
# this is identical to above:
(2, 3, 3, 4)

ix = np.array([[0], [1], [0], [1], [0]])
x[ix,:,:,y].shape
# ix is broadcast so each row of ix has 3 columns, the length of y
(5, 3, 3, 4)

Encontre uma breve explicação nos documentos: https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html#combining-advanced-and-basic-indexing


Editar:

A partir da pergunta original, para obter uma linha do seu sub-corte desejado, você pode usar x[0][:,:,y]:

x[0][:,:,y].shape
# returns
(2, 50, 3)

No entanto, se você estiver tentando atribuir a essas sub-fatias, tenha muito cuidado para ver uma exibição de memória compartilhada da matriz original. Caso contrário, a atribuição não será para a matriz original, mas uma cópia.

A memória compartilhada ocorre apenas quando você usa um número inteiro ou uma fatia para subconjunto de sua matriz, x[:,0:3,:,:]ou seja, ou x[0,:,:,1:-1].

np.shares_memory(x, x[0])
# returns:
True

np.shares_memory(x, x[:,:,:,y])
# returns:
False

Na sua pergunta original e no meu exemplo y não é um int ou uma fatia, portanto, sempre acabará atribuindo uma cópia do original.

MAS! Como sua matriz para ypode ser expressa como uma fatia, você PODE obter uma visualização atribuível de sua matriz por meio de:

x[0,:,:,0:21:10].shape
# returns:
(2, 50, 3)

np.shares_memory(x, x[0,:,:,0:21:10])
# returns:
True

# actually assigns to the original array
x[0,:,:,0:21:10] = 100

Aqui, usamos a fatia 0:21:10para obter todos os índices em que estaria range(0,21,10). Temos que usar 21e não 20porque o ponto de parada é excluído da fatia, assim como norange função.

Então, basicamente, se você pode construir uma fatia que atenda aos seus critérios de subslicing, poderá fazer a atribuição.

James
fonte
4

É chamado combining advanced and basic indexing. Em combining advanced and basic indexing, numpy, faça primeiro a indexação na indexação avançada e subespaça / concatene o resultado para a dimensão da indexação básica.

Exemplo dos documentos:

Seja x.shape como (10,20,30,40,50) e suponha que ind_1 e ind_2 possam ser transmitidos para a forma (2,3,4). Então x [:, ind_1, ind_2] tem forma (10,2,3,4,40,50) porque o subespaço em forma de (20,30) de X foi substituído pelo subespaço (2,3,4) de os índices. No entanto, x [:, ind_1,:, ind_2] tem forma (2,3,4,10,30,50) porque não há um lugar inequívoco para o espaço de indexação, portanto ele é aplicado desde o início . Sempre é possível usar .transpose () para mover o subespaço para qualquer lugar desejado. Observe que este exemplo não pode ser replicado usando take.

assim, em x[0,:,:,y], 0e ysão antecedência indexação. Eles são transmitidos juntos para gerar dimensão (3,).

In [239]: np.broadcast(0,y).shape
Out[239]: (3,)

este (3,) segue para o início da 2ª e 3ª dimensão para fazer(3, 2, 50)

Para ver que a 1ª e última dimensão realmente estão transmitindo em conjunto, você pode tentar mudar 0para [0,1]ver o erro da radiodifusão

print(x[[0,1],:,:,y])

Output:
IndexError                                Traceback (most recent call last)
<ipython-input-232-5d10156346f5> in <module>
----> 1 x[[0,1],:,:,y]

IndexError: shape mismatch: indexing arrays could not be broadcast together with
 shapes (2,) (3,)
Andy L.
fonte