discotoolkit 1.1.2__tar.gz → 1.1.4__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.
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/PKG-INFO +1 -1
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/README.md +1 -1
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/discotoolkit/CELLiD.py +244 -91
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/discotoolkit/DiscoClass.py +7 -7
- discotoolkit-1.1.4/discotoolkit/DownloadDiscoData.py +238 -0
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/discotoolkit/GetMetadata.py +159 -74
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/discotoolkit/GlobalVariable.py +13 -5
- discotoolkit-1.1.4/discotoolkit/Utilities.py +74 -0
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/discotoolkit/__init__.py +1 -1
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/discotoolkit.egg-info/PKG-INFO +1 -1
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/discotoolkit.egg-info/SOURCES.txt +1 -0
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/setup.py +1 -1
- discotoolkit-1.1.2/discotoolkit/DownloadDiscoData.py +0 -144
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/MANIFEST.in +0 -0
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/discotoolkit/CellMapper.py +0 -0
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/discotoolkit/GeneSearch.py +0 -0
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/discotoolkit.egg-info/dependency_links.txt +0 -0
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/discotoolkit.egg-info/requires.txt +0 -0
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/discotoolkit.egg-info/top_level.txt +0 -0
- {discotoolkit-1.1.2 → discotoolkit-1.1.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: discotoolkit
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.4
|
|
4
4
|
Summary: DISCOtoolkit is an python package that allows users to access data and use the tools provided by the DISCO database.
|
|
5
5
|
Home-page: http://www.immunesinglecell.org/
|
|
6
6
|
Author: Li Mengwei, Rom Uddamvathanak
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
-->
|
|
9
9
|
[](https://discotoolkit-py.readthedocs.io/en/latest/?badge=latest) [](https://pepy.tech/project/discotoolkit) [](https://pypi.org/project/discotoolkit)
|
|
10
10
|
|
|
11
|
-
# DISCOtoolkit 1.1.
|
|
11
|
+
# DISCOtoolkit 1.1.4
|
|
12
12
|
|
|
13
13
|
DISCOtoolkit is an python package that allows users to access data and use the tools provided by the [DISCO database](https://www.immunesinglecell.org/). Read the documentation [DISCOtoolkit](https://discotoolkit-py.readthedocs.io/en/latest/). It provides the following functions:
|
|
14
14
|
|
|
@@ -9,10 +9,11 @@ import os
|
|
|
9
9
|
import hashlib
|
|
10
10
|
import requests
|
|
11
11
|
from scipy import stats
|
|
12
|
+
|
|
12
13
|
# from fast_fisher import fast_fisher_exact, odds_ratio
|
|
13
14
|
from scipy.stats.contingency import odds_ratio
|
|
14
|
-
from joblib import Parallel, delayed
|
|
15
|
-
from pandarallel import pandarallel
|
|
15
|
+
from joblib import Parallel, delayed # multiprocessing library
|
|
16
|
+
from pandarallel import pandarallel # multiprocessing library
|
|
16
17
|
|
|
17
18
|
pd.options.mode.chained_assignment = None
|
|
18
19
|
|
|
@@ -24,17 +25,45 @@ from .GetMetadata import check_in_list
|
|
|
24
25
|
"""
|
|
25
26
|
CELLiD cell type annotation function block.
|
|
26
27
|
"""
|
|
27
|
-
# get the total atlas from DISCO website for the user to select which atlas to use
|
|
28
|
-
def get_atlas(ref_data = None, ref_path = None):
|
|
29
28
|
|
|
29
|
+
|
|
30
|
+
def generate_correlation_map(x, y):
|
|
31
|
+
"""Correlate each n with each m.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
x : np.array
|
|
36
|
+
Shape N X T.
|
|
37
|
+
|
|
38
|
+
y : np.array
|
|
39
|
+
Shape M X T.
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
np.array
|
|
44
|
+
N X M array in which each element is a correlation coefficient.
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
mu_x = x.mean(1)
|
|
48
|
+
mu_y = y.mean(1)
|
|
49
|
+
n = x.shape[1]
|
|
50
|
+
if n != y.shape[1]:
|
|
51
|
+
raise ValueError("x and y must " + "have the same number of timepoints.")
|
|
52
|
+
s_x = x.std(1, ddof=n - 1)
|
|
53
|
+
s_y = y.std(1, ddof=n - 1)
|
|
54
|
+
cov = np.dot(x, y.T) - n * np.dot(mu_x[:, np.newaxis], mu_y[np.newaxis, :])
|
|
55
|
+
return cov / np.dot(s_x[:, np.newaxis], s_y[np.newaxis, :])
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# get the total atlas from DISCO website for the user to select which atlas to use
|
|
59
|
+
def get_atlas(ref_data=None, ref_path=None):
|
|
30
60
|
"""get the all the atlas string from the DISCO website and return to the user
|
|
31
61
|
|
|
32
62
|
Returns:
|
|
33
63
|
List: return list of string
|
|
34
|
-
"""
|
|
64
|
+
"""
|
|
35
65
|
# Download reference data and ref_deg if missing
|
|
36
66
|
if ref_data is None:
|
|
37
|
-
|
|
38
67
|
# check for the data path
|
|
39
68
|
if ref_path is None:
|
|
40
69
|
ref_path = "DISCOtmp"
|
|
@@ -42,19 +71,33 @@ def get_atlas(ref_data = None, ref_path = None):
|
|
|
42
71
|
# if the path does not exist, we create a default directory
|
|
43
72
|
if not os.path.exists(ref_path):
|
|
44
73
|
os.mkdir(ref_path)
|
|
45
|
-
|
|
74
|
+
|
|
46
75
|
# download reference data from the server in pickle extension and read as pandas dataframe
|
|
47
76
|
if ref_data is None:
|
|
48
|
-
if not(os.path.exists(ref_path + "/ref_data.pkl")):
|
|
77
|
+
if not (os.path.exists(ref_path + "/ref_data.pkl")):
|
|
49
78
|
logging.info("Downloading reference dataset...")
|
|
50
|
-
response = requests.get(
|
|
79
|
+
response = requests.get(
|
|
80
|
+
url=prefix_disco_url + "toolkit/getRef?type=pkl", timeout=timeout
|
|
81
|
+
)
|
|
51
82
|
open(ref_path + "/ref_data.pkl", "wb").write(response.content)
|
|
52
|
-
ref_data = pd.read_pickle(
|
|
83
|
+
ref_data = pd.read_pickle(
|
|
84
|
+
ref_path + "/ref_data.pkl",
|
|
85
|
+
compression={"method": "gzip", "compresslevel": 6},
|
|
86
|
+
)
|
|
53
87
|
|
|
54
88
|
all_atlas = [each.split("--")[1] for each in ref_data.columns]
|
|
55
89
|
return list(set(all_atlas))
|
|
56
90
|
|
|
57
|
-
|
|
91
|
+
|
|
92
|
+
def CELLiD_cluster(
|
|
93
|
+
rna,
|
|
94
|
+
ref_data: pd.DataFrame = None,
|
|
95
|
+
ref_deg: pd.DataFrame = None,
|
|
96
|
+
atlas: str = None,
|
|
97
|
+
n_predict: int = 1,
|
|
98
|
+
ref_path: str = None,
|
|
99
|
+
ncores: int = 10,
|
|
100
|
+
):
|
|
58
101
|
"""Cell type annotation using reference data and compute the correlation between the user cell gene expression as compare
|
|
59
102
|
to the reference data. The celltype with highest correlation will be concluded as the celltype
|
|
60
103
|
|
|
@@ -73,20 +116,21 @@ def CELLiD_cluster(rna, ref_data : pd.DataFrame = None, ref_deg : pd.DataFrame =
|
|
|
73
116
|
|
|
74
117
|
# initialize the pandarallel for parallel processing
|
|
75
118
|
pandarallel.initialize(nb_workers=ncores)
|
|
76
|
-
|
|
119
|
+
|
|
77
120
|
# check for the input data
|
|
78
|
-
if not(isinstance(rna, pd.DataFrame) or isinstance(rna, np.ndarray)):
|
|
121
|
+
if not (isinstance(rna, pd.DataFrame) or isinstance(rna, np.ndarray)):
|
|
79
122
|
logging.error("the rna must be a pandas DataFrame or Numpy Array")
|
|
80
123
|
|
|
81
124
|
# check for number of prediction of celltypes and set the largest number to 3 only
|
|
82
125
|
if n_predict is not None:
|
|
83
126
|
if n_predict > 3:
|
|
84
|
-
logging.info(
|
|
127
|
+
logging.info(
|
|
128
|
+
"Any value of n_predict that exceeds 3 will be automatically adjusted to 3."
|
|
129
|
+
)
|
|
85
130
|
n_predict = 3
|
|
86
|
-
|
|
131
|
+
|
|
87
132
|
# Download reference data and ref_deg if missing
|
|
88
133
|
if (ref_data is None) or (ref_deg is None):
|
|
89
|
-
|
|
90
134
|
# check for the data path
|
|
91
135
|
if ref_path is None:
|
|
92
136
|
ref_path = "DISCOtmp"
|
|
@@ -94,42 +138,60 @@ def CELLiD_cluster(rna, ref_data : pd.DataFrame = None, ref_deg : pd.DataFrame =
|
|
|
94
138
|
# if the path does not exist, we create a default directory
|
|
95
139
|
if not os.path.exists(ref_path):
|
|
96
140
|
os.mkdir(ref_path)
|
|
97
|
-
|
|
141
|
+
|
|
98
142
|
# download reference data from the server in pickle extension and read as pandas dataframe
|
|
99
143
|
if ref_data is None:
|
|
100
|
-
if not(os.path.exists(ref_path + "/ref_data.pkl")):
|
|
144
|
+
if not (os.path.exists(ref_path + "/ref_data.pkl")):
|
|
101
145
|
logging.info("Downloading reference dataset...")
|
|
102
|
-
response = requests.get(
|
|
146
|
+
response = requests.get(
|
|
147
|
+
url=prefix_disco_url + "toolkit/getRef?type=pkl", timeout=timeout
|
|
148
|
+
)
|
|
103
149
|
open(ref_path + "/ref_data.pkl", "wb").write(response.content)
|
|
104
|
-
ref_data = pd.read_pickle(
|
|
150
|
+
ref_data = pd.read_pickle(
|
|
151
|
+
ref_path + "/ref_data.pkl",
|
|
152
|
+
compression={"method": "gzip", "compresslevel": 6},
|
|
153
|
+
)
|
|
105
154
|
|
|
106
155
|
# similarly do the same for the DEG reference
|
|
107
156
|
if ref_deg is None:
|
|
108
|
-
if not(os.path.exists(ref_path + "/ref_deg.pkl")):
|
|
157
|
+
if not (os.path.exists(ref_path + "/ref_deg.pkl")):
|
|
109
158
|
logging.info("Downloading deg dataset...")
|
|
110
|
-
response = requests.get(
|
|
159
|
+
response = requests.get(
|
|
160
|
+
url=prefix_disco_url + "toolkit/getRefDeg?type=pkl", timeout=timeout
|
|
161
|
+
)
|
|
111
162
|
open(ref_path + "/ref_deg.pkl", "wb").write(response.content)
|
|
112
|
-
ref_deg = pd.read_pickle(
|
|
113
|
-
|
|
163
|
+
ref_deg = pd.read_pickle(
|
|
164
|
+
ref_path + "/ref_deg.pkl",
|
|
165
|
+
compression={"method": "gzip", "compresslevel": 6},
|
|
166
|
+
)
|
|
167
|
+
|
|
114
168
|
####
|
|
115
169
|
# write list of atlas function
|
|
116
170
|
# we can write a function to get all the atlas from disco to the users
|
|
117
171
|
# subsetting to the selected atlas by the user
|
|
118
172
|
if atlas is not None:
|
|
119
|
-
select_ref = [
|
|
173
|
+
select_ref = [
|
|
174
|
+
index
|
|
175
|
+
for index, each in enumerate(ref_data.columns)
|
|
176
|
+
if each.split("--")[1] in list(atlas)
|
|
177
|
+
]
|
|
120
178
|
ref_data = ref_data.iloc[:, select_ref]
|
|
121
|
-
|
|
179
|
+
|
|
122
180
|
# get the intersection of the gene for the user data and the reference data
|
|
123
181
|
genes = set.intersection(set(rna.index), set(ref_data.index))
|
|
124
182
|
|
|
125
183
|
# give an error as 2000 genes are very low
|
|
126
184
|
if len(genes) <= 2000:
|
|
127
|
-
logging.error(
|
|
185
|
+
logging.error(
|
|
186
|
+
"Less than 2000 genes are shared between the input data and the reference dataset!"
|
|
187
|
+
)
|
|
128
188
|
|
|
129
189
|
# give a warning to the user
|
|
130
190
|
if len(genes) <= 5000:
|
|
131
|
-
logging.warning(
|
|
132
|
-
|
|
191
|
+
logging.warning(
|
|
192
|
+
"The input data and reference dataset have a limited number of overlapping genes, which may potentially impact the accuracy of the CELLiD."
|
|
193
|
+
)
|
|
194
|
+
|
|
133
195
|
# subsetting the dataframe to the common genes across data and the reference
|
|
134
196
|
rna = rna.loc[list(genes)]
|
|
135
197
|
ref_data = ref_data.loc[list(genes)]
|
|
@@ -137,7 +199,7 @@ def CELLiD_cluster(rna, ref_data : pd.DataFrame = None, ref_deg : pd.DataFrame =
|
|
|
137
199
|
# nested apply function to compute correlation for each cell type between the user and reference database
|
|
138
200
|
def each_ref_correlation(input, ref_data):
|
|
139
201
|
input = list(input)
|
|
140
|
-
res = ref_data.parallel_apply(basic_correlation, input
|
|
202
|
+
res = ref_data.parallel_apply(basic_correlation, input=input, axis=0)
|
|
141
203
|
return res
|
|
142
204
|
|
|
143
205
|
# apply the correlation to all the columns in user data
|
|
@@ -148,9 +210,14 @@ def CELLiD_cluster(rna, ref_data : pd.DataFrame = None, ref_deg : pd.DataFrame =
|
|
|
148
210
|
predicted = pd.Series(list(ref)).corr(pd.Series(list(input)), method="spearman")
|
|
149
211
|
|
|
150
212
|
return predicted
|
|
151
|
-
|
|
213
|
+
|
|
152
214
|
# get the predicted correlation as in pandas DataFrame
|
|
153
|
-
predicted_cell =
|
|
215
|
+
# predicted_cell = pd.DataFrame(
|
|
216
|
+
# generate_correlation_map(ref_data.to_numpy().T, rna.to_numpy().T)
|
|
217
|
+
# )
|
|
218
|
+
predicted_cell = rna.apply(
|
|
219
|
+
each_ref_correlation, result_type="expand", axis=0, ref_data=ref_data
|
|
220
|
+
)
|
|
154
221
|
predicted_cell = pd.DataFrame(predicted_cell)
|
|
155
222
|
|
|
156
223
|
# set the index to be the celltype based on the reference_data
|
|
@@ -161,60 +228,84 @@ def CELLiD_cluster(rna, ref_data : pd.DataFrame = None, ref_deg : pd.DataFrame =
|
|
|
161
228
|
# define a function to apply to each column of the dataframe
|
|
162
229
|
def get_top_5_indices(col):
|
|
163
230
|
# get the indices of the top 5 values in the column
|
|
164
|
-
indices = np.where(col.rank(method=
|
|
231
|
+
indices = np.where(col.rank(method="min", ascending=False) <= 5)[0]
|
|
165
232
|
# convert to numeric and return
|
|
166
|
-
return pd.Series(indices).astype(
|
|
167
|
-
|
|
168
|
-
ct = predicted_cell.apply(get_top_5_indices)
|
|
169
|
-
|
|
170
|
-
# ct = pd.DataFrame(predicted_cell.parallel_apply(lambda x: [index for index, each in enumerate(x.rank()) if each <= 5], axis = 0, result_type = "expand"))
|
|
233
|
+
return pd.Series(indices).astype("int")
|
|
171
234
|
|
|
235
|
+
ct = predicted_cell.apply(get_top_5_indices)
|
|
172
236
|
# compute the correleration based on the genes set that is intersection between the user data and reference dataset
|
|
237
|
+
|
|
173
238
|
def second_correlation(i, ref_data, rna, ct):
|
|
174
|
-
ref = ref_data.iloc[:,ct[i]].copy()
|
|
175
|
-
g = set(
|
|
239
|
+
ref = ref_data.iloc[:, list(ct[str(i)])].copy()
|
|
240
|
+
g = set(
|
|
241
|
+
ref_deg.iloc[check_in_list(ref_deg["group"], ref.columns)].copy()["gene"]
|
|
242
|
+
)
|
|
176
243
|
g = set.intersection(set(ref.index), g)
|
|
177
244
|
ref = ref.loc[list(g)].copy()
|
|
178
|
-
|
|
245
|
+
# print(rna.loc[list(g), str(i)])
|
|
246
|
+
input = rna.loc[list(g), str(i)].copy()
|
|
179
247
|
# predict = ref.apply(lambda i: np.round(stats.spearmanr(pd.to_numeric(i), pd.to_numeric(input), nan_policy='omit')[0], 3))
|
|
180
|
-
predict = ref.apply(
|
|
248
|
+
predict = ref.apply(
|
|
249
|
+
lambda i: np.round(
|
|
250
|
+
pd.Series(i).corr(pd.Series(input), method="spearman"), 3
|
|
251
|
+
)
|
|
252
|
+
) # trying with pandas correlation
|
|
181
253
|
predict = pd.DataFrame(predict)
|
|
182
254
|
predict.columns = ["cor"]
|
|
183
|
-
predict = predict.sort_values(["cor"], ascending
|
|
184
|
-
res = [
|
|
185
|
-
|
|
255
|
+
predict = predict.sort_values(["cor"], ascending=False)
|
|
256
|
+
res = [
|
|
257
|
+
[each.split("--")[0], each.split("--")[1], predict["cor"][index], i]
|
|
258
|
+
for index, each in enumerate(predict.index)
|
|
259
|
+
if index < n_predict
|
|
260
|
+
]
|
|
261
|
+
res = pd.DataFrame(res, columns=["cell_type", "atlas", "score", "input_index"])
|
|
186
262
|
return res # return list in the format cell_type, atlas, score, input_index
|
|
187
263
|
|
|
188
264
|
# get a data in the format of cell_type, atlas, score, input_index
|
|
189
|
-
predicted_cell = Parallel(n_jobs=ncores, batch_size=32, verbose
|
|
265
|
+
predicted_cell = Parallel(n_jobs=ncores, batch_size=32, verbose=2)(
|
|
266
|
+
delayed(second_correlation)(int(y), ref_data, rna, ct)
|
|
267
|
+
for y in range(rna.shape[1])
|
|
268
|
+
)
|
|
190
269
|
|
|
191
270
|
# first concatenate in row wise manner
|
|
192
271
|
predicted_cell = pd.concat(predicted_cell)
|
|
193
272
|
|
|
194
|
-
pivot = pd.pivot_table(
|
|
195
|
-
|
|
273
|
+
pivot = pd.pivot_table(
|
|
274
|
+
predicted_cell,
|
|
275
|
+
index="input_index",
|
|
276
|
+
columns=predicted_cell.groupby("input_index").cumcount() + 1,
|
|
277
|
+
values=["cell_type", "atlas", "score"],
|
|
278
|
+
aggfunc="first",
|
|
279
|
+
)
|
|
196
280
|
|
|
197
|
-
pivot = pivot[["cell_type", "atlas", "score"]]
|
|
281
|
+
pivot = pivot[["cell_type", "atlas", "score"]]
|
|
198
282
|
|
|
199
283
|
# flatten multi-level column index
|
|
200
|
-
pivot.columns = [
|
|
284
|
+
pivot.columns = ["_".join(map(str, col)).strip() for col in pivot.columns.values]
|
|
201
285
|
|
|
202
286
|
# rename columns
|
|
203
287
|
column_names = {}
|
|
204
288
|
for i in range(n_predict):
|
|
205
|
-
column_names[f
|
|
206
|
-
column_names[f
|
|
207
|
-
column_names[f
|
|
208
|
-
|
|
289
|
+
column_names[f"cell_type_{i+1}"] = f"predicted_cell_type_{i+1}"
|
|
290
|
+
column_names[f"atlas_{i+1}"] = f"source_atlas_{i+1}"
|
|
291
|
+
column_names[f"score_{i+1}"] = f"score_{i+1}"
|
|
292
|
+
|
|
209
293
|
pivot = pivot.rename(columns=column_names)
|
|
210
294
|
|
|
211
|
-
return pivot# return pandas dataframe in the format the same original R package
|
|
295
|
+
return pivot # return pandas dataframe in the format the same original R package
|
|
296
|
+
|
|
212
297
|
|
|
213
298
|
"""
|
|
214
299
|
Geneset enrichment analysis using DISCO data
|
|
215
300
|
"""
|
|
216
301
|
|
|
217
|
-
|
|
302
|
+
|
|
303
|
+
def CELLiD_enrichment(
|
|
304
|
+
input: pd.DataFrame,
|
|
305
|
+
reference: pd.DataFrame = None,
|
|
306
|
+
ref_path: str = None,
|
|
307
|
+
ncores: int = 1,
|
|
308
|
+
):
|
|
218
309
|
"""Function to generate enrichment analysis based on the reference gene sets and following the DISCO pipeline.
|
|
219
310
|
|
|
220
311
|
Args:
|
|
@@ -229,7 +320,7 @@ def CELLiD_enrichment(input : pd.DataFrame, reference : pd.DataFrame = None, ref
|
|
|
229
320
|
|
|
230
321
|
# check input data
|
|
231
322
|
# check if the input type is a DataFrame
|
|
232
|
-
if not(isinstance(input, pd.DataFrame)):
|
|
323
|
+
if not (isinstance(input, pd.DataFrame)):
|
|
233
324
|
logging.error("The input must be a dataframe")
|
|
234
325
|
|
|
235
326
|
# check if the user provide more than the needed column which are gene and fc
|
|
@@ -240,7 +331,7 @@ def CELLiD_enrichment(input : pd.DataFrame, reference : pd.DataFrame = None, ref
|
|
|
240
331
|
if input.shape[1] == 2:
|
|
241
332
|
input_shape = 2
|
|
242
333
|
input.columns = ["gene", "fc"]
|
|
243
|
-
|
|
334
|
+
|
|
244
335
|
# else we can just take a gene list for the enrichment
|
|
245
336
|
else:
|
|
246
337
|
input_shape = 1
|
|
@@ -248,7 +339,6 @@ def CELLiD_enrichment(input : pd.DataFrame, reference : pd.DataFrame = None, ref
|
|
|
248
339
|
|
|
249
340
|
# if the user does not provide reference geneset list
|
|
250
341
|
if reference is None:
|
|
251
|
-
|
|
252
342
|
# check for the data path if it is provided by the user
|
|
253
343
|
if ref_path is None:
|
|
254
344
|
ref_path = "DISCOtmp"
|
|
@@ -256,31 +346,45 @@ def CELLiD_enrichment(input : pd.DataFrame, reference : pd.DataFrame = None, ref
|
|
|
256
346
|
# if the path does not exist, we create a default directory
|
|
257
347
|
if not os.path.exists(ref_path):
|
|
258
348
|
os.mkdir(ref_path)
|
|
259
|
-
|
|
349
|
+
|
|
260
350
|
# check if the file does not exist
|
|
261
351
|
if not (os.path.exists(ref_path + "/ref_geneset.pkl")):
|
|
262
352
|
# downloading the geneset data from disco database
|
|
263
|
-
response = requests.get(
|
|
353
|
+
response = requests.get(
|
|
354
|
+
url=prefix_disco_url + "/getGeneSetPkl", timeout=timeout
|
|
355
|
+
)
|
|
264
356
|
open(ref_path + "/ref_geneset.pkl", "wb").write(response.content)
|
|
265
|
-
reference = pd.read_pickle(
|
|
357
|
+
reference = pd.read_pickle(
|
|
358
|
+
ref_path + "/ref_geneset.pkl",
|
|
359
|
+
compression={"method": "gzip", "compresslevel": 6},
|
|
360
|
+
)
|
|
266
361
|
|
|
267
362
|
else:
|
|
268
363
|
# read the data into pandas dataframe for subsequent analysis
|
|
269
|
-
reference = pd.read_pickle(
|
|
364
|
+
reference = pd.read_pickle(
|
|
365
|
+
ref_path + "/ref_geneset.pkl",
|
|
366
|
+
compression={"method": "gzip", "compresslevel": 6},
|
|
367
|
+
)
|
|
270
368
|
|
|
271
369
|
# rename the name data to include the reference atlas
|
|
272
370
|
reference["name"] = reference["name"] + " in " + reference["atlas"]
|
|
273
|
-
input["gene"] = input[
|
|
371
|
+
input["gene"] = input[
|
|
372
|
+
"gene"
|
|
373
|
+
].str.upper() # convert all the gene to upper string as the reference gene name is in uppercase
|
|
274
374
|
# condition when the user provide the fold change for the enrichment analysis
|
|
275
375
|
if input_shape == 2:
|
|
276
|
-
input["fc"] = 2 ** input["fc"]
|
|
277
|
-
input = input.set_index("gene", drop
|
|
278
|
-
input = input.loc[
|
|
376
|
+
input["fc"] = 2 ** input["fc"] # 2 to the power of fc
|
|
377
|
+
input = input.set_index("gene", drop=False) # set index to the gene
|
|
378
|
+
input = input.loc[
|
|
379
|
+
list(set(reference["gene"]) & set(input["gene"]))
|
|
380
|
+
] # only get the common genes for comparison
|
|
279
381
|
|
|
280
382
|
# else we only look at the ranked gene list
|
|
281
383
|
else:
|
|
282
|
-
input = input.set_index("gene", drop
|
|
283
|
-
input = input.loc[
|
|
384
|
+
input = input.set_index("gene", drop=False)
|
|
385
|
+
input = input.loc[
|
|
386
|
+
list(set(reference["gene"]) & set(input["gene"]))
|
|
387
|
+
] # only get the common genes for comparison
|
|
284
388
|
# filter to get only the geneset that contain the input genes from the user
|
|
285
389
|
# this might good different result from the website so lets remove it for
|
|
286
390
|
# unique_names = reference[reference["gene"].isin(input["gene"])]["name"].unique()
|
|
@@ -291,8 +395,8 @@ def CELLiD_enrichment(input : pd.DataFrame, reference : pd.DataFrame = None, ref
|
|
|
291
395
|
# optimise the CELLiD_enrichment function
|
|
292
396
|
atlas_dict = {}
|
|
293
397
|
name_dict = {}
|
|
294
|
-
reference_atlas = reference[
|
|
295
|
-
reference_name = reference[
|
|
398
|
+
reference_atlas = reference["atlas"]
|
|
399
|
+
reference_name = reference["name"]
|
|
296
400
|
|
|
297
401
|
# pre compute the parameters of the fisher exact test for the reference data
|
|
298
402
|
for rownum in range(len(reference)):
|
|
@@ -305,57 +409,105 @@ def CELLiD_enrichment(input : pd.DataFrame, reference : pd.DataFrame = None, ref
|
|
|
305
409
|
name_dict[reference_name[rownum]] = [rownum]
|
|
306
410
|
else:
|
|
307
411
|
name_dict[reference_name[rownum]].append(rownum)
|
|
308
|
-
|
|
412
|
+
|
|
309
413
|
# apply function to the dataframe
|
|
310
|
-
def process_unique_name(
|
|
414
|
+
def process_unique_name(
|
|
415
|
+
reference_filter, input, atlas_dfs, input_shape, atlas_dict
|
|
416
|
+
):
|
|
311
417
|
# use the compile`d pattern to search for matches
|
|
312
|
-
unique_name = reference_filter[
|
|
313
|
-
atlas = reference_filter[
|
|
418
|
+
unique_name = reference_filter["name"].head(1).values[0]
|
|
419
|
+
atlas = reference_filter["atlas"].head(1).values
|
|
314
420
|
# print(unique_name)
|
|
315
421
|
reference_full = atlas_dfs[list(atlas_dict.keys()).index(atlas)]
|
|
316
422
|
# reference_filter = reference.iloc[name_dict[unique_name]].copy() # subset the reference data
|
|
317
423
|
# reference_full = reference.iloc[atlas_dict[atlas]].copy() # full reference to the atlas
|
|
318
|
-
reference_filter = reference_filter.set_index(
|
|
424
|
+
reference_filter = reference_filter.set_index(
|
|
425
|
+
["gene"], drop=False
|
|
426
|
+
) # set gene as the index
|
|
319
427
|
# reference_filter["gene"] = reference_filter.index
|
|
320
|
-
input_filter = input.loc[
|
|
428
|
+
input_filter = input.loc[
|
|
429
|
+
list(set(reference_full["gene"]) & set(input["gene"]))
|
|
430
|
+
] # subset the input genes
|
|
321
431
|
|
|
322
432
|
# condition for no passed gene set
|
|
323
433
|
if input_filter.empty:
|
|
324
434
|
return None
|
|
325
|
-
|
|
435
|
+
|
|
326
436
|
# different computation to either include the fold change in the fisher exact test
|
|
327
437
|
if input_shape == 2:
|
|
328
|
-
a = (
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
438
|
+
a = (
|
|
439
|
+
reference_filter.loc[
|
|
440
|
+
np.intersect1d(reference_filter["gene"], (input_filter["gene"]))
|
|
441
|
+
].iloc[:, 0]
|
|
442
|
+
* input_filter.loc[
|
|
443
|
+
np.intersect1d(input_filter["gene"], reference_filter["gene"]), "fc"
|
|
444
|
+
]
|
|
445
|
+
).sum() + 1
|
|
446
|
+
b = (
|
|
447
|
+
input_filter.loc[
|
|
448
|
+
np.setdiff1d(input_filter["gene"], reference_filter["gene"]), "fc"
|
|
449
|
+
].sum()
|
|
450
|
+
+ 1
|
|
451
|
+
)
|
|
452
|
+
c = (
|
|
453
|
+
reference_filter.loc[
|
|
454
|
+
np.setdiff1d(reference_filter["gene"], input_filter["gene"])
|
|
455
|
+
]
|
|
456
|
+
.iloc[:, 0]
|
|
457
|
+
.sum()
|
|
458
|
+
+ 1
|
|
459
|
+
)
|
|
460
|
+
d = len(set(reference_full["gene"])) - len(
|
|
461
|
+
set(reference_filter["gene"]).union(set(input_filter["gene"]))
|
|
462
|
+
)
|
|
332
463
|
else:
|
|
333
464
|
a = len(np.intersect1d(reference_filter["gene"], input_filter["gene"])) + 1
|
|
334
465
|
b = len(np.setdiff1d(input_filter["gene"], reference_filter["gene"])) + 1
|
|
335
466
|
c = len(np.setdiff1d(reference_filter["gene"], input_filter["gene"])) + 1
|
|
336
467
|
d = len(set(reference_full["gene"])) - a - b - c
|
|
337
|
-
|
|
468
|
+
|
|
338
469
|
# get both value from the fisher exact test
|
|
339
470
|
# pval = pvalue(a, b, c, d).two_tail
|
|
340
471
|
_, pval = stats.fisher_exact(np.array([[a, b], [c, d]]))
|
|
341
|
-
odds = (a * d)/(b * c)
|
|
472
|
+
odds = (a * d) / (b * c)
|
|
342
473
|
# only get the significant p_value
|
|
343
474
|
# we can make changes to the p_value as in like we allow the user to specify it so that they have more control on the result they can obtain
|
|
344
475
|
if pval < 0.01:
|
|
345
|
-
return pd.Series(
|
|
346
|
-
|
|
347
|
-
|
|
476
|
+
return pd.Series(
|
|
477
|
+
{
|
|
478
|
+
"pval": pval,
|
|
479
|
+
"or": odds,
|
|
480
|
+
"name": unique_name,
|
|
481
|
+
"gene": ",".join(
|
|
482
|
+
reference_filter.loc[
|
|
483
|
+
reference_filter["gene"].isin(input_filter["gene"]), "gene"
|
|
484
|
+
]
|
|
485
|
+
),
|
|
486
|
+
"background": len(set(reference_full["gene"])),
|
|
487
|
+
"overlap": len(
|
|
488
|
+
reference_filter.loc[
|
|
489
|
+
reference_filter["gene"].isin(input_filter["gene"])
|
|
490
|
+
]
|
|
491
|
+
),
|
|
492
|
+
"geneset": len(reference_filter),
|
|
493
|
+
}
|
|
494
|
+
)
|
|
348
495
|
else:
|
|
349
496
|
return None
|
|
350
497
|
|
|
351
498
|
# Split them into cores
|
|
352
499
|
unique_name_dfs = [reference.iloc[name_dict[key], :] for key in name_dict.keys()]
|
|
353
500
|
atlas_dfs = [reference.iloc[atlas_dict[key], :] for key in atlas_dict.keys()]
|
|
354
|
-
|
|
501
|
+
|
|
355
502
|
# return unique_name_dfs, atlas_dfs, input, input_shape
|
|
356
503
|
# return unique_name_dfs, atlas_dfs
|
|
357
504
|
# using joblib to apply multiprocessing
|
|
358
|
-
results = Parallel(n_jobs=ncores, verbose
|
|
505
|
+
results = Parallel(n_jobs=ncores, verbose=2, batch_size=64)(
|
|
506
|
+
delayed(process_unique_name)(
|
|
507
|
+
unique_name_df, input, atlas_dfs, input_shape, atlas_dict
|
|
508
|
+
)
|
|
509
|
+
for unique_name_df in unique_name_dfs
|
|
510
|
+
)
|
|
359
511
|
results = [res for res in results if res is not None]
|
|
360
512
|
|
|
361
513
|
# concatenate the result into the dataframe to the user
|
|
@@ -363,8 +515,9 @@ def CELLiD_enrichment(input : pd.DataFrame, reference : pd.DataFrame = None, ref
|
|
|
363
515
|
res_df = pd.concat(results, axis=1).transpose()
|
|
364
516
|
res_df["pval"] = res_df["pval"].astype(float).round(3)
|
|
365
517
|
res_df["or"] = res_df["or"].astype(float).round(3)
|
|
366
|
-
res_df = res_df.sort_values(by=["pval", "or"], ascending=[True, False]).head(
|
|
518
|
+
res_df = res_df.sort_values(by=["pval", "or"], ascending=[True, False]).head(
|
|
519
|
+
min(50, len(results))
|
|
520
|
+
)
|
|
367
521
|
return res_df
|
|
368
522
|
else:
|
|
369
523
|
return None
|
|
370
|
-
|
|
@@ -6,10 +6,10 @@ class Filter:
|
|
|
6
6
|
"""
|
|
7
7
|
Filter class object to save the attributes for filtering the dataset from DISCO
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
tissue String e.g.
|
|
12
|
-
disease String e.g.
|
|
9
|
+
sample_id String e.g. ERX2757110;
|
|
10
|
+
project_id String;
|
|
11
|
+
tissue String e.g. lung, bladder;
|
|
12
|
+
disease String e.g. COVID-19;
|
|
13
13
|
platform String e.g. 10x3';
|
|
14
14
|
sample_type String;
|
|
15
15
|
cell_type String;
|
|
@@ -20,12 +20,12 @@ class Filter:
|
|
|
20
20
|
return Class object
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
def __init__(self,
|
|
23
|
+
def __init__(self, sample_id = None, project_id = None, tissue = None, disease = None, platform = None, sample_type = None,
|
|
24
24
|
cell_type = None, cell_type_confidence : str = "medium", include_cell_type_children : bool = True, min_cell_per_sample : int = 100):
|
|
25
25
|
|
|
26
26
|
# handling for string and list input
|
|
27
|
-
self.
|
|
28
|
-
self.
|
|
27
|
+
self.sample_id = self.convert_to_list(sample_id) # sample id
|
|
28
|
+
self.project_id = self.convert_to_list(project_id) # project, lab, or dataset from different author
|
|
29
29
|
self.tissue = self.convert_to_list(tissue) # organ tissue
|
|
30
30
|
self.disease = self.convert_to_list(disease) # cancer or non cancer, or COVID-1e9 disease
|
|
31
31
|
self.platform = self.convert_to_list(platform) # sequencing platform
|