Machine Learning: Engenharia de Features

Última atualização: 2026-08-26

Os dados definem o teto; o modelo apenas se aproxima dele — a engenharia de features é o que eleva esse teto.

1. O que você vai aprender


2. A história real de um engenheiro de ML

(1) O problema: 3 dias de ajuste para 1% de ganho vs. uma feature para 10%

O Bob gastou 3 dias ajustando hiperparâmetros do XGBoost, elevando o R² de 0,78 para 0,79 — uma mera melhoria de 1%. Mas quando adicionou uma única feature nova, "frequência de compras nos últimos 7 dias", o R² saltou direto para 0,86 — um ganho de 8%. A engenharia de features oferece retornos muito maiores do que o ajuste de hiperparâmetros, mas a maioria dos engenheiros carece de uma metodologia sistemática para ela.

(2) Uma abordagem sistemática para engenharia de features

A engenharia de features não é experimentação aleatória — segue um fluxo de trabalho sistemático: entender o negócio → construir features → selecionar features → validar eficácia.

PYTHON
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline

# Pipeline sistemático de engenharia de features
num_features = ["ad_spend", "traffic", "log_monetary", "purchase_freq_7d"]
cat_features = ["category", "tier"]

preprocessor = ColumnTransformer([
    ("num", StandardScaler(), num_features),
    ("cat", OneHotEncoder(drop="first", handle_unknown="ignore"), cat_features),
])

pipe = Pipeline([("prep", preprocessor), ("model", XGBRegressor())])

(3) O resultado: engenharia sistemática de features eleva o R² de 0,78 para 0,88

Depois que o Bob construiu sua fábrica de features, cada adição de nova feature passou a ser respaldada por evidência. O R² subiu sistematicamente de 0,78 para 0,88 — superando em muito o que apenas o ajuste poderia alcançar.


3. Transformações de features numéricas

(1) Padronização vs. Normalização

▶ Exemplo: Comparação entre StandardScaler, MinMaxScaler e RobustScaler

PYTHON
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
import numpy as np

data = np.array([[1], [5], [10], [50], [100], [500], [1000]])

scalers = {
    "Original": data,
    "StandardScaler": StandardScaler().fit_transform(data),
    "MinMaxScaler": MinMaxScaler().fit_transform(data),
    "RobustScaler": RobustScaler().fit_transform(data),
}

for name, scaled in scalers.items():
    print(f"{name:16s}: min={scaled.min():8.3f}, max={scaled.max():8.3f}, "
          f"média={scaled.mean():8.3f}, desvio={scaled.std():8.3f}")

Saída:

TEXT 📖 Somente leitura
# Executado com sucesso
Scaler Intervalo de saída Robustez a outliers Caso de uso
StandardScaler Média 0, desvio 1 Baixa Distribuição normal, sem outliers
MinMaxScaler [0, 1] Baixa Intervalo fixo, redes neurais
RobustScaler Baseado em quartis Alta Presença de outliers

(2) Log Transform e Binning

▶ Exemplo: Lidando com distribuições enviesadas

PYTHON
import numpy as np
import pandas as pd

# Dados com distribuição enviesada à direita
data = np.array([1, 5, 10, 50, 100, 500, 1000, 5000, 10000])

# Log transform
log_data = np.log1p(data)

# Binning em 3 grupos
bins = pd.cut(data, bins=3, labels=["Baixo", "Médio", "Alto"])
print(f"Dados originais: {data}")
print(f"Após log: {log_data.round(2)}")
print(f"Bins: {bins}")

Saída:

TEXT 📖 Somente leitura
# Executado com sucesso

(3) Features polinomiais e interações

▶ Exemplo: Capturando relações não lineares

PYTHON
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
import numpy as np

rng = np.random.default_rng(42)
X = rng.uniform(-3, 3, (100, 1))
y = 0.5 * X[:, 0]**2 + X[:, 0] + rng.normal(0, 0.5, 100)

# Sem features polinomiais (linear)
linear = LinearRegression().fit(X, y)
print(f"Linear R²: {linear.score(X, y):.3f}")

