TP 8 : Bioinformatique appliqué à l’ADN du choléra
Contents
TP 8 : Bioinformatique appliqué à l’ADN du choléra#
Lors de la réplication de l’ADN, deux chaînes de l’ADN se séparent pour produire deux copies identiques. La réplication commence en une séquence particulière de l’ADN appelée origine de réplication (ori).
Un problème important est de localiser l’ori d’un génome. On sait par la biologie que l’ori contient de nombreuses de la même séquence de nucléotides, qui est appelée dnaA et permet de localiser le début de l’ori.
Exercice
Télécharger (clic droit puis enregistrer sous) l’ADN de Vibrio cholerae (bactérie du choléra) et le sauvegarder dans le même dossier que votre fichier Python.
Exécuter le code ci-dessous pour charger l’ADN dans une variable
cholerae
(sous forme de chaîne de caractères, chaque caractère étant un nucléotide).Afficher les 10 premiers nucléotides de l’ADN de Vibrio cholerae.
Combien de nucléotides contient l’ADN de Vibrio cholerae ?
with open("Vibrio_cholerae.txt") as f:
cholerae = f.read()
Solution
for i in range(10):
print(cholerae[i], end="")
len(cholerae)
1108250
Considérons aussi l’ori de l’ADN de Vibrio cholerae, sous forme de chaîne de caractères (chaque caractère étant un nucléotide) :
cholerae_ori = "ATCAATGATCAACGTAAGCTTCTAAGCATGATCAAGGTGCTCACACAGTTTATCCACAACCTGAGTGGATGACATCAAGATAGGTCGTTGTATCTCCTTCCTCTCGTACTCTCATGACCACGGAAAGATGATCAAGAGAGGATGATTTCTTGGCCATATCGCAATGAATACTTGTGACTTGTGCTTCCAATTGACATCTTCAGCGCCATATTGCGCTGGCCAAGGTGACGGAGCGGGATTACGAAAGCATGATCATGGCTGTTGTTCTGTTTATCTTGTTTTGACTGAGACTTGTTAGGATAGACGGTTTTTCATCACTGACTAGCCAAAGCCTTACTCTGCCTGACATCGACCGTAAATTGATAATGAATTTACATGCTTCCGCGACGATTTACCTCTTGATCATCGATCCGATTGAAGATCTTCAATTGTTAATTCTCTTGCCTCGACTCATAGCCATGATGAGCTCTTGATCATGTTTCCTTAACCCTCTATTTTTTACGGAAGAATGATCAAGCTGCTGCTCTTGATCATCGTTTC"
Question
Quel est le nombre de nucléotides de l’ori de Vibrio cholerae ?
Solution
len(cholerae_ori)
540
Question
La règle de Chargaff dit que la proportion de nucléotides \(A\) est environ égale à celle de nucléotides \(T\) et que la proportion de nucléotides \(C\) est environ égale à celle de nucléotides \(G\). Vérifiez que cette règle est respectée pour Vibrio cholerae.
Solution
def p(nucl):
n = 0
for c in cholerae:
if c == nucl:
n += 1
return n/len(cholerae)
[p(c) for c in "ATGC"]
[0.2652307692307692,
0.26592465598917214,
0.23101646740356419,
0.23782810737649449]
Motif dans un ADN#
On rappelle que, Comme pour les listes, s[i:j]
extrait une sous-chaîne de s
allant du caractère d’indice i
(inclus) au caractère d’indice j
(exclus) :
cholerae_ori[0:3] # 3 premiers nucléotides
'ATC'
On peut comparer deux chaînes de caractères avec l’opérateur ==
:
cholerae_ori[0:3] == "ATC" # est-ce que les 3 premiers nucléotides sont égaux à "ATC" ?
True
Question
À quel indice l’ori de Vibrio cholerae commence t-il dans le génome de Vibrio cholerae ?
Solution
for i in range(len(cholerae)):
if cholerae[i:i+len(cholerae_ori)] == cholerae_ori:
print(i)
Question
Écrire une fonction compter(adn, motif)
qui compte le nombre d’apparitions de motif
dans adn
.
Solution
def compter(adn, motif):
"""Compte le nombre d'occurences du motif dans l'ADN"""
compteur = 0
for i in range(len(adn) - len(motif) + 1):
if adn[i:i+len(motif)] == motif:
compteur += 1
return compteur
compter(cholerae_ori, "ATG")
15
k-mer le plus fréquent#
Un k-mer est une séquence de \(k\) nucléotides. Par exemple, ATG est un 3-mer.
Version naïve#
Question
Écrire une fonction kmer_plus_frequent(adn, k)
qui renvoie le k
-mer qui apparaît le plus souvent dans adn
(s’il y a plusieurs k
-mers de fréquence maximum, on en renverra un quelconque).
Pour cela, on parcourt tous les k
-mers de adn
et on compte le nombre d’occurences de chacun d’eux, avec la fonction compter
écrite précédemment.
Solution
def kmer_plus_frequent(adn, k):
"""Retourne le k-mer le plus fréquent dans l'ADN"""
kmer = ""
max_compteur = 0
for i in range(len(adn) - k + 1):
motif = adn[i:i+k]
compteur = compter(adn, motif)
if compteur > max_compteur:
kmer = motif
max_compteur = compteur
return kmer
kmer_plus_frequent(cholerae_ori, 3)
'TGA'
Si adn
est de longueur \(n\), kmer_plus_frequent(adn, k)
appelle environ \(n\) fois compter
, qui effectue elle-même environ \(n\) opérations. Donc kmer_plus_frequent(adn, k)
effectue de l’ordre de \(n^2\) opérations, ce qui demanderait trop de temps pour le génome complet de Vibrio cholerae.
Version avec un dictionnaire#
Pour avoir une fonction plus efficace, on va utiliser un dictionnaire.
Un dictonnaire est une structure de données qui associe à chaque clé une valeur. Par exemple, on peut créer un dictonnaire qui à chaque utilisateur associe son âge :
age = {"michel" : 42, "pierre" : 12, "marie" : 32} # définition d'un dictionnaire, sous forme d'association clé-valeur
On peut accéder à la valeur associée à une clé dans un dictionnaire d
avec d[cle]
:
age["marie"] # donne la valeur associée à la clé "marie", c'est-à-dire 32
32
On peut ajouter une nouvelle paire clé-valeur avec d[cle] = valeur
:
age["jean"] = 22 # ajoute une nouvelle association clé-valeur
age # age a été modifié
{'michel': 42, 'pierre': 12, 'marie': 32, 'jean': 22}
Question
Créer un dictionnaire complement
qui associe à chaque nucléotide son complément. Ainsi, complement['A']
doit valoir 'T'
, complement['T']
doit valoir 'A'
, complement['C']
doit valoir G
et complement['G']
doit valoir 'C'
.
Solution
complement = {"A" : "T", "T" : "A", "C" : "G", "G" : "C"}
On peut parcourir les clés d’un dictionnaire avec une boucle for
:
for k in age: # parcours des clés du dictionnaire
print(k, age[k]) # affiche la clé et la valeur associée
michel 42
pierre 12
marie 32
jean 22
Question
Écrire une fonction cle_max(d)
qui renvoie la clé associée à la plus grande valeur dans le dictionnaire d
.
Solution
def cle_max(d):
kmax = None
for k in d:
if kmax is None or d[k] > d[kmax]:
kmax = k
return kmax
cle_max(age) # michel est la personne dont l'âge est le plus élevé
'michel'
Pour obtenir plus efficacement le k-mer le plus fréquent, on va calculer un dictionnaire qui à chaque k-mer associe le nombre de fois qu’il apparaît. Puis on utilise la fonction cle_max
pour obtenir le k-mer le plus fréquent de ce dictionnaire.
Question
Réécrire une fonction kmer_plus_frequent(adn, k)
qui renvoie le k
-mer qui apparaît le plus souvent dans adn
, en utilisant un dictionnaire.
Solution
def kmer_plus_frequent(adn, k):
d = {}
for i in range(len(adn) - k + 1):
motif = adn[i:i+k]
if motif not in d:
d[motif] = 0
d[motif] += 1
return cle_max(d)
kmer_plus_frequent(cholerae_ori, 3)
'TGA'
Question
Quel est le 9-mer le plus fréquent dans l’ADN de Vibrio cholerae ?
Remarques :
Le DnaA est souvent de taille 9 pour une bactérie.
Avec notre première version de
kmer_plus_frequent
, il n’aurait pas été possible d’obtenir la réponse à cette dernière question en temps raisonnable.
Nucléotide complémentaire#
L’ori est composé de deux brins d’ADN complémentaires et dans des sens opposés. Pour que la cellule sache où se trouve l’ori, il faut donc que le dnaA soit présent sur les deux brins, dans des sens opposés.
Ainsi, dans l’ori de Vibrio cholerae, le DnaA ATGATCAAG est présent ainsi que son complémentaire inversé CTTGATCAT.
Pour la question suivante, on pourra utiliser +
sur des chaînes de caractères pour les concaténer :
s = "" # s est une chaîne de caractères vide
s += "Bonne " # on ajoute "Bonne " à s
s += "année" # on ajoute "année" à s
s
'Bonne année'
On pourra aussi utiliser la fonction s[::-1]
pour inverser une chaîne de caractères :
s[::-1] # on inverse la chaîne de caractères
'eénna ennoB'
Question
Écrire une fonction complementaire_inverse(adn)
qui renvoie le complémentaire inversé de adn
. Ainsi, si adn
vaut \(n_1n_2\ldots n_k\), complementaire_inverse(adn)
doit renvoyer \(n_k^*n_{k-1}^*\ldots n_1^*\), où \(n_i^*\) est le complémentaire de \(n_i\) (A <-> T, C -> G).
Solution
def complementaire_inverse(adn):
d = {"A" : "T", "T" : "A", "C" : "G", "G" : "C"}
adn_complementaire = ""
for nucl in adn[::-1]:
adn_complementaire += d[nucl]
return adn_complementaire
complementaire_inverse("ATGATCAAG")
'CTTGATCAT'
Question
Écrire une fonction plus_frequent_inverse(adn, k)
qui renvoie le k
-mer s
le plus fréquent dans adn
, en comptant aussi les occurrences de complementaire_inverse(s)
. On pourra s’inspirer de la fonction kmer_plus_frequent
écrite précédemment.
Solution
def plus_frequent_inverse(adn, k):
d = {}
for i in range(len(adn) - k + 1):
motif = adn[i:i+k]
motif_inverse = complementaire_inverse(motif)
if motif not in d:
d[motif] = 0
if motif_inverse not in d:
d[motif_inverse] = 0
d[motif] += 1
d[motif_inverse] += 1
return cle_max(d)
plus_frequent_inverse(cholerae_ori, 9)
'ATGATCAAG'
Recherche de phase ouverte de lecture (open reading frame)#
Un codon est une séquence de trois nucléotides (par exemple ATT).
Une “phase ouverte de lecture” (ORF) est une séquence d’ADN commençant par un codon d’initiation ATG, se terminant par un codon STOP
(TAA, TAG, TGA), et ne contenant aucun codon STOP en son sein.
Question
Écrire une fonction orf(adn)
qui renvoie la liste des ORFs de adn
.
Solution
def orf(adn):
"""Retourne la liste des ORFs de l'ADN"""
orfs = []
for i in range(len(adn) - 2):
if adn[i:i+3] == "ATG":
for j in range(i + 3, len(adn) - 2, 3):
if adn[j:j+3] in ["TAA", "TAG", "TGA"]:
orfs.append(adn[i:j+3])
break
return orfs
orf(cholerae_ori)
['ATGATCAACGTAAGCTTCTAA',
'ATGATCAAGGTGCTCACACAGTTTATCCACAACCTGAGTGGATGA',
'ATGACATCAAGATAG',
'ATGACCACGGAAAGATGA',
'ATGATCAAGAGAGGATGA',
'ATGATTTCTTGGCCATATCGCAATGAATACTTGTGA',
'ATGAATACTTGTGACTTGTGCTTCCAATTGACATCTTCAGCGCCATATTGCGCTGGCCAAGGTGACGGAGCGGGATTACGAAAGCATGATCATGGCTGTTGTTCTGTTTATCTTGTTTTGACTGAGACTTGTTAG',
'ATGATCATGGCTGTTGTTCTGTTTATCTTGTTTTGA',
'ATGGCTGTTGTTCTGTTTATCTTGTTTTGA',
'ATGAATTTACATGCTTCCGCGACGATTTACCTCTTGATCATCGATCCGATTGAAGATCTTCAATTGTTAATTCTCTTGCCTCGACTCATAGCCATGATGAGCTCTTGA',
'ATGCTTCCGCGACGATTTACCTCTTGA',
'ATGATGAGCTCTTGA',
'ATGAGCTCTTGA',
'ATGTTTCCTTAA']