learn-feature-engineering-with-phoebe / Practitioner session 6 of 10
Learn Feature Engineering with Phoebe · Practitioner track · Session 6 of 10

Interactions and aggregations

Sessions 2 to 4 improved columns one at a time. Tonight you build the features that live between columns: the relationships no single column carries, the crosses that turn two categoricals into one, and the group-level history that describes an entity rather than a row. This is the most creative session in the track and the one with the sharpest edges - both of tonight's techniques can blow up your feature width or quietly leak the future, and you will learn where each line is.

🟠 Practitioner track DS · analysts · ML engineers Python · live workbench on key pages 45 min
0-3 · Welcome 3-18 · Relationships no column holds 18-42 · Build-along: crosses + entity history 42-45 · Q&A
Part 1 · covers Kaggle Learn "Creating Features", Rules of ML #20

When one feature depends on another 7 min live

An interaction exists when the effect of one feature depends on the value of another. Lumen has a real one, and you have been carrying it since b1: spend matters more for returning customers than for new ones. A returning customer with high prior spend is not simply "returning plus high spend" - the combination behaves differently from what either column predicts on its own. A linear model, given only the two main effects, has no way to say that.

prior_30d_spend x new_vs_returning LOW prior spend HIGH prior spend NEW RETURNING baseline behaviour nothing to explain here a little above baseline the spend main effect a little above baseline the returning main effect THE INTERACTION CELL further from baseline than the two main effects add up to If this cell were just the sum of the other two, no interaction term would be needed.
🔍 Click to zoom - the cell neither main effect can predict
LiveThe four shapes an interaction takes5 min▶

Rule #20 from Google's Rules of Machine Learning is the licence for all of this: "Combine and modify existing features to create new features in human-understandable ways." The last four words are the discipline - if you cannot say out loud what the new column means, it will not survive review or debugging.

  • Products. spend * returning is the Lumen one, and it is worth +0.006 on the workbench ladder. Multiplication is the plain arithmetic form of "these two matter together."
  • Ratios. Spend per page viewed, items per order, revenue per session. Ratios are often the most human-readable features on the whole sheet, because they are the numbers the business already talks in.
  • Differences. Price minus category median, order value minus this customer's own average. A difference reframes an absolute number as "relative to what is normal here."
  • Polynomial expansion. When you want every pair at once rather than a chosen few.
from sklearn.preprocessing import PolynomialFeatures

# every pairwise product, no squares, no constant column
PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)
Chosen beats exhaustive, most of the time interaction_only=True on twenty columns gives you 190 new ones, and Rule #21 is waiting: "The number of feature weights you can learn in a linear model is roughly proportional to the amount of data you have." Expansion is a fine way to explore. Shipping the three pairs you can explain is usually the better model.
Part 2 · covers Google MLCC "Working with categorical data" - feature crosses

Crosses, and the number that should stop you 8 min live

A feature cross is the categorical version of an interaction. Google's definition: feature crosses are "created by crossing (taking the Cartesian product of) two or more categorical or bucketed features". Instead of asking the model to combine two one-hot blocks itself, you hand it one block whose levels are the combinations.

LiveGoogle's leaf example, exactly4 min▶

Two categorical features describing a leaf. Edges: {smooth, toothed, lobed}. Arrangement: {opposite, alternate}. Crossing them gives 3 x 2 = 6 combinations, and a leaf that is lobed with alternate arrangement is represented as {0, 0, 0, 0, 0, 1}.

Notice what the model gains. With separate one-hot blocks it can learn "lobed matters" and "alternate matters" and add them. With the cross it can learn that lobed-and-alternate specifically behaves unlike either piece - the same thing the 2x2 grid above showed for numerics.

Columns produced by the cross (log scale) leaf edges x arrangement (3 x 2) 6 channel x device (9 x 3) 27 channel x postal_code (9 x 1,200) 10,800 Google's warning case (100 x 200) 20,000 Cross bucketed or low-cardinality features - and check the width before you fit.
🔍 Click to zoom - two of these are fine, two are a sparsity problem
Real world

