exprmat 0.1.5__tar.gz → 0.1.6__tar.gz

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 (68) hide show
  1. {exprmat-0.1.5 → exprmat-0.1.6}/PKG-INFO +2 -1
  2. exprmat-0.1.6/exprmat/clustering/rogue.py +333 -0
  3. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/data/finders.py +31 -0
  4. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/plotting/palettes.py +1 -1
  5. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/__init__.py +1 -1
  6. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/plot.py +21 -1
  7. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/qc.py +10 -9
  8. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reader/experiment.py +127 -25
  9. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reader/matcher.py +46 -5
  10. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/plot.py +137 -34
  11. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat.egg-info/PKG-INFO +2 -1
  12. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat.egg-info/SOURCES.txt +1 -1
  13. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat.egg-info/requires.txt +1 -0
  14. {exprmat-0.1.5 → exprmat-0.1.6}/setup.py +3 -2
  15. exprmat-0.1.5/exprmat.egg-info/.ipynb_checkpoints/requires-checkpoint.txt +0 -22
  16. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/__init__.py +0 -0
  17. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/ansi.py +0 -0
  18. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/clustering/__init__.py +0 -0
  19. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/clustering/leiden.py +0 -0
  20. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/clustering/spectral.py +0 -0
  21. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/clustering/utils.py +0 -0
  22. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/cnv/__init__.py +0 -0
  23. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/cnv/cnvscore.py +0 -0
  24. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/cnv/infercnv.py +0 -0
  25. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/configuration.py +0 -0
  26. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/data/__init__.py +0 -0
  27. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/data/delim.py +0 -0
  28. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/data/gff.py +0 -0
  29. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/data/hsa/genome.tsv.gz +0 -0
  30. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/data/mmu/genome.tsv.gz +0 -0
  31. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/data/private.py +0 -0
  32. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/data/ranges.py +0 -0
  33. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/descriptive/__init__.py +0 -0
  34. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/descriptive/de.py +0 -0
  35. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/descriptive/kde.py +0 -0
  36. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/plotting/__init__.py +0 -0
  37. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/plotting/cnv.py +0 -0
  38. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/plotting/de.py +0 -0
  39. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/plotting/expression.py +0 -0
  40. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/doublets/__init__.py +0 -0
  41. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/doublets/composite.py +0 -0
  42. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/doublets/scrublet.py +0 -0
  43. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/filter.py +0 -0
  44. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/integrate.py +0 -0
  45. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/linear.py +0 -0
  46. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/normal.py +0 -0
  47. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/sctransform/__init__.py +0 -0
  48. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/sctransform/estimate.py +0 -0
  49. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/sctransform/ksmooth.py +0 -0
  50. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/sctransform/residual.py +0 -0
  51. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/sctransform/transform.py +0 -0
  52. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/sparse.py +0 -0
  53. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/uniform.py +0 -0
  54. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reader/__init__.py +0 -0
  55. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reader/metadata.py +0 -0
  56. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/__init__.py +0 -0
  57. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/forceatlas.py +0 -0
  58. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/mde.py +0 -0
  59. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/nn.py +0 -0
  60. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/pca.py +0 -0
  61. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/pumap.py +0 -0
  62. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/tsne.py +0 -0
  63. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/umap.py +0 -0
  64. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/utils.py +0 -0
  65. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat.egg-info/dependency_links.txt +0 -0
  66. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat.egg-info/not-zip-safe +0 -0
  67. {exprmat-0.1.5 → exprmat-0.1.6}/exprmat.egg-info/top_level.txt +0 -0
  68. {exprmat-0.1.5 → exprmat-0.1.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: exprmat
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: routines to process expression matrices
5
5
  Home-page: https://github.com/yang-z-03/exprmat
6
6
  Author: Zheng Yang
@@ -28,6 +28,7 @@ Requires-Dist: datashader
28
28
  Requires-Dist: fa2_modified
29
29
  Requires-Dist: pynndescent
30
30
  Requires-Dist: gseapy
31
+ Requires-Dist: sh
31
32
  Dynamic: author
32
33
  Dynamic: author-email
33
34
  Dynamic: home-page
@@ -0,0 +1,333 @@
1
+
2
+ import numpy as np
3
+ import pandas as pd
4
+ import statsmodels.api as sm
5
+ from skmisc import loess
6
+ from scipy.stats import norm
7
+ from statsmodels.stats.multitest import multipletests
8
+ from exprmat.ansi import error, warning, info
9
+
10
+
11
+ # the entropy of the genes across cell j is defined to be:
12
+ #
13
+ # s1 = ln(mean(expr_j))
14
+ #
15
+ # this assumes that all cells in the cluster is homogenous. while the alternative
16
+ # assumption is that cells are totally heterogenous, and each cell represent its
17
+ # distinct state. the entropy for a totally heterogenous dataset is:
18
+ #
19
+ # s2 = mean(log(expr_ij))
20
+ #
21
+ # for calculation, this is simply a conversion of mean and log transform.
22
+ # r is a small positive value to avoid nan's been introduced due to invalid log.
23
+ # we require that the expression matrix have gene names as row names.
24
+
25
+ def rogue_entropy(C, varname, r = 1):
26
+ '''
27
+ Calculate entropy for genes. A completely homogenous gene will yield entropy
28
+ as mean logrithmized counts (mean.expr). Expect raw count matrix.
29
+ '''
30
+
31
+ entropy = np.log(C + 1).mean(axis = 0) # per gene.
32
+ mean_expr = np.log(C.mean(axis = 0) + r)
33
+ return pd.DataFrame({
34
+ 'gene': varname,
35
+ 'entropy': entropy.tolist()[0],
36
+ 'mean.expr': mean_expr.tolist()[0]
37
+ })
38
+
39
+
40
+ # fit the relationship between expression entropy and mean gene expression:
41
+ #
42
+ # in real-world dataset, there must be to some extent, heterogenous components
43
+ # in the dataset clusters. s1 > s2. and for different techniques for rna sequencing
44
+ # there is a systematic bias on the data distribution. we need a fitting step
45
+ # to make sure that the highly-variable genes in the clusters are picked.
46
+ #
47
+ #' @param span is a parameter for loess for controls of fit smoothness.
48
+ #' @param mt.method multiple testing method used in p.adjust.
49
+ #
50
+ # the returning dataframe consists of [1] gene names, [2] log mean expression (s1),
51
+ # [3] fit, the fitted curve of real world log mean expression trajectory (s1'),
52
+ # [4] ds, delta of entropy, the difference between s2 and s1',
53
+ # [5] p.value, assume that ds is normal, and the p value of normal distribution.
54
+ # [6] p.adj, the multi-test adjusted p value for normal approximation. and
55
+ # [7] entropy, the expression entropy per se.
56
+ #
57
+ # the returning values are sorted according to decreasing delta of entropy, so
58
+ # that the most highly-variable genes ranked on the top.
59
+
60
+ # available correction methods:
61
+ # * bonferroni : one-step correction
62
+ # * sidak : one-step correction
63
+ # * holm-sidak : step down method using Sidak adjustments
64
+ # * holm : step-down method using Bonferroni adjustments
65
+ # * simes-hochberg : step-up method (independent)
66
+ # * hommel : closed method based on Simes tests (non-negative)
67
+ # * fdr_bh : Benjamini/Hochberg (non-negative)
68
+ # * fdr_by : Benjamini/Yekutieli (negative)
69
+ # * fdr_tsbh : two stage fdr correction (non-negative)
70
+ # * fdr_tsbky : two stage fdr correction (non-negative)
71
+
72
+ def smoothen_entropy_lowess(df, span = 0.5, correction = "fdr_bh"):
73
+
74
+ df = df.loc[
75
+ np.isfinite(df['mean.expr']) &
76
+ df['entropy'] > 0, :
77
+ ].copy()
78
+
79
+ fit = sm.nonparametric.lowess(
80
+ exog = df['mean.expr'],
81
+ endog = df['entropy'], frac = span,
82
+ xvals = df['mean.expr']
83
+ )
84
+
85
+ df['fit'] = fit
86
+ df['ds'] = df['fit'] - df['entropy']
87
+ df['pval'] = 1 - norm.cdf(df['ds'], np.mean(df['ds']), np.std(df['ds']))
88
+ training = df.loc[df['pval'] > 0.1, :].copy()
89
+
90
+ fit2 = sm.nonparametric.lowess(
91
+ exog = training['mean.expr'],
92
+ endog = training['entropy'], frac = span,
93
+ xvals = df['mean.expr']
94
+ )
95
+
96
+ df['fit'] = fit2
97
+ df['ds'] = df['fit'] - df['entropy']
98
+ df['pval'] = 1 - norm.cdf(df['ds'], np.mean(df['ds']), np.std(df['ds']))
99
+ training = df.loc[df['pval'] > 0.1, :].copy()
100
+
101
+ fit3 = sm.nonparametric.lowess(
102
+ exog = training['mean.expr'],
103
+ endog = training['entropy'], frac = span,
104
+ xvals = df['mean.expr']
105
+ )
106
+
107
+ df['fit'] = fit3
108
+ df['ds'] = df['fit'] - df['entropy']
109
+ df = df.loc[np.isfinite(df['ds']), :]
110
+ df['pval'] = 1 - norm.cdf(df['ds'], np.mean(df['ds']), np.std(df['ds']))
111
+ df['qval'] = multipletests(df['pval'], method = correction)[1]
112
+
113
+ return df.sort_values(['ds'], ascending = False)
114
+
115
+
116
+ def smoothen_entropy_loess(df, span = 0.5, correction = "fdr_bh"):
117
+
118
+ df = df.loc[
119
+ np.isfinite(df['mean.expr']) &
120
+ df['entropy'] > 0, :
121
+ ].copy()
122
+
123
+ model = loess.loess(df['mean.expr'], df['entropy'], span = span)
124
+ model.fit()
125
+ fit = model.predict(df['mean.expr'])
126
+
127
+ df['fit'] = fit.values
128
+ df['ds'] = df['fit'] - df['entropy']
129
+ df['pval'] = 1 - norm.cdf(df['ds'], np.mean(df['ds']), np.std(df['ds']))
130
+ training = df.loc[df['pval'] > 0.1, :].copy()
131
+
132
+ df = df.loc[df['mean.expr'] <= np.max(training['mean.expr']), :].copy()
133
+ model = loess.loess(training['mean.expr'], training['entropy'], span = span)
134
+ model.fit()
135
+ fit2 = model.predict(df['mean.expr'])
136
+
137
+ df['fit'] = fit2.values
138
+ df['ds'] = df['fit'] - df['entropy']
139
+ df['pval'] = 1 - norm.cdf(df['ds'], np.mean(df['ds']), np.std(df['ds']))
140
+ training = df.loc[df['pval'] > 0.1, :].copy()
141
+
142
+ df = df.loc[df['mean.expr'] <= np.max(training['mean.expr']), :].copy()
143
+ model = loess.loess(training['mean.expr'], training['entropy'], span = span)
144
+ model.fit()
145
+ fit3 = model.predict(df['mean.expr'])
146
+
147
+ df['fit'] = fit3.values
148
+ df['ds'] = df['fit'] - df['entropy']
149
+ df = df.loc[np.isfinite(df['ds']), :]
150
+ df['pval'] = 1 - norm.cdf(df['ds'], np.mean(df['ds']), np.std(df['ds']))
151
+ df['qval'] = multipletests(df['pval'], method = correction)[1]
152
+
153
+ return df.sort_values(['ds'], ascending = False)
154
+
155
+
156
+ smoothen_entropy = smoothen_entropy_loess
157
+
158
+ # identify highly informative genes using rogue's s-e model
159
+ #
160
+ # returns a tibble object with seven columns:
161
+ # [1] gene, the gene name.
162
+ # [2] mean.expr, the mean expression levels of genes.
163
+ # [3] entropy, the expected expression entropy from a given mean gene expression.
164
+ # [4] fit, the mean expression levels of genes.
165
+ # [5] ds, the entropy reduction against the null expectation.
166
+ # [6] p.value, the significance of ds against normal distribution.
167
+ # [7] p.adj, adjusted p value, or a copy of p.value if specifying not to adjust it.
168
+
169
+ def rogue_hvg(C, varname, span = 0.5, r = 1, correction = "fdr_bh", adjust_p = True):
170
+
171
+ entropy = rogue_entropy(C, varname, r = r)
172
+ entropy = smoothen_entropy(entropy, span = span, correction = correction)
173
+ if not adjust_p: entropy['qval'] = entropy["pval"]
174
+ return entropy
175
+
176
+
177
+ # assess the purity of single cell population by rogue index.
178
+ #
179
+ # we may either specify the platform to access the default k value, or specify
180
+ # a k value manually. the rogue test is meaned to be run with highly variable
181
+ # genes. you can either specify them manually in `features`, or it is recommended
182
+ # to use the s-e model to pick the hvgs for you.
183
+
184
+ def rogue_index(df, platform = "umi", cutoff = 0.05, k = None, features = None):
185
+
186
+ if k is None:
187
+ if platform is None:
188
+ warning("you should supply a constant k yourself, or specify the platform for ")
189
+ warning("us to automatically pick the k constant for you. possible platforms ")
190
+ warning("are `umi` or `full-length`, the default k value for each case is 45 ")
191
+ warning("and 500 respectively.")
192
+ error('routine exit.')
193
+
194
+ elif platform == 'umi': k = 45
195
+ elif platform == 'full-length': k = 500
196
+ else: error('platform should be one of `umi` or `full-length`.')
197
+
198
+ if features is not None:
199
+ df = df.loc[[x in features for x in df['gene'].tolist()], :]
200
+ sig = np.abs(df['ds'].to_numpy()).sum()
201
+ rogue = 1 - sig / (sig + k)
202
+
203
+ else:
204
+
205
+ df = df.loc[
206
+ (df['pval'] < cutoff) &
207
+ (df['qval'] < cutoff), :
208
+ ]
209
+
210
+ sig = np.abs(df['ds'].to_numpy()).sum()
211
+ rogue = 1 - sig / (sig + k)
212
+
213
+ return rogue
214
+
215
+
216
+ # remove outlier cells when calculating ROGUE
217
+ #' @param n remove this many outlier cells.
218
+
219
+ def remove_outliers(df, C, varname, n = 2, span = 0.5, r = 1, correction = 'fdr_bh'):
220
+
221
+ from exprmat.utils import choose_layer
222
+ signif_genes = df.loc[df['qval'] < 0.05, :]['gene'].tolist()
223
+ ngenes = len(signif_genes)
224
+ bool_mask_gene = [x in signif_genes for x in varname]
225
+ expr_cutoff = df['mean.expr'].to_numpy().min()
226
+
227
+ expr = C[:, bool_mask_gene]
228
+ signif_genes = []
229
+ for imask in range(len(bool_mask_gene)):
230
+ if bool_mask_gene[imask]: signif_genes.append(varname[imask])
231
+
232
+ for i in range(ngenes):
233
+ gene_expr = np.flip(np.array(np.sort(expr[:, i].T))[0])
234
+ gene_expr = gene_expr[n:]
235
+ mean = np.log(gene_expr.mean() + r)
236
+ entropy = np.mean(np.log(gene_expr + 1))
237
+ df.loc[df['gene'] == signif_genes[i], 'mean.expr'] = mean
238
+ df.loc[df['gene'] == signif_genes[i], 'entropy'] = entropy
239
+
240
+ df = df.loc[df['mean.expr'] > expr_cutoff, :]
241
+ del df['qval']
242
+ df = smoothen_entropy(df, span = span, correction = correction)
243
+ return df
244
+
245
+
246
+ def rogue(
247
+ adata, counts = 'X', platform = "umi", k = None,
248
+ min_cells = 10, min_genes = 10, outlier_n = 2,
249
+ span = 0.5, r = 1, correction = 'fdr_bh'
250
+ ):
251
+ '''
252
+ Calculate ROGUE heterogeneity index for the whole annotated data.
253
+ Users are recommended to filter the minimally expressing genes and cells,
254
+ and pick out the subset of interest before feeding into this method. Or
255
+ more conveniently, run the analysis on given categorical properties via
256
+ the wrapper function ``experiment.run_rna_rogue``.
257
+
258
+ Parameters
259
+ -----------
260
+
261
+ adata : anndata.AnnData
262
+ The set of data you would like to test for heterogeneity
263
+
264
+ counts : str = 'X'
265
+ ROGUE index is calculated on raw counts. Specify a layer of raw counts.
266
+
267
+ platform : Literal['umi', 'full-length']
268
+ Choose k value with preset technology
269
+
270
+ k : int = None
271
+ Manually picks a k value. It can be estimated from dataset using
272
+ ``estimate_k`` function, or use the recommended value via ``platform``.
273
+
274
+ outlier_n : int = 2
275
+ Remove n top-expressing cells during analysis
276
+
277
+ span : float = 0.5
278
+ Controls the smoothness of LOESS regression
279
+
280
+ correction : str = 'fdr_bh'
281
+ Multitest correction of p-value. Available correction methods:
282
+ * bonferroni : one-step correction
283
+ * sidak : one-step correction
284
+ * holm-sidak : step down method using Sidak adjustments
285
+ * holm : step-down method using Bonferroni adjustments
286
+ * simes-hochberg : step-up method (independent)
287
+ * hommel : closed method based on Simes tests (non-negative)
288
+ * fdr_bh : Benjamini/Hochberg (non-negative)
289
+ * fdr_by : Benjamini/Yekutieli (negative)
290
+ * fdr_tsbh : two stage fdr correction (non-negative)
291
+ * fdr_tsbky : two stage fdr correction (non-negative)
292
+
293
+ Returns
294
+ -----------
295
+ A floating point value, rogue index
296
+ '''
297
+
298
+ from exprmat.utils import choose_layer
299
+ C = choose_layer(adata, layer = counts).todense()
300
+ hvg = rogue_hvg(
301
+ C, adata.var_names.tolist(),
302
+ span = span, r = r, correction = correction
303
+ )
304
+
305
+ hvg = remove_outliers(
306
+ hvg, C, adata.var_names.tolist(),
307
+ n = outlier_n, span = span, r = r, correction = correction
308
+ )
309
+
310
+ return rogue_index(hvg, platform = platform, k = k)
311
+
312
+
313
+ # calculate the value of the reference factor k
314
+
315
+ def estimate_k(C, varname, span = 0.5, r = 1, correction = "fdr_bh", adjust_p = True):
316
+
317
+ entropy = rogue_entropy(C, varname, r = r)
318
+ entropy = smoothen_entropy(entropy, span = span, correction = correction)
319
+ if not adjust_p: entropy['qval'] = entropy["pval"]
320
+ entropy = entropy.loc[entropy['qval'] < 0.05, :]
321
+ k = entropy['ds'].to_numpy().sum()
322
+ return k // 2
323
+
324
+
325
+ def se_plot(df):
326
+
327
+ import seaborn as sns
328
+ return sns.scatterplot(
329
+ data = df,
330
+ x = 'mean.expr',
331
+ y = 'entropy',
332
+ hue = df['pval'].to_numpy() < 0.05
333
+ )
@@ -1,6 +1,7 @@
1
1
 
2
2
  import os
3
3
  import pandas
4
+ from exprmat.ansi import warning, info
4
5
  from exprmat.configuration import default as cfg
5
6
 
6
7
  basepath = os.path.dirname(__file__)
@@ -124,6 +125,36 @@ def get_mapper_ensembl(taxa):
124
125
  return ensembl_finder
125
126
 
126
127
 
128
+ def update_mapper(taxa, name, ens):
129
+ '''
130
+ Update the name ENSEMBL ID pair.
131
+ '''
132
+
133
+ if taxa not in genome.keys():
134
+ get_mapper_name(taxa)
135
+
136
+ if 'mapper.name' not in genome[taxa].keys():
137
+ get_mapper_name(taxa)
138
+
139
+ if 'mapper.ensembl' not in genome[taxa].keys():
140
+ get_mapper_ensembl(taxa)
141
+
142
+ if (name not in genome[taxa]['mapper.name'].keys()):
143
+ if (ens in genome[taxa]['mapper.ensembl'].keys()):
144
+ genome[taxa]['mapper.name'][name] = genome[taxa]['mapper.ensembl'][ens]
145
+ else: info(f'{ens} -> {name} pair could not be established.')
146
+
147
+ else:
148
+ if (ens not in genome[taxa]['mapper.ensembl'].keys()):
149
+ genome[taxa]['mapper.ensembl'][ens] = genome[taxa]['mapper.name'][name]
150
+ else: # both name and ens present in the dictionary, check for inconsistancy
151
+ if genome[taxa]['mapper.ensembl'][ens] != genome[taxa]['mapper.name'][name]:
152
+ warning(f'{ens} -> {name} is conflicted. ({ens} registered to {genome[taxa]["mapper.ensembl"][ens]}, while {name} to {genome[taxa]["mapper.name"][name]})')
153
+
154
+
155
+ return
156
+
157
+
127
158
  def update_mapper_name(taxa, name, ugene):
128
159
  '''
129
160
  Update the gene name to unique gene id mapping.
@@ -29,7 +29,7 @@ module attribute, and the 'small' palettes (i.e. excluding the ones with 256
29
29
  colors) are collected and in a ``small_palettes`` attribute.
30
30
 
31
31
  This module includes some palettes that are useful for addressing color deficiencies,
32
- which contains `Paul Tol's color schemes`_, and ``Colorblind`` from
32
+ which contains Paul Tol's color schemes, and ``Colorblind`` from
33
33
  https://jfly.uni-koeln.de/color/#pallet.
34
34
 
35
35
  The following palettes are also introduced in `Paul Tol's color schemes` but with
@@ -84,7 +84,7 @@ def highly_variable(
84
84
  elif method == 'vst':
85
85
  df = highly_variable_genes_seurat(
86
86
  m_count, obs = adata.obs, var = adata.var, subset = False,
87
- **kwargs
87
+ n_top_genes = n_top_genes, **kwargs
88
88
  )
89
89
  adata.var[dest + '.means'] = df['means']
90
90
  adata.var[dest + '.vars'] = df['variances']
@@ -57,4 +57,24 @@ def rna_plot_qc_metrics(adata, sample_name, figsize = (10, 2.2)):
57
57
  fig.set_dpi(100)
58
58
  fig.tight_layout(pad = 0.1)
59
59
 
60
- return fig
60
+ return fig
61
+
62
+
63
+ def rna_plot_gene_histogram(adata, sample_name, ax):
64
+
65
+ h = ax.hist(adata.obs['n.genes'].tolist(), bins = 100)
66
+ ax.set_xlabel('Gene detection')
67
+ ax.set_ylabel('Frequency')
68
+ ax.set_title(sample_name if len(sample_name) < 20 else sample_name[:17] + ' ..')
69
+
70
+ ax.vlines(
71
+ x = [0, 150], ymin = 0, ymax = 10,
72
+ colors = 'red', linestyles = 'dotted'
73
+ )
74
+
75
+ ax.vlines(
76
+ x = [300, 600], ymin = 0, ymax = 10,
77
+ colors = 'black', linestyles = 'dotted'
78
+ )
79
+
80
+ ax.set_yscale('log')
@@ -153,15 +153,16 @@ def rna_qc(
153
153
  adata.obs['score.doublet.se'] = 0.0
154
154
  adata.obs['is.doublet'] = False
155
155
 
156
- adata.obs.loc[subset.obs['score.doublet'].index, 'score.doublet'] = \
157
- subset.obs['score.doublet']
158
- adata.obs.loc[subset.obs['score.doublet.se'].index, 'score.doublet.se'] = \
159
- subset.obs['score.doublet.se']
160
- if 'is.doublet' in subset.obs.columns.tolist():
161
- adata.obs.loc[subset.obs['score.doublet'].index, 'is.doublet'] = \
162
- subset.obs['is.doublet']
163
-
164
- adata.uns['scrublet'] = subset.uns['scrublet']
156
+ if doublet_method == 'scrublet':
157
+ adata.obs.loc[subset.obs['score.doublet'].index, 'score.doublet'] = \
158
+ subset.obs['score.doublet']
159
+ adata.obs.loc[subset.obs['score.doublet.se'].index, 'score.doublet.se'] = \
160
+ subset.obs['score.doublet.se']
161
+ if 'is.doublet' in subset.obs.columns.tolist():
162
+ adata.obs.loc[subset.obs['score.doublet'].index, 'is.doublet'] = \
163
+ subset.obs['is.doublet']
164
+ adata.uns['scrublet'] = subset.uns['scrublet']
165
+
165
166
  adata.obs['qc'] = adata.obs['filter']
166
167
  adata.var['qc'] = f_var
167
168
 
@@ -323,8 +323,10 @@ class experiment:
323
323
  if not samp in self.modalities[mod].keys():
324
324
  warning(f'{samp} not loaded in the {mod} modality.')
325
325
  continue
326
-
327
- results[samp] = func(self.modalities[mod][samp], samp, **kwargs)
326
+
327
+ try:
328
+ results[samp] = func(self.modalities[mod][samp], samp, **kwargs)
329
+ except: warning(f'method failed for sample {samp}')
328
330
 
329
331
  return results
330
332
 
@@ -694,6 +696,13 @@ class experiment:
694
696
  from exprmat.reduction.plot import embedding
695
697
  return embedding(adata, sample_name = sample_name, **kwargs)
696
698
 
699
+
700
+ @staticmethod
701
+ def rna_plot_embedding_atlas(adata, sample_name, **kwargs):
702
+ from exprmat.reduction.plot import embedding_atlas
703
+ return embedding_atlas(adata, sample_name = sample_name, **kwargs)
704
+
705
+
697
706
  @staticmethod
698
707
  def rna_plot_gene_gene(adata, sample_name, **kwargs):
699
708
  from exprmat.reduction.plot import gene_gene
@@ -886,6 +895,43 @@ class experiment:
886
895
  return fig
887
896
 
888
897
 
898
+ @staticmethod
899
+ def rna_plot_multiple_embedding_atlas(
900
+ adata, sample_name, basis, features, ncols,
901
+ figsize = (3, 3), dpi = 100, **kwargs
902
+ ):
903
+ from exprmat.reduction.plot import embedding_atlas
904
+ import matplotlib.pyplot as plt
905
+ import warnings
906
+ warnings.filterwarnings('ignore')
907
+
908
+ n_features = len(features)
909
+ nrows = n_features // ncols
910
+ if n_features % ncols != 0: nrows += 1
911
+ fig, axes = plt.subplots(nrows, ncols, dpi = dpi)
912
+
913
+ for feat_id in range(len(features)):
914
+
915
+ if len(axes.shape) == 2:
916
+ embedding_atlas(
917
+ adata, basis, color = features[feat_id],
918
+ ax = axes[feat_id // ncols, feat_id % ncols],
919
+ sample_name = sample_name, dpi = dpi, **kwargs
920
+ )
921
+
922
+ elif len(axes.shape) == 1:
923
+ embedding_atlas(
924
+ adata, basis, color = features[feat_id],
925
+ ax = axes[feat_id], dpi = dpi,
926
+ sample_name = sample_name, **kwargs
927
+ )
928
+
929
+ fig.set_figwidth(figsize[0])
930
+ fig.set_figheight(figsize[1])
931
+ fig.tight_layout()
932
+ return fig
933
+
934
+
889
935
  @staticmethod
890
936
  def rna_plot_multiple_gene_gene(
891
937
  adata, sample_name, color, features_xy, ncols,
@@ -1000,9 +1046,15 @@ class experiment:
1000
1046
 
1001
1047
  def plot_rna_embedding(self, run_on_samples = False, **kwargs):
1002
1048
  return self.plot_for_rna(run_on_samples, experiment.rna_plot_embedding, **kwargs)
1049
+
1050
+ def plot_rna_embedding_atlas(self, run_on_samples = False, **kwargs):
1051
+ return self.plot_for_rna(run_on_samples, experiment.rna_plot_embedding_atlas, **kwargs)
1003
1052
 
1004
1053
  def plot_rna_embedding_multiple(self, run_on_samples = False, **kwargs):
1005
1054
  return self.plot_for_rna(run_on_samples, experiment.rna_plot_multiple_embedding, **kwargs)
1055
+
1056
+ def plot_rna_embedding_atlas_multiple(self, run_on_samples = False, **kwargs):
1057
+ return self.plot_for_rna(run_on_samples, experiment.rna_plot_multiple_embedding_atlas, **kwargs)
1006
1058
 
1007
1059
  def plot_rna_markers(self, run_on_samples = False, **kwargs):
1008
1060
  return self.plot_for_rna(run_on_samples, experiment.rna_plot_markers, **kwargs)
@@ -1028,6 +1080,50 @@ class experiment:
1028
1080
  def plot_rna_expression_bar(self, run_on_samples = False, **kwargs):
1029
1081
  return self.plot_for_rna(run_on_samples, experiment.rna_plot_expression_bar, **kwargs)
1030
1082
 
1083
+ def plot_rna_qc_gene_counts(
1084
+ self, ncols = 4, figsize = (3, 3)
1085
+ ):
1086
+ from exprmat.utils import setup_styles, plotting_styles
1087
+ setup_styles(**plotting_styles)
1088
+
1089
+ from exprmat.preprocessing.plot import rna_plot_gene_histogram
1090
+ import matplotlib.pyplot as plt
1091
+ import warnings
1092
+ warnings.filterwarnings('ignore')
1093
+ if self.modalities is None: error('samples are not loaded')
1094
+ if not 'rna' in self.modalities.keys(): error('samples are not loaded')
1095
+
1096
+ n_features = len(self.modalities['rna'])
1097
+ if n_features == 0: error('samples are not loaded')
1098
+
1099
+ nrows = n_features // ncols
1100
+ if n_features % ncols != 0: nrows += 1
1101
+ fig, axes = plt.subplots(nrows, ncols)
1102
+
1103
+ samples = list(self.modalities['rna'].keys())
1104
+ samples.sort()
1105
+ for feat_id in range(n_features):
1106
+
1107
+ if len(axes.shape) == 2:
1108
+ rna_plot_gene_histogram(
1109
+ self.modalities['rna'][samples[feat_id]],
1110
+ sample_name = samples[feat_id],
1111
+ ax = axes[feat_id // ncols, feat_id % ncols]
1112
+ )
1113
+
1114
+ elif len(axes.shape) == 1:
1115
+ rna_plot_gene_histogram(
1116
+ self.modalities['rna'][samples[feat_id]],
1117
+ sample_name = samples[feat_id],
1118
+ ax = axes[feat_id]
1119
+ )
1120
+
1121
+ fig.set_figwidth(figsize[0])
1122
+ fig.set_figheight(figsize[1])
1123
+ fig.tight_layout()
1124
+ return fig
1125
+
1126
+
1031
1127
 
1032
1128
  # accessor wrappers
1033
1129
 
@@ -1164,30 +1260,36 @@ class experiment:
1164
1260
 
1165
1261
  else: print(red('[!]'), 'dataset not integrated.')
1166
1262
 
1167
- print(red('[*]'), 'composed of samples:')
1168
- for i_loc, i_sample, i_batch, i_grp, i_mod, i_taxa in zip(
1169
- self.metadata.dataframe['location'],
1170
- self.metadata.dataframe['sample'],
1171
- self.metadata.dataframe['batch'],
1172
- self.metadata.dataframe['group'],
1173
- self.metadata.dataframe['modality'],
1174
- self.metadata.dataframe['taxa']
1175
- ):
1176
- loaded = False
1177
- if (self.modalities is not None) and \
1178
- (i_mod in self.modalities.keys()) and \
1179
- (i_sample in self.modalities[i_mod].keys()):
1180
- loaded = True
1181
-
1182
- print(
1183
- f' {i_sample:20}', cyan(f'{i_mod:4}'), yellow(f'{i_taxa:4}'),
1184
- f'batch {green(f"{i_batch:10}")}',
1185
- red('dataset not loaded') if not loaded else
1186
- f'{green(str(self.modalities[i_mod][i_sample].n_obs))} × ' +
1187
- f'{yellow(str(self.modalities[i_mod][i_sample].n_vars))}'
1188
- )
1263
+ if self.modalities is None or len(self.modalities) == 0:
1264
+ print(red('[*]'), 'samples not loaded from disk.')
1265
+
1266
+ else:
1267
+ print(red('[*]'), 'composed of samples:')
1268
+ for i_loc, i_sample, i_batch, i_grp, i_mod, i_taxa in zip(
1269
+ self.metadata.dataframe['location'],
1270
+ self.metadata.dataframe['sample'],
1271
+ self.metadata.dataframe['batch'],
1272
+ self.metadata.dataframe['group'],
1273
+ self.metadata.dataframe['modality'],
1274
+ self.metadata.dataframe['taxa']
1275
+ ):
1276
+ loaded = False
1277
+ if (self.modalities is not None) and \
1278
+ (i_mod in self.modalities.keys()) and \
1279
+ (i_sample in self.modalities[i_mod].keys()):
1280
+ loaded = True
1281
+
1282
+ p_sample = i_sample if len(i_sample) < 30 else i_sample[:27] + ' ..'
1283
+ p_batch = i_batch if len(i_batch) < 30 else i_batch[:27] + ' ..'
1284
+ print(
1285
+ f' {p_sample:30}', cyan(f'{i_mod:4}'), yellow(f'{i_taxa:4}'),
1286
+ f'batch {green(f"{p_batch:30}")}',
1287
+ red('dataset not loaded') if not loaded else
1288
+ f'{green(str(self.modalities[i_mod][i_sample].n_obs))} × ' +
1289
+ f'{yellow(str(self.modalities[i_mod][i_sample].n_vars))}'
1290
+ )
1189
1291
 
1190
- return '<exprmat.reader.experiment>'
1292
+ return f'<exprmat.reader.experiment> ({len(self.metadata.dataframe)} samples)'
1191
1293
 
1192
1294
  pass
1193
1295
 
@@ -15,12 +15,53 @@ import numpy as np
15
15
  from scipy.sparse import csr_matrix
16
16
  from exprmat.ansi import error, warning, info
17
17
  from exprmat.data.finders import get_mapper_ensembl, get_mapper_name, get_genome
18
- from exprmat.data.finders import update_mapper_ensembl, update_mapper_name, save_genome_changes
18
+ from exprmat.data.finders import update_mapper_ensembl, update_mapper, save_genome_changes
19
19
  from exprmat.reader.metadata import metadata
20
20
  from exprmat.configuration import default as cfg
21
21
  from exprmat.ansi import info
22
22
 
23
23
 
24
+ def refine_finder(features_fpath, taxa = 'mmu'):
25
+ table = pd.read_table(features_fpath, sep = '\t', header = None)
26
+ assert len(table.columns) >= 2
27
+
28
+ name_finder = get_mapper_name(taxa)
29
+ ensembl_finder = get_mapper_ensembl(taxa)
30
+
31
+ ensembls = table[0].tolist()
32
+ names = table[1].tolist()
33
+ mentioned_taxa = []
34
+
35
+ for ens, nm in zip(ensembls, names):
36
+
37
+ # here, we will check whether the names and ensembls starts with a reference
38
+ # prefix. 10x's default will append something like mm10_ before the gene names
39
+ # and indices in multi-species joint analysis. if there is no such prefix detected
40
+ # this means the gene comes from the default taxa.
41
+
42
+ if '_' in ens:
43
+ reference_name = ens.split('_')[0]
44
+ pure_ens = ens.replace(reference_name + '_', '')
45
+ pure_nm = nm.replace(reference_name + '_', '')
46
+ if not reference_name in cfg['taxa.reference'].keys():
47
+ warning(f'gene {ens} seems to have a reference prefix, but not registered to taxa.')
48
+ continue
49
+
50
+ reference_taxa = cfg['taxa.reference'][reference_name]
51
+ alt_name_finder = get_mapper_name(reference_taxa)
52
+ alt_ens_finder = get_mapper_ensembl(reference_taxa)
53
+ if ((pure_nm not in alt_name_finder.keys()) or (pure_ens not in alt_ens_finder.keys())):
54
+ update_mapper(reference_taxa, pure_nm, pure_ens)
55
+ if reference_taxa not in mentioned_taxa: mentioned_taxa += [reference_taxa]
56
+
57
+ elif ((nm not in name_finder.keys()) or (ens not in ensembl_finder.keys())):
58
+ update_mapper(taxa, nm, ens)
59
+ if taxa not in mentioned_taxa: mentioned_taxa += [taxa]
60
+
61
+ for taxa in mentioned_taxa:
62
+ save_genome_changes(taxa)
63
+
64
+
24
65
  def adjust_features(path, refine_finder = False, default_taxa = 'mmu'):
25
66
  '''
26
67
  There are three main types of features table that may occur in the matrix data folder.
@@ -91,12 +132,12 @@ def adjust_features(path, refine_finder = False, default_taxa = 'mmu'):
91
132
  reference_taxa = cfg['taxa.reference'][reference_name]
92
133
  alt_name_finder = get_mapper_name(reference_taxa)
93
134
  alt_ens_finder = get_mapper_ensembl(reference_taxa)
94
- if ((pure_nm not in alt_name_finder.keys()) and (pure_ens in alt_ens_finder.keys())):
95
- update_mapper_name(reference_taxa, pure_nm, alt_ens_finder[pure_ens])
135
+ if ((pure_nm not in alt_name_finder.keys()) or (pure_ens not in alt_ens_finder.keys())):
136
+ update_mapper(reference_taxa, pure_nm, pure_ens)
96
137
  if reference_taxa not in mentioned_taxa: mentioned_taxa += [reference_taxa]
97
138
 
98
- elif ((nm not in name_finder.keys()) and (ens in ensembl_finder.keys())):
99
- update_mapper_name(default_taxa, nm, ensembl_finder[ens])
139
+ elif ((nm not in name_finder.keys()) or (ens not in ensembl_finder.keys())):
140
+ update_mapper(default_taxa, nm, ens)
100
141
  if default_taxa not in mentioned_taxa: mentioned_taxa += [default_taxa]
101
142
 
102
143
  for taxa in mentioned_taxa:
@@ -57,17 +57,23 @@ class index_object_handler:
57
57
 
58
58
  def embedding_atlas(
59
59
  adata, basis, color,
60
+ hue_order = None,
60
61
  title = None, figsize = (4, 4), ax = None,
61
- cmap = 'Turbo', legend_loc = 'right margin',frameon = 'small', fontsize = 10
62
+ cmap = 'Turbo', cmap_reverse = False, cmap_lower = '#000000',
63
+ legend = True, legend_loc = 'right margin', legend_col = 1,
64
+ frameon = 'small', fontsize = 10,
65
+ annotate = True, annotate_style = 'index', annot_size = 12,
66
+ annotate_foreground = 'black', annotate_stroke = 'white',
67
+ dpi = 100, sample_name = None
62
68
  ):
63
- setup_styles(**plotting_styles)
69
+ res_factor = 1.5
64
70
  import pandas as pd
65
71
  import datashader as ds
66
72
  import datashader.transfer_functions as tf
67
73
  from scipy.sparse import issparse
68
74
  from exprmat.plotting import palettes
69
75
 
70
- cvs = ds.Canvas(plot_width = 800, plot_height = 800)
76
+ cvs = ds.Canvas(plot_width = int(figsize[0] * dpi * res_factor), plot_height = int(figsize[1] * dpi * res_factor))
71
77
  embedding = adata.obsm[basis]
72
78
  df = pd.DataFrame(embedding, columns=['x', 'y'])
73
79
 
@@ -81,25 +87,51 @@ def embedding_atlas(
81
87
  X = adata.raw[:,color].X
82
88
  if issparse(X): labels = X.toarray().reshape(-1)
83
89
  else: labels = X.reshape(-1)
90
+
91
+ # try some conventions
92
+ elif 'gene' in adata.var.keys() and color in adata.var['gene'].tolist():
93
+ genes = adata.var['gene'].tolist()
94
+ X = adata.X[:, genes.index(color)]
95
+ if issparse(X): labels = X.toarray().reshape(-1)
96
+ else: labels = X.reshape(-1)
97
+
98
+ # try some conventions
99
+ elif 'ensembl' in adata.var.keys() and color in adata.var['ensembl'].tolist():
100
+ genes = adata.var['ensembl'].tolist()
101
+ X = adata.X[:, genes.index(color)]
102
+ if issparse(X): labels = X.toarray().reshape(-1)
103
+ else: labels = X.reshape(-1)
104
+
105
+ else: error(f'unable to find feature `{color}` in metadata or variables.')
84
106
 
85
107
  df['label'] = labels
86
108
 
109
+ if ax is None: fig, ax = plt.subplots(figsize = figsize, dpi = dpi)
110
+ else: fig = ax.figure
111
+
87
112
  if type(labels[0]) is str:
88
113
  df['label'] = df['label'].astype('category')
89
114
  agg = cvs.points(df, 'x', 'y', ds.count_cat('label'))
90
- legend_tag = True
115
+ legend_tag = legend
91
116
 
92
- default_palette = palettes.linear_palette(palettes.all_palettes[cmap][
117
+ original_cat = df['label'].value_counts().index.tolist()
118
+ original_cat = sorted(original_cat, key = lambda s: int(s) if str.isdigit(s) else s)
119
+ hue_order = original_cat if hue_order is None else hue_order
120
+
121
+ default_palette = list(palettes.linear_palette(palettes.all_palettes[cmap][
93
122
  list(palettes.all_palettes[cmap].keys())[-1]
94
- ], len(adata.obs[color].cat.categories))
123
+ ], len(adata.obs[color].cat.categories))) if isinstance(cmap, str) else cmap
95
124
 
96
125
  if f'{color}.colors' not in adata.uns.keys():
97
126
  adata.uns[f'{color}.colors'] = default_palette
98
127
  elif len(adata.uns[f'{color}.colors']) != len(default_palette):
99
128
  adata.uns[f'{color}.colors'] = default_palette
129
+ elif not isinstance(adata.uns[f'{color}.colors'], list):
130
+ adata.uns[f'{color}.colors'] = list(adata.uns[f'{color}.colors'])
100
131
 
132
+ default_palette = adata.uns[f'{color}.colors']
101
133
  color_key = dict(zip(
102
- adata.obs[color].cat.categories,
134
+ hue_order,
103
135
  adata.uns[f'{color}.colors']
104
136
  ))
105
137
 
@@ -108,7 +140,65 @@ def embedding_atlas(
108
140
  color_key = [color_key[i] for i in df['label'].cat.categories],
109
141
  how = 'eq_hist'
110
142
  )
143
+
144
+ ax.imshow(
145
+ img.to_pil(),
146
+ aspect = 'auto',
147
+ interpolation = 'bicubic',
148
+ extent = (0, dpi * figsize[0] * res_factor, 0, dpi * figsize[1] * res_factor)
149
+ )
150
+
151
+ if legend:
152
+ assert len(adata.uns[f'{color}.colors']) == len(hue_order)
153
+ dummy_objects = []
154
+ legend_artists = {}
155
+ for legend_t, legend_c, legend_id in zip(
156
+ hue_order, adata.uns[f'{color}.colors'], range(len(hue_order))
157
+ ):
158
+ dummy = index_object()
159
+ dummy_objects += [dummy]
160
+ handler = index_object_handler()
161
+ handler.set_option(legend_id + 1, legend_t, legend_c)
162
+ legend_artists[dummy] = handler
163
+ pass
164
+
165
+ plt.legend(
166
+ dummy_objects, hue_order, handler_map = legend_artists, ncol = legend_col,
167
+ loc = 'upper left', bbox_to_anchor = (1, 1), frameon = False
168
+ )
169
+
170
+ if annotate:
171
+
172
+ scale_max_x = dpi * figsize[0] * res_factor
173
+ scale_max_y = dpi * figsize[1] * res_factor
174
+ min_x, min_y, max_x, max_y = np.min(df['x']), np.min(df['y']), np.max(df['x']), np.max(df['y'])
175
+
176
+ for legend_t, legend_c, legend_id in zip(
177
+ hue_order, adata.uns[f'{color}.colors'], range(len(hue_order))
178
+ ):
179
+ # calculate gravity for legend_t class.
180
+ xs = np.array(df.loc[df['label'] == legend_t, 'x'])
181
+ ys = np.array(df.loc[df['label'] == legend_t, 'y'])
182
+
183
+ mx, my = np.mean(xs), np.mean(ys)
184
+ mx = scale_max_x * ((mx - min_x) / (max_x - min_x))
185
+ my = scale_max_y * ((my - min_y) / (max_y - min_y))
186
+ text = mtext.Text(
187
+ x = mx, y = my, # fontproperties = 'bold',
188
+ text = str(legend_id + 1) if annotate_style == 'index' else legend_t,
189
+ color = annotate_foreground,
190
+ ha = 'center', va = 'center', size = annot_size
191
+ )
192
+
193
+ text.set_path_effects([
194
+ mpe.Stroke(linewidth = 3, foreground = annotate_stroke),
195
+ mpe.Normal()
196
+ ])
197
+
198
+ ax.add_artist(text)
199
+ pass
111
200
 
201
+
112
202
  elif (type(labels[0]) is int) or \
113
203
  (type(labels[0]) is float) or \
114
204
  (type(labels[0]) is np.float32) or \
@@ -117,36 +207,49 @@ def embedding_atlas(
117
207
 
118
208
  agg = cvs.points(df, 'x', 'y', ds.mean('label'))
119
209
  legend_tag = False
120
- if cmap in palettes.all_palettes.keys():
121
- num = list(palettes.all_palettes[cmap].keys())[-1]
122
- img = tf.shade(agg,cmap = palettes.all_palettes[cmap][num])
123
- else: img = tf.shade(agg,cmap = cmap)
210
+
211
+ cmap = list(palettes.interp_palette(palettes.all_palettes[cmap][
212
+ list(palettes.all_palettes[cmap].keys())[-1]
213
+ ], 256)) if isinstance(cmap, str) else cmap
214
+
215
+ if cmap_reverse:
216
+ cmap = cmap[::-1]
217
+
218
+ # np.array([0.95, 0.95, 0.98, 1]) # a tint of bluish gray
219
+ if cmap_lower is not None:
220
+ for ix in range(5): cmap[ix] = cmap_lower
221
+
222
+ cmap = listedcm(cmap)
223
+ img = tf.shade(agg,cmap = cmap)
224
+
225
+ ax.imshow(
226
+ img.to_pil(),
227
+ aspect = 'auto',
228
+ interpolation = 'bicubic',
229
+ extent = (0, dpi * figsize[0] * res_factor, 0, dpi * figsize[1] * res_factor)
230
+ )
124
231
 
125
232
  else:
126
233
  warning('color label must be categorical (string) or numerical.')
127
234
  return None
128
235
 
129
- if ax is None: fig, ax = plt.subplots(figsize=figsize)
130
- else: fig = ax.figure
131
-
132
- ax.imshow(img.to_pil(), aspect = 'auto')
133
-
236
+
134
237
  def format_coord(x, y): return f"x = {x:.2f}, y = {y:.2f}"
135
238
  ax.format_coord = format_coord
136
239
 
137
- if legend_tag == True:
138
- unique_labels = adata.obs[color].cat.categories
139
- for label in unique_labels:
140
- ax.scatter([], [], c = color_key[label], label = label)
141
-
142
- if legend_loc == 'right margin':
143
- ax.legend(
144
- frameon = False,
145
- loc = 'center left',
146
- bbox_to_anchor = (1, 0.5),
147
- ncol = (1 if len(unique_labels) <= 14 else 2 if len(unique_labels) <= 30 else 3),
148
- fontsize = fontsize,
149
- )
240
+ # if legend_tag == True:
241
+ # unique_labels = adata.obs[color].cat.categories
242
+ # for label in unique_labels:
243
+ # ax.scatter([], [], c = color_key[label], label = label)
244
+ #
245
+ # if legend_loc == 'right margin':
246
+ # ax.legend(
247
+ # frameon = False,
248
+ # loc = 'center left',
249
+ # bbox_to_anchor = (1, 0.5),
250
+ # ncol = (1 if len(unique_labels) <= 14 else 2 if len(unique_labels) <= 30 else 3),
251
+ # fontsize = fontsize,
252
+ # )
150
253
 
151
254
  if frameon == False:
152
255
  ax.axis('off')
@@ -159,8 +262,6 @@ def embedding_atlas(
159
262
  ax.spines['bottom'].set_visible(True)
160
263
  ax.spines['top'].set_visible(False)
161
264
  ax.spines['right'].set_visible(False)
162
- # ax.spines['bottom'].set_bounds(0,150)
163
- # ax.spines['left'].set_bounds(0,150)
164
265
  ax.set_xlabel(f'{basis}.1',loc='left',fontsize = fontsize)
165
266
  ax.set_ylabel(f'{basis}.2',loc='bottom',fontsize = fontsize)
166
267
 
@@ -175,13 +276,15 @@ def embedding_atlas(
175
276
  ax.set_xlabel(f'{basis}.1',loc='center',fontsize=fontsize)
176
277
  ax.set_ylabel(f'{basis}.2',loc='center',fontsize=fontsize)
177
278
 
178
- line_width = 1
279
+ line_width = 0.6
179
280
  ax.spines['left'].set_linewidth(line_width)
180
281
  ax.spines['bottom'].set_linewidth(line_width)
181
282
  if title is None: title = color
182
- ax.set_title(title,fontsize = fontsize)
283
+ ax.set_title(title,fontsize = fontsize * 1.2)
284
+ ax.set_xlim(dpi * figsize[0] * -0.05 * res_factor, dpi * figsize[0] * 1.05 * res_factor)
285
+ ax.set_ylim(dpi * figsize[1] * -0.05 * res_factor, dpi * figsize[1] * 1.05 * res_factor)
183
286
 
184
- return ax
287
+ return ax.figure
185
288
 
186
289
 
187
290
  def embedding(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: exprmat
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: routines to process expression matrices
5
5
  Home-page: https://github.com/yang-z-03/exprmat
6
6
  Author: Zheng Yang
@@ -28,6 +28,7 @@ Requires-Dist: datashader
28
28
  Requires-Dist: fa2_modified
29
29
  Requires-Dist: pynndescent
30
30
  Requires-Dist: gseapy
31
+ Requires-Dist: sh
31
32
  Dynamic: author
32
33
  Dynamic: author-email
33
34
  Dynamic: home-page
@@ -9,9 +9,9 @@ exprmat.egg-info/dependency_links.txt
9
9
  exprmat.egg-info/not-zip-safe
10
10
  exprmat.egg-info/requires.txt
11
11
  exprmat.egg-info/top_level.txt
12
- exprmat.egg-info/.ipynb_checkpoints/requires-checkpoint.txt
13
12
  exprmat/clustering/__init__.py
14
13
  exprmat/clustering/leiden.py
14
+ exprmat/clustering/rogue.py
15
15
  exprmat/clustering/spectral.py
16
16
  exprmat/clustering/utils.py
17
17
  exprmat/cnv/__init__.py
@@ -20,3 +20,4 @@ datashader
20
20
  fa2_modified
21
21
  pynndescent
22
22
  gseapy
23
+ sh
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
3
3
 
4
4
  setup(
5
5
  name = 'exprmat',
6
- version = '0.1.5',
6
+ version = '0.1.6',
7
7
  description = 'routines to process expression matrices',
8
8
  url = 'https://github.com/yang-z-03/exprmat',
9
9
  author = 'Zheng Yang',
@@ -32,7 +32,8 @@ setup(
32
32
  'datashader',
33
33
  'fa2_modified',
34
34
  'pynndescent',
35
- 'gseapy'
35
+ 'gseapy',
36
+ 'sh'
36
37
  ],
37
38
  include_package_data = False,
38
39
  package_data = {
@@ -1,22 +0,0 @@
1
- umap-learn>=0.5.0
2
- scvi-tools>=1.3.0
3
- scanpy
4
- anndata
5
- mudata
6
- pandas
7
- numpy
8
- matplotlib
9
- seaborn
10
- torch>=2.0
11
- annoy
12
- scikit-image
13
- scikit-misc
14
- igraph
15
- bbknn
16
- scanorama
17
- harmonypy
18
- pymde
19
- datashader
20
- fa2_modified
21
- pynndescent
22
- gseapy
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes