Free tutorials & notes in Hindi & English · Clean code examples 🧰 Free PDF & Image Tools →
⚙️ Week 8 – Advanced ML  ·  Day 44

Day 44: Feature Engineering & Scaling

⏱ 2.5 hours📶 Intermediate🎯 100% self-study
Day 44 / 60

✅ Today you will learn

  • Why raw columns are rarely model-ready
  • Scaling: Standard, MinMax, Robust – when each
  • Encoding: one-hot, ordinal, frequency
  • New features: ratios, dates, text, interactions
  • Data leakage – the silent killer

🏢 In the real world

Two teams get the same loan data and the same algorithm. Team A feeds raw columns and gets AUC 0.65. Team B adds a loan-to-income ratio, scales features and encodes employment – AUC 0.91. Feature preparation, not the algorithm, made the difference. That is why “feature engineering” appears in nearly every data-science job description.

Same model, better features

Python
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.metrics import roc_auc_score
l = pd.read_csv("loan_applications.csv")
y = l["approved"]
raw = l[["monthly_income", "credit_score", "loan_amount", "years_employed"]]
eng = raw.assign(lti=l["loan_amount"] / (l["monthly_income"] * 12), salaried=(l["employment"] == "Salaried").astype(int))
for name, X, model in [("raw columns", raw, LogisticRegression(max_iter=5000)),
                       ("+ scaling", raw, make_pipeline(StandardScaler(), LogisticRegression())),
                       ("+ scaling + new features", eng, make_pipeline(StandardScaler(), LogisticRegression()))]:
    a, b, c, d = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)
    print(f"{name:26s} AUC = {roc_auc_score(d, model.fit(a, c).predict_proba(b)[:, 1]):.3f}")
raw columns AUC = 0.654 + scaling AUC = 0.908 + scaling + new features AUC = 0.914

Scaling

Python
import pandas as pd
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
l = pd.read_csv("loan_applications.csv")
x = l[["monthly_income"]]
out = pd.DataFrame({"raw": x["monthly_income"],
                    "standard": StandardScaler().fit_transform(x).ravel(),
                    "minmax": MinMaxScaler().fit_transform(x).ravel(),
                    "robust": RobustScaler().fit_transform(x).ravel()})
print(out.describe().loc[["mean", "std", "min", "50%", "max"]].round(2))
raw standard minmax robust mean 60598.67 0.00 0.18 0.14 std 28728.39 1.00 0.11 0.83 min 15000.00 -1.59 0.00 -1.18 50% 55750.00 -0.17 0.16 0.00 max 266800.00 7.18 1.00 6.10
ScalerFormulaUse when
StandardScaler(x − mean) / stdDefault for linear models, KNN, SVM, neural nets
MinMaxScaler(x − min) / (max − min) → 0–1Bounded inputs, images, neural nets
RobustScaler(x − median) / IQRHeavy outliers (incomes, prices)
None–Tree models (trees, forests, boosting)
⚠️ Fit the scaler on training data only

If you call fit on all data before splitting, the test set’s mean and spread leak into training. Use fit on train, transform on test – or simply put the scaler inside a Pipeline (Day 45).

Encoding categories

Python
import pandas as pd
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder
h = pd.read_csv("house_prices.csv")
ohe = OneHotEncoder(sparse_output=False, handle_unknown="ignore").fit(h[["city"]])
print(ohe.get_feature_names_out(), ohe.transform(pd.DataFrame({"city": ["Pune", "Indore"]})))
ordi = OrdinalEncoder(categories=[["Unfurnished", "Semi-Furnished", "Furnished"]]).fit(h[["furnishing"]])
print(ordi.transform(pd.DataFrame({"furnishing": ["Unfurnished", "Furnished"]})).ravel())
['city_Bengaluru' 'city_Hyderabad' 'city_Jaipur' 'city_Lucknow' 'city_Pune'] [[0. 0. 0. 0. 1.] [0. 0. 0. 0. 0.]] [0. 2.]
🔢
One-hotNo order (city, payment mode). One 0/1 column per value. handle_unknown="ignore" for new cities.
📶
OrdinalNatural order (Unfurnished < Semi < Furnished, education levels). One column of 0,1,2.
📊
Frequency / targetMany categories (1,000 PIN codes): replace by count or by average target – compute on training data only.
Pick the encoding from the meaning of the column
Python
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder, StandardScaler
h = pd.read_csv("house_prices.csv")
num = ["area_sqft", "bedrooms", "bathrooms", "age_years", "metro_km"]
pre = ColumnTransformer([
    ("num", StandardScaler(), num),
    ("city", OneHotEncoder(handle_unknown="ignore", sparse_output=False), ["city", "parking"]),
    ("furn", OrdinalEncoder(categories=[["Unfurnished", "Semi-Furnished", "Furnished"]]), ["furnishing"]),
])
Xt = pre.fit_transform(h)
print(Xt.shape)
print(pd.DataFrame(Xt, columns=pre.get_feature_names_out()).head(3).round(2))
(800, 13) num__area_sqft num__bedrooms num__bathrooms num__age_years num__metro_km city__city_Bengaluru city__city_Hyderabad city__city_Jaipur \ 0 0.14 -0.42 -0.01 0.71 1.12 0.0 0.0 0.0 1 -0.71 -0.42 -0.01 0.31 0.38 1.0 0.0 0.0 2 -1.47 -1.42 -1.01 -0.63 -0.40 0.0 1.0 0.0 city__city_Lucknow city__city_Pune city__parking_No city__parking_Yes furn__furnishing 0 0.0 1.0 1.0 0.0 2.0 1 0.0 0.0 1.0 0.0 1.0 2 0.0 0.0 0.0 1.0 1.0

