05 — Evaluate Your Model

Comparing Model Runs#

TestingStatistical 1 min read
◆ The PatternIs this improvement real? Statistical tests for comparing model performance across runs

Model A got 0.87 F1. Model B got 0.89. Is that difference real or just noise? You need statistical tests on paired cross-validation scores to answer confidently. Without them, you're just reading tea leaves.

t = (μA − μB) / SE(μA − μB)
Paired t-test on k-fold scores — the simplest comparison. Assumes normality of differences.
McNemar: χ² = (b − c)² / (b + c)
McNemar’s test — compares errors on the same test set. b,c = discordant predictions.
// Interactive — two model score distributions, see p-value
Model A mean0.85
Model B mean0.88
p-value—
Rule of thumb: If p < 0.05, the difference is statistically significant — but also check effect size. A significant p-value with tiny effect size means the improvement is real but may not matter.
Pattern bridge: Two runs differ by 0.4 points: signal or seed? Experiment tracking keeps the runs to compare, and traders ask the same of two strategies through risk-adjusted performance.
Worked example

Model B beats model A by about one point on each of five CV folds, while the folds themselves vary from 0.78 to 0.90. An unpaired t-test sees the fold-to-fold variation and gives p = 0.736. A paired test compares the models fold by fold and gives p = 0.00015. When two models are scored on the same splits, compare them on the same splits.

Where it misleads
  • Folds share training data, so even the paired test is too optimistic; the corrected resampled t-test adjusts for it (Nadeau & Bengio 2003).
  • Across many datasets, use rank-based tests such as Wilcoxon or Friedman (Demšar 2006).
  • A significant difference can still be too small to matter; report the size of the gain.
In code
from scipy.stats import ttest_ind, ttest_rel
a = np.array([0.81, 0.86, 0.78, 0.90, 0.84])        # model A, five CV folds
b = a + np.array([0.012, 0.009, 0.011, 0.008, 0.010])   # B is a little better on every fold
p_unpaired = ttest_ind(b, a).pvalue
p_paired = ttest_rel(b, a).pvalue
Python, in your browser — edit the code and run it again

Assumes import numpy as np and import pandas as pd (and SciPy where imported). Each snippet simulates or makes up its own data, as the comments say.

The same idea with a library
# Python — comparing two models
from scipy.stats import ttest_rel, wilcoxon

# Paired t-test on k-fold scores
scores_a = cross_val_score(model_a, X, y, cv=10)
scores_b = cross_val_score(model_b, X, y, cv=10)
t_stat, p_val = ttest_rel(scores_a, scores_b)

# Non-parametric alternative
w_stat, p_val = wilcoxon(scores_a, scores_b)
print(f"p = {p_val:.4f}")
Sources
  • T. G. Dietterich, “Approximate Statistical Tests for Comparing Supervised Classification Learning Algorithms”, Neural Computation 10(7), 1998
  • C. Nadeau & Y. Bengio, “Inference for the Generalization Error”, Machine Learning 52(3), 2003
  • J. Demšar, “Statistical Comparisons of Classifiers over Multiple Data Sets”, Journal of Machine Learning Research 7, 2006

Last reviewed October 2026 · How this site checks its content · Report a mistake

Linked from 3
← Previous
Cross-Validation Done Right
Open in the full reader, with the topic sidebar