I am writing a python script that requires a reverse complement function to be called on DNA strings of length 1 through around length 30. Line profiling programs indicate that my functions spend a lot of time getting the reverse complements, so I am looking to optimize.
What is the fastest way to get the reverse complement of a sequence in python? I am posting my skeleton program to test different implementations below with DNA string size 17 as an example.
#!/usr/bin/env python
import random
import timeit
global complement
complement = {'A': 'T', 'C': 'G', 'G': 'C', 'T': 'A'}
DNAlength = 17
#randomly generate 100k bases
int_to_basemap = {1: 'A', 2: 'C', 3: 'G', 4: 'T'}
num_strings = 500000
random.seed(90210)
DNAstrings = ["".join([int_to_basemap[random.randint(1,4)] for i in range(DNAlength)])
for j in range(num_strings)]
#get an idea of what the DNAstrings look like
print(DNAstrings[0:5])
def reverse_complement_naive(seq):
this_complement = {'A': 'T', 'C': 'G', 'G': 'C', 'T': 'A'}
return "".join(this_complement.get(base, base) for base in reversed(seq))
def reverse_complement(seq):
return "".join(complement.get(base, base) for base in reversed(seq))
tic=timeit.default_timer()
rcs = [reverse_complement_naive(seq) for seq in DNAstrings]
toc=timeit.default_timer()
baseline = toc - tic
namefunc = {"naive implementation": reverse_complement_naive,
"global dict implementation": reverse_complement}
for function_name in namefunc:
func = namefunc[function_name]
tic=timeit.default_timer()
rcs = [func(seq) for seq in DNAstrings]
toc=timeit.default_timer()
walltime = toc-tic
print("""{}
{:.5f}s total,
{:.1f} strings per second
{:.1f}% increase over baseline""".format(
function_name,
walltime,
num_strings/walltime,
100- ((walltime/baseline)*100) ))
By the way, I get output like this. It varies by the call, of course!
naive implementation
1.83880s total,
271916.7 strings per second
-0.7% increase over baseline
global dict implementation
1.74645s total,
286294.3 strings per second
4.3% increase over baseline
Edit: Great answers, everyone! When I get a chance in a day or two I will add all of these to a test file for the final run. When I asked the question, I had not considered whether I would allow for cython or c extensions when selecting the final answer. What do you all think?
Edit 2: Here are the results of the final simulation with everyone's implementations. I am going to accept the highest scoring pure python code with no Cython/C. For my own sake I ended up using user172818's c implementation. If you feel like contributing to this in the future, check out the github page I made for this question.
the runtime of reverse complement implementations.
10000 strings and 250 repetitions
╔══════════════════════════════════════════════════════╗
║ name %inc s total str per s ║
╠══════════════════════════════════════════════════════╣
║ user172818 seqpy.c 93.7% 0.002344 4266961.4 ║
║ alexreynolds Cython (v2) 93.4% 0.002468 4051583.1 ║
║ alexreynolds Cython (v1) 90.4% 0.003596 2780512.1 ║
║ devonryan string 86.1% 0.005204 1921515.6 ║
║ jackaidley bytes 84.7% 0.005716 1749622.2 ║
║ jackaidley bytesstring 83.0% 0.006352 1574240.6 ║
║ global dict 5.4% 0.035330 283046.7 ║
║ revcomp_translateSO 45.9% 0.020202 494999.4 ║
║ string_replace 37.5% 0.023345 428364.9 ║
║ revcom from SO 28.0% 0.026904 371694.5 ║
║ naive (baseline) 1.5% 0.036804 271711.5 ║
║ lambda from SO -39.9% 0.052246 191401.3 ║
║ biopython seq then rc -32.0% 0.049293 202869.7 ║
╚══════════════════════════════════════════════════════╝