# Com features polinomiais de grau 2
poly_pipe = Pipeline([
    ("poly", PolynomialFeatures(degree=2, include_bias=False)),
    ("model", LinearRegression()),
])
poly_pipe.fit(X, y)
print(f"Polynomial R²: {poly_pipe.score(X, y):.3f}")

Saída:

TEXT 📖 Somente leitura
# Executado com sucesso

4. Codificação de features categóricas

(1) Comparação de métodos de codificação

▶ Exemplo: One-Hot vs. Ordinal vs. Target Encoding

PYTHON
import pandas as pd
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder

df = pd.DataFrame({
    "category": ["Eletrônicos", "Roupas", "Alimentos", "Eletrônicos", "Casa"],
    "tier": ["Bronze", "Prata", "Ouro", "Prata", "Bronze"],
    "revenue": [1000, 500, 300, 800, 1200],
})

# One-Hot Encoding
ohe = OneHotEncoder(sparse_output=False, drop="first")
category_encoded = ohe.fit_transform(df[["category"]])
print(f"One-Hot shape: {category_encoded.shape}")

# Ordinal Encoding
tier_order = ["Bronze", "Prata", "Ouro"]
oe = OrdinalEncoder(categories=[tier_order])
tier_encoded = oe.fit_transform(df[["tier"]])
print(f"Ordinal: {tier_encoded.flatten()}")

Saída:

TEXT 📖 Somente leitura
# Executado com sucesso
Método Quando usar Vantagem Risco
One-Hot Baixa cardinalidade (<10) Sem viés ordinal Dimensionalidade explode
Ordinal Tem ordem natural (Bronze<Prata<Ouro) Compacto Presume ordem linear
Target Alta cardinalidade (centenas) Compacto + informativo Risco de vazamento de dados

(2) Target Encoding com Validação Cruzada

▶ Exemplo: Target encoding seguro

PYTHON
from sklearn.model_selection import KFold

def target_encode_cv(df, col, target, n_splits=5, smoothing=10):
    """Target encoding com validação cruzada para evitar vazamento."""
    global_mean = df[target].mean()
    encoded = pd.Series(index=df.index, dtype=float)
    
    kf = KFold(n_splits=n_splits, shuffle=True, random_state=42)
    for train_idx, val_idx in kf.split(df):
        train = df.iloc[train_idx]
        means = train.groupby(col)[target].agg(["mean", "count"])
        smoothed = (means["mean"] * means["count"] + global_mean * smoothing) / (means["count"] + smoothing)
        encoded.iloc[val_idx] = df.iloc[val_idx][col].map(smoothed)
    
    return encoded.fillna(global_mean)

# Aplicar em uma coluna categórica de alta cardinalidade
df = pd.DataFrame({
    "city": ["SP", "RJ", "SP", "MG", "RJ", "SP", "MG", "RJ"] * 100,
    "revenue": range(800),
})
df["city_encoded"] = target_encode_cv(df, "city", "revenue")
print(df.groupby("city")["city_encoded"].mean())

Saída:

TEXT 📖 Somente leitura
# Executado com sucesso

5. Features temporais

(1) Decomposição de data/hora

▶ Exemplo: Extraindo features temporais

PYTHON
import pandas as pd
import numpy as np

rng = np.random.default_rng(42)
dates = pd.date_range("2024-01-01", periods=365, freq="D")
df = pd.DataFrame({"date": dates, "sales": rng.normal(1000, 200, 365)})

# Decompor data
df["year"] = df["date"].dt.year
df["month"] = df["date"].dt.month
df["day"] = df["date"].dt.day
df["day_of_week"] = df["date"].dt.dayofweek
df["is_weekend"] = (df["day_of_week"] >= 5).astype(int)
df["quarter"] = df["date"].dt.quarter
df["is_month_start"] = df["date"].dt.is_month_start.astype(int)
df["is_month_end"] = df["date"].dt.is_month_end.astype(int)

# Features cíclicas (sazonalidade)
df["month_sin"] = np.sin(2 * np.pi * df["month"] / 12)
df["month_cos"] = np.cos(2 * np.pi * df["month"] / 12)

print(df.head())

Saída:

TEXT 📖 Somente leitura
# Executado com sucesso

(2) Features de defasagem e janela rolante

▶ Exemplo: Lag features e rolling stats

