Home/Machine Learning/ML Modeling & Model Selection
Back
๐ŸŽ›๏ธ

Applied Scientist

ML Modeling & Model Selection

Applied Scientists are the bridge between ML research and business impact. You must be able to translate a business problem into an ML objective, select the right model, evaluate it correctly, and interpret it for stakeholders. 'Build a model to predict X' is the most common Applied Scientist interview format.

ML fundamentalsStatisticsPython/sklearnFeature engineeringXGBoostModel SelectionSHAPCalibrationFeature EngineeringAUC

Core Theory

  • 1Problem framing: before any model: (1) define the ML objective (maximize P(click)? minimize churn risk? rank items by relevance?), (2) define the training label (what is a positive? what is a negative?), (3) identify the prediction horizon (predict next 30 days? next session? real-time?), (4) identify constraints (latency, interpretability requirements, minimum recall threshold). Wrong objective โ†’ wrong model โ†’ no business impact.
  • 2Model selection framework: start simple. Logistic regression baseline first โ€” it's interpretable, fast, and often surprisingly competitive. Then try GBDT (XGBoost/LightGBM) for structured/tabular data. Neural networks only when: data is unstructured (text, images), or dataset is very large (>1M samples), or feature interactions are extremely complex. Deep learning for tabular data rarely beats well-tuned GBDT.
  • 3XGBoost/LightGBM key hyperparameters: max_depth (3-6 for most tasks โ€” deeper = more overfitting), learning_rate (0.01-0.1, lower = more trees needed), n_estimators (use early stopping, not a fixed number), subsample (0.7-0.9, bagging fraction), colsample_bytree (0.7-0.9, feature subsampling per tree), min_child_weight (regularizes leaf nodes), reg_lambda/reg_alpha (L2/L1 on leaf weights).
  • 4Evaluation for classification: AUC-ROC (threshold-invariant, probability that model ranks a random positive above a random negative), AUC-PR (better for imbalanced data โ€” measures precision-recall tradeoff), F1/F-beta at business threshold, log-loss (measures calibration quality). Calibration: are predicted probabilities accurate? A model can have AUC=0.92 but terrible calibration (predicted 90% probability for events that occur 50% of the time).
  • 5SHAP values: SHapley Additive exPlanations. For each prediction, ฯ†_i = the contribution of feature i. Properties: (1) efficiency (ฮฃฯ†_i = model output - baseline), (2) consistency (if model relies more on feature i, its SHAP value can only increase), (3) null player (unused features get SHAP=0). Tree SHAP is O(TLD) โ€” fast enough for large models. More reliable than gain-based feature importance, which is biased toward high-cardinality features.
  • 6Distribution shift / train-serve skew: model trained on historical data but deployed on future data with different distributions. Symptoms: high training AUC, low production AUC. Causes: (1) covariate shift โ€” input distribution changed (e.g., new user demographics), (2) label shift โ€” proportion of positives changed (e.g., seasonality), (3) concept drift โ€” relationship between X and Y changed (e.g., user behavior evolved). Detection: compare training vs serving input distributions (PSI), monitor prediction score distribution.
  • 7Imbalanced classes: when positive class is rare (<5%), models can achieve high accuracy by predicting all negatives. Solutions: (1) class weighting (class_weight='balanced' in sklearn, scale_pos_weight in XGBoost), (2) resampling (oversample minority with SMOTE, undersample majority), (3) threshold tuning (move the classification threshold from 0.5 to optimize the business metric), (4) anomaly detection framing (for <1% positive rate). Evaluation: always use AUC-PR or F1 at appropriate threshold, never accuracy.
  • 8Feature engineering for tabular ML: (1) encoding categoricals โ€” one-hot for low cardinality (<20 categories), target encoding for high cardinality (mean of Y per category, with smoothing to prevent leakage), entity embeddings for neural networks. (2) DateTime features โ€” day-of-week, hour-of-day, time since last event, rolling statistics. (3) Interaction features โ€” multiply two features for XGBoost (trees can find interactions but benefit from explicit help). (4) Text features โ€” TF-IDF, sentence embeddings (FinBERT, SBERT) for semantic search.

Key Formulas

