Objetivo
Aprofundar o uso do Pandas com as operações mais usadas em projetos reais de dados: limpeza avançada de dados, agrupamento com groupby, combinação de DataFrames com merge e join, e transformações com apply e map. Estas são as ferramentas que você vai usar em todo projeto de ML para preparar os dados antes de modelar.
1. Por que Limpeza de Dados é Tão Importante?
Existe um ditado clássico em ciência de dados: "garbage in, garbage out". Um modelo treinado com dados sujos produz predições ruins, independente de quão sofisticado seja o algoritmo.
Na prática, cientistas de dados gastam entre 60% e 80% do tempo de um projeto limpando e preparando dados. As tarefas mais comuns são:
- Remover ou preencher valores ausentes
- Corrigir tipos de dados incorretos
- Remover duplicatas
- Tratar outliers
- Padronizar formatos (datas, strings, categorias)
- Combinar dados de múltiplas fontes
2. Limpeza Avançada de Dados
2.1 Duplicatas
import pandas as pd
import numpy as np
df = pd.DataFrame({
"id": [1, 2, 2, 3, 4, 4, 4],
"nome": ["Ana", "Bruno", "Bruno", "Carla", "Diego", "Diego", "Diego"],
"nota": [8.5, 7.0, 7.0, 9.2, 6.5, 6.5, 6.8]
})
# Identificar duplicatas
print(df.duplicated())
print(df.duplicated().sum()) # quantas linhas duplicadas
# Mostrar as linhas duplicadas
print(df[df.duplicated()])
# Duplicatas considerando apenas certas colunas
print(df.duplicated(subset=["id", "nome"]))
# Remover duplicatas — manter primeira ocorrência
df_unico = df.drop_duplicates()
# Manter última ocorrência
df_unico_ultimo = df.drop_duplicates(keep="last")
# Remover duplicatas por colunas específicas
df_sem_dup_nome = df.drop_duplicates(subset=["nome"])
print(f"Original: {len(df)} linhas")
print(f"Sem duplicatas: {len(df_unico)} linhas")
2.2 Outliers
np.random.seed(42)
df = pd.DataFrame({
"salario": np.concatenate([
np.random.normal(5000, 1000, 95),
[50000, 55000, 60000, -1000, 200000] # outliers artificiais
])
})
# Método 1: Z-score — remove valores a mais de N desvios padrão da média
media = df["salario"].mean()
desvio = df["salario"].std()
z_scores = (df["salario"] - media) / desvio
df_sem_outliers_z = df[z_scores.abs() < 3]
print(f"Z-score: {len(df)} → {len(df_sem_outliers_z)} linhas")
# Método 2: IQR (Interquartile Range) — mais robusto a outliers extremos
Q1 = df["salario"].quantile(0.25)
Q3 = df["salario"].quantile(0.75)
IQR = Q3 - Q1
limite_inferior = Q1 - 1.5 * IQR
limite_superior = Q3 + 1.5 * IQR
df_sem_outliers_iqr = df[
(df["salario"] >= limite_inferior) &
(df["salario"] <= limite_superior)
]
print(f"IQR: {len(df)} → {len(df_sem_outliers_iqr)} linhas")
print(f"Limites IQR: [{limite_inferior:.0f}, {limite_superior:.0f}]")
# Método 3: Clipping — em vez de remover, limitar ao intervalo
df["salario_clipado"] = df["salario"].clip(
lower=limite_inferior,
upper=limite_superior
)
2.3 Conversão e padronização de tipos
df = pd.DataFrame({
"data_cadastro": ["2024-01-15", "2024-02-20", "2024-03-10"],
"valor": ["R$ 1.500,00", "R$ 2.300,50", "R$ 890,00"],
"ativo": ["sim", "não", "sim"],
"categoria": ["A", "B", "A"]
})
# Converter strings de data para datetime
df["data_cadastro"] = pd.to_datetime(df["data_cadastro"])
print(df["data_cadastro"].dtype) # datetime64[ns]
# Extrair componentes de data
df["ano"] = df["data_cadastro"].dt.year
df["mes"] = df["data_cadastro"].dt.month
df["dia"] = df["data_cadastro"].dt.day
df["dia_semana"] = df["data_cadastro"].dt.day_name()
# Limpar e converter coluna de valor monetário
df["valor_num"] = (df["valor"]
.str.replace("R$ ", "", regex=False)
.str.replace(".", "", regex=False)
.str.replace(",", ".", regex=False)
.astype(float))
# Converter booleano textual
df["ativo_bool"] = df["ativo"].map({"sim": True, "não": False})
# Converter para categoria (economiza memória para strings repetidas)
df["categoria"] = df["categoria"].astype("category")
print(df[["data_cadastro", "valor_num", "ativo_bool", "categoria"]])
Saída:
data_cadastro valor_num ativo_bool categoria
0 2024-01-15 1500.00 True A
1 2024-02-20 2300.50 False B
2 2024-03-10 890.00 True A
2.4 Padronização de strings
df = pd.DataFrame({
"cidade": ["são paulo", "SÃO PAULO", "São Paulo ", " são paulo",
"rio de janeiro", "RIO DE JANEIRO", "Rio De Janeiro"]
})
# Padronizar: strip + title case
df["cidade_std"] = df["cidade"].str.strip().str.title()
print(df["cidade_std"].value_counts())
# São Paulo 4
# Rio De Janeiro 3
3. Transformações com apply e map
3.1 apply em Series
Aplica uma função a cada elemento de uma coluna:
df = pd.DataFrame({
"nome": ["Ana", "Bruno", "Carla"],
"nota": [8.5, 5.5, 9.2],
"idade": [25, 30, 22]
})
# apply com lambda
df["nota_categoria"] = df["nota"].apply(
lambda n: "Excelente" if n >= 9 else "Bom" if n >= 7 else "Regular"
)
# apply com função nomeada
def classificar_idade(idade):
if idade < 25:
return "jovem"
elif idade < 35:
return "adulto"
else:
return "sênior"
df["faixa_etaria"] = df["idade"].apply(classificar_idade)
print(df)
3.2 apply em DataFrame (axis=1)
Aplica uma função a cada linha, recebendo a linha como Series:
df = pd.DataFrame({
"nota1": [8.0, 6.0, 9.5],
"nota2": [7.5, 5.5, 8.5],
"nota3": [9.0, 7.0, 10.0]
})
# Calcular média ponderada (nota3 tem peso 2)
def media_ponderada(linha):
return (linha["nota1"] + linha["nota2"] + 2 * linha["nota3"]) / 4
df["media_pond"] = df.apply(media_ponderada, axis=1)
print(df)
3.3 map — substituição por dicionário
df = pd.DataFrame({
"cidade": ["SP", "RJ", "MG", "SP", "RJ"],
"regiao": ["SE", "SE", "SE", "SE", "SE"]
})
# Mapear abreviações para nomes completos
mapa_cidades = {
"SP": "São Paulo",
"RJ": "Rio de Janeiro",
"MG": "Minas Gerais"
}
df["cidade_completa"] = df["cidade"].map(mapa_cidades)
# Mapear labels para números (encoding manual)
mapa_regioes = {"SE": 0, "S": 1, "N": 2, "NE": 3, "CO": 4}
df["regiao_cod"] = df["regiao"].map(mapa_regioes)
print(df)
3.4 applymap / map para DataFrame inteiro
# Aplicar função a todos os elementos numéricos
df_notas = pd.DataFrame({
"mat": [8.0, 6.5, 9.0],
"por": [7.5, 7.0, 8.5],
"cie": [9.0, 5.5, 7.0]
})
# Arredondar para 1 casa decimal
df_arredondado = df_notas.map(lambda x: round(x, 1))
# Normalizar todos os valores entre 0 e 1
df_norm = df_notas.apply(lambda col: (col - col.min()) / (col.max() - col.min()))
print(df_norm.round(3))
4. groupby — Agrupamento de Dados
groupby é uma das operações mais poderosas do Pandas. Segue a lógica "split-apply-combine":
1. Divide o DataFrame em grupos
2. Aplica uma função a cada grupo
3. Combina os resultados
df = pd.DataFrame({
"departamento": ["TI", "RH", "TI", "Vendas", "RH", "TI", "Vendas"],
"funcionario": ["Ana", "Bruno", "Carla", "Diego", "Eva", "Fábio", "Gabi"],
"salario": [8000, 5000, 9500, 6000, 5500, 7500, 7000],
"anos": [5, 2, 8, 3, 1, 4, 6],
"avaliacao": [4, 3, 5, 4, 2, 4, 5]
})
4.1 Agregações simples
# Média de salários por departamento
print(df.groupby("departamento")["salario"].mean())
# Múltiplas agregações em uma coluna
print(df.groupby("departamento")["salario"].agg(["mean", "min", "max", "std"]))
# Múltiplas colunas com múltiplas agregações
resultado = df.groupby("departamento").agg({
"salario": ["mean", "max"],
"anos": "mean",
"avaliacao": "mean"
})
print(resultado.round(2))
Saída:
salario anos avaliacao
mean max mean mean
departamento
RH 5250.00 5500 1.5 2.5
TI 8333.33 9500 5.67 4.33
Vendas 6500.00 7000 4.5 4.5
4.2 Agregações customizadas
# Função customizada de agregação
def amplitude(serie):
return serie.max() - serie.min()
resultado = df.groupby("departamento")["salario"].agg(amplitude)
print("Amplitude salarial por departamento:")
print(resultado)
4.3 transform — manter o tamanho original
Diferente de agg (que reduz), transform retorna um resultado com o mesmo tamanho do DataFrame original. Muito útil para criar features.
# Média do departamento em cada linha
df["media_depto"] = df.groupby("departamento")["salario"].transform("mean")
# Salário normalizado dentro do departamento
df["salario_norm"] = (
df.groupby("departamento")["salario"]
.transform(lambda x: (x - x.mean()) / x.std())
)
print(df[["funcionario", "departamento", "salario", "media_depto", "salario_norm"]].round(2))
Saída:
funcionario departamento salario media_depto salario_norm
0 Ana TI 8000 8333.33 -0.33
1 Bruno RH 5000 5250.00 -0.71
2 Carla TI 9500 8333.33 1.17
3 Diego Vendas 6000 6500.00 -0.71
4 Eva RH 5500 5250.00 0.71
5 Fábio TI 7500 8333.33 -0.83
6 Gabi Vendas 7000 6500.00 0.71
4.4 filter — filtrar grupos inteiros
# Manter apenas departamentos com salário médio acima de 6000
deptos_bem_pagos = df.groupby("departamento").filter(
lambda grupo: grupo["salario"].mean() > 6000
)
print(deptos_bem_pagos)
4.5 Agrupamento por múltiplas colunas
df2 = pd.DataFrame({
"departamento": ["TI", "TI", "TI", "RH", "RH"],
"nivel": ["junior", "senior", "senior", "junior", "senior"],
"salario": [5000, 9000, 10000, 4000, 6000]
})
resultado = df2.groupby(["departamento", "nivel"])["salario"].agg(["mean", "count"])
print(resultado)
Saída:
mean count
departamento nivel
RH junior 4000.0 1
senior 6000.0 1
TI junior 5000.0 1
senior 9500.0 2
5. Merge e Join — Combinando DataFrames
5.1 merge — equivalente ao SQL JOIN
clientes = pd.DataFrame({
"cliente_id": [1, 2, 3, 4],
"nome": ["Ana", "Bruno", "Carla", "Diego"],
"cidade": ["SP", "RJ", "SP", "MG"]
})
pedidos = pd.DataFrame({
"pedido_id": [101, 102, 103, 104, 105],
"cliente_id": [1, 2, 1, 3, 5], # cliente 5 não existe
"valor": [150.0, 300.0, 75.0, 200.0, 100.0],
"produto": ["A", "B", "C", "A", "D"]
})
INNER JOIN — apenas registros com match em ambos:
inner = pd.merge(clientes, pedidos, on="cliente_id", how="inner")
print("INNER JOIN:")
print(inner)
Saída:
cliente_id nome cidade pedido_id valor produto
0 1 Ana SP 101 150.0 A
1 1 Ana SP 103 75.0 C
2 2 Bruno RJ 102 300.0 B
3 3 Carla SP 104 200.0 A
LEFT JOIN — todos os clientes, mesmo sem pedidos:
left = pd.merge(clientes, pedidos, on="cliente_id", how="left")
print("\nLEFT JOIN:")
print(left)
Saída:
cliente_id nome cidade pedido_id valor produto
0 1 Ana SP 101.0 150.0 A
1 1 Ana SP 103.0 75.0 C
2 2 Bruno RJ 102.0 300.0 B
3 3 Carla SP 104.0 200.0 A
4 4 Diego MG NaN NaN NaN
RIGHT JOIN — todos os pedidos, mesmo sem cliente correspondente:
right = pd.merge(clientes, pedidos, on="cliente_id", how="right")
print("\nRIGHT JOIN:")
print(right)
OUTER JOIN — todos os registros de ambos:
outer = pd.merge(clientes, pedidos, on="cliente_id", how="outer")
print("\nOUTER JOIN:")
print(outer)
5.2 Merge com chaves diferentes
df_a = pd.DataFrame({
"id_cliente": [1, 2, 3],
"nome": ["Ana", "Bruno", "Carla"]
})
df_b = pd.DataFrame({
"cod_cliente": [1, 2, 4],
"saldo": [100, 200, 300]
})
resultado = pd.merge(df_a, df_b,
left_on="id_cliente",
right_on="cod_cliente",
how="inner")
print(resultado)
5.3 Merge com suffixes para colunas duplicadas
notas_1 = pd.DataFrame({"aluno_id": [1, 2, 3], "nota": [8.0, 7.0, 9.0]})
notas_2 = pd.DataFrame({"aluno_id": [1, 2, 3], "nota": [7.5, 8.5, 8.0]})
resultado = pd.merge(notas_1, notas_2,
on="aluno_id",
suffixes=("_prova1", "_prova2"))
print(resultado)
Saída:
aluno_id nota_prova1 nota_prova2
0 1 8.0 7.5
1 2 7.0 8.5
2 3 9.0 8.0
5.4 concat — empilhar DataFrames
treino = pd.DataFrame({
"feature1": [1.0, 2.0, 3.0],
"feature2": [0.1, 0.2, 0.3],
"label": [0, 1, 0]
})
teste = pd.DataFrame({
"feature1": [4.0, 5.0],
"feature2": [0.4, 0.5],
"label": [1, 0]
})
# Empilhar verticalmente (axis=0)
tudo = pd.concat([treino, teste], ignore_index=True)
print(tudo)
# ignore_index=True reinicia o índice de 0
# Empilhar horizontalmente (axis=1)
df_a = pd.DataFrame({"A": [1, 2, 3]})
df_b = pd.DataFrame({"B": [4, 5, 6]})
lado_a_lado = pd.concat([df_a, df_b], axis=1)
print(lado_a_lado)
6. Pivot Tables
Pivot tables reorganizam dados de formato longo para formato largo, muito útil para análise e visualização.
df = pd.DataFrame({
"mes": ["Jan", "Jan", "Fev", "Fev", "Mar", "Mar"],
"produto": ["A", "B", "A", "B", "A", "B"],
"vendas": [100, 150, 120, 130, 140, 170],
"lucro": [20, 30, 25, 28, 30, 35]
})
# Pivot: meses como linhas, produtos como colunas
pivot = df.pivot_table(
values="vendas",
index="mes",
columns="produto",
aggfunc="sum"
)
print("Vendas por mês e produto:")
print(pivot)
# Com múltiplos valores
pivot_multi = df.pivot_table(
values=["vendas", "lucro"],
index="mes",
columns="produto",
aggfunc="mean"
)
print("\nVendas e lucro médio:")
print(pivot_multi)
Saída:
produto A B
mes
Fev 120 130
Jan 100 150
Mar 140 170
7. Operações com Datas
Datas são muito comuns em datasets de ML, especialmente em séries temporais.
df = pd.DataFrame({
"data": pd.date_range("2024-01-01", periods=10, freq="D"),
"vendas": np.random.randint(100, 500, 10)
})
# Extrair componentes
df["ano"] = df["data"].dt.year
df["mes"] = df["data"].dt.month
df["dia"] = df["data"].dt.day
df["dia_semana"] = df["data"].dt.dayofweek # 0=segunda, 6=domingo
df["nome_dia"] = df["data"].dt.day_name()
df["trimestre"] = df["data"].dt.quarter
# Filtrar por período
janeiro = df[df["mes"] == 1]
dias_uteis = df[df["dia_semana"] < 5] # segunda a sexta
# Diferença entre datas
df["dias_desde_inicio"] = (df["data"] - df["data"].min()).dt.days
# Resample — agregar por período de tempo
df_semanal = df.set_index("data")["vendas"].resample("W").sum()
print("Vendas por semana:")
print(df_semanal)
8. Encoding de Variáveis Categóricas
Modelos de ML trabalham com números, não com strings. É preciso converter variáveis categóricas.
8.1 Label Encoding (ordinal)
df = pd.DataFrame({
"nivel": ["junior", "senior", "pleno", "junior", "senior"]
})
# Mapeamento manual (quando há ordem)
mapa_nivel = {"junior": 0, "pleno": 1, "senior": 2}
df["nivel_cod"] = df["nivel"].map(mapa_nivel)
print(df)
8.2 One-Hot Encoding
Para variáveis nominais sem ordem (cidade, cor, categoria):
df = pd.DataFrame({
"cidade": ["SP", "RJ", "SP", "MG", "RJ"],
"produto": ["A", "B", "A", "C", "B"],
"vendas": [100, 150, 120, 90, 130]
})
# get_dummies: cria colunas binárias para cada categoria
df_encoded = pd.get_dummies(df, columns=["cidade", "produto"], dtype=int)
print(df_encoded)
Saída:
vendas cidade_MG cidade_RJ cidade_SP produto_A produto_B produto_C
0 100 0 0 1 1 0 0
1 150 0 1 0 0 1 0
2 120 0 0 1 1 0 0
3 90 1 0 0 0 0 1
4 130 0 1 0 0 1 0
# drop_first=True para evitar multicolinearidade
df_encoded = pd.get_dummies(df, columns=["cidade"], drop_first=True, dtype=int)
9. Exemplo Completo: Pipeline de Preparação de Dados para ML
import pandas as pd
import numpy as np
np.random.seed(42)
print("Criando dataset bruto...")
# Dataset simulado de empréstimos bancários
n = 300
df_raw = pd.DataFrame({
"id": range(1, n + 1),
"idade": np.random.randint(18, 70, n),
"renda_mensal": np.random.uniform(1000, 20000, n).round(2),
"tempo_emprego": np.random.randint(0, 30, n),
"valor_emprestimo": np.random.uniform(5000, 100000, n).round(2),
"educacao": np.random.choice(
["fundamental", "medio", "superior", "pos"],
n, p=[0.1, 0.4, 0.35, 0.15]
),
"estado": np.random.choice(["SP", "RJ", "MG", "RS", "BA"], n),
"inadimplente": np.random.choice([0, 1], n, p=[0.80, 0.20])
})
# Inserir problemas artificiais
idx = np.random.choice(n, 30, replace=False)
df_raw.loc[idx[:10], "renda_mensal"] = np.nan
df_raw.loc[idx[10:20], "tempo_emprego"] = np.nan
df_raw.loc[idx[20:], "educacao"] = None
df_raw.loc[np.random.choice(n, 5), "renda_mensal"] = 999999 # outliers
df_raw = pd.concat([df_raw, df_raw.iloc[:5]], ignore_index=True) # duplicatas
print(f"Dataset bruto: {df_raw.shape}")
# ─────────────────────────────────────────────
# PASSO 1: Remover duplicatas
# ─────────────────────────────────────────────
df = df_raw.drop_duplicates(subset=["id"])
print(f"\n[1] Após remover duplicatas: {df.shape}")
# ─────────────────────────────────────────────
# PASSO 2: Tratar valores ausentes
# ─────────────────────────────────────────────
print(f"\n[2] Ausentes antes:")
print(df.isna().sum()[df.isna().sum() > 0])
df["renda_mensal"] = df["renda_mensal"].fillna(df["renda_mensal"].median())
df["tempo_emprego"] = df["tempo_emprego"].fillna(df["tempo_emprego"].median())
df["educacao"] = df["educacao"].fillna(df["educacao"].mode()[0])
print(f" Ausentes depois: {df.isna().sum().sum()}")
# ─────────────────────────────────────────────
# PASSO 3: Tratar outliers com IQR
# ─────────────────────────────────────────────
for col in ["renda_mensal", "valor_emprestimo"]:
Q1 = df[col].quantile(0.25)
Q3 = df[col].quantile(0.75)
IQR = Q3 - Q1
limite_inf = Q1 - 1.5 * IQR
limite_sup = Q3 + 1.5 * IQR
df[col] = df[col].clip(lower=limite_inf, upper=limite_sup)
print(f"\n[3] Outliers tratados com clipping.")
# ─────────────────────────────────────────────
# PASSO 4: Feature engineering
# ─────────────────────────────────────────────
df["razao_emprestimo_renda"] = (df["valor_emprestimo"] /
df["renda_mensal"]).round(4)
df["faixa_etaria"] = pd.cut(
df["idade"],
bins=[17, 25, 35, 50, 70],
labels=["18-25", "26-35", "36-50", "51-70"]
)
print(f"\n[4] Features criadas: razao_emprestimo_renda, faixa_etaria")
# ─────────────────────────────────────────────
# PASSO 5: Encoding categórico
# ─────────────────────────────────────────────
mapa_educacao = {"fundamental": 0, "medio": 1, "superior": 2, "pos": 3}
df["educacao_cod"] = df["educacao"].map(mapa_educacao)
df = pd.get_dummies(df, columns=["estado", "faixa_etaria"], dtype=int)
print(f"\n[5] Após encoding: {df.shape[1]} colunas")
# ─────────────────────────────────────────────
# PASSO 6: Análise por grupo
# ─────────────────────────────────────────────
print("\n[6] Inadimplência por nível de educação:")
analise = (df.groupby("educacao")["inadimplente"]
.agg(["mean", "count"])
.rename(columns={"mean": "taxa_inadimplencia", "count": "total"})
.round(3))
print(analise)
# ─────────────────────────────────────────────
# PASSO 7: Preparar arrays para modelo
# ─────────────────────────────────────────────
colunas_para_remover = ["id", "educacao", "inadimplente"]
colunas_features = [c for c in df.columns if c not in colunas_para_remover]
X = df[colunas_features].values.astype(np.float32)
y = df["inadimplente"].values.astype(np.int64)
print(f"\n[7] Arrays prontos para o modelo:")
print(f" X: {X.shape}, dtype={X.dtype}")
print(f" y: {y.shape}, dtype={y.dtype}")
print(f" Taxa de inadimplência: {y.mean():.1%}")
Saída resumida:
Dataset bruto: (305, 10)
[1] Após remover duplicatas: (300, 10)
[2] Ausentes antes:
renda_mensal 10
tempo_emprego 10
educacao 10
Ausentes depois: 0
[3] Outliers tratados com clipping.
[4] Features criadas: razao_emprestimo_renda, faixa_etaria
[5] Após encoding: 21 colunas
[6] Inadimplência por nível de educação:
taxa_inadimplencia total
educacao
fundamental 0.241 29
medio 0.200 121
pos 0.178 45
superior 0.190 105
[7] Arrays prontos para o modelo:
X: (300, 19), dtype=float32
y: (300,), dtype=int64
Taxa de inadimplência: 20.0%
Resumo da Aula
drop_duplicates()remove linhas duplicadas, com opção de considerar apenas colunas específicas- Outliers podem ser tratados por Z-score, IQR ou clipping — cada método tem prós e contras
apply()aplica uma função a cada elemento ou linha,map()substitui por dicionáriogroupby()segue split-apply-combine: divide, agrega e combinatransform()mantém o tamanho original do DataFrame (diferente deagg()que reduz)merge()combina DataFrames pelo equivalente SQL: inner, left, right, outerconcat()empilha DataFrames vertical ou horizontalmentepd.get_dummies()faz one-hot encoding de variáveis categóricas- Variáveis ordinais devem ser codificadas com mapeamento manual preservando a ordem
- O pipeline completo é: remover duplicatas → tratar ausentes → tratar outliers → engenharia de features → encoding → exportar como NumPy
Resumo do Curso — O que foi visto e o que falta
Status Geral
Total de aulas planejadas: 30
Aulas concluídas: 12
Aulas restantes: 18
Progresso: 40%
O que foi concluído
Aula 00 — Apresentação do curso
Visão geral completa do curso, mapa das tecnologias, plano de estudos aula a aula e o que você será capaz de fazer ao final.
Fase 1 — Python para quem já programa (Aulas 01 a 07) ✓ COMPLETA
Aula 01 — Variáveis, tipos de dados e operadores Tipagem dinâmica, tipos nativos (int, float, str, bool, None), f-strings, operadores aritméticos e de comparação, conversão de tipos, entrada do usuário.
Aula 02 — Lógica booleana e estruturas de controle Operadores relacionais e lógicos (and, or, not), valores truthy e falsy, if/elif/else, operador ternário, comparações encadeadas, isinstance().
Aula 03 — Loops: for, while, break, continue for sobre iteráveis, range(), enumerate(), zip(), sorted(), reversed(), while, break, continue, else em loops, loops aninhados, list comprehension.
Aula 04 — Funções, escopo e recursão Definição de funções, parâmetros posicionais e com default, args e *kwargs, escopo LEGB, funções como objetos de primeira classe, lambda, docstrings, recursão, type hints.
Aula 05 — Estruturas de dados: listas e tuplas Criação, indexação, slicing, métodos de lista (append, insert, extend, remove, pop, sort), cópia superficial vs profunda, list comprehensions avançadas, tuplas e imutabilidade, desempacotamento.
Aula 06 — Estruturas de dados: dicionários e sets Criação e acesso seguro com .get(), iteração com .items(), dictionary comprehension, dicionários aninhados, Counter, defaultdict, sets e operações de conjunto (união, interseção, diferença), performance O(1) vs O(n).
Aula 07 — Exceções, módulos e organização de código
Try/except/else/finally, raise, exceções personalizadas, context managers com with, módulos e pacotes, if __name__ == "__main__", ambientes virtuais (venv e conda), logging, boas práticas de organização.
Fase 2 — Ferramentas de Dados (Aulas 08 a 12) — 4 de 5 concluídas
Aula 08 — Anaconda e Jupyter Notebooks Instalação e configuração do Anaconda, gerenciamento de ambientes conda, diferença conda vs pip, Jupyter Notebook e JupyterLab, tipos de células, atalhos de teclado, magic commands, Google Colab como alternativa.
Aula 09 — NumPy: arrays, operações e broadcasting ndarray, criação de arrays (zeros, ones, arange, linspace, random), atributos (shape, dtype, ndim, size), indexação booleana, operações vetorizadas, ufuncs, funções de ativação com NumPy, agregações com axis, reshape, transpose, expand_dims, broadcasting, álgebra linear (@, dot, linalg), implementação de camada linear do zero.
Aula 10 — Pandas: Series e DataFrames Series com índice, criação de DataFrames, leitura de CSV/Excel/JSON, inspeção inicial (head, info, describe, dtypes), seleção com loc e iloc, filtragem booleana, query(), manipulação de colunas, valores ausentes (isna, fillna, dropna), operações de string com .str, ordenação, conversão para NumPy com .values.
Aula 11 — Pandas: limpeza, agrupamento e merge Remoção de duplicatas, detecção e tratamento de outliers (Z-score, IQR, clipping), conversão de tipos e datas, padronização de strings, apply e map, groupby com agg/transform/filter, merge com inner/left/right/outer join, concat, pivot tables, encoding de variáveis categóricas (label encoding e one-hot encoding), pipeline completo de preparação de dados.
O que falta
Fase 2 — Ferramentas de Dados (1 aula restante)
Aula 12 — Matplotlib e Seaborn: visualização de dados Gráficos de linha, barra, dispersão, histograma, boxplot, heatmap, subplots, personalização de gráficos, visualizações para EDA em ML.
Fase 3 — Machine Learning (5 aulas) — não iniciada
Aula 13 — Introdução ao Machine Learning O que é ML, tipos (supervisionado, não supervisionado, por reforço), terminologia (features, labels, treino, teste, validação), bias-variance tradeoff, introdução ao Scikit-learn.
Aula 14 — Regressão linear e logística Regressão linear simples e múltipla, mínimos quadrados, gradiente descendente, regressão logística, função sigmoid, classificação binária, fronteira de decisão.
Aula 15 — Árvores de decisão e Random Forest Como árvores de decisão funcionam, critérios de divisão (Gini, entropia), overfitting em árvores, Random Forest, bagging, feature importance.
Aula 16 — Avaliação de modelos e validação cruzada Métricas de classificação (acurácia, precisão, recall, F1, AUC-ROC), métricas de regressão (MSE, RMSE, MAE, R²), matriz de confusão, cross-validation, overfitting vs underfitting.
Aula 17 — Pipelines e projeto de ML completo Pipeline do Scikit-learn, pré-processamento dentro do pipeline, GridSearchCV, RandomizedSearchCV, projeto completo do zero ao modelo avaliado.
Fase 4 — Redes Neurais e Deep Learning (6 aulas) — não iniciada
Aula 18 — Fundamentos de redes neurais Perceptron, funções de ativação (ReLU, sigmoid, tanh, softmax), arquitetura de redes feedforward, camadas densas, forward pass.
Aula 19 — Gradiente descendente e backpropagation Função de perda (loss), gradiente descendente, learning rate, backpropagation passo a passo, chain rule, vanishing gradient.
Aula 20 — PyTorch: tensores e autograd Tensores vs arrays NumPy, operações com tensores, GPU vs CPU, autograd e grafo computacional, requires_grad, backward().
Aula 21 — Treinamento de redes neurais com PyTorch nn.Module, DataLoader e Dataset, loop de treinamento completo, otimizadores (SGD, Adam), funções de perda do PyTorch, salvando e carregando modelos.
Aula 22 — CNNs: redes convolucionais para imagens Operação de convolução, filtros e feature maps, pooling, arquitetura CNN, classificação de imagens, transfer learning básico.
Aula 23 — RNNs e LSTMs: dados sequenciais O problema de dados sequenciais, RNN simples, vanishing gradient em RNNs, LSTM e gates, aplicações em texto e séries temporais.
Fase 5 — IA Generativa e Transformers (6 aulas) — não iniciada
Aula 24 — Word embeddings e representação de texto Bag of words, TF-IDF, Word2Vec, GloVe, embeddings como representação densa, similaridade de cosseno.
Aula 25 — Mecanismo de atenção e arquitetura Transformer Self-attention, queries, keys e values, multi-head attention, positional encoding, encoder e decoder, arquitetura completa do Transformer.
Aula 26 — LLMs: GPT, BERT e como funcionam Pré-treinamento e fine-tuning, BERT (encoder-only, bidirecional), GPT (decoder-only, autoregressivo), diferenças e casos de uso de cada arquitetura.
Aula 27 — Hugging Face: usando modelos pré-treinados Hub do Hugging Face, pipeline API, tokenizers, AutoModel e AutoTokenizer, inferência com modelos pré-treinados para classificação, geração de texto e QA.
Aula 28 — Fine-tuning de modelos de linguagem O que é fine-tuning e quando usar, preparar dataset para fine-tuning, Trainer API do Hugging Face, métricas de avaliação em NLP, LoRA e PEFT para fine-tuning eficiente.
Aula 29 — Projeto final: aplicação NLP completa Projeto do zero: definição do problema, coleta e preparação dos dados, escolha e fine-tuning do modelo, avaliação, deploy básico com Gradio ou Streamlit.
Visão Geral em Uma Tabela
Fase Aulas Status
──────────────────────────────────────────────────
Fase 0 00 ✓ Concluída
Fase 1 01 a 07 ✓ Concluída (7/7)
Fase 2 08 a 12 ◑ Em andamento (4/5)
Fase 3 13 a 17 ○ Não iniciada (0/5)
Fase 4 18 a 23 ○ Não iniciada (0/6)
Fase 5 24 a 29 ○ Não iniciada (0/6)
──────────────────────────────────────────────────
Total 30 aulas 12 feitas / 18 restantes
Próxima aula a produzir
Aula 12 — Matplotlib e Seaborn: visualização de dados Última aula da Fase 2, que fecha o bloco de ferramentas de dados antes de entrar em Machine Learning na Fase 3.
Exercícios
-
Dado o DataFrame abaixo com dados de vendas, use
groupbypara calcular: total de vendas por região, ticket médio (media de valor) por categoria de produto, e o produto mais vendido (maior soma de vendas) em cada região.df = pd.DataFrame({ "regiao": ["Sul", "Norte", "Sul", "Norte", "Sul", "Norte"], "produto": ["A", "B", "B", "A", "A", "C"], "categoria": ["Eletrônico", "Roupa", "Roupa", "Eletrônico", "Eletrônico", "Alimento"], "vendas": [1200, 800, 950, 1100, 1350, 600], "valor": [350, 120, 130, 370, 340, 45] })✓ Resposta:df = pd.DataFrame({ "regiao": ["Sul", "Norte", "Sul", "Norte", "Sul", "Norte"], "produto": ["A", "B", "B", "A", "A", "C"], "categoria": ["Eletrônico", "Roupa", "Roupa", "Eletrônico", "Eletrônico", "Alimento"], "vendas": [1200, 800, 950, 1100, 1350, 600], "valor": [350, 120, 130, 370, 340, 45] }) # Total de vendas por região print("Total de vendas por região:") print(df.groupby("regiao")["vendas"].sum()) # Ticket médio por categoria print("\nTicket médio por categoria:") print(df.groupby("categoria")["valor"].mean().round(2)) # Produto mais vendido por região print("\nProduto mais vendido por região:") mais_vendido = (df.groupby(["regiao", "produto"])["vendas"] .sum() .reset_index() .sort_values("vendas", ascending=False) .groupby("regiao") .first()) print(mais_vendido)Saída:
Total de vendas por região: regiao Norte 2500 Sul 3500 Ticket médio por categoria: categoria Alimento 45.00 Eletrônico 353.33 Roupa 125.00 Produto mais vendido por região: produto vendas regiao Norte A 1100 Sul A 2550 -
Explique a diferença entre
groupby().agg()egroupby().transform(). Dê um exemplo de quando você usaria cada um em um projeto de ML.✓ Resposta:groupby().agg()reduz o DataFrame: retorna um resultado com uma linha por grupo. Use quando quiser resumos estatísticos como médias, totais ou contagens por categoria.groupby().transform()mantém o tamanho original: retorna um valor para cada linha original, baseado no grupo ao qual ela pertence. Use quando quiser adicionar uma coluna ao DataFrame original com informações do grupo.Exemplo em ML com
agg(): calcular a taxa de inadimplência por estado para um relatório de análise.taxa_por_estado = df.groupby("estado")["inadimplente"].agg(["mean", "count"])Exemplo em ML com
transform(): criar uma feature de engenharia que mostra a diferença entre o salário de um funcionário e a média do seu departamento — sem reduzir o DataFrame.df["desvio_do_depto"] = ( df["salario"] - df.groupby("departamento")["salario"].transform("mean") )A diferença fundamental:
agg()é para análise,transform()é para criar novas features que ficam no DataFrame original. -
Dados dois DataFrames abaixo, realize um LEFT JOIN para manter todos os alunos, mesmo os sem nota de projeto. Depois calcule a nota final como média de
nota_provaenota_projeto(usando 0 quando nota_projeto for ausente).alunos = pd.DataFrame({ "aluno_id": [1, 2, 3, 4, 5], "nome": ["Ana", "Bruno", "Carla", "Diego", "Eva"], "nota_prova": [8.0, 7.5, 9.0, 6.0, 8.5] }) projetos = pd.DataFrame({ "aluno_id": [1, 3, 5], "nota_projeto": [9.0, 8.5, 7.0] })✓ Resposta:alunos = pd.DataFrame({ "aluno_id": [1, 2, 3, 4, 5], "nome": ["Ana", "Bruno", "Carla", "Diego", "Eva"], "nota_prova": [8.0, 7.5, 9.0, 6.0, 8.5] }) projetos = pd.DataFrame({ "aluno_id": [1, 3, 5], "nota_projeto": [9.0, 8.5, 7.0] }) # LEFT JOIN — todos os alunos df_merged = pd.merge(alunos, projetos, on="aluno_id", how="left") # Substituir NaN em nota_projeto por 0 df_merged["nota_projeto"] = df_merged["nota_projeto"].fillna(0) # Calcular nota final df_merged["nota_final"] = ( (df_merged["nota_prova"] + df_merged["nota_projeto"]) / 2 ) print(df_merged)Saída:
aluno_id nome nota_prova nota_projeto nota_final 0 1 Ana 8.0 9.0 8.50 1 2 Bruno 7.5 0.0 3.75 2 3 Carla 9.0 8.5 8.75 3 4 Diego 6.0 0.0 3.00 4 5 Eva 8.5 7.0 7.75 -
Explique o que é one-hot encoding e por que ele é necessário para variáveis categóricas nominais em modelos de ML. O que é o problema da "armadilha das variáveis dummy" e como o parâmetro
drop_first=Trueo resolve?✓ Resposta:One-hot encoding transforma uma variável categórica com N categorias em N colunas binárias (0 ou 1), onde cada coluna indica a presença ou ausência de uma categoria. É necessário porque modelos de ML como regressão logística, SVM e redes neurais trabalham com operações matemáticas, e não faz sentido matemático calcular "SP + RJ" ou afirmar que "MG > RJ" — categorias nominais não têm ordem nem relação numérica entre si.
A armadilha das variáveis dummy (dummy variable trap): se você criar N colunas para N categorias, há multicolinearidade perfeita — a última coluna é sempre determinada pelas outras (se as primeiras N-1 forem 0, a última é necessariamente 1). Modelos lineares têm problemas com isso pois não conseguem calcular a inversa da matriz de features.
drop_first=Trueresolve removendo a primeira categoria, deixando apenas N-1 colunas. A categoria removida se torna o grupo de referência implícito.df = pd.DataFrame({"cor": ["vermelho", "azul", "verde", "azul"]}) # Sem drop_first: 3 colunas (uma é redundante) print(pd.get_dummies(df, dtype=int)) # cor_azul cor_verde cor_vermelho # 0 0 1 # 1 0 0 # 0 1 0 # 1 0 0 # Com drop_first: 2 colunas (vermelho é o grupo de referência) print(pd.get_dummies(df, drop_first=True, dtype=int)) # cor_verde cor_vermelho # 0 1 # 0 0 # 1 0 # 0 0 -
Dado o DataFrame abaixo, use
applycomaxis=1para criar uma coluna de classificação de risco de crédito baseada em múltiplas colunas simultaneamente.df = pd.DataFrame({ "renda": [3000, 8000, 1500, 12000, 5000], "divida": [500, 6000, 1200, 1000, 4000], "historico": ["bom", "ruim", "ruim", "bom", "regular"] })Regras: risco "alto" se divida/renda > 0.5 OU historico == "ruim". Risco "médio" se divida/renda entre 0.3 e 0.5 E historico != "ruim". Risco "baixo" nos demais casos.
✓ Resposta:df = pd.DataFrame({ "renda": [3000, 8000, 1500, 12000, 5000], "divida": [500, 6000, 1200, 1000, 4000], "historico": ["bom", "ruim", "ruim", "bom", "regular"] }) def classificar_risco(linha): razao = linha["divida"] / linha["renda"] if razao > 0.5 or linha["historico"] == "ruim": return "alto" elif 0.3 <= razao <= 0.5 and linha["historico"] != "ruim": return "médio" else: return "baixo" df["risco"] = df.apply(classificar_risco, axis=1) print(df)Saída:
renda divida historico risco 0 3000 500 bom baixo 1 8000 6000 ruim alto 2 1500 1200 ruim alto 3 12000 1000 bom baixo 4 5000 4000 regular médio -
Explique o que são os quatro tipos de join (inner, left, right, outer) usando uma analogia simples. Em qual situação de ML você usaria cada tipo?
✓ Resposta:Imagine duas listas de convidados de uma festa: lista A (convidados da família) e lista B (convidados do trabalho). Algumas pessoas estão em ambas.
INNER JOIN: apenas as pessoas que estão em AMBAS as listas — os que aparecem tanto na família quanto no trabalho. Em ML: combinar features de um dataset com labels de outro, mantendo apenas os exemplos que existem nos dois.
LEFT JOIN: todos da lista A, e quando a pessoa também está em B, adiciona as informações de B. Para quem está só em A, o campo de B fica vazio. Em ML: enriquecer um dataset principal com informações opcionais de outra fonte, mantendo todos os exemplos originais mesmo sem correspondência.
RIGHT JOIN: todos da lista B, e quando a pessoa também está em A, adiciona as informações de A. É o espelho do LEFT JOIN. Em ML: garantir que todos os exemplos de uma fonte secundária apareçam no resultado, mesmo sem correspondência na fonte principal.
OUTER JOIN: todos de ambas as listas, com campos vazios onde não há correspondência. Em ML: fazer um merge completo de dois datasets para análise, identificando quais exemplos só existem em uma das fontes — útil para auditoria de dados ou detecção de inconsistências entre sistemas.
Referências
- Documentação Pandas — groupby
- Documentação Pandas — merge, join, concat
- Documentação Pandas — missing data
- Documentação Pandas — categorical data
- Real Python — Pandas GroupBy
- Real Python — Pandas merge, join, concat
- Towards Data Science — Handling Outliers in ML
- Kaggle — Data Cleaning course (gratuito)