(+351) 21 24 10006  ·  info@bconcepts.pt
Carnaxide, Lisboa

Como criar um Pipeline no scikit-learn: passo a passo

João Barros 13 de July de 2026 4 min de leitura

Criar um Pipeline no scikit-learn junta o pré-processamento e o modelo num único objeto: o mesmo tratamento aplicado ao treino é aplicado, automaticamente, aos dados novos. É a forma mais simples de evitar data leakage e de garantir que o que vai para produção é exatamente aquilo que foi validado.

Pré-requisitos

  • Python 3.10 ou superior com scikit-learn e pandas instalados (pip install scikit-learn pandas).
  • Um ficheiro CSV com dados tabulares e uma coluna alvo (aqui usamos clientes.csv e a coluna churn).
  • Noções básicas de treino e teste de modelos.

Passo 1: Separar treino e teste antes de tudo

A regra de ouro: o conjunto de teste não pode influenciar nada, nem sequer a média usada para normalizar. Por isso, divide os dados antes de qualquer transformação.

import pandas as pd
from sklearn.model_selection import train_test_split

df = pd.read_csv("clientes.csv")
X = df.drop(columns=["churn"])
y = df["churn"]

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

Passo 2: Criar o Pipeline mais simples

Um Pipeline é uma lista de passos com nome. Todos os passos, menos o último, têm de transformar os dados; o último é normalmente o modelo.

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

pipe = Pipeline(steps=[
    ("scaler", StandardScaler()),
    ("modelo", LogisticRegression(max_iter=1000)),
])

Os nomes ("scaler", "modelo") são escolhidos por ti e vão dar jeito mais à frente.

Passo 3: Treinar e prever com uma só linha

Ao chamar fit, o Pipeline aprende a escala com o treino e treina o modelo. Ao chamar predict, aplica a mesma escala aos dados novos, sem tu teres de te lembrar disso.

pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)

from sklearn.metrics import accuracy_score
print(accuracy_score(y_test, y_pred))

Passo 4: Tratar colunas numéricas e categóricas com ColumnTransformer

Na vida real as colunas não são todas iguais. O ColumnTransformer aplica um caminho diferente a cada grupo de colunas e encaixa dentro do Pipeline como se fosse um passo qualquer.

from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.impute import SimpleImputer

numericas = ["idade", "mensalidade", "meses_cliente"]
categoricas = ["plano", "regiao"]

pre = ColumnTransformer(transformers=[
    ("num", Pipeline([
        ("imputer", SimpleImputer(strategy="median")),
        ("scaler", StandardScaler()),
    ]), numericas),
    ("cat", OneHotEncoder(handle_unknown="ignore"), categoricas),
])

pipe = Pipeline([
    ("pre", pre),
    ("modelo", LogisticRegression(max_iter=1000)),
])

pipe.fit(X_train, y_train)

O handle_unknown="ignore" evita o erro clássico de aparecer, em produção, uma categoria que não existia no treino.

Passo 5: Validar e afinar o Pipeline inteiro

Como o Pipeline é um estimador normal, podes passá-lo diretamente à validação cruzada. Em cada dobra, o pré-processamento é reaprendido só com a parte de treino — é exatamente isto que elimina o leakage.

from sklearn.model_selection import cross_val_score, GridSearchCV

scores = cross_val_score(pipe, X_train, y_train, cv=5, scoring="f1")
print(scores.mean())

grelha = {"modelo__C": [0.1, 1, 10]}
busca = GridSearchCV(pipe, grelha, cv=5, scoring="f1")
busca.fit(X_train, y_train)
print(busca.best_params_)

Repara no duplo underscore: modelo__C significa "o parâmetro C do passo chamado modelo". É assim que se afinam hiperparâmetros dentro de um Pipeline.

Passo 6: Guardar o Pipeline completo

Guarda o Pipeline, não só o modelo. Assim, o ficheiro já contém o pré-processamento e recebe dados em bruto.

import joblib

joblib.dump(busca.best_estimator_, "modelo_churn.joblib")

modelo = joblib.load("modelo_churn.joblib")
print(modelo.predict(X_test.head()))

Verificar o resultado

Três verificações rápidas:

  • pipe.named_steps mostra os passos pela ordem certa.
  • pipe.predict(X_test.head()) funciona com o DataFrame em bruto, sem escalar nem codificar nada à mão. Se funcionar, o Pipeline está a fazer o trabalho todo.
  • Se aparecer o erro could not convert string to float, ficou uma coluna de texto fora do ColumnTransformer; se aparecer columns are missing, os dados novos não têm as mesmas colunas do treino.
Compara o resultado da validação cruzada com e sem Pipeline. Se o valor "melhorar" quando normalizas tudo antes de dividir, isso não é um modelo melhor — é leakage.

Conclusão

Com meia dúzia de linhas passaste de um conjunto de passos soltos para um objeto único, validável e pronto a servir. O passo seguinte natural é acrescentar ao Pipeline uma seleção de variáveis ou um TargetEncoder, e registar o modelo numa plataforma de MLOps. Uma pergunta para levares contigo: se amanhã chegar uma linha nova com uma categoria nunca vista, o teu Pipeline aguenta — ou rebenta?