Pré-processamento de Dados
Dados brutos quase nunca estão prontos para um algoritmo de aprendizado. Os atributos vivem em escalas diferentes, categorias são armazenadas como strings, valores estão faltando e distribuições são assimétricas. O pré-processamento transforma atributos brutos em uma representação que o algoritmo consegue explorar — e, feito errado, é também o lugar mais fácil de vazar informação (ver Validação & Vazamento de Dados).
Por que escalonar atributos?
Muitos algoritmos são baseados em distância ou em gradiente, e ambos são distorcidos quando os atributos têm magnitudes muito diferentes:
- o k-NN calcula distâncias euclidianas: um atributo na casa dos milhares (renda) abafa um na casa das dezenas (idade);
- o gradiente descendente converge devagar em superfícies de perda alongadas criadas por atributos não escalonados;
- SVMs e modelos regularizados (Ridge/Lasso) penalizam coeficientes como se os atributos fossem comparáveis;
- o PCA encontra direções de variância máxima — o atributo de maior escala vence por padrão.
Modelos baseados em árvores (árvores de decisão, random forests, gradient boosting) são a exceção notável: eles dividem por limiares, então transformações monotônicas não os afetam.
Métodos de escalonamento
Padronização (z-score): centrar em zero, variância unitária —
Normalização min-max: comprimir para \([0, 1]\) —
Escalonamento robusto: usar mediana e IQR em vez de média e desvio padrão —
A escolha importa quando há outliers. O min-max é totalmente determinado pelos dois pontos mais extremos; a padronização é um pouco distorcida por eles; o escalonamento robusto os ignora:
| Escalonador | Âncoras da fórmula | Sensível a outliers? | Uso típico |
|---|---|---|---|
StandardScaler | média, desvio | moderadamente | padrão para modelos lineares, SVM, PCA |
MinMaxScaler | mín, máx | muito | quando é preciso um intervalo limitado (ex.: valores de pixel, algumas redes neurais) |
RobustScaler | mediana, IQR | robusto | dados com caudas pesadas / outliers |
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # aprende μ, σ APENAS no TREINO
X_test_scaled = scaler.transform(X_test) # aplica os MESMOS μ, σ
Ajuste no treino, transforme no teste
O fit aprende os parâmetros (\(\mu, \sigma\), mín/máx...). Chamar fit (ou fit_transform) no conjunto de teste — ou no conjunto completo antes da divisão — vaza informação sobre a distribuição de teste para o treino. Esse é o bug de vazamento mais comum de todos, e os Pipelines existem em grande parte para evitá-lo.
Atributos assimétricos
Atributos assimétricos à direita (renda, preços, contagens) muitas vezes se beneficiam de uma transformação logarítmica, \(x' = \log(1 + x)\), que comprime a cauda longa e torna a distribuição mais simétrica. Ferramentas mais gerais: PowerTransformer (Box-Cox, Yeo-Johnson) e QuantileTransformer.
Codificação de atributos categóricos
Algoritmos consomem números, não strings. Os dois cavalos de batalha:
One-hot encoding — uma coluna binária por categoria:
from sklearn.preprocessing import OneHotEncoder
enc = OneHotEncoder(handle_unknown='ignore') # categorias não vistas → tudo zero
- Seguro para categorias nominais (sem ordem): cidade, cor, tipo de produto;
- Explode a dimensionalidade para atributos de alta cardinalidade (CEPs → milhares de colunas).
Codificação ordinal — mapear categorias para inteiros:
from sklearn.preprocessing import OrdinalEncoder
enc = OrdinalEncoder(categories=[['pequeno', 'médio', 'grande']])
- Correta para categorias ordinais (pequeno < médio < grande);
- Errada para as nominais: codificar cidades como São Paulo=0, Rio=1, Recife=2 inventa uma ordem e distâncias que não existem — modelos lineares e o k-NN explorarão alegremente a ficção.
Para atributos de alta cardinalidade, considere target encoding (substituir cada categoria por uma média suavizada do alvo) — poderoso, mas propenso a vazamento: precisa ser ajustado dentro da validação cruzada.
Imputação de valores faltantes
Complementando a discussão da EDA sobre mecanismos de falta:
from sklearn.impute import SimpleImputer, KNNImputer
SimpleImputer(strategy='median') # numérico: padrão robusto
SimpleImputer(strategy='most_frequent') # categórico
KNNImputer(n_neighbors=5) # imputar a partir de linhas semelhantes
Duas práticas úteis:
- adicionar uma coluna de indicador de falta (
add_indicator=True) — o fato de um valor estar faltando muitas vezes é preditivo; - imputar dentro de um Pipeline, para que as estatísticas de imputação sejam aprendidas apenas dos folds de treino.
Nunca impute o alvo
Linhas com alvo faltante devem ser descartadas do treino supervisionado — inventar rótulos fabrica sinal que não existe.
O mapa do pré-processamento
flowchart TD
A[Atributo bruto] --> B{Tipo?}
B -->|numérico| C{Assimétrico?}
C -->|sim| D[transformação log / power] --> E
C -->|não| E{Outliers?}
E -->|sim| F[RobustScaler]
E -->|não| G[StandardScaler]
B -->|categórico| H{Ordenado?}
H -->|sim| I[OrdinalEncoder]
H -->|não| J{Cardinalidade?}
J -->|baixa| K[OneHotEncoder]
J -->|alta| L[target encoding / agrupamento] Material de aula
Notebook da aula (em português)
Notebook prático usado em sala — Aula 03 — Normalização: abrir no Colab