Metrics of Clustering

Loading Packages

[ ]:
import warnings
warnings.filterwarnings('ignore')

import scanpy as sc
import pandas as pd
from sklearn.metrics import adjusted_rand_score, adjusted_mutual_info_score, normalized_mutual_info_score
import numpy as np
import matplotlib.pyplot as plt

Loading Results

[7]:
adata_results_all = {}

Noise_Combination = ['Noise_Combination_1', 'Noise_Combination_2', 'Noise_Combination_3', 'Noise_Combination_4']

for Combination in Noise_Combination:
    adata = sc.read_h5ad(f'../../{Combination}.h5ad')
    adata_results_all[Combination] = adata
[8]:
adata_results_all
[8]:
{'Noise_Combination_1': AnnData object with n_obs × n_vars = 4800 × 0
     obs: 'Ground Truth', 'noise_level', 'SpaGCN', 'SpatialGlue', 'MultiMAP', 'STAGATE', 'Modality1', 'Modality2', 'SpatialCOC', 'MultiVI', 'COSMOS'
     obsm: 'COSMOS', 'Modality1', 'Modality2', 'MultiMAP', 'MultiVI', 'STAGATE', 'SpatialCOC', 'SpatialGlue', 'spatial',
 'Noise_Combination_2': AnnData object with n_obs × n_vars = 4800 × 0
     obs: 'Ground Truth', 'noise_level', 'SpaGCN', 'SpatialGlue', 'MultiMAP', 'STAGATE', 'Modality1', 'Modality2', 'SpatialCOC', 'MultiVI', 'COSMOS'
     obsm: 'COSMOS', 'Modality1', 'Modality2', 'MultiMAP', 'MultiVI', 'STAGATE', 'SpatialCOC', 'SpatialGlue', 'spatial',
 'Noise_Combination_3': AnnData object with n_obs × n_vars = 4800 × 0
     obs: 'Ground Truth', 'noise_level', 'SpaGCN', 'SpatialGlue', 'MultiMAP', 'STAGATE', 'Modality1', 'Modality2', 'SpatialCOC', 'MultiVI', 'COSMOS'
     obsm: 'COSMOS', 'Modality1', 'Modality2', 'MultiMAP', 'MultiVI', 'STAGATE', 'SpatialCOC', 'SpatialGlue', 'spatial',
 'Noise_Combination_4': AnnData object with n_obs × n_vars = 4800 × 0
     obs: 'Ground Truth', 'noise_level', 'SpaGCN', 'SpatialGlue', 'MultiMAP', 'STAGATE', 'Modality1', 'Modality2', 'SpatialCOC', 'MultiVI', 'COSMOS'
     obsm: 'COSMOS', 'Modality1', 'Modality2', 'MultiMAP', 'MultiVI', 'STAGATE', 'SpatialCOC', 'SpatialGlue', 'spatial'}

Calculating Metrics

[9]:
# Define methods and metrics
methods = ['SpatialCOC', 'COSMOS', 'SpatialGlue', 'MultiVI', 'MultiMAP', 'STAGATE', 'SpaGCN', 'Modality1', 'Modality2']
metrics = ['ARI', 'AMI', 'NMI']

# Initialize an empty dictionary to store metrics for all combinations
metrics_dict = {}

# Function to calculate metrics for a given level
def calculate_metrics(level_data, methods):
    true_labels = level_data.obs['Ground Truth'].values
    level_metrics = {method: {'ARI': [], 'AMI': [], 'NMI': []} for method in methods}
    for method in methods:
        pred_labels = level_data.obs[method].values
        ari = adjusted_rand_score(true_labels, pred_labels)
        ami = adjusted_mutual_info_score(true_labels, pred_labels)
        nmi = normalized_mutual_info_score(true_labels, pred_labels)
        level_metrics[method] = {'ARI': ari, 'AMI': ami, 'NMI': nmi}
    return level_metrics

# Iterate over each combinations
for combination in Noise_Combination:
    adata = adata_results_all[combination]
    # Get unique levels
    levels = adata.obs['noise_level'].unique()
    # Initialize a dictionary to store metrics for each method
    combination_metrics = {method: {'ARI': [], 'AMI': [], 'NMI': []} for method in methods}
    # Iterate over each level
    for level in levels:
        # Get data for this level
        level_data = adata[adata.obs['noise_level'] == level]
        # Calculate metrics for this level
        level_metrics = calculate_metrics(level_data, methods)
        # Append metrics to pattern_metrics
        for method in methods:
            for metric in metrics:
                combination_metrics[method][metric].append(level_metrics[method][metric])
    # Save metrics for this pattern
    metrics_dict[combination] = combination_metrics

# Save to Excel
with pd.ExcelWriter('metrics_results.xlsx') as writer:
    for combination, metrics_data in metrics_dict.items():
        # Create a DataFrame for this combination
        rows = []
        for method in methods:
            for i, level in enumerate(levels):
                row = {
                    'Method': method,
                    'Noise_Level': level,
                    'ARI': metrics_data[method]['ARI'][i],
                    'AMI': metrics_data[method]['AMI'][i],
                    'NMI': metrics_data[method]['NMI'][i]
                }
                rows.append(row)

        df = pd.DataFrame(rows)
        sheet_name = str(combination)
        df.to_excel(writer, sheet_name=sheet_name, index=False)

