scMultiChat 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. MultiChat/Analysis/Intra_strength.py +1758 -0
  2. MultiChat/Analysis/Processing.py +152 -0
  3. MultiChat/Analysis/__init__.py +2 -0
  4. MultiChat/Heterogeneous_g_emb/__init__.py +13 -0
  5. MultiChat/Heterogeneous_g_emb/_settings.py +156 -0
  6. MultiChat/Heterogeneous_g_emb/_utils.py +143 -0
  7. MultiChat/Heterogeneous_g_emb/_version.py +3 -0
  8. MultiChat/Heterogeneous_g_emb/plotting/__init__.py +19 -0
  9. MultiChat/Heterogeneous_g_emb/plotting/_palettes.py +180 -0
  10. MultiChat/Heterogeneous_g_emb/plotting/_plot.py +1498 -0
  11. MultiChat/Heterogeneous_g_emb/plotting/_post_training.py +742 -0
  12. MultiChat/Heterogeneous_g_emb/plotting/_utils.py +103 -0
  13. MultiChat/Heterogeneous_g_emb/preprocessing/__init__.py +26 -0
  14. MultiChat/Heterogeneous_g_emb/preprocessing/_general.py +91 -0
  15. MultiChat/Heterogeneous_g_emb/preprocessing/_pca.py +182 -0
  16. MultiChat/Heterogeneous_g_emb/preprocessing/_qc.py +727 -0
  17. MultiChat/Heterogeneous_g_emb/preprocessing/_utils.py +60 -0
  18. MultiChat/Heterogeneous_g_emb/preprocessing/_variable_genes.py +82 -0
  19. MultiChat/Heterogeneous_g_emb/readwrite.py +250 -0
  20. MultiChat/Heterogeneous_g_emb/tools/__init__.py +23 -0
  21. MultiChat/Heterogeneous_g_emb/tools/_gene_scores.py +346 -0
  22. MultiChat/Heterogeneous_g_emb/tools/_general.py +71 -0
  23. MultiChat/Heterogeneous_g_emb/tools/_integration.py +197 -0
  24. MultiChat/Heterogeneous_g_emb/tools/_pbg.py +1184 -0
  25. MultiChat/Heterogeneous_g_emb/tools/_post_training.py +919 -0
  26. MultiChat/Heterogeneous_g_emb/tools/_umap.py +58 -0
  27. MultiChat/Heterogeneous_g_emb/tools/_utils.py +253 -0
  28. MultiChat/Model/Layers.py +116 -0
  29. MultiChat/Model/__init__.py +3 -0
  30. MultiChat/Model/model_training.py +166 -0
  31. MultiChat/Model/modules.py +93 -0
  32. MultiChat/Model/utilities.py +234 -0
  33. MultiChat/Plot/Visualization.py +470 -0
  34. MultiChat/Plot/__init__.py +1 -0
  35. MultiChat/__init__.py +12 -0
  36. scmultichat-0.1.0.dist-info/METADATA +156 -0
  37. scmultichat-0.1.0.dist-info/RECORD +39 -0
  38. scmultichat-0.1.0.dist-info/WHEEL +5 -0
  39. scmultichat-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,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