web-dev-qa-db-fra.com

Différence entre la forme numpy.array (R, 1) et (R,)

Dans numpy, certaines opérations retournent sous la forme (R, 1) mais certaines retournent (R,). Cela rendra plus fastidieuse la multiplication de matrices car il est nécessaire d'utiliser reshape explicitement. Par exemple, étant donné une matrice M, si nous voulons faire numpy.dot(M[:,0], numpy.ones((1, R)))R est le nombre de lignes (bien entendu, le même problème se produit également au niveau des colonnes). Nous obtiendrons une erreur matrices are not aligned puisque M[:,0] est en forme (R,) mais numpy.ones((1, R)) est en forme (1, R).

Donc mes questions sont:

  1. Quelle est la différence entre forme (R, 1) et (R,). Je sais littéralement que c'est une liste de chiffres et une liste de listes où toute liste ne contient qu'un nombre. Pourquoi ne pas concevoir numpy de manière à privilégier la forme (R, 1) au lieu de (R,) pour faciliter la multiplication de matrices.

  2. Existe-t-il de meilleurs moyens pour l'exemple ci-dessus? Sans refaçonner explicitement comme ceci: numpy.dot(M[:,0].reshape(R, 1), numpy.ones((1, R)))

263
clwen

1. La signification des formes dans NumPy

Vous écrivez: "Je sais littéralement que c'est une liste de chiffres et une liste de listes où toute liste ne contient qu'un nombre", mais c'est une façon un peu inutile de penser à cela.

La meilleure façon de penser aux tableaux NumPy est qu’ils se composent de deux parties, un tampon de données qui n’est qu’un bloc d’éléments bruts et un view qui décrit comment interpréter le tampon de données.

Par exemple, si nous créons un tableau de 12 entiers:

_>>> a = numpy.arange(12)
>>> a
array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
_

Alors, a consiste en un tampon de données, organisé de la manière suivante:

_┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
_

et une vue décrivant comment interpréter les données:

_>>> a.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : True
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False
>>> a.dtype
dtype('int64')
>>> a.itemsize
8
>>> a.strides
(8,)
>>> a.shape
(12,)
_

Ici, la forme _(12,)_ signifie que le tableau est indexé par un index unique qui va de 0 à 11. Conceptuellement, si nous étiquetons cet index unique i, le tableau a ressemble à ceci:

_i= 0    1    2    3    4    5    6    7    8    9   10   11
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
_

Si nous remodelons un tableau, cela ne change pas le tampon de données. Au lieu de cela, il crée une nouvelle vue qui décrit une manière différente d'interpréter les données. Donc après:

_>>> b = a.reshape((3, 4))
_

le tableau b a le même tampon de données que a, mais il est maintenant indexé par deux indices allant de 0 à 2 et 0 à 3 respectivement. Si nous étiquetons les deux index i et j, le tableau b ressemble à ceci:

_i= 0    0    0    0    1    1    1    1    2    2    2    2
j= 0    1    2    3    0    1    2    3    0    1    2    3
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
_

ce qui signifie que:

_>>> b[2,1]
9
_

Vous pouvez voir que le deuxième index change rapidement et que le premier index change lentement. Si vous préférez que ce soit l'inverse, vous pouvez spécifier le paramètre order:

_>>> c = a.reshape((3, 4), order='F')
_

ce qui donne un tableau indexé comme ceci:

_i= 0    1    2    0    1    2    0    1    2    0    1    2
j= 0    0    0    1    1    1    2    2    2    3    3    3
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
_

ce qui signifie que:

_>>> c[2,1]
5
_

Vous devez maintenant comprendre ce que cela signifie pour un tableau d'avoir une forme avec une ou plusieurs dimensions de taille 1. Après:

_>>> d = a.reshape((12, 1))
_

le tableau d est indexé par deux index, le premier va de 0 à 11, et le second est toujours 0:

_i= 0    1    2    3    4    5    6    7    8    9   10   11
j= 0    0    0    0    0    0    0    0    0    0    0    0
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
_

et donc:

_>>> d[10,0]
10
_

Une dimension de longueur 1 est "libre" (dans un certain sens), donc rien ne vous empêche d'aller en ville:

_>>> e = a.reshape((1, 2, 1, 6, 1))
_

donnant un tableau indexé comme ceci:

_i= 0    0    0    0    0    0    0    0    0    0    0    0
j= 0    0    0    0    0    0    1    1    1    1    1    1
k= 0    0    0    0    0    0    0    0    0    0    0    0
l= 0    1    2    3    4    5    0    1    2    3    4    5
m= 0    0    0    0    0    0    0    0    0    0    0    0
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│  0 │  1 │  2 │  3 │  4 │  5 │  6 │  7 │  8 │  9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
_

et donc:

_>>> e[0,1,0,0,0]
6
_

Voir le documentation interne de NumPy pour plus de détails sur la manière dont les tableaux sont implémentés.

2. Que faire?

Puisque numpy.reshape crée simplement une nouvelle vue, vous ne devriez pas avoir peur de l'utiliser si nécessaire. C'est le bon outil à utiliser lorsque vous souhaitez indexer un tableau d'une manière différente.

