Découpage d'un tableau NumPy 2d, ou comment extraire une sous-matrice mxm d'un tableau nxn (n> m)?

174

Je veux découper un tableau NumPy nxn. Je veux extraire une sélection arbitraire de m lignes et colonnes de ce tableau (c'est-à-dire sans aucun motif dans le nombre de lignes / colonnes), ce qui en fait un nouveau tableau mxm. Pour cet exemple, disons que le tableau est 4x4 et que je veux en extraire un tableau 2x2.

Voici notre tableau:

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 ligne et les colonnes à supprimer sont les mêmes. Le cas le plus simple est celui où je veux extraire une sous-matrice 2x2 qui se trouve au début ou à la fin, c'est-à-dire:

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

Mais que faire si je dois supprimer un autre mélange de lignes / colonnes? Que faire si je dois supprimer les première et troisième lignes / lignes, extrayant ainsi la sous [[5,7],[13,15]]- matrice ? Il peut y avoir n'importe quelle composition de lignes / lignes. J'ai lu quelque part que j'ai juste besoin d'indexer mon tableau à l'aide de tableaux / listes d'indices pour les lignes et les colonnes, mais cela ne semble pas fonctionner:

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

J'ai trouvé un moyen, qui est:

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

Le premier problème avec ceci est qu'il est à peine lisible, même si je peux vivre avec cela. Si quelqu'un a une meilleure solution, j'aimerais certainement l'entendre.

Une autre chose est que j'ai lu sur un forum que l'indexation de tableaux avec des tableaux oblige NumPy à faire une copie du tableau souhaité, donc lors du traitement avec de grands tableaux, cela pourrait devenir un problème. Pourquoi est-ce ainsi / comment fonctionne ce mécanisme?

levesque
la source

Réponses:

62

Comme Sven l'a mentionné, x[[[0],[2]],[1,3]]rendra les lignes 0 et 2 qui correspondent aux colonnes 1 et 3 tandis que x[[0,2],[1,3]]retournera les valeurs x [0,1] et x [2,3] dans un tableau.

Il y a une fonction utile pour faire le premier exemple que je donnais, numpy.ix_. Vous pouvez faire la même chose que mon premier exemple avec x[numpy.ix_([0,2],[1,3])]. Cela peut vous éviter d'avoir à entrer toutes ces parenthèses supplémentaires.

Justin Peel
la source
111

Pour répondre à cette question, nous devons examiner comment l'indexation d'un tableau multidimensionnel fonctionne dans Numpy. Disons d'abord que vous avez le tableau xde votre question. Le tampon affecté à xcontiendra 16 entiers ascendants de 0 à 15. Si vous accédez à un élément, disons x[i,j], NumPy doit déterminer l'emplacement mémoire de cet élément par rapport au début du tampon. Cela se fait en calculant en effet i*x.shape[1]+j(et en multipliant par la taille d'un int pour obtenir un décalage de mémoire réel).

Si vous extrayez un sous-tableau par découpage de base comme y = x[0:2,0:2], l'objet résultant partagera le tampon sous-jacent avec x. Mais que se passe-t-il si vous y accédez y[i,j]? NumPy ne peut pas utiliser i*y.shape[1]+jpour calculer le décalage dans le tableau, car les données appartenant à yne sont pas consécutives en mémoire.

NumPy résout ce problème en introduisant des foulées . Lors du calcul du décalage de mémoire pour l'accès x[i,j], ce qui est réellement calculé est i*x.strides[0]+j*x.strides[1](et cela inclut déjà le facteur pour la taille d'un int):

x.strides
(16, 4)

Quand yest extrait comme ci - dessus, NumPy ne crée pas un nouveau tampon, mais il fait créer un nouvel objet tableau faisant référence au même tampon (sinon yserait juste égale à x.) Le nouvel objet tableau aura une forme différente alors xet peut - être un autre départ offset dans la mémoire tampon, mais partagera les foulées avec x(dans ce cas au moins):

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

De cette façon, le calcul de l'offset mémoire pour y[i,j]donnera le résultat correct.

Mais que devrait faire NumPy pour quelque chose comme z=x[[1,3]]? Le mécanisme de foulées ne permettra pas une indexation correcte si le tampon d'origine est utilisé pour z. NumPy pourrait théoriquement ajouter un mécanisme plus sophistiqué que les foulées, mais cela rendrait l'accès aux éléments relativement coûteux, défiant en quelque sorte l'idée même d'un tableau. De plus, une vue ne serait plus un objet vraiment léger.

Ceci est traité en détail dans la documentation NumPy sur l'indexation .

Oh, et j'ai presque oublié votre question réelle: voici comment faire fonctionner l'indexation avec plusieurs listes comme prévu:

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

En effet, les tableaux d'index sont diffusés sous une forme commune. Bien sûr, pour cet exemple particulier, vous pouvez également vous contenter du découpage de base:

x[1::2, 1::2]
Sven Marnach
la source
Il devrait être possible de sous-classer les tableaux afin que l'on puisse avoir un objet "slcie-view" qui remapperait les index sur le tableau d'origine. Cela pourrait éventuellement répondre aux besoins de l'OP
jsbueno
@jsbueno: cela fonctionnera pour le code Python mais pas pour les routines C / Fortran que Scipy / Numpy encapsule. Ces routines enveloppées sont là où réside la puissance de Numpy.
Dat Chu
Soo .. quelle est la différence entre x [[[1], [3]], [1,3]] et x [[1,3],:] [:, [1,3]]? Je veux dire, y a-t-il une variante qu'il vaut mieux utiliser que l'autre?
levesque
1
@JC: x[[[1],[3]],[1,3]]crée un seul nouveau tableau, tandis que x[[1,3],:][:,[1,3]]copie deux fois, utilisez donc le premier.
Sven Marnach
@JC: Ou utilisez la méthode de la réponse de Justin.
Sven Marnach
13

Je ne pense pas que ce x[[1,3]][:,[1,3]]soit à peine lisible. Si vous voulez être plus clair sur votre intention, vous pouvez faire:

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

Je ne suis pas un expert en tranchage, mais généralement, si vous essayez de découper dans un tableau et que les valeurs sont continues, vous obtenez une vue où la valeur de la foulée est modifiée.

Par exemple, dans vos entrées 33 et 34, bien que vous obteniez un tableau 2x2, la foulée est 4. Ainsi, lorsque vous indexez la ligne suivante, le pointeur se déplace vers la position correcte en mémoire.

De toute évidence, ce mécanisme ne s'intègre pas bien dans le cas d'un tableau d'indices. Par conséquent, numpy devra faire la copie. Après tout, de nombreuses autres fonctions mathématiques matricielles reposent sur la taille, la foulée et l'allocation continue de la mémoire.

Dat Chu
la source
10

Si vous souhaitez ignorer toutes les autres lignes et toutes les autres colonnes, vous pouvez le faire avec un découpage de 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]])

