Lisa Dunlap
restart
4862c84
"""Logic for the **Frequency Comparison** tab."""
from typing import List, Tuple, Dict, Any
import pandas as pd
from .state import app_state
# ---------------------------------------------------------------------------
# NOTE: app_state currently stores metrics under the legacy key 'model_stats'.
# During later cleanup this module will switch to 'metrics'. For now we treat
# the value as already being the new FunctionalMetrics dict.
# ---------------------------------------------------------------------------
__all__ = ["create_frequency_comparison", "create_frequency_plots"]
def create_frequency_comparison(
selected_models: List[str],
) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame, str]:
"""Create frequency comparison tables for the 3 functional metrics tables."""
if not app_state["model_stats"]:
empty_df = pd.DataFrame({"Message": ["Please load data first"]})
return empty_df, empty_df, empty_df, ""
if not selected_models:
empty_df = pd.DataFrame({"Message": ["Please select at least one model"]})
return empty_df, empty_df, empty_df, ""
# Get the functional metrics data
metrics_data = app_state["model_stats"]
# Debug: Print data structure info
print(f"DEBUG: Creating frequency comparison tables")
print(f" - Selected models: {selected_models}")
print(f" - Available keys in metrics_data: {list(metrics_data.keys())}")
if "model_cluster_scores" in metrics_data:
model_cluster_scores = metrics_data["model_cluster_scores"]
print(f" - Model cluster scores keys: {list(model_cluster_scores.keys())}")
for model in selected_models:
if model in model_cluster_scores:
clusters = model_cluster_scores[model]
print(f" - {model}: {len(clusters)} clusters")
else:
print(f" - {model}: NOT FOUND in model_cluster_scores")
if "cluster_scores" in metrics_data:
cluster_scores = metrics_data["cluster_scores"]
print(f" - Cluster scores: {len(cluster_scores)} clusters")
if "model_scores" in metrics_data:
model_scores = metrics_data["model_scores"]
print(f" - Model scores: {list(model_scores.keys())}")
# Create the three tables
model_cluster_df = create_model_cluster_table(metrics_data, selected_models)
cluster_df = create_cluster_table(metrics_data, selected_models)
model_df = create_model_table(metrics_data, selected_models)
print(f" - Created tables with rows: Model-Cluster={len(model_cluster_df)}, Cluster={len(cluster_df)}, Model={len(model_df)}")
info_text = f"**Model-Cluster Scores:** {len(model_cluster_df)} rows | **Cluster Scores:** {len(cluster_df)} rows | **Model Scores:** {len(model_df)} rows"
return model_cluster_df, cluster_df, model_df, info_text
def create_model_cluster_table(metrics_data: Dict[str, Any], selected_models: List[str]) -> pd.DataFrame:
"""Create table for model-cluster scores."""
model_cluster_scores = metrics_data.get("model_cluster_scores", {})
print(f"DEBUG: Creating model-cluster table")
print(f" - Available models in model_cluster_scores: {list(model_cluster_scores.keys())}")
print(f" - Selected models: {selected_models}")
rows = []
for model_name, clusters in model_cluster_scores.items():
if model_name not in selected_models:
print(f" - Skipping {model_name} (not in selected_models)")
continue
print(f" - Processing {model_name} with {len(clusters)} clusters")
for cluster_name, metrics in clusters.items():
# Filter out "No properties" clusters
if cluster_name == "No properties":
continue
# Basic metrics
size = metrics.get("size", 0)
proportion = metrics.get("proportion", 0) * 100 # Convert to percentage
proportion_delta = metrics.get("proportion_delta", 0) * 100 # Convert to percentage
# Quality metrics - show each metric separately
quality = metrics.get("quality", {})
quality_delta = metrics.get("quality_delta", {})
# Create base row
row = {
"Model": model_name,
"Cluster": cluster_name,
"Size": size,
"Proportion (%)": f"{proportion:.1f}",
"Proportion Delta (%)": f"{proportion_delta:.1f}",
# "Examples": len(metrics.get("examples", []))
}
# Add quality metrics for each individual metric
for metric_name, quality_val in quality.items():
row[f"Quality_{metric_name.title()}"] = f"{quality_val:.3f}"
for metric_name, delta_val in quality_delta.items():
row[f"Quality_Delta_{metric_name.title()}"] = f"{delta_val:+.3f}"
# Confidence intervals
proportion_ci = metrics.get("proportion_ci", {})
proportion_delta_ci = metrics.get("proportion_delta_ci", {})
# Significance flags
proportion_delta_significant = metrics.get("proportion_delta_significant", False)
quality_delta_significant = metrics.get("quality_delta_significant", {})
# Format confidence intervals
proportion_ci_str = format_ci(proportion_ci)
proportion_delta_ci_str = format_ci(proportion_delta_ci)
# Add confidence intervals and significance
row.update({
"Proportion CI": proportion_ci_str,
"Proportion Delta CI": proportion_delta_ci_str,
"Proportion Delta Significant": "Yes" if proportion_delta_significant else "No",
})
# Add quality delta significance for each metric
for metric_name, is_significant in quality_delta_significant.items():
row[f"Quality_Delta_{metric_name.title()}_Significant"] = "Yes" if is_significant else "No"
rows.append(row)
print(f" - Created {len(rows)} rows for model-cluster table")
return pd.DataFrame(rows)
def create_cluster_table(metrics_data: Dict[str, Any], selected_models: List[str]) -> pd.DataFrame:
"""Create table for cluster scores (aggregated across all models)."""
cluster_scores = metrics_data.get("cluster_scores", {})
print(f"DEBUG: Creating cluster table")
print(f" - Available clusters: {list(cluster_scores.keys())}")
print(f" - Number of clusters: {len(cluster_scores)}")
rows = []
for cluster_name, metrics in cluster_scores.items():
# Filter out "No properties" clusters
if cluster_name == "No properties":
continue
# Basic metrics
size = metrics.get("size", 0)
proportion = metrics.get("proportion", 0) * 100 # Convert to percentage
# Quality metrics - show each metric separately
quality = metrics.get("quality", {})
quality_delta = metrics.get("quality_delta", {})
# Create base row
row = {
"Cluster": cluster_name,
"Size": size,
"Proportion (%)": f"{proportion:.1f}",
# "Examples": len(metrics.get("examples", []))
}
# Add quality metrics for each individual metric
for metric_name, quality_val in quality.items():
row[f"Quality_{metric_name.title()}"] = f"{quality_val:.3f}"
for metric_name, delta_val in quality_delta.items():
row[f"Quality_Delta_{metric_name.title()}"] = f"{delta_val:+.3f}"
# Confidence intervals
proportion_ci = metrics.get("proportion_ci", {})
quality_ci = metrics.get("quality_ci", {})
quality_delta_ci = metrics.get("quality_delta_ci", {})
# Significance flags
quality_delta_significant = metrics.get("quality_delta_significant", {})
# Format confidence intervals
proportion_ci_str = format_ci(proportion_ci)
quality_ci_str = format_ci(quality_ci)
quality_delta_ci_str = format_ci(quality_delta_ci)
# Add confidence intervals and significance
row.update({
"Proportion CI": proportion_ci_str,
})
# Add quality CI and significance for each metric
for metric_name in quality.keys():
if metric_name in quality_ci:
ci = quality_ci[metric_name]
row[f"Quality_{metric_name.title()}_CI"] = format_ci(ci)
for metric_name in quality_delta.keys():
if metric_name in quality_delta_ci:
ci = quality_delta_ci[metric_name]
row[f"Quality_Delta_{metric_name.title()}_CI"] = format_ci(ci)
row[f"Quality_Delta_{metric_name.title()}_Significant"] = "Yes" if quality_delta_significant.get(metric_name, False) else "No"
rows.append(row)
print(f" - Created {len(rows)} rows for cluster table")
return pd.DataFrame(rows)
def create_model_table(metrics_data: Dict[str, Any], selected_models: List[str]) -> pd.DataFrame:
"""Create table for model scores (aggregated across all clusters)."""
model_scores = metrics_data.get("model_scores", {})
print(f"DEBUG: Creating model table")
print(f" - Available models in model_scores: {list(model_scores.keys())}")
print(f" - Selected models: {selected_models}")
rows = []
for model_name, metrics in model_scores.items():
# Filter by selected models
if model_name not in selected_models:
print(f" - Skipping {model_name} (not in selected_models)")
continue
print(f" - Processing {model_name}")
# Basic metrics
size = metrics.get("size", 0)
proportion = metrics.get("proportion", 0) * 100 # Convert to percentage
# Quality metrics - show each metric separately
quality = metrics.get("quality", {})
quality_delta = metrics.get("quality_delta", {})
# Create base row
row = {
"Model": model_name,
"Size": size,
# "Proportion (%)": f"{proportion:.1f}",
# "Examples": len(metrics.get("examples", []))
}
# Add quality metrics for each individual metric
for metric_name, quality_val in quality.items():
row[f"Quality_{metric_name.title()}"] = f"{quality_val:.3f}"
# for metric_name, delta_val in quality_delta.items():
# row[f"Quality_Delta_{metric_name.title()}"] = f"{delta_val:+.3f}"
# Confidence intervals
proportion_ci = metrics.get("proportion_ci", {})
quality_ci = metrics.get("quality_ci", {})
quality_delta_ci = metrics.get("quality_delta_ci", {})
# Significance flags
quality_delta_significant = metrics.get("quality_delta_significant", {})
# Format confidence intervals
proportion_ci_str = format_ci(proportion_ci)
# Add confidence intervals and significance
row.update({
"Proportion CI": proportion_ci_str,
})
# Add quality CI and significance for each metric
for metric_name in quality.keys():
if metric_name in quality_ci:
ci = quality_ci[metric_name]
row[f"Quality_{metric_name.title()}_CI"] = format_ci(ci)
# for metric_name in quality_delta.keys():
# if metric_name in quality_delta_ci:
# ci = quality_delta_ci[metric_name]
# row[f"Quality_Delta_{metric_name.title()}_CI"] = format_ci(ci)
# row[f"Quality_Delta_{metric_name.title()}_Significant"] = "Yes" if quality_delta_significant.get(metric_name, False) else "No"
rows.append(row)
print(f" - Created {len(rows)} rows for model table")
return pd.DataFrame(rows)
def format_ci(ci_dict: Dict[str, Any]) -> str:
"""Format confidence interval dictionary to string."""
if not ci_dict or not isinstance(ci_dict, dict):
return "N/A"
lower = ci_dict.get("lower")
upper = ci_dict.get("upper")
mean = ci_dict.get("mean")
if lower is not None and upper is not None:
return f"[{lower:.3f}, {upper:.3f}]"
elif mean is not None:
return f"Mean: {mean:.3f}"
else:
return "N/A"
def create_frequency_plots(*_args, **_kwargs):
"""Removed for now – kept as a stub for backward compatibility."""
return None, None