import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import scipy.stats as stats
import glob
import os
import re
import numpy as np
OUTPUT_DIR = "benches/output"
ARTIFACTS_DIR = "benches/benchmark_artifacts"
KEY_SCENARIOS_FOR_PLOTS_STATS = ["Large", "Wide", "Sparse-W"]
def load_data(artifacts_path):
all_files = glob.glob(os.path.join(artifacts_path, "*.tsv"))
if not all_files:
print(f"Warning: No TSV files found in {artifacts_path}")
return pd.DataFrame()
df_list = []
for f in all_files:
try:
df = pd.read_csv(f, sep='\t')
if 'BackendName' not in df.columns:
match = re.search(r'raw-benchmark-results-(.*?)\.tsv', os.path.basename(f))
if match:
df['BackendName'] = match.group(1)
else:
df['BackendName'] = 'unknown'
df_list.append(df)
except pd.errors.EmptyDataError:
print(f"Warning: File {f} is empty and will be skipped.")
except Exception as e:
print(f"Warning: Could not read file {f} due to error: {e}")
if not df_list:
return pd.DataFrame()
full_df = pd.concat(df_list, ignore_index=True)
return full_df
def clean_data(df):
if df.empty:
return df
for col in ['TimeSec', 'RSSDeltaKB', 'VirtDeltaKB']:
if col in df.columns:
df[col] = pd.to_numeric(df[col], errors='coerce')
if 'NumSamples' in df.columns:
df['NumSamples'] = pd.to_numeric(df['NumSamples'], errors='coerce')
if 'NumFeatures' in df.columns:
df['NumFeatures'] = pd.to_numeric(df['NumFeatures'], errors='coerce')
if 'Iteration' in df.columns: df['Iteration'] = pd.to_numeric(df['Iteration'], errors='coerce')
if 'NumComponentsOverride' in df.columns:
df['NumComponentsOverride'] = df['NumComponentsOverride'].replace('None', np.nan)
df['NumComponentsOverride'] = pd.to_numeric(df['NumComponentsOverride'], errors='coerce')
for col in ['ScenarioName', 'BackendName', 'RunType']:
if col in df.columns:
df[col] = df[col].astype(str)
return df
def aggregate_data(df):
if df.empty:
return pd.DataFrame()
group_by_cols = [
'ScenarioName', 'NumSamples', 'NumFeatures',
'BackendName', 'RunType', 'NumComponentsOverride'
]
missing_cols = [col for col in group_by_cols if col not in df.columns]
if missing_cols:
print(f"Warning: Missing columns for aggregation: {missing_cols}. Skipping aggregation.")
return pd.DataFrame()
grouped = df.groupby(group_by_cols, dropna=False)
aggregated_df = grouped[['TimeSec', 'RSSDeltaKB', 'VirtDeltaKB']].agg(['mean', 'std']).reset_index()
new_cols = []
for col_top, col_stat in aggregated_df.columns:
if col_stat: new_cols.append(f"{col_top}_{col_stat}")
else: new_cols.append(col_top)
aggregated_df.columns = new_cols
return aggregated_df
def generate_plots(df_raw):
if df_raw.empty:
print("Warning: Raw data is empty. Skipping plot generation.")
return
if not os.path.exists(OUTPUT_DIR):
os.makedirs(OUTPUT_DIR)
sns.set_theme(style="whitegrid", palette="pastel")
for scenario in KEY_SCENARIOS_FOR_PLOTS_STATS:
for run_type in ["fit", "rfit"]:
scenario_df = df_raw[(df_raw['ScenarioName'] == scenario) & (df_raw['RunType'] == run_type)]
if scenario_df.empty:
print(f"Warning: No data for scenario {scenario}, run_type {run_type}. Skipping plots.")
continue
plt.figure(figsize=(10, 6))
sns.boxplot(x='BackendName', y='TimeSec', data=scenario_df)
plt.title(f'Execution Time: {scenario} - {run_type}', fontsize=16)
plt.ylabel('Time (seconds)', fontsize=12)
plt.xlabel('Backend', fontsize=12)
plt.xticks(fontsize=10)
plt.yticks(fontsize=10)
plt.tight_layout()
plot_filename_time = os.path.join(OUTPUT_DIR, f'plot_time_{scenario}_{run_type}.png')
try:
plt.savefig(plot_filename_time, dpi=150)
print(f"Saved plot: {plot_filename_time}")
except Exception as e:
print(f"Error saving plot {plot_filename_time}: {e}")
plt.close()
plt.figure(figsize=(10, 6))
sns.boxplot(x='BackendName', y='RSSDeltaKB', data=scenario_df)
plt.title(f'RSS Memory Usage: {scenario} - {run_type}', fontsize=16)
plt.ylabel('RSS Delta (KB)', fontsize=12)
plt.xlabel('Backend', fontsize=12)
plt.xticks(fontsize=10)
plt.yticks(fontsize=10)
plt.tight_layout()
plot_filename_rss = os.path.join(OUTPUT_DIR, f'plot_rss_{scenario}_{run_type}.png')
try:
plt.savefig(plot_filename_rss, dpi=150)
print(f"Saved plot: {plot_filename_rss}")
except Exception as e:
print(f"Error saving plot {plot_filename_rss}: {e}")
plt.close()
def perform_statistical_tests(df_raw):
if df_raw.empty:
print("Warning: Raw data is empty. Skipping statistical tests.")
return pd.DataFrame()
results_list = []
for scenario in KEY_SCENARIOS_FOR_PLOTS_STATS:
for run_type in ["fit", "rfit"]:
for metric in ['TimeSec', 'RSSDeltaKB']: scenario_run_metric_df = df_raw[
(df_raw['ScenarioName'] == scenario) &
(df_raw['RunType'] == run_type) &
df_raw[metric].notna() ]
if scenario_run_metric_df.empty:
continue
backends = scenario_run_metric_df['BackendName'].unique()
if len(backends) < 2:
continue
grouped_data = [
scenario_run_metric_df[scenario_run_metric_df['BackendName'] == backend][metric]
for backend in backends
]
if any(len(group) < 2 for group in grouped_data): continue
try:
h_stat, p_value = stats.kruskal(*grouped_data)
results_list.append({
'ScenarioName': scenario,
'RunType': run_type,
'Metric': metric,
'Test': 'Kruskal-Wallis',
'H-Statistic': h_stat,
'P-Value': p_value,
'Significant_Overall': p_value < 0.05 })
except ValueError as e:
print(f"Error during Kruskal-Wallis for {scenario}/{run_type}/{metric}: {e}")
results_list.append({
'ScenarioName': scenario,
'RunType': run_type,
'Metric': metric,
'Test': 'Kruskal-Wallis',
'Error': str(e)
})
if not results_list:
return pd.DataFrame()
return pd.DataFrame(results_list)
def main():
if not os.path.exists(OUTPUT_DIR):
os.makedirs(OUTPUT_DIR)
print(f"Created output directory: {OUTPUT_DIR}")
if not os.path.exists(ARTIFACTS_DIR):
os.makedirs(ARTIFACTS_DIR) print(f"Created artifacts directory (for local testing): {ARTIFACTS_DIR}")
raw_df = load_data(ARTIFACTS_DIR)
if raw_df.empty:
print(f"No data loaded from {ARTIFACTS_DIR}. Exiting analysis script.")
return
raw_df = clean_data(raw_df)
aggregated_df = aggregate_data(raw_df.copy())
if not aggregated_df.empty:
agg_file_path = os.path.join(OUTPUT_DIR, "consolidated_benchmark_analysis.tsv")
aggregated_df.to_csv(agg_file_path, sep='\t', index=False, float_format='%.6f')
print(f"Consolidated analysis TSV saved to: {agg_file_path}")
else:
print("Aggregated data is empty. Skipping saving consolidated_benchmark_analysis.tsv.")
generate_plots(raw_df)
stats_results_df = perform_statistical_tests(raw_df)
print("\nStatistical Test Summary:")
if stats_results_df is not None and not stats_results_df.empty:
print(stats_results_df.to_string())
stats_file_path = os.path.join(OUTPUT_DIR, "statistical_analysis_summary.tsv")
stats_results_df.to_csv(stats_file_path, sep='\t', index=False, float_format='%.6f')
print(f"Statistical analysis summary saved to: {stats_file_path}")
else:
print("No statistical tests performed or no results generated.")
if __name__ == "__main__":
main()