| |
| """Gene-gene gamma correlation structure. |
| |
| Do genes with correlated degradation rates share RBP regulators? |
| Identifies co-degradation modules and tests for shared eCLIP RBP targets. |
| """ |
| from _common import * |
| from sklearn.cluster import AgglomerativeClustering |
|
|
| OUT = output_dir("09_gamma_coexpression") |
|
|
|
|
| def run(name, loader, cluster_key): |
| print(f"\n{'=' * 60}\n{name.upper()}: Gamma co-regulation\n{'=' * 60}") |
|
|
| adata_an = run_analytical(loader) |
| gamma = adata_an.layers["gamma"] |
|
|
| |
| gamma_med = np.median(gamma, axis=0) |
| active = gamma_med > 0.01 |
| gamma_active = gamma[:, active] |
| gene_names = adata_an.var_names[active].tolist() |
| print(f" Active genes (median gamma > 0.01): {len(gene_names)}") |
|
|
| |
| |
| rng = np.random.RandomState(42) |
| n_sub = min(1000, gamma_active.shape[0]) |
| idx = rng.choice(gamma_active.shape[0], n_sub, replace=False) |
| gamma_sub = gamma_active[idx] |
|
|
| |
| n_genes = gamma_sub.shape[1] |
| if n_genes > 2000: |
| |
| var = gamma_sub.var(axis=0) |
| top = np.argsort(var)[::-1][:500] |
| gamma_sub = gamma_sub[:, top] |
| gene_names = [gene_names[i] for i in top] |
| n_genes = 500 |
|
|
| print(f" Computing {n_genes}x{n_genes} correlation matrix...") |
| corr = np.corrcoef(gamma_sub.T) |
| corr = np.nan_to_num(corr) |
|
|
| |
| n_clusters = min(10, max(2, n_genes // 50)) |
| clust = AgglomerativeClustering(n_clusters=n_clusters, metric="precomputed", |
| linkage="average") |
| dist = 1 - np.abs(corr) |
| np.fill_diagonal(dist, 0) |
| labels = clust.fit_predict(dist) |
|
|
| module_sizes = pd.Series(labels).value_counts().sort_index() |
| print(f" Found {n_clusters} co-degradation modules: {module_sizes.to_dict()}") |
|
|
| |
| eclip = pd.read_csv(DATA_DIR / "eclip_targets.csv") |
| eclip_by_rbp = eclip.groupby("rbp")["target_gene"].apply(lambda x: set(x.str.upper())).to_dict() |
|
|
| module_results = [] |
| for mod_id in range(n_clusters): |
| mod_genes = [gene_names[i] for i in range(len(gene_names)) if labels[i] == mod_id] |
| mod_upper = set(g.upper() for g in mod_genes) |
|
|
| |
| rbp_counts = {} |
| for rbp, targets in eclip_by_rbp.items(): |
| n_hit = len(mod_upper & targets) |
| if n_hit > 0: |
| rbp_counts[rbp] = n_hit |
|
|
| top_rbp = sorted(rbp_counts.items(), key=lambda x: x[1], reverse=True)[:3] |
|
|
| |
| mod_idx = [i for i in range(len(gene_names)) if labels[i] == mod_id] |
| if len(mod_idx) > 1: |
| sub_corr = corr[np.ix_(mod_idx, mod_idx)] |
| mean_corr = (sub_corr.sum() - len(mod_idx)) / (len(mod_idx) * (len(mod_idx) - 1)) |
| else: |
| mean_corr = 1.0 |
|
|
| module_results.append({ |
| "module": mod_id, |
| "n_genes": len(mod_genes), |
| "mean_within_corr": float(mean_corr), |
| "top_rbps": [(r, c) for r, c in top_rbp], |
| "example_genes": mod_genes[:10], |
| }) |
|
|
| if top_rbp: |
| rbp_str = ", ".join(f"{r}({c})" for r, c in top_rbp) |
| print(f" Module {mod_id}: {len(mod_genes)} genes, r={mean_corr:.3f}, RBPs: {rbp_str}") |
|
|
| |
| between_corrs = [] |
| for i in range(n_clusters): |
| for j in range(i + 1, n_clusters): |
| idx_i = [k for k in range(len(gene_names)) if labels[k] == i] |
| idx_j = [k for k in range(len(gene_names)) if labels[k] == j] |
| if idx_i and idx_j: |
| between = corr[np.ix_(idx_i, idx_j)] |
| between_corrs.append(between.mean()) |
|
|
| mean_within = np.mean([m["mean_within_corr"] for m in module_results]) |
| mean_between = np.mean(between_corrs) if between_corrs else 0 |
|
|
| print(f"\n Mean within-module correlation: {mean_within:.4f}") |
| print(f" Mean between-module correlation: {mean_between:.4f}") |
| print(f" Ratio: {mean_within / max(abs(mean_between), 1e-8):.2f}x") |
|
|
| results = { |
| "n_active_genes": len(gene_names), |
| "n_modules": n_clusters, |
| "mean_within_corr": float(mean_within), |
| "mean_between_corr": float(mean_between), |
| "modules": module_results, |
| } |
| save_json(results, f"{name}_gamma_coexpression", OUT) |
|
|
| |
| order = np.argsort(labels) |
| corr_ordered = corr[np.ix_(order, order)] |
|
|
| fig, axes = plt.subplots(1, 2, figsize=(12, 5)) |
| im = axes[0].imshow(corr_ordered, cmap="RdBu_r", vmin=-0.5, vmax=0.5, aspect="auto") |
| plt.colorbar(im, ax=axes[0]) |
| axes[0].set_title(f"{name}: Gamma correlation ({n_genes} genes)") |
|
|
| |
| axes[1].bar(range(n_clusters), |
| [m["mean_within_corr"] for m in module_results], |
| color="steelblue", alpha=0.7) |
| axes[1].axhline(mean_between, color="red", ls="--", label=f"Between={mean_between:.3f}") |
| axes[1].set_xlabel("Module") |
| axes[1].set_ylabel("Mean within-module correlation") |
| axes[1].set_title("Co-degradation module structure") |
| axes[1].legend() |
|
|
| fig.tight_layout() |
| save_fig(fig, f"{name}_gamma_corr", OUT) |
|
|
| return results |
|
|
|
|
| def main(): |
| set_figure_style() |
| for name, loader, ck in DATASETS: |
| run(name, loader, ck) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|