The purpose of my code here is to play a part in genome sequencing analysis, and while functional it takes days to run, so I am looking for any way I can improve speed. The input is up to 500 million lines long (making speed code efficiency important) and contains sequencing reads and corresponding info. Each read takes up 4 lines within the input file and looks something like this:
@A001 <-header
AAAAACCCCCCCCCCCC <-seq read (finalRead)
+
################# <-quality (trimmed_quality)
The portion of my code that is very slow takes a dictionary as input, which contains all of the data found within the input sequencing file and is in the form shown below:
duplexDict[umi] = {'header':header, 'seq':finalRead, 'qual':trimmed_quality}
In the first part of the code I am looking for pairs of sequences by checking for similar keys (termed umi in the code). The goal is to find keys that when converted to complement sequence are only different by a single letter. Then for each key if there is only one closely matching key, the associated dictionaries are retained. If there are no matches or more than one matching key, all of these keys should be ignored.
from Levenshtein import distance
deDuplexDict = {} # dict that will contain key pairs
finalList = [] # list to keep track of valid key pairs
for i in duplexDict: # dict with sequencing file info
tempList = []
for j in duplexDict:
complement = str(Seq(j).complement()) # this is just finding complementary sequence
if distance(i,complement) <= 1: # find similar umi/read seq pairs
tempList.append(j) # make a list of all similar pairs
# only keep a complementary pair if there are exactly two matching consensus reads
if len(tempList) == 1:
if i not in finalList and j not in finalList:
finalList.append(i)
finalList.append(j)
# only retain those dict values that are true pairs
for key in finalList:
deDuplexDict[key] = duplexDict[key]
The second piece is designed to now collapse combine the sequences of two matching dictionary keys together and output to file. This is done by taking the complement of one of the sequences and then comparing each character position along the sequence strings. If anything doesn't match the character in a final string is just set to 'N' rather than the character found in the reads.
from itertools import combinations
prevScanned=[]
plus = '+'
# only pairs now exist, just search for them
for key1, key2 in combinations(deDuplexDict, 2):
finalRead = ''
complement = str(Seq(key2).complement()) # complement of second read sequence
# if neither key has been analysed and they are a matching pair then use for consensus read
if distance(key1, complement) <= 1 and key1 not in prevScanned and key2 not in prevScanned:
prevScanned.extend([key1,key2]) # keep track of analyzed keys
# convert to complementary matches
refRead = deDuplexDict[key1]['seq']
compRead = str(Seq(deDuplexDict[key2]['seq']).complement())
# iterate through by locus and derive consensus
for base in range(readLength):
if refRead[base] == compRead[base]:
finalRead += refRead[base]
else:
finalRead += 'N' # only perfect matches are permitted
# output consensus and associated info
target = open(final_output_file, 'a')
target.write(deDuplexDict[umi]['header'] + '\n' + finalRead + '\n' + plus + '\n' + deDuplexDict[umi]['qual'] + '\n')
target.close()
duplexDict
contains associative arrays, and yet the main code seems to pass the entries ofduplexDict
directly toLevenshtein.distance
. Have I misunderstood something here? 3. Isquality
actually used anywhere? \$\endgroup\$