Skip to main content

Results Analysis & Aggregation

After running a benchmark, the framework automatically aggregates results across multiple runs using rigorous statistical methods. This guide explains how aggregation works, what statistics are computed, and how to interpret the results.


Overview

Why Aggregation Matters

Running a benchmark multiple times (configured via runs in your YAML) provides several benefits:

  • Variance Reduction: Single LLM responses can be noisy. Multiple runs average out random variation.
  • Confidence Intervals: With multiple data points, we can estimate the true underlying score with confidence bands.
  • Inter-Rater Reliability: When using multiple evaluators (different LLM models), we can measure agreement/disagreement.
  • Statistical Rigor: Mean, median, and standard deviation reveal the distribution of scores, not just point estimates.

Aggregation Pipeline

Raw Results (Multiple Runs)

├─ Run 1: Question A → Clarity Score: 75
├─ Run 2: Question A → Clarity Score: 78
├─ Run 3: Question A → Clarity Score: 76
└─ ...


ResultsAggregator

├─ Bootstrap Confidence Intervals
├─ Descriptive Statistics
├─ Inter-Rater Reliability (if multiple evaluators)
└─ Per-Quiz Breakdowns


Aggregated Results (JSON + TXT Reports)

Output Files

Each benchmark run produces the following files in data/results/<run-bundle>/:

1. results.json — Raw Results (All Runs)

Contains every individual score from every run, useful for detailed analysis:

[
{
"benchmark_id": "benchmark-20250512-123456",
"quiz_id": "python_quiz_1",
"question_id": "q_001",
"run_number": 1,
"metrics": [
{
"metric_name": "clarity",
"metric_version": "1.0",
"score": 78.5,
"evaluator_model": "gpt-4",
"evaluated_at": "2025-05-12T12:34:56Z",
"raw_response": "{...full LLM response...}"
}
]
},
{
"benchmark_id": "benchmark-20250512-123456",
"quiz_id": "python_quiz_1",
"question_id": "q_001",
"run_number": 2,
"metrics": [
{
"metric_name": "clarity",
"metric_version": "1.0",
"score": 81.2,
"evaluator_model": "gpt-4",
"evaluated_at": "2025-05-12T12:45:22Z",
"raw_response": "{...full LLM response...}"
}
]
}
]

2. aggregated.json — Aggregated Statistics

Contains summarized statistics across all runs:

{
"benchmark_config_name": "comprehensive_eval",
"benchmark_version": "1.0.0",
"quiz_ids": ["python_quiz_1", "python_quiz_2", "advanced_python"],
"total_runs": 5,
"aggregations": {
"clarity_gpt-4": {
"metric_name": "clarity",
"evaluator_model": "gpt-4",
"mean": 77.8,
"median": 78.5,
"std_dev": 2.34,
"min": 74.1,
"max": 82.3,
"ci_lower": 76.2,
"ci_upper": 79.4,
"per_run_scores": [75.5, 78.2, 77.1, 79.3, 78.8],
"num_runs": 5
},
"clarity_claude-3": {
"metric_name": "clarity",
"evaluator_model": "claude-3-opus-20240229",
"mean": 79.2,
"median": 79.8,
"std_dev": 1.89,
"min": 76.9,
"max": 81.5,
"ci_lower": 77.8,
"ci_upper": 80.6,
"per_run_scores": [79.1, 80.5, 78.3, 79.4, 79.8],
"num_runs": 5
}
},
"inter_rater_reliability": {
"clarity": {
"icc": 0.851,
"icc_ci_lower": 0.723,
"icc_ci_upper": 0.931,
"mad": 3.45,
"spearman_rho": 0.876,
"num_raters": 2
}
}
}

3. summary.txt — Human-Readable Report

Text summary ideal for quick review:

================================================================================
BENCHMARK RESULTS SUMMARY
================================================================================
Configuration: comprehensive_eval
Version: 1.0.0
Total Runs: 5
Quizzes Evaluated: 3

INTER-RATER RELIABILITY
--------------------------------------------------------------------------------

clarity:
ICC(2,1): 0.8510 [95% CI: 0.7230, 0.9310]
MAD (Mean Absolute Deviation): 3.45 points
Spearman rho: 0.8760
Number of Raters: 2

