1. O significado das formas no NumPy
Você escreve: "Eu sei literalmente que é uma lista de números e uma lista de listas em que toda a lista contém apenas um número", mas essa é uma maneira inútil de pensar sobre isso.
A melhor maneira de pensar sobre matrizes NumPy é que elas consistem em duas partes, um buffer de dados que é apenas um bloco de elementos brutos e uma exibição que descreve como interpretar o buffer de dados.
Por exemplo, se criarmos uma matriz de 12 números inteiros:
>>> a = numpy.arange(12)
>>> a
array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
Em seguida, a
consiste em um buffer de dados, organizado assim:
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
e uma visão que descreve como interpretar os dados:
>>> 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,)
Aqui, a forma (12,)
significa que a matriz é indexada por um único índice, que varia de 0 a 11. Conceitualmente, se rotularmos esse único índice i
, a matriz terá a seguinte a
aparência:
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 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
Se remodelarmos uma matriz, isso não altera o buffer de dados. Em vez disso, ele cria uma nova exibição que descreve uma maneira diferente de interpretar os dados. Então depois:
>>> b = a.reshape((3, 4))
a matriz b
possui o mesmo buffer de dados que a
, mas agora é indexada por dois índices que variam de 0 a 2 e 0 a 3, respectivamente. Se rotularmos os dois índices i
e j
, a matriz será b
assim:
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 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
o que significa que:
>>> b[2,1]
9
Você pode ver que o segundo índice muda rapidamente e o primeiro índice muda lentamente. Se você preferir que isso seja o contrário, você pode especificar o order
parâmetro:
>>> c = a.reshape((3, 4), order='F')
o que resulta em uma matriz indexada assim:
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 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
o que significa que:
>>> c[2,1]
5
Agora deve ficar claro o que significa para uma matriz ter uma forma com uma ou mais dimensões de tamanho 1. Depois:
>>> d = a.reshape((12, 1))
a matriz d
é indexada por dois índices, o primeiro dos quais é executado de 0 a 11 e o segundo índice é sempre 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 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
e entao:
>>> d[10,0]
10
Uma dimensão de comprimento 1 é "livre" (em certo sentido), então não há nada que o impeça de ir à cidade:
>>> e = a.reshape((1, 2, 1, 6, 1))
dando uma matriz indexada assim:
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 │
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
e entao:
>>> e[0,1,0,0,0]
6
Consulte a documentação interna do NumPy para obter mais detalhes sobre como as matrizes são implementadas.
2. O que fazer?
Como numpy.reshape
apenas cria uma nova exibição, você não deve ter medo de usá-la sempre que necessário. É a ferramenta certa a ser usada quando você deseja indexar uma matriz de uma maneira diferente.
No entanto, em um cálculo longo, geralmente é possível organizar matrizes com a forma "correta" em primeiro lugar e, assim, minimizar o número de remodelações e transposições. Mas sem ver o contexto real que levou à necessidade de uma remodelação, é difícil dizer o que deve ser mudado.
O exemplo na sua pergunta é:
numpy.dot(M[:,0], numpy.ones((1, R)))
mas isso não é realista. Primeiro, esta expressão:
M[:,0].sum()
calcula o resultado de maneira mais simples. Segundo, há realmente algo de especial na coluna 0? Talvez o que você realmente precise seja:
M.sum(axis=0)