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,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)