drep 4.0.2__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.
drep/d_bonus.py ADDED
@@ -0,0 +1,429 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import logging
4
+ import os
5
+ import pandas as pd
6
+ import sys
7
+ import shutil
8
+ import subprocess
9
+ import time
10
+
11
+ import drep
12
+ import drep.WorkDirectory
13
+ import drep.d_filter
14
+
15
+ def d_bonus_wrapper(wd,**kwargs):
16
+ logging.info("Loading work directory")
17
+ wd = drep.WorkDirectory.WorkDirectory(wd)
18
+ logging.debug(str(wd))
19
+
20
+ if kwargs.get('check_dependencies'):
21
+ logging.info('Checking dependencies')
22
+ check_dependencies()
23
+
24
+ if kwargs.get('run_tax'):
25
+ logging.info('Running tax')
26
+ run_taxonomy(wd,**kwargs)
27
+
28
+ def run_taxonomy(wd, **kwargs):
29
+ # Validate arguments- make sure you have everything you need
30
+ Bdb, prod_dir, cent_dir = validate_arguments(wd, **kwargs)
31
+
32
+ # Run prodigal
33
+ drep.d_filter.run_prodigal(Bdb['location'].tolist(), prod_dir, **kwargs)
34
+
35
+ # Run centrifuge
36
+ run_centrifuge(Bdb, prod_dir, cent_dir, wd=wd, **kwargs)
37
+
38
+ # # Call a centrifuge parser that returns Tdb
39
+ # Tdb = parse_centrifuge(Bdb, cent_dir, **kwargs)
40
+ #
41
+ # # Add taxonomic info to Bdb
42
+ # Bdb = add_taxonomy(Bdb,Tdb)
43
+
44
+ # Parse taxonomy
45
+ Tdb, Bdb = parse_taxonomy(Bdb, cent_dir, **kwargs)
46
+
47
+ # Save Tdb and Bdb
48
+ wd.store_db(Tdb,'Tdb',overwrite=True)
49
+ wd.store_db(Bdb,'Bdb',overwrite=True)
50
+
51
+ def parse_taxonomy(Bdb, cent_dir, **kwargs):
52
+ '''
53
+ take the centrifuge directory and Bdb, return Tdb and Bdb with an added 'taxonomy' column
54
+ '''
55
+
56
+ method = kwargs.get('tax_method')
57
+
58
+ if method == 'max':
59
+ Tdb = parse_centrifuge(Bdb, cent_dir, **kwargs)
60
+
61
+ elif method == 'percent':
62
+ Tdb = parse_centrifuge_percent(Bdb, cent_dir, **kwargs)
63
+
64
+ else:
65
+ logging.error("dont recognize method {0}, quitting".format(method))
66
+ sys.exit()
67
+
68
+ Bdb = add_taxonomy(Bdb,Tdb)
69
+ return Tdb, Bdb
70
+
71
+ def validate_arguments(wd, **kwargs):
72
+ '''
73
+ make sure you have everything you need
74
+ '''
75
+ if wd.hasDb('Bdb'):
76
+ if kwargs.get('genomes',None) != None:
77
+ logging.error("Both Bdb and a genome list are found- either don't include "\
78
+ + "a genome list or start a new work directory!")
79
+ sys.exit()
80
+ Bdb = wd.get_db('Bdb')
81
+
82
+ else:
83
+ if kwargs.get('genomes',None) == None:
84
+ logging.error("I don't have anything to determine the taxonomy of! Give me a genome list")
85
+ sys.exit()
86
+ Bdb = drep.d_cluster.utils.load_genomes(kwargs['genomes'])
87
+
88
+ prod_dir = wd.get_dir('prodigal')
89
+ cent_dir = wd.get_dir('centrifuge')
90
+ # if wd.hasDb('Tdb') and (kwargs.get('overwrite',False) == False):
91
+ # logging.error('Tdb already exists- run with overwrite to overwrite')
92
+ # sys.exit()
93
+
94
+ return Bdb, prod_dir, cent_dir
95
+
96
+ def check_dependencies(print_out=False):
97
+ '''
98
+ For all possible dependencies, see if you can find them
99
+ '''
100
+ for dep in ['mash', 'nucmer', 'checkm', 'ANIcalculator', 'prodigal', 'centrifuge',
101
+ 'nsimscan', 'fastANI', 'skani']:
102
+ loc, works = find_program(dep)
103
+ works_message = {True:'all good', False:'!!! ERROR !!!'}[works]
104
+ message = '{0:.<40} {1:15} (location = {2})'.format(dep, works_message, loc)
105
+ if print_out:
106
+ print(message)
107
+ else:
108
+ logging.info(message)
109
+
110
+ def find_program(dep):
111
+ '''
112
+ return location of progrgam, works = True/False (based on calling the help)
113
+ '''
114
+ # find the location of the program
115
+ loc = shutil.which(dep)
116
+
117
+ # make sure the help on the program works
118
+ works = False
119
+ if loc != None:
120
+ try:
121
+ result = subprocess.run([loc, '-h'], capture_output=True)
122
+ # Some tools (e.g. older fastANI) exit with code 1 on -h even when working
123
+ if len(result.stdout) > 0 or len(result.stderr) > 0:
124
+ works = True
125
+ except Exception:
126
+ pass
127
+
128
+ return loc, works
129
+
130
+ def run_centrifuge(Bdb, prod_dir, cent_dir, **kwargs):
131
+ t = kwargs.get('processors','6')
132
+
133
+ cmds = []
134
+ files = []
135
+ for genome in Bdb['genome'].unique():
136
+ genes = "{0}{1}.fna".format(prod_dir, genome)
137
+ cent = "{0}{1}".format(cent_dir, genome)
138
+ if not (os.path.exists("{0}_hits.tsv".format(cent)) and \
139
+ os.path.exists("{0}_report.tsv".format(cent))):
140
+ cmds.append(gen_centrifuge_cmd(genes,cent,**kwargs))
141
+
142
+ if len(cmds) >= 1:
143
+ logging.info('Running Centrifuge')
144
+ for cmd in cmds:
145
+ logging.debug(' '.join(cmd))
146
+
147
+ if 'wd' in kwargs:
148
+ logdir = kwargs.get('wd').get_dir('cmd_logs')
149
+ else:
150
+ logdir = False
151
+ drep.thread_cmds(cmds, shell=False, logdir=logdir, t=int(t))
152
+ #drep.d_cluster.thread_mash_cmds_status(cmds,t=int(t))
153
+
154
+ else:
155
+ logging.info('Past centrifuge runs found- will not re-run')
156
+
157
+ def gen_read2bin(gene_files):
158
+ r2b = {}
159
+ for f in gene_files:
160
+ genome = os.path.basename(f)[:-4]
161
+ with open(f) as handle:
162
+ for line in handle:
163
+ if line.startswith('>'):
164
+ r2b.setdefault(genome,[]).append(line.strip()[1:].split(' ')[0])
165
+ return r2b
166
+
167
+ def parse_centrifuge(Bdb, cent_dir, **kwargs):
168
+
169
+ Tdb = pd.DataFrame()
170
+ for genome in Bdb['genome'].unique():
171
+
172
+ hits = parse_raw_centrifuge("{0}{1}_hits.tsv".format(cent_dir,genome), \
173
+ "{0}{1}_report.tsv".format(cent_dir,genome))
174
+
175
+ if hits.empty:
176
+ logging.debug("No centrifuge hits found for {0}- skipping".format(genome))
177
+ continue
178
+
179
+ x = gen_phylo_db(hits)
180
+ x['genome'] = genome
181
+ Tdb = pd.concat([x,Tdb], ignore_index=True)
182
+
183
+ # Find the best hits
184
+ g2t = {}
185
+ for genome in Tdb['genome'].unique():
186
+ d = Tdb[Tdb['genome'] == genome]
187
+ taxID = d['tax_ID'][d['tax_confidence'] == d['tax_confidence'].max()].tolist()[0]
188
+ g2t[genome] = taxID
189
+ Tdb['best_hit'] = [True if g2t[g] == t else False for g,t in zip(Tdb['genome'], Tdb['tax_ID'])]
190
+
191
+ # Try and add full taxonomy string
192
+ try:
193
+ Tdb['full_tax'] = [lineage_from_taxId(t) if b else False for t, b in zip(\
194
+ Tdb['tax_ID'], Tdb['best_hit'])]
195
+ except:
196
+ logging.info("problem determing full tax string with ete3 - skipping")
197
+
198
+ return Tdb
199
+
200
+ def parse_centrifuge_percent(Bdb, cent_dir, **kwargs):
201
+ min_perc = int(kwargs.get('percent'))
202
+ min_score = kwargs.get('min_score', 250)
203
+
204
+ Tdb = pd.DataFrame()
205
+ for genome in Bdb['genome'].unique():
206
+ hits = parse_raw_centrifuge("{0}{1}_hits.tsv".format(cent_dir,genome), \
207
+ "{0}{1}_report.tsv".format(cent_dir,genome))
208
+ tdb = tdb_from_hits(hits[hits['score'] > min_score], minPerc= int(min_perc))
209
+ tdb['genome'] = genome
210
+
211
+ Tdb = pd.concat([Tdb, tdb])
212
+ # Find the best hit
213
+
214
+ # THIS IS HARDER BECAUSE YOU HAVE TO PICK THE LOWEST LEVEL OVER the percent
215
+ # # Find the best hits
216
+ # g2t = {}
217
+ # for genome in Tdb['genome'].unique():
218
+ # d = Tdb[Tdb['genome'] == genome]
219
+ # taxID = d['tax_ID'][d['tax_confidence'] == d['tax_confidence'].max()].tolist()[0]
220
+ # g2t[genome] = taxID
221
+ # Tdb['best_hit'] = [True if g2t[g] == t else False for g,t in zip(Tdb['genome'], Tdb['tax_ID'])]
222
+ #
223
+ # # Try and add full taxonomy string
224
+ # try:
225
+ # Tdb['full_tax'] = [lineage_from_taxId(t) if b else False for t, b in zip(\
226
+ # Tdb['tax_ID'], Tdb['best_hit'])]
227
+ # except:
228
+ # logging.info("problem determing full tax string with ete3 - skipping")
229
+
230
+ return Tdb
231
+
232
+ def tdb_from_hits(hits, minPerc= 50, testing=False):
233
+ '''
234
+ Determines the lowest taxonomic level with at least minPerc certainty
235
+
236
+ For every hit:
237
+ reconstruct the lineage (kingdom, phylum, class, ect.)
238
+ add a count to every rank in the lineage
239
+
240
+ For every rank:
241
+ see if the number of hits matching one taxa at that rank is above the minPerc
242
+ the denominator for this equation is the number of hits that have a phyla rank
243
+
244
+ * Note: this is complicated because some lower ranks don't have higher ranks
245
+ For example, species [Eubacterium] rectale (taxID 39491) has no genus
246
+ Also, species [artifical construct] (taxID 32630) has no anything but species
247
+
248
+ '''
249
+
250
+ from ete3 import NCBITaxa
251
+ ncbi = NCBITaxa()
252
+
253
+ Levels = ['superkingdom','phylum','class','order','family','genus','species']
254
+
255
+ # generate nested dictionary for levels
256
+ countDic = {}
257
+ for level in Levels:
258
+ countDic[level] = {}
259
+
260
+ # fill in nested dictionary
261
+ for t in hits['taxID'].tolist():
262
+ if t == 0:
263
+ continue
264
+
265
+ # This try / except thing is trying to catch sporatic errors of:
266
+ # sqlite3.OperationalError: disk I/O error
267
+ try:
268
+ lin = ncbi.get_lineage(t)
269
+ lin2name = ncbi.get_taxid_translator(lin)
270
+ name2rank = ncbi.get_rank(lin)
271
+ except:
272
+ time.sleep(1)
273
+ lin = ncbi.get_lineage(t)
274
+ lin2name = ncbi.get_taxid_translator(lin)
275
+ name2rank = ncbi.get_rank(lin)
276
+
277
+ for i in lin:
278
+ rank = name2rank[i]
279
+ name = lin2name[i]
280
+ if rank in countDic:
281
+ countDic[rank][i] = countDic[rank].get(i,0) + 1
282
+
283
+ # make the table
284
+ total = sum(countDic['phylum'].values())
285
+ table = {'tax_ID':[], 'tax_confidence':[], 'tax_level':[], 'taxonomy':[]}
286
+ count = None
287
+
288
+ for level in Levels:
289
+ dic = countDic[level]
290
+ for name in sorted(dic, key=dic.get, reverse= True):
291
+ count = dic[name]
292
+ break
293
+
294
+ if count == None:
295
+ table['tax_ID'].append(None)
296
+ table['tax_confidence'].append(0)
297
+ table['tax_level'].append(level)
298
+ table['taxonomy'].append('unk')
299
+
300
+ else:
301
+ lin = ncbi.get_lineage(name)
302
+ lin2name = ncbi.get_taxid_translator(lin)
303
+ name2rank = ncbi.get_rank(lin)
304
+ rank2name = {v: k for k, v in name2rank.items()}
305
+ tax = (lin2name[rank2name[level]])
306
+
307
+ table['tax_ID'].append(name)
308
+ table['tax_confidence'].append(((count/total) *100))
309
+ table['tax_level'].append(level)
310
+ table['taxonomy'].append(tax)
311
+
312
+ count = None
313
+ tdb = pd.DataFrame(table)
314
+
315
+ # find and mark the best hit
316
+ best = tdb['tax_ID'][tdb['tax_confidence'] >= minPerc].tolist()[-1]
317
+ tdb['best_hit'] = [True if i == best else False for i in tdb['tax_ID']]
318
+
319
+ # get the full taxonomy for the best hit
320
+ tdb['full_tax'] = [lineage_from_taxId(t) if b else False for t, b in zip(\
321
+ tdb['tax_ID'], tdb['best_hit'])]
322
+
323
+ return tdb
324
+
325
+ def lineage_from_taxId(t):
326
+ from ete3 import NCBITaxa
327
+ ncbi = NCBITaxa()
328
+
329
+ Levels = ['superkingdom','phylum','class','order','family','genus','species']
330
+ name = []
331
+
332
+ lin = ncbi.get_lineage(t)
333
+
334
+ lin2name = ncbi.get_taxid_translator(lin)
335
+ name2rank = ncbi.get_rank(lin)
336
+ rank2name = {v: k for k, v in name2rank.items()}
337
+
338
+ for level in Levels:
339
+ if level in rank2name:
340
+ name.append(lin2name[rank2name[level]])
341
+ else:
342
+ name.append('unk')
343
+
344
+ return '|'.join([str(int(t))] + name)
345
+
346
+ def parse_raw_centrifuge(hits, report):
347
+ tax = pd.read_table(report)
348
+ t2l = tax.set_index('taxID')['taxRank'].to_dict()
349
+ t2n = tax.set_index('taxID')['name'].to_dict()
350
+
351
+ hits = pd.read_table(hits)
352
+ hits['gene'] = hits['readID']
353
+ hits['scaffold'] = hits['readID'].map(get_scaff)
354
+ hits['level'] = hits['taxID'].map(t2l)
355
+ hits['name'] = hits['taxID'].map(t2n)
356
+ del hits['readID']
357
+
358
+ return hits
359
+
360
+ def add_taxonomy(Bdb,Tdb):
361
+ g2t = {}
362
+ for genome in Tdb['genome'].unique():
363
+ d = Tdb[(Tdb['genome'] == genome) & (Tdb['best_hit'] == True)]
364
+ if len(d) != 1:
365
+ raise ValueError('Bug')
366
+
367
+ tax = d['taxonomy'].tolist()[0]
368
+ g2t[genome] = tax
369
+ Bdb['taxonomy'] = Bdb['genome'].map(g2t)
370
+ return Bdb
371
+
372
+ def gen_phylo_db(hits):
373
+ Table = {'tax_confidence':[],'taxonomy':[],'tax_level':[],'tax_ID':[]}
374
+
375
+ skip = ['uncultured bacterium','Mus musculus', 'Vitis vinifera', 'Homo sapiens']
376
+
377
+ levels = ['leaf','subspecies','species','genus','family']
378
+ total_ORFS = len(hits['gene'].unique())
379
+ for level in levels:
380
+
381
+ # Restrict to the level in question
382
+ d = hits[hits['level'] == level]
383
+
384
+ # Get rid of hits to shitty things
385
+ d = d[~d['name'].isin(skip)]
386
+
387
+ # Only get the top hit for each gene
388
+ d = d.sort_values(by='score')
389
+ d = d[~d.duplicated('gene')]
390
+
391
+ # Find percentage of top tax
392
+ try:
393
+ top_tax = d['name'].mode()[0]
394
+ except:
395
+ Table['tax_level'].append(level)
396
+ Table['tax_ID'].append(None)
397
+ Table['taxonomy'].append(None)
398
+ Table['tax_confidence'].append(None)
399
+ continue
400
+
401
+ x = d[d['name'] == top_tax]
402
+ top_perc = (len(x['gene'].unique()) / total_ORFS) * 100
403
+ tax_ID = x['taxID'].unique()[0]
404
+
405
+ Table['tax_level'].append(level)
406
+ Table['tax_ID'].append(tax_ID)
407
+ Table['taxonomy'].append(top_tax)
408
+ Table['tax_confidence'].append(top_perc)
409
+
410
+ return pd.DataFrame(Table)
411
+
412
+ def get_scaff(read):
413
+ return "_".join(read.split('_')[:-1])
414
+
415
+ def gen_centrifuge_cmd(genes,cent,**kwargs):
416
+ cent_exe = shutil.which('centrifuge')
417
+ if cent_exe == None:
418
+ logging.error("Can't find centrifuge- make sure it's in your system path")
419
+ sys.exit()
420
+
421
+ cent_indicies = kwargs.get('cent_index', False)
422
+ if cent_indicies == False:
423
+ logging.error("Can't find centrifuge index- must provide for taxonomy")
424
+ sys.exit()
425
+
426
+ cmd = [cent_exe, '-f', '-x', cent_indicies, genes, '-S', "{0}_hits.tsv".format(cent),\
427
+ '-p','1','--report-file',"{0}_report.tsv".format(cent)]
428
+
429
+ return cmd