distractor_quality:
ICC(2,1): 0.7940 [95% CI: 0.5820, 0.9060]
MAD (Mean Absolute Deviation): 5.12 points
Spearman rho: 0.7290
Number of Raters: 2

CLARITY
--------------------------------------------------------------------------------

Evaluator: gpt-4
Mean: 77.80
Median: 78.50
Std Dev: 2.34
95% CI: [76.20, 79.40]
Min: 74.10
Max: 82.30
N: 5

Evaluator: claude-3-opus-20240229
Mean: 79.20
Median: 79.80
Std Dev: 1.89
95% CI: [77.80, 80.60]
Min: 76.90
Max: 81.50
N: 5

DISTRACTOR_QUALITY
...

Statistical Measures Explained

Descriptive Statistics

StatisticDefinitionInterpretation
MeanAverage score across all runsThe "typical" score. Use for comparing evaluators.
MedianMiddle value when sortedRobust to outliers. If median ≠ mean, check for outliers.
Std DevStandard deviationSpread of scores. Lower = more consistent. High (>5) = high variance.
Min/MaxMinimum and maximum scoresRange of observed values. Watch for extreme outliers.

95% Confidence Interval (CI)

What it means: If you re-ran the benchmark many times, 95% of the results would fall within [CI_lower, CI_upper].

Calculation: Bootstrap resampling with 10,000 iterations (seed=42 for reproducibility)

Interpretation:

  • Narrow CI (e.g., [78.0, 79.5]): Consistent scores, high precision
  • Wide CI (e.g., [70.0, 85.0]): Variable scores, low precision

Example:

Clarity (GPT-4): Mean=77.8, 95% CI=[76.2, 79.4]
→ You can be 95% confident the true mean is between 76.2 and 79.4

Inter-Rater Reliability

When you use multiple evaluators (e.g., GPT-4 and Claude), the framework automatically measures agreement between them using three complementary metrics.

Intraclass Correlation Coefficient (ICC)

Type: ICC(2,1) — two-way mixed effects, absolute agreement, single measurement

What it measures: Overall consistency in absolute scores across raters

Scale: 0 to 1 (higher is better)

  • ICC < 0.5: Poor
  • 0.5 ≤ ICC < 0.75: Moderate
  • 0.75 ≤ ICC < 0.9: Good
  • ICC ≥ 0.9: Excellent

With 95% CI: [CI_lower, CI_upper] indicates confidence in the ICC estimate

Example:

Distractor Quality across GPT-4 and Claude:
ICC = 0.794 [95% CI: 0.582, 0.906]
→ Good agreement; CI is wide, suggesting moderate precision

Mean Absolute Deviation (MAD)

What it measures: Average disagreement between raters, expressed on the original score scale (0–100)

Scale: 0–100 points

  • MAD < 3: Excellent agreement (very close ratings)
  • 3 ≤ MAD < 5: Good agreement (minor differences)
  • 5 ≤ MAD < 10: Moderate agreement (some systematic differences)
  • MAD ≥ 10: Poor agreement (large systematic differences)

Example:

Clarity metric across GPT-4 and Claude:
MAD = 3.45 points
→ On average, raters differ by 3.45 points (on 0–100 scale)

Advantages: Directly interpretable — a MAD of 5 means raters disagree by 5 points on average.

Spearman Rank Correlation (ρ)

What it measures: Whether raters rank quiz items in the same order (rank agreement)

Scale: −1 to 1

  • ρ < 0: Negative correlation (opposite ranking)
  • 0 ≤ ρ ≤ 0.5: Weak rank agreement
  • 0.5 < ρ ≤ 0.8: Moderate to good rank agreement
  • ρ > 0.8: Excellent rank agreement

Example:

Clarity metric across GPT-4 and Claude:
Spearman ρ = 0.876
→ Strong rank agreement: both raters agree on which questions are clear/unclear

Advantages: Insensitive to systematic bias (e.g., one rater always scoring 10 points higher). Focuses on relative rather than absolute agreement.


How to Interpret Results

Scenario 1: High Consistency (Good)

{
"metric_name": "clarity",
"evaluator_model": "gpt-4",
"mean": 82.5,
"std_dev": 1.2,
"ci_lower": 81.8,
"ci_upper": 83.2,
"num_runs": 5
}

Interpretation:

  • Scores are tightly clustered (std_dev = 1.2)
  • Narrow confidence interval
  • Conclusion: GPT-4's clarity assessment is reliable and repeatable

