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
Drawing plot for Noise_Combination_1 - AMI
Drawing plot for Noise_Combination_1 - NMI
Drawing plot for Noise_Combination_2 - ARI
Drawing plot for Noise_Combination_2 - AMI
Drawing plot for Noise_Combination_2 - NMI
Drawing plot for Noise_Combination_3 - ARI
Drawing plot for Noise_Combination_3 - AMI
Drawing plot for Noise_Combination_3 - NMI
Drawing plot for Noise_Combination_4 - ARI
Drawing plot for Noise_Combination_4 - AMI
Drawing plot for Noise_Combination_4 - NMI