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 a
consiste 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 a
ve 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 b
tiene 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 i
y j
, la matriz se b
ve 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 order
pará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)