Aula 3 de 6
Gráficos com matplotlib
Objetivo: desenhar funções, dados e imagens.
Por que isso importa: em ML, o gráfico é o seu depurador. É olhando que você percebe que o modelo parou de aprender, que um dado tem um valor absurdo ou que dois grupos se misturam. Nas próximas aulas (álgebra linear, cálculo e estatística), todo conceito vai vir acompanhado de um gráfico.
1. Anatomia: Figure e Axes
O matplotlib confunde no começo porque tem dois jeitos de usar:
plt.plot(...)(jeito implícito): o matplotlib guarda uma “figura atual” numa variável global escondida e desenha nela. É prático, mas é mágica: você não vê onde está desenhando.fig, ax = plt.subplots()(jeito explícito): você recebe os objetos e chama métodos neles. É o que vamos usar, e é o que você vai ver em código sério.
Você vai encontrar os dois jeitos em exemplos na internet. Eles fazem a mesma coisa.
┌─────────────── Figure (fig) ───────────────┐
│ ┌──── Axes (ax) ─────┐ ┌──── Axes ─────┐ │
│ │ título │ │ │ │
│ │ ╱╲ ← linhas │ │ · · · │ │
│ │ ╱ ╲ │ │ · · · │ │
│ │ eixo x, eixo y │ │ │ │
│ └────────────────────┘ └───────────────┘ │
└────────────────────────────────────────────┘
figé a tela inteira, o que vira a imagem final.axé uma área de desenho com seus eixos. É como octx = canvas.getContext("2d")do JS ou o objetoGraphicsdo C#. Uma figura pode ter vários.
Cuidado com o nome: Axes (a área do gráfico) é diferente de axis (um eixo, x ou y). Todo mundo tropeça nisso.
import numpy as np
import matplotlib.pyplot as plt
# Três cores que continuam distinguíveis para quem é daltônico (a seção 9 explica)
AZUL, LARANJA, VERDE = "#2a78d6", "#eb6834", "#1baf7a"
2. Primeiro gráfico: uma função
x = np.linspace(-3, 3, 100) # 100 pontos de -3 a 3 (lembra: o linspace INCLUI o fim)
y = x ** 2 # vetorizado: calcula y para todos os x de uma vez
fig, ax = plt.subplots(figsize=(6, 4)) # tamanho em polegadas: (largura, altura)
ax.plot(x, y, color=AZUL)
ax.set_title("f(x) = x²")
ax.set_xlabel("x")
ax.set_ylabel("f(x)")
ax.grid(alpha=0.3) # grade clarinha, pra não brigar com a curva
plt.show()

Sem mágica: o matplotlib não sabe o que é x². Ele recebe duas listas de números e liga os pontos com retas. Com poucos pontos, dá pra ver:
x5 = np.linspace(-3, 3, 5)
x100 = np.linspace(-3, 3, 100)
fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(x100, x100 ** 2, color=AZUL, label="100 pontos")
ax.plot(x5, x5 ** 2, color=LARANJA, marker="o", label="5 pontos") # marker desenha cada ponto
ax.set_title("plot() só liga os pontos com retas")
ax.legend() # mostra os nomes dados em label=
ax.grid(alpha=0.3)
plt.show()

Por isso o linspace vem com bastante ponto: 100 retas curtinhas parecem uma curva.
3. Várias curvas no mesmo gráfico
Cada chamada de ax.plot acrescenta uma curva. Com label= e ax.legend(), cada uma ganha um nome. Aqui estão três funções de ativação, que vão aparecer na disciplina de redes neurais:
- sigmoide, : espreme qualquer número pra dentro do intervalo de 0 a 1
- tanh: parecida, só que entre -1 e 1
- ReLU, : zera os negativos e deixa passar os positivos. É a mais usada hoje
x = np.linspace(-3, 3, 200)
fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(x, 1 / (1 + np.exp(-x)), color=AZUL, label="sigmoide")
ax.plot(x, np.tanh(x), color=LARANJA, linestyle="--", label="tanh")
ax.plot(x, np.maximum(0, x), color=VERDE, linestyle=":", label="ReLU") # np.maximum compara elemento a elemento
ax.axhline(0, color="gray", linewidth=0.8) # linha horizontal em y = 0
ax.set_title("Funções de ativação")
ax.set_xlabel("x")
ax.legend()
ax.grid(alpha=0.3)
plt.show()

