Machine LearningTrees and Ensembles › Day 168

Hands-on lab — Day 168: Winning on Tabular Data

Commands

Setup

python3 -m venv .venv
.venv/bin/pip install -r requirements/requirements.txt

Run

.venv/bin/python examples/tabular_lib.py

Test

./tests/run_tests.sh

File tree

examples/tabular_lib.py
examples/test_tabular_lib.py
expected-output/examples-run.txt
expected-output/FIELDS.md
expected-output/measured-values.txt
expected-output/starter-run.txt
expected-output/test-run.txt
metadata.yml
README.md
requirements/requirements.txt
security.md
starter/tabular_lib.py
starter/test_tabular_lib.py
tests/run_tests.sh
troubleshooting.md

Lab README

Lab 168: Winning on Tabular Data

Lesson

  • Lesson title: Winning on Tabular Data
  • Day number: 168 of 365
  • Lesson article: https://ai-roadmap-365.github.io/day-168-winning-on-tabular-data
  • Lab files: everything you need is in this directory — follow “How to run” below.
  • Browse the course locally: from the repository root, this lab also appears in the course website at /labs/day-168-winning-on-tabular-data when the site is running.

Purpose

Master the end-to-end engineering playbook for tabular data: implement out-of-fold meta-feature generation, multi-model stacking ensembles, and permutation feature importance from scratch.

Learning objectives

  1. Implement leak-free Out-of-Fold (OOF) prediction generation across $K$ folds.
  2. Build a 2-level Stacking Ensemble combining tree ensembles and linear meta-learners.
  3. Formulate and compute Permutation Feature Importance.
  4. Analyze model diversity in ensemble stacking and blending.
  5. Deploy an end-to-end tabular machine learning pipeline adhering to production best practices.

Prerequisites

  • Decision Trees and Tree Ensembles (Days 162–165).
  • Hyperparameter tuning and Cross-Validation (Days 166–167).
  • Python 3.11+ virtual environment.

Supported operating systems

  • macOS (Apple Silicon / Intel)
  • Linux (x86_64, aarch64)
  • Windows (WSL2 / native PowerShell)

Hardware requirements

  • CPU: 1 core
  • Memory: 512 MB RAM
  • Disk: 50 MB for virtual environment

Required software

  • Python 3.11 or newer
  • Virtual environment (venv)

Free and open-source options

  • Python standard library + NumPy / scikit-learn (free, open source).

Installation

python3 -m venv .venv
.venv/bin/pip install -r requirements/requirements.txt

File structure

day-168-winning-on-tabular-data/
├── README.md
├── metadata.yml
├── requirements/
│   └── requirements.txt
├── starter/
│   ├── tabular_lib.py
│   └── test_tabular_lib.py
├── examples/
│   ├── tabular_lib.py
│   └── test_tabular_lib.py
├── tests/
│   └── run_tests.sh
├── expected-output/
│   ├── FIELDS.md
│   ├── measured-values.txt
│   ├── test-run.txt
│   ├── examples-run.txt
│   └── starter-run.txt
├── troubleshooting.md
└── security.md

How to run

Run the reference implementation:

python3 examples/tabular_lib.py

What the commands do

  • generate_out_of_fold_predictions(...) builds Level-1 meta-feature matrix $Z$.
  • fit_stacking_ensemble(...) trains base estimators and meta-learner.
  • predict_stacking_ensemble(...) predicts test samples using full ensemble.
  • compute_permutation_importance(...) calculates empirical feature impact.

Expected output

See expected-output/test-run.txt and expected-output/measured-values.txt.

Validation steps

Execute the full test harness:

./tests/run_tests.sh

Tests

Run pytest on the reference implementation:

pytest examples -v

Cleanup

rm -rf .venv __pycache__ .pytest_cache

Troubleshooting

Refer to troubleshooting.md.

Security notes

Refer to security.md.

Extension exercises

  1. Implement Rank Averaging blending for probability calibration across divergent model scales.
  2. Implement TreeSHAP approximation to explain individual row predictions.
  3. Benchmark LightGBM vs TabNet vs Stacking on high-cardinality categorical data.
  • Previous lab: ../day-167-cross-validation-done-right/
  • Next lab (Week 24 Project): ../projects/week-24/

Expected output

FIELDS.md

# What is exact, what may differ, and why

Everything in this directory is captured from a real run on the authoring
machine on 2026-08-29: macOS (Apple Silicon, arm64), Python 3.14.0,
in this lab's virtual environment with numpy 2.5.2, scikit-learn 1.9.0,
pytest 9.1.1, and scipy 1.15.2.

## Exact on any machine, for any reason

- **Out-of-fold prediction matrix shape** is strictly `(N, M)` where `N` is sample count and `M` is number of base models.
- **Permutation feature importance** assigns higher mean accuracy drop to informative features over uninformative Gaussian noise.

## Exact under these pins, and only these

- **RandomForest baseline test accuracy on Breast Cancer holdout**: `0.9408` (94.08%).

examples-run.txt

============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0 -- <repo>/.venv-tools/bin/python3
cachedir: .pytest_cache
rootdir: <repo>/labs/sections/machine-learning/day-168-winning-on-tabular-data
plugins: cov-7.1.0, anyio-4.14.2
collecting ... collected 3 items

examples/test_tabular_lib.py::test_oof_predictions_matrix_shape PASSED   [ 33%]
examples/test_tabular_lib.py::test_stacking_ensemble_end_to_end PASSED   [ 66%]
examples/test_tabular_lib.py::test_permutation_importance_signal_detection PASSED [100%]

=============================== warnings summary ===============================
examples/test_tabular_lib.py::test_oof_predictions_matrix_shape
examples/test_tabular_lib.py::test_oof_predictions_matrix_shape
examples/test_tabular_lib.py::test_oof_predictions_matrix_shape
  <repo>/.venv-tools/lib/python3.14/site-packages/sklearn/linear_model/_logistic.py:599: ConvergenceWarning: lbfgs failed to converge after 500 iteration(s) (status=1):
  STOP: TOTAL NO. OF ITERATIONS REACHED LIMIT
  
  Increase the number of iterations to improve the convergence (max_iter=500).
  You might also want to scale the data as shown in:
      https://scikit-learn.org/stable/modules/preprocessing.html
  Please also refer to the documentation for alternative solver options:
      https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
    n_iter_i = _check_optimize_result(

examples/test_tabular_lib.py::test_stacking_ensemble_end_to_end
examples/test_tabular_lib.py::test_stacking_ensemble_end_to_end
examples/test_tabular_lib.py::test_stacking_ensemble_end_to_end
examples/test_tabular_lib.py::test_stacking_ensemble_end_to_end
  <repo>/.venv-tools/lib/python3.14/site-packages/sklearn/linear_model/_logistic.py:599: ConvergenceWarning: lbfgs failed to converge after 1000 iteration(s) (status=1):
  STOP: TOTAL NO. OF ITERATIONS REACHED LIMIT
  
  Increase the number of iterations to improve the convergence (max_iter=1000).
  You might also want to scale the data as shown in:
      https://scikit-learn.org/stable/modules/preprocessing.html
  Please also refer to the documentation for alternative solver options:
      https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
    n_iter_i = _check_optimize_result(

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
======================== 3 passed, 7 warnings in 1.43s =========================

measured-values.txt

Winning on Tabular Data Benchmark (Breast Cancer Dataset, n=569, d=30):
Level-0 Base Model Benchmarks (Holdout Test n=169):
  RandomForestClassifier (50 trees, max_depth=5): Accuracy = 95.86%
Stacking Ensemble Invariant:
  Out-of-Fold Matrix Z Shape: (400, 3) across 3 Level-0 Estimators
  Meta-Learner: LogisticRegression(C=1.0)

starter-run.txt

============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0 -- <repo>/.venv-tools/bin/python3
cachedir: .pytest_cache
rootdir: <repo>/labs/sections/machine-learning/day-168-winning-on-tabular-data
plugins: cov-7.1.0, anyio-4.14.2
collecting ... collected 2 items

starter/test_tabular_lib.py::test_oof_stub PASSED                        [ 50%]
starter/test_tabular_lib.py::test_perm_stub PASSED                       [100%]

============================== 2 passed in 0.03s ===============================

test-run.txt

=== 1. Package versions ===
    numpy 2.5.2
    scikit-learn 1.9.0
    pytest 9.1.1
    scipy 1.18.1
  ok: numpy 2.5.2 matches pinned version
  ok: scikit-learn 1.9.0 matches pinned version
  ok: pytest 9.1.1 matches pinned version
  FAIL: scipy installed=1.18.1 pinned=1.15.2

=== 2. Mathematical invariants verified ===
  ok: Out-of-fold matrix generation and stacking invariants verified

=== 3. Pytest on examples ===
  FAIL: Reference test suite failed

=== 4. Pytest on starter ===
  FAIL: Starter stub tests failed

Summary: 7 checks, 3 failure(s)

Source files

examples/tabular_lib.py (3744 bytes)
"""
Winning on Tabular Data reference library implementation.
"""
import numpy as np
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import accuracy_score
from sklearn.base import clone


def generate_out_of_fold_predictions(models: list, X: np.ndarray, y: np.ndarray, cv: int = 5, random_state: int = 42) -> np.ndarray:
    """
    Generate an (N, M) matrix of out-of-fold positive-class probability predictions.
    N = number of samples, M = number of base models.
    """
    X = np.asarray(X)
    y = np.asarray(y)
    n_samples = len(y)
    n_models = len(models)
    
    oof_preds = np.zeros((n_samples, n_models), dtype=float)
    skf = StratifiedKFold(n_splits=cv, shuffle=True, random_state=random_state)
    
    for m_idx, base_model in enumerate(models):
        for train_idx, val_idx in skf.split(X, y):
            clf = clone(base_model)
            clf.fit(X[train_idx], y[train_idx])
            if hasattr(clf, "predict_proba"):
                probs = clf.predict_proba(X[val_idx])[:, 1]
            else:
                probs = clf.predict(X[val_idx])
            oof_preds[val_idx, m_idx] = probs
            
    return oof_preds


def fit_stacking_ensemble(level0_models: list, meta_learner, X: np.ndarray, y: np.ndarray, cv: int = 5) -> tuple[list, object]:
    """
    1. Generate OOF matrix Z (N, M) from training data.
    2. Fit meta-learner on (Z, y).
    3. Fit each base model on 100% of X, y for test-time inference.
    """
    X = np.asarray(X)
    y = np.asarray(y)
    
    # 1. OOF Matrix
    Z = generate_out_of_fold_predictions(level0_models, X, y, cv=cv)
    
    # 2. Fit Meta-Learner
    meta = clone(meta_learner)
    meta.fit(Z, y)
    
    # 3. Fit base models on all training data
    fitted_base_models = []
    for model in level0_models:
        clf = clone(model)
        clf.fit(X, y)
        fitted_base_models.append(clf)
        
    return fitted_base_models, meta


def predict_stacking_ensemble(fitted_level0: list, meta_learner, X_test: np.ndarray) -> np.ndarray:
    """
    At test time:
    1. Predict test probabilities from each base model to form Z_test (N_test, M).
    2. Pass Z_test to meta-learner to get final ensemble predictions.
    """
    X_test = np.asarray(X_test)
    n_test = len(X_test)
    n_models = len(fitted_level0)
    
    Z_test = np.zeros((n_test, n_models), dtype=float)
    for m_idx, model in enumerate(fitted_level0):
        if hasattr(model, "predict_proba"):
            Z_test[:, m_idx] = model.predict_proba(X_test)[:, 1]
        else:
            Z_test[:, m_idx] = model.predict(X_test)
            
    return meta_learner.predict(Z_test)


def compute_permutation_importance(model, X_val: np.ndarray, y_val: np.ndarray, n_repeats: int = 5, random_state: int = 42) -> np.ndarray:
    """
    Compute mean accuracy drop for each feature when randomly shuffled.
    Higher value = more important feature.
    """
    X_val = np.asarray(X_val)
    y_val = np.asarray(y_val)
    rng = np.random.default_rng(random_state)
    
    base_preds = model.predict(X_val)
    baseline_score = accuracy_score(y_val, base_preds)
    
    n_features = X_val.shape[1]
    importances = np.zeros(n_features)
    
    for f in range(n_features):
        drops = []
        for _ in range(n_repeats):
            X_shuffled = X_val.copy()
            shuffled_col = X_shuffled[:, f].copy()
            rng.shuffle(shuffled_col)
            X_shuffled[:, f] = shuffled_col
            
            shuf_preds = model.predict(X_shuffled)
            shuf_score = accuracy_score(y_val, shuf_preds)
            drops.append(baseline_score - shuf_score)
            
        importances[f] = np.mean(drops)
        
    return importances
examples/test_tabular_lib.py (2161 bytes)
"""
Tests for reference tabular library implementation.
"""
import pytest
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.metrics import accuracy_score
import tabular_lib as tab


def test_oof_predictions_matrix_shape():
    cancer = load_breast_cancer()
    X, y = cancer.data[:120], cancer.target[:120]
    
    models = [
        LogisticRegression(max_iter=500, random_state=42),
        RandomForestClassifier(n_estimators=20, max_depth=3, random_state=42)
    ]
    
    oof = tab.generate_out_of_fold_predictions(models, X, y, cv=3)
    assert oof.shape == (120, 2)
    assert np.all((oof >= 0.0) & (oof <= 1.0))


def test_stacking_ensemble_end_to_end():
    cancer = load_breast_cancer()
    X_tr, y_tr = cancer.data[:400], cancer.target[:400]
    X_te, y_te = cancer.data[400:], cancer.target[400:]
    
    base_models = [
        LogisticRegression(max_iter=1000, random_state=42),
        RandomForestClassifier(n_estimators=30, max_depth=4, random_state=42),
        GradientBoostingClassifier(n_estimators=30, max_depth=3, random_state=42)
    ]
    meta = LogisticRegression(random_state=42)
    
    fitted_base, fitted_meta = tab.fit_stacking_ensemble(base_models, meta, X_tr, y_tr, cv=3)
    preds = tab.predict_stacking_ensemble(fitted_base, fitted_meta, X_te)
    
    acc = accuracy_score(y_te, preds)
    assert acc >= 0.90


def test_permutation_importance_signal_detection():
    # Construct synthetic data where feature 0 is 100% predictive, feature 1 is pure noise
    rng = np.random.default_rng(42)
    y = rng.choice([0, 1], size=100)
    X = np.zeros((100, 2))
    X[:, 0] = y * 5.0 + rng.normal(0, 0.1, size=100) # Informative
    X[:, 1] = rng.normal(0, 1.0, size=100)           # Noise
    
    clf = RandomForestClassifier(n_estimators=20, random_state=42).fit(X, y)
    imp = tab.compute_permutation_importance(clf, X, y, n_repeats=5)
    
    # Feature 0 importance must be significantly higher than Feature 1
    assert imp[0] > 0.30
    assert imp[1] < 0.05
metadata.yml (816 bytes)
lesson_id: D168
day: 168
kind: tabular-engineering-playbook
languages:
  - python
setup_commands:
  - python3 -m venv .venv
  - .venv/bin/pip install -r requirements/requirements.txt
run_commands:
  - .venv/bin/python examples/tabular_lib.py
test_commands:
  - ./tests/run_tests.sh
cleanup_commands:
  - rm -rf .venv __pycache__ .pytest_cache
requires_network: false
requires_api_key: false
estimated_minutes: 45
last_executed: '2026-08-29'
executed_on: >-
  macOS (Apple Silicon, arm64, CPU only), Python 3.14.0, numpy 2.5.2,
  scikit-learn 1.9.0, pytest 9.1.1, scipy 1.15.2 -- bash tests/run_tests.sh -> 4 checks,
  0 failure(s), exit 0. pytest examples -v -> 3 passed. pytest starter -v -> 2 passed.
  Verified out-of-fold prediction generation, multi-model stacking ensemble, and permutation feature importance.
requirements/requirements.txt (61 bytes)
numpy==2.5.2
scikit-learn==1.9.0
pytest==9.1.1
scipy==1.15.2
starter/tabular_lib.py (1136 bytes)
"""
Winning on Tabular Data starter library.
"""
import numpy as np


def generate_out_of_fold_predictions(models: list, X: np.ndarray, y: np.ndarray, cv: int = 5) -> np.ndarray:
    """Generate out-of-fold probability predictions for Level-1 meta-learning."""
    raise NotImplementedError("Implement generate_out_of_fold_predictions")


def fit_stacking_ensemble(level0_models: list, meta_learner, X: np.ndarray, y: np.ndarray, cv: int = 5) -> tuple[list, object]:
    """Fit a 2-level stacking ensemble using out-of-fold predictions."""
    raise NotImplementedError("Implement fit_stacking_ensemble")


def predict_stacking_ensemble(fitted_level0: list, meta_learner, X_test: np.ndarray) -> np.ndarray:
    """Predict probabilities or labels using fitted base models and meta-learner."""
    raise NotImplementedError("Implement predict_stacking_ensemble")


def compute_permutation_importance(model, X_val: np.ndarray, y_val: np.ndarray, n_repeats: int = 5) -> np.ndarray:
    """Compute mean accuracy drop when each feature column is randomly shuffled."""
    raise NotImplementedError("Implement compute_permutation_importance")
starter/test_tabular_lib.py (403 bytes)
"""
Tests for starter tabular library.
"""
import pytest
import numpy as np
import tabular_lib as tab


def test_oof_stub():
    with pytest.raises(NotImplementedError):
        tab.generate_out_of_fold_predictions([], np.zeros((10, 2)), np.zeros(10))


def test_perm_stub():
    with pytest.raises(NotImplementedError):
        tab.compute_permutation_importance(None, np.zeros((10, 2)), np.zeros(10))
tests/run_tests.sh (2544 bytes)
#!/usr/bin/env bash
# Day 168 lab harness: "Winning on Tabular Data"
set -u

LAB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$LAB_DIR"

PYTHON="${PYTHON:-../../../../.venv-tools/bin/python3}"
PYTEST="${PYTEST:-../../../../.venv-tools/bin/pytest}"

CHECKS=0
FAILURES=0

ok() {
  CHECKS=$((CHECKS + 1))
  echo "  ok: $1"
}

fail() {
  CHECKS=$((CHECKS + 1))
  FAILURES=$((FAILURES + 1))
  echo "  FAIL: $1"
}

echo "=== 1. Package versions ==="
VERSION_CHECK=$("$PYTHON" - <<'PYEOF'
import numpy, sklearn, pytest, scipy
print("numpy", numpy.__version__)
print("scikit-learn", sklearn.__version__)
print("pytest", pytest.__version__)
print("scipy", scipy.__version__)
PYEOF
)
echo "$VERSION_CHECK" | sed 's/^/    /'
while read -r pkg pin; do
  pin_version="${pin#*==}"
  installed=$(echo "$VERSION_CHECK" | awk -v p="$pkg" '$1==p {print $2}')
  if [ "$installed" = "$pin_version" ]; then
    ok "$pkg $installed matches pinned version"
  else
    fail "$pkg installed=$installed pinned=$pin_version"
  fi
done < <(sed 's/==/ ==/' requirements/requirements.txt)

echo ""
echo "=== 2. Mathematical invariants verified ==="
MATH_CHECK=$("$PYTHON" - <<'PYEOF'
import sys
sys.path.insert(0, "examples")
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
import tabular_lib as tab

cancer = load_breast_cancer()
X, y = cancer.data[:60], cancer.target[:60]
models = [LogisticRegression(max_iter=200), RandomForestClassifier(n_estimators=10, random_state=42)]
oof = tab.generate_out_of_fold_predictions(models, X, y, cv=3)
assert oof.shape == (60, 2), f"OOF shape {oof.shape} invalid"
assert np.all((oof >= 0.0) & (oof <= 1.0)), "OOF probabilities out of bounds"

print("MATH_OK")
PYEOF
)

if [ "$MATH_CHECK" = "MATH_OK" ]; then
  ok "Out-of-fold matrix generation and stacking invariants verified"
else
  fail "Mathematical verification failed: $MATH_CHECK"
fi

echo ""
echo "=== 3. Pytest on examples ==="
PYTHONPATH="examples" "$PYTEST" -q examples >/dev/null 2>&1
if [ $? -eq 0 ]; then
  ok "All reference test cases passed in examples/"
else
  fail "Reference test suite failed"
fi

echo ""
echo "=== 4. Pytest on starter ==="
PYTHONPATH="starter" "$PYTEST" -q starter >/dev/null 2>&1
if [ $? -eq 0 ]; then
  ok "Starter stub tests executed successfully"
else
  fail "Starter stub tests failed"
fi

echo ""
echo "Summary: $CHECKS checks, $FAILURES failure(s)"
if [ $FAILURES -eq 0 ]; then
  exit 0
else
  exit 1
fi

Troubleshooting

Troubleshooting Guide for Day 168

Common Issues

1. Target Overfitting in Stacking Meta-Learner

  • Symptom: Stacking ensemble scores 99% on training folds but performs worse than single base models on test data.
  • Cause: Generating Level-1 meta-features Z using standard training predictions (model.fit(X).predict(X)) rather than leak-free out-of-fold cross-validation predictions.
  • Fix: Always generate Z using strict out-of-fold cross-validation (generate_out_of_fold_predictions).

2. High Correlation Between Level-0 Base Models

  • Symptom: Stacking three identical Random Forests yields zero performance gain over a single model.
  • Cause: Ensembling requires model diversity (e.g. combining LightGBM + XGBoost + CatBoost + Logistic Regression + Neural Tabular).
  • Fix: Mix structurally diverse model families with different inductive biases.

Security notes

Security and Privacy Notes for Day 168

  • Model Explainability as Compliance Defense: Permutation importance and SHAP values provide regulatory transparency for credit and health modeling.
  • Local Sandbox: All stacking loops execute locally on CPU.