Didacticiel :
Automatiser le parcours et la manipulation d’arbres phylogénétiques avec le module Bio.Phylo de BioPython

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.

biopython

Logo officiel de BioPython

 

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 :

ex_arbre

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 :

structure

Ainsi, quand un arbre est créé, un objet Tree est créé. Cet objet pointe vers un objet Clade correspondant à la racine, que l’arbre soit raciné ou pas. Cet objet 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 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 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 Tree et 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 Tree, identique quel que soit le format d’entrée.

La méthode  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.

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 parse :

Il est aussi possible d’utiliser directement des chaines de caractères pour charger un arbre avec StringIO :

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 from_clade. L’arbre obtenu correspond alors au sous-arbre démarrant au clade sélectionné, avec ses descendants.

tree2

 Exportation des arbres

La méthode write permet d’exporter le (ou les) arbre(s) dans un fichier ou une chaine de caractères

Pour convertir des fichiers entre les formats Newick, Nexus, PhyloXML, …, on utilise la méthode  convert

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
  • Dendogramme simple en ASCII
  • Phylogramme, si les libraires matplotlib ou PyLab sont installées
  • Cladogramme, si Graphviz, PyDot ou PyGraphviz, NetworkX et matplotlib ou PyLab installés

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 c1 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

  • if c1 in c2.clades : teste si un clade c1 est le descendant direct d’un clade c2
  • 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 get_nonterminals et get_terminals, qui renvoient une liste avec des références sur les clades correspondants. Similairement, la méthode count_terminals compte le nombre de nœuds terminaux au sein de l’arbre.

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 get_path (décrite par la suite) pour retrouver le lien parent-fils avec la méthode suivante :

Parcours et navigation dans les arbres

Parcours des arbres

Pour parcourir l’arbre phylogénétique, il faut partir de la racine avec tree.root, qui pointe vers un objet Clade. On peut ensuite parcourir les clades de l’arbre en profondeur, en visitant les clades fils avec  for clade_fils in clade.clades:... dans une méthode récursive, par exemple :

Le chemin entre deux clades parentes peut être récupéré avec la méthode 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  trace

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 ( find_clades, find_elements, 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
  • 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 find_elements cherche les éléments de l’arbre qui correspondent à la cible. find_clades fonctionne sur le même principe, mais retourne les objets Clade  correspondants. Ces méthodes renvoient un objet itérable selon l’ordre défini par 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  find_any renvoie le premier élément trouvé par find_element ou None , permettant de tester l’existence de l’élément « recherché » dans l’arbre et utilisable dans une expression conditionnelle.

Pour obtenir l’ancêtre commun le plus récent (MRCA, sous forme d’un objet Clade ) de cibles données, il faut utiliser common_ancestor. Si aucune cible n’est fournie, la racine du clade à partir de laquelle la méthode est appelée est renvoyée.

 Métriques

Plusieurs métriques sont accessibles sur les clades et les arbres. La méthode 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 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 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 unit_branch_length = True).

 Modifications d'arbres

Rotations

Les clades peuvent être triés selon le nombre de nœuds terminaux avec ladderize. Par défaut, les clades les plus profonds sont placés en dernier. L’inverse est possible avec 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.

tree_ladderize

clade_ladderize

Raciner et reraciner

Pour raciner ou reraciner l’arbre, il existe deux méthodes. La première  root_at_midpoint reracine l’arbre au milieu calculé entre les extrémités les plus distantes de l’arbre. La seconde méthode  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.

tree_root_at_midpoint

tree_root_with_outgroup_ca

tree_root_with_outgroup_terminal

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 is_monophyletic avant de reraciner un arbre.

Ajout de clades

Pour ajouter des descendants, on peut utiliser la méthode 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.

tree_split

clade_split

Suppression de clades

La suppression de clade peut se faire avec différentes méthodes. prune élimine un clade terminal de l’arbre, soit à partir de la référence du clade, soit en cherchant le clade avec 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. collapse supprime la cible de l’arbre, en liant les clades fils au clade parent de la cible. 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.

tree_prune

tree_collapse

tree_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.

  • À propos de
  • Je suis en post-doc à Freiburg, après un premier post-doc sur le traitement des données liées aux microbiotes à Clermont-Ferrand. Ma thèse en évolution bactérienne était à l'interface entre biologie computationnelle (évolution artificielle) et bioinformatique (génomique comparative). Mais, avant tout ça, j'ai suivi la formation d'ingénieur en Bioinformatique et Modélisation (BIM à l'INSA de Lyon) et un master d'Informatique Fondamentale (option Modélisation des systèmes complexes à l'ENS de Lyon). Je m'intéresse à ce qui tourne autour de l'open science, l'open source, la communication scientifique, la science des données et de nombreux autres sujets. Compte Twitter : @bebatut

Laisser un commentaire