0%
NumPy: o Guia Completo de Computação Numérica em Python

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.

I

InSkillCoach

· min

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érioLista PythonArray NumPy
Tipos dos elementosHeterogêneosHomogêneos (um único dtype)
Armazenamento em memóriaPonteiros para objetos dispersosBloco contíguo compacto
Operações elemento a elementoLoop Python explícitoVetorizadas em C
Consumo de memóriaAltoBaixo
DimensõesAninhamento manualN 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:

  1. As formas são alinhadas pela direita.
  2. Duas dimensões são compatíveis se forem iguais, ou se uma delas valer 1.
  3. 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 for por 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 shape e dtype sempre 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/.npz para 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.

InSkillCoach

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+
2.0k
93

Comentários

Os comentários são alimentados pelo GitHub Discussions

Conecte-se com o GitHub para participar da discussão

Link copiado!