Você sabia que é possível “ver a prova antes” sem perceber ao treinar um modelo de machine learning? Esse erro tem nome: data leakage. Ele acontece quando informação do conjunto de teste vaza para o treinamento, e o resultado é um modelo que parece ótimo na avaliação e decepciona em produção. A boa notícia é que o scikit-learn tem uma ferramenta feita exatamente para evitar isso por construção: o pipeline.
Neste tutorial, você vai entender o que os métodos fit e transform realmente fazem, ver o vazamento de dados acontecendo com números reais e montar um pipeline scikit-learn completo, do pré-processamento ao modelo, usando o dataset do Titanic.
Código do Artigo
Acesse o código-fonte deste artigo gratuitamente.
Informe seu email para acessar o código:
✓ Seu código está pronto!
Abrir no Google Colab →Por que dividir os dados antes de qualquer transformação?
O conjunto de teste existe para simular dados que o modelo nunca viu. É a sua estimativa de como o modelo vai se comportar no mundo real. Se qualquer estatística do teste (uma média, um desvio padrão, as categorias existentes) influenciar o pré-processamento, essa simulação deixa de ser honesta.
Por isso, a primeira regra é simples: divida primeiro, transforme depois. Vamos carregar os dados e fazer exatamente isso.
import pandas as pd
import seaborn as sns
from sklearn.model_selection import train_test_split
df = sns.load_dataset("titanic")
df = df[["survived", "pclass", "sex", "age", "fare", "embarked"]]
X = df.drop(columns="survived")
y = df["survived"]
X_treino, X_teste, y_treino, y_teste = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
Com essa divisão, ficamos com 712 passageiros no treino e 179 no teste. O dataset também tem valores ausentes (177 em age e 2 em embarked), o que é ótimo para o nosso exemplo: dados do mundo real raramente vêm completos. Se quiser se aprofundar nesse tema, veja o guia de como tratar dados ausentes com Pandas.
O que fit e transform realmente fazem?
Todo transformador do scikit-learn segue o mesmo contrato. O método fit aprende os parâmetros a partir dos dados; o método transform aplica a transformação usando o que foi aprendido. No caso do StandardScaler, o fit calcula a média
e o desvio padrão
de cada coluna, e o transform aplica a fórmula
.
Como o StandardScaler não aceita valores ausentes, nos exemplos manuais removemos as linhas sem valor antes do fit, coluna a coluna (mais adiante, a imputação do pipeline resolve isso de forma automática):
from sklearn.preprocessing import StandardScaler scaler_age = StandardScaler().fit(X_treino[["age"]].dropna()) scaler_fare = StandardScaler().fit(X_treino[["fare"]].dropna()) print(scaler_age.mean_, scaler_age.scale_) print(scaler_fare.mean_, scaler_fare.scale_)
Rodando no nosso conjunto de treino, o scaler aprendeu que age tem média 29,81 (desvio 14,47) e fare tem média 31,82 (desvio 48,03). Esses números ficam guardados dentro do objeto. Quando chamamos transform no teste, é essa média do treino que será usada, e não a média do teste. Esse é o ponto central de todo o tutorial.

