web-dev-qa-db-fra.com

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 souhaite extraire une sélection arbitraire de m lignes et de 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 lorsque je veux extraire une sous-matrice 2x2 qui est 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 se passe-t-il si je dois supprimer un autre mélange de lignes/colonnes? Que se passe-t-il si je dois supprimer les première et troisième lignes/lignes afin d’extraire le sous-masque [[5,7],[13,15]]? Il peut y avoir n'importe quelle composition de lignes/lignes. J'ai lu quelque part que je dois juste indexer mon tableau en utilisant des tableaux/listes d'index 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 difficilement lisible, bien que je puisse vivre avec cela. Si quelqu'un a une meilleure solution, j'aimerais certainement l'entendre.

Une autre chose est que je lis sur un forum que l’indexation des tableaux avec des tableaux force NumPy à faire une copie du tableau souhaité. Ainsi, le traitement avec des tableaux de grande taille peut poser problème. Pourquoi est-ce vrai/comment fonctionne ce mécanisme?

155
levesque

Comme Sven l'a mentionné, x[[[0],[2]],[1,3]] renverra les 0 et 2 lignes correspondant aux colonnes 1 et 3, tandis que x[[0,2],[1,3]] renverra les valeurs x [0,1] et x [2,3] d'un tableau. 

Il y a une fonction utile pour faire 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 à entrer dans tous ces crochets supplémentaires. 

54
Justin Peel

Pour répondre à cette question, nous devons examiner le fonctionnement de l'indexation d'un tableau multidimensionnel dans Numpy. Disons d'abord que vous avez le tableau x de votre question. Le tampon assigné à x contiendra 16 entiers croissants allant de 0 à 15. Si vous accédez à un élément, par exemple x[i,j], NumPy doit déterminer l'emplacement mémoire de cet élément par rapport au début du tampon. Ceci est effectué en calculant 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 tel que y = x[0:2,0:2], l'objet obtenu partagera le tampon sous-jacent 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 ne sont pas consécutives en mémoire.

NumPy résout ce problème en introduisant strides. Lors du calcul du décalage de 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 de la taille d'un int):

x.strides
(16, 4)

Lorsque y est extrait comme ci-dessus, NumPy ne crée pas de nouveau tampon, mais ne crée pas crée un nouvel objet tableau référençant le même tampon (sinon y serait simplement égal à x.) forme alors x et peut-être un décalage de départ différent dans la mémoire tampon, mais partagera les pas 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 résultat correct.

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

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

Oh, et vous avez presque oublié votre question: voici comment faire en sorte que l'indexation avec plusieurs listes fonctionne comme prévu:

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

Cela est dû au fait que les tableaux d'index sont broadcasted 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]
107
Sven Marnach

Je ne pense pas que x[[1,3]][:,[1,3]] soit difficilement 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 découpage, 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 foulée est modifiée.

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

Il est clair que ce mécanisme ne porte pas bien dans le cas d’un tableau d’index. Par conséquent, numpy devra faire la copie. Après tout, de nombreuses autres fonctions mathématiques matricielles reposent sur la taille, le pas et l’allocation continue de la mémoire.

11
Dat Chu

Si vous voulez ignorer toutes les lignes et toutes les 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 retourne 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 l'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 en tranches de base. Vous devrez utiliser l'indexation avancée, en utilisant quelque chose comme x[rows,:][:,columns], où rows et columns sont des séquences. Ceci va bien sûr vous donner une copie et non une vue de votre tableau d'origine. C'est ce à quoi on devrait s'attendre, car un tableau numpy utilise une mémoire contiguë (avec des pas constants) et il serait impossible de générer une vue avec des lignes et des colonnes arbitraires (car cela nécessiterait des pas non constants).

10
unutbu

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

Si vous souhaitez simplement ignorer les colonnes ou les lignes, vous pouvez transmettre 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: slice dans la première dimension, avec le début à l'index 1, s'arrête lorsque l'index est égal ou supérieur à 4 et ajoute 2 à l'index à chaque passage. Même chose pour la deuxième dimension. Encore une fois: cela ne fonctionne que pour des pas constants.

La syntaxe que vous avez à faire quelque chose de très différent en interne - ce que fait réellement x[[1,3]][:,[1,3]] est de créer un nouveau tableau comprenant uniquement les lignes 1 et 3 du tableau d'origine (fait avec la partie x[[1,3]]), puis de redimensionner ce dernier - créer un troisième tableau - comprenant uniquement les colonnes 1 et 3 du tableau précédent.

5
jsbueno

J'ai une question similaire ici: Écriture dans un sous-tableau d'un tableau de la manière la plus pythonienne. Python 2 .

Après la solution de post précédent pour votre cas, la solution se présente comme suit:

columns_to_keep = [1,3] 
rows_to_keep = [1,3]

Un en utilisant ix_:

x[np.ix_(rows_to_keep, columns_to_keep)] 

Lequel est:

array([[ 5,  7],
       [13, 15]])
3
Rafael Valero

Je ne sais pas si c'est efficace, mais vous pouvez utiliser range () pour découper les deux axes

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