Dois detalhes:
np.maximum(0, x), e nãomax(0, x): omaxdo Python compara dois valores; onp.maximumcompara0com cada elemento dex(broadcasting, da aula passada).linestylediferente, além da cor: quem é daltônico, ou imprime em preto e branco, continua distinguindo as curvas.
4. Dispersão (scatter): olhando dados
O plot liga os pontos; o scatter desenha só os pontos. É o gráfico pra olhar dados, em que cada ponto é um exemplo.
rng = np.random.default_rng(seed=0)
area = rng.uniform(40, 150, size=50) # 50 casas entre 40 e 150 m²
preco = 3 * area + 50 + rng.normal(0, 40, size=50) # preço ≈ 3 × área + 50, mais um "ruído"
fig, ax = plt.subplots(figsize=(6, 4))
ax.scatter(area, preco, color=AZUL, alpha=0.7) # alpha = transparência (ajuda quando os pontos se sobrepõem)
ax.set_title("Casas: área × preço")
ax.set_xlabel("área (m²)")
ax.set_ylabel("preço (mil R$)")
ax.grid(alpha=0.3)
plt.show()

Dá pra ver uma tendência: quanto maior a área, maior o preço. Regressão linear (disciplina 01) é achar a reta que passa o mais perto possível desses pontos.
Vamos chutar uma reta e desenhar o erro de cada casa:
def modelo(area):
return 2.5 * area + 80 # um chute: preço = 2.5 × área + 80
previsto = modelo(area)
x_reta = np.linspace(40, 150, 2) # pra desenhar uma reta, 2 pontos bastam
mse = np.mean((preco - previsto) ** 2)
fig, ax = plt.subplots(figsize=(6, 4))
ax.vlines(area, previsto, preco, color=LARANJA, alpha=0.6, label="erros") # um tracinho por casa: do previsto até o real
ax.scatter(area, preco, color=AZUL, alpha=0.7, label="casas")
ax.plot(x_reta, modelo(x_reta), color="black", label="modelo (chute)")
ax.set_title(f"Erro quadrático médio do chute: {mse:.0f}")
ax.set_xlabel("área (m²)")
ax.set_ylabel("preço (mil R$)")
ax.legend()
ax.grid(alpha=0.3)
plt.show()

O erro quadrático médio que você programou nas aulas passadas é a média dos quadrados desses tracinhos laranja. Treinar o modelo é ajustar a inclinação (2.5) e o deslocamento (80) da reta até esse número ficar o menor possível.
🧪 Experimente: mude o 2.5 e o 80 na função modelo e rode de novo. Quanto menor você consegue deixar o erro no título? Na disciplina 01, o computador vai fazer essa busca sozinho.
5. Grupos: um scatter por categoria
Muitas vezes cada ponto pertence a um grupo. Aqui temos 3 grupos de pontos em volta de 3 centros. Desenhamos um scatter por grupo, com cor e marcador diferentes.
rng = np.random.default_rng(seed=1)
centros = np.array([[0, 0], [4, 1], [1, 4]])
grupos = [c + rng.normal(0, 0.7, size=(40, 2)) for c in centros] # 40 pontos em volta de cada centro
fig, ax = plt.subplots(figsize=(5, 5))
for pontos, cor, marcador, nome in zip(grupos, [AZUL, LARANJA, VERDE], ["o", "s", "^"], ["grupo A", "grupo B", "grupo C"]):
ax.scatter(pontos[:, 0], pontos[:, 1], color=cor, marker=marcador, alpha=0.8, label=nome)
ax.set_title("Três grupos")
ax.set_aspect("equal") # mesma escala em x e y, senão as distâncias ficam distorcidas
ax.legend()
ax.grid(alpha=0.3)
plt.show()

