2
\$\begingroup\$

I'd like feedback on my function to convert FASTA data to a dictionary in Python. Here's an example of FASTA text:

>gi|33112219|sp|O93279.1|A4_TAKRU RecName: Full=Amyloid beta A4 protein; AltName: Full=ABPP; Short=APP; AltName: Full=Alzheimer disease amyloid A4 protein homolog; Contains: RecName: Full=Beta-amyloid protein; AltName: Full=A-beta; AltName: Full=Beta-APP; Flags: Precursor
MGETTAFVLLLVATLTRSSEIPADDTVGLLTEPQVAMFCGKLNMHINVQNGKWESDPSGTKSCLNTKEGI
LQYCQEVYPELQITNVVEANQPVSIQNWCKKGRKQCRSHTHIVVPYRCLVGEFVSDALLVPDKCKFLHQE
RMNQCESHLHWHTVAKESCGDRSMNLHDYGMLLPCGIDRFRGVKFVCCPAETEQETDSSEVEGEESDVWW
GGADPEYSENSPPTPSRATYVAGDAFERDENGDGDEDEEDDEDVDPTDEQESDERTANVAMTTTTTTTTE
SVEEVVRAVCWAQAESGPCRAMLERWYFNPKKRRCVPFLFGGCGGNRNNFESEEYCLAVCSSSLPTVAPS
PPDAVDQYFEAPGDDNEHADFRKAKESLEAKHRERMSQVMREWEEAERQAKNLPRADKKAVIQHFQEKVE
ALEQEAAGERQQLVETHMARVEALLNSRRRLTLENYLGALQANPPRARQVLSLLKKYVRAEQKDRQHTLK
HYEHVRTVDPKKAAQIRPQVLTHLRVIDERMNQSLALLYKVPSVASEIQNQIYPAAGSDCKDPVEHCVCP
QVDGLVSYGNDALMPDQAYSSAPMDMGVDGLGSIDQSFNQANTENHVEPVDARPIPDRGLPTRPVSSLKL
EEMPEVRTETDKRQSAGYEVYHQKLVFFADDVGSNKGAIIGLMVGGVVIATVIVITLVMLRKKQYTSIHH
GVIEVDAAVTPEERHLARMQQNGYENPTYKFFEQMQN
  • FASTA lines to be used as a key begin with '>'
  • FASTA lines to be used as a value follow the key line.
  • Repeat

Is this the best way to remove whitespace, tabs, newlines, etc? I suspect it isn't because I can't tell which escape characters my .txt has.

def txt2dict(filename, key_id, \
    replace_id_list = ['\r','\n','\w','\s','\t','\v','\f'], min_len = 1):

    """A .txt file is opened as lines. Every line containing 'key_id' is added
    to the dictionary as a key. Every line which doesn't contain 'key_id' is 
    added to the dictionary as a value for the previous key."""

    dictionary = dict()
    open_file = open(filename, 'r')
    lines = open_file.readlines()
    open_file.close()
    for line in lines:
        if key_id in line:
            key_line = line
            value_line = str()
        if key_id not in line:
            value_line = value_line + line
            dictionary.update({key_line:value_line})
    copy = dictionary.copy()
    for key, value in dictionary.items():
        if len(value) <= min_len:
            del copy[key]
        else:
            for replace_id in replace_id_list:
                value = value.replace(replace_id, '')
            copy.update({key:value})
    return copy

Here's an example returned dictionary. I have truncated the keys and values.

{'>gi|33112219|sp|O93279.1|A4': 'MGETTAFVLLLVATLTRSSEIPADDTVG', '>gi|another protein': 'ANDITSSEQUENCE'}

The above dictionary could have come from the text:

>gi|33112219|sp|O93279.1|A4\n
MGETTAFVLLLVATLTRSSEIPADDTVG\n
>gi|another protein\n
ANDITS\n
SEQUENCE\n
\$\endgroup\$
  • 1
    \$\begingroup\$ Obligatory link: legacy.python.org/dev/peps/pep-0008 \$\endgroup\$ – jonrsharpe Aug 27 '14 at 13:36
  • \$\begingroup\$ Would you be able to provide the expected output for the input you have provided ? \$\endgroup\$ – SylvainD Aug 27 '14 at 14:38
  • \$\begingroup\$ I have updated the question with an example dictionary for example txt. \$\endgroup\$ – 12345678910111213 Aug 27 '14 at 14:46
3
\$\begingroup\$

Here are my suggestions:

  1. It's a good practice to use a with statement to handle the opening and closing of files for you. Not only does it keep you from having to worry about closing every file that you open, it also simplifies your code.

  2. You might consider taking the file I/O part out of your function altogether, thereby simplifying what your function does. Your function would still work exactly the same way, you would just be passing it the actual FASTA text (including linebreaks) as a string, rather than a filename. Your function call could like something like this:

    with open("some_fasta.txt", "r") as f:
        txt = f.readlines()
        d = parse_fasta(txt, "this_is_a_key_id")
    

    (I also took the liberty of renaming the function parse_fasta so as to better describe what it does.)

  3. A simpler way to update a dictionary entry is dictionary["key"] = "new value" (as opposed to dictionary.update({"key": "new value"}).

  4. Instead of adding all of the keys and values to the dictonary, and then going through them one by one and deleting them or replacing escape characters, you could simplify things by validating the entries before adding them to the dictionary. You can also use str.translate() as a simpler way to remove any escape characters from the string. Conveniently, you can specify the escape characters as a string, allowing you to represent them more concisely (see my revision at the end -- I managed to get your argument list down to one line!)

    "fix\n\r\n\r\t me".translate(None, escape_chars)
    #=> "fix me"
    

    (I would also rename replace_id_list to escape_chars.)

  5. You might consider changing if key_id not in line: to simply else:, to better reflect that your code either does one thing or the other, depending on whether or not key_id is in line.

  6. Instead of updating the value of value_line and then updating the value in the dictionary to that updated value, you can just update the value in the dictionary directly.

  7. The way your code works currently, a KeyError will be thrown if the first line doesn't contain the given key_id. A simple way to fix this would be to initially (before the for loop) define key_line to be something like "key_id not found" -- that way any lines at the beginning of the file that do not contain the key_id will be collected as the value of that key. A better solution would involve exception handling.

  8. Another possible problem with your function is that you are deleting the entry for a key altogether if any subsequent "value" line has less than the minimum required length of characters. This might be the behavior you want, but I have a hunch that what you really want to do is to ignore any lines that are not of the minimum required length. I've taken that approach with my revision below.

  9. As you currently have it, you are checking the length of each line, including any escape characters. You should check after removing the escape characters.

  10. You should also remove the escape characters from the key lines.

Here is your code with my suggested revisions:

def parse_fasta(txt, key_id, escape_chars="\r\n\w\s\t\v\f", min_len=1):
    """Returns a dictionary representation of FASTA text. Every line containing the
       supplied key_id is added to the dictionary as a key, with every subsequent
       line until the next key_id line as its value."""
    d = dict()
    key = "key_id not found"
    for line in txt:
        line = line.translate(None, escape_chars)
        if key_id in line:
            key = line
            d[key] = ""
        else:
            if len(line) >= min_len:
                d[key] += line   # (or optionally, d[key] += line + "\n"
                                 # if you want to keep the lines separate)
    return d
\$\endgroup\$
  • 1
    \$\begingroup\$ min_len comparison should be done after the whole value is collected. \$\endgroup\$ – vnp Aug 27 '14 at 17:43
  • \$\begingroup\$ That's a good call. I will edit that in. \$\endgroup\$ – Dave Yarwood Aug 27 '14 at 17:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.