richiam commited on
Commit
b236ae9
·
verified ·
1 Parent(s): 2d8411c

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +73 -8
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 = [