Introdução ao NumPy

Operações matriciais

Uma das principais vantagens da estrutura ndarray é sua habilidade de processamento matricial. Assim, para se multiplicar todos os elementos de um array por um escalar basta escrever a 5 por exemplo. Para se fazer qualquer operação lógica ou aritmética entre arrays, basta escrever a b*:


In [89]:
a = np.arange(20).reshape(5,4)
b = 2 * np.ones((5,4))    
c = np.arange(12,0,-1).reshape(4,3)
print('a=\n', a )
print('b=\n', b )
print('c=\n', c )


a=
 [[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]]
b=
 [[ 2.  2.  2.  2.]
 [ 2.  2.  2.  2.]
 [ 2.  2.  2.  2.]
 [ 2.  2.  2.  2.]
 [ 2.  2.  2.  2.]]
c=
 [[12 11 10]
 [ 9  8  7]
 [ 6  5  4]
 [ 3  2  1]]

Multiplicação de array por escalar: b x 5


In [90]:
b5 = 5 * b

print('b5=\n', b5 )


b5=
 [[ 10.  10.  10.  10.]
 [ 10.  10.  10.  10.]
 [ 10.  10.  10.  10.]
 [ 10.  10.  10.  10.]
 [ 10.  10.  10.  10.]]

Soma de arrays: a + b


In [91]:
amb = a + b

print('amb=\n', amb )


amb=
 [[  2.   3.   4.   5.]
 [  6.   7.   8.   9.]
 [ 10.  11.  12.  13.]
 [ 14.  15.  16.  17.]
 [ 18.  19.  20.  21.]]

Transposta de uma matriz: a.T

A transposta de uma matriz, troca os eixos das coordenadas. O elemento que estava na posição (r,c) vai agora estar na posição (c,r). O shape da matriz resultante ficará portanto com os valores trocados. A operação de transposição é feita através de cópia rasa, portanto é uma operação muito eficiente e deve ser utilizada sempre que possível.

Veja o exemplo a seguir:


In [92]:
at = a.T
print('a.shape=',a.shape )
print('a.T.shape=',a.T.shape )    
print('a=\n', a )
print('at=\n', at )


a.shape= (5, 4)
a.T.shape= (4, 5)
a=
 [[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]]
at=
 [[ 0  4  8 12 16]
 [ 1  5  9 13 17]
 [ 2  6 10 14 18]
 [ 3  7 11 15 19]]

Multiplicação de matrizes: a x c

A multiplicação de matrizes é feita através do operador dot. Para que a multiplicação seja possível é importante que o número de colunas do primeiro ndarray seja igual ao número de linhas do segundo. As dimensões do resultado será o número de linhas do primeiro ndarray pelo número de colunas do segundo ndarray. Confira:


In [93]:
ac = a.dot(c)

print('a.shape:',a.shape )
print('c.shape:',c.shape )
print('a=\n',a )
print('c=\n',c )
print('ac=\n', ac )
print('ac.shape:',ac.shape )


a.shape: (5, 4)
c.shape: (4, 3)
a=
 [[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]]
c=
 [[12 11 10]
 [ 9  8  7]
 [ 6  5  4]
 [ 3  2  1]]
ac=
 [[ 30  24  18]
 [150 128 106]
 [270 232 194]
 [390 336 282]
 [510 440 370]]
ac.shape: (5, 3)

Linspace e Arange

As funções do numpy linspace e arange tem o mesmo objetivo: gerar numpy.arrays linearmente espaçados em um intervalo indicado como parâmetro.

A diferença primordial entre essas funções é como será realizada a divisão no intervalo especificado. Na função linspace essa divisão é feita através da definição do intervalo fechado [inicio,fim], isto é, contém o início e o fim, e da quantidade de elementos que o numpy.array final terá. O passo portanto é calculado como (fim - inicio)/(n - 1). Dessa forma, se queremos gerar um numpy.array entre 0 e 1 com 10 elementos, utilizaremos o linspace da seguinte forma


In [94]:
# gera um numpy.array de 10 elementos, linearmente espaçados entre 0 a 1
print(np.linspace(0, 1.0, num=10).round(2) )


[ 0.    0.11  0.22  0.33  0.44  0.56  0.67  0.78  0.89  1.  ]

Já na função arange, define-se o intervalo semi-aberto [inicio,fim) e o passo que será dado entre um elemento e outro. Dessa forma, para gerar um numpy.array entre 0 e 1 com 10 elementos, temos que calcular o passo (0.1) e passar esse passo como parâmetro.


In [95]:
# gera um numpy.array linearmente espaçados entre 0 a 1 com passo 0.1