Creating new features

Python
import numpy as np
import pandas as pd
h = pd.read_csv("house_prices.csv")
f = pd.DataFrame(index=h.index)
f["area_per_bedroom"] = h["area_sqft"] / h["bedrooms"]                  # ratio
f["near_metro"] = (h["metro_km"] <= 1).astype(int)                       # flag from domain knowledge
f["age_band"] = pd.cut(h["age_years"], [-1, 5, 15, 30], labels=[0, 1, 2]).astype(int)   # binning
f["log_area"] = np.log(h["area_sqft"])                                   # skew fix
f["area_x_metro"] = h["area_sqft"] * h["metro_km"]                       # interaction
print(f.head().round(2))
print(f.corrwith(h["price_lakh"]).round(2).to_string())
area_per_bedroom near_metro age_band log_area area_x_metro 0 605.0 0 2 7.10 8470.0 1 425.0 0 1 6.75 3910.0 2 530.0 0 1 6.27 1113.0 3 528.0 0 2 7.88 5016.0 4 800.0 0 2 6.68 4640.0 area_per_bedroom -0.01 near_metro 0.07 age_band -0.15 log_area 0.81 area_x_metro 0.08
Python
import pandas as pd
s = pd.read_csv("sales_data.csv", parse_dates=["order_date"])
feat = pd.DataFrame({"month": s["order_date"].dt.month,
                     "weekday": s["order_date"].dt.dayofweek,
                     "is_weekend": (s["order_date"].dt.dayofweek >= 5).astype(int),
                     "days_to_diwali": (pd.Timestamp("2024-10-31") - s["order_date"]).dt.days.clip(lower=0),
                     "festive": s["order_date"].between("2024-10-01", "2024-11-15").astype(int)})
print(feat.head(3))
r = pd.read_csv("product_reviews.csv")
txt = pd.DataFrame({"n_words": r["review"].str.split().str.len(),
                    "n_exclaim": r["review"].str.count("!"),
                    "has_refund": r["review"].str.contains("refund|return", case=False).astype(int)})
print(txt.assign(stars=r["stars"]).groupby("has_refund")["stars"].mean().round(2))
month weekday is_weekend days_to_diwali festive 0 1 0 0 304 0 1 1 0 0 304 0 2 1 0 0 304 0 has_refund 0 3.34 1 1.48 Name: stars, dtype: float64

Missing values inside the model

Python
import pandas as pd
from sklearn.impute import SimpleImputer
s = pd.read_csv("sales_data.csv")
imp = SimpleImputer(strategy="median", add_indicator=True)
out = imp.fit_transform(s[["customer_age", "rating"]])
print(pd.DataFrame(out, columns=imp.get_feature_names_out()).iloc[s["rating"].isna().to_numpy().nonzero()[0][:3]])
customer_age rating missingindicator_customer_age missingindicator_rating 15 32.0 4.0 0.0 1.0 64 18.0 4.0 0.0 1.0 103 25.0 4.0 0.0 1.0

Data leakage

Python
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_auc_score
l = pd.read_csv("loan_applications.csv")
rng = np.random.default_rng(0)
# a column only known AFTER the decision: the disbursed amount (0 if rejected)
l["disbursed_amount"] = np.where(l["approved"] == 1, l["loan_amount"] * rng.uniform(0.9, 1.0, len(l)), 0)
y = l["approved"]
for cols in [["credit_score", "monthly_income", "loan_amount"], ["credit_score", "monthly_income", "loan_amount", "disbursed_amount"]]:
    a, b, c, d = train_test_split(l[cols], y, test_size=0.25, random_state=0, stratify=y)
    auc = roc_auc_score(d, RandomForestClassifier(random_state=0).fit(a, c).predict_proba(b)[:, 1])
    print(f"{'+ disbursed_amount' if len(cols) == 4 else 'honest features':20s} AUC = {auc:.3f}")
honest features AUC = 0.882 + disbursed_amount AUC = 1.000
⚠️ “Too good to be true” = leakage

AUC 1.000 is not a great model – it is cheating. Ask for every feature: “Will I know this value at the moment I make the prediction?” If not, remove it. Other leaks: scaling before splitting, duplicates across train and test, using future dates in time series.