Google's warning, verbatim. "Crossing two sparse features produces an even sparser new feature than the two original features." Their worked case: a 100-element feature crossed with a 200-element feature produces a 20,000-element sparse feature. On Lumen the same arithmetic decides the design for you - channel x device is 9 x 3 = 27 columns, perfectly reasonable, while channel x postal_code is 9 x 1,200 = roughly 10,800 columns, almost all of them empty almost all of the time. Rule #21 again: you cannot learn 10,800 weights from 40,000 rows with a 3.2% base rate.

Self-studyThe discipline, in three checks3 min read▶
  • Bucket first, then cross. Continuous features get discretized before crossing, which is why b2's binning work comes before tonight. A crossed pair of 4-bucket features is 16 columns, not thousands.
  • Multiply the cardinalities before you write the code. Two numbers and ten seconds. If the product is in the thousands, you are building a sparsity problem, not a feature.
  • For genuinely high-cardinality categoricals, cross is the wrong tool. b3 gave you the right one: target encoding with cross-fitting, which turns postal_code into a single column instead of 1,200. And per Google, "For natural-language data, the main method of reducing dimensionality is to convert feature vectors to embedding vectors" - the b9 bridge.
Part 3 · the pattern most teams never build

Group aggregates and entity history 8 min live

Every feature so far describes a row. Aggregations describe the group the row belongs to: this customer's mean order value, this channel's conversion rate, this category's return rate. They give a single session the context of everything else in its group, and they are usually cheap, because the data is already in your warehouse.

The version most teams never build is the one worth the most: features describing an entity's history up to the prediction moment. Orders in the prior 30 and 90 days. Days since last order. Lifetime value to date. Sessions in the last week. These are not group statistics, they are a small biography of the customer as of the instant you have to decide something about them.

LiveThe groupby and merge, with the cutoff in it5 min▶

Read the filter on the first line before anything else. It is the whole session.

hist = (
    orders[orders["order_ts"] < orders["cutoff_ts"]]   # the rail
    .groupby("customer_id")
    .agg(
        orders_prior_90d=("order_id", "count"),
        mean_order_value=("order_value", "mean"),
        last_order_ts=("order_ts", "max"),
    )
    .reset_index()
)

sessions = sessions.merge(hist, on="customer_id", how="left")
sessions["days_since_last_order"] = (
    sessions["prediction_ts"] - sessions["last_order_ts"]
).dt.days

Without that filter this is a four-line snippet that any analyst would wave through in review. With it, it is a correct feature. The difference is invisible in the output and worth everything in production.

The leakage rail - say this one out loud Aggregates must be computed only from data that existed before each row's timestamp. A naive groupby over the whole table is a leak by construction: a customer's mean order value computed across all time includes orders they placed after the session you are scoring, so the feature knows the future. This is exactly the case that point-in-time joins exist to handle (leader session a4), and it is the same rule b4 applied to tenure and recency.
Self-studyWhy this one survives code review3 min read▶

b5 named the four leakage families and gave you the three rules. Aggregation leakage is the family that slips past all of your existing habits, and it is worth understanding why.

  • It passes the train/test split test. You can split correctly, fit only on train, wrap everything in a Pipeline, and still leak - because the leak happened upstream, in the table you built before sklearn ever saw the data.
  • It looks like good engineering. A clean groupby joined onto a session table is the shape of code that gets praised in review.
  • It shows up as a better score, which is sklearn's whole warning: leakage "results in overly optimistic performance estimates... and thus poorer performance when the model is used on actually novel data."
  • The fix is structural, not clever. Every aggregate carries a cutoff timestamp, or it does not ship. Feast's own value list names the same thing: "Avoid data leakage by generating point-in-time correct feature sets."
Build-along

Turn on the interactions lever ★ 10 min · everyone runs it

Numerics, encoding and datetime are on, and the ladder is at 0.806. Add the interaction and watch a smaller, more honest kind of win.

