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 cinco 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:

  • 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_C Embarked_Q Embarked_S
3 1 17.5 1 1 15.2458 1 0 0
2 1 31 0 0 10.5 0 0 1
3 1 20 0 0 7.925 0 0 1
2 0 6 0 1 33 0 0 1
3 0 14 1 0 11.2417 1 0 0
1 0 26 0 0 78.85 0 0 1
3 0 17.5 0 0 7.75 0 1 0
3 1 16 2 0 18 0 0 1
3 0 16 0 0 7.75 0 1 0
1 0 19 0 2 26.2833 0 0 1
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())

    # Encode the categorical variables: two values fit in one 0/1 column,
    # and every port gets a column of its own, so that no port sits between two others
    df['Sex'] = (df['Sex'] == 'male').astype(int)
    df = pd.get_dummies(df, columns=['Embarked'], dtype=int)

    # Select features
    features = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare',
                'Embarked_C', 'Embarked_Q', 'Embarked_S']
    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 rows of the dataset
print(df.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, tanto a mediana que preenche Age quanto as categorias que constroem o bloco one-hot são passos de fit e pertencem ao Pipeline acima — reajustados a cada dobra.