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

321

Dans numpy, certaines opérations reviennent en forme (R, 1)mais d'autres reviennent (R,). Cela rendra la multiplication de la matrice plus fastidieuse car explicite reshapeest requise. Par exemple, étant donné une matrice M, si nous voulons faire numpy.dot(M[:,0], numpy.ones((1, R)))Rest le nombre de lignes (bien sûr, le même problème se produit également par colonne). Nous obtiendrons une matrices are not alignederreur car M[:,0]est en forme (R,)mais numpy.ones((1, R))est en forme (1, R).

Mes questions sont donc:

  1. Quelle est la différence entre la forme (R, 1)et (R,). Je sais littéralement que c'est une liste de nombres et une liste de listes où toute la liste ne contient qu'un nombre. Je me demande simplement pourquoi ne pas concevoir de numpysorte qu'il favorise la forme (R, 1)au lieu de (R,)faciliter la multiplication de la matrice.

  2. Y a-t-il de meilleures façons pour l'exemple ci-dessus? Sans remodeler explicitement comme ceci:numpy.dot(M[:,0].reshape(R, 1), numpy.ones((1, R)))

clwen
la source
3
Cela pourrait aider. Mais pas pour trouver une solution pratique.
keyser
1
Solution appropriée: numpy.ravel (M [:, 0]) - convertit la forme de (R, 1) en (R,)
Andi R

Réponses:

546

1. La signification des formes dans NumPy

Vous écrivez: «Je sais que c'est littéralement une liste de nombres et une liste de listes où toute la liste ne contient qu'un nombre» mais c'est un peu une façon inutile de penser à ce sujet.

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

Se acompose alors d'un tampon de données, arrangé quelque chose comme ceci:

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

et une vue qui décrit 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 aressemble à 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 ba le même tampon de données que a, mais maintenant il est indexé par deux indices qui vont de 0 à 2 et de 0 à 3 respectivement. Si nous étiquetons les deux indices iet j, le tableau bressemble à 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 orderparamètre:

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

ce qui se traduit par 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

Il doit maintenant être clair ce que 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 dest indexé par deux indices, dont le premier va de 0 à 11, et le deuxième 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

Consultez la documentation des composants internes NumPy pour plus de détails sur la façon dont les tableaux sont implémentés.

2. Que faire?

Comme il numpy.reshapesuffit de créer une nouvelle vue, vous ne devriez pas avoir peur de l'utiliser chaque fois que nécessaire. C'est le bon outil à utiliser lorsque vous souhaitez indexer un tableau d'une manière différente.

Cependant, dans un calcul long, il est généralement possible d'arranger pour construire des tableaux avec la "bonne" forme en premier lieu, et ainsi minimiser le nombre de remodelages et de transpositions. 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 de 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 que ce dont vous avez réellement besoin est:

M.sum(axis=0)
Gareth Rees
la source
34
Cela a été extrêmement utile pour réfléchir à la façon dont les tableaux sont stockés. Je vous remercie! L'accès à une colonne (ou une ligne) d'une matrice (2D) pour un calcul matriciel supplémentaire n'est cependant pas pratique car je dois toujours remodeler la colonne de manière appropriée. Chaque fois que je dois changer la forme de (n,) à (n, 1).
OfLettersAndNumbers
4
@SammyLee: à utiliser newaxissi vous avez besoin d'un autre axe, par exemple a[:, j, np.newaxis]la jth colonne aet a[np.newaxis, i]la ith ligne.
Gareth Rees
j'essaie de tracer des indices pour obtenir une meilleure compréhension sur papier par ce modèle et je ne semble pas l'obtenir, si j'avais une forme 2 x 2 x 4, je comprends que les 2 premiers peuvent être compris comme 0000000011111111 et les 4 derniers peuvent être compris comme 0123012301230123 qu'arrive-t-il à celui du milieu?
PirateApp
3
Un moyen facile de penser à cela est que numpy fonctionne exactement comme prévu ici, mais l'impression de tuples par Python peut être trompeuse. Dans le (R, )cas, la forme de ndarrayest un tuple avec un seul élément, est donc imprimé par Python avec une virgule de fin. Sans la virgule supplémentaire, ce serait ambigu avec une expression entre parenthèses . Un ndarrayavec une seule dimension peut être considéré comme un vecteur de colonne de longueur R. Dans le (R, 1)cas, le tuple a deux éléments, il peut donc être considéré comme un vecteur de ligne (ou une matrice avec 1 ligne de longueur R.
Michael Yang
1
@ Alex-droidAD: Voir cette question et ses réponses.
Gareth Rees
16

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 se trouve n'avoir qu'une seule ligne. ones(R)est un vecteur. En général, si la variable n'a pas de sens d'avoir plus d'une ligne / colonne, vous devez utiliser un vecteur, pas une matrice avec une dimension singleton.

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

