Introduction
In our last lesson, you designed a comprehensive, multi-layered evaluation workflow. You learned how to create a funnel that uses fast, code-based checks, powerful LLM-as-a-judge assessments, and strategic human review to generate key performance metrics. Now, you might have a dashboard showing that your new fine-tuned model (Model A) achieves an 88% score on your custom evaluation set, while the base model (Model B) scores 85%.
This brings us to a critical question for any top-tier engineer: Is that 3% difference a genuine improvement, or is it just statistical noise from the specific evaluation set you used? Answering this question with confidence is the core of this lesson. Today, we will address the learning outcome: Apply statistical tests to determine if observed performance differences between models are significant. We will explore how to move beyond simple score comparisons to make rigorous, data-driven decisions about which model is truly better.
1. Why We Need Statistical Tests: Uncertainty in Evaluation
Any performance metric you calculate—whether it's accuracy on MMLU, an average score from your LLM-as-a-judge, or ROUGE—is an estimate based on a finite sample of data. If you were to use a different evaluation set, you'd likely get slightly different scores. Statistical tests are the formal tools we use to quantify this uncertainty and determine the probability that an observed difference is real and not just a result of random chance.

The central concepts are:
- Null Hypothesis (): This is the default assumption that there is no real difference between the models. For example, : "The performance of Model A is the same as the performance of Model B."
- p-value: This is the probability of observing the performance difference you saw (or an even larger one) if the null hypothesis were true.
- Significance Level (): This is a threshold you set beforehand, typically 0.05 (or 5%). If the p-value is less than , you reject the null hypothesis and conclude that the difference is statistically significant.
2. Comparing Two Classifiers on a Single Test Set: McNemar's Test
A very common scenario is comparing the classification accuracy of two models (e.g., your new model vs. the production model) on the same, fixed test set.
While you could use a simple test of proportions, it has a high false-positive rate because it incorrectly assumes the models' errors are independent. A much better choice is McNemar's test, a non-parametric test designed for paired nominal data. It's powerful because it focuses only on the instances where the two models disagree.
To get started, we need to build a 2x2 contingency table that compares the predictions of the two models.
Comparing Two Models with the McNemar Test
The article 'Model evaluation, model selection, and algorithm selection in machine learning' by Sebastian Raschka provides an excellent explanation of McNemar's test. It clearly lays out the contingency table and the intuition behind the test.
Read the section titled 'Comparing Two Models with the McNemar Test'. Focus on understanding the structure of the 2x2 contingency table (cells A, B, C, and D) and how it's different from a standard confusion matrix. Pay close attention to why cells B and C (the disagreements) are the only ones that matter for the test.
As you read, you saw that the contingency table looks like this:

