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,919 @@
|
|
|
1
|
+
"""Functions and classes for the analysis after PBG training"""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import anndata as ad
|
|
6
|
+
from scipy.stats import entropy
|
|
7
|
+
from sklearn.neighbors import KDTree
|
|
8
|
+
from scipy.spatial import distance
|
|
9
|
+
# import faiss
|
|
10
|
+
|
|
11
|
+
from ._utils import _gini, _get_fdr
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def softmax(adata_ref,
|
|
15
|
+
adata_query,
|
|
16
|
+
T=0.5,
|
|
17
|
+
n_top=None,
|
|
18
|
+
percentile=0):
|
|
19
|
+
"""Softmax-based transformation
|
|
20
|
+
|
|
21
|
+
This will transform query data to reference-comparable data
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
adata_ref: `AnnData`
|
|
26
|
+
Reference anndata.
|
|
27
|
+
adata_query: `list`
|
|
28
|
+
Query anndata objects
|
|
29
|
+
T: `float`
|
|
30
|
+
Temperature parameter.
|
|
31
|
+
It controls the output probability distribution.
|
|
32
|
+
When T goes to inf, it becomes a discrete uniform distribution,
|
|
33
|
+
each query becomes the average of reference;
|
|
34
|
+
When T goes to zero, softargmax converges to arg max,
|
|
35
|
+
each query is approximately the best of reference.
|
|
36
|
+
cutoff: `float`
|
|
37
|
+
The cutoff used to filter out low-probability reference entities
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
updates `adata_query` with the following field.
|
|
41
|
+
softmax: `array_like` (`.layers['softmax']`)
|
|
42
|
+
Store #observations × #dimensions softmax transformed data matrix.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
scores_ref_query = np.matmul(adata_ref.X, adata_query.X.T)
|
|
46
|
+
# avoid overflow encountered
|
|
47
|
+
scores_ref_query = scores_ref_query - scores_ref_query.max()
|
|
48
|
+
scores_softmax = np.exp(scores_ref_query/T) / \
|
|
49
|
+
(np.exp(scores_ref_query/T).sum(axis=0))[None, :]
|
|
50
|
+
if n_top is None:
|
|
51
|
+
thresh = np.percentile(scores_softmax, q=percentile, axis=0)
|
|
52
|
+
else:
|
|
53
|
+
thresh = (np.sort(scores_softmax, axis=0)[::-1, :])[n_top-1, ]
|
|
54
|
+
mask = scores_softmax < thresh[None, :]
|
|
55
|
+
scores_softmax[mask] = 0
|
|
56
|
+
# rescale to make scores add up to 1
|
|
57
|
+
scores_softmax = scores_softmax/scores_softmax.sum(axis=0, keepdims=1)
|
|
58
|
+
X_query = np.dot(scores_softmax.T, adata_ref.X)
|
|
59
|
+
adata_query.layers['softmax'] = X_query
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class HgeEmbed:
|
|
63
|
+
"""A class used to represent post-training embedding analyis
|
|
64
|
+
|
|
65
|
+
Attributes
|
|
66
|
+
----------
|
|
67
|
+
|
|
68
|
+
Methods
|
|
69
|
+
-------
|
|
70
|
+
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
def __init__(self,
|
|
74
|
+
adata_ref,
|
|
75
|
+
list_adata_query,
|
|
76
|
+
T=0.5,
|
|
77
|
+
list_T=None,
|
|
78
|
+
percentile=50,
|
|
79
|
+
n_top=None,
|
|
80
|
+
list_percentile=None,
|
|
81
|
+
use_precomputed=True,
|
|
82
|
+
):
|
|
83
|
+
"""
|
|
84
|
+
Parameters
|
|
85
|
+
----------
|
|
86
|
+
adata_ref: `AnnData`
|
|
87
|
+
Reference anndata.
|
|
88
|
+
list_adata_query: `list`
|
|
89
|
+
A list query anndata objects
|
|
90
|
+
T: `float`
|
|
91
|
+
Temperature parameter shared by all query adata objects.
|
|
92
|
+
It controls the output probability distribution.
|
|
93
|
+
when T goes to inf, it becomes a discrete uniform distribution,
|
|
94
|
+
each query becomes the average of reference;
|
|
95
|
+
when T goes to zero, softargmax converges to arg max,
|
|
96
|
+
each query is approximately the best of reference.
|
|
97
|
+
list_T: `list`, (default: None)
|
|
98
|
+
A list of temperature parameters.
|
|
99
|
+
It should correspond to each of query data.
|
|
100
|
+
Once it's specified, it will override `T`.
|
|
101
|
+
cutoff: `float`, (default: None)
|
|
102
|
+
The cutoff used to filter out low-probability reference entities
|
|
103
|
+
list_cutoff: `list`, (default: None)
|
|
104
|
+
A list of cutoff values.
|
|
105
|
+
It should correspond to each of query data.
|
|
106
|
+
Once it's specified, it will override `cutoff`.
|
|
107
|
+
"""
|
|
108
|
+
assert isinstance(list_adata_query, list), \
|
|
109
|
+
"`list_adata_query` must be list"
|
|
110
|
+
if list_T is not None:
|
|
111
|
+
assert isinstance(list_T, list), \
|
|
112
|
+
"`list_T` must be list"
|
|
113
|
+
self.adata_ref = adata_ref
|
|
114
|
+
self.list_adata_query = list_adata_query
|
|
115
|
+
self.T = T
|
|
116
|
+
self.list_T = list_T
|
|
117
|
+
self.percentile = percentile
|
|
118
|
+
self.n_top = n_top
|
|
119
|
+
self.list_percentile = list_percentile
|
|
120
|
+
self.use_precomputed = use_precomputed
|
|
121
|
+
|
|
122
|
+
def embed(self):
|
|
123
|
+
"""Embed a list of query datasets along with reference dataset
|
|
124
|
+
into the same space
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
adata_all: `AnnData`
|
|
129
|
+
Store #entities × #dimensions.
|
|
130
|
+
"""
|
|
131
|
+
adata_ref = self.adata_ref
|
|
132
|
+
list_adata_query = self.list_adata_query
|
|
133
|
+
use_precomputed = self.use_precomputed
|
|
134
|
+
T = self.T
|
|
135
|
+
list_T = self.list_T
|
|
136
|
+
n_top = self.n_top
|
|
137
|
+
percentile = self.percentile
|
|
138
|
+
list_percentile = self.list_percentile
|
|
139
|
+
X_all = adata_ref.X.copy()
|
|
140
|
+
# obs_all = pd.DataFrame(
|
|
141
|
+
# data=['ref']*adata_ref.shape[0],
|
|
142
|
+
# index=adata_ref.obs.index,
|
|
143
|
+
# columns=['id_dataset'])
|
|
144
|
+
obs_all = adata_ref.obs.copy()
|
|
145
|
+
obs_all['id_dataset'] = ['ref']*adata_ref.shape[0]
|
|
146
|
+
for i, adata_query in enumerate(list_adata_query):
|
|
147
|
+
if list_T is not None:
|
|
148
|
+
param_T = list_T[i]
|
|
149
|
+
else:
|
|
150
|
+
param_T = T
|
|
151
|
+
if list_percentile is not None:
|
|
152
|
+
param_percentile = list_percentile[i]
|
|
153
|
+
else:
|
|
154
|
+
param_percentile = percentile
|
|
155
|
+
if use_precomputed:
|
|
156
|
+
if 'softmax' in adata_query.layers.keys():
|
|
157
|
+
print(f'Reading in precomputed softmax-transformed matrix '
|
|
158
|
+
f'for query data {i};')
|
|
159
|
+
else:
|
|
160
|
+
print(f'No softmax-transformed matrix exists '
|
|
161
|
+
f'for query data {i}')
|
|
162
|
+
print("Performing softmax transformation;")
|
|
163
|
+
softmax(
|
|
164
|
+
adata_ref,
|
|
165
|
+
adata_query,
|
|
166
|
+
T=param_T,
|
|
167
|
+
percentile=param_percentile,
|
|
168
|
+
n_top=n_top,
|
|
169
|
+
)
|
|
170
|
+
else:
|
|
171
|
+
print(f'Performing softmax transformation '
|
|
172
|
+
f'for query data {i};')
|
|
173
|
+
softmax(
|
|
174
|
+
adata_ref,
|
|
175
|
+
adata_query,
|
|
176
|
+
T=param_T,
|
|
177
|
+
percentile=param_percentile,
|
|
178
|
+
n_top=n_top,
|
|
179
|
+
)
|
|
180
|
+
X_all = np.vstack((X_all, adata_query.layers['softmax']))
|
|
181
|
+
# obs_all = obs_all.append(
|
|
182
|
+
# pd.DataFrame(
|
|
183
|
+
# data=[f'query_{i}']*adata_query.shape[0],
|
|
184
|
+
# index=adata_query.obs.index,
|
|
185
|
+
# columns=['id_dataset'])
|
|
186
|
+
# )
|
|
187
|
+
obs_query = adata_query.obs.copy()
|
|
188
|
+
obs_query['id_dataset'] = [f'query_{i}']*adata_query.shape[0]
|
|
189
|
+
obs_all = pd.concat(
|
|
190
|
+
[obs_all, obs_query], ignore_index=False)
|
|
191
|
+
adata_all = ad.AnnData(X=X_all,
|
|
192
|
+
obs=obs_all)
|
|
193
|
+
return adata_all
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def embed(adata_ref,
|
|
197
|
+
list_adata_query,
|
|
198
|
+
T=0.5,
|
|
199
|
+
list_T=None,
|
|
200
|
+
percentile=0,
|
|
201
|
+
n_top=None,
|
|
202
|
+
list_percentile=None,
|
|
203
|
+
use_precomputed=False):
|
|
204
|
+
"""Embed a list of query datasets along with reference dataset
|
|
205
|
+
into the same space
|
|
206
|
+
|
|
207
|
+
Parameters
|
|
208
|
+
----------
|
|
209
|
+
adata_ref: `AnnData`
|
|
210
|
+
Reference anndata.
|
|
211
|
+
list_adata_query: `list`
|
|
212
|
+
A list query anndata objects
|
|
213
|
+
T: `float`
|
|
214
|
+
Temperature parameter shared by all query adata objects.
|
|
215
|
+
It controls the output probability distribution.
|
|
216
|
+
when T goes to inf, it becomes a discrete uniform distribution,
|
|
217
|
+
each query becomes the average of reference;
|
|
218
|
+
when T goes to zero, softargmax converges to arg max,
|
|
219
|
+
each query is approximately the best of reference.
|
|
220
|
+
list_T: `list`, (default: None)
|
|
221
|
+
A list of temperature parameters.
|
|
222
|
+
It should correspond to each of query data.
|
|
223
|
+
Once it's specified, it will override `T`.
|
|
224
|
+
|
|
225
|
+
Returns
|
|
226
|
+
-------
|
|
227
|
+
adata_all: `AnnData`
|
|
228
|
+
Store #entities × #dimensions.
|
|
229
|
+
updates `adata_query` with the following field.
|
|
230
|
+
softmax: `array_like` (`.layers['softmax']`)
|
|
231
|
+
Store #observations × #dimensions softmax transformed data matrix.
|
|
232
|
+
"""
|
|
233
|
+
SE = HgeEmbed(adata_ref,
|
|
234
|
+
list_adata_query,
|
|
235
|
+
T=T,
|
|
236
|
+
list_T=list_T,
|
|
237
|
+
percentile=percentile,
|
|
238
|
+
n_top=n_top,
|
|
239
|
+
list_percentile=list_percentile,
|
|
240
|
+
use_precomputed=use_precomputed)
|
|
241
|
+
adata_all = SE.embed()
|
|
242
|
+
return adata_all
|
|
243
|
+
|
|
244
|
+
def _compare_entities(adata_ref,
|
|
245
|
+
adata_query,
|
|
246
|
+
n_top_cells=50,
|
|
247
|
+
T=1):
|
|
248
|
+
"""Compare the embeddings of two entities by calculating
|
|
249
|
+
|
|
250
|
+
the following values between reference and query entities:
|
|
251
|
+
|
|
252
|
+
- dot product
|
|
253
|
+
- normalized dot product
|
|
254
|
+
- softmax probability
|
|
255
|
+
|
|
256
|
+
and the following metrics for each query entity:
|
|
257
|
+
|
|
258
|
+
- max (The average maximum dot product of top-rank reference entities,
|
|
259
|
+
based on normalized dot product)
|
|
260
|
+
- std (standard deviation of reference entities,
|
|
261
|
+
based on dot product)
|
|
262
|
+
- gini (Gini coefficients of reference entities,
|
|
263
|
+
based on softmax probability)
|
|
264
|
+
- entropy (The entropy of reference entities,
|
|
265
|
+
based on softmax probability)
|
|
266
|
+
|
|
267
|
+
Parameters
|
|
268
|
+
----------
|
|
269
|
+
adata_ref: `AnnData`
|
|
270
|
+
Reference entity anndata.
|
|
271
|
+
adata_query: `AnnData`
|
|
272
|
+
Query entity anndata.
|
|
273
|
+
n_top_cells: `int`, optional (default: 50)
|
|
274
|
+
The number of cells to consider when calculating the metric 'max'
|
|
275
|
+
T: `float`
|
|
276
|
+
Temperature parameter for softmax.
|
|
277
|
+
It controls the output probability distribution.
|
|
278
|
+
When T goes to inf, it becomes a discrete uniform distribution,
|
|
279
|
+
each query becomes the average of reference;
|
|
280
|
+
When T goes to zero, softargmax converges to arg max,
|
|
281
|
+
each query is approximately the best of reference.
|
|
282
|
+
|
|
283
|
+
Returns
|
|
284
|
+
-------
|
|
285
|
+
adata_cmp: `AnnData`
|
|
286
|
+
Store reference entity as observations and query entity as variables
|
|
287
|
+
"""
|
|
288
|
+
X_ref = adata_ref.X
|
|
289
|
+
X_query = adata_query.X
|
|
290
|
+
X_cmp = np.matmul(X_ref, X_query.T)
|
|
291
|
+
adata_cmp = ad.AnnData(X=X_cmp,
|
|
292
|
+
obs=adata_ref.obs,
|
|
293
|
+
var=adata_query.obs)
|
|
294
|
+
adata_cmp.layers['norm'] = X_cmp \
|
|
295
|
+
- np.log(np.exp(X_cmp).mean(axis=0)).reshape(1, -1)
|
|
296
|
+
X_sorted_normed = np.sort(adata_cmp.layers['norm'], axis=0)
|
|
297
|
+
adata_cmp.layers['softmax'] = np.exp(X_cmp/T) \
|
|
298
|
+
/ np.exp(X_cmp/T).sum(axis=0).reshape(1, -1)
|
|
299
|
+
adata_cmp.var['max'] = \
|
|
300
|
+
np.clip(np.sort(adata_cmp.layers['norm'], axis=0)[-n_top_cells:, ],
|
|
301
|
+
a_min=0,
|
|
302
|
+
a_max=None).mean(axis=0)
|
|
303
|
+
adata_cmp.var['maxmin'] = (X_sorted_normed[-n_top_cells:, ]-X_sorted_normed[:n_top_cells,]).mean(axis=0)
|
|
304
|
+
adata_cmp.var['std'] = np.std(X_cmp, axis=0, ddof=1)
|
|
305
|
+
adata_cmp.var['gini'] = np.array([_gini(adata_cmp.layers['softmax'][:, i])
|
|
306
|
+
for i in np.arange(X_cmp.shape[1])])
|
|
307
|
+
adata_cmp.var['entropy'] = entropy(adata_cmp.layers['softmax'])
|
|
308
|
+
return adata_cmp
|
|
309
|
+
|
|
310
|
+
def compare_entities(adata_ref,
|
|
311
|
+
adata_query,
|
|
312
|
+
adata_query_null = None,
|
|
313
|
+
n_top_cells=50,
|
|
314
|
+
T=1,
|
|
315
|
+
):
|
|
316
|
+
"""Compare the embeddings of two entities by calculating
|
|
317
|
+
|
|
318
|
+
the following values between reference and query entities:
|
|
319
|
+
|
|
320
|
+
- dot product
|
|
321
|
+
- normalized dot product
|
|
322
|
+
- softmax probability
|
|
323
|
+
|
|
324
|
+
and the following metrics for each query entity:
|
|
325
|
+
|
|
326
|
+
- max (The average maximum dot product of top-rank reference entities,
|
|
327
|
+
based on normalized dot product)
|
|
328
|
+
- std (standard deviation of reference entities,
|
|
329
|
+
based on dot product)
|
|
330
|
+
- gini (Gini coefficients of reference entities,
|
|
331
|
+
based on softmax probability)
|
|
332
|
+
- entropy (The entropy of reference entities,
|
|
333
|
+
based on softmax probability)
|
|
334
|
+
|
|
335
|
+
Optionally produces FDR of metrics when null query embedding is provided.
|
|
336
|
+
Metric score of null embedding will be used as the null disbribution of the
|
|
337
|
+
metrics.
|
|
338
|
+
|
|
339
|
+
Parameters
|
|
340
|
+
----------
|
|
341
|
+
adata_ref: `AnnData`
|
|
342
|
+
Reference entity anndata.
|
|
343
|
+
adata_query: `AnnData`
|
|
344
|
+
Query entity anndata.
|
|
345
|
+
adata_query_null: `AnnData`, optional (default: None)
|
|
346
|
+
Null query entity anndata generated by random graph.
|
|
347
|
+
FDR of each metric is calculated using the metrics calcualted
|
|
348
|
+
from the embedding of these null nodes as the null distribution.
|
|
349
|
+
n_top_cells: `int`, optional (default: 50)
|
|
350
|
+
The number of cells to consider when calculating the metric 'max'
|
|
351
|
+
T: `float`
|
|
352
|
+
Temperature parameter for softmax.
|
|
353
|
+
It controls the output probability distribution.
|
|
354
|
+
When T goes to inf, it becomes a discrete uniform distribution,
|
|
355
|
+
each query becomes the average of reference;
|
|
356
|
+
When T goes to zero, softargmax converges to arg max,
|
|
357
|
+
each query is approximately the best of reference.
|
|
358
|
+
|
|
359
|
+
Returns
|
|
360
|
+
-------
|
|
361
|
+
adata_cmp: `AnnData`
|
|
362
|
+
Store reference entity as observations and query entity as variables
|
|
363
|
+
"""
|
|
364
|
+
adata_cmp = _compare_entities(adata_ref, adata_query, n_top_cells = n_top_cells, T = T)
|
|
365
|
+
if adata_query_null is None:
|
|
366
|
+
return(adata_cmp)
|
|
367
|
+
|
|
368
|
+
adata_cmp_null = _compare_entities(adata_ref, adata_query_null, n_top_cells = n_top_cells, T = T)
|
|
369
|
+
for metric in ['max', 'std', 'gini', 'entropy']:
|
|
370
|
+
if metric == 'entropy':
|
|
371
|
+
# for entropy, we need the p_value in the opposite direction (lower value if significant)
|
|
372
|
+
p_val, fdr = _get_fdr(-adata_cmp.var[metric], -adata_cmp_null.var[metric])
|
|
373
|
+
else:
|
|
374
|
+
p_val, fdr = _get_fdr(adata_cmp.var[metric], adata_cmp_null.var[metric])
|
|
375
|
+
adata_cmp.var[f"{metric}_p"] = p_val
|
|
376
|
+
adata_cmp.var[f"{metric}_fdr"] = fdr
|
|
377
|
+
return adata_cmp
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def query(adata,
|
|
381
|
+
obsm='X_umap',
|
|
382
|
+
layer=None,
|
|
383
|
+
metric='euclidean',
|
|
384
|
+
anno_filter=None,
|
|
385
|
+
filters=None,
|
|
386
|
+
entity=None,
|
|
387
|
+
pin=None,
|
|
388
|
+
k=20,
|
|
389
|
+
use_radius=False,
|
|
390
|
+
r=None,
|
|
391
|
+
**kwargs
|
|
392
|
+
):
|
|
393
|
+
"""Query the "database" of entites
|
|
394
|
+
|
|
395
|
+
Parameters
|
|
396
|
+
----------
|
|
397
|
+
adata : `AnnData`
|
|
398
|
+
Anndata object to query.
|
|
399
|
+
obsm : `str`, optional (default: "X_umap")
|
|
400
|
+
The multi-dimensional annotation to use for calculating the distance.
|
|
401
|
+
layer : `str`, optional (default: None)
|
|
402
|
+
The layer to use for calculating the distance.
|
|
403
|
+
metric : `str`, optional (default: "euclidean")
|
|
404
|
+
The distance metric to use.
|
|
405
|
+
More metrics can be found at "`DistanceMetric class
|
|
406
|
+
<https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.DistanceMetric.html>`__"
|
|
407
|
+
anno_filter : `str`, optional (default: None)
|
|
408
|
+
The annotation of filter to use.
|
|
409
|
+
It should be one of ``adata.obs_keys()``
|
|
410
|
+
filters : `list`, optional (default: None)
|
|
411
|
+
The filters to use.
|
|
412
|
+
It should be a list of values in ``adata.obs[anno_filter]``
|
|
413
|
+
entity : `list`, optional (default: None)
|
|
414
|
+
Query entity. It needs to be in ``adata.obs_names()``
|
|
415
|
+
k : `int`, optional (default: 20)
|
|
416
|
+
The number of nearest neighbors to return.
|
|
417
|
+
Only valid if ``use_radius`` is False
|
|
418
|
+
use_radius : `bool`, optional (default: False)
|
|
419
|
+
If True, query for neighbors within a given radius
|
|
420
|
+
r: `float`, optional (default: None)
|
|
421
|
+
Distance within which neighbors are returned.
|
|
422
|
+
If None, it will be estimated based the range of the space.
|
|
423
|
+
**kwargs: `dict`, optional
|
|
424
|
+
Extra arguments to ``sklearn.neighbors.KDTree``.
|
|
425
|
+
|
|
426
|
+
Returns
|
|
427
|
+
-------
|
|
428
|
+
updates `adata` with the following fields.
|
|
429
|
+
|
|
430
|
+
params: `dict`, (`adata.uns['query']['params']`)
|
|
431
|
+
Parameters used for the query
|
|
432
|
+
output: `pandas.DataFrame`, (`adata.uns['query']['output']`)
|
|
433
|
+
Query result.
|
|
434
|
+
"""
|
|
435
|
+
if sum(list(map(lambda x: x is None,
|
|
436
|
+
[entity, pin]))) == 2:
|
|
437
|
+
raise ValueError("One of `entity` and `pin` must be specified")
|
|
438
|
+
if sum(list(map(lambda x: x is not None,
|
|
439
|
+
[entity, pin]))) == 2:
|
|
440
|
+
print("`entity` will be ignored.")
|
|
441
|
+
if entity is not None:
|
|
442
|
+
entity = np.array(entity).flatten()
|
|
443
|
+
|
|
444
|
+
if sum(list(map(lambda x: x is not None,
|
|
445
|
+
[layer, obsm]))) == 2:
|
|
446
|
+
raise ValueError("Only one of `layer` and `obsm` can be used")
|
|
447
|
+
elif obsm is not None:
|
|
448
|
+
X = adata.obsm[obsm].copy()
|
|
449
|
+
if pin is None:
|
|
450
|
+
pin = adata[entity, :].obsm[obsm].copy()
|
|
451
|
+
elif layer is not None:
|
|
452
|
+
X = adata.layers[layer].copy()
|
|
453
|
+
if pin is None:
|
|
454
|
+
pin = adata[entity, :].layers[layer].copy()
|
|
455
|
+
else:
|
|
456
|
+
X = adata.X.copy()
|
|
457
|
+
if pin is None:
|
|
458
|
+
pin = adata[entity, :].X.copy()
|
|
459
|
+
pin = np.reshape(np.array(pin), [-1, X.shape[1]])
|
|
460
|
+
|
|
461
|
+
if use_radius:
|
|
462
|
+
kdt = KDTree(X, metric=metric, **kwargs)
|
|
463
|
+
if r is None:
|
|
464
|
+
r = np.mean(X.max(axis=0) - X.min(axis=0))/5
|
|
465
|
+
ind, dist = kdt.query_radius(pin,
|
|
466
|
+
r=r,
|
|
467
|
+
sort_results=True,
|
|
468
|
+
return_distance=True)
|
|
469
|
+
df_output = pd.DataFrame()
|
|
470
|
+
for ii in np.arange(pin.shape[0]):
|
|
471
|
+
df_output_ii = adata.obs.iloc[ind[ii], ].copy()
|
|
472
|
+
df_output_ii['distance'] = dist[ii]
|
|
473
|
+
if entity is not None:
|
|
474
|
+
df_output_ii['query'] = entity[ii]
|
|
475
|
+
else:
|
|
476
|
+
df_output_ii['query'] = ii
|
|
477
|
+
df_output = pd.concat(
|
|
478
|
+
[df_output, df_output_ii], ignore_index=False)
|
|
479
|
+
if anno_filter is not None:
|
|
480
|
+
if anno_filter in adata.obs_keys():
|
|
481
|
+
if filters is None:
|
|
482
|
+
filters = df_output[anno_filter].unique().tolist()
|
|
483
|
+
df_output.query(f'{anno_filter} == @filters', inplace=True)
|
|
484
|
+
else:
|
|
485
|
+
raise ValueError(f'could not find {anno_filter}')
|
|
486
|
+
df_output = df_output.sort_values(by='distance')
|
|
487
|
+
else:
|
|
488
|
+
# assert (metric in ['euclidean', 'dot_product']),\
|
|
489
|
+
# "`metric` must be one of ['euclidean','dot_product']"
|
|
490
|
+
if anno_filter is not None:
|
|
491
|
+
if anno_filter in adata.obs_keys():
|
|
492
|
+
if filters is None:
|
|
493
|
+
filters = adata.obs[anno_filter].unique().tolist()
|
|
494
|
+
ids_filters = \
|
|
495
|
+
np.where(np.isin(adata.obs[anno_filter], filters))[0]
|
|
496
|
+
else:
|
|
497
|
+
raise ValueError(f'could not find {anno_filter}')
|
|
498
|
+
else:
|
|
499
|
+
ids_filters = np.arange(X.shape[0])
|
|
500
|
+
kdt = KDTree(X[ids_filters, :], metric=metric, **kwargs)
|
|
501
|
+
dist, ind = kdt.query(pin,
|
|
502
|
+
k=k,
|
|
503
|
+
sort_results=True,
|
|
504
|
+
return_distance=True)
|
|
505
|
+
|
|
506
|
+
# use faiss
|
|
507
|
+
# X = X.astype('float32')
|
|
508
|
+
# if metric == 'euclidean':
|
|
509
|
+
# faiss_index = faiss.IndexFlatL2(X.shape[1]) # build the index
|
|
510
|
+
# faiss_index.add(X[ids_filters, :])
|
|
511
|
+
# dist, ind = faiss_index.search(pin, k)
|
|
512
|
+
# if metric == 'dot_product':
|
|
513
|
+
# faiss_index = faiss.IndexFlatIP(X.shape[1]) # build the index
|
|
514
|
+
# faiss_index.add(X[ids_filters, :])
|
|
515
|
+
# sim, ind = faiss_index.search(pin, k)
|
|
516
|
+
# dist = -sim
|
|
517
|
+
|
|
518
|
+
df_output = pd.DataFrame()
|
|
519
|
+
for ii in np.arange(pin.shape[0]):
|
|
520
|
+
df_output_ii = \
|
|
521
|
+
adata.obs.iloc[ids_filters, ].iloc[ind[ii, ], ].copy()
|
|
522
|
+
df_output_ii['distance'] = dist[ii, ]
|
|
523
|
+
if entity is not None:
|
|
524
|
+
df_output_ii['query'] = entity[ii]
|
|
525
|
+
else:
|
|
526
|
+
df_output_ii['query'] = ii
|
|
527
|
+
df_output = pd.concat(
|
|
528
|
+
[df_output, df_output_ii], ignore_index=False)
|
|
529
|
+
df_output = df_output.sort_values(by='distance')
|
|
530
|
+
|
|
531
|
+
adata.uns['query'] = dict()
|
|
532
|
+
adata.uns['query']['params'] = {'obsm': obsm,
|
|
533
|
+
'layer': layer,
|
|
534
|
+
'entity': entity,
|
|
535
|
+
'pin': pin,
|
|
536
|
+
'k': k,
|
|
537
|
+
'use_radius': use_radius,
|
|
538
|
+
'r': r}
|
|
539
|
+
adata.uns['query']['output'] = df_output.copy()
|
|
540
|
+
return df_output
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def find_master_regulators(adata_all,
|
|
544
|
+
list_tf_motif=None,
|
|
545
|
+
list_tf_gene=None,
|
|
546
|
+
metric='euclidean',
|
|
547
|
+
anno_filter='entity_anno',
|
|
548
|
+
filter_gene='gene',
|
|
549
|
+
metrics_gene=None,
|
|
550
|
+
metrics_motif=None,
|
|
551
|
+
cutoff_gene_max=1.5,
|
|
552
|
+
cutoff_gene_gini=0.3,
|
|
553
|
+
cutoff_gene_std=None,
|
|
554
|
+
cutoff_gene_entropy=None,
|
|
555
|
+
cutoff_motif_max=1.5,
|
|
556
|
+
cutoff_motif_gini=0.3,
|
|
557
|
+
cutoff_motif_std=None,
|
|
558
|
+
cutoff_motif_entropy=None,
|
|
559
|
+
):
|
|
560
|
+
"""Find all the master regulators
|
|
561
|
+
|
|
562
|
+
Parameters
|
|
563
|
+
----------
|
|
564
|
+
adata_all : `AnnData`
|
|
565
|
+
Anndata object storing MultiChat embedding of all entities.
|
|
566
|
+
list_tf_motif : `list`
|
|
567
|
+
A list of TF motifs. They should match TF motifs in `list_tf_gene`.
|
|
568
|
+
list_tf_gene : `list`
|
|
569
|
+
A list TF genes. They should match TF motifs in `list_tf_motif`.
|
|
570
|
+
metric : `str`, optional (default: "euclidean")
|
|
571
|
+
The distance metric to use. It can be ‘braycurtis’, ‘canberra’,
|
|
572
|
+
‘chebyshev’, ‘cityblock’, ‘correlation’, ‘cosine’, ‘dice’, ‘euclidean’,
|
|
573
|
+
‘hamming’, ‘jaccard’, ‘jensenshannon’, ‘kulsinski’, ‘mahalanobis’,
|
|
574
|
+
‘matching’, ‘minkowski’, ‘rogerstanimoto’, ‘russellrao’, ‘seuclidean’,
|
|
575
|
+
‘sokalmichener’, ‘sokalsneath’, ‘sqeuclidean’, ‘wminkowski’, ‘yule’.
|
|
576
|
+
anno_filter : `str`, optional (default: None)
|
|
577
|
+
The annotation of filter to use.
|
|
578
|
+
It should be one of ``adata.obs_keys()``
|
|
579
|
+
filter_gene : `str`, optional (default: None)
|
|
580
|
+
The filter for gene.
|
|
581
|
+
It should be in ``adata.obs[anno_filter]``
|
|
582
|
+
metrics_gene : `pandas.DataFrame`, optional (default: None)
|
|
583
|
+
HGE metrics for genes.
|
|
584
|
+
metrics_motif : `pandas.DataFrame`, optional (default: None)
|
|
585
|
+
HGE metrics for motifs.
|
|
586
|
+
cutoff_gene_max, cutoff_motif_max: `float`
|
|
587
|
+
cutoff of HGE metric `max value` for genes and motifs
|
|
588
|
+
cutoff_gene_gini, cutoff_motif_gini: `float`
|
|
589
|
+
cutoff of HGE metric `Gini index` for genes and motifs
|
|
590
|
+
cutoff_gene_gini, cutoff_motif_gini: `float`
|
|
591
|
+
cutoff of HGE metric `Gini index` for genes and motifs
|
|
592
|
+
cutoff_gene_std, cutoff_motif_std: `float`
|
|
593
|
+
cutoff of HGE metric `standard deviation` for genes and motifs
|
|
594
|
+
cutoff_gene_entropy, cutoff_motif_entropy: `float`
|
|
595
|
+
cutoff of HGE metric `entropy` for genes and motifs
|
|
596
|
+
|
|
597
|
+
Returns
|
|
598
|
+
-------
|
|
599
|
+
df_MR: `pandas.DataFrame`
|
|
600
|
+
Dataframe of master regulators
|
|
601
|
+
"""
|
|
602
|
+
if sum(list(map(lambda x: x is None,
|
|
603
|
+
[list_tf_motif, list_tf_gene]))) > 0:
|
|
604
|
+
return "Please specify both `list_tf_motif` and `list_tf_gene`"
|
|
605
|
+
|
|
606
|
+
assert isinstance(list_tf_motif, list), \
|
|
607
|
+
"`list_tf_motif` must be list"
|
|
608
|
+
assert isinstance(list_tf_gene, list), \
|
|
609
|
+
"`list_tf_gene` must be list"
|
|
610
|
+
assert len(list_tf_motif) == len(list_tf_gene), \
|
|
611
|
+
"`list_tf_motif` and `list_tf_gene` must have the same length"
|
|
612
|
+
assert len(list_tf_motif) == len(set(list_tf_motif)), \
|
|
613
|
+
"Duplicates are found in `list_tf_motif`"
|
|
614
|
+
|
|
615
|
+
genes = adata_all[adata_all.obs[anno_filter] == filter_gene].\
|
|
616
|
+
obs_names.tolist().copy()
|
|
617
|
+
# Master Regulator
|
|
618
|
+
df_MR = pd.DataFrame(list(zip(list_tf_motif, list_tf_gene)),
|
|
619
|
+
columns=['motif', 'gene'])
|
|
620
|
+
|
|
621
|
+
if metrics_motif is not None:
|
|
622
|
+
print('Adding motif metrics ...')
|
|
623
|
+
assert isinstance(metrics_motif, pd.DataFrame), \
|
|
624
|
+
"`metrics_motif` must be pd.DataFrame"
|
|
625
|
+
df_metrics_motif = metrics_motif.loc[list_tf_motif, ].copy()
|
|
626
|
+
df_metrics_motif.columns = df_metrics_motif.columns + '_motif'
|
|
627
|
+
df_MR = df_MR.merge(df_metrics_motif,
|
|
628
|
+
how='left',
|
|
629
|
+
left_on='motif',
|
|
630
|
+
right_index=True)
|
|
631
|
+
|
|
632
|
+
if metrics_gene is not None:
|
|
633
|
+
print('Adding gene metrics ...')
|
|
634
|
+
assert isinstance(metrics_gene, pd.DataFrame), \
|
|
635
|
+
"`metrics_gene` must be pd.DataFrame"
|
|
636
|
+
df_metrics_gene = metrics_gene.loc[list_tf_gene, ].copy()
|
|
637
|
+
df_metrics_gene.index = list_tf_motif # avoid duplicate genes
|
|
638
|
+
df_metrics_gene.columns = df_metrics_gene.columns + '_gene'
|
|
639
|
+
df_MR = df_MR.merge(df_metrics_gene,
|
|
640
|
+
how='left',
|
|
641
|
+
left_on='motif',
|
|
642
|
+
right_index=True)
|
|
643
|
+
print('Computing distances between TF motifs and genes ...')
|
|
644
|
+
dist_MG = distance.cdist(adata_all[df_MR['motif'], ].X,
|
|
645
|
+
adata_all[genes, ].X,
|
|
646
|
+
metric=metric)
|
|
647
|
+
dist_MG = pd.DataFrame(dist_MG,
|
|
648
|
+
index=df_MR['motif'].tolist(),
|
|
649
|
+
columns=genes)
|
|
650
|
+
df_MR.insert(2, 'rank', -1)
|
|
651
|
+
df_MR.insert(3, 'dist', -1)
|
|
652
|
+
for i in np.arange(df_MR.shape[0]):
|
|
653
|
+
x_motif = df_MR['motif'].iloc[i]
|
|
654
|
+
x_gene = df_MR['gene'].iloc[i]
|
|
655
|
+
df_MR.loc[i, 'rank'] = dist_MG.loc[x_motif, ].rank()[x_gene]
|
|
656
|
+
df_MR.loc[i, 'dist'] = dist_MG.loc[x_motif, x_gene]
|
|
657
|
+
|
|
658
|
+
if metrics_gene is not None:
|
|
659
|
+
print('filtering master regulators based on gene metrics:')
|
|
660
|
+
if cutoff_gene_entropy is not None:
|
|
661
|
+
print('entropy')
|
|
662
|
+
df_MR = df_MR[df_MR['entropy_gene'] > cutoff_gene_entropy]
|
|
663
|
+
if cutoff_gene_gini is not None:
|
|
664
|
+
print('Gini index')
|
|
665
|
+
df_MR = df_MR[df_MR['gini_gene'] > cutoff_gene_gini]
|
|
666
|
+
if cutoff_gene_max is not None:
|
|
667
|
+
print('max')
|
|
668
|
+
df_MR = df_MR[df_MR['max_gene'] > cutoff_gene_max]
|
|
669
|
+
if cutoff_gene_std is not None:
|
|
670
|
+
print('standard deviation')
|
|
671
|
+
df_MR = df_MR[df_MR['std_gene'] > cutoff_gene_std]
|
|
672
|
+
if metrics_motif is not None:
|
|
673
|
+
print('filtering master regulators based on motif metrics:')
|
|
674
|
+
if cutoff_motif_entropy is not None:
|
|
675
|
+
print('entropy')
|
|
676
|
+
df_MR = df_MR[df_MR['entropy_motif'] > cutoff_motif_entropy]
|
|
677
|
+
if cutoff_motif_gini is not None:
|
|
678
|
+
print('Gini index')
|
|
679
|
+
df_MR = df_MR[df_MR['gini_motif'] > cutoff_motif_gini]
|
|
680
|
+
if cutoff_motif_max is not None:
|
|
681
|
+
print('max')
|
|
682
|
+
df_MR = df_MR[df_MR['max_motif'] > cutoff_motif_max]
|
|
683
|
+
if cutoff_motif_std is not None:
|
|
684
|
+
print('standard deviation')
|
|
685
|
+
df_MR = df_MR[df_MR['std_motif'] > cutoff_motif_std]
|
|
686
|
+
df_MR = df_MR.sort_values(by='rank', ignore_index=True)
|
|
687
|
+
return df_MR
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
def find_target_genes(adata_all,
|
|
691
|
+
adata_PM,
|
|
692
|
+
list_tf_motif=None,
|
|
693
|
+
list_tf_gene=None,
|
|
694
|
+
adata_CP=None,
|
|
695
|
+
metric='euclidean',
|
|
696
|
+
anno_filter='entity_anno',
|
|
697
|
+
filter_peak='peak',
|
|
698
|
+
filter_gene='gene',
|
|
699
|
+
n_genes=200,
|
|
700
|
+
cutoff_gene=None,
|
|
701
|
+
cutoff_peak=1000,
|
|
702
|
+
use_precomputed=True,
|
|
703
|
+
):
|
|
704
|
+
"""For a given TF, infer its target genes
|
|
705
|
+
|
|
706
|
+
Parameters
|
|
707
|
+
----------
|
|
708
|
+
adata_all : `AnnData`
|
|
709
|
+
Anndata object storing HGE embedding of all entities.
|
|
710
|
+
adata_PM : `AnnData`
|
|
711
|
+
Peaks-by-motifs anndata object.
|
|
712
|
+
list_tf_motif : `list`
|
|
713
|
+
A list of TF motifs. They should match TF motifs in `list_tf_gene`.
|
|
714
|
+
list_tf_gene : `list`
|
|
715
|
+
A list TF genes. They should match TF motifs in `list_tf_motif`.
|
|
716
|
+
adata_CP : `AnnData`, optional (default: None)
|
|
717
|
+
When ``use_precomputed`` is True, it can be set None
|
|
718
|
+
metric : `str`, optional (default: "euclidean")
|
|
719
|
+
The distance metric to use. It can be ‘braycurtis’, ‘canberra’,
|
|
720
|
+
‘chebyshev’, ‘cityblock’, ‘correlation’, ‘cosine’, ‘dice’, ‘euclidean’,
|
|
721
|
+
‘hamming’, ‘jaccard’, ‘jensenshannon’, ‘kulsinski’, ‘mahalanobis’,
|
|
722
|
+
‘matching’, ‘minkowski’, ‘rogerstanimoto’, ‘russellrao’, ‘seuclidean’,
|
|
723
|
+
‘sokalmichener’, ‘sokalsneath’, ‘sqeuclidean’, ‘wminkowski’, ‘yule’.
|
|
724
|
+
anno_filter : `str`, optional (default: None)
|
|
725
|
+
The annotation of filter to use.
|
|
726
|
+
It should be one of ``adata.obs_keys()``
|
|
727
|
+
filter_gene : `str`, optional (default: None)
|
|
728
|
+
The filter for gene.
|
|
729
|
+
It should be in ``adata.obs[anno_filter]``
|
|
730
|
+
filter_peak : `str`, optional (default: None)
|
|
731
|
+
The filter for peak.
|
|
732
|
+
It should be in ``adata.obs[anno_filter]``
|
|
733
|
+
n_genes : `int`, optional (default: 200)
|
|
734
|
+
The number of neighbor genes to consider initially
|
|
735
|
+
around TF gene or TF motif
|
|
736
|
+
cutoff_gene : `float`, optional (default: None)
|
|
737
|
+
Cutoff of "average_rank"
|
|
738
|
+
cutoff_peak : `int`, optional (default: 1000)
|
|
739
|
+
Cutoff for peaks-associated ranks, including
|
|
740
|
+
"rank_peak_to_gene" and "rank_peak_to_TFmotif".
|
|
741
|
+
use_precomputed : `bool`, optional (default: True)
|
|
742
|
+
Distances calculated between genes, peaks, and motifs
|
|
743
|
+
(stored in `adata.uns['tf_targets']`) will be imported
|
|
744
|
+
|
|
745
|
+
Returns
|
|
746
|
+
-------
|
|
747
|
+
dict_tf_targets : `dict`
|
|
748
|
+
Target genes for each TF.
|
|
749
|
+
|
|
750
|
+
updates `adata` with the following fields.
|
|
751
|
+
|
|
752
|
+
tf_targets: `dict`, (`adata.uns['tf_targets']`)
|
|
753
|
+
Distances calculated between genes, peaks, and motifs
|
|
754
|
+
"""
|
|
755
|
+
if sum(list(map(lambda x: x is None,
|
|
756
|
+
[list_tf_motif, list_tf_gene]))) > 0:
|
|
757
|
+
return "Please specify both `list_tf_motif` and `list_tf_gene`"
|
|
758
|
+
|
|
759
|
+
assert isinstance(list_tf_motif, list), \
|
|
760
|
+
"`list_tf_motif` must be list"
|
|
761
|
+
assert isinstance(list_tf_gene, list), \
|
|
762
|
+
"`list_tf_gene` must be list"
|
|
763
|
+
assert len(list_tf_motif) == len(list_tf_gene), \
|
|
764
|
+
"`list_tf_motif` and `list_tf_gene` must have the same length"
|
|
765
|
+
|
|
766
|
+
def isin(a, b):
|
|
767
|
+
return np.array([item in b for item in a])
|
|
768
|
+
|
|
769
|
+
print('Preprocessing ...')
|
|
770
|
+
if use_precomputed and 'tf_targets' in adata_all.uns_keys():
|
|
771
|
+
print('importing precomputed variables ...')
|
|
772
|
+
genes = adata_all.uns['tf_targets']['genes']
|
|
773
|
+
peaks = adata_all.uns['tf_targets']['peaks']
|
|
774
|
+
peaks_in_genes = adata_all.uns['tf_targets']['peaks_in_genes']
|
|
775
|
+
dist_PG = adata_all.uns['tf_targets']['dist_PG']
|
|
776
|
+
overlap_PG = adata_all.uns['tf_targets']['overlap']
|
|
777
|
+
else:
|
|
778
|
+
assert (adata_CP is not None), \
|
|
779
|
+
'`adata_CP` needs to be specified '\
|
|
780
|
+
'when no precomputed variable is stored'
|
|
781
|
+
if 'gene_scores' not in adata_CP.uns_keys():
|
|
782
|
+
print('Please run "si.tl.gene_scores(adata_CP)" first.')
|
|
783
|
+
else:
|
|
784
|
+
overlap_PG = adata_CP.uns['gene_scores']['overlap'].copy()
|
|
785
|
+
overlap_PG['peak'] = \
|
|
786
|
+
overlap_PG[['chr_p', 'start_p', 'end_p']].apply(
|
|
787
|
+
lambda row: '_'.join(row.values.astype(str)), axis=1)
|
|
788
|
+
tuples = list(zip(overlap_PG['symbol_g'], overlap_PG['peak']))
|
|
789
|
+
multi_indices = pd.MultiIndex.from_tuples(
|
|
790
|
+
tuples, names=["gene", "peak"])
|
|
791
|
+
overlap_PG.index = multi_indices
|
|
792
|
+
|
|
793
|
+
genes = adata_all[adata_all.obs[anno_filter] == filter_gene].\
|
|
794
|
+
obs_names.tolist().copy()
|
|
795
|
+
peaks = adata_all[adata_all.obs[anno_filter] == filter_peak].\
|
|
796
|
+
obs_names.tolist().copy()
|
|
797
|
+
peaks_in_genes = list(set(overlap_PG['peak']))
|
|
798
|
+
|
|
799
|
+
print(f'#genes: {len(genes)}')
|
|
800
|
+
print(f'#peaks: {len(peaks)}')
|
|
801
|
+
print(f'#genes-associated peaks: {len(peaks_in_genes)}')
|
|
802
|
+
print('computing distances between genes '
|
|
803
|
+
'and genes-associated peaks ...')
|
|
804
|
+
dist_PG = distance.cdist(
|
|
805
|
+
adata_all[peaks_in_genes, ].X,
|
|
806
|
+
adata_all[genes, ].X,
|
|
807
|
+
metric=metric,
|
|
808
|
+
)
|
|
809
|
+
dist_PG = pd.DataFrame(dist_PG, index=peaks_in_genes, columns=[genes])
|
|
810
|
+
print("Saving variables into `.uns['tf_targets']` ...")
|
|
811
|
+
adata_all.uns['tf_targets'] = dict()
|
|
812
|
+
adata_all.uns['tf_targets']['overlap'] = overlap_PG
|
|
813
|
+
adata_all.uns['tf_targets']['dist_PG'] = dist_PG
|
|
814
|
+
adata_all.uns['tf_targets']['peaks_in_genes'] = peaks_in_genes
|
|
815
|
+
adata_all.uns['tf_targets']['genes'] = genes
|
|
816
|
+
adata_all.uns['tf_targets']['peaks'] = peaks
|
|
817
|
+
adata_all.uns['tf_targets']['peaks_in_genes'] = peaks_in_genes
|
|
818
|
+
|
|
819
|
+
dict_tf_targets = dict()
|
|
820
|
+
for tf_motif, tf_gene in zip(list_tf_motif, list_tf_gene):
|
|
821
|
+
|
|
822
|
+
print(f'searching for target genes of {tf_motif}')
|
|
823
|
+
motif_peaks = adata_PM.obs_names[adata_PM[:, tf_motif].X.nonzero()[0]]
|
|
824
|
+
motif_genes = list(
|
|
825
|
+
set(overlap_PG[isin(overlap_PG['peak'], motif_peaks)]['symbol_g'])
|
|
826
|
+
.intersection(genes))
|
|
827
|
+
|
|
828
|
+
# rank of the distances from genes to tf_motif
|
|
829
|
+
dist_GM_motif = distance.cdist(adata_all[genes, ].X,
|
|
830
|
+
adata_all[tf_motif, ].X,
|
|
831
|
+
metric=metric)
|
|
832
|
+
dist_GM_motif = pd.DataFrame(dist_GM_motif,
|
|
833
|
+
index=genes,
|
|
834
|
+
columns=[tf_motif])
|
|
835
|
+
rank_GM_motif = dist_GM_motif.rank(axis=0)
|
|
836
|
+
|
|
837
|
+
# rank of the distances from genes to tf_gene
|
|
838
|
+
dist_GG_motif = distance.cdist(adata_all[genes, ].X,
|
|
839
|
+
adata_all[tf_gene, ].X,
|
|
840
|
+
metric=metric)
|
|
841
|
+
dist_GG_motif = pd.DataFrame(dist_GG_motif,
|
|
842
|
+
index=genes,
|
|
843
|
+
columns=[tf_gene])
|
|
844
|
+
rank_GG_motif = dist_GG_motif.rank(axis=0)
|
|
845
|
+
|
|
846
|
+
# rank of the distances from peaks to tf_motif
|
|
847
|
+
dist_PM_motif = distance.cdist(
|
|
848
|
+
adata_all[peaks_in_genes, ].X,
|
|
849
|
+
adata_all[tf_motif, ].X,
|
|
850
|
+
metric=metric)
|
|
851
|
+
dist_PM_motif = pd.DataFrame(dist_PM_motif,
|
|
852
|
+
index=peaks_in_genes,
|
|
853
|
+
columns=[tf_motif])
|
|
854
|
+
rank_PM_motif = dist_PM_motif.rank(axis=0)
|
|
855
|
+
|
|
856
|
+
# rank of the distances from peaks to candidate genes
|
|
857
|
+
cand_genes = \
|
|
858
|
+
dist_GG_motif[tf_gene].nsmallest(n_genes).index.tolist()\
|
|
859
|
+
+ dist_GM_motif[tf_motif].nsmallest(n_genes).index.tolist()
|
|
860
|
+
print(f'#candinate genes is {len(cand_genes)}')
|
|
861
|
+
print('removing duplicate genes ...')
|
|
862
|
+
print('removing genes that do not contain TF motif ...')
|
|
863
|
+
cand_genes = list(set(cand_genes).intersection(set(motif_genes)))
|
|
864
|
+
print(f'#candinate genes is {len(cand_genes)}')
|
|
865
|
+
dist_PG_motif = distance.cdist(
|
|
866
|
+
adata_all[peaks_in_genes, ].X,
|
|
867
|
+
adata_all[cand_genes, ].X,
|
|
868
|
+
metric=metric
|
|
869
|
+
)
|
|
870
|
+
dist_PG_motif = pd.DataFrame(dist_PG_motif,
|
|
871
|
+
index=peaks_in_genes,
|
|
872
|
+
columns=cand_genes)
|
|
873
|
+
rank_PG_motif = dist_PG_motif.rank(axis=0)
|
|
874
|
+
|
|
875
|
+
df_tf_targets = pd.DataFrame(index=cand_genes)
|
|
876
|
+
df_tf_targets['average_rank'] = -1
|
|
877
|
+
df_tf_targets['has_motif'] = 'no'
|
|
878
|
+
df_tf_targets['rank_gene_to_TFmotif'] = -1
|
|
879
|
+
df_tf_targets['rank_gene_to_TFgene'] = -1
|
|
880
|
+
df_tf_targets['rank_peak_to_TFmotif'] = -1
|
|
881
|
+
df_tf_targets['rank_peak2_to_TFmotif'] = -1
|
|
882
|
+
df_tf_targets['rank_peak_to_gene'] = -1
|
|
883
|
+
df_tf_targets['rank_peak2_to_gene'] = -1
|
|
884
|
+
for i, g in enumerate(cand_genes):
|
|
885
|
+
g_peaks = list(set(overlap_PG.loc[[g]]['peak']))
|
|
886
|
+
g_motif_peaks = list(set(g_peaks).intersection(motif_peaks))
|
|
887
|
+
if len(g_motif_peaks) > 0:
|
|
888
|
+
df_tf_targets.loc[g, 'has_motif'] = 'yes'
|
|
889
|
+
df_tf_targets.loc[g, 'rank_gene_to_TFmotif'] = \
|
|
890
|
+
rank_GM_motif[tf_motif][g]
|
|
891
|
+
df_tf_targets.loc[g, 'rank_gene_to_TFgene'] = \
|
|
892
|
+
rank_GG_motif[tf_gene][g]
|
|
893
|
+
df_tf_targets.loc[g, 'rank_peak_to_TFmotif'] = \
|
|
894
|
+
rank_PM_motif.loc[g_peaks, tf_motif].min()
|
|
895
|
+
df_tf_targets.loc[g, 'rank_peak2_to_TFmotif'] = \
|
|
896
|
+
rank_PM_motif.loc[g_motif_peaks, tf_motif].min()
|
|
897
|
+
df_tf_targets.loc[g, 'rank_peak_to_gene'] = \
|
|
898
|
+
rank_PG_motif.loc[g_peaks, g].min()
|
|
899
|
+
df_tf_targets.loc[g, 'rank_peak2_to_gene'] = \
|
|
900
|
+
rank_PG_motif.loc[g_peaks, g].min()
|
|
901
|
+
if i % int(len(cand_genes)/5) == 0:
|
|
902
|
+
print(f'completed: {i/len(cand_genes):.1%}')
|
|
903
|
+
df_tf_targets['average_rank'] = \
|
|
904
|
+
df_tf_targets[['rank_gene_to_TFmotif',
|
|
905
|
+
'rank_gene_to_TFgene']].mean(axis=1)
|
|
906
|
+
if cutoff_peak is not None:
|
|
907
|
+
print('Pruning candidate genes based on nearby peaks ...')
|
|
908
|
+
df_tf_targets = df_tf_targets[
|
|
909
|
+
(df_tf_targets[[
|
|
910
|
+
'rank_peak_to_TFmotif',
|
|
911
|
+
'rank_peak_to_gene']]
|
|
912
|
+
< cutoff_peak).sum(axis=1) > 0]
|
|
913
|
+
if cutoff_gene is not None:
|
|
914
|
+
print('Pruning candidate genes based on average rank ...')
|
|
915
|
+
df_tf_targets = df_tf_targets[
|
|
916
|
+
df_tf_targets['average_rank'] < cutoff_gene]
|
|
917
|
+
dict_tf_targets[tf_motif] = \
|
|
918
|
+
df_tf_targets.sort_values(by='average_rank').copy()
|
|
919
|
+
return dict_tf_targets
|