🏅 Job skills unlocked today

✔ Choose the right scaler✔ Encode categories with ColumnTransformer✔ Create ratio, date, text and interaction features✔ Detect and prevent data leakage

Practice tasks

🏋️ Practice – do it yourself

Task 1: Add area_per_bedroom, near_metro and log_area to the house model from Day 39. Does MAE improve? Then add city × area and metro × area interactions instead.
Show solution
Python
import numpy as np, pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error
h = pd.read_csv("house_prices.csv")
X = pd.get_dummies(h.drop(columns="price_lakh"), drop_first=True, dtype=int)
X2 = X.assign(area_per_bed=h["area_sqft"] / h["bedrooms"], near_metro=(h["metro_km"] <= 1).astype(int), log_area=np.log(h["area_sqft"]))
X3 = X.copy()
for c in [k for k in X if k.startswith("city_")]:
    X3[c + "_x_area"] = X[c] * h["area_sqft"]            # each city gets its own price per sqft
X3["metro_x_area"] = h["metro_km"] * h["area_sqft"]; X3["age_x_area"] = h["age_years"] * h["area_sqft"]
for name, XX in [("original", X), ("+ ratio/flag/log", X2), ("+ interactions", X3)]:
    a, b, c, d = train_test_split(XX, h["price_lakh"], test_size=0.25, random_state=42)
    print(f"{name:17s} MAE ₹{mean_absolute_error(d, LinearRegression().fit(a, c).predict(b)):.2f} lakh")
print("Prices are multiplicative: a sqft costs more in Bengaluru than in Lucknow – only interactions capture that.")
original MAE ₹4.63 lakh + ratio/flag/log MAE ₹4.63 lakh + interactions MAE ₹3.69 lakh Prices are multiplicative: a sqft costs more in Bengaluru than in Lucknow – only interactions capture that.
Task 2: Build a ColumnTransformer for the student data: scale numbers, one-hot gender/school_type/tuition/internet, ordinal-encode parent_education.
Show solution
Python
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder, OrdinalEncoder
st = pd.read_csv("students_performance.csv")
pre = ColumnTransformer([("n", StandardScaler(), ["study_hours", "attendance"]),
                         ("o", OneHotEncoder(drop="first", sparse_output=False), ["gender", "school_type", "tuition", "internet"]),
                         ("e", OrdinalEncoder(categories=[["School", "Graduate", "Post-Graduate"]]), ["parent_education"])])
print(pre.fit_transform(st).shape, list(pre.get_feature_names_out()))
(500, 7) ['n__study_hours', 'n__attendance', 'o__gender_Male', 'o__school_type_Private', 'o__tuition_Yes', 'o__internet_Yes', 'e__parent_education']
Task 3: List two features in the telecom data that could leak churn if you are not careful.

Quick quiz

🧠 Quick Quiz – test yourself

1. Which models need feature scaling?
Distance and penalty-based models.
2. City (no natural order) should be…
Ordinal would invent a fake order.
3. RobustScaler uses…
Outliers barely affect it.
4. A feature known only after the outcome causes…
Fake-high scores that fail in production.
5. handle_unknown="ignore" in OneHotEncoder…
Safe for new cities in production.

Interview corner

💼 Interview Corner

What is feature engineering? Give examples.

Transforming raw data into inputs that expose the signal: ratios (loan/income), aggregates (orders in last 30 days), date parts (weekday, festive), text counts, binning, log transforms, interactions and proper encoding of categories.

How do you avoid data leakage?

Split first, then fit all transformations on training data only (use Pipelines); remove features not available at prediction time; split time series by time; deduplicate across splits; be suspicious of near-perfect scores.

Summary

  • Better features often beat better algorithms.
  • Scale for linear/KNN/neural models; not needed for trees.
  • One-hot for nominal, ordinal for ordered categories – via ColumnTransformer.
  • Create ratios, flags, dates, text and interactions; never leak future information.
➡️ Next

Day 45: pipelines, cross-validation and hyper-parameter tuning – build models the professional way.

Frequently Asked Questions

get_dummies or OneHotEncoder?
get_dummies is quick for exploration. OneHotEncoder remembers the categories, handles unknown values and fits inside pipelines – use it for models.
Can too many features hurt?
Yes – noise features add variance and slow training. Use regularisation (Lasso), permutation importance or domain sense to prune.
← Data Science (60 Days)
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

Next topicDay 45: Pipelines, Cross-Validation & Tuning Your progress: 44 / 60 (73%) · Lesson 45 / 60 Continue →

How was this lesson?

Your feedback helps us make the content better.

Write a review or suggestion (optional)

After approval, your name and city may appear publicly with your review. Please do not write your email or phone number.

▶ CodingEasily on YouTube

Learn with video lessons in easy Hindi

Featured video: RDBMS Part 3 – Class XI Commerce IT

▶ Subscribe – it’s free