Indexation bizarre avec numpy

27

J'ai une variable, x, qui est de la forme (2,2,50,100).

J'ai également un tableau, y, qui est égal à np.array ([0,10,20]). Une chose étrange se produit lorsque j'indexe 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)

Pourquoi le dernier produit-il (3,2,50) et non (2,50,3)?

Paul Scotti
la source
Je suis novice en numpy, donc je n'ai pas de réponse à votre question. Pour approfondir cela, je suggère de trouver un exemple plus petit qui n'est que 2D ou 3D et qui ne ressemble qu'à au plus 10 éléments sur n'importe quel axe.
Code-Apprentice

Réponses:

21

C'est ainsi que numpy utilise l'indexation avancée pour diffuser des formes de tableau. Lorsque vous passez un 0pour le premier index et ypour le dernier index, numpy diffusera le 0pour avoir la même forme que y. L'équivalence suivante est: x[0,:,:,y] == x[(0, 0, 0),:,:,y]. Voici un exemple

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

Maintenant, comme vous transmettez effectivement deux ensembles d'indices, vous utilisez l'API d'indexation avancée pour former (dans ce cas) des paires d'indices.

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

Qui a une première dimension identique à la longueur de y. Voilà ce que vous voyez.

Par exemple, regardez un tableau avec 4 dimensions qui sont décrites dans le bloc suivant:

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 a une forme séquentielle vraiment facile à comprendre que nous pouvons maintenant utiliser pour montrer ce qui se passe ...

La première dimension est comme avoir 2 classeurs Excel, la deuxième dimension est comme avoir 3 feuilles dans chaque classeur, la troisième dimension est comme avoir 4 lignes par feuille et la dernière dimension est 5 valeurs pour chaque ligne (ou colonnes par feuille).

En le regardant de cette façon, en demandant x[0,:,:,0], c'est le dicton: "dans le premier classeur, pour chaque feuille, pour chaque ligne, donnez-moi la première valeur / colonne."

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]

Mais maintenant, avec l'indexation avancée, nous pouvons penser x[(0,0,0),:,:,y]à "dans le premier classeur, pour chaque feuille, pour chaque ligne, donnez-moi la yvaleur / colonne. Ok, maintenant faites-le pour chaque valeur 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]]])

Là où ça devient fou, c'est que numpy diffusera pour correspondre aux dimensions externes du tableau d'index. Donc, si vous voulez faire la même opération que ci-dessus, mais pour les DEUX "classeurs Excel", vous n'avez pas à boucler et à concaténer. Vous pouvez simplement passer un tableau à la première dimension, mais il DOIT avoir une forme compatible.

La transmission d'un entier est diffusée vers y.shape == (3,). Si vous voulez passer un tableau comme premier index, seule la dernière dimension du tableau doit être compatible avec y.shape. C'est-à-dire que la dernière dimension du premier index doit être soit 3 soit 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)

Trouvé une courte explication dans les documents: https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html#combining-advanced-and-basic-indexing


Éditer:

À partir de la question d'origine, pour obtenir une ligne de votre sous-découpage souhaité, vous pouvez utiliser x[0][:,:,y]:

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

Cependant, si vous essayez d' affecter à ces sous-tranches, vous devez être très prudent que vous regardez une vue de mémoire partagée du tableau d'origine. Sinon, l'affectation ne concernera pas le tableau d'origine, mais une copie.

La mémoire partagée se produit uniquement lorsque vous utilisez un entier ou une tranche pour sous-définir votre tableau, c'est x[:,0:3,:,:]-à- dire ou x[0,:,:,1:-1].

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

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

Dans votre question d'origine et mon exemple yn'est ni un entier ni une tranche, donc finira toujours par être attribué à une copie de l'original.

MAIS! Parce que votre tableau pour ypeut être exprimé sous la forme d' une tranche, vous POUVEZ réellement obtenir une vue assignable de votre choix via:

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

Ici, nous utilisons la tranche 0:21:10pour saisir chaque index qui se trouverait range(0,21,10). Nous devons utiliser 21et non 20parce que le point d'arrêt est exclu de la tranche, tout comme dans la rangefonction.

Donc, fondamentalement, si vous pouvez construire une tranche qui correspond à vos critères de sous-découpage, vous pouvez faire une affectation.

James
la source
4

Il est appelé combining advanced and basic indexing. Dans combining advanced and basic indexing, numpy fait d'abord l'indexation dans l'indexation avancée et sous-espace / concatène le résultat à la dimension de l'indexation de base.

Exemple de documents:

Soit x.shape (10,20,30,40,50) et supposons que ind_1 et ind_2 peuvent être diffusés à la forme (2,3,4). Alors x [:, ind_1, ind_2] a la forme (10,2,3,4,40,50) car le sous-espace en forme de (20,30) de X a été remplacé par le sous-espace (2,3,4) de les indices. Cependant, x [:, ind_1,:, ind_2] a une forme (2,3,4,10,30,50) car il n'y a pas d'endroit sans ambiguïté à déposer dans le sous-espace d'indexation, il est donc cloué au début . Il est toujours possible d'utiliser .transpose () pour déplacer le sous-espace où vous le souhaitez. Notez que cet exemple ne peut pas être répliqué à l'aide de take.

donc, sur x[0,:,:,y], 0et ysont l'indexation avancée. Ils sont diffusés ensemble pour donner une dimension (3,).

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

Cela (3,)colle au début des 2e et 3e dimensions pour faire(3, 2, 50)

Pour voir que la 1ère et dernière dimension sont vraiment ensemble diffusent, vous pouvez essayer le changement 0de [0,1]voir l'erreur de la radiodiffusion

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.
la source