Cependant, dans un long calcul, il est généralement possible de commencer par construire des tableaux ayant la "bonne" forme, afin de minimiser le nombre de modifications de forme et de transposition. Mais sans voir le contexte réel qui a conduit à la nécessité d'une refonte, il est difficile de dire ce qui devrait être changé.

L'exemple dans votre question est:

_numpy.dot(M[:,0], numpy.ones((1, R)))
_

mais ce n'est pas réaliste. Tout d'abord, cette expression:

_M[:,0].sum()
_

calcule le résultat plus simplement. Deuxièmement, y a-t-il vraiment quelque chose de spécial dans la colonne 0? Peut-être avez-vous réellement besoin:

_M.sum(axis=0)
_
462
Gareth Rees

La différence entre (R,) et (1,R) est littéralement le nombre d'indices que vous devez utiliser. ones((1,R)) est un tableau 2D qui n'a qu'une seule ligne. ones(R) est un vecteur. En règle générale, s'il n'est pas logique que la variable ait plus d'une ligne/colonne, vous devez utiliser un vecteur et non une matrice avec une dimension singleton.

Pour votre cas spécifique, il existe deux options:

1) Il suffit de faire du deuxième argument un vecteur. Ce qui suit fonctionne bien:

    np.dot(M[:,0], np.ones(R))

2) Si vous souhaitez utiliser des opérations matlab analogues à celles de la matrice, utilisez la classe matrix au lieu de ndarray. Toutes les matrices sont forcées de constituer des tableaux 2D, et l'opérateur * effectue la multiplication de matrice au lieu d'élément (vous n'avez donc pas besoin de points). D'après mon expérience, cela représente plus de problèmes que cela en vaut la peine, mais cela peut être agréable si vous êtes habitué à matlab.

14
Evan

La forme est un tuple. S'il n'y a qu'une seule dimension, la forme sera un nombre et juste vide après une virgule. Pour les dimensions 2+, il y aura un nombre après toutes les virgules.

# 1 dimension with 2 elements, shape = (2,). 
# Note there's nothing after the comma.
z=np.array([  # start dimension
    10,       # not a dimension
    20        # not a dimension
])            # end dimension
print(z.shape)

(2)

# 2 dimensions, each with 1 element, shape = (2,1)
w=np.array([  # start outer dimension 
    [10],     # element is in an inner dimension
    [20]      # element is in an inner dimension
])            # end outer dimension
print(w.shape)

(2,1)

6
Katie Jergens

Pour sa classe de tableaux de base, les tableaux 2D ne sont pas plus spéciaux que les tableaux 1d ou 3D. Certaines opérations permettent de conserver les dimensions, d’autres de les réduire, de les combiner ou même de les développer.

M=np.arange(9).reshape(3,3)
M[:,0].shape # (3,) selects one column, returns a 1d array
M[0,:].shape # same, one row, 1d array
M[:,[0]].shape # (3,1), index with a list (or array), returns 2d
M[:,[0,1]].shape # (3,2)

In [20]: np.dot(M[:,0].reshape(3,1),np.ones((1,3)))

Out[20]: 
array([[ 0.,  0.,  0.],
       [ 3.,  3.,  3.],
       [ 6.,  6.,  6.]])

In [21]: np.dot(M[:,[0]],np.ones((1,3)))
Out[21]: 
array([[ 0.,  0.,  0.],
       [ 3.,  3.,  3.],
       [ 6.,  6.,  6.]])

Autres expressions qui donnent le même tableau

np.dot(M[:,0][:,np.newaxis],np.ones((1,3)))
np.dot(np.atleast_2d(M[:,0]).T,np.ones((1,3)))
np.einsum('i,j',M[:,0],np.ones((3)))
M1=M[:,0]; R=np.ones((3)); np.dot(M1[:,None], R[None,:])

MATLAB a commencé avec seulement des tableaux 2D. Les versions les plus récentes autorisent davantage de dimensions, mais conservent la limite inférieure de 2. Toutefois, vous devez faire attention à la différence entre une matrice de lignes et une colonne, une de forme (1,3) v (3,1). Combien de fois avez-vous écrit [1,2,3].'? J'allais écrire row vector et column vector, mais avec cette contrainte 2d, il n'y a pas de vecteurs dans MATLAB - du moins pas au sens mathématique de vecteur comme étant 1d.

Avez-vous consulté np.atleast_2d (ainsi que les versions _1d et _3d)?

4
hpaulj

1) La raison pour ne pas préférer une forme de (R, 1) à (R,) est qu'elle complique inutilement les choses. En outre, pourquoi serait-il préférable que la forme (R, 1) soit définie par défaut pour un vecteur longueur-R au lieu de (1, R)? Mieux vaut rester simple et explicite lorsque vous avez besoin de dimensions supplémentaires.

2) Pour votre exemple, vous calculez un produit externe pour pouvoir le faire sans appel reshape à l'aide de np.outer:

np.outer(M[:,0], numpy.ones((1, R)))
0
bogatron