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,60 @@
|
|
|
1
|
+
"""Utility functions and classes"""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from kneed import KneeLocator
|
|
5
|
+
from scipy.sparse import csr_matrix, diags
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def locate_elbow(x, y, S=10, min_elbow=0,
|
|
9
|
+
curve='convex', direction='decreasing', online=False,
|
|
10
|
+
**kwargs):
|
|
11
|
+
"""Detect knee points
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
x : `array_like`
|
|
16
|
+
x values
|
|
17
|
+
y : `array_like`
|
|
18
|
+
y values
|
|
19
|
+
S : `float`, optional (default: 10)
|
|
20
|
+
Sensitivity
|
|
21
|
+
min_elbow: `int`, optional (default: 0)
|
|
22
|
+
The minimum elbow location
|
|
23
|
+
curve: `str`, optional (default: 'convex')
|
|
24
|
+
Choose from {'convex','concave'}
|
|
25
|
+
If 'concave', algorithm will detect knees,
|
|
26
|
+
If 'convex', algorithm will detect elbows.
|
|
27
|
+
direction: `str`, optional (default: 'decreasing')
|
|
28
|
+
Choose from {'decreasing','increasing'}
|
|
29
|
+
online: `bool`, optional (default: False)
|
|
30
|
+
kneed will correct old knee points if True,
|
|
31
|
+
kneed will return first knee if False.
|
|
32
|
+
**kwargs: `dict`, optional
|
|
33
|
+
Extra arguments to KneeLocator.
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
elbow: `int`
|
|
38
|
+
elbow point
|
|
39
|
+
"""
|
|
40
|
+
kneedle = KneeLocator(x[int(min_elbow):], y[int(min_elbow):],
|
|
41
|
+
S=S, curve=curve,
|
|
42
|
+
direction=direction,
|
|
43
|
+
online=online,
|
|
44
|
+
**kwargs,
|
|
45
|
+
)
|
|
46
|
+
if kneedle.elbow is None:
|
|
47
|
+
elbow = len(y)
|
|
48
|
+
else:
|
|
49
|
+
elbow = int(kneedle.elbow)
|
|
50
|
+
return elbow
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def cal_tf_idf(mat):
|
|
54
|
+
"""Transform a count matrix to a tf-idf representation
|
|
55
|
+
"""
|
|
56
|
+
mat = csr_matrix(mat)
|
|
57
|
+
tf = csr_matrix(mat/(mat.sum(axis=0)))
|
|
58
|
+
idf = np.array(np.log(1 + mat.shape[1] / mat.sum(axis=1))).flatten()
|
|
59
|
+
tf_idf = csr_matrix(np.dot(diags(idf), tf))
|
|
60
|
+
return tf_idf
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Preprocess"""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.sparse import (
|
|
5
|
+
csr_matrix,
|
|
6
|
+
)
|
|
7
|
+
from sklearn.utils import sparsefuncs
|
|
8
|
+
from skmisc.loess import loess
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def select_variable_genes(adata,
|
|
12
|
+
layer='raw',
|
|
13
|
+
span=0.3,
|
|
14
|
+
n_top_genes=2000,
|
|
15
|
+
):
|
|
16
|
+
"""Select highly variable genes.
|
|
17
|
+
|
|
18
|
+
This function implenments the method 'vst' in Seurat v3.
|
|
19
|
+
Inspired by Scanpy.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
adata: AnnData
|
|
24
|
+
Annotated data matrix.
|
|
25
|
+
layer: `str`, optional (default: 'raw')
|
|
26
|
+
The layer to use for calculating variable genes.
|
|
27
|
+
span: `float`, optional (default: 0.3)
|
|
28
|
+
Loess smoothing factor
|
|
29
|
+
n_top_genes: `int`, optional (default: 2000)
|
|
30
|
+
The number of genes to keep
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
updates `adata` with the following fields.
|
|
35
|
+
|
|
36
|
+
variances_norm: `float`, (`adata.var['variances_norm']`)
|
|
37
|
+
Normalized variance per gene
|
|
38
|
+
variances: `float`, (`adata.var['variances']`)
|
|
39
|
+
Variance per gene.
|
|
40
|
+
means: `float`, (`adata.var['means']`)
|
|
41
|
+
Means per gene
|
|
42
|
+
highly_variable: `bool` (`adata.var['highly_variable']`)
|
|
43
|
+
Indicator of variable genes
|
|
44
|
+
"""
|
|
45
|
+
if layer is None:
|
|
46
|
+
X = adata.X
|
|
47
|
+
else:
|
|
48
|
+
X = adata.layers[layer].astype(np.float64).copy()
|
|
49
|
+
mean, variance = sparsefuncs.mean_variance_axis(X, axis=0)
|
|
50
|
+
variance_expected = np.zeros(adata.shape[1], dtype=np.float64)
|
|
51
|
+
not_const = variance > 0
|
|
52
|
+
|
|
53
|
+
model = loess(np.log10(mean[not_const]),
|
|
54
|
+
np.log10(variance[not_const]),
|
|
55
|
+
span=span,
|
|
56
|
+
degree=2)
|
|
57
|
+
model.fit()
|
|
58
|
+
variance_expected[not_const] = 10**model.outputs.fitted_values
|
|
59
|
+
N = adata.shape[0]
|
|
60
|
+
clip_max = np.sqrt(N)
|
|
61
|
+
clip_val = np.sqrt(variance_expected) * clip_max + mean
|
|
62
|
+
|
|
63
|
+
X = csr_matrix(X)
|
|
64
|
+
mask = X.data > clip_val[X.indices]
|
|
65
|
+
X.data[mask] = clip_val[X.indices[mask]]
|
|
66
|
+
|
|
67
|
+
squared_X_sum = np.array(X.power(2).sum(axis=0))
|
|
68
|
+
X_sum = np.array(X.sum(axis=0))
|
|
69
|
+
|
|
70
|
+
norm_gene_var = (1 / ((N - 1) * variance_expected)) \
|
|
71
|
+
* ((N * np.square(mean))
|
|
72
|
+
+ squared_X_sum
|
|
73
|
+
- 2 * X_sum * mean
|
|
74
|
+
)
|
|
75
|
+
norm_gene_var = norm_gene_var.flatten()
|
|
76
|
+
|
|
77
|
+
adata.var['variances_norm'] = norm_gene_var
|
|
78
|
+
adata.var['variances'] = variance
|
|
79
|
+
adata.var['means'] = mean
|
|
80
|
+
ids_top = norm_gene_var.argsort()[-n_top_genes:][::-1]
|
|
81
|
+
adata.var['highly_variable'] = np.isin(range(adata.shape[1]), ids_top)
|
|
82
|
+
print(f'{n_top_genes} variable genes are selected.')
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""reading and writing"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import json
|
|
6
|
+
from anndata import (
|
|
7
|
+
AnnData,
|
|
8
|
+
read_h5ad,
|
|
9
|
+
read_csv,
|
|
10
|
+
read_excel,
|
|
11
|
+
read_hdf,
|
|
12
|
+
read_loom,
|
|
13
|
+
read_mtx,
|
|
14
|
+
read_text,
|
|
15
|
+
read_umi_tools,
|
|
16
|
+
read_zarr,
|
|
17
|
+
)
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
import tables
|
|
20
|
+
|
|
21
|
+
from ._settings import settings
|
|
22
|
+
from ._utils import _read_legacy_10x_h5, _read_v3_10x_h5
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def read_embedding(path_emb=None,
|
|
26
|
+
path_entity=None,
|
|
27
|
+
convert_alias=True,
|
|
28
|
+
path_entity_alias=None,
|
|
29
|
+
prefix=None,
|
|
30
|
+
num_epochs=None,
|
|
31
|
+
get_marker_significance=False,
|
|
32
|
+
path_entity_alias_marker=None):
|
|
33
|
+
"""Read in entity embeddings from pbg training
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
path_emb: `str`, optional (default: None)
|
|
38
|
+
Path to directory for pbg embedding model
|
|
39
|
+
If None, .settings.pbg_params['checkpoint_path'] will be used.
|
|
40
|
+
path_entity: `str`, optional (default: None)
|
|
41
|
+
Path to entity name file
|
|
42
|
+
prefix: `list`, optional (default: None)
|
|
43
|
+
A list of entity type prefixes to include.
|
|
44
|
+
By default, it reads in the embeddings of all entities.
|
|
45
|
+
convert_alias: `bool`, optional (default: True)
|
|
46
|
+
If True, it will convert entity aliases to the original indices
|
|
47
|
+
path_entity_alias: `str`, optional (default: None)
|
|
48
|
+
Path to entity alias file
|
|
49
|
+
num_epochs: `int`, optional (default: None)
|
|
50
|
+
The embedding result associated with num_epochs to read in
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
dict_adata: `dict`
|
|
55
|
+
A dictionary of anndata objects of shape
|
|
56
|
+
(#entities x #dimensions)
|
|
57
|
+
"""
|
|
58
|
+
pbg_params = settings.pbg_params
|
|
59
|
+
if path_emb is None:
|
|
60
|
+
path_emb = pbg_params['checkpoint_path']
|
|
61
|
+
if path_entity is None:
|
|
62
|
+
path_entity = pbg_params['entity_path']
|
|
63
|
+
if num_epochs is None:
|
|
64
|
+
num_epochs = pbg_params["num_epochs"]
|
|
65
|
+
if prefix is None:
|
|
66
|
+
prefix = []
|
|
67
|
+
assert isinstance(prefix, list), \
|
|
68
|
+
"`prefix` must be list"
|
|
69
|
+
if convert_alias:
|
|
70
|
+
if path_entity_alias is None:
|
|
71
|
+
path_entity_alias = Path(path_emb).parent.as_posix()
|
|
72
|
+
df_entity_alias = pd.read_csv(
|
|
73
|
+
os.path.join(path_entity_alias, 'entity_alias.txt'),
|
|
74
|
+
header=0,
|
|
75
|
+
index_col=0,
|
|
76
|
+
sep='\t')
|
|
77
|
+
df_entity_alias['id'] = df_entity_alias.index
|
|
78
|
+
df_entity_alias.index = df_entity_alias['alias'].values
|
|
79
|
+
|
|
80
|
+
if get_marker_significance:
|
|
81
|
+
path_emb_marker = os.path.join(Path(path_emb).parent.as_posix() + "_with_sig", os.path.basename(path_emb))
|
|
82
|
+
path_entity_marker = os.path.join(Path(path_entity).parent.parent.as_posix() + "_with_sig", 'input/entity')
|
|
83
|
+
if path_entity_alias_marker is None:
|
|
84
|
+
path_entity_alias = Path(path_emb).parent.as_posix() + "_with_sig"
|
|
85
|
+
dict_adata_with_sig = read_embedding(
|
|
86
|
+
path_emb=path_emb_marker,
|
|
87
|
+
path_entity=path_entity_marker,
|
|
88
|
+
convert_alias=True,
|
|
89
|
+
path_entity_alias=path_entity_alias_marker,
|
|
90
|
+
prefix=prefix,
|
|
91
|
+
num_epochs=num_epochs,
|
|
92
|
+
get_marker_significance=False
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
dict_adata = dict()
|
|
96
|
+
for x in os.listdir(path_emb):
|
|
97
|
+
if x.startswith('embeddings'):
|
|
98
|
+
entity_type = x.split('_')[1]
|
|
99
|
+
if (len(prefix) == 0) or (entity_type in prefix):
|
|
100
|
+
adata = \
|
|
101
|
+
read_hdf(os.path.join(path_emb,
|
|
102
|
+
f'embeddings_{entity_type}_0.'
|
|
103
|
+
f'v{num_epochs}.h5'),
|
|
104
|
+
key="embeddings")
|
|
105
|
+
with open(
|
|
106
|
+
os.path.join(path_entity,
|
|
107
|
+
f'entity_names_{entity_type}_0.json'), "rt")\
|
|
108
|
+
as tf:
|
|
109
|
+
names_entity = json.load(tf)
|
|
110
|
+
if convert_alias:
|
|
111
|
+
names_entity = \
|
|
112
|
+
df_entity_alias.loc[names_entity, 'id'].tolist()
|
|
113
|
+
adata.obs.index = names_entity
|
|
114
|
+
dict_adata[entity_type] = adata
|
|
115
|
+
if get_marker_significance:
|
|
116
|
+
try:
|
|
117
|
+
dict_adata[f"n{entity_type}"] = dict_adata_with_sig[f"n{entity_type}"]
|
|
118
|
+
except KeyError:
|
|
119
|
+
print(f"Null feature nodes for entity {entity_type} not embedded.")
|
|
120
|
+
|
|
121
|
+
return dict_adata
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# modifed from
|
|
125
|
+
# scanpy https://github.com/theislab/scanpy/blob/master/scanpy/readwrite.py
|
|
126
|
+
def read_10x_h5(filename,
|
|
127
|
+
genome=None,
|
|
128
|
+
gex_only=True):
|
|
129
|
+
"""Read 10x-Genomics-formatted hdf5 file.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
filename
|
|
134
|
+
Path to a 10x hdf5 file.
|
|
135
|
+
genome
|
|
136
|
+
Filter expression to genes within this genome. For legacy 10x h5
|
|
137
|
+
files, this must be provided if the data contains more than one genome.
|
|
138
|
+
gex_only
|
|
139
|
+
Only keep 'Gene Expression' data and ignore other feature types,
|
|
140
|
+
e.g. 'Antibody Capture', 'CRISPR Guide Capture', or 'Custom'
|
|
141
|
+
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
adata: AnnData
|
|
145
|
+
Annotated data matrix, where observations/cells are named by their
|
|
146
|
+
barcode and variables/genes by gene name
|
|
147
|
+
"""
|
|
148
|
+
with tables.open_file(str(filename), 'r') as f:
|
|
149
|
+
v3 = '/matrix' in f
|
|
150
|
+
if v3:
|
|
151
|
+
adata = _read_v3_10x_h5(filename)
|
|
152
|
+
if genome:
|
|
153
|
+
if genome not in adata.var['genome'].values:
|
|
154
|
+
raise ValueError(
|
|
155
|
+
f"Could not find data corresponding to "
|
|
156
|
+
f"genome '{genome}' in '{filename}'. "
|
|
157
|
+
f'Available genomes are:'
|
|
158
|
+
f' {list(adata.var["genome"].unique())}.'
|
|
159
|
+
)
|
|
160
|
+
adata = adata[:, adata.var['genome'] == genome]
|
|
161
|
+
if gex_only:
|
|
162
|
+
adata = adata[:, adata.var['feature_types'] == 'Gene Expression']
|
|
163
|
+
if adata.is_view:
|
|
164
|
+
adata = adata.copy()
|
|
165
|
+
else:
|
|
166
|
+
adata = _read_legacy_10x_h5(filename, genome=genome)
|
|
167
|
+
return adata
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def load_pbg_config(path=None):
|
|
171
|
+
"""Load PBG configuration into global setting
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
path: `str`, optional (default: None)
|
|
176
|
+
Path to the directory for pbg configuration file
|
|
177
|
+
If None, `.settings.pbg_params['checkpoint_path']` will be used
|
|
178
|
+
|
|
179
|
+
Returns
|
|
180
|
+
-------
|
|
181
|
+
Updates `.settings.pbg_params`
|
|
182
|
+
|
|
183
|
+
"""
|
|
184
|
+
if path is None:
|
|
185
|
+
path = settings.pbg_params['checkpoint_path']
|
|
186
|
+
path = os.path.normpath(path)
|
|
187
|
+
with open(os.path.join(path, 'config.json'), "rt") as tf:
|
|
188
|
+
pbg_params = json.load(tf)
|
|
189
|
+
settings.set_pbg_params(config=pbg_params)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def load_graph_stats(path=None):
|
|
193
|
+
"""Load graph statistics into global setting
|
|
194
|
+
|
|
195
|
+
Parameters
|
|
196
|
+
----------
|
|
197
|
+
path: `str`, optional (default: None)
|
|
198
|
+
Path to the directory for graph statistics file
|
|
199
|
+
If None, `.settings.pbg_params['checkpoint_path']` will be used
|
|
200
|
+
|
|
201
|
+
Returns
|
|
202
|
+
-------
|
|
203
|
+
Updates `.settings.graph_stats`
|
|
204
|
+
"""
|
|
205
|
+
if path is None:
|
|
206
|
+
path = \
|
|
207
|
+
Path(settings.pbg_params['entity_path']).parent.parent.as_posix()
|
|
208
|
+
path = os.path.normpath(path)
|
|
209
|
+
with open(os.path.join(path, 'graph_stats.json'), "rt") as tf:
|
|
210
|
+
dict_graph_stats = json.load(tf)
|
|
211
|
+
dirname = os.path.basename(path)
|
|
212
|
+
settings.graph_stats[dirname] = dict_graph_stats.copy()
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def write_bed(adata,
|
|
216
|
+
use_top_pcs=True,
|
|
217
|
+
filename=None
|
|
218
|
+
):
|
|
219
|
+
"""Write peaks into .bed file
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
adata: AnnData
|
|
224
|
+
Annotated data matrix with peaks as variables.
|
|
225
|
+
use_top_pcs: `bool`, optional (default: True)
|
|
226
|
+
Use top-PCs-associated features
|
|
227
|
+
filename: `str`, optional (default: None)
|
|
228
|
+
Filename name for peaks.
|
|
229
|
+
By default, a file named 'peaks.bed' will be written to
|
|
230
|
+
`.settings.workdir`
|
|
231
|
+
"""
|
|
232
|
+
if filename is None:
|
|
233
|
+
filename = os.path.join(settings.workdir, 'peaks.bed')
|
|
234
|
+
for x in ['chr', 'start', 'end']:
|
|
235
|
+
if x not in adata.var_keys():
|
|
236
|
+
raise ValueError(f"could not find {x} in `adata.var_keys()`")
|
|
237
|
+
if use_top_pcs:
|
|
238
|
+
assert 'top_pcs' in adata.var_keys(), \
|
|
239
|
+
"please run `si.pp.select_pcs_features()` first"
|
|
240
|
+
peaks_selected = adata.var[
|
|
241
|
+
adata.var['top_pcs']][['chr', 'start', 'end']]
|
|
242
|
+
else:
|
|
243
|
+
peaks_selected = adata.var[
|
|
244
|
+
['chr', 'start', 'end']]
|
|
245
|
+
peaks_selected.to_csv(filename,
|
|
246
|
+
sep='\t',
|
|
247
|
+
header=False,
|
|
248
|
+
index=False)
|
|
249
|
+
fp, fn = os.path.split(filename)
|
|
250
|
+
print(f'"{fn}" was written to "{fp}".')
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""The core functionality"""
|
|
2
|
+
|
|
3
|
+
from ._general import (
|
|
4
|
+
discretize,
|
|
5
|
+
)
|
|
6
|
+
from ._umap import umap
|
|
7
|
+
from ._gene_scores import gene_scores
|
|
8
|
+
from ._integration import (
|
|
9
|
+
infer_edges,
|
|
10
|
+
trim_edges
|
|
11
|
+
)
|
|
12
|
+
from ._pbg import (
|
|
13
|
+
gen_graph,
|
|
14
|
+
pbg_train
|
|
15
|
+
)
|
|
16
|
+
from ._post_training import (
|
|
17
|
+
softmax,
|
|
18
|
+
embed,
|
|
19
|
+
compare_entities,
|
|
20
|
+
query,
|
|
21
|
+
find_master_regulators,
|
|
22
|
+
find_target_genes,
|
|
23
|
+
)
|