01XGBoost objective: L(ฯ•)=โˆ‘il(yi,y^i)+โˆ‘k(ฮณTk+12ฮปโˆฃโˆฃwkโˆฃโˆฃ2)L(\phi) = \sum_i l(y_i, \hat{y}_i) + \sum_k (\gamma T_k + \frac{1}{2}\lambda||w_k||^2)
02SHAP: g(xโ€ฒ)=ฯ•0+โˆ‘iฯ•ixiโ€ฒg(x') = \phi_0 + \sum_i \phi_i x'_i, โˆ‘ฯ•i=f(x)โˆ’E[f(X)]\sum \phi_i = f(x) - E[f(X)]
03Log-loss: L=โˆ’1nโˆ‘i[yilogโกp^i+(1โˆ’yi)logโก(1โˆ’p^i)]L = -\frac{1}{n}\sum_i [y_i \log \hat{p}_i + (1-y_i)\log(1-\hat{p}_i)]
04Target encoding: ฮผ^k=nkyห‰k+myห‰globalnk+m\hat{\mu}_k = \frac{n_k \bar{y}_k + m \bar{y}_{global}}{n_k + m} (smoothed)
05F-beta: Fฮฒ=(1+ฮฒ2)โ‹…precisionโ‹…recallฮฒ2โ‹…precision+recallF_\beta = (1+\beta^2) \cdot \frac{precision \cdot recall}{\beta^2 \cdot precision + recall}
python
1import numpy as np
2import pandas as pd
3from sklearn.datasets import make_classification
4from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score
5from sklearn.metrics import roc_auc_score, average_precision_score, brier_score_loss
6from sklearn.calibration import CalibratedClassifierCV
7from sklearn.linear_model import LogisticRegression
8
9np.random.seed(42)
10
11# โ”€โ”€โ”€ Create imbalanced churn prediction dataset โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
12X, y = make_classification(n_samples=50_000, n_features=20, n_informative=8,
13 weights=[0.92, 0.08], random_state=42)
14X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2,
15 stratify=y, random_state=42)
16
17# โ”€โ”€โ”€ Baseline: Logistic Regression โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
18lr = LogisticRegression(class_weight='balanced', max_iter=500)
19lr.fit(X_tr, y_tr)
20lr_proba = lr.predict_proba(X_te)[:, 1]
21
22# โ”€โ”€โ”€ Main model: XGBoost with class imbalance handling โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
23try:
24 from xgboost import XGBClassifier
25 pos_weight = (y_tr == 0).sum() / (y_tr == 1).sum() # ~11.5 for 8% churn
26 xgb = XGBClassifier(
27 n_estimators=300, max_depth=4, learning_rate=0.05,
28 subsample=0.8, colsample_bytree=0.8,
29 scale_pos_weight=pos_weight, # handle class imbalance
30 eval_metric='logloss', random_state=42, verbosity=0
31 )
32 xgb.fit(X_tr, y_tr, eval_set=[(X_te, y_te)], verbose=False)
33 xgb_proba = xgb.predict_proba(X_te)[:, 1]
34 model_name = "XGBoost"
35 proba = xgb_proba
36except ImportError:
37 from sklearn.ensemble import GradientBoostingClassifier
38 model = GradientBoostingClassifier(n_estimators=100, max_depth=3, random_state=42)
39 model.fit(X_tr, y_tr)
40 proba = model.predict_proba(X_te)[:, 1]
41 model_name = "GBM (sklearn)"
42
43# โ”€โ”€โ”€ Evaluation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
44print(f"\n{'Metric':<25} | {'Logistic':>10} | {model_name:>12}")
45print("-" * 52)
46print(f"{'AUC-ROC':<25} | {roc_auc_score(y_te, lr_proba):>10.4f} | {roc_auc_score(y_te, proba):>12.4f}")
47print(f"{'AUC-PR':<25} | {average_precision_score(y_te, lr_proba):>10.4f} | {average_precision_score(y_te, proba):>12.4f}")
48print(f"{'Brier score':<25} | {brier_score_loss(y_te, lr_proba):>10.4f} | {brier_score_loss(y_te, proba):>12.4f}")
49
50# โ”€โ”€โ”€ Threshold tuning for business objective โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
51from sklearn.metrics import precision_recall_curve, f1_score
52
53precisions, recalls, thresholds = precision_recall_curve(y_te, proba)
54
55# Business scenario: calling churners costs $2/call, saving a churner is worth $50
56# Expected value = recall * $50 * base_rate - precision_mistake_rate * $2
57# Simplified: find threshold maximizing F2 (recall-weighted)
58f2_scores = [(1+4) * p * r / (4*p + r + 1e-8) for p, r in zip(precisions, recalls)]
59best_thresh_idx = np.argmax(f2_scores)
60best_threshold = thresholds[best_thresh_idx]
61
62y_pred = (proba >= best_threshold).astype(int)
63print(f"\nOptimal threshold (F2): {best_threshold:.3f}")
64print(f" Precision: {precisions[best_thresh_idx]:.3f}")
65print(f" Recall: {recalls[best_thresh_idx]:.3f}")
66print(f" Users flagged: {y_pred.sum()} / {len(y_pred)} ({y_pred.mean()*100:.1f}%)")