Repare no pontos[:, 0]: é o fatiamento 2D da aula passada (todas as linhas, coluna 0, ou seja, os x).
Aqui nós sabíamos de que grupo cada ponto veio e pintamos. O k-means (disciplina 01) faz o contrário: recebe os pontos todos da mesma cor e descobre os grupos sozinho.
6. Histograma: a forma de uma distribuição
Com milhares de valores, um scatter não ajuda. O histograma divide o intervalo em faixas (bins) e conta quantos valores caem em cada uma. A altura da barra é essa contagem.
rng = np.random.default_rng(seed=2)
alturas = rng.normal(170, 8, size=5000) # 5000 alturas: média de 170 cm, desvio padrão de 8 cm
fig, ax = plt.subplots(figsize=(6, 4))
ax.hist(alturas, bins=40, color=AZUL, edgecolor="white") # a borda branca separa as barras
ax.axvline(alturas.mean(), color=LARANJA, linestyle="--", label=f"média = {alturas.mean():.1f} cm")
ax.set_title("5000 alturas sorteadas")
ax.set_xlabel("altura (cm)")
ax.set_ylabel("quantidade")
ax.legend()
plt.show()

Esse formato de sino é a distribuição normal, tema da aula 00.06.
🧪 Experimente bins=5 e bins=300. Com poucas faixas, a forma some; com faixas demais, vira ruído.
7. Vários gráficos lado a lado (subplots)
plt.subplots(linhas, colunas) devolve a figura e um array NumPy de Axes. Numa linha só, você acessa com axs[0], axs[1]…; numa grade, com axs[linha, coluna], a mesma sintaxe da aula passada.
x = np.linspace(-3, 3, 200)
fig, axs = plt.subplots(1, 3, figsize=(12, 3.5))
print(type(axs), axs.shape)
for ax, grau in zip(axs, [1, 2, 3]):
ax.plot(x, x ** grau, color=AZUL)
ax.set_title(f"$x^{grau}$") # entre $...$ o texto vira fórmula matemática (é LaTeX)
ax.grid(alpha=0.3)
fig.suptitle("Potências de x") # título da figura inteira
fig.tight_layout() # ajusta os espaços pra nada se sobrepor
plt.show()
<class 'numpy.ndarray'> (3,)

8. Imagens e mapas
Um array 2D vira imagem
O imshow desenha um array 2D como imagem: cada número vira um quadradinho colorido. Você já viu isso com a foto da aula 00.02. Agora, uma imagem feita à mão:
carinha = np.array([
[0, 0, 1, 1, 1, 1, 0, 0],
[0, 1, 0, 0, 0, 0, 1, 0],
[1, 0, 1, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 0, 1, 0, 1],
[1, 0, 0, 1, 1, 0, 0, 1],
[0, 1, 0, 0, 0, 0, 1, 0],
[0, 0, 1, 1, 1, 1, 0, 0],
])
fig, ax = plt.subplots(figsize=(3, 3))
ax.imshow(carinha, cmap="gray_r") # cmap = "mapa de cores". "gray_r" = cinza invertido (r de reversed): 0 é branco e 1 é preto
ax.set_title("8×8 pixels")
ax.axis("off")
plt.show()

