Ablation study on the simulation dataset

[ ]:
# import sys
# sys.path.append(r'/import/home2/yzengbj/my_project/code')
# sys.path.append(r'/import/home2/share/yqzeng/MacSGP/codes')

# import pandas as pd
# import numpy as np
# import scanpy as sc
# import anndata as ad
# import os
# import warnings
# import libpysal
# import esda
# from libpysal.weights import KNN

# warnings.filterwarnings("ignore")
# os.environ["CUDA_VISIBLE_DEVICES"] = "1"

# import simulation as construct_st
# import MacSGP

# # ============================================================
# # Experiment parameters (same as ablation_graph_rep5.py)
# # ============================================================
# BASE_PATH = '/import/home2/share/yqzeng/MacSGP/experiments/ablation'

# factor_pattern = 'gradient_y'
# factor_celltype = [0]

# n_spots = 2000
# n_genes = 2000
# n_celltypes = 2
# n_cells = 500
# n_covariates = 1
# n_loading_genes = 400
# n_groups = 4
# library_size = 1000
# prop_confound = True
# prop_min = 0.1
# prop_max = 0.9
# v = 4

# same_tau = True
# spots_effects = False
# platform_effects = False

# ref_noise_ratios = np.arange(3.0, 6.5, 0.5)  # 3.0, 3.5, ..., 6.0
# SEEDS = [2025001, 2025002, 2025003, 2025004, 2025005]  # same as ablation_graph_rep5.py

# # RAE conditions: reuse existing adata_result_l{n}.h5ad from ablation_graph_rep5.py
# # noRAE conditions: run fresh (Stage1 + Stage2 with coef_fe=0)
# RAE_CONFIGS = [
#     {'n_layers': 0, 'label': 'L0_RAE',   'existing_file': 'adata_result_l0.h5ad'},
#     {'n_layers': 4, 'label': 'L4_RAE',   'existing_file': 'adata_result_l4.h5ad'},
# ]
# NORAE_CONFIGS = [
#     {'n_layers': 0, 'label': 'L0_noRAE', 'save_file': 'adata_result_L0_noRAE.h5ad'},
#     {'n_layers': 4, 'label': 'L4_noRAE', 'save_file': 'adata_result_L4_noRAE.h5ad'},
# ]

# # ============================================================
# # Evaluation
# # ============================================================
# def evaluate_result(adata_result, k_moran=20):
#     sub = adata_result[adata_result.obsm['proportion']['celltype_0'] > 0.0].copy()

#     factor_true = sub.obsm['simulated_factor']
#     factor_pred = sub.obsm['factor']['celltype_0']
#     coords = sub.obsm['spatial']
#     w = KNN.from_array(coords, k=k_moran)
#     w.transform = 'r'
#     bi = esda.Moran_BV(factor_true, factor_pred, w, permutations=999)

#     loading_true = sub.varm['simulated_loading'][:, 0]
#     loading_pred = sub.varm['loading']['celltype_0']
#     cor = np.corrcoef(loading_true, loading_pred)[0, 1]

#     return bi.I, bi.p_sim, cor

# # ============================================================
# # Main loop
# # ============================================================
# results = []

# for ratio in ref_noise_ratios:
#     ratio_str = f"{ratio:.1f}"
#     print(f"\n{'='*60}")
#     print(f"ref_noise_ratio = {ratio_str}")
#     print(f"{'='*60}")

#     for seed_idx, seed in enumerate(SEEDS):
#         print(f"\n  *** Seed {seed_idx+1}/{len(SEEDS)}: {seed} ***")

#         exp_path = os.path.join(
#             BASE_PATH,
#             f'noise_{ratio_str}_lib{library_size}_v{v}',
#             f'seed_{seed}'
#         )
#         os.makedirs(exp_path, exist_ok=True)

#         st_path  = os.path.join(exp_path, 'adata_st.h5ad')
#         ref_path = os.path.join(exp_path, 'adata_ref.h5ad')

#         # ---- RAE conditions: load existing results from ablation_graph_rep5.py ----
#         for cfg in RAE_CONFIGS:
#             label       = cfg['label']
#             result_path = os.path.join(exp_path, cfg['existing_file'])
#             print(f"\n    --- {label} (loading from {cfg['existing_file']}) ---")

#             if not os.path.exists(result_path):
#                 print(f"      WARNING: {result_path} not found, skipping.")
#                 continue

#             adata_result = sc.read_h5ad(result_path)
#             moran_I, moran_p, loading_cor = evaluate_result(adata_result)
#             print(f"      Moran's I: {moran_I:.4f}  (p={moran_p:.4f})")
#             print(f"      Loading corr: {loading_cor:.4f}")

