Indexación extraña usando numpy

27

Tengo una variable, x, que tiene la forma (2,2,50,100).

También tengo una matriz, y, que es igual a np.array ([0,10,20]). Algo extraño sucede cuando 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 qué sale el último (3,2,50) y no (2,50,3)?

Paul Scotti
fuente
Soy nuevo en numpy, así que no tengo una respuesta a tu pregunta. Para investigar esto más a fondo, sugiero encontrar un ejemplo más pequeño que sea solo 2D o 3D y que solo tenga como máximo 10 elementos en cualquier eje.
Code-Apprentice

Respuestas:

21

Así es como numpy usa la indexación avanzada para transmitir formas de matriz. Cuando pasa un 0para el primer índice, y ypara el último índice, numpy transmitirá el 0para que tenga la misma forma que y. El siguiente equivalencia sostiene: x[0,:,:,y] == x[(0, 0, 0),:,:,y]. Aquí hay un ejemplo

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

Ahora, debido a que está pasando efectivamente dos conjuntos de índices, está utilizando la API de indexación avanzada para formar (en este 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)
]

Que tiene una primera dimensión igual a la longitud de y. Esto es lo que estás viendo.

Como ejemplo, observe una matriz con 4 dimensiones que se describen en el siguiente fragmento:

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 tiene una forma secuencial realmente fácil de entender que ahora podemos usar para mostrar lo que está sucediendo ...

La primera dimensión es como tener 2 libros de Excel, la segunda dimensión es como tener 3 hojas en cada libro, la tercera dimensión es como tener 4 filas por hoja, y la última dimensión es 5 valores para cada fila (o columnas por hoja).

Mirándolo de esta manera, preguntando x[0,:,:,0], está el dicho: "en el primer libro de trabajo, para cada hoja, para cada fila, dame el primer valor / columna".

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]

Pero ahora con la indexación avanzada, podemos pensar x[(0,0,0),:,:,y]como "en el primer libro de trabajo, para cada hoja, para cada fila, dame el yvalor / columna th. Ok, ahora hazlo 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]]])

Donde se vuelve loco es que numpy se transmitirá para que coincida con las dimensiones externas de la matriz de índice. Entonces, si desea hacer la misma operación que anteriormente, pero para AMBOS "libros de Excel", no tiene que hacer un bucle y concatenar. Puede pasar una matriz a la primera dimensión, pero DEBE tener una forma compatible.

Pasar un número entero se transmite a y.shape == (3,). Si desea pasar una matriz como primer índice, solo la última dimensión de la matriz debe ser compatible y.shape. Es decir, la última dimensión del primer índice debe ser 3 o 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)

Encontré una breve explicación en los documentos: https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html#combining-advanced-and-basic-indexing


Editar:

De la pregunta original, para obtener una línea de su subslicing deseado, puede usar x[0][:,:,y]:

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

Sin embargo, si está intentando asignar a esos subslices, debe tener mucho cuidado de ver una vista de memoria compartida de la matriz original. De lo contrario, la asignación no será a la matriz original, sino a una copia.

La memoria compartida solo ocurre cuando usa un número entero o un segmento para subconjuntar su matriz, es decir, x[:,0:3,:,:]o x[0,:,:,1:-1].

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

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

Tanto en su pregunta original como en mi ejemplo yno se trata de un int o un sector, por lo que siempre se terminará asignando a una copia del original.

¡PERO! Debido a que su matriz ypuede expresarse como un segmento, usted PUEDE realmente obtener una vista asignable de su matriz a través 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

Aquí usamos el segmento 0:21:10para tomar cada índice que estaría en range(0,21,10). Tenemos que usar 21y no 20porque el punto de parada está excluido del segmento, al igual que en la rangefunción.

Básicamente, si puede construir un segmento que se ajuste a sus criterios de sublición, puede hacer la asignación.

James
fuente
4

Se llama combining advanced and basic indexing. En combining advanced and basic indexingnumpy, primero haga la indexación en la indexación avanzada y subespacie / concatene el resultado a la dimensión de la indexación básica.

Ejemplo de documentos:

Deje que x.shape sea (10,20,30,40,50) y suponga que ind_1 e ind_2 pueden transmitirse a la forma (2,3,4). Entonces x [:, ind_1, ind_2] tiene forma (10,2,3,4,40,50) porque el subespacio en forma de (20,30) de X ha sido reemplazado por el subespacio (2,3,4) de los índices Sin embargo, x [:, ind_1,:, ind_2] tiene forma (2,3,4,10,30,50) porque no hay un lugar inequívoco para colocar en el subespacio de indexación, por lo tanto, se agrega al principio . Siempre es posible usar .transpose () para mover el subespacio a cualquier lugar deseado. Tenga en cuenta que este ejemplo no se puede replicar con take.

así, adelante x[0,:,:,y], 0y yson indexación anticipada. Se transmiten juntos para producir dimensión (3,).

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

Esta (3,) al comienzo de la 2da y 3ra dimensión para(3, 2, 50)

Para ver que la primera y la última dimensión realmente están transmitiendo en conjunto, puede intentar el cambio 0a [0,1]ver el error de la radiodifusión

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.
fuente