É literalmente isso que uma imagem é para o computador. Cada dígito da base MNIST, que você vai usar na disciplina 02, é um array 28×28 como esse.
Funções de duas variáveis: curvas de nível
Em ML, a função de custo depende de vários parâmetros. Com dois parâmetros, vira um terreno, e dá pra desenhar como um mapa topográfico: cada cor é uma “altitude”.
O problema: pra desenhar o terreno, é preciso calcular para todas as combinações de x e y. Com xs = [0, 1, 2] e ys = [10, 20], são 2 × 3 = 6 pontos. Dá pra organizar como uma tabela, com os x nas colunas e os y nas linhas:
x=0 x=1 x=2
y=10 (0, 10) (1, 10) (2, 10)
y=20 (0, 20) (1, 20) (2, 20)
Em C#, você preencheria essa tabela com dois for aninhados:
for (int i = 0; i < ys.Length; i++) // cada linha (um y)
for (int j = 0; j < xs.Length; j++) // cada coluna (um x)
Z[i, j] = f(xs[j], ys[i]);
O meshgrid pega essa tabela de pares e separa em duas tabelas do mesmo formato, uma só com o x de cada célula (X) e outra só com o y (Y):
xs = np.array([0, 1, 2])
ys = np.array([10, 20])
X, Y = np.meshgrid(xs, ys)
print(X)
print(Y)
[[0 1 2]
[0 1 2]]
[[10 10 10]
[20 20 20]]
- No
X, cada linha repete osxs, porque o x muda de coluna em coluna. - No
Y, cada coluna repete osys, porque o y muda de linha em linha.
A mesma posição nas duas tabelas dá um ponto. X[1, 2] é 2 e Y[1, 2] é 20, ou seja, a célula da linha 1, coluna 2 é o ponto (2, 20).
Pra que separar: com o x e o y de cada ponto em arrays do mesmo formato, a conta vira elemento a elemento, que é a vetorização da aula 00.02. Os dois for aninhados viram uma linha:
print(X[1, 2], Y[1, 2]) # o ponto da linha 1, coluna 2
Z = X + Y # f(x, y) = x + y, calculada na tabela inteira de uma vez
print(Z)
2 20
[[10 11 12]
[20 21 22]]
Cada célula de Z é o x mais o y daquele ponto. Abaixo, com 200 valores de x e 200 de y, o meshgrid monta uma grade de 40.000 pontos, e Z = X ** 2 + Y ** 2 calcula a “altitude” de todos eles de uma vez. O contourf só pinta essa tabela.
x = np.linspace(-3, 3, 200)
y = np.linspace(-3, 3, 200)
X, Y = np.meshgrid(x, y)
Z = X ** 2 + Y ** 2 # uma "tigela": o ponto mais baixo é o (0, 0)
fig, ax = plt.subplots(figsize=(5.5, 4.5))
mapa = ax.contourf(X, Y, Z, levels=20, cmap="Blues") # contourf = curvas de nível preenchidas
fig.colorbar(mapa, ax=ax, label="f(x, y)") # a legenda das cores
ax.plot(0, 0, marker="*", markersize=15, color=LARANJA, label="mínimo")
ax.set_title("f(x, y) = x² + y²")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_aspect("equal")
ax.legend()
plt.show()