Cela renvoie une vue, pas une copie de votre tableau.

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)]utilise une indexation avancée et renvoie donc une copie:

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

Notez que xc'est inchangé:

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

Si vous souhaitez sélectionner des lignes et des colonnes arbitraires, vous ne pouvez pas utiliser le découpage de base. Vous devrez utiliser l'indexation avancée, en utilisant quelque chose comme x[rows,:][:,columns], où rowset columnssont des séquences. Cela va bien sûr vous donner une copie, pas une vue, de votre tableau d'origine. C'est comme on devrait s'y attendre, car un tableau numpy utilise une mémoire contiguë (avec des foulées constantes), et il n'y aurait aucun moyen de générer une vue avec des lignes et des colonnes arbitraires (car cela nécessiterait des foulées non constantes).

unutbu
la source
5

Avec numpy, vous pouvez passer une tranche pour chaque composant de l'index - ainsi, votre x[0:2,0:2]exemple ci-dessus fonctionne.

Si vous voulez simplement sauter des colonnes ou des lignes de manière uniforme, vous pouvez passer des tranches avec trois composants (c'est-à-dire démarrer, arrêter, étape).

Encore une fois, pour votre exemple ci-dessus:

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

Ce qui est fondamentalement: tranche dans la première dimension, avec début à l'index 1, arrête lorsque l'index est égal ou supérieur à 4, et ajoute 2 à l'index à chaque passage. Idem pour la deuxième dimension. Encore une fois: cela ne fonctionne que pour des étapes constantes.

La syntaxe que vous devez faire quelque chose de très différent en interne - ce qui x[[1,3]][:,[1,3]]fait en fait est de créer un nouveau tableau comprenant uniquement les lignes 1 et 3 du tableau d'origine (fait avec la x[[1,3]]partie), puis de le re-découper - en créant un troisième tableau - y compris seulement colonnes 1 et 3 du tableau précédent.

jsbueno
la source
1
Cette solution ne fonctionne pas car elle est spécifique aux lignes / colonnes que j'essayais d'extraire. Imaginez la même chose dans une matrice 50x50, lorsque je veux extraire des lignes / colonnes 5,11,12,32,39,45, il n'y a aucun moyen de le faire avec de simples tranches. Désolé si je n'ai pas été clair dans ma question.
levesque
0

Je ne sais pas à quel point c'est efficace, mais vous pouvez utiliser range () pour couper dans les deux axes

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