From 55c72f5eec7a0858aa15daee5289a76df6aa3210 Mon Sep 17 00:00:00 2001 From: Monimu Date: Wed, 4 Feb 2026 09:56:02 +0100 Subject: [PATCH 1/2] Change in clustering, handling NANs for calculating SI and CH handling NANs when averaging SI and CH and saving the SI and CH values of the null distribution --- jale/core/utils/hierarchical.py | 40 +++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/jale/core/utils/hierarchical.py b/jale/core/utils/hierarchical.py index 6279683..5b026ef 100644 --- a/jale/core/utils/hierarchical.py +++ b/jale/core/utils/hierarchical.py @@ -89,8 +89,10 @@ def hierarchical_clustering_pipeline( project_path=project_path, meta_name=meta_name, silhouette_scores=silhouette_scores, + null_silhouette_scores=null_silhouette_scores, silhouette_scores_z=silhouette_scores_z, calinski_harabasz_scores=calinski_harabasz_scores, + null_calinski_harabasz_scores=null_calinski_harabasz_scores, calinski_harabasz_scores_z=calinski_harabasz_scores_z, exp_separation_density=exp_separation_density, correlation_type=correlation_type, @@ -299,8 +301,8 @@ def compute_hierarchical_clustering(correlation_matrix, k, linkage_method): Z = linkage(condensed_distance, method=linkage_method) cluster_labels = fcluster(Z, k, criterion="maxclust") - # Safeguard: If only one unique cluster label, set metrics to np.nan - if len(np.unique(cluster_labels)) < 2: + # Safeguard: If number of cluster labels is smaller than k, set metrics to np.nan + if len(np.unique(cluster_labels)) < k: silhouette = np.nan calinski_harabasz = np.nan else: @@ -328,21 +330,21 @@ def compute_hc_metrics_z( def pooled_std(sample1, sample2): """Compute the pooled standard deviation of two samples.""" n1, n2 = sample1.shape[1], sample2.shape[1] - var1, var2 = np.var(sample1, axis=1, ddof=1), np.var(sample2, axis=1, ddof=1) + var1, var2 = np.nanvar(sample1, axis=1, ddof=1), np.nanvar(sample2, axis=1, ddof=1) return np.sqrt(((n1 - 1) * var1 + (n2 - 1) * var2) / (n1 + n2 - 2)) - silhouette_scores_avg = np.average(silhouette_scores, axis=1) - null_silhouette_scores_avg = np.average(null_silhouette_scores, axis=1) + silhouette_scores_avg = np.nanmean(silhouette_scores, axis=1) + null_silhouette_scores_avg = np.nanmean(null_silhouette_scores, axis=1) if use_pooled_std: silhouette_std = pooled_std(silhouette_scores, null_silhouette_scores) else: - silhouette_std = np.std(null_silhouette_scores, axis=1, ddof=1) + silhouette_std = np.nanstd(null_silhouette_scores, axis=1, ddof=1) silhouette_z = (silhouette_scores_avg - null_silhouette_scores_avg) / silhouette_std - calinski_harabasz_scores_avg = np.average(calinski_harabasz_scores, axis=1) - null_calinski_harabasz_scores_avg = np.average( + calinski_harabasz_scores_avg = np.nanmean(calinski_harabasz_scores, axis=1) + null_calinski_harabasz_scores_avg = np.nanmean( null_calinski_harabasz_scores, axis=1 ) @@ -351,7 +353,7 @@ def pooled_std(sample1, sample2): calinski_harabasz_scores, null_calinski_harabasz_scores ) else: - calinski_harabasz_std = np.std(null_calinski_harabasz_scores, axis=1, ddof=1) + calinski_harabasz_std = np.nanstd(null_calinski_harabasz_scores, axis=1, ddof=1) calinski_harabasz_z = ( calinski_harabasz_scores_avg - null_calinski_harabasz_scores_avg @@ -435,8 +437,10 @@ def save_hc_metrics( project_path, meta_name, silhouette_scores, + null_silhouette_scores, silhouette_scores_z, calinski_harabasz_scores, + null_calinski_harabasz_scores, calinski_harabasz_scores_z, exp_separation_density, correlation_type, @@ -448,9 +452,13 @@ def save_hc_metrics( "Number of Clusters": range(2, max_k + 1), "Silhouette Scores": np.average(silhouette_scores, axis=1), "Silhouette Scores SD": np.std(silhouette_scores, axis=1), + "Silhouette Scores NULL": np.nanmean(null_silhouette_scores, axis=1), + "Silhouette Scores NULL SD": np.nanstd(null_silhouette_scores, axis=1), "Silhouette Scores Z": silhouette_scores_z, "Calinski-Harabasz Scores": np.average(calinski_harabasz_scores, axis=1), "Calinski-Harabasz Scores SD": np.std(calinski_harabasz_scores, axis=1), + "Calinski-Harabasz Scores NULL": np.nanmean(null_calinski_harabasz_scores, axis=1), + "Calinski-Harabasz Scores NULL SD": np.nanstd(null_calinski_harabasz_scores, axis=1), "Calinski-Harabasz Scores Z": calinski_harabasz_scores_z, # Pad with NaN for k=2 as metrics start at k=3 "Experiment Separation Density": np.concatenate( @@ -471,6 +479,13 @@ def save_hc_metrics( header=[f"k={k}" for k in range(2, max_k + 1)], ) + pd.DataFrame(null_silhouette_scores.T).to_csv( + project_path + / f"Results/MA_Clustering/metrics/{meta_name}_NULLsilhouette_scores_{correlation_type}_hc_{linkage_method}.csv", + index=False, + header=[f"k={k}" for k in range(2, max_k + 1)], + ) + pd.DataFrame(calinski_harabasz_scores.T).to_csv( project_path / f"Results/MA_Clustering/metrics/{meta_name}_calinski_harabasz_scores_{correlation_type}_hc_{linkage_method}.csv", @@ -478,6 +493,13 @@ def save_hc_metrics( header=[f"k={k}" for k in range(2, max_k + 1)], ) + pd.DataFrame(null_calinski_harabasz_scores.T).to_csv( + project_path + / f"Results/MA_Clustering/metrics/{meta_name}_NULLcalinski_harabasz_scores_{correlation_type}_hc_{linkage_method}.csv", + index=False, + header=[f"k={k}" for k in range(2, max_k + 1)], + ) + pd.DataFrame(exp_separation_density.T).to_csv( project_path / f"Results/MA_Clustering/metrics/{meta_name}_exp_separation_density_{correlation_type}_hc_{linkage_method}.csv", From 9e094628c1760199312ea59a1ca2eeb8c4c26e0f Mon Sep 17 00:00:00 2001 From: Monimu Date: Thu, 5 Feb 2026 17:40:44 +0100 Subject: [PATCH 2/2] fixed indent errors --- jale/core/utils/hierarchical.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jale/core/utils/hierarchical.py b/jale/core/utils/hierarchical.py index 5b026ef..91db4da 100644 --- a/jale/core/utils/hierarchical.py +++ b/jale/core/utils/hierarchical.py @@ -479,7 +479,7 @@ def save_hc_metrics( header=[f"k={k}" for k in range(2, max_k + 1)], ) - pd.DataFrame(null_silhouette_scores.T).to_csv( + pd.DataFrame(null_silhouette_scores.T).to_csv( project_path / f"Results/MA_Clustering/metrics/{meta_name}_NULLsilhouette_scores_{correlation_type}_hc_{linkage_method}.csv", index=False, @@ -493,7 +493,7 @@ def save_hc_metrics( header=[f"k={k}" for k in range(2, max_k + 1)], ) - pd.DataFrame(null_calinski_harabasz_scores.T).to_csv( + pd.DataFrame(null_calinski_harabasz_scores.T).to_csv( project_path / f"Results/MA_Clustering/metrics/{meta_name}_NULLcalinski_harabasz_scores_{correlation_type}_hc_{linkage_method}.csv", index=False,