FunVIP 0.3.20__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.
- FunVIP-0.3.20.dist-info/LICENSE +674 -0
- FunVIP-0.3.20.dist-info/METADATA +32 -0
- FunVIP-0.3.20.dist-info/RECORD +36 -0
- FunVIP-0.3.20.dist-info/WHEEL +5 -0
- FunVIP-0.3.20.dist-info/entry_points.txt +3 -0
- FunVIP-0.3.20.dist-info/top_level.txt +3 -0
- data/__init__.py +0 -0
- external/BLAST_Windows/bin/cleanup-blastdb-volumes.py +162 -0
- external/__init__.py +0 -0
- src/__init__.py +0 -0
- src/align.py +124 -0
- src/cluster.py +510 -0
- src/command.py +360 -0
- src/concatenate.py +356 -0
- src/dataset.py +716 -0
- src/ext.py +448 -0
- src/hasher.py +98 -0
- src/initialize.py +335 -0
- src/logger.py +69 -0
- src/logics.py +104 -0
- src/modeltest.py +443 -0
- src/ncbi.py +160 -0
- src/opt_generator.py +72 -0
- src/patch.py +261 -0
- src/reporter.py +875 -0
- src/save.py +181 -0
- src/search.py +440 -0
- src/tool.py +309 -0
- src/tree.py +222 -0
- src/tree_interpretation.py +1379 -0
- src/tree_interpretation_pipe.py +679 -0
- src/trim.py +118 -0
- src/validate_input.py +846 -0
- src/validate_option.py +1609 -0
- src/validation.py +38 -0
- src/version.py +337 -0
src/cluster.py
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import multiprocessing as mp
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import numpy as np
|
|
4
|
+
import shutil
|
|
5
|
+
import os, sys, subprocess
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from functools import lru_cache
|
|
8
|
+
from time import sleep
|
|
9
|
+
|
|
10
|
+
# from Bio.Blast import NCBIXML
|
|
11
|
+
from Bio import SeqIO
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import gc
|
|
15
|
+
from funvip.src.ext import mmseqs
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# return list of original group of given FI
|
|
19
|
+
def get_naive_group(V):
|
|
20
|
+
return list(set([FI.group for FI in V.list_FI if type(FI.group) == str]))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Append query_group information column to concatenated search result
|
|
24
|
+
def append_query_group(V):
|
|
25
|
+
# For concatenated gene matrix
|
|
26
|
+
group_dict = {}
|
|
27
|
+
for FI in V.list_FI:
|
|
28
|
+
group_dict[FI.hash] = FI.adjusted_group
|
|
29
|
+
|
|
30
|
+
V.cSR["query_group"] = V.cSR["qseqid"].apply(lambda x: group_dict.get(x))
|
|
31
|
+
|
|
32
|
+
# Indicate queries without any corresponding group
|
|
33
|
+
for FI in V.list_FI:
|
|
34
|
+
if FI.adjusted_group == "" and not ("noseq" in FI.issues):
|
|
35
|
+
FI.issues.add("nodb")
|
|
36
|
+
|
|
37
|
+
return V
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# assign gene to unclassified gene by search result
|
|
41
|
+
def assign_gene(result_dict, V, cutoff=0.99):
|
|
42
|
+
# if no query exists to assign, return dataset without operations
|
|
43
|
+
if len(result_dict.keys()) == 0:
|
|
44
|
+
return V
|
|
45
|
+
|
|
46
|
+
for result in result_dict:
|
|
47
|
+
# add gene column to result_dict
|
|
48
|
+
result_dict[result]["gene"] = result
|
|
49
|
+
|
|
50
|
+
# combine all result in single dataframe
|
|
51
|
+
gene_result_all = pd.concat([result_dict[result] for result in result_dict], axis=0)
|
|
52
|
+
|
|
53
|
+
# split by query
|
|
54
|
+
gene_result_grouped = gene_result_all.groupby(gene_result_all["qseqid"])
|
|
55
|
+
|
|
56
|
+
for FI in V.list_FI:
|
|
57
|
+
# for seq of FI
|
|
58
|
+
for n, seq in enumerate(FI.unclassified_seq):
|
|
59
|
+
try:
|
|
60
|
+
# get each of the dataframe for each FI
|
|
61
|
+
current_df = gene_result_grouped.get_group(f"{FI.hash}_{n}")
|
|
62
|
+
# sort by bitscore
|
|
63
|
+
# sorting has peformed after split for better performance
|
|
64
|
+
current_df.sort_values(by=["bitscore"], inplace=True, ascending=False)
|
|
65
|
+
# reset index to easily get maximum
|
|
66
|
+
current_df.reset_index(inplace=True, drop=True)
|
|
67
|
+
# get result stasifies over cutoff
|
|
68
|
+
cutoff_df = current_df[
|
|
69
|
+
current_df["bitscore"] > current_df["bitscore"][0] * cutoff
|
|
70
|
+
]
|
|
71
|
+
gene_count = len(set(cutoff_df["gene"]))
|
|
72
|
+
gene_list = list(set(cutoff_df["gene"]))
|
|
73
|
+
|
|
74
|
+
except:
|
|
75
|
+
gene_count = 0
|
|
76
|
+
gene_list = []
|
|
77
|
+
|
|
78
|
+
# if only 1 gene available, take it
|
|
79
|
+
if gene_count == 1:
|
|
80
|
+
FI.update_seq(gene_list[0], seq)
|
|
81
|
+
logging.info(f" Query seq in {FI.id} has assigned to {gene_list[0]}.")
|
|
82
|
+
# if no gene matched, warn it
|
|
83
|
+
elif gene_count == 0:
|
|
84
|
+
logging.warning(
|
|
85
|
+
f" Query seq in {FI.id} cannot be assigned to any gene. Check sequence. Skipping {FI.id}"
|
|
86
|
+
)
|
|
87
|
+
elif gene_count >= 2:
|
|
88
|
+
logging.warning(f" Query seq in {FI.id} has multiple matches to gene.")
|
|
89
|
+
FI.update_seq(gene_list[0], seq)
|
|
90
|
+
|
|
91
|
+
else:
|
|
92
|
+
logging.error("DEVELOPMENTAL ERROR IN GENE ASSIGN")
|
|
93
|
+
raise Exception
|
|
94
|
+
return V
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# cluster each of FI object and assign group
|
|
98
|
+
def cluster(FI, V, path, opt):
|
|
99
|
+
list_group = deepcopy(V.list_group)
|
|
100
|
+
|
|
101
|
+
# Reduce search df space by selecting only FI related ones
|
|
102
|
+
df_group = V.cSR.groupby(V.cSR["qseqid"])
|
|
103
|
+
list_id = list(set(V.cSR["qseqid"]))
|
|
104
|
+
df_search = df_group.get_group(FI.hash)
|
|
105
|
+
|
|
106
|
+
# delete V to reduce memory assumption
|
|
107
|
+
del V
|
|
108
|
+
gc.collect()
|
|
109
|
+
|
|
110
|
+
# If no evidence available, return it
|
|
111
|
+
if df_search is None:
|
|
112
|
+
# if confident is True, no adjusted_group for db is normal situation
|
|
113
|
+
if opt.confident is False and FI.datatype is "db":
|
|
114
|
+
logging.warning(f"No adjusted_group assigned to {FI}")
|
|
115
|
+
FI.adjusted_group = FI.group
|
|
116
|
+
return FI, None
|
|
117
|
+
|
|
118
|
+
# for db sequence with group, retain it
|
|
119
|
+
elif not (FI.group == "") and FI.datatype == "db": # or type(FI.group) != str):
|
|
120
|
+
FI.adjusted_group = FI.group
|
|
121
|
+
return FI, FI.adjusted_group
|
|
122
|
+
|
|
123
|
+
# update group if sequence does not have group
|
|
124
|
+
else:
|
|
125
|
+
# sorting has peformed after split for better performance
|
|
126
|
+
sorted_df_search = df_search.sort_values(by=["bitscore"], ascending=False)
|
|
127
|
+
# reset index to easily get maximum
|
|
128
|
+
sorted_df_search.reset_index(inplace=True, drop=True)
|
|
129
|
+
# get result stasifies over cutoff
|
|
130
|
+
cutoff_df = sorted_df_search[
|
|
131
|
+
sorted_df_search["bitscore"]
|
|
132
|
+
> sorted_df_search["bitscore"][0] * opt.cluster.cutoff
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
# Garbage collection
|
|
136
|
+
del df_search
|
|
137
|
+
del sorted_df_search
|
|
138
|
+
gc.collect()
|
|
139
|
+
|
|
140
|
+
group_count = len(set(cutoff_df["subject_group"]))
|
|
141
|
+
list_group = list(set(cutoff_df["subject_group"]))
|
|
142
|
+
|
|
143
|
+
# if first time of group update
|
|
144
|
+
if FI.adjusted_group == "" or FI.adjusted_group == "":
|
|
145
|
+
# if only 1 group available, take it
|
|
146
|
+
if group_count == 1:
|
|
147
|
+
FI.adjusted_group = list_group[0]
|
|
148
|
+
# if no group matched, warn it
|
|
149
|
+
elif group_count == 0:
|
|
150
|
+
logging.warning(
|
|
151
|
+
f"Query seq in {FI.id} cannot be assigned to group. Check sequence"
|
|
152
|
+
)
|
|
153
|
+
elif group_count >= 2:
|
|
154
|
+
logging.warning(
|
|
155
|
+
f"Query seq in {FI.id} has multiple matches to group, {list_group}."
|
|
156
|
+
)
|
|
157
|
+
FI.adjusted_group = list_group[0]
|
|
158
|
+
else:
|
|
159
|
+
logging.error("DEVELOPMENTAL ERROR IN GROUP ASSIGN")
|
|
160
|
+
raise Exception
|
|
161
|
+
|
|
162
|
+
logging.info(f"{FI.id} has clustered to {FI.adjusted_group}")
|
|
163
|
+
|
|
164
|
+
# if group already updated
|
|
165
|
+
else:
|
|
166
|
+
if not (FI.adjusted_group == ""):
|
|
167
|
+
if not (FI.adjusted_group in list_group):
|
|
168
|
+
logging.warning(f"Clustering result colliding in {FI.id}")
|
|
169
|
+
|
|
170
|
+
if len(list_group) > 0:
|
|
171
|
+
return FI, list_group[0]
|
|
172
|
+
else:
|
|
173
|
+
return FI, None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
### Append outgroup to given group-gene dataset by search matrix
|
|
177
|
+
def append_outgroup(V, df_search, gene, group, path, opt):
|
|
178
|
+
logging.info(f"Appending outgroup on group: {group}, Gene: {gene}")
|
|
179
|
+
list_FI = deepcopy(V.list_FI)
|
|
180
|
+
|
|
181
|
+
# In multiprocessing, delete V to reduce memory consumption
|
|
182
|
+
del V
|
|
183
|
+
gc.collect()
|
|
184
|
+
|
|
185
|
+
# ready for by sseqid hash, which group to append
|
|
186
|
+
# this time, append adjusted group
|
|
187
|
+
group_dict = {}
|
|
188
|
+
FI_dict = {}
|
|
189
|
+
|
|
190
|
+
for FI in list_FI:
|
|
191
|
+
group_dict[FI.hash] = FI.adjusted_group
|
|
192
|
+
FI_dict[FI.hash] = FI
|
|
193
|
+
|
|
194
|
+
# For non-concatenated analysis
|
|
195
|
+
# if gene != "concatenated":
|
|
196
|
+
# generate minimal bitscore cutoff that does not overlaps to query-query bitscore value range
|
|
197
|
+
|
|
198
|
+
## For getting inner group
|
|
199
|
+
cutoff_set_df = df_search[df_search["subject_group"] == group]
|
|
200
|
+
try:
|
|
201
|
+
# offset will prevent selecting outgroup too close to ingroup, which may confuse the monophyly of outgroup
|
|
202
|
+
bitscore_cutoff = max(
|
|
203
|
+
1, min(cutoff_set_df["bitscore"]) - opt.cluster.outgroupoffset
|
|
204
|
+
)
|
|
205
|
+
except:
|
|
206
|
+
bitscore_cutoff = 999999 # use infinite if failed
|
|
207
|
+
|
|
208
|
+
# print(f"Ingroup cutoff {bitscore_cutoff} selected for group {group} gene {gene}")
|
|
209
|
+
|
|
210
|
+
## get result stasifies over cutoff
|
|
211
|
+
# outgroup should be outside of ingroup
|
|
212
|
+
cutoff_df = df_search[df_search["bitscore"] < bitscore_cutoff]
|
|
213
|
+
|
|
214
|
+
# Remove malformat result, which bitscore is under 0
|
|
215
|
+
cutoff_df = cutoff_df[cutoff_df["bitscore"] > 0]
|
|
216
|
+
|
|
217
|
+
# split that same group to include all to alignment, and leave other groups for outgroup selection
|
|
218
|
+
cutoff_df = cutoff_df[cutoff_df["subject_group"] != group]
|
|
219
|
+
|
|
220
|
+
## For ambiugous database, mostly because of contaminated database
|
|
221
|
+
# For each of the input, should use different cutoff
|
|
222
|
+
ambiguous_db = set()
|
|
223
|
+
for qseqid, _df in cutoff_set_df.groupby(["qseqid"]):
|
|
224
|
+
# Select dataframe corresponding to current qseqid
|
|
225
|
+
df_qseqid = df_search[df_search["qseqid"] == qseqid]
|
|
226
|
+
"""
|
|
227
|
+
print(
|
|
228
|
+
f"Ambiguous ingroup cutoff selected for query {qseqid} group {group} gene {gene} cutoff {min(list(_df['bitscore']))}"
|
|
229
|
+
)
|
|
230
|
+
"""
|
|
231
|
+
# Get the list of subjects, which is closer than furtest ingroup
|
|
232
|
+
ambiguous_df = df_qseqid[df_qseqid["bitscore"] >= min(list(_df["bitscore"]))]
|
|
233
|
+
# Within the furthest match, get possible ingroups with ambiguous group
|
|
234
|
+
ambiguous_df = ambiguous_df[ambiguous_df["subject_group"] != group]
|
|
235
|
+
# Add inner ambiugities to ambiguous db
|
|
236
|
+
ambiguous_db.update([FI_dict[i] for i in list(ambiguous_df["sseqid"])])
|
|
237
|
+
|
|
238
|
+
ambiguous_db = list(ambiguous_db)
|
|
239
|
+
|
|
240
|
+
# If no or fewer than designated number of outgroup matches to condition, use flexible criteria
|
|
241
|
+
if cutoff_df.groupby(["subject_group"]).count().empty:
|
|
242
|
+
logging.warning(
|
|
243
|
+
f"Not enough outgroup sequences matched for group {group} | gene {gene}. There might be outlier sequence that does not matches to group. Trying flexible cutoff"
|
|
244
|
+
)
|
|
245
|
+
cutoff_df = df_search[df_search["bitscore"] > 0]
|
|
246
|
+
cutoff_df = cutoff_df[cutoff_df["subject_group"] != group]
|
|
247
|
+
|
|
248
|
+
elif cutoff_df.groupby(["subject_group"]).count()["sseqid"].max() < opt.maxoutgroup:
|
|
249
|
+
logging.warning(
|
|
250
|
+
f"Not enough outgroup sequences matched for group {group} | gene {gene}. There might be outlier sequence that does not matches to group. Trying flexible cutoff"
|
|
251
|
+
)
|
|
252
|
+
cutoff_df = df_search[df_search["bitscore"] > 0]
|
|
253
|
+
cutoff_df = cutoff_df[cutoff_df["subject_group"] != group]
|
|
254
|
+
|
|
255
|
+
# Garbage collection to reduce memory consumption in this process
|
|
256
|
+
del df_search
|
|
257
|
+
gc.collect()
|
|
258
|
+
|
|
259
|
+
# sort by bitscore
|
|
260
|
+
cutoff_df.sort_values(by=["bitscore"], inplace=True, ascending=False)
|
|
261
|
+
# reset index to easily get maximum
|
|
262
|
+
cutoff_df.reset_index(inplace=True, drop=True)
|
|
263
|
+
|
|
264
|
+
# iterate until designated number of sequences from the most closest group selected
|
|
265
|
+
# we should get outgroup from columns
|
|
266
|
+
# outgroup_dict = {group1 : [FI1, FI2, ...]}
|
|
267
|
+
outgroup_dict = {}
|
|
268
|
+
max_cnt = 0
|
|
269
|
+
max_group = ""
|
|
270
|
+
|
|
271
|
+
for n, subject_group in enumerate(cutoff_df["subject_group"]):
|
|
272
|
+
## Check if outgroup sequence that we're going to use actually exists
|
|
273
|
+
# If gene is not "concatenated", the outgroup sequence should actually exists
|
|
274
|
+
# keep in mind if case of certain gene is blank
|
|
275
|
+
|
|
276
|
+
cond1 = gene in FI_dict[cutoff_df["sseqid"][n]].seq
|
|
277
|
+
if cond1 is True:
|
|
278
|
+
cond1 = FI_dict[cutoff_df["sseqid"][n]].seq[gene] != ""
|
|
279
|
+
|
|
280
|
+
# If gene is "concatenated", any of the genes should exists
|
|
281
|
+
cond2 = (
|
|
282
|
+
gene == "concatenated"
|
|
283
|
+
and len(FI_dict[cutoff_df["sseqid"][n]].seq.keys()) > 0
|
|
284
|
+
)
|
|
285
|
+
if cond1 or cond2:
|
|
286
|
+
# if first sequence belonging to the group found, make new key to dict
|
|
287
|
+
if not (subject_group) in outgroup_dict:
|
|
288
|
+
outgroup_dict[subject_group] = [FI_dict[cutoff_df["sseqid"][n]]]
|
|
289
|
+
# if group key already exists in dict, append it
|
|
290
|
+
else:
|
|
291
|
+
if (
|
|
292
|
+
not (FI_dict[cutoff_df["sseqid"][n]])
|
|
293
|
+
in outgroup_dict[subject_group]
|
|
294
|
+
):
|
|
295
|
+
outgroup_dict[subject_group].append(FI_dict[cutoff_df["sseqid"][n]])
|
|
296
|
+
|
|
297
|
+
# if enough outgroup sequences found while running
|
|
298
|
+
if len(outgroup_dict[subject_group]) >= opt.maxoutgroup:
|
|
299
|
+
text_outgroup_list = "\n ".join(
|
|
300
|
+
[FI.id for FI in outgroup_dict[subject_group]]
|
|
301
|
+
)
|
|
302
|
+
logging.info(
|
|
303
|
+
f"Outgroup [{subject_group}] selected to [{group}]\n {text_outgroup_list}"
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
return (
|
|
307
|
+
group,
|
|
308
|
+
gene,
|
|
309
|
+
outgroup_dict[subject_group],
|
|
310
|
+
ambiguous_db,
|
|
311
|
+
)
|
|
312
|
+
else:
|
|
313
|
+
if len(outgroup_dict[subject_group]) > max_cnt:
|
|
314
|
+
max_cnt = len(outgroup_dict[subject_group])
|
|
315
|
+
max_group = subject_group
|
|
316
|
+
|
|
317
|
+
# If not enough outgroup sequences found while running
|
|
318
|
+
logging.warning(
|
|
319
|
+
f"Not enough sequences are available for outgroup number {opt.maxoutgroup} in {group}, using '{max_group}' despite of lower number"
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# If outgroup are selected
|
|
323
|
+
if not (max_group) == "":
|
|
324
|
+
logging.info(
|
|
325
|
+
f"Final outgroup selection for group {group} : {outgroup_dict[max_group]}"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
return (group, gene, outgroup_dict[max_group], ambiguous_db)
|
|
329
|
+
|
|
330
|
+
# If outgroup cannot be selected
|
|
331
|
+
else:
|
|
332
|
+
logging.warning(
|
|
333
|
+
f"No outgroup sequence available for {group}. Try to use\n A. Higher --cluster-evalue\n B. Lower --outgroupoffset\n C. Add closer sequence of {group} to database"
|
|
334
|
+
)
|
|
335
|
+
return (group, gene, [], [])
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def group_cluster_opt_generator(V, opt, path):
|
|
339
|
+
# cluster(FO, df_search, V, path, opt)
|
|
340
|
+
if len(V.list_qr_gene) == 0:
|
|
341
|
+
logging.error(
|
|
342
|
+
"In group_cluster_option_generator, no available query genes were selected"
|
|
343
|
+
)
|
|
344
|
+
raise Exception
|
|
345
|
+
|
|
346
|
+
# For concatenated analysis
|
|
347
|
+
else:
|
|
348
|
+
# cluster group by concatenated search result
|
|
349
|
+
list_id = list(set(V.cSR["qseqid"]))
|
|
350
|
+
for FI in V.list_FI:
|
|
351
|
+
if FI.hash in list_id:
|
|
352
|
+
V.opt_cluster.append((FI, V, path, opt))
|
|
353
|
+
|
|
354
|
+
return V
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
# opts ready for multithreading in outgroup append
|
|
358
|
+
def outgroup_append_opt_generator(V, path, opt):
|
|
359
|
+
opt_append_outgroup = []
|
|
360
|
+
|
|
361
|
+
#### Pararellize this part
|
|
362
|
+
# Assign different outgroup for each dataset
|
|
363
|
+
|
|
364
|
+
# if concatenated analysis is true
|
|
365
|
+
# concatenated
|
|
366
|
+
for group in V.dict_dataset:
|
|
367
|
+
if "concatenated" in V.dict_dataset[group]:
|
|
368
|
+
try:
|
|
369
|
+
df = V.cSR
|
|
370
|
+
df_group = df.groupby(df["query_group"])
|
|
371
|
+
df_group_ = df_group.get_group(group)
|
|
372
|
+
# Generating outgroup opt for multiprocessing
|
|
373
|
+
for gene in V.dict_dataset[group]:
|
|
374
|
+
opt_append_outgroup.append((V, df_group_, gene, group, path, opt))
|
|
375
|
+
|
|
376
|
+
except:
|
|
377
|
+
logging.warning(
|
|
378
|
+
f"{group} / concatenated dataset exists, but cannot append outgroup due to no corresponding search result"
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
return opt_append_outgroup
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
## Main cluster pipe
|
|
385
|
+
def pipe_cluster(V, opt, path):
|
|
386
|
+
# If clustering enabled
|
|
387
|
+
if opt.method.search in ("blast", "mmseqs"):
|
|
388
|
+
logging.info("group clustering")
|
|
389
|
+
|
|
390
|
+
# cluster opt generation for multiprocessing
|
|
391
|
+
# (FI, V, path, opt)
|
|
392
|
+
V = group_cluster_opt_generator(V, opt, path)
|
|
393
|
+
|
|
394
|
+
# run multiprocessing start
|
|
395
|
+
if opt.verbose < 3:
|
|
396
|
+
p = mp.Pool(opt.thread)
|
|
397
|
+
V.rslt_cluster = p.starmap(cluster, V.opt_cluster)
|
|
398
|
+
p.close()
|
|
399
|
+
p.join()
|
|
400
|
+
else:
|
|
401
|
+
# non-multithreading mode for debugging
|
|
402
|
+
V.rslt_cluster = [cluster(*o) for o in V.opt_cluster]
|
|
403
|
+
# gather cluster result
|
|
404
|
+
for cluster_result in V.rslt_cluster:
|
|
405
|
+
FI = cluster_result[0]
|
|
406
|
+
logging.debug((FI.id, FI.datatype, FI.group, FI.adjusted_group))
|
|
407
|
+
|
|
408
|
+
# replace group assigning result
|
|
409
|
+
# collect FI from cluster result
|
|
410
|
+
replace_FI = [r[0] for r in V.rslt_cluster]
|
|
411
|
+
# collect hash
|
|
412
|
+
replace_hash_FI = [FI.hash for FI in replace_FI]
|
|
413
|
+
# maintain not clustered result and append clustered result
|
|
414
|
+
V.list_FI = [
|
|
415
|
+
FI for FI in V.list_FI if not (FI.hash in replace_hash_FI)
|
|
416
|
+
] + replace_FI
|
|
417
|
+
# For syncyhronizing FI in dict_hash_FI to prevent error
|
|
418
|
+
for FI in replace_FI:
|
|
419
|
+
V.dict_hash_FI[FI.hash] = FI
|
|
420
|
+
|
|
421
|
+
V.list_group = list(set([r[1] for r in V.rslt_cluster if (not (r[1] is None))]))
|
|
422
|
+
|
|
423
|
+
if opt.queryonly is True:
|
|
424
|
+
for FI in V.list_FI:
|
|
425
|
+
if FI.datatype == "db":
|
|
426
|
+
FI.adjusted_group = FI.group
|
|
427
|
+
|
|
428
|
+
# Update dict_hash_FI
|
|
429
|
+
for FI in V.list_FI:
|
|
430
|
+
V.dict_hash_FI[FI.hash] = FI
|
|
431
|
+
|
|
432
|
+
for FI in V.list_FI:
|
|
433
|
+
logging.debug((FI.id, FI.datatype, FI.group, FI.adjusted_group))
|
|
434
|
+
|
|
435
|
+
# If not, try to use original groups in tabled format
|
|
436
|
+
else:
|
|
437
|
+
logging.info(
|
|
438
|
+
"[INFO] No searching method designated. Trying to use designated group"
|
|
439
|
+
)
|
|
440
|
+
group_list = get_naive_group(V)
|
|
441
|
+
## [WIP] Need to make validation process and warn if the input does not have
|
|
442
|
+
for FI in V.list_FI:
|
|
443
|
+
FI.adjusted_group = FI.group
|
|
444
|
+
|
|
445
|
+
# Update dict_hash_FI
|
|
446
|
+
for FI in V.list_FI:
|
|
447
|
+
V.dict_hash_FI[FI.hash] = FI
|
|
448
|
+
|
|
449
|
+
return V, opt, path
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
## Main outgroup appending pipeline
|
|
453
|
+
def pipe_append_outgroup(V, path, opt):
|
|
454
|
+
opt_append_outgroup = outgroup_append_opt_generator(V, path, opt)
|
|
455
|
+
|
|
456
|
+
# run multiprocessing start
|
|
457
|
+
if opt.verbose < 3:
|
|
458
|
+
p = mp.Pool(opt.thread)
|
|
459
|
+
result_append_outgroup = p.starmap(append_outgroup, opt_append_outgroup)
|
|
460
|
+
p.close()
|
|
461
|
+
p.join()
|
|
462
|
+
|
|
463
|
+
else:
|
|
464
|
+
# non-multithreading mode for debugging
|
|
465
|
+
result_append_outgroup = [append_outgroup(*o) for o in opt_append_outgroup]
|
|
466
|
+
|
|
467
|
+
# append outgroup by running result
|
|
468
|
+
# (group, gene, outgroup, ambiguous_group)
|
|
469
|
+
for result in result_append_outgroup:
|
|
470
|
+
# Parsing result
|
|
471
|
+
group = result[0]
|
|
472
|
+
gene = result[1]
|
|
473
|
+
outgroup = result[2]
|
|
474
|
+
ambiguous_group = result[3]
|
|
475
|
+
|
|
476
|
+
# Add outgroup and ambiguous groups to dataset
|
|
477
|
+
# Ambiguous groups are strains locating between outgroup and ingroups, so cannot be decided
|
|
478
|
+
if len(outgroup) == 0 and len(ambiguous_group) == 0:
|
|
479
|
+
logging.warning(
|
|
480
|
+
f"Removing {group} {gene} from analysis because outgroup cannot be selected"
|
|
481
|
+
)
|
|
482
|
+
V.dict_dataset[group].pop(gene, None)
|
|
483
|
+
|
|
484
|
+
else:
|
|
485
|
+
V.dict_dataset[group][gene].list_og_FI = outgroup
|
|
486
|
+
# Add ambiguous group to FI
|
|
487
|
+
V.dict_dataset[group][gene].list_db_FI += ambiguous_group
|
|
488
|
+
# Add outgroup and db in to dict_hash_FI
|
|
489
|
+
|
|
490
|
+
# Should find out why this does not work at the end
|
|
491
|
+
"""
|
|
492
|
+
for FI in outgroup + ambiguous_group:
|
|
493
|
+
# If already in dict_hash_FI, they have priority
|
|
494
|
+
if not (FI.hash in V.dict_hash_FI):
|
|
495
|
+
V.dict_hash_FI[FI.hash] = FI
|
|
496
|
+
"""
|
|
497
|
+
|
|
498
|
+
groups = deepcopy(list(V.dict_dataset.keys()))
|
|
499
|
+
|
|
500
|
+
for group in groups:
|
|
501
|
+
try:
|
|
502
|
+
if len(V.dict_dataset[group]) == 0:
|
|
503
|
+
logging.warning(
|
|
504
|
+
f"Removing {group} from analysis because outgroup cannot be selected to all genes"
|
|
505
|
+
)
|
|
506
|
+
V.dict_dataset.pop(group, None)
|
|
507
|
+
except:
|
|
508
|
+
pass
|
|
509
|
+
|
|
510
|
+
return V, path, opt
|