Scenario 2: High Variance (Concerning)

{
"metric_name": "alignment",
"evaluator_model": "gpt-4",
"mean": 72.3,
"std_dev": 8.9,
"ci_lower": 62.1,
"ci_upper": 82.5,
"num_runs": 5
}

Interpretation:

  • Scores vary widely (std_dev = 8.9)
  • Wide confidence interval (20.4 points)
  • Possible causes:
    • Ambiguous metric prompt
    • Complex questions with multiple interpretations
    • Stochasticity in LLM (try temperature=0.0)
  • Action: Increase runs to 10+, refine metric prompt, or simplify questions

Scenario 3: Evaluator Disagreement

"inter_rater_reliability": {
"clarity": {
"icc": 0.51,
"mad": 7.2,
"spearman_rho": 0.42,
"num_raters": 2
}
}

Interpretation:

  • GPT-4 and Claude show weak agreement (ICC = 0.51, rho = 0.42)
  • Average disagreement is significant (MAD = 7.2 points)
  • Possible causes:
    • Different evaluation styles/priorities
    • One model more lenient than other
    • Metric prompt is ambiguous
  • Action:
    • Review raw responses to understand disagreement
    • Refine metric prompt to be more specific
    • Use model-specific evaluation approaches

Analyzing Raw Results Programmatically

Load and Analyze Results

import json
import statistics
from pathlib import Path

# Load aggregated results
agg_path = "data/results/my_benchmark_20250512/aggregated.json"
with open(agg_path) as f:
agg = json.load(f)

# Print all metric statistics
for agg_key, metric_stats in agg['aggregations'].items():
print(f"\n{agg_key}:")
print(f" Mean: {metric_stats['mean']:.2f}")
print(f" Median: {metric_stats['median']:.2f}")
print(f" Std Dev: {metric_stats['std_dev']:.2f}")
print(f" 95% CI: [{metric_stats['ci_lower']:.2f}, {metric_stats['ci_upper']:.2f}]")
print(f" Runs: {metric_stats['num_runs']}")

# Check inter-rater reliability
print("\nInter-Rater Reliability:")
for metric_name, irr_metrics in agg['inter_rater_reliability'].items():
icc = irr_metrics.get('icc')
mad = irr_metrics.get('mad')
rho = irr_metrics.get('spearman_rho')
print(f"\n{metric_name}:")
if icc is not None:
ci_lower = irr_metrics.get('icc_ci_lower')
ci_upper = irr_metrics.get('icc_ci_upper')
if ci_lower and ci_upper:
print(f" ICC(2,1): {icc:.4f} [95% CI: {ci_lower:.4f}, {ci_upper:.4f}]")
else:
print(f" ICC(2,1): {icc:.4f}")
if mad is not None:
print(f" MAD: {mad:.2f} points")
if rho is not None:
print(f" Spearman rho: {rho:.4f}")

Compare Evaluators

# Extract all aggregations for a specific metric
metric_name = "clarity"
evaluator_stats = {}

for agg_key, stats in agg['aggregations'].items():
if stats['metric_name'] == metric_name:
evaluator_name = stats['evaluator_model']
evaluator_stats[evaluator_name] = {
'mean': stats['mean'],
'std_dev': stats['std_dev'],
'ci_lower': stats['ci_lower'],
'ci_upper': stats['ci_upper'],
}

# Print comparison
print(f"\n{metric_name} Comparison:")
print(f"{'Evaluator':<25} {'Mean':<10} {'Std Dev':<10} {'95% CI':<20}")
print("-" * 65)
for evaluator, stats in evaluator_stats.items():
ci_str = f"[{stats['ci_lower']:.1f}, {stats['ci_upper']:.1f}]"
print(f"{evaluator:<25} {stats['mean']:<10.2f} {stats['std_dev']:<10.2f} {ci_str:<20}")

Identify High-Variance Metrics

# Find metrics with high variance
print("Metrics with High Variance (Std Dev > 5):")
for agg_key, stats in agg['aggregations'].items():
if stats['std_dev'] > 5:
print(f"\n{stats['metric_name']} ({stats['evaluator_model']}):")
print(f" Mean: {stats['mean']:.2f}")
print(f" Std Dev: {stats['std_dev']:.2f}")
print(f" Recommendation: Increase runs or refine metric")

Best Practices for Result Interpretation

✅ DO

  • Report confidence intervals alongside means: "Clarity: 78.5 ± [76.2, 80.8]"
  • Note standard deviation: "Results were consistent (σ = 1.2)"
  • Check inter-rater reliability: "Two models showed good agreement (ICC = 0.82)"
  • Use medians for skewed data: "Median clarity score was 79 (less affected by outliers)"
  • Compare across multiple runs: "Metric became more consistent after 10 runs"
  • Document model versions: "Using GPT-4-0613, not just 'gpt-4'"

❌ DON'T

  • Report single scores without context: "Clarity: 78.5" (no confidence band)
  • Ignore high variance: "Std Dev = 12, but we'll ignore it"
  • Over-interpret single data points: "One score was 95, so clarity is perfect"
  • Compare metrics across different evaluators without noting differences
  • Trust only one evaluator without checking inter-rater reliability
  • Use results from single run (no averaging)

Exporting Results for Papers

JSON for Data Analysis

# Copy aggregated results for analysis
cp data/results/my_benchmark_20250512/aggregated.json results/my_study.json

# Load in R, Python, or Excel
# See code examples above

Summary Tables in Markdown

import json
import pandas as pd

with open('data/results/my_benchmark_20250512/aggregated.json') as f:
agg = json.load(f)

# Create DataFrame
rows = []
for agg_key, stats in agg['aggregations'].items():
rows.append({
'Metric': stats['metric_name'],
'Evaluator': stats['evaluator_model'],
'Mean': f"{stats['mean']:.2f}",
'Std Dev': f"{stats['std_dev']:.2f}",
'95% CI': f"[{stats['ci_lower']:.2f}, {stats['ci_upper']:.2f}]",
})

df = pd.DataFrame(rows)
print(df.to_markdown(index=False))

Visualizing Confidence Intervals

import matplotlib.pyplot as plt
import numpy as np

# Extract data
metrics = []
means = []
cis_lower = []
cis_upper = []

for agg_key, stats in agg['aggregations'].items():
metrics.append(f"{stats['metric_name']}\n({stats['evaluator_model'][:8]})")
means.append(stats['mean'])
cis_lower.append(stats['ci_lower'])
cis_upper.append(stats['ci_upper'])

# Plot
fig, ax = plt.subplots(figsize=(10, 6))
x = np.arange(len(metrics))
yerr = [
[means[i] - cis_lower[i] for i in range(len(means))],
[cis_upper[i] - means[i] for i in range(len(means))]
]

ax.bar(x, means, yerr=yerr, capsize=5, alpha=0.7)
ax.set_ylabel('Score (0-100)')
ax.set_title('Metric Scores with 95% Confidence Intervals')
ax.set_xticks(x)
ax.set_xticklabels(metrics, rotation=45, ha='right')
ax.set_ylim([0, 100])
plt.tight_layout()
plt.savefig('results/confidence_intervals.png', dpi=300)

Advanced: Understanding Bootstrap Confidence Intervals

The framework uses bootstrap resampling to compute confidence intervals without assuming a particular distribution.

How It Works

def bootstrap_confidence_interval(data, ci=0.95, n_bootstrap=10000):
"""
1. Original data: [75, 78, 76, 79, 77]
2. Resample 10,000 times with replacement:
- Bootstrap sample 1: [75, 79, 78, 75, 76] → mean = 76.6
- Bootstrap sample 2: [77, 78, 76, 79, 78] → mean = 77.6
- Bootstrap sample 3: [75, 75, 78, 77, 79] → mean = 76.8
- ... (9,997 more)

3. Calculate percentiles:
- 2.5th percentile = 76.2
- 97.5th percentile = 79.4

4. Result: 95% CI = [76.2, 79.4]
"""

Why Bootstrap?

  • No distribution assumptions (works with any data distribution)
  • Reproducible (seed=42 ensures same results)
  • Robust to skewed data (doesn't assume normality)

Summary

AspectKey Takeaway
Why aggregate?Reduce noise, estimate confidence, detect agreement/disagreement
Key statisticsMean, median, std dev, confidence intervals
Reliability metricsICC, MAD, and Spearman ρ measure evaluator agreement
InterpretationReport with confidence intervals; watch for high variance
For papersExport JSON for analysis, create visualizations from CI data