inStrain 1.11.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- inStrain/GeneProfile.py +892 -0
- inStrain/SNVprofile.py +1183 -0
- inStrain/__init__.py +1 -0
- inStrain/_version.py +1 -0
- inStrain/argumentParser.py +399 -0
- inStrain/compare_controller.py +715 -0
- inStrain/compare_greedy.py +134 -0
- inStrain/compare_utils.py +285 -0
- inStrain/controller.py +536 -0
- inStrain/deprecated/DEPRECATEDmapping_plots.py +82 -0
- inStrain/deprecated/__init__.py +901 -0
- inStrain/deprecated/deprecated_filter_reads.py +491 -0
- inStrain/deprecated/deprecated_gene_statistics.py +226 -0
- inStrain/deprecated/plottingUtilities.py +1767 -0
- inStrain/filter_reads.py +956 -0
- inStrain/genomeUtilities.py +981 -0
- inStrain/helper_files/NullModel.txt +10000 -0
- inStrain/irep_utilities.py +294 -0
- inStrain/logUtils.py +1013 -0
- inStrain/parse_annotations.py +463 -0
- inStrain/plotting/SNV_plots.py +77 -0
- inStrain/plotting/__init__.py +18 -0
- inStrain/plotting/compare_plots.py +263 -0
- inStrain/plotting/gene_plots.py +87 -0
- inStrain/plotting/linkage_plots.py +196 -0
- inStrain/plotting/mapping_plots.py +276 -0
- inStrain/plotting/plotting_controller.py +170 -0
- inStrain/plotting/positional_plots.py +569 -0
- inStrain/plotting/utilities.py +96 -0
- inStrain/polymorpher.py +586 -0
- inStrain/profile/__init__.py +18 -0
- inStrain/profile/fasta.py +169 -0
- inStrain/profile/linkage.py +283 -0
- inStrain/profile/profile_controller.py +473 -0
- inStrain/profile/profile_utilities.py +905 -0
- inStrain/profile/samtools_ops.py +164 -0
- inStrain/profile/snv_utilities.py +290 -0
- inStrain/quickProfile.py +169 -0
- inStrain/readComparer.py +911 -0
- inStrain/utils.py +94 -0
- instrain-1.11.0.data/scripts/inStrain +31 -0
- instrain-1.11.0.dist-info/METADATA +30 -0
- instrain-1.11.0.dist-info/RECORD +46 -0
- instrain-1.11.0.dist-info/WHEEL +5 -0
- instrain-1.11.0.dist-info/licenses/LICENSE +21 -0
- instrain-1.11.0.dist-info/top_level.txt +1 -0
inStrain/GeneProfile.py
ADDED
|
@@ -0,0 +1,892 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import csv
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
import glob
|
|
8
|
+
import logging
|
|
9
|
+
import warnings
|
|
10
|
+
import argparse
|
|
11
|
+
import traceback
|
|
12
|
+
import multiprocessing
|
|
13
|
+
|
|
14
|
+
import Bio
|
|
15
|
+
import numpy as np
|
|
16
|
+
import pandas as pd
|
|
17
|
+
from tqdm import tqdm
|
|
18
|
+
from Bio import SeqIO
|
|
19
|
+
from Bio.Seq import Seq, MutableSeq
|
|
20
|
+
import concurrent.futures
|
|
21
|
+
from concurrent import futures
|
|
22
|
+
from inStrain import SNVprofile
|
|
23
|
+
from collections import defaultdict
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from Bio.Data.CodonTable import standard_dna_table as default_codon_table
|
|
27
|
+
except ImportError:
|
|
28
|
+
with warnings.catch_warnings():
|
|
29
|
+
warnings.simplefilter("ignore")
|
|
30
|
+
from Bio.codonalign.codonalphabet import default_codon_table
|
|
31
|
+
|
|
32
|
+
import inStrain.SNVprofile
|
|
33
|
+
import inStrain.controller
|
|
34
|
+
#import inStrain.profileUtilities
|
|
35
|
+
import inStrain.logUtils
|
|
36
|
+
|
|
37
|
+
class Controller():
|
|
38
|
+
'''
|
|
39
|
+
The command line access point to the program
|
|
40
|
+
'''
|
|
41
|
+
def main(self, args):
|
|
42
|
+
'''
|
|
43
|
+
The main method when run on the command line
|
|
44
|
+
'''
|
|
45
|
+
# Parse arguments
|
|
46
|
+
args = self.validate_input(args)
|
|
47
|
+
|
|
48
|
+
vargs = vars(args)
|
|
49
|
+
IS = vargs.pop('IS')
|
|
50
|
+
GF = vargs.pop('gene_file')
|
|
51
|
+
|
|
52
|
+
# Read the genes file
|
|
53
|
+
logging.debug('Loading genes')
|
|
54
|
+
scaff_2_gene_database, scaff2gene2sequence = parse_genes(GF, **vargs)
|
|
55
|
+
GdbP = pd.concat([x for x in scaff_2_gene_database.values()])
|
|
56
|
+
|
|
57
|
+
# Calculate all your parallelized gene-level stuff
|
|
58
|
+
name2result = calculate_gene_metrics(IS, GdbP, scaff2gene2sequence, **vargs)
|
|
59
|
+
|
|
60
|
+
# Store information
|
|
61
|
+
IS.store('genes_fileloc', GF, 'value', 'Location of genes file that was used to call genes')
|
|
62
|
+
IS.store('genes_table', GdbP, 'pandas', 'Location of genes in the associated genes_file')
|
|
63
|
+
IS.store('genes_coverage', name2result['coverage'], 'pandas', 'Coverage of individual genes')
|
|
64
|
+
IS.store('genes_clonality', name2result['clonality'], 'pandas', 'Clonality of individual genes')
|
|
65
|
+
IS.store('genes_SNP_count', name2result['SNP_density'], 'pandas', 'SNP density and counts of individual genes')
|
|
66
|
+
IS.store('SNP_mutation_types', name2result['SNP_mutation_types'], 'pandas', 'The mutation types of SNPs')
|
|
67
|
+
|
|
68
|
+
if vargs.get('store_everything', False):
|
|
69
|
+
IS.store('scaff2gene2sequence', scaff2gene2sequence, 'pickle', 'Dicitonary of scaff -> gene -> nucleotide sequence')
|
|
70
|
+
|
|
71
|
+
# Store the output
|
|
72
|
+
IS.generate('gene_info', **vargs)
|
|
73
|
+
IS.generate("SNVs", **vargs)
|
|
74
|
+
|
|
75
|
+
def validate_input(self, args):
|
|
76
|
+
'''
|
|
77
|
+
Validate and mess with the arguments a bit
|
|
78
|
+
'''
|
|
79
|
+
# Make sure the IS object is OK and load it
|
|
80
|
+
assert os.path.exists(args.IS)
|
|
81
|
+
args.IS = inStrain.SNVprofile.SNVprofile(args.IS)
|
|
82
|
+
|
|
83
|
+
# Set up the logger
|
|
84
|
+
log_loc = args.IS.get_location('log') + 'log.log'
|
|
85
|
+
inStrain.controller.setup_logger(log_loc)
|
|
86
|
+
|
|
87
|
+
return args
|
|
88
|
+
|
|
89
|
+
def gene_profile_worker(gene_cmd_queue, gene_result_queue, single_thread=False):
|
|
90
|
+
'''
|
|
91
|
+
Worker to profile splits
|
|
92
|
+
'''
|
|
93
|
+
while True:
|
|
94
|
+
# Get command
|
|
95
|
+
if not single_thread:
|
|
96
|
+
cmds = gene_cmd_queue.get(True)
|
|
97
|
+
else:
|
|
98
|
+
try:
|
|
99
|
+
cmds = gene_cmd_queue.get(timeout=5)
|
|
100
|
+
except:
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
# Process cmd
|
|
104
|
+
GPs = profile_genes_wrapper(cmds)
|
|
105
|
+
gene_result_queue.put(GPs)
|
|
106
|
+
|
|
107
|
+
def profile_genes_wrapper(cmds):
|
|
108
|
+
'''
|
|
109
|
+
Take a group of commands and run geneprofile
|
|
110
|
+
'''
|
|
111
|
+
results = []
|
|
112
|
+
for cmd in cmds:
|
|
113
|
+
try:
|
|
114
|
+
results.append(profile_genes(cmd.scaffold, **cmd.arguments))
|
|
115
|
+
except Exception as e:
|
|
116
|
+
logging.error("FAILURE GeneException {0}".format(str(cmd.scaffold)))
|
|
117
|
+
logging.error(str(traceback.format_exc()))
|
|
118
|
+
logging.error(str(e))
|
|
119
|
+
|
|
120
|
+
results.append(None)
|
|
121
|
+
return results
|
|
122
|
+
|
|
123
|
+
def calculate_gene_metrics(IS, GdbP, scaff2gene2sequenceP, **kwargs):
|
|
124
|
+
'''
|
|
125
|
+
Calculate the metrics of all genes on a parallelized scaffold-level basis
|
|
126
|
+
|
|
127
|
+
IS = Initialized inStrain.SNVprofile
|
|
128
|
+
GdbP = List of gene locations
|
|
129
|
+
gene2sequenceP = Dicitonary of gene -> nucleotide sequence
|
|
130
|
+
'''
|
|
131
|
+
inStrain.logUtils.log_checkpoint("GeneProfile", "calculate_gene_metrics", "start")
|
|
132
|
+
|
|
133
|
+
# Get key word arguments for the wrapper
|
|
134
|
+
p = int(kwargs.get('processes', 6))
|
|
135
|
+
|
|
136
|
+
# Make a list of scaffolds to profile the genes of
|
|
137
|
+
scaffolds_with_genes = set(GdbP['scaffold'].unique())
|
|
138
|
+
scaffolds_in_IS = set(IS._get_covt_keys())
|
|
139
|
+
scaffolds_to_profile = scaffolds_with_genes.intersection(scaffolds_in_IS)
|
|
140
|
+
logging.info("{0} scaffolds with genes in the input; {1} scaffolds in the IS, {2} to compare".format(
|
|
141
|
+
len(scaffolds_with_genes), len(scaffolds_in_IS), len(scaffolds_to_profile)))
|
|
142
|
+
|
|
143
|
+
# Calculate scaffold -> number of genes to profile
|
|
144
|
+
s2g = GdbP['scaffold'].value_counts().to_dict()
|
|
145
|
+
kwargs['s2g'] = s2g
|
|
146
|
+
|
|
147
|
+
# Make global objects for the profiling
|
|
148
|
+
inStrain.logUtils.log_checkpoint("GeneProfile", "make_globals", "start")
|
|
149
|
+
global CumulativeSNVtable
|
|
150
|
+
CumulativeSNVtable = IS.get('cumulative_snv_table')
|
|
151
|
+
if len(CumulativeSNVtable) > 0:
|
|
152
|
+
CumulativeSNVtable = CumulativeSNVtable.sort_values('mm')
|
|
153
|
+
else:
|
|
154
|
+
CumulativeSNVtable = pd.DataFrame(columns=['scaffold'])
|
|
155
|
+
|
|
156
|
+
global covTs
|
|
157
|
+
covTs = IS.get('covT', scaffolds=scaffolds_to_profile)
|
|
158
|
+
|
|
159
|
+
global clonTs
|
|
160
|
+
clonTs = IS.get('clonT', scaffolds=scaffolds_to_profile)
|
|
161
|
+
|
|
162
|
+
global scaff2gene2sequence
|
|
163
|
+
scaff2gene2sequence = scaff2gene2sequenceP
|
|
164
|
+
|
|
165
|
+
global Gdb
|
|
166
|
+
Gdb = GdbP
|
|
167
|
+
inStrain.logUtils.log_checkpoint("GeneProfile", "make_globals", "end")
|
|
168
|
+
|
|
169
|
+
ctx = multiprocessing.get_context('fork')
|
|
170
|
+
|
|
171
|
+
# Generate commands and queue them
|
|
172
|
+
logging.debug('Creating commands')
|
|
173
|
+
cmd_groups = [x for x in iterate_commands(scaffolds_to_profile, Gdb, kwargs)]
|
|
174
|
+
logging.debug('There are {0} cmd groups'.format(len(cmd_groups)))
|
|
175
|
+
|
|
176
|
+
inStrain.logUtils.log_checkpoint("GeneProfile", "create_queue", "start")
|
|
177
|
+
gene_cmd_queue = ctx.Queue()
|
|
178
|
+
gene_result_queue = ctx.Queue()
|
|
179
|
+
GeneProfiles = []
|
|
180
|
+
|
|
181
|
+
for cmd_group in cmd_groups:
|
|
182
|
+
gene_cmd_queue.put(cmd_group)
|
|
183
|
+
|
|
184
|
+
inStrain.logUtils.log_checkpoint("GeneProfile", "create_queue", "end")
|
|
185
|
+
|
|
186
|
+
if p > 1:
|
|
187
|
+
logging.debug('Establishing processes')
|
|
188
|
+
processes = []
|
|
189
|
+
for i in range(0, p):
|
|
190
|
+
processes.append(ctx.Process(target=gene_profile_worker, args=(gene_cmd_queue, gene_result_queue)))
|
|
191
|
+
for proc in processes:
|
|
192
|
+
proc.start()
|
|
193
|
+
|
|
194
|
+
# Set up progress bar
|
|
195
|
+
pbar = tqdm(desc='Profiling genes: ', total=len(cmd_groups))
|
|
196
|
+
|
|
197
|
+
# Get the results
|
|
198
|
+
recieved_profiles = 0
|
|
199
|
+
while recieved_profiles < len(cmd_groups):
|
|
200
|
+
GPs = gene_result_queue.get()
|
|
201
|
+
recieved_profiles += 1
|
|
202
|
+
pbar.update(1)
|
|
203
|
+
for GP in GPs:
|
|
204
|
+
if GP is not None:
|
|
205
|
+
logging.debug(GP[4])
|
|
206
|
+
GeneProfiles.append(GP)
|
|
207
|
+
|
|
208
|
+
# Close multi-processing
|
|
209
|
+
for proc in processes:
|
|
210
|
+
proc.terminate()
|
|
211
|
+
|
|
212
|
+
# Close progress bar
|
|
213
|
+
pbar.close()
|
|
214
|
+
|
|
215
|
+
else:
|
|
216
|
+
gene_profile_worker(gene_cmd_queue, gene_result_queue, single_thread=True)
|
|
217
|
+
logging.info("Done profiling genes")
|
|
218
|
+
|
|
219
|
+
# Get the genes
|
|
220
|
+
recieved_profiles = 0
|
|
221
|
+
while recieved_profiles < len(cmd_groups):
|
|
222
|
+
logging.debug('going to grab at {0}'.format(recieved_profiles))
|
|
223
|
+
GPs = gene_result_queue.get(timeout=5)
|
|
224
|
+
logging.debug('did a grab at {0}'.format(recieved_profiles))
|
|
225
|
+
recieved_profiles += 1
|
|
226
|
+
for GP in GPs:
|
|
227
|
+
if GP is not None:
|
|
228
|
+
logging.debug(GP[4])
|
|
229
|
+
GeneProfiles.append(GP)
|
|
230
|
+
|
|
231
|
+
inStrain.logUtils.log_checkpoint("GeneProfile", "return_results", "start")
|
|
232
|
+
name2result = {}
|
|
233
|
+
for i, name in enumerate(['coverage', 'clonality', 'SNP_density', 'SNP_mutation_types']):
|
|
234
|
+
name2result[name] = pd.concat([G[i] for G in GeneProfiles])
|
|
235
|
+
inStrain.logUtils.log_checkpoint("GeneProfile", "return_results", "end")
|
|
236
|
+
|
|
237
|
+
inStrain.logUtils.log_checkpoint("GeneProfile", "calculate_gene_metrics", "end")
|
|
238
|
+
return name2result
|
|
239
|
+
|
|
240
|
+
def profile_genes(scaffold, **kwargs):
|
|
241
|
+
'''
|
|
242
|
+
This is the money that gets multiprocessed
|
|
243
|
+
|
|
244
|
+
Relies on having a global "Gdb", "gene2sequence", "CumulativeSNVtable", "covTs", and "clonTs"
|
|
245
|
+
|
|
246
|
+
* Calculate the clonality, coverage, linkage, and SNV_density for each gene
|
|
247
|
+
* Determine whether each SNP is synynomous or nonsynonymous
|
|
248
|
+
'''
|
|
249
|
+
# Log
|
|
250
|
+
pid = os.getpid()
|
|
251
|
+
log_message = "\nSpecialPoint_genes {0} PID {1} whole start {2}".format(scaffold, pid, time.time())
|
|
252
|
+
|
|
253
|
+
# For testing purposes
|
|
254
|
+
if ((scaffold == 'FailureScaffoldHeaderTesting')):
|
|
255
|
+
assert False
|
|
256
|
+
|
|
257
|
+
# Get the list of genes for this scaffold
|
|
258
|
+
gdb = Gdb[Gdb['scaffold'] == scaffold]
|
|
259
|
+
|
|
260
|
+
# Calculate gene-level coverage
|
|
261
|
+
log_message += "\nSpecialPoint_genes {0} PID {1} coverage start {2}".format(scaffold, pid, time.time())
|
|
262
|
+
if scaffold not in covTs:
|
|
263
|
+
logging.info("{0} isnt in covT!".format(scaffold))
|
|
264
|
+
cdb = pd.DataFrame()
|
|
265
|
+
else:
|
|
266
|
+
covT = covTs[scaffold]
|
|
267
|
+
cdb = calc_gene_coverage(gdb, covT)
|
|
268
|
+
del covT
|
|
269
|
+
log_message += "\nSpecialPoint_genes {0} PID {1} coverage end {2}".format(scaffold, pid, time.time())
|
|
270
|
+
|
|
271
|
+
# Calculate gene-level clonality
|
|
272
|
+
log_message += "\nSpecialPoint_genes {0} PID {1} clonality start {2}".format(scaffold, pid, time.time())
|
|
273
|
+
if scaffold not in clonTs:
|
|
274
|
+
logging.info("{0} isnt in clovT!".format(scaffold))
|
|
275
|
+
cldb = pd.DataFrame()
|
|
276
|
+
else:
|
|
277
|
+
clonT = clonTs[scaffold]
|
|
278
|
+
cldb = calc_gene_clonality(gdb, clonT)
|
|
279
|
+
del clonT
|
|
280
|
+
log_message += "\nSpecialPoint_genes {0} PID {1} clonality end {2}".format(scaffold, pid, time.time())
|
|
281
|
+
|
|
282
|
+
# Determine whether SNPs are synonmous or non-synonmous
|
|
283
|
+
log_message += "\nSpecialPoint_genes {0} PID {1} SNP_character start {2}".format(scaffold, pid, time.time())
|
|
284
|
+
Ldb = CumulativeSNVtable[CumulativeSNVtable['scaffold'] == scaffold]
|
|
285
|
+
if len(Ldb) == 0:
|
|
286
|
+
sdb = pd.DataFrame()
|
|
287
|
+
else:
|
|
288
|
+
sdb = Characterize_SNPs_wrapper(Ldb, gdb, scaff2gene2sequence[scaffold])
|
|
289
|
+
log_message += "\nSpecialPoint_genes {0} PID {1} SNP_character end {2}".format(scaffold, pid, time.time())
|
|
290
|
+
|
|
291
|
+
# Calculate gene-level SNP counts
|
|
292
|
+
log_message += "\nSpecialPoint_genes {0} PID {1} SNP_counts start {2}".format(scaffold, pid, time.time())
|
|
293
|
+
if len(Ldb) == 0:
|
|
294
|
+
ldb = pd.DataFrame()
|
|
295
|
+
sublog = ''
|
|
296
|
+
else:
|
|
297
|
+
#ldb = calc_gene_snp_density(gdb, Ldb)
|
|
298
|
+
ldb, sublog = calc_gene_snp_counts(gdb, Ldb, sdb, scaff2gene2sequence[scaffold], scaffold=scaffold)
|
|
299
|
+
log_message += "\nSpecialPoint_genes {0} PID {1} SNP_counts end {2}".format(scaffold, pid, time.time())
|
|
300
|
+
log_message += sublog
|
|
301
|
+
|
|
302
|
+
log_message += "\nSpecialPoint_genes {0} PID {1} whole end {2}".format(scaffold, pid, time.time())
|
|
303
|
+
|
|
304
|
+
results = (cdb, cldb, ldb, sdb, log_message)
|
|
305
|
+
|
|
306
|
+
return results
|
|
307
|
+
|
|
308
|
+
def profile_genes_from_profile(scaffold, gdb, covT, clonT, Ldb, gene2sequence):
|
|
309
|
+
'''
|
|
310
|
+
Call profile genes from elsewhere
|
|
311
|
+
|
|
312
|
+
Arguments:
|
|
313
|
+
scaffold = name of scaffold
|
|
314
|
+
gdb = gene_datatable
|
|
315
|
+
covT = covT for this scaffold
|
|
316
|
+
clonT = clonT for this scaffold
|
|
317
|
+
Ldb = cumulative SNP table for this scaffold
|
|
318
|
+
|
|
319
|
+
* Calculate the clonality, coverage, linkage, and SNV_density for each gene
|
|
320
|
+
* Determine whether each SNP is synynomous or nonsynonymous
|
|
321
|
+
'''
|
|
322
|
+
# Log
|
|
323
|
+
log = inStrain.logUtils.get_worker_log('ProfileGenes', scaffold, 'start')
|
|
324
|
+
|
|
325
|
+
# For testing purposes
|
|
326
|
+
if ((scaffold == 'FailureScaffoldHeaderTesting')):
|
|
327
|
+
assert False
|
|
328
|
+
|
|
329
|
+
# Calculate gene-level coverage
|
|
330
|
+
#log_message += "\nSpecialPoint_genes {0} PID {1} coverage start {2}".format(scaffold, pid, time.time())
|
|
331
|
+
cdb = calc_gene_coverage(gdb, covT)
|
|
332
|
+
#log_message += "\nSpecialPoint_genes {0} PID {1} coverage end {2}".format(scaffold, pid, time.time())
|
|
333
|
+
|
|
334
|
+
# Calculate gene-level clonality
|
|
335
|
+
# log_message += "\nSpecialPoint_genes {0} PID {1} clonality start {2}".format(scaffold, pid, time.time())
|
|
336
|
+
cldb = calc_gene_clonality(gdb, clonT)
|
|
337
|
+
# log_message += "\nSpecialPoint_genes {0} PID {1} clonality end {2}".format(scaffold, pid, time.time())
|
|
338
|
+
|
|
339
|
+
# Determine whether SNPs are synonmous or non-synonmous
|
|
340
|
+
#log_message += "\nSpecialPoint_genes {0} PID {1} SNP_character start {2}".format(scaffold, pid, time.time())
|
|
341
|
+
sdb = Characterize_SNPs_wrapper(Ldb, gdb, gene2sequence)
|
|
342
|
+
#log_message += "\nSpecialPoint_genes {0} PID {1} SNP_character end {2}".format(scaffold, pid, time.time())
|
|
343
|
+
|
|
344
|
+
# Calculate gene-level SNP counts
|
|
345
|
+
#log_message += "\nSpecialPoint_genes {0} PID {1} SNP_counts start {2}".format(scaffold, pid, time.time())
|
|
346
|
+
ldb, sublog = calc_gene_snp_counts(gdb, Ldb, sdb, gene2sequence, scaffold=scaffold)
|
|
347
|
+
#log_message += "\nSpecialPoint_genes {0} PID {1} SNP_counts end {2}".format(scaffold, pid, time.time())
|
|
348
|
+
#log_message += sublog
|
|
349
|
+
|
|
350
|
+
log += inStrain.logUtils.get_worker_log('ProfileGenes', scaffold, 'end')
|
|
351
|
+
|
|
352
|
+
results = (cdb, cldb, ldb, sdb, log)
|
|
353
|
+
|
|
354
|
+
return results
|
|
355
|
+
|
|
356
|
+
def calc_gene_coverage(gdb, covT):
|
|
357
|
+
'''
|
|
358
|
+
Gene-level and mm-level coverage
|
|
359
|
+
'''
|
|
360
|
+
table = defaultdict(list)
|
|
361
|
+
|
|
362
|
+
for mm, cov in iterate_covT_mms(covT):
|
|
363
|
+
if len(cov) == 0:
|
|
364
|
+
continue
|
|
365
|
+
|
|
366
|
+
for i, row in gdb.iterrows():
|
|
367
|
+
gcov = cov.loc[int(row['start']):int(row['end'])]
|
|
368
|
+
gLen = abs(row['end'] - row['start']) + 1
|
|
369
|
+
|
|
370
|
+
table['gene'].append(row['gene'])
|
|
371
|
+
table['coverage'].append(gcov.sum() / gLen)
|
|
372
|
+
table['breadth'].append(len(gcov) / gLen)
|
|
373
|
+
table['mm'].append(mm)
|
|
374
|
+
|
|
375
|
+
return pd.DataFrame(table)
|
|
376
|
+
|
|
377
|
+
def iterate_clonT_mms(clonT):
|
|
378
|
+
p2c = {}
|
|
379
|
+
mms = sorted([int(mm) for mm in list(clonT.keys())])
|
|
380
|
+
for mm in mms:
|
|
381
|
+
for pos, val in clonT[mm].items():
|
|
382
|
+
p2c[pos] = val
|
|
383
|
+
|
|
384
|
+
inds = []
|
|
385
|
+
vals = []
|
|
386
|
+
for ind in sorted(p2c.keys()):
|
|
387
|
+
inds.append(ind)
|
|
388
|
+
vals.append(p2c[ind])
|
|
389
|
+
|
|
390
|
+
yield mm, pd.Series(data = vals, index = np.array(inds).astype('int'), dtype='float64')
|
|
391
|
+
|
|
392
|
+
def iterate_covT_mms(clonT):
|
|
393
|
+
counts = pd.Series(dtype='float64')
|
|
394
|
+
mms = sorted([int(mm) for mm in list(clonT.keys())])
|
|
395
|
+
for mm in mms:
|
|
396
|
+
count = clonT[mm]
|
|
397
|
+
counts = counts.add(count, fill_value=0)
|
|
398
|
+
yield mm, counts
|
|
399
|
+
|
|
400
|
+
def calc_gene_clonality(gdb, clonT):
|
|
401
|
+
'''
|
|
402
|
+
Gene-level and mm-level clonality
|
|
403
|
+
'''
|
|
404
|
+
table = defaultdict(list)
|
|
405
|
+
|
|
406
|
+
for mm, cov in iterate_clonT_mms(clonT):
|
|
407
|
+
if len(cov) == 0:
|
|
408
|
+
continue
|
|
409
|
+
|
|
410
|
+
for i, row in gdb.iterrows():
|
|
411
|
+
gcov = cov.loc[int(row['start']):int(row['end'])]
|
|
412
|
+
gLen = abs(row['end'] - row['start']) + 1
|
|
413
|
+
|
|
414
|
+
table['gene'].append(row['gene'])
|
|
415
|
+
|
|
416
|
+
try:
|
|
417
|
+
microdiversity = 1 - gcov.mean()
|
|
418
|
+
except :
|
|
419
|
+
microdiversity = np.nan
|
|
420
|
+
|
|
421
|
+
#table['clonality'].append(gcov.mean())
|
|
422
|
+
table['nucl_diversity'].append(microdiversity)
|
|
423
|
+
table['breadth_minCov'].append(len(gcov) / gLen)
|
|
424
|
+
table['mm'].append(mm)
|
|
425
|
+
|
|
426
|
+
return pd.DataFrame(table)
|
|
427
|
+
#
|
|
428
|
+
# def calc_gene_snp_density(gdb, ldb):
|
|
429
|
+
# '''
|
|
430
|
+
# Gene-level and mm-level clonality
|
|
431
|
+
# '''
|
|
432
|
+
# table = defaultdict(list)
|
|
433
|
+
#
|
|
434
|
+
# for mm in sorted(ldb['mm'].unique()):
|
|
435
|
+
# db = ldb[ldb['mm'] <= mm].drop_duplicates(subset=['scaffold', 'position'], keep='last')
|
|
436
|
+
# cov = db.set_index('position')['ref_base'].sort_index()
|
|
437
|
+
# if len(cov) == 0:
|
|
438
|
+
# continue
|
|
439
|
+
#
|
|
440
|
+
# for i, row in gdb.iterrows():
|
|
441
|
+
# gcov = cov.loc[int(row['start']):int(row['end'])]
|
|
442
|
+
# gLen = abs(row['end'] - row['start']) + 1
|
|
443
|
+
#
|
|
444
|
+
# table['gene'].append(row['gene'])
|
|
445
|
+
# table['SNPs_per_bp'].append(len(gcov) / gLen)
|
|
446
|
+
# table['mm'].append(mm)
|
|
447
|
+
#
|
|
448
|
+
# return pd.DataFrame(table)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def count_sites(seq, k=1, codon_table=None):
|
|
453
|
+
'''
|
|
454
|
+
From a nucleotide sequence and codon table, calculate S and N sites
|
|
455
|
+
'''
|
|
456
|
+
codon_lst = convert_to_codons(seq)
|
|
457
|
+
|
|
458
|
+
if codon_table is None:
|
|
459
|
+
codon_table = default_codon_table
|
|
460
|
+
S_site = 0.0 # synonymous sites
|
|
461
|
+
N_site = 0.0 # non-synonymous sites
|
|
462
|
+
purine = ('A', 'G')
|
|
463
|
+
pyrimidine = ('T', 'C')
|
|
464
|
+
base_tuple = ('A', 'T', 'C', 'G')
|
|
465
|
+
for codon in codon_lst:
|
|
466
|
+
neighbor_codon = {'transition': [], 'transversion': []}
|
|
467
|
+
# classify neighbor codons
|
|
468
|
+
codon = codon.replace('U', 'T')
|
|
469
|
+
if codon == '---':
|
|
470
|
+
continue
|
|
471
|
+
if 'N' in codon:
|
|
472
|
+
continue
|
|
473
|
+
for n, i in enumerate(codon):
|
|
474
|
+
for j in base_tuple:
|
|
475
|
+
if i == j:
|
|
476
|
+
pass
|
|
477
|
+
elif i in purine and j in purine:
|
|
478
|
+
codon_chars = [c for c in codon]
|
|
479
|
+
codon_chars[n] = j
|
|
480
|
+
this_codon = ''.join(codon_chars)
|
|
481
|
+
neighbor_codon['transition'].append(this_codon)
|
|
482
|
+
elif i in pyrimidine and j in pyrimidine:
|
|
483
|
+
codon_chars = [c for c in codon]
|
|
484
|
+
codon_chars[n] = j
|
|
485
|
+
this_codon = ''.join(codon_chars)
|
|
486
|
+
neighbor_codon['transition'].append(this_codon)
|
|
487
|
+
else:
|
|
488
|
+
codon_chars = [c for c in codon]
|
|
489
|
+
codon_chars[n] = j
|
|
490
|
+
this_codon = ''.join(codon_chars)
|
|
491
|
+
neighbor_codon['transversion'].append(this_codon)
|
|
492
|
+
# count synonymous and non-synonymous sites
|
|
493
|
+
#codon = codon.replace('T', 'U')
|
|
494
|
+
if (codon in ['TAG', 'TAA', 'TGA']):
|
|
495
|
+
#print("STOP DETECTED")
|
|
496
|
+
continue
|
|
497
|
+
aa = codon_table.forward_table[codon]
|
|
498
|
+
this_codon_N_site = this_codon_S_site = 0
|
|
499
|
+
for neighbor in neighbor_codon['transition']:
|
|
500
|
+
if neighbor in codon_table.stop_codons:
|
|
501
|
+
this_codon_N_site += 1
|
|
502
|
+
elif codon_table.forward_table[neighbor] == aa:
|
|
503
|
+
this_codon_S_site += 1
|
|
504
|
+
else:
|
|
505
|
+
this_codon_N_site += 1
|
|
506
|
+
for neighbor in neighbor_codon['transversion']:
|
|
507
|
+
if neighbor in codon_table.stop_codons:
|
|
508
|
+
this_codon_N_site += k
|
|
509
|
+
elif codon_table.forward_table[neighbor] == aa:
|
|
510
|
+
this_codon_S_site += k
|
|
511
|
+
else:
|
|
512
|
+
this_codon_N_site += k
|
|
513
|
+
norm_const = (this_codon_N_site + this_codon_S_site)/3
|
|
514
|
+
S_site += float(this_codon_S_site) / float(norm_const)
|
|
515
|
+
N_site += float(this_codon_N_site) / float(norm_const)
|
|
516
|
+
return (S_site, N_site)
|
|
517
|
+
|
|
518
|
+
def convert_to_codons(seq):
|
|
519
|
+
codons = []
|
|
520
|
+
for c in zip(*(iter(seq),) * 3):
|
|
521
|
+
co = ''.join(c)
|
|
522
|
+
assert len(co) == 3
|
|
523
|
+
codons.append(co)
|
|
524
|
+
return codons
|
|
525
|
+
|
|
526
|
+
def calc_gene_snp_counts(gdb, ldb, sdb, gene2sequence, scaffold=None):
|
|
527
|
+
'''
|
|
528
|
+
Count the number of SNPs in each gene, as well as N and S sites
|
|
529
|
+
|
|
530
|
+
RELIES ON HAVING gene2sequence AS A GLOBAL (needed for multiprocessing speed)
|
|
531
|
+
|
|
532
|
+
Argumnets:
|
|
533
|
+
gdb = table of genes
|
|
534
|
+
ldb = Raw cumulative snp table for a single scaffold (mm-level)
|
|
535
|
+
sdb = SNP table with N and S and I annotated
|
|
536
|
+
'''
|
|
537
|
+
if len(ldb) == 0:
|
|
538
|
+
return pd.DataFrame(), ''
|
|
539
|
+
|
|
540
|
+
pid = os.getpid()
|
|
541
|
+
|
|
542
|
+
# Merge ldb and sdb
|
|
543
|
+
xdb = pd.merge(ldb, sdb[['position', 'mutation_type', 'gene']],
|
|
544
|
+
on=['position'], how='left').reset_index(drop=True)
|
|
545
|
+
|
|
546
|
+
# Calculate counts of N and S sites
|
|
547
|
+
log_message = "\nSpecialPoint_genes {0} PID {1} SNP_counts_SiteCalc start {2}".format(scaffold, pid, time.time())
|
|
548
|
+
table = defaultdict(list)
|
|
549
|
+
for i, row in gdb.iterrows():
|
|
550
|
+
try:
|
|
551
|
+
S_site, N_site = count_sites(gene2sequence[row['gene']])
|
|
552
|
+
except:
|
|
553
|
+
S_site = np.nan
|
|
554
|
+
N_site = np.nan
|
|
555
|
+
|
|
556
|
+
table['gene'].append(row['gene'])
|
|
557
|
+
table['S_sites'].append(S_site)
|
|
558
|
+
table['N_sites'].append(N_site)
|
|
559
|
+
SiteDb = pd.DataFrame(table)
|
|
560
|
+
log_message += "\nSpecialPoint_genes {0} PID {1} SNP_counts_SiteCalc end {2}".format(scaffold, pid, time.time())
|
|
561
|
+
|
|
562
|
+
log_message += "\nSpecialPoint_genes {0} PID {1} SNP_counts_geneCalc start {2}".format(scaffold, pid, time.time())
|
|
563
|
+
table = defaultdict(list)
|
|
564
|
+
for mm in sorted(xdb['mm'].unique()):
|
|
565
|
+
# Filter to this mm level and set up for quick indexing
|
|
566
|
+
fdb = xdb[xdb['mm'] <= mm].sort_values('mm').drop_duplicates(subset=['scaffold', 'position'], keep='last').sort_values('position').set_index('position')
|
|
567
|
+
|
|
568
|
+
for i, row in gdb.iterrows():
|
|
569
|
+
# Calculate gene length
|
|
570
|
+
gLen = abs(row['end'] - row['start']) + 1
|
|
571
|
+
|
|
572
|
+
# Subset to this gene
|
|
573
|
+
db = fdb.loc[int(row['start']):int(row['end'])]
|
|
574
|
+
|
|
575
|
+
# Report summary stuff
|
|
576
|
+
table['mm'].append(mm)
|
|
577
|
+
table['gene'].append(row['gene'])
|
|
578
|
+
table['gene_length'].append(gLen)
|
|
579
|
+
table['divergent_site_count'].append(len(db))
|
|
580
|
+
|
|
581
|
+
# Report type counts (SNS = 1 allele; SNV = more than 1 allele, same as at the scaffold level)
|
|
582
|
+
for name, is_type in zip(['SNS', 'SNV'], [db['allele_count'] == 1, db['allele_count'] > 1]):
|
|
583
|
+
table['{0}_count'.format(name)].append(len(db[is_type]))
|
|
584
|
+
|
|
585
|
+
for snp_type in ['N', 'S']:
|
|
586
|
+
table["{0}_{1}_count".format(name, snp_type)].append(
|
|
587
|
+
len(db[is_type & (db['mutation_type'] == snp_type)]))
|
|
588
|
+
|
|
589
|
+
GGdb = pd.DataFrame(table).merge(SiteDb, on='gene', how='left').reset_index(drop=True)
|
|
590
|
+
log_message += "\nSpecialPoint_genes {0} PID {1} SNP_counts_geneCalc end {2}".format(scaffold, pid, time.time())
|
|
591
|
+
|
|
592
|
+
# Calculate dn/ds
|
|
593
|
+
GGdb['dNdS_substitutions'] = [((nC/nS) / (sC/sS)) if ((sC > 0) & (sS > 0)) else np.nan for nC, nS, sC, sS in zip(
|
|
594
|
+
GGdb['SNS_N_count'], GGdb['N_sites'],
|
|
595
|
+
GGdb['SNS_S_count'], GGdb['S_sites'])]
|
|
596
|
+
GGdb['pNpS_variants'] = [((nC/nS) / (sC/sS)) if ((sC > 0) & (sS > 0)) else np.nan for nC, nS, sC, sS in zip(
|
|
597
|
+
GGdb['SNV_N_count'], GGdb['N_sites'],
|
|
598
|
+
GGdb['SNV_S_count'], GGdb['S_sites'])]
|
|
599
|
+
# GGdb['SNPs_per_bp'] = [x/y if y > 0 else np.nan for x, y in \
|
|
600
|
+
# zip(GGdb['divergent_site_count'], GGdb['gene_length'])]
|
|
601
|
+
|
|
602
|
+
return GGdb, log_message
|
|
603
|
+
|
|
604
|
+
def Characterize_SNPs_wrapper(Ldb, gdb, gene2sequence):
|
|
605
|
+
'''
|
|
606
|
+
A wrapper for characterizing SNPs
|
|
607
|
+
|
|
608
|
+
RELIES ON HAVING gene2sequence AS A GLOBAL (needed for multiprocessing speed)
|
|
609
|
+
|
|
610
|
+
Arguments:
|
|
611
|
+
Ldb = CumulativeSNVtable for a single scaffold
|
|
612
|
+
gdb = table of genes
|
|
613
|
+
|
|
614
|
+
Returns:
|
|
615
|
+
Sdb = The Cumulative SNV table with extra information added
|
|
616
|
+
'''
|
|
617
|
+
if len(Ldb) == 0:
|
|
618
|
+
return pd.DataFrame()
|
|
619
|
+
|
|
620
|
+
# Get a non-nonredundant list of SNPs (the highest mm, to match the SNVs.tsv output)
|
|
621
|
+
Sdb = Ldb.sort_values('mm').drop_duplicates(subset=['scaffold', 'position'], keep='last')\
|
|
622
|
+
.sort_index().drop(columns=['mm'])
|
|
623
|
+
Sdb['position'] = Sdb['position'].astype(int)
|
|
624
|
+
|
|
625
|
+
# Filter out SNPs that shouldn't be profiled like this
|
|
626
|
+
# Sdb = Sdb[Sdb['cryptic'] == False]
|
|
627
|
+
# Sdb = Sdb.drop(columns="cryptic")
|
|
628
|
+
|
|
629
|
+
if 'morphia' in Sdb.columns:
|
|
630
|
+
col = 'morphia'
|
|
631
|
+
else:
|
|
632
|
+
col = 'allele_count'
|
|
633
|
+
Sdb[col] = Sdb[col].astype(int)
|
|
634
|
+
# Sites with more than 2 alleles are characterized using the consensus and variant (2nd most common) base
|
|
635
|
+
Sdb = Sdb[Sdb[col] > 0]
|
|
636
|
+
|
|
637
|
+
# Make sure some SNPs to profile remain
|
|
638
|
+
if len(Sdb) == 0:
|
|
639
|
+
return pd.DataFrame()
|
|
640
|
+
|
|
641
|
+
# Characterize
|
|
642
|
+
sdb = characterize_SNPs(gdb, Sdb, gene2sequence)
|
|
643
|
+
assert len(Sdb) == len(sdb)
|
|
644
|
+
sdb = pd.merge(Sdb, sdb, on=['position'], how='left').reset_index(drop=True)
|
|
645
|
+
|
|
646
|
+
# Return
|
|
647
|
+
return sdb
|
|
648
|
+
|
|
649
|
+
def characterize_SNPs(gdb, Sdb, gene2sequence):
|
|
650
|
+
'''
|
|
651
|
+
Determine the type of SNP (synonymous, non-synynomous, or intergenic)
|
|
652
|
+
|
|
653
|
+
RELIES ON HAVING gene2sequence AS A GLOBAL (needed for multiprocessing speed)
|
|
654
|
+
'''
|
|
655
|
+
table = defaultdict(list)
|
|
656
|
+
for i, row in Sdb.iterrows():
|
|
657
|
+
db = gdb[(gdb['start'] <= row['position']) & (gdb['end'] >= row['position'])]
|
|
658
|
+
if len(db) == 0:
|
|
659
|
+
table['position'].append(row['position'])
|
|
660
|
+
table['mutation_type'].append('I')
|
|
661
|
+
table['mutation'].append('')
|
|
662
|
+
table['gene'].append('')
|
|
663
|
+
elif len(db) > 1:
|
|
664
|
+
table['position'].append(row['position'])
|
|
665
|
+
table['mutation_type'].append('M')
|
|
666
|
+
table['mutation'].append('')
|
|
667
|
+
table['gene'].append(','.join(db['gene'].tolist()))
|
|
668
|
+
else:
|
|
669
|
+
# Get the original sequence
|
|
670
|
+
original_sequence = gene2sequence[db ['gene'].tolist()[0]]
|
|
671
|
+
if db['direction'].tolist()[0] == '-1':
|
|
672
|
+
original_sequence = original_sequence.reverse_complement()
|
|
673
|
+
|
|
674
|
+
# Make the new sequence
|
|
675
|
+
snp_start = row['position'] - db['start'].tolist()[0]
|
|
676
|
+
new_sequence = MutableSeq(str(original_sequence))
|
|
677
|
+
try:
|
|
678
|
+
new_sequence[snp_start] = row['con_base']
|
|
679
|
+
except:
|
|
680
|
+
print(f'ERROR!! {row} {db["gene"].tolist()[0]} {len(new_sequence)}\n{new_sequence}\n{db}')
|
|
681
|
+
|
|
682
|
+
if new_sequence[snp_start] == original_sequence[snp_start]:
|
|
683
|
+
new_sequence[snp_start] = row['var_base']
|
|
684
|
+
new_sequence = Seq(str(new_sequence))
|
|
685
|
+
|
|
686
|
+
# Translate
|
|
687
|
+
if db['direction'].tolist()[0] == '-1':
|
|
688
|
+
old_aa_sequence = original_sequence.reverse_complement().translate()
|
|
689
|
+
new_aa_sequence = new_sequence.reverse_complement().translate()
|
|
690
|
+
else:
|
|
691
|
+
old_aa_sequence = original_sequence.translate()
|
|
692
|
+
new_aa_sequence = new_sequence.translate()
|
|
693
|
+
|
|
694
|
+
# old_aa_sequence = original_sequence.translate()
|
|
695
|
+
# new_aa_sequence = new_sequence.translate()
|
|
696
|
+
|
|
697
|
+
# Find mutation
|
|
698
|
+
mut_type = 'S'
|
|
699
|
+
mut = 'S:' + str(snp_start)
|
|
700
|
+
for aa in range(0, len(old_aa_sequence)):
|
|
701
|
+
if new_aa_sequence[aa] != old_aa_sequence[aa]:
|
|
702
|
+
mut_type = 'N'
|
|
703
|
+
mut = 'N:' + str(old_aa_sequence[aa]) + str(snp_start) + str(new_aa_sequence[aa])
|
|
704
|
+
break
|
|
705
|
+
|
|
706
|
+
# Store
|
|
707
|
+
table['position'].append(row['position'])
|
|
708
|
+
table['mutation_type'].append(mut_type)
|
|
709
|
+
table['mutation'].append(mut)
|
|
710
|
+
table['gene'].append(db['gene'].tolist()[0])
|
|
711
|
+
|
|
712
|
+
return pd.DataFrame(table)
|
|
713
|
+
|
|
714
|
+
def iterate_commands(scaffolds_to_profile, Gdb, kwargs):
|
|
715
|
+
'''
|
|
716
|
+
Break into individual scaffolds
|
|
717
|
+
'''
|
|
718
|
+
processes = kwargs.get('processes', 6)
|
|
719
|
+
s2g = kwargs.get('s2g', None)
|
|
720
|
+
SECONDS = min(60, sum(calc_estimated_runtime(s2g[scaffold]) for scaffold in scaffolds_to_profile)/(processes+1))
|
|
721
|
+
|
|
722
|
+
cmds = []
|
|
723
|
+
seconds = 0
|
|
724
|
+
for scaffold, gdb in Gdb.groupby('scaffold'):
|
|
725
|
+
if scaffold not in scaffolds_to_profile:
|
|
726
|
+
continue
|
|
727
|
+
|
|
728
|
+
# make this comammand
|
|
729
|
+
cmd = Command()
|
|
730
|
+
cmd.scaffold = scaffold
|
|
731
|
+
cmd.arguments = kwargs
|
|
732
|
+
|
|
733
|
+
# Add estimated seconds
|
|
734
|
+
seconds += calc_estimated_runtime(s2g[scaffold])
|
|
735
|
+
cmds.append(cmd)
|
|
736
|
+
|
|
737
|
+
# See if you're done
|
|
738
|
+
if seconds >= SECONDS:
|
|
739
|
+
yield cmds
|
|
740
|
+
seconds = 0
|
|
741
|
+
cmds = []
|
|
742
|
+
|
|
743
|
+
yield cmds
|
|
744
|
+
|
|
745
|
+
def calc_estimated_runtime(pairs):
|
|
746
|
+
SLOPE_CONSTANT = 0.01
|
|
747
|
+
return pairs * SLOPE_CONSTANT
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
class Command():
|
|
752
|
+
def __init__(self):
|
|
753
|
+
pass
|
|
754
|
+
|
|
755
|
+
def parse_genes(gene_file_loc, **kwargs):
|
|
756
|
+
'''
|
|
757
|
+
Parse a file of genes based on the file extention.
|
|
758
|
+
|
|
759
|
+
Currently supported extentions are:
|
|
760
|
+
.fna (prodigal)
|
|
761
|
+
.gb / .gbk (genbank)
|
|
762
|
+
|
|
763
|
+
Methods return a table of genes (Gdb) and a dictionary of gene -> sequence
|
|
764
|
+
'''
|
|
765
|
+
if ((gene_file_loc[-4:] == '.fna') | (gene_file_loc[-3:] == '.fa')):
|
|
766
|
+
return parse_prodigal_genes(gene_file_loc)
|
|
767
|
+
|
|
768
|
+
elif ((gene_file_loc[-3:] == '.gb') | (gene_file_loc[-4:] == '.gbk')):
|
|
769
|
+
return parse_genbank_genes(gene_file_loc)
|
|
770
|
+
|
|
771
|
+
else:
|
|
772
|
+
print("I dont know how to process {0}".format(gene_file_loc))
|
|
773
|
+
raise Exception
|
|
774
|
+
|
|
775
|
+
def parse_prodigal_genes(gene_fasta):
|
|
776
|
+
'''
|
|
777
|
+
Parse the prodigal .fna file
|
|
778
|
+
|
|
779
|
+
Return a datatable with gene info and a dictionary of gene -> sequence
|
|
780
|
+
'''
|
|
781
|
+
scaff2gene2sequence = {}
|
|
782
|
+
scaff2geneinfo = {}
|
|
783
|
+
for record in SeqIO.parse(gene_fasta, 'fasta'):
|
|
784
|
+
gene = str(record.id)
|
|
785
|
+
scaff = "_".join(gene.split("_")[:-1])
|
|
786
|
+
|
|
787
|
+
if scaff not in scaff2geneinfo:
|
|
788
|
+
scaff2geneinfo[scaff] = defaultdict(list)
|
|
789
|
+
table = scaff2geneinfo[scaff]
|
|
790
|
+
|
|
791
|
+
table['gene'].append(gene)
|
|
792
|
+
table['scaffold'].append(scaff)
|
|
793
|
+
table['direction'].append(record.description.split("#")[3].strip())
|
|
794
|
+
table['partial'].append('partial=01' in record.description)
|
|
795
|
+
|
|
796
|
+
# NOTE: PRODIGAL USES A 1-BASED INDEX AND WE USE 0, SO CONVERT TO 0 HERE
|
|
797
|
+
start = int(record.description.split("#")[1].strip())-1
|
|
798
|
+
end = int(record.description.split("#")[2].strip())-1
|
|
799
|
+
table['start'].append(start)
|
|
800
|
+
table['end'].append(end)
|
|
801
|
+
|
|
802
|
+
if scaff not in scaff2gene2sequence:
|
|
803
|
+
scaff2gene2sequence[scaff] = {}
|
|
804
|
+
scaff2gene2sequence[scaff][gene] = record.seq
|
|
805
|
+
|
|
806
|
+
# Ensure the start and ends are parsed correctly
|
|
807
|
+
if (end - start) + 1 != len(record.seq):
|
|
808
|
+
logging.error(f"ERROR PARSING GENE {gene}; start={start} end={end} length={len(record.seq)}")
|
|
809
|
+
logging.error(f"Make sure that you report genes with 1-based indexing and with an INCLUSIVE end")
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
scaffs = list(scaff2geneinfo.keys())
|
|
813
|
+
for scaff in scaffs:
|
|
814
|
+
scaff2geneinfo[scaff] = pd.DataFrame(scaff2geneinfo[scaff])
|
|
815
|
+
|
|
816
|
+
return scaff2geneinfo, scaff2gene2sequence
|
|
817
|
+
|
|
818
|
+
def parse_genbank_genes(gene_file, gene_name='gene'):
|
|
819
|
+
'''
|
|
820
|
+
Parse a genbank file. Gets features marked as CDS
|
|
821
|
+
'''
|
|
822
|
+
scaff2geneinfo = {}
|
|
823
|
+
scaff2gene2sequence = {}
|
|
824
|
+
for record in SeqIO.parse(gene_file, 'gb'):
|
|
825
|
+
scaffold = record.id
|
|
826
|
+
for feature in record.features:
|
|
827
|
+
if feature.type == 'CDS':
|
|
828
|
+
gene = feature.qualifiers[gene_name][0]
|
|
829
|
+
loc = feature.location
|
|
830
|
+
if type(loc) is Bio.SeqFeature.CompoundLocation:
|
|
831
|
+
partial = 'compound'
|
|
832
|
+
else:
|
|
833
|
+
partial = False
|
|
834
|
+
|
|
835
|
+
if scaffold not in scaff2geneinfo:
|
|
836
|
+
scaff2geneinfo[scaffold] = defaultdict(list)
|
|
837
|
+
table = scaff2geneinfo[scaffold]
|
|
838
|
+
|
|
839
|
+
table['gene'].append(gene)
|
|
840
|
+
table['scaffold'].append(scaffold)
|
|
841
|
+
table['direction'].append(feature.location.strand)
|
|
842
|
+
table['partial'].append(partial)
|
|
843
|
+
|
|
844
|
+
table['start'].append(loc.start)
|
|
845
|
+
table['end'].append(loc.end - 1)
|
|
846
|
+
|
|
847
|
+
if scaffold not in scaff2gene2sequence:
|
|
848
|
+
scaff2gene2sequence[scaffold] = {}
|
|
849
|
+
scaff2gene2sequence[scaffold][gene] = feature.location.extract(record).seq
|
|
850
|
+
|
|
851
|
+
scaffs = list(scaff2geneinfo.keys())
|
|
852
|
+
for scaff in scaffs:
|
|
853
|
+
scaff2geneinfo[scaff] = pd.DataFrame(scaff2geneinfo[scaff])
|
|
854
|
+
|
|
855
|
+
return scaff2geneinfo, scaff2gene2sequence
|
|
856
|
+
|
|
857
|
+
def get_gene_info(IS, ANI_level=0):
|
|
858
|
+
#IS = inStrain.SNVprofile.SNVprofile(IS_loc)
|
|
859
|
+
|
|
860
|
+
# Get the mm level
|
|
861
|
+
mm = _get_mm(IS, ANI_level)
|
|
862
|
+
|
|
863
|
+
# Load all genes
|
|
864
|
+
Gdb = IS.get('genes_table')
|
|
865
|
+
|
|
866
|
+
# Load coverage, clonality, and SNPs
|
|
867
|
+
for thing in ['genes_coverage', 'genes_clonality', 'genes_SNP_count']:
|
|
868
|
+
db = IS.get(thing)
|
|
869
|
+
if db is None:
|
|
870
|
+
logging.debug('Skipping {0} gene calculation; you have none'.format(thing))
|
|
871
|
+
continue
|
|
872
|
+
if len(db) == 0:
|
|
873
|
+
logging.debug('Skipping {0} gene calculation; you have none'.format(thing))
|
|
874
|
+
continue
|
|
875
|
+
db = db[db['mm'] <= mm].sort_values('mm').drop_duplicates(subset=['gene'], keep='last')
|
|
876
|
+
del db['mm']
|
|
877
|
+
Gdb = pd.merge(Gdb, db, on='gene', how='left')
|
|
878
|
+
|
|
879
|
+
Gdb['min_ANI'] = ANI_level
|
|
880
|
+
|
|
881
|
+
return Gdb
|
|
882
|
+
|
|
883
|
+
def _get_mm(IS, ANI):
|
|
884
|
+
'''
|
|
885
|
+
Get the mm corresponding to an ANI level in an IS
|
|
886
|
+
'''
|
|
887
|
+
if ANI > 1:
|
|
888
|
+
ANI = ANI / 100
|
|
889
|
+
|
|
890
|
+
rLen = IS.get('mapping_info')['mean_pair_length'].tolist()[0]
|
|
891
|
+
mm = int(round((rLen - (rLen * ANI))))
|
|
892
|
+
return mm
|