Guarde esta imagem: o gradiente descendente (disciplina 01) é uma bolinha que começa em algum ponto desse mapa e vai descendo até a estrela.
9. Boas práticas (e como salvar)
- Sempre ponha título e nome nos eixos, com a unidade. Um gráfico sem rótulo vira uma charada.
- Com duas ou mais séries, use legenda. E diferencie também por marcador ou tipo de linha, não só pela cor.
- Use uma escala de y por gráfico. Se duas grandezas têm escalas muito diferentes (preço em milhares e quartos de 1 a 5), faça dois gráficos lado a lado, em vez de um eixo y de cada lado.
- Para intensidade (mapas e curvas de nível), use um colormap de uma cor só, do claro ao escuro (
"Blues","Greys"). O arco-íris ("jet") cria “fronteiras” que não existem nos dados. - Deixe a grade clarinha (
alpha=0.3): ela ajuda a ler, mas não pode chamar mais atenção que os dados. - Escolha cores pensando em daltonismo. As três cores desta aula foram validadas para continuar distinguíveis. Com mais de 3 grupos num scatter, prefira juntar os menores em “outros” ou separar em subplots.
Para salvar um gráfico em arquivo: fig.savefig("grafico.png", dpi=150, bbox_inches="tight"). O bbox_inches="tight" corta as bordas brancas sobrando.
Exercícios
Cada exercício tem uma função que recebe o ax (ou os axs) e desenha nele. A célula já cria a figura, chama a sua função e confere o resultado. A imagem “Objetivo” mostra aonde você precisa chegar.
As cores e os detalhes de estilo ficam por sua conta. A conferência olha só o essencial: a curva certa, os títulos e a legenda.
Código de apoio dos exercícios
from matplotlib.contour import ContourSet
def _tem_curva(ax, f, min_pontos=2):
# existe alguma linha cujos pontos seguem y = f(x)?
for linha in ax.lines:
x = np.asarray(linha.get_xdata(), dtype=float)
y = np.asarray(linha.get_ydata(), dtype=float)
if len(x) >= min_pontos and np.allclose(y, f(x), atol=1e-6):
return True
return False
def _tem_horizontal(ax, y0, tracejada=False):
for linha in ax.lines:
y = np.asarray(linha.get_ydata(), dtype=float)
if len(y) and np.allclose(y, y0) and (not tracejada or linha.get_linestyle() not in ("-", "None")):
return True
return False
def _tem_ponto(ax, x0, y0):
# vale tanto ax.plot(x0, y0, marker=...) quanto ax.scatter
for linha in ax.lines:
x, y = np.asarray(linha.get_xdata(), dtype=float), np.asarray(linha.get_ydata(), dtype=float)
if len(x) == 1 and np.isclose(x[0], x0) and np.isclose(y[0], y0):
return True
for colecao in ax.collections:
offsets = np.asarray(colecao.get_offsets())
if offsets.size and np.any(np.all(np.isclose(offsets, [x0, y0]), axis=1)):
return True
return False
def confere_grafico(ax, itens):
if not (ax.lines or ax.collections or ax.patches or ax.images):
print("⏳ ainda não implementado")
return
for descricao, ok in itens.items():
print(("✅ " if ok else "❌ falta: ") + descricao)
def _basicos(ax, rotulos=True):
itens = {"título": ax.get_title() != ""}
if rotulos:
itens["nome dos dois eixos"] = ax.get_xlabel() != "" and ax.get_ylabel() != ""
return itens
def confere_ex1(ax):
confere_grafico(ax, {
**_basicos(ax),
"curva da sigmoide de -6 a 6": _tem_curva(ax, lambda x: 1 / (1 + np.exp(-x)), min_pontos=10),
"linha horizontal tracejada em y = 0.5": _tem_horizontal(ax, 0.5, tracejada=True),
})
def confere_ex2(ax):
confere_grafico(ax, {
**_basicos(ax),
"pontos dos alunos (scatter)": len(ax.collections) >= 1,
"reta do modelo: nota = 0.8 × horas + 2": _tem_curva(ax, lambda x: 0.8 * x + 2),
"legenda": ax.get_legend() is not None,
})
def confere_ex3(ax):
transparentes = len(ax.patches) > 0 and all((p.get_alpha() or 1) < 1 for p in ax.patches)
legenda = ax.get_legend()
confere_grafico(ax, {
**_basicos(ax),
"barras semitransparentes": transparentes,
"legenda com as duas turmas": legenda is not None and len(legenda.get_texts()) >= 2,
})
def confere_ex4(axs):
print("Gráfico da esquerda:")
confere_grafico(axs[0], {
**_basicos(axs[0], rotulos=False),
"curva de f(x) = (x - 2)²": _tem_curva(axs[0], lambda x: (x - 2) ** 2, min_pontos=10),
})
print("Gráfico da direita:")
confere_grafico(axs[1], {
**_basicos(axs[1], rotulos=False),
"curva de f'(x) = 2(x - 2)": _tem_curva(axs[1], lambda x: 2 * (x - 2), min_pontos=10),
"linha horizontal em y = 0": _tem_horizontal(axs[1], 0),
})
def confere_ex5(fig, ax):
confere_grafico(ax, {
**_basicos(ax),
"curvas de nível (contourf)": any(isinstance(c, ContourSet) for c in ax.collections),
"barra de cores (colorbar)": len(fig.axes) >= 2,
"mínimo marcado em (1, -1)": _tem_ponto(ax, 1, -1),
})
Exercício 1 · Sigmoide
Desenhe a sigmoide para x de -6 a 6, com título, nome nos eixos, grade e uma linha horizontal tracejada em y = 0.5.
Por que 0.5? Na regressão logística, a sigmoide dá a “probabilidade” de algo ser verdade, e 0.5 é a fronteira da decisão: acima dela, o modelo responde “sim”.
Objetivo:

def desenhar_sigmoide(ax):
... # seu código aqui
fig, ax = plt.subplots(figsize=(6, 4))
desenhar_sigmoide(ax)
plt.show()
confere_ex1(ax)
Dica
np.exp(-x) calcula para cada elemento. A linha horizontal sai com ax.axhline(0.5, linestyle="--").
Gabarito
def desenhar_sigmoide(ax):
x = np.linspace(-6, 6, 200)
ax.plot(x, 1 / (1 + np.exp(-x)), color=AZUL)
ax.axhline(0.5, color="gray", linestyle="--")
ax.set_title("Sigmoide")
ax.set_xlabel("x")
ax.set_ylabel("σ(x)")
ax.grid(alpha=0.3)
Exercício 2 · Dados e modelo
Desenhe os dados de 30 alunos (horas de estudo × nota) como pontos, com a legenda “alunos”, e a reta do modelo nota = 0.8 × horas + 2, com a legenda “modelo”. Não esqueça título, nome nos eixos e legenda.
Objetivo:

rng = np.random.default_rng(seed=3)
horas = rng.uniform(0, 10, size=30) # horas de estudo de 30 alunos
nota = 0.8 * horas + 2 + rng.normal(0, 0.8, size=30) # nota na prova
def desenhar_estudo(ax, horas, nota):
... # seu código aqui
fig, ax = plt.subplots(figsize=(6, 4))
desenhar_estudo(ax, horas, nota)
plt.show()
confere_ex2(ax)
Dica
É a mesma receita da seção 4: ax.scatter para os pontos e ax.plot para a reta. Pra desenhar uma reta, dois pontos bastam: np.linspace(0, 10, 2).
Gabarito
def desenhar_estudo(ax, horas, nota):
ax.scatter(horas, nota, color=AZUL, alpha=0.7, label="alunos")
x = np.linspace(0, 10, 2)
ax.plot(x, 0.8 * x + 2, color=LARANJA, label="modelo")
ax.set_title("Horas de estudo × nota")
ax.set_xlabel("horas de estudo")
ax.set_ylabel("nota")
ax.legend()
ax.grid(alpha=0.3)
Exercício 3 · Comparando duas turmas
Desenhe no mesmo gráfico os histogramas das notas das turmas A e B. Use barras semitransparentes, pra uma não esconder a outra, e ponha uma legenda com o nome de cada turma.
Objetivo:

rng = np.random.default_rng(seed=4)
turma_a = rng.normal(6.0, 1.5, size=500) # notas da turma A
turma_b = rng.normal(7.5, 1.0, size=500) # notas da turma B
def desenhar_turmas(ax, turma_a, turma_b):
... # seu código aqui
fig, ax = plt.subplots(figsize=(6, 4))
desenhar_turmas(ax, turma_a, turma_b)
plt.show()
confere_ex3(ax)
Dica
Chame ax.hist duas vezes, com alpha=0.5 e label=. Truque: passe as mesmas faixas para as duas turmas, com bins=np.linspace(0, 11, 34), que as barras ficam alinhadas.
Gabarito
def desenhar_turmas(ax, turma_a, turma_b):
faixas = np.linspace(0, 11, 34) # as mesmas faixas pras duas turmas, pras barras alinharem
ax.hist(turma_a, bins=faixas, color=AZUL, alpha=0.5, label="turma A")
ax.hist(turma_b, bins=faixas, color=LARANJA, alpha=0.5, label="turma B")
ax.set_title("Notas das turmas")
ax.set_xlabel("nota")
ax.set_ylabel("quantidade de alunos")
ax.legend()
Exercício 4 · Uma função e a derivada dela, lado a lado
Em dois gráficos lado a lado, para x de -1 a 5, desenhe:
- à esquerda:
- à direita: a derivada dela, , com uma linha horizontal em y = 0
Dê um título a cada gráfico. Se quiser, marque x = 2 nos dois com uma linha vertical tracejada (ax.axvline).
Objetivo:

def desenhar_funcao_e_derivada(axs):
... # seu código aqui
fig, axs = plt.subplots(1, 2, figsize=(10, 3.5))
desenhar_funcao_e_derivada(axs)
fig.tight_layout()
plt.show()
confere_ex4(axs)
Repare no resultado: onde a função tem o ponto mais baixo (x = 2), a derivada vale zero. À esquerda do mínimo a derivada é negativa (a função desce) e à direita é positiva (a função sobe). Essa é a ideia central do gradiente descendente, e é o assunto da aula 00.05.
Dica
axs[0] e axs[1] são dois ax normais. Use o mesmo x para os dois.
Gabarito
def desenhar_funcao_e_derivada(axs):
x = np.linspace(-1, 5, 200)
axs[0].plot(x, (x - 2) ** 2, color=AZUL)
axs[0].set_title("f(x) = (x - 2)²")
axs[1].plot(x, 2 * (x - 2), color=LARANJA)
axs[1].axhline(0, color="gray", linewidth=0.8)
axs[1].set_title("f'(x) = 2(x - 2)")
for ax in axs:
ax.axvline(2, color="gray", linestyle="--")
ax.set_xlabel("x")
ax.grid(alpha=0.3)
Exercício 5 (desafio) · O terreno de uma função de custo
Desenhe as curvas de nível de para x e y de -3 a 3, com a barra de cores e o ponto mínimo, (1, -1), marcado.
Repare que o “2” na frente do deixa a tigela mais estreita numa direção. Quando a gente chegar no gradiente descendente, você vai ver que esse tipo de terreno “achatado” deixa o treino mais difícil.
Objetivo:

def desenhar_terreno(fig, ax):
... # seu código aqui
fig, ax = plt.subplots(figsize=(5.5, 4.5))
desenhar_terreno(fig, ax)
plt.show()
confere_ex5(fig, ax)
Dica
Siga a receita da seção 8: linspace → meshgrid → calcular Z → contourf → fig.colorbar. Para marcar o mínimo: ax.plot(1, -1, marker="*", markersize=15).
Gabarito
def desenhar_terreno(fig, ax):
x = np.linspace(-3, 3, 200)
y = np.linspace(-3, 3, 200)
X, Y = np.meshgrid(x, y)
Z = (X - 1) ** 2 + 2 * (Y + 1) ** 2
mapa = ax.contourf(X, Y, Z, levels=20, cmap="Blues")
fig.colorbar(mapa, ax=ax, label="f(x, y)")
ax.plot(1, -1, marker="*", markersize=15, color=LARANJA, label="mínimo")
ax.set_title("f(x, y) = (x - 1)² + 2(y + 1)²")
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_aspect("equal")
ax.legend()
Resumo
| Quero… | Código |
|---|---|
| criar uma figura | fig, ax = plt.subplots(figsize=(largura, altura)) |
| vários gráficos | fig, axs = plt.subplots(linhas, colunas) |
| curva / função | ax.plot(x, y, color=..., linestyle="--", label="...") |
| pontos (dados) | ax.scatter(x, y, alpha=0.7, marker="o") |
| distribuição | ax.hist(valores, bins=30) |
| imagem / matriz | ax.imshow(array, cmap="gray") |
| função de 2 variáveis | X, Y = np.meshgrid(x, y) e depois ax.contourf(X, Y, Z, cmap="Blues") |
| linha de referência | ax.axhline(y), ax.axvline(x) |
| textos | ax.set_title(...), ax.set_xlabel(...), ax.set_ylabel(...), ax.legend() |
| mostrar / salvar | plt.show(), fig.savefig("arquivo.png", dpi=150) |