NumPy: o Guia Completo de Computação Numérica em Python
Guia completo de NumPy para dominar arrays ndarray, broadcasting, operações vetorizadas e álgebra linear em Python com exemplos práticos.
InSkillCoach
NumPy: o Guia Completo de Computação Numérica em Python
NumPy (Numerical Python) é a biblioteca fundamental de computação numérica em Python. Ela fornece um objeto central, o array multidimensional ndarray, além de um vasto conjunto de funções matemáticas otimizadas escritas em C. Praticamente todo o ecossistema científico do Python (Pandas, SciPy, scikit-learn, Matplotlib) é construído sobre o NumPy. Este guia apresenta os conceitos essenciais para manipular arrays numéricos de forma eficiente: criação, indexação, broadcasting, vetorização e álgebra linear.
Por que NumPy em vez de listas Python?
As listas Python são flexíveis, mas lentas para computação numérica. Cada elemento de uma lista é um objeto Python completo, com seu próprio cabeçalho de memória, enquanto um array NumPy armazena seus valores em um bloco de memória contíguo e homogêneo. As operações são então delegadas a código C compilado, o que elimina o custo do interpretador.
import numpy as np
import time
n = 10_000_000
liste = list(range(n))
tableau = np.arange(n)
# Multiplicação por 2 com uma lista
debut = time.perf_counter()
resultat_liste = [x * 2 for x in liste]
print(f"Liste : {time.perf_counter() - debut:.3f} s") # Liste : 0.520 s (ordem de grandeza)
# Multiplicação por 2 com NumPy
debut = time.perf_counter()
resultat_np = tableau * 2
print(f"NumPy : {time.perf_counter() - debut:.3f} s") # NumPy : 0.015 s (ordem de grandeza)
O ganho é tipicamente de um fator de 10 a 100, dependendo da operação.
| Critério | Lista Python | Array NumPy |
|---|---|---|
| Tipos dos elementos | Heterogêneos | Homogêneos (um único dtype) |
| Armazenamento em memória | Ponteiros para objetos dispersos | Bloco contíguo compacto |
| Operações elemento a elemento | Loop Python explícito | Vetorizadas em C |
| Consumo de memória | Alto | Baixo |
| Dimensões | Aninhamento manual | N dimensões nativas |
Criação de arrays
A partir de dados existentes
import numpy as np
# A partir de uma lista
a = np.array([1, 2, 3, 4])
print(a) # [1 2 3 4]
# Array 2D a partir de uma lista de listas
m = np.array([[1, 2, 3], [4, 5, 6]])
print(m)
# [[1 2 3]
# [4 5 6]]
Funções de geração
# Array de zeros (3 linhas, 4 colunas)
zeros = np.zeros((3, 4))
# Array de uns
uns = np.ones((2, 3))
# Array não inicializado (mais rápido, conteúdo arbitrário)
vide = np.empty((2, 2))
# Valor constante
sept = np.full((2, 2), 7)
# Matriz identidade
identite = np.eye(3)
# Sequência com um passo (como range, mas retorna um ndarray)
seq = np.arange(0, 10, 2)
print(seq) # [0 2 4 6 8]
# Número fixo de pontos igualmente espaçados entre dois limites (limites incluídos)
lin = np.linspace(0, 1, 5)
print(lin) # [0. 0.25 0.5 0.75 1. ]
Prefira linspace a arange para valores de ponto flutuante: o número de pontos é garantido, enquanto o passo flutuante do arange pode produzir surpresas de arredondamento.
dtype e shape: o documento de identidade de um array
Cada array possui um tipo de elementos (dtype) e uma forma (shape).
a = np.array([[1, 2, 3], [4, 5, 6]])
print(a.shape) # (2, 3) -> 2 linhas, 3 colunas
print(a.ndim) # 2 -> número de dimensões
print(a.size) # 6 -> número total de elementos
print(a.dtype) # int64 (int32 no Windows)
# Impor um tipo na criação
b = np.array([1, 2, 3], dtype=np.float64)
print(b.dtype) # float64
# Conversão de tipo (cria um novo array)
c = b.astype(np.int32)
print(c) # [1 2 3]
# Mudar a forma sem copiar os dados
d = np.arange(12).reshape(3, 4)
print(d.shape) # (3, 4)
# Achatar em 1D
print(d.ravel()) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
Atenção aos estouros de capacidade (overflow): um int8 só pode conter valores entre -128 e 127. Para data science, float64 e int64 são as escolhas padrão seguras; reduza a precisão somente se a memória se tornar uma restrição.
Indexação e slicing
A indexação do NumPy generaliza a das listas Python para arrays multidimensionais.
a = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
print(a[0]) # 0
print(a[-1]) # 9
print(a[2:7]) # [2 3 4 5 6]
print(a[::2]) # [0 2 4 6 8]
print(a[::-1]) # [9 8 7 6 5 4 3 2 1 0]
m = np.arange(12).reshape(3, 4)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(m[1, 2]) # 6 -> linha 1, coluna 2
print(m[0]) # [0 1 2 3] -> primeira linha
print(m[:, 1]) # [1 5 9] -> segunda coluna
print(m[1:, 2:]) # [[ 6 7]
# [10 11]]
Ponto importante: um slice do NumPy retorna uma view (visão), não uma cópia. Modificar a view modifica o array original.
v = m[0, :]
v[0] = 99
print(m[0, 0]) # 99 -> o array original foi modificado
# Para obter uma cópia independente
c = m[0, :].copy()
Máscaras booleanas
A filtragem por condição é um dos idiomas mais poderosos do NumPy.
notes = np.array([12, 8, 15, 17, 9, 14])
masque = notes >= 10
print(masque) # [ True False True True False True]
# Seleção dos elementos que satisfazem a condição
print(notes[masque]) # [12 15 17 14]
# Condições combinadas: & (e), | (ou), ~ (não), com parênteses obrigatórios
print(notes[(notes >= 10) & (notes < 16)]) # [12 15 14]
# Modificação condicional
notes[notes < 10] = 10
print(notes) # [12 10 15 17 10 14]
# np.where: escolher entre dois valores conforme uma condição
mention = np.where(notes >= 14, "admis avec mention", "admis")
print(mention)
# ['admis' 'admis' 'admis avec mention' 'admis avec mention' 'admis' 'admis avec mention']
O broadcasting explicado passo a passo
O broadcasting é o mecanismo que permite ao NumPy combinar arrays de formas diferentes sem copiar os dados. As regras são as seguintes:
- As formas são alinhadas pela direita.
- Duas dimensões são compatíveis se forem iguais, ou se uma delas valer 1.
- Uma dimensão de tamanho 1 é virtualmente esticada para corresponder à outra.
Caso 1: array e escalar
a = np.array([1, 2, 3])
print(a + 10) # [11 12 13]
# O escalar 10 é tratado como um array de forma (1,) esticado para (3,)
Caso 2: matriz e vetor linha
m = np.arange(6).reshape(2, 3) # forma (2, 3)
v = np.array([10, 20, 30]) # forma (3,)
# Alinhamento pela direita: (2, 3) e ( 3) -> compatível
print(m + v)
# [[10 21 32]
# [13 24 35]]
# v é somado a cada linha de m
Caso 3: vetor coluna e vetor linha
col = np.array([[1], [2], [3]]) # forma (3, 1)
lig = np.array([10, 20, 30]) # forma (3,)
# (3, 1) e (3,) -> alinhadas em (3, 1) e (1, 3) -> resultado (3, 3)
print(col + lig)
# [[11 21 31]
# [12 22 32]
# [13 23 33]]
Caso incompatível
a = np.ones((2, 3))
b = np.ones((2, 2))
# a + b -> ValueError: operands could not be broadcast together
# (2, 3) e (2, 2) : a última dimensão difere e nenhuma vale 1
Para centralizar as colunas de uma matriz, o broadcasting deixa o código muito conciso:
donnees = np.array([[1.0, 200.0], [2.0, 300.0], [3.0, 400.0]])
centrees = donnees - donnees.mean(axis=0)
print(centrees)
# [[-1. -100.]
# [ 0. 0.]
# [ 1. 100.]]
Operações vetorizadas e funções universais
As funções universais (ufuncs) são aplicadas elemento a elemento, sem loop Python.
a = np.array([1.0, 4.0, 9.0])
print(np.sqrt(a)) # [1. 2. 3.]
print(np.exp(a)) # [2.71828183e+00 5.45981500e+01 8.10308393e+03]
print(np.log(a)) # [0. 1.38629436 2.19722458]
angles = np.linspace(0, np.pi, 3)
print(np.sin(angles)) # [0.0000000e+00 1.0000000e+00 1.2246468e-16]
# Operadores aritméticos: todos vetorizados
b = np.array([10.0, 20.0, 30.0])
print(a + b) # [11. 24. 39.]
print(a * b) # [ 10. 80. 270.] -> produto elemento a elemento
print(b / a) # [10. 5. 3.33333333]
print(a ** 2) # [ 1. 16. 81.]
# Comparações vetorizadas
print(a > 3) # [False True True]
# Arredondamentos
x = np.array([1.234, 5.678])
print(np.round(x, 1)) # [1.2 5.7]
print(np.floor(x)) # [1. 5.]
print(np.ceil(x)) # [2. 6.]
A regra de ouro: se você escreve um loop for sobre os elementos de um array NumPy, quase sempre existe uma formulação vetorizada mais rápida e mais legível.
Agregações: sum, mean e o parâmetro axis
m = np.array([[1, 2, 3],
[4, 5, 6]])
# Agregação global
print(m.sum()) # 21
print(m.mean()) # 3.5
print(m.min(), m.max()) # 1 6
print(m.std()) # 1.707825127659933
# axis=0: reduz-se ao longo das linhas -> um resultado por coluna
print(m.sum(axis=0)) # [5 7 9]
# axis=1: reduz-se ao longo das colunas -> um resultado por linha
print(m.sum(axis=1)) # [ 6 15]
# Índice do máximo
print(m.argmax()) # 5 (índice no array achatado)
print(m.argmax(axis=0)) # [1 1 1]
# Somas cumulativas
print(np.cumsum([1, 2, 3, 4])) # [ 1 3 6 10]
Um truque mnemônico: axis designa a dimensão que desaparece. Com axis=0 em uma forma (2, 3), o resultado tem a forma (3,).
Álgebra linear
A = np.array([[1, 2],
[3, 4]])
B = np.array([[5, 6],
[7, 8]])
# Produto matricial: operador @ (recomendado) ou np.dot
print(A @ B)
# [[19 22]
# [43 50]]
print(np.dot(A, B)) # resultado idêntico
# Atenção: * é o produto elemento a elemento, não matricial
print(A * B)
# [[ 5 12]
# [21 32]]
# Transposta
print(A.T)
# [[1 3]
# [2 4]]
# Determinante e inversa
print(np.linalg.det(A)) # -2.0000000000000004
print(np.linalg.inv(A))
# [[-2. 1. ]
# [ 1.5 -0.5]]
# Resolução do sistema linear A x = b (preferível a inv(A) @ b)
b = np.array([5, 11])
x = np.linalg.solve(A, b)
print(x) # [1. 2.]
# Autovalores e autovetores
valeurs, vecteurs = np.linalg.eig(A)
print(valeurs) # [-0.37228132 5.37228132]
# Norma de um vetor
v = np.array([3.0, 4.0])
print(np.linalg.norm(v)) # 5.0
Geração de números aleatórios com default_rng
Desde o NumPy 1.17, a API recomendada usa um gerador explícito criado com np.random.default_rng, mais rápido e melhor projetado que as antigas funções np.random.rand e companhia.
# Gerador com seed para resultados reproduzíveis
rng = np.random.default_rng(seed=42)
# Floats uniformes em [0, 1)
print(rng.random(3)) # [0.77395605 0.43887844 0.85859792]
# Inteiros uniformes em [0, 10)
print(rng.integers(0, 10, size=5)) # [8 6 5 2 3]
# Distribuição normal (média 0, desvio padrão 1)
print(rng.normal(loc=0, scale=1, size=3)) # [ 0.7611397 -0.86157352 0.36371758]
# Amostragem sem reposição em um conjunto
print(rng.choice([10, 20, 30, 40], size=2, replace=False)) # [20 40]
# Embaralhamento in-place
a = np.arange(5)
rng.shuffle(a)
print(a) # [4 0 3 1 2] (por exemplo)
Leitura e escrita de arrays
a = np.arange(12).reshape(3, 4)
# Formato binário nativo .npy: rápido, preserva dtype e shape
np.save("tableau.npy", a)
b = np.load("tableau.npy")
print(np.array_equal(a, b)) # True
# Vários arrays em um arquivo .npz
np.savez("archive.npz", donnees=a, labels=np.array([0, 1, 2]))
archive = np.load("archive.npz")
print(archive["labels"]) # [0 1 2]
# Formato texto: legível, porém mais lento e menos preciso
np.savetxt("tableau.csv", a, delimiter=",", fmt="%d")
c = np.loadtxt("tableau.csv", delimiter=",")
print(c.dtype) # float64 (loadtxt lê como float por padrão)
Para arquivos CSV com cabeçalhos, colunas heterogêneas ou valores ausentes, prefira pandas.read_csv, muito mais robusto que loadtxt.
Boas práticas
- Vetorize sempre: substitua loops
forpor operações sobre arrays inteiros; é mais rápido e mais legível. - Desconfie das views: os slices compartilham a memória do array original; use
.copy()quando precisar de um array independente. - Fixe a seed aleatória com
np.random.default_rng(seed)para tornar seus experimentos reproduzíveis. - Use
@para o produto matricial e reserve*para o produto elemento a elemento, a fim de evitar confusões. - Prefira
np.linalg.solveà inversão de matriz para resolver um sistema linear: é numericamente mais estável. - Verifique
shapeedtypesempre que um resultado o surpreender: a maioria dos bugs de NumPy vem de um broadcasting involuntário ou de um tipo inesperado. - Escolha o formato
.npy/.npzpara salvar arrays entre sessões Python, e o CSV apenas para troca com outras ferramentas.
Conclusão
O NumPy é a base sobre a qual se apoia toda a análise de dados em Python. Dominando o ndarray, a indexação, as máscaras booleanas, o broadcasting e as operações vetorizadas, você dispõe das ferramentas necessárias para escrever código numérico ao mesmo tempo conciso e performático. Esses conceitos aparecem diretamente no Pandas (cujas colunas são arrays NumPy), no SciPy (que estende o NumPy para computação científica avançada) e nas bibliotecas de machine learning. O próximo passo natural é explorar o Pandas para a manipulação de dados tabulares e, em seguida, o Matplotlib para visualização.
Sobre InSkillCoach
Especialista em treinamento e tecnologias
Coach especializado em tecnologias avançadas e IA, apoiado pela GNeurone Inc.
Certificações:
- AWS Certified Solutions Architect – Professional
- Certificações Google Cloud
- Microsoft Certified: DevOps Engineer Expert
- Certified Kubernetes Administrator (CKA)
- CompTIA Security+
Comentários
Os comentários são alimentados pelo GitHub Discussions
Conecte-se com o GitHub para participar da discussão