Fundamentos do Machine Learning com Python

0/25 aulas0%
teoria

Introdução ao NumPy: Trabalhando com Arrays Numéricos (Documentação NumPy)

Aprenda sobre introdução ao numpy: trabalhando com arrays numéricos (documentação numpy)

60 min
Aula 4 de 5

Introdução ao NumPy: Trabalhando com Arrays Numéricos

Olá, futuros cientistas de dados e engenheiros de Machine Learning! 👋 Sejam bem-vindos à nossa aula sobre NumPy, uma das bibliotecas mais fundamentais e poderosas do ecossistema Python para computação numérica.

Nesta aula, vamos desvendar o NumPy e entender por que ele é a espinha dorsal de muitas operações em Machine Learning. Prepare-se para mergulhar no mundo dos arrays multidimensionais e operações otimizadas!


1. Introdução ao NumPy: O Pilar da Computação Numérica 🚀

NumPy, que significa Numerical Python, é uma biblioteca de código aberto para a linguagem de programação Python, que oferece suporte para arrays e matrizes multidimensionais grandes, juntamente com uma vasta coleção de funções matemáticas de alto nível para operar sobre esses arrays.

Por que NumPy é Essencial para Machine Learning?

  1. Eficiência e Velocidade: Diferente das listas padrão do Python, os arrays NumPy (chamados ndarray) são implementados em C, o que os torna significativamente mais rápidos e eficientes em termos de memória para armazenar e manipular grandes volumes de dados numéricos. Isso é crucial em Machine Learning, onde trabalhamos com datasets que podem ter milhões de pontos de dados.
  2. Arrays Multidimensionais (ndarray): O objeto central do NumPy é o ndarray, que permite a criação de arrays com 1, 2, 3 ou mais dimensões. Isso é perfeito para representar vetores, matrizes, imagens (que são matrizes de pixels) e tensores (usados em Deep Learning).
  3. Operações Vetorizadas: O NumPy permite aplicar operações matemáticas a arrays inteiros de uma só vez, sem a necessidade de loops explícitos em Python. Isso não só simplifica o código, mas também o torna muito mais rápido (um conceito conhecido como "vetorização").
  4. Base para Outras Bibliotecas: Bibliotecas populares como Pandas, SciPy, Scikit-learn e Matplotlib são construídas sobre o NumPy, utilizando seus arrays como estrutura de dados fundamental.

Em resumo, se você vai trabalhar com dados numéricos em Python, o NumPy é uma ferramenta indispensável!


2. Exploração Detalhada do NumPy com Exemplos 💡

Vamos agora explorar os principais conceitos e funcionalidades do NumPy.

Primeiro, precisamos importar a biblioteca. É uma convenção comum importá-la com o alias np:

import numpy as np

2.1. O Objeto ndarray: O Coração do NumPy ❤️

O ndarray (N-dimensional array) é a estrutura de dados fundamental do NumPy. Ele é um contêiner homogêneo, o que significa que todos os elementos dentro dele devem ser do mesmo tipo de dado (inteiro, float, etc.).

Criação de um ndarray a partir de uma lista Python:

# Array 1D (vetor)
lista_1d = [1, 2, 3, 4, 5]
array_1d = np.array(lista_1d)
print(f"Array 1D: {array_1d}")
print(f"Tipo: {type(array_1d)}")
 
# Array 2D (matriz)
lista_2d = [[1, 2, 3], [4, 5, 6]]
array_2d = np.array(lista_2d)
print(f"\nArray 2D:\n{array_2d}")

2.2. Métodos de Criação de Arrays 🛠️

NumPy oferece diversas maneiras convenientes de criar arrays:

a) Arrays Pré-Preenchidos

  • np.zeros(shape): Cria um array preenchido com zeros.
  • np.ones(shape): Cria um array preenchido com uns.
  • np.empty(shape): Cria um array com valores arbitrários (não inicializados), útil para alocar espaço rapidamente.
  • np.full(shape, fill_value): Cria um array preenchido com um valor específico.
# Array de zeros 3x4
zeros_array = np.zeros((3, 4))
print(f"Array de zeros:\n{zeros_array}")
 
# Array de uns 2x3
ones_array = np.ones((2, 3))
print(f"\nArray de uns:\n{ones_array}")
 
# Array vazio (valores podem variar)
empty_array = np.empty((2, 2))
print(f"\nArray vazio:\n{empty_array}")
 
# Array preenchido com 7, de forma 2x2
full_array = np.full((2, 2), 7)
print(f"\nArray preenchido com 7:\n{full_array}")

b) Arrays com Sequências Numéricas

  • np.arange(start, stop, step): Semelhante ao range() do Python, mas retorna um ndarray.
  • np.linspace(start, stop, num): Retorna números espaçados uniformemente em um intervalo especificado.
