Overview
Pré-processamento
Tudo nesta página acontece à direita da divisão. Isso não é convenção de arquivo: todo passo daqui estima alguma coisa a partir das linhas que recebe — uma média, uma mediana, uma lista de categorias, uma base de projeção — e estimá-la a partir de linhas nas quais você será pontuado depois é do que trata o capítulo de vazamento.
Então há uma regra estrutural e, depois dela, um conjunto de escolhas.
A regra
Tudo que tem um
fitvai dentro doPipelinee é oPipelineque você valida cruzadamente.
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder, StandardScaler
numericas = Pipeline([('impute', SimpleImputer(strategy='median')),
('scale', StandardScaler())])
categoricas = Pipeline([('impute', SimpleImputer(strategy='most_frequent')),
('encode', OneHotEncoder(handle_unknown='ignore'))])
prep = ColumnTransformer([('num', numericas, colunas_numericas),
('cat', categoricas, colunas_categoricas)])
model = Pipeline([('prep', prep), ('net', MLPClassifier())])
cross_val_score(model, X, y, cv=5) # cada fit roda de novo dentro de cada dobra
Fazer à mão está correto exatamente uma vez, para uma divisão. No instante em que você valida cruzadamente ou ajusta hiperparâmetros, a versão manual é ajustada uma vez enquanto as dobras mudam cinco vezes — e todas voltam a ficar contaminadas.
As quatro perguntas
-
Escala e Normalização
As colunas estão em unidades incomparáveis, então o primeiro gradiente é sobre o sistema de medidas. Qual scaler — e o que um erro de digitação faz com cada um deles.
-
Transformando a Forma
Escalar não muda a forma de uma distribuição. Quando a forma é o problema: log, potência, quantil, discretização — e o critério que não é um histograma simétrico.
-
Codificação
Transformar categorias em números é afirmar quais comparações entre elas são significativas. Coberto junto com a taxonomia, na página de tipos de features.
-
A Maldição da Dimensionalidade
O que a dimensão alta de fato faz com distâncias, volumes e ângulos — quatro fenômenos distintos, dos quais só um depende dos seus dados. A razão pela qual a próxima página existe.
-
Redução de Dimensionalidade
PCA, t-SNE e UMAP respondem três perguntas diferentes e perdem três coisas diferentes. Qual distorção cada um introduz e como julgar uma projeção.
A ordem das operações
Dentro do pipeline, a ordem também não é arbitrária:
flowchart LR
A["imputar"] --> B["codificar<br/><small>categóricas</small>"]
B --> C["transformar a forma<br/><small>log, potência</small>"]
C --> D["escalar"]
D --> E["reduzir<br/><small>opcional</small>"]
E --> F["modelo"]
classDef s fill:#eef2f7,stroke:#8b949e,color:#1f2937
class A,B,C,D,E,F s - Imputar primeiro, porque tudo depois disso quebra com
NaN. - Transformar antes de escalar, porque um log muda a média e o desvio padrão que o scaler está prestes a estimar.
- Escalar antes de reduzir, porque o PCA maximiza variância e variância tem unidade — sem padronizar, a coluna de maior unidade vira a primeira componente principal por construção.
Duas coisas que parecem pré-processamento e não são
Reamostrar para desbalanceamento de classes não é uma transformação das features — muda as linhas e precisa acontecer só dentro da dobra de treino, nunca nas linhas de validação. O Pipeline do sklearn não faz isso corretamente; o imblearn.pipeline.Pipeline faz. Veja desbalanceamento de classes.
Limpeza — remover duplicatas, corrigir um erro de unidade conhecido, trocar uma sentinela por NaN — não estima nada, então pertence a antes da divisão, onde também é mais seguro. Veja qualidade dos dados.
Um exemplo passo a passo
O conjunto Titanic, como ele chega:
| PassengerId | Survived | Pclass | Name | Sex | Age | SibSp | Parch | Ticket | Fare | Cabin | Embarked |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 710 | 1 | 3 | Moubarek, Master. Halim Gonios ("William George") | male | nan | 1 | 1 | 2661 | 15.2458 | nan | C |
| 440 | 0 | 2 | Kvillner, Mr. Johan Henrik Johannesson | male | 31 | 0 | 0 | C.A. 18723 | 10.5 | nan | S |
| 841 | 0 | 3 | Alhomaki, Mr. Ilmari Rudolf | male | 20 | 0 | 0 | SOTON/O2 3101287 | 7.925 | nan | S |
| 721 | 1 | 2 | Harper, Miss. Annie Jessie "Nina" | female | 6 | 0 | 1 | 248727 | 33 | nan | S |
| 40 | 1 | 3 | Nicola-Yarred, Miss. Jamila | female | 14 | 1 | 0 | 2651 | 11.2417 | nan | C |
| 291 | 1 | 1 | Barber, Miss. Ellen "Nellie" | female | 26 | 0 | 0 | 19877 | 78.85 | nan | S |
| 301 | 1 | 3 | Kelly, Miss. Anna Katherine "Annie Kate" | female | nan | 0 | 0 | 9234 | 7.75 | nan | Q |
| 334 | 0 | 3 | Vander Planke, Mr. Leo Edmondus | male | 16 | 2 | 0 | 345764 | 18 | nan | S |
| 209 | 1 | 3 | Carr, Miss. Helen "Ellen" | female | 16 | 0 | 0 | 367231 | 7.75 | nan | Q |
| 137 | 1 | 1 | Newsom, Miss. Helen Monypeny | female | 19 | 0 | 2 | 11752 | 26.2833 | D47 | S |
Dez linhas da tabela crua: tipos misturados, idades faltantes, um porto de embarque categórico.
| Pclass | Sex | Age | SibSp | Parch | Fare | Embarked |
|---|---|---|---|---|---|---|
| 3 | male | 17.5 | 1 | 1 | 15.2458 | C |
| 2 | male | 31 | 0 | 0 | 10.5 | S |
| 3 | male | 20 | 0 | 0 | 7.925 | S |
| 2 | female | 6 | 0 | 1 | 33 | S |
| 3 | female | 14 | 1 | 0 | 11.2417 | C |
| 1 | female | 26 | 0 | 0 | 78.85 | S |
| 3 | female | 17.5 | 0 | 0 | 7.75 | Q |
| 3 | male | 16 | 2 | 0 | 18 | S |
| 3 | female | 16 | 0 | 0 | 7.75 | Q |
| 1 | female | 19 | 0 | 2 | 26.2833 | S |
import pandas as pd
# Preprocess the data
def preprocess(df):
# Fill missing values
df['Age'] = df['Age'].fillna(df['Age'].median())
df['Embarked'] = df['Embarked'].fillna(df['Embarked'].mode()[0])
df['Fare'] = df['Fare'].fillna(df['Fare'].median())
# Select features
features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']
return df[features]
# Load the Titanic dataset
df = pd.read_csv('https://raw.githubusercontent.com/hsandmann/ml/refs/heads/main/data/kaggle/titanic-dataset.csv')
df = df.sample(n=10, random_state=42)
# Preprocessing
df = preprocess(df)
# Display the first few rows of the dataset
print(df.to_markdown(index=False))
| Pclass | Sex | Age | SibSp | Parch | Fare | Embarked |
|---|---|---|---|---|---|---|
| 3 | 0 | 16 | 0 | 0 | 7.75 | 1 |
| 2 | 1 | 31 | 0 | 0 | 10.5 | 2 |
| 1 | 0 | 26 | 0 | 0 | 78.85 | 2 |
| 3 | 1 | 17.5 | 1 | 1 | 15.2458 | 0 |
| 3 | 1 | 16 | 2 | 0 | 18 | 2 |
| 3 | 1 | 20 | 0 | 0 | 7.925 | 2 |
| 1 | 0 | 19 | 0 | 2 | 26.2833 | 2 |
import pandas as pd
from sklearn.preprocessing import LabelEncoder
# Preprocess the data
def preprocess(df):
# Fill missing values
df['Age'] = df['Age'].fillna(df['Age'].median())
df['Embarked'] = df['Embarked'].fillna(df['Embarked'].mode()[0])
df['Fare'] = df['Fare'].fillna(df['Fare'].median())
# Convert categorical variables
label_encoder = LabelEncoder()
df['Sex'] = label_encoder.fit_transform(df['Sex'])
df['Embarked'] = label_encoder.fit_transform(df['Embarked'])
# Select features
features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']
return df[features]
# Load the Titanic dataset
df = pd.read_csv('https://raw.githubusercontent.com/hsandmann/ml/refs/heads/main/data/kaggle/titanic-dataset.csv')
df = df.sample(n=10, random_state=42)
# Preprocessing
df = preprocess(df)
# Display the first few rows of the dataset
print(df.sample(n=7, random_state=42).to_markdown(index=False))
Leia esse código como ilustração, não como modelo a copiar
Os dois scripts calculam as estatísticas sobre a tabela inteira, porque estão imprimindo uma tabela e não treinando um modelo. Num pipeline de verdade, a mediana que preenche Age e as categorias que constroem o bloco one-hot são as duas passos de fit e pertencem ao Pipeline acima — reajustados a cada dobra.