181 votes

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

Je veux découper un tableau NumPy nxn. Je veux extraire un arbitraire sélection de m lignes et colonnes de ce tableau (c'est-à-dire sans motif dans le nombre de lignes/colonnes), ce qui en fait un nouveau tableau mxm. Pour cet exemple, disons que le tableau est de 4x4 et que je veux en extraire un tableau de 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 j'ai besoin de supprimer un autre mélange de lignes/colonnes ? Et si j'ai besoin de supprimer les première et troisième lignes/lignes, en extrayant ainsi la sous-matrice [[5,7],[13,15]] ? Il peut y avoir n'importe quelle composition de rangées/lignes. J'ai lu quelque part que je devais simplement indexer mon tableau en utilisant des 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 est qu'il est difficilement lisible, mais je peux m'en accommoder. Si quelqu'un a une meilleure solution, j'aimerais bien l'entendre.

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

115voto

Sven Marnach Points 133943

Pour répondre à cette question, nous devons examiner comment fonctionne l'indexation d'un tableau multidimensionnel dans Numpy. Disons d'abord que vous avez le tableau x de votre question. Le tampon attribué à x contiendra 16 nombres 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. Ceci est fait en calculant en effet i*x.shape[1]+j (et en multipliant avec la taille d'un int pour obtenir un décalage mémoire réel).

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

NumPy résout ce problème en introduisant enjambées . Lors du calcul du décalage de la mémoire pour accéder à 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)

Lorsque y est extraite comme ci-dessus, NumPy ne crée pas un nouveau tampon, mais il fait créer un nouvel objet tableau référençant le même tampon (autrement y serait simplement égal à x .) Le nouvel objet tableau aura une forme différente de celle de l'objet tableau. x et peut-être un décalage de départ différent dans le tampon, mais il 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 du décalage de la mémoire pour y[i,j] donnera le bon résultat.

Mais que devrait faire NumPy pour quelque chose comme z=x[[1,3]] ? Le mécanisme des strides ne permet pas une indexation correcte si le tampon d'origine est utilisé pour les éléments suivants z . En théorie, NumPy pourrait ajouter un mécanisme plus sophistiqué que les strides, mais cela rendrait l'accès aux éléments relativement coûteux, ce qui va à l'encontre de l'idée même d'un tableau. En outre, une vue ne serait plus un objet vraiment léger.

Ce sujet est traité en profondeur dans la documentation NumPy sur l'indexation .

Oh, et j'ai presque oublié votre question : Voici comment faire fonctionner l'indexation avec des listes multiples comme prévu :

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

C'est parce que les tableaux d'index sont diffusé sur à une forme commune. Bien entendu, pour cet exemple particulier, vous pouvez également vous contenter d'un découpage de base :

x[1::2, 1::2]

0 votes

Il devrait être possible de sous-classer les tableaux de sorte que l'on puisse avoir un objet "slcie-view" qui remappe les index au tableau original. Cela pourrait peut-être répondre aux besoins de l'OP

0 votes

@jsbueno : cela fonctionnera pour le code Python mais pas pour les routines C/Fortran que Scipy/Numpy enveloppe. C'est dans ces routines enveloppées que réside la puissance de Numpy.

0 votes

Alors quelle est la différence entre x[[[1],[3]],[1,3]] et x[[1,3], :][ :,[1,3]] ? Je veux dire qu'il y a une variante qui est meilleure à utiliser que l'autre ?

64voto

Justin Peel Points 17348

Comme Sven l'a mentionné, x[[[0],[2]],[1,3]] donnera 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 existe une fonction utile pour réaliser le premier exemple que j'ai donné, 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 à saisir toutes ces parenthèses supplémentaires.

13voto

Dat Chu Points 3810

Je ne pense pas que x[[1,3]][:,[1,3]] est difficilement lisible. Si vous voulez être plus clair sur votre intention, vous pouvez le faire :

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

Je ne suis pas un expert en matière de découpage, mais en général, si vous essayez de découper un tableau et que les valeurs sont continues, vous obtenez une vue où la valeur du stride est modifiée.

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

Il est clair que ce mécanisme ne s'applique pas bien dans le cas d'un tableau d'indices. Par conséquent, c'est numpy qui devra effectuer la copie. Après tout, de nombreuses autres fonctions mathématiques matricielles s'appuient sur la taille, le stride et l'allocation continue de mémoire.

10voto

unutbu Points 222216

Si vous voulez sauter une ligne sur deux et une colonne sur deux, vous pouvez le faire avec le 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]])

tandis que 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 x 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 une indexation avancée, en utilisant quelque chose comme x[rows,:][:,columns]rows et columns sont des séquences. Cela va bien sûr vous donner une copie, et non une vue, de votre tableau original. C'est ce à quoi il faut s'attendre, puisqu'un tableau numpy utilise une mémoire contiguë (avec des pas constants), et qu'il n'y aurait aucun moyen de générer une vue avec des lignes et des colonnes arbitraires (puisque cela nécessiterait des pas non constants).

5voto

jsbueno Points 22212

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

Si vous voulez simplement sauter uniformément des colonnes ou des lignes, vous pouvez passer des tranches avec trois composants (c'est-à-dire start, stop, step).

Encore une fois, pour votre exemple ci-dessus :

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

En gros, il s'agit de trancher dans la première dimension, en commençant à l'indice 1, en s'arrêtant lorsque l'indice est égal ou supérieur à 4, et en ajoutant 2 à l'indice à chaque passage. La même chose pour la deuxième dimension. Encore une fois : cela ne fonctionne que pour des étapes constantes.

La syntaxe que vous avez obtenu pour faire quelque chose de tout à fait différent en interne - ce qui x[[1,3]][:,[1,3]] crée en fait un nouveau tableau comprenant uniquement les lignes 1 et 3 du tableau d'origine (ce qui est fait avec la fonction x[[1,3]] ), puis de le redécouper en tranches - créant ainsi un troisième tableau - comprenant uniquement les colonnes 1 et 3 du tableau précédent.

1 votes

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X