Upload folder using huggingface_hub
Browse files
app.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
import json
|
|
|
|
| 2 |
import os
|
| 3 |
import re
|
| 4 |
import glob
|
|
@@ -736,6 +737,7 @@ def clustering_tab():
|
|
| 736 |
),
|
| 737 |
], width=4),
|
| 738 |
], className="mb-2"),
|
|
|
|
| 739 |
dbc.Row([dbc.Col(html.Div(id="plot-status", className="text-danger small mb-1"))]),
|
| 740 |
dbc.Row([
|
| 741 |
dbc.Col(
|
|
@@ -2300,9 +2302,69 @@ def populate_cross_filter_values(cross_field, model, min_val, threshold):
|
|
| 2300 |
return options, False, None
|
| 2301 |
|
| 2302 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2303 |
@app.callback(
|
| 2304 |
Output("cluster-graph", "figure"),
|
| 2305 |
Output("plot-status", "children"),
|
|
|
|
| 2306 |
Input("dd-model", "value"),
|
| 2307 |
Input("dd-min", "value"),
|
| 2308 |
Input("dd-threshold", "value"),
|
|
@@ -2317,12 +2379,13 @@ def populate_cross_filter_values(cross_field, model, min_val, threshold):
|
|
| 2317 |
def update_cluster_plot(model, min_val, threshold, field, plot_type, top_n, groups, cross_field, cross_value, era):
|
| 2318 |
empty_fig = go.Figure()
|
| 2319 |
empty_fig.update_layout(paper_bgcolor="#ffffff", plot_bgcolor="#f9fafc")
|
|
|
|
| 2320 |
if not all([model, min_val, threshold, field, plot_type]):
|
| 2321 |
-
return empty_fig, "Select all parameters above."
|
| 2322 |
|
| 2323 |
cdf = _load_cluster_csv(model, min_val, threshold)
|
| 2324 |
if cdf is None:
|
| 2325 |
-
return empty_fig, f"Data file not found for model={model} min={min_val} t={threshold}"
|
| 2326 |
|
| 2327 |
# Apply era filter based on PMID as publication year proxy
|
| 2328 |
if era:
|
|
@@ -2332,18 +2395,18 @@ def update_cluster_plot(model, min_val, threshold, field, plot_type, top_n, grou
|
|
| 2332 |
pmid_num = pd.to_numeric(cdf["key"], errors="coerce")
|
| 2333 |
cdf = cdf[(pmid_num >= lo) & (pmid_num < hi)]
|
| 2334 |
if cdf.empty:
|
| 2335 |
-
return empty_fig, f"No data found for era '{era}'."
|
| 2336 |
|
| 2337 |
# Apply cross-field filter: keep only (key, protein_index) in the selected cross-field cluster
|
| 2338 |
if cross_field and cross_value:
|
| 2339 |
keep = cdf[(cdf["field"] == cross_field) & (cdf["cluster_label"] == cross_value)][["key", "protein_index"]]
|
| 2340 |
if keep.empty:
|
| 2341 |
-
return empty_fig, f"No proteins found for {cross_field} = '{cross_value}'."
|
| 2342 |
cdf = cdf.merge(keep, on=["key", "protein_index"], how="inner")
|
| 2343 |
|
| 2344 |
field_df = cdf[cdf["field"] == field].copy()
|
| 2345 |
if field_df.empty:
|
| 2346 |
-
return empty_fig, f"No data for field '{field}' in this parameter combination."
|
| 2347 |
|
| 2348 |
# Filter by group if selected
|
| 2349 |
if groups:
|
|
@@ -2354,7 +2417,9 @@ def update_cluster_plot(model, min_val, threshold, field, plot_type, top_n, grou
|
|
| 2354 |
)
|
| 2355 |
]
|
| 2356 |
if field_df.empty:
|
| 2357 |
-
return empty_fig, f"No data for the selected group(s) in this field."
|
|
|
|
|
|
|
| 2358 |
|
| 2359 |
n = top_n or 0
|
| 2360 |
suffix_parts = []
|
|
@@ -2366,9 +2431,9 @@ def update_cluster_plot(model, min_val, threshold, field, plot_type, top_n, grou
|
|
| 2366 |
suffix_parts.append(f"{cross_field.replace('_',' ')}={cross_value}")
|
| 2367 |
suffix = f" — {' | '.join(suffix_parts)}" if suffix_parts else ""
|
| 2368 |
if plot_type == "cluster":
|
| 2369 |
-
return _umap_figure(field_df, field + suffix, top_n=n), ""
|
| 2370 |
else:
|
| 2371 |
-
return _distribution_figure(field_df, field + suffix, top_n=n), ""
|
| 2372 |
|
| 2373 |
|
| 2374 |
_CONDITION_COLS = [
|
|
|
|
| 1 |
import json
|
| 2 |
+
import math
|
| 3 |
import os
|
| 4 |
import re
|
| 5 |
import glob
|
|
|
|
| 737 |
),
|
| 738 |
], width=4),
|
| 739 |
], className="mb-2"),
|
| 740 |
+
html.Div(id="diversity-cards", className="mb-2"),
|
| 741 |
dbc.Row([dbc.Col(html.Div(id="plot-status", className="text-danger small mb-1"))]),
|
| 742 |
dbc.Row([
|
| 743 |
dbc.Col(
|
|
|
|
| 2302 |
return options, False, None
|
| 2303 |
|
| 2304 |
|
| 2305 |
+
def _compute_diversity_cards(field_df):
|
| 2306 |
+
"""Return a dbc.Row of diversity metric cards from a filtered field DataFrame."""
|
| 2307 |
+
n_total = len(field_df)
|
| 2308 |
+
if n_total == 0:
|
| 2309 |
+
return html.Div()
|
| 2310 |
+
|
| 2311 |
+
clustered = field_df[field_df["cluster_id"] != -1]
|
| 2312 |
+
n_clustered = len(clustered)
|
| 2313 |
+
coverage = n_clustered / n_total if n_total > 0 else 0.0
|
| 2314 |
+
|
| 2315 |
+
if n_clustered == 0:
|
| 2316 |
+
return _diversity_row(n_total, 0, 0, 0.0, 0.0, coverage, 0.0)
|
| 2317 |
+
|
| 2318 |
+
sizes = clustered["cluster_id"].value_counts()
|
| 2319 |
+
n_clusters = len(sizes)
|
| 2320 |
+
p = sizes / n_clustered
|
| 2321 |
+
entropy = -sum(pi * math.log(pi) for pi in p if pi > 0)
|
| 2322 |
+
effective_n = math.exp(entropy) if entropy > 0 else 1.0
|
| 2323 |
+
dominance = sizes.iloc[0] / n_clustered # sizes is sorted descending
|
| 2324 |
+
|
| 2325 |
+
return _diversity_row(n_total, n_clustered, n_clusters, entropy, effective_n, coverage, dominance)
|
| 2326 |
+
|
| 2327 |
+
|
| 2328 |
+
def _diversity_row(n_total, n_clustered, n_clusters, entropy, effective_n, coverage, dominance):
|
| 2329 |
+
def _card(label, value, tooltip, color="#1a3a5c"):
|
| 2330 |
+
return dbc.Col(
|
| 2331 |
+
dbc.Card(
|
| 2332 |
+
dbc.CardBody([
|
| 2333 |
+
html.Div(label, className="text-muted mb-1",
|
| 2334 |
+
style={"fontSize": "11px", "textTransform": "uppercase",
|
| 2335 |
+
"letterSpacing": "0.05em"}),
|
| 2336 |
+
html.Div(value, className="fw-bold",
|
| 2337 |
+
style={"fontSize": "20px", "color": color}),
|
| 2338 |
+
html.Div(tooltip, className="text-muted",
|
| 2339 |
+
style={"fontSize": "10px", "lineHeight": "1.3"}),
|
| 2340 |
+
], className="p-2 text-center"),
|
| 2341 |
+
className="border-0 shadow-sm h-100",
|
| 2342 |
+
),
|
| 2343 |
+
xs=6, sm=4, md=2,
|
| 2344 |
+
)
|
| 2345 |
+
|
| 2346 |
+
return dbc.Row([
|
| 2347 |
+
_card("Entries", f"{n_total:,}",
|
| 2348 |
+
"total in field after filters"),
|
| 2349 |
+
_card("Clusters", f"{n_clusters:,}",
|
| 2350 |
+
"distinct clusters (excl. noise)"),
|
| 2351 |
+
_card("Coverage", f"{coverage:.1%}",
|
| 2352 |
+
"assigned to a cluster (not noise)",
|
| 2353 |
+
"#198754" if coverage >= 0.7 else "#dc3545"),
|
| 2354 |
+
_card("Shannon H", f"{entropy:.2f}",
|
| 2355 |
+
"entropy of cluster-size distribution"),
|
| 2356 |
+
_card("Effective N", f"{effective_n:.1f}",
|
| 2357 |
+
"exp(H) — diversity-adjusted cluster count"),
|
| 2358 |
+
_card("Dominance", f"{dominance:.1%}",
|
| 2359 |
+
"entries in the single largest cluster",
|
| 2360 |
+
"#dc3545" if dominance >= 0.5 else "#1a3a5c"),
|
| 2361 |
+
], className="g-2")
|
| 2362 |
+
|
| 2363 |
+
|
| 2364 |
@app.callback(
|
| 2365 |
Output("cluster-graph", "figure"),
|
| 2366 |
Output("plot-status", "children"),
|
| 2367 |
+
Output("diversity-cards", "children"),
|
| 2368 |
Input("dd-model", "value"),
|
| 2369 |
Input("dd-min", "value"),
|
| 2370 |
Input("dd-threshold", "value"),
|
|
|
|
| 2379 |
def update_cluster_plot(model, min_val, threshold, field, plot_type, top_n, groups, cross_field, cross_value, era):
|
| 2380 |
empty_fig = go.Figure()
|
| 2381 |
empty_fig.update_layout(paper_bgcolor="#ffffff", plot_bgcolor="#f9fafc")
|
| 2382 |
+
no_cards = html.Div()
|
| 2383 |
if not all([model, min_val, threshold, field, plot_type]):
|
| 2384 |
+
return empty_fig, "Select all parameters above.", no_cards
|
| 2385 |
|
| 2386 |
cdf = _load_cluster_csv(model, min_val, threshold)
|
| 2387 |
if cdf is None:
|
| 2388 |
+
return empty_fig, f"Data file not found for model={model} min={min_val} t={threshold}", no_cards
|
| 2389 |
|
| 2390 |
# Apply era filter based on PMID as publication year proxy
|
| 2391 |
if era:
|
|
|
|
| 2395 |
pmid_num = pd.to_numeric(cdf["key"], errors="coerce")
|
| 2396 |
cdf = cdf[(pmid_num >= lo) & (pmid_num < hi)]
|
| 2397 |
if cdf.empty:
|
| 2398 |
+
return empty_fig, f"No data found for era '{era}'.", no_cards
|
| 2399 |
|
| 2400 |
# Apply cross-field filter: keep only (key, protein_index) in the selected cross-field cluster
|
| 2401 |
if cross_field and cross_value:
|
| 2402 |
keep = cdf[(cdf["field"] == cross_field) & (cdf["cluster_label"] == cross_value)][["key", "protein_index"]]
|
| 2403 |
if keep.empty:
|
| 2404 |
+
return empty_fig, f"No proteins found for {cross_field} = '{cross_value}'.", no_cards
|
| 2405 |
cdf = cdf.merge(keep, on=["key", "protein_index"], how="inner")
|
| 2406 |
|
| 2407 |
field_df = cdf[cdf["field"] == field].copy()
|
| 2408 |
if field_df.empty:
|
| 2409 |
+
return empty_fig, f"No data for field '{field}' in this parameter combination.", no_cards
|
| 2410 |
|
| 2411 |
# Filter by group if selected
|
| 2412 |
if groups:
|
|
|
|
| 2417 |
)
|
| 2418 |
]
|
| 2419 |
if field_df.empty:
|
| 2420 |
+
return empty_fig, f"No data for the selected group(s) in this field.", no_cards
|
| 2421 |
+
|
| 2422 |
+
diversity = _compute_diversity_cards(field_df)
|
| 2423 |
|
| 2424 |
n = top_n or 0
|
| 2425 |
suffix_parts = []
|
|
|
|
| 2431 |
suffix_parts.append(f"{cross_field.replace('_',' ')}={cross_value}")
|
| 2432 |
suffix = f" — {' | '.join(suffix_parts)}" if suffix_parts else ""
|
| 2433 |
if plot_type == "cluster":
|
| 2434 |
+
return _umap_figure(field_df, field + suffix, top_n=n), "", diversity
|
| 2435 |
else:
|
| 2436 |
+
return _distribution_figure(field_df, field + suffix, top_n=n), "", diversity
|
| 2437 |
|
| 2438 |
|
| 2439 |
_CONDITION_COLS = [
|