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,346 @@
1
+ """Predict gene scores based on chromatin accessibility"""
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ import anndata as ad
6
+ import io
7
+ import pybedtools
8
+ from scipy.sparse import (
9
+ coo_matrix,
10
+ csr_matrix
11
+ )
12
+ import pkgutil
13
+
14
+ from ._utils import _uniquify
15
+
16
+
17
+ class GeneScores:
18
+ """A class used to represent gene scores
19
+
20
+ Attributes
21
+ ----------
22
+
23
+ Methods
24
+ -------
25
+
26
+ """
27
+ def __init__(self,
28
+ adata,
29
+ genome,
30
+ gene_anno=None,
31
+ tss_upstream=1e5,
32
+ tss_downsteam=1e5,
33
+ gb_upstream=5000,
34
+ cutoff_weight=1,
35
+ use_top_pcs=True,
36
+ use_precomputed=True,
37
+ use_gene_weigt=True,
38
+ min_w=1,
39
+ max_w=5):
40
+ """
41
+ Parameters
42
+ ----------
43
+ adata: `Anndata`
44
+ Input anndata
45
+ genome : `str`
46
+ The genome name
47
+ """
48
+ self.adata = adata
49
+ self.genome = genome
50
+ self.gene_anno = gene_anno
51
+ self.tss_upstream = tss_upstream
52
+ self.tss_downsteam = tss_downsteam
53
+ self.gb_upstream = gb_upstream
54
+ self.cutoff_weight = cutoff_weight
55
+ self.use_top_pcs = use_top_pcs
56
+ self.use_precomputed = use_precomputed
57
+ self.use_gene_weigt = use_gene_weigt
58
+ self.min_w = min_w
59
+ self.max_w = max_w
60
+
61
+ def _read_gene_anno(self):
62
+ """Read in gene annotation
63
+
64
+ Parameters
65
+ ----------
66
+
67
+ Returns
68
+ -------
69
+
70
+ """
71
+ assert (self.genome in ['hg19', 'hg38', 'mm9', 'mm10']),\
72
+ "`genome` must be one of ['hg19','hg38','mm9','mm10']"
73
+
74
+ bin_str = pkgutil.get_data('hge',
75
+ f'data/gene_anno/{self.genome}_genes.bed')
76
+ gene_anno = pd.read_csv(io.BytesIO(bin_str),
77
+ encoding='utf8',
78
+ sep='\t',
79
+ header=None,
80
+ names=['chr', 'start', 'end',
81
+ 'symbol', 'strand'])
82
+ self.gene_anno = gene_anno
83
+ return self.gene_anno
84
+
85
+ def _extend_tss(self, pbt_gene):
86
+ """Extend transcription start site in both directions
87
+
88
+ Parameters
89
+ ----------
90
+
91
+ Returns
92
+ -------
93
+
94
+ """
95
+ ext_tss = pbt_gene
96
+ if ext_tss['strand'] == '+':
97
+ ext_tss.start = max(0, ext_tss.start - self.tss_upstream)
98
+ ext_tss.end = max(ext_tss.end, ext_tss.start + self.tss_downsteam)
99
+ else:
100
+ ext_tss.start = max(0, min(ext_tss.start,
101
+ ext_tss.end - self.tss_downsteam))
102
+ ext_tss.end = ext_tss.end + self.tss_upstream
103
+ return ext_tss
104
+
105
+ def _extend_genebody(self, pbt_gene):
106
+ """Extend gene body upstream
107
+
108
+ Parameters
109
+ ----------
110
+
111
+ Returns
112
+ -------
113
+
114
+ """
115
+ ext_gb = pbt_gene
116
+ if ext_gb['strand'] == '+':
117
+ ext_gb.start = max(0, ext_gb.start - self.gb_upstream)
118
+ else:
119
+ ext_gb.end = ext_gb.end + self.gb_upstream
120
+ return ext_gb
121
+
122
+ def _weight_genes(self):
123
+ """Weight genes
124
+
125
+ Parameters
126
+ ----------
127
+
128
+ Returns
129
+ -------
130
+
131
+ """
132
+ gene_anno = self.gene_anno
133
+ gene_size = gene_anno['end'] - gene_anno['start']
134
+ w = 1/gene_size
135
+ w_scaled = (self.max_w-self.min_w) * (w-min(w)) / (max(w)-min(w)) \
136
+ + self.min_w
137
+ return w_scaled
138
+
139
+ def cal_gene_scores(self):
140
+ """Calculate gene scores
141
+
142
+ Parameters
143
+ ----------
144
+
145
+ Returns
146
+ -------
147
+
148
+ """
149
+ adata = self.adata
150
+ if self.gene_anno is None:
151
+ gene_ann = self._read_gene_anno()
152
+ else:
153
+ gene_ann = self.gene_anno
154
+
155
+ df_gene_ann = gene_ann.copy()
156
+ df_gene_ann.index = _uniquify(df_gene_ann['symbol'].values)
157
+ if self.use_top_pcs:
158
+ mask_p = adata.var['top_pcs']
159
+ else:
160
+ mask_p = pd.Series(True, index=adata.var_names)
161
+ df_peaks = adata.var[mask_p][['chr', 'start', 'end']].copy()
162
+
163
+ if 'gene_scores' not in adata.uns_keys():
164
+ print('Gene scores are being calculated for the first time')
165
+ print('`use_precomputed` has been ignored')
166
+ self.use_precomputed = False
167
+
168
+ if self.use_precomputed:
169
+ print('Using precomputed overlap')
170
+ df_overlap_updated = adata.uns['gene_scores']['overlap'].copy()
171
+ else:
172
+ # add the fifth column
173
+ # so that pybedtool can recognize the sixth column as the strand
174
+ df_gene_ann_for_pbt = df_gene_ann.copy()
175
+ df_gene_ann_for_pbt['score'] = 0
176
+ df_gene_ann_for_pbt = df_gene_ann_for_pbt[['chr', 'start', 'end',
177
+ 'symbol', 'score',
178
+ 'strand']]
179
+ df_gene_ann_for_pbt['id'] = range(df_gene_ann_for_pbt.shape[0])
180
+
181
+ df_peaks_for_pbt = df_peaks.copy()
182
+ df_peaks_for_pbt['id'] = range(df_peaks_for_pbt.shape[0])
183
+
184
+ pbt_gene_ann = pybedtools.BedTool.from_dataframe(
185
+ df_gene_ann_for_pbt
186
+ )
187
+ pbt_gene_ann_ext = pbt_gene_ann.each(self._extend_tss)
188
+ pbt_gene_gb_ext = pbt_gene_ann.each(self._extend_genebody)
189
+
190
+ pbt_peaks = pybedtools.BedTool.from_dataframe(df_peaks_for_pbt)
191
+
192
+ # peaks overlapping with extended TSS
193
+ pbt_overlap = pbt_peaks.intersect(pbt_gene_ann_ext,
194
+ wa=True,
195
+ wb=True)
196
+ df_overlap = pbt_overlap.to_dataframe(
197
+ names=[x+'_p' for x in df_peaks_for_pbt.columns]
198
+ + [x+'_g' for x in df_gene_ann_for_pbt.columns])
199
+ # peaks overlapping with gene body
200
+ pbt_overlap2 = pbt_peaks.intersect(pbt_gene_gb_ext,
201
+ wa=True,
202
+ wb=True)
203
+ df_overlap2 = pbt_overlap2.to_dataframe(
204
+ names=[x+'_p' for x in df_peaks_for_pbt.columns]
205
+ + [x+'_g' for x in df_gene_ann_for_pbt.columns])
206
+
207
+ # add distance and weight for each overlap
208
+ df_overlap_updated = df_overlap.copy()
209
+ df_overlap_updated['dist'] = 0
210
+
211
+ for i, x in enumerate(df_overlap['symbol_g'].unique()):
212
+ # peaks within the extended TSS
213
+ df_overlap_x = \
214
+ df_overlap[df_overlap['symbol_g'] == x].copy()
215
+ # peaks within the gene body
216
+ df_overlap2_x = \
217
+ df_overlap2[df_overlap2['symbol_g'] == x].copy()
218
+ # peaks that are not intersecting with the promoter
219
+ # and gene body of gene x
220
+ id_overlap = df_overlap_x.index[
221
+ ~np.isin(df_overlap_x['id_p'], df_overlap2_x['id_p'])]
222
+ mask_x = (df_gene_ann['symbol'] == x)
223
+ range_x = df_gene_ann[mask_x][['start', 'end']].values\
224
+ .flatten()
225
+ if df_overlap_x['strand_g'].iloc[0] == '+':
226
+ df_overlap_updated.loc[id_overlap, 'dist'] = pd.concat(
227
+ [abs(df_overlap_x.loc[id_overlap, 'start_p']
228
+ - (range_x[1])),
229
+ abs(df_overlap_x.loc[id_overlap, 'end_p']
230
+ - max(0, range_x[0]-self.gb_upstream))],
231
+ axis=1, sort=False).min(axis=1)
232
+ else:
233
+ df_overlap_updated.loc[id_overlap, 'dist'] = pd.concat(
234
+ [abs(df_overlap_x.loc[id_overlap, 'start_p']
235
+ - (range_x[1]+self.gb_upstream)),
236
+ abs(df_overlap_x.loc[id_overlap, 'end_p']
237
+ - (range_x[0]))],
238
+ axis=1, sort=False).min(axis=1)
239
+
240
+ n_batch = int(df_gene_ann_for_pbt.shape[0]/5)
241
+ if i % n_batch == 0:
242
+ print(f'Processing: {i/df_gene_ann_for_pbt.shape[0]:.1%}')
243
+ df_overlap_updated['dist'] = df_overlap_updated['dist']\
244
+ .astype(float)
245
+
246
+ adata.uns['gene_scores'] = dict()
247
+ adata.uns['gene_scores']['overlap'] = df_overlap_updated.copy()
248
+
249
+ df_overlap_updated['weight'] = np.exp(
250
+ -(df_overlap_updated['dist'].values/self.gb_upstream))
251
+ mask_w = (df_overlap_updated['weight'] < self.cutoff_weight)
252
+ df_overlap_updated.loc[mask_w, 'weight'] = 0
253
+ # construct genes-by-peaks matrix
254
+ mat_GP = csr_matrix(coo_matrix((df_overlap_updated['weight'],
255
+ (df_overlap_updated['id_g'],
256
+ df_overlap_updated['id_p'])),
257
+ shape=(df_gene_ann.shape[0],
258
+ df_peaks.shape[0])))
259
+ # adata_GP = ad.AnnData(X=csr_matrix(mat_GP),
260
+ # obs=df_gene_ann,
261
+ # var=df_peaks)
262
+ # adata_GP.layers['weight'] = adata_GP.X.copy()
263
+ if self.use_gene_weigt:
264
+ gene_weights = self._weight_genes()
265
+ gene_scores = adata[:, mask_p].X * \
266
+ (mat_GP.T.multiply(gene_weights))
267
+ else:
268
+ gene_scores = adata[:, mask_p].X * mat_GP.T
269
+ adata_CG_atac = ad.AnnData(gene_scores,
270
+ obs=adata.obs.copy(),
271
+ var=df_gene_ann.copy())
272
+ return adata_CG_atac
273
+
274
+
275
+ def gene_scores(adata,
276
+ genome,
277
+ gene_anno=None,
278
+ tss_upstream=1e5,
279
+ tss_downsteam=1e5,
280
+ gb_upstream=5000,
281
+ cutoff_weight=1,
282
+ use_top_pcs=True,
283
+ use_precomputed=True,
284
+ use_gene_weigt=True,
285
+ min_w=1,
286
+ max_w=5):
287
+ """Calculate gene scores
288
+
289
+ Parameters
290
+ ----------
291
+ adata : AnnData
292
+ Annotated data matrix.
293
+ genome : `str`
294
+ Reference genome. Choose from {'hg19', 'hg38', 'mm9', 'mm10'}
295
+ gene_anno : `pandas.DataFrame`, optional (default: None)
296
+ Dataframe of gene annotation.
297
+ If None, built-in gene annotation will be used depending on `genome`;
298
+ If provided, custom gene annotation will be used instead.
299
+ tss_upstream : `int`, optional (default: 1e5)
300
+ The number of base pairs upstream of TSS
301
+ tss_downsteam : `int`, optional (default: 1e5)
302
+ The number of base pairs downstream of TSS
303
+ gb_upstream : `int`, optional (default: 5000)
304
+ The number of base pairs upstream by which gene body is extended.
305
+ Peaks within the extended gene body are given the weight of 1.
306
+ cutoff_weight : `float`, optional (default: 1)
307
+ Weight cutoff for peaks
308
+ use_top_pcs : `bool`, optional (default: True)
309
+ If True, only peaks associated with top PCs will be used
310
+ use_precomputed : `bool`, optional (default: True)
311
+ If True, overlap bewteen peaks and genes
312
+ (stored in `adata.uns['gene_scores']['overlap']`) will be imported
313
+ use_gene_weigt : `bool`, optional (default: True)
314
+ If True, for each gene, the number of peaks assigned to it
315
+ will be rescaled based on gene size
316
+ min_w : `int`, optional (default: 1)
317
+ The minimum weight for each gene.
318
+ Only valid if `use_gene_weigt` is True
319
+ max_w : `int`, optional (default: 5)
320
+ The maximum weight for each gene.
321
+ Only valid if `use_gene_weigt` is True
322
+
323
+ Returns
324
+ -------
325
+ adata_new: AnnData
326
+ Annotated data matrix.
327
+ Stores #cells x #genes gene score matrix
328
+
329
+ updates `adata` with the following fields.
330
+ overlap: `pandas.DataFrame`, (`adata.uns['gene_scores']['overlap']`)
331
+ Dataframe of overlap between peaks and genes
332
+ """
333
+ GS = GeneScores(adata,
334
+ genome,
335
+ gene_anno=gene_anno,
336
+ tss_upstream=tss_upstream,
337
+ tss_downsteam=tss_downsteam,
338
+ gb_upstream=gb_upstream,
339
+ cutoff_weight=cutoff_weight,
340
+ use_top_pcs=use_top_pcs,
341
+ use_precomputed=use_precomputed,
342
+ use_gene_weigt=use_gene_weigt,
343
+ min_w=min_w,
344
+ max_w=max_w)
345
+ adata_CG_atac = GS.cal_gene_scores()
346
+ return adata_CG_atac
@@ -0,0 +1,71 @@
1
+ """General-purpose tools"""
2
+
3
+ import numpy as np
4
+ from sklearn.cluster import KMeans
5
+
6
+
7
+ def discretize(adata,
8
+ layer=None,
9
+ n_bins=5,
10
+ max_bins=100):
11
+ """Discretize continous values
12
+
13
+ Parameters
14
+ ----------
15
+ adata: AnnData
16
+ Annotated data matrix.
17
+ layer: `str`, optional (default: None)
18
+ The layer used to perform discretization
19
+ n_bins: `int`, optional (default: 5)
20
+ The number of bins to produce.
21
+ It must be smaller than `max_bins`.
22
+ max_bins: `int`, optional (default: 100)
23
+ The number of bins used in the initial approximation.
24
+ i.e. the number of bins to cluster.
25
+
26
+ Returns
27
+ -------
28
+ updates `adata` with the following fields
29
+
30
+ `.layer['hge']` : `array_like`
31
+ The matrix of discretized values to build HGE(MultiChat) graph.
32
+ `.uns['disc']` : `dict`
33
+ `bin_edges`: The edges of each bin.
34
+ `bin_count`: The number of values in each bin.
35
+ `hist_edges`: The edges of each bin \
36
+ in the initial approximation.
37
+ `hist_count`: The number of values in each bin \
38
+ for the initial approximation.
39
+ """
40
+ if layer is None:
41
+ X = adata.X
42
+ else:
43
+ X = adata.layers[layer]
44
+ nonzero_cont = X.data
45
+
46
+ hist_count, hist_edges = np.histogram(
47
+ nonzero_cont,
48
+ bins=max_bins,
49
+ density=False)
50
+ hist_centroids = (hist_edges[0:-1] + hist_edges[1:])/2
51
+
52
+ kmeans = KMeans(n_clusters=n_bins, random_state=2021).fit(
53
+ hist_centroids.reshape(-1, 1),
54
+ sample_weight=hist_count)
55
+ cluster_centers = np.sort(kmeans.cluster_centers_.flatten())
56
+
57
+ padding = (hist_edges[-1] - hist_edges[0])/(max_bins*10)
58
+ bin_edges = np.array(
59
+ [hist_edges[0]-padding] +
60
+ list((cluster_centers[0:-1] + cluster_centers[1:])/2) +
61
+ [hist_edges[-1]+padding])
62
+ nonzero_disc = np.digitize(nonzero_cont, bin_edges).reshape(-1,)
63
+ bin_count = np.unique(nonzero_disc, return_counts=True)[1]
64
+
65
+ adata.layers['hge'] = X.copy()
66
+ adata.layers['hge'].data = nonzero_disc
67
+ adata.uns['disc'] = dict()
68
+ adata.uns['disc']['bin_edges'] = bin_edges
69
+ adata.uns['disc']['bin_count'] = bin_count
70
+ adata.uns['disc']['hist_edges'] = hist_edges
71
+ adata.uns['disc']['hist_count'] = hist_count
@@ -0,0 +1,197 @@
1
+ """Integration across experimental conditions or single cell modalities"""
2
+
3
+ import numpy as np
4
+ import anndata as ad
5
+ # from sklearn.metrics.pairwise import pairwise_distances
6
+ from sklearn.utils.extmath import randomized_svd
7
+ from scipy.sparse import csr_matrix, find
8
+
9
+ from ._utils import _knn
10
+
11
+
12
+ def infer_edges(adata_ref,
13
+ adata_query,
14
+ feature='highly_variable',
15
+ n_components=20,
16
+ random_state=42,
17
+ layer=None,
18
+ k=20,
19
+ metric='euclidean',
20
+ leaf_size=40,
21
+ **kwargs):
22
+ """Infer edges between reference and query observations
23
+
24
+ Parameters
25
+ ----------
26
+ adata_ref: `AnnData`
27
+ Annotated reference data matrix.
28
+ adata_query: `AnnData`
29
+ Annotated query data matrix.
30
+ feature: `str`, optional (default: None)
31
+ Feature used for edges inference.
32
+ The data type of `.var[feature]` needs to be `bool`
33
+ n_components: `int`, optional (default: 20)
34
+ The number of components used in `randomized_svd`
35
+ for comparing reference and query observations
36
+ random_state: `int`, optional (default: 42)
37
+ The seed used for truncated randomized SVD
38
+ n_top_edges: `int`, optional (default: None)
39
+ The number of edges to keep
40
+ If specified, `percentile` will be ignored
41
+ percentile: `float`, optional (default: 0.01)
42
+ The percentile of edges to keep
43
+ k: `int`, optional (default: 5)
44
+ The number of nearest neighbors to consider within each dataset
45
+ metric: `str`, optional (default: 'euclidean')
46
+ The metric to use when calculating distance between
47
+ reference and query observations
48
+ layer: `str`, optional (default: None)
49
+ The layer used to perform edge inference
50
+ If None, `.X` will be used.
51
+ kwargs:
52
+ Other keyword arguments are passed down to `randomized_svd()`
53
+
54
+ Returns
55
+ -------
56
+ adata_ref_query: `AnnData`
57
+ Annotated relation matrix betwewn reference and query observations
58
+ Store reference entity as observations and query entity as variables
59
+ """
60
+
61
+ mask_ref = adata_ref.var[feature]
62
+ feature_ref = adata_ref.var_names[mask_ref]
63
+ feature_query = adata_query.var_names
64
+ feature_shared = list(set(feature_ref).intersection(set(feature_query)))
65
+ print(f'#shared features: {len(feature_shared)}')
66
+ if layer is None:
67
+ X_ref = adata_ref[:, feature_shared].X
68
+ X_query = adata_query[:, feature_shared].X
69
+ else:
70
+ X_ref = adata_ref[:, feature_shared].layers[layer]
71
+ X_query = adata_query[:, feature_shared].layers[layer]
72
+
73
+ if any(X_ref.sum(axis=1) == 0) or any(X_query.sum(axis=1) == 0):
74
+ raise ValueError(
75
+ f'Some nodes contain zero expressed {feature} features.\n'
76
+ f'Please try to include more {feature} features.')
77
+
78
+ print('Performing randomized SVD ...')
79
+ mat = X_ref * X_query.T
80
+ U, Sigma, VT = randomized_svd(mat,
81
+ n_components=n_components,
82
+ random_state=random_state,
83
+ **kwargs)
84
+ svd_data = np.vstack((U, VT.T))
85
+ X_svd_ref = svd_data[:U.shape[0], :]
86
+ X_svd_query = svd_data[-VT.shape[1]:, :]
87
+ X_svd_ref = X_svd_ref / (X_svd_ref**2).sum(-1, keepdims=True)**0.5
88
+ X_svd_query = X_svd_query / (X_svd_query**2).sum(-1, keepdims=True)**0.5
89
+
90
+ # print('Searching for neighbors within each dataset ...')
91
+ # knn_conn_ref, knn_dist_ref = _knn(
92
+ # X_ref=X_svd_ref,
93
+ # k=k,
94
+ # leaf_size=leaf_size,
95
+ # metric=metric)
96
+ # knn_conn_query, knn_dist_query = _knn(
97
+ # X_ref=X_svd_query,
98
+ # k=k,
99
+ # leaf_size=leaf_size,
100
+ # metric=metric)
101
+
102
+ print('Searching for mutual nearest neighbors ...')
103
+ knn_conn_ref_query, knn_dist_ref_query = _knn(
104
+ X_ref=X_svd_ref,
105
+ X_query=X_svd_query,
106
+ k=k,
107
+ leaf_size=leaf_size,
108
+ metric=metric)
109
+ knn_conn_query_ref, knn_dist_query_ref = _knn(
110
+ X_ref=X_svd_query,
111
+ X_query=X_svd_ref,
112
+ k=k,
113
+ leaf_size=leaf_size,
114
+ metric=metric)
115
+
116
+ sum_conn_ref_query = knn_conn_ref_query + knn_conn_query_ref.T
117
+ id_x, id_y, values = find(sum_conn_ref_query > 1)
118
+ print(f'{len(id_x)} edges are selected')
119
+ conn_ref_query = csr_matrix(
120
+ (values*1, (id_x, id_y)),
121
+ shape=(knn_conn_ref_query.shape))
122
+ dist_ref_query = csr_matrix(
123
+ (knn_dist_ref_query[id_x, id_y].A.flatten(), (id_x, id_y)),
124
+ shape=(knn_conn_ref_query.shape))
125
+ # it's easier to distinguish zeros (no connection vs zero distance)
126
+ # using similarity scores
127
+ sim_ref_query = csr_matrix(
128
+ (1/(dist_ref_query.data+1), dist_ref_query.nonzero()),
129
+ shape=(dist_ref_query.shape)) # similarity scores
130
+
131
+ # print('Computing similarity scores ...')
132
+ # dist_ref_query = pairwise_distances(X_svd_ref,
133
+ # X_svd_query,
134
+ # metric=metric)
135
+ # sim_ref_query = 1/(1+dist_ref_query)
136
+ # # remove low similarity entries to save memory
137
+ # sim_ref_query = np.where(
138
+ # sim_ref_query < np.percentile(sim_ref_query, pct_keep*100),
139
+ # 0, sim_ref_query)
140
+ # sim_ref_query = csr_matrix(sim_ref_query)
141
+
142
+ adata_ref_query = ad.AnnData(X=sim_ref_query,
143
+ obs=adata_ref.obs,
144
+ var=adata_query.obs)
145
+ adata_ref_query.layers['hge'] = conn_ref_query
146
+ adata_ref_query.obsm['svd'] = X_svd_ref
147
+ # adata_ref_query.obsp['conn'] = knn_conn_ref
148
+ # adata_ref_query.obsp['dist'] = knn_dist_ref
149
+ adata_ref_query.varm['svd'] = X_svd_query
150
+ # adata_ref_query.varp['conn'] = knn_conn_query
151
+ # adata_ref_query.varp['dist'] = knn_dist_query
152
+ return adata_ref_query
153
+
154
+
155
+ def trim_edges(adata_ref_query,
156
+ cutoff=None,
157
+ n_edges=None):
158
+ """Trim edges based on the similarity scores
159
+
160
+ Parameters
161
+ ----------
162
+ adata_ref_query: `AnnData`
163
+ Annotated relation matrix betwewn reference and query observations.
164
+ n_edges: `int`, optional (default: None)
165
+ The number of edges to keep
166
+ If specified, `percentile` will be ignored
167
+ cutoff: `float`, optional (default: None)
168
+ The distance cutoff.
169
+ If None, it will be decided by `n_top_edges`
170
+ If specified, `n_top_edges` will be ignored
171
+
172
+ Returns
173
+ -------
174
+ updates `adata_ref_query` with the following field.
175
+ `.layers['hge']` : `array_like`
176
+ relation matrix betwewn reference and query observations
177
+ """
178
+ sim_ref_query = adata_ref_query.X
179
+ if cutoff is None:
180
+ if n_edges is None:
181
+ raise ValueError('"cutoff" or "n_edges" has to be specified')
182
+ else:
183
+ cutoff = \
184
+ np.partition(sim_ref_query.data,
185
+ (sim_ref_query.size-n_edges))[
186
+ sim_ref_query.size-n_edges]
187
+ # cutoff = \
188
+ # np.partition(sim_ref_query.flatten(),
189
+ # (len(sim_ref_query.flatten())-n_edges))[
190
+ # len(sim_ref_query.flatten())-n_edges]
191
+ id_x, id_y, values = find(sim_ref_query > cutoff)
192
+
193
+ print(f'{len(id_x)} edges are selected')
194
+ conn_ref_query = csr_matrix(
195
+ (values*1, (id_x, id_y)),
196
+ shape=(sim_ref_query.shape))
197
+ adata_ref_query.layers['hge'] = conn_ref_query