Repare que a padronização não muda o formato da distribuição. Ela apenas reposiciona a escala: a tarifa, que ia de 0 a mais de 500, passa a ter média 0 e desvio padrão 1. Modelos como regressão logística e SVM convergem melhor quando as variáveis estão na mesma escala.
O que é data leakage na prática?
Pense em um aluno que estuda para uma prova tendo visto as questões antes. A nota dele vai ser ótima, mas não mede o que ele sabe. Data leakage é isso: o modelo (ou o pré-processamento) teve acesso a informação que só deveria existir na avaliação.
O exemplo mais comum é ajustar o scaler no dataset inteiro antes de dividir. Vamos medir a diferença com a coluna fare:
scaler_certo = StandardScaler().fit(X_treino[["fare"]].dropna())
scaler_vazado = StandardScaler().fit(X[["fare"]].dropna())
print(f"Média aprendida só com o treino: {scaler_certo.mean_[0]:.4f}")
print(f"Média aprendida com tudo: {scaler_vazado.mean_[0]:.4f}")
O resultado: 31,8198 contra 32,2042. A diferença parece inofensiva, mas o segundo scaler conhece estatísticas dos 179 passageiros do teste antes da avaliação começar. Em datasets pequenos, com outliers ou em técnicas mais sensíveis (imputação, seleção de features, redução de dimensionalidade), esse vazamento infla a métrica de forma silenciosa. Você só descobre quando o modelo chega em produção.
Como tratar colunas numéricas e categóricas juntas?
Nosso dataset mistura tipos: age e fare são numéricas; pclass, sex e embarked são categóricas. Cada grupo pede um tratamento diferente, e o ColumnTransformer organiza isso:
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
transformador_numerico = Pipeline(steps=[
("imputador", SimpleImputer(strategy="median")),
("padronizador", StandardScaler()),
])
transformador_categorico = Pipeline(steps=[
("imputador", SimpleImputer(strategy="most_frequent")),
("codificador", OneHotEncoder(handle_unknown="ignore")),
])
pre_processador = ColumnTransformer(transformers=[
("numerico", transformador_numerico, ["age", "fare"]),
("categorico", transformador_categorico, ["pclass", "sex", "embarked"]),
])
As colunas numéricas recebem imputação pela mediana e padronização. As categóricas recebem imputação pela moda e one-hot encoding. O parâmetro handle_unknown="ignore" evita erro se aparecer uma categoria nova em produção.
Repare que a imputação também é um transformador com fit e transform. A mediana usada para preencher as idades ausentes do teste é a mediana do treino. Imputar antes de dividir os dados é outra forma clássica de vazamento.
Como juntar tudo em um pipeline no scikit-learn?
Agora vem a parte elegante. O Pipeline encadeia o pré-processamento e o modelo em um único objeto, que se comporta como um estimador comum:
from sklearn.linear_model import LogisticRegression
modelo = Pipeline(steps=[
("pre_processamento", pre_processador),
("classificador", LogisticRegression(max_iter=1000)),
])
modelo.fit(X_treino, y_treino)
print(f"Acurácia no treino: {modelo.score(X_treino, y_treino):.3f}")
print(f"Acurácia no teste: {modelo.score(X_teste, y_teste):.3f}")
Uma única chamada de fit ajustou a imputação, a padronização, o encoding e a regressão logística, tudo usando apenas o treino. O resultado: acurácia de 0,801 no treino e 0,771 no teste. Valores próximos, sem sinal de sobreajuste (quando o modelo decora o treino em vez de aprender o padrão), e uma avaliação em que podemos confiar porque nenhuma estatística do teste participou do treinamento.
Além de eliminar o vazamento por construção, o pipeline vira um artefato único: você salva um objeto só, versiona um objeto só e coloca em produção um objeto só, com o pré-processamento embutido.
Como fazer validação cruzada sem vazamento?
A validação cruzada divide o treino em partes (folds) e treina o modelo várias vezes. Sem pipeline, é fácil errar: se você padroniza o treino inteiro antes do cross_val_score, cada fold de validação já foi contaminado pelas estatísticas dos outros. Com o pipeline, o scikit-learn refaz o fit das transformações dentro de cada fold, do jeito certo:
from sklearn.model_selection import cross_val_score
import numpy as np
scores = cross_val_score(modelo, X_treino, y_treino, cv=5, scoring="accuracy")
print(f"Média: {scores.mean():.3f} ± {scores.std():.3f}")
O resultado foi uma acurácia média de 0,798 com desvio de 0,023 entre os cinco folds. A consistência entre os folds e a proximidade com a acurácia do teste (0,771) reforçam que a avaliação está sólida.
Se você quer entender a matemática que acontece dentro da regressão logística e dos outros modelos, o guia essencial de matemática para machine learning é o próximo passo natural.
Takeaways
- Divida primeiro, transforme depois. O conjunto de teste simula dados novos e não pode influenciar nenhuma etapa do treinamento.
fitaprende,transformaplica. Os parâmetros (média, mediana, categorias) são aprendidos no treino e reutilizados no teste.- Data leakage infla métricas silenciosamente. Ajustar transformações no dataset inteiro contamina a avaliação, mesmo quando a diferença numérica parece pequena.
ColumnTransformertrata cada tipo de coluna com o seu próprio caminho de transformações, numéricas e categóricas juntas.Pipelineelimina o vazamento por construção e transforma pré-processamento e modelo em um único objeto, pronto para validação cruzada e produção.

