Confirm the rung you are standing on. Press Score on held-out with the first three levers on: 0.806. Everything up to here has been about improving individual columns.

Switch on Interactions and score again: 0.812. The lever adds spend-times-returning, the relationship from the 2x2 grid at the top of this page.

Read the honest size of it: +0.006. Encoding bought 0.089 and datetime bought 0.021. One interaction is a fraction of either, and that is the normal shape of this technique.

Understand why it is still worth your evening. Encoding wins are one-time - once a column is readable, it is readable. Interaction wins compound across pairs, and the search space is enormous, which is why judgement about which pairs to build matters more here than anywhere else in the track.

How to read the workbench Training AUC is always visible; held-out stays masked until you ask for it. A real logistic regression, trained by gradient descent in your browser on 1,800 Lumen sessions and scored on 1,200 it never saw. Only the data is synthetic - the split, the fit, and every AUC are computed live.
Homework

Try it yourself - this week ◐ 20-30 min total

Source material

Official sources covered

This session teaches the working core of Google's feature-cross material and the interaction-building content from Kaggle Learn, plus the aggregation pattern that sits underneath every feature store. Certificates and graded exercises stay with the official providers.

✓Google MLCC "Working with categorical data" - feature crosses and the dimensionality warningPart 2 · the leaf example and the 20,000-element case
✓Google "Rules of Machine Learning" - rule 20Combine and modify in human-understandable ways · rule 21 as the width check
◐Kaggle Learn "Feature Engineering" - Creating FeaturesProducts, ratios, differences · mutual information lands in b8
◐sklearn "Preprocessing data" - PolynomialFeatures with interaction_onlyThe exhaustive option · splines and binning were b2 and b4
✓fe-live canon - the interactions rung of the Lumen ladder0.806 to 0.812 · spend x returning, +0.006
Check yourself

Three questions before you go 🎯 ◐ 90 seconds

1 · Google documents a cross of a 100-element feature with a 200-element feature. How wide is the result?

A cross is the Cartesian product, so widths multiply. Google states the consequence directly: "Crossing two sparse features produces an even sparser new feature than the two original features." On Lumen, channel x device is 9 x 3 = 27 columns and fine; channel x postal_code is 9 x 1,200 = roughly 10,800 and is not.

2 · What makes something an interaction rather than two ordinary features?

Dependence is the definition. On Lumen, spend matters more for returning customers than for new ones, so the returning-and-high-spend cell sits further from baseline than the two main effects add up to. A linear model given only main effects cannot express that. Rule #20 licenses building it: "Combine and modify existing features to create new features in human-understandable ways."

3 · What is the rail on group aggregates and entity-history features?

A naive groupby over the whole table is a leak by construction - a customer's mean order value across all time includes orders placed after the session being scored. Splitting correctly does not save you, because the leak happened upstream while building the table. Point-in-time joins exist for exactly this (leader session a4); the full leakage family is b5.

Practitioner session 6 cheat sheet · pin this

Interaction, definedThe effect of one feature depends on another. Lumen's: spend matters more for returning customers than new ones. Worth +0.006 on the ladder.
Four shapesProducts (spend x returning) · ratios (spend per page) · differences (value minus category median) · PolynomialFeatures(interaction_only=True).
Rule #20"Combine and modify existing features to create new features in human-understandable ways." If you cannot say what it means, do not ship it.
Feature cross"Created by crossing (taking the Cartesian product of) two or more categorical or bucketed features." Leaf: 3 edges x 2 arrangements = 6 combos.
The width warning100 x 200 = a 20,000-element sparse feature. Lumen: channel x device = 27 (fine), channel x postal_code = ~10,800 (not fine).
Cross disciplineBucket first, cross low-cardinality only, multiply the cardinalities before you write the code. High cardinality goes to target encoding (b3).
Entity historyOrders in prior 30/90 days, days since last order, lifetime value to date. The pattern most teams never build and the one that pays.
The aggregation railCompute aggregates only from data before each row's timestamp. A naive groupby over the whole table leaks by construction. Point-in-time joins (a4), b5.