NumPy: Conceitos Centrais do ndarray
Última atualização: 2026-08-26
A estrutura de dados central do NumPy é o ndarray — o array N-dimensional. Ele se parece com uma lista Python, mas por baixo dos panos é completamente diferente: tipo fixo, memória contígua, operações vetorizadas. Entender os detalhes internos do ndarray é o primeiro passo para usar o NumPy efetivamente.
1. O que Você Aprenderá
- ❶ A essência do ndarray: homogêneo, tipo fixo, memória contígua
- ❷ Dimensões (ndim), shape e size
- ❸ A intuição por trás dos eixos
- ❹ O sistema dtype e o consumo de memória
- ❺ Comparação do modelo de memória ndarray vs lista
2. Uma História Real de um Analista de Dados
(1) O Problema: Excel em Python, Confusão
Bob tem uma tabela Excel de 3 linhas e 4 colunas. Ele a armazena como uma lista Python de listas — 3 sublistas aninhadas dentro de uma lista externa. Ele quer somar a segunda coluna, então escreve um loop for para iterar por cada linha. Para 1 milhão de linhas, isso leva 5 segundos. Pior, quando uma string se infiltra em uma das linhas, o loop não falha até chegar àquela célula.
(2) A Solução ndarray
Charlie sugere que Bob converta a tabela para um ndarray com np.array. NumPy achata os dados em um bloco contíguo de memória e realiza a soma da coluna em uma operação vetorizada em nível C — as mesmas 1 milhão de linhas levam apenas 5 milissegundos. Os tipos são verificados no momento da criação, então uma string errante é capturada imediatamente, não no meio da computação.
(3) O Resultado
- Computação 1000x mais rápida
- Incompatibilidades de tipo são capturadas no momento da criação
- Metadados como shape, eixos e strides tornam operações multidimensionais intuitivas
3. A Essência do ndarray
(1) Tipos de Dados Homogêneos
Todos os elementos de um ndarray devem ser do mesmo tipo. Isso é fundamentalmente diferente das listas Python — listas podem misturar inteiros, strings e até objetos, enquanto ndarray permite apenas um dtype.
import numpy as np
a = np.array([1, 2, 3])
print(a.dtype) # int64
b = np.array([1.0, 2.0, 3.0])
print(b.dtype) # float64
c = np.array([1, 2.0, 3])
print(c.dtype) # float64 (promoção automática)
> **Saída:** Execute NumPy 2.x no seu ambiente Python local para ver a saída do ndarray. O servidor Piston não tem NumPy pré-instalado — instale localmente (`pip install numpy`) e compare. Os valores reais podem variar conforme a versão do NumPy e a semente aleatória.
(2) Armazenamento em Memória Contígua
Listas Python armazenam um array de ponteiros — cada ponteiro aponta para um PyObject separado espalhado pela memória heap. ndarray, por outro lado, organiza todos os dados em um único bloco contíguo de memória, sem sobrecarga de ponteiros e excelente eficiência de cache da CPU.
| Característica | Lista Python | ndarray |
|---|---|---|
| Tipo de elemento | Qualquer mistura | Mesmo dtype |
| Armazenamento | Array de ponteiros → PyObjects espalhados | Bloco de memória contíguo |
| Sobrecarga por elemento | 28+ bytes (cabeçalho PyObject) | Dependente do dtype (1/2/4/8 bytes) |
| Cache-friendly | Ruim (salto de ponteiros) | Excelente (acesso sequencial) |
| Segurança de tipo | Nenhuma (capturada em tempo de execução) | Verificada na criação |
▶ Exemplo
> **Saída:** Execute NumPy 2.x no seu ambiente Python local para ver a saída do ndarray. O servidor Piston não tem NumPy pré-instalado — instale localmente (`pip install numpy`) e compare. Os valores reais podem variar conforme a versão do NumPy e a semente aleatória.
: Propriedades de arrays 0D/1D/2D/3D (Dificuldade ⭐)
import numpy as np
a0 = np.array(42) # 0D escalar
a1 = np.array([1, 2, 3]) # 1D
a2 = np.array([[1, 2], [3, 4]]) # 2D
a3 = np.array([[[1, 2], [3, 4]],
[[5, 6], [7, 8]]]) # 3D
for name, arr in [("0D", a0), ("1D", a1), ("2D", a2), ("3D", a3)]:
print(f"{name}: ndim={arr.ndim}, shape={arr.shape}, size={arr.size}")
# 0D: ndim=0, shape=(), size=1
# 1D: ndim=1, shape=(3,), size=3
# 2D: ndim=2, shape=(2,2), size=4
# 3D: ndim=3, shape=(2,2,2), size=8
> **Saída:** Execute NumPy 2.x no seu ambiente Python local para ver a saída do ndarray. O servidor Piston não tem NumPy pré-instalado — instale localmente (`pip install numpy`) e compare. Os valores reais podem variar conforme a versão do NumPy e a semente aleatória.
4. Dimensões, Shape e Size
(1) ndim / shape / size
| Atributo | Significado | Exemplo (shape=(3,4)) |
|---|---|---|
ndim |
Número de dimensões (eixos) | 2 |
shape |
Comprimento de cada eixo, como tupla | (3, 4) |
size |
Número total de elementos = produto dos comprimentos dos eixos | 12 |
import numpy as np
a = np.zeros((3, 4))
print(a.ndim) # 2
print(a.shape) # (3, 4)
print(a.size) # 12
> **Saída:** Execute NumPy 2.x no seu ambiente Python local para ver a saída do ndarray. O servidor Piston não tem NumPy pré-instalado — instale localmente (`pip install numpy`) e compare. Os valores reais podem variar conforme a versão do NumPy e a semente aleatória.
(2) Strides
strides indica quantos bytes pular na memória para mover um passo ao longo de um dado eixo.
| Atributo | Significado | shape=(3,4) dtype=int64 |
|---|---|---|
strides |
Stride por eixo (bytes) | (32, 8) |
itemsize |
Bytes por elemento | 8 |
import numpy as np
a = np.zeros((3, 4), dtype=np.int64)
print(a.strides) # (32, 8)
# eixo 0: mover 1 linha = 4 * 8 = 32 bytes
# eixo 1: mover 1 coluna = 1 * 8 = 8 bytes
> **Saída:** Execute NumPy 2.x no seu ambiente Python local para ver a saída do ndarray. O servidor Piston não tem NumPy pré-instalado — instale localmente (`pip install numpy`) e compare. Os valores reais podem variar conforme a versão do NumPy e a semente aleatória.
▶ Exemplo
> **Saída:** Execute NumPy 2.x no seu ambiente Python local para ver a saída do ndarray. O servidor Piston não tem NumPy pré-instalado — instale localmente (`pip install numpy`) e compare. Os valores reais podem variar conforme a versão do NumPy e a semente aleatória.
: Visualização de strides (Dificuldade ⭐⭐)
import numpy as np
a = np.arange(12, dtype=np.int32).reshape(3, 4)
print(a)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(f"shape: {a.shape}") # (3, 4)
print(f"strides: {a.strides}") # (16, 4)
print(f"itemsize: {a.itemsize}") # 4
# stride do eixo 0: 4 elementos * 4 bytes = 16
# stride do eixo 1: 1 elemento * 4 bytes = 4
> **Saída:** Execute NumPy 2.x no seu ambiente Python local para ver a saída do ndarray. O servidor Piston não tem NumPy pré-instalado — instale localmente (`pip install numpy`) e compare. Os valores reais podem variar conforme a versão do NumPy e a semente aleatória.
5. Intuição Por Trás dos Eixos
Eixos são a chave para entender operações com ndarray. axis=0 é a dimensão mais externa, axis=1 é a próxima, e assim por diante.
| Eixo | Posição no shape | Intuição | 2D shape=(3,4) | 3D shape=(2,3,4) |
|---|---|---|---|---|
| axis=0 | 0º | Mais externo | Direção da linha (através das linhas) | Ao longo do eixo "camada" |
| axis=1 | 1º | Segundo | Direção da coluna (através das colunas) | Ao longo do eixo "linha" |
| axis=2 | 2º | Mais interno | — | Ao longo do eixo "coluna" |
Dica de memorização: axis=k corresponde à direção de shape[k]. Fazer sum/reduce ao longo desse eixo "elimina" aquela dimensão.
import numpy as np
a = np.arange(24).reshape(2, 3, 4)
print(a.shape) # (2, 3, 4)
print(a.sum(axis=0).shape) # (3, 4) — eixo 0 eliminado
print(a.sum(axis=1).shape) # (2, 4) — eixo 1 eliminado
print(a.sum(axis=2).shape) # (2, 3) — eixo 2 eliminado
> **Saída:** Execute NumPy 2.x no seu ambiente Python local para ver a saída do ndarray. O servidor Piston não tem NumPy pré-instalado — instale localmente (`pip install numpy`) e compare. Os valores reais podem variar conforme a versão do NumPy e a semente aleatória.
6. dtype e Uso de Memória
(1) Sistema de Tipos dtype
| dtype | Descrição | Bytes | Intervalo |
|---|---|---|---|
np.int8 |
Inteiro com sinal de 8 bits | 1 | -128 ~ 127 |
np.int32 |
Inteiro com sinal de 32 bits | 4 | -2^31 ~ 2^31-1 |
np.int64 |
Inteiro com sinal de 64 bits | 8 | -2^63 ~ 2^63-1 |
np.float32 |
Ponto flutuante de 32 bits | 4 | ~±3.4e38, precisão de 7 dígitos |
np.float64 |
Ponto flutuante de 64 bits | 8 | ~±1.8e308, precisão de 15 dígitos |
np.bool_ |
Booleano | 1 | True/False |
np.complex128 |
Complexo de 128 bits | 16 | Dois float64 |
(2) Modelo de Memória: ndarray Não É uma Lista Aninhada
graph TB
A["Objeto ndarray"] --> B["ponteiro de dados<br/>bloco de memória bruta"]
A --> C["dtype<br/>tipo de elemento"]
A --> D["shape<br/>(3, 4)"]
A --> E["strides<br/>(32, 8)"]
B --> F["0,0 | 0,1 | 0,2 | 0,3 | 1,0 | 1,1 | ... | 2,3"]
style A fill:#4CAF50,color:#fff
style B fill:#2196F3,color:#fff
style F fill:#FF9800,color:#fff
> **Saída:** Execute NumPy 2.x no seu ambiente Python local para ver a saída do ndarray. O servidor Piston não tem NumPy pré-instalado — instale localmente (`pip install numpy`) e compare. Os valores reais podem variar conforme a versão do NumPy e a semente aleatória.
(3) Tabela de Referência Shape-Memória
| shape | ndim | size | dtype=float64 nbytes | strides |
|---|---|---|---|---|
| (10,) | 1 | 10 | 80 | (8,) |
| (3, 4) | 2 | 12 | 96 | (32, 8) |
| (2, 3, 4) | 3 | 24 | 192 | (96, 32, 8) |
| (5, 2, 3, 4) | 4 | 120 | 960 | (192, 96, 32, 8) |
▶ Exemplo
> **Saída:** Execute NumPy 2.x no seu ambiente Python local para ver a saída do ndarray. O servidor Piston não tem NumPy pré-instalado — instale localmente (`pip install numpy`) e compare. Os valores reais podem variar conforme a versão do NumPy e a semente aleatória.
: Comparação de memória por dtype (Dificuldade ⭐)
import numpy as np
for dtype in [np.int8, np.int32, np.int64, np.float32, np.float64]:
a = np.zeros(1000, dtype=dtype)
print(f"{dtype.__name__:10s} itemsize={a.itemsize} nbytes={a.nbytes}")
# int8 itemsize=1 nbytes=1000
# int32 itemsize=4 nbytes=4000
# int64 itemsize=8 nbytes=8000
# float32 itemsize=4 nbytes=4000
# float64 itemsize=8 nbytes=8000
> **Saída:** Execute NumPy 2.x no seu ambiente Python local para ver a saída do ndarray. O servidor Piston não tem NumPy pré-instalado — instale localmente (`pip install numpy`) e compare. Os valores reais podem variar conforme a versão do NumPy e a semente aleatória.
▶ Exemplo
> **Saída:** Execute NumPy 2.x no seu ambiente Python local para ver a saída do ndarray. O servidor Piston não tem NumPy pré-instalado — instale localmente (`pip install numpy`) e compare. Os valores reais podem variar conforme a versão do NumPy e a semente aleatória.
: Verificar que reshape não copia dados (Dificuldade ⭐⭐)
import numpy as np
a = np.arange(12, dtype=np.int64)
b = a.reshape(3, 4)
print(a.shape, b.shape) # (12,) (3, 4)
print(a.strides, b.strides) # (8,) (32, 8)
print(a.nbytes, b.nbytes) # 96 96
b[0, 0] = 999
print(a[0]) # 999 — mesma memória!
> **Saída:** Execute NumPy 2.x no seu ambiente Python local para ver a saída do ndarray. O servidor Piston não tem NumPy pré-instalado — instale localmente (`pip install numpy`) e compare. Os valores reais podem variar conforme a versão do NumPy e a semente aleatória.
7. Exemplo Abrangente: Análise Profunda de Array 3D
▶ Exemplo
> **Saída:** Execute NumPy 2.x no seu ambiente Python local para ver a saída do ndarray. O servidor Piston não tem NumPy pré-instalado — instale localmente (`pip install numpy`) e compare. Os valores reais podem variar conforme a versão do NumPy e a semente aleatória.
: Análise completa de propriedades de um array 3×4×2 (Dificuldade ⭐⭐⭐)
import numpy as np
a = np.arange(24, dtype=np.int64).reshape(3, 4, 2)
print("=== Array completo ===")
print(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]]]
print(f"ndim: {a.ndim}") # 3
print(f"shape: {a.shape}") # (3, 4, 2)
print(f"size: {a.size}") # 24
print(f"dtype: {a.dtype}") # int64
print(f"itemsize:{a.itemsize}") # 8
print(f"nbytes: {a.nbytes}") # 192 (24 * 8)
print(f"strides: {a.strides}") # (64, 16, 8)
# eixo 0: 4*2*8 = 64 bytes por passo
# eixo 1: 2*8 = 16 bytes por passo
# eixo 2: 1*8 = 8 bytes por passo
# Verificar strides manualmente
expected_strides = (
a.shape[1] * a.shape[2] * a.itemsize, # 4*2*8 = 64
a.shape[2] * a.itemsize, # 2*8 = 16
a.itemsize # 8
)
print(f"manual strides: {expected_strides}") # (64, 16, 8)
print(f"match: {a.strides == expected_strides}") # True
# Redução de eixo
print(a.sum(axis=0).shape) # (4, 2)
print(a.sum(axis=1).shape) # (3, 2)
print(a.sum(axis=2).shape) # (3, 4)
> **Saída:** Execute NumPy 2.x no seu ambiente Python local para ver a saída do ndarray. O servidor Piston não tem NumPy pré-instalado — instale localmente (`pip install numpy`) e compare. Os valores reais podem variar conforme a versão do NumPy e a semente aleatória.
❓ Perguntas Frequentes
P: O ndarray pode armazenar tipos diferentes? R: Não. ndarray é homogêneo — todos os elementos devem compartilhar o mesmo dtype. Se você tentar misturar tipos, NumPy fará uma promoção automática (ex.: int → float). Tipos incompatíveis gerarão um erro.
P: Qual é a linha — axis=0 ou axis=1? R: Em um array 2D, axis=0 é a direção da linha (através das linhas) e axis=1 é a direção da coluna (através das colunas). Somar ao longo de axis=0 "comprime as linhas em uma linha"; somar ao longo de axis=1 "comprime as colunas em uma coluna".
P: O que são strides? R: Strides é uma tupla indicando quantos bytes pular na memória para mover um passo ao longo de cada eixo. Eles são a chave para como ndarray pode fazer reshape/transposição sem copiar dados.
P: Qual a diferença entre float64 e float32? R: float64 ocupa 8 bytes com ~15 dígitos decimais de precisão; float32 ocupa 4 bytes com ~7 dígitos. float32 usa metade da memória, mas tem precisão menor. Aprendizado profundo comumente usa float32, enquanto computação científica tipicamente usa float64.
P: Por que ndarray é chamado de "homogêneo"? R: Porque todos os elementos são empacotados de forma contígua na memória, cada um ocupando o mesmo número de bytes e interpretados da mesma forma (determinada pelo dtype compartilhado). Isso permite que a CPU processe múltiplos elementos de uma vez com instruções vetorizadas — a razão fundamental do alto desempenho do NumPy.
📖 Resumo
- ndarray é um array N-dimensional homogêneo, de tipo fixo e memória contígua
ndimé o número de dimensões,shapeé a tupla dos comprimentos dos eixos,sizeé a contagem total de elementos- Eixos são numerados do mais externo ao mais interno: axis=0 é o mais externo, axis=ndim-1 é o mais interno
stridesdescreve o deslocamento de memória para mover ao longo de cada eixo; reshape não altera os dados subjacentesdtypedetermina o tipo de elemento eitemsize;nbytes = size * itemsize- O modelo de memória contígua do ndarray vs o modelo de salto de ponteiros da lista é a raiz da diferença de desempenho
📝 Exercícios
-
Básico (Dificuldade ⭐): Crie quatro arrays de comprimento 5000 com dtypes int8, int32, float32 e float64. Imprima o
nbytesde cada array e explique por que diferentes dtypes usam diferentes quantidades de memória. -
Intermediário (Dificuldade ⭐⭐): Dado um ndarray com shape=(5, 3, 2) e dtype=int16, calcule seus strides e nbytes manualmente, então verifique seu cálculo com código.
-
Desafio (Dificuldade ⭐⭐⭐): Explique com suas próprias palavras como um ndarray com shape=(2,3,4) é disposto na memória. Desenhe um diagrama, rotule os strides e mostre o deslocamento de memória para cada elemento.