McNemar's test focuses on cells B and C. The null hypothesis is that the two models have the same error rate, meaning we'd expect the number of disagreements to be split evenly (). The test statistic, which follows a (chi-squared) distribution, is calculated as:
This is the continuity-corrected version, which is more accurate for smaller sample sizes.
Practical Implementation
You don't need to calculate this by hand. Libraries like mlxtend make it straightforward.
The article 'Validating your Machine Learning Model' by Maarten Grootendorst shows a concise Python implementation of McNemar's test.
Review the code block under the 'McNemar’s Test' section. Notice how it takes the ground truth labels and the predicted labels from two models to compute the p-value directly.
Here's an example of what that looks like:
import numpy as np
from mlxtend.evaluate import mcnemar_table, mcnemar
# Ground truth labels
y_target = np.array([0, 1, 0, 1, 0, 1])
# Predictions from Model A
y_model_A = np.array([0, 0, 0, 1, 0, 1]) # 4/6 correct (66.7%)
# Predictions from Model B
y_model_B = np.array([0, 1, 1, 1, 1, 1]) # 5/6 correct (83.3%)
# Is Model B significantly better?
tb = mcnemar_table(y_target=y_target,
y_model1=y_model_A,
y_model2=y_model_B)
# tb will be: [[1, 3], [0, 2]]
# Cell B = 0 (A correct, B wrong)
# Cell C = 2 (A wrong, B correct)
chi2, p = mcnemar(ary=tb, corrected=True)
print(f"p-value: {p:.4f}")
# p-value: 1.0000
# With alpha = 0.05, since p > 0.05, we fail to reject the null hypothesis.
# Conclusion: There is not enough evidence to say Model B is significantly better.
3. Comparing Models with Any Metric: The Power of Bootstrapping
What if your metric isn't simple accuracy? What if you're comparing average ROUGE scores, latency, or scores from an LLM-as-a-judge? For these, a wonderfully intuitive and powerful non-parametric technique is bootstrapping.
The core idea is to simulate re-running your evaluation many times by resampling your existing evaluation set. This allows you to build an empirical distribution of your performance metric and see how much it varies.
The 'Bootstrapping Main Ideas' video from StatQuest with Josh Starmer is one of the best explanations of this concept available. It builds the idea from the ground up with clear visuals.
Watch from the beginning to 07:39. Focus on these key steps: The Problem: We have one sample and want to know the variability of a statistic (like the mean). Resampling with Replacement: How to create a 'bootstrapped dataset' from your original dataset. Building a Distribution: Repeating this thousands of times to create a histogram of the statistic. Creating a Confidence Interval: Using this distribution to find the range where the true value likely lies (e.g., the 95% confidence interval).
To compare two models, we apply bootstrapping to the difference in their performance:
- Get Paired Scores: For each of the examples in your evaluation set, you have a score for Model A () and a score for Model B (). These could be binary (1/0 for correct/incorrect), or continuous (e.g., a 1-5 judge score).
- Calculate Differences: Create a list of performance differences: for all .
- Bootstrap the Differences:
- Create a bootstrapped sample by drawing differences with replacement from your list of differences.
- Calculate the average of this bootstrapped sample.
- Repeat this process 10,000 times.
- Analyze the Distribution: You now have 10,000 average differences.
- Sort them and find the 2.5th percentile and the 97.5th percentile. This range is your 95% confidence interval for the mean difference.
- Conclusion: If this confidence interval does not contain 0, you can conclude the performance difference is statistically significant.
Bootstrapping is a go-to tool for a top engineer because it can be applied to virtually any evaluation metric, making it incredibly versatile.
4. Comparing Algorithms Across Multiple Folds
Sometimes you don't just compare two static models; you want to compare two learning algorithms. This involves accounting for the variance that comes from training on different subsets of data, which is exactly what k-fold cross-validation does.
In this scenario, you train and evaluate both algorithms on the exact same folds. This gives you two lists of performance scores, one for each algorithm. Because these scores are paired (each pair comes from the same fold), we can use a paired statistical test.
Wilcoxon Signed-Rank Test
Since the scores from folds might not be normally distributed (especially if is small), a non-parametric test is safer. The Wilcoxon signed-rank test is an excellent choice. It's like a paired t-test but works on ranks instead of the raw scores, so it doesn't assume a normal distribution. It not only looks at the sign of the difference (like the Sign Test) but also considers the magnitude of the differences.
Non-parametric tests - Sign test, Wilcoxon signed rank, Mann-Whitney
The video 'Non-parametric tests' from zedstatistics provides a great explanation of the Wilcoxon signed-rank test and its advantages over the simpler Sign Test.
Watch the segment from 11:24 to 20:05. Don't worry about memorizing the formulas. Focus on the core idea: how the test incorporates the rank of the differences to gain more statistical power than just counting positive/negative differences.
A practical implementation is straightforward using scipy.
from scipy.stats import wilcoxon
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn.model_selection import cross_val_score, KFold
X, y = load_iris(return_X_y=True)
# Prepare models
model1 = LogisticRegression(max_iter=200)
model2 = SVC()
# Use the same CV splits for both models to ensure pairing
kf = KFold(n_splits=10, random_state=42, shuffle=True)
# Get the 10 scores for each model
results_model1 = cross_val_score(model1, X, y, cv=kf)
results_model2 = cross_val_score(model2, X, y, cv=kf)
# Apply the Wilcoxon signed-rank test
stat, p = wilcoxon(results_model1, results_model2)
print(f"Model 1 scores: {[f'{x:.2f}' for x in results_model1]}")
print(f"Model 2 scores: {[f'{x:.2f}' for x in results_model2]}")
print(f"\np-value: {p:.4f}")
if p < 0.05:
print("\nThe difference is statistically significant.")
else:
print("\nThe difference is not statistically significant.")
Test your understanding!
You've run an LLM-as-a-judge evaluation on 1,000 test cases for two models, Llama-3-70B and Mistral-Large. You perform bootstrapping on the difference in their judge scores (Llama3 - Mistral) and get a 95% confidence interval of [0.15, 0.45]. What can you conclude and why?
What if the confidence interval was [-0.05, 0.25] instead?
Show answer
-
For the interval
[0.15, 0.45]: You can conclude that Llama-3-70B is statistically significantly better than Mistral-Large according to your judge. The reason is that the entire confidence interval is above zero. This means we are 95% confident that the true average difference in scores is positive, in favor of Llama-3. -
For the interval
[-0.05, 0.25]: You cannot conclude that there is a significant difference between the models. The interval contains zero, which means it's plausible that the true average difference is zero (i.e., the null hypothesis is true). While the interval is mostly positive, the possibility that the true difference is zero or even slightly negative cannot be ruled out at the 95% confidence level.
5. Advanced Topic: Handling Multiple Comparisons
A final word of caution. If you are comparing more than two models (e.g., GPT-4o vs. Claude 3 Opus vs. Llama 3 70B), running pairwise tests between all pairs inflates your chance of finding a significant result just by chance (a Type I error).
The standard approach is a two-step process:
- Omnibus Test: First, run a single test like Cochran's Q (a generalization of McNemar's test for >2 models) to see if there is any significant difference within the group of models.
- Post-Hoc Tests: If the omnibus test is significant, you can then proceed with pairwise tests (like McNemar's), but you must apply a correction to your significance level . The simplest is the Bonferroni correction, where you divide your original by the number of comparisons (e.g., for 3 models, you have 3 comparisons, so your new is ).
You can read more about this in the "Multiple Hypotheses Testing" section of the Raschka article (LINK, section 4) if you wish to dive deeper. For now, simply being aware of this problem is a sign of statistical maturity.
Conclusion
You are now equipped to move beyond "vibe checks" and simple score comparisons. By applying the right statistical test, you can rigorously defend your choice of one model over another and make decisions with quantified confidence.
Key Takeaways:
- Significance is Key: A difference in metrics is not meaningful until it is shown to be statistically significant. Always state your null hypothesis and significance level.
- Choose the Right Tool:
- For comparing two classifiers on a single test set, use McNemar's test.
- For any metric (especially custom or non-normal ones like judge scores), bootstrapping is your most versatile and intuitive tool.
- For comparing algorithms across k-fold CV scores, use a paired non-parametric test like the Wilcoxon signed-rank test.
- Beware Multiple Comparisons: When comparing more than two models, use an omnibus test followed by corrected post-hoc tests to avoid false positives.
What's Next?
This lesson concludes our module on LLM Evaluation. You have mastered a full-stack workflow: curating datasets, implementing scalable LLM-as-a-judge systems, designing comprehensive evaluation pipelines, and now, rigorously interpreting the results.
The next logical step is to use these evaluation insights to improve your model's core capabilities. In our next module, Adapting LLMs: Fine-Tuning and Alignment, we will begin by exploring the different ways you can adapt a pre-trained model to your specific domain. We'll start by explaining the crucial differences between full fine-tuning and modern, highly efficient techniques like PEFT and LoRA. The high-quality datasets of model successes and failures you curate through your evaluation workflow are the essential fuel for this powerful adaptation process.