La génomique comparative permet d'étudier l'évolution d'organismes par comparaison de leur génome. La représentation de la proximité entre les organismes, élément essentiel de la génomique comparative, repose sur des arbres phylogénétiques. Mais comment manipuler ces arbres facilement ? Quand il n’y en a qu’un, pas de problème : on utilise un visualisateur comme ceux proposés dans l’article sur les arbres phylogénétiques. Cependant, avec plusieurs centaines ou milliers d’arbres, les traitements manuels doivent être remplacés par des routines. Pour cela, des boîtes à outils pour travailler avec l’information phylogénétique sont disponibles et largement utilisées : Ape de R, Bio::Phylo (Vos et al, 2011) de BioPerl, … En Python, plusieurs librairies ont été conçues pour la phylogénie : PyCogent (Knight et al, 2007), DendroPy (Sukumaran & Holder, 2010) , ETE (Huerta-Cepas et al, 2010), p4 (Foster, 2003) et Bio.Phylo (Talevich et al, 2012). Cette dernière, contrairement aux autres, a pour objectif d’être généraliste, facile à utiliser et compatible avec d'autres programmes, permettant ainsi le développement rapide de scripts pour l’automatisation de la manipulation des arbres. Totalement intégré à BioPython et codé en Python, ce module n’a pas besoin de librairie supplémentaire et fonctionne sur des implémentations alternatives de Python comme Jython ou Pypy.
Ce module est de plus en plus utilisé grâce au développement continu de ses fonctionnalités et de sa documentation. Ce module a, par exemple, servi dans une étude de phylogénomie microbienne (Beiko, 2010) où les topologies de plus de 100 000 arbres ont été permutées grâce à des routines. Pour ma part, j’ai utilisé Bio.Phylo pour comparer des vitesses d’évolution entre des familles de gènes, récupérer des familles monophylétiques, réconcilier des arbres de familles de gènes avec un arbre d’espèces …
Le module Bio.Phylo donne accès à des méthodes pour la manipulation et l’analyse simple d’arbres phylogénétiques, comme la recherche et le parcours d’arbres, l’extraction d’informations basiques et leur modification. Ces méthodes peuvent être facilement utilisées pour des routines dans des workflows bioinformatiques grâce à la documentation du module (wiki et les exemples d'utilisation, manuel BioPython et article de Talevich et al, 2012). Contrairement aux méthodes d’entrées/sorties et de visualisation des arbres, les méthodes pour manipuler et parcourir les arbres sont peu documentées et ne sont souvent pas illustrées. Ces lacunes rendent le développement des routines un peu complexe. Le but de cet article est donc de compléter la documentation existante afin de faciliter votre travail de développement. Pour illustrer les méthodes, je vais utiliser l’arbre suivant :
L’arbre est enregistré dans le fichier "tree" sous le format Newick avec la chaine suivante : (((H:.1,G:.2)5:.5,(((F:.1,E:.2)7:.3,D:.1)6:.4,C:.1)4:.5)3:1.5,(B:.1,A:.1)2:2)1 ;
Objets pour gérer les arbres dans Bio.Phylo
Les arbres dans le module sont enregistrés en suivant la structure :
Ainsi, quand un arbre est créé, un objet
1 |
Tree |
est créé. Cet objet pointe vers un objet
1 |
Clade |
correspondant à la racine, que l’arbre soit raciné ou pas. Cet objet
1 |
Clade |
contient les informations liées à chaque clade (ou sous-arbre démarrant à la branche courante et terminant avec les branches terminales liées à ce sous-arbre) ainsi que des références vers les clades fils. La structure d’enregistrement des arbres dans Bio.Phylo repose sur l’hypothèse de topologie d’arbre et ne permet pas une topologie de type réseaux avec plus d’un parent pour un clade.
Ainsi, les objets de la classe
1 |
Tree |
ont comme attributs :
name : Nom
id : Identifiant
root : Référence vers un objet
Clade correspondant à la racine
weight : Poids
rooted : Booléen indiquant si l’arbre est raciné ou pas
Ces attributs sont accessibles (en lecture et écriture) directement. Les attributs des instances de la classe
1 |
Clade |
sont :
name : Nom
branch_length
: Longueur de la branche conduisant au clade
clades
: Liste de références vers les clades fils
confidence
: Confiance
comment
: Commentaire
width
: Largeur de la branche pour la visualisation
_color
: Couleur pour la visualisation
Pour stocker l’information supplémentaire spécifiques à certains formats de fichiers (PhyloXML et Newick, par exemple), des sous-modules offrent des classes supplémentaires qui héritent des classes
1 |
Tree |
et
1 |
Clade |
. Je ne vais pas présenter ici les spécificités de ces sous-modules.
Création d’arbres dans Bio.Phylo
Les arbres dans Bio.Phylo sont généralement crées par lecture de fichiers dans différents formats possibles : Newick, NEXUS, PhyloXML, NeXML, Comparative Data Analysis Ontology (CDAO). Une interface de programmation (API) unifiée permet de prendre en charge tous les formats d’entrée et renvoie un objet
1 |
Tree |
, identique quel que soit le format d’entrée.
La méthode
1 |
read |
permet de lire un arbre dans le fichier donné et le renvoie. Le format du fichier correspond au deuxième argument des méthodes précédentes.
1 |
> ;> ;> ; tree = Phylo.read("tree","newick") |
Une erreur est générée lorsque le fichier passé en argument contient plus d’un arbre ou pas d’arbre. Cependant, les arbres contenus dans un même fichier peuvent être chargés avec la méthode
1 |
parse |
:
1 2 3 4 |
> ;> ;> ; trees = Phylo.parse("trees","newick")<br> > ;> ;> ; for tree in trees :<br> ...  ;  ;  ;  ;   ;  ;  ;  ; print tree.name<br> > ;> ;> ; tree_list = list(trees) |
Il est aussi possible d’utiliser directement des chaines de caractères pour charger un arbre avec
1 |
StringIO |
:
1 2 3 |
> ;> ;> ; from StringIO import StringIO<br> > ;> ;> ; handle = StringIO("(((H:.1,G:.2)5:.5,(((F:.1,E:.2)7:.3,D:.1)6:.4,C:.1)4:.5)3:1.5,(B:.1,A:.1)2:2)1;")<br> > ;> ;> ; tree = Phylo.read(handle,"newick") |
Des sous-modules spécifiques à chaque format de fichiers (PhyloXMLIO, NeXMLIO, NewickIO, NexusIO, CDAOIO) permettent de charger les arbres et toutes les informations spécifiques à chaque format.
Un arbre peut aussi être créé à partir d’un clade avec
1 |
from_clade |
. L’arbre obtenu correspond alors au sous-arbre démarrant au clade sélectionné, avec ses descendants.
1 |
> ;> ;> ; tree2 = tree.from_clade(c3) # c3 est un clade |
Exportation des arbres
La méthode
1 |
write |
permet d’exporter le (ou les) arbre(s) dans un fichier ou une chaine de caractères
1 2 3 4 5 |
> ;> ;> ; Phylo.write(tree, "tree","newick")<br> > ;> ;> ; Phylo.write(trees, "trees","newick")<br> > ;> ;> ; Phylo.write([tree,tree,tree], "trees","newick")<br> > ;> ;> ; handle = StringIO()<br> > ;> ;> ; Phylo.write(tree, handle, "newick") |
Pour convertir des fichiers entre les formats Newick, Nexus, PhyloXML, …, on utilise la méthode
1 |
convert |
1 |
> ;> ;> ; Phylo.convert("tree","newick","tree.nex","nexus") |
Les arbres peuvent aussi être exportés vers d’autres formats pour être utilisables dans d’autres modules Python ou logiciels comme R. Ainsi, dans le wiki, sont présentées des méthodes pour exporter pour Ape avec RPy2, DendroPy ou PyCogent, mais aussi en matrice Numpy.
Visualisation des arbres
Dans Bio.Phylo, la visualisation des arbres peut se faire en :
- Chaine de caractères avec la hiérarchie complète de l’objet
1>>> print(tree)<br> - Dendogramme simple en ASCII
1>>> Phylo.draw_ascii(tree)<br> - Phylogramme, si les libraires matplotlib ou PyLab sont installées
1>>> Phylo.draw(tree)<br> - Cladogramme, si Graphviz, PyDot ou PyGraphviz, NetworkX et matplotlib ou PyLab installés
1 2 3 4 |
> ;> ;> ; import pylab<br> > ;> ;> ; Phylo.draw_graphviz(tree, prog = "dot")<br> > ;> ;> ; pylab.show()<br> > ;> ;> ; pylab.savefig(‘phylo-dot.png’) |
Les représentations peuvent être modifiées avec l’ajout de couleurs par exemple, mais aussi avec des librairies comme matplotlib ou PyLab. Les méthodes permettant ces modifications sont bien décrites dans le tutoriel de BioPython ou le wiki.
Récupération d’informations sur l’arbre et sa topologie
La topologie d’un arbre ou d’un sous-arbre peut être interrogée avec plusieurs méthodes :
is_monophyletic
teste si la liste des cibles fournies forme un sous-clade complet dans l’arbre à partir duquel la méthode est appelée. Cette méthode teste donc s’il existe un clade tel que ces nœuds terminaux soient exactement les cibles. Si c’est le cas, la méthode renvoie le clade correspondant à l'ancêtre commun le plus proche, ou Faux sinon.
is_bifurcating teste si un arbre est strictement bifurcant, c’est-à-dire que tous les nœuds ont 0 ou 2 enfants. La racine peut cependant avoir 3 descendants, l’arbre sera toujours considéré comme bifurcant.
La position d’un clade dans l’arbre est interrogeable avec :
is_terminal teste si un clade est terminal. C’est équivalent à
if c1.clades : … qui teste si un clade <em>c1</em> a des descendants
is_preterminal teste si tous les descendants directs sont terminaux et renvoie Faux si au moins une des descendants directs ne l’est pas
is_semipreterminal
teste si au moins un des descendant est terminal et renvoie Faux si tous les descendants directs ne le sont pas ou si le clade est terminal
1 2 3 4 5 |
> ;> ;> ; def is_semipreterminal(clade):<br> ...  ;  ;  ;  ; for child in clade :<br> ...  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ; if child.is_terminal():<br> ...  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ; return True<br> ...  ;  ;  ;  ; return False |
if c1 in c2.clades :… teste si un clade <em>c1</em> est le descendant direct d’un clade <em>c2</em>
is_parent_of
teste si une cible est un descendant (pas obligatoirement direct) du clade à partir de laquelle la méthode est appelée.
Les nœuds internes et externes d’un clade ou de l’arbre entier sont récupérés avec les méthodes
1 |
get_nonterminals |
et
1 |
get_terminals |
, qui renvoient une liste avec des références sur les clades correspondants. Similairement, la méthode
1 |
count_terminals |
compte le nombre de nœuds terminaux au sein de l’arbre.
1 2 3 4 5 6 7 8 |
> ;> ;> ; terminals = tree.get_terminals()<br> > ;> ;> ; terminals<br> [Clade(branch_length=0.1, name='H'), Clade(branch_length=0.2, name='G'), Clade(branch_length=0.05, name='F'), Clade(branch_length=0.2, name='E'), Clade(branch_length=0.1, name='D'), Clade(branch_length=0.05, name='C'), Clade(branch_length=0.05, name='B'), Clade(branch_length=0.1, name='A')]<br> > ;> ;> ; non_terminals = tree.get_nonterminals()<br> > ;> ;> ; non_terminals<br> [Clade(branch_length=1.0), Clade(branch_length=3.0, confidence=3.0), Clade(branch_length=0.5, confidence=5.0), Clade(branch_length=0.5, confidence=4.0), Clade(branch_length=0.4, confidence=6.0), Clade(branch_length=0.3, confidence=7.0), Clade(branch_length=2.0, confidence=2.0)]<br> > ;> ;> ; tree.count_terminals()<br> 8 |
Avec la structure d’arbres dans Bio.Phylo, les références vers les parents ne sont pas stockées pour chaque clade. On peut utiliser la méthode
1 |
get_path |
(décrite par la suite) pour retrouver le lien parent-fils avec la méthode suivante :
1 2 3 |
> ;> ;> ; def get_parent(tree, child_clade):<br> ...  ;  ;  ;  ;   ;  ;  ;  ; node_path = tree.get_path(child_clade)<br> ...  ;  ;  ;  ;   ;  ;  ;  ; return node_path[-2] |
Parcours et navigation dans les arbres
Parcours des arbres
Pour parcourir l’arbre phylogénétique, il faut partir de la racine avec
1 |
tree.root |
, qui pointe vers un objet
1 |
Clade |
. On peut ensuite parcourir les clades de l’arbre en profondeur, en visitant les clades fils avec
1 |
for clade_fils in clade.clades :... |
dans une méthode récursive, par exemple :
1 2 3 4 5 6 |
> ;> ;> ; def parcours_en_profondeur(clade):<br> ...  ;  ;  ;  ; if not clade.is_terminal():<br> ...  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ; for clade_fils in clade.clades :<br> ...  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ;  ; parcours_en_profondeur(clade_fils)<br> ...<br> > ;> ;> ; parcours_en_profondeur(tree.root) |
Le chemin entre deux clades parentes peut être récupéré avec la méthode
1 |
get_path |
. Celle-ci renvoie la liste des clades entre la racine ou le clade courant et un clade cible, en terminant par le clade cible et en excluant le clade racine. Cependant, le chemin renvoyé est toujours un chemin en profondeur. Ainsi, si le clade courant n’est pas parent du clade cible, la méthode ne renvoie rien. La liste des clades entre deux clades cibles, pas obligatoirement avec un chemin descendant, est accessible avec la méthode
1 |
trace |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
> ;> ;> ; c1 = tree.get_terminals()[0]<br> > ;> ;> ; tree.get_path(c1)<br> [Clade(branch_length=1.5, confidence=3.0), Clade(branch_length=0.5, confidence=5.0), Clade(branch_length=0.1, name='H')]<br> > ;> ;> ; c2 = tree.get_nonterminals()[0]<br> > ;> ;> ; c2.is_parent_of(c1)<br> True<br> > ;> ;> ; c2.get_path(c1)<br> [Clade(branch_length=1.5, confidence=3.0), Clade(branch_length=0.5, confidence=5.0), Clade(branch_length=0.1, name='H')]<br> > ;> ;> ; c3 = tree.get_nonterminals()[3]<br> > ;> ;> ; c3.is_parent_of(c1)<br> False<br> > ;> ;> ; c3.get_path(c1)<br> > ;> ;> ; tree.trace(c1,c3)<br> [Clade(branch_length=0.5, confidence=5.0), Clade(branch_length=1.5, confidence=3.0), Clade(branch_length=1.0)] |
Recherche dans les arbres
Pour rechercher des nœuds au sein de l’arbre ou dans des sous-clades, il existe 3 trois méthodes (
1 |
find_clades |
,
1 |
find_elements |
,
1 |
find_any |
) qui reposent toutes les trois sur la même définition, avec les attributs :
-
Target (
None par défaut) : spécifie les caractéristiques à rechercher, comme
- Une instance de
Clade qui pourrait correspondre par identité, pour rechercher, par exemple, la position d’un clade dans un arbre - Une chaine de caractères qui puisse correspondre à une représentation en chaine de caractères d’un attribut de
Clade (nom, par exemple) - Une classe ou un type pour lequel tout élément de l’arbre du même type pourrait correspondre
- Un dictionnaire où les clés sont des attributs de
Clade (nom, longueur, …) et les valeurs sont testées aux attributs correspondant de chaque élément de l’arbre - Une fonction prenant un argument et renvoyant Vrai ou Faux
- Une instance de
-
Terminal (
None, par défaut) : valeur booléenne pour ou contre les nœuds terminaux.
True
: recherche uniquement des nœuds terminaux
False
: exclusion des nœuds terminaux
None
: recherche à la fois des nœuds terminaux et internes
-
Order (
preorder, par défaut) : ordre de parcours de l’arbre
preorder : recherche selon un algorithme de recherche en profondeur
postorder : recherche selon un algorithme de recherche en profondeur où les nœuds fils précèdent les parents
level : recherche selon un algorithme de recherche en largeur
La méthode
1 |
find_elements |
cherche les éléments de l’arbre qui correspondent à la cible.
1 |
find_clades |
fonctionne sur le même principe, mais retourne les objets
1 |
Clade |
correspondants. Ces méthodes renvoient un objet itérable selon l’ordre défini par
1 |
order |
et qui n’est pas nécessairement le même ordre que celui d’apparition des éléments dans le fichier source. La méthode
1 |
find_any |
renvoie le premier élément trouvé par
1 |
find_element |
ou
1 |
None |
, permettant de tester l’existence de l’élément « recherché » dans l’arbre et utilisable dans une expression conditionnelle.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
> ;> ;> ; tree.find_any({'name' : 'H'})<br> Clade(branch_length=0.1, name='H')<br> > ;> ;> ; tree.find_elements({'name' : 'H'})<br> < ;itertools.ifilter object at 0x10a0d5690> ;<br> > ;> ;> ; tree.find_elements({'name' : 'H'}).next()<br> Clade(branch_length=0.1, name='H')<br> > ;> ;> ; resultat = tree.find_elements(lambda c  ;: c.branch_length < ; 1)<br> > ;> ;> ; resultat.next()<br> Clade(branch_length=0.5, confidence=5.0)<br> > ;> ;> ; resultat.next()<br> Clade(branch_length=0.1, name='H')<br> > ;> ;> ; resultat.next()<br> Clade(branch_length=0.2, name='G') |
Pour obtenir l’ancêtre commun le plus récent (MRCA, sous forme d’un objet
1 |
Clade |
) de cibles données, il faut utiliser
1 |
common_ancestor |
. Si aucune cible n’est fournie, la racine du clade à partir de laquelle la méthode est appelée est renvoyée.
1 2 3 |
> ;> ;> ; tree.common_ancestor(c1,c2)<br> Clade(branch_length=1.5, confidence=3.0)<br> > ;> ;> ; c3 = tree.common_ancestor(c1,c2) |
Métriques
Plusieurs métriques sont accessibles sur les clades et les arbres. La méthode
1 |
total_branch_length |
calcule la somme de toutes les longueurs de branche de l’arbre. La distance entre deux cibles, c’est-à-dire la somme des longueurs des branches entre les cibles, est obtenue avec la méthode
1 |
distance |
. Cette méthode est appelée à partir de l’arbre et si une seule cible est spécifiée, l’autre cible est définie comme la racine de l’arbre. La méthode
1 |
depths |
crée un mapping des clades aux profondeurs. Le résultat est un dictionnaire où les clés sont tous les clades de l’arbre et les valeurs les distances entre la racine et les clades. Par défaut, la distance est la somme des longueurs des branches mais il est possible de compter seulement de nombre de branches (avec
1 |
unit_branch_length = True |
).
1 2 |
> ;> ;> ; tree.depths()<br> {Clade(branch_length=1.0): 1.0, Clade(branch_length=0.1, name='B'): 3.1, Clade(branch_length=0.4, confidence=6.0): 3.4, Clade(branch_length=0.1, name='D'): 3.5, Clade(branch_length=1.5, confidence=3.0): 2.5, Clade(branch_length=0.2, name='E'): 3.9, Clade(branch_length=0.3, confidence=7.0): 3.6999999999999997, Clade(branch_length=2.0, confidence=2.0): 3.0, Clade(branch_length=0.5, confidence=5.5): 3.0, Clade(branch_length=0.5, confidence=4.0): 3.0, Clade(branch_length=0.1, name='C'): 3.1, Clade(branch_length=0.1, name='H'): 3.1, Clade(branch_length=0.1, name='A'): 3.1, Clade(branch_length=0.2, name='G'): 3.2, Clade(branch_length=0.1, name='F'): 3.8} |
Modifications d'arbres
Rotations
Les clades peuvent être triés selon le nombre de nœuds terminaux avec
1 |
ladderize |
. Par défaut, les clades les plus profonds sont placés en dernier. L’inverse est possible avec
1 |
reverse  ;= True |
. Cette méthode peut être appelée pour l’arbre complet ou pour un sous-arbre dont la racine correspond au clade courant.
1 |
> ;> ;> ; tree.ladderize() |
1 2 |
> ;> ;> ; c4 = tree.get_nonterminals()[4]<br> > ;> ;> ; c4.ladderize(reverse = True) |
Raciner et reraciner
Pour raciner ou reraciner l’arbre, il existe deux méthodes. La première
1 |
root_at_midpoint |
reracine l’arbre au milieu calculé entre les extrémités les plus distantes de l’arbre. La seconde méthode
1 |
root_with_outgroup |
reracine l’arbre avec un clade outgroup contenant l’ancêtre commun de l’outgroup. Si l’outgroup est identique à la racine de l’arbre, il n’y a pas de changement. Si l’outgroup est terminal, un nouveau clade racine bifurcant est créé avec une branche de longueur nulle vers l’outgroup donné. Dans les autres cas, le nœud interne à la base de l’outgroup devient une racine trifurquante pour l’arbre entier. Si la racine originale était bifurcante, elle est éliminée de l’arbre.
1 |
> ;> ;> ; tree.root_at_midpoint() |
1 2 |
> ;> ;> ; tree_ca = tree.common_ancestor('E','F')<br> > ;> ;> ; tree.root_with_outgroup(tree_ca) |
1 2 |
> ;> ;> ; c0 = tree.get_terminals()[0]<br> > ;> ;> ; tree.root_with_outgroup(c0) |
Idéalement, il faudrait que l’outgroup soit monophylétique plutôt qu’un taxon seul. Ce n’est pas vérifié automatiquement. Il est donc préférable d’utiliser
1 |
is_monophyletic |
avant de reraciner un arbre.
Ajout de clades
Pour ajouter des descendants, on peut utiliser la méthode
1 |
split |
. Celle-ci ajoute n descendants (2 par défaut) à la racine ou au clade courant. Les nouveaux clades ont des longueurs de branches définies (1 par défaut) et le même nom que la racine du clade avec l’ajout d’un entier en suffixe.
1 |
> ;> ;> ; tree.split() |
1 2 |
> ;> ;> ; ca = tree.common_ancestor('E','C')<br> > ;> ;> ; ca.split(3) |
Suppression de clades
La suppression de clade peut se faire avec différentes méthodes.
1 |
prune |
élimine un clade terminal de l’arbre, soit à partir de la référence du clade, soit en cherchant le clade avec
1 |
find_any |
. Si le taxon est issu d’une bifurcation, le nœud correspondant est éliminé et la longueur de branche est ajoutée à celle du nœud terminal restant.
1 |
collapse |
supprime la cible de l’arbre, en liant les clades fils au clade parent de la cible.
1 |
collapse_all |
élimine tous les descendants de l’arbre, laissant seulement les nœuds terminaux. Les longueurs de branches de la racine à chaque nœud terminal sont conservées. Si une cible est fournie, seuls les nœuds internes correspondant à la cible sont touchés.
1 2 3 |
> ;> ;> ; c0 = tree.get_terminals()[0]<br> > ;> ;> ; tree.prune(c0)<br> Clade(branch_length=0.4, confidence=6.0) |
1 2 3 |
> ;> ;> ; ca = tree.common_ancestor('F0','C')<br> > ;> ;> ; tree.collapse(ca)<br> Clade(branch_length=3.5, confidence=3.0) |
1 |
> ;> ;> ; tree2.collapse_all() |
Conclusion
Bio.Phylo est un module facile d’utilisation et permettant le développement rapide de routines pour la manipulation et le parcours d’arbres phylogénétiques. Ce module est toujours en développement et j’espère que de nouvelles fonctionnalités, comme la comparaison d’arbres, viendront s’y greffer.
Merci aux relecteurs pour leurs commentaires : Clem_, Darkgilou, Waqueteu et Yoann M.
Références
Beiko (2010). Telling the Whole Story in a 10,000-GenomeWorld. Biol Direct, 6:34.
Foster (2003). p4 : A Python package for phylogenetics. [http://code. google.com/p/p4-phylogenetics/].
Huerta-Cepas et al (2010). ETE : a python Environment for Tree Exploration. BMC Bioinformatics. 11:24.
Knight et al (2007). PyCogent : a toolkit for making sense from sequence. Genome Biol, 8(8):R171.
Sukumaran & Holder (2010). DendroPy : a Python library from phylogenetic computing. Bioinformatics. 26(12) : 1569–1571.
Talevich et al (2012). Bio.Phylo : A unified toolkit for processing, analyzing and visualizing phylogenetic trees in Biopython. BMC Bioinformatics, 13:209
Vos et al (2011). Bio : Phylo – phyloinformatic analysis usiing Perl. BMC Bioinformatics, 12:63.
Laisser un commentaire