"""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