Seven fields, one of which is the whole course 9 min live
A feature spec is not documentation you write afterwards to satisfy someone. It is the shortest form of every decision this course taught you, and each field maps to a session you have already run. Write it for every feature, not just the interesting ones - the boring columns are where the surprises hide.
LiveThe seven fields, in order6 min▶
1 · Name and a one-sentence definition. What it is, in language a person outside the team can read. If the sentence needs a second sentence, the feature is doing two things and should be two features (Rule 19: "Use very specific features when you can").
2 · Source table and column. Where the raw value comes from, exactly. Not "the warehouse" - the table and the column, so a schema change has an owner and a blast radius.
3 · WRITE-TIME relative to the outcome. When is this value written, by which system, relative to the thing you are predicting? This is the leakage field, and it is the single most important line in the document. Everything in b5 exists to make this question askable, and every failure in b5 was a spec that never asked it.
4 · Transformation applied, and where it is fitted. Not just "scaled" - scaled by what, fitted on which rows, inside which pipeline stage. b5 Demo 2 is the proof that a transformation is not safe in the abstract, only in the way it is fitted.
5 · Owner. A name. Not a team, not a squad - a person who answers when the feature breaks at 2am and nobody remembers why it exists.
6 · Refresh cadence. How often the source updates, and therefore how stale the feature can be at serving time. A daily feature serving a real-time model is a defect that produces no error.
7 · Deprecation status. Rule 22: "Clean up features you are no longer using." A feature nobody uses is still a column somebody maintains, still a dependency on a source table, and still a line of risk. Mark it, then remove it.
The whole course, in one Pipeline 10 min live
Here is Lumen, assembled. Every stage below traces to a session you ran, and the ordering is not stylistic: the split comes first because sklearn says "Always split the data into train and test subsets first, particularly before any preprocessing steps," and everything else lives inside the container because "the scikit-learn pipeline is a great way to prevent data leakage as it ensures that the appropriate method is performed on the correct data subset."
LiveThe capstone pipeline7 min▶
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler, OneHotEncoder, TargetEncoder
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split, cross_val_score
X_train, X_test, y_train, y_test = train_test_split( # b5 rule 1: FIRST
X, y, test_size=0.4, stratify=y, random_state=42)
numeric = ["prior_30d_spend", "pages_viewed", "tenure_days"] # b4 derived
categorical = ["channel", "product_category", "device", "new_vs_returning"]
high_card = ["postal_code"] # ~1,200 levels
prep = ColumnTransformer([
("num", Pipeline([
("impute", SimpleImputer(strategy="median", # b7 imputation
add_indicator=True)), # b7 missingness
("scale", StandardScaler()), # b2 scaling
]), numeric),
("cat", OneHotEncoder(handle_unknown="ignore", # b3 encoding
min_frequency=0.01), categorical),
("hc", TargetEncoder(), high_card), # b3 cross-fitted
])
pipe = Pipeline([ # b5 the container
("prep", prep),
("select", SelectKBest(f_classif, k=25)), # b8 selection
("model", LogisticRegression(max_iter=1000)),
])
pipe.fit(X_train, y_train) # b5 rule 2
print(cross_val_score(pipe, X_train, y_train, cv=5).mean())
Walk the mapping out loud, because this is the moment the ten sessions become one object:
- b2 · scaling.
StandardScalersits after imputation, inside the numeric sub-pipeline, so its mean and variance are learned from training rows only. - b3 · encoding.
OneHotEncoder(handle_unknown="ignore")for the low-cardinality columns, withmin_frequencygrouping rare levels;TargetEncoderforpostal_code, whose internal cross-fitting is the only reason a 1,200-level column is safe here at all. - b4 · datetime.
tenure_daysand the weekday flags were derived fromsignup_dateupstream and enter as ordinary numeric and categorical columns. Decomposition happens before the pipeline; the pipeline only transforms. - b5 · the pipeline itself. Not a convenience. The structure is what makes the wrong thing hard to type, which is worth more than remembering to do the right thing.
- b7 · imputation.
add_indicator=Truekeeps the missingness as its own feature, because "this was blank" is frequently the signal. - b8 · selection.
SelectKBestsits inside, so it re-fits within every cross-validation fold. Outside, it sees every row before the split - which is precisely the sklearn experiment that scored 0.76 accuracy on pure noise where the honest answer was 0.50.
Self-studyWhat is deliberately not in this pipeline3 min read▶
- The b9 embedding block. Text encoding runs upstream and caches; the vectors join as columns. Add it only when it beats the hand-crafted flags, per Rule 17.
- Interactions (b6). Lumen's spend-times-returning term is derived before the pipeline for readability. Anything you can name explicitly, name explicitly.
- The leakage lever.
post_purchase_flagis not innumeric, not incategorical, not anywhere. That absence is field 3 doing its job in code.
Verify, then close the lever for good ★ 12 min · everyone runs it
Two last runs. The first reproduces the ladder you have climbed since b1 and then presses the leakage lever one final time, deliberately, so you remember the shape of it. The second is field 4 of the spec, demonstrated: the same transformation, safe or catastrophic depending only on how it was fitted.
Turn on the five honest levers one at a time and watch the held-out ladder rebuild itself: 0.500 → 0.696 → 0.785 → 0.806 → 0.812 → 0.813. Every rung is a session, and the algorithm never changed.
Now press the dashed target leakage lever one last time: train 0.967, held-out 0.789 - still below the honest 0.813. Then switch it off and leave it off forever.
The point of pressing it one final time: the spec's field 3 is what prevents that column ever entering the pipeline in the first place. Not vigilance, not code review, not a good week. A written line stating when the value is recorded.
Second check. Field 4 asks not only what transformation but where it is fitted, and this is why that half of the field exists.
Naive encoding of postal_code, a 1,200-level column drawn independently of the target: train 0.931, held-out 0.493. A column that knows nothing, scoring 0.931.
Cross-fitted, which is what TargetEncoder.fit_transform does internally: train 0.509, held-out 0.493. Honest and boring.
Read the lesson as a spec rule: a transformation is not safe in the abstract, only in the way it is fitted. "TargetEncoder" in field 4 is an incomplete answer; "TargetEncoder, fit_transform inside the pipeline, cross-fitted over 5 folds" is a complete one.
Defend the spec 8 min live
Pair up. One of you reads a feature spec aloud; the other asks the hostile question for each field and does not accept "I would have to check." Every question below has cost somebody a quarter.
| Field | The hostile question | What a weak answer means |
|---|---|---|
| 3 · Write-time | "When exactly is this written, and by which system?" | You cannot rule out target leakage. Stop here; nothing downstream matters yet. |
| 4 · Transformation | "What did this transform fit on?" | Preprocessing leakage is live and invisible. Move the transform inside the pipeline before answering anything else. |
| 5 · Owner | "Who do I call when this feature breaks?" | The feature is unowned, which means it is unmaintained, which means it will drift silently. |
| 2 · Source | "What happens to this feature when the source table changes?" | No blast radius, no alerting. A schema migration will take the model down and nobody will connect the two. |
| 7 · Deprecation | "Which features here are we no longer using?" | Feature debt, accumulating. Rule 22 exists because this list is never empty and never volunteers itself. |
| The split | "Does the split respect time and entities?" | Families 3 and 4 from b5 are open. Repeat customers on both sides is the commercial default, Lumen included. |
The ship gate, in one sentence. A spec whose every line survives these questions is a feature set you can hand over - to a colleague, to a platform team, to whoever inherits this in eighteen months. One that does not is a notebook with ambitions. The most common failure is not a wrong answer; it is a confident answer to field 3 that turns out, when someone finally checks the source system, to have been an assumption nobody ever tested.
Where this goes next ◐ read after class
Self-studyThe three courses on either side of this one3 min read▶
- learn-model-evaluation-with-phoebe judges what these features produced: metrics, thresholds, and the cost of being wrong in each direction. This course owns the inputs; that one owns the verdict.
- learn-intro-ml-with-phoebe is the modelling mechanics underneath - the estimator, the split, the fit. Worth a revisit now that you know what you were feeding it.
- The leader track (a1-a6) is for whoever funds and reviews this work. a5 covers hand-crafted versus learned features for decision-makers, and a6 is the feature review checklist that the seven-field spec is designed to survive.
Self-studyThe honest frontier2 min read▶
Named, not taught, and you should know they exist:
- Automated feature engineering - featuretools, autoML feature synthesis. They generate candidate features at volume. They do not answer field 3, which means everything in this course still has to happen afterwards, on a much larger pile.
- Time-series features - lags, rolling windows, seasonality. That is learn-timeseries-forecasting-with-phoebe.
- Representation learning - training the encoder as part of your model rather than calling a frozen one. That is learn-deep-learning-with-phoebe; b9 was the bridge, not the destination.
- Feature stores - the concept and when it pays are in the leader track (a4); implementation and ops belong to the data-engineering bucket.
Try it yourself - this week ◐ 45-60 min total
- Write the seven-field spec for every feature in one model you currently run. Not the interesting ones - all of them. The count of fields you cannot fill is the honest state of that model.
- Run the red-team round with a colleague on that spec. Swap roles. Record only the questions that produced "I would have to check", because that list is the actual deliverable.
- Move every transform in that model inside a
Pipelineand compare the cross-validated score before and after. A drop is a leak you just found; a hold is a leak you just made impossible. - Open field 7 for the first time: list every feature nobody has used in six months, and delete one this week. Rule 22 is easy to agree with and hard to schedule.
Official sources covered
This capstone synthesizes the whole track. Certificates and graded exercises stay with the official providers (Kaggle Learn, Google MLCC). This page covers:
Three questions before you go 🎯 ◐ 90 seconds
1 · Of the seven spec fields, which one carries the most weight, and why?
The other six make a feature maintainable; field 3 makes it real. A feature written at or after the outcome is target leakage no matter how well it is owned, scaled, or documented - and no amount of cross-validation detects it, because the column is contaminated in every fold.
2 · Putting SelectKBest inside the Pipeline rather than before it prevents what, structurally?
sklearn's own words: pipelines "help avoid leaking statistics from your test data into the trained model in cross-validation, by ensuring that the same samples are used to train the transformers and predictors." Their worked example is the price tag: selection fitted before the split scored 0.76 accuracy on pure noise, where the honest score was 0.50 and cross-validation gave 0.43.
3 · What is the ship gate for a feature set leaving your hands?
Scores and clean runs are exactly what leakage produces, which is why neither can be the gate. The gate is that someone else can own this: every feature has a stated write-time, a stated fitting location, a named owner, and a deprecation status. A spec that survives is hand-overable; one that does not is a notebook with ambitions.