print("Metrics saved to metrics_results.xlsx")
Metrics saved to metrics_results.xlsx
[5]:
# Define the metrics
metrics = ['ARI', 'AMI', 'NMI']
# Define methods and corresponding colors
methods = ['SpatialCOC', 'COSMOS', 'SpatialGlue', 'MultiVI', 'MultiMAP', 'STAGATE', 'SpaGCN']
colors = ['#D0836F', '#df871b', '#E4BE64', '#87AC9A', '#9B8169', '#8D8E99', '#F5ECBA']
# Set the y-axis range (adjust according to actual data)
y_min = -0.05
y_max = 1.05

# Set global font size and font
fontsize = 24
plt.rcParams['font.sans-serif'] = ['Arial']
plt.rcParams['font.size'] = fontsize

# Iterate over each pattern
for i, combination in enumerate(Noise_Combination):
    # Get the metrics results for this pattern
    combination_metrics = metrics_dict[combination]
    # Iterate over each metric
    for j, metric in enumerate(metrics):
        # Create a new figure
        plt.figure(figsize=(5.25, 5))

        # Calculate the medians of Modality1 and Modality2
        modality1_median = np.median(combination_metrics['Modality1'][metric])
        modality2_median = np.median(combination_metrics['Modality2'][metric])

        # Calculate the maximum of the two modality medians
        max_median = max(modality1_median, modality2_median)

        # Prepare the data for other methods
        data = [combination_metrics[method][metric] for method in methods]

        # Plot the boxplot
        bp = plt.boxplot(data, patch_artist=True,
                         boxprops=dict(linestyle='-', linewidth=1, edgecolor='black'),  # Box edges
                         medianprops=dict(linestyle='-', linewidth=1, color='black'),  # Median line
                         whiskerprops=dict(linestyle='-', linewidth=1, color='black'),  # Whiskers
                         capprops=dict(linestyle='-', linewidth=1, color='black'),  # Caps
                         widths=0.85,  # Box width
                         showfliers=False)  # Do not show outliers

        # Color the boxes
        for patch, color in zip(bp['boxes'], colors):
            patch.set_facecolor(color)

        # Plot a line representing the maximum of the two modality medians
        # Ensure this line is on top by setting a high zorder value
        plt.axhline(y=max_median, color='#FF0000', linestyle='--', linewidth=2.5, zorder=10)  # Thicker red line on top

        # Remove x-axis tick labels
        plt.xticks([])  # Remove x-axis tick labels

        # Set the unified y-axis range
        plt.ylim(y_min, y_max)

        # Add y-axis label
        plt.ylabel(metric, fontsize=fontsize)

        # Thicken the figure's border
        for spine in plt.gca().spines.values():
            spine.set_linewidth(2)

        # Add grid lines
        plt.grid(axis='y', color='gray', linestyle='--', alpha=1, zorder=-10, linewidth=1.5)

        # Add vertical dashed lines
        for x in range(1, len(methods) + 1):
            plt.axvline(x=x, color='gray', linestyle='--', alpha=1, zorder=-10, linewidth=1.5)

        # Print plotting information
        print(f"Drawing plot for {combination} - {metric}")

        # Display the figure
        plt.tight_layout(pad=0.1, rect=[0, 0, 1, 1])

        plt.savefig(f"./Combination{i+1}/Metrics_{metric}.png", dpi=500)
        plt.savefig(f"./Combination{i+1}/Metrics_{metric}.eps", dpi=500)

        plt.show()
Drawing plot for Noise_Combination_1 - ARI
../../_images/Reproduction_HLN_Augmented_Datasets_6_Metrics_Four_Combinations_8_1.png
Drawing plot for Noise_Combination_1 - AMI
../../_images/Reproduction_HLN_Augmented_Datasets_6_Metrics_Four_Combinations_8_3.png
Drawing plot for Noise_Combination_1 - NMI
../../_images/Reproduction_HLN_Augmented_Datasets_6_Metrics_Four_Combinations_8_5.png
Drawing plot for Noise_Combination_2 - ARI
../../_images/Reproduction_HLN_Augmented_Datasets_6_Metrics_Four_Combinations_8_7.png
Drawing plot for Noise_Combination_2 - AMI
../../_images/Reproduction_HLN_Augmented_Datasets_6_Metrics_Four_Combinations_8_9.png
Drawing plot for Noise_Combination_2 - NMI
../../_images/Reproduction_HLN_Augmented_Datasets_6_Metrics_Four_Combinations_8_11.png
Drawing plot for Noise_Combination_3 - ARI
../../_images/Reproduction_HLN_Augmented_Datasets_6_Metrics_Four_Combinations_8_13.png
Drawing plot for Noise_Combination_3 - AMI
../../_images/Reproduction_HLN_Augmented_Datasets_6_Metrics_Four_Combinations_8_15.png
Drawing plot for Noise_Combination_3 - NMI
../../_images/Reproduction_HLN_Augmented_Datasets_6_Metrics_Four_Combinations_8_17.png
Drawing plot for Noise_Combination_4 - ARI
../../_images/Reproduction_HLN_Augmented_Datasets_6_Metrics_Four_Combinations_8_19.png
Drawing plot for Noise_Combination_4 - AMI
../../_images/Reproduction_HLN_Augmented_Datasets_6_Metrics_Four_Combinations_8_21.png
Drawing plot for Noise_Combination_4 - NMI
../../_images/Reproduction_HLN_Augmented_Datasets_6_Metrics_Four_Combinations_8_23.png