#             results.append({
#                 'ref_noise_ratio': ratio, 'seed': seed,
#                 'n_layers': cfg['n_layers'], 'use_rae': True, 'label': label,
#                 'moran_I': moran_I, 'moran_p': moran_p, 'loading_corr': loading_cor,
#             })

#         # ---- noRAE conditions: run Stage1+Stage2 only if any result is missing ----
#         norae_paths   = [os.path.join(exp_path, cfg['save_file']) for cfg in NORAE_CONFIGS]
#         norae_missing = [not os.path.exists(p) for p in norae_paths]

#         if any(norae_missing):
#             print("\n    Running Stage 1 (DeconvNet) for noRAE variants...")
#             adata_st  = sc.read_h5ad(st_path)
#             adata_ref = sc.read_h5ad(ref_path)
#             MacSGP.utils.Cal_Spatial_Net(adata_st, mode='KNN', k_cutoff=12)
#             adata_st_proc, adata_basis = MacSGP.utils.preprocess(
#                 adata_st, adata_ref, celltype_ref_col="celltype", n_hvg_group=1000
#             )
#             model_deconv = MacSGP.model.Model_deconv(
#                 adata_st_proc, adata_basis, n_layers=4, training_steps=10000
#             )
#             model_deconv.train(step_interval=1000)
#             adata_st_deconv = model_deconv.eval()
#         else:
#             adata_st_deconv = None
#             adata_basis     = None

#         for cfg, result_path, is_missing in zip(NORAE_CONFIGS, norae_paths, norae_missing):
#             label    = cfg['label']
#             n_layers = cfg['n_layers']
#             print(f"\n    --- {label} (n_layers={n_layers}, coef_fe=0) ---")

#             if is_missing:
#                 model = MacSGP.model.Model(
#                     adata_st_deconv, adata_basis,
#                     n_layers=n_layers, training_steps=3000, coef_fe=0
#                 )
#                 model.train(step_interval=600)
#                 adata_result = model.eval()
#                 adata_result.write_h5ad(result_path)
#             else:
#                 print(f"      Loading existing result...")
#                 adata_result = sc.read_h5ad(result_path)

#             moran_I, moran_p, loading_cor = evaluate_result(adata_result)
#             print(f"      Moran's I: {moran_I:.4f}  (p={moran_p:.4f})")
#             print(f"      Loading corr: {loading_cor:.4f}")

#             results.append({
#                 'ref_noise_ratio': ratio, 'seed': seed,
#                 'n_layers': n_layers, 'use_rae': False, 'label': label,
#                 'moran_I': moran_I, 'moran_p': moran_p, 'loading_corr': loading_cor,
#             })

# # ============================================================
# # Save raw results
# # ============================================================
# df_results = pd.DataFrame(results)
# out_csv = os.path.join(BASE_PATH, f'ablation_graph_rae_all_seeds_lib{library_size}_v{v}.csv')
# df_results.to_csv(out_csv, index=False)
# print(f"\nSaved raw results to {out_csv}")

# # ============================================================
# # Summary: mean ± std over seeds, absolute values
# # ============================================================
# df_results['moran_I_abs'] = df_results['moran_I'].abs()
# df_results['loading_corr_abs'] = df_results['loading_corr'].abs()

# df_summary = (
#     df_results
#     .groupby(['ref_noise_ratio', 'label'])
#     .agg(
#         moran_I_abs_mean=('moran_I_abs', 'mean'),
#         moran_I_abs_std=('moran_I_abs', 'std'),
#         moran_p_mean=('moran_p', 'mean'),
#         loading_corr_abs_mean=('loading_corr_abs', 'mean'),
#         loading_corr_abs_std=('loading_corr_abs', 'std'),
#     )
#     .reset_index()
# )
# summary_csv = os.path.join(BASE_PATH, f'ablation_graph_rae_summary_lib{library_size}_v{v}.csv')
# df_summary.to_csv(summary_csv, index=False)

# print(f"\n{'='*60}")
# print("Summary (|mean| ± std over 5 seeds):")
# print(f"{'='*60}")
# print(df_summary.to_string(index=False))

# # ============================================================
# # Pivot table: easy comparison across the 4 conditions
# # ============================================================
# labels = [cfg['label'] for cfg in RAE_CONFIGS + NORAE_CONFIGS]

