Um Pipeline do scikit-learn conecta pré-processamento e estimador em um único objeto. A resposta direta para evitar data leakage é: divida os dados, coloque toda etapa que aprende parâmetros dentro do pipeline e passe esse objeto inteiro para a validação cruzada. Não calcule imputação, escala, seleção de atributos ou categorias no conjunto completo antes de validar.

O padrão mantém treino e previsão consistentes e simplifica a busca de hiperparâmetros. Este guia complementa pandas e machine learning com Python.

O problema que o Pipeline resolve

Considere uma tabela com idade, renda, cidade, canal e o alvo cancelou. Colunas numéricas precisam de imputação e escala; categóricas precisam de imputação e one-hot encoding. Se você ajustar essas operações antes de separar treino e teste, médias, medianas e vocabulários carregam informação do teste para o treino. A métrica deixa de representar dados realmente desconhecidos.

Um pipeline implementa a sequência fit, transform e predict. Ao chamar fit, transformadores aprendem somente com os dados recebidos. Ao chamar predict, reutilizam os parâmetros já aprendidos, sem recalculá-los. Essa distinção é a base da prevenção de leakage.

Reserve o teste antes de explorar decisões do modelo

import pandas as pd
from sklearn.model_selection import train_test_split

dados = pd.read_parquet('clientes.parquet') X = dados.drop(columns='cancelou') y = dados['cancelou']

X_treino, X_teste, y_treino, y_teste = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y, )

stratify=y preserva aproximadamente a proporção das classes. Para linhas do mesmo cliente ou dispositivo, separe por grupos; para prever o futuro, divida cronologicamente. Mantenha o teste intocado até escolher o pipeline.

Pré-processamento com ColumnTransformer

ColumnTransformer aplica fluxos diferentes a grupos de colunas e concatena as saídas. Selecione colunas explicitamente para tornar o contrato auditável:

from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

numericas = ['idade', 'renda_mensal', 'meses_cliente'] categoricas = ['cidade', 'canal']

prep_numerico = Pipeline([ ('imputar', SimpleImputer(strategy='median')), ('escalar', StandardScaler()), ])

prep_categorico = Pipeline([ ('imputar', SimpleImputer(strategy='most_frequent')), ('codificar', OneHotEncoder(handle_unknown='ignore')), ])

preprocessamento = ColumnTransformer([ ('num', prep_numerico, numericas), ('cat', prep_categorico, categoricas), ])

handle_unknown='ignore' permite transformar uma categoria que não apareceu no treino, produzindo zeros para aquele atributo desconhecido. Isso evita erro operacional, mas não substitui monitoramento de categorias novas. A documentação oficial de ColumnTransformer para dados heterogêneos apresenta outros seletores e combinações.

Conecte o modelo ao pré-processamento

from sklearn.linear_model import LogisticRegression

modelo = Pipeline([ ('preprocessar', preprocessamento), ('classificar', LogisticRegression( max_iter=1000, class_weight='balanced', )), ])

modelo.fit(X_treino, y_treino) probabilidades = modelo.predict_proba(X_teste)[:, 1]

O objeto recebe as colunas originais tanto no treino quanto na inferência. Não chame preprocessamento.fit_transform(X) antes. Também não aplique pd.get_dummies separadamente em treino e produção, pois as colunas podem divergir. O guia oficial de Pipeline explica o encadeamento e o acesso aos parâmetros.

Validação cruzada correta

A validação cruzada estima a variação do desempenho em múltiplas divisões. Passe o pipeline completo para cross_validate. Em cada fold, o scikit-learn clona o pipeline e ajusta imputadores, codificadores, escala e modelo somente no subconjunto de treino:

from sklearn.model_selection import StratifiedKFold, cross_validate

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) metricas = cross_validate( modelo, X_treino, y_treino, cv=cv, scoring={'roc_auc': 'roc_auc', 'f1': 'f1'}, return_train_score=True, n_jobs=-1, )

