What feature engineering is, and why it still matters
What it is
Constructing new input columns from existing ones — and from domain knowledge that is nowhere in the table — so that the relationship the model has to learn becomes one it can represent easily. It is a change to the hypothesis space, not to the algorithm.
Why it matters
Every model has a representational limit. A linear model cannot multiply two features; a tree needs exponentially many axis-aligned splits to imitate one sum; no model at all can know that two timestamps are 23 hours apart on a 24-hour clock. A feature that encodes such a fact hands the model something no amount of capacity would find.
In the real world: Adding “days since last order” often improves a churn model more than switching to a fancier algorithm.
Visual example
Before / afterThe same three raw columns, plus three derived ones. The model's capacity did not change; the hypothesis space did.
Why each one helped
kg_per_km: a ratio; a tree needs many splits to approximate a division
over_legal_hours: a legal cliff at 9h that no smooth function would find
is_weekend: a fact about the calendar, absent from every raw column
none of the three is derivable from more trees or more depth
Why this matters
The last row is the control. Quadrupling the model's budget on the raw columns buys 0.005; three lines of domain knowledge buy 0.129.
Common misconception
Wrong idea: modern models learn interactions on their own, so feature engineering is legacy practice.
Correct idea: they learn interactions they can represent. Ratios, thresholds, cyclical structure and information from outside the table are not among them.
import pandas as pd, numpy as np
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import HistGradientBoostingRegressor
base = ["distance_km", "weight_kg", "driver_hours"]
cross_val_score(HistGradientBoostingRegressor(), df[base], y, cv=5).mean() # 0.612
# three columns from domain knowledge, not from the algorithm
df["kg_per_km"] = df["weight_kg"] / df["distance_km"].clip(lower=0.1)
df["over_legal_hours"] = (df["driver_hours"] > 9).astype(int)
df["is_weekend"] = df["pickup_ts"].dt.dayofweek.isin([5, 6]).astype(int)
cross_val_score(HistGradientBoostingRegressor(),
df[base + ["kg_per_km", "over_legal_hours", "is_weekend"]],
y, cv=5).mean() # 0.741Generating hundreds of candidate features automatically and keeping whatever improves the validation score. With enough candidates some will improve it by chance, and you have just fitted the validation set — the selection-bias problem from the Cross-Validation module, at feature scale. Keep features you can justify, and confirm the ones you cannot on a set you have not touched.