# Sequência de 0 a 9
seq_arange = np.arange(10)
print(f"Array com arange: {seq_arange}")
 
# Sequência de 0 a 10 com 5 elementos igualmente espaçados
seq_linspace = np.linspace(0, 10, 5)
print(f"Array com linspace: {seq_linspace}")

c) Arrays Aleatórios

O submódulo np.random é essencial para gerar dados aleatórios, muito usado em simulações e inicialização de pesos em redes neurais.

  • np.random.rand(d0, d1, ..., dn): Gera números aleatórios de uma distribuição uniforme no intervalo [0, 1).
  • np.random.randn(d0, d1, ..., dn): Gera números aleatórios de uma distribuição normal (gaussiana) padrão (média 0, desvio padrão 1).
  • np.random.randint(low, high, size): Gera inteiros aleatórios no intervalo [low, high).
# Array 2x3 com números aleatórios uniformes
rand_uniform = np.random.rand(2, 3)
print(f"Array aleatório uniforme:\n{rand_uniform}")
 
# Array 2x2 com números aleatórios normais
rand_normal = np.random.randn(2, 2)
print(f"\nArray aleatório normal:\n{rand_normal}")
 
# Array 1x5 com inteiros aleatórios entre 0 e 10
rand_int = np.random.randint(0, 11, size=(1, 5)) # high é exclusivo
print(f"\nArray de inteiros aleatórios:\n{rand_int}")

2.3. Atributos de um Array 📏

Os arrays NumPy possuem atributos que fornecem informações importantes sobre sua estrutura:

  • .shape: Uma tupla que indica as dimensões do array.
  • .ndim: O número de dimensões (eixos) do array.
  • .size: O número total de elementos no array.
  • .dtype: O tipo de dado dos elementos no array.
  • .itemsize: O tamanho em bytes de cada elemento do array.
  • .nbytes: O tamanho total em bytes do array.
arr = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float64)
 
print(f"Array:\n{arr}")
print(f"Shape (dimensões): {arr.shape}") # (linhas, colunas)
print(f"Número de dimensões: {arr.ndim}")
print(f"Número total de elementos: {arr.size}")
print(f"Tipo de dado dos elementos: {arr.dtype}")
print(f"Tamanho em bytes de cada elemento: {arr.itemsize} bytes")
print(f"Tamanho total do array em bytes: {arr.nbytes} bytes")

2.4. Indexação e Fatiamento (Slicing) ✂️

Acessar e manipular partes de arrays é fundamental. O NumPy oferece poderosas ferramentas de indexação e fatiamento, semelhantes às listas Python, mas estendidas para múltiplas dimensões.

a) Indexação Básica

array_1d = np.array([10, 20, 30, 40, 50])
print(f"Elemento na posição 0: {array_1d[0]}")
print(f"Último elemento: {array_1d[-1]}")
 
array_2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(f"\nArray 2D:\n{array_2d}")
# Acessando o elemento na linha 1, coluna 2 (índices começam em 0)
print(f"Elemento [1, 2]: {array_2d[1, 2]}") # Equivalente a array_2d[1][2]

b) Fatiamento (Slicing)

Sintaxe: [start:stop:step] para cada dimensão.

array_1d = np.arange(10, 101, 10) # [10, 20, ..., 100]
print(f"Array 1D: {array_1d}")
print(f"Primeiros 3 elementos: {array_1d[:3]}")
print(f"Elementos do índice 2 ao 5: {array_1d[2:6]}")
print(f"Elementos do índice 2 até o final: {array_1d[2:]}")
print(f"Elementos com passo de 2: {array_1d[::2]}")
 
print(f"\nArray 2D:\n{array_2d}")
# Selecionar as duas primeiras linhas e as duas primeiras colunas
sub_array = array_2d[:2, :2]
print(f"Sub-array (2x2 superior esquerdo):\n{sub_array}")
 
# Selecionar todas as linhas, mas apenas a segunda coluna
coluna_2 = array_2d[:, 1]
print(f"Segunda coluna: {coluna_2}")
 
# Selecionar a primeira linha
linha_1 = array_2d[0, :]
print(f"Primeira linha: {linha_1}")

c) Indexação Booleana

Permite selecionar elementos de um array que satisfaçam uma determinada condição.

data = np.array([10, 25, 5, 40, 15, 30])
print(f"Dados: {data}")
 
# Selecionar elementos maiores que 20
condicao = data > 20
print(f"Condição booleana: {condicao}")
elementos_maiores_que_20 = data[condicao]
print(f"Elementos maiores que 20: {elementos_maiores_que_20}")
 
