Análise da sequência e das features presentes no NCBI

  1. util.www.fetch_genbank(start, end)
  2. extract_features(record)
  3. features_to_dictionary(start, features)
  4. util.www.fetch_table()
  5. util.www.parse_xml(file_path, add_root, start, end)
  6. verify(dictionary, table)
  7. retrieve_uniprot_ids(dictionary)
  8. main()

Os resultados desta fase podem ser encontrados aqui.

Começamos por aceder ao NCBI para fazer download de um ficheiro no formato genbank com informação correspondente à nossa zona do genoma, da posição 270001 a 505535.

O primeiro passo foi descobrir qual o genbank_id associado ao accession number, NC_002942.5, da estirpe em estudo. Para tal, usamos a função Entrez.search.

Tendo o genbank_id, obtivemos o ficheiro genbank, com recurso à função Entrez.efetch.

def fetch_genbank(start, end):
    """
    Procura no NCBI pelo Accession NC_002942.5 e 
    faz download de toda a informação em formato genbank,
    retornando o respectivo record com as features
    entre a posição start e end.
    """

    Entrez.email = "vitorenesduarte@gmail.com"

    handle = Entrez.esearch(db="nucleotide", term="NC_002942.5")
    record = Entrez.read(handle)
    genbank_id = record["IdList"][0]
    handle.close()

    handle = Entrez.efetch(db="nucleotide",
                           rettype="gbwithparts",
                           retmod="text",
                           id=genbank_id,
                           seq_start=start,
                           seq_stop=end)
    record = SeqIO.read(handle, "gb")
    handle.close()

    return record

Esta função foi adicionada ao módulo util.www.

Deste record extraímos as features gene, CDS, tRNA e rRNA.

def extract_features(record):
    """
    Das várias features encontradas no ficheiro genbank, extraímos:
      - gene
      - CDS
      - tRNA
      - rRNA

    Esta função retorna uma lista apenas com as features
    dos tipos indicados acima.
    """

    result = []
    features = record.features
    feature_count = len(features)
    types = ["gene", "CDS", "tRNA", "rRNA"]

    for i in range(feature_count):
        feature = features[i]
        should_extract = feature.type in types

        if should_extract:
            result.append(feature)

    return result

De seguida, convertemos esta lista de features num dicionário. A cada uma retirámos:

  • db_xref (renomeado para gene_id)
  • EC_number
  • function
  • gene
  • gene_synonym
  • locus_tag
  • note
  • product
  • protein_id
  • translation

Além disso, também ficamos com informação sobre a localização de cada gene, cadeias em que se encontra, número de aminoácidos da proteína codificada e tipo de feature (tRNA, rRNA e mRNA).

def features_to_dictionary(start, features):
    """
    Para cada uma das features, extraimos a seguintes propriedades:
      - db_xref
      - EC_number
      - function
      - gene
      - gene_synonym
      - locus_tag
      - note
      - product
      - protein_id
      - translation

    Também extraímos a localização:
      - start
      - end
      - strand
    """
    dictionary = {}
    properties = ["db_xref",
                  "EC_number",
                  "function",
                  "gene",
                  "gene_synonym",
                  "note",
                  "product",
                  "protein_id",
                  "translation"]

    for feature in features:
        tag = feature.qualifiers["locus_tag"][0]

        if not tag in dictionary:
            # se esta tag ainda não existe,
            # criar um dicionário vazio para ela
            dictionary[tag] = {}

        location = feature.location
        dictionary[tag]["location"] = {}
        dictionary[tag]["location"]["start"] = location._start + start
        dictionary[tag]["location"]["end"] = location._end + start - 1
        dictionary[tag]["location"]["strand"] = location._strand

        if feature.type in ["tRNA", "rRNA"]:
            # Taggar as features com estes dois tipos
            dictionary[tag]["type"] = feature.type
        else:
            # se não, taggar com "mRNA"
            dictionary[tag]["type"] = "mRNA"

        for prop in properties:
            if prop in feature.qualifiers:
                value = feature.qualifiers[prop][0]

                if prop == "db_xref":
                    # renomear esta propriedade e remover "GeneID:"
                    prop = "gene_id"
                    value = value[7:]

                if prop == "translation":
                    # se translation, também guardar o tamanho
                    dictionary[tag]["length"] = len(value)

                if prop in dictionary[tag]:
                    # se já encontramos esta propriedade
                    # para este locus_tag
                    # verificar que é a mesma
                    current_value = dictionary[tag][prop]
                    assert value == current_value
                else:
                    # se não, adicionar
                    dictionary[tag][prop] = value

    return dictionary

Neste momento já temos a informação presente no NCBI relativa à nossa zona do genoma.

Falta verificar se esta informação está de acordo com a da tabela presente em https://www.ncbi.nlm.nih.gov/genome/proteins/416?genome_assembly_id=166758.

Reparámos que esta página faz um pedido HTTP a uma API do NCBI, e usamos essa mesma API para retirar as informações que nos interessavam:

  • gene_id na coluna 5
  • gene na coluna 6
  • locus_tag na coluna 7
  • protein_id na coluna 8
  • product na coluna 11