Complete churn prediction pipeline. Key design choices: (1) scale_pos_weight for class imbalance โ€” tells XGBoost to weight positive (churner) examples ~11.5ร— higher; (2) AUC-PR instead of AUC-ROC for the imbalanced 8% churn rate; (3) threshold tuning with F2 score (ฮฒ=2, recall-weighted) to match a business scenario where missing a churner is more costly than false alarms; (4) Brier score to measure probability calibration quality.

Worked Interview Problems

3 problems

Problem

You're an Applied Scientist at a subscription product. Leadership wants to proactively identify users likely to cancel in the next 30 days to offer them a retention incentive. Walk through your complete approach.

Solution

1

Problem framing: label = 1 if user cancels within 30 days of today, 0 otherwise. Prediction horizon: 30 days. Positive class rate: ~5% monthly churn. Constraint: the retention team can contact 2,000 users/week (precision matters, not just recall).

2

Feature engineering: (1) Engagement features โ€” login frequency (7/14/30 day), feature usage rate, content consumed, time since last session. (2) Account features โ€” subscription tier, tenure, payment method, past support contacts. (3) Trend features โ€” is engagement increasing or decreasing? ratio of (last 7d logins / prior 7d logins). (4) NLP features โ€” sentiment of last support ticket (if any).

3

Model selection: logistic regression baseline (interpretable, helps understand feature importance direction). XGBoost main model (handles non-linear interactions, robust to skewed distributions). Train/val/test split: last 3 months as test (temporal split โ€” never shuffle for time-based labels), previous 12 months for training, 1 month validation.

4

Evaluation: AUC-PR (imbalanced), precision at recall=30% (what fraction of contacted users actually churn?), lift chart (are we targeting the right people?). Business metric: expected ROI = (precision ร— 2000 ร— 50retentionvalue)โˆ’(2000ร—50 retention value) - (2000 ร— 2 contact cost). Threshold: set based on this ROI calculation.

5

SHAP analysis: compute SHAP values to understand model drivers. Report to PM: 'The top predictors are declining login frequency (-0.08 SHAP), no mobile app use in 14 days (-0.06), and 2+ support contacts in 30 days (+0.05 toward churn).' This makes the model actionable โ€” the retention team knows what to address.

Answer

Full pipeline: frame (30-day binary label, 5% churn rate, 2k contact limit) โ†’ feature engineering (engagement trends, account health, NLP) โ†’ temporal train/test split โ†’ XGBoost with scale_pos_weight โ†’ evaluate on AUC-PR + precision at recall threshold โ†’ tune threshold for business ROI โ†’ SHAP for actionable interpretation.

Common Mistakes

!

Using accuracy as the evaluation metric for imbalanced datasets. With 92% negatives, a model that predicts all negative gets 92% accuracy. AUC-ROC, AUC-PR, or F1 at a meaningful threshold are the right metrics.

!

Not doing a temporal train/test split for time-series-like labels (e.g., 30-day churn). If you randomly shuffle and split, future information leaks into training data. Always train on earlier data and test on later data.

!

Reporting AUC-ROC for imbalanced data as if it's meaningful. For 1% positive rate, a random model gets AUC=0.50 but AUC-PR=0.01 (= base rate). AUC-ROC can look fine (0.85) even when precision at any practical threshold is terrible. Use AUC-PR for imbalanced problems.

!

Using target encoding without a holdout or smoothing โ€” target encoding (mean outcome per category) leaks label information if computed on the training set and used in the same model. Use out-of-fold target encoding: for fold k, compute encoding using all other folds.

!

Treating feature importance from a trained model as a causal explanation. Feature importance says 'the model uses this feature to predict.' It doesn't mean 'this feature causes the outcome.' Always distinguish predictive from causal claims.

Only works in the Electron app

<webview> is an Electron-only tag. Run npm run electron:dev to use this.

25:00Focus
0

25 min focus ยท 5 min break ยท long break every 4 sessions

The Ultimate Grind