AiTechWorlds
AiTechWorlds
The quality of your features determines the ceiling of your model's performance. Even the best algorithm can't save you from bad features — but great features make even simple algorithms shine. This is where data science becomes an art.
Feature engineering is transforming raw data into representations that better capture the underlying patterns for your model. It includes:
import pandas as pd
import numpy as np
df = pd.read_csv('housing.csv')
# Interaction features
df['price_per_sqft'] = df['price'] / df['size']
df['size_per_bedroom'] = df['size'] / df['bedrooms']
# Age features
df['house_age'] = 2026 - df['year_built']
df['years_since_renovation'] = 2026 - df['last_renovation'].fillna(df['year_built'])
# Binned features (convert continuous to categorical)
df['size_category'] = pd.cut(
df['size'],
bins=[0, 1200, 1800, 2500, 99999],
labels=['Small', 'Medium', 'Large', 'XL']
)
# Boolean features
df['has_pool'] = (df['pool_area'] > 0).astype(int)
df['renovated_recently'] = (df['years_since_renovation'] < 10).astype(int)
Feature creation intuition: Think like a domain expert. What would a real estate agent consider? That's often what the model needs too.
ML algorithms need numbers. Here's how to encode categories:
from sklearn.preprocessing import LabelEncoder
# Use when order matters: Small < Medium < Large
size_order = {'Small': 0, 'Medium': 1, 'Large': 2, 'XL': 3}
df['size_encoded'] = df['size_category'].map(size_order)
# Use when categories have no natural order
# pandas get_dummies
df = pd.get_dummies(df, columns=['city', 'property_type'], drop_first=True)
# sklearn OneHotEncoder (better for pipelines)
from sklearn.preprocessing import OneHotEncoder
enc = OneHotEncoder(sparse_output=False, drop='first')
city_encoded = enc.fit_transform(df[['city']])
The dummy variable trap: Always use drop_first=True to avoid perfect multicollinearity.
# Useful when a category has hundreds of unique values
city_avg_price = df.groupby('city')['price'].mean()
df['city_target_encoded'] = df['city'].map(city_avg_price)
Many ML algorithms are sensitive to the scale of features. A feature ranging 0–1 and one ranging 0–1,000,000 will cause algorithms to heavily weight the large-valued feature.
Algorithms that NEED scaling: KNN, SVM, neural networks, logistic regression, PCA, gradient descent-based models
Algorithms that DON'T need scaling: Decision trees, random forests, gradient boosting (XGBoost, LightGBM)
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X_train)
# IMPORTANT: fit only on training data, then transform test data
X_test_scaled = scaler.transform(X_test) # Do NOT fit again
Use when: you need values bounded in a specific range, neural networks.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
Use when: features follow a roughly normal distribution. Most common choice for traditional ML.
from sklearn.preprocessing import RobustScaler
# Less sensitive to outliers than StandardScaler
scaler = RobustScaler()
X_scaled = scaler.fit_transform(X_train)
Use when: data has significant outliers you can't or won't remove.
Critical rule: Always fit your scaler on training data only, then transform both train and test.
# WRONG — leaks test data statistics into training
scaler.fit(X) # Fits on all data including test
X_scaled = scaler.transform(X)
# RIGHT — fit only on training data
X_train, X_test, y_train, y_test = train_test_split(X, y)
scaler.fit(X_train) # Fit on training only
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test) # Apply same transformation
Data leakage gives you artificially inflated validation scores and models that underperform in production.
# Log transformation — for right-skewed data (most common)
df['price_log'] = np.log1p(df['price']) # log(1+x), safe for zeros
# Square root — gentler transformation
df['rooms_sqrt'] = np.sqrt(df['total_rooms'])
# Box-Cox — optimal transformation found automatically
from scipy import stats
df['size_boxcox'], _ = stats.boxcox(df['size'] + 1)
# Check before and after
print(f"Original skew: {df['price'].skew():.3f}")
print(f"Log skew: {df['price_log'].skew():.3f}")
More features is not always better — irrelevant features add noise.
from sklearn.ensemble import RandomForestRegressor
from sklearn.feature_selection import SelectKBest, f_regression
# Method 1: Feature importance from tree models
model = RandomForestRegressor(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
importances = pd.Series(model.feature_importances_, index=feature_names)
importances.sort_values(ascending=False).plot(kind='bar')
plt.title('Feature Importances')
plt.show()
# Method 2: Select top K features by statistical test
selector = SelectKBest(f_regression, k=10)
X_selected = selector.fit_transform(X_train, y_train)
selected_features = [feature_names[i] for i in selector.get_support(indices=True)]
print("Top 10 features:", selected_features)
# Method 3: Drop low-variance features
from sklearn.feature_selection import VarianceThreshold
selector = VarianceThreshold(threshold=0.01) # Drop features with < 1% variance
X_filtered = selector.fit_transform(X)
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
numeric_features = ['size', 'bedrooms', 'house_age', 'price_per_sqft']
categorical_features = ['property_type', 'neighborhood_grade']
numeric_pipeline = Pipeline([
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler()),
])
categorical_pipeline = Pipeline([
('imputer', SimpleImputer(strategy='most_frequent')),
('encoder', OneHotEncoder(drop='first', sparse_output=False)),
])
preprocessor = ColumnTransformer([
('num', numeric_pipeline, numeric_features),
('cat', categorical_pipeline, categorical_features),
])
# Combine with your model
full_pipeline = Pipeline([
('preprocessor', preprocessor),
('model', LinearRegression()),
])
full_pipeline.fit(X_train, y_train)
score = full_pipeline.score(X_test, y_test)
Using Pipeline prevents data leakage automatically and makes deployment much cleaner.
Next lesson: Train, Validation & Test Splits — setting up your evaluation framework correctly from the start.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises