From fa884c71c1980dd63854ca83738e3d00e596c091 Mon Sep 17 00:00:00 2001 From: Neil Horner Date: Tue, 26 May 2026 17:10:46 +0000 Subject: [PATCH] Clustering plots low data [CW-7275] --- bin/workflow_glue/hierarchical_clustering.py | 39 +++++++++++-------- bin/workflow_glue/report.py | 40 ++++++++++++-------- 2 files changed, 48 insertions(+), 31 deletions(-) diff --git a/bin/workflow_glue/hierarchical_clustering.py b/bin/workflow_glue/hierarchical_clustering.py index 0f334a0..c14ae57 100644 --- a/bin/workflow_glue/hierarchical_clustering.py +++ b/bin/workflow_glue/hierarchical_clustering.py @@ -1,10 +1,10 @@ """Hierarchical clustering heatmap plots.""" +from dataclasses import dataclass from bokeh.layouts import column as bokeh_column, row as bokeh_row from bokeh.models import ( ColorBar, ColumnDataSource, - Div, FixedTicker, HoverTool, LinearColorMapper, @@ -122,19 +122,10 @@ def _dendrogram_source(linked, orientation): return ColumnDataSource({"xs": xs, "ys": ys}) -def _empty_plot(message): - """Return a BokehPlot containing a message instead of a heatmap.""" - plot = BokehPlot() - plot._fig = Div(text=message) - return plot - - def _sample_pca(matrix, sample_names): """Project samples onto the first two principal components.""" sample_matrix = np.asarray(matrix, dtype=float).T centered = sample_matrix - sample_matrix.mean(axis=0, keepdims=True) - if centered.shape[0] < 2 or centered.shape[1] == 0: - return None u, singular_values, _ = np.linalg.svd(centered, full_matrices=False) scores = u * singular_values @@ -543,6 +534,16 @@ def pca_plot(matrix, col_order, sample_names, sample_metadata, condition_column) return pca_and_legend +@dataclass +class ClusteringResult: + """Container for hierarchical clustering results.""" + + heatmap: object | None # noqa: NT001 + pca: object | None # noqa: NT001 + distance: object | None # noqa: NT001 + error: str | None = None # noqa: NT001 + + def hierarchical( data, id_column, @@ -559,11 +560,15 @@ def hierarchical( ) if log2_matrix.shape[0] < 2 or log2_matrix.shape[1] < 2: - return _empty_plot( - "Hierarchical clustering requires at least two features and " - "two numeric sample columns." + return ClusteringResult( + heatmap=None, + pca=None, + distance=None, + error=( + "Generation of clustering plots requires at least two features " + "and two sample columns." + ) ) - log2_matrix = np.log2(log2_matrix + 1) log2_matrix, labels = _top_variable_rows(log2_matrix, labels, top_n=top_n) log2_z_matrix = _row_zscore(log2_matrix) @@ -601,4 +606,8 @@ def hierarchical( height=hm_height ) - return heatmap_plt, pca_plt, distance_plt + return ClusteringResult( + heatmap=heatmap_plt, + pca=pca_plt, + distance=distance_plt + ) diff --git a/bin/workflow_glue/report.py b/bin/workflow_glue/report.py index 32ce8f1..fb4efe7 100644 --- a/bin/workflow_glue/report.py +++ b/bin/workflow_glue/report.py @@ -1054,20 +1054,24 @@ def main(args): _create_warning_banner( "Cohort gene CPM table is missing or empty. ") else: - heatmap, pca, dist = hierarchical( + hierarchical_result = hierarchical( cohort_cpm["gene"], id_column="GENEID", samples=cohort_samples, condition_column=condition_column, top_n=150, ) - with div(cls="heatmap-table-grid"): - EZChart(heatmap, width="100%") - EZChart(pca, width="100%") - EZChart(dist, width="100%") - with div(cls="clustering-info"): - br() - clustering_info('gene') + if hierarchical_result.error is not None: + _create_warning_banner( + hierarchical_result.error, level='warning') + else: + with div(cls="heatmap-table-grid"): + EZChart(hierarchical_result.heatmap, width="100%") + EZChart(hierarchical_result.pca, width="100%") + EZChart(hierarchical_result.distance, width="100%") + with div(cls="clustering-info"): + br() + clustering_info('gene') tabs = Tabs() for contrast, table in _contrast_results( args.de_dir, "results_dge.tsv", n=20 @@ -1114,20 +1118,24 @@ def main(args): _create_warning_banner( "Cohort transcript CPM table is missing or empty.") else: - tx_heatmap, tx_pca, tx_dist = hierarchical( + hierarchical_result = hierarchical( cohort_cpm["transcript"], id_column="TXNAME", top_n=150, samples=cohort_samples, condition_column=condition_column ) - with div(cls="heatmap-table-grid"): - EZChart(tx_heatmap, width="100%") - EZChart(tx_pca, width="100%") - EZChart(tx_dist, width="100%") - with div(cls="clustering-info"): - br() - clustering_info('transcript') + if hierarchical_result.error is not None: + _create_warning_banner( + hierarchical_result.error, level='warning') + else: + with div(cls="heatmap-table-grid"): + EZChart(hierarchical_result.heatmap, width="100%") + EZChart(hierarchical_result.pca, width="100%") + EZChart(hierarchical_result.distance, width="100%") + with div(cls="clustering-info"): + br() + clustering_info('transcript') tabs = Tabs() dtu_tables = _contrast_results(