1) Faites simplement du deuxième argument un vecteur. Les éléments suivants fonctionnent bien:

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

2) Si vous voulez des opérations matricielles comme la matrice, utilisez la classe à la matrixplace de ndarray. Toutes les matricies sont forcées à être des tableaux 2D, et l'opérateur *fait la multiplication de la matrice au lieu de l'élément (donc vous n'avez pas besoin de point). D'après mon expérience, c'est plus de problèmes que cela en vaut la peine, mais cela peut être bien si vous avez l'habitude de matlab.

Evan
la source
Oui. Je m'attendais à un comportement plus matlab. Je vais jeter un oeil à la matrixclasse. Quel est le problème pour la matrixclasse BTW?
clwen
2
Le problème matrixest que ce n'est que 2D, et aussi parce qu'il surcharge l'opérateur '*', les fonctions écrites pour ndarraypeuvent échouer si elles sont utilisées sur a matrix.
Evan
11

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)

Katie Jergens
la source
5

Pour sa classe de tableau de base, les tableaux 2D ne sont pas plus spéciaux que les tableaux 1D ou 3D. Il y a des opérations qui préservent les dimensions, certaines qui les réduisent, d'autres les combinent ou même les développent.

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 plus récentes autorisent plus de dimensions, mais conservent la limite inférieure de 2. Mais vous devez toujours faire attention à la différence entre une matrice de lignes et une colonne, une de forme (1,3)v (3,1). À quelle fréquence avez-vous écrit [1,2,3].'? J'allais écrire row vectoret column vector, mais avec cette contrainte 2d, il n'y a pas de vecteurs dans MATLAB - du moins pas au sens mathématique du vecteur comme étant 1d.

Avez-vous regardé np.atleast_2d(également les versions _1d et _3d)?

hpaulj
la source
1

1) La raison de ne pas préférer une forme de (R, 1)sur (R,)est qu'elle complique inutilement les choses. Par ailleurs, pourquoi serait-il préférable d'avoir une forme (R, 1)par défaut pour un vecteur longueur-R au lieu de (1, R)? Il vaut mieux rester simple et explicite lorsque vous avez besoin de dimensions supplémentaires.

2) Pour votre exemple, vous calculez un produit externe, vous pouvez donc le faire sans reshapeappel en utilisant np.outer:

np.outer(M[:,0], numpy.ones((1, R)))
bogatron
la source
Merci d'avoir répondu. 1) M[:,0]obtient essentiellement toutes les lignes avec le premier élément, il est donc plus logique d'avoir (R, 1)que (1, R). 2) Il n'est pas toujours remplaçable, par np.outerexemple, par un point pour la matrice de forme (1, R) puis (R, 1).
clwen
1) Oui, cela pourrait être la convention mais cela la rend moins pratique dans d'autres circonstances. La convention pourrait également être que M [1, 1] renvoie un tableau de formes (1, 1), mais cela est également généralement moins pratique qu'un scalaire. Si vous voulez vraiment un comportement matriciel, vous feriez mieux d'utiliser un matrixobjet. 2) En fait, np.outerfonctionne indépendamment du fait que les formes sont (1, R), (R, 1)ou une combinaison des deux.
bogatron
0

Il y a déjà beaucoup de bonnes réponses ici. Mais pour moi, il était difficile de trouver un exemple, où la forme ou le tableau peut casser tout le programme.

Voici donc celui-ci:

import numpy as np
a = np.array([1,2,3,4])
b = np.array([10,20,30,40])


from sklearn.linear_model import LinearRegression
regr = LinearRegression()
regr.fit(a,b)

Cela échouera avec une erreur:

ValueError: tableau 2D attendu, tableau 1D à la place

mais si on ajoute reshapeà a:

a = np.array([1,2,3,4]).reshape(-1,1)

cela fonctionne correctement!

Mikhail_Sam
la source