PYTHON
import pandas as pd
import numpy as np

rng = np.random.default_rng(42)
sales = pd.Series(rng.normal(1000, 200, 90), index=pd.date_range("2024-01-01", periods=90))

# Lag features (valores passados)
df = pd.DataFrame({"sales": sales})
df["sales_lag1"] = df["sales"].shift(1)    # Vendas de ontem
df["sales_lag7"] = df["sales"].shift(7)    # Vendas de uma semana atrás
df["sales_lag30"] = df["sales"].shift(30)  # Vendas de um mês atrás

# Rolling stats (médias móveis)
df["sales_ma7"] = df["sales"].rolling(window=7).mean()
df["sales_ma30"] = df["sales"].rolling(window=30).mean()
df["sales_std7"] = df["sales"].rolling(window=7).std()

# Diferenças (variação)
df["sales_diff1"] = df["sales"].diff(1)
df["sales_pct_change"] = df["sales"].pct_change()

print(df.tail())

Saída:

TEXT 📖 Somente leitura
# Executado com sucesso

6. Seleção de features

(1) Filtro: Correlação e Informação Mútua

▶ Exemplo: Seleção por correlação

PYTHON
import pandas as pd
import numpy as np

rng = np.random.default_rng(42)
n = 500
df = pd.DataFrame({
    "feat_strong": rng.normal(0, 1, n),
    "feat_medium": rng.normal(0, 1, n),
    "feat_noise": rng.normal(0, 1, n),
    "feat_redundant": rng.normal(0, 1, n),
})
df["target"] = 3 * df["feat_strong"] + 1.5 * df["feat_medium"] + 0.1 * df["feat_redundant"] + rng.normal(0, 0.5, n)

# Calcular correlação com o alvo
correlations = df.corr()["target"].drop("target").abs().sort_values(ascending=False)
print("Features por correlação absoluta com o alvo:")
print(correlations.round(3))

Saída:

TEXT 📖 Somente leitura
# Executado com sucesso
Feature Correlação com o alvo
feat_strong 0.95
feat_medium 0.74
feat_redundant 0.10
feat_noise 0.04

(2) Wrapper: Eliminação Recursiva de Features

▶ Exemplo: RFE

PYTHON
from sklearn.feature_selection import RFE
from sklearn.ensemble import RandomForestRegressor
import numpy as np

rng = np.random.default_rng(42)
n, p = 200, 10
X = rng.standard_normal((n, p))
y = X[:, 0] * 3 + X[:, 2] * 2 + rng.normal(0, 0.5, n)

# Selecionar top 3 features
rfe = RFE(estimator=RandomForestRegressor(n_estimators=50, random_state=42),
          n_features_to_select=3)
rfe.fit(X, y)
print(f"Features selecionadas: {np.where(rfe.support_)[0]}")
print(f"Ranking: {rfe.ranking_}")

Saída:

TEXT 📖 Somente leitura
# Executado com sucesso

(3) Embutida: L1 Regularization

▶ Exemplo: Seleção automática via Lasso

PYTHON
from sklearn.linear_model import Lasso
import numpy as np

rng = np.random.default_rng(42)
n, p = 200, 10
X = rng.standard_normal((n, p))
true_coefs = np.zeros(p)
true_coefs[:3] = [3, -2, 1.5]
y = X @ true_coefs + rng.normal(0, 0.5, n)

# Lasso (L1) força coeficientes irrelevantes a zero
lasso = Lasso(alpha=0.5)
lasso.fit(X, y)
print(f"Coeficientes do Lasso: {lasso.coef_.round(2)}")
print(f"Features com coef ≠ 0: {np.where(np.abs(lasso.coef_) > 0.01)[0]}")

Saída:

TEXT 📖 Somente leitura
# Executado com sucesso

7. A fábrica de features de e-commerce do Bob

▶ Exemplo: Features de negócio personalizadas

PYTHON
import pandas as pd
import numpy as np

rng = np.random.default_rng(42)
n = 5000
df = pd.DataFrame({
    "user_id": range(n),
    "category": rng.choice(["Eletrônicos", "Roupas", "Alimentos", "Casa"], n),
    "purchase_date": pd.to_datetime("2024-01-01") + pd.to_timedelta(rng.integers(0, 365, n), unit="D"),
    "amount": rng.exponential(100, n),
})

