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,742 @@
|
|
|
1
|
+
"""post-training plotting functions"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import json
|
|
7
|
+
import matplotlib as mpl
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
import seaborn as sns
|
|
10
|
+
from matplotlib.collections import LineCollection
|
|
11
|
+
from adjustText import adjust_text
|
|
12
|
+
from pandas.api.types import (
|
|
13
|
+
is_numeric_dtype
|
|
14
|
+
)
|
|
15
|
+
from scipy.stats import rankdata
|
|
16
|
+
|
|
17
|
+
from ._utils import (
|
|
18
|
+
get_colors,
|
|
19
|
+
generate_palette
|
|
20
|
+
)
|
|
21
|
+
from .._settings import settings
|
|
22
|
+
from ._plot import _scatterplot2d
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def pbg_metrics(metrics=['mrr'],
|
|
26
|
+
path_emb=None,
|
|
27
|
+
fig_size=(5, 3),
|
|
28
|
+
fig_ncol=1,
|
|
29
|
+
save_fig=None,
|
|
30
|
+
fig_path=None,
|
|
31
|
+
fig_name='pbg_metrics.pdf',
|
|
32
|
+
pad=1.08,
|
|
33
|
+
w_pad=None,
|
|
34
|
+
h_pad=None,
|
|
35
|
+
**kwargs):
|
|
36
|
+
"""Plot PBG training metrics
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
metrics: `list`, optional (default: ['mrr])
|
|
41
|
+
Evalulation metrics for PBG training. Possible metrics:
|
|
42
|
+
|
|
43
|
+
- 'pos_rank' : the average of the ranks of all positives
|
|
44
|
+
(lower is better, best is 1).
|
|
45
|
+
- 'mrr' : the average of the reciprocal of the ranks of all positives
|
|
46
|
+
(higher is better, best is 1).
|
|
47
|
+
- 'r1' : the fraction of positives that rank better than
|
|
48
|
+
all their negatives, i.e., have a rank of 1
|
|
49
|
+
(higher is better, best is 1).
|
|
50
|
+
- 'r10' : the fraction of positives that rank in the top 10
|
|
51
|
+
among their negatives
|
|
52
|
+
(higher is better, best is 1).
|
|
53
|
+
- 'r50' : the fraction of positives that rank in the top 50
|
|
54
|
+
among their negatives
|
|
55
|
+
(higher is better, best is 1).
|
|
56
|
+
- 'auc' : Area Under the Curve (AUC)
|
|
57
|
+
path_emb: `str`, optional (default: None)
|
|
58
|
+
Path to directory for pbg embedding model.
|
|
59
|
+
If None, .settings.pbg_params['checkpoint_path'] will be used.
|
|
60
|
+
pad: `float`, optional (default: 1.08)
|
|
61
|
+
Padding between the figure edge and the edges of subplots,
|
|
62
|
+
as a fraction of the font size.
|
|
63
|
+
h_pad, w_pad: `float`, optional (default: None)
|
|
64
|
+
Padding (height/width) between edges of adjacent subplots,
|
|
65
|
+
as a fraction of the font size. Defaults to pad.
|
|
66
|
+
fig_size: `tuple`, optional (default: (5, 3))
|
|
67
|
+
figure size.
|
|
68
|
+
fig_ncol: `int`, optional (default: 1)
|
|
69
|
+
the number of columns of the figure panel
|
|
70
|
+
save_fig: `bool`, optional (default: False)
|
|
71
|
+
if True,save the figure.
|
|
72
|
+
fig_path: `str`, optional (default: None)
|
|
73
|
+
If save_fig is True, specify figure path.
|
|
74
|
+
fig_name: `str`, optional (default: 'plot_umap.pdf')
|
|
75
|
+
if save_fig is True, specify figure name.
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
None
|
|
79
|
+
"""
|
|
80
|
+
if save_fig is None:
|
|
81
|
+
save_fig = settings.save_fig
|
|
82
|
+
if fig_path is None:
|
|
83
|
+
fig_path = os.path.join(settings.workdir, 'figures')
|
|
84
|
+
|
|
85
|
+
assert isinstance(metrics, list), "`metrics` must be list"
|
|
86
|
+
for x in metrics:
|
|
87
|
+
if x not in ['pos_rank', 'mrr', 'r1',
|
|
88
|
+
'r10', 'r50', 'auc']:
|
|
89
|
+
raise ValueError(f'unrecognized metric {x}')
|
|
90
|
+
pbg_params = settings.pbg_params
|
|
91
|
+
if path_emb is None:
|
|
92
|
+
path_emb = pbg_params['checkpoint_path']
|
|
93
|
+
training_loss = []
|
|
94
|
+
eval_stats_before = dict()
|
|
95
|
+
with open(os.path.join(path_emb, 'training_stats.json'), 'r') as f:
|
|
96
|
+
for line in f:
|
|
97
|
+
line_json = json.loads(line)
|
|
98
|
+
if 'stats' in line_json.keys():
|
|
99
|
+
training_loss.append(line_json['stats']['metrics']['loss'])
|
|
100
|
+
line_stats_before = line_json['eval_stats_before']['metrics']
|
|
101
|
+
for x in line_stats_before.keys():
|
|
102
|
+
if x not in eval_stats_before.keys():
|
|
103
|
+
eval_stats_before[x] = [line_stats_before[x]]
|
|
104
|
+
else:
|
|
105
|
+
eval_stats_before[x].append(line_stats_before[x])
|
|
106
|
+
df_metrics = pd.DataFrame(index=range(pbg_params['num_epochs']))
|
|
107
|
+
df_metrics['epoch'] = range(pbg_params['num_epochs'])
|
|
108
|
+
df_metrics['training_loss'] = training_loss
|
|
109
|
+
df_metrics['validation_loss'] = eval_stats_before['loss']
|
|
110
|
+
for x in metrics:
|
|
111
|
+
df_metrics[x] = eval_stats_before[x]
|
|
112
|
+
|
|
113
|
+
fig_nrow = int(np.ceil((df_metrics.shape[1]-1)/fig_ncol))
|
|
114
|
+
fig = plt.figure(figsize=(fig_size[0]*fig_ncol*1.05,
|
|
115
|
+
fig_size[1]*fig_nrow))
|
|
116
|
+
dict_palette = generate_palette(df_metrics.columns[1:].values)
|
|
117
|
+
for i, metric in enumerate(df_metrics.columns[1:]):
|
|
118
|
+
ax_i = fig.add_subplot(fig_nrow, fig_ncol, i+1)
|
|
119
|
+
ax_i.scatter(df_metrics['epoch'],
|
|
120
|
+
df_metrics[metric],
|
|
121
|
+
c=dict_palette[metric],
|
|
122
|
+
**kwargs)
|
|
123
|
+
ax_i.set_title(metric)
|
|
124
|
+
ax_i.set_xlabel('epoch')
|
|
125
|
+
ax_i.set_ylabel(metric)
|
|
126
|
+
plt.tight_layout(pad=pad, h_pad=h_pad, w_pad=w_pad)
|
|
127
|
+
if save_fig:
|
|
128
|
+
if not os.path.exists(fig_path):
|
|
129
|
+
os.makedirs(fig_path)
|
|
130
|
+
plt.savefig(os.path.join(fig_path, fig_name),
|
|
131
|
+
pad_inches=1,
|
|
132
|
+
bbox_inches='tight')
|
|
133
|
+
plt.close(fig)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def entity_metrics(adata_cmp,
|
|
137
|
+
x,
|
|
138
|
+
y,
|
|
139
|
+
show_texts=True,
|
|
140
|
+
show_cutoff=False,
|
|
141
|
+
show_contour=True,
|
|
142
|
+
levels=4,
|
|
143
|
+
thresh=0.05,
|
|
144
|
+
cutoff_x=0,
|
|
145
|
+
cutoff_y=0,
|
|
146
|
+
cutoff_fdr=None,
|
|
147
|
+
cutoff_p=None,
|
|
148
|
+
color_by_fdr=None,
|
|
149
|
+
n_texts=10,
|
|
150
|
+
size=8,
|
|
151
|
+
texts=None,
|
|
152
|
+
text_size=10,
|
|
153
|
+
text_expand=(1.05, 1.2),
|
|
154
|
+
fig_size=None,
|
|
155
|
+
save_fig=None,
|
|
156
|
+
fig_path=None,
|
|
157
|
+
fig_name='entity_metrics.pdf',
|
|
158
|
+
pad=1.08,
|
|
159
|
+
w_pad=None,
|
|
160
|
+
h_pad=None,
|
|
161
|
+
**kwargs):
|
|
162
|
+
"""Plot entity metrics
|
|
163
|
+
|
|
164
|
+
Parameters
|
|
165
|
+
----------
|
|
166
|
+
adata_cmp: `AnnData`
|
|
167
|
+
Anndata object from `compare_entities`
|
|
168
|
+
x, y: `str`
|
|
169
|
+
Variables that specify positions on the x and y axes.
|
|
170
|
+
Possible values:
|
|
171
|
+
- max (The average maximum dot product of top-rank reference entities,
|
|
172
|
+
based on normalized dot product)
|
|
173
|
+
- std (standard deviation of reference entities,
|
|
174
|
+
based on dot product)
|
|
175
|
+
- gini (Gini coefficients of reference entities,
|
|
176
|
+
based on softmax probability)
|
|
177
|
+
- entropy (The entropy of reference entities,
|
|
178
|
+
based on softmax probability)
|
|
179
|
+
show_texts : `bool`, optional (default: True)
|
|
180
|
+
If True, text annotation will be shown.
|
|
181
|
+
show_cutoff : `bool`, optional (default: False)
|
|
182
|
+
If True, cutoff of `x` and `y` will be shown.
|
|
183
|
+
show_contour : `bool`, optional (default: True)
|
|
184
|
+
If True, the plot will overlaid with contours
|
|
185
|
+
texts: `list` optional (default: None)
|
|
186
|
+
Entity names to plot
|
|
187
|
+
text_size : `int`, optional (default: 10)
|
|
188
|
+
The text size
|
|
189
|
+
text_expand : `tuple`, optional (default: (1.05, 1.2))
|
|
190
|
+
Two multipliers (x, y) by which to expand the bounding box of texts
|
|
191
|
+
when repelling them from each other/points/other objects.
|
|
192
|
+
cutoff_x : `float`, optional (default: 0)
|
|
193
|
+
Cutoff of axis x
|
|
194
|
+
cutoff_y : `float`, optional (default: 0)
|
|
195
|
+
Cutoff of axis y
|
|
196
|
+
cutoff_fdr: `float`, optional (default: None)
|
|
197
|
+
FDR cutoff of each metric that will be displayed as horizontal and
|
|
198
|
+
vertical lines.
|
|
199
|
+
color_by_fdr: `str`, optional (default: None)
|
|
200
|
+
Metric whose FDR will be used to color the points.
|
|
201
|
+
levels: `int`, optional (default: 6)
|
|
202
|
+
Number of contour levels or values to draw contours at
|
|
203
|
+
thresh: `float`, optional ([0, 1], default: 0.05)
|
|
204
|
+
Lowest iso-proportion level at which to draw a contour line.
|
|
205
|
+
pad: `float`, optional (default: 1.08)
|
|
206
|
+
Padding between the figure edge and the edges of subplots,
|
|
207
|
+
as a fraction of the font size.
|
|
208
|
+
h_pad, w_pad: `float`, optional (default: None)
|
|
209
|
+
Padding (height/width) between edges of adjacent subplots,
|
|
210
|
+
as a fraction of the font size. Defaults to pad.
|
|
211
|
+
fig_size: `tuple`, optional (default: None)
|
|
212
|
+
figure size.
|
|
213
|
+
If None, `mpl.rcParams['figure.figsize']` will be used.
|
|
214
|
+
fig_ncol: `int`, optional (default: 1)
|
|
215
|
+
the number of columns of the figure panel
|
|
216
|
+
save_fig: `bool`, optional (default: False)
|
|
217
|
+
if True,save the figure.
|
|
218
|
+
fig_path: `str`, optional (default: None)
|
|
219
|
+
If save_fig is True, specify figure path.
|
|
220
|
+
fig_name: `str`, optional (default: 'plot_umap.pdf')
|
|
221
|
+
if save_fig is True, specify figure name.
|
|
222
|
+
|
|
223
|
+
Returns
|
|
224
|
+
-------
|
|
225
|
+
None
|
|
226
|
+
"""
|
|
227
|
+
if fig_size is None:
|
|
228
|
+
fig_size = mpl.rcParams['figure.figsize']
|
|
229
|
+
if save_fig is None:
|
|
230
|
+
save_fig = settings.save_fig
|
|
231
|
+
if fig_path is None:
|
|
232
|
+
fig_path = os.path.join(settings.workdir, 'figures')
|
|
233
|
+
|
|
234
|
+
assert (x in ['max', 'std', 'gini', 'entropy']), \
|
|
235
|
+
"x must be one of ['max','std','gini','entropy']"
|
|
236
|
+
assert (y in ['max', 'std', 'gini', 'entropy']), \
|
|
237
|
+
"y must be one of ['max','std','gini','entropy']"
|
|
238
|
+
try:
|
|
239
|
+
if not cutoff_fdr is None or (not cutoff_p is None):
|
|
240
|
+
assert f"{x}_fdr" in adata_cmp.var.columns
|
|
241
|
+
assert f"{y}_fdr" in adata_cmp.var.columns
|
|
242
|
+
except AssertionError:
|
|
243
|
+
print(f"{x}_fdr or {y}_fdr not calculated in adata_cmp.")
|
|
244
|
+
print("HGE(MultiChat) needs to be trained with null genes in order to calculate FDR for marker features.\nDid you train your model with null genes?")
|
|
245
|
+
print("If not, re-run your graph training from graph generation step with `get_marker_significance=True`.")
|
|
246
|
+
try:
|
|
247
|
+
if not color_by_fdr is None:
|
|
248
|
+
assert f"{color_by_fdr}_fdr" in adata_cmp.var.columns
|
|
249
|
+
except AssertionError:
|
|
250
|
+
print(f"{color_by_fdr}_fdr not calculated in adata_cmp.")
|
|
251
|
+
print("HGE(MultiChat) needs to be trained with null genes in order to calculate FDR for marker features.\nDid you train your model with null genes?")
|
|
252
|
+
print("If not, re-run your graph training from graph generation step with `get_marker_significance=True`.")
|
|
253
|
+
|
|
254
|
+
fig, ax = plt.subplots(figsize=fig_size)
|
|
255
|
+
if not color_by_fdr is None:
|
|
256
|
+
kwargs['c'] = -np.log10(adata_cmp.var[f"{color_by_fdr}_fdr"])
|
|
257
|
+
|
|
258
|
+
spt = ax.scatter(adata_cmp.var[x],
|
|
259
|
+
adata_cmp.var[y],
|
|
260
|
+
s=size,
|
|
261
|
+
**kwargs)
|
|
262
|
+
if not color_by_fdr is None:
|
|
263
|
+
fig.colorbar(spt, label=f'-log10(FDR, {color_by_fdr})')
|
|
264
|
+
if show_texts:
|
|
265
|
+
if texts is not None:
|
|
266
|
+
plt_texts = [plt.text(adata_cmp.var[x][t],
|
|
267
|
+
adata_cmp.var[y][t],
|
|
268
|
+
t,
|
|
269
|
+
fontdict={'family': 'serif',
|
|
270
|
+
'color': 'black',
|
|
271
|
+
'weight': 'normal',
|
|
272
|
+
'size': text_size})
|
|
273
|
+
for t in texts]
|
|
274
|
+
else:
|
|
275
|
+
if x == 'entropy':
|
|
276
|
+
ranks_x = rankdata(-adata_cmp.var[x])
|
|
277
|
+
else:
|
|
278
|
+
ranks_x = rankdata(adata_cmp.var[x])
|
|
279
|
+
if y == 'entropy':
|
|
280
|
+
ranks_y = rankdata(-adata_cmp.var[y])
|
|
281
|
+
else:
|
|
282
|
+
ranks_y = rankdata(adata_cmp.var[y])
|
|
283
|
+
ids = np.argsort(ranks_x + ranks_y)[::-1][:n_texts]
|
|
284
|
+
plt_texts = [plt.text(adata_cmp.var[x][i],
|
|
285
|
+
adata_cmp.var[y][i],
|
|
286
|
+
adata_cmp.var_names[i],
|
|
287
|
+
fontdict={'family': 'serif',
|
|
288
|
+
'color': 'black',
|
|
289
|
+
'weight': 'normal',
|
|
290
|
+
'size': text_size})
|
|
291
|
+
for i in ids]
|
|
292
|
+
adjust_text(plt_texts,
|
|
293
|
+
expand_text=text_expand,
|
|
294
|
+
expand_points=text_expand,
|
|
295
|
+
expand_objects=text_expand,
|
|
296
|
+
arrowprops=dict(arrowstyle='-', color='black'))
|
|
297
|
+
if show_cutoff:
|
|
298
|
+
def _get_fdr_thres(metric, cutoff, sig="fdr"):
|
|
299
|
+
metric_fdrs = adata_cmp.var[f"{metric}_{sig}"]
|
|
300
|
+
closest_idx = np.where((metric_fdrs-cutoff).abs() == (metric_fdrs-cutoff).abs().min())[0]
|
|
301
|
+
return(adata_cmp.var[metric][closest_idx][0])
|
|
302
|
+
if cutoff_fdr is None and cutoff_p is None:
|
|
303
|
+
ax.axvline(x=cutoff_x, linestyle='--', color='#CE3746')
|
|
304
|
+
ax.axhline(y=cutoff_y, linestyle='--', color='#CE3746')
|
|
305
|
+
elif not cutoff_fdr is None and cutoff_p is None:
|
|
306
|
+
ax.axvline(x=_get_fdr_thres(x, cutoff_fdr), linestyle='--', color='#CE3746')
|
|
307
|
+
ax.axhline(y=_get_fdr_thres(y, cutoff_fdr), linestyle='--', color='#CE3746')
|
|
308
|
+
elif cutoff_fdr is None and (not cutoff_p is None):
|
|
309
|
+
ax.axvline(x=_get_fdr_thres(x, cutoff_p, "p"), linestyle='--', color='#CE3746')
|
|
310
|
+
ax.axhline(y=_get_fdr_thres(y, cutoff_p, "p"), linestyle='--', color='#CE3746')
|
|
311
|
+
if show_contour:
|
|
312
|
+
sns.kdeplot(ax=ax,
|
|
313
|
+
data=adata_cmp.var,
|
|
314
|
+
x=x,
|
|
315
|
+
y=y,
|
|
316
|
+
alpha=0.7,
|
|
317
|
+
color='black',
|
|
318
|
+
levels=levels,
|
|
319
|
+
thresh=thresh)
|
|
320
|
+
ax.set_xlabel(x)
|
|
321
|
+
ax.set_ylabel(y)
|
|
322
|
+
ax.locator_params(axis='x', tight=True)
|
|
323
|
+
ax.locator_params(axis='y', tight=True)
|
|
324
|
+
fig.tight_layout(pad=pad, h_pad=h_pad, w_pad=w_pad)
|
|
325
|
+
if save_fig:
|
|
326
|
+
if not os.path.exists(fig_path):
|
|
327
|
+
os.makedirs(fig_path)
|
|
328
|
+
fig.savefig(os.path.join(fig_path, fig_name),
|
|
329
|
+
pad_inches=1,
|
|
330
|
+
bbox_inches='tight')
|
|
331
|
+
plt.close(fig)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def entity_barcode(adata_cmp,
|
|
335
|
+
entities,
|
|
336
|
+
anno_ref=None,
|
|
337
|
+
layer='softmax',
|
|
338
|
+
palette=None,
|
|
339
|
+
alpha=0.8,
|
|
340
|
+
linewidths=1,
|
|
341
|
+
show_cutoff=False,
|
|
342
|
+
cutoff=0.5,
|
|
343
|
+
min_rank=None,
|
|
344
|
+
max_rank=None,
|
|
345
|
+
fig_size=(6, 2),
|
|
346
|
+
fig_ncol=1,
|
|
347
|
+
save_fig=None,
|
|
348
|
+
fig_path=None,
|
|
349
|
+
fig_name='plot_barcode.pdf',
|
|
350
|
+
pad=1.08,
|
|
351
|
+
w_pad=None,
|
|
352
|
+
h_pad=None,
|
|
353
|
+
**kwargs
|
|
354
|
+
):
|
|
355
|
+
"""Plot query entity barcode
|
|
356
|
+
|
|
357
|
+
Parameters
|
|
358
|
+
----------
|
|
359
|
+
adata_cmp : `AnnData`
|
|
360
|
+
Anndata object from `compare_entities`
|
|
361
|
+
entities : `list`
|
|
362
|
+
Entity names to plot.
|
|
363
|
+
anno_ref : `str`
|
|
364
|
+
Annotation used for reference entity
|
|
365
|
+
layer : `str`, optional (default: 'softmax')
|
|
366
|
+
Layer to use make barcode plots
|
|
367
|
+
palette : `dict`, optional (default: None)
|
|
368
|
+
Color palette used for `anno_ref`
|
|
369
|
+
alpha : `float`, optional (default: 0.8)
|
|
370
|
+
0.0 transparent through 1.0 opaque
|
|
371
|
+
linewidths : `int`, optional (default: 1)
|
|
372
|
+
The width of each line.
|
|
373
|
+
show_cutoff : `bool`, optional (default: True)
|
|
374
|
+
If True, cutoff will be shown
|
|
375
|
+
cutoff : `float`, optional (default: 0.5)
|
|
376
|
+
Cutoff value for y axis
|
|
377
|
+
min_rank : `int`, optional (default: None)
|
|
378
|
+
Specify the minimum rank of observations to show.
|
|
379
|
+
If None, `min_rank` is set to 0.
|
|
380
|
+
max_rank : `int`, optional (default: None)
|
|
381
|
+
Specify the maximum rank of observations to show.
|
|
382
|
+
If None, `max_rank` is set to the number of observations.
|
|
383
|
+
fig_size: `tuple`, optional (default: (6,2))
|
|
384
|
+
figure size.
|
|
385
|
+
fig_ncol: `int`, optional (default: 1)
|
|
386
|
+
the number of columns of the figure panel
|
|
387
|
+
save_fig: `bool`, optional (default: False)
|
|
388
|
+
if True,save the figure.
|
|
389
|
+
fig_path: `str`, optional (default: None)
|
|
390
|
+
If save_fig is True, specify figure path.
|
|
391
|
+
fig_name: `str`, optional (default: 'plot_barcode.pdf')
|
|
392
|
+
if `save_fig` is True, specify figure name.
|
|
393
|
+
**kwargs: `dict`, optional
|
|
394
|
+
Other keyword arguments are passed through to
|
|
395
|
+
``mpl.collections.LineCollection``
|
|
396
|
+
|
|
397
|
+
Returns
|
|
398
|
+
-------
|
|
399
|
+
None
|
|
400
|
+
"""
|
|
401
|
+
if fig_size is None:
|
|
402
|
+
fig_size = mpl.rcParams['figure.figsize']
|
|
403
|
+
if save_fig is None:
|
|
404
|
+
save_fig = settings.save_fig
|
|
405
|
+
if fig_path is None:
|
|
406
|
+
fig_path = os.path.join(settings.workdir, 'figures')
|
|
407
|
+
|
|
408
|
+
assert isinstance(entities, list), "`entities` must be list"
|
|
409
|
+
|
|
410
|
+
if layer is None:
|
|
411
|
+
X = adata_cmp[:, entities].X.copy()
|
|
412
|
+
else:
|
|
413
|
+
X = adata_cmp[:, entities].layers[layer].copy()
|
|
414
|
+
df_scores = pd.DataFrame(
|
|
415
|
+
data=X,
|
|
416
|
+
index=adata_cmp.obs_names,
|
|
417
|
+
columns=entities)
|
|
418
|
+
|
|
419
|
+
if min_rank is None:
|
|
420
|
+
min_rank = 0
|
|
421
|
+
if max_rank is None:
|
|
422
|
+
max_rank = df_scores.shape[0]
|
|
423
|
+
|
|
424
|
+
n_plots = len(entities)
|
|
425
|
+
fig_nrow = int(np.ceil(n_plots/fig_ncol))
|
|
426
|
+
fig = plt.figure(figsize=(fig_size[0]*fig_ncol*1.05,
|
|
427
|
+
fig_size[1]*fig_nrow))
|
|
428
|
+
|
|
429
|
+
for i, x in enumerate(entities):
|
|
430
|
+
ax_i = fig.add_subplot(fig_nrow, fig_ncol, i+1)
|
|
431
|
+
scores_x_sorted = df_scores[x].sort_values(ascending=False)
|
|
432
|
+
lines = []
|
|
433
|
+
for xx, yy in zip(np.arange(len(scores_x_sorted))[min_rank:max_rank],
|
|
434
|
+
scores_x_sorted[min_rank:max_rank]):
|
|
435
|
+
lines.append([(xx, 0), (xx, yy)])
|
|
436
|
+
if anno_ref is None:
|
|
437
|
+
colors = get_colors(np.array([""]*len(scores_x_sorted)))
|
|
438
|
+
else:
|
|
439
|
+
ids_ref = scores_x_sorted.index
|
|
440
|
+
if palette is None:
|
|
441
|
+
colors = get_colors(adata_cmp[ids_ref, :].obs[anno_ref])
|
|
442
|
+
else:
|
|
443
|
+
colors = [palette[adata_cmp.obs.loc[xx, anno_ref]]
|
|
444
|
+
for xx in scores_x_sorted.index]
|
|
445
|
+
stemlines = LineCollection(
|
|
446
|
+
lines,
|
|
447
|
+
colors=colors,
|
|
448
|
+
alpha=alpha,
|
|
449
|
+
linewidths=linewidths,
|
|
450
|
+
**kwargs)
|
|
451
|
+
ax_i.add_collection(stemlines)
|
|
452
|
+
ax_i.autoscale()
|
|
453
|
+
ax_i.set_title(x)
|
|
454
|
+
ax_i.set_ylabel(layer)
|
|
455
|
+
ax_i.locator_params(axis='y', tight=True)
|
|
456
|
+
if show_cutoff:
|
|
457
|
+
ax_i.axhline(y=cutoff,
|
|
458
|
+
color='#CC6F47',
|
|
459
|
+
linestyle='--')
|
|
460
|
+
plt.tight_layout(pad=pad, h_pad=h_pad, w_pad=w_pad)
|
|
461
|
+
if save_fig:
|
|
462
|
+
if not os.path.exists(fig_path):
|
|
463
|
+
os.makedirs(fig_path)
|
|
464
|
+
plt.savefig(os.path.join(fig_path, fig_name),
|
|
465
|
+
pad_inches=1,
|
|
466
|
+
bbox_inches='tight')
|
|
467
|
+
plt.close(fig)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def query(adata,
|
|
471
|
+
comp1=1,
|
|
472
|
+
comp2=2,
|
|
473
|
+
obsm='X_umap',
|
|
474
|
+
layer=None,
|
|
475
|
+
color=None,
|
|
476
|
+
dict_palette=None,
|
|
477
|
+
size=8,
|
|
478
|
+
drawing_order='random',
|
|
479
|
+
dict_drawing_order=None,
|
|
480
|
+
show_texts=False,
|
|
481
|
+
texts=None,
|
|
482
|
+
text_expand=(1.05, 1.2),
|
|
483
|
+
text_size=10,
|
|
484
|
+
n_texts=8,
|
|
485
|
+
fig_size=None,
|
|
486
|
+
fig_ncol=3,
|
|
487
|
+
fig_legend_ncol=1,
|
|
488
|
+
fig_legend_order=None,
|
|
489
|
+
alpha=0.9,
|
|
490
|
+
alpha_bg=0.3,
|
|
491
|
+
pad=1.08,
|
|
492
|
+
w_pad=None,
|
|
493
|
+
h_pad=None,
|
|
494
|
+
save_fig=None,
|
|
495
|
+
fig_path=None,
|
|
496
|
+
fig_name='plot_query.pdf',
|
|
497
|
+
vmin=None,
|
|
498
|
+
vmax=None,
|
|
499
|
+
**kwargs):
|
|
500
|
+
"""Plot query output
|
|
501
|
+
|
|
502
|
+
Parameters
|
|
503
|
+
----------
|
|
504
|
+
adata : `Anndata`
|
|
505
|
+
Annotated data matrix.
|
|
506
|
+
comp1 : `int`, optional (default: 1)
|
|
507
|
+
Component used for x axis.
|
|
508
|
+
comp2 : `int`, optional (default: 2)
|
|
509
|
+
Component used for y axis.
|
|
510
|
+
obsm : `str`, optional (default: 'X_umap')
|
|
511
|
+
The field to use for plotting
|
|
512
|
+
layer : `str`, optional (default: None)
|
|
513
|
+
The layer to use for plotting
|
|
514
|
+
color: `list`, optional (default: None)
|
|
515
|
+
A list of variables that will produce points with different colors.
|
|
516
|
+
e.g. color = ['anno1', 'anno2']
|
|
517
|
+
dict_palette: `dict`,optional (default: None)
|
|
518
|
+
A dictionary of palettes for different variables in `color`.
|
|
519
|
+
Only valid for categorical/string variables
|
|
520
|
+
e.g. dict_palette = {'ann1': {},'ann2': {}}
|
|
521
|
+
size: `int` (default: 8)
|
|
522
|
+
Point size.
|
|
523
|
+
drawing_order: `str` (default: 'random')
|
|
524
|
+
The order in which values are plotted, This can be
|
|
525
|
+
one of the following values
|
|
526
|
+
|
|
527
|
+
- 'original': plot points in the same order as in input dataframe
|
|
528
|
+
- 'sorted' : plot points with higher values on top.
|
|
529
|
+
- 'random' : plot points in a random order
|
|
530
|
+
dict_drawing_order: `dict`,optional (default: None)
|
|
531
|
+
A dictionary of drawing_order for different variables in `color`.
|
|
532
|
+
Only valid for categorical/string variables
|
|
533
|
+
e.g. dict_drawing_order = {'ann1': 'original','ann2': 'sorted'}
|
|
534
|
+
show_texts : `bool`, optional (default: False)
|
|
535
|
+
If True, text annotation will be shown.
|
|
536
|
+
text_size : `int`, optional (default: 10)
|
|
537
|
+
The text size.
|
|
538
|
+
texts: `list` optional (default: None)
|
|
539
|
+
Point names to plot.
|
|
540
|
+
text_expand : `tuple`, optional (default: (1.05, 1.2))
|
|
541
|
+
Two multipliers (x, y) by which to expand the bounding box of texts
|
|
542
|
+
when repelling them from each other/points/other objects.
|
|
543
|
+
n_texts : `int`, optional (default: 8)
|
|
544
|
+
The number of texts to plot.
|
|
545
|
+
fig_size: `tuple`, optional (default: (4, 4))
|
|
546
|
+
figure size.
|
|
547
|
+
fig_ncol: `int`, optional (default: 3)
|
|
548
|
+
the number of columns of the figure panel
|
|
549
|
+
fig_legend_order: `dict`,optional (default: None)
|
|
550
|
+
Specified order for the appearance of the annotation keys.
|
|
551
|
+
Only valid for categorical/string variable
|
|
552
|
+
e.g. fig_legend_order = {'ann1':['a','b','c'],'ann2':['aa','bb','cc']}
|
|
553
|
+
fig_legend_ncol: `int`, optional (default: 1)
|
|
554
|
+
The number of columns that the legend has.
|
|
555
|
+
vmin,vmax: `float`, optional (default: None)
|
|
556
|
+
The min and max values are used to normalize continuous values.
|
|
557
|
+
If None, the respective min and max of continuous values is used.
|
|
558
|
+
alpha: `float`, optional (default: 0.9)
|
|
559
|
+
The alpha blending value, between 0 (transparent) and 1 (opaque)
|
|
560
|
+
for returned points.
|
|
561
|
+
alpha_bg: `float`, optional (default: 0.3)
|
|
562
|
+
The alpha blending value, between 0 (transparent) and 1 (opaque)
|
|
563
|
+
for background points
|
|
564
|
+
pad: `float`, optional (default: 1.08)
|
|
565
|
+
Padding between the figure edge and the edges of subplots,
|
|
566
|
+
as a fraction of the font size.
|
|
567
|
+
h_pad, w_pad: `float`, optional (default: None)
|
|
568
|
+
Padding (height/width) between edges of adjacent subplots,
|
|
569
|
+
as a fraction of the font size. Defaults to pad.
|
|
570
|
+
save_fig: `bool`, optional (default: False)
|
|
571
|
+
if True,save the figure.
|
|
572
|
+
fig_path: `str`, optional (default: None)
|
|
573
|
+
If save_fig is True, specify figure path.
|
|
574
|
+
fig_name: `str`, optional (default: 'plot_query.pdf')
|
|
575
|
+
if save_fig is True, specify figure name.
|
|
576
|
+
|
|
577
|
+
Returns
|
|
578
|
+
-------
|
|
579
|
+
None
|
|
580
|
+
"""
|
|
581
|
+
if fig_size is None:
|
|
582
|
+
fig_size = mpl.rcParams['figure.figsize']
|
|
583
|
+
if save_fig is None:
|
|
584
|
+
save_fig = settings.save_fig
|
|
585
|
+
if fig_path is None:
|
|
586
|
+
fig_path = os.path.join(settings.workdir, 'figures')
|
|
587
|
+
|
|
588
|
+
if dict_palette is None:
|
|
589
|
+
dict_palette = dict()
|
|
590
|
+
|
|
591
|
+
query_output = adata.uns['query']['output']
|
|
592
|
+
nn = query_output.index.tolist() # nearest neighbors
|
|
593
|
+
query_params = adata.uns['query']['params']
|
|
594
|
+
query_obsm = query_params['obsm']
|
|
595
|
+
query_layer = query_params['layer']
|
|
596
|
+
entity = query_params['entity']
|
|
597
|
+
use_radius = query_params['use_radius']
|
|
598
|
+
r = query_params['r']
|
|
599
|
+
if (obsm == query_obsm) and (layer == query_layer):
|
|
600
|
+
pin = query_params['pin']
|
|
601
|
+
else:
|
|
602
|
+
if entity is not None:
|
|
603
|
+
if obsm is not None:
|
|
604
|
+
pin = adata[entity, :].obsm[obsm].copy()
|
|
605
|
+
elif layer is not None:
|
|
606
|
+
pin = adata[entity, :].layers[layer].copy()
|
|
607
|
+
else:
|
|
608
|
+
pin = adata[entity, :].X.copy()
|
|
609
|
+
else:
|
|
610
|
+
pin = None
|
|
611
|
+
|
|
612
|
+
if sum(list(map(lambda x: x is not None,
|
|
613
|
+
[layer, obsm]))) == 2:
|
|
614
|
+
raise ValueError("Only one of `layer` and `obsm` can be used")
|
|
615
|
+
if obsm is not None:
|
|
616
|
+
X = adata.obsm[obsm].copy()
|
|
617
|
+
X_nn = adata[nn, :].obsm[obsm].copy()
|
|
618
|
+
elif layer is not None:
|
|
619
|
+
X = adata.layers[layer].copy()
|
|
620
|
+
X_nn = adata[nn, :].layers[layer].copy()
|
|
621
|
+
else:
|
|
622
|
+
X = adata.X.copy()
|
|
623
|
+
X_nn = adata[nn, :].X.copy()
|
|
624
|
+
df_plot = pd.DataFrame(index=adata.obs.index,
|
|
625
|
+
data=X[:, [comp1-1, comp2-1]],
|
|
626
|
+
columns=[f'Dim {comp1}', f'Dim {comp2}'])
|
|
627
|
+
df_plot_nn = pd.DataFrame(index=adata[nn, :].obs.index,
|
|
628
|
+
data=X_nn[:, [comp1-1, comp2-1]],
|
|
629
|
+
columns=[f'Dim {comp1}', f'Dim {comp2}'])
|
|
630
|
+
if show_texts:
|
|
631
|
+
if texts is None:
|
|
632
|
+
texts = nn[:n_texts]
|
|
633
|
+
if color is None:
|
|
634
|
+
list_ax = _scatterplot2d(df_plot,
|
|
635
|
+
x=f'Dim {comp1}',
|
|
636
|
+
y=f'Dim {comp2}',
|
|
637
|
+
drawing_order=drawing_order,
|
|
638
|
+
size=size,
|
|
639
|
+
fig_size=fig_size,
|
|
640
|
+
alpha=alpha_bg,
|
|
641
|
+
pad=pad,
|
|
642
|
+
w_pad=w_pad,
|
|
643
|
+
h_pad=h_pad,
|
|
644
|
+
save_fig=False,
|
|
645
|
+
copy=True,
|
|
646
|
+
**kwargs)
|
|
647
|
+
else:
|
|
648
|
+
color = list(dict.fromkeys(color)) # remove duplicate keys
|
|
649
|
+
for ann in color:
|
|
650
|
+
if ann in adata.obs_keys():
|
|
651
|
+
df_plot[ann] = adata.obs[ann]
|
|
652
|
+
if not is_numeric_dtype(df_plot[ann]):
|
|
653
|
+
if 'color' not in adata.uns_keys():
|
|
654
|
+
adata.uns['color'] = dict()
|
|
655
|
+
|
|
656
|
+
if ann not in dict_palette.keys():
|
|
657
|
+
if (ann+'_color' in adata.uns['color'].keys()) \
|
|
658
|
+
and \
|
|
659
|
+
(all(np.isin(np.unique(df_plot[ann]),
|
|
660
|
+
list(adata.uns['color']
|
|
661
|
+
[ann+'_color'].keys())))):
|
|
662
|
+
dict_palette[ann] = \
|
|
663
|
+
adata.uns['color'][ann+'_color']
|
|
664
|
+
else:
|
|
665
|
+
dict_palette[ann] = \
|
|
666
|
+
generate_palette(adata.obs[ann])
|
|
667
|
+
adata.uns['color'][ann+'_color'] = \
|
|
668
|
+
dict_palette[ann].copy()
|
|
669
|
+
else:
|
|
670
|
+
if ann+'_color' not in adata.uns['color'].keys():
|
|
671
|
+
adata.uns['color'][ann+'_color'] = \
|
|
672
|
+
dict_palette[ann].copy()
|
|
673
|
+
|
|
674
|
+
elif ann in adata.var_names:
|
|
675
|
+
df_plot[ann] = adata.obs_vector(ann)
|
|
676
|
+
else:
|
|
677
|
+
raise ValueError(f"could not find {ann} in `adata.obs.columns`"
|
|
678
|
+
" and `adata.var_names`")
|
|
679
|
+
list_ax = _scatterplot2d(df_plot,
|
|
680
|
+
x=f'Dim {comp1}',
|
|
681
|
+
y=f'Dim {comp2}',
|
|
682
|
+
list_hue=color,
|
|
683
|
+
hue_palette=dict_palette,
|
|
684
|
+
drawing_order=drawing_order,
|
|
685
|
+
dict_drawing_order=dict_drawing_order,
|
|
686
|
+
size=size,
|
|
687
|
+
fig_size=fig_size,
|
|
688
|
+
fig_ncol=fig_ncol,
|
|
689
|
+
fig_legend_ncol=fig_legend_ncol,
|
|
690
|
+
fig_legend_order=fig_legend_order,
|
|
691
|
+
vmin=vmin,
|
|
692
|
+
vmax=vmax,
|
|
693
|
+
alpha=alpha_bg,
|
|
694
|
+
pad=pad,
|
|
695
|
+
w_pad=w_pad,
|
|
696
|
+
h_pad=h_pad,
|
|
697
|
+
save_fig=False,
|
|
698
|
+
copy=True,
|
|
699
|
+
**kwargs)
|
|
700
|
+
for ax in list_ax:
|
|
701
|
+
ax.scatter(df_plot_nn[f'Dim {comp1}'],
|
|
702
|
+
df_plot_nn[f'Dim {comp2}'],
|
|
703
|
+
s=size,
|
|
704
|
+
color='#AE6C68',
|
|
705
|
+
alpha=alpha,
|
|
706
|
+
lw=0)
|
|
707
|
+
if pin is not None:
|
|
708
|
+
ax.scatter(pin[:, 0],
|
|
709
|
+
pin[:, 1],
|
|
710
|
+
s=20*size,
|
|
711
|
+
marker='+',
|
|
712
|
+
color='#B33831')
|
|
713
|
+
if use_radius:
|
|
714
|
+
circle = plt.Circle((pin[:, 0],
|
|
715
|
+
pin[:, 1]),
|
|
716
|
+
radius=r,
|
|
717
|
+
color='#B33831',
|
|
718
|
+
fill=False)
|
|
719
|
+
ax.add_artist(circle)
|
|
720
|
+
if show_texts:
|
|
721
|
+
plt_texts = [ax.text(df_plot_nn[f'Dim {comp1}'][t],
|
|
722
|
+
df_plot_nn[f'Dim {comp2}'][t],
|
|
723
|
+
t,
|
|
724
|
+
fontdict={'family': 'serif',
|
|
725
|
+
'color': 'black',
|
|
726
|
+
'weight': 'normal',
|
|
727
|
+
'size': text_size})
|
|
728
|
+
for t in texts]
|
|
729
|
+
adjust_text(plt_texts,
|
|
730
|
+
ax=ax,
|
|
731
|
+
expand_text=text_expand,
|
|
732
|
+
expand_points=text_expand,
|
|
733
|
+
expand_objects=text_expand,
|
|
734
|
+
arrowprops=dict(arrowstyle='->', color='black'))
|
|
735
|
+
if save_fig:
|
|
736
|
+
fig = plt.gcf()
|
|
737
|
+
if not os.path.exists(fig_path):
|
|
738
|
+
os.makedirs(fig_path)
|
|
739
|
+
fig.savefig(os.path.join(fig_path, fig_name),
|
|
740
|
+
pad_inches=1,
|
|
741
|
+
bbox_inches='tight')
|
|
742
|
+
plt.close(fig)
|