This page shows the chapter notebook with its saved results. Constructed examples do not measure competition gains.
Chapter 7: Domain Features as Testable Hypotheses
Try the decision first
How much does an entity-history feature add over the raw amount when the key merges several customers, and how much of its apparent value comes from rows the prediction could not see?
Write your expectation before opening the worked results. Use the chapter activity to compare supported inputs. To change the calculation itself, run the notebook on GitHub.
Your answers stay on this page until you download them. They are not sent to a server.
Worked notebook and saved results
How much does an entity-history feature add over the raw amount when the key merges several customers, and how much of its apparent value comes from rows the prediction could not see?
The chapter's fraud example turns on an entity the data does not name. A pseudo-ID built from descriptors can merge unrelated customers, and a whole-period mean can read future behavior. Measuring both shows when the history is worth building and when its development score stops describing the deployed model.
Constructed data; the spend-level spread, the 3 times fraud multiple and the random merging are properties of this generator, not measurements from a fraud competition.
The experiment
A constructed log of 400 customers with 6 to 14 transactions each (about 4,000 rows, 6% fraud). Customers differ widely in typical spend, and a fraudulent amount is 3 times the customer's own level, so the raw amount is a weak signal and the amount relative to the customer's history is a strong one. The model sees only a pseudo-ID key shared by the number of customers set by the control. Three feature sets feed a histogram gradient boosting classifier: the raw amount, the raw amount plus the log ratio to the past-only mean of the key, and the raw amount plus the log ratio to a whole-period mean (every development row of the key, including later ones). Each is scored by AUC two ways: 5-fold cross-validation on the first 70% of time, and a forward holdout on the last 30%, where only history up to the current row exists. Every score is the mean over 8 constructed logs.
Calculation code
import numpy as np
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import StratifiedKFold
from kaggle_companion.activities._common import clean
SEED = 7
REPLICATES = 8 # independent constructed transaction logs; every score is their mean
N_CUSTOMERS = 400
CUTOFF = 0.7 # transactions before this time are development rows, the rest are the forward holdout
FRAUD_RATE, FRAUD_MULTIPLE = 0.06, 3.0
def generate(per_key, seed):
"""One transaction log sorted by time. Customers differ a lot in spend level; fraud is 3x the customer's own level.
The pipeline never sees the customer, only a pseudo-ID key shared by `per_key` customers chosen at random."""
rng = np.random.default_rng(seed)
level = np.exp(rng.normal(3.5, 1.2, N_CUSTOMERS)) # each customer's typical amount
customer = np.repeat(np.arange(N_CUSTOMERS), rng.integers(6, 15, N_CUSTOMERS))
time = rng.random(len(customer))
fraud = (rng.random(len(customer)) < FRAUD_RATE).astype(int)
amount = level[customer] * np.exp(rng.normal(0, 0.5, len(customer)) + fraud * np.log(FRAUD_MULTIPLE))
key = (rng.permutation(N_CUSTOMERS) // per_key)[customer] # per_key = 1 is a perfect entity ID
order = np.argsort(time, kind="stable")
return time[order], key[order], amount[order], fraud[order]
def history_means(time, key, amount):
"""Two entity means for every row. Past-only: earlier rows of the same key. Whole-period: every development
row of the key (past, present and future) for development rows; rows seen up to now for holdout rows."""
n = len(amount)
dev = time < CUTOFF
past, to_date = np.full(n, np.nan), np.empty(n)
total, count = {}, {}
for i in range(n): # rows are in time order
s, c = total.get(key[i], 0.0), count.get(key[i], 0)
past[i] = s / c if c else np.nan # the first row of a key has no history
total[key[i]], count[key[i]] = s + amount[i], c + 1
to_date[i] = total[key[i]] / count[key[i]] # includes the current row, no future
dev_total, dev_count = {}, {}
for i in np.where(dev)[0]:
dev_total[key[i]] = dev_total.get(key[i], 0.0) + amount[i]
dev_count[key[i]] = dev_count.get(key[i], 0) + 1
whole = np.array([dev_total[key[i]] / dev_count[key[i]] if dev[i] else to_date[i] for i in range(n)])
return dev, past, whole
def columns(amount, mean):
"""log(amount / mean) plus a flag for rows with no history (ratio set to 0 there)."""
missing = np.isnan(mean)
ratio = np.where(missing, 0.0, np.log(amount) - np.log(np.where(missing, 1.0, mean)))
return [ratio, missing.astype(float)]
def model():
return HistGradientBoostingClassifier(max_iter=80, learning_rate=0.1, max_depth=3, random_state=0)
def one_log(per_key, seed):
time, key, amount, y = generate(per_key, seed)
dev, past, whole = history_means(time, key, amount)
sets = {"raw": [np.log(amount)],
"past_only": [np.log(amount)] + columns(amount, past),
"whole_period": [np.log(amount)] + columns(amount, whole)}
out = {}
for name, cols in sets.items():
X = np.column_stack(cols)
Xd, yd, Xh, yh = X[dev], y[dev], X[~dev], y[~dev]
oof = np.zeros(len(yd))
for a, b in StratifiedKFold(5, shuffle=True, random_state=1).split(Xd, yd):
oof[b] = model().fit(Xd[a], yd[a]).predict_proba(Xd[b])[:, 1]
out[name] = {"cv": roc_auc_score(yd, oof), # development estimate: 5-fold on development rows
"holdout": roc_auc_score(yh, model().fit(Xd, yd).predict_proba(Xh)[:, 1])}
out["rows"] = [int(dev.sum()), int((~dev).sum())]
return out
def run(customers_per_key):
logs = [one_log(customers_per_key, SEED * 100 + i) for i in range(REPLICATES)]
names = ["raw", "past_only", "whole_period"]
result = {"customers_per_key": customers_per_key, "replicates": REPLICATES, "customers": N_CUSTOMERS,
"development_rows": float(np.mean([g["rows"][0] for g in logs])),
"holdout_rows": float(np.mean([g["rows"][1] for g in logs]))}
for name in names:
result[name] = {m: float(np.mean([g[name][m] for g in logs])) for m in ("cv", "holdout")}
result[name]["holdout_per_log"] = [g[name]["holdout"] for g in logs]
return clean(result)
Measure it across the control
The control is customers sharing one pseudo-id key. Each value below runs the whole experiment from the generator up.
Calculation code
import pandas as pd
from kaggle_companion.activities.ch07 import explain
VALUES = [1, 2, 5, 20]
results = {value: run(value) for value in VALUES}
table = pd.DataFrame({str(v): explain(results[v], v)['metrics'] for v in VALUES})
print(table.to_string())1 2 5 20 Raw amount, holdout 0.706 0.706 0.706 0.706 Past-only ratio, holdout 0.908 0.771 0.730 0.707 Past-only lift +0.202 +0.065 +0.024 +0.001 Whole-period, cross-validation 0.907 0.773 0.725 0.705 Whole-period, holdout 0.906 0.781 0.729 0.709
Look at it
The figure shows the default setting, customers sharing one pseudo-id key = 1.
Calculation code
import matplotlib.pyplot as plt
from kaggle_companion.activities.ch07 import draw, NCOLS, HEIGHT
DEFAULT = 1
fig, axes = plt.subplots(1, NCOLS, figsize=(6.6 if NCOLS == 1 else 10.4, HEIGHT), layout='constrained')
draw(axes, results[DEFAULT], DEFAULT)
plt.show()What the result says
With 1 customer per key, the raw amount scores 0.706 on the forward holdout and the past-only ratio 0.908: 0.908 - 0.706 = 0.202 of lift, so the past-only history is worth building at this key quality. In cross-validation the whole-period mean scores 0.043 above the past-only mean (0.907 against 0.864); on the holdout, where later rows do not exist, the difference is -0.002.
- Lift of the past-only ratio on the holdout: 0.908 - 0.706 = +0.202.
- Development advantage of the whole-period mean: 0.907 - 0.864 = +0.043.
- Holdout advantage of the whole-period mean: 0.906 - 0.908 = -0.002.
- Past-only ratio, cross-validation minus holdout: 0.864 - 0.908 = -0.044 (holdout rows have longer histories).
Apply this to your competition
- Write down the prediction unit, the contextual entity and the history cutoff before building the feature.
- Count how many records each pseudo-ID key holds; a key that mixes many entities has a mean that describes none of them.
- Build histories from rows before the prediction time only, and test a whole-period mean only to see what it would falsely add.
- Keep the raw-amount baseline and compare the history feature against it on a later period, not on cross-validation alone.
Book location: Chapter 7, The Pattern: Find the Entity the Problem Is Really About.
Apply a lesson from this chapter
Select a lesson whose conditions match your project. State how you would test it and what would make you reject it.
Expand recency-frequency features across windows and rhythms
Conditions
- customer activity logs lack direct monetary detail
Action
- Proposed conditional application: Build recency, distinct-day/count frequencies, multi-window counts, growth rates and event intervals, including category purchase counts.
Check
- Proposed check: Ablate each family on next-month user holdouts, computing windows before forecast origin.
Limits
- Category counts are not monetary values; seasonality and changing user behavior matter.
Model at purchase level then aggregate to the target unit
Conditions
- an aggregate regression target has accessible finer-grained parent data
Action
- Proposed adaptation of source observation: Compare fine-grained purchase modelling with aggregate-only modelling and reproduce the target’s aggregation exactly.
Check
- Proposed verification: Reconcile reconstructed training aggregates to target rows and validate on complete state-month groups.
Limits
- The claim of stronger fine-grained signal is the authors’ interpretation; the packet does not give a matched split comparison.
Aggregate multiple people at the application level
Conditions
- one prediction concerns an application containing several people
Action
- Proposed adaptation of source observation: Compare first-person features with any-person indicators and per-application maxima over participants.
Check
- Proposed verification: Check joins for duplicate applications and assess aggregation choices with group-disjoint validation.
Limits
- The source does not ablate aggregation rules or define availability of past-success features.
- Source author Jeremy Howard explicitly disclaims prize eligibility; prize winner is Quan Sun. First-place model rank remains unverified source metadata, distinct from prize award.
Aggregate bidder behavior at multiple relational levels
Conditions
- individual entities have repeated events linked to auctions and network identifiers
Action
- Proposed adaptation of source observation: Compute entity-level distinct counts and within-auction mean, spread and maxima of behavior.
Check
- Proposed verification: Use entity-disjoint validation and ablate groups of aggregates.
Limits
- Source explanations about bot behavior are hypotheses; aggregates may leak unavailable future events.
Build a workload feature from computational scaling and fit by regime
Conditions
- runtime regression has known parallel workload and algorithm regimes
Action
- Proposed adaptation of source observation: Construct a workload-scaled feature and fit a simple regressor per sufficiently supported regime.
Check
- Proposed verification: Compare against all-feature regression on held-out configurations and verify prediction-ID assembly.
Limits
- Linear scaling and n_jobs=-1-to-sixteen mapping are machine-specific; extrapolation and overhead need checks.
Test domain-derived delinquency and cashflow aggregates
Conditions
- credit variables allow delinquency-duration and disposable-income summaries
Action
- Proposed adaptation of source observation: Construct late-day totals and income-minus-expense features from legitimately available inputs.
Check
- Proposed verification: Ablate aggregates on borrower-disjoint validation and inspect missing-value behavior.
Limits
- Exact calculation from this competition’s columns is not supplied; observed predictive association is not a causal risk explanation.
Aggregate relational credit history over ordered slices
Conditions
- credit risk features span many-to-one historical tables
Action
- Proposed adaptation of source observation: Create applicant-level aggregates over meaningful recent and earliest slices and explicit payment lateness.
Check
- Proposed verification: Audit sorting, cutoff availability, zero denominators and feature-family ablations.
Limits
- Time-window choices were selected locally; target-time availability must be checked for transfer.
Normalize medications to clinical groups before outcome modeling
Conditions
- clinical medication names fragment equivalent therapies
Action
- Proposed adaptation of source observation: Create auditable ingredient/route/indication groups and compare counts, doses and flags per group.
Check
- Proposed verification: Validate clinical mappings with domain expertise and evaluate patient-held-out incremental value.
Limits
- Dose equivalence is drug-specific; source supplies no complete mapping or causal clinical claim.
Parse floor structure into checked housing features
Conditions
- housing fields mix text floors and quantized sale prices
Action
- Parse floor numerals, top-floor status and floor/total-floor ratios with explicit missing and basement handling.
Check
- Test basement, multifloor, top-floor and zero-total-floor cases, then ablate features on held-out transactions.
Limits
- Source parsing details are partial; floor semantics vary by market.
- Rounding price outputs is a separate metric/granularity decision.
Treat intuitive handcrafted features as validation experiments
Conditions
- manual features are costly to validate
Action
- Proposed adaptation of source observation: Prototype feature families against a fixed baseline and budget repeated-validation time as part of iteration cost.
Check
- Proposed verification: Record incremental effects and rejected feature families.
Limits
- A plausible feature need not help; no numerical feature ablations are provided.
Smooth sparse historical averages before blending current signals
Conditions
- pairwise history is sparse and current external signals use different scales
Action
- Proposed adaptation of source observation: Shrink sparse means toward an explicit prior and calibrate external features before blending them.
Check
- Proposed verification: Hold out complete events and compare raw means, smoothed means and blended signals.
Limits
- The smoothing prior of one and reciprocal-log odds are local choices; odds near one create instability.
Summarize temporal bursts and simultaneous events
Conditions
- automated behavior may create bursts in event streams
Action
- Proposed adaptation of source observation: Sort each entity’s events and compute gaps, rolling burst counts, simultaneous-event counts and time-period proportions.
Check
- Proposed verification: Check boundary cases and validate whether time features add value on held-out entities.
Limits
- The first event lacks a gap; source dropped such rows and did not establish this is safe elsewhere.
Remove the prediction event before history features
Conditions
- event sequences where the final event is the training target
Action
- Proposed: sort events by entity and timestamp, reserve the final event as target, and compute features from the preceding history.
Check
- Proposed: assert no target row contributes to counts or last-event features; handle histories with fewer than two prior events.
Limits
- The code indexes a second-last event without a short-history guard. Chronological ordering is performed before grouping; test histories are not stripped.
Test domain-inspired feature families for incremental lift
Conditions
- domain reasoning suggests a new transactional feature family
- domain knowledge suggests an additional feature family
Action
- Proposed adaptation of source observation: Compare its incremental benefit against existing aggregates before investing further.
- Proposed adaptation of source observation: Add the candidate feature family to a fixed baseline and measure its incremental held-out value.
Check
- Proposed verification: Ablate feature families on the same patient-disjoint split.
- Proposed verification: Compare with and without that family using identical splits and training settings.
Limits
- Domain plausibility did not guarantee lift; no numeric isolated prescription comparison is given.
- The source reports a qualitative result and does not establish why prescription features added little signal.
Audit near-deterministic price formulas for target availability
Conditions
- input fields may encode a near-deterministic version of the target
Action
- Proposed adaptation of source observation: Verify field definitions and prediction-time availability before comparing a formula baseline with learned regression.
Check
- Proposed verification: Check formulas by parking condition and test whether unit price itself derives from the target.
Limits
- Very high explanatory power can reflect leakage; the conditional formula and factor five are dataset-specific and not verified physical laws.