Feature Engineering: Knowledge + Patterns
You are a feature engineering expert optimizing AUC on a tabular benchmark. Use the knowledge below however makes sense — there is no required workflow. Simple ideas may need one evaluation; complex ones may need systematic ablation.
Your goal: maximize AUC by iteratively building and evaluating feature sets via bench.evaluate(features_list). Stop when AUC stops improving (implement early-stop in your code).
First: detect which dataset you are on by checking bench.dataset (returns 'taobao' or 'dia'). Use the corresponding section below. Inspect bench.feature_columns_original and bench.X_train.dtypes to understand the actual schema — do not rely on skill hardcoded names.
Benchmark API
import sys
sys.path.insert(0, "benchmarks/feature_engineering") # relative to AdaSkill project root
from feature_engineering import Benchmark, BenchmarkLimitReached
bench = Benchmark(dataset='<taobao or dia>', work_dir='<provided in prompt>', max_evaluations=500)
orig = bench.feature_columns_original.copy() # inspect this first
dataset = bench.dataset # 'taobao' or 'dia'
# Evaluate
auc = bench.evaluate(features_list) # returns float AUC; raises BenchmarkLimitReached
Non-negotiable rules:
- Always modify both
bench.X_train and bench.X_test when adding features
- For Taobao: compute CVR/count features from
train_hist / valid_hist — never from X_train/X_test labels
- For Dia: all features derived directly from
bench.X_train / bench.X_test columns (no history log)
fillna() every new column before calling evaluate()
- Pass only columns that exist in
bench.X_train to evaluate()
Early-stop (implement this in your code):
best_auc, no_improve = 0.0, 0
PATIENCE = 30 # from --early-stop arg passed in prompt
while True:
auc = bench.evaluate(features)
if auc > best_auc:
best_auc = auc
no_improve = 0
else:
no_improve += 1
if no_improve >= PATIENCE:
break
Taobao Dataset
Ad conversion prediction. Target column indicates whether a purchase occurred (~1.9% positive rate).
The dataset contains features in these semantic groups — inspect the actual column names via bench.feature_columns_original:
- User entity: user ID, demographic attributes (age level, gender, occupation, star level)
- Item entity: item ID, category, brand, city, price level, sales level, collection level, page-view level, property count
- Shop entity: shop ID, review count level, positive review rate, star level, service/delivery/description scores
- Context: timestamp, page ID (extracted hour and day are also available)
- Prediction signal: predicted category/property match features
Time split: history log covers a train window and a validation window (sequential days).
train_hist = bench.train_history_df # history log — safe to use (no leakage)
valid_hist = bench.valid_history_df
TARGET = bench.target_column # purchase indicator column name
G_CVR = train_hist[TARGET].mean()
G_CVR_V = valid_hist[TARGET].mean()
Taobao: Feature Engineering Directions
This is a sparse, high-cardinality CTR/CVR prediction task. The most valuable signal comes from behavioral statistics computed from the history log.
Productive directions:
- Entity CVR: For each entity type (user, item, shop, category, brand), compute its historical conversion rate from the history log. Bayesian smoothing prevents overfitting on rare entities.
- Interaction CVR: CVR conditioned on two entities jointly (e.g., user × category, user × shop) captures preference alignment beyond marginal rates.
- Frequency/activity counts: How often does each entity appear in history? Log-transform these — count distributions are heavy-tailed.
- Price deviation: Whether this item is cheap or expensive relative to what this user typically sees — captures price sensitivity.
- Quality composites: The item has multiple popularity-related ordinal features (sales, collection, page-views); combining them into a single composite reduces noise. Similarly for shop quality scores.
- Temporal features: Hour of day and day extracted from the timestamp — useful when combined with CVR, weak alone.
Taobao: Code Patterns
# Bayesian-smoothed CVR — core pattern for any high-cardinality entity column
def add_cvr(key_col, feat_name, k=20):
"""k controls smoothing: larger k = more shrinkage toward global mean.
Use larger k for 1-way, smaller k for 2-way (fewer samples per cell)."""
def _cvr(hist, g):
s = hist.groupby(key_col)[TARGET].agg(['sum', 'count'])
s['v'] = (s['sum'] + k * g) / (s['count'] + k)
return s['v'].to_dict()
tr = _cvr(train_hist, G_CVR)
va = _cvr(valid_hist, G_CVR_V)
bench.X_train[feat_name] = bench.X_train[key_col].map(tr).fillna(G_CVR)
bench.X_test[feat_name] = bench.X_test[key_col].map(va).fillna(G_CVR_V)
# Log-count frequency — how active is this entity in history?
def add_count(key_col, feat_name):
tr = train_hist[key_col].value_counts().apply(np.log1p).to_dict()
va = valid_hist[key_col].value_counts().apply(np.log1p).to_dict()
bench.X_train[feat_name] = bench.X_train[key_col].map(tr).fillna(0)
bench.X_test[feat_name] = bench.X_test[key_col].map(va).fillna(0)
# Numeric aggregation from history (e.g., user's average price preference)
def add_agg(group_col, value_col, agg_fn, feat_name):
tr = train_hist.groupby(group_col)[value_col].agg(agg_fn).to_dict()
va = valid_hist.groupby(group_col)[value_col].agg(agg_fn).to_dict()
fb_tr = train_hist[value_col].agg(agg_fn)
fb_va = valid_hist[value_col].agg(agg_fn)
bench.X_train[feat_name] = bench.X_train[group_col].map(tr).fillna(fb_tr)
bench.X_test[feat_name] = bench.X_test[group_col].map(va).fillna(fb_va)
# Cross-feature product of two CVR signals
# bench.X_train['user_x_cat_cvr'] = bench.X_train['user_cvr'] * bench.X_train['cat_cvr']
# bench.X_test['user_x_cat_cvr'] = bench.X_test['user_cvr'] * bench.X_test['cat_cvr']
Taobao: Engineering Principles
- Raw high-cardinality ID columns become redundant once you have their CVR. The entity ID columns (user, item, shop, brand, city) as raw integers carry almost no signal for tree models once their corresponding CVR features are computed. Explicitly exclude them from
FEATURES — keeping them adds noise. This is a standard principle in CTR feature engineering, not dataset-specific.
- Ratio/division needs protection. Always use
denominator + 1e-6. Groupby means on sparse keys: fillna with global mean.
- Log-transform count features. Raw counts are heavy-tailed and hurt model calibration.
- Smoothing strength scales with data density. Denser keys tolerate less smoothing; sparse 2-way combination keys need more smoothing.
- Composite scores reduce noise. Summing a group of related ordinal features is usually better than using each separately.
- Ablate from a strong set. Once you have a promising feature set, try removing features one at a time — lean sets often generalize better.
Dia Dataset
Diabetes risk prediction based on a health survey. Target column is a binary diabetes indicator. All original features are binary (0/1) or low-cardinality ordinal integers. No history log — all features derived directly from X_train/X_test columns.
Inspect the actual column names via bench.feature_columns_original. The dataset contains features in these semantic groups:
- Metabolic/cardiovascular risk indicators: binary flags for high blood pressure, high cholesterol, cholesterol check, heart disease/attack history, stroke history
- BMI: continuous body mass index
- Lifestyle behaviors: binary flags for smoking, physical activity, fruit/vegetable consumption, heavy alcohol consumption
- Healthcare access: binary flags for healthcare coverage, cost-related doctor avoidance
- Functional health: ordinal general health status (1–5), days of poor physical/mental health (0–30), walking difficulty
- Sociodemographic: sex (binary), age category (ordinal), education level (ordinal), income level (ordinal)
Dia: Feature Engineering Directions
The original features are already clinically meaningful, so gains come from capturing non-linearity and interactions between them.
Productive directions:
- BMI non-linearity: BMI has well-known clinical thresholds (overweight ≥25, obese ≥30) with a non-linear relationship to metabolic risk. Threshold indicators and polynomial terms capture this.
- Cardiovascular composite: Blood pressure, heart disease, stroke, and cholesterol indicators share common pathological mechanisms (insulin resistance, inflammation). Summing them into a composite score can be more informative than using each separately.
- Lifestyle composite: Smoking, physical inactivity, heavy alcohol, and poor diet interact synergistically — a cumulative unhealthy lifestyle score captures joint behavioral risk.
- Health burden composite: General health status, days of poor physical/mental health, and walking difficulty all reflect functional health — combining them is a natural direction.
- Target encoding of ordinals: Age, general health, income, and education are ordinal but their relationship with diabetes risk may not be strictly linear. Cross-validated target encoding captures the true pattern without leakage.
- Pairwise interaction encodings: Combinations of age × general health, age × blood pressure, etc. may capture interaction effects worth encoding via cross-validated target encoding.
- Socioeconomic interactions: Income, education, and healthcare access interact — lower socioeconomic status affects both lifestyle choices and medical access.
- Age × sex interaction: Diabetes risk has different age trajectories by sex (e.g., post-menopausal elevated risk). An interaction term can capture this.
Important: The baseline is already high. Each feature must earn its place. A clean set of 20–30 well-chosen features typically beats 100+ noisy ones.
Dia: Code Patterns
# Cross-validated target encoding — prevents leakage from target variable
from sklearn.model_selection import KFold
def cv_target_encode(X_tr, X_te, col, y_tr, n_splits=5):
"""Encode ordinal/categorical column by per-fold mean target rate.
KFold prevents target leakage on the training set."""
global_mean = y_tr.mean()
kf = KFold(n_splits=n_splits, shuffle=True, random_state=42)
train_enc = np.zeros(len(X_tr))
for tr_idx, val_idx in kf.split(X_tr):
fold_mean = (X_tr.iloc[tr_idx]
.assign(_y=y_tr.iloc[tr_idx])
.groupby(col)['_y'].mean())
train_enc[val_idx] = X_tr.iloc[val_idx][col].map(fold_mean).fillna(global_mean)
full_mean = X_tr.assign(_y=y_tr).groupby(col)['_y'].mean()
test_enc = X_te[col].map(full_mean).fillna(global_mean)
return train_enc, test_enc
y_train = bench.y_train # target labels — safe to use for computing train-side encodings
# Encode ordinal columns by their mean target rate (identify them from bench.feature_columns_original)
# for col in <ordinal columns>:
# tr_enc, te_enc = cv_target_encode(bench.X_train, bench.X_test, col, y_train)
# bench.X_train[f'{col}_te'] = tr_enc
# bench.X_test[f'{col}_te'] = te_enc
# BMI threshold indicators (WHO/CDC clinical standards — not data-derived)
# for df in [bench.X_train, bench.X_test]:
# df['bmi_overweight'] = (df[bmi_col] >= 25).astype(int)
# df['bmi_obese'] = (df[bmi_col] >= 30).astype(int)
# df['bmi_sq'] = df[bmi_col] ** 2
# Composite scores — sum binary indicators that share a clinical mechanism
# for df in [bench.X_train, bench.X_test]:
# df['cardio_composite'] = df[bp_col] + df[heart_col] + df[stroke_col] + df[chol_col]
# df['lifestyle_risk'] = df[smoker_col] + (1 - df[activity_col]) + df[alcohol_col] + ...
# df['health_burden'] = df[physhlth_col] + df[menthlth_col] + df[diffwalk_col] * 5
# Polynomial terms for non-linear ordinals
# for df in [bench.X_train, bench.X_test]:
# df['age_sq'] = df[age_col] ** 2
# df['genhlth_sq'] = df[genhlth_col] ** 2
# df['age_x_genhlth'] = df[age_col] * df[genhlth_col]
# Interaction target encoding: build combo key, encode, drop string column
# for col_a, col_b in [<pair1>, <pair2>]:
# combo = f'{col_a}_x_{col_b}'
# bench.X_train[combo] = bench.X_train[col_a].astype(str) + '_' + bench.X_train[col_b].astype(str)
# bench.X_test[combo] = bench.X_test[col_a].astype(str) + '_' + bench.X_test[col_b].astype(str)
# tr_enc, te_enc = cv_target_encode(bench.X_train, bench.X_test, combo, y_train)
# bench.X_train[f'{combo}_te'] = tr_enc
# bench.X_test[f'{combo}_te'] = te_enc
# bench.X_train.drop(columns=[combo], inplace=True)
# bench.X_test.drop(columns=[combo], inplace=True)
Dia: Engineering Principles
- Always use KFold target encoding, never fit-on-full-train. Encoding ordinals without cross-validation leaks the target into training features and inflates AUC artificially.
- BMI thresholds are clinically grounded, not data-mined. The ≥25 (overweight) and ≥30 (obese) cutoffs come from WHO/CDC standards.
- Composite scores capture synergy. Multiple co-occurring risk factors (cardiovascular, lifestyle) have cumulative effects — a sum composite is often more informative than each factor individually.
- Log-transform day-count features. The 0–30 count features for days of poor health are right-skewed; log1p stabilizes variance.
- Non-linearity in ordinals matters. Age and general health status have non-linear effects; squared terms and target encoding both worth trying.
- Lean sets outperform large ones here. The baseline is already high; noise features actively hurt. Test removal of features that don't contribute.
- Ratio features need protection. Any division: use
denominator + 1e-6.
1---2name: very-simple-fela3description: Feature engineering skill for taobao/dia AUC maximization. Provides dataset-specific domain knowledge and code patterns — no forced pipeline.4---56# Feature Engineering: Knowledge + Patterns78You are a feature engineering expert optimizing AUC on a tabular benchmark. Use the knowledge below however makes sense — there is no required workflow. Simple ideas may need one evaluation; complex ones may need systematic ablation.910Your goal: maximize AUC by iteratively building and evaluating feature sets via `bench.evaluate(features_list)`. Stop when AUC stops improving (implement early-stop in your code).1112**First: detect which dataset you are on** by checking `bench.dataset` (returns `'taobao'` or `'dia'`). Use the corresponding section below. Inspect `bench.feature_columns_original` and `bench.X_train.dtypes` to understand the actual schema — do not rely on skill hardcoded names.1314---1516## Benchmark API1718```python19import sys20sys.path.insert(0, "benchmarks/feature_engineering") # relative to AdaSkill project root21from feature_engineering import Benchmark, BenchmarkLimitReached2223bench = Benchmark(dataset='<taobao or dia>', work_dir='<provided in prompt>', max_evaluations=500)24orig = bench.feature_columns_original.copy() # inspect this first25dataset = bench.dataset # 'taobao' or 'dia'2627# Evaluate28auc = bench.evaluate(features_list) # returns float AUC; raises BenchmarkLimitReached29```3031**Non-negotiable rules:**32- Always modify **both** `bench.X_train` and `bench.X_test` when adding features33- For Taobao: compute CVR/count features from `train_hist` / `valid_hist` — never from X_train/X_test labels34- For Dia: all features derived directly from `bench.X_train` / `bench.X_test` columns (no history log)35- `fillna()` every new column before calling `evaluate()`36- Pass only columns that exist in `bench.X_train` to `evaluate()`3738**Early-stop (implement this in your code):**39```python40best_auc, no_improve = 0.0, 041PATIENCE = 30 # from --early-stop arg passed in prompt4243while True:44 auc = bench.evaluate(features)45 if auc > best_auc:46 best_auc = auc47 no_improve = 048 else:49 no_improve += 150 if no_improve >= PATIENCE:51 break52```5354---5556## Taobao Dataset5758Ad conversion prediction. Target column indicates whether a purchase occurred (~1.9% positive rate).5960The dataset contains features in these semantic groups — inspect the actual column names via `bench.feature_columns_original`:61- **User entity**: user ID, demographic attributes (age level, gender, occupation, star level)62- **Item entity**: item ID, category, brand, city, price level, sales level, collection level, page-view level, property count63- **Shop entity**: shop ID, review count level, positive review rate, star level, service/delivery/description scores64- **Context**: timestamp, page ID (extracted hour and day are also available)65- **Prediction signal**: predicted category/property match features6667**Time split:** history log covers a train window and a validation window (sequential days).6869```python70train_hist = bench.train_history_df # history log — safe to use (no leakage)71valid_hist = bench.valid_history_df72TARGET = bench.target_column # purchase indicator column name73G_CVR = train_hist[TARGET].mean()74G_CVR_V = valid_hist[TARGET].mean()75```7677### Taobao: Feature Engineering Directions7879This is a sparse, high-cardinality CTR/CVR prediction task. The most valuable signal comes from **behavioral statistics** computed from the history log.8081**Productive directions:**82- **Entity CVR**: For each entity type (user, item, shop, category, brand), compute its historical conversion rate from the history log. Bayesian smoothing prevents overfitting on rare entities.83- **Interaction CVR**: CVR conditioned on two entities jointly (e.g., user × category, user × shop) captures preference alignment beyond marginal rates.84- **Frequency/activity counts**: How often does each entity appear in history? Log-transform these — count distributions are heavy-tailed.85- **Price deviation**: Whether this item is cheap or expensive relative to what this user typically sees — captures price sensitivity.86- **Quality composites**: The item has multiple popularity-related ordinal features (sales, collection, page-views); combining them into a single composite reduces noise. Similarly for shop quality scores.87- **Temporal features**: Hour of day and day extracted from the timestamp — useful when combined with CVR, weak alone.8889### Taobao: Code Patterns9091```python92# Bayesian-smoothed CVR — core pattern for any high-cardinality entity column93def add_cvr(key_col, feat_name, k=20):94 """k controls smoothing: larger k = more shrinkage toward global mean.95 Use larger k for 1-way, smaller k for 2-way (fewer samples per cell)."""96 def _cvr(hist, g):97 s = hist.groupby(key_col)[TARGET].agg(['sum', 'count'])98 s['v'] = (s['sum'] + k * g) / (s['count'] + k)99 return s['v'].to_dict()100 tr = _cvr(train_hist, G_CVR)101 va = _cvr(valid_hist, G_CVR_V)102 bench.X_train[feat_name] = bench.X_train[key_col].map(tr).fillna(G_CVR)103 bench.X_test[feat_name] = bench.X_test[key_col].map(va).fillna(G_CVR_V)104105# Log-count frequency — how active is this entity in history?106def add_count(key_col, feat_name):107 tr = train_hist[key_col].value_counts().apply(np.log1p).to_dict()108 va = valid_hist[key_col].value_counts().apply(np.log1p).to_dict()109 bench.X_train[feat_name] = bench.X_train[key_col].map(tr).fillna(0)110 bench.X_test[feat_name] = bench.X_test[key_col].map(va).fillna(0)111112# Numeric aggregation from history (e.g., user's average price preference)113def add_agg(group_col, value_col, agg_fn, feat_name):114 tr = train_hist.groupby(group_col)[value_col].agg(agg_fn).to_dict()115 va = valid_hist.groupby(group_col)[value_col].agg(agg_fn).to_dict()116 fb_tr = train_hist[value_col].agg(agg_fn)117 fb_va = valid_hist[value_col].agg(agg_fn)118 bench.X_train[feat_name] = bench.X_train[group_col].map(tr).fillna(fb_tr)119 bench.X_test[feat_name] = bench.X_test[group_col].map(va).fillna(fb_va)120121# Cross-feature product of two CVR signals122# bench.X_train['user_x_cat_cvr'] = bench.X_train['user_cvr'] * bench.X_train['cat_cvr']123# bench.X_test['user_x_cat_cvr'] = bench.X_test['user_cvr'] * bench.X_test['cat_cvr']124```125126### Taobao: Engineering Principles1271281. **Raw high-cardinality ID columns become redundant once you have their CVR.** The entity ID columns (user, item, shop, brand, city) as raw integers carry almost no signal for tree models once their corresponding CVR features are computed. Explicitly exclude them from `FEATURES` — keeping them adds noise. This is a standard principle in CTR feature engineering, not dataset-specific.1292. **Ratio/division needs protection.** Always use `denominator + 1e-6`. Groupby means on sparse keys: fillna with global mean.1303. **Log-transform count features.** Raw counts are heavy-tailed and hurt model calibration.1314. **Smoothing strength scales with data density.** Denser keys tolerate less smoothing; sparse 2-way combination keys need more smoothing.1325. **Composite scores reduce noise.** Summing a group of related ordinal features is usually better than using each separately.1336. **Ablate from a strong set.** Once you have a promising feature set, try removing features one at a time — lean sets often generalize better.134135---136137## Dia Dataset138139Diabetes risk prediction based on a health survey. Target column is a binary diabetes indicator. All original features are binary (0/1) or low-cardinality ordinal integers. **No history log — all features derived directly from X_train/X_test columns.**140141Inspect the actual column names via `bench.feature_columns_original`. The dataset contains features in these semantic groups:142- **Metabolic/cardiovascular risk indicators**: binary flags for high blood pressure, high cholesterol, cholesterol check, heart disease/attack history, stroke history143- **BMI**: continuous body mass index144- **Lifestyle behaviors**: binary flags for smoking, physical activity, fruit/vegetable consumption, heavy alcohol consumption145- **Healthcare access**: binary flags for healthcare coverage, cost-related doctor avoidance146- **Functional health**: ordinal general health status (1–5), days of poor physical/mental health (0–30), walking difficulty147- **Sociodemographic**: sex (binary), age category (ordinal), education level (ordinal), income level (ordinal)148149### Dia: Feature Engineering Directions150151The original features are already clinically meaningful, so gains come from **capturing non-linearity and interactions** between them.152153**Productive directions:**154- **BMI non-linearity**: BMI has well-known clinical thresholds (overweight ≥25, obese ≥30) with a non-linear relationship to metabolic risk. Threshold indicators and polynomial terms capture this.155- **Cardiovascular composite**: Blood pressure, heart disease, stroke, and cholesterol indicators share common pathological mechanisms (insulin resistance, inflammation). Summing them into a composite score can be more informative than using each separately.156- **Lifestyle composite**: Smoking, physical inactivity, heavy alcohol, and poor diet interact synergistically — a cumulative unhealthy lifestyle score captures joint behavioral risk.157- **Health burden composite**: General health status, days of poor physical/mental health, and walking difficulty all reflect functional health — combining them is a natural direction.158- **Target encoding of ordinals**: Age, general health, income, and education are ordinal but their relationship with diabetes risk may not be strictly linear. Cross-validated target encoding captures the true pattern without leakage.159- **Pairwise interaction encodings**: Combinations of age × general health, age × blood pressure, etc. may capture interaction effects worth encoding via cross-validated target encoding.160- **Socioeconomic interactions**: Income, education, and healthcare access interact — lower socioeconomic status affects both lifestyle choices and medical access.161- **Age × sex interaction**: Diabetes risk has different age trajectories by sex (e.g., post-menopausal elevated risk). An interaction term can capture this.162163**Important:** The baseline is already high. Each feature must earn its place. A clean set of 20–30 well-chosen features typically beats 100+ noisy ones.164165### Dia: Code Patterns166167```python168# Cross-validated target encoding — prevents leakage from target variable169from sklearn.model_selection import KFold170171def cv_target_encode(X_tr, X_te, col, y_tr, n_splits=5):172 """Encode ordinal/categorical column by per-fold mean target rate.173 KFold prevents target leakage on the training set."""174 global_mean = y_tr.mean()175 kf = KFold(n_splits=n_splits, shuffle=True, random_state=42)176 train_enc = np.zeros(len(X_tr))177 for tr_idx, val_idx in kf.split(X_tr):178 fold_mean = (X_tr.iloc[tr_idx]179 .assign(_y=y_tr.iloc[tr_idx])180 .groupby(col)['_y'].mean())181 train_enc[val_idx] = X_tr.iloc[val_idx][col].map(fold_mean).fillna(global_mean)182 full_mean = X_tr.assign(_y=y_tr).groupby(col)['_y'].mean()183 test_enc = X_te[col].map(full_mean).fillna(global_mean)184 return train_enc, test_enc185186y_train = bench.y_train # target labels — safe to use for computing train-side encodings187188# Encode ordinal columns by their mean target rate (identify them from bench.feature_columns_original)189# for col in <ordinal columns>:190# tr_enc, te_enc = cv_target_encode(bench.X_train, bench.X_test, col, y_train)191# bench.X_train[f'{col}_te'] = tr_enc192# bench.X_test[f'{col}_te'] = te_enc193194# BMI threshold indicators (WHO/CDC clinical standards — not data-derived)195# for df in [bench.X_train, bench.X_test]:196# df['bmi_overweight'] = (df[bmi_col] >= 25).astype(int)197# df['bmi_obese'] = (df[bmi_col] >= 30).astype(int)198# df['bmi_sq'] = df[bmi_col] ** 2199200# Composite scores — sum binary indicators that share a clinical mechanism201# for df in [bench.X_train, bench.X_test]:202# df['cardio_composite'] = df[bp_col] + df[heart_col] + df[stroke_col] + df[chol_col]203# df['lifestyle_risk'] = df[smoker_col] + (1 - df[activity_col]) + df[alcohol_col] + ...204# df['health_burden'] = df[physhlth_col] + df[menthlth_col] + df[diffwalk_col] * 5205206# Polynomial terms for non-linear ordinals207# for df in [bench.X_train, bench.X_test]:208# df['age_sq'] = df[age_col] ** 2209# df['genhlth_sq'] = df[genhlth_col] ** 2210# df['age_x_genhlth'] = df[age_col] * df[genhlth_col]211212# Interaction target encoding: build combo key, encode, drop string column213# for col_a, col_b in [<pair1>, <pair2>]:214# combo = f'{col_a}_x_{col_b}'215# bench.X_train[combo] = bench.X_train[col_a].astype(str) + '_' + bench.X_train[col_b].astype(str)216# bench.X_test[combo] = bench.X_test[col_a].astype(str) + '_' + bench.X_test[col_b].astype(str)217# tr_enc, te_enc = cv_target_encode(bench.X_train, bench.X_test, combo, y_train)218# bench.X_train[f'{combo}_te'] = tr_enc219# bench.X_test[f'{combo}_te'] = te_enc220# bench.X_train.drop(columns=[combo], inplace=True)221# bench.X_test.drop(columns=[combo], inplace=True)222```223224### Dia: Engineering Principles2252261. **Always use KFold target encoding, never fit-on-full-train.** Encoding ordinals without cross-validation leaks the target into training features and inflates AUC artificially.2272. **BMI thresholds are clinically grounded, not data-mined.** The ≥25 (overweight) and ≥30 (obese) cutoffs come from WHO/CDC standards.2283. **Composite scores capture synergy.** Multiple co-occurring risk factors (cardiovascular, lifestyle) have cumulative effects — a sum composite is often more informative than each factor individually.2294. **Log-transform day-count features.** The 0–30 count features for days of poor health are right-skewed; log1p stabilizes variance.2305. **Non-linearity in ordinals matters.** Age and general health status have non-linear effects; squared terms and target encoding both worth trying.2316. **Lean sets outperform large ones here.** The baseline is already high; noise features actively hurt. Test removal of features that don't contribute.2327. **Ratio features need protection.** Any division: use `denominator + 1e-6`.