Diferencia entre numpy.array forma (R, 1) y (R,)

321

En numpy, algunas de las operaciones vuelven en forma (R, 1)pero algunas vuelven (R,). Esto hará que la multiplicación de matrices sea más tediosa ya que reshapese requiere explícito . Por ejemplo, dada una matriz M, si queremos hacer numpy.dot(M[:,0], numpy.ones((1, R)))dónde Restá el número de filas (por supuesto, el mismo problema también ocurre en forma de columna). Obtendremos un matrices are not alignederror ya que M[:,0]está en forma (R,)pero numpy.ones((1, R))está en forma (1, R).

Entonces mis preguntas son:

  1. ¿Cuál es la diferencia entre forma (R, 1)y (R,). Sé literalmente que es una lista de números y una lista de listas donde toda lista contiene solo un número. Solo me pregunto por qué no diseñar numpypara que favorezca la forma en (R, 1)lugar de (R,)una multiplicación de matriz más fácil.

  2. ¿Hay mejores formas para el ejemplo anterior? Sin explícitamente remodelar así:numpy.dot(M[:,0].reshape(R, 1), numpy.ones((1, R)))

clwen
fuente
3
Esto puede ayudar. Sin embargo, no con encontrar una solución práctica.
keyser
1
Solución adecuada: numpy.ravel (M [:, 0]) - convierte la forma de (R, 1) a (R,)
Andi R

Respuestas:

546

1. El significado de las formas en NumPy

Usted escribe: "Sé, literalmente, que es una lista de números y una lista de listas donde todas las listas contienen solo un número", pero esa es una forma poco útil de pensarlo.

La mejor manera de pensar sobre las matrices NumPy es que consisten en dos partes, un búfer de datos que es solo un bloque de elementos sin procesar y una vista que describe cómo interpretar el búfer de datos.

Por ejemplo, si creamos una matriz de 12 enteros:

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

Luego aconsiste en un búfer de datos, organizado de esta manera:

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

y una vista que describe cómo interpretar los datos:

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

Aquí la forma (12,) significa que la matriz está indexada por un único índice que va de 0 a 11. Conceptualmente, si etiquetamos este índice único i, la matriz se ave así:

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 reformamos una matriz, esto no cambia el búfer de datos. En cambio, crea una nueva vista que describe una forma diferente de interpretar los datos. Así que después:

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

la matriz btiene el mismo búfer de datos que a, pero ahora está indexada por dos índices que se ejecutan de 0 a 2 y de 0 a 3 respectivamente. Si etiquetamos los dos índices iy j, la matriz se bve así:

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 
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

Lo que significa que:

>>> b[2,1]
9

Puede ver que el segundo índice cambia rápidamente y el primer índice cambia lentamente. Si prefiere que esto sea al revés, puede especificar el orderparámetro:

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

lo que resulta en una matriz indexada de esta manera:

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 
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

Lo que significa que:

>>> c[2,1]
5

Ahora debería quedar claro lo que significa que una matriz tenga una forma con una o más dimensiones de tamaño 1. Después:

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

la matriz d está indexada por dos índices, el primero de los cuales va de 0 a 11, y el segundo índice siempre es 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 
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

y entonces:

>>> d[10,0]
10

Una dimensión de longitud 1 es "gratuita" (en cierto sentido), por lo que no hay nada que te impida ir a la ciudad:

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

dando una matriz indexada así:

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 
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘

y entonces:

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

Ver el documentación interna de NumPy para obtener más detalles sobre cómo se implementan las matrices.

2. ¿Qué hacer?

Ya que numpy.reshape solo crea una nueva vista, no debe tener miedo de usarla siempre que sea necesario. Es la herramienta adecuada para usar cuando desea indexar una matriz de una manera diferente.

Sin embargo, en un cálculo largo, generalmente es posible organizar la construcción de matrices con la forma "correcta" en primer lugar, y así minimizar el número de formas y transposiciones. Pero sin ver el contexto real que llevó a la necesidad de una reforma, es difícil decir qué se debe cambiar.

El ejemplo en su pregunta es:

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

Pero esto no es realista. Primero, esta expresión:

M[:,0].sum()

calcula el resultado de manera más simple. Segundo, ¿hay realmente algo especial en la columna 0? Quizás lo que realmente necesitas es:

