Introdução ao Numpy

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 = b.copy()

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.


In [45]:
import numpy as np
    
a = np.arange(6) 
b = a
print("a =\n",a)
print("b =\n",b)
b.shape = (2,3)                           # mudança no shape de b,
print("\na shape =",a.shape)               # altera o shape de a

b[0,0] = -1                               # mudança no conteúdo de b
print("a =\n",a)                             # altera o conteudo de a  

print("\nid de a = ",id(a))                # id é um identificador único de objeto
print("id de b = ",id(b))                  # a e b possuem o mesmo id

print('np.may_share_memory(a,b):',np.may_share_memory(a,b))


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 =  4508641920
id de b =  4508641920
np.may_share_memory(a,b): True

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:


In [39]:
def cc(a):
  return a

b = cc(a)
print("id de a = ",id(a))
print("id de b = ",id(b))
print('np.may_share_memory(a,b):',np.may_share_memory(a,b))


id de a =  4503215200
id de b =  4503215200
np.may_share_memory(a,b): True

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, sendo as principais:

1) no caso do reshape onde o número de elementos do ndarray é o mesmo, porém sua dimensionalidade é alterada; 2) no caso de fatiamento onde um subarray é indexado; 3) no caso de transposição do array; 4) no caso de linearização do raster através do ravel(). entre outros.

Reshape

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


In [40]:
a = np.arange(30)
print("a =\n", a)
print('a.shape:',a.shape)


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]
a.shape: (30,)

In [41]:
b = a.reshape( (5, 6))
print("b =\n", b)


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

In [42]:
b[:, 0] = -1
print('b=\n',b)
print("a =\n", a)


b=
 [[-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]]
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]

In [43]:
c = a.reshape( (2, 3, 5) )
print("c =\n", c)
print('c.base is a:',c.base is a)
print('np.may_share_memory(a,c):',np.may_share_memory(a,c))
print('id(a),id(c):',id(a),id(c))


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]]]
c.base is a: True
np.may_share_memory(a,c): True
id(a),id(c): 4508640480 4508641760

Slice - Fatiamento

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.


In [47]:
a = np.zeros( (5, 6))
print('a.shape:',a.shape)
b = a[::2,::2]
print('b.shape:',b.shape)
b[:,:] = 1.
print('b=\n', b) 
print('a=\n', a) 
print('b.base is a:',b.base is a)
print('np.may_share_memory(a,b):',np.may_share_memory(a,b))


a.shape: (5, 6)
b.shape: (3, 3)
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.]]
b.base is a: True
np.may_share_memory(a,b): True

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.


In [ ]:
a = np.arange(25).reshape((5,5))
print('a=\n',a)
b = a[:,0]
print('b=',b)

In [ ]:
b[:] = np.arange(5)
b[2] = 100
print('b=',b)
print('a=\n',a)

In [ ]:
a = np.arange(25).reshape((5,5))
print('a=\n',a)
b = np.arange(5)
print('b=',b)
print('a=\n',a)

Transposto

A operação matricial de transposição que troca linhas por colunas produz também um view da imagem, sem necessidade de cópia:


In [ ]:
a = np.arange(24).reshape((4,6))
print('a:\n',a)
at = a.T
print('at:\n',at)
print('at.shape',at.shape)
print('np.may_share_memory(a,at):',np.may_share_memory(a,at))

Ravel

Aplicando-se o método ravel() a um ndarray, gera-se um view do raster linearizado (i.e. uma única dimensão) do ndarray.


In [ ]:
a = np.arange(24).reshape((4,6))
print('a:\n',a)
av = a.ravel()
print('av.shape:',av.shape)
print('av:\n',av)
print('np.may_share_memory(a,av):',np.may_share_memory(a,av))

Cópia profunda

Cria uma copia completa do array, do seu shape e conteúdo. A recomendação é utilizar a função copy() para realizar a copia profunda, entretanto é possível conseguir a copia profunda pelo np.array.


In [ ]:
b = a.copy()
c = np.array(a, copy=True)

print("id de a = ",id(a))
print("id de b = ",id(b))
print("id de c = ",id(c))
print('np.may_share_memory(a,b):',np.may_share_memory(a,b))
print('np.may_share_memory(a,c):',np.may_share_memory(a,c))

Documentação Oficial Numpy

Copies and Views