# Combinando condições
condicao_complexa = (data > 10) & (data < 30)
elementos_entre_10_e_30 = data[condicao_complexa]
print(f"Elementos entre 10 e 30 (exclusivo): {elementos_entre_10_e_30}")

d) Indexação "Fancy"

Permite selecionar elementos usando um array de índices.

arr_fancy = np.array([100, 200, 300, 400, 500])
indices = np.array([0, 2, 4]) # Queremos o 1º, 3º e 5º elemento
selected_elements = arr_fancy[indices]
print(f"Elementos selecionados por índices: {selected_elements}")
 
# Em 2D
matriz_fancy = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
linhas_selecionadas = np.array([0, 2]) # Selecionar a primeira e a terceira linha
colunas_selecionadas = np.array([0, 2]) # Selecionar a primeira e a terceira coluna
 
# Selecionar elementos nas posições (0,0), (2,2)
elementos_especificos = matriz_fancy[linhas_selecionadas, colunas_selecionadas]
print(f"\nMatriz:\n{matriz_fancy}")
print(f"Elementos específicos (diagonal principal): {elementos_especificos}")

2.5. Operações Básicas com Arrays ➕➖✖️➗

As operações em NumPy são geralmente elemento a elemento (element-wise) por padrão, o que as torna muito eficientes.

arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
 
print(f"Array 1: {arr1}")
print(f"Array 2: {arr2}")
 
# Soma
print(f"Soma: {arr1 + arr2}")
 
# Subtração
print(f"Subtração: {arr1 - arr2}")
 
# Multiplicação (elemento a elemento)
print(f"Multiplicação (elemento a elemento): {arr1 * arr2}")
 
# Divisão
print(f"Divisão: {arr1 / arr2}")
 
# Potência
print(f"Potência: {arr1 ** 2}") # Eleva cada elemento de arr1 ao quadrado
 
# Operações com escalares
print(f"Array 1 + 10: {arr1 + 10}")
print(f"Array 2 * 3: {arr2 * 3}")

Operações Matriciais

Para multiplicação de matrizes (produto escalar), usamos np.dot() ou o operador @ (Python 3.5+).

matriz_a = np.array([[1, 2], [3, 4]])
matriz_b = np.array([[5, 6], [7, 8]])
 
print(f"Matriz A:\n{matriz_a}")
print(f"Matriz B:\n{matriz_b}")
 
# Produto escalar de matrizes usando np.dot()
produto_dot = np.dot(matriz_a, matriz_b)
print(f"\nProduto escalar (np.dot):\n{produto_dot}")
 
# Produto escalar de matrizes usando o operador @
produto_at = matriz_a @ matriz_b
print(f"\nProduto escalar (@):\n{produto_at}")

Broadcasting 📡

Broadcasting é um mecanismo poderoso que permite que o NumPy execute operações em arrays com diferentes shapes (formas). Ele tenta "esticar" o array menor para que ele tenha a mesma forma do array maior, permitindo a operação.

Regras Básicas de Broadcasting:

  1. Se os arrays não tiverem o mesmo número de dimensões, a forma do array com menos dimensões é prefixada com 1s até que ambos os arrays tenham o mesmo número de dimensões.
  2. Duas dimensões são compatíveis quando:
    • elas são iguais.
    • uma delas é 1.
arr_broad = np.array([[1, 2, 3], [4, 5, 6]]) # Shape (2, 3)
scalar = 10                                 # Shape ()
vetor = np.array([100, 200, 300])           # Shape (3,)
 
print(f"Array:\n{arr_broad}")
print(f"Escalar: {scalar}")
print(f"Vetor: {vetor}")
 
# Broadcasting com escalar: o escalar é "esticado" para o shape do array
result_scalar = arr_broad + scalar
print(f"\nArray + Escalar:\n{result_scalar}")
 
# Broadcasting com vetor: o vetor é "esticado" ao longo das linhas
# (3,) -> (1, 3) -> (2, 3)
result_vetor = arr_broad + vetor
print(f"\nArray + Vetor:\n{result_vetor}")
 
# Exemplo de Broadcasting que falha (shapes incompatíveis)
# vetor_incompativel = np.array([10, 20]) # Shape (2,)
# result_fail = arr_broad + vetor_incompativel # Erro: (2,3) vs (2,)
# print(result_fail)

2.6. Funções Universais (Ufuncs) e Agregações ✨

Ufuncs (Universal Functions) são funções que operam elemento a elemento em arrays NumPy. Elas são implementadas em C, tornando-as extremamente rápidas.

arr_ufunc = np.array([1, 4, 9, 16])
print(f"Array: {arr_ufunc}")
 
# Raiz quadrada
print(f"Raiz quadrada (np.sqrt): {np.sqrt(arr_ufunc)}")
 
