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.
- MultiChat/Analysis/Intra_strength.py +1758 -0
- MultiChat/Analysis/Processing.py +152 -0
- MultiChat/Analysis/__init__.py +2 -0
- MultiChat/Heterogeneous_g_emb/__init__.py +13 -0
- MultiChat/Heterogeneous_g_emb/_settings.py +156 -0
- MultiChat/Heterogeneous_g_emb/_utils.py +143 -0
- MultiChat/Heterogeneous_g_emb/_version.py +3 -0
- MultiChat/Heterogeneous_g_emb/plotting/__init__.py +19 -0
- MultiChat/Heterogeneous_g_emb/plotting/_palettes.py +180 -0
- MultiChat/Heterogeneous_g_emb/plotting/_plot.py +1498 -0
- MultiChat/Heterogeneous_g_emb/plotting/_post_training.py +742 -0
- MultiChat/Heterogeneous_g_emb/plotting/_utils.py +103 -0
- MultiChat/Heterogeneous_g_emb/preprocessing/__init__.py +26 -0
- MultiChat/Heterogeneous_g_emb/preprocessing/_general.py +91 -0
- MultiChat/Heterogeneous_g_emb/preprocessing/_pca.py +182 -0
- MultiChat/Heterogeneous_g_emb/preprocessing/_qc.py +727 -0
- MultiChat/Heterogeneous_g_emb/preprocessing/_utils.py +60 -0
- MultiChat/Heterogeneous_g_emb/preprocessing/_variable_genes.py +82 -0
- MultiChat/Heterogeneous_g_emb/readwrite.py +250 -0
- MultiChat/Heterogeneous_g_emb/tools/__init__.py +23 -0
- MultiChat/Heterogeneous_g_emb/tools/_gene_scores.py +346 -0
- MultiChat/Heterogeneous_g_emb/tools/_general.py +71 -0
- MultiChat/Heterogeneous_g_emb/tools/_integration.py +197 -0
- MultiChat/Heterogeneous_g_emb/tools/_pbg.py +1184 -0
- MultiChat/Heterogeneous_g_emb/tools/_post_training.py +919 -0
- MultiChat/Heterogeneous_g_emb/tools/_umap.py +58 -0
- MultiChat/Heterogeneous_g_emb/tools/_utils.py +253 -0
- MultiChat/Model/Layers.py +116 -0
- MultiChat/Model/__init__.py +3 -0
- MultiChat/Model/model_training.py +166 -0
- MultiChat/Model/modules.py +93 -0
- MultiChat/Model/utilities.py +234 -0
- MultiChat/Plot/Visualization.py +470 -0
- MultiChat/Plot/__init__.py +1 -0
- MultiChat/__init__.py +12 -0
- scmultichat-0.1.0.dist-info/METADATA +156 -0
- scmultichat-0.1.0.dist-info/RECORD +39 -0
- scmultichat-0.1.0.dist-info/WHEEL +5 -0
- 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)
|