Ir para o conteúdo

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 fit vai dentro do Pipeline e é o Pipeline que 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.

    Escala

  • 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.

    Transformações

  • 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.

    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.

    A maldição

  • 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.

    Reduçã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.