M.sum(axis=0)
Gareth Rees
fuente
34
Esto fue extremadamente útil al pensar cómo se almacenan las matrices. ¡Gracias! Sin embargo, el acceso a una columna (o fila) de una matriz (2-d) para el cálculo adicional de la matriz es inconveniente ya que siempre tengo que cambiar la forma de la columna de manera adecuada. Cada vez que tengo que cambiar la forma de (n,) a (n, 1).
OfLettersAndNumbers
44
@SammyLee: Úselo newaxissi necesita otro eje, por ejemplo, a[:, j, np.newaxis]es la jcolumna th ay a[np.newaxis, i]es la ifila th.
Gareth Rees
Estoy tratando de trazar índices para obtener una mejor comprensión en papel de este modelo y parece que no lo entiendo, si tuviera una forma 2 x 2 x 4, entiendo que los primeros 2 pueden entenderse como 0000000011111111 y los últimos 4 pueden ser entendido como 0123012301230123, ¿qué pasa con el del medio?
PirateApp
3
Una manera fácil de pensar en esto es que numpy está funcionando exactamente como se esperaba aquí, pero la impresión de tuplas de Python puede ser engañosa. En el (R, )caso, la forma de la ndarrayes una tupla con elementos individuales, por lo que Python lo imprime con una coma final. Sin la coma adicional, sería ambiguo con una expresión entre paréntesis . A ndarraycon una sola dimensión puede considerarse como un vector de columna de longitud R. En el (R, 1)caso, la tupla tiene dos elementos, por lo que puede considerarse como un vector de fila (o una matriz con 1 fila de longitud R.)
Michael Yang
1
@ Alex-droidAD: Vea esta pregunta y sus respuestas.
Gareth Rees
16

La diferencia entre (R,)y (1,R)es literalmente la cantidad de índices que necesita usar. ones((1,R))es una matriz bidimensional que tiene solo una fila. ones(R)es un vector En general, si no tiene sentido que la variable tenga más de una fila / columna, debería usar un vector, no una matriz con una dimensión de un solo tono.

Para su caso específico, hay un par de opciones:

1) Simplemente haga que el segundo argumento sea un vector. Lo siguiente funciona bien:

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

2) Si desea operaciones de matriz como matlab, use la clase en matrixlugar de ndarray. Todas las matrices se ven obligadas a ser matrices en 2-D, y el operador *realiza la multiplicación de matrices en lugar de las de elementos (por lo que no necesita puntos). En mi experiencia, esto es más problema de lo que vale, pero puede ser bueno si estás acostumbrado a matlab.

Evan
fuente
Si. Esperaba un comportamiento más parecido a matlab. Echaré un vistazo a la matrixclase. ¿Cuál es el problema para la matrixclase BTW?
clwen
2
El problema con matrixes que es solo 2D, y también porque sobrecarga al operador '*', las funciones escritas ndarraypueden fallar si se usan en a matrix.
Evan
11

La forma es una tupla. Si solo hay 1 dimensión, la forma será un número y solo en blanco después de una coma. Para 2+ dimensiones, habrá un número después de todas las comas.

# 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
fuente
5

Para su clase de matriz base, las matrices 2d no son más especiales que las de 1d o 3d. Hay algunas operaciones que preservan las dimensiones, algunas que las reducen, otras las combinan o incluso las expanden.

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

Otras expresiones que dan la misma matriz

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 comenzó con solo matrices 2D. Las versiones más nuevas permiten más dimensiones, pero conservan el límite inferior de 2. Pero aún debe prestar atención a la diferencia entre una matriz de fila y una columna, una con forma (1,3)v (3,1). ¿Con qué frecuencia has escrito [1,2,3].'? Iba a escribir row vectory column vector, pero con esa restricción 2d, no hay vectores en MATLAB, al menos no en el sentido matemático del vector como 1d.

¿Has mirado np.atleast_2d(también versiones _1d y _3d)?

hpaulj
fuente
1

1) La razón para no preferir una forma de (R, 1)sobre (R,)es que complica innecesariamente las cosas. Además, ¿por qué sería preferible tener forma (R, 1)por defecto para un vector de longitud R en lugar de (1, R)? Es mejor mantenerlo simple y ser explícito cuando requiera dimensiones adicionales.

2) Para su ejemplo, está calculando un producto externo para que pueda hacer esto sin una reshapellamada usando np.outer:

np.outer(M[:,0], numpy.ones((1, R)))
bogatron
fuente
Gracias por la respuesta. 1) M[:,0]esencialmente obtiene todas las filas con el primer elemento, por lo que tiene más sentido tener(R, 1) que (1, R). 2) No siempre se puede reemplazar por np.outer, por ejemplo, punto para matriz en forma (1, R) y luego (R, 1).
clwen
1) Sí, eso podría ser la convención, pero eso la hace menos conveniente en otras circunstancias. La convención también podría ser que M [1, 1] devuelva una matriz de forma (1, 1), pero eso también suele ser menos conveniente que un escalar. Si realmente quieres un comportamiento similar a una matriz, entonces sería mejor usar un matrixobjeto. 2) En realidad, np.outerfunciona independientemente de si las formas son (1, R), (R, 1)o una combinación de los dos.
bogatron
0

Ya hay muchas buenas respuestas aquí. Pero para mí fue difícil encontrar algún ejemplo, donde la forma o la matriz pueden romper todo el programa.

Así que aquí está el uno:

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)

Esto fallará con el error:

ValueError: matriz 2D esperada, obtuvo 1D matriz en su lugar

pero si agregamos reshapea a:

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

esto funciona correctamente!

Mikhail_Sam
fuente