print(metricas['test_roc_auc'].mean()) print(metricas['test_roc_auc'].std())

Relate média e dispersão e compare treino com validação. A métrica deve refletir o custo: ROC AUC não define um limiar, e acurácia pode esconder falhas em classe rara. Para grupos, use GroupKFold; para tempo, considere TimeSeriesSplit.

Busca de hiperparâmetros sem contaminação

Parâmetros internos usam nomes unidos por dois sublinhados. A busca recebe o pipeline, não uma matriz previamente transformada:

from sklearn.model_selection import GridSearchCV

grade = { 'preprocessarnumimputarstrategy': ['mean', 'median'], 'classificarC': [0.1, 1.0, 10.0], }

busca = GridSearchCV( modelo, param_grid=grade, scoring='roc_auc', cv=cv, n_jobs=-1, refit=True, ) busca.fit(X_treino, y_treino)

melhor_modelo = busca.bestestimator

refit=True reajusta a melhor configuração em todo o conjunto de treino. Só então avalie uma vez no teste reservado. Se você usar repetidamente o teste para escolher atributos, limiar ou parâmetros, ele vira validação e sua estimativa fica otimista.

Avaliação final e persistência

from sklearn.metrics import classification_report, roc_auc_score
import joblib

proba = melhor_modelo.predict_proba(X_teste)[:, 1] pred = melhor_modelo.predict(X_teste)

print(roc_auc_score(y_teste, proba)) print(classification_report(y_teste, pred, zero_division=0)) joblib.dump(melhor_modelo, 'modelo_cancelamento.joblib')

Persista o pipeline inteiro: ele contém transformações e estimador. Carregue artefatos somente de origem confiável: formatos baseados em pickle podem executar código. Registre versões, tipos das colunas, alvo e período dos dados. Siga práticas de código Python sustentável e use pytest para verificar amostras válidas, ausências e categorias inéditas.

Leakage que Pipeline não corrige sozinho

  • Atributo posterior ao evento: “data de cancelamento” revela o alvo de cancelamento, mesmo dentro do pipeline.
  • Agregação global: médias por cliente calculadas com eventos futuros contaminam a linha histórica.
  • Duplicatas e entidades: o mesmo cliente em treino e validação permite memorizar padrões individuais.
  • Seleção manual no teste: repetir decisões após observar a métrica final sobreajusta ao teste.
  • Divisão aleatória temporal: treinar com o futuro para prever o passado produz uma avaliação irreal.

O capítulo oficial de erros comuns e práticas recomendadas detalha pré-processamento inconsistente e leakage. A prevenção também depende de entender quando cada dado estaria disponível no uso real.

Erros comuns

  • Ajustar StandardScaler, imputador ou seletor antes da divisão.
  • Passar apenas o classificador para a validação cruzada.
  • Codificar o alvo ou incluir uma coluna derivada diretamente dele entre os atributos.
  • Usar K-fold aleatório para grupos ou séries temporais.
  • Otimizar hiperparâmetros e publicar a melhor pontuação da mesma validação como estimativa final.
  • Salvar somente o estimador e tentar recriar o pré-processamento na aplicação.

Checklist antes de treinar

  1. Defina alvo, instante da previsão e quais dados existem naquele instante.
  2. Reserve o teste com uma estratégia adequada a classes, grupos e tempo.
  3. Coloque transformações aprendidas dentro de Pipeline.
  4. Use ColumnTransformer para contratos numéricos e categóricos explícitos.
  5. Valide o pipeline completo com métricas alinhadas ao problema.
  6. Faça a busca apenas no treino e avalie o teste uma única vez.
  7. Persista o pipeline completo e registre dependências e esquema.
  8. Monitore categorias, distribuição, qualidade dos dados e desempenho após implantação.

Dados brutos entram no pipeline e previsões saem dele. Transformadores aprendem em cada fold, sem consultar o teste final, e o mesmo objeto atende produção. Ainda é indispensável raciocinar sobre tempo e causalidade.