# Construir features de negócio
ref_date = pd.to_datetime("2024-12-31")
df["days_since_purchase"] = (ref_date - df["purchase_date"]).dt.days

# Frequência de compra nos últimos 7/30/90 dias
for window in [7, 30, 90]:
    cutoff = ref_date - pd.Timedelta(days=window)
    df[f"purchases_last_{window}d"] = (df["purchase_date"] >= cutoff).astype(int)

# Entropia de preferência por categoria (1 user = 1 linha)
user_stats = df.groupby("user_id").agg(
    unique_categories=("category", "nunique"),
    total_purchases=("amount", "count"),
    total_spent=("amount", "sum"),
    avg_purchase=("amount", "mean"),
).reset_index()

# Diversidade de categorias (alta = explorador, baixa = fiel a uma)
user_stats["category_diversity"] = user_stats["unique_categories"] / 4

# Indicador de recompra
user_stats["is_repeat_buyer"] = (user_stats["total_purchases"] > 1).astype(int)

print(user_stats.head())

Saída:

TEXT 📖 Somente leitura
# Executado com sucesso
Feature Tipo Significado de negócio
days_since_purchase Numérica Recência — quanto menor, mais ativo
purchases_last_7d Binária Engajamento recente
total_spent Numérica Valor Monetário — LTV
category_diversity Numérica Exploração vs. lealdade
is_repeat_buyer Binária Indicador de fidelidade

❓ Perguntas Frequentes

P: Padronização ou normalização — qual escolher? R: Padronização (StandardScaler) para a maioria dos modelos (regressão, SVM, redes neurais). Use normalização (MinMaxScaler) quando precisa de intervalo [0,1] (por exemplo, entradas de rede neural com sigmoid). Use RobustScaler quando há outliers significativos.

P: Target Encoding causa vazamento de dados? R: Sim, se feito incorretamente. Use validação cruzada (computar a média apenas no fold de treino) e smoothing (combinar com a média global) para evitar isso. Nunca use todo o conjunto de dados para calcular a média de target encoding.

P: Quando usar features polinomiais? R: Quando você suspeita de relações não lineares, mas está usando um modelo linear. Para modelos baseados em árvore (Random Forest, XGBoost), features polinomiais são desnecessárias — as próprias árvores já capturam não linearidades.

P: Como lidar com alta cardinalidade em features categóricas? R: Três opções — 1) Target encoding com validação cruzada; 2) Embedding de entidades (aprendido pela rede neural); 3) Agrupar categorias raras em "Outros". Evite one-hot com 1000+ categorias.

P: Vale a pena selecionar features? R: Sim. Remove ruído, acelera o treinamento e melhora a interpretabilidade. Mas modelos de árvore têm seleção embutida, então o benefício é menor para eles. Modelos lineares se beneficiam muito da seleção de features.

P: Como validar se uma nova feature é útil? R: Adicione-a ao modelo e compare o desempenho com validação cruzada. Se a métrica melhora consistentemente em múltiplos folds, a feature é útil. Use SHAP ou permutation importance para quantificar a contribuição.


📖 Resumo


📝 Exercícios

  1. Básico (Dificuldade ⭐): Aplique StandardScaler, MinMaxScaler e RobustScaler a um array de dados com outliers e compare os intervalos de saída. Dica: use np.array([1, 5, 10, 50, 100, 500, 1000]).
  2. Intermediário (Dificuldade ⭐⭐): Gere um DataFrame temporal de 365 dias, decomponha em features de mês/dia da semana/hora, crie lag features de 1/7/30 dias e calcule médias móveis. Dica: pd.date_range() + df['col'].shift().
  3. Desafio (Dificuldade ⭐⭐⭐): Implemente o pipeline completo de feature engineering — gere dados sintéticos de e-commerce (categoria, gasto, data), construa 5 features de negócio, treine um modelo com/sem essas features e compare o R² para validar a eficácia. Dica: Use train_test_split + cross_val_score.

← Anterior: KNN e Agrupamento | Próximo: Aprendizado por Ensemble — XGBoost e LightGBM →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%