Upload folder using huggingface_hub
Browse files
app.py
CHANGED
|
@@ -768,18 +768,30 @@ _METRIC_LABELS = {
|
|
| 768 |
}
|
| 769 |
|
| 770 |
|
| 771 |
-
|
| 772 |
-
""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 773 |
field=None/'__global__' → average over all fields.
|
| 774 |
-
min_size=None/'__all__'
|
|
|
|
| 775 |
"""
|
| 776 |
if METRICS_DF.empty:
|
| 777 |
return go.Figure()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 778 |
|
| 779 |
def _short(m):
|
| 780 |
return m.split("__")[-1] if "__" in m else m
|
| 781 |
|
| 782 |
-
src = METRICS_DF.dropna(subset=[
|
| 783 |
if field and field != "__global__":
|
| 784 |
src = src[src["field"] == field]
|
| 785 |
if src.empty:
|
|
@@ -794,7 +806,7 @@ def _build_grid_summary_figure(field=None, min_size=None):
|
|
| 794 |
|
| 795 |
agg = (
|
| 796 |
src
|
| 797 |
-
.groupby(["model", "threshold"])[
|
| 798 |
.mean()
|
| 799 |
.reset_index()
|
| 800 |
)
|
|
@@ -802,50 +814,54 @@ def _build_grid_summary_figure(field=None, min_size=None):
|
|
| 802 |
|
| 803 |
pivot = agg.pivot_table(
|
| 804 |
index="short_model", columns="threshold",
|
| 805 |
-
values=
|
| 806 |
)
|
| 807 |
-
# Sort rows
|
| 808 |
-
|
|
|
|
| 809 |
pivot = pivot.loc[row_order]
|
| 810 |
|
| 811 |
-
#
|
| 812 |
-
best_idx = agg[
|
| 813 |
best = agg.loc[best_idx]
|
| 814 |
best_short = _short(best["model"])
|
| 815 |
best_thresh = best["threshold"]
|
| 816 |
|
| 817 |
rows_list = list(pivot.index)
|
| 818 |
-
cols_list
|
|
|
|
| 819 |
text = []
|
| 820 |
for ri, row_name in enumerate(rows_list):
|
| 821 |
row_text = []
|
| 822 |
for ci, col_val in enumerate(cols_list):
|
| 823 |
v = pivot.iloc[ri, ci]
|
| 824 |
if pd.notna(v):
|
| 825 |
-
cell = f"★ {v:
|
| 826 |
else:
|
| 827 |
cell = ""
|
| 828 |
row_text.append(cell)
|
| 829 |
text.append(row_text)
|
| 830 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 831 |
fig = go.Figure(go.Heatmap(
|
| 832 |
z=pivot.values,
|
| 833 |
x=[str(c) for c in pivot.columns],
|
| 834 |
y=list(pivot.index),
|
| 835 |
-
colorscale=
|
| 836 |
text=text,
|
| 837 |
texttemplate="%{text}",
|
| 838 |
textfont=dict(size=11),
|
| 839 |
-
colorbar=dict(title=dict(text=
|
| 840 |
-
hovertemplate="Model: %{y}<br>Threshold: %{x}<br>
|
| 841 |
))
|
| 842 |
|
| 843 |
-
|
| 844 |
-
min_label = "all min-sizes" if (not min_size or min_size == "__all__") else f"min={min_size}"
|
| 845 |
-
n_models = len(pivot.index)
|
| 846 |
fig.update_layout(
|
| 847 |
title=dict(
|
| 848 |
-
text=f"Grid Search Overview —
|
| 849 |
font=dict(size=13),
|
| 850 |
),
|
| 851 |
xaxis=dict(title="Threshold", type="category", tickfont=dict(size=11)),
|
|
@@ -880,6 +896,17 @@ def grid_metrics_tab():
|
|
| 880 |
dbc.Card([
|
| 881 |
dbc.CardBody([
|
| 882 |
dbc.Row([
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 883 |
dbc.Col([
|
| 884 |
html.Label("Field", className="fw-semibold small mb-1"),
|
| 885 |
dcc.Dropdown(
|
|
@@ -2646,11 +2673,12 @@ def download_cluster_table(n_clicks, rows):
|
|
| 2646 |
|
| 2647 |
@app.callback(
|
| 2648 |
Output("metrics-summary-graph", "figure"),
|
|
|
|
| 2649 |
Input("dd-summary-field", "value"),
|
| 2650 |
Input("dd-summary-minsize", "value"),
|
| 2651 |
)
|
| 2652 |
-
def update_summary_graph(field, min_size):
|
| 2653 |
-
return _build_grid_summary_figure(field, min_size)
|
| 2654 |
|
| 2655 |
|
| 2656 |
@app.callback(
|
|
|
|
| 768 |
}
|
| 769 |
|
| 770 |
|
| 771 |
+
_OVERVIEW_METRICS = {
|
| 772 |
+
"silhouette_cosine": ("Silhouette Score", True, "RdYlGn", "Mean<br>Silhouette"),
|
| 773 |
+
"davies_bouldin": ("Davies-Bouldin Index", False, "RdYlGn_r", "Mean<br>Davies-Bouldin"),
|
| 774 |
+
"n_clusters": ("N Clusters", True, "Blues", "Mean<br>N Clusters"),
|
| 775 |
+
}
|
| 776 |
+
|
| 777 |
+
|
| 778 |
+
def _build_grid_summary_figure(field=None, min_size=None, metric="silhouette_cosine"):
|
| 779 |
+
"""Heatmap of a clustering metric per (model, threshold).
|
| 780 |
field=None/'__global__' → average over all fields.
|
| 781 |
+
min_size=None/'__all__' → average over all min community sizes.
|
| 782 |
+
metric: one of silhouette_cosine | davies_bouldin | n_clusters.
|
| 783 |
"""
|
| 784 |
if METRICS_DF.empty:
|
| 785 |
return go.Figure()
|
| 786 |
+
if metric not in _OVERVIEW_METRICS:
|
| 787 |
+
metric = "silhouette_cosine"
|
| 788 |
+
|
| 789 |
+
metric_label, higher_better, colorscale, cb_title = _OVERVIEW_METRICS[metric]
|
| 790 |
|
| 791 |
def _short(m):
|
| 792 |
return m.split("__")[-1] if "__" in m else m
|
| 793 |
|
| 794 |
+
src = METRICS_DF.dropna(subset=[metric])
|
| 795 |
if field and field != "__global__":
|
| 796 |
src = src[src["field"] == field]
|
| 797 |
if src.empty:
|
|
|
|
| 806 |
|
| 807 |
agg = (
|
| 808 |
src
|
| 809 |
+
.groupby(["model", "threshold"])[metric]
|
| 810 |
.mean()
|
| 811 |
.reset_index()
|
| 812 |
)
|
|
|
|
| 814 |
|
| 815 |
pivot = agg.pivot_table(
|
| 816 |
index="short_model", columns="threshold",
|
| 817 |
+
values=metric, aggfunc="mean",
|
| 818 |
)
|
| 819 |
+
# Sort rows so the best model appears at the top
|
| 820 |
+
best_per_row = pivot.max(axis=1) if higher_better else pivot.min(axis=1)
|
| 821 |
+
row_order = best_per_row.sort_values(ascending=higher_better).index.tolist()
|
| 822 |
pivot = pivot.loc[row_order]
|
| 823 |
|
| 824 |
+
# Identify best cell and embed star in cell text
|
| 825 |
+
best_idx = agg[metric].idxmax() if higher_better else agg[metric].idxmin()
|
| 826 |
best = agg.loc[best_idx]
|
| 827 |
best_short = _short(best["model"])
|
| 828 |
best_thresh = best["threshold"]
|
| 829 |
|
| 830 |
rows_list = list(pivot.index)
|
| 831 |
+
cols_list = list(pivot.columns)
|
| 832 |
+
fmt = ".0f" if metric == "n_clusters" else ".3f"
|
| 833 |
text = []
|
| 834 |
for ri, row_name in enumerate(rows_list):
|
| 835 |
row_text = []
|
| 836 |
for ci, col_val in enumerate(cols_list):
|
| 837 |
v = pivot.iloc[ri, ci]
|
| 838 |
if pd.notna(v):
|
| 839 |
+
cell = f"★ {v:{fmt}}" if (row_name == best_short and col_val == best_thresh) else f"{v:{fmt}}"
|
| 840 |
else:
|
| 841 |
cell = ""
|
| 842 |
row_text.append(cell)
|
| 843 |
text.append(row_text)
|
| 844 |
|
| 845 |
+
field_label = "all fields" if (not field or field == "__global__") else field.replace("_", " ").title()
|
| 846 |
+
min_label = "all min-sizes" if (not min_size or min_size == "__all__") else f"min={min_size}"
|
| 847 |
+
n_models = len(pivot.index)
|
| 848 |
+
|
| 849 |
fig = go.Figure(go.Heatmap(
|
| 850 |
z=pivot.values,
|
| 851 |
x=[str(c) for c in pivot.columns],
|
| 852 |
y=list(pivot.index),
|
| 853 |
+
colorscale=colorscale,
|
| 854 |
text=text,
|
| 855 |
texttemplate="%{text}",
|
| 856 |
textfont=dict(size=11),
|
| 857 |
+
colorbar=dict(title=dict(text=cb_title, font=dict(size=11)), thickness=14),
|
| 858 |
+
hovertemplate=f"Model: %{{y}}<br>Threshold: %{{x}}<br>{metric_label}: %{{z:{fmt}}}<extra></extra>",
|
| 859 |
))
|
| 860 |
|
| 861 |
+
better_str = "higher = better" if higher_better else "lower = better"
|
|
|
|
|
|
|
| 862 |
fig.update_layout(
|
| 863 |
title=dict(
|
| 864 |
+
text=f"Grid Search Overview — {metric_label} ({better_str}) · {field_label} · {min_label} ★ = best",
|
| 865 |
font=dict(size=13),
|
| 866 |
),
|
| 867 |
xaxis=dict(title="Threshold", type="category", tickfont=dict(size=11)),
|
|
|
|
| 896 |
dbc.Card([
|
| 897 |
dbc.CardBody([
|
| 898 |
dbc.Row([
|
| 899 |
+
dbc.Col([
|
| 900 |
+
html.Label("Metric", className="fw-semibold small mb-1"),
|
| 901 |
+
dcc.Dropdown(
|
| 902 |
+
id="dd-summary-metric",
|
| 903 |
+
options=[{"label": v[0], "value": k}
|
| 904 |
+
for k, v in _OVERVIEW_METRICS.items()],
|
| 905 |
+
value="silhouette_cosine",
|
| 906 |
+
clearable=False,
|
| 907 |
+
style={"fontSize": "13px"},
|
| 908 |
+
),
|
| 909 |
+
], width=3),
|
| 910 |
dbc.Col([
|
| 911 |
html.Label("Field", className="fw-semibold small mb-1"),
|
| 912 |
dcc.Dropdown(
|
|
|
|
| 2673 |
|
| 2674 |
@app.callback(
|
| 2675 |
Output("metrics-summary-graph", "figure"),
|
| 2676 |
+
Input("dd-summary-metric", "value"),
|
| 2677 |
Input("dd-summary-field", "value"),
|
| 2678 |
Input("dd-summary-minsize", "value"),
|
| 2679 |
)
|
| 2680 |
+
def update_summary_graph(metric, field, min_size):
|
| 2681 |
+
return _build_grid_summary_figure(field, min_size, metric)
|
| 2682 |
|
| 2683 |
|
| 2684 |
@app.callback(
|