# Feature Engineering

> When to activate: feature engineering, feature selection, encoding, feature store, Feast, target encoding, scaling, time-series features

- Skill: `mattakushi432/feature-engineering` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/feature-engineering`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/feature-engineering/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/feature-engineering

---

# Feature Engineering Patterns

## Feature Selection

```python
from sklearn.feature_selection import (
    SelectKBest, mutual_info_classif, RFE, SelectFromModel
)
from sklearn.ensemble import RandomForestClassifier
import shap

# Mutual information
selector = SelectKBest(score_func=mutual_info_classif, k=20)
X_selected = selector.fit_transform(X_train, y_train)
selected_features = X_train.columns[selector.get_support()].tolist()

# Recursive Feature Elimination
rfe = RFE(estimator=RandomForestClassifier(n_estimators=100), n_features_to_select=15)
rfe.fit(X_train, y_train)
rfe_features = X_train.columns[rfe.support_].tolist()

# SHAP-based selection
model = RandomForestClassifier().fit(X_train, y_train)
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_train)
mean_abs_shap = pd.DataFrame({
    "feature": X_train.columns,
    "importance": np.abs(shap_values[1]).mean(axis=0),
}).sort_values("importance", ascending=False)
```

## Encoding Patterns

```python
import pandas as pd
import numpy as np
from sklearn.preprocessing import OrdinalEncoder, TargetEncoder
from category_encoders import WOEEncoder

# Target encoding (safe with cross-val)
te = TargetEncoder(smooth="auto")
X_train["city_encoded"] = te.fit_transform(X_train[["city"]], y_train)
X_test["city_encoded"] = te.transform(X_test[["city"]])

# Ordinal encoding with unknown handling
oe = OrdinalEncoder(handle_unknown="use_encoded_value", unknown_value=-1)
X_train[["size"]] = oe.fit_transform(X_train[["size"]])

# Weight of Evidence (for binary classification)
woe = WOEEncoder(cols=["category"])
X_train_woe = woe.fit_transform(X_train, y_train)
X_test_woe = woe.transform(X_test)

# Frequency encoding
freq_map = X_train["product"].value_counts(normalize=True).to_dict()
X_train["product_freq"] = X_train["product"].map(freq_map)
X_test["product_freq"] = X_test["product"].map(freq_map).fillna(0)
```

## Scaling

```python
from sklearn.preprocessing import StandardScaler, RobustScaler, QuantileTransformer
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer

numeric_features = ["amount", "age", "duration"]
categorical_features = ["city", "product"]

preprocessor = ColumnTransformer([
    ("num", RobustScaler(), numeric_features),       # robust to outliers
    ("cat", TargetEncoder(), categorical_features),
])

pipeline = Pipeline([
    ("preprocessor", preprocessor),
    ("model", RandomForestClassifier()),
])
pipeline.fit(X_train, y_train)
```

## Time-Series Features

```python
def add_time_features(df: pd.DataFrame, ts_col: str) -> pd.DataFrame:
    df = df.copy()
    ts = pd.to_datetime(df[ts_col])
    df["hour"] = ts.dt.hour
    df["dow"] = ts.dt.dayofweek
    df["month"] = ts.dt.month
    df["is_weekend"] = (ts.dt.dayofweek >= 5).astype(int)
    df["quarter"] = ts.dt.quarter
    # Cyclical encoding
    df["hour_sin"] = np.sin(2 * np.pi * df["hour"] / 24)
    df["hour_cos"] = np.cos(2 * np.pi * df["hour"] / 24)
    df["dow_sin"] = np.sin(2 * np.pi * df["dow"] / 7)
    df["dow_cos"] = np.cos(2 * np.pi * df["dow"] / 7)
    return df

def add_lag_features(df: pd.DataFrame, col: str, lags: list[int]) -> pd.DataFrame:
    df = df.sort_values("ts")
    for lag in lags:
        df[f"{col}_lag_{lag}"] = df.groupby("user_id")[col].shift(lag)
    df[f"{col}_rolling_7d"] = (
        df.groupby("user_id")[col]
        .transform(lambda x: x.rolling(7, min_periods=1).mean())
    )
    return df
```

## Feast Feature Store

```python
from feast import FeatureStore, Entity, FeatureView, Field, FileSource
from feast.types import Float64, Int64

store = FeatureStore(repo_path="feature_repo/")

# Define entity
user = Entity(name="user_id", join_keys=["user_id"])

# Define feature view
user_features = FeatureView(
    name="user_stats",
    entities=[user],
    ttl=timedelta(days=7),
    schema=[
        Field(name="daily_spend", dtype=Float64),
        Field(name="tx_count", dtype=Int64),
    ],
    source=FileSource(path="data/user_stats.parquet", timestamp_field="event_timestamp"),
)

# Retrieve features for training
entity_df = pd.DataFrame({"user_id": user_ids, "event_timestamp": timestamps})
training_df = store.get_historical_features(
    entity_df=entity_df,
    features=["user_stats:daily_spend", "user_stats:tx_count"],
).to_df()

# Online retrieval
online_features = store.get_online_features(
    features=["user_stats:daily_spend", "user_stats:tx_count"],
    entity_rows=[{"user_id": "u123"}],
).to_dict()
```

## Anti-Patterns

- **Target leakage**: never use post-event data as features; always validate with time-based splits
- **Train-test contamination**: fit encoders on train only, transform both
- **High cardinality raw**: raw user IDs as features cause memorization — encode or embed
- **Scaling tree models**: tree-based models don't need feature scaling; only linear/SVM/NN do