print(np.arange(0, 1.0, 0.1) )


[ 0.   0.1  0.2  0.3  0.4  0.5  0.6  0.7  0.8  0.9]

Confirme que a principal diferença entre os dois que pode ser verificada nos exemplos acima é que no linspace o limite superior da distribuição é inclusivo (intervalo fechado), enquanto no arange isso não ocorre (intervalo semi-aberto).

Funções indices e meshgrid

As funções indices e meshgrid são extremamente úteis na geração de imagens sintéticas e o seu aprendizado permite também entender as vantagens de programação matricial, evitando-se a varredura seqüencial da imagem muito usual na programação na linguagem C.

Operador indices em pequenos exemplos numéricos

A função indices recebe como parâmetros uma tupla com as dimensões (H,W) das matrizes a serem criadas. No exemplo a seguir, estamos gerando matrizes de 5 linhas e 10 colunas. Esta função retorna uma tupla de duas matrizes que podem ser obtidas fazendo suas atribuições como no exemplo a seguir onde criamos as matrizes r e c, ambas de tamanho (5,10), isto é, 5 linhas e 10 colunas:


In [96]:
r,c = np.indices( (5, 10) )
print('r=\n', r )
print('c=\n', c )


r=
 [[0 0 0 0 0 0 0 0 0 0]
 [1 1 1 1 1 1 1 1 1 1]
 [2 2 2 2 2 2 2 2 2 2]
 [3 3 3 3 3 3 3 3 3 3]
 [4 4 4 4 4 4 4 4 4 4]]
c=
 [[0 1 2 3 4 5 6 7 8 9]
 [0 1 2 3 4 5 6 7 8 9]
 [0 1 2 3 4 5 6 7 8 9]
 [0 1 2 3 4 5 6 7 8 9]
 [0 1 2 3 4 5 6 7 8 9]]

Note que a matriz r é uma matriz onde cada elemento é a sua coordenada linha e a matriz c é uma matriz onde cada elemento é a sua coordenada coluna. Desta forma, qualquer operação matricial feita com r e c, na realidade você está processando as coordenadas da matriz. Assim, é possível gerar diversas imagens sintéticas a partir de uma função de suas coordenadas.

Como o NumPy processa as matrizes diretamente, sem a necessidade de fazer um for explícito, a notação do programa fica bem simples e a eficiência também. O único inconveniente é o uso da memória para se calcular as matrizes de índices r e c. Iremos ver mais à frente que isto pode ser minimizado.

Por exemplo seja a função que seja a soma de suas coordenadas $f(r,c) = r + c$:


In [97]:
f = r + c
print('f=\n', f )


f=
 [[ 0  1  2  3  4  5  6  7  8  9]
 [ 1  2  3  4  5  6  7  8  9 10]
 [ 2  3  4  5  6  7  8  9 10 11]
 [ 3  4  5  6  7  8  9 10 11 12]
 [ 4  5  6  7  8  9 10 11 12 13]]

Ou ainda a função diferença entre a coordenada linha e coluna $f(r,c) = r - c$:


In [98]:
f = r - c
print('f=\n', f )


f=
 [[ 0 -1 -2 -3 -4 -5 -6 -7 -8 -9]
 [ 1  0 -1 -2 -3 -4 -5 -6 -7 -8]
 [ 2  1  0 -1 -2 -3 -4 -5 -6 -7]
 [ 3  2  1  0 -1 -2 -3 -4 -5 -6]
 [ 4  3  2  1  0 -1 -2 -3 -4 -5]]

Ou ainda a função $f(r,c) = (r + c) \% 2$ onde % é operador módulo. Esta função retorna 1 se a soma das coordenadas for ímpar e 0 caso contrário. É uma imagem no estilo de um tabuleiro de xadrez de valores 0 e 1:


In [99]:
f = (r + c) % 2
print('f=\n', f )


f=
 [[0 1 0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1 0 1]]

Ou ainda a função de uma reta $f(r,c) = (r = \frac{1}{2}c)$:


In [100]:
f = (r == c//2)
print('f=\n', f )


f=
 [[ True  True False False False False False False False False]
 [False False  True  True False False False False False False]
 [False False False False  True  True False False False False]
 [False False False False False False  True  True False False]
 [False False False False False False False False  True  True]]

Ou ainda a função parabólica dada pela soma do quadrado de suas coordenadas $f(r,c) = r^2 + c^2$:


In [101]:
f = r**2 + c**2
print('f=\n', f )


f=
 [[ 0  1  4  9 16 25 36 49 64 81]
 [ 1  2  5 10 17 26 37 50 65 82]
 [ 4  5  8 13 20 29 40 53 68 85]
 [ 9 10 13 18 25 34 45 58 73 90]
 [16 17 20 25 32 41 52 65 80 97]]

Ou ainda a função do círculo de raio 4, com centro em (0,0) $f(r,c) = (r^2 + c^2 < 4^2)$:


In [102]:
f = ((r**2 + c**2) < 4**2)
print('f=\n', f * 1 )


f=
 [[1 1 1 1 0 0 0 0 0 0]
 [1 1 1 1 0 0 0 0 0 0]
 [1 1 1 1 0 0 0 0 0 0]
 [1 1 1 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0]]

In [122]:
a = np.array([[0,1],[2,3]])

print('a = \n', a )
print()

print('np.resize(a,(1,7)) = \n', np.resize(a,(1,7)) )
print()

print('np.resize(a,(2,5)) = \n', np.resize(a,(2,5)) )


a = 
 [[0 1]
 [2 3]]

np.resize(a,(1,7)) = 
 [[0 1 2 3 0 1 2]]

np.resize(a,(2,5)) = 
 [[0 1 2 3 0]
 [1 2 3 0 1]]

Clip

A função clip substitui os valores de um array que estejam abaixo de um limiar mínimo ou que estejam acima de um limiar máximo, por esses limiares mínimo e máximo, respectivamente. Esta função é especialmente útil em processamento de imagens para evitar que os índices ultrapassem os limites das imagens.

Exemplos


In [123]:
a = np.array([11,1,2,3,4,5,12,-3,-4,7,4])
print('a = ',a )
print('np.clip(a,0,10) = ', np.clip(a,0,10) )


a =  [11  1  2  3  4  5 12 -3 -4  7  4]
np.clip(a,0,10) =  [10  1  2  3  4  5 10  0  0  7  4]

Exemplo com ponto flutuante

Observe que se os parâmetros do clip estiverem em ponto flutuante, o resultado também será em ponto flutuante:


In [124]:
a = np.arange(10).astype(np.int)
print('a=',a )
print('np.clip(a,2.5,7.5)=',np.clip(a,2.5,7.5) )


a= [0 1 2 3 4 5 6 7 8 9]
np.clip(a,2.5,7.5)= [ 2.5  2.5  2.5  3.   4.   5.   6.   7.   7.5  7.5]

Formatando arrays para impressão

Imprimindo arrays de ponto flutuante

Ao se imprimir arrays com valores em ponto flutuante, o NumPy em geral, imprime o array com muitas as casas decimais e com notação científica, o que dificulta a visualização.


In [125]:
A = np.exp(np.linspace(0.1,10,32)).reshape(4,8)/3000.
print('A: \n', A )


A: 
 [[ 0.     0.001  0.001  0.001  0.001  0.002  0.003  0.003]
 [ 0.005  0.007  0.009  0.012  0.017  0.023  0.032  0.044]
 [ 0.061  0.084  0.116  0.159  0.219  0.301  0.415  0.571]
 [ 0.785  1.081  1.487  2.047  2.817  3.876  5.335  7.342]]

É possível diminuir o número de casas decimais e suprimir a notação exponencial utilizando a função set_printoption do numpy:


In [126]:
np.set_printoptions(suppress=True, precision=3)

print('A: \n', A )


A: 
 [[ 0.     0.001  0.001  0.001  0.001  0.002  0.003  0.003]
 [ 0.005  0.007  0.009  0.012  0.017  0.023  0.032  0.044]
 [ 0.061  0.084  0.116  0.159  0.219  0.301  0.415  0.571]
 [ 0.785  1.081  1.487  2.047  2.817  3.876  5.335  7.342]]

Imprimindo arrays binários

Array booleanos são impressos com as palavras True e False, como no exemplo a seguir:


In [127]:
A = np.random.rand(5,10) > 0.5
print('A = \n', A )


A = 
 [[ True  True  True  True False  True  True False  True False]
 [False  True False False False  True  True False False False]
 [ True False  True False False False  True False False  True]
 [False False False  True  True False  True  True  True  True]
 [False  True  True False  True False False  True  True False]]

Para facilitar a visualização destes arrays, é possível converter os valores para inteiros utilizando o método astype(int):


In [128]:
print ('A = \n', A.astype(int))


A = 
 [[1 1 1 1 0 1 1 0 1 0]
 [0 1 0 0 0 1 1 0 0 0]
 [1 0 1 0 0 0 1 0 0 1]
 [0 0 0 1 1 0 1 1 1 1]
 [0 1 1 0 1 0 0 1 1 0]]