scMultiChat 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- MultiChat/Analysis/Intra_strength.py +1758 -0
- MultiChat/Analysis/Processing.py +152 -0
- MultiChat/Analysis/__init__.py +2 -0
- MultiChat/Heterogeneous_g_emb/__init__.py +13 -0
- MultiChat/Heterogeneous_g_emb/_settings.py +156 -0
- MultiChat/Heterogeneous_g_emb/_utils.py +143 -0
- MultiChat/Heterogeneous_g_emb/_version.py +3 -0
- MultiChat/Heterogeneous_g_emb/plotting/__init__.py +19 -0
- MultiChat/Heterogeneous_g_emb/plotting/_palettes.py +180 -0
- MultiChat/Heterogeneous_g_emb/plotting/_plot.py +1498 -0
- MultiChat/Heterogeneous_g_emb/plotting/_post_training.py +742 -0
- MultiChat/Heterogeneous_g_emb/plotting/_utils.py +103 -0
- MultiChat/Heterogeneous_g_emb/preprocessing/__init__.py +26 -0
- MultiChat/Heterogeneous_g_emb/preprocessing/_general.py +91 -0
- MultiChat/Heterogeneous_g_emb/preprocessing/_pca.py +182 -0
- MultiChat/Heterogeneous_g_emb/preprocessing/_qc.py +727 -0
- MultiChat/Heterogeneous_g_emb/preprocessing/_utils.py +60 -0
- MultiChat/Heterogeneous_g_emb/preprocessing/_variable_genes.py +82 -0
- MultiChat/Heterogeneous_g_emb/readwrite.py +250 -0
- MultiChat/Heterogeneous_g_emb/tools/__init__.py +23 -0
- MultiChat/Heterogeneous_g_emb/tools/_gene_scores.py +346 -0
- MultiChat/Heterogeneous_g_emb/tools/_general.py +71 -0
- MultiChat/Heterogeneous_g_emb/tools/_integration.py +197 -0
- MultiChat/Heterogeneous_g_emb/tools/_pbg.py +1184 -0
- MultiChat/Heterogeneous_g_emb/tools/_post_training.py +919 -0
- MultiChat/Heterogeneous_g_emb/tools/_umap.py +58 -0
- MultiChat/Heterogeneous_g_emb/tools/_utils.py +253 -0
- MultiChat/Model/Layers.py +116 -0
- MultiChat/Model/__init__.py +3 -0
- MultiChat/Model/model_training.py +166 -0
- MultiChat/Model/modules.py +93 -0
- MultiChat/Model/utilities.py +234 -0
- MultiChat/Plot/Visualization.py +470 -0
- MultiChat/Plot/__init__.py +1 -0
- MultiChat/__init__.py +12 -0
- scmultichat-0.1.0.dist-info/METADATA +156 -0
- scmultichat-0.1.0.dist-info/RECORD +39 -0
- scmultichat-0.1.0.dist-info/WHEEL +5 -0
- scmultichat-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Utility functions and classes"""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from pandas.api.types import (
|
|
6
|
+
is_numeric_dtype,
|
|
7
|
+
is_string_dtype,
|
|
8
|
+
is_categorical_dtype,
|
|
9
|
+
)
|
|
10
|
+
import matplotlib as mpl
|
|
11
|
+
|
|
12
|
+
from ._palettes import (
|
|
13
|
+
default_20,
|
|
14
|
+
default_28,
|
|
15
|
+
default_102
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_colors(arr,
|
|
20
|
+
vmin=None,
|
|
21
|
+
vmax=None,
|
|
22
|
+
clip=False):
|
|
23
|
+
"""Generate a list of colors for a given array
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
if not isinstance(arr, (pd.Series, np.ndarray)):
|
|
27
|
+
raise TypeError("`arr` must be pd.Series or np.ndarray")
|
|
28
|
+
colors = []
|
|
29
|
+
if is_numeric_dtype(arr):
|
|
30
|
+
image_cmap = mpl.rcParams['image.cmap']
|
|
31
|
+
cm = mpl.cm.get_cmap(image_cmap, 512)
|
|
32
|
+
if vmin is None:
|
|
33
|
+
vmin = min(arr)
|
|
34
|
+
if vmax is None:
|
|
35
|
+
vmax = max(arr)
|
|
36
|
+
norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax, clip=clip)
|
|
37
|
+
colors = [mpl.colors.to_hex(cm(norm(x))) for x in arr]
|
|
38
|
+
elif is_string_dtype(arr) or is_categorical_dtype(arr):
|
|
39
|
+
categories = np.unique(arr)
|
|
40
|
+
length = len(categories)
|
|
41
|
+
# check if default matplotlib palette has enough colors
|
|
42
|
+
# mpl.style.use('default')
|
|
43
|
+
if len(mpl.rcParams['axes.prop_cycle'].by_key()['color']) >= length:
|
|
44
|
+
cc = mpl.rcParams['axes.prop_cycle']()
|
|
45
|
+
palette = [mpl.colors.rgb2hex(next(cc)['color'])
|
|
46
|
+
for _ in range(length)]
|
|
47
|
+
else:
|
|
48
|
+
if length <= 20:
|
|
49
|
+
palette = default_20
|
|
50
|
+
elif length <= 28:
|
|
51
|
+
palette = default_28
|
|
52
|
+
elif length <= len(default_102): # 103 colors
|
|
53
|
+
palette = default_102
|
|
54
|
+
else:
|
|
55
|
+
rgb_rainbow = mpl.cm.rainbow(np.linspace(0, 1, length))
|
|
56
|
+
palette = [mpl.colors.rgb2hex(rgb_rainbow[i, :-1])
|
|
57
|
+
for i in range(length)]
|
|
58
|
+
colors = pd.Series(['']*len(arr))
|
|
59
|
+
for i, x in enumerate(categories):
|
|
60
|
+
ids = np.where(arr == x)[0]
|
|
61
|
+
colors[ids] = palette[i]
|
|
62
|
+
colors = list(colors)
|
|
63
|
+
else:
|
|
64
|
+
raise TypeError("unsupported data type for `arr`")
|
|
65
|
+
return colors
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def generate_palette(arr):
|
|
69
|
+
"""Generate a color palette for a given array
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
if not isinstance(arr, (pd.Series, np.ndarray)):
|
|
73
|
+
raise TypeError("`arr` must be pd.Series or np.ndarray")
|
|
74
|
+
colors = []
|
|
75
|
+
if is_string_dtype(arr) or is_categorical_dtype(arr):
|
|
76
|
+
categories = np.unique(arr)
|
|
77
|
+
length = len(categories)
|
|
78
|
+
# check if default matplotlib palette has enough colors
|
|
79
|
+
# mpl.style.use('default')
|
|
80
|
+
if len(mpl.rcParams['axes.prop_cycle'].by_key()['color']) >= length:
|
|
81
|
+
cc = mpl.rcParams['axes.prop_cycle']()
|
|
82
|
+
palette = [mpl.colors.rgb2hex(next(cc)['color'])
|
|
83
|
+
for _ in range(length)]
|
|
84
|
+
else:
|
|
85
|
+
if length <= 20:
|
|
86
|
+
palette = default_20
|
|
87
|
+
elif length <= 28:
|
|
88
|
+
palette = default_28
|
|
89
|
+
elif length <= len(default_102): # 103 colors
|
|
90
|
+
palette = default_102
|
|
91
|
+
else:
|
|
92
|
+
rgb_rainbow = mpl.cm.rainbow(np.linspace(0, 1, length))
|
|
93
|
+
palette = [mpl.colors.rgb2hex(rgb_rainbow[i, :-1])
|
|
94
|
+
for i in range(length)]
|
|
95
|
+
colors = pd.Series(['']*len(arr))
|
|
96
|
+
for i, x in enumerate(categories):
|
|
97
|
+
ids = np.where(arr == x)[0]
|
|
98
|
+
colors[ids] = palette[i]
|
|
99
|
+
colors = list(colors)
|
|
100
|
+
else:
|
|
101
|
+
raise TypeError("unsupported data type for `arr`")
|
|
102
|
+
dict_palette = dict(zip(arr, colors))
|
|
103
|
+
return dict_palette
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Preprocessing"""
|
|
2
|
+
|
|
3
|
+
from ._general import (
|
|
4
|
+
log_transform,
|
|
5
|
+
normalize,
|
|
6
|
+
binarize
|
|
7
|
+
)
|
|
8
|
+
from ._qc import (
|
|
9
|
+
cal_qc,
|
|
10
|
+
cal_qc_rna,
|
|
11
|
+
cal_qc_atac,
|
|
12
|
+
filter_samples,
|
|
13
|
+
filter_cells_rna,
|
|
14
|
+
filter_cells_atac,
|
|
15
|
+
filter_features,
|
|
16
|
+
filter_genes,
|
|
17
|
+
filter_peaks,
|
|
18
|
+
)
|
|
19
|
+
from ._pca import (
|
|
20
|
+
pca,
|
|
21
|
+
select_pcs,
|
|
22
|
+
select_pcs_features,
|
|
23
|
+
)
|
|
24
|
+
from ._variable_genes import (
|
|
25
|
+
select_variable_genes
|
|
26
|
+
)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""General preprocessing functions"""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from sklearn.utils import sparsefuncs
|
|
5
|
+
from sklearn import preprocessing
|
|
6
|
+
from ._utils import (
|
|
7
|
+
cal_tf_idf
|
|
8
|
+
)
|
|
9
|
+
from scipy.sparse import (
|
|
10
|
+
issparse,
|
|
11
|
+
csr_matrix,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def log_transform(adata):
|
|
16
|
+
"""Return the natural logarithm of one plus the input array, element-wise.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
adata: AnnData
|
|
21
|
+
Annotated data matrix.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
updates `adata` with the following fields.
|
|
26
|
+
X: `numpy.ndarray` (`adata.X`)
|
|
27
|
+
Store #observations × #var_genes logarithmized data matrix.
|
|
28
|
+
"""
|
|
29
|
+
if not issparse(adata.X):
|
|
30
|
+
adata.X = csr_matrix(adata.X)
|
|
31
|
+
adata.X = np.log1p(adata.X)
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def binarize(adata,
|
|
36
|
+
threshold=1e-5):
|
|
37
|
+
"""Binarize an array.
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
adata: AnnData
|
|
41
|
+
Annotated data matrix.
|
|
42
|
+
threshold: `float`, optional (default: 1e-5)
|
|
43
|
+
Values below or equal to this are replaced by 0, above it by 1.
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
updates `adata` with the following fields.
|
|
48
|
+
X: `numpy.ndarray` (`adata.X`)
|
|
49
|
+
Store #observations × #var_genes binarized data matrix.
|
|
50
|
+
"""
|
|
51
|
+
if not issparse(adata.X):
|
|
52
|
+
adata.X = csr_matrix(adata.X)
|
|
53
|
+
adata.X = preprocessing.binarize(adata.X,
|
|
54
|
+
threshold=threshold,
|
|
55
|
+
copy=True)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def normalize(adata,
|
|
59
|
+
method='lib_size',
|
|
60
|
+
scale_factor=1e4,
|
|
61
|
+
save_raw=True):
|
|
62
|
+
"""Normalize count matrix.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
adata: AnnData
|
|
67
|
+
Annotated data matrix.
|
|
68
|
+
method: `str`, optional (default: 'lib_size')
|
|
69
|
+
Choose from {{'lib_size','tf_idf'}}
|
|
70
|
+
Method used for dimension reduction.
|
|
71
|
+
'lib_size': Total-count normalize (library-size correct)
|
|
72
|
+
'tf_idf': TF-IDF (term frequency–inverse document frequency)
|
|
73
|
+
transformation
|
|
74
|
+
|
|
75
|
+
Returns
|
|
76
|
+
-------
|
|
77
|
+
updates `adata` with the following fields.
|
|
78
|
+
X: `numpy.ndarray` (`adata.X`)
|
|
79
|
+
Store #observations × #var_genes normalized data matrix.
|
|
80
|
+
"""
|
|
81
|
+
if method not in ['lib_size', 'tf_idf']:
|
|
82
|
+
raise ValueError("unrecognized method '%s'" % method)
|
|
83
|
+
if not issparse(adata.X):
|
|
84
|
+
adata.X = csr_matrix(adata.X)
|
|
85
|
+
if save_raw:
|
|
86
|
+
adata.layers['raw'] = adata.X.copy()
|
|
87
|
+
if method == 'lib_size':
|
|
88
|
+
sparsefuncs.inplace_row_scale(adata.X, 1/adata.X.sum(axis=1).A)
|
|
89
|
+
adata.X = adata.X*scale_factor
|
|
90
|
+
if method == 'tf_idf':
|
|
91
|
+
adata.X = cal_tf_idf(adata.X)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Principal component analysis"""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from sklearn.decomposition import TruncatedSVD
|
|
5
|
+
from ._utils import (
|
|
6
|
+
locate_elbow,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def pca(adata,
|
|
11
|
+
n_components=50,
|
|
12
|
+
algorithm='randomized',
|
|
13
|
+
n_iter=5,
|
|
14
|
+
random_state=2021,
|
|
15
|
+
tol=0.0,
|
|
16
|
+
feature=None,
|
|
17
|
+
**kwargs,
|
|
18
|
+
):
|
|
19
|
+
"""perform Principal Component Analysis (PCA)
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
adata: AnnData
|
|
24
|
+
Annotated data matrix.
|
|
25
|
+
n_components: `int`, optional (default: 50)
|
|
26
|
+
Desired dimensionality of output data
|
|
27
|
+
algorithm: `str`, optional (default: 'randomized')
|
|
28
|
+
SVD solver to use. Choose from {'arpack', 'randomized'}.
|
|
29
|
+
n_iter: `int`, optional (default: '5')
|
|
30
|
+
Number of iterations for randomized SVD solver.
|
|
31
|
+
Not used by ARPACK.
|
|
32
|
+
tol: `float`, optional (default: 0)
|
|
33
|
+
Tolerance for ARPACK. 0 means machine precision.
|
|
34
|
+
Ignored by randomized SVD solver.
|
|
35
|
+
feature: `str`, optional (default: None)
|
|
36
|
+
Feature used to perform PCA.
|
|
37
|
+
The data type of `.var[feature]` needs to be `bool`
|
|
38
|
+
If None, adata.X will be used.
|
|
39
|
+
kwargs:
|
|
40
|
+
Other keyword arguments are passed down to `TruncatedSVD()`
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
updates `adata` with the following fields:
|
|
45
|
+
`.obsm['X_pca']` : `array`
|
|
46
|
+
PCA transformed X.
|
|
47
|
+
`.uns['pca']['PCs']` : `array`
|
|
48
|
+
Principal components in feature space,
|
|
49
|
+
representing the directions of maximum variance in the data.
|
|
50
|
+
`.uns['pca']['variance']` : `array`
|
|
51
|
+
The variance of the training samples transformed by a
|
|
52
|
+
projection to each component.
|
|
53
|
+
`.uns['pca']['variance_ratio']` : `array`
|
|
54
|
+
Percentage of variance explained by each of the selected components.
|
|
55
|
+
"""
|
|
56
|
+
if feature is None:
|
|
57
|
+
X = adata.X.copy()
|
|
58
|
+
else:
|
|
59
|
+
mask = adata.var[feature]
|
|
60
|
+
X = adata[:, mask].X.copy()
|
|
61
|
+
svd = TruncatedSVD(n_components=n_components,
|
|
62
|
+
algorithm=algorithm,
|
|
63
|
+
n_iter=n_iter,
|
|
64
|
+
random_state=random_state,
|
|
65
|
+
tol=tol,
|
|
66
|
+
**kwargs)
|
|
67
|
+
svd.fit(X)
|
|
68
|
+
adata.obsm['X_pca'] = svd.transform(X)
|
|
69
|
+
adata.uns['pca'] = dict()
|
|
70
|
+
adata.uns['pca']['n_pcs'] = n_components
|
|
71
|
+
adata.uns['pca']['PCs'] = svd.components_.T
|
|
72
|
+
adata.uns['pca']['variance'] = svd.explained_variance_
|
|
73
|
+
adata.uns['pca']['variance_ratio'] = svd.explained_variance_ratio_
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def select_pcs(adata,
|
|
77
|
+
n_pcs=None,
|
|
78
|
+
S=1,
|
|
79
|
+
curve='convex',
|
|
80
|
+
direction='decreasing',
|
|
81
|
+
online=False,
|
|
82
|
+
min_elbow=None,
|
|
83
|
+
**kwargs):
|
|
84
|
+
"""select top PCs based on variance_ratio
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
n_pcs: `int`, optional (default: None)
|
|
89
|
+
If n_pcs is None,
|
|
90
|
+
the number of PCs will be automatically selected with "`kneed
|
|
91
|
+
<https://kneed.readthedocs.io/>`__"
|
|
92
|
+
S : `float`, optional (default: 1)
|
|
93
|
+
Sensitivity
|
|
94
|
+
min_elbow: `int`, optional (default: None)
|
|
95
|
+
The minimum elbow location
|
|
96
|
+
By default, it is n_components/10
|
|
97
|
+
curve: `str`, optional (default: 'convex')
|
|
98
|
+
Choose from {'convex','concave'}
|
|
99
|
+
If 'concave', algorithm will detect knees,
|
|
100
|
+
If 'convex', algorithm will detect elbows.
|
|
101
|
+
direction: `str`, optional (default: 'decreasing')
|
|
102
|
+
Choose from {'decreasing','increasing'}
|
|
103
|
+
online: `bool`, optional (default: False)
|
|
104
|
+
kneed will correct old knee points if True,
|
|
105
|
+
kneed will return first knee if False.
|
|
106
|
+
**kwargs: `dict`, optional
|
|
107
|
+
Extra arguments to KneeLocator.
|
|
108
|
+
Returns
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
if n_pcs is None:
|
|
112
|
+
n_components = adata.obsm['X_pca'].shape[1]
|
|
113
|
+
if min_elbow is None:
|
|
114
|
+
min_elbow = n_components/10
|
|
115
|
+
n_pcs = locate_elbow(range(n_components),
|
|
116
|
+
adata.uns['pca']['variance_ratio'],
|
|
117
|
+
S=S,
|
|
118
|
+
curve=curve,
|
|
119
|
+
min_elbow=min_elbow,
|
|
120
|
+
direction=direction,
|
|
121
|
+
online=online,
|
|
122
|
+
**kwargs)
|
|
123
|
+
adata.uns['pca']['n_pcs'] = n_pcs
|
|
124
|
+
else:
|
|
125
|
+
adata.uns['pca']['n_pcs'] = n_pcs
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def select_pcs_features(adata,
|
|
129
|
+
S=1,
|
|
130
|
+
curve='convex',
|
|
131
|
+
direction='decreasing',
|
|
132
|
+
online=False,
|
|
133
|
+
min_elbow=None,
|
|
134
|
+
**kwargs):
|
|
135
|
+
"""select features that contribute to the top PCs
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
S : `float`, optional (default: 10)
|
|
140
|
+
Sensitivity
|
|
141
|
+
min_elbow: `int`, optional (default: None)
|
|
142
|
+
The minimum elbow location.
|
|
143
|
+
By default, it is #features/6
|
|
144
|
+
curve: `str`, optional (default: 'convex')
|
|
145
|
+
Choose from {'convex','concave'}
|
|
146
|
+
If 'concave', algorithm will detect knees,
|
|
147
|
+
If 'convex', algorithm will detect elbows.
|
|
148
|
+
direction: `str`, optional (default: 'decreasing')
|
|
149
|
+
Choose from {'decreasing','increasing'}
|
|
150
|
+
online: `bool`, optional (default: False)
|
|
151
|
+
kneed will correct old knee points if True,
|
|
152
|
+
kneed will return first knee if False.
|
|
153
|
+
**kwargs: `dict`, optional
|
|
154
|
+
Extra arguments to KneeLocator.
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
"""
|
|
158
|
+
n_pcs = adata.uns['pca']['n_pcs']
|
|
159
|
+
n_features = adata.uns['pca']['PCs'].shape[0]
|
|
160
|
+
if min_elbow is None:
|
|
161
|
+
min_elbow = n_features/6
|
|
162
|
+
adata.uns['pca']['features'] = dict()
|
|
163
|
+
ids_features = list()
|
|
164
|
+
for i in range(n_pcs):
|
|
165
|
+
elbow = locate_elbow(range(n_features),
|
|
166
|
+
np.sort(
|
|
167
|
+
np.abs(adata.uns['pca']['PCs'][:, i],))[::-1],
|
|
168
|
+
S=S,
|
|
169
|
+
min_elbow=min_elbow,
|
|
170
|
+
curve=curve,
|
|
171
|
+
direction=direction,
|
|
172
|
+
online=online,
|
|
173
|
+
**kwargs)
|
|
174
|
+
ids_features_i = \
|
|
175
|
+
list(np.argsort(np.abs(
|
|
176
|
+
adata.uns['pca']['PCs'][:, i],))[::-1][:elbow])
|
|
177
|
+
adata.uns['pca']['features'][f'pc_{i}'] = ids_features_i
|
|
178
|
+
ids_features = ids_features + ids_features_i
|
|
179
|
+
print(f'#features selected from PC {i}: {len(ids_features_i)}')
|
|
180
|
+
adata.var['top_pcs'] = False
|
|
181
|
+
adata.var.loc[adata.var_names[np.unique(ids_features)], 'top_pcs'] = True
|
|
182
|
+
print(f'#features in total: {adata.var["top_pcs"].sum()}')
|