Diferença entre a forma numpy.array (R, 1) e (R,)


320

Em numpy, algumas das operações retornam em forma, (R, 1)mas algumas retornam (R,). Isso tornará a multiplicação de matrizes mais tediosa, pois reshapeé necessário explícito . Por exemplo, dada uma matriz M, se queremos fazer numpy.dot(M[:,0], numpy.ones((1, R)))onde Restá o número de linhas (é claro, o mesmo problema também ocorre em colunas). Iremos receber um matrices are not alignederro, pois M[:,0]está em forma, (R,)mas numpy.ones((1, R))está em forma (1, R).

Então, minhas perguntas são:

  1. Qual é a diferença entre shape (R, 1)e (R,). Eu sei literalmente que é lista de números e lista de listas onde toda a lista contém apenas um número. Apenas me pergunto por que não projetar numpypara favorecer a forma em (R, 1)vez de (R,)facilitar a multiplicação da matriz.

  2. Existem maneiras melhores para o exemplo acima? Sem remodelar explicitamente assim:numpy.dot(M[:,0].reshape(R, 1), numpy.ones((1, R)))


3
Isso pode ajudar. Não é encontrar uma solução prática.
Keyser

1
Solução apropriada: numpy.ravel (M [:, 0]) - converte a forma de (R, 1) para (R,)
Andi R

Respostas:


545

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, aconsiste 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 aaparê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 bpossui 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 ie j, a matriz será bassim:

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 orderparâ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.reshapeapenas 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)

34
Isso foi extremamente útil para pensar em como as matrizes são armazenadas. Obrigado! Acessar uma coluna (ou linha) de uma matriz (2-d) para posterior cálculo da matriz é inconveniente, pois eu sempre tenho que remodelar a coluna adequadamente. Toda vez que eu tenho que mudar a forma de (n,) para (n, 1).
OfLettersAndNumbers

3
@ SammyLee: use newaxisse você precisar de outro eixo, por exemplo, a[:, j, np.newaxis]é a jth coluna de ae a[np.newaxis, i]é a ith row.
Gareth Rees

Estou tentando plotar índices para obter uma melhor compreensão no papel por este modelo e não parece entendê-lo, se eu tivesse uma forma 2 x 2 x 4, entendo que os 2 primeiros podem ser entendidos como 0000000011111111 e os 4 últimos podem ser entendido como 0123012301230123 o que acontece com o meio?
PirateApp

3
Uma maneira fácil de pensar nisso é que o numpy está funcionando exatamente como o esperado aqui, mas a impressão de tuplas no Python pode ser enganosa. No (R, )caso, a forma do ndarrayé uma tupla com um único elemento; portanto, é impressa pelo Python com uma vírgula à direita. Sem a vírgula extra, seria ambíguo com uma expressão entre parênteses . Um ndarraycom uma única dimensão pode ser considerado como um vetor de comprimento de coluna R. No (R, 1)caso, o tuplo tem dois elementos, por isso podem ser consideradas como um vector da linha (ou uma matriz com uma linha de comprimento R.
Michael Yang

1
@ Alex-droidAD: Veja esta pergunta e suas respostas.
Gareth Rees

16

A diferença entre (R,)e (1,R)é literalmente o número de índices que você precisa usar. ones((1,R))é uma matriz 2D que possui apenas uma linha. ones(R)é um vetor. Geralmente, se não faz sentido que a variável tenha mais de uma linha / coluna, você deve usar um vetor, não uma matriz com uma dimensão singleton.

Para o seu caso específico, existem algumas opções:

1) Apenas faça do segundo argumento um vetor. O seguinte funciona bem:

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

2) Se você deseja operações matlab como matriz, use a classe em matrixvez de ndarray. Todas as matrizes são forçadas a serem matrizes 2-D e o operador *faz a multiplicação de matrizes em vez de elementos (por isso, não é necessário ponto). Na minha experiência, isso vale mais a pena, mas pode ser bom se você estiver acostumado com o matlab.


Sim. Eu estava esperando um comportamento mais semelhante ao matlab. Vou dar uma olhada na matrixaula. Qual é o problema da matrixclasse BTW?
clwen

2
O problema matrixé que ele é apenas 2D e também porque sobrecarrega o operador '*', as funções escritas para ndarraypodem falhar se usadas em a matrix.
Evan

11

A forma é uma tupla. Se houver apenas uma dimensão, a forma será um número e ficará em branco após uma vírgula. Para 2 ou mais dimensões, haverá um número depois de todas as vírgulas.

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


5

Para sua classe de matriz base, as matrizes 2d não são mais especiais que as 1d ou 3d. Existem operações que preservam as dimensões, outras que as reduzem, outras as combinam ou até as expandem.

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

Outras expressões que dão a mesma 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,:])

O MATLAB começou com apenas matrizes 2D. As versões mais recentes permitem mais dimensões, mas mantêm o limite inferior de 2. Mas você ainda precisa prestar atenção à diferença entre uma matriz de linhas e uma coluna, uma com a forma (1,3)v (3,1). Quantas vezes você escreveu [1,2,3].'? Eu ia escrever row vectore column vector, mas com essa restrição 2D, não existem vetores no MATLAB - pelo menos não no sentido matemático do vetor como sendo 1d.

Você já viu np.atleast_2d(também as versões _1d e _3d)?


1

1) A razão para não preferir uma forma de (R, 1)excesso (R,)é que ela complica desnecessariamente as coisas. Além disso, por que seria preferível ter forma (R, 1)por padrão para um vetor comprimento-R em vez de (1, R)? É melhor mantê-lo simples e ser explícito quando você precisar de dimensões adicionais.

2) No seu exemplo, você está computando um produto externo para poder fazer isso sem uma reshapechamada usando np.outer:

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

Obrigado pela resposta. 1) M[:,0]está essencialmente obtendo todas as linhas com o primeiro elemento, por isso faz mais sentido ter do (R, 1)que (1, R). 2) Nem sempre é substituível por np.outer, por exemplo, ponto para matriz em forma (1, R) e depois (R, 1).
clwen

1) Sim, essa poderia ser a convenção, mas a torna menos conveniente em outras circunstâncias. A convenção também poderia ser para M [1, 1] retornar uma matriz de forma (1, 1), mas isso também é geralmente menos conveniente que um escalar. Se você realmente deseja um comportamento de matriz, é melhor usar um matrixobjeto. 2) Na verdade, np.outerfunciona independentemente do facto das formas são (1, R), (R, 1), ou uma combinação dos dois.
bogatron

0

Já existem muitas boas respostas aqui. Mas, para mim, foi difícil encontrar algum exemplo, onde a forma ou matriz possa quebrar todo o programa.

Então aqui está o seguinte:

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)

Isso falhará com erro:

ValueError: matriz 2D esperada, obteve matriz 1D

mas se adicionarmos reshapea a:

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

isso funciona corretamente!

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.