Ajuda Adessowiki | Introdução Python Adessowiki

Introdução ao Numpy no ambiente Adessowiki

O que é NumPy

NumPy é o pacote fundamental para a computação científica com Python. Ele estende os dados do Python para o ndarray, uma implementação muito eficiente de vetores n-dimensionais. Este pacote é muito apropriado para processamento de imagens n-dimensionais, como sinais unidimensionais, imagens em níveis de cinza, coloridas, tomográficas e sinais de vídeo.

Referências

Existe uma farta documentação on-line do NumPy. Aqui estão as principais:

A versão do NumPy instalado no Adessowiki quando esta página foi salva pela última vez é:

1 import numpy as np
2 print 'Versão do Python:', sys.version
3 print 'Versão do NumPy:', np.version.version
Versão do Python: 2.7.3 (default, Apr 10 2013, 06:20:15) 
[GCC 4.6.3]
Versão do NumPy: 1.7.1

O tipo ndarray

O tipo ndarray, ou apenas array é um arranjo de itens homogêneos de dimensionalidade N, indexados por uma tupla de N inteiros. Existem 3 informações essenciais associadas ao ndarray: o tipo dos dados, suas dimensões e seus dados em si. A propriedade dtype permite saber o tipo de dados enquanto que shape é uma tupla que indica o tamanho de cada dimensão do arranjo. O acesso aos dados em si deve ser feito por indexação, por fatiamento ou pela variável em si.

Existem várias maneiras de criar uma variável do tipo ndarray. Por exemplo, é possível criar uma a partir de uma lista (1D) ou lista de listas usando a função array. O tipo de matriz resultante é deduzida a partir do tipo de elementos nas sequências.

Veja a seguir um vetor de inteiros com 5 elementos. Note que o vetor é uma linha com 5 colunas. Observe também que o shape é uma tupla de um único elemento (veja a vírgula que aparece por ser uma tupla).

1 a = np.array( [2,3,4,-1,-2] )
2 print 'Dimensões: ', a.shape
3 print 'Tipo dos elementos: ', a.dtype
4 print 'Imprimindo o array completo:\n',a
Dimensões:  (5,)
Tipo dos elementos:  int64
Imprimindo o array completo:
[ 2  3  4 -1 -2]

Veja uma matriz bidimensional de dados ponto flutuante de 2 linhas e 3 colunas. Observe que a tupla do shape aumenta para a esquerda. Assim o último elemento da tupla do shape indica o número de colunas, a penúltima o número de linhas. Assim se quisermos sempre buscar o número de colunas, independentemente do número de dimensões, shape[-1] informa sempre o número de colunas, shape[-2], o número de linhas.

1 b = np.array( [ [1.5,2,3],
2                 [4.2,5,6] ] )
3 print 'Uma matriz bidimensional, dimensões: %s; tipo dos elementos: %s' % (b.shape, b.dtype)
4 print 'Número de colunas:', b.shape[-1]
5 print 'Número de linhas:', b.shape[-2]
6 print b
Uma matriz bidimensional, dimensões: (2, 3); tipo dos elementos: float64
Número de colunas: 3
Número de linhas: 2
[[ 1.5  2.   3. ]
 [ 4.2  5.   6. ]]

Manipulação de arrays

Criando arrays inicializados

É possível um array de zeros, uns ou valores não inicializados usando as funções zeros, ones ou empty. As dimensões do array são obrigatórias e é dado por tupla e o tipo dos elementos é opcional, sendo que o default é tipo float.

1 d = zeros( (2,4) )
2 print(d)
3 d = ones( (2,3,5), dtype=int16 )
4 print(d)
5 
6 print(' ')
7 print('Array não inicializado')
8 d = empty( (2,3), 'bool' )
9 print(d)
[[ 0.  0.  0.  0.]
 [ 0.  0.  0.  0.]]
[[[1 1 1 1 1]
  [1 1 1 1 1]
  [1 1 1 1 1]]

 [[1 1 1 1 1]
  [1 1 1 1 1]
  [1 1 1 1 1]]]
 
Array não inicializado
[[ True  True False]
 [ True  True  True]]

Criando arrays com valores sequenciais

