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/reporter.py
ADDED
|
@@ -0,0 +1,875 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import io
|
|
5
|
+
import logging
|
|
6
|
+
import plotly.express as px
|
|
7
|
+
import sys
|
|
8
|
+
from tabulate import tabulate
|
|
9
|
+
from funvip.src.tool import index_step
|
|
10
|
+
from funvip.src.save import save_df
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Temporary report for tree_interpretation_pipe
|
|
14
|
+
# To collect result from multiprocessing on tree_interpretation
|
|
15
|
+
# All abnormalities should be listed here
|
|
16
|
+
class Singlereport:
|
|
17
|
+
def __init__(self):
|
|
18
|
+
# FI info
|
|
19
|
+
self.id = ""
|
|
20
|
+
self.hash = ""
|
|
21
|
+
self.group = ""
|
|
22
|
+
self.gene = ""
|
|
23
|
+
self.species_original = ""
|
|
24
|
+
self.species_assigned = ""
|
|
25
|
+
|
|
26
|
+
# Analysis info
|
|
27
|
+
self.group_analysis = ""
|
|
28
|
+
|
|
29
|
+
# abnormalities
|
|
30
|
+
# for if ambiguous clade exists e.g. Amanita subglobosa "1", Amanita subglobosa "2"
|
|
31
|
+
# 0 if none of them exists
|
|
32
|
+
self.ambiguous = ""
|
|
33
|
+
self.flat = False
|
|
34
|
+
|
|
35
|
+
def update_group_analysis(self, group):
|
|
36
|
+
self.group_analysis = group
|
|
37
|
+
|
|
38
|
+
def update_group(self, group):
|
|
39
|
+
self.group = group
|
|
40
|
+
|
|
41
|
+
def update_gene(self, gene):
|
|
42
|
+
self.gene = gene
|
|
43
|
+
|
|
44
|
+
# Update original species string from (genus, species) tuple
|
|
45
|
+
def update_species_original(self, taxon):
|
|
46
|
+
self.species_original = " ".join(list(taxon))
|
|
47
|
+
|
|
48
|
+
# Update assigned species string
|
|
49
|
+
def update_species_assigned(self, taxon_str):
|
|
50
|
+
self.species_assigned = taxon_str
|
|
51
|
+
|
|
52
|
+
def __repr__(self):
|
|
53
|
+
return f"{self.hash} {self.gene}"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
#############################################################
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
#############################################################
|
|
60
|
+
### Final reporting data
|
|
61
|
+
### All reports should be generated from here
|
|
62
|
+
class Report:
|
|
63
|
+
# Try to make report in dictionary form, most of them should be printed in tabular form
|
|
64
|
+
def __init__(self):
|
|
65
|
+
# Should be finished after release
|
|
66
|
+
# self.metadata = Metadata()
|
|
67
|
+
|
|
68
|
+
# Dataset - group by gene
|
|
69
|
+
# Example)
|
|
70
|
+
# GROUP ITS BenA CaM Concatenated
|
|
71
|
+
# Penicillium O O Fail outgroup O
|
|
72
|
+
# Exilicaluis O no query O O
|
|
73
|
+
# Whether to use just simple O/X or detailed description is not determined
|
|
74
|
+
|
|
75
|
+
self.dataset = {"GROUP": []}
|
|
76
|
+
|
|
77
|
+
# Identification result statistics : how many of them were well identified
|
|
78
|
+
# Example)
|
|
79
|
+
#
|
|
80
|
+
|
|
81
|
+
self.statistics = {
|
|
82
|
+
"GROUP": [],
|
|
83
|
+
"IDENTIFIED": [],
|
|
84
|
+
"NEW SPECIES CANDIDATE": [],
|
|
85
|
+
"MISIDENTIFIED": [],
|
|
86
|
+
"AMBIGUOUS": [],
|
|
87
|
+
"ERROR": [],
|
|
88
|
+
"TOTAL": [],
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# For linking in html, should be added later (on GUI)
|
|
92
|
+
# self.statistics_link = {}
|
|
93
|
+
|
|
94
|
+
self.result = {
|
|
95
|
+
"ID": [],
|
|
96
|
+
"HASH": [],
|
|
97
|
+
"DATATYPE": [],
|
|
98
|
+
"ISSUES": [],
|
|
99
|
+
"GROUP_ORIGINAL": [],
|
|
100
|
+
"GROUP_ASSIGNED": [],
|
|
101
|
+
"SPECIES_ORIGINAL": [],
|
|
102
|
+
"SPECIES_ASSIGNED": [],
|
|
103
|
+
"STATUS": [],
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
self.query_result = None
|
|
107
|
+
|
|
108
|
+
def update_dataset(self, V, opt):
|
|
109
|
+
# Refresh when updating dataset
|
|
110
|
+
self.dataset = {"GROUP": []}
|
|
111
|
+
|
|
112
|
+
for gene in V.list_qr_gene:
|
|
113
|
+
self.dataset[gene.upper()] = []
|
|
114
|
+
|
|
115
|
+
self.dataset["CONCATENATE"] = []
|
|
116
|
+
|
|
117
|
+
for group in V.list_group:
|
|
118
|
+
self.dataset["GROUP"].append(group)
|
|
119
|
+
if group in V.dict_dataset:
|
|
120
|
+
for gene in V.list_qr_gene:
|
|
121
|
+
# if group - gene dataset exists
|
|
122
|
+
if gene in V.dict_dataset[group]:
|
|
123
|
+
self.dataset[gene.upper()].append("O")
|
|
124
|
+
# if gene exists but group does not exists
|
|
125
|
+
else:
|
|
126
|
+
self.dataset[gene.upper()].append("-")
|
|
127
|
+
|
|
128
|
+
if "concatenated" in V.dict_dataset[group]:
|
|
129
|
+
self.dataset["CONCATENATE"].append("O")
|
|
130
|
+
else:
|
|
131
|
+
self.dataset["CONCATENATE"].append("-")
|
|
132
|
+
|
|
133
|
+
else:
|
|
134
|
+
# If group does not exists
|
|
135
|
+
for gene in V.list_qr_gene:
|
|
136
|
+
self.dataset[gene.upper()].append("-")
|
|
137
|
+
|
|
138
|
+
self.dataset["CONCATENATE"].append("-")
|
|
139
|
+
|
|
140
|
+
# Combining result into dictionary form
|
|
141
|
+
def update_result(self, V, opt):
|
|
142
|
+
# get all gene list -> check if V.list_qr_gene is enough for queryonly : False analysis
|
|
143
|
+
set_gene = set(V.list_db_gene + V.list_qr_gene)
|
|
144
|
+
|
|
145
|
+
# Generate each gene identification report
|
|
146
|
+
for gene in sorted(list(set_gene)):
|
|
147
|
+
if gene != "concatenated":
|
|
148
|
+
self.result[f"{gene.upper()}_ASSIGNED"] = []
|
|
149
|
+
|
|
150
|
+
# Concatenated analysis will be operated seperately
|
|
151
|
+
# set_gene.discard("concatenated") try if without discarding works
|
|
152
|
+
|
|
153
|
+
## Collect result from each hash
|
|
154
|
+
# Reset self.result to prevent continue mode stacking result double for some columns
|
|
155
|
+
|
|
156
|
+
self.result["ID"] = []
|
|
157
|
+
self.result["HASH"] = []
|
|
158
|
+
self.result["DATATYPE"] = []
|
|
159
|
+
self.result["ISSUES"] = []
|
|
160
|
+
self.result["GROUP_ORIGINAL"] = []
|
|
161
|
+
self.result["GROUP_ASSIGNED"] = []
|
|
162
|
+
self.result["SPECIES_ORIGINAL"] = []
|
|
163
|
+
self.result["SPECIES_ASSIGNED"] = []
|
|
164
|
+
self.result["STATUS"] = []
|
|
165
|
+
|
|
166
|
+
# Make FI categorizing system
|
|
167
|
+
FI_category = {}
|
|
168
|
+
dict_og = {}
|
|
169
|
+
for group in V.dict_dataset:
|
|
170
|
+
for gene in V.dict_dataset[group]:
|
|
171
|
+
for FI in V.dict_dataset[group][gene].list_og_FI:
|
|
172
|
+
dict_og[FI.hash] = group
|
|
173
|
+
|
|
174
|
+
for _hash in V.dict_hash_FI:
|
|
175
|
+
FI = V.dict_hash_FI[_hash]
|
|
176
|
+
if FI.adjusted_group in V.dict_dataset:
|
|
177
|
+
FI_category[_hash] = FI.datatype
|
|
178
|
+
elif FI.hash in dict_og:
|
|
179
|
+
FI_category[FI.hash] = "outgroup"
|
|
180
|
+
|
|
181
|
+
for _hash in V.dict_hash_FI:
|
|
182
|
+
FI = V.dict_hash_FI[_hash]
|
|
183
|
+
|
|
184
|
+
# Common results
|
|
185
|
+
self.result["ID"].append(FI.original_id)
|
|
186
|
+
self.result["HASH"].append(FI.hash)
|
|
187
|
+
if str(FI.group).strip() == "":
|
|
188
|
+
self.result["GROUP_ORIGINAL"].append("-")
|
|
189
|
+
else:
|
|
190
|
+
self.result["GROUP_ORIGINAL"].append(FI.group)
|
|
191
|
+
|
|
192
|
+
if f"{FI.ori_genus} {FI.ori_species}".strip() == "":
|
|
193
|
+
self.result["SPECIES_ORIGINAL"].append("-")
|
|
194
|
+
else:
|
|
195
|
+
self.result["SPECIES_ORIGINAL"].append(
|
|
196
|
+
f"{FI.ori_genus} {FI.ori_species}"
|
|
197
|
+
)
|
|
198
|
+
self.result["ISSUES"].append(FI.get_issue_str())
|
|
199
|
+
|
|
200
|
+
# Collect result from only used sequences
|
|
201
|
+
if _hash in FI_category:
|
|
202
|
+
self.result["DATATYPE"].append(FI_category[_hash])
|
|
203
|
+
|
|
204
|
+
# If any of appropriate gene used
|
|
205
|
+
if FI.adjusted_group in V.dict_dataset:
|
|
206
|
+
self.result["GROUP_ASSIGNED"].append(FI.adjusted_group)
|
|
207
|
+
|
|
208
|
+
# Collect assigned result
|
|
209
|
+
for gene in set_gene:
|
|
210
|
+
# Check if data analysis had performed for specific FI, group, gene combination
|
|
211
|
+
if (
|
|
212
|
+
gene in FI.bygene_species
|
|
213
|
+
and len(FI.seq[gene]) > 0
|
|
214
|
+
and gene in V.dict_dataset[FI.adjusted_group]
|
|
215
|
+
):
|
|
216
|
+
self.result[f"{gene.upper()}_ASSIGNED"].append(
|
|
217
|
+
FI.bygene_species[gene]
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
else:
|
|
221
|
+
self.result[f"{gene.upper()}_ASSIGNED"].append("-")
|
|
222
|
+
|
|
223
|
+
# Add final identification result
|
|
224
|
+
if FI.final_species != "":
|
|
225
|
+
self.result["SPECIES_ASSIGNED"].append(f"{FI.final_species}")
|
|
226
|
+
else:
|
|
227
|
+
self.result["SPECIES_ASSIGNED"].append("UNDETERMINED")
|
|
228
|
+
|
|
229
|
+
# Should fix here
|
|
230
|
+
if FI_category[_hash] == "query":
|
|
231
|
+
if FI.final_species.strip() == "":
|
|
232
|
+
self.result["STATUS"].append("failed")
|
|
233
|
+
elif f"{FI.ori_genus} {FI.ori_species}".strip() == "":
|
|
234
|
+
if "sp." in FI.final_species.strip():
|
|
235
|
+
self.result["STATUS"].append("new species")
|
|
236
|
+
else:
|
|
237
|
+
self.result["STATUS"].append("assigned")
|
|
238
|
+
|
|
239
|
+
elif FI.final_species == f"{FI.ori_genus} {FI.ori_species}":
|
|
240
|
+
self.result["STATUS"].append("match")
|
|
241
|
+
|
|
242
|
+
elif FI.final_species != f"{FI.ori_genus} {FI.ori_species}":
|
|
243
|
+
self.result["STATUS"].append("conflict")
|
|
244
|
+
else:
|
|
245
|
+
self.result["STATUS"].append("-")
|
|
246
|
+
# For outgroup or ambiguous db
|
|
247
|
+
else:
|
|
248
|
+
self.result["GROUP_ASSIGNED"].append("-")
|
|
249
|
+
# Collect assigned result
|
|
250
|
+
for gene in set_gene:
|
|
251
|
+
self.result[f"{gene.upper()}_ASSIGNED"].append("-")
|
|
252
|
+
if FI.final_species != "":
|
|
253
|
+
self.result["SPECIES_ASSIGNED"].append(f"{FI.final_species}")
|
|
254
|
+
else:
|
|
255
|
+
self.result["SPECIES_ASSIGNED"].append("UNDETERMINED")
|
|
256
|
+
self.result["STATUS"].append("-")
|
|
257
|
+
|
|
258
|
+
# For unused FI
|
|
259
|
+
else:
|
|
260
|
+
self.result["DATATYPE"].append("unused")
|
|
261
|
+
|
|
262
|
+
for gene in set_gene:
|
|
263
|
+
# Check if data analysis had performed for specific FI, group, gene combination
|
|
264
|
+
self.result[f"{gene.upper()}_ASSIGNED"].append("-")
|
|
265
|
+
|
|
266
|
+
# Add final identification result
|
|
267
|
+
if FI.final_species != "":
|
|
268
|
+
self.result["SPECIES_ASSIGNED"].append(f"{FI.final_species}")
|
|
269
|
+
else:
|
|
270
|
+
self.result["SPECIES_ASSIGNED"].append("-")
|
|
271
|
+
|
|
272
|
+
self.result["STATUS"].append("unused")
|
|
273
|
+
|
|
274
|
+
## update query only result on report.txt
|
|
275
|
+
self.query_result = pd.DataFrame(self.result)
|
|
276
|
+
# Filter if queryonly is True
|
|
277
|
+
if opt.queryonly is True:
|
|
278
|
+
self.query_result = self.query_result[
|
|
279
|
+
self.query_result["DATATYPE"] == "query"
|
|
280
|
+
]
|
|
281
|
+
|
|
282
|
+
### Update statistics by result
|
|
283
|
+
|
|
284
|
+
# Groupby group
|
|
285
|
+
df_result_group = self.query_result.groupby(["GROUP_ASSIGNED"])
|
|
286
|
+
|
|
287
|
+
# Count groups
|
|
288
|
+
for group in sorted(list(set(self.query_result["GROUP_ASSIGNED"]))):
|
|
289
|
+
df_group = df_result_group.get_group(group)
|
|
290
|
+
|
|
291
|
+
# Collect statistics
|
|
292
|
+
"""
|
|
293
|
+
cnt_correctly_identified = list(df_group["STATUS"]).count(
|
|
294
|
+
"correctly identified"
|
|
295
|
+
)
|
|
296
|
+
cnt_undetermined = list(df_group["STATUS"]).count("undetermined")
|
|
297
|
+
cnt_new_species_candidate = list(df_group["STATUS"]).count(
|
|
298
|
+
"new species candidate"
|
|
299
|
+
)
|
|
300
|
+
cnt_misidentified = list(df_group["STATUS"]).count("misidentified")
|
|
301
|
+
cnt_error = list(df_group["STATUS"]).count("ERROR")
|
|
302
|
+
cnt_total = sum(
|
|
303
|
+
(
|
|
304
|
+
cnt_correctly_identified,
|
|
305
|
+
cnt_undetermined,
|
|
306
|
+
cnt_new_species_candidate,
|
|
307
|
+
cnt_misidentified,
|
|
308
|
+
cnt_error,
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
# Write into dictionary
|
|
314
|
+
"""
|
|
315
|
+
self.statistics["GROUP"].append(group)
|
|
316
|
+
self.statistics["IDENTIFIED"].append(cnt_correctly_identified)
|
|
317
|
+
self.statistics["AMBIGUOUS"].append(cnt_undetermined)
|
|
318
|
+
self.statistics["NEW SPECIES CANDIDATE"].append(cnt_new_species_candidate)
|
|
319
|
+
self.statistics["MISIDENTIFIED"].append(cnt_misidentified)
|
|
320
|
+
self.statistics["ERROR"].append(cnt_error)
|
|
321
|
+
self.statistics["TOTAL"].append(cnt_total)
|
|
322
|
+
"""
|
|
323
|
+
|
|
324
|
+
# Add final summations
|
|
325
|
+
"""
|
|
326
|
+
self.statistics["GROUP"].append("TOTAL")
|
|
327
|
+
self.statistics["IDENTIFIED"].append(sum(self.statistics["IDENTIFIED"]))
|
|
328
|
+
self.statistics["AMBIGUOUS"].append(sum(self.statistics["AMBIGUOUS"]))
|
|
329
|
+
self.statistics["NEW SPECIES CANDIDATE"].append(
|
|
330
|
+
sum(self.statistics["NEW SPECIES CANDIDATE"])
|
|
331
|
+
)
|
|
332
|
+
self.statistics["MISIDENTIFIED"].append(sum(self.statistics["MISIDENTIFIED"]))
|
|
333
|
+
self.statistics["ERROR"].append(sum(self.statistics["ERROR"]))
|
|
334
|
+
self.statistics["TOTAL"].append(sum(self.statistics["TOTAL"]))
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
### Main report runner
|
|
338
|
+
# Update report by pipeline step
|
|
339
|
+
def update_report(self, V, path, opt, step, version, GenMine_flag):
|
|
340
|
+
if step == "setup":
|
|
341
|
+
self.report_text(V, path, opt, step, version, GenMine_flag)
|
|
342
|
+
elif step == "search":
|
|
343
|
+
self.report_table(V, path, opt, step)
|
|
344
|
+
self.report_text(V, path, opt, step, version, GenMine_flag)
|
|
345
|
+
elif step == "cluster":
|
|
346
|
+
self.update_dataset(V, opt)
|
|
347
|
+
self.report_table(V, path, opt, step)
|
|
348
|
+
self.report_text(V, path, opt, step, version, GenMine_flag)
|
|
349
|
+
# Datasets are made after clustering
|
|
350
|
+
elif step == "align":
|
|
351
|
+
self.report_text(V, path, opt, step, version, GenMine_flag)
|
|
352
|
+
elif step == "trim":
|
|
353
|
+
self.report_text(V, path, opt, step, version, GenMine_flag)
|
|
354
|
+
elif step == "concatenate":
|
|
355
|
+
self.update_dataset(V, opt) # To check align fails
|
|
356
|
+
self.report_text(V, path, opt, step, version, GenMine_flag)
|
|
357
|
+
elif step == "modeltest":
|
|
358
|
+
self.report_text(V, path, opt, step, version, GenMine_flag)
|
|
359
|
+
elif step == "tree":
|
|
360
|
+
self.report_text(V, path, opt, step, version, GenMine_flag)
|
|
361
|
+
elif step == "visualize":
|
|
362
|
+
self.report_text(V, path, opt, step, version, GenMine_flag)
|
|
363
|
+
elif step == "report":
|
|
364
|
+
self.update_result(V, opt)
|
|
365
|
+
self.report_table(V, path, opt, step)
|
|
366
|
+
self.report_text(V, path, opt, step, version, GenMine_flag)
|
|
367
|
+
else:
|
|
368
|
+
logging.error(
|
|
369
|
+
f"DEVELOPMENTAL ERROR : BAD STEP INPUT {step} WHILE UPDATE REPORT"
|
|
370
|
+
)
|
|
371
|
+
raise Exception
|
|
372
|
+
|
|
373
|
+
# Generate report in text file
|
|
374
|
+
def report_text(self, V, path, opt, step, version, GenMine_flag):
|
|
375
|
+
# Rewrite everytime when called, the io step won't be that much
|
|
376
|
+
with open(f"{path.root}/{opt.runname}.report.txt", "wt", encoding="UTF8") as f:
|
|
377
|
+
if index_step(step) >= 0:
|
|
378
|
+
f.write(
|
|
379
|
+
"Visualization of this file is optimized to web browsers (such as chrome) or width unlimited text editors (such as visual studio code)\n\n"
|
|
380
|
+
)
|
|
381
|
+
f.write("FunVIP Report\n\n")
|
|
382
|
+
## Write runinfo
|
|
383
|
+
f.write("[INFO]\n")
|
|
384
|
+
# Runname
|
|
385
|
+
f.write(f"RUNNAME : {opt.runname}\n")
|
|
386
|
+
# Running date
|
|
387
|
+
f.write(
|
|
388
|
+
f"DATE : {datetime.datetime.now().strftime('%Y%m%d-%H%M%S')}\n"
|
|
389
|
+
)
|
|
390
|
+
# Running time
|
|
391
|
+
# f.write(f"TIME CONSUMED: {}\n")
|
|
392
|
+
# Number of warnings
|
|
393
|
+
# f.write(f"WARNINGS : {}\n")
|
|
394
|
+
# Number of errors
|
|
395
|
+
# f.write(f"ERRORS : {}\n")
|
|
396
|
+
if step == "report":
|
|
397
|
+
step_status = "pipeline succesfully finished"
|
|
398
|
+
else:
|
|
399
|
+
step_status = (
|
|
400
|
+
"pipeline unexpectly terminated - please check log file"
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
f.write(f"FINAL STEP : {step} ({step_status})\n")
|
|
404
|
+
|
|
405
|
+
f.write("\n")
|
|
406
|
+
|
|
407
|
+
## Write locations to result files
|
|
408
|
+
if index_step(step) >= 0:
|
|
409
|
+
# DB and Query locations can be written after input step
|
|
410
|
+
f.write(f"* DB files are saved in {path.out_db}\n")
|
|
411
|
+
f.write(f"* Query files are saved in {path.out_query}\n")
|
|
412
|
+
# Options location can be written after option validation step
|
|
413
|
+
f.write(f"* Options are saved in {path.root}/Options.yaml\n")
|
|
414
|
+
|
|
415
|
+
if index_step(step) >= 2:
|
|
416
|
+
# Datset table can be written after cluster and outgroup selection step
|
|
417
|
+
f.write(
|
|
418
|
+
f"* Dataset table can be found in {path.root}/{opt.runname}_Dataset.{opt.tableformat}\n"
|
|
419
|
+
)
|
|
420
|
+
# Identification table can be written after cluster step
|
|
421
|
+
f.write(
|
|
422
|
+
f"* Identification result table can be found in {path.root}/{opt.runname}_Identification.{opt.tableformat}\n"
|
|
423
|
+
)
|
|
424
|
+
f.write(f"* Log files can be found in {path.log}\n")
|
|
425
|
+
|
|
426
|
+
if index_step(step) >= 8:
|
|
427
|
+
f.write(f"* Tree files can be found in {path.out_tree}\n")
|
|
428
|
+
|
|
429
|
+
f.write("\n")
|
|
430
|
+
|
|
431
|
+
## Write full commands used
|
|
432
|
+
if index_step(step) >= 0:
|
|
433
|
+
f.write(f"[COMMAND]\n")
|
|
434
|
+
f.write(f"If you want to regenerate this result, use\n")
|
|
435
|
+
f.write(f"\n\tFUNVIP {' '.join(sys.argv[:-1])}\n\n")
|
|
436
|
+
|
|
437
|
+
## Write options used (in concise form)
|
|
438
|
+
if index_step(step) >= 0:
|
|
439
|
+
f.write(f"[OPTION]\n")
|
|
440
|
+
f.write(f"DB: {opt.query}\n")
|
|
441
|
+
f.write(f"QUERY: {opt.db}\n")
|
|
442
|
+
f.write(f"GENE: {opt.gene}\n")
|
|
443
|
+
f.write(f"EMAIL: {opt.email}\n")
|
|
444
|
+
f.write(f"API: {opt.api}\n")
|
|
445
|
+
f.write(f"TEST: {opt.test}\n")
|
|
446
|
+
f.write(f"THREAD: {opt.thread}\n")
|
|
447
|
+
f.write(f"OUTDIR: {opt.outdir}\n")
|
|
448
|
+
f.write(f"RUNNAME: {opt.runname}\n")
|
|
449
|
+
f.write(f"MODE: {opt.mode}\n")
|
|
450
|
+
f.write(f"CONTINUE_FROM_PREVIOUS: {opt.continue_from_previous}\n")
|
|
451
|
+
f.write(f"CRITERION: {opt.criterion}\n")
|
|
452
|
+
f.write(f"STEP: {opt.step}\n")
|
|
453
|
+
f.write(f"LEVEL: {opt.level}\n")
|
|
454
|
+
f.write(f"QUERYONLY: {opt.queryonly}\n")
|
|
455
|
+
f.write(f"CONFIDENT: {opt.confident}\n")
|
|
456
|
+
f.write(f"VERBOSE: {opt.verbose}\n")
|
|
457
|
+
f.write(f"MAXOUTGROUP: {opt.maxoutgroup}\n")
|
|
458
|
+
f.write(f"COLLAPSEDISTCUTOFF: {opt.collapsedistcutoff}\n")
|
|
459
|
+
f.write(f"COLLAPSEBSCUTOFF: {opt.collapsebscutoff}\n")
|
|
460
|
+
f.write(f"BOOTSTRAP: {opt.bootstrap}\n")
|
|
461
|
+
f.write(f"SOLVEFLAT: {opt.solveflat}\n")
|
|
462
|
+
f.write(f"REGEX: {opt.regex}\n")
|
|
463
|
+
f.write(f"AVX: {opt.avx}\n")
|
|
464
|
+
f.write(f"CACHEDB: {opt.cachedb}\n")
|
|
465
|
+
f.write(f"USECACHE: {opt.usecache}\n")
|
|
466
|
+
f.write(f"TABLEFORMAT: {opt.tableformat}\n")
|
|
467
|
+
f.write(f"NOSEARCHRESULT: {opt.nosearchresult}\n")
|
|
468
|
+
f.write(f"METHOD: \n")
|
|
469
|
+
f.write(f" - SEARCH: {opt.method.search}\n")
|
|
470
|
+
f.write(f" - ALIGNMENT: {opt.method.alignment}\n")
|
|
471
|
+
f.write(f" - TRIM: {opt.method.trim}\n")
|
|
472
|
+
f.write(f" - MODELTEST: {opt.method.modeltest}\n")
|
|
473
|
+
f.write(f" - TREE: {opt.method.tree}\n")
|
|
474
|
+
f.write(f"VISUALIZE: \n")
|
|
475
|
+
f.write(f" - BSCUTOFF: {opt.visualize.bscutoff}\n")
|
|
476
|
+
f.write(f" - HIGHLIGHT: {opt.visualize.highlight}\n")
|
|
477
|
+
f.write(f" - HEIGHTMULTIPLIER: {opt.visualize.heightmultiplier}\n")
|
|
478
|
+
f.write(f" - MAXWORDLENGTH: {opt.visualize.maxwordlength}\n")
|
|
479
|
+
f.write(f" - BGCOlOR: {opt.visualize.backgroundcolor}\n")
|
|
480
|
+
f.write(f" - OUTGROUPCOLOR: {opt.visualize.outgroupcolor}\n")
|
|
481
|
+
f.write(f" - FTYPE: {opt.visualize.ftype}\n")
|
|
482
|
+
f.write(f" - FSIZE: {opt.visualize.fsize}\n")
|
|
483
|
+
f.write(f" - FSIZE_BOOTSTRAP: {opt.visualize.fsize_bootstrap}\n")
|
|
484
|
+
f.write(f"CLUSTER: \n")
|
|
485
|
+
f.write(f" - CUTOFF: {opt.cluster.cutoff}\n")
|
|
486
|
+
f.write(f" - EVALUE: {opt.cluster.evalue}\n")
|
|
487
|
+
f.write(f" - WORDSIZE: {opt.cluster.wordsize}\n")
|
|
488
|
+
f.write(f"MAFFT: \n")
|
|
489
|
+
f.write(f" - ALGORITHM: {opt.mafft.algorithm}\n")
|
|
490
|
+
f.write(f" - OP: {opt.mafft.op}\n")
|
|
491
|
+
f.write(f" - EP: {opt.mafft.ep}\n")
|
|
492
|
+
f.write(f"TRIMAL: \n")
|
|
493
|
+
f.write(f" - ALGORITHM: {opt.trimal.algorithm}\n")
|
|
494
|
+
f.write(f" - GT: {opt.trimal.gt}\n")
|
|
495
|
+
f.write("\n")
|
|
496
|
+
|
|
497
|
+
## Write datasets
|
|
498
|
+
# Should be written after clustering
|
|
499
|
+
if index_step(step) >= 2:
|
|
500
|
+
f.write(f"[DATASET]\n")
|
|
501
|
+
f.write(tabulate(self.dataset, headers=self.dataset.keys()))
|
|
502
|
+
f.write("\n\n")
|
|
503
|
+
f.write("O : Group - Gene dataset analysis done\n")
|
|
504
|
+
f.write(
|
|
505
|
+
"- : Group - Gene dataset analysis cannot be performed (absence of query sequence, no appropriate outgroup, alignment fail etc..)\n"
|
|
506
|
+
)
|
|
507
|
+
f.write("\n\n")
|
|
508
|
+
|
|
509
|
+
## Write identification statistics
|
|
510
|
+
# Should be written after tree_interpretation
|
|
511
|
+
'''
|
|
512
|
+
if index_step(step) >= 9:
|
|
513
|
+
f.write(f"[STATISTICS]\n")
|
|
514
|
+
f.write(tabulate(self.statistics, headers=self.statistics.keys()))
|
|
515
|
+
f.write("\n\n")
|
|
516
|
+
f.write(
|
|
517
|
+
"IDENTIFIED : Number of well-identified strains without any concerns. \n"
|
|
518
|
+
)
|
|
519
|
+
"""
|
|
520
|
+
f.write(
|
|
521
|
+
"AMBIGUOUS : Multiple clades with same taxon name. Your database may contain misidentified sequences. \n"
|
|
522
|
+
)
|
|
523
|
+
"""
|
|
524
|
+
f.write(
|
|
525
|
+
"NEW SPECIES CANDIDATE : New species candidate strains found by topology, phylogenetic distance and bootstrap criteria\n"
|
|
526
|
+
)
|
|
527
|
+
f.write(
|
|
528
|
+
"MISIDENTIFIED : Strains that shows different identification result from original annotation\n"
|
|
529
|
+
)
|
|
530
|
+
f.write(
|
|
531
|
+
"ERROR : Strains that cannot be analyzed. Please check if appropriate database sequence / outgroup sequences are given\n"
|
|
532
|
+
)
|
|
533
|
+
f.write("\n\n")
|
|
534
|
+
'''
|
|
535
|
+
|
|
536
|
+
## Write identification result
|
|
537
|
+
# Should be written after tree_interpretation
|
|
538
|
+
if index_step(step) >= 9:
|
|
539
|
+
f.write(f"[IDENTIFICATION]\n")
|
|
540
|
+
if opt.queryonly is True:
|
|
541
|
+
f.write(
|
|
542
|
+
tabulate(
|
|
543
|
+
self.query_result,
|
|
544
|
+
headers=self.query_result.columns,
|
|
545
|
+
showindex=False,
|
|
546
|
+
)
|
|
547
|
+
)
|
|
548
|
+
else:
|
|
549
|
+
f.write(tabulate(self.result, headers=self.result.keys()))
|
|
550
|
+
f.write("\n\n")
|
|
551
|
+
f.write("ID : Name of the strain\n")
|
|
552
|
+
f.write(
|
|
553
|
+
"HASH : Temporary name of the strain to prevent unexpected error during run. Use this when manually edit intermediate step data and run from middle, or debugging unexpectively terminated run\n"
|
|
554
|
+
)
|
|
555
|
+
f.write("DATATYPE : query or database\n")
|
|
556
|
+
f.write("GROUP_ORIGINAL : group name given by user\n")
|
|
557
|
+
f.write("GROUP_ASSIGNED : group assigned by FunVIP clustering\n")
|
|
558
|
+
f.write("SPECIES_ORIGINAL : species name given by user\n")
|
|
559
|
+
f.write(
|
|
560
|
+
"SPECIES_ASSIGNED : final species name (usually result from concatenated analysis) assigned by FunVIP tree_interpretation\n"
|
|
561
|
+
)
|
|
562
|
+
f.write(
|
|
563
|
+
"FLAT_BRANCH : Strains with flat_branch in phylogenetic analysis. If checked, please check your barcode region have enough taxonomic resolution\n"
|
|
564
|
+
)
|
|
565
|
+
f.write(
|
|
566
|
+
"INCONSISTENT : Strains that show different identification result across genes. Please check sequences were contaminated or misused. \n"
|
|
567
|
+
)
|
|
568
|
+
"""
|
|
569
|
+
f.write(
|
|
570
|
+
"AMBIGUOUS : Multiple clades with same taxon name. Your database may contain misidentified sequences. \n"
|
|
571
|
+
)
|
|
572
|
+
"""
|
|
573
|
+
|
|
574
|
+
f.write("\n\n")
|
|
575
|
+
|
|
576
|
+
## Write identification methods
|
|
577
|
+
# Can be written after clustering step
|
|
578
|
+
if index_step(step) >= 1:
|
|
579
|
+
f.write(f"[METHOD]\n")
|
|
580
|
+
f.write(f"Sequences were identified with FunVIP {version.FunVIP}\n")
|
|
581
|
+
f.write("\n")
|
|
582
|
+
|
|
583
|
+
cnt_db = len([FI for FI in V.list_FI if FI.datatype == "db"])
|
|
584
|
+
cnt_query = len([FI for FI in V.list_FI if FI.datatype == "query"])
|
|
585
|
+
|
|
586
|
+
f.write("- Sequence validation -\n")
|
|
587
|
+
f.write(
|
|
588
|
+
f"Total of {cnt_db} database strains and {cnt_query} strains "
|
|
589
|
+
f"were used for analysis. "
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
f.write(
|
|
593
|
+
f"Sequences used for analysis were first adjusted to prevent errors during analysis, "
|
|
594
|
+
f"such as containing invalid bases, non-ascii unicodes, or empty database.\n"
|
|
595
|
+
# f"During sequences validation step, {cnt_warning} warnings and {cnt_error} errors occured"
|
|
596
|
+
)
|
|
597
|
+
f.write("\n")
|
|
598
|
+
"""
|
|
599
|
+
for warning in validate_input_warning:
|
|
600
|
+
f.write(f"\n")
|
|
601
|
+
for error in validate_input_error:
|
|
602
|
+
f.write(f"\n")
|
|
603
|
+
"""
|
|
604
|
+
f.write(
|
|
605
|
+
"* Most of the warnings in this step are usually typo problems "
|
|
606
|
+
"(blanks, tabs, foreign languages that cannot be used in certain programs - like german umlauts) "
|
|
607
|
+
"and can be automatically fixed by FunVIP. So you don't have to consider about it that much if you are not going to directly publish.\n"
|
|
608
|
+
)
|
|
609
|
+
f.write("\n")
|
|
610
|
+
|
|
611
|
+
if index_step(step) >= 2:
|
|
612
|
+
f.write("- Sequence type identification -\n")
|
|
613
|
+
f.write(
|
|
614
|
+
f"Sequence type (loci or gene) of query sequences were examined through {opt.method.search} search "
|
|
615
|
+
f"by using gene of the closeset match. However, ambiguous match with multiple number of genes were revised. "
|
|
616
|
+
# f"During gene clustering, {cnt_warning} warnings and {cnt_error} errors occured "
|
|
617
|
+
)
|
|
618
|
+
f.write("\n")
|
|
619
|
+
"""
|
|
620
|
+
for warning in gene_clustering_warning:
|
|
621
|
+
f.write(f"\n")
|
|
622
|
+
for error in gene_clustering_error:
|
|
623
|
+
f.write(f"\n")
|
|
624
|
+
"""
|
|
625
|
+
f.write("\n")
|
|
626
|
+
|
|
627
|
+
f.write("- Group assignment -\n")
|
|
628
|
+
f.write(
|
|
629
|
+
f"Sequences were grouped to each datasets by {opt.level} level for phylogenetic analysis through {opt.method.search} search. "
|
|
630
|
+
f"Therefore, {opt.level} of query sequences are either assigned (when {opt.level} is not given) or validated for clustering "
|
|
631
|
+
# f"During clustering, {cnt_warning} warnings and {cnt_error} errors occured "
|
|
632
|
+
)
|
|
633
|
+
f.write("\n")
|
|
634
|
+
"""
|
|
635
|
+
for warning in group_clustering_warning:
|
|
636
|
+
f.write(f"\n")
|
|
637
|
+
for error in group_clustering_error:
|
|
638
|
+
f.write(f"\n")
|
|
639
|
+
"""
|
|
640
|
+
f.write("\n")
|
|
641
|
+
|
|
642
|
+
f.write("- Outgroup selection -\n")
|
|
643
|
+
f.write(
|
|
644
|
+
f"Outgroup sequences were appended to each datasets. "
|
|
645
|
+
f"{opt.maxoutgroup} sequences closest, but apperantly in distinct group were found by {opt.method.search} search. "
|
|
646
|
+
# f"During outgroup selection, {cnt_warning} warnings and {cnt_error} errors occured"
|
|
647
|
+
)
|
|
648
|
+
f.write("\n")
|
|
649
|
+
"""
|
|
650
|
+
for warning in append_outgroup_warning:
|
|
651
|
+
f.write(f"\n")
|
|
652
|
+
for error in append_outgroup_error:
|
|
653
|
+
f.write(f"\n")
|
|
654
|
+
"""
|
|
655
|
+
f.write("\n")
|
|
656
|
+
|
|
657
|
+
f.write("- Multiple sequence alignment -\n")
|
|
658
|
+
f.write(
|
|
659
|
+
f"Multiple sequence alignment to each datasets were performed with {opt.method.alignment}. "
|
|
660
|
+
f"{opt.mafft.algorithm} algorithm was selected with --op {opt.mafft.op} and --ep {opt.mafft.ep} options. "
|
|
661
|
+
# f"During alignment, {cnt_warning} warnings and {cnt_error} errors occured"
|
|
662
|
+
)
|
|
663
|
+
f.write("\n")
|
|
664
|
+
"""
|
|
665
|
+
for warning in mafft_warning:
|
|
666
|
+
f.write(f"\n")
|
|
667
|
+
for error in mafft_error:
|
|
668
|
+
f.write(f"\n")
|
|
669
|
+
"""
|
|
670
|
+
f.write("\n")
|
|
671
|
+
|
|
672
|
+
f.write("- Alignment trimming -\n")
|
|
673
|
+
|
|
674
|
+
if not opt.method.trim == "none":
|
|
675
|
+
f.write(
|
|
676
|
+
f"Trimming to each datasets were performed with {opt.method.trim}. "
|
|
677
|
+
# f"{somewhat} options were used"
|
|
678
|
+
# f"During trimming, {cnt_warning} warnings and {cnt_error} errors occured"
|
|
679
|
+
)
|
|
680
|
+
else:
|
|
681
|
+
f.write(f"No trimming performed on sequence alignments. ")
|
|
682
|
+
f.write("\n")
|
|
683
|
+
"""
|
|
684
|
+
for warning in trim_warning:
|
|
685
|
+
f.write(f"\n")
|
|
686
|
+
for error in trim_error:
|
|
687
|
+
f.write(f"\n")
|
|
688
|
+
"""
|
|
689
|
+
f.write("\n")
|
|
690
|
+
|
|
691
|
+
f.write("- Phylogenetic tree construction -\n")
|
|
692
|
+
f.write(
|
|
693
|
+
f"Maximum Likelihood tree analysis to each datasets were performed with {opt.method.tree}. "
|
|
694
|
+
# f"{somewhat} options were used"
|
|
695
|
+
# f"During trimming, {cnt_warning} warnings and {cnt_error} errors occured"
|
|
696
|
+
)
|
|
697
|
+
f.write("\n")
|
|
698
|
+
"""
|
|
699
|
+
for warning in tree_warning:
|
|
700
|
+
f.write(f"\n")
|
|
701
|
+
for error in tree_error:
|
|
702
|
+
f.write(f"\n")
|
|
703
|
+
"""
|
|
704
|
+
f.write("\n")
|
|
705
|
+
|
|
706
|
+
f.write("- Phylogenetic tree interpretation and identification -\n")
|
|
707
|
+
f.write(
|
|
708
|
+
f"Tree interpretation were performed to each datasets for species delimitation and tree visualization. "
|
|
709
|
+
f"Trees were rerooted by outgroup clades. "
|
|
710
|
+
f"Leaves in ambiguous tree topology, with over {opt.collapsedistcutoff} tree distance "
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
if opt.collapsebscutoff < 100:
|
|
714
|
+
f.write(
|
|
715
|
+
f"or over {opt.collapsebscutoff} support"
|
|
716
|
+
# f"During tree interpretation, {cnt_warning} warnings and {cnt_error} errors occured"
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
f.write(
|
|
720
|
+
f"in common ancestor diverge were considered as distinct species. "
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
f.write("\n")
|
|
724
|
+
"""
|
|
725
|
+
for warning in tree_interpretation_warning:
|
|
726
|
+
f.write(f"\n")
|
|
727
|
+
for error in tree_interpretation_error:
|
|
728
|
+
f.write(f"\n")
|
|
729
|
+
"""
|
|
730
|
+
f.write("\n")
|
|
731
|
+
|
|
732
|
+
"""
|
|
733
|
+
f.write(
|
|
734
|
+
f"As a result of FunVIP analysis, a total of {cnt_query} strains were identified"
|
|
735
|
+
f"{cnt_query} sequences constists of"
|
|
736
|
+
f"{cnt_consistent} well identified strains,"
|
|
737
|
+
f"{cnt_ambiguous} strains that showed different results by genes,"
|
|
738
|
+
f"{cnt_new_species_candidates} new species candidates,"
|
|
739
|
+
f"{cnt_error} failed on analysis due to error"
|
|
740
|
+
f"During tree interpreation, {cnt_warning} warnings and {cnt_error} errors occured"
|
|
741
|
+
)
|
|
742
|
+
for warning in tree_interpretation_warning:
|
|
743
|
+
f.write(f"\n")
|
|
744
|
+
for error in tree_interpretation_error:
|
|
745
|
+
f.write(f"\n")
|
|
746
|
+
"""
|
|
747
|
+
|
|
748
|
+
f.write("\n")
|
|
749
|
+
f.write(
|
|
750
|
+
f"* For precise identification and publications, please double check all warnings and errors\n"
|
|
751
|
+
)
|
|
752
|
+
f.write("\n")
|
|
753
|
+
|
|
754
|
+
# Generate software list by options
|
|
755
|
+
software_list = ["FunVIP"]
|
|
756
|
+
if GenMine_flag != 0:
|
|
757
|
+
software_list.append("GenMine")
|
|
758
|
+
|
|
759
|
+
if index_step(step) >= 1:
|
|
760
|
+
if opt.method.search == "blast":
|
|
761
|
+
software_list.append("BLASTn")
|
|
762
|
+
elif opt.method.search == "mmseqs":
|
|
763
|
+
software_list.append("MMseqs2")
|
|
764
|
+
|
|
765
|
+
if index_step(step) >= 3:
|
|
766
|
+
if opt.method.alignment == "mafft":
|
|
767
|
+
software_list.append("MAFFT")
|
|
768
|
+
|
|
769
|
+
if index_step(step) >= 4:
|
|
770
|
+
if opt.method.trim == "gblocks":
|
|
771
|
+
software_list.append("Gblocks")
|
|
772
|
+
elif opt.method.trim == "trimal":
|
|
773
|
+
software_list.append("TrimAl")
|
|
774
|
+
|
|
775
|
+
if index_step(step) >= 6:
|
|
776
|
+
if opt.method.modeltest == "modeltest-ng":
|
|
777
|
+
software_list.append("Modeltest-NG")
|
|
778
|
+
elif opt.method.modeltest == "iqtree":
|
|
779
|
+
software_list.append("Partitionfinder")
|
|
780
|
+
|
|
781
|
+
if index_step(step) >= 7:
|
|
782
|
+
if opt.method.tree == "fasttree":
|
|
783
|
+
software_list.append("FastTree")
|
|
784
|
+
elif opt.method.tree == "iqtree":
|
|
785
|
+
software_list.append("IQTREE2")
|
|
786
|
+
elif opt.method.tree == "raxml":
|
|
787
|
+
software_list.append("RAxML")
|
|
788
|
+
|
|
789
|
+
# append software list by step
|
|
790
|
+
# Should add GenMine version here
|
|
791
|
+
dict_version = {
|
|
792
|
+
"FunVIP": version.FunVIP,
|
|
793
|
+
"GenMine": version.GenMine,
|
|
794
|
+
"BLASTn": version.BLASTn,
|
|
795
|
+
"MMseqs2": version.MMseqs2,
|
|
796
|
+
"MAFFT": version.MAFFT,
|
|
797
|
+
"Gblocks": version.Gblocks,
|
|
798
|
+
"TrimAl": version.trimAl,
|
|
799
|
+
"Modeltest-NG": version.Modeltest_NG,
|
|
800
|
+
"Partitionfinder": version.IQTREE2,
|
|
801
|
+
"FastTree": version.FastTree,
|
|
802
|
+
"IQTREE2": version.IQTREE2,
|
|
803
|
+
"RAxML": version.RAxML,
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
### Version for each software notation
|
|
807
|
+
f.write(f"[VERSIONS]\n")
|
|
808
|
+
|
|
809
|
+
for n, software in enumerate(software_list):
|
|
810
|
+
f.write(f"{'{:<15}'.format(software)} : {dict_version[software]}\n")
|
|
811
|
+
|
|
812
|
+
f.write(f"\n")
|
|
813
|
+
|
|
814
|
+
### Write citations
|
|
815
|
+
f.write(f"[CITATION]\n")
|
|
816
|
+
dict_citation = {
|
|
817
|
+
"FunVIP": "https://github.com/Changwanseo/FunVIP",
|
|
818
|
+
"GenMine": "Seo, C. W., Kim, S. H., Lim, Y. W., & Park, M. S. (2022). Re-identification on Korean Penicillium sequences in GenBank collected by software GenMine. Mycobiology, 50(4), 231-237.",
|
|
819
|
+
"BLASTn": "Altschul, S. F., Gish, W., Miller, W., Myers, E. W., & Lipman, D. J. (1990). Basic local alignment search tool. Journal of molecular biology, 215(3), 403-410.",
|
|
820
|
+
"MMseqs2": "Steinegger, M., & Söding, J. (2017). MMseqs2 enables sensitive protein sequence searching for the analysis of massive data sets. Nature biotechnology, 35(11), 1026-1028.",
|
|
821
|
+
"MAFFT": "Katoh, K., & Standley, D. M. (2013). MAFFT multiple sequence alignment software version 7: improvements in performance and usability. Molecular biology and evolution, 30(4), 772-780.",
|
|
822
|
+
"Gblocks": "Talavera, G., & Castresana, J. (2007). Improvement of phylogenies after removing divergent and ambiguously aligned blocks from protein sequence alignments. Systematic biology, 56(4), 564-577.",
|
|
823
|
+
"TrimAl": "Capella-Gutiérrez, S., Silla-Martínez, J. M., & Gabaldón, T. (2009). trimAl: a tool for automated alignment trimming in large-scale phylogenetic analyses. Bioinformatics, 25(15), 1972-1973.",
|
|
824
|
+
"Modeltest-NG": "Darriba, D., Posada, D., Kozlov, A. M., Stamatakis, A., Morel, B., & Flouri, T. (2020). ModelTest-NG: a new and scalable tool for the selection of DNA and protein evolutionary models. Molecular biology and evolution, 37(1), 291-294.",
|
|
825
|
+
"Partitionfinder": "Lanfear, R., Frandsen, P. B., Wright, A. M., Senfeld, T., & Calcott, B. (2017). PartitionFinder 2: new methods for selecting partitioned models of evolution for molecular and morphological phylogenetic analyses. Molecular biology and evolution, 34(3), 772-773.",
|
|
826
|
+
"FastTree": "Price, M. N., Dehal, P. S., & Arkin, A. P. (2010). FastTree 2–approximately maximum-likelihood trees for large alignments. PloS one, 5(3), e9490.",
|
|
827
|
+
"IQTREE2": "Minh, B. Q., Schmidt, H. A., Chernomor, O., Schrempf, D., Woodhams, M. D., Von Haeseler, A., & Lanfear, R. (2020). IQ-TREE 2: new models and efficient methods for phylogenetic inference in the genomic era. Molecular biology and evolution, 37(5), 1530-1534.",
|
|
828
|
+
"RAxML": "Stamatakis, A. (2014). RAxML version 8: a tool for phylogenetic analysis and post-analysis of large phylogenies. Bioinformatics, 30(9), 1312-1313.",
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
for n, software in enumerate(software_list):
|
|
832
|
+
f.write(
|
|
833
|
+
f"[{n+1}] {'{:<15}'.format(software)} : {dict_citation[software]}\n"
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
def report_table(self, V, path, opt, step):
|
|
837
|
+
# Generate list of table to be written
|
|
838
|
+
list_table = []
|
|
839
|
+
|
|
840
|
+
if index_step(step) >= 2:
|
|
841
|
+
list_table.append("dataset")
|
|
842
|
+
|
|
843
|
+
if index_step(step) >= 9:
|
|
844
|
+
list_table.append("identification")
|
|
845
|
+
# list_table.append("statistics")
|
|
846
|
+
|
|
847
|
+
# Write tables
|
|
848
|
+
for table in list_table:
|
|
849
|
+
if table == "dataset":
|
|
850
|
+
save_df(
|
|
851
|
+
df=pd.DataFrame(self.dataset),
|
|
852
|
+
out=f"{path.root}/{opt.runname}.dataset.{opt.tableformat}",
|
|
853
|
+
fmt=opt.tableformat,
|
|
854
|
+
)
|
|
855
|
+
elif table == "identification":
|
|
856
|
+
save_df(
|
|
857
|
+
df=pd.DataFrame(self.result),
|
|
858
|
+
out=f"{path.root}/{opt.runname}.result.{opt.tableformat}",
|
|
859
|
+
fmt=opt.tableformat,
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
elif table == "statistics":
|
|
863
|
+
save_df(
|
|
864
|
+
df=pd.DataFrame(self.statistics),
|
|
865
|
+
out=f"{path.root}/{opt.runname}.statistics.{opt.tableformat}",
|
|
866
|
+
fmt=opt.tableformat,
|
|
867
|
+
)
|
|
868
|
+
else:
|
|
869
|
+
logging.error(
|
|
870
|
+
f"DEVELOPMENTAL ERROR : WRONG TABLE TYPE {table} ATTEMPTED TO BE WRITTEN"
|
|
871
|
+
)
|
|
872
|
+
raise Exception
|
|
873
|
+
|
|
874
|
+
def report_html(V, path, opt):
|
|
875
|
+
pass
|