multineuronchat 2025.11.10.dev0__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.
@@ -0,0 +1,67 @@
1
+ import os.path
2
+
3
+ from importlib.resources import files
4
+
5
+ import pandas as pd
6
+
7
+ from .InteractionDBRow import InteractionDBRow
8
+
9
+ from typing import Optional, List, Set, Union
10
+
11
+
12
+ class InteractionDB:
13
+ def __init__(self,
14
+ db: Optional[str] = 'human_extended'):
15
+ if (db not in ['human', 'mouse', 'human_extended']) and (not os.path.exists(db)):
16
+ raise ValueError('DB must be "human", "human_extended", "mouse", or a valid path to a dataset')
17
+
18
+ if db in ['human', 'mouse', 'human_extended']:
19
+ with files('multineuronchat.db').joinpath(f'../db/interactionDB_{db}.pkl').open('rb') as file:
20
+ #with files('MultiNeuronChat.db').joinpath(f'../db/interactionDB_{db}.pkl').open('rb') as file:
21
+ self.interaction_df: pd.DataFrame = pd.read_pickle(file)
22
+ else:
23
+ self.interaction_df: pd.DataFrame = pd.read_pickle(db)
24
+
25
+ self.__set_of_genes: Set[str] = self.__extract_set_of_genes()
26
+
27
+ def __extract_set_of_genes(self) -> Set[str]:
28
+ set_of_genes: Set[str] = set()
29
+
30
+ for lig_contributor in self.interaction_df['lig_contributor']:
31
+ genes: List[str] = lig_contributor.split('-')
32
+ set_of_genes.update(genes)
33
+
34
+ for target_contributor in self.interaction_df['target_subunit']:
35
+ genes: List[str] = target_contributor.split('-')
36
+ set_of_genes.update(genes)
37
+
38
+ return set_of_genes
39
+
40
+ def get_set_of_genes(self) -> Set[str]:
41
+ return self.__set_of_genes
42
+
43
+ def get_interaction_names(self) -> List[str]:
44
+ return self.interaction_df['interaction_name'].tolist()
45
+
46
+ def __getitem__(self, item: Union[str, int]) -> InteractionDBRow:
47
+ if type(item) is str:
48
+ return InteractionDBRow(self.interaction_df[self.interaction_df['interaction_name'] == item].iloc[0])
49
+ elif type(item) is int:
50
+ return InteractionDBRow(self.interaction_df.iloc[item])
51
+ else:
52
+ raise ValueError('Access is only defined for the type str or int')
53
+
54
+ def __len__(self) -> int:
55
+ return len(self.interaction_df)
56
+
57
+ def __iter__(self):
58
+ self.__iter_idx = 0
59
+ return self
60
+
61
+ def __next__(self) -> InteractionDBRow:
62
+ if self.__iter_idx < len(self.interaction_df):
63
+ interaction = InteractionDBRow(self.interaction_df.iloc[self.__iter_idx])
64
+ self.__iter_idx += 1
65
+ return interaction
66
+ else:
67
+ raise StopIteration
@@ -0,0 +1,50 @@
1
+ import pandas as pd
2
+
3
+ from typing import List, Dict, Set
4
+
5
+
6
+ class InteractionDBRow:
7
+ def __init__(self, row_series: pd.Series):
8
+ self.interaction_name: str = row_series['interaction_name']
9
+ self.ligand_type: str = row_series['ligand_type']
10
+ self.interaction_type: str = row_series['interaction_type']
11
+
12
+ self.ligand_contributor: List[str] = row_series['lig_contributor'].split('-')
13
+ self.ligand_contributor_group: List[int] = list(
14
+ map(lambda x: int(x), row_series['lig_contributor_group'].split('-'))
15
+ )
16
+ self.ligand_contributor_coeff: List[int] = list(
17
+ map(lambda x: int(x), row_series['lig_contributor_coeff'].split('-'))
18
+ )
19
+
20
+ self.ligand_group_to_gene_dict: Dict[int, List[str]] = {}
21
+ self.ligand_group_to_coeff_dict: Dict[int, int] = {}
22
+ for ligand, group in zip(self.ligand_contributor,
23
+ self.ligand_contributor_group):
24
+ if group not in self.ligand_group_to_gene_dict.keys():
25
+ self.ligand_group_to_gene_dict[group] = []
26
+ self.ligand_group_to_coeff_dict[group] = self.ligand_contributor_coeff[group-1]
27
+
28
+ self.ligand_group_to_gene_dict[group].append(ligand)
29
+
30
+ self.target_subunit: List[str] = row_series['target_subunit'].split('-')
31
+ self.target_subunit_group: List[int] = list(
32
+ map(lambda x: int(x), row_series['target_subunit_group'].split('-'))
33
+ )
34
+ self.target_subunit_coeff: List[int] = list(
35
+ map(lambda x: int(x), row_series['target_subunit_coeff'].split('-'))
36
+ )
37
+
38
+ self.target_group_to_gene_dict: Dict[int, List[str]] = {}
39
+ self.target_group_to_coeff_dict: Dict[int, int] = {}
40
+ for target, group in zip(self.target_subunit,
41
+ self.target_subunit_group):
42
+ if group not in self.target_group_to_gene_dict.keys():
43
+ self.target_group_to_gene_dict[group] = []
44
+ self.target_group_to_coeff_dict[group] = self.target_subunit_coeff[group-1]
45
+
46
+ self.target_group_to_gene_dict[group].append(target)
47
+
48
+ # Create sets of groups so that one can simply iterate over them
49
+ self.ligand_groups: Set[int] = set(self.ligand_contributor_group)
50
+ self.target_groups: Set[int] = set(self.target_subunit_group)
@@ -0,0 +1,4 @@
1
+ from .InteractionDB import InteractionDB
2
+ from .InteractionDBRow import InteractionDBRow
3
+
4
+ __all__ = ['InteractionDB', 'InteractionDBRow']
@@ -0,0 +1,447 @@
1
+ import os
2
+
3
+ import warnings
4
+
5
+ from multiprocessing import Pool
6
+
7
+ import loompy
8
+
9
+ import numpy as np
10
+ import scipy.stats as stats
11
+ import xarray as xr
12
+
13
+ from .InteractionDB import *
14
+
15
+ from typing import List, Tuple, Set, Dict, Optional
16
+
17
+
18
+ def compute_subject_specific_avg_expression(
19
+ path_to_loom: str,
20
+ subject_id: str,
21
+ subject_label_column: str,
22
+ cell_type_label_column: str,
23
+ gene_label_row: str = 'Gene',
24
+ min_n_cells_threshold: int = -1,
25
+ mean_type: str = 'tri_mean',
26
+ trim_mean_fraction: Optional[float] = None,
27
+ ) -> xr.DataArray:
28
+ """
29
+ Compute the average expression of all genes for a single individual and all cell-types that are present in the loom
30
+ file. The average expression is computed using the Tukey's tri_mean.
31
+
32
+ If the number of cells for a specific cell-type is less than min_n_cells_threshold, the average expression for this
33
+ cell-type is set to NaN.
34
+
35
+ :param path_to_loom: Path to the loom file from which the average expression should be computed. Important: The loom file must not be open in write mode by any other thread!
36
+ :param subject_id: The subject id from the subject_label_column for which the average expression should be computed.
37
+ :param subject_label_column: The column in the loom file that contains the subject id.
38
+ :param cell_type_label_column: The column in the loom file that contains the cell-type labels.
39
+ :param gene_label_row: Name of the row attribute in the loom file that contains the gene labels (default: 'Gene')
40
+ :param min_n_cells_threshold: The minimum number of cells that are required for a cell-type to be included in the average expression computation. If the number of cells is less than min_n_cells_threshold, the average expression for this cell-type is set to NaN.
41
+ :param mean_type: The type of mean that should be computed. The mean can be either 'tri_mean', 'mean', or 'trim_mean'.
42
+ :param trim_mean_fraction: The fraction of the data that should be trimmed when the mean type is set to 'trim_mean'.
43
+ :return: A matrix of the average expression of all genes for the individual and all cell-types.
44
+ """
45
+
46
+ if not os.path.isfile(path_to_loom):
47
+ raise FileNotFoundError(f"File {path_to_loom} not found")
48
+
49
+ if not path_to_loom.endswith('.loom'):
50
+ raise ValueError('The file must be a loom file')
51
+
52
+ if subject_id == '' or subject_id is None:
53
+ raise ValueError('The subject id must be provided')
54
+
55
+ if subject_label_column == '' or subject_label_column is None:
56
+ raise ValueError('The subject label column must be provided')
57
+
58
+ if cell_type_label_column == '' or cell_type_label_column is None:
59
+ raise ValueError('The cell type label column must be provided')
60
+
61
+ if mean_type not in ['tri_mean', 'mean', 'trim_mean']:
62
+ raise ValueError('The mean type must be either "tri_mean", "mean", or "trim_mean"')
63
+ if mean_type == 'trim_mean' and trim_mean_fraction is None:
64
+ raise ValueError('The trim_mean fraction must be provided when the mean type is set to "trim_mean"')
65
+
66
+ with loompy.connect(path_to_loom, mode='r') as data_loom:
67
+ # Get all genes and cell-types included in the loom file
68
+ genes: List[str] = data_loom.ra[gene_label_row].tolist()
69
+ cell_types: List[str] = list(set(data_loom.ca[cell_type_label_column]))
70
+
71
+ # Sort the cell-types
72
+ cell_types.sort()
73
+
74
+ # Initialize the average expression matrix
75
+ avg_expression: np.array = np.zeros(shape=(len(genes), len(cell_types)))
76
+
77
+ for i, cell_type in enumerate(cell_types):
78
+ # Get all cells with the specific cell-type and subject id
79
+ subject_and_cell_type_mask: np.array = (data_loom.ca[cell_type_label_column] == cell_type) & \
80
+ (data_loom.ca[subject_label_column] == subject_id)
81
+
82
+ # If there are no cells with the specific cell-type and subject id, set the average expression to NaN
83
+ if not np.any(subject_and_cell_type_mask):
84
+ warnings.warn(f'There are no cell-types with label "{cell_type}" for subject "{subject_id}"')
85
+ avg_expression[:, i] = np.nan
86
+ continue
87
+
88
+ # If the number of cells with the specific cell-type and subject id is less than min_n_cells_threshold,
89
+ # set the average expression to NaN
90
+ if min_n_cells_threshold > 0 and np.sum(subject_and_cell_type_mask) < min_n_cells_threshold:
91
+ warnings.warn(f'There are less than {min_n_cells_threshold} cells with label "{cell_type}" for subject "{subject_id}"')
92
+ avg_expression[:, i] = np.nan
93
+ continue
94
+
95
+ cell_type_matrix: np.array = data_loom[:, subject_and_cell_type_mask]
96
+
97
+ if mean_type == 'mean':
98
+ avg_expression[:, i] = np.mean(cell_type_matrix, axis=1)
99
+ elif mean_type == 'trim_mean':
100
+ avg_expression[:, i] = stats.trim_mean(cell_type_matrix, trim_mean_fraction, axis=1)
101
+ else:
102
+ # compute turkey's tri_mean
103
+ q1: np.array = np.percentile(cell_type_matrix, 25, axis=1)
104
+ q2: np.array = np.percentile(cell_type_matrix, 50, axis=1)
105
+ q3: np.array = np.percentile(cell_type_matrix, 75, axis=1)
106
+
107
+ tri_mean: np.array = (q1 + 2 * q2 + q3) / 4
108
+
109
+ avg_expression[:, i] = tri_mean
110
+
111
+ avg_expression_xr: xr.DataArray = xr.DataArray(data=avg_expression,
112
+ coords={'genes': genes, 'cell_types': cell_types},
113
+ dims=['genes', 'cell_types'])
114
+
115
+ return avg_expression_xr
116
+
117
+
118
+ def compute_avg_expression(
119
+ path_to_loom: str,
120
+
121
+ condition_label_column: str,
122
+ condition_label_a: str,
123
+ condition_label_b: str,
124
+
125
+ subject_label_column: str,
126
+ cell_type_label_column: str,
127
+
128
+ min_n_cells_threshold: int = -1,
129
+
130
+ mean_type: str = 'trimean',
131
+ trim_mean_fraction: Optional[float] = None,
132
+
133
+ gene_label_row: str = 'Gene',
134
+
135
+ n_processes: Optional[int] = 1
136
+ ) -> Dict[str, Dict[str, xr.DataArray]]:
137
+ """
138
+ Compute the average expression of all genes for all individuals and all cell-types.
139
+
140
+ :param path_to_loom: Path to the loom file from which the average expression should be computed. Important: The loom file must not be open in write mode by any other thread!
141
+ :param condition_label_column: The column in the loom file that contains the condition label.
142
+ :param condition_label_a: The label of the first condition.
143
+ :param condition_label_b: The label of the second condition.
144
+ :param subject_label_column: The column in the loom file that contains the subject id.
145
+ :param cell_type_label_column: The column in the loom file that contains the cell-type labels.
146
+ :param min_n_cells_threshold: The minimum number of cells that are required for a cell-type to be included in the average expression computation. If the number of cells is less than min_n_cells_threshold, the average expression for this cell-type is set to NaN.
147
+ :param mean_type: The type of mean that should be computed. The mean can be either 'tri_mean', 'mean', or 'trim_mean'.
148
+ :param trim_mean_fraction: The fraction of the data that should be trimmed when the mean type is set to 'trim_mean'.
149
+ :param gene_label_row: Name of the row attribute in the loom file that contains the gene labels (default: 'Gene')
150
+ :param n_processes: The number of processes that should be used for the computation. If n_processes is set to 1, the computation is done in a single process.
151
+ :return: A dictionary with keys 'condition_label_a' and 'condition_label_b'. Each key contains a dictionary with the subject id as key and a matrix of the average expression as value.
152
+ """
153
+
154
+ if not os.path.isfile(path_to_loom):
155
+ raise FileNotFoundError(f"File {path_to_loom} not found")
156
+
157
+ if not path_to_loom.endswith('.loom'):
158
+ raise ValueError('The file must be a loom file')
159
+
160
+ if condition_label_a == condition_label_b:
161
+ raise ValueError('The condition labels must be different')
162
+
163
+ if condition_label_a == '' or condition_label_a is None:
164
+ raise ValueError('The condition label a must be provided')
165
+
166
+ if condition_label_b == '' or condition_label_b is None:
167
+ raise ValueError('The condition label b must be provided')
168
+
169
+ if subject_label_column == '' or subject_label_column is None:
170
+ raise ValueError('The subject label column must be provided')
171
+
172
+ if cell_type_label_column == '' or cell_type_label_column is None:
173
+ raise ValueError('The cell type label column must be provided')
174
+
175
+ if n_processes <= 0:
176
+ raise ValueError('The number of processes must be greater than zero')
177
+
178
+ with loompy.connect(path_to_loom, mode='r') as data_loom:
179
+ # containing all subjects regardless of the conditon label that were provided
180
+ all_subjects: List[str] = list(set(data_loom.ca[subject_label_column]))
181
+
182
+ # Create a dictionary of all subjects to their condition
183
+ # Important: we only keep those subjects that have either condition_label_a or condition_label_b
184
+ subjects_to_condition_dict: Dict[str, str] = {
185
+ subject: data_loom.ca[data_loom.ca[subject_label_column] == subject][condition_label_column][0]
186
+ for subject in all_subjects
187
+ }
188
+ subjects_to_condition_dict = {
189
+ key: value
190
+ for key, value in subjects_to_condition_dict.items()
191
+ if value in [condition_label_a, condition_label_b]
192
+ }
193
+
194
+ subjects: List[str] = list(subjects_to_condition_dict.keys())
195
+
196
+ # Multiprocessing of avg_expression computation
197
+ multiprocessing_pool_input: List[Tuple[str, str, str, str, str, int, str, float]] = [
198
+ (
199
+ path_to_loom,
200
+ subject,
201
+ subject_label_column,
202
+ cell_type_label_column,
203
+ gene_label_row,
204
+ min_n_cells_threshold,
205
+ mean_type,
206
+ trim_mean_fraction
207
+ )
208
+ for subject in subjects
209
+ ]
210
+
211
+ if n_processes == 1:
212
+ avg_expressions_list: List[xr.DataArray] = [
213
+ compute_subject_specific_avg_expression(*method_input)
214
+ for method_input in multiprocessing_pool_input
215
+
216
+ ]
217
+ else:
218
+ with Pool(processes=n_processes) as pool:
219
+ avg_expressions_list: List[xr.DataArray] = pool.starmap(
220
+ compute_subject_specific_avg_expression,
221
+ multiprocessing_pool_input
222
+ )
223
+
224
+ # Create a dictionary of dictionaries containing the average expression matrix for all subjects separated by
225
+ # the condition.
226
+ avg_expressions: Dict[str, Dict[str, xr.DataArray]] = {
227
+ condition_label_a: {
228
+ subject: avg_expressions_list[i] for i, subject in enumerate(subjects)
229
+ if subjects_to_condition_dict[subject] == condition_label_a
230
+ },
231
+ condition_label_b: {
232
+ subject: avg_expressions_list[i] for i, subject in enumerate(subjects)
233
+ if subjects_to_condition_dict[subject] == condition_label_b
234
+ }
235
+ }
236
+
237
+ return avg_expressions
238
+
239
+
240
+ def compute_subject_specific_communication_score_matrix(
241
+ avg_expression: xr.DataArray,
242
+ interaction_db: InteractionDB
243
+ ) -> Tuple[xr.DataArray, Tuple[xr.DataArray, xr.DataArray]]:
244
+ """
245
+ Compute the communication score matrix for a single individual based on the average expression of all genes and the
246
+ interaction database.
247
+
248
+ The communication score matrix is computed as follows:
249
+ Step 1: Compute the abundance of the ligand and target subunit for each interaction. The abundance of a single
250
+ production step is the defined as the arithmetic mean of the isoenzyme gene expression. Each production
251
+ step is then multiplied by the coefficient of the production step. Then, the geometric mean over all
252
+ production steps is computed.
253
+ Step 2: Compute the communication score for each interaction pair. The communication score is computed
254
+ as the product of the abundance of the ligand and target subunit.
255
+
256
+ :param avg_expression: A matrix describing the average expression of all synthesizing genes for each cell-type.
257
+ :param interaction_db: The interaction database that contains all interactions.
258
+ :return: A three-dimensional matrix of the communication score for each cell-type pair and
259
+ ligand-target pair interaction. Secondary, a tuple of two matrices containing the abundance of the ligand
260
+ and target subunit for each interaction.
261
+ """
262
+ genes: Set[str] = set(avg_expression['genes'].values.tolist())
263
+ cell_types: List[str] = avg_expression['cell_types'].values.tolist()
264
+ interaction_names: List[str] = interaction_db.get_interaction_names()
265
+
266
+ n_cell_types: int = len(cell_types)
267
+ n_interactions: int = len(interaction_names)
268
+
269
+ ligand_abundance: np.array = np.zeros(shape=(n_interactions, n_cell_types))
270
+ target_abundance: np.array = np.zeros(shape=(n_interactions, n_cell_types))
271
+
272
+ # Iterate through all interaction pairs
273
+ for i, interaction_name in enumerate(interaction_names):
274
+ interaction_info: InteractionDBRow = interaction_db[interaction_name]
275
+
276
+ # If either there are no genes present for the ligand production or target subunit production:
277
+ # skip this interaction
278
+ if (len(set(interaction_info.ligand_contributor).intersection(genes)) == 0) or (len(set(interaction_info.target_subunit).intersection(genes)) == 0):
279
+ # Set the communication score to NaN as the interaction cannot be computed
280
+ ligand_abundance[i, :] = np.nan
281
+ target_abundance[i, :] = np.nan
282
+ continue
283
+
284
+ group_coeff_sum: int = 0
285
+ for ligand_group in interaction_info.ligand_groups:
286
+ group_genes: Set[str] = set(interaction_info.ligand_group_to_gene_dict[ligand_group])
287
+ group_coeff: int = interaction_info.ligand_group_to_coeff_dict[ligand_group]
288
+
289
+ group_coeff_sum += group_coeff
290
+
291
+ available_genes: List[str] = list(set(group_genes).intersection(genes))
292
+
293
+ if len(available_genes) == 0:
294
+ ligand_abundance[i, :] += 0
295
+ else:
296
+ # TODO potentially rewrite this to handle zeros in the log more elegantly?
297
+ with warnings.catch_warnings():
298
+ # Ignore warnings that are raised when the mean of an empty slice is calculated
299
+ warnings.filterwarnings(action='ignore', message='Mean of empty slice')
300
+ # Ignore warnings that are raised when the log of zero is calculated
301
+ warnings.filterwarnings(action='ignore', message='divide by zero encountered in log')
302
+
303
+ group_score: np.array = np.mean(avg_expression.loc[available_genes, cell_types], axis=0)
304
+ ligand_abundance[i, :] += group_coeff * np.log(group_score)
305
+
306
+ ligand_abundance[i, :] = np.exp(ligand_abundance[i, :] / group_coeff_sum)
307
+
308
+ group_coeff_sum: int = 0
309
+ for target_group in interaction_info.target_groups:
310
+ group_genes: List[str] = interaction_info.target_group_to_gene_dict[target_group]
311
+ group_coeff: int = interaction_info.target_group_to_coeff_dict[target_group]
312
+
313
+ group_coeff_sum += group_coeff
314
+
315
+ available_genes: List[str] = list(set(group_genes).intersection(genes))
316
+
317
+ if len(available_genes) == 0:
318
+ target_abundance[i, :] += 0
319
+ else:
320
+ # TODO potentially rewrite this to handle zeros in the log more elegantly?
321
+ with warnings.catch_warnings():
322
+ # Ignore warnings that are raised when the mean of an empty slice is calculated
323
+ warnings.filterwarnings(action='ignore', message='Mean of empty slice')
324
+ # Ignore warnings that are raised when the log of zero is calculated
325
+ warnings.filterwarnings(action='ignore', message='divide by zero encountered in log')
326
+
327
+ group_score: float = np.mean(avg_expression.loc[available_genes, cell_types], axis=0)
328
+ target_abundance[i, :] += group_coeff * np.log(group_score)
329
+
330
+ target_abundance[i, :] = np.exp(target_abundance[i, :] / group_coeff_sum)
331
+
332
+ communication_score_matrix: xr.DataArray = xr.DataArray(
333
+ data=np.zeros(shape=(n_cell_types, n_cell_types, n_interactions), dtype=float),
334
+ dims=['source', 'receiver', 'ligand_target_interactions'],
335
+ coords=[cell_types, cell_types, interaction_names]
336
+ )
337
+
338
+ ligand_abundance_matrix: xr.DataArray = xr.DataArray(
339
+ data=ligand_abundance.T,
340
+ dims=['cell_types', 'ligand_target_interactions'],
341
+ coords=[cell_types, interaction_names]
342
+ )
343
+
344
+ target_abundance_matrix: xr.DataArray = xr.DataArray(
345
+ data=target_abundance.T,
346
+ dims=['cell_types', 'ligand_target_interactions'],
347
+ coords=[cell_types, interaction_names]
348
+ )
349
+
350
+ for s, source in enumerate(cell_types):
351
+ for r, receiver in enumerate(cell_types):
352
+ communication_score_matrix.loc[source, receiver, :] = ligand_abundance[:, s] * target_abundance[:, r]
353
+
354
+ return communication_score_matrix, (ligand_abundance_matrix, target_abundance_matrix)
355
+
356
+
357
+ def compute_communication_score_matrix(
358
+ avg_expression_dict: Dict[str, Dict[str, xr.DataArray]],
359
+ interaction_db: InteractionDB,
360
+ n_processes: Optional[int] = 1
361
+ ) -> Tuple[Dict[str, Dict[str, xr.DataArray]], Dict[str, Dict[str, xr.DataArray]], Dict[str, Dict[str, xr.DataArray]]]:
362
+ """
363
+ Compute the communication score matrices for each condition, subject, and cell-type pair.
364
+
365
+ :param avg_expression_dict: A dictionary containing the average expression off all subjects for each condition. The first key is the condition, the second key is the subject id, and the value is a matrix of the average expression for each cell-type pair and ligand-target pair.
366
+ :param interaction_db: The interaction database that contains all interactions.
367
+ :param n_processes: The number of processes that should be used for the computation. If n_processes is set to 1, the computation is done in a single process.
368
+ :return: A triplet of dictionaries containing the communication score matrix, the ligand abundance matrix, and the target abundance matrix. The first key is the condition, the second key is the subject id, and the value is a matrix of the communication score, ligand abundance, or target abundance, split by condition and subject id.
369
+ """
370
+
371
+ if n_processes <= 0:
372
+ raise ValueError('The number of processes must be greater than zero')
373
+
374
+ # Create a dictionary to look up the condition for a specific subject
375
+ subject_to_condition_dict: Dict[str, str] = {
376
+ subject: condition
377
+ for condition in avg_expression_dict.keys()
378
+ for subject in avg_expression_dict[condition].keys()
379
+ }
380
+
381
+ conditions: List[str] = list(avg_expression_dict.keys())
382
+ subjects: List[str] = list(subject_to_condition_dict.keys())
383
+
384
+ multiprocessing_pool_input: List[Tuple[xr.DataArray, InteractionDB]] = [
385
+ (avg_expression_dict[subject_to_condition_dict[subject]][subject], interaction_db)
386
+ for subject in subjects
387
+ ]
388
+
389
+ # Multiprocessing of communication score computation
390
+ if n_processes == 1:
391
+ communication_score_matrices: List[Tuple[xr.DataArray, Tuple[xr.DataArray, xr.DataArray]]] = [
392
+ compute_subject_specific_communication_score_matrix(*method_input)
393
+ for method_input in multiprocessing_pool_input
394
+ ]
395
+ else:
396
+ with Pool(processes=n_processes) as pool:
397
+ communication_score_matrices: List[Tuple[xr.DataArray, Tuple[xr.DataArray, xr.DataArray]]] = pool.starmap(
398
+ compute_subject_specific_communication_score_matrix,
399
+ multiprocessing_pool_input
400
+ )
401
+
402
+ # Create a dictionary of dictionaries containing the communication score matrix for all subjects separated by
403
+ # the condition.
404
+ communication_scores_dict: Dict[str, Dict[str, xr.DataArray]] = {
405
+ conditions[0]: {
406
+ subject: communication_score_matrices[i][0]
407
+ for i, subject in enumerate(subjects)
408
+ if subject_to_condition_dict[subject] == conditions[0]
409
+ },
410
+ conditions[1]: {
411
+ subject: communication_score_matrices[i][0]
412
+ for i, subject in enumerate(subjects)
413
+ if subject_to_condition_dict[subject] == conditions[1]
414
+ }
415
+ }
416
+
417
+ # Create a dictionary of dictionaries containing the ligand abundance matrix for all subjects separated by
418
+ # the condition.
419
+ ligand_abundance_dict: Dict[str, Dict[str, xr.DataArray]] = {
420
+ conditions[0]: {
421
+ subject: communication_score_matrices[i][1][0]
422
+ for i, subject in enumerate(subjects)
423
+ if subject_to_condition_dict[subject] == conditions[0]
424
+ },
425
+ conditions[1]: {
426
+ subject: communication_score_matrices[i][1][0]
427
+ for i, subject in enumerate(subjects)
428
+ if subject_to_condition_dict[subject] == conditions[1]
429
+ }
430
+ }
431
+
432
+ # Create a dictionary of dictionaries containing the target abundance matrix for all subjects separated by
433
+ # the condition.
434
+ target_abundance_dict: Dict[str, Dict[str, xr.DataArray]] = {
435
+ conditions[0]: {
436
+ subject: communication_score_matrices[i][1][1]
437
+ for i, subject in enumerate(subjects)
438
+ if subject_to_condition_dict[subject] == conditions[0]
439
+ },
440
+ conditions[1]: {
441
+ subject: communication_score_matrices[i][1][1]
442
+ for i, subject in enumerate(subjects)
443
+ if subject_to_condition_dict[subject] == conditions[1]
444
+ }
445
+ }
446
+
447
+ return communication_scores_dict, ligand_abundance_dict, target_abundance_dict