As funções arange e linspace geram um vetor sequencial. Eles diferem apenas nos parâmetros. Enquanto o arange gera uma sequência a partir dos valores inicial (includente e opcional), final( excludente) e passo (opcional), linspace gera uma sequência com valores inicial e final e número de elementos. Note as diferenças nos exemplos a seguir:

1 print 'arange( 10) = ', arange(10)
2 print 'arange( 3, 8) = ', arange(3,8)
3 print 'arange( 0, 2, 0.5) = ', arange(0, 2, 0.5)
4 print 'linspace( 0, 2, 5 ) = ', linspace( 0, 2, 5 )
arange( 10) =  [0 1 2 3 4 5 6 7 8 9]
arange( 3, 8) =  [3 4 5 6 7]
arange( 0, 2, 0.5) =  [ 0.   0.5  1.   1.5]
linspace( 0, 2, 5 ) =  [ 0.   0.5  1.   1.5  2. ]

Fatiamento

Uma forma muito eficiente e poderosa de acessar um subarray em NumPy é através da indexação por fatiamento (slicing). A sintaxe do fatiamento é semelhante à do arange porém seus parâmetros são separados por dois pontos. Veja os exemplos a seguir. Eles mostram como selecionar um subarray, uma única linha ou coluna, ou uma subamostragem no array. Lembrar que o processamento pode ser sempre n-dimensional.

Criando array de 3 linhas e 10 colunas. Lembrar que a indexação sempre começa a partir de 0.

1 a = array([[0,1,2,3,4,5,6,7,8,9],
2            [1,2,3,4,5,6,7,8,9,0],
3            [2,3,4,5,6,7,8,9,0,1]])
4 print a
5 print 'dimensões: ', a.shape
[[0 1 2 3 4 5 6 7 8 9]
 [1 2 3 4 5 6 7 8 9 0]
 [2 3 4 5 6 7 8 9 0 1]]
dimensões:  (3, 10)

Fatiando a linha 1 e colunas 3 e 4. O resultado continua sendo array bidimensional.

1 print a[1:, 3:5]
[[4 5]
 [5 6]]

Subamostragem, linhas pares e colunas ímpares:

1 print a[::2,1::2]
[[1 3 5 7 9]
 [3 5 7 9 1]]

Selecionando a coluna 3, porém retornando um vetor unidimensional:

1 print a[:, 3]
[3 4 5]

Selecionando a linha 1 e retornando vetor unidimensional:

1 print a[1,:]
[1 2 3 4 5 6 7 8 9 0]

Outra forma é interpretar que uma matriz bidimensional é um vetor de vetores linha e se deseja selecionar o vetor linha 1:

1 print a[1]
[1 2 3 4 5 6 7 8 9 0]

Se quando se extrar uma linha ou coluna, for desejável que o resultado seja bidimensional, há necessidade de especificar explicitamente o índice final (excludente) como nos exemplos a seguir, extraindo a coluna 3 e a linha 1:

1 print a[:, 3:4]
2 print
3 print a[1:2, :]
[[3]
 [4]
 [5]]

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

É possível espelhar verticalmente ou horizontalmente o array utilizando passo -1 de acordo com a dimensão:

1 print a[:,::-1]
2 print
3 print a[::-1,:]
[[9 8 7 6 5 4 3 2 1 0]
 [0 9 8 7 6 5 4 3 2 1]
 [1 0 9 8 7 6 5 4 3 2]]

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

Copiando variáveis ndarray

O ndarray foi projetado para acesso otimizado a uma grande quantidade de dados. Neste sentido, os conceitos descritos a seguir sobre as três formas de cópias entre variáveis ditas sem cópia, cópia rasa (shallow) e cópia profunda (deep) são fundamentais para uma codificação eficiente. Podemos dizer que um ndarray possui o cabeçalho que contém dados pelas informações sobre o tipo do elemento, a dimensionalidade (shape) e passo ou deslocamento para o próximo elemento (strides) e os dados raster em si. A tabela a seguir mostra a situação do cabeçalho e dos dados nos três tipos de cópias.

Tipo Cabeçalho: Type, Shape, Strides Dados raster Exemplo
Sem cópia, apenas ref apontador original apontador original a = b
Cópia rasa novo apontador original b = a.reshape, slicing, a.T
Cópia profunda novo novo a = array(b)

Sem cópia explícita, apenas referência

No caso abaixo, usaremos o comando normal de igual como atribuição do array a para o array b. Verifica-se que tanto o shape como os dados de b são os mesmos de a. Tudo se passa como b fosse apenas um apontador para a. Qualquer modificação em b é refletida em a.

 1 a = arange(6)
 2 b = a
 3 print "a =\n",a
 4 print "b =\n",b
 5 b.shape = (2,3)                           # mudança no shape de b,
 6 print "\na shape =",a.shape               # altera o shape de a
 7 
 8 b[0,0] = -1                               # mudança no conteúdo de b
 9 print "a =\n",a                             # altera o conteudo de a
10 
11 print "\nid de a = ",id(a)                # id é um identificador único de objeto
12 print "id de b = ",id(b)                  # a e b possuem o mesmo id
a =
[0 1 2 3 4 5]
b =
[0 1 2 3 4 5]

a shape = (2, 3)
a =
[[-1  1  2]
 [ 3  4  5]]

id de a =  139852926454256
id de b =  139852926454256

Observe que mesmo no retorno de uma função, a cópia explícita pode não acontecer. Veja o exemplo a seguir de uma função que apenas retorna a variável de entrada:

1 def cc(a):
2   return a
3 b = cc(a)
4 print "id de a = ",id(a)
5 print "id de b = ",id(b)
id de a =  139852926454256
id de b =  139852926454256

Cópia rasa

A cópia rasa é muito útil e extensivamente utilizada. É usada quando se quer indexar o array original através da mudança de dimensionalidade ou do refatiamento, porém sem a necessidade de realizar uma cópia dos dados raster. Desta forma consegue-se uma otimização no acesso ao array n-dimensional. Existem várias formas onde a cópia rasa acontece: 1) no caso do reshape onde o número de elementos do ndarray é o mesmo, porém sua dimensionalidade é alterada, e 2) no caso de refatiamento onde um subarray é indexado, 3) no caso de transposição do array, entre outros.

O exemplo a seguir mostrar inicialmente a criação de um vetor unidimensional sequencial sendo "visto" de forma bidimensional ou tridimensional.

1 a = arange(30)
2 print "a =\n", a
3 b = a.reshape( (5, 6))
4 print "b =\n", b
5 b[:, 0] = -1
6 print "a =\n", a
7 c = a.reshape( (2, 3, 5) )
8 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 20 21 22 23 24
 25 26 27 28 29]
b =
[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]
 [24 25 26 27 28 29]]
a =
[-1  1  2  3  4  5 -1  7  8  9 10 11 -1 13 14 15 16 17 -1 19 20 21 22 23 -1
 25 26 27 28 29]
c =
[[[-1  1  2  3  4]
  [ 5 -1  7  8  9]
  [10 11 -1 13 14]]

 [[15 16 17 -1 19]
  [20 21 22 23 -1]
  [25 26 27 28 29]]]

O exemplo a seguir mostra a cópia rasa no uso de fatiamento. No exemplo, todos os elementos de linhas e colunas pares são modificados para 1. CUIDADO: quando é feita a atribuição de b = 1., é importante que b seja referenciado como ndarray na forma b[:,:], caso contrário, se fizermos b = 1., uma nova variável é criada.

1 from ia636 import iaimginfo
2 a = zeros( (5, 6))
3 print iaimginfo(a)
4 b = a[::2,::2]
5 print iaimginfo(b)
6 b[:,:] = 1.
7 print 'b=\n', b
8 print 'a=\n', a
<type 'numpy.ndarray'> (5, 6) float64 0.000000 0.000000
<type 'numpy.ndarray'> (3, 3) float64 0.000000 0.000000
b=
[[ 1.  1.  1.]
 [ 1.  1.  1.]
 [ 1.  1.  1.]]
a=
[[ 1.  0.  1.  0.  1.  0.]
 [ 0.  0.  0.  0.  0.  0.]
 [ 1.  0.  1.  0.  1.  0.]
 [ 0.  0.  0.  0.  0.  0.]
 [ 1.  0.  1.  0.  1.  0.]]

Este outro exemplo é uma forma atraente de processar uma coluna de uma matriz bidimensional, porém é preciso CUIDADO, pois o uso de b deve ser com b[:] se for atribuído um novo valor para ele, caso contrário, se fizermos b = arange(5), uma nova variável é criada.

1 a = arange(25).reshape((5,5))
2 print 'a=\n',a
3 b = a[:,0]
4 print 'b=',b
5 b[:] = arange(5)
6 print 'b=',b
7 print 'a=\n',a
a=
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]
 [20 21 22 23 24]]
b= [ 0  5 10 15 20]
b= [0 1 2 3 4]
a=
[[ 0  1  2  3  4]
 [ 1  6  7  8  9]
 [ 2 11 12 13 14]
 [ 3 16 17 18 19]
 [ 4 21 22 23 24]]

Cópia profunda

Cria uma copia completa do array, do seu shape e conteúdo

1 b = array(a)
2 print "id de a = ",id(a)
3 print "id de b = ",id(b)
id de a =  139852933923216
id de b =  139852930073536

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 <oper> b:

1 a = arange(20).reshape(5,4)
2 b = 2*ones((5,4))
3 c = arange(20,0,-1).reshape(4,5)
4 print "\nMultiplicação de matriz por escalar:\n B*5 = \n", b*5
5 print "\nSoma de matrizes: \nA+B = \n", a+b
6 print "\nProduto das matrizes A e B, elemento por elemento \n", a*b
7 print "\nProduto das matrizes A e C: \n", dot(a,c)
8 print "\nTransposta de A: \n", a.transpose()
Multiplicação de matriz por escalar:
 B*5 = 
[[ 10.  10.  10.  10.]
 [ 10.  10.  10.  10.]
 [ 10.  10.  10.  10.]
 [ 10.  10.  10.  10.]
 [ 10.  10.  10.  10.]]

Soma de matrizes: 
A+B = 
[[  2.   3.   4.   5.]
 [  6.   7.   8.   9.]
 [ 10.  11.  12.  13.]
 [ 14.  15.  16.  17.]
 [ 18.  19.  20.  21.]]

Produto das matrizes A e B, elemento por elemento 
[[  0.   2.   4.   6.]
 [  8.  10.  12.  14.]
 [ 16.  18.  20.  22.]
 [ 24.  26.  28.  30.]
 [ 32.  34.  36.  38.]]

Produto das matrizes A e C: 
[[ 50  44  38  32  26]
 [250 228 206 184 162]
 [450 412 374 336 298]
 [650 596 542 488 434]
 [850 780 710 640 570]]

Transposta de A: 
[[ 0  4  8 12 16]
 [ 1  5  9 13 17]
 [ 2  6 10 14 18]
 [ 3  7 11 15 19]]

Extração de valores, redução no eixo

1 a = arange(20).reshape(5,4)
2 print "a = \n", a
3 print "máximo de a: %f, mínimo de a: %f, soma dos elementos de a: %f" % (a.max(), a.min(), a.sum())
4 print "máximo de cada linha = \n", a.max(1)
5 print "máximo de cada coluna = \n", a.max(0)
6 print "Soma acumulativa dos elementos ao longo de cada linha:\n", a.cumsum(1)
7 print "Soma acumulativa dos elementos ao longo de cada coluna:\n", a.cumsum(0)
a = 
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]]
máximo de a: 19.000000, mínimo de a: 0.000000, soma dos elementos de a: 190.000000
máximo de cada linha = 
[ 3  7 11 15 19]
máximo de cada coluna = 
[16 17 18 19]
Soma acumulativa dos elementos ao longo de cada linha:
[[ 0  1  3  6]
 [ 4  9 15 22]
 [ 8 17 27 38]
 [12 25 39 54]
 [16 33 51 70]]
Soma acumulativa dos elementos ao longo de cada coluna:
[[ 0  1  2  3]
 [ 4  6  8 10]
 [12 15 18 21]
 [24 28 32 36]
 [40 45 50 55]]