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.
- {exprmat-0.1.5 → exprmat-0.1.6}/PKG-INFO +2 -1
- exprmat-0.1.6/exprmat/clustering/rogue.py +333 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/data/finders.py +31 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/plotting/palettes.py +1 -1
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/__init__.py +1 -1
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/plot.py +21 -1
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/qc.py +10 -9
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reader/experiment.py +127 -25
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reader/matcher.py +46 -5
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/plot.py +137 -34
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat.egg-info/PKG-INFO +2 -1
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat.egg-info/SOURCES.txt +1 -1
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat.egg-info/requires.txt +1 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/setup.py +3 -2
- exprmat-0.1.5/exprmat.egg-info/.ipynb_checkpoints/requires-checkpoint.txt +0 -22
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/__init__.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/ansi.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/clustering/__init__.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/clustering/leiden.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/clustering/spectral.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/clustering/utils.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/cnv/__init__.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/cnv/cnvscore.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/cnv/infercnv.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/configuration.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/data/__init__.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/data/delim.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/data/gff.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/data/hsa/genome.tsv.gz +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/data/mmu/genome.tsv.gz +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/data/private.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/data/ranges.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/descriptive/__init__.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/descriptive/de.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/descriptive/kde.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/plotting/__init__.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/plotting/cnv.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/plotting/de.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/plotting/expression.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/doublets/__init__.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/doublets/composite.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/doublets/scrublet.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/filter.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/integrate.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/linear.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/normal.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/sctransform/__init__.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/sctransform/estimate.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/sctransform/ksmooth.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/sctransform/residual.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/sctransform/transform.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/sparse.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/preprocessing/uniform.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reader/__init__.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reader/metadata.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/__init__.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/forceatlas.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/mde.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/nn.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/pca.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/pumap.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/tsne.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/reduction/umap.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat/utils.py +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat.egg-info/dependency_links.txt +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat.egg-info/not-zip-safe +0 -0
- {exprmat-0.1.5 → exprmat-0.1.6}/exprmat.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|
|
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
|
-
|
|
157
|
-
subset.obs['score.doublet']
|
|
158
|
-
|
|
159
|
-
subset.obs['score.doublet.se']
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
subset.obs['is.doublet']
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
loaded =
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
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,
|
|
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())
|
|
95
|
-
|
|
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())
|
|
99
|
-
|
|
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',
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
115
|
+
legend_tag = legend
|
|
91
116
|
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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 =
|
|
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.
|
|
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
|
|
@@ -3,7 +3,7 @@ from setuptools import setup, find_packages
|
|
|
3
3
|
|
|
4
4
|
setup(
|
|
5
5
|
name = 'exprmat',
|
|
6
|
-
version = '0.1.
|
|
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
|
|
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
|
|
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
|
|
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
|