scMultiChat 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. MultiChat/Analysis/Intra_strength.py +1758 -0
  2. MultiChat/Analysis/Processing.py +152 -0
  3. MultiChat/Analysis/__init__.py +2 -0
  4. MultiChat/Heterogeneous_g_emb/__init__.py +13 -0
  5. MultiChat/Heterogeneous_g_emb/_settings.py +156 -0
  6. MultiChat/Heterogeneous_g_emb/_utils.py +143 -0
  7. MultiChat/Heterogeneous_g_emb/_version.py +3 -0
  8. MultiChat/Heterogeneous_g_emb/plotting/__init__.py +19 -0
  9. MultiChat/Heterogeneous_g_emb/plotting/_palettes.py +180 -0
  10. MultiChat/Heterogeneous_g_emb/plotting/_plot.py +1498 -0
  11. MultiChat/Heterogeneous_g_emb/plotting/_post_training.py +742 -0
  12. MultiChat/Heterogeneous_g_emb/plotting/_utils.py +103 -0
  13. MultiChat/Heterogeneous_g_emb/preprocessing/__init__.py +26 -0
  14. MultiChat/Heterogeneous_g_emb/preprocessing/_general.py +91 -0
  15. MultiChat/Heterogeneous_g_emb/preprocessing/_pca.py +182 -0
  16. MultiChat/Heterogeneous_g_emb/preprocessing/_qc.py +727 -0
  17. MultiChat/Heterogeneous_g_emb/preprocessing/_utils.py +60 -0
  18. MultiChat/Heterogeneous_g_emb/preprocessing/_variable_genes.py +82 -0
  19. MultiChat/Heterogeneous_g_emb/readwrite.py +250 -0
  20. MultiChat/Heterogeneous_g_emb/tools/__init__.py +23 -0
  21. MultiChat/Heterogeneous_g_emb/tools/_gene_scores.py +346 -0
  22. MultiChat/Heterogeneous_g_emb/tools/_general.py +71 -0
  23. MultiChat/Heterogeneous_g_emb/tools/_integration.py +197 -0
  24. MultiChat/Heterogeneous_g_emb/tools/_pbg.py +1184 -0
  25. MultiChat/Heterogeneous_g_emb/tools/_post_training.py +919 -0
  26. MultiChat/Heterogeneous_g_emb/tools/_umap.py +58 -0
  27. MultiChat/Heterogeneous_g_emb/tools/_utils.py +253 -0
  28. MultiChat/Model/Layers.py +116 -0
  29. MultiChat/Model/__init__.py +3 -0
  30. MultiChat/Model/model_training.py +166 -0
  31. MultiChat/Model/modules.py +93 -0
  32. MultiChat/Model/utilities.py +234 -0
  33. MultiChat/Plot/Visualization.py +470 -0
  34. MultiChat/Plot/__init__.py +1 -0
  35. MultiChat/__init__.py +12 -0
  36. scmultichat-0.1.0.dist-info/METADATA +156 -0
  37. scmultichat-0.1.0.dist-info/RECORD +39 -0
  38. scmultichat-0.1.0.dist-info/WHEEL +5 -0
  39. scmultichat-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,234 @@
1
+ import os
2
+ import time
3
+ import argparse
4
+ import torch
5
+ import random
6
+ import os
7
+ import numpy as np
8
+ import pandas as pd
9
+ import scanpy as sc
10
+ import scipy.sparse as sp
11
+ from tqdm import tqdm
12
+
13
+ from sklearn.metrics import pairwise_distances
14
+ from scipy.spatial.distance import cosine
15
+
16
+ import sys
17
+ sys.path.append('/home/nas2/biod/zhencaiwei/RegChatz_V2/')
18
+
19
+ if 'ipykernel' in sys.modules:
20
+ sys.argv = sys.argv[:1]
21
+
22
+
23
+ def parameter_setting():
24
+ parser = argparse.ArgumentParser(description='Spatial transcriptomics analysis by HIN')
25
+
26
+ parser.add_argument('--inputPath', '-IP', type=str, default='Datasets/MISAR/', help='data directory')
27
+ parser.add_argument('--outPath', '-od', type=str, default='Datasets/MISAR/CCC/', help='Output path')
28
+ parser.add_argument('--utilitePath', '-uP', type=str, default='Datasets/MISAR/inputs/', help='data directory')
29
+ parser.add_argument('--spatialLocation', '-sLocation', type=str, default='Coord.csv', help='spot physical location')
30
+ parser.add_argument('--annoFile', '-aFile', type=str, default='CellType.csv', help='annotation file')
31
+ parser.add_argument('--pos_pair', '-posP', type=str, default='Spot_positive_pairs.txt', help='positive pairs between spots')
32
+ parser.add_argument('--Ligands_exp', '-Ligands_exp', type=str, default='ligands_expression.txt', help='Expression of ligands per spot')
33
+ parser.add_argument('--Receptors_exp', '-Receptors_exp', type=str, default='receptors_expression.txt', help='Expression of receptors per spot')
34
+ parser.add_argument('--cci_pairs', '-cci_pairs', type=int, default = 4019, help='The number of receptors for each spot')
35
+ parser.add_argument('--locMeasure', '-locMeas', type=str, default='euclidean', help='Calculate spot location similarity by euclidean')
36
+ parser.add_argument('--Cell_pos_nos', '-CellPN', type=int, default=6, help='The number of positive cells for each cell')
37
+ parser.add_argument('--tau', '-tau', type=float, default=0.8)
38
+ parser.add_argument('--attn_drop', '-attn_drop', type=float, default=0.5)
39
+ parser.add_argument('--lr_cci', '-lr_cci', type=float, default=0.002, help='Learning rate')
40
+ parser.add_argument('--l2_coef', '-l2_coef', type=float, default=0)
41
+ parser.add_argument('--patience', '-patience', type=int, default=30)
42
+ parser.add_argument('--use_cuda', dest='use_cuda', default=True, action='store_true', help="whether use cuda(default: True)")
43
+ parser.add_argument('--gpu_id', type=int, default=0, help='GPU ID to use')
44
+ parser.add_argument('--seed', type=int, default=200, help='Random seed for repeat results')
45
+ parser.add_argument('--selected_cell_type', '-sct', type=str, default=None, help='Specify cell type to select nodes from (e.g. "Astro")')
46
+ parser.add_argument('--cell_type_column', '-ctc', type=str, default='cell_type', help='Column name in annotation file containing cell type information')
47
+ parser.add_argument('--k_neighbors', '-k', type=int, default=11, help='Number of nearest neighbors to include (including self)')
48
+ parser.add_argument('--InterCCC_Name', '-InterCCC_Name', type=str, default='CCC_module_LRP_strength.txt', help='Name of the InterCCC output file')
49
+
50
+ return parser
51
+
52
+
53
+
54
+
55
+
56
+ def get_cell_positive_pairs(cell_clus, cell_loc, args):
57
+ '''
58
+ get edges between cells: positive pairs
59
+ '''
60
+ cell_clus_values = cell_clus['cell_type'].values.astype('str')
61
+ dist_out = pairwise_distances(cell_loc)
62
+ cell_cell_adj = np.zeros((len(cell_clus), len(cell_clus)), dtype=int)
63
+ for index in range(len(cell_clus)):
64
+ match_int = np.where(cell_clus_values[index] == cell_clus_values)[0]
65
+ sorted_knn = dist_out[index, match_int].argsort()
66
+ cell_cell_adj[index, match_int[sorted_knn[:args.Cell_pos_nos]]] = 1
67
+ pd.DataFrame(cell_cell_adj).to_csv(args.outPath + args.pos_pair, header=None, index=None, sep='\t')
68
+
69
+
70
+
71
+
72
+ def load_ccc_data(args):
73
+ print("spot location for adjacency")
74
+ spot_loc = pd.read_table(args.spatialLocation, header=0, index_col=0, sep=',')
75
+
76
+ print("loading cell type annotations")
77
+ cell_type_df = pd.read_csv(args.annoFile, header=0, index_col=0, sep="\t")
78
+
79
+ print("Calculating pairwise distances between spots")
80
+ dist_loc = pairwise_distances(spot_loc.values, metric=args.locMeasure)
81
+ sorted_knn = dist_loc.argsort(axis=1)
82
+
83
+ selected_node = []
84
+
85
+ if args.selected_cell_type:
86
+ print(f"Selecting nodes for cell type: {args.selected_cell_type}")
87
+ safe_cell_type = args.selected_cell_type.replace('/', '-').replace(' ', '-')
88
+ selected_cell_samples = cell_type_df[cell_type_df['cell_type'] == args.selected_cell_type].index
89
+ selected_row_numbers = [cell_type_df.index.get_loc(idx) for idx in selected_cell_samples]
90
+ # print("selected cell type samples: ", selected_cell_samples)
91
+
92
+ for target_node in tqdm(range(len(cell_type_df)), desc="Processing nodes"):
93
+ all_neighbors = sorted_knn[target_node, :]
94
+ same_type_neighbors = [n for n in all_neighbors if n in selected_row_numbers]
95
+ top11_same_type = [target_node] + same_type_neighbors[:10]
96
+
97
+ if len(top11_same_type) < 11:
98
+ print(f"Warning: Only found {len(top11_same_type)} neighbors for cell {target_node}")
99
+
100
+ selected_node.append(top11_same_type)
101
+
102
+ pd.DataFrame(selected_node).to_csv(args.outPath + 'Nei_adj_' + safe_cell_type +'.csv', header=None, index=None, sep='\t')
103
+
104
+ else:
105
+ for index in list(range(np.shape(dist_loc)[0])):
106
+ selected_node.append(sorted_knn[index, :11])
107
+ pd.DataFrame(selected_node).to_csv(args.outPath + 'Nei_adj.csv' , header=None, index=None, sep='\t')
108
+
109
+ selected_node = np.array(selected_node)
110
+ selected_node = torch.LongTensor(selected_node)
111
+
112
+ print("spot-ligand data")
113
+ spots_ligand = pd.read_table(args.Ligands_exp, header=0, index_col=0)
114
+ spots_ligand_n = torch.FloatTensor(spots_ligand.values)
115
+
116
+ print("spot-receptor data")
117
+ spots_recep = pd.read_table(args.Receptors_exp, header=0, index_col=0)
118
+ spots_recep_n = torch.FloatTensor(spots_recep.values)
119
+
120
+ pos = pd.read_table(args.pos_pair, header=None, index_col=None).values
121
+ pos = torch.FloatTensor(pos)
122
+
123
+ return selected_node, spots_ligand_n, spots_recep_n, pos, spots_ligand.index, spots_ligand.columns
124
+
125
+
126
+
127
+
128
+
129
+
130
+ def perturb_pos_pair_row(row):
131
+ '''get random positive pairs: perturb edge'''
132
+ ones_pos = row[row == 1].index.tolist()
133
+ remaining_pos = [col for col in row.index if col not in ones_pos]
134
+ new_ones_pos = np.random.choice(remaining_pos, size=6, replace=False)
135
+ new_row = row.copy()
136
+ new_row[:] = 0
137
+ new_row[new_ones_pos] = 1
138
+
139
+ return new_row
140
+
141
+
142
+
143
+
144
+ def get_CCC_data(adata, latent, args, threthold = 5):
145
+
146
+ exp_data = sp.csr_matrix.toarray(adata.X)
147
+ exp_data_n = np.zeros( (exp_data.shape[0], exp_data.shape[1]) )
148
+ exp_data_n[ np.where(exp_data > 0) ] = 1
149
+ sum_gene = np.sum(exp_data_n, axis = 0)
150
+
151
+ CCC = pd.read_table(args.inputPath + args.CCC_file, header=None, index_col=None).values
152
+ ligands = list(set( adata.var_names[np.where(sum_gene>=threthold)] ) & set(CCC[:,0]))
153
+ receptors = list(set( adata.var_names[np.where(sum_gene>=threthold)] ) & set(CCC[:,1]))
154
+
155
+ lrp_list = []
156
+ symbol = '->'
157
+ for index, (lig, rec) in enumerate(CCC):
158
+ if (lig in ligands) and (rec in receptors):
159
+ lrp_list.append( symbol.join( [lig, rec] ) )
160
+
161
+ used_ligands_n = []
162
+ used_receptors_n = []
163
+
164
+ for str in list(set(lrp_list)):
165
+ temps = str.split( '->' )
166
+ used_ligands_n.append( temps[0] )
167
+ used_receptors_n.append( temps[1] )
168
+
169
+ ligand_int = [ adata.var_names.tolist().index(item) for item in used_ligands_n if item in adata.var_names.tolist() ]
170
+ receptor_int = [ adata.var_names.tolist().index(item) for item in used_receptors_n if item in adata.var_names.tolist() ]
171
+
172
+ exp_data_s = knn_smoothing(latent, 3, exp_data)
173
+ adata.X = sp.csr_matrix( exp_data_s )
174
+
175
+ sc.pp.normalize_total(adata, inplace=True)
176
+ sc.pp.scale(adata, max_value=10)
177
+
178
+ ligands_exp = adata.X[:,ligand_int]
179
+ receptors_exp = adata.X[:,receptor_int]
180
+
181
+ liagand_exps_n = (ligands_exp-ligands_exp.min(axis=0))/(ligands_exp.max(axis=0)-ligands_exp.min(axis=0))
182
+ recep_exps_n = (receptors_exp-receptors_exp.min(axis=0))/(receptors_exp.max(axis=0)-receptors_exp.min(axis=0))
183
+
184
+ pd.DataFrame( liagand_exps_n, index = adata.obs_names.tolist(), columns=list(set(lrp_list)) ).to_csv( args.outPath + args.Ligands_exp, sep='\t' )
185
+ pd.DataFrame( recep_exps_n, index = adata.obs_names.tolist(), columns=list(set(lrp_list)) ).to_csv( args.outPath + args.Receptors_exp, sep='\t' )
186
+ pd.DataFrame( adata.X, index = adata.obs_names.tolist(), columns=adata.var_names.tolist() ).to_csv( args.outPath + args.Denoised_exp, sep='\t' )
187
+
188
+
189
+ def knn_smoothing(latent, k, mat):
190
+ dist = pairwise_distances(latent)
191
+ row = []
192
+ col = []
193
+ sorted_knn = dist.argsort(axis=1)
194
+ for idx in list(range(np.shape(dist)[0])):
195
+ col.extend(sorted_knn[idx, : k].tolist())
196
+ row.extend([idx] * k)
197
+
198
+ res = np.zeros((mat.shape[0], mat.shape[1]))
199
+ for i in range(len(col)):
200
+ res[row[i]] += mat[col[i]]
201
+
202
+ return res
203
+
204
+ def save_checkpoint(model, folder='./saved_model/', filename='model_best.pth.tar'):
205
+ if not os.path.isdir(folder):
206
+ os.mkdir(folder)
207
+
208
+ torch.save(model.state_dict(), os.path.join(folder, filename))
209
+
210
+ def load_checkpoint(file_path, model, use_cuda=False):
211
+
212
+ if use_cuda:
213
+ device = torch.device( "cuda" )
214
+ model.load_state_dict( torch.load(file_path) )
215
+ model.to(device)
216
+
217
+ else:
218
+ device = torch.device('cpu')
219
+ model.load_state_dict( torch.load(file_path, map_location=device) )
220
+
221
+ model.eval()
222
+ return model
223
+
224
+ def adjust_learning_rate(init_lr, optimizer, iteration, max_lr, adjust_epoch):
225
+
226
+ lr = max(init_lr * (0.9 ** (iteration//adjust_epoch)), max_lr)
227
+ for param_group in optimizer.param_groups:
228
+ param_group["lr"] = lr
229
+
230
+ return lr
231
+
232
+
233
+
234
+
@@ -0,0 +1,470 @@
1
+ from matplotlib.colors import ListedColormap
2
+ import pandas as pd
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+ import seaborn as sns
6
+ from matplotlib.colors import LinearSegmentedColormap
7
+ from matplotlib.patches import FancyArrowPatch, ArrowStyle
8
+ from collections import defaultdict
9
+ from tqdm import tqdm
10
+ from itertools import product
11
+ from scipy import stats
12
+
13
+
14
+
15
+
16
+ def get_sender_adj(Nei_adj, cell_type):
17
+ num_cells = len(cell_type)
18
+ adj = np.zeros((num_cells, num_cells))
19
+ for _, row in Nei_adj.iterrows():
20
+ sender = int(row[0])
21
+ neighbors = row[1:].dropna().astype(int).tolist()
22
+ for neighbor in neighbors:
23
+ adj[sender][neighbor] = 1
24
+ return adj
25
+
26
+
27
+ def get_Sig_all_vectors(pathway_name, Sig_LR, cell_type, coord, adj, mode = 'Pathway_Name'):
28
+ if mode == 'Pathway_Name':
29
+ path_df = Sig_LR[Sig_LR['Pathway_Name'] == pathway_name]
30
+ elif mode == 'LR_Symbol':
31
+ path_df = Sig_LR[Sig_LR['LR_Symbol'] == pathway_name]
32
+ current_df = path_df[['Sample_Name', 'Inter_Score', 'Z_Score']].groupby('Sample_Name', as_index=False).mean()
33
+ sample_lst = []
34
+ interccc_lst = []
35
+ zscore_lst = []
36
+ sender_lst = []
37
+ sender_x_lst = []
38
+ sender_y_lst = []
39
+ receiver_x_lst = []
40
+ receiver_y_lst = []
41
+ for index, row in current_df.iterrows():
42
+ sample_name = row['Sample_Name']
43
+ interccc = row['Inter_Score']
44
+ zscore = row['Z_Score']
45
+ idx = cell_type.index.get_loc(sample_name)
46
+ senders = np.where(adj[idx] > 0)[0]
47
+ for sender in senders:
48
+ sample_lst.append(sample_name)
49
+ interccc_lst.append(interccc)
50
+ zscore_lst.append(zscore)
51
+ sender_lst.append(sender)
52
+ sender_x_lst.append(coord.iloc[sender]['x'])
53
+ sender_y_lst.append(coord.iloc[sender]['y'])
54
+ receiver_x_lst.append(coord.iloc[idx]['x'])
55
+ receiver_y_lst.append(coord.iloc[idx]['y'])
56
+ res_df = pd.DataFrame({
57
+ 'Sample_Name': sample_lst,
58
+ 'Inter_Score': interccc_lst,
59
+ 'Z_Score': zscore_lst,
60
+ 'Sender': sender_lst,
61
+ 'Sender_x': sender_x_lst,
62
+ 'Sender_y': sender_y_lst,
63
+ 'Receiver_x': receiver_x_lst,
64
+ 'Receiver_y': receiver_y_lst
65
+ })
66
+ return res_df
67
+
68
+
69
+ def get_weighted_vector(res):
70
+ x = res['Sender_x'].values[0]
71
+ y = res['Sender_y'].values[0]
72
+ x1 = res['Receiver_x'].values
73
+ y1 = res['Receiver_y'].values
74
+ weights = res['Inter_Score'].values
75
+ total_weight = np.sum(weights)
76
+ ratios = weights / total_weight
77
+ weighted_x = np.sum(ratios * x1)
78
+ weighted_y = np.sum(ratios * y1)
79
+ mean_strength = np.mean(weights)
80
+ return x, y, weighted_x, weighted_y, mean_strength
81
+
82
+
83
+ def get_Sig_weighted_one_vector(res_all_df):
84
+ sample_lst = []
85
+ interccc_lst = []
86
+ zscore_lst = []
87
+ sender_lst = []
88
+ sender_x_lst = []
89
+ sender_y_lst = []
90
+ receiver_x_lst = []
91
+ receiver_y_lst = []
92
+ finded_idx = {}
93
+ for index, row in res_all_df.iterrows():
94
+ sample_name = row['Sample_Name']
95
+ zscore = row['Z_Score']
96
+ sender_idx = row['Sender']
97
+ if sender_idx in finded_idx:
98
+ continue
99
+ finded_idx[sender_idx] = 1
100
+ sub_df = res_all_df[res_all_df['Sender'] == sender_idx]
101
+ x, y, weighted_x, weighted_y, mean_strength = get_weighted_vector(sub_df)
102
+ sample_lst.append(sample_name)
103
+ interccc_lst.append(mean_strength)
104
+ zscore_lst.append(zscore)
105
+ sender_lst.append(sender_idx)
106
+ sender_x_lst.append(x)
107
+ sender_y_lst.append(y)
108
+ receiver_x_lst.append(weighted_x)
109
+ receiver_y_lst.append(weighted_y)
110
+ res_weighted_df = pd.DataFrame({
111
+ 'Sample_Name': sample_lst,
112
+ 'Inter_Score': interccc_lst,
113
+ 'Z_Score': zscore_lst,
114
+ 'Sender': sender_lst,
115
+ 'Sender_x': sender_x_lst,
116
+ 'Sender_y': sender_y_lst,
117
+ 'Receiver_x': receiver_x_lst,
118
+ 'Receiver_y': receiver_y_lst
119
+ })
120
+ return res_weighted_df
121
+
122
+
123
+ def get_two_hop_cascade_vectors(Sig_path, condition, one_hop_paths):
124
+ focus_sig_path = Sig_path[condition].copy()
125
+ one_hop_sig_path = Sig_path[Sig_path['path_symbol'] == one_hop_paths]
126
+ one_hop_sig_path = Sig_path[Sig_path['path_symbol'] == one_hop_paths]
127
+ one_hop_sig_path = one_hop_sig_path.rename(columns={'from_cell':'X_name',
128
+ 'to_cell':'Relay_name',
129
+ 'source':'X_Source',
130
+ 'target':'Relay_Target',
131
+ 'path_symbol':'X_Path_Symbol',
132
+ 'comm_score':'X_Comm_Score',
133
+ 'z_score':'X_Z_Score'})
134
+ merged_df = pd.merge(
135
+ left=one_hop_sig_path,
136
+ right=focus_sig_path,
137
+ left_on='Relay_name',
138
+ right_on='from_cell',
139
+ how='inner'
140
+ )
141
+ x_relay_y = merged_df[['X_name', 'Relay_name', 'to_cell', 'X_Path_Symbol','path_symbol', 'X_Comm_Score', 'comm_score']].copy()
142
+ x_relay_y.rename(columns={
143
+ 'to_cell': 'Y_name',
144
+ 'X_Path_Symbol': 'XR_Path_Symbol',
145
+ 'path_symbol': 'RY_Path_Symbol',
146
+ 'X_Comm_Score': 'XR_Comm_Score',
147
+ 'comm_score': 'RY_Comm_Score'
148
+ }, inplace=True)
149
+
150
+ return x_relay_y
151
+
152
+
153
+
154
+ def stable_cauchy_combination(pvalues, min_p=1e-16):
155
+ if len(pvalues) == 0:
156
+ return np.nan, np.nan
157
+
158
+ pvalues = np.array(pvalues)
159
+ pvalues = np.clip(pvalues, min_p, 1.0) # First layer of protection: limiting input range
160
+
161
+ try:
162
+ t = np.tan((0.5 - pvalues) * np.pi)
163
+ t = np.clip(t, -1e10, 1e10) # Second layer of protection: limiting extreme tan values to avoid explosion
164
+ combined_stat = np.sum(t)
165
+ combined_p = 0.5 - np.arctan(combined_stat) / np.pi
166
+ combined_p = np.clip(combined_p, min_p, 1.0) # Third layer of protection: limiting output range
167
+ return combined_p, combined_stat
168
+ except Exception:
169
+ return min_p, np.nan
170
+
171
+
172
+ def get_cauchy_res(ct_target, condition,sig_path,sig_ccc,cell_type,top_n=20):
173
+ focus_sig_ccc_res = sig_path[condition].copy()
174
+ top_20_paths = focus_sig_ccc_res['path_symbol'].value_counts().head(top_n).index.tolist()
175
+ print(f"Fixed top {top_n} paths for {ct_target} target:")
176
+ print(top_20_paths)
177
+
178
+ # Create cauchy combination results for all cell types
179
+ cur_df = pd.DataFrame(
180
+ np.random.uniform(0.05, 1, size=(len(cell_type.index), len(top_20_paths))),
181
+ index=cell_type.index.tolist(),
182
+ columns=top_20_paths
183
+ )
184
+ for path in top_20_paths:
185
+ temp = sig_ccc[sig_ccc['Path_Symbol'] == path].copy()
186
+ z_score_lst = temp['Z_Score'].tolist()
187
+ sample_lst = temp['Sample_Name'].tolist()
188
+ p_value_lst = [stats.norm.sf(z_score) for z_score in z_score_lst]
189
+ cur_df.loc[sample_lst, path] = p_value_lst
190
+ matrix_df = cur_df.copy()
191
+ merged_df = matrix_df.merge(cell_type[['cell_type']], left_index=True, right_index=True, how='left')
192
+ res = []
193
+ for t, sub_df in merged_df.groupby("cell_type"):
194
+ for path in matrix_df.columns:
195
+ pvals = sub_df[path].dropna().values
196
+ p_comb, combined_stat = stable_cauchy_combination(pvals)
197
+ res.append({
198
+ "type": t,
199
+ "pathway": path,
200
+ "combined_pvalue": p_comb,
201
+ "combined_stat": combined_stat
202
+ })
203
+ cauchy_df = pd.DataFrame(res)
204
+ cauchy_df = cauchy_df.sort_values("combined_pvalue")
205
+ cauchy_df['neg_log10_p'] = -np.log10(cauchy_df['combined_pvalue'])
206
+
207
+ return cauchy_df
208
+
209
+
210
+
211
+ def get_Sig_all_vectors_mlpath(pathway_name, Sig_LR, cell_type, coord, adj):
212
+ path_df = Sig_LR[Sig_LR['Path_Symbol'].str.contains(pathway_name)]
213
+ current_df = path_df[['Sample_Name', 'Comm_Score', 'Z_Score']].groupby('Sample_Name', as_index=False).mean()
214
+ sample_lst = []
215
+ interccc_lst = []
216
+ zscore_lst = []
217
+ sender_lst = []
218
+ sender_x_lst = []
219
+ sender_y_lst = []
220
+ receiver_x_lst = []
221
+ receiver_y_lst = []
222
+ for index, row in current_df.iterrows():
223
+ sample_name = row['Sample_Name']
224
+ interccc = row['Comm_Score']
225
+ zscore = row['Z_Score']
226
+ idx = cell_type.index.get_loc(sample_name)
227
+ senders = np.where(adj[idx] > 0)[0]
228
+ for sender in senders:
229
+ sample_lst.append(sample_name)
230
+ interccc_lst.append(interccc)
231
+ zscore_lst.append(zscore)
232
+ sender_lst.append(sender)
233
+ sender_x_lst.append(coord.iloc[sender]['x'])
234
+ sender_y_lst.append(coord.iloc[sender]['y'])
235
+ receiver_x_lst.append(coord.iloc[idx]['x'])
236
+ receiver_y_lst.append(coord.iloc[idx]['y'])
237
+ res_df = pd.DataFrame({
238
+ 'Sample_Name': sample_lst,
239
+ 'Comm_Score': interccc_lst,
240
+ 'Z_Score': zscore_lst,
241
+ 'Sender': sender_lst,
242
+ 'Sender_x': sender_x_lst,
243
+ 'Sender_y': sender_y_lst,
244
+ 'Receiver_x': receiver_x_lst,
245
+ 'Receiver_y': receiver_y_lst
246
+ })
247
+ return res_df
248
+
249
+
250
+
251
+ def get_weighted_vector_mlpath(res):
252
+ x = res['Sender_x'].values[0]
253
+ y = res['Sender_y'].values[0]
254
+ x1 = res['Receiver_x'].values
255
+ y1 = res['Receiver_y'].values
256
+ weights = res['Comm_Score'].values
257
+ total_weight = np.sum(weights)
258
+ ratios = weights / total_weight
259
+ weighted_x = np.sum(ratios * x1)
260
+ weighted_y = np.sum(ratios * y1)
261
+ mean_strength = np.mean(weights)
262
+ return x, y, weighted_x, weighted_y, mean_strength
263
+
264
+ def get_Sig_weighted_one_vector_mlpath(res_all_df):
265
+ sample_lst = []
266
+ interccc_lst = []
267
+ zscore_lst = []
268
+ sender_lst = []
269
+ sender_x_lst = []
270
+ sender_y_lst = []
271
+ receiver_x_lst = []
272
+ receiver_y_lst = []
273
+ finded_idx = {}
274
+ for index, row in res_all_df.iterrows():
275
+ sample_name = row['Sample_Name']
276
+ zscore = row['Z_Score']
277
+ sender_idx = row['Sender']
278
+ if sender_idx in finded_idx:
279
+ continue
280
+ finded_idx[sender_idx] = 1
281
+ sub_df = res_all_df[res_all_df['Sender'] == sender_idx]
282
+ x, y, weighted_x, weighted_y, mean_strength = get_weighted_vector_mlpath(sub_df)
283
+ sample_lst.append(sample_name)
284
+ interccc_lst.append(mean_strength)
285
+ zscore_lst.append(zscore)
286
+ sender_lst.append(sender_idx)
287
+ sender_x_lst.append(x)
288
+ sender_y_lst.append(y)
289
+ receiver_x_lst.append(weighted_x)
290
+ receiver_y_lst.append(weighted_y)
291
+ res_weighted_df = pd.DataFrame({
292
+ 'Sample_Name': sample_lst,
293
+ 'Comm_Score': interccc_lst,
294
+ 'Z_Score': zscore_lst,
295
+ 'Sender': sender_lst,
296
+ 'Sender_x': sender_x_lst,
297
+ 'Sender_y': sender_y_lst,
298
+ 'Receiver_x': receiver_x_lst,
299
+ 'Receiver_y': receiver_y_lst
300
+ })
301
+ return res_weighted_df
302
+
303
+
304
+
305
+
306
+
307
+
308
+ def plot_communication_double_panel(
309
+ strength_df,
310
+ coord_df,
311
+ res_weighted_df,
312
+ color_map,
313
+ pathway_name,
314
+ figpath,
315
+ strength_scale=10,
316
+ arrow_rad=0.2,
317
+ point_size=80,
318
+ figsize=(24, 10),
319
+ dpi=300,
320
+ bbox_inches='tight'
321
+ ):
322
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=figsize, constrained_layout=True)
323
+
324
+ scatter1 = ax1.scatter(
325
+ strength_df['x'], strength_df['y'],
326
+ c=strength_df['Comm_Score'],
327
+ cmap='viridis',
328
+ s=point_size,
329
+ linewidths=0
330
+ )
331
+
332
+ for _, row in res_weighted_df.iterrows():
333
+ strength = row['Comm_Score']
334
+ lw = max(0.5, strength * strength_scale)
335
+ arrowstyle = ArrowStyle("Simple", head_length=1, head_width=1.5, tail_width=0.1)
336
+ arrow = FancyArrowPatch(
337
+ (row['Sender_x'], row['Sender_y']),
338
+ (row['Receiver_x'], row['Receiver_y']),
339
+ connectionstyle=f"arc3,rad={arrow_rad}",
340
+ arrowstyle=arrowstyle,
341
+ color='black',
342
+ lw=lw,
343
+ mutation_scale=2
344
+ )
345
+ ax1.add_patch(arrow)
346
+
347
+ ax1.set_title('Communication Strength Background')
348
+ fig.colorbar(scatter1, ax=ax1, label='Intensity')
349
+
350
+ scatter2 = ax2.scatter(
351
+ coord_df['x'], coord_df['y'],
352
+ c=coord_df['color'],
353
+ alpha=0.9,
354
+ s=point_size,
355
+ linewidths=0
356
+ )
357
+
358
+ for _, row in res_weighted_df.iterrows():
359
+ strength = row['Comm_Score']
360
+ lw = max(0.5, strength * strength_scale)
361
+ arrowstyle = ArrowStyle("Simple", head_length=1, head_width=1.5, tail_width=0.1)
362
+ arrow = FancyArrowPatch(
363
+ (row['Sender_x'], row['Sender_y']),
364
+ (row['Receiver_x'], row['Receiver_y']),
365
+ connectionstyle=f"arc3,rad={arrow_rad}",
366
+ arrowstyle=arrowstyle,
367
+ color='black',
368
+ lw=lw,
369
+ mutation_scale=2
370
+ )
371
+ ax2.add_patch(arrow)
372
+
373
+ handles = [
374
+ plt.Line2D(
375
+ [0], [0],
376
+ marker='o',
377
+ color='w',
378
+ label=celltype,
379
+ markerfacecolor=color,
380
+ markersize=10
381
+ ) for celltype, color in color_map.items()
382
+ ]
383
+ ax2.legend(
384
+ handles=handles,
385
+ title='Cell Types',
386
+ bbox_to_anchor=(1.35, 1),
387
+ loc='upper right'
388
+ )
389
+ ax2.set_title('Cell Type Communication')
390
+
391
+ plt.suptitle(f'Communication Direction under: {pathway_name}', y=1.03)
392
+
393
+ pathway_name_new = pathway_name.replace('->', '_')
394
+ filename_base = f'Combined_plot_{pathway_name_new}'
395
+
396
+ plt.savefig(f"{figpath}{filename_base}.png", dpi=dpi, bbox_inches=bbox_inches)
397
+ plt.savefig(f"{figpath}{filename_base}.pdf", bbox_inches=bbox_inches)
398
+
399
+ plt.show()
400
+ plt.close()
401
+
402
+
403
+
404
+
405
+
406
+
407
+ def find_signal_paths_with_relay(df):
408
+ # dict:{receiver: [sender1, sender2, ...]}
409
+ receiver_to_senders = defaultdict(list)
410
+
411
+ for _, row in df.iterrows():
412
+ receiver = row[0]
413
+ senders = row[1:].dropna().astype(int).tolist()
414
+ receiver_to_senders[receiver].extend(senders)
415
+
416
+ # all paths (x, relay, y)
417
+ paths = []
418
+ for relay in receiver_to_senders:
419
+ # 1.relay <-(x)
420
+ senders_to_relay = receiver_to_senders[relay]
421
+
422
+ # 2.relay ->(y)
423
+ receivers_from_relay = []
424
+ for potential_receiver, senders in receiver_to_senders.items():
425
+ if relay in senders:
426
+ receivers_from_relay.append(potential_receiver)
427
+
428
+ # 3. (x, relay, y)
429
+ for x in senders_to_relay:
430
+ for y in receivers_from_relay:
431
+ if x != relay and relay != y and x != y: # Ensure no self-loops
432
+ paths.append({'x': x, 'relay': relay, 'y': y})
433
+
434
+ result_df = pd.DataFrame(paths).drop_duplicates()
435
+
436
+ return result_df
437
+
438
+
439
+
440
+ def create_sig_x_relay_y(x_relay_y, sig_path):
441
+ significant_receivers = set(sig_path['Sample_Name'].unique())
442
+ sig_path_dict = {}
443
+ for _, row in sig_path.iterrows():
444
+ sig_path_dict.setdefault(row['Sample_Name'], []).append(
445
+ (row['Path_Symbol'], row['Comm_Score'])
446
+ )
447
+
448
+ results = []
449
+
450
+ for _, row in tqdm(x_relay_y.iterrows(), total=x_relay_y.shape[0], desc='Processing paths'):
451
+ x_name = row['x_name']
452
+ relay_name = row['relay_name']
453
+ y_name = row['y_name']
454
+
455
+ if relay_name in significant_receivers and y_name in significant_receivers:
456
+ relay_paths = sig_path_dict.get(relay_name, [])
457
+ y_paths = sig_path_dict.get(y_name, [])
458
+
459
+ for (xr_symbol, xr_score), (ry_symbol, ry_score) in product(relay_paths, y_paths):
460
+ results.append({
461
+ 'X_Name': x_name,
462
+ 'Relay_Name': relay_name,
463
+ 'Y_Name': y_name,
464
+ 'XR_Path_Symbol': xr_symbol,
465
+ 'XR_Comm_Score': xr_score,
466
+ 'RY_Path_Symbol': ry_symbol,
467
+ 'RY_Comm_Score': ry_score
468
+ })
469
+
470
+ return pd.DataFrame(results)