def fetch_table():
    """
    A página https://www.ncbi.nlm.nih.gov/genome/proteins/416?genome_assembly_id=166758
    faz um pedido http ao url_prefix para preencher a tabela.
    Este método implementa a paginação que seria feita manualmente
    no website, e guarda a informação que queremos num dicionário.

      - A coluna 5  corresponde à propriedade db_xref    do genbank (gene_id para nós)
      - A coluna 6  corresponde à propriedade gene       do genbank
      - A coluna 7  corresponde à propriedade locus_tag  do genbank
      - A coluna 8  corresponde à propriedade protein_id do genbank
      - A coluna 11 corresponde à propriedade product    do genbank
    """

    url_prefix = "https://www.ncbi.nlm.nih.gov/genomes/Genome2BE/genome2srv.cgi?action=GetFeatures4Grid&type=Proteins&genome_id=416&genome_assembly_id=166758&gi=&mode=2&is_locus=1&is_locus_tag=1&optcols=1,1,1,0,0&replicons=52840256,NC_002942.5,chr"

    end = False
    page = 1
    page_size = 100

    locus_tag_index = 7
    column_mapping = {
        5: "gene_id",
        6: "gene",
        8: "protein_id",
        11: "product"
    }
    nested_values = [5, 8]

    dictionary = {}

    while not end:
        # o crawling acaba quando a página que pedi for vazia

        try:
            url = url_prefix + "&page=" + str(page) + "&pageSize=" + str(page_size)
            file_path, _ = urlretrieve(url)
            tree = parse_xml(file_path, add_root=True)
            rows = tree.findall("TR")

            for row in rows:
                cols = row.findall("TD")
                locus_tag = cols[locus_tag_index].text

                # criar um dicionário vazio para esta locus tag
                dictionary[locus_tag] = {}

                # para cada uma das colunas que nos interessa,
                # guardar o valor no dicionário se for
                # diferente de -
                for index in column_mapping:
                    prop = column_mapping[index]
                    if index in nested_values:
                        value = cols[index].find("a").text
                    else:
                        value = cols[index].text

                    if value != "-":
                        dictionary[locus_tag][prop] = value

            page += 1
            end = len(rows) == 0
        except Exception as ex:
            print(ex)
            end = True

    return dictionary

Esta função está definida no módulo util.www e utiliza uma outra função parse_xml também presente no mesmo módulo que pode modificar o ficheiro onde se encontra o XML antes de fazer parsing dele, dependendo dos argumentos:

  • add_root
  • start
  • end
def parse_xml(file_path, add_root=False, start=0, end=0):
    """
    Faz parsing de um ficheiro xml e retorna um 'ElementTree'.
    """
    if start > 0 or end > 0:
        rw.truncate_file(start, end, file_path)

    if add_root:
        rw.wrap_file("<root>", "</root>", file_path)

    fd = open(file_path, "r")
    tree = ElementTree.parse(fd)
    fd.close()

    return tree

Criámos ainda uma função que verifica se os valores presentes na tabela correspondem aos valores provenientes do NCBI. Esta função retorna os valores diferentes para uma posterior análise manual.

def verify(dictionary, table):
    """
    Verifica se os valores presentes na tabela correspondem
    aos valores presentes no dicionário.
    Se encontrar alguma diferença retorna um dicionário
    para análise manual dos resultados.
    """
    diff = {}

    for tag in dictionary:
        # para cada uma das locus_tag da nossa zona
        if tag in table:
            # se estiver na tabela
            values = table[tag]
            for prop in values:

                dict_value = dictionary[tag][prop]
                table_value = values[prop]

                if not table_value == dict_value:
                    diff[tag] = {}
                    diff[tag]["prop"] = prop
                    diff[tag]["dictionary"] = dict_value
                    diff[tag]["table"] = table_value

    return diff

Durante a análise manual das diferenças concluímos que a informação era a mesma, e, portanto, não houve necessidade de atualizar a que já tínhamos.

Por fim, para sabermos mais sobre a nossa zona do genoma, mapeamos os gene_id retirados do NCBI para identificadores UniProt, adicionando uma nova propriedade uniprot_id.

def retrieve_uniprot_ids(dictionary):
    """
    Adicionar ao dictionário uma nova propriedade:
    - uniprot_id
    """
    gene_id_to_tag = {}

    for tag in dictionary:
        if "gene_id" in dictionary[tag]:
            gene_id = dictionary[tag]["gene_id"]
            gene_id_to_tag[gene_id] = tag

    gene_ids = list(gene_id_to_tag.keys())
    gene_id_to_uniprot_id = www.gene_ids_to_uniprot_ids(gene_ids)

    for gene_id in gene_id_to_uniprot_id:
        uniprot_id = gene_id_to_uniprot_id[gene_id]
        tag = gene_id_to_tag[gene_id]

        # adicionar uma nova propriedade ao dicionário: "uniprot_id"
        dictionary[tag]["uniprot_id"] = uniprot_id

    return dictionary

Esta função usa uma outra função de mapeamento presente no módulo util.www chamada gene_ids_to_uniprot_ids. Mais informação sobre esta função pode ser encontrada aqui.

Todos estes passos podem ser encontrados no ficheiro first.py e resumem-se a:

import util.www as www

start = 270001
end = 505535

record = www.fetch_genbank(start, end)
features = extract_features(record)
dictionary = features_to_dictionary(start, features)
table = www.fetch_table()
diff = verify(dictionary, table)
# análise manual de diff
dictionary = retrieve_uniprot_ids(dictionary)

Como algumas destas funções requerem pedidos HTTP, para agilizar o desenvolvimento do trabalho criámos o módulo util.rw que nos permite gravar os resultados intermédios. A descrição deste módulo pode ser encontrada aqui.

Na verdade, o que podemos encontrar no ficheiro first.py é:

import util.www as www
import util.rw as rw

start = 270001
end = 505535

ncbi_gb_path = ".ncbi.gb"
ncbi_json_path = ".ncbi.json"

#record = www.fetch_genbank(start, end)
#rw.write_genbank(record, ncbi_gb_path)

record = rw.read_genbank(ncbi_gb_path)
features = extract_features(record)

dictionary = features_to_dictionary(start, features)
rw.write_json(dictionary, ncbi_json_path)

#...