# Exponencial
print(f"Exponencial (np.exp): {np.exp(arr_ufunc)}")
 
# Funções trigonométricas (ex: seno)
arr_angles = np.array([0, np.pi/2, np.pi])
print(f"\nÂngulos: {arr_angles}")
print(f"Seno (np.sin): {np.sin(arr_angles)}")

Funções de Agregação (Reduções)

Ufuncs também incluem funções de agregação que calculam um único valor a partir de um array (ou ao longo de um eixo).

  • np.sum(): Soma de todos os elementos.
  • np.min(): Valor mínimo.
  • np.max(): Valor máximo.
  • np.mean(): Média.
  • np.std(): Desvio padrão.
  • np.var(): Variância.

O parâmetro axis é crucial para especificar ao longo de qual dimensão a operação deve ser realizada.

  • axis=0: Opera ao longo das colunas (reduz as linhas).
  • axis=1: Opera ao longo das linhas (reduz as colunas).
matrix = np.array([[1, 2, 3], [4, 5, 6]])
print(f"Matriz:\n{matrix}")
 
# Soma total
print(f"Soma total: {np.sum(matrix)}")
 
# Média total
print(f"Média total: {np.mean(matrix)}")
 
# Soma ao longo das colunas (axis=0): [1+4, 2+5, 3+6]
print(f"Soma por coluna (axis=0): {np.sum(matrix, axis=0)}")
 
# Soma ao longo das linhas (axis=1): [1+2+3, 4+5+6]
print(f"Soma por linha (axis=1): {np.sum(matrix, axis=1)}")
 
# Valor máximo por coluna
print(f"Máximo por coluna (axis=0): {np.max(matrix, axis=0)}")

3. Exercícios e Desafios Conceituais 🧠

Para consolidar o conhecimento, tente responder a estas perguntas e resolver os pequenos desafios de código.

  1. Diferença Fundamental: Qual a principal vantagem de usar um ndarray do NumPy em vez de uma lista Python para armazenar dados numéricos em larga escala?
  2. Criação de Array: Crie um array NumPy 4x3 preenchido com o número 5.
  3. Atributos: Dado o array arr = np.array([[1.5, 2.5], [3.5, 4.5]]), quais seriam os valores de arr.shape, arr.ndim e arr.dtype?
  4. Fatiamento: Como você selecionaria as duas últimas linhas e a segunda coluna do array matriz = np.array([[10, 20, 30], [40, 50, 60], [70, 80, 90], [100, 110, 120]])?
  5. Indexação Booleana: Crie um array de 10 números inteiros aleatórios entre 0 e 100. Em seguida, use indexação booleana para extrair todos os números pares.
  6. Broadcasting: Explique com suas palavras o conceito de broadcasting no NumPy e forneça um exemplo simples onde ele é aplicado.
  7. Agregação: Dada a matriz M = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), calcule a média de cada coluna.

4. Resumo e Próximos Passos 🚀➡️

Nesta aula, desvendamos o NumPy, a biblioteca essencial para computação numérica em Python:

  • ndarray: Aprendemos que o objeto central do NumPy é o ndarray, uma estrutura de dados homogênea, eficiente e multidimensional.
  • Criação: Exploramos diversas formas de criar arrays, desde listas Python até funções como zeros, ones, arange, linspace e geradores de números aleatórios.
  • Atributos: Vimos como inspecionar as características de um array usando atributos como shape, ndim, size e dtype.
  • Manipulação: Dominamos as técnicas de indexação, fatiamento, indexação booleana e indexação "fancy" para acessar e modificar elementos de arrays.
  • Operações: Entendemos que as operações em NumPy são vetorizadas e elemento a elemento por padrão, e aprendemos sobre o poderoso mecanismo de broadcasting.
  • Ufuncs e Agregações: Conhecemos as funções universais (ufuncs) para operações rápidas e as funções de agregação como sum, mean, max, com o uso do parâmetro axis.

O domínio do NumPy é um passo gigantesco para qualquer um que deseje trabalhar com dados e Machine Learning em Python. Ele fornece a base para muitas outras bibliotecas que veremos em breve.

Nos próximos módulos, exploraremos:

  • Pandas: A biblioteca para manipulação e análise de dados tabulares.
  • Matplotlib e Seaborn: Ferramentas para visualização de dados.
  • Introdução aos Modelos de Machine Learning: Começaremos a construir nossos primeiros modelos!

Continue praticando e experimentando com o NumPy. A fluência virá com a prática! Nos vemos na próxima aula! 👋

© 2025 Escola All Dev. Todos os direitos reservados.

Introdução ao NumPy: Trabalhando com Arrays Numéricos (Documentação NumPy) - Fundamentos do Machine Learning com Python | escola.all.dev.br