Feature Engineering
A model never sees your data. It only ever sees the table of numbers you hand it, and feature engineering is every decision that turns one into the other.
Why Does This Exist?
Two teams take the same forecasting problem. Same gradient-boosted trees, same rows, same tuning budget. One lands at 12% error and the other at 4%. Nothing about the model differed. One team handed it a raw timestamp column; the other handed it hours_since_previous_order, orders_in_prior_7_days, and is_public_holiday.
That gap is feature engineering, and on tabular problems it is routinely wider than the gap between two algorithms.
The reason it exists at all comes down to what a model actually consumes. A model is a function fitted to a fixed set of columns. It does not read your database, infer your business, or notice that two of your columns should be divided by each other. It sees a grid: one row per thing you want to predict about, one column per number you decided to show it. That grid is the design matrix, usually written , and it is the model's entire universe. A pattern that isn't expressed in cannot be learned from .
So "which model should I use?" is downstream of a bigger question: what does the model get to look at?
The vocabulary, in one paragraph
A feature is one input column — distance_km, is_weekend, word_count. A row is one thing you're predicting about. The target is what you're predicting. Feature engineering is everything that turns your records into that grid: making columns that didn't exist, turning non-numbers into numbers, putting numbers on a comparable footing, and deleting what hurts.
Think of It Like This
Pricing a house over the phone
You're on the phone to a valuer who has priced thousands of houses. They're very good. They also cannot see the house.
Read out the 40-page survey and you'll lose them — page after page of boiler serial numbers and boundary wording, with the two facts that matter buried somewhere inside. Instead you say: three bedrooms, eight minutes' walk from the station, south-facing garden, roof needs replacing. Four facts, and the number comes back accurate.
Now the important half. If you never mention the station, the valuer cannot price the station. No amount of expertise recovers a fact they were never told. They'll give you a confident number, and it will be wrong for a reason that is invisible from their side of the call.
The valuer is your model. The four facts are your features. Choosing them is the job — and the survey sitting in your other hand is not evidence that you did it.
How It Actually Works
Four operations, in this order, because each one assumes the last.
- Construct — create columns that don't exist yet. Ratios, differences, date parts, rolling aggregates, lags. This is where domain knowledge enters, and it has the most headroom.
- Encode — turn non-numbers into numbers. Categories, text, timestamps, identifiers. Every model consumes floats, so something has to do the conversion, and how decides what the model can infer.
- Scale — put numeric columns on a comparable footing, and reshape distributions that break a model's assumptions.
- Select — remove columns that add noise, leak, cost money, or duplicate each other. Fewer features often means a better model, which surprises people.
Every one of those four steps learns from data
This matters more than any individual technique, and it's why this page points at leakage before it points at anything else.
A scaler learns a mean and a standard deviation. An imputer learns a median. A one-hot encoder learns the list of categories. A target encoder learns a per-category average of the thing you're trying to predict. None of these are pure functions — each is fitted, and what it gets fitted to is a choice you make.
Fit them on all your data and you've moved information out of your test set and into your training set. Your validation score improves. Production doesn't. That's data leakage, and preprocessing causes it far more often than anything exotic does.
The rule: fit on training rows, apply everywhere. Every fitted transform, every time, including inside every cross-validation fold.
What the model can and can't work out for itself
Model families need different amounts of help, and knowing which is which saves a lot of pointless work.
- Linear models can't discover interactions or curvature at all. If the effect is
price / square_metres, you have to build that column. They also need scaled inputs, because a coefficient's size is entangled with its column's units. - Trees and gradient boosting get monotone transforms for free — a split on
x > 3and a split onlog(x) > 1.1partition the rows identically, so scaling changes nothing. What they can't do cheaply is arithmetic across columns. A ratio costs you one line and costs them a staircase of dozens of splits, approximated rather than exact. - Neural networks learn features, given enough data and scaled inputs. That's why feature engineering feels dead in vision and text, where the encoder does it, and very much alive on tabular data, where you rarely have the volume to earn it.
Feature engineering didn't die with deep learning. On raw modalities it moved into curation and representation, which is what most of the rest of this band is about.
Worked example
Predicting a health outcome from weight_kg and height_m. Take two people: 95kg at 1.62m, and 61kg at 1.80m.
On the raw columns they're awkward to separate, because the heavier person is also the shorter one — any rule using weight alone gets the light-and-tall case wrong. Body mass index is :
One column, one line of code, and a single threshold now separates them cleanly. A gradient-boosted tree given only weight and height would need many axis-aligned splits to trace that curve, and it would still only approximate it. You handed it the exact answer instead.
That's the shape of the whole discipline: you know something about the world that the loss function can't discover from the columns in front of it.
Show Me the Code
Two things worth having in muscle memory — constructing a column, and fitting a transform on training rows only.
import numpy as np
weight_kg = np.array([95.0, 61.0, 88.0, 54.0])height_m = np.array([1.62, 1.80, 1.91, 1.55])
def build_bmi(weight: np.ndarray, height: np.ndarray) -> np.ndarray: """One constructed column. A tree needs a staircase of splits to fake it.""" return weight / height**2
def fit_scaler(train: np.ndarray) -> tuple[float, float]: """Statistics come from the TRAINING rows only. That is the whole contract.""" return float(train.mean()), float(train.std())
bmi = build_bmi(weight_kg, height_m)print(np.round(bmi, 1)) # -> [36.2 18.8 24.1 22.5]
mean, std = fit_scaler(bmi[:2]) # fitted on train, applied to everythingprint(np.round((bmi - mean) / std, 2)) # -> [ 1. -1. -0.39 -0.58]fit_scaler receives bmi[:2] and nothing else. That deliberate narrowing is the difference between a validation number you can trust and one you can't, and it's why scikit-learn's Pipeline exists — it makes the narrowing structural instead of something you have to remember.
Watch Out For
Fitting the transform before splitting
Written as scaler.fit_transform(X) followed by train_test_split(X_scaled, y). It reads more tidily than the correct order, which is why it's everywhere.
That mean and standard deviation now carry information from every row, including the ones you're about to hold out. So the test set has been standardised using knowledge of itself. Same story for a SimpleImputer fitted on the full frame, for a OneHotEncoder that saw a category present only in test, and much worse for anything derived from the target.
The damage is mild for a scaler on a large dataset and severe for a target encoder on a small one. That's the trap: the mild case teaches you the habit is harmless, then you carry it somewhere it isn't. There's no error message either way — just a validation score you believed.
Fix it structurally rather than by being careful. Put every fitted step inside a Pipeline, pass the pipeline to the cross-validator, and each fold refits from scratch. Correct behaviour becomes the default.
Building a feature that won't exist at prediction time
This one produces the most exciting wrong results you will ever see.
You're predicting hospital readmission and the table has discharge_notes_length. You're predicting whether an order arrives late and there's total_delivery_duration. You're predicting churn and someone joined in days_since_account_closed. Every one of those is a real column in a real warehouse, and every one is a fact from after the moment you'd actually have to predict.
The symptom is an AUC around 0.99 and a quiet sense that this went unusually well. In production the column arrives null, or worse, it arrives populated with something stale and the model keeps answering confidently.
The check takes a minute per column and nobody who has been burned skips it: fix a prediction timestamp, then ask of every column whether its value was knowable at that instant. Not "is it in the table" — knowable, then. Anything that fails gets dropped, or recomputed as an as-of value.
The Quick Version
- The model only ever sees the design matrix you built. A pattern not expressed in the columns cannot be learned from them.
- Four operations, in order: construct, encode, scale, select.
- On tabular problems, the gap between good and bad features is usually wider than the gap between two algorithms.
- All four steps are fitted — each learns a statistic — so all four can leak. Fit on training rows, apply everywhere.
- Trees ignore monotone scaling but can't do arithmetic across columns. Linear models can't find interactions. Neural nets learn features, given volume.
- One good column replaces dozens of splits: instead of a staircase.
- Fewer features frequently beats more.
- For every column, ask whether its value was knowable at prediction time. If not, it's a leak wearing a useful name.
What to Read Next
- Exploratory Data Analysis comes before any of this — you can't engineer features for data you haven't looked at.
- Feature Construction is step one in depth: interactions, ratios, date parts, lags, aggregations.
- Categorical Encoding is step two, where cardinality picks the method.
- Feature Scaling is step three, including which models genuinely don't care.
- Feature Selection is step four, and why importance is not causation.
- Data Leakage is the failure every step above can cause. Read it early rather than late.
- Definitions worth a look: Feature, Feature Matrix, Data Leakage, and Ground Truth.