github_sync / A9 /aggregate_results.py
Amol Kaushik
report
cb9b728
"""
A9 Results Aggregation Script
Aggregates all cross-validation results from the cv_results folders
and produces summary tables for the A9_Report.ipynb notebook.
Usage:
python aggregate_results.py
Output:
- results_summary.csv: All experiment results in one table
- results_summary.json: Same data in JSON format
- Prints summary to console
"""
import os
import json
import re
import pandas as pd
from pathlib import Path
# Result directories
RESULT_DIRS = {
'different_models': 'cv_results_different_models',
'conv1d_variants': 'cv_results_conv1D_variants',
'optimizer': 'cv_results_optimizer',
'loss_optimizer': 'cv_results_loss_optimizer',
}
def parse_summary_txt(filepath):
"""Parse summary.txt file to extract metrics."""
results = []
with open(filepath, 'r') as f:
content = f.read()
# Find all model sections with their metrics
# Pattern matches lines like " Optimizer: SGD, Loss: MSE" or just model names
sections = re.split(r'\n(?=[A-Z_]+(?:_[A-Z]+)*\n-{10,})', content)
for section in sections:
if 'Val RMSE:' in section or 'Test RMSE:' in section:
lines = section.strip().split('\n')
model_name = lines[0].strip() if lines else 'unknown'
# Skip separator lines
if model_name.startswith('-'):
continue
row = {'model': model_name.lower()}
# Extract metrics using regex
val_rmse = re.search(r'Val RMSE:\s*([\d.]+)\s*±\s*([\d.]+)', section)
val_mae = re.search(r'Val MAE:\s*([\d.]+)\s*±\s*([\d.]+)', section)
val_r2 = re.search(r'Val R²:\s*([\d.]+)\s*±\s*([\d.]+)', section)
test_rmse = re.search(r'Test RMSE:\s*([\d.]+)', section)
test_mae = re.search(r'Test MAE:\s*([\d.]+)', section)
test_r2 = re.search(r'Test R²:\s*([\d.]+)', section)
best_fold = re.search(r'Best Fold:\s*(\d+)', section)
if val_rmse:
row['val_rmse_mean'] = float(val_rmse.group(1))
row['val_rmse_std'] = float(val_rmse.group(2))
if val_mae:
row['val_mae_mean'] = float(val_mae.group(1))
row['val_mae_std'] = float(val_mae.group(2))
if val_r2:
row['val_r2_mean'] = float(val_r2.group(1))
row['val_r2_std'] = float(val_r2.group(2))
if test_rmse:
row['test_rmse'] = float(test_rmse.group(1))
if test_mae:
row['test_mae'] = float(test_mae.group(1))
if test_r2:
row['test_r2'] = float(test_r2.group(1))
if best_fold:
row['best_fold'] = int(best_fold.group(1))
# Only add if we found some metrics
if len(row) > 1:
results.append(row)
return results
def parse_summary_header(filepath):
"""Parse the header summary section of summary.txt."""
results = []
with open(filepath, 'r') as f:
content = f.read()
# Pattern 1: Standard format (DENSE:, CONV1D:, CONV1D_V3: etc.)
# Fixed to properly capture model names with underscores and numbers
pattern1 = r'([A-Z][A-Z0-9_]*):\s*\n\s*Best Fold:\s*(\d+)\s*\n\s*Val RMSE:\s*([\d.]+)\s*±\s*([\d.]+)\s*\n\s*Val MAE:\s*([\d.]+)\s*±\s*([\d.]+)\s*\n\s*Val R²:\s*([\d.]+)\s*±\s*([\d.]+)(?:\s*\n\s*Test RMSE:\s*([\d.]+))?(?:\s*\n\s*Test MAE:\s*([\d.]+))?(?:\s*\n\s*Test R²:\s*([\d.]+))?'
# Pattern 2: Optimizer format (Optimizer: SGD, etc.)
pattern2 = r'Optimizer:\s*([A-Z]+)(?:,\s*Loss:\s*([A-Z]+))?\s*\n\s*Best Fold:\s*(\d+)\s*\n\s*Val RMSE:\s*([\d.]+)\s*±\s*([\d.]+)\s*\n\s*Val MAE:\s*([\d.]+)\s*±\s*([\d.]+)\s*\n\s*Val R²:\s*([\d.]+)\s*±\s*([\d.]+)(?:\s*\n\s*Test RMSE:\s*([\d.]+))?(?:\s*\n\s*Test MAE:\s*([\d.]+))?(?:\s*\n\s*Test R²:\s*([\d.]+))?'
seen_models = set()
# Try pattern 1
matches = re.finditer(pattern1, content, re.MULTILINE)
for match in matches:
model_name = match.group(1).lower()
# Skip detailed sections (those that start with model name and have "-----" after)
# We only want the summary sections at the top
pos = match.start()
next_line_start = content.find('\n', match.end()) + 1
if next_line_start < len(content):
next_line = content[next_line_start:next_line_start+50]
if '---' in next_line:
continue
# Skip duplicates
if model_name in seen_models:
continue
seen_models.add(model_name)
row = {
'model': model_name,
'best_fold': int(match.group(2)),
'val_rmse_mean': float(match.group(3)),
'val_rmse_std': float(match.group(4)),
'val_mae_mean': float(match.group(5)),
'val_mae_std': float(match.group(6)),
'val_r2_mean': float(match.group(7)),
'val_r2_std': float(match.group(8)),
}
if match.group(9):
row['test_rmse'] = float(match.group(9))
if match.group(10):
row['test_mae'] = float(match.group(10))
if match.group(11):
row['test_r2'] = float(match.group(11))
results.append(row)
# Try pattern 2 for optimizer/loss variants
matches = re.finditer(pattern2, content, re.MULTILINE)
for match in matches:
optimizer = match.group(1).lower()
loss = match.group(2).lower() if match.group(2) else 'mse'
model_name = f"conv1d_v3_{optimizer}_{loss}"
# Skip duplicates
if model_name in seen_models:
continue
seen_models.add(model_name)
row = {
'model': model_name,
'best_fold': int(match.group(3)),
'val_rmse_mean': float(match.group(4)),
'val_rmse_std': float(match.group(5)),
'val_mae_mean': float(match.group(6)),
'val_mae_std': float(match.group(7)),
'val_r2_mean': float(match.group(8)),
'val_r2_std': float(match.group(9)),
}
if match.group(10):
row['test_rmse'] = float(match.group(10))
if match.group(11):
row['test_mae'] = float(match.group(11))
if match.group(12):
row['test_r2'] = float(match.group(12))
results.append(row)
return results
def main():
script_dir = Path(__file__).parent
os.chdir(script_dir)
all_results = []
print("=" * 60)
print("A9 Results Aggregation")
print("=" * 60)
# Load results from each experiment directory by parsing summary.txt
for exp_name, dir_name in RESULT_DIRS.items():
summary_path = Path(dir_name) / 'summary.txt'
print(f"\nLoading from {summary_path}...")
if not summary_path.exists():
print(f" Warning: {summary_path} not found")
continue
results = parse_summary_header(summary_path)
print(f" Found {len(results)} model results")
for row in results:
row['experiment'] = exp_name
all_results.append(row)
if not all_results:
print("\nNo results found!")
return
# Create DataFrame
df = pd.DataFrame(all_results)
# Reorder columns
cols = ['experiment', 'model', 'best_fold', 'val_r2_mean', 'val_r2_std',
'val_rmse_mean', 'val_rmse_std', 'val_mae_mean', 'val_mae_std',
'test_r2', 'test_rmse', 'test_mae']
df = df[[c for c in cols if c in df.columns]]
# Sort by test_r2 descending, then val_r2_mean
df['sort_key'] = df['test_r2'].fillna(df['val_r2_mean'])
df = df.sort_values('sort_key', ascending=False).drop('sort_key', axis=1)
# Save to CSV
df.to_csv('results_summary.csv', index=False)
print(f"\nSaved to results_summary.csv ({len(df)} rows)")
# Save to JSON
df.to_json('results_summary.json', orient='records', indent=2)
print(f"Saved to results_summary.json")
# Print summary tables
print("\n" + "=" * 60)
print("SUMMARY: All Experiments Ranked by R²")
print("=" * 60)
print(df.to_string(index=False, float_format=lambda x: f'{x:.4f}' if pd.notna(x) else 'N/A'))
# Best model
print("\n" + "=" * 60)
print("CHAMPION MODEL")
print("=" * 60)
best = df.iloc[0]
print(f"Model: {best['model']}")
print(f"Experiment: {best['experiment']}")
if pd.notna(best.get('val_r2_mean')):
print(f"Validation R²: {best['val_r2_mean']:.4f} ± {best['val_r2_std']:.4f}")
print(f"Validation RMSE: {best['val_rmse_mean']:.4f} ± {best['val_rmse_std']:.4f}")
if pd.notna(best.get('test_r2')):
print(f"Test R²: {best['test_r2']:.4f}")
print(f"Test RMSE: {best['test_rmse']:.4f}")
# Dead ends (R² < 0.85)
print("\n" + "=" * 60)
print("DEAD ENDS (R² < 0.85)")
print("=" * 60)
r2_col = 'test_r2' if 'test_r2' in df.columns else 'val_r2_mean'
dead_ends = df[df[r2_col] < 0.85]
if len(dead_ends) > 0:
print(dead_ends[['experiment', 'model', 'val_r2_mean', 'test_r2']].to_string(index=False))
else:
print("None found")
# Group summaries
print("\n" + "=" * 60)
print("EXPERIMENT SUMMARIES")
print("=" * 60)
for exp_name in RESULT_DIRS.keys():
exp_df = df[df['experiment'] == exp_name]
if len(exp_df) > 0:
print(f"\n{exp_name.upper()}:")
print(exp_df[['model', 'val_r2_mean', 'val_rmse_mean', 'test_r2', 'test_rmse']].to_string(index=False))
if __name__ == '__main__':
main()