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,1758 @@
|
|
|
1
|
+
from scipy.stats import pearsonr
|
|
2
|
+
from tqdm import tqdm
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import numpy as np
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Dict, Tuple, List, Optional
|
|
8
|
+
from collections import defaultdict
|
|
9
|
+
from scipy.sparse import csr_matrix, save_npz, load_npz, csc_matrix, vstack
|
|
10
|
+
from scipy.spatial.distance import cdist
|
|
11
|
+
import json
|
|
12
|
+
from scipy.stats import norm
|
|
13
|
+
import glob
|
|
14
|
+
import multiprocessing
|
|
15
|
+
from functools import partial
|
|
16
|
+
from statsmodels.robust import mad
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def calculate_tf_re(tf_name, re_name, sample_rna, sample_atac, tf_rep, peak_rep, tf_re_ba):
|
|
20
|
+
"""
|
|
21
|
+
Get TF-RE score
|
|
22
|
+
"""
|
|
23
|
+
tf_expr = sample_rna[tf_name]
|
|
24
|
+
re_access = sample_atac[re_name]
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
tf_vec = tf_rep.loc[tf_name].values
|
|
28
|
+
re_vec = peak_rep.loc[re_name].values
|
|
29
|
+
pcc = abs(pearsonr(tf_vec, re_vec)[0])
|
|
30
|
+
except:
|
|
31
|
+
pcc = 0
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
ba_score = tf_re_ba.loc[re_name, tf_name]
|
|
35
|
+
except:
|
|
36
|
+
ba_score = 0
|
|
37
|
+
|
|
38
|
+
return tf_expr * re_access * (pcc + ba_score)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def calculate_re_tg(re_name, tg_name, sample_rna, sample_atac, tg_re_df, gene_rep, peak_rep):
|
|
42
|
+
"""
|
|
43
|
+
Get RE-TG score
|
|
44
|
+
"""
|
|
45
|
+
tg_expr = sample_rna[tg_name]
|
|
46
|
+
re_access = sample_atac[re_name]
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
tg_vec = gene_rep.loc[tg_name].values
|
|
50
|
+
re_vec = peak_rep.loc[re_name].values
|
|
51
|
+
pcc = abs(pearsonr(tg_vec, re_vec)[0])
|
|
52
|
+
except:
|
|
53
|
+
pcc = 0
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
re_tg_score = tg_re_df[(tg_re_df['genes'] == tg_name) &
|
|
57
|
+
(tg_re_df['peaks'] == re_name)]['scores'].values[0]
|
|
58
|
+
except:
|
|
59
|
+
re_tg_score = 0
|
|
60
|
+
|
|
61
|
+
return (re_tg_score+pcc) * re_access * tg_expr
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def calculate_tf_re_tg(tf_name, tg_name, sample_rna, sample_atac, tg_re_df, tf_rep, peak_rep, tf_re_ba, gene_rep):
|
|
65
|
+
"""
|
|
66
|
+
Get TF-RE-TG score
|
|
67
|
+
"""
|
|
68
|
+
associated_res = tg_re_df[tg_re_df['genes'] == tg_name]['peaks'].unique()
|
|
69
|
+
|
|
70
|
+
total_score = 0
|
|
71
|
+
for re_name in associated_res:
|
|
72
|
+
try:
|
|
73
|
+
if sample_atac[re_name] == 0:
|
|
74
|
+
continue
|
|
75
|
+
except:
|
|
76
|
+
continue
|
|
77
|
+
|
|
78
|
+
tf_re_score = calculate_tf_re(tf_name, re_name, sample_rna, sample_atac, tf_rep, peak_rep, tf_re_ba)
|
|
79
|
+
if tf_re_score == 0:
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
re_tg_score = calculate_re_tg(re_name, tg_name, sample_rna, sample_atac, tg_re_df, gene_rep, peak_rep)
|
|
83
|
+
|
|
84
|
+
total_score += tf_re_score * re_tg_score
|
|
85
|
+
|
|
86
|
+
return total_score
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def calculate_all_tf_tg_scores(rna_mat, atac_mat, tg_re_df, tf_rep, peak_rep, tf_re_ba, gene_rep, path, cell_rep=None):
|
|
90
|
+
"""
|
|
91
|
+
Get and save all samples TF-TG score
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
os.makedirs(path, exist_ok=True)
|
|
95
|
+
samples = rna_mat.columns
|
|
96
|
+
tfs = tf_rep.index
|
|
97
|
+
tgs = tg_re_df['genes'].unique()
|
|
98
|
+
|
|
99
|
+
tf_tg_columns = [f"{tf}->{tg}" for tf in tfs for tg in tgs]
|
|
100
|
+
|
|
101
|
+
final_results = pd.DataFrame(
|
|
102
|
+
index=samples,
|
|
103
|
+
columns=tf_tg_columns,
|
|
104
|
+
dtype=float
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
for sample in tqdm(samples, desc="Processing cells"):
|
|
108
|
+
sample_results = pd.DataFrame(index=tfs, columns=tgs, dtype=float)
|
|
109
|
+
|
|
110
|
+
sample_rna = rna_mat[sample]
|
|
111
|
+
sample_atac = atac_mat.loc[sample]
|
|
112
|
+
|
|
113
|
+
for tf in tfs:
|
|
114
|
+
try:
|
|
115
|
+
if sample_rna[tf] == 0:
|
|
116
|
+
sample_results.loc[tf] = 0
|
|
117
|
+
continue
|
|
118
|
+
except:
|
|
119
|
+
sample_results.loc[tf] = 0
|
|
120
|
+
continue
|
|
121
|
+
for tg in tgs:
|
|
122
|
+
try:
|
|
123
|
+
if sample_rna[tg] == 0:
|
|
124
|
+
sample_results.loc[tf, tg] = 0
|
|
125
|
+
continue
|
|
126
|
+
except:
|
|
127
|
+
sample_results.loc[tf, tg] = 0
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
sample_results.loc[tf, tg] = calculate_tf_re_tg(
|
|
131
|
+
tf, tg,
|
|
132
|
+
sample_rna, sample_atac, tg_re_df,
|
|
133
|
+
tf_rep, peak_rep, tf_re_ba, gene_rep
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
sample_results.to_csv(os.path.join(path, f"{sample}.csv"))
|
|
137
|
+
|
|
138
|
+
final_results.loc[sample] = sample_results.values.ravel()
|
|
139
|
+
|
|
140
|
+
final_results.to_csv(os.path.join(path, "all_samples_tf_tg_scores.csv"))
|
|
141
|
+
return final_results
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def calculate_pcc_rec_tf(gene_rep, tf_rep, receptor_symbol, tf_symbol):
|
|
145
|
+
"""
|
|
146
|
+
Get PCC(emb_rec, emb_tf)
|
|
147
|
+
"""
|
|
148
|
+
if '_' in receptor_symbol:
|
|
149
|
+
receptor_parts = receptor_symbol.split('_')
|
|
150
|
+
pccs = []
|
|
151
|
+
for part in receptor_parts:
|
|
152
|
+
if part in gene_rep.index and tf_symbol in tf_rep.index:
|
|
153
|
+
pcc, _ = pearsonr(gene_rep.loc[part], tf_rep.loc[tf_symbol])
|
|
154
|
+
pccs.append(abs(pcc))
|
|
155
|
+
return np.mean(pccs) if pccs else np.nan
|
|
156
|
+
else:
|
|
157
|
+
if receptor_symbol in gene_rep.index and tf_symbol in tf_rep.index:
|
|
158
|
+
pcc, _ = pearsonr(gene_rep.loc[receptor_symbol], tf_rep.loc[tf_symbol])
|
|
159
|
+
return abs(pcc)
|
|
160
|
+
else:
|
|
161
|
+
return np.nan
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def build_tf_tg_mapping(tg_re_df, tf_re_ba):
|
|
165
|
+
"""
|
|
166
|
+
Build TF-TG dictonary, with TF-TG pair as key and common peaks as values
|
|
167
|
+
"""
|
|
168
|
+
result = {}
|
|
169
|
+
|
|
170
|
+
gene_peak_dict = {}
|
|
171
|
+
for _, row in tg_re_df.iterrows():
|
|
172
|
+
gene = row['genes']
|
|
173
|
+
peak = row['peaks']
|
|
174
|
+
if gene not in gene_peak_dict:
|
|
175
|
+
gene_peak_dict[gene] = []
|
|
176
|
+
gene_peak_dict[gene].append(peak)
|
|
177
|
+
|
|
178
|
+
tf_peak_dict = {
|
|
179
|
+
tf: tf_re_ba.index[tf_re_ba[tf].notna()].tolist()
|
|
180
|
+
for tf in tf_re_ba.columns
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
gene_peak_sets = {gene: set(peaks) for gene, peaks in gene_peak_dict.items()}
|
|
184
|
+
|
|
185
|
+
for tf, tf_peaks in tf_peak_dict.items():
|
|
186
|
+
tf_peak_set = set(tf_peaks)
|
|
187
|
+
for gene, gene_peak_set in gene_peak_sets.items():
|
|
188
|
+
common_peaks = tf_peak_set & gene_peak_set
|
|
189
|
+
if common_peaks:
|
|
190
|
+
key = f"{tf}->{gene}"
|
|
191
|
+
result[key] = list(common_peaks)
|
|
192
|
+
|
|
193
|
+
return result
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def generate_l_r_tf_pairs(l_r_df, r_tf_cellcall):
|
|
197
|
+
'''
|
|
198
|
+
Generate ligand-receptor-TF links based on CellChatDB and CellCallDB
|
|
199
|
+
'''
|
|
200
|
+
sub_r_tf_df = r_tf_cellcall[['Receptor_Symbol', 'TF_Symbol']]
|
|
201
|
+
sub_r_tf_df = sub_r_tf_df.drop_duplicates()
|
|
202
|
+
|
|
203
|
+
r_tf_dict = {}
|
|
204
|
+
for item in sub_r_tf_df['Receptor_Symbol'].tolist():
|
|
205
|
+
if item not in r_tf_dict:
|
|
206
|
+
r_tf_dict[item] = []
|
|
207
|
+
|
|
208
|
+
for index, row in sub_r_tf_df.iterrows():
|
|
209
|
+
r_tf_dict[row['Receptor_Symbol']].append(row['TF_Symbol'])
|
|
210
|
+
|
|
211
|
+
l_lst = []
|
|
212
|
+
r_lst = []
|
|
213
|
+
f_lst = []
|
|
214
|
+
|
|
215
|
+
for index, row in l_r_df.iterrows():
|
|
216
|
+
if '_' in row['Receptor_Symbol']:
|
|
217
|
+
r_items = row['Receptor_Symbol'].split('_')
|
|
218
|
+
for item in r_items:
|
|
219
|
+
if item in r_tf_dict:
|
|
220
|
+
for f_item in r_tf_dict[item]:
|
|
221
|
+
l_lst.append(row['Ligand_Symbol'])
|
|
222
|
+
r_lst.append(row['Receptor_Symbol'])
|
|
223
|
+
f_lst.append(f_item)
|
|
224
|
+
else:
|
|
225
|
+
if row['Receptor_Symbol'] in r_tf_dict:
|
|
226
|
+
for item in r_tf_dict[row['Receptor_Symbol']]:
|
|
227
|
+
l_lst.append(row['Ligand_Symbol'])
|
|
228
|
+
r_lst.append(row['Receptor_Symbol'])
|
|
229
|
+
f_lst.append(item)
|
|
230
|
+
|
|
231
|
+
l_r_tf_df = pd.DataFrame({'Ligand_Symbol': l_lst, 'Receptor_Symbol': r_lst, 'TF_Symbol': f_lst})
|
|
232
|
+
l_r_tf_df = l_r_tf_df.drop_duplicates()
|
|
233
|
+
l_r_tf_df = l_r_tf_df.reset_index(drop=True)
|
|
234
|
+
|
|
235
|
+
return l_r_tf_df
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def generate_l_r_tf_tg_pairs(l_r_tf_df, tf_tg_common_peaks):
|
|
239
|
+
'''
|
|
240
|
+
Generate ligand-receptor-TF-TG links
|
|
241
|
+
'''
|
|
242
|
+
l_lst = []
|
|
243
|
+
r_lst = []
|
|
244
|
+
f_lst = []
|
|
245
|
+
g_lst = []
|
|
246
|
+
for index, row in tqdm(l_r_tf_df.iterrows(), desc="Processing L-R-TF-TG pairs"):
|
|
247
|
+
l = row['Ligand_Symbol']
|
|
248
|
+
r = row['Receptor_Symbol']
|
|
249
|
+
tf = row['TF_Symbol']
|
|
250
|
+
|
|
251
|
+
matching_keys = [key for key in tf_tg_common_peaks.keys() if key.startswith(f"{tf}->")]
|
|
252
|
+
if matching_keys:
|
|
253
|
+
for key in matching_keys:
|
|
254
|
+
tg = key.split('->')[1]
|
|
255
|
+
l_lst.append(l)
|
|
256
|
+
r_lst.append(r)
|
|
257
|
+
f_lst.append(tf)
|
|
258
|
+
g_lst.append(tg)
|
|
259
|
+
L_R_TF_TG_df = pd.DataFrame({
|
|
260
|
+
'Ligand_Symbol': l_lst,
|
|
261
|
+
'Receptor_Symbol': r_lst,
|
|
262
|
+
'TF_Symbol': f_lst,
|
|
263
|
+
'TG_Symbol': g_lst
|
|
264
|
+
})
|
|
265
|
+
return L_R_TF_TG_df
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def calculate_r_tf_tg_cor(
|
|
269
|
+
gene_rep: pd.DataFrame,
|
|
270
|
+
tf_rep: pd.DataFrame,
|
|
271
|
+
cell_rep: pd.DataFrame,
|
|
272
|
+
receptors: List[str],
|
|
273
|
+
tf_tg_pairs: List[str],
|
|
274
|
+
reg_dir: str = "R_TF_TG_Reg",
|
|
275
|
+
output_dir: str = "output_cells_cor"
|
|
276
|
+
) -> Tuple[Dict[str, csr_matrix], csr_matrix]:
|
|
277
|
+
"""
|
|
278
|
+
Get correlation score
|
|
279
|
+
"""
|
|
280
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
281
|
+
|
|
282
|
+
with open(f"{output_dir}/global_row_names.json", "w") as f:
|
|
283
|
+
json.dump(receptors, f)
|
|
284
|
+
with open(f"{output_dir}/global_col_names.json", "w") as f:
|
|
285
|
+
json.dump(tf_tg_pairs, f)
|
|
286
|
+
|
|
287
|
+
cells = cell_rep.index.tolist()
|
|
288
|
+
|
|
289
|
+
gene_index = {gene: idx for idx, gene in enumerate(gene_rep.index)}
|
|
290
|
+
tf_index = {tf: idx for idx, tf in enumerate(tf_rep.index)}
|
|
291
|
+
|
|
292
|
+
parsed_pairs = []
|
|
293
|
+
for pair in tf_tg_pairs:
|
|
294
|
+
tf, tg = pair.split("->")
|
|
295
|
+
parsed_pairs.append((tf, tg, gene_index.get(tg, -1)))
|
|
296
|
+
|
|
297
|
+
print("Precomputing PCC matrices...")
|
|
298
|
+
cell_gene_pcc_matrix = 1 - cdist(cell_rep.values, gene_rep.values, metric='correlation')
|
|
299
|
+
cell_tf_pcc_matrix = 1 - cdist(cell_rep.values, tf_rep.values, metric='correlation')
|
|
300
|
+
cell_gene_pcc_matrix = np.abs(cell_gene_pcc_matrix)
|
|
301
|
+
cell_tf_pcc_matrix = np.abs(cell_tf_pcc_matrix)
|
|
302
|
+
|
|
303
|
+
cell_results = {}
|
|
304
|
+
combined_data = np.zeros((len(cells), len(receptors)*len(tf_tg_pairs)))
|
|
305
|
+
|
|
306
|
+
for cell_idx, cell in enumerate(tqdm(cells, desc="Processing cells")):
|
|
307
|
+
reg_path = f"{reg_dir}/{cell}.npz"
|
|
308
|
+
if not os.path.exists(reg_path):
|
|
309
|
+
continue
|
|
310
|
+
|
|
311
|
+
reg_sparse = load_npz(reg_path)
|
|
312
|
+
reg_coo = reg_sparse.tocoo()
|
|
313
|
+
|
|
314
|
+
cell_data = np.zeros((len(receptors), len(tf_tg_pairs)))
|
|
315
|
+
|
|
316
|
+
for rec_idx, pair_idx, reg_val in zip(reg_coo.row, reg_coo.col, reg_coo.data):
|
|
317
|
+
receptor = receptors[rec_idx]
|
|
318
|
+
tf, tg, tg_idx = parsed_pairs[pair_idx]
|
|
319
|
+
|
|
320
|
+
if '_' in receptor:
|
|
321
|
+
items = receptor.split('_')
|
|
322
|
+
item_pccs = []
|
|
323
|
+
for item in items:
|
|
324
|
+
if item in gene_index:
|
|
325
|
+
item_pccs.append(cell_gene_pcc_matrix[cell_idx, gene_index[item]])
|
|
326
|
+
rec_pcc = np.mean(item_pccs) if item_pccs else 0
|
|
327
|
+
else:
|
|
328
|
+
rec_pcc = cell_gene_pcc_matrix[cell_idx, gene_index[receptor]] if receptor in gene_index else 0
|
|
329
|
+
|
|
330
|
+
tf_pcc = cell_tf_pcc_matrix[cell_idx, tf_index[tf]] if tf in tf_index else 0
|
|
331
|
+
tg_pcc = cell_gene_pcc_matrix[cell_idx, tg_idx] if tg_idx != -1 else 0
|
|
332
|
+
|
|
333
|
+
cell_data[rec_idx, pair_idx] = rec_pcc * tf_pcc * tg_pcc
|
|
334
|
+
|
|
335
|
+
cell_sparse = csr_matrix(cell_data)
|
|
336
|
+
cell_results[cell] = cell_sparse
|
|
337
|
+
save_npz(f"{output_dir}/{cell}.npz", cell_sparse)
|
|
338
|
+
|
|
339
|
+
combined_data[cell_idx, :] = cell_data.ravel()
|
|
340
|
+
|
|
341
|
+
combined_sparse = csr_matrix(combined_data)
|
|
342
|
+
save_npz(f"{output_dir}/combined_results.npz", combined_sparse)
|
|
343
|
+
|
|
344
|
+
with open(f"{output_dir}/combined_row_names.json", "w") as f:
|
|
345
|
+
json.dump(cells, f)
|
|
346
|
+
with open(f"{output_dir}/combined_col_names.json", "w") as f:
|
|
347
|
+
json.dump([f"{rec}->{pair}" for rec in receptors for pair in tf_tg_pairs], f)
|
|
348
|
+
|
|
349
|
+
return cell_results, combined_sparse
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def calculate_r_tf_tg_strength(
|
|
353
|
+
cell_rep: pd.DataFrame,
|
|
354
|
+
cor_dir: str,
|
|
355
|
+
reg_dir: str,
|
|
356
|
+
output_dir: str = "output_cells_total"
|
|
357
|
+
) -> Tuple[Dict[str, csr_matrix], csr_matrix]:
|
|
358
|
+
"""
|
|
359
|
+
Both correlation and regulation score
|
|
360
|
+
"""
|
|
361
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
362
|
+
|
|
363
|
+
with open(f"{cor_dir}/global_row_names.json", "r") as f:
|
|
364
|
+
receptors = json.load(f)
|
|
365
|
+
with open(f"{cor_dir}/global_col_names.json", "r") as f:
|
|
366
|
+
tf_tg_pairs = json.load(f)
|
|
367
|
+
|
|
368
|
+
with open(f"{output_dir}/global_row_names.json", "w") as f:
|
|
369
|
+
json.dump(receptors, f)
|
|
370
|
+
with open(f"{output_dir}/global_col_names.json", "w") as f:
|
|
371
|
+
json.dump(tf_tg_pairs, f)
|
|
372
|
+
|
|
373
|
+
cells = cell_rep.index.tolist()
|
|
374
|
+
|
|
375
|
+
cell_results = {}
|
|
376
|
+
combined_data = []
|
|
377
|
+
|
|
378
|
+
for cell in tqdm(cells, desc="Calculating total scores"):
|
|
379
|
+
cor_path = f"{cor_dir}/{cell}.npz"
|
|
380
|
+
cor_mat = load_npz(cor_path)
|
|
381
|
+
|
|
382
|
+
reg_path = f"{reg_dir}/{cell}.npz"
|
|
383
|
+
reg_mat = load_npz(reg_path)
|
|
384
|
+
|
|
385
|
+
total_mat = cor_mat.multiply(reg_mat)
|
|
386
|
+
cell_results[cell] = total_mat
|
|
387
|
+
|
|
388
|
+
save_npz(f"{output_dir}/{cell}.npz", total_mat)
|
|
389
|
+
|
|
390
|
+
combined_data.append(total_mat.toarray().ravel())
|
|
391
|
+
|
|
392
|
+
combined_sparse = csr_matrix(np.vstack(combined_data))
|
|
393
|
+
save_npz(f"{output_dir}/combined_results.npz", combined_sparse)
|
|
394
|
+
|
|
395
|
+
with open(f"{output_dir}/combined_row_names.json", "w") as f:
|
|
396
|
+
json.dump(cells, f)
|
|
397
|
+
with open(f"{output_dir}/combined_col_names.json", "w") as f:
|
|
398
|
+
json.dump([f"{rec}->{pair}" for rec in receptors for pair in tf_tg_pairs], f)
|
|
399
|
+
|
|
400
|
+
return cell_results, combined_sparse
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def calculate_r_tf_tg_reg(
|
|
405
|
+
cell_rep: pd.DataFrame,
|
|
406
|
+
tf_tg_score_df: pd.DataFrame,
|
|
407
|
+
rec_tf_pcc: pd.DataFrame,
|
|
408
|
+
rna_mat: pd.DataFrame,
|
|
409
|
+
output_dir: str = "output_cells"
|
|
410
|
+
) -> Tuple[Dict[str, csr_matrix], csr_matrix]:
|
|
411
|
+
"""
|
|
412
|
+
Get receptor-tf-tg regulation score for each cell, save as sparse format
|
|
413
|
+
"""
|
|
414
|
+
assert tf_tg_score_df.index.isin(cell_rep.index).all(), "Cell names mismatch"
|
|
415
|
+
assert {"Receptor_Symbol", "TF_Symbol"}.issubset(rec_tf_pcc.columns), "rec_tf_pcc Missing necessary columns"
|
|
416
|
+
|
|
417
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
418
|
+
|
|
419
|
+
def get_receptor_expression(receptor: str, cell: str) -> float:
|
|
420
|
+
"""receptor expression"""
|
|
421
|
+
if '_' in receptor:
|
|
422
|
+
parts = receptor.split('_')
|
|
423
|
+
exprs = [rna_mat.loc[p, cell] if p in rna_mat.index else 0 for p in parts]
|
|
424
|
+
return np.mean(exprs)
|
|
425
|
+
return rna_mat.loc[receptor, cell] if receptor in rna_mat.index else 0
|
|
426
|
+
|
|
427
|
+
rec_tf_map = rec_tf_pcc.groupby("Receptor_Symbol")["TF_Symbol"].agg(set).to_dict()
|
|
428
|
+
tf_to_pairs = defaultdict(list)
|
|
429
|
+
for pair in tf_tg_score_df.columns:
|
|
430
|
+
tf = pair.split("->")[0]
|
|
431
|
+
tf_to_pairs[tf].append(pair)
|
|
432
|
+
pcc_map = rec_tf_pcc.set_index(["Receptor_Symbol", "TF_Symbol"])["scores"].to_dict()
|
|
433
|
+
|
|
434
|
+
all_receptors = list(rec_tf_map.keys())
|
|
435
|
+
all_tf_tg_pairs = tf_tg_score_df.columns.tolist()
|
|
436
|
+
cells = tf_tg_score_df.index.tolist()
|
|
437
|
+
|
|
438
|
+
with open(f"{output_dir}/global_row_names.json", "w") as f:
|
|
439
|
+
json.dump(all_receptors, f)
|
|
440
|
+
with open(f"{output_dir}/global_col_names.json", "w") as f:
|
|
441
|
+
json.dump(all_tf_tg_pairs, f)
|
|
442
|
+
|
|
443
|
+
cell_results = {}
|
|
444
|
+
combined_data = np.zeros((len(cells), len(all_receptors)*len(all_tf_tg_pairs)))
|
|
445
|
+
|
|
446
|
+
for i, cell in enumerate(tqdm(cells, desc="Processing cells")):
|
|
447
|
+
cell_scores = tf_tg_score_df.loc[cell]
|
|
448
|
+
cell_data = np.zeros((len(all_receptors), len(all_tf_tg_pairs)))
|
|
449
|
+
|
|
450
|
+
for rec_idx, receptor in enumerate(all_receptors):
|
|
451
|
+
rec_exp = get_receptor_expression(receptor, cell)
|
|
452
|
+
if rec_exp == 0:
|
|
453
|
+
continue
|
|
454
|
+
for tf in rec_tf_map.get(receptor, set()):
|
|
455
|
+
if tf not in tf_to_pairs:
|
|
456
|
+
continue
|
|
457
|
+
|
|
458
|
+
pcc = pcc_map.get((receptor, tf), 0)
|
|
459
|
+
pair_indices = [tf_tg_score_df.columns.get_loc(p) for p in tf_to_pairs[tf]]
|
|
460
|
+
cell_data[rec_idx, pair_indices] = rec_exp * pcc * cell_scores.iloc[pair_indices].values
|
|
461
|
+
|
|
462
|
+
cell_sparse = csr_matrix(cell_data)
|
|
463
|
+
cell_results[cell] = cell_sparse
|
|
464
|
+
save_npz(f"{output_dir}/{cell}.npz", cell_sparse)
|
|
465
|
+
|
|
466
|
+
combined_data[i, :] = cell_data.ravel()
|
|
467
|
+
|
|
468
|
+
combined_sparse = csr_matrix(combined_data)
|
|
469
|
+
save_npz(f"{output_dir}/combined_results.npz", combined_sparse)
|
|
470
|
+
|
|
471
|
+
with open(f"{output_dir}/combined_row_names.json", "w") as f:
|
|
472
|
+
json.dump(cells, f)
|
|
473
|
+
with open(f"{output_dir}/combined_col_names.json", "w") as f:
|
|
474
|
+
json.dump([f"{rec}->{pair}" for rec in all_receptors for pair in all_tf_tg_pairs], f)
|
|
475
|
+
|
|
476
|
+
return cell_results, combined_sparse
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def calculate_l_r_tf_tg_strength(
|
|
481
|
+
l_r_tf_tg_df: pd.DataFrame,
|
|
482
|
+
combined_npz_path: str,
|
|
483
|
+
global_row_names_path: str,
|
|
484
|
+
global_col_names_path: str,
|
|
485
|
+
ccc_lrp_path: str,
|
|
486
|
+
output_dir: str = "ligand_cascade_results"
|
|
487
|
+
) -> None:
|
|
488
|
+
"""
|
|
489
|
+
Get ligand-receptor-tf-tg score, save as signaling transduction response score for ligands
|
|
490
|
+
"""
|
|
491
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
492
|
+
|
|
493
|
+
print("Loading data...")
|
|
494
|
+
try:
|
|
495
|
+
combined_sparse = load_npz(combined_npz_path).tocsc()
|
|
496
|
+
with open(global_row_names_path, 'r') as f:
|
|
497
|
+
cell_names = json.load(f)
|
|
498
|
+
with open(global_col_names_path, 'r') as f:
|
|
499
|
+
r_tf_tg_pairs = json.load(f)
|
|
500
|
+
ccc_lrp_df = pd.read_csv(ccc_lrp_path, sep='\t', index_col=0)
|
|
501
|
+
except Exception as e:
|
|
502
|
+
print(f"Error loading data: {e}")
|
|
503
|
+
return
|
|
504
|
+
print("Data loaded successfully")
|
|
505
|
+
|
|
506
|
+
r_tf_tg_to_col = {pair: idx for idx, pair in enumerate(r_tf_tg_pairs)}
|
|
507
|
+
|
|
508
|
+
print("Identifying zero L-R pairs...")
|
|
509
|
+
zero_lr_pairs = set()
|
|
510
|
+
for col in ccc_lrp_df.columns:
|
|
511
|
+
if np.all(ccc_lrp_df[col] == 0):
|
|
512
|
+
zero_lr_pairs.add(col)
|
|
513
|
+
print(f"Found {len(zero_lr_pairs)} zero L-R pairs to skip")
|
|
514
|
+
|
|
515
|
+
ligands = l_r_tf_tg_df['Ligand_Symbol'].unique()
|
|
516
|
+
|
|
517
|
+
for ligand in tqdm(ligands, desc="Processing ligands"):
|
|
518
|
+
ligand_subdf = l_r_tf_tg_df[l_r_tf_tg_df['Ligand_Symbol'] == ligand]
|
|
519
|
+
result_data = {}
|
|
520
|
+
|
|
521
|
+
for _, row in ligand_subdf.iterrows():
|
|
522
|
+
receptor = row['Receptor_Symbol']
|
|
523
|
+
tf = row['TF_Symbol']
|
|
524
|
+
tg = row['TG_Symbol']
|
|
525
|
+
l_r_pair = f"{ligand}->{receptor}"
|
|
526
|
+
r_tf_tg_pair = f"{receptor}->{tf}->{tg}"
|
|
527
|
+
|
|
528
|
+
if l_r_pair in zero_lr_pairs:
|
|
529
|
+
continue
|
|
530
|
+
|
|
531
|
+
if l_r_pair not in ccc_lrp_df.columns:
|
|
532
|
+
continue
|
|
533
|
+
|
|
534
|
+
if r_tf_tg_pair not in r_tf_tg_to_col:
|
|
535
|
+
continue
|
|
536
|
+
|
|
537
|
+
col_idx = r_tf_tg_to_col[r_tf_tg_pair]
|
|
538
|
+
|
|
539
|
+
inter_signal = ccc_lrp_df[l_r_pair].values
|
|
540
|
+
intra_signal = combined_sparse[:, col_idx].toarray().flatten()
|
|
541
|
+
|
|
542
|
+
total_signal = inter_signal * intra_signal
|
|
543
|
+
pathway = f"{ligand}->{receptor}->{tf}->{tg}"
|
|
544
|
+
result_data[pathway] = total_signal
|
|
545
|
+
|
|
546
|
+
if result_data:
|
|
547
|
+
result_df = pd.DataFrame(result_data, index=cell_names)
|
|
548
|
+
result_df.to_csv(f"{output_dir}/{ligand}.csv", index=True)
|
|
549
|
+
|
|
550
|
+
print(f"Processing completed. Results saved to {output_dir}")
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def calculate_l_r_tf_tg_strength_by_tg(
|
|
555
|
+
l_r_tf_tg_df: pd.DataFrame,
|
|
556
|
+
combined_npz_path: str,
|
|
557
|
+
global_row_names_path: str,
|
|
558
|
+
global_col_names_path: str,
|
|
559
|
+
ccc_lrp_path: str,
|
|
560
|
+
output_dir: str = "TG_cascade_results"
|
|
561
|
+
) -> None:
|
|
562
|
+
"""
|
|
563
|
+
Get ligand-receptor-tf-tg score, save as signaling transduction response score for TGs
|
|
564
|
+
"""
|
|
565
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
566
|
+
|
|
567
|
+
print("Loading data...")
|
|
568
|
+
combined_sparse = load_npz(combined_npz_path).tocsc()
|
|
569
|
+
with open(global_row_names_path, 'r') as f:
|
|
570
|
+
cell_names = json.load(f)
|
|
571
|
+
with open(global_col_names_path, 'r') as f:
|
|
572
|
+
r_tf_tg_pairs = json.load(f)
|
|
573
|
+
ccc_lrp_df = pd.read_csv(ccc_lrp_path, sep='\t', index_col=0)
|
|
574
|
+
|
|
575
|
+
r_tf_tg_to_col = {pair: idx for idx, pair in enumerate(r_tf_tg_pairs)}
|
|
576
|
+
|
|
577
|
+
l_r_pairs = ccc_lrp_df.columns
|
|
578
|
+
|
|
579
|
+
print("Identifying zero L-R pairs...")
|
|
580
|
+
zero_lr_pairs = set()
|
|
581
|
+
for col in ccc_lrp_df.columns:
|
|
582
|
+
if np.all(ccc_lrp_df[col] == 0):
|
|
583
|
+
zero_lr_pairs.add(col)
|
|
584
|
+
print(f"Found {len(zero_lr_pairs)} zero L-R pairs to skip")
|
|
585
|
+
|
|
586
|
+
all_tgs = l_r_tf_tg_df["TG_Symbol"].unique()
|
|
587
|
+
for tg in tqdm(all_tgs, desc="Processing TGs"):
|
|
588
|
+
tg_subset = l_r_tf_tg_df[l_r_tf_tg_df["TG_Symbol"] == tg]
|
|
589
|
+
tg_data = {}
|
|
590
|
+
|
|
591
|
+
for _, row in tg_subset.iterrows():
|
|
592
|
+
ligand = row["Ligand_Symbol"]
|
|
593
|
+
receptor = row["Receptor_Symbol"]
|
|
594
|
+
tf = row["TF_Symbol"]
|
|
595
|
+
l_r_pair = f"{ligand}->{receptor}"
|
|
596
|
+
r_tf_tg_pair = f"{receptor}->{tf}->{tg}"
|
|
597
|
+
|
|
598
|
+
if l_r_pair in zero_lr_pairs:
|
|
599
|
+
continue
|
|
600
|
+
|
|
601
|
+
if l_r_pair not in l_r_pairs or r_tf_tg_pair not in r_tf_tg_to_col:
|
|
602
|
+
continue
|
|
603
|
+
|
|
604
|
+
l_r_signal = ccc_lrp_df[l_r_pair].values
|
|
605
|
+
r_tf_tg_col = r_tf_tg_to_col[r_tf_tg_pair]
|
|
606
|
+
r_tf_tg_signal = combined_sparse[:, r_tf_tg_col].toarray().flatten()
|
|
607
|
+
total_signal = l_r_signal * r_tf_tg_signal
|
|
608
|
+
|
|
609
|
+
path = f"{ligand}->{receptor}->{tf}->{tg}"
|
|
610
|
+
tg_data[path] = total_signal
|
|
611
|
+
|
|
612
|
+
if tg_data:
|
|
613
|
+
tg_df = pd.DataFrame(tg_data, index=cell_names)
|
|
614
|
+
tg_df.to_csv(os.path.join(output_dir, f"{tg}.csv"), index=True)
|
|
615
|
+
|
|
616
|
+
print(f"All TG networks saved to {output_dir}")
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def generate_background_l_r_tf_tg_strength(
|
|
621
|
+
l_r_tf_tg_df: pd.DataFrame,
|
|
622
|
+
combined_npz_path: str,
|
|
623
|
+
global_row_names_path: str,
|
|
624
|
+
global_col_names_path: str,
|
|
625
|
+
ccc_lrp_path: str,
|
|
626
|
+
output_dir: str = "random_ligand_cascade_results",
|
|
627
|
+
random_seed: int = 42
|
|
628
|
+
) -> None:
|
|
629
|
+
"""
|
|
630
|
+
Generate background data
|
|
631
|
+
"""
|
|
632
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
633
|
+
|
|
634
|
+
print("Loading data...")
|
|
635
|
+
try:
|
|
636
|
+
original_sparse = load_npz(combined_npz_path).tocsc()
|
|
637
|
+
with open(global_row_names_path, 'r') as f:
|
|
638
|
+
cell_names = json.load(f)
|
|
639
|
+
with open(global_col_names_path, 'r') as f:
|
|
640
|
+
r_tf_tg_pairs = json.load(f)
|
|
641
|
+
ccc_lrp_df = pd.read_csv(ccc_lrp_path, sep='\t', index_col=0)
|
|
642
|
+
except Exception as e:
|
|
643
|
+
print(f"Error loading data: {e}")
|
|
644
|
+
return
|
|
645
|
+
print("Data loaded successfully")
|
|
646
|
+
|
|
647
|
+
final_cell_names = cell_names * 10
|
|
648
|
+
print("Final cell names length:", len(final_cell_names))
|
|
649
|
+
|
|
650
|
+
print("Generating background data by row permutation...")
|
|
651
|
+
np.random.seed(random_seed)
|
|
652
|
+
nonzero_rows = np.unique(original_sparse.nonzero()[0])
|
|
653
|
+
background_matrices = []
|
|
654
|
+
|
|
655
|
+
for _ in range(10):
|
|
656
|
+
new_nonzero_rows = np.random.choice(np.arange(original_sparse.shape[0]), size=len(nonzero_rows), replace=False)
|
|
657
|
+
|
|
658
|
+
permuted_indices = np.arange(original_sparse.shape[0])
|
|
659
|
+
permuted_indices[nonzero_rows] = new_nonzero_rows
|
|
660
|
+
|
|
661
|
+
background_sparse = csc_matrix(
|
|
662
|
+
(original_sparse.data,
|
|
663
|
+
permuted_indices[original_sparse.indices],
|
|
664
|
+
original_sparse.indptr),
|
|
665
|
+
shape=original_sparse.shape
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
background_matrices.append(background_sparse)
|
|
669
|
+
|
|
670
|
+
final_background_sparse = vstack(background_matrices)
|
|
671
|
+
|
|
672
|
+
print("Final background sparse matrix shape:", final_background_sparse.shape)
|
|
673
|
+
|
|
674
|
+
r_tf_tg_to_col = {pair: idx for idx, pair in enumerate(r_tf_tg_pairs)}
|
|
675
|
+
|
|
676
|
+
print("Identifying zero L-R pairs...")
|
|
677
|
+
zero_lr_pairs = set()
|
|
678
|
+
for col in ccc_lrp_df.columns:
|
|
679
|
+
if np.all(ccc_lrp_df[col] == 0):
|
|
680
|
+
zero_lr_pairs.add(col)
|
|
681
|
+
print(f"Found {len(zero_lr_pairs)} zero L-R pairs to skip")
|
|
682
|
+
|
|
683
|
+
ligands = l_r_tf_tg_df['Ligand_Symbol'].unique()
|
|
684
|
+
|
|
685
|
+
for ligand in tqdm(ligands, desc="Processing ligands"):
|
|
686
|
+
ligand_subdf = l_r_tf_tg_df[l_r_tf_tg_df['Ligand_Symbol'] == ligand]
|
|
687
|
+
result_data = {}
|
|
688
|
+
|
|
689
|
+
for _, row in ligand_subdf.iterrows():
|
|
690
|
+
receptor = row['Receptor_Symbol']
|
|
691
|
+
tf = row['TF_Symbol']
|
|
692
|
+
tg = row['TG_Symbol']
|
|
693
|
+
l_r_pair = f"{ligand}->{receptor}"
|
|
694
|
+
r_tf_tg_pair = f"{receptor}->{tf}->{tg}"
|
|
695
|
+
|
|
696
|
+
if l_r_pair in zero_lr_pairs:
|
|
697
|
+
continue
|
|
698
|
+
|
|
699
|
+
if l_r_pair not in ccc_lrp_df.columns:
|
|
700
|
+
continue
|
|
701
|
+
|
|
702
|
+
if r_tf_tg_pair not in r_tf_tg_to_col:
|
|
703
|
+
continue
|
|
704
|
+
|
|
705
|
+
col_idx = r_tf_tg_to_col[r_tf_tg_pair]
|
|
706
|
+
|
|
707
|
+
inter_signal = ccc_lrp_df[l_r_pair].values
|
|
708
|
+
intra_signal = final_background_sparse[:, col_idx].toarray().flatten()
|
|
709
|
+
|
|
710
|
+
total_signal = inter_signal * intra_signal
|
|
711
|
+
pathway = f"{ligand}->{receptor}->{tf}->{tg}"
|
|
712
|
+
result_data[pathway] = total_signal
|
|
713
|
+
|
|
714
|
+
if result_data:
|
|
715
|
+
result_df = pd.DataFrame(result_data, index=final_cell_names)
|
|
716
|
+
result_df.to_csv(f"{output_dir}/{ligand}.csv", index=True)
|
|
717
|
+
|
|
718
|
+
print(f"Processing completed. Results saved to {output_dir}")
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
def generate_background_l_r_tf_tg_strength_parallel(
|
|
723
|
+
l_r_tf_tg_df: pd.DataFrame,
|
|
724
|
+
combined_npz_path: str,
|
|
725
|
+
global_row_names_path: str,
|
|
726
|
+
global_col_names_path: str,
|
|
727
|
+
ccc_lrp_path: str,
|
|
728
|
+
output_dir: str = "random_ligand_cascade_results",
|
|
729
|
+
random_seed: int = 42,
|
|
730
|
+
n_processes: int = None
|
|
731
|
+
) -> None:
|
|
732
|
+
"""
|
|
733
|
+
Generate background data by parallel
|
|
734
|
+
"""
|
|
735
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
736
|
+
|
|
737
|
+
print("Loading data...")
|
|
738
|
+
try:
|
|
739
|
+
original_sparse = load_npz(combined_npz_path).tocsc()
|
|
740
|
+
with open(global_row_names_path, 'r') as f:
|
|
741
|
+
cell_names = json.load(f)
|
|
742
|
+
with open(global_col_names_path, 'r') as f:
|
|
743
|
+
r_tf_tg_pairs = json.load(f)
|
|
744
|
+
ccc_lrp_df = pd.read_csv(ccc_lrp_path, sep='\t', index_col=0)
|
|
745
|
+
except Exception as e:
|
|
746
|
+
print(f"Error loading data: {e}")
|
|
747
|
+
return
|
|
748
|
+
print("Data loaded successfully")
|
|
749
|
+
|
|
750
|
+
final_cell_names = cell_names * 10
|
|
751
|
+
print("Final cell names length:", len(final_cell_names))
|
|
752
|
+
|
|
753
|
+
print("Generating background data by row permutation...")
|
|
754
|
+
np.random.seed(random_seed)
|
|
755
|
+
nonzero_rows = np.unique(original_sparse.nonzero()[0])
|
|
756
|
+
background_matrices = []
|
|
757
|
+
|
|
758
|
+
for _ in range(10):
|
|
759
|
+
new_nonzero_rows = np.random.choice(np.arange(original_sparse.shape[0]), size=len(nonzero_rows), replace=False)
|
|
760
|
+
permuted_indices = np.arange(original_sparse.shape[0])
|
|
761
|
+
permuted_indices[nonzero_rows] = new_nonzero_rows
|
|
762
|
+
|
|
763
|
+
background_sparse = csc_matrix(
|
|
764
|
+
(original_sparse.data,
|
|
765
|
+
permuted_indices[original_sparse.indices],
|
|
766
|
+
original_sparse.indptr),
|
|
767
|
+
shape=original_sparse.shape
|
|
768
|
+
)
|
|
769
|
+
background_matrices.append(background_sparse)
|
|
770
|
+
|
|
771
|
+
final_background_sparse = vstack(background_matrices)
|
|
772
|
+
print("Final background sparse matrix shape:", final_background_sparse.shape)
|
|
773
|
+
|
|
774
|
+
r_tf_tg_to_col = {pair: idx for idx, pair in enumerate(r_tf_tg_pairs)}
|
|
775
|
+
|
|
776
|
+
print("Identifying zero L-R pairs...")
|
|
777
|
+
zero_lr_pairs = set()
|
|
778
|
+
for col in ccc_lrp_df.columns:
|
|
779
|
+
if np.all(ccc_lrp_df[col] == 0):
|
|
780
|
+
zero_lr_pairs.add(col)
|
|
781
|
+
print(f"Found {len(zero_lr_pairs)} zero L-R pairs to skip")
|
|
782
|
+
|
|
783
|
+
ligands = l_r_tf_tg_df['Ligand_Symbol'].unique()
|
|
784
|
+
|
|
785
|
+
process_ligand_partial = partial(
|
|
786
|
+
_process_single_ligand,
|
|
787
|
+
l_r_tf_tg_df=l_r_tf_tg_df,
|
|
788
|
+
ccc_lrp_df=ccc_lrp_df,
|
|
789
|
+
r_tf_tg_to_col=r_tf_tg_to_col,
|
|
790
|
+
zero_lr_pairs=zero_lr_pairs,
|
|
791
|
+
final_background_sparse=final_background_sparse,
|
|
792
|
+
final_cell_names=final_cell_names,
|
|
793
|
+
output_dir=output_dir
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
if n_processes is None:
|
|
797
|
+
n_processes = multiprocessing.cpu_count() - 1
|
|
798
|
+
|
|
799
|
+
print(f"Starting parallel processing with {n_processes} processes...")
|
|
800
|
+
with multiprocessing.Pool(processes=n_processes) as pool:
|
|
801
|
+
pool.map(process_ligand_partial, ligands)
|
|
802
|
+
|
|
803
|
+
print(f"Processing completed. Results saved to {output_dir}")
|
|
804
|
+
|
|
805
|
+
def _process_single_ligand(
|
|
806
|
+
ligand: str,
|
|
807
|
+
l_r_tf_tg_df: pd.DataFrame,
|
|
808
|
+
ccc_lrp_df: pd.DataFrame,
|
|
809
|
+
r_tf_tg_to_col: dict,
|
|
810
|
+
zero_lr_pairs: set,
|
|
811
|
+
final_background_sparse: csc_matrix,
|
|
812
|
+
final_cell_names: list,
|
|
813
|
+
output_dir: str
|
|
814
|
+
) -> None:
|
|
815
|
+
ligand_subdf = l_r_tf_tg_df[l_r_tf_tg_df['Ligand_Symbol'] == ligand]
|
|
816
|
+
result_data = {}
|
|
817
|
+
|
|
818
|
+
for _, row in ligand_subdf.iterrows():
|
|
819
|
+
receptor = row['Receptor_Symbol']
|
|
820
|
+
tf = row['TF_Symbol']
|
|
821
|
+
tg = row['TG_Symbol']
|
|
822
|
+
l_r_pair = f"{ligand}->{receptor}"
|
|
823
|
+
r_tf_tg_pair = f"{receptor}->{tf}->{tg}"
|
|
824
|
+
|
|
825
|
+
if l_r_pair in zero_lr_pairs:
|
|
826
|
+
continue
|
|
827
|
+
if l_r_pair not in ccc_lrp_df.columns:
|
|
828
|
+
continue
|
|
829
|
+
if r_tf_tg_pair not in r_tf_tg_to_col:
|
|
830
|
+
continue
|
|
831
|
+
|
|
832
|
+
col_idx = r_tf_tg_to_col[r_tf_tg_pair]
|
|
833
|
+
inter_signal = ccc_lrp_df[l_r_pair].values
|
|
834
|
+
intra_signal = final_background_sparse[:, col_idx].toarray().flatten()
|
|
835
|
+
total_signal = inter_signal * intra_signal
|
|
836
|
+
pathway = f"{ligand}->{receptor}->{tf}->{tg}"
|
|
837
|
+
result_data[pathway] = total_signal
|
|
838
|
+
|
|
839
|
+
if result_data:
|
|
840
|
+
result_df = pd.DataFrame(result_data, index=final_cell_names)
|
|
841
|
+
result_df.to_csv(f"{output_dir}/{ligand}.csv", index=True)
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
def generate_background_l_r_tf_tg_strength_by_tg(
|
|
847
|
+
l_r_tf_tg_df: pd.DataFrame,
|
|
848
|
+
combined_npz_path: str,
|
|
849
|
+
global_row_names_path: str,
|
|
850
|
+
global_col_names_path: str,
|
|
851
|
+
ccc_lrp_path: str,
|
|
852
|
+
output_dir: str = "random_TG_cascade_results",
|
|
853
|
+
random_seed: int = 42
|
|
854
|
+
) -> None:
|
|
855
|
+
"""
|
|
856
|
+
Generate background data grouped by TG through permutation
|
|
857
|
+
"""
|
|
858
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
859
|
+
|
|
860
|
+
print("Loading data...")
|
|
861
|
+
try:
|
|
862
|
+
original_sparse = load_npz(combined_npz_path).tocsc()
|
|
863
|
+
with open(global_row_names_path, 'r') as f:
|
|
864
|
+
cell_names = json.load(f)
|
|
865
|
+
with open(global_col_names_path, 'r') as f:
|
|
866
|
+
r_tf_tg_pairs = json.load(f)
|
|
867
|
+
ccc_lrp_df = pd.read_csv(ccc_lrp_path, sep='\t', index_col=0)
|
|
868
|
+
except Exception as e:
|
|
869
|
+
print(f"Error loading data: {e}")
|
|
870
|
+
return
|
|
871
|
+
print("Data loaded successfully")
|
|
872
|
+
|
|
873
|
+
print("Generating background matrix by row permutation...")
|
|
874
|
+
np.random.seed(random_seed)
|
|
875
|
+
|
|
876
|
+
nonzero_rows = np.unique(original_sparse.nonzero()[0])
|
|
877
|
+
new_nonzero_rows = np.random.choice(np.arange(original_sparse.shape[0]), size=len(nonzero_rows), replace=False)
|
|
878
|
+
permuted_indices = np.arange(original_sparse.shape[0])
|
|
879
|
+
permuted_indices[nonzero_rows] = new_nonzero_rows
|
|
880
|
+
|
|
881
|
+
background_sparse = csc_matrix(
|
|
882
|
+
(original_sparse.data,
|
|
883
|
+
permuted_indices[original_sparse.indices],
|
|
884
|
+
original_sparse.indptr),
|
|
885
|
+
shape=original_sparse.shape
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
r_tf_tg_to_col = {pair: idx for idx, pair in enumerate(r_tf_tg_pairs)}
|
|
889
|
+
|
|
890
|
+
l_r_pairs = ccc_lrp_df.columns
|
|
891
|
+
|
|
892
|
+
print("Identifying zero L-R pairs...")
|
|
893
|
+
zero_lr_pairs = set()
|
|
894
|
+
for col in ccc_lrp_df.columns:
|
|
895
|
+
if np.all(ccc_lrp_df[col] == 0):
|
|
896
|
+
zero_lr_pairs.add(col)
|
|
897
|
+
print(f"Found {len(zero_lr_pairs)} zero L-R pairs to skip")
|
|
898
|
+
|
|
899
|
+
all_tgs = l_r_tf_tg_df["TG_Symbol"].unique()
|
|
900
|
+
|
|
901
|
+
for tg in tqdm(all_tgs, desc="Processing TGs (background)"):
|
|
902
|
+
tg_subset = l_r_tf_tg_df[l_r_tf_tg_df["TG_Symbol"] == tg]
|
|
903
|
+
tg_data = {}
|
|
904
|
+
|
|
905
|
+
for _, row in tg_subset.iterrows():
|
|
906
|
+
ligand = row["Ligand_Symbol"]
|
|
907
|
+
receptor = row["Receptor_Symbol"]
|
|
908
|
+
tf = row["TF_Symbol"]
|
|
909
|
+
l_r_pair = f"{ligand}->{receptor}"
|
|
910
|
+
r_tf_tg_pair = f"{receptor}->{tf}->{tg}"
|
|
911
|
+
|
|
912
|
+
if l_r_pair in zero_lr_pairs:
|
|
913
|
+
continue
|
|
914
|
+
|
|
915
|
+
if l_r_pair not in l_r_pairs or r_tf_tg_pair not in r_tf_tg_to_col:
|
|
916
|
+
continue
|
|
917
|
+
|
|
918
|
+
l_r_signal = ccc_lrp_df[l_r_pair].values
|
|
919
|
+
r_tf_tg_col = r_tf_tg_to_col[r_tf_tg_pair]
|
|
920
|
+
r_tf_tg_signal = background_sparse[:, r_tf_tg_col].toarray().flatten()
|
|
921
|
+
total_signal = l_r_signal * r_tf_tg_signal
|
|
922
|
+
|
|
923
|
+
path = f"{ligand}->{receptor}->{tf}->{tg}"
|
|
924
|
+
tg_data[path] = total_signal
|
|
925
|
+
|
|
926
|
+
if tg_data:
|
|
927
|
+
tg_df = pd.DataFrame(tg_data, index=cell_names)
|
|
928
|
+
tg_df.to_csv(os.path.join(output_dir, f"{tg}.csv"), index=True)
|
|
929
|
+
|
|
930
|
+
print(f"Background data generation completed. Results saved to {output_dir}")
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
def generate_background_l_r_tf_tg_strength_by_tg_parallel(
|
|
935
|
+
l_r_tf_tg_df: pd.DataFrame,
|
|
936
|
+
combined_npz_path: str,
|
|
937
|
+
global_row_names_path: str,
|
|
938
|
+
global_col_names_path: str,
|
|
939
|
+
ccc_lrp_path: str,
|
|
940
|
+
output_dir: str = "random_TG_cascade_results",
|
|
941
|
+
random_seed: int = 42,
|
|
942
|
+
n_processes: int = None
|
|
943
|
+
) -> None:
|
|
944
|
+
|
|
945
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
946
|
+
|
|
947
|
+
print("Loading data...")
|
|
948
|
+
try:
|
|
949
|
+
original_sparse = load_npz(combined_npz_path).tocsc()
|
|
950
|
+
with open(global_row_names_path, 'r') as f:
|
|
951
|
+
cell_names = json.load(f)
|
|
952
|
+
with open(global_col_names_path, 'r') as f:
|
|
953
|
+
r_tf_tg_pairs = json.load(f)
|
|
954
|
+
ccc_lrp_df = pd.read_csv(ccc_lrp_path, sep='\t', index_col=0)
|
|
955
|
+
except Exception as e:
|
|
956
|
+
print(f"Error loading data: {e}")
|
|
957
|
+
return
|
|
958
|
+
print("Data loaded successfully")
|
|
959
|
+
|
|
960
|
+
final_cell_names = cell_names * 10
|
|
961
|
+
print("Final cell names length:", len(final_cell_names))
|
|
962
|
+
|
|
963
|
+
print("Generating background matrix by row permutation...")
|
|
964
|
+
np.random.seed(random_seed)
|
|
965
|
+
nonzero_rows = np.unique(original_sparse.nonzero()[0])
|
|
966
|
+
background_matrices = []
|
|
967
|
+
|
|
968
|
+
for _ in range(10):
|
|
969
|
+
new_nonzero_rows = np.random.choice(np.arange(original_sparse.shape[0]), size=len(nonzero_rows), replace=False)
|
|
970
|
+
permuted_indices = np.arange(original_sparse.shape[0])
|
|
971
|
+
permuted_indices[nonzero_rows] = new_nonzero_rows
|
|
972
|
+
|
|
973
|
+
background_sparse = csc_matrix(
|
|
974
|
+
(original_sparse.data,
|
|
975
|
+
permuted_indices[original_sparse.indices],
|
|
976
|
+
original_sparse.indptr),
|
|
977
|
+
shape=original_sparse.shape
|
|
978
|
+
)
|
|
979
|
+
background_matrices.append(background_sparse)
|
|
980
|
+
|
|
981
|
+
final_background_sparse = vstack(background_matrices)
|
|
982
|
+
print("Final background sparse matrix shape:", final_background_sparse.shape)
|
|
983
|
+
|
|
984
|
+
r_tf_tg_to_col = {pair: idx for idx, pair in enumerate(r_tf_tg_pairs)}
|
|
985
|
+
l_r_pairs = ccc_lrp_df.columns
|
|
986
|
+
|
|
987
|
+
print("Identifying zero L-R pairs...")
|
|
988
|
+
zero_lr_pairs = set()
|
|
989
|
+
for col in ccc_lrp_df.columns:
|
|
990
|
+
if np.all(ccc_lrp_df[col] == 0):
|
|
991
|
+
zero_lr_pairs.add(col)
|
|
992
|
+
print(f"Found {len(zero_lr_pairs)} zero L-R pairs to skip")
|
|
993
|
+
|
|
994
|
+
all_tgs = l_r_tf_tg_df["TG_Symbol"].unique()
|
|
995
|
+
|
|
996
|
+
process_tg_partial = partial(
|
|
997
|
+
_process_single_tg_background,
|
|
998
|
+
l_r_tf_tg_df=l_r_tf_tg_df,
|
|
999
|
+
ccc_lrp_df=ccc_lrp_df,
|
|
1000
|
+
r_tf_tg_to_col=r_tf_tg_to_col,
|
|
1001
|
+
zero_lr_pairs=zero_lr_pairs,
|
|
1002
|
+
final_background_sparse=final_background_sparse,
|
|
1003
|
+
final_cell_names=final_cell_names,
|
|
1004
|
+
l_r_pairs=l_r_pairs,
|
|
1005
|
+
output_dir=output_dir
|
|
1006
|
+
)
|
|
1007
|
+
|
|
1008
|
+
if n_processes is None:
|
|
1009
|
+
n_processes = multiprocessing.cpu_count() - 1
|
|
1010
|
+
|
|
1011
|
+
print(f"Starting parallel processing with {n_processes} processes...")
|
|
1012
|
+
with multiprocessing.Pool(processes=n_processes) as pool:
|
|
1013
|
+
list(tqdm(
|
|
1014
|
+
pool.imap(process_tg_partial, all_tgs),
|
|
1015
|
+
total=len(all_tgs),
|
|
1016
|
+
desc="Processing TGs (background)"
|
|
1017
|
+
))
|
|
1018
|
+
# pool.map(process_tg_partial, all_tgs)
|
|
1019
|
+
|
|
1020
|
+
print(f"Background data generation completed. Results saved to {output_dir}")
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
def _process_single_tg_background(
|
|
1024
|
+
tg: str,
|
|
1025
|
+
l_r_tf_tg_df: pd.DataFrame,
|
|
1026
|
+
ccc_lrp_df: pd.DataFrame,
|
|
1027
|
+
r_tf_tg_to_col: dict,
|
|
1028
|
+
zero_lr_pairs: set,
|
|
1029
|
+
final_background_sparse: csc_matrix,
|
|
1030
|
+
final_cell_names: list,
|
|
1031
|
+
l_r_pairs: list,
|
|
1032
|
+
output_dir: str
|
|
1033
|
+
) -> None:
|
|
1034
|
+
tg_subset = l_r_tf_tg_df[l_r_tf_tg_df["TG_Symbol"] == tg]
|
|
1035
|
+
tg_data = {}
|
|
1036
|
+
|
|
1037
|
+
for _, row in tg_subset.iterrows():
|
|
1038
|
+
ligand = row["Ligand_Symbol"]
|
|
1039
|
+
receptor = row["Receptor_Symbol"]
|
|
1040
|
+
tf = row["TF_Symbol"]
|
|
1041
|
+
l_r_pair = f"{ligand}->{receptor}"
|
|
1042
|
+
r_tf_tg_pair = f"{receptor}->{tf}->{tg}"
|
|
1043
|
+
|
|
1044
|
+
if l_r_pair in zero_lr_pairs:
|
|
1045
|
+
continue
|
|
1046
|
+
if l_r_pair not in l_r_pairs or r_tf_tg_pair not in r_tf_tg_to_col:
|
|
1047
|
+
continue
|
|
1048
|
+
|
|
1049
|
+
l_r_signal = ccc_lrp_df[l_r_pair].values
|
|
1050
|
+
r_tf_tg_col = r_tf_tg_to_col[r_tf_tg_pair]
|
|
1051
|
+
r_tf_tg_signal = final_background_sparse[:, r_tf_tg_col].toarray().flatten()
|
|
1052
|
+
total_signal = l_r_signal * r_tf_tg_signal
|
|
1053
|
+
|
|
1054
|
+
path = f"{ligand}->{receptor}->{tf}->{tg}"
|
|
1055
|
+
tg_data[path] = total_signal
|
|
1056
|
+
|
|
1057
|
+
if tg_data:
|
|
1058
|
+
tg_df = pd.DataFrame(tg_data, index=final_cell_names)
|
|
1059
|
+
tg_df.to_csv(os.path.join(output_dir, f"{tg}.csv"), index=True)
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
def load_background_inter(base_path, file_pattern="CCC_module_LRP_strength_run_*.txt"):
|
|
1064
|
+
file_paths = glob.glob(base_path + file_pattern)
|
|
1065
|
+
|
|
1066
|
+
dfs = []
|
|
1067
|
+
for file in file_paths:
|
|
1068
|
+
df = pd.read_csv(file, sep='\t', index_col='Unnamed: 0')
|
|
1069
|
+
dfs.append(df)
|
|
1070
|
+
|
|
1071
|
+
combined_df = pd.concat(dfs, ignore_index=False)
|
|
1072
|
+
return combined_df
|
|
1073
|
+
|
|
1074
|
+
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
def Identify_significant_lr_pairs(
|
|
1078
|
+
background_inter_df: pd.DataFrame,
|
|
1079
|
+
sample_inter_df: pd.DataFrame,
|
|
1080
|
+
output_path: str = "sc_Significant_LR.csv",
|
|
1081
|
+
z_critical: float = None,
|
|
1082
|
+
alpha: float = 0.05,
|
|
1083
|
+
) -> pd.DataFrame:
|
|
1084
|
+
"""
|
|
1085
|
+
Identify the significant pairs (based on background and obsevered data to calculate Z-score, α=0.05, Z=1.645)
|
|
1086
|
+
"""
|
|
1087
|
+
if z_critical is None:
|
|
1088
|
+
z_critical = norm.ppf(1 - alpha)
|
|
1089
|
+
|
|
1090
|
+
combined_df = pd.concat([background_inter_df, sample_inter_df], axis=0)
|
|
1091
|
+
|
|
1092
|
+
global_mean = combined_df.mean(axis=0)
|
|
1093
|
+
global_std = combined_df.std(axis=0)
|
|
1094
|
+
|
|
1095
|
+
sample_zscore = (sample_inter_df - global_mean) / global_std
|
|
1096
|
+
|
|
1097
|
+
all_sample_results = []
|
|
1098
|
+
for sample_name, sample_data in tqdm(sample_inter_df.iterrows(), desc=f"Processing samples"):
|
|
1099
|
+
sample_zscore_row = sample_zscore.loc[sample_name]
|
|
1100
|
+
significant_mask = sample_zscore_row > z_critical
|
|
1101
|
+
significant_pathways = sample_zscore_row[significant_mask].index.tolist()
|
|
1102
|
+
|
|
1103
|
+
sample_names = [sample_name] * len(significant_pathways)
|
|
1104
|
+
sample_zscores = sample_zscore_row[significant_mask].values
|
|
1105
|
+
sample_interactions = sample_inter_df.loc[sample_name, significant_pathways].values
|
|
1106
|
+
sample_results = pd.DataFrame({
|
|
1107
|
+
"Sample_Name": sample_names,
|
|
1108
|
+
"LR_Symbol": significant_pathways,
|
|
1109
|
+
"Inter_Score": sample_interactions,
|
|
1110
|
+
"Z_Score": sample_zscores
|
|
1111
|
+
})
|
|
1112
|
+
sample_results = sample_results.sort_values("Z_Score", ascending=False)
|
|
1113
|
+
all_sample_results.append(sample_results)
|
|
1114
|
+
all_sample_results = pd.concat(all_sample_results, ignore_index=True)
|
|
1115
|
+
all_sample_results.to_csv(output_path, index=False)
|
|
1116
|
+
print(f"Significant_L->R pairs saved to {output_path}")
|
|
1117
|
+
|
|
1118
|
+
return all_sample_results
|
|
1119
|
+
|
|
1120
|
+
|
|
1121
|
+
def Identify_significant_paths(
|
|
1122
|
+
background_inter_df: pd.DataFrame,
|
|
1123
|
+
sample_inter_df: pd.DataFrame,
|
|
1124
|
+
output_path: str = "sc_Significant_LR.csv",
|
|
1125
|
+
z_critical: float = None,
|
|
1126
|
+
alpha: float = 0.05,
|
|
1127
|
+
) -> pd.DataFrame:
|
|
1128
|
+
"""
|
|
1129
|
+
Identify the significant paths (based on background and obsevered data to calculate Z-score, α=0.05, Z=1.645)
|
|
1130
|
+
"""
|
|
1131
|
+
if z_critical is None:
|
|
1132
|
+
z_critical = norm.ppf(1 - alpha)
|
|
1133
|
+
|
|
1134
|
+
combined_df = pd.concat([background_inter_df, sample_inter_df], axis=0)
|
|
1135
|
+
|
|
1136
|
+
global_mean = combined_df.mean(axis=0)
|
|
1137
|
+
global_std = combined_df.std(axis=0)
|
|
1138
|
+
|
|
1139
|
+
sample_zscore = (sample_inter_df - global_mean) / global_std
|
|
1140
|
+
|
|
1141
|
+
all_sample_results = []
|
|
1142
|
+
for sample_name, sample_data in tqdm(sample_inter_df.iterrows(), desc=f"Processing samples"):
|
|
1143
|
+
sample_zscore_row = sample_zscore.loc[sample_name]
|
|
1144
|
+
significant_mask = sample_zscore_row > z_critical
|
|
1145
|
+
significant_pathways = sample_zscore_row[significant_mask].index.tolist()
|
|
1146
|
+
|
|
1147
|
+
sample_names = [sample_name] * len(significant_pathways)
|
|
1148
|
+
sample_zscores = sample_zscore_row[significant_mask].values
|
|
1149
|
+
sample_interactions = sample_inter_df.loc[sample_name, significant_pathways].values
|
|
1150
|
+
sample_results = pd.DataFrame({
|
|
1151
|
+
"Sample_Name": sample_names,
|
|
1152
|
+
"Path_Symbol": significant_pathways,
|
|
1153
|
+
"Comm_Score": sample_interactions,
|
|
1154
|
+
"Z_Score": sample_zscores
|
|
1155
|
+
})
|
|
1156
|
+
sample_results = sample_results.sort_values("Z_Score", ascending=False)
|
|
1157
|
+
all_sample_results.append(sample_results)
|
|
1158
|
+
all_sample_results = pd.concat(all_sample_results, ignore_index=True)
|
|
1159
|
+
all_sample_results.to_csv(output_path, index=False)
|
|
1160
|
+
print(f"Significant_L->R->TF->TG paths saved to {output_path}")
|
|
1161
|
+
|
|
1162
|
+
return all_sample_results
|
|
1163
|
+
|
|
1164
|
+
|
|
1165
|
+
|
|
1166
|
+
def Identify_significant_lr_pairs_celltype(sif_df, celltype, agg_method='mean'):
|
|
1167
|
+
if not isinstance(sif_df, pd.DataFrame) or not isinstance(celltype, pd.DataFrame):
|
|
1168
|
+
raise ValueError("inputs need pandas DataFrame")
|
|
1169
|
+
|
|
1170
|
+
merged_df = sif_df.merge(
|
|
1171
|
+
celltype,
|
|
1172
|
+
left_on='Sample_Name',
|
|
1173
|
+
right_index=True,
|
|
1174
|
+
how='left'
|
|
1175
|
+
)
|
|
1176
|
+
|
|
1177
|
+
if isinstance(agg_method, dict):
|
|
1178
|
+
result_df = merged_df.groupby(['LR_Symbol', 'celltype']).agg(agg_method)
|
|
1179
|
+
else:
|
|
1180
|
+
result_df = merged_df.groupby(['LR_Symbol', 'celltype']).agg({
|
|
1181
|
+
'Inter_Score': agg_method,
|
|
1182
|
+
'Z_Score': agg_method
|
|
1183
|
+
})
|
|
1184
|
+
|
|
1185
|
+
result_df = result_df.reset_index()
|
|
1186
|
+
result_df.rename(columns={'celltype': 'Cell_Type'}, inplace=True)
|
|
1187
|
+
|
|
1188
|
+
return result_df
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
|
|
1192
|
+
def Identify_significant_paths_celltype(sif_df, celltype, agg_method='mean'):
|
|
1193
|
+
if not isinstance(sif_df, pd.DataFrame) or not isinstance(celltype, pd.DataFrame):
|
|
1194
|
+
raise ValueError("inputs need pandas DataFrame")
|
|
1195
|
+
|
|
1196
|
+
merged_df = sif_df.merge(
|
|
1197
|
+
celltype,
|
|
1198
|
+
left_on='Sample_Name',
|
|
1199
|
+
right_index=True,
|
|
1200
|
+
how='left'
|
|
1201
|
+
)
|
|
1202
|
+
|
|
1203
|
+
if isinstance(agg_method, dict):
|
|
1204
|
+
result_df = merged_df.groupby(['Path_Symbol', 'celltype']).agg(agg_method)
|
|
1205
|
+
else:
|
|
1206
|
+
result_df = merged_df.groupby(['Path_Symbol', 'celltype']).agg({
|
|
1207
|
+
'Comm_Score': agg_method,
|
|
1208
|
+
'Z_Score': agg_method
|
|
1209
|
+
})
|
|
1210
|
+
|
|
1211
|
+
result_df = result_df.reset_index()
|
|
1212
|
+
result_df.rename(columns={'celltype': 'Cell_Type'}, inplace=True)
|
|
1213
|
+
|
|
1214
|
+
return result_df
|
|
1215
|
+
|
|
1216
|
+
|
|
1217
|
+
|
|
1218
|
+
def mad_based_score(values):
|
|
1219
|
+
eps = 1e-8
|
|
1220
|
+
med = np.median(values)
|
|
1221
|
+
dev = abs(values - med)
|
|
1222
|
+
mad_val = mad(values)
|
|
1223
|
+
return dev / (1.4826 * mad_val + eps)
|
|
1224
|
+
|
|
1225
|
+
def Identify_volatile_lr_pairs_celltype(data, threshold=None, method='mad'):
|
|
1226
|
+
volatility_df = pd.DataFrame(
|
|
1227
|
+
index=data.index,
|
|
1228
|
+
columns=data.columns,
|
|
1229
|
+
dtype=float
|
|
1230
|
+
)
|
|
1231
|
+
outlier_report = {}
|
|
1232
|
+
|
|
1233
|
+
for path in data.columns:
|
|
1234
|
+
values = data[path].values
|
|
1235
|
+
|
|
1236
|
+
if method == 'median':
|
|
1237
|
+
scores = np.abs(values - np.median(values)) / np.median(values)
|
|
1238
|
+
volatility_df[path] = scores
|
|
1239
|
+
path_threshold = 0.3 if threshold is None else threshold
|
|
1240
|
+
elif method == 'mad':
|
|
1241
|
+
scores = mad_based_score(values)
|
|
1242
|
+
volatility_df[path] = scores
|
|
1243
|
+
path_threshold = 2.5 if threshold is None else threshold
|
|
1244
|
+
elif method == 'ratio':
|
|
1245
|
+
total = sum(values)
|
|
1246
|
+
if total == 0:
|
|
1247
|
+
scores = [0] * len(values)
|
|
1248
|
+
else:
|
|
1249
|
+
scores = [x/total for x in values]
|
|
1250
|
+
volatility_df[path] = scores
|
|
1251
|
+
path_threshold = 0.3 if threshold is None else threshold
|
|
1252
|
+
else:
|
|
1253
|
+
raise ValueError("method must be 'median' or 'mad'")
|
|
1254
|
+
|
|
1255
|
+
outliers = volatility_df[path][volatility_df[path] > path_threshold]
|
|
1256
|
+
if not outliers.empty:
|
|
1257
|
+
outlier_report[path] = list(zip(outliers.index, outliers.values))
|
|
1258
|
+
|
|
1259
|
+
volatility_bin = (volatility_df > path_threshold).astype(int)
|
|
1260
|
+
|
|
1261
|
+
rows, cols = np.where(volatility_bin == 1)
|
|
1262
|
+
|
|
1263
|
+
celltype_lst = volatility_bin.index[rows].tolist()
|
|
1264
|
+
lr_symbol_lst = volatility_bin.columns[cols].tolist()
|
|
1265
|
+
inter_score_lst = data.values[rows, cols].tolist()
|
|
1266
|
+
|
|
1267
|
+
result_df = pd.DataFrame({
|
|
1268
|
+
'LR_Symbol': lr_symbol_lst,
|
|
1269
|
+
'Cell_Type': celltype_lst,
|
|
1270
|
+
'Inter_Score': inter_score_lst,
|
|
1271
|
+
'Z_Score': 2
|
|
1272
|
+
})
|
|
1273
|
+
result_df = result_df.reset_index(drop=True)
|
|
1274
|
+
|
|
1275
|
+
return result_df, volatility_df, volatility_bin, outlier_report
|
|
1276
|
+
|
|
1277
|
+
|
|
1278
|
+
|
|
1279
|
+
def Identify_concat_lr_pairs_celltype(sig_LR_pair_celltype,vola_LR_pair_celltype,out_path):
|
|
1280
|
+
merged = pd.merge(vola_LR_pair_celltype,
|
|
1281
|
+
sig_LR_pair_celltype[['LR_Symbol', 'Cell_Type']],
|
|
1282
|
+
on=['LR_Symbol', 'Cell_Type'],
|
|
1283
|
+
how='left',
|
|
1284
|
+
indicator=True)
|
|
1285
|
+
|
|
1286
|
+
to_add = merged[merged['_merge'] == 'left_only'].drop(columns='_merge')
|
|
1287
|
+
sig_LR_pair_celltype_updated = pd.concat([sig_LR_pair_celltype, to_add], ignore_index=True)
|
|
1288
|
+
sig_LR_pair_celltype_updated = sig_LR_pair_celltype_updated.sort_values(['LR_Symbol', 'Cell_Type'])
|
|
1289
|
+
sig_LR_pair_celltype_updated = sig_LR_pair_celltype_updated.reset_index(drop=True)
|
|
1290
|
+
|
|
1291
|
+
sig_LR_pair_celltype_updated.to_csv(out_path, sep=",", index=False)
|
|
1292
|
+
print(f"sig_LR_pair_celltype_updated saved to {out_path}")
|
|
1293
|
+
return sig_LR_pair_celltype_updated
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
|
|
1297
|
+
def calculate_l_r_tf_tg_strength_cellwise(
|
|
1298
|
+
l_r_tf_tg_df: pd.DataFrame,
|
|
1299
|
+
combined_npz_path: str,
|
|
1300
|
+
global_row_names_path: str,
|
|
1301
|
+
global_col_names_path: str,
|
|
1302
|
+
ccc_lrp_path: str,
|
|
1303
|
+
output_dir: str = "cellwise_cascade_results"
|
|
1304
|
+
) -> None:
|
|
1305
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
1306
|
+
|
|
1307
|
+
print("Loading data...")
|
|
1308
|
+
try:
|
|
1309
|
+
combined_sparse = load_npz(combined_npz_path).tocsc()
|
|
1310
|
+
with open(global_row_names_path, 'r') as f:
|
|
1311
|
+
cell_names = json.load(f)
|
|
1312
|
+
with open(global_col_names_path, 'r') as f:
|
|
1313
|
+
r_tf_tg_pairs = json.load(f)
|
|
1314
|
+
ccc_lrp_df = pd.read_csv(ccc_lrp_path, sep='\t', index_col=0)
|
|
1315
|
+
except Exception as e:
|
|
1316
|
+
print(f"Error loading data: {e}")
|
|
1317
|
+
return
|
|
1318
|
+
print("Data loaded successfully")
|
|
1319
|
+
|
|
1320
|
+
r_tf_tg_to_col = {pair: idx for idx, pair in enumerate(r_tf_tg_pairs)}
|
|
1321
|
+
|
|
1322
|
+
print("Identifying zero L-R pairs...")
|
|
1323
|
+
zero_lr_pairs = set()
|
|
1324
|
+
for col in ccc_lrp_df.columns:
|
|
1325
|
+
if np.all(ccc_lrp_df[col] == 0):
|
|
1326
|
+
zero_lr_pairs.add(col)
|
|
1327
|
+
print(f"Found {len(zero_lr_pairs)} zero L-R pairs to skip")
|
|
1328
|
+
|
|
1329
|
+
print("Processing all ligand-receptor-TF-TG paths...")
|
|
1330
|
+
result_data = {}
|
|
1331
|
+
|
|
1332
|
+
for _, row in tqdm(l_r_tf_tg_df.iterrows(), total=len(l_r_tf_tg_df), desc="Calculating paths"):
|
|
1333
|
+
ligand = row['Ligand_Symbol']
|
|
1334
|
+
receptor = row['Receptor_Symbol']
|
|
1335
|
+
tf = row['TF_Symbol']
|
|
1336
|
+
tg = row['TG_Symbol']
|
|
1337
|
+
l_r_pair = f"{ligand}->{receptor}"
|
|
1338
|
+
r_tf_tg_pair = f"{receptor}->{tf}->{tg}"
|
|
1339
|
+
path = f"{ligand}->{receptor}->{tf}->{tg}"
|
|
1340
|
+
|
|
1341
|
+
if l_r_pair in zero_lr_pairs:
|
|
1342
|
+
continue
|
|
1343
|
+
|
|
1344
|
+
if l_r_pair not in ccc_lrp_df.columns:
|
|
1345
|
+
continue
|
|
1346
|
+
|
|
1347
|
+
if r_tf_tg_pair not in r_tf_tg_to_col:
|
|
1348
|
+
continue
|
|
1349
|
+
|
|
1350
|
+
col_idx = r_tf_tg_to_col[r_tf_tg_pair]
|
|
1351
|
+
|
|
1352
|
+
inter_signal = ccc_lrp_df[l_r_pair].values
|
|
1353
|
+
intra_signal = combined_sparse[:, col_idx].toarray().flatten()
|
|
1354
|
+
|
|
1355
|
+
total_signal = inter_signal * intra_signal
|
|
1356
|
+
result_data[path] = total_signal
|
|
1357
|
+
|
|
1358
|
+
if result_data:
|
|
1359
|
+
result_df = pd.DataFrame(result_data, index=cell_names)
|
|
1360
|
+
output_path = os.path.join(output_dir, "cellwise_cascade_results.csv")
|
|
1361
|
+
result_df.to_csv(output_path, index=True)
|
|
1362
|
+
print(f"Processing completed. Results saved to {output_path}")
|
|
1363
|
+
else:
|
|
1364
|
+
print("No valid ligand-receptor-TF-TG paths found.")
|
|
1365
|
+
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
def calculate_l_r_tf_tg_strength_cellwise_with_lexp(
|
|
1369
|
+
l_r_tf_tg_df: pd.DataFrame,
|
|
1370
|
+
combined_npz_path: str,
|
|
1371
|
+
global_row_names_path: str,
|
|
1372
|
+
global_col_names_path: str,
|
|
1373
|
+
ccc_lrp_path: str,
|
|
1374
|
+
expression_matrix: pd.DataFrame,
|
|
1375
|
+
output_dir: str = None,
|
|
1376
|
+
output_file: str = "cellwise_cascade_results.csv"
|
|
1377
|
+
) -> None:
|
|
1378
|
+
|
|
1379
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
1380
|
+
|
|
1381
|
+
print("Loading data...")
|
|
1382
|
+
try:
|
|
1383
|
+
combined_sparse = load_npz(combined_npz_path).tocsc()
|
|
1384
|
+
with open(global_row_names_path, 'r') as f:
|
|
1385
|
+
cell_names = json.load(f)
|
|
1386
|
+
with open(global_col_names_path, 'r') as f:
|
|
1387
|
+
r_tf_tg_pairs = json.load(f)
|
|
1388
|
+
ccc_lrp_df = pd.read_csv(ccc_lrp_path, sep='\t', index_col=0)
|
|
1389
|
+
expression_matrix = expression_matrix[cell_names]
|
|
1390
|
+
except Exception as e:
|
|
1391
|
+
print(f"Error loading data: {e}")
|
|
1392
|
+
return
|
|
1393
|
+
print("Data loaded successfully")
|
|
1394
|
+
|
|
1395
|
+
r_tf_tg_to_col = {pair: idx for idx, pair in enumerate(r_tf_tg_pairs)}
|
|
1396
|
+
|
|
1397
|
+
print("Identifying zero L-R pairs...")
|
|
1398
|
+
zero_lr_pairs = set()
|
|
1399
|
+
for col in ccc_lrp_df.columns:
|
|
1400
|
+
if np.all(ccc_lrp_df[col] == 0):
|
|
1401
|
+
zero_lr_pairs.add(col)
|
|
1402
|
+
print(f"Found {len(zero_lr_pairs)} zero L-R pairs to skip")
|
|
1403
|
+
|
|
1404
|
+
print("Processing all ligand-receptor-TF-TG paths...")
|
|
1405
|
+
result_data = {}
|
|
1406
|
+
|
|
1407
|
+
for _, row in tqdm(l_r_tf_tg_df.iterrows(), total=len(l_r_tf_tg_df), desc="Calculating paths"):
|
|
1408
|
+
ligand = row['Ligand_Symbol']
|
|
1409
|
+
receptor = row['Receptor_Symbol']
|
|
1410
|
+
tf = row['TF_Symbol']
|
|
1411
|
+
tg = row['TG_Symbol']
|
|
1412
|
+
l_r_pair = f"{ligand}->{receptor}"
|
|
1413
|
+
r_tf_tg_pair = f"{receptor}->{tf}->{tg}"
|
|
1414
|
+
path = f"{ligand}->{receptor}->{tf}->{tg}"
|
|
1415
|
+
|
|
1416
|
+
if l_r_pair in zero_lr_pairs:
|
|
1417
|
+
continue
|
|
1418
|
+
|
|
1419
|
+
if l_r_pair not in ccc_lrp_df.columns:
|
|
1420
|
+
continue
|
|
1421
|
+
|
|
1422
|
+
if r_tf_tg_pair not in r_tf_tg_to_col:
|
|
1423
|
+
continue
|
|
1424
|
+
|
|
1425
|
+
if ligand not in expression_matrix.index:
|
|
1426
|
+
if '_' in ligand:
|
|
1427
|
+
ligand_components = ligand.split('_')
|
|
1428
|
+
available_components = [comp for comp in ligand_components if comp in expression_matrix.index]
|
|
1429
|
+
if not available_components:
|
|
1430
|
+
print(f"Warning: Ligand {ligand} not found in expression matrix")
|
|
1431
|
+
continue
|
|
1432
|
+
ligand_expression = expression_matrix.loc[available_components].mean(axis=0).values
|
|
1433
|
+
# print(f"Using average expression for ligand components: {ligand_components}")
|
|
1434
|
+
else:
|
|
1435
|
+
print(f"Warning: Ligand {ligand} not found in expression matrix")
|
|
1436
|
+
continue
|
|
1437
|
+
else:
|
|
1438
|
+
ligand_expression = expression_matrix.loc[ligand].values
|
|
1439
|
+
|
|
1440
|
+
|
|
1441
|
+
col_idx = r_tf_tg_to_col[r_tf_tg_pair]
|
|
1442
|
+
|
|
1443
|
+
inter_signal = ccc_lrp_df[l_r_pair].values
|
|
1444
|
+
intra_signal = combined_sparse[:, col_idx].toarray().flatten()
|
|
1445
|
+
|
|
1446
|
+
total_signal = inter_signal * intra_signal * ligand_expression
|
|
1447
|
+
result_data[path] = total_signal
|
|
1448
|
+
|
|
1449
|
+
if result_data:
|
|
1450
|
+
result_df = pd.DataFrame(result_data, index=cell_names)
|
|
1451
|
+
output_path = os.path.join(output_dir+output_file)
|
|
1452
|
+
result_df.to_csv(output_path, index=True)
|
|
1453
|
+
print(f"Processing completed. Results saved to {output_path}")
|
|
1454
|
+
else:
|
|
1455
|
+
print("No valid ligand-receptor-TF-TG paths found.")
|
|
1456
|
+
|
|
1457
|
+
|
|
1458
|
+
|
|
1459
|
+
|
|
1460
|
+
def generate_background_l_r_tf_tg_strength_cellwise(
|
|
1461
|
+
l_r_tf_tg_df: pd.DataFrame,
|
|
1462
|
+
combined_npz_path: str,
|
|
1463
|
+
global_row_names_path: str,
|
|
1464
|
+
global_col_names_path: str,
|
|
1465
|
+
ccc_lrp_path: str,
|
|
1466
|
+
output_dir: str = None,
|
|
1467
|
+
random_seed: int = 42
|
|
1468
|
+
) -> None:
|
|
1469
|
+
|
|
1470
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
1471
|
+
|
|
1472
|
+
print("Loading data...")
|
|
1473
|
+
try:
|
|
1474
|
+
original_sparse = load_npz(combined_npz_path).tocsc()
|
|
1475
|
+
with open(global_row_names_path, 'r') as f:
|
|
1476
|
+
cell_names = json.load(f)
|
|
1477
|
+
with open(global_col_names_path, 'r') as f:
|
|
1478
|
+
r_tf_tg_pairs = json.load(f)
|
|
1479
|
+
ccc_lrp_df = pd.read_csv(ccc_lrp_path, sep='\t', index_col=0)
|
|
1480
|
+
except Exception as e:
|
|
1481
|
+
print(f"Error loading data: {e}")
|
|
1482
|
+
return
|
|
1483
|
+
print("Data loaded successfully")
|
|
1484
|
+
|
|
1485
|
+
final_cell_names = cell_names * 10
|
|
1486
|
+
print("Final cell names length:", len(final_cell_names))
|
|
1487
|
+
|
|
1488
|
+
print("Generating background data by row permutation...")
|
|
1489
|
+
np.random.seed(random_seed)
|
|
1490
|
+
nonzero_rows = np.unique(original_sparse.nonzero()[0])
|
|
1491
|
+
background_matrices = []
|
|
1492
|
+
|
|
1493
|
+
for _ in range(10):
|
|
1494
|
+
new_nonzero_rows = np.random.choice(np.arange(original_sparse.shape[0]), size=len(nonzero_rows), replace=False)
|
|
1495
|
+
|
|
1496
|
+
permuted_indices = np.arange(original_sparse.shape[0])
|
|
1497
|
+
permuted_indices[nonzero_rows] = new_nonzero_rows
|
|
1498
|
+
|
|
1499
|
+
background_sparse = csc_matrix(
|
|
1500
|
+
(original_sparse.data,
|
|
1501
|
+
permuted_indices[original_sparse.indices],
|
|
1502
|
+
original_sparse.indptr),
|
|
1503
|
+
shape=original_sparse.shape
|
|
1504
|
+
)
|
|
1505
|
+
|
|
1506
|
+
background_matrices.append(background_sparse)
|
|
1507
|
+
|
|
1508
|
+
final_background_sparse = vstack(background_matrices)
|
|
1509
|
+
|
|
1510
|
+
print("Final background sparse matrix shape:", final_background_sparse.shape)
|
|
1511
|
+
|
|
1512
|
+
r_tf_tg_to_col = {pair: idx for idx, pair in enumerate(r_tf_tg_pairs)}
|
|
1513
|
+
|
|
1514
|
+
print("Identifying zero L-R pairs...")
|
|
1515
|
+
zero_lr_pairs = set()
|
|
1516
|
+
for col in ccc_lrp_df.columns:
|
|
1517
|
+
if np.all(ccc_lrp_df[col] == 0):
|
|
1518
|
+
zero_lr_pairs.add(col)
|
|
1519
|
+
print(f"Found {len(zero_lr_pairs)} zero L-R pairs to skip")
|
|
1520
|
+
|
|
1521
|
+
print("Processing all ligand-receptor-TF-TG paths on randomized background...")
|
|
1522
|
+
result_data = {}
|
|
1523
|
+
|
|
1524
|
+
for _, row in tqdm(l_r_tf_tg_df.iterrows(), total=len(l_r_tf_tg_df), desc="Calculating background paths"):
|
|
1525
|
+
ligand = row['Ligand_Symbol']
|
|
1526
|
+
receptor = row['Receptor_Symbol']
|
|
1527
|
+
tf = row['TF_Symbol']
|
|
1528
|
+
tg = row['TG_Symbol']
|
|
1529
|
+
l_r_pair = f"{ligand}->{receptor}"
|
|
1530
|
+
r_tf_tg_pair = f"{receptor}->{tf}->{tg}"
|
|
1531
|
+
path = f"{ligand}->{receptor}->{tf}->{tg}"
|
|
1532
|
+
|
|
1533
|
+
if l_r_pair in zero_lr_pairs:
|
|
1534
|
+
continue
|
|
1535
|
+
|
|
1536
|
+
if l_r_pair not in ccc_lrp_df.columns:
|
|
1537
|
+
continue
|
|
1538
|
+
|
|
1539
|
+
if r_tf_tg_pair not in r_tf_tg_to_col:
|
|
1540
|
+
continue
|
|
1541
|
+
|
|
1542
|
+
col_idx = r_tf_tg_to_col[r_tf_tg_pair]
|
|
1543
|
+
|
|
1544
|
+
inter_signal = ccc_lrp_df[l_r_pair].values
|
|
1545
|
+
intra_signal = final_background_sparse[:, col_idx].toarray().flatten()
|
|
1546
|
+
|
|
1547
|
+
total_signal = inter_signal * intra_signal
|
|
1548
|
+
result_data[path] = total_signal
|
|
1549
|
+
|
|
1550
|
+
if result_data:
|
|
1551
|
+
result_df = pd.DataFrame(result_data, index=final_cell_names)
|
|
1552
|
+
output_path = os.path.join(output_dir+"background_cellwise_cascade_results.csv")
|
|
1553
|
+
result_df.to_csv(output_path, index=True)
|
|
1554
|
+
print(f"Unified background result saved to: {output_path}")
|
|
1555
|
+
else:
|
|
1556
|
+
print("No valid background paths found.")
|
|
1557
|
+
|
|
1558
|
+
|
|
1559
|
+
|
|
1560
|
+
def generate_background_l_r_tf_tg_strength_cellwise_with_lexp(
|
|
1561
|
+
l_r_tf_tg_df: pd.DataFrame,
|
|
1562
|
+
combined_npz_path: str,
|
|
1563
|
+
global_row_names_path: str,
|
|
1564
|
+
global_col_names_path: str,
|
|
1565
|
+
ccc_lrp_path: str,
|
|
1566
|
+
expression_matrix: pd.DataFrame,
|
|
1567
|
+
output_dir: str = None,
|
|
1568
|
+
output_file: str = "background_cellwise_cascade_results.csv",
|
|
1569
|
+
random_seed: int = 42
|
|
1570
|
+
) -> None:
|
|
1571
|
+
|
|
1572
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
1573
|
+
|
|
1574
|
+
print("Loading data...")
|
|
1575
|
+
try:
|
|
1576
|
+
original_sparse = load_npz(combined_npz_path).tocsc()
|
|
1577
|
+
with open(global_row_names_path, 'r') as f:
|
|
1578
|
+
cell_names = json.load(f)
|
|
1579
|
+
with open(global_col_names_path, 'r') as f:
|
|
1580
|
+
r_tf_tg_pairs = json.load(f)
|
|
1581
|
+
ccc_lrp_df = pd.read_csv(ccc_lrp_path, sep='\t', index_col=0)
|
|
1582
|
+
expression_matrix = expression_matrix[cell_names]
|
|
1583
|
+
except Exception as e:
|
|
1584
|
+
print(f"Error loading data: {e}")
|
|
1585
|
+
return
|
|
1586
|
+
print("Data loaded successfully")
|
|
1587
|
+
|
|
1588
|
+
final_cell_names = cell_names * 10
|
|
1589
|
+
print("Final cell names length:", len(final_cell_names))
|
|
1590
|
+
|
|
1591
|
+
print("Generating background data by row permutation...")
|
|
1592
|
+
np.random.seed(random_seed)
|
|
1593
|
+
nonzero_rows = np.unique(original_sparse.nonzero()[0])
|
|
1594
|
+
background_matrices = []
|
|
1595
|
+
|
|
1596
|
+
for _ in range(10):
|
|
1597
|
+
new_nonzero_rows = np.random.choice(np.arange(original_sparse.shape[0]), size=len(nonzero_rows), replace=False)
|
|
1598
|
+
|
|
1599
|
+
permuted_indices = np.arange(original_sparse.shape[0])
|
|
1600
|
+
permuted_indices[nonzero_rows] = new_nonzero_rows
|
|
1601
|
+
|
|
1602
|
+
background_sparse = csc_matrix(
|
|
1603
|
+
(original_sparse.data,
|
|
1604
|
+
permuted_indices[original_sparse.indices],
|
|
1605
|
+
original_sparse.indptr),
|
|
1606
|
+
shape=original_sparse.shape
|
|
1607
|
+
)
|
|
1608
|
+
|
|
1609
|
+
background_matrices.append(background_sparse)
|
|
1610
|
+
|
|
1611
|
+
final_background_sparse = vstack(background_matrices)
|
|
1612
|
+
|
|
1613
|
+
print("Final background sparse matrix shape:", final_background_sparse.shape)
|
|
1614
|
+
|
|
1615
|
+
r_tf_tg_to_col = {pair: idx for idx, pair in enumerate(r_tf_tg_pairs)}
|
|
1616
|
+
|
|
1617
|
+
print("Identifying zero L-R pairs...")
|
|
1618
|
+
zero_lr_pairs = set()
|
|
1619
|
+
for col in ccc_lrp_df.columns:
|
|
1620
|
+
if np.all(ccc_lrp_df[col] == 0):
|
|
1621
|
+
zero_lr_pairs.add(col)
|
|
1622
|
+
print(f"Found {len(zero_lr_pairs)} zero L-R pairs to skip")
|
|
1623
|
+
|
|
1624
|
+
print("Processing all ligand-receptor-TF-TG paths on randomized background...")
|
|
1625
|
+
result_data = {}
|
|
1626
|
+
|
|
1627
|
+
for _, row in tqdm(l_r_tf_tg_df.iterrows(), total=len(l_r_tf_tg_df), desc="Calculating background paths"):
|
|
1628
|
+
ligand = row['Ligand_Symbol']
|
|
1629
|
+
receptor = row['Receptor_Symbol']
|
|
1630
|
+
tf = row['TF_Symbol']
|
|
1631
|
+
tg = row['TG_Symbol']
|
|
1632
|
+
l_r_pair = f"{ligand}->{receptor}"
|
|
1633
|
+
r_tf_tg_pair = f"{receptor}->{tf}->{tg}"
|
|
1634
|
+
path = f"{ligand}->{receptor}->{tf}->{tg}"
|
|
1635
|
+
|
|
1636
|
+
if l_r_pair in zero_lr_pairs:
|
|
1637
|
+
continue
|
|
1638
|
+
|
|
1639
|
+
if l_r_pair not in ccc_lrp_df.columns:
|
|
1640
|
+
continue
|
|
1641
|
+
|
|
1642
|
+
if r_tf_tg_pair not in r_tf_tg_to_col:
|
|
1643
|
+
continue
|
|
1644
|
+
|
|
1645
|
+
if ligand not in expression_matrix.index:
|
|
1646
|
+
if '_' in ligand:
|
|
1647
|
+
ligand_components = ligand.split('_')
|
|
1648
|
+
available_components = [comp for comp in ligand_components if comp in expression_matrix.index]
|
|
1649
|
+
if not available_components:
|
|
1650
|
+
print(f"Warning: Ligand {ligand} not found in expression matrix")
|
|
1651
|
+
continue
|
|
1652
|
+
ligand_expression = expression_matrix.loc[available_components].mean(axis=0).values
|
|
1653
|
+
# print(f"Using average expression for ligand components: {ligand_components}")
|
|
1654
|
+
else:
|
|
1655
|
+
print(f"Warning: Ligand {ligand} not found in expression matrix")
|
|
1656
|
+
continue
|
|
1657
|
+
else:
|
|
1658
|
+
ligand_expression = expression_matrix.loc[ligand].values
|
|
1659
|
+
|
|
1660
|
+
|
|
1661
|
+
|
|
1662
|
+
col_idx = r_tf_tg_to_col[r_tf_tg_pair]
|
|
1663
|
+
|
|
1664
|
+
inter_signal = ccc_lrp_df[l_r_pair].values
|
|
1665
|
+
intra_signal = final_background_sparse[:, col_idx].toarray().flatten()
|
|
1666
|
+
|
|
1667
|
+
total_signal = inter_signal * intra_signal * ligand_expression
|
|
1668
|
+
result_data[path] = total_signal
|
|
1669
|
+
|
|
1670
|
+
if result_data:
|
|
1671
|
+
result_df = pd.DataFrame(result_data, index=final_cell_names)
|
|
1672
|
+
output_path = os.path.join(output_dir+output_file)
|
|
1673
|
+
result_df.to_csv(output_path, index=True)
|
|
1674
|
+
print(f"Unified background result saved to: {output_path}")
|
|
1675
|
+
else:
|
|
1676
|
+
print("No valid background paths found.")
|
|
1677
|
+
|
|
1678
|
+
|
|
1679
|
+
|
|
1680
|
+
|
|
1681
|
+
def Identify_volatile_paths_celltype(data, threshold=None, method='mad'):
|
|
1682
|
+
volatility_df = pd.DataFrame(
|
|
1683
|
+
index=data.index,
|
|
1684
|
+
columns=data.columns,
|
|
1685
|
+
dtype=float
|
|
1686
|
+
)
|
|
1687
|
+
outlier_report = {}
|
|
1688
|
+
|
|
1689
|
+
for path in data.columns:
|
|
1690
|
+
values = data[path].values
|
|
1691
|
+
|
|
1692
|
+
if method == 'median':
|
|
1693
|
+
median = np.median(values)
|
|
1694
|
+
adjusted_median = median if median != 0 else 1e-10
|
|
1695
|
+
scores = np.abs(values - median) / adjusted_median
|
|
1696
|
+
# scores = np.abs(values - np.median(values)) / np.median(values)
|
|
1697
|
+
volatility_df[path] = scores
|
|
1698
|
+
path_threshold = 0.3 if threshold is None else threshold
|
|
1699
|
+
elif method == 'mad':
|
|
1700
|
+
scores = mad_based_score(values)
|
|
1701
|
+
volatility_df[path] = scores
|
|
1702
|
+
path_threshold = 2.5 if threshold is None else threshold
|
|
1703
|
+
elif method == 'mean':
|
|
1704
|
+
mean = np.mean(values)
|
|
1705
|
+
adjusted_mean = mean if mean != 0 else 1e-10
|
|
1706
|
+
scores = np.abs(values - mean) / adjusted_mean
|
|
1707
|
+
volatility_df[path] = scores
|
|
1708
|
+
path_threshold = 0.3 if threshold is None else threshold
|
|
1709
|
+
elif method == 'ratio':
|
|
1710
|
+
total = sum(values)
|
|
1711
|
+
if total == 0:
|
|
1712
|
+
scores = [0] * len(values)
|
|
1713
|
+
else:
|
|
1714
|
+
scores = [x/total for x in values]
|
|
1715
|
+
volatility_df[path] = scores
|
|
1716
|
+
path_threshold = 0.3 if threshold is None else threshold
|
|
1717
|
+
else:
|
|
1718
|
+
raise ValueError("method must be 'median' or 'mad' or 'mean" or "ratio")
|
|
1719
|
+
|
|
1720
|
+
outliers = volatility_df[path][volatility_df[path] > path_threshold]
|
|
1721
|
+
if not outliers.empty:
|
|
1722
|
+
outlier_report[path] = list(zip(outliers.index, outliers.values))
|
|
1723
|
+
|
|
1724
|
+
volatility_bin = (volatility_df > path_threshold).astype(int)
|
|
1725
|
+
|
|
1726
|
+
rows, cols = np.where(volatility_bin == 1)
|
|
1727
|
+
|
|
1728
|
+
celltype_lst = volatility_bin.index[rows].tolist()
|
|
1729
|
+
lr_symbol_lst = volatility_bin.columns[cols].tolist()
|
|
1730
|
+
inter_score_lst = data.values[rows, cols].tolist()
|
|
1731
|
+
|
|
1732
|
+
result_df = pd.DataFrame({
|
|
1733
|
+
'Path_Symbol': lr_symbol_lst,
|
|
1734
|
+
'Cell_Type': celltype_lst,
|
|
1735
|
+
'Comm_Score': inter_score_lst,
|
|
1736
|
+
'Z_Score': 2
|
|
1737
|
+
})
|
|
1738
|
+
result_df = result_df.reset_index(drop=True)
|
|
1739
|
+
|
|
1740
|
+
return result_df, volatility_df, volatility_bin, outlier_report
|
|
1741
|
+
|
|
1742
|
+
|
|
1743
|
+
|
|
1744
|
+
def Identify_concat_paths_celltype(sig_LR_pair_celltype,vola_LR_pair_celltype,out_path):
|
|
1745
|
+
merged = pd.merge(vola_LR_pair_celltype,
|
|
1746
|
+
sig_LR_pair_celltype[['Path_Symbol', 'Cell_Type']],
|
|
1747
|
+
on=['Path_Symbol', 'Cell_Type'],
|
|
1748
|
+
how='left',
|
|
1749
|
+
indicator=True)
|
|
1750
|
+
|
|
1751
|
+
to_add = merged[merged['_merge'] == 'left_only'].drop(columns='_merge')
|
|
1752
|
+
sig_LR_pair_celltype_updated = pd.concat([sig_LR_pair_celltype, to_add], ignore_index=True)
|
|
1753
|
+
sig_LR_pair_celltype_updated = sig_LR_pair_celltype_updated.sort_values(['Path_Symbol', 'Cell_Type'])
|
|
1754
|
+
sig_LR_pair_celltype_updated = sig_LR_pair_celltype_updated.reset_index(drop=True)
|
|
1755
|
+
|
|
1756
|
+
sig_LR_pair_celltype_updated.to_csv(out_path, sep=",", index=False)
|
|
1757
|
+
print(f"sig_Path_pair_celltype_updated saved to {out_path}")
|
|
1758
|
+
return sig_LR_pair_celltype_updated
|