Machine learning / Feature engineering and preprocessing
The Importance of Feature Engineering in Data Science
Contents
- Understanding Feature Engineering
- Feature Creation
- Feature Transformation
- Feature Extraction
- Feature Selection
- Feature Scaling
- Real-Life Example: Customer Churn Prediction
- Real-Life Example: Text Classification
- Handling Missing Data
- Encoding Categorical Variables
- Handling Outliers
- Feature Interaction and Polynomial Features
- Time-based Feature Engineering
- Feature Importance and Selection Techniques
- Additional Resources
Understanding Feature Engineering
Feature engineering is a crucial process in data science that involves transforming raw data into meaningful features that can significantly improve the performance of machine learning models. It's not just about data manipulation; it's about making your data work harder and extracting maximum value from it. Feature engineering can impact model accuracy, data usability, team efficiency, and overall project success.
# Example of feature engineering impact
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
import numpy as np
# Load Boston housing dataset
boston = load_boston()
X, y = boston.data, boston.target
# Split data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Train model without feature engineering
model = LinearRegression()
model.fit(X_train, y_train)
predictions = model.predict(X_test)
mse_without_fe = mean_squared_error(y_test, predictions)
# Simple feature engineering: add interaction term
X_train_fe = np.column_stack((X_train, X_train[:, 5] * X_train[:, 12]))
X_test_fe = np.column_stack((X_test, X_test[:, 5] * X_test[:, 12]))
# Train model with feature engineering
model_fe = LinearRegression()
model_fe.fit(X_train_fe, y_train)
predictions_fe = model_fe.predict(X_test_fe)
mse_with_fe = mean_squared_error(y_test, predictions_fe)
print(f"MSE without feature engineering: {mse_without_fe:.2f}")
print(f"MSE with feature engineering: {mse_with_fe:.2f}")
print(f"Improvement: {(mse_without_fe - mse_with_fe) / mse_without_fe * 100:.2f}%")
Feature Creation
Feature creation involves generating new features from existing data to capture more information or represent the data in a more meaningful way. This process can uncover hidden patterns and relationships within the data, potentially improving model performance.
import pandas as pd
# Sample dataset
data = pd.DataFrame({
'date': ['2023-01-01', '2023-02-15', '2023-03-30', '2023-04-10'],
'sales': [1000, 1200, 950, 1100]
})
# Convert date to datetime
data['date'] = pd.to_datetime(data['date'])
# Create new features
data['year'] = data['date'].dt.year
data['month'] = data['date'].dt.month
data['day_of_week'] = data['date'].dt.dayofweek
data['is_weekend'] = data['day_of_week'].isin([5, 6]).astype(int)
# Calculate rolling average sales (last 2 periods)
data['rolling_avg_sales'] = data['sales'].rolling(window=2, min_periods=1).mean()
print(data)
Feature Transformation
Feature transformation involves modifying existing features to improve their representation or distribution. This can help in dealing with skewed data, outliers, or non-linear relationships between features and the target variable.
import numpy as np
import matplotlib.pyplot as plt
# Generate sample data
np.random.seed(42)
income = np.random.lognormal(mean=10, sigma=1, size=1000)
# Plot original distribution
plt.figure(figsize=(12, 5))
plt.subplot(121)
plt.hist(income, bins=50)
plt.title('Original Income Distribution')
plt.xlabel('Income')
plt.ylabel('Frequency')
# Apply log transformation
log_income = np.log(income)
# Plot transformed distribution
plt.subplot(122)
plt.hist(log_income, bins=50)
plt.title('Log-transformed Income Distribution')
plt.xlabel('Log(Income)')
plt.ylabel('Frequency')
plt.tight_layout()
plt.show()
# Calculate statistics
print(f"Original Income - Mean: {income.mean():.2f}, Std: {income.std():.2f}")
print(f"Log Income - Mean: {log_income.mean():.2f}, Std: {log_income.std():.2f}")
Feature Extraction
Feature extraction is the process of reducing high-dimensional data to a lower-dimensional space while preserving the most important information. This technique is particularly useful for dealing with large datasets or when working with complex data types like images or text.
from sklearn.decomposition import PCA
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
# Load the Iris dataset
iris = load_iris()
X = iris.data
y = iris.target
# Apply PCA
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
# Plot the results
plt.figure(figsize=(10, 8))
colors = ['navy', 'turquoise', 'darkorange']
for color, i, target_name in zip(colors, [0, 1, 2], iris.target_names):
plt.scatter(X_pca[y == i, 0], X_pca[y == i, 1], color=color, lw=2,
label=target_name)
plt.legend(loc='best', shadow=False, scatterpoints=1)
plt.title('PCA of Iris dataset')
plt.show()
# Print explained variance ratio
print("Explained variance ratio:", pca.explained_variance_ratio_)
Feature Selection
Feature selection is the process of choosing the most relevant features for a given task. This technique can improve model performance, reduce overfitting, and decrease computational complexity by removing irrelevant or redundant features.
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.datasets import load_breast_cancer
import numpy as np
# Load the breast cancer dataset
cancer = load_breast_cancer()
X, y = cancer.data, cancer.target
# Apply feature selection
selector = SelectKBest(score_func=f_classif, k=5)
X_new = selector.fit_transform(X, y)
# Get selected feature indices
selected_feature_indices = selector.get_support(indices=True)
# Print selected features and their scores
feature_scores = zip(cancer.feature_names, selector.scores_)
sorted_features = sorted(feature_scores, key=lambda x: x[1], reverse=True)
print("Top 5 selected features:")
for feature, score in sorted_features[:5]:
print(f"{feature}: {score:.2f}")
# Calculate and print improvement in dimensionality
dimensionality_reduction = (1 - X_new.shape[1] / X.shape[1]) * 100
print(f"\nDimensionality reduction: {dimensionality_reduction:.2f}%")
Feature Scaling
Feature scaling is the process of standardizing the range or distribution of features. This technique is essential when working with algorithms that are sensitive to the scale of input features, such as distance-based algorithms or gradient descent optimization.
from sklearn.preprocessing import MinMaxScaler, StandardScaler
import numpy as np
import matplotlib.pyplot as plt
# Generate sample data
np.random.seed(42)
age = np.random.normal(loc=40, scale=10, size=1000)
income = np.random.lognormal(mean=10, sigma=1, size=1000)
# Create scaler objects
minmax_scaler = MinMaxScaler()
standard_scaler = StandardScaler()
# Apply scaling
age_minmax = minmax_scaler.fit_transform(age.reshape(-1, 1)).flatten()
income_standard = standard_scaler.fit_transform(income.reshape(-1, 1)).flatten()
# Plotting
fig, axs = plt.subplots(2, 2, figsize=(12, 10))
axs[0, 0].hist(age, bins=30)
axs[0, 0].set_title('Original Age Distribution')
axs[0, 1].hist(age_minmax, bins=30)
axs[0, 1].set_title('Min-Max Scaled Age Distribution')
axs[1, 0].hist(income, bins=30)
axs[1, 0].set_title('Original Income Distribution')
axs[1, 1].hist(income_standard, bins=30)
axs[1, 1].set_title('Standardized Income Distribution')
plt.tight_layout()
plt.show()
# Print statistics
print("Age - Original range:", age.min(), "to", age.max())
print("Age - Scaled range:", age_minmax.min(), "to", age_minmax.max())
print("Income - Original mean and std:", income.mean(), income.std())
print("Income - Scaled mean and std:", income_standard.mean(), income_standard.std())
Real-Life Example: Customer Churn Prediction
Let's explore a real-life example of feature engineering in customer churn prediction. We'll create and transform features to improve our model's ability to predict customer churn.
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
# Sample customer data
data = pd.DataFrame({
'customer_id': range(1, 1001),
'tenure': np.random.randint(1, 72, 1000),
'monthly_charges': np.random.uniform(20, 100, 1000),
'total_charges': np.random.uniform(100, 5000, 1000),
'contract_type': np.random.choice(['Month-to-Month', 'One Year', 'Two Year'], 1000),
'churn': np.random.choice([0, 1], 1000, p=[0.8, 0.2])
})
# Feature engineering
data['annual_charges'] = data['monthly_charges'] * 12
data['average_monthly_charges'] = data['total_charges'] / data['tenure']
data['contract_length'] = data['contract_type'].map({'Month-to-Month': 1, 'One Year': 12, 'Two Year': 24})
data['is_long_term'] = (data['contract_length'] > 1).astype(int)
# Prepare features and target
X = data[['tenure', 'monthly_charges', 'total_charges', 'annual_charges', 'average_monthly_charges', 'contract_length', 'is_long_term']]
y = data['churn']
# Split data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Train model
model = RandomForestClassifier(random_state=42)
model.fit(X_train, y_train)
# Make predictions
y_pred = model.predict(X_test)
# Calculate accuracy
accuracy = accuracy_score(y_test, y_pred)
print(f"Model accuracy: {accuracy:.2f}")
# Feature importance
feature_importance = pd.DataFrame({'feature': X.columns, 'importance': model.feature_importances_})
print("\nFeature Importance:")
print(feature_importance.sort_values('importance', ascending=False))
Real-Life Example: Text Classification
In this example, we'll demonstrate feature engineering techniques for text classification, specifically sentiment analysis of movie reviews.
import re
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import accuracy_score, classification_report
# Sample movie reviews data
reviews = [
"This movie was fantastic! I loved every moment.",
"Terrible acting and boring plot. Waste of time.",
"Great special effects but the story was lacking.",
"A masterpiece of cinema. Highly recommended!",
"I fell asleep halfway through. Very disappointing."
]
labels = [1, 0, 0, 1, 0] # 1 for positive, 0 for negative
# Text preprocessing
def preprocess_text(text):
text = text.lower()
text = re.sub(r'[^\w\s]', '', text)
return text
preprocessed_reviews = [preprocess_text(review) for review in reviews]
# Feature extraction: Bag of Words
bow_vectorizer = CountVectorizer()
X_bow = bow_vectorizer.fit_transform(preprocessed_reviews)
# Feature extraction: TF-IDF
tfidf_vectorizer = TfidfVectorizer()
X_tfidf = tfidf_vectorizer.fit_transform(preprocessed_reviews)
# Split data
X_train_bow, X_test_bow, y_train, y_test = train_test_split(X_bow, labels, test_size=0.2, random_state=42)
X_train_tfidf, X_test_tfidf, _, _ = train_test_split(X_tfidf, labels, test_size=0.2, random_state=42)
# Train and evaluate BoW model
bow_model = MultinomialNB()
bow_model.fit(X_train_bow, y_train)
bow_predictions = bow_model.predict(X_test_bow)
bow_accuracy = accuracy_score(y_test, bow_predictions)
# Train and evaluate TF-IDF model
tfidf_model = MultinomialNB()
tfidf_model.fit(X_train_tfidf, y_train)
tfidf_predictions = tfidf_model.predict(X_test_tfidf)
tfidf_accuracy = accuracy_score(y_test, tfidf_predictions)
print("Bag of Words Model Accuracy:", bow_accuracy)
print("TF-IDF Model Accuracy:", tfidf_accuracy)
# Feature importance (top words for each class)
feature_names = bow_vectorizer.get_feature_names_out()
top_positive = bow_model.feature_log_prob_[1].argsort()[-5:][::-1]
top_negative = bow_model.feature_log_prob_[0].argsort()[-5:][::-1]
print("\nTop positive words:", [feature_names[i] for i in top_positive])
print("Top negative words:", [feature_names[i] for i in top_negative])
Handling Missing Data
Dealing with missing data is a crucial part of feature engineering. Here, we'll explore different techniques to handle missing values and their impact on model performance.
import pandas as pd
import numpy as np
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
# Create a sample dataset with missing values
np.random.seed(42)
data = pd.DataFrame({
'feature1': np.random.rand(1000),
'feature2': np.random.rand(1000),
'feature3': np.random.rand(1000),
'target': np.random.rand(1000)
})
# Introduce missing values
data.loc[np.random.choice(data.index, 100), 'feature1'] = np.nan
data.loc[np.random.choice(data.index, 150), 'feature2'] = np.nan
data.loc[np.random.choice(data.index, 200), 'feature3'] = np.nan
# Split data
X = data.drop('target', axis=1)
y = data['target']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Function to evaluate model performance
def evaluate_model(X_train, X_test, y_train, y_test):
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
mse = mean_squared_error(y_test, predictions)
return mse
# Evaluate model with missing data
mse_missing = evaluate_model(X_train, X_test, y_train, y_test)
# Impute missing values with mean
imputer = SimpleImputer(strategy='mean')
X_train_imputed = pd.DataFrame(imputer.fit_transform(X_train), columns=X_train.columns)
X_test_imputed = pd.DataFrame(imputer.transform(X_test), columns=X_test.columns)
# Evaluate model with imputed data
mse_imputed = evaluate_model(X_train_imputed, X_test_imputed, y_train, y_test)
print(f"MSE with missing data: {mse_missing:.4f}")
print(f"MSE with imputed data: {mse_imputed:.4f}")
print(f"Improvement: {(mse_missing - mse_imputed) / mse_missing * 100:.2f}%")
Encoding Categorical Variables
Categorical variables often need to be encoded into numerical form for machine learning algorithms. We'll explore two common encoding techniques: one-hot encoding and label encoding.
import pandas as pd
from sklearn.preprocessing import OneHotEncoder, LabelEncoder
from sklearn.compose import ColumnTransformer
# Sample dataset with categorical variables
data = pd.DataFrame({
'color': ['red', 'blue', 'green', 'red', 'blue'],
'size': ['small', 'medium', 'large', 'medium', 'small'],
'price': [10, 15, 20, 12, 11]
})
# One-hot encoding
onehot_encoder = OneHotEncoder(sparse=False)
onehot_encoded = onehot_encoder.fit_transform(data[['color', 'size']])
onehot_columns = onehot_encoder.get_feature_names(['color', 'size'])
onehot_df = pd.DataFrame(onehot_encoded, columns=onehot_columns)
# Label encoding
label_encoder = LabelEncoder()
data['color_encoded'] = label_encoder.fit_transform(data['color'])
data['size_encoded'] = label_encoder.fit_transform(data['size'])
print("Original data:")
print(data)
print("\nOne-hot encoded data:")
print(onehot_df)
print("\nLabel encoded data:")
print(data[['color_encoded', 'size_encoded', 'price']])
Handling Outliers
Outliers can significantly impact model performance. We'll demonstrate techniques to detect and handle outliers in your dataset.
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
# Generate sample data with outliers
np.random.seed(42)
data = np.concatenate([
np.random.normal(0, 1, 1000),
np.random.normal(10, 1, 5) # Outliers
])
# Function to plot data distribution
def plot_distribution(data, title):
plt.figure(figsize=(10, 5))
plt.hist(data, bins=50, edgecolor='black')
plt.title(title)
plt.xlabel('Value')
plt.ylabel('Frequency')
plt.show()
# Plot original distribution
plot_distribution(data, 'Original Data Distribution')
# Z-score method for outlier detection
z_scores = np.abs(stats.zscore(data))
data_z_filtered = data[z_scores < 3]
# Plot Z-score filtered distribution
plot_distribution(data_z_filtered, 'Data Distribution after Z-score Filtering')
# IQR method for outlier detection
Q1 = np.percentile(data, 25)
Q3 = np.percentile(data, 75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
data_iqr_filtered = data[(data >= lower_bound) & (data <= upper_bound)]
# Plot IQR filtered distribution
plot_distribution(data_iqr_filtered, 'Data Distribution after IQR Filtering')
print(f"Original data points: {len(data)}")
print(f"Data points after Z-score filtering: {len(data_z_filtered)}")
print(f"Data points after IQR filtering: {len(data_iqr_filtered)}")
Feature Interaction and Polynomial Features
Creating interaction features and polynomial features can capture complex relationships between variables, potentially improving model performance.
import numpy as np
import pandas as pd
from sklearn.preprocessing import PolynomialFeatures
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
# Generate sample data
np.random.seed(42)
X = np.random.rand(1000, 2)
y = 3*X[:, 0]**2 + 2*X[:, 1]**3 + 5*X[:, 0]*X[:, 1] + np.random.randn(1000)*0.1
# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Train linear model without feature engineering
model_basic = LinearRegression()
model_basic.fit(X_train, y_train)
y_pred_basic = model_basic.predict(X_test)
mse_basic = mean_squared_error(y_test, y_pred_basic)
# Create polynomial features
poly = PolynomialFeatures(degree=3, include_bias=False)
X_train_poly = poly.fit_transform(X_train)
X_test_poly = poly.transform(X_test)
# Train linear model with polynomial features
model_poly = LinearRegression()
model_poly.fit(X_train_poly, y_train)
y_pred_poly = model_poly.predict(X_test_poly)
mse_poly = mean_squared_error(y_test, y_pred_poly)
print(f"MSE without polynomial features: {mse_basic:.4f}")
print(f"MSE with polynomial features: {mse_poly:.4f}")
print(f"Improvement: {(mse_basic - mse_poly) / mse_basic * 100:.2f}%")
# Display new feature names
feature_names = poly.get_feature_names(['X1', 'X2'])
print("\nPolynomial features:")
print(feature_names)
Time-based Feature Engineering
When working with time series data, creating time-based features can significantly improve model performance. Let's explore some common time-based feature engineering techniques.
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
# Generate sample time series data
start_date = datetime(2023, 1, 1)
dates = [start_date + timedelta(days=i) for i in range(365)]
np.random.seed(42)
sales = np.random.randint(50, 200, size=365) + np.sin(np.arange(365) * 2 * np.pi / 365) * 50
data = pd.DataFrame({'date': dates, 'sales': sales})
data['date'] = pd.to_datetime(data['date'])
data.set_index('date', inplace=True)
# Extract time-based features
data['year'] = data.index.year
data['month'] = data.index.month
data['day_of_week'] = data.index.dayofweek
data['quarter'] = data.index.quarter
data['is_weekend'] = (data['day_of_week'] >= 5).astype(int)
# Create lag features
data['sales_lag1'] = data['sales'].shift(1)
data['sales_lag7'] = data['sales'].shift(7)
# Create rolling window features
data['sales_rolling_mean_7'] = data['sales'].rolling(window=7).mean()
data['sales_rolling_std_7'] = data['sales'].rolling(window=7).std()
# Create seasonal features
data['day_of_year'] = data.index.dayofyear
data['sales_last_year'] = data['sales'].shift(365)
# Display the first few rows of the engineered dataset
print(data.head())
# Calculate correlation between features and target
correlation = data.corr()['sales'].sort_values(ascending=False)
print("\nCorrelation with sales:")
print(correlation)
Feature Importance and Selection Techniques
Understanding which features are most important for your model can help in feature selection and model interpretation. We'll explore different techniques for assessing feature importance.
import pandas as pd
import numpy as np
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from sklearn.feature_selection import mutual_info_regression, SelectKBest
from sklearn.inspection import permutation_importance
# Load Boston Housing dataset
boston = load_boston()
X = pd.DataFrame(boston.data, columns=boston.feature_names)
y = boston.target
# Split the data
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Random Forest Feature Importance
rf_model = RandomForestRegressor(n_estimators=100, random_state=42)
rf_model.fit(X_train, y_train)
rf_importance = pd.DataFrame({'feature': X.columns, 'importance': rf_model.feature_importances_})
rf_importance = rf_importance.sort_values('importance', ascending=False)
# Mutual Information
mi_importance = mutual_info_regression(X_train, y_train)
mi_importance = pd.DataFrame({'feature': X.columns, 'importance': mi_importance})
mi_importance = mi_importance.sort_values('importance', ascending=False)
# Permutation Importance
perm_importance = permutation_importance(rf_model, X_test, y_test, n_repeats=10, random_state=42)
perm_importance = pd.DataFrame({'feature': X.columns, 'importance': perm_importance.importances_mean})
perm_importance = perm_importance.sort_values('importance', ascending=False)
print("Random Forest Feature Importance:")
print(rf_importance)
print("\nMutual Information Importance:")
print(mi_importance)
print("\nPermutation Importance:")
print(perm_importance)
# Select top K features
k = 5
selector = SelectKBest(mutual_info_regression, k=k)
X_selected = selector.fit_transform(X, y)
selected_features = X.columns[selector.get_support()]
print(f"\nTop {k} selected features based on Mutual Information:")
print(selected_features)
Additional Resources
For those interested in diving deeper into feature engineering, here are some valuable resources:
- "Feature Engineering for Machine Learning" by Alice Zheng and Amanda Casari
- A comprehensive guide covering various aspects of feature engineering.
- "Automated Feature Engineering in Python" by Roy Keyes
- ArXiv paper: https://arxiv.org/abs/1904.01387
- Explores automated approaches to feature engineering.
- "An Introduction to Feature Selection" by Jason Brownlee
- Machine Learning Mastery blog post providing an overview of feature selection techniques.
- "Feature Engineering and Selection: A Practical Approach for Predictive Models" by Max Kuhn and Kjell Johnson
- A book that combines theoretical concepts with practical examples.
- Scikit-learn Feature Selection Documentation
- Official documentation covering various feature selection techniques implemented in scikit-learn.
These resources offer a mix of theoretical foundations and practical implementations to help you master the art of feature engineering.