Machine LearningFeatures and Support Vector Machines › Day 170

Hands-on lab — Day 170: Feature Scaling and Encoding

Commands

Setup

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

Run

.venv/bin/python examples/scaling_encoding_lib.py

Test

./tests/run_tests.sh

File tree

examples/scaling_encoding_lib.py
examples/test_scaling_encoding_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/scaling_encoding_lib.py
starter/test_scaling_encoding_lib.py
tests/run_tests.sh
troubleshooting.md

Lab README

Lab 170: Feature Scaling and Categorical Encoding from Scratch

Lesson

Purpose

Build robust feature preprocessing transformations from scratch: implement StandardScaler, outlier-resilient RobustScaler, and leak-free Out-of-Fold Smoothed Target Encoding in pure NumPy.

Learning objectives

  1. Implement Z-Score Standardization (StandardScaler) with zero-mean and unit-variance.
  2. Implement Median and Interquartile Range scaling (RobustScaler) for outlier robustness.
  3. Formulate and implement leak-free Out-of-Fold Smoothed Target Encoding with Bayesian shrinkage.
  4. Compare One-Hot Encoding, Ordinal Encoding, and Target Encoding trade-offs.
  5. Benchmark model sensitivity to feature scaling across Linear Models, SVMs, and Decision Trees.

Prerequisites

  • Linear and Logistic Regression (Days 148–155).
  • Cross-Validation fundamentals (Day 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-170-feature-scaling-and-encoding/
├── README.md
├── metadata.yml
├── requirements/
│   └── requirements.txt
├── starter/
│   ├── scaling_encoding_lib.py
│   └── test_scaling_encoding_lib.py
├── examples/
│   ├── scaling_encoding_lib.py
│   └── test_scaling_encoding_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/scaling_encoding_lib.py

What the commands do

  • StandardScalerScratch().fit_transform(X) standardizes numerical features.
  • RobustScalerScratch().fit_transform(X) robustly centers using median and IQR.
  • out_of_fold_target_encode(cats, y) computes leak-free smoothed category stats.

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 Box-Cox and Yeo-Johnson Power Transformers from scratch to normalize skewed distributions.
  2. Implement CatBoost-style online ordered target encoding to eliminate out-of-fold permutation variance.
  3. Build a sparse MaxAbsScaler for memory-efficient scaling of large TF-IDF matrices.
  • Previous lab: ../day-169-support-vector-machines/
  • Next lab: ../day-171-feature-engineering/

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

- **StandardScaler output properties** strictly satisfy empirical mean `0.0` and empirical standard deviation `1.0` along all axes.
- **RobustScaler center property** strictly satisfies empirical median `0.0`.
- **Smoothed Target Encoding** satisfies `S_c = (n_c * mean_c + m * global_mean) / (n_c + m)`.

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-170-feature-scaling-and-encoding
plugins: cov-7.1.0, anyio-4.14.2
collecting ... collected 3 items

examples/test_scaling_encoding_lib.py::test_standard_scaler_mean_and_variance PASSED [ 33%]
examples/test_scaling_encoding_lib.py::test_robust_scaler_outlier_resilience PASSED [ 66%]
examples/test_scaling_encoding_lib.py::test_oof_target_encoding_leak_free PASSED [100%]

============================== 3 passed in 0.81s ===============================

measured-values.txt

Feature Scaling & Encoding Measurements on California Housing (n=500, d=8):
Feature 0 (MedInc: Median Income) Raw vs Standardized:
  Raw Mean: 3.2637, Raw Std: 1.8282
  Standardized Mean: -4.2633e-17, Standardized Std: 1.0000
Target Encoding Invariant:
  Out-of-Fold target values computed strictly on training folds with empirical Bayesian smoothing.

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-170-feature-scaling-and-encoding
plugins: cov-7.1.0, anyio-4.14.2
collecting ... collected 2 items

starter/test_scaling_encoding_lib.py::test_standard_scaler_stub PASSED   [ 50%]
starter/test_scaling_encoding_lib.py::test_target_encode_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: StandardScaler and Target Encoding mathematical 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/scaling_encoding_lib.py (3165 bytes)
"""
Feature Scaling and Encoding reference library implementation.
"""
import numpy as np
from sklearn.model_selection import KFold


class StandardScalerScratch:
    """
    Standardize features: z = (x - mu) / sigma
    """
    def __init__(self):
        self.mean_ = None
        self.scale_ = None

    def fit(self, X: np.ndarray):
        X = np.asarray(X, dtype=float)
        self.mean_ = np.mean(X, axis=0)
        self.scale_ = np.std(X, axis=0)
        # Avoid division by zero for constant features
        self.scale_ = np.where(self.scale_ < 1e-9, 1.0, self.scale_)
        return self

    def transform(self, X: np.ndarray) -> np.ndarray:
        X = np.asarray(X, dtype=float)
        return (X - self.mean_) / self.scale_

    def fit_transform(self, X: np.ndarray) -> np.ndarray:
        return self.fit(X).transform(X)


class RobustScalerScratch:
    """
    Robust scaling using median and Interquartile Range (IQR = Q75 - Q25):
    z = (x - median) / IQR
    """
    def __init__(self):
        self.center_ = None
        self.scale_ = None

    def fit(self, X: np.ndarray):
        X = np.asarray(X, dtype=float)
        self.center_ = np.median(X, axis=0)
        q25 = np.percentile(X, 25, axis=0)
        q75 = np.percentile(X, 75, axis=0)
        iqr = q75 - q25
        self.scale_ = np.where(iqr < 1e-9, 1.0, iqr)
        return self

    def transform(self, X: np.ndarray) -> np.ndarray:
        X = np.asarray(X, dtype=float)
        return (X - self.center_) / self.scale_

    def fit_transform(self, X: np.ndarray) -> np.ndarray:
        return self.fit(X).transform(X)


def out_of_fold_target_encode(
    categories: np.ndarray, target: np.ndarray, cv: int = 5, smoothing: float = 10.0, random_state: int = 42
) -> np.ndarray:
    """
    Compute leak-free Out-of-Fold smoothed target encoding:
    S_c = (n_c * mean_c + smoothing * global_mean) / (n_c + smoothing)
    """
    categories = np.asarray(categories)
    target = np.asarray(target, dtype=float)
    n_samples = len(categories)
    encoded = np.zeros(n_samples, dtype=float)
    
    kf = KFold(n_splits=cv, shuffle=True, random_state=random_state)
    
    for train_idx, val_idx in kf.split(categories, target):
        cat_tr, y_tr = categories[train_idx], target[train_idx]
        cat_va = categories[val_idx]
        
        global_mean = np.mean(y_tr)
        
        # Compute category statistics on training fold ONLY
        unique_cats, counts = np.unique(cat_tr, return_counts=True)
        cat_sums = {c: np.sum(y_tr[cat_tr == c]) for c in unique_cats}
        cat_counts = dict(zip(unique_cats, counts))
        
        # Apply smoothed formula to validation fold
        val_encoded = np.zeros(len(cat_va))
        for i, c in enumerate(cat_va):
            if c in cat_counts:
                n_c = cat_counts[c]
                sum_c = cat_sums[c]
                val_encoded[i] = (sum_c + smoothing * global_mean) / (n_c + smoothing)
            else:
                # Unseen category gets global training mean
                val_encoded[i] = global_mean
                
        encoded[val_idx] = val_encoded
        
    return encoded
examples/test_scaling_encoding_lib.py (1351 bytes)
"""
Tests for reference feature scaling and encoding.
"""
import pytest
import numpy as np
import scaling_encoding_lib as se


def test_standard_scaler_mean_and_variance():
    rng = np.random.default_rng(42)
    X = rng.normal(loc=10.0, scale=3.0, size=(1000, 3))
    
    scaler = se.StandardScalerScratch()
    X_std = scaler.fit_transform(X)
    
    assert np.allclose(np.mean(X_std, axis=0), 0.0, atol=1e-7)
    assert np.allclose(np.std(X_std, axis=0), 1.0, atol=1e-7)


def test_robust_scaler_outlier_resilience():
    # Data with extreme outliers
    X = np.array([[1.0], [2.0], [3.0], [4.0], [5.0], [10000.0]])
    
    scaler = se.RobustScalerScratch()
    X_rob = scaler.fit_transform(X)
    
    # Median is (3+4)/2 = 3.5 -> centered around 0
    assert np.isclose(np.median(X_rob, axis=0)[0], 0.0, atol=1e-7)


def test_oof_target_encoding_leak_free():
    # 3 categories: High (target=1.0), Low (target=0.0), Mix (target=0.5)
    cats = np.array(["High"] * 50 + ["Low"] * 50 + ["Mix"] * 50)
    target = np.array([1.0] * 50 + [0.0] * 50 + [0.0] * 25 + [1.0] * 25)
    
    encoded = se.out_of_fold_target_encode(cats, target, cv=5, smoothing=5.0)
    
    # "High" encoded values should be significantly higher than "Low"
    assert np.mean(encoded[:50]) > 0.80
    assert np.mean(encoded[50:100]) < 0.20
    assert len(encoded) == 150
metadata.yml (797 bytes)
lesson_id: D170
day: 170
kind: feature-engineering-primitives
languages:
  - python
setup_commands:
  - python3 -m venv .venv
  - .venv/bin/pip install -r requirements/requirements.txt
run_commands:
  - .venv/bin/python examples/scaling_encoding_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 StandardScaler, RobustScaler, and out-of-fold smoothed Target Encoding.
requirements/requirements.txt (61 bytes)
numpy==2.5.2
scikit-learn==1.9.0
pytest==9.1.1
scipy==1.15.2
starter/scaling_encoding_lib.py (1254 bytes)
"""
Feature Scaling and Encoding starter library.
"""
import numpy as np


class StandardScalerScratch:
    """Standardize features by removing mean and scaling to unit variance."""
    def __init__(self):
        self.mean_ = None
        self.scale_ = None

    def fit(self, X: np.ndarray):
        raise NotImplementedError("Implement fit")

    def transform(self, X: np.ndarray) -> np.ndarray:
        raise NotImplementedError("Implement transform")

    def fit_transform(self, X: np.ndarray) -> np.ndarray:
        return self.fit(X).transform(X)


class RobustScalerScratch:
    """Scale features using statistics robust to outliers (Median and IQR)."""
    def __init__(self):
        self.center_ = None
        self.scale_ = None

    def fit(self, X: np.ndarray):
        raise NotImplementedError("Implement fit")

    def transform(self, X: np.ndarray) -> np.ndarray:
        raise NotImplementedError("Implement transform")


def out_of_fold_target_encode(categories: np.ndarray, target: np.ndarray, cv: int = 5, smoothing: float = 10.0, random_state: int = 42) -> np.ndarray:
    """Compute leak-free smoothed out-of-fold target encoding for a categorical column."""
    raise NotImplementedError("Implement out_of_fold_target_encode")
starter/test_scaling_encoding_lib.py (453 bytes)
"""
Tests for starter feature scaling and encoding.
"""
import pytest
import numpy as np
import scaling_encoding_lib as se


def test_standard_scaler_stub():
    scaler = se.StandardScalerScratch()
    with pytest.raises(NotImplementedError):
        scaler.fit(np.array([[1.0, 2.0], [3.0, 4.0]]))


def test_target_encode_stub():
    with pytest.raises(NotImplementedError):
        se.out_of_fold_target_encode(np.array(["A", "B"]), np.array([1, 0]))
tests/run_tests.sh (2592 bytes)
#!/usr/bin/env bash
# Day 170 lab harness: "Feature Scaling and Encoding"
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
import scaling_encoding_lib as se

# StandardScaler Zero-Mean Unit-Variance Invariant
X = np.array([[1.0, 100.0], [2.0, 200.0], [3.0, 300.0]])
scaler = se.StandardScalerScratch()
X_s = scaler.fit_transform(X)
assert np.allclose(np.mean(X_s, axis=0), 0.0), "Mean must be zero"
assert np.allclose(np.std(X_s, axis=0), 1.0), "Variance must be 1.0"

# OOF Target Encoding Bounds
cats = np.array(["A", "A", "B", "B", "C", "C"] * 10)
y = np.array([1, 1, 0, 0, 1, 0] * 10)
enc = se.out_of_fold_target_encode(cats, y, cv=3, smoothing=2.0)
assert len(enc) == 60
assert np.all((enc >= 0.0) & (enc <= 1.0))

print("MATH_OK")
PYEOF
)

if [ "$MATH_CHECK" = "MATH_OK" ]; then
  ok "StandardScaler and Target Encoding mathematical 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 170

Common Issues

1. Target Leakage in Mean Target Encoding

  • Symptom: Target encoding feature gives 99% training score but fails catastrophically on test data.
  • Cause: Calculating target means across the entire dataset without cross-validation splits.
  • Fix: Always compute target encodings strictly out-of-fold (OOF) across $K$ folds with smoothing parameter $m$.

2. Dimension Explosion with High-Cardinality One-Hot Encoding

  • Symptom: Memory exhausted when one-hot encoding a ZipCode feature with 40,000 unique values.
  • Cause: One-hot encoding creates 40,000 sparse columns, triggering compute starvation.
  • Fix: Use Target Encoding, Frequency Encoding, or Feature Hashing for high-cardinality nominal features ($k > 15$).

Security notes

Security and Privacy Notes for Day 170

  • Privacy Inversion in Target Encoding: High-cardinality target encodings on small rare categories ($n_c = 1$) can reveal exact individual target labels. Enforce minimum sample thresholds ($n_c \ge 5$) or additive differential privacy noise.
  • Local Sandbox: All scalers and encoders run locally on CPU.