Advanced Feature Engineering: Complete Guide
Guide
Table of Contents
- Overview
- Introduction
- Part 1: Feature Selection
- Part 2: Feature Extraction
- Part 3: Vector Embeddings
- Part 4: Latent Space
- Part 5: Dimensionality Reduction
- Part 6: Handling Data Imbalance
- Part 7: Synthetic Data Generation
- Part 8: Data Leakage
- Complete Feature Engineering Pipeline
- Best Practices
- Resources
Overview
This post is a comprehensive guide to feature engineering for machine learning and data science. It covers advanced techniques for feature selection, extraction, embeddings, dimensionality reduction, handling data imbalance, synthetic data generation, and preventing data leakage. The guide includes practical code examples, best practices, and resources to help you transform raw data into powerful features for better models and results.
Introduction
Feature engineering is the art and science of transforming raw data into meaningful features that improve machine learning model performance. It’s often considered one of the most important skills in data science, directly impacting model accuracy, efficiency, and interpretability.
Why Feature Engineering Matters:
Feature engineering is the critical bridge between raw data and high-performing machine learning models. The process transforms unrefined data into meaningful, informative features, which directly leads to better models and results. In practice, the workflow looks like this:
Raw Data → Feature Engineering → Better Features → Better Models → Better Results
Well-engineered features can improve model accuracy by 20-50%, reduce training time, and make models more interpretable. They help models generalize to new data and ensure that insights are actionable and aligned with business goals. In short, feature engineering is often the difference between mediocre and outstanding machine learning solutions.
Part 1: Feature Selection
Feature selection is the process of choosing the most relevant features for your model while removing redundant or irrelevant ones. The advantages of using feature selection:
- Reduces Overfitting: Fewer features = simpler models = better generalization
- Improves Speed: Fewer features = faster training and prediction
- Reduces Costs: Less storage and computation needed
- Better Interpretability: Easier to understand which features matter
- Avoids Curse of Dimensionality: Handles high-dimensional data
Feature Selection Methods
1. Univariate Statistical Methods
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from sklearn.feature_selection import SelectKBest, f_classif, f_regression
import pandas as pd
# For classification
selector = SelectKBest(score_func=f_classif, k=10)
X_selected = selector.fit_transform(X, y)
# Get selected feature names
selected_features = X.columns[selector.get_support()].tolist()
print(f"Selected features: {selected_features}")
# Visualize feature importance scores
feature_scores = selector.scores_
feature_importance = pd.DataFrame({
'feature': X.columns,
'score': feature_scores
}).sort_values('score', ascending=False)
print(feature_importance.head(10))
2. Model-Based Feature Selection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier
# Train a model
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X, y)
# Select features based on importance
selector = SelectFromModel(model, prefit=True)
X_selected = selector.transform(X)
# Get feature importances
importances = model.feature_importances_
feature_names = X.columns
importance_df = pd.DataFrame({
'feature': feature_names,
'importance': importances
}).sort_values('importance', ascending=False)
print(importance_df.head(10))
# Visualize
import matplotlib.pyplot as plt
plt.barh(importance_df['feature'][:10], importance_df['importance'][:10])
plt.xlabel('Importance')
plt.title('Top 10 Feature Importances')
plt.show()
3. Recursive Feature Elimination (RFE)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
# Use RFE to select top 10 features
model = LogisticRegression(max_iter=1000)
rfe = RFE(estimator=model, n_features_to_select=10)
X_selected = rfe.fit_transform(X, y)
# Get selected features
selected_mask = rfe.support_
selected_features = X.columns[selected_mask].tolist()
print(f"Selected features: {selected_features}")
# Feature ranking
feature_ranking = pd.DataFrame({
'feature': X.columns,
'ranking': rfe.ranking_
}).sort_values('ranking')
print(feature_ranking)
4. Correlation Analysis
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import numpy as np
# Calculate correlation matrix
correlation_matrix = X.corr()
# Remove highly correlated features
def remove_correlated_features(df, threshold=0.95):
"""Remove features with correlation > threshold"""
corr_matrix = df.corr().abs()
# Select upper triangle
upper = corr_matrix.where(
np.triu(np.ones(corr_matrix.shape), k=1).astype(bool)
)
# Find features with correlation > threshold
to_drop = [column for column in upper.columns if any(upper[column] > threshold)]
return df.drop(columns=to_drop)
X_uncorrelated = remove_correlated_features(X, threshold=0.9)
print(f"Dropped features: {set(X.columns) - set(X_uncorrelated.columns)}")
5. Mutual Information
1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.feature_selection import mutual_info_classif, SelectKBest
# Calculate mutual information
mutual_info = mutual_info_classif(X, y)
mi_scores = pd.Series(mutual_info, index=X.columns).sort_values(ascending=False)
print("Top 10 features by mutual information:")
print(mi_scores.head(10))
# Select top k features
selector = SelectKBest(score_func=mutual_info_classif, k=10)
X_selected = selector.fit_transform(X, y)
Part 2: Feature Extraction
Feature extraction is creating new features from raw data by transforming or combining existing features. Feature extraction is essential when the original features are insufficient for capturing the underlying patterns in your data. By transforming or combining existing features, you can reveal hidden structures, reduce noise, and create more informative representations that improve model performance. This process is especially valuable for complex data types like text, images, or signals, where raw features may not be directly useful for machine learning algorithms. Effective feature extraction can lead to better accuracy, more robust models, and deeper insights into your data.
Feature Extraction Methods
1. Principal Component Analysis (PCA)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from sklearn.decomposition import PCA
# Create PCA
pca = PCA(n_components=10)
X_pca = pca.fit_transform(X)
# Explained variance ratio
print(f"Explained variance ratio: {pca.explained_variance_ratio_}")
print(f"Total variance explained: {sum(pca.explained_variance_ratio_)}")
# Visualize cumulative variance
cumsum = np.cumsum(pca.explained_variance_ratio_)
plt.plot(cumsum)
plt.xlabel('Number of Components')
plt.ylabel('Cumulative Explained Variance')
plt.title('PCA Explained Variance')
plt.show()
2. Independent Component Analysis (ICA)
1
2
3
4
5
6
7
from sklearn.decomposition import FastICA
# Create ICA
ica = FastICA(n_components=10, random_state=42)
X_ica = ica.fit_transform(X)
print(f"ICA components shape: {X_ica.shape}")
3. Text Feature Extraction
1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer
# TF-IDF Vectorization
tfidf = TfidfVectorizer(max_features=100, ngram_range=(1, 2))
X_tfidf = tfidf.fit_transform(text_data)
# Count Vectorization
count_vec = CountVectorizer(max_features=100)
X_count = count_vec.fit_transform(text_data)
print(f"TF-IDF shape: {X_tfidf.shape}")
print(f"Feature names: {tfidf.get_feature_names_out()[:10]}")
4. Polynomial Feature Expansion
1
2
3
4
5
6
7
8
9
from sklearn.preprocessing import PolynomialFeatures
# Create polynomial features (degree 2)
poly = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly.fit_transform(X)
print(f"Original features: {X.shape[1]}")
print(f"Polynomial features: {X_poly.shape[1]}")
print(f"Feature names: {poly.get_feature_names_out()}")
Part 3: Vector Embeddings
Vector embeddings are dense, low-dimensional vector representations that capture semantic meaning and relationships in data. They are essential for transforming complex, unstructured data—such as text, images, or audio—into a numerical format that machine learning models can understand and process efficiently. By encoding high-dimensional or categorical information into compact vectors, embeddings enable algorithms to measure similarity, perform clustering, and support advanced tasks like search, recommendation, and natural language understanding. Their ability to capture context and relationships makes them foundational for modern AI applications, including NLP, computer vision, and retrieval-augmented generation (RAG) systems.
Applications & Techniques
1. Word Embeddings (Word2Vec)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from gensim.models import Word2Vec
import nltk
# Sample text data
sentences = [
["machine", "learning", "is", "powerful"],
["deep", "learning", "uses", "neural", "networks"],
["natural", "language", "processing", "is", "important"]
]
# Train Word2Vec
w2v_model = Word2Vec(sentences, vector_size=100, window=5, min_count=1)
# Get word vectors
word_vector = w2v_model.wv['machine']
print(f"Word vector shape: {word_vector.shape}")
# Find similar words
similar_words = w2v_model.wv.most_similar('learning', topn=5)
print(f"Words similar to 'learning': {similar_words}")
2. Sentence Embeddings (BERT)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from sentence_transformers import SentenceTransformer
# Load pre-trained model
model = SentenceTransformer('all-MiniLM-L6-v2')
# Generate embeddings
sentences = [
"Machine learning is a subset of AI",
"Deep learning uses neural networks",
"Natural language processing analyzes text"
]
embeddings = model.encode(sentences)
print(f"Embedding shape: {embeddings.shape}")
# Calculate similarity
from sklearn.metrics.pairwise import cosine_similarity
similarity = cosine_similarity(embeddings)
print(f"Similarity between first two sentences: {similarity[0, 1]:.4f}")
3. Image Embeddings (CNN Features)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from tensorflow.keras.applications import VGG16
from tensorflow.keras.preprocessing import image
import numpy as np
# Load pre-trained VGG16
model = VGG16(weights='imagenet', include_top=False)
# Load and preprocess image
img = image.load_img('image.jpg', target_size=(224, 224))
img_array = image.img_to_array(img)
img_array = np.expand_dims(img_array, axis=0)
# Extract features
features = model.predict(img_array)
print(f"Image embedding shape: {features.shape}")
Part 4: Latent Space
Latent space is a lower-dimensional representation of data where similar items are close together and underlying patterns are captured. Using latent space is crucial because it allows machine learning models to focus on the most meaningful aspects of the data, filtering out noise and redundancy. By mapping complex, high-dimensional data into a latent space, models can discover hidden structures, relationships, and features that are not obvious in the original space. This makes tasks like clustering, visualization, anomaly detection, and generative modeling more effective and interpretable. Latent spaces are foundational for techniques such as autoencoders, variational autoencoders (VAE), and generative adversarial networks (GANs), enabling advanced applications in image, text, and signal processing.
Latent Space Techniques
1. Autoencoders
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from tensorflow.keras import layers, models
# Build autoencoder
encoder = models.Sequential([
layers.Dense(128, activation='relu', input_shape=(784,)),
layers.Dense(64, activation='relu'),
layers.Dense(32, activation='relu'), # Bottleneck (latent space)
layers.Dense(64, activation='relu'),
layers.Dense(128, activation='relu'),
layers.Dense(784, activation='sigmoid')
])
encoder.compile(optimizer='adam', loss='mse')
# Train
encoder.fit(X_train, X_train, epochs=10, batch_size=32)
# Extract latent representation
latent_model = models.Model(
inputs=encoder.input,
outputs=encoder.layers[2].output
)
latent_vectors = latent_model.predict(X_test)
print(f"Latent vector shape: {latent_vectors.shape}")
2. Variational Autoencoders (VAE)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from tensorflow.keras import layers, Model
from tensorflow.keras.losses import binary_crossentropy
import tensorflow as tf
# VAE implementation (simplified)
def create_vae(input_dim, latent_dim):
# Encoder
inputs = layers.Input(shape=(input_dim,))
x = layers.Dense(128, activation='relu')(inputs)
z_mean = layers.Dense(latent_dim)(x)
z_log_var = layers.Dense(latent_dim)(x)
# Sampling
def sampling(args):
z_mean, z_log_var = args
epsilon = tf.random.normal(tf.shape(z_mean))
return z_mean + tf.exp(0.5 * z_log_var) * epsilon
z = layers.Lambda(sampling)([z_mean, z_log_var])
# Decoder
x = layers.Dense(128, activation='relu')(z)
outputs = layers.Dense(input_dim, activation='sigmoid')(x)
return Model(inputs, outputs), Model(inputs, z)
vae, encoder = create_vae(input_dim=784, latent_dim=32)
vae.compile(optimizer='adam', loss='mse')
vae.fit(X_train, epochs=10)
Part 5: Dimensionality Reduction
Reducing the number of features while preserving important information. Dimensionality reduction is essential when working with high-dimensional datasets, as it helps to simplify models, reduce computational costs, and mitigate the risk of overfitting. By projecting data into a lower-dimensional space, you can uncover hidden patterns, improve visualization, and enhance the interpretability of your results. It also addresses the curse of dimensionality, where too many features can dilute meaningful signals and make learning difficult. Effective dimensionality reduction leads to faster training, better generalization, and more robust machine learning pipelines.
Dimensionality Reduction Techniques
1. PCA (Already Covered)
1
2
3
4
from sklearn.decomposition import PCA
pca = PCA(n_components=50)
X_reduced = pca.fit_transform(X)
2. t-SNE
1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
# Reduce to 2D for visualization
tsne = TSNE(n_components=2, random_state=42)
X_tsne = tsne.fit_transform(X)
# Visualize
plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='viridis')
plt.title('t-SNE Visualization')
plt.colorbar()
plt.show()
3. UMAP
1
2
3
4
5
6
7
8
9
import umap
# UMAP reduction
reducer = umap.UMAP(n_components=2, random_state=42)
X_umap = reducer.fit_transform(X)
plt.scatter(X_umap[:, 0], X_umap[:, 1], c=y, cmap='viridis')
plt.title('UMAP Visualization')
plt.show()
4. Feature Hashing
1
2
3
4
5
6
7
from sklearn.feature_extraction import FeatureHasher
# Hash sparse features
hasher = FeatureHasher(n_features=1024, input_type='dict')
X_hashed = hasher.transform(X_dict).toarray()
print(f"Hashed features shape: {X_hashed.shape}")
Part 6: Handling Data Imbalance
Techniques to handle imbalanced class distributions in classification tasks. Handling data imbalance is critical because, in many real-world datasets, one class may significantly outnumber others (for example, fraud detection or rare disease diagnosis). If left unaddressed, machine learning models tend to be biased toward the majority class, resulting in poor detection of minority cases and misleading performance metrics. Properly addressing imbalance ensures that your model learns to recognize all classes, improves generalization, and leads to fairer, more reliable predictions. Techniques like resampling, synthetic data generation, and algorithmic adjustments help create balanced training data and robust models that perform well on both common and rare cases.
Handling Data Imbalance Techniques
1. Upsampling (Oversampling)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from imblearn.over_sampling import RandomOverSampler, SMOTE
# Random Oversampling
ros = RandomOverSampler(random_state=42)
X_upsampled, y_upsampled = ros.fit_resample(X, y)
print(f"Original class distribution: {np.bincount(y)}")
print(f"Upsampled class distribution: {np.bincount(y_upsampled)}")
# SMOTE (Synthetic Minority Oversampling Technique)
smote = SMOTE(random_state=42)
X_smote, y_smote = smote.fit_resample(X, y)
print(f"SMOTE class distribution: {np.bincount(y_smote)}")
2. Downsampling (Undersampling)
1
2
3
4
5
6
7
8
9
10
11
from imblearn.under_sampling import RandomUnderSampler, TomekLinks
# Random Undersampling
rus = RandomUnderSampler(random_state=42)
X_downsampled, y_downsampled = rus.fit_resample(X, y)
print(f"Downsampled class distribution: {np.bincount(y_downsampled)}")
# Tomek Links
tomek = TomekLinks()
X_tomek, y_tomek = tomek.fit_resample(X, y)
3. Hybrid Approaches
1
2
3
4
5
6
7
8
9
10
11
12
from imblearn.pipeline import Pipeline
from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
# Combine SMOTE + Undersampling
pipeline = Pipeline([
('smote', SMOTE(random_state=42)),
('undersampler', RandomUnderSampler(random_state=42))
])
X_balanced, y_balanced = pipeline.fit_resample(X, y)
print(f"Balanced class distribution: {np.bincount(y_balanced)}")
Part 7: Synthetic Data Generation
Creating artificial data that mimics real data distribution to augment training data. Synthetic data generation is especially valuable when real-world data is scarce, imbalanced, or sensitive due to privacy concerns. By generating realistic samples, you can improve model performance, address class imbalance, and enable experimentation without risking exposure of confidential information. Synthetic data also helps test model robustness, simulate rare events, and accelerate development when collecting new data is costly or impractical. Techniques like SMOTE, GANs, and data augmentation empower you to build more generalizable and resilient machine learning solutions.
Synthetic Data Generation Techniques
1. Synthetic Data with SMOTE (Already Covered)
2. Generative Adversarial Networks (GANs)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from tensorflow import keras
from tensorflow.keras import layers
# Simple GAN
def create_generator(latent_dim):
model = keras.Sequential([
layers.Dense(128, activation='relu', input_shape=(latent_dim,)),
layers.Dense(256, activation='relu'),
layers.Dense(512, activation='relu'),
layers.Dense(784, activation='sigmoid')
])
return model
def create_discriminator():
model = keras.Sequential([
layers.Dense(512, activation='relu', input_shape=(784,)),
layers.Dense(256, activation='relu'),
layers.Dense(1, activation='sigmoid')
])
return model
generator = create_generator(latent_dim=100)
discriminator = create_discriminator()
# Generate synthetic samples
latent_vectors = np.random.normal(0, 1, (100, 100))
synthetic_data = generator.predict(latent_vectors)
3. Data Augmentation for Images
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from tensorflow.keras.preprocessing.image import ImageDataGenerator
# Image augmentation
augmentation = ImageDataGenerator(
rotation_range=20,
width_shift_range=0.2,
height_shift_range=0.2,
horizontal_flip=True,
zoom_range=0.2,
shear_range=0.15,
fill_mode='nearest'
)
# Apply augmentation
augmented_images = augmentation.flow(
X_train,
batch_size=32
)
4. Mixup Augmentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def mixup(X, y, alpha=0.2):
"""Mix pairs of examples and labels"""
mixed_X = []
mixed_y = []
for i in range(len(X)):
j = np.random.randint(0, len(X))
lam = np.random.beta(alpha, alpha)
mixed_x = lam * X[i] + (1 - lam) * X[j]
mixed_label = lam * y[i] + (1 - lam) * y[j]
mixed_X.append(mixed_x)
mixed_y.append(mixed_label)
return np.array(mixed_X), np.array(mixed_y)
X_mixed, y_mixed = mixup(X_train, y_train_onehot)
Part 8: Data Leakage
When information from outside the training dataset is leaked into the model, causing artificially inflated performance. Data leakage is a critical issue because it leads to models that appear highly accurate during training and validation but fail in real-world deployment. Leakage allows the model to “cheat” by accessing information it would not have in production, resulting in over-optimistic metrics and unreliable predictions. This undermines trust in the model, wastes resources, and can have serious consequences in sensitive applications like healthcare, finance, or security. Detecting and preventing data leakage is essential for building robust, generalizable, and trustworthy machine learning solutions.
Common Causes & Solutions
1. Target Leakage
1
2
3
4
5
6
7
8
9
10
# BAD: Using future information
df['days_since_purchase'] = (df['prediction_date'] - df['purchase_date']).dt.days
df['customer_future_purchases'] = df.groupby('customer_id')['purchase_date'].transform(
lambda x: (x > x.iloc[0]).sum() # Counts FUTURE purchases
)
# GOOD: Use only past information
df['customer_past_purchases'] = df.groupby('customer_id')['purchase_date'].transform(
lambda x: (x < x.iloc[0]).sum() # Counts PAST purchases
)
2. Train-Test Leakage
1
2
3
4
5
6
7
8
9
10
11
# BAD: Scale before train-test split
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y)
# GOOD: Split first, then scale
X_train, X_test, y_train, y_test = train_test_split(X, y)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test) # Use training statistics
3. Feature Engineering Leakage
1
2
3
4
5
6
7
8
# BAD: Use global statistics
mean_value = df['price'].mean() # Computed on entire dataset
df['price_normalized'] = df['price'] / mean_value
# GOOD: Use training set statistics only
train_mean = X_train['price'].mean()
X_train['price_normalized'] = X_train['price'] / train_mean
X_test['price_normalized'] = X_test['price'] / train_mean
4. Temporal Leakage
1
2
3
4
5
6
7
8
9
# BAD: Random split on time series
X_train, X_test, y_train, y_test = train_test_split(X_time_series, y, random_state=42)
# GOOD: Temporal split
split_point = int(0.8 * len(X_time_series))
X_train = X_time_series[:split_point]
X_test = X_time_series[split_point:]
y_train = y[:split_point]
y_test = y[split_point:]
5. Group Leakage
1
2
3
4
5
6
7
8
9
10
# BAD: Same person in train and test
X_train, X_test, y_train, y_test = train_test_split(X, y)
# GOOD: Ensure different groups in train/test
from sklearn.model_selection import GroupShuffleSplit
gss = GroupShuffleSplit(n_splits=1, test_size=0.2)
for train_idx, test_idx in gss.split(X, y, groups=customer_id):
X_train, X_test = X[train_idx], X[test_idx]
y_train, y_test = y[train_idx], y[test_idx]
Detecting Data Leakage
Detecting data leakage is crucial for building trustworthy machine learning models. Leakage can cause your model to perform exceptionally well during training but fail in real-world scenarios due to information bleeding from the test set or future data. This section explains how to identify leakage by monitoring the gap between training and test performance, and provides practical code to help you diagnose and address this common pitfall.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def detect_leakage(model_train_score, model_test_score, threshold=0.1):
"""
High gap between train and test performance suggests leakage
"""
gap = model_train_score - model_test_score
if gap > threshold:
print(f"WARNING: Large performance gap ({gap:.2%})")
print("Possible causes:")
print(" - Target leakage")
print(" - Train-test contamination")
print(" - Feature engineering leakage")
else:
print(f"Performance gap acceptable: {gap:.2%}")
return gap
# Example
train_score = 0.95
test_score = 0.72
gap = detect_leakage(train_score, test_score)
Complete Feature Engineering Pipeline
This section demonstrates how to bring together all the concepts and techniques discussed in this guide into a single, unified pipeline. By combining preprocessing, feature selection, resampling, and modeling steps, you can create a robust and reproducible workflow for real-world machine learning projects. The following code example shows how to implement a complete feature engineering pipeline using scikit-learn and imbalanced-learn.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler
from sklearn.feature_selection import SelectKBest, f_classif
from imblearn.pipeline import Pipeline as ImbPipeline
from imblearn.over_sampling import SMOTE
# Define preprocessor
preprocessor = ColumnTransformer(
transformers=[
('num', StandardScaler(), numeric_features),
('cat', OneHotEncoder(), categorical_features)
]
)
# Create feature engineering pipeline
feature_engineering = Pipeline([
('preprocessing', preprocessor),
('feature_selection', SelectKBest(score_func=f_classif, k=20)),
])
# Create full pipeline with resampling
full_pipeline = ImbPipeline([
('features', feature_engineering),
('resampling', SMOTE()),
('model', RandomForestClassifier())
])
# Train and evaluate
full_pipeline.fit(X_train, y_train)
score = full_pipeline.score(X_test, y_test)
print(f"Model score: {score:.4f}")
Best Practices
The following best practices will help you maximize the impact of your feature engineering efforts. By following these guidelines, you can ensure your features are robust, interpretable, and effective for building high-performing machine learning models. These principles apply across all stages of the feature engineering pipeline, from initial exploration to production deployment.
- Start Simple: Begin with basic features before complex engineering
- Domain Knowledge: Incorporate expert insights
- Avoid Leakage: Rigorously separate train/test
- Monitor Performance: Track improvement from each feature
- Document Features: Keep record of engineering decisions
- Validate Assumptions: Test if features work as expected
- Regular Review: Reassess features periodically
- Handle Imbalance Early: Address before feature engineering