# for metric, col_mean, col_std in [
#     ("|Moran's I|", 'moran_I_abs_mean', 'moran_I_abs_std'),
#     ("|Loading Corr|", 'loading_corr_abs_mean', 'loading_corr_abs_std'),
# ]:
#     print(f"\n{metric} — mean ± std by condition:")
#     print(f"{'='*60}")
#     pivot_mean = df_summary.pivot(index='ref_noise_ratio', columns='label', values=col_mean)
#     pivot_std  = df_summary.pivot(index='ref_noise_ratio', columns='label', values=col_std)
#     disp = pd.DataFrame(index=pivot_mean.index)
#     for lbl in labels:
#         if lbl in pivot_mean.columns:
#             disp[lbl] = [
#                 f"{m:.4f}±{s:.4f}"
#                 for m, s in zip(pivot_mean[lbl], pivot_std[lbl])
#             ]
#     # Add difference columns for the two key comparisons
#     if 'L0_RAE' in pivot_mean.columns and 'L4_RAE' in pivot_mean.columns:
#         disp['Δ GNN (L4−L0, RAE)'] = (pivot_mean['L4_RAE'] - pivot_mean['L0_RAE']).map(lambda x: f"{x:+.4f}")
#     if 'L4_RAE' in pivot_mean.columns and 'L4_noRAE' in pivot_mean.columns:
#         disp['Δ RAE (RAE−noRAE, L4)'] = (pivot_mean['L4_RAE'] - pivot_mean['L4_noRAE']).map(lambda x: f"{x:+.4f}")
#     print(disp.to_string())
[1]:
import sys
sys.path.append(r'/import/home2/yzengbj/my_project/code')

import pandas as pd
import numpy as np
import scanpy as sc
import anndata as ad
import scipy.io
import matplotlib.pyplot as plt
import os

import simulation as construct_st
[ ]:
import matplotlib as mpl
mpl.rcParams.update({
    'font.family'       : 'Helvetica',
    'font.size'         : 7,
    'axes.titlesize'    : 8,
    'axes.labelsize'    : 7,
    'xtick.labelsize'   : 6.5,
    'ytick.labelsize'   : 6.5,
    'axes.linewidth'    : 0.5,
    'xtick.major.width' : 0.5,
    'ytick.major.width' : 0.5,
    'xtick.major.size'  : 2.5,
    'ytick.major.size'  : 2.5,
    'pdf.fonttype'      : 42,
    'ps.fonttype'       : 42,
    'savefig.dpi'       : 300,
    'figure.dpi'        : 150,
})
[ ]:
# ============================================================
# Load 2x2 ablation results (GNN × RAE)
# ============================================================
import pandas as pd
import numpy as np
import os

BASE_PATH = '/import/home2/share/yqzeng/MacSGP/experiments/ablation'
library_size = 1000
v = 4

df_rae_all = pd.read_csv(
    os.path.join(BASE_PATH, f'ablation_graph_rae_all_seeds_lib{library_size}_v{v}.csv')
)
df_rae_all['moran_I_abs']      = df_rae_all['moran_I'].abs()
df_rae_all['loading_corr_abs'] = df_rae_all['loading_corr'].abs()

df_rae_summary = pd.read_csv(
    os.path.join(BASE_PATH, f'ablation_graph_rae_summary_lib{library_size}_v{v}.csv')
)

noise_levels = sorted(df_rae_summary['ref_noise_ratio'].unique())
#print(df_rae_summary.to_string(index=False))

ref_noise_ratios_show = [3.5,4.0,4.5,5.0,5.5]
df_rae_summary_show = df_rae_summary[df_rae_summary['ref_noise_ratio'].isin(ref_noise_ratios_show)]
noise_levels = ref_noise_ratios_show
[ ]:

import matplotlib import matplotlib.pyplot as plt matplotlib.rcParams.update({ 'xtick.labelsize': 10, 'ytick.labelsize': 10, }) matplotlib.rcParams['font.size'] = 11 matplotlib.rcParams['axes.linewidth'] = 0.8 STYLES = { 'L4_noRAE': {'color': '#C44E52', 'marker': 'o', 'ls': '-', 'label': 'w/ GNN, w/o RAE'}, 'L0_RAE': {'color': '#4C72B0', 'marker': 's', 'ls': '--', 'label': 'w/o GNN, w/ RAE'}, 'L0_noRAE': {'color': '#55A868', 'marker': '^', 'ls': '--', 'label': 'w/o GNN, w/o RAE'}, 'L4_RAE': {'color': '#DD8452', 'marker': 'D', 'ls': '-', 'label': 'w/ GNN, w/ RAE (Full)'}, } LABEL_ORDER = ['L4_noRAE', 'L0_RAE', 'L0_noRAE', 'L4_RAE'] METRICS = [ ('moran_I_abs_mean', 'moran_I_abs_std', "Spatial coherence", "Spatial coherence"), ('loading_corr_abs_mean', 'loading_corr_abs_std', "Program coherence", "Program coherence"), ] fig, axes = plt.subplots(2, 3, figsize=(15, 8)) fig.subplots_adjust(hspace=0.40, wspace=0.32) for row, (col_mean, col_std, metric_short, metric_long) in enumerate(METRICS): pivot = df_rae_summary_show.pivot(index='ref_noise_ratio', columns='label', values=col_mean) # ---- (a/d) All 4 conditions ---- ax = axes[row, 0] for lbl in LABEL_ORDER: sub = df_rae_summary_show[df_rae_summary_show['label'] == lbl].sort_values('ref_noise_ratio') st = STYLES[lbl] mean = sub[col_mean].values std = sub[col_std].values ax.plot(sub['ref_noise_ratio'], mean, marker=st['marker'], color=st['color'], ls=st['ls'], linewidth=1.0, markersize=3, label=st['label']) # ax.fill_between(sub['ref_noise_ratio'], mean - std, mean + std, # color=st['color'], alpha=0.10) ax.set_xlabel(r'$\rho$', fontsize=15) ax.set_ylabel(metric_short, fontsize=15) #ax.set_title(f'({chr(97 + row*3)}) {metric_long}: all conditions', fontsize=15) #ax.legend(fontsize=8, frameon=False) handles, labels = ax.get_legend_handles_labels() handle_dict = dict(zip(labels, handles)) desired_order = [ 'w/ GNN, w/ RAE (Full)', 'w/o GNN, w/ RAE', 'w/ GNN, w/o RAE', 'w/o GNN, w/o RAE', ] new_labels = [ r'w/ GNN, w/ $L_{Rec}$ (Full)', r'w/o GNN, w/ $L_{Rec}$', r'w/ GNN, w/o $L_{Rec}$', r'w/o GNN, w/o $L_{Rec}$', ] ordered_handles = [handle_dict[l] for l in desired_order] ax.legend(ordered_handles, desired_order, fontsize=7.5) # reset labels for legends [1,2,3,4] -> [a,b,c,d] legend = ax.get_legend() for text, new_label in zip(legend.get_texts(), new_labels): text.set_text(new_label) ax.set_xticks(noise_levels) ax.grid(axis='y', alpha=0.3) ax.set_ylim(bottom=0) # ---- (b/e) Δ GNN = L4_RAE − L0_RAE ---- ax = axes[row, 1] delta = pivot['L4_RAE'] - pivot['L0_RAE'] colors = ['#DD8452' if d > 0 else '#4C72B0' for d in delta.values] bars = ax.bar(delta.index, delta.values, width=0.35, color=colors, edgecolor='white', linewidth=0.8) ax.axhline(0, color='gray', ls='--', lw=1) ax.set_xlabel(r'$\rho$', fontsize=15) ax.set_ylabel(f'Δ {metric_short}', fontsize=15) ax.set_title(r'GNN effect (w/ GNN − w/o GNN)', fontsize=15) ax.set_xticks(noise_levels) ax.grid(axis='y', alpha=0.3) for bar, val in zip(bars, delta.values): offset = 0.000 if val >= 0 else -0.000 ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + offset, f'{val:+.3f}', ha='center', va='bottom' if val >= 0 else 'top', fontsize=8) # ---- (c/f) Δ RAE = L4_RAE − L4_noRAE ---- ax = axes[row, 2] delta = pivot['L4_RAE'] - pivot['L4_noRAE'] colors = ['#DD8452' if d > 0 else '#C44E52' for d in delta.values] bars = ax.bar(delta.index, delta.values, width=0.35, color=colors, edgecolor='white', linewidth=0.8) ax.axhline(0, color='gray', ls='--', lw=1) ax.set_xlabel(r'$\rho$', fontsize=15) ax.set_ylabel(f'Δ {metric_short}', fontsize=15) ax.set_title(r'$L_{Rec}$ effect (w/ $L_{Rec}$ − w/o $L_{Rec}$)', fontsize=15) ax.set_xticks(noise_levels) ax.grid(axis='y', alpha=0.3) for bar, val in zip(bars, delta.values): offset = 0.000 if val >= 0 else -0.000 ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + offset, f'{val:+.3f}', ha='center', va='bottom' if val >= 0 else 'top', fontsize=8) out_prefix = os.path.join(BASE_PATH, f'ablation_graph_rae_lib{library_size}_v{v}') # plt.savefig(f'{out_prefix}.pdf', dpi=300, bbox_inches='tight') # plt.savefig(f'{out_prefix}.png', dpi=300, bbox_inches='tight') print(f"Saved: {out_prefix}.pdf / .png") plt.show()
Saved: /import/home2/share/yqzeng/MacSGP/experiments/ablation/ablation_graph_rae_lib1000_v4.pdf / .png
../../_images/analysis_simulation_simulation_ablation_5_1.png