O NumPy é uma das principais bibliotecas para computação científica em Python. Com ela é possível criar arrays multidimensionais de alta performance, além de possuir ferramentas para manipular estes arrays. O NumPy serve de base para outras bibliotecas e frameworks na área de ciência de dados e inteligência artificial.
O NumPy é frequentemente importado da seguinte forma:
import numpy as np
Os arrays do NumPy podem armazenar dados multidimensionais, porém todos os dados devem ser do mesmo tipo.
Vamos criar um array a partir de uma lista.
a = np.array([1, 2, 3])
Podemos acessar os valores do array e alterá-los.
print(a[0], a[1], a[2])
a[0] = 9
print(a)
1 2 3 [9 2 3]
A propriedade shape
retorna o tamanho do array.
a.shape
(3,)
Vamos agora criar um array com duas dimensões.
b = np.array([[1, 2, 3], [4, 5, 6]])
print(b)
print(b.shape)
[[1 2 3] [4 5 6]] (2, 3)
A propriedade shape
agora traz o tamanho do array em cada uma das dimensões. Nesse caso, interpretamos o tamanho como: 2 linhas e 3 colunas.
A indexação para arrays com 2 ou mais dimensões é feita através de uma tupla.
print(b[0, 0], b[0, 1], b[0, 2])
print(b[1, 0], b[1, 1], b[1, 2])
1 2 3 4 5 6
b[0, 0] = 9
print(b[0, 0], b[0, 1], b[0, 2])
print(b[1, 0], b[1, 1], b[1, 2])
9 2 3 4 5 6
O NumPy traz algumas funções para facilitar a criação de arrays.
np.zeros
cria um array com todos os elementos igual a zero.
a = np.zeros((2, 2)) # (2, 2) é o tamanho do array
print(a)
[[0. 0.] [0. 0.]]
np.ones
cria um array com todos os elementos igual a um.
b = np.ones((2, 2)) # (2, 2) é o tamanho do array
print(b)
[[1. 1.] [1. 1.]]
np.full
cria um array com todos os elementos iguais a uma constante passada como parâmetro.
c = np.full((2, 2), 7) # (2, 2) é o tamanho do array e 7 é a constante
print(c)
[[7 7] [7 7]]
np.arange
cria um array com um intervalo de números.
d1 = np.arange(10) # intervalo de 0 até antes de 10
print(d1)
d2 = np.arange(1, 9) # intervalo de 1 até antes de 9
print(d2)
d3 = np.arange(1, 9, 2) # intervalo de 1 até antes de 9 com um passo de tamanho 2
print(d3)
[0 1 2 3 4 5 6 7 8 9] [1 2 3 4 5 6 7 8] [1 3 5 7]
np.eye
Cria uma matriz identidade.
e = np.eye(2) # 2 é o tamanho da matriz identidade
print(e)
[[1. 0.] [0. 1.]]
np.random.random
Cria um array com valores aleatórios.
f = np.random.random((2, 2)) # (2, 2) é o tamanho do array
print(f)
[[0.11682408 0.89582127] [0.8772504 0.33403101]]
Ao criar um array, o NumPy tenta inferir o tipo de dado dos elementos.
a = np.array([1, 2, 3])
print(a.dtype)
b = np.array([1.0, 2.0, 3.0])
print(b.dtype)
int64 float64
Entretanto, é possível explicitar o tipo que deve ser usado.
a = np.array([1, 2, 3], dtype=float)
print(a.dtype)
float64
O NumPy suporta diversos tipos de dados que podem ser usados para armazenar de forma eficiente os diferentes valores suportados pela linguagem. Para mais detalhes: https://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html
Além da indexação vista anteriormente, o NumPy fornece outras maneiras de acessar os elementos de um array.
Assim como as listas do Python, os arrays do NumPy também podem "fatiados".
A sintaxe do slice é: meu_array[início:fim]
. Onde início
é a posição de início e fim
é o limite final do slice.
a = np.array([1, 2, 3, 4, 5])
a[1:4] # começa na posição 1 e termina antes da posição 4
array([2, 3, 4])
Omitindo o início
o slice é feito a partir do primeiro elemento. Omitindo o fim
o slice é feito até o último elemento.
print(a[:4])
print(a[2:])
[1 2 3 4] [3 4 5]
Arrays multidimensionais também suportam slicing.
b = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
print(b)
[[ 1 2 3 4] [ 5 6 7 8] [ 9 10 11 12]]
b[0:2, 1:3]
array([[2, 3], [6, 7]])
A figura abaixo ilustra o resultado do slicing anterior. Na primeira dimensão, foi especificado o slice 0:2
, que é representado pelo retângulo verde na figura. Esse slice é composto da primeira e da segunda linha. Na segunda dimensão, foi especificado o slice 1:3
, representado pelo retângulo vermelho. Esse slice é composto da segunda e da terceira coluna. O resultado do slicing é a interseção dos retângulos verde e vermelho.
Podemos combinar slicing e atribuição.
a = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
a[5:] = 10
print(a)
b = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
b[2:5] = np.array([20, 30, 40]) # esse array precisa ser do mesmo tamanho do slice
print(b)
[ 0 1 2 3 4 10 10 10 10 10] [ 0 1 20 30 40 5 6 7 8 9]
O resultado de um slice é uma espécie de janela para o array original. Assim, modificando o slice você modificará o array original.
a = np.array([1, 2, 3, 4, 5])
a_slice = a[1:3]
a_slice[0] = 9
print(a_slice)
print(a)
[9 3] [1 9 3 4 5]
A indexação avançada é utilizada para criar novos arrays a partir de um array original através de máscaras definidas por números inteiros ou valores booleanos.
Para máscaras booleanas, definimos um array com a mesma quantidade de elementos do array original, onde True indica que o elemento naquela posição vai fazer parte do novo array e False indica que o elemento naquela posição não vai fazer parte do array.
a = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
mask = [True, True, True, True, True, True, False, False, False, False]
a[mask]
array([0, 1, 2, 3, 4, 5])
Podemos criar máscaras booleanas aplicando operadores lógicos aos arrays.
a <= 5
array([ True, True, True, True, True, True, False, False, False, False])
Com isso, conseguimos usar a máscara booleana de forma direta.
a[a <= 5]
array([0, 1, 2, 3, 4, 5])
Usando máscaras booleanas, podemos filtrar o array de acordo com os seus elementos. Por exemplo, para criar um array apenas com números pares, temos:
a[a%2 == 0]
array([0, 2, 4, 6, 8])
As máscaras booleanas também podem auxiliar na atribuição de novos valores. No exemplo abaixo, vamos substituir todas as entradas nulas por zero.
b = np.array([20, 50, None, 35, 90, None, 110])
b[b == None] = 0
print(b)
[20 50 0 35 90 0 110]
As máscaras precisam ter as mesmas dimensões do array original.
a = np.array([[1, 2], [3, 4], [5, 6]])
mask = a > 2
print(mask)
[[False False] [ True True] [ True True]]
Entretanto, a indexação usando máscaras multidimensionais retornam arrays unidimensionais.
a[mask]
array([3, 4, 5, 6])
Com a indexação com arrays de inteiros, novos arrays são criados escolhendo os elementos do array original de acordo com suas posições.
a = np.array([0, 10, 20, 30, 40, 50, 60, 70 , 80, 90])
a[[1, 5, 9]]
array([10, 50, 90])
Na indexação, os índices não precisam aparecer em ordem.
a[[5, 1, 9]]
array([50, 10, 90])
Os índices também podem ser repetidos.
a[[5, 1, 5, 9, 9, 5, 5]]
array([50, 10, 50, 90, 90, 50, 50])
Assim como nas máscaras booleanas, podemos usar o array de inteiros para atribuições de novos valores.
a[[5, 1, 9]] = 100
a
array([ 0, 100, 20, 30, 40, 100, 60, 70, 80, 100])
Para arrays multidimensionais, precisamos passar arrays multidimensionais na indexação. No exemplo abaixo, são selecionados os elementos a[0,0] , a[1,1] e a[2,0].
a = np.array([[1 ,2], [3, 4], [5, 6]])
a[[0, 1, 2], [0, 1, 0]]
array([1, 4, 5])
Operações matemáticas básicas são realizadas elemento por elemento nos arrays.
a = np.array([1, 2, 3, 4, 5])
print(a + 1) # 1 é somado a cada elemento do array
print(2 * a) # 2 é multiplicado a cada elemento do array
[2 3 4 5 6] [ 2 4 6 8 10]
Também conseguimos fazer operações matemáticas onde os dois operandos são arrays.
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
print(a+b) # cada elemento do array a é somado com o elemento do array b da posição correspondente
print(a*b) # cada elemento do array a é mutiplicado pelo o elemento do array b da posição correspondente
[[ 6 8] [10 12]] [[ 5 12] [21 32]]
No NumPy, usar o operador de multiplicação entre arrays não significa que estamos fazendo multiplicação de matrizes. Para multiplicar duas matrizes usamos a função np.dot
.
print(a*b)
print(np.dot(a, b))
[[ 5 12] [21 32]] [[19 22] [43 50]]
Além de operações aritméticas, também é possível fazer operações lógicas entre arrays.
a = np.array([1, 2, 3, 4])
b = np.array([4, 2, 2, 4])
a == b
array([False, True, False, True])
Usando o operador ==
foi realizada uma comparação elemento por elemento e o resultado foi mostrado elemento por elemento. Para comparar se todos os elementos de dois arrays são iguais usamos a função np.array_equal
.
c = np.array([1, 2, 3, 4])
print(np.array_equal(a, b))
print(np.array_equal(a, c))
False True
Para as operações OR e AND, temos:
a = np.array([1, 1, 0, 0])
b = np.array([1, 0, 1, 0])
print(a | b) # OR
print(a & b) # AND
[1 1 1 0] [1 0 0 0]
Funções de agregação são úteis para sumarizar informações contidas nos arrays.
np.sum
soma todos os valores de um array.
a = np.array([1, 2, 3, 4])
np.sum(a)
10
b = np.array([[1, 2, 3], [4, 5, 6]])
np.sum(b)
21
Utilizando o parâmetro axis
especificamos a agregação numa determinada dimensão.
np.sum(b, axis=0) # 0 é a primeira dimensão, ou seja, as linhas.
array([5, 7, 9])
Note que somar os valores na dimensão das linhas não significa somar as linhas, mas somar os valores na direção das linhas. Veja a figura abaixo.
np.sum(b, axis=1) # 1 é a segunda dimensão, ou seja, as colunas
array([ 6, 15])
np.min
encontra o valor mínimo de um array e np.max
encontra o valor máximo.
print(np.min(a))
print(np.min(b))
print(np.max(a))
print(np.max(b))
1 1 4 6
Utilizando o parâmetro axis
encontramos o valor máximo ou mínimo numa determinada dimensão.
print(np.min(b, axis=0)) # encontra o valor mínimo seguindo a dimensão das linhas
print(np.min(b, axis=1)) # encontra o valor mínimo seguindo a dimensão das colunas
print(np.max(b, axis=0)) # encontra o valor máximo seguindo a dimensão das linhas
print(np.max(b, axis=1)) # encontra o valor máximo seguindo a dimensão das colunas
[1 2 3] [1 4] [4 5 6] [3 6]
np.mean
calcula a média dos elementos de um array, np.median
calcula a mediana e np.std
calcula o desvio padrão.
print(np.mean(a))
print(np.mean(b))
print(np.median(a))
print(np.median(b))
print(np.std(a))
print(np.std(b))
2.5 3.5 2.5 3.5 1.118033988749895 1.707825127659933
print(np.mean(b, axis=0))
print(np.mean(b, axis=1))
print(np.median(b, axis=0))
print(np.median(b, axis=1))
print(np.std(b, axis=0))
print(np.std(b, axis=1))
[2.5 3.5 4.5] [2. 5.] [2.5 3.5 4.5] [2. 5.] [1.5 1.5 1.5] [0.81649658 0.81649658]
np.any
avalia se algum elemento satisfaz uma expressão booleana, np.all
avalia se todos os elementos satisfazem uma expressão booleana.
c = np.array([1, 2, 3, 4])
d = np.array([2, 4, 6, 8])
print(np.any(c == 2)) # True, pois um dos elementos de c é igual a 2
print(np.all(c == 2)) # False, pois existem elementos de c que não são iguais a 2
print(np.all(c < 5)) # True, pois todos os elementos de c são menores que 5
True False True
print(np.any(c % 2 == 0)) # True, pois o elemento 2 e o elemento 4 são divisíveis por 2
print(np.all(c % 2 == 0)) # False, pois o elemento 1 e o element 3 não são divisíveis por 2
print(np.any(d % 2 == 0)) # True, pois todos os elementos de d são divisíveis por 2
True False True
Para arrays de mesmo tamanho, as operações são realizadas elemento por elemento. Entretanto, através do broadcast, o NumPy permite que essas operações sejam feitas usando arrays com diferentes dimensões.
a = np.array([[1, 2, 3],
[4, 5, 6]])
b = np.array([10, 20, 30])
a+b
array([[11, 22, 33], [14, 25, 36]])
Percebam que o NumPy somou a linha [1, 2, 3]
com o array [10, 20, 30]
e somou a linha [4, 5, 6]
com o mesmo array [10, 20, 30]
. Através do broadcast, o NumPy "estica" o array com menos dimensões repetindo os seus dados. A soma anterior é a mesma que:
a = np.array([[1, 2, 3],
[4, 5, 6]])
b = np.array([[10, 20, 30],
[10, 20, 30]])
a+b
array([[11, 22, 33], [14, 25, 36]])
O broadcast segue três regras para determinar a interação entre os arrays:
Se dois arrays possuirem dimensões diferentes, o formato do array com menos dimensões é acrescido de 1 do lado esquerdo.
Se os tamanhos dos arrays não forem iguais em alguma dimensão, o array com tamanho igual a 1 numa determinada dimensão é esticado, repetindo seus elementos, para ficar igual ao outro tamanho.
Se os tamanhos dos array não forem iguais e nenhum dos dois for igual a 1, então é retornado um erro.
a = np.array([[1, 2, 3],
[4, 5, 6]])
b = np.array([10, 20, 30])
print(a.shape)
print(b.shape)
(2, 3) (3,)
Nesse caso, b
tem menos dimensões que a
. Assim, de acordo com a regra número 1, seu formato vai ter 1 adicionado do lado esquerdo, resultando em (1, 3)
.
Agora, o formato de a
é (2, 3)
e o de b
é (1, 3)
. Seguindo a regra 2, b
vai ser esticado para ficar com tamanho (2, 3)
.
Utilizando as regras do broadcast, percebemos que é possível esticar dois arrays ao mesmo tempo.
a = np.array([1, 2, 3])
b = np.array([[10],
[20],
[30]])
a+b
array([[11, 12, 13], [21, 22, 23], [31, 32, 33]])
print(a.shape)
print(b.shape)
(3,) (3, 1)
Nesse caso, a
tem menos dimensões que b
. Assim, seu formato vai ter 1 adicionado do lado esquerdo, resultando em (1, 3)
.
Agora, a
tem o formato (1, 3)
e b
tem o formato (3, 1)
. Seguindo a regra 2, a
vai ser esticado para ficar com tamanho (3, 3)
e b
vai ser esticado para ficar com o tamanho (3, 3)
.
O NumPy permite que um array tenha o seu formato alterado para distribuir seus valores usando diferentes dimensões.
a = np.array([1, 2, 3, 4])
a.reshape((2, 2)) # transforma o array a num array 2x2
array([[1, 2], [3, 4]])
Note que a quantidade de elementos do array inicial precisa ser a mesma do novo array.
Podemos usar o reshape
para transformar um array multidimensional num array de uma dimensão.
b = np.array([[1, 2, 3],
[4, 5, 6]])
b.reshape((6,))
array([1, 2, 3, 4, 5, 6])
Também podemos converter um array de uma dimensão em uma matriz coluna ou matriz linha.
print(a.reshape((1, 4))) # matriz linha
print(a.reshape((4, 1))) # matriz coluna
[[1 2 3 4]] [[1] [2] [3] [4]]
Trabalhando com matrizes, uma operação comum é obtenção de uma nova matriz através da troca de suas dimensões, também conhecida como a matriz transposta.
b.T
array([[1, 4], [2, 5], [3, 6]])
Para ordenar um array usamos a função np.sort()
que implementa o algoritmo quicksort.
a = np.array([4, 1, 3, 5, 2])
np.sort(a)
array([1, 2, 3, 4, 5])
A função anterior retorna o array ordenado. Se você desejar ordenar o próprio array, basta usar o método sort()
.
print(a)
a.sort()
print(a)
[4 1 3 5 2] [1 2 3 4 5]
Ao invés de ordenar o array, é possível obter um array com os índices dos elementos ordenados utilizando a função np.argsort()
.
a = np.array([4, 1, 3, 5, 2])
np.argsort(a)
array([1, 4, 2, 0, 3])
O resultado anterior significa que o menor elemento está na posição 1, o segundo menor na posição 4, o terceiro menor na posição 2, o quarto menor na posição 0 e o quinto menor (ou o maior) na posição 3.
Utilizando a indexação avançada, podemos usar o resultado do np.argsort()
para obter o array ordenado.
a = np.array([4, 1, 3, 5, 2])
sort_pos = np.argsort(a)
a[sort_pos]
array([1, 2, 3, 4, 5])
Por fim, o parâmetro axis
está disponível para as funções de ordenação. Com ele, especificamos que a ordenação deve ser feita através de uma determinada dimensão.
b = np.array([[4, 1, 3, 5, 2],
[2, 5, 2, 3, 1],
[3, 2, 5, 1, 4]])
np.sort(b, axis=0) # ordena seguindo a dimensão das linhas
array([[2, 1, 2, 1, 1], [3, 2, 3, 3, 2], [4, 5, 5, 5, 4]])
np.sort(b, axis=1) # ordena seguindo a dimensão das colunas
array([[1, 2, 3, 4, 5], [1, 2, 2, 3, 5], [1, 2, 3, 4, 5]])
np.sort(b) # omitindo axis, a última dimensão é usada, nesse caso, as colunas
array([[1, 2, 3, 4, 5], [1, 2, 2, 3, 5], [1, 2, 3, 4, 5]])
np.sort(b, axis=None) # usando None, o array é transformado em unidimensional e depois ordenado
array([1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 5])
O conteúdo deste notebook foi inspirado no material dos seguintes links: