Quantcast
Channel: Archives des R - Bioinfo-fr.net
Viewing all 33 articles
Browse latest View live

Les langages de programmation

$
0
0

Bonjour à tous et toutes, j'ai l'honneur d'écrire l'un des tout premiers articles du blog Bioinfo-fr. Étant (presque) plus passionné par l'informatique que par la biologie, je vais vous expliquer l'un des outils les plus importants pour un bioinformaticien : la programmation. En effet, il n'existerait pas de bioinformatique sans informatique et donc sans programmation. Pour ceux qui ne le savent pas, la programmation c'est ce qui permet de créer un programme.

Jusque là, rien de particulièrement exotique. Ce qui l'est davantage, c'est qu'il existe plusieurs langages de programmation, chacun ayant ses qualités et ses défauts. Comme un langage naturel, ils ont été étudiés pour avoir une grammaire et une syntaxe particulières. Et comme chaque langage, il est possible de dire la même chose, mais pas forcément de la même manière. Ainsi chaque langage a été étudié pour une utilité. Je vais vous détailler les principaux langages de programmation ainsi que l'utilité, les avantages et les défauts de chacun.

C

c/c++Le C est l'un des langages les plus utilisés par les programmeurs de part le monde. Et ce pour une raison particulière : c'est l'un des langages les plus rapides en exécution. Pour ceux qui ne le savent pas, la plupart des interpréteurs d'autres langages (logiciels qui lisent directement les programmes pour les exécuter) sont fait en C, du moins à la base. Mais cette rapidité impose un avantage et un inconvénient qui sont en fait la même chose : vous devez TOUT gérer.

Et là se pose un problème : c'est très contraignant de tout gérer. C'est comme pour un outil tel un couteau par exemple, tout gérer signifie que vous devriez extraire les matières premières pour la fabrication, le fabriquer, l'utiliser puis ensuite le recycler de manière « propre ». Bref, c'est très astreignant, mais vous savez comment tout fonctionne et vous pouvez tout calibrer pour que ça aille le plus vite possible. Mais attention, un seul grain de poussière et votre couteau casserait sur une simple motte de beurre. C'est pourquoi beaucoup de monde trouve que c'est un langage lourd, compliqué et pas forcément utile. Mais c'est tout de même l'un des langages les plus puissants.

Il est donc utilisé quand de gros calculs sont à effectuer (de grosses banques de données à comparer, comme avec BLAST, ou bien des dynamiques moléculaires à faire avec GROMACS par exemple). C'est aussi le langage qui se cache dans vos différents systèmes d'exploitation.

Java

logo-javaLe Java est certainement le langage le plus utilisé dans les entreprises privées tout simplement parce qu'il permet, à partir du même code, d'être utilisé sur n'importe quel ordinateur ayant au préalable Java d'installé. Il existe en effet de nombreuses différences entre les ordinateurs suivant leurs systèmes d'exploitation (GNU/Linux, Windows, iOS ...). Java a la particularité d'avoir ce que l'on appelle une machine virtuelle qui lui permet d'avoir une couche intermédiaire entre les lignes de codes et le langage ordinateur.

Autre avantage par rapport au C c'est qu'il gère lui même la création et la destruction des objets. Pas besoin de lui dire de détruire le couteau, il sait qu'il doit le faire. Mais il le fait... quand il le veut. Il ne s'amuse pas à recycler ses couteaux un par un, mais régulièrement il fait des vérifications pour savoir si certains ne sont pas inutiles, et au besoin les recycle tous d'un coup. Ceci peut prendre un peu de temps. En dehors de ces légers temps de latences, le Java est presque aussi rapide que le C.

Comme exemple de logiciel écrit en Java, il existe Artemis, un logiciel de gestion et d'annotation de génome. Les suites Open Office et Libre Office sont aussi codées en Java, mais avec une couche de C++ (C « amélioré ») pour les fonctions demandant beaucoup de temps de calcul.

Python

logo pythonLe Python est plus utilisé comme langage de script et non comme langage de programmation. Tout simplement parce qu'il est bien plus lent que le C ou le Java (entre 10 et 100 fois plus lent). Sur un programme comme un éditeur de texte, ça ne se ressent absolument pas, mais pour réaliser des dynamiques moléculaires, qui durent plusieurs jours même en C... Il serait hors de question d'attendre plusieurs centaines de jours pour obtenir le même résultat.

Mais alors quel est l'intérêt du Python ? Sa rapidité d'écriture. Là où en C il faudrait une dizaine de lignes pour afficher le résultat de 1+1, en Python une seule ligne suffit. Bref, c'est très simple à utiliser, aussi puissant que le C ou le Java, ça s'écrit très vite et, même si c'est un peu lent, ça suffit amplement pour faire des tâches automatisées.

Autre avantage énorme par rapport aux deux autres, c'est la lecture/écriture de fichiers. Ces opérations extrêmement fastidieuses en temps normal (lecture caractère par caractère) est simplifiée. Il devient donc très facile de lire un fichier formaté, comme un fasta, ou un PDB. Il existe en plus de nombreuses bibliothèques (codes déjà écrits par d'autres et qui évite de réinventer la roue à chaque fois) pour gérer les données biologiques, comme les fichiers PDB : BioPython, l'une des bibliothèques les plus au points. C'est pour ça qu'il est utilisé pour faire des programmes généralement courts qui peuvent facilement être améliorés.

Mais il peut être utilisé pour faire de beaux et puissants programmes : PyMOL par exemple utilise de nombreux scripts Python (d'où le Py) pour lire les fichiers PDB ou pour ajouter des plug-in facilement.

Perl

Je vais passer assez rapidement sur le Perl non pas car il ne présent aucun intérêt, mais parce qu'il a à peu près les mêmes utilités et les mêmes caractéristiques que le Python.

Sa syntaxe est (très) différente et la philosophie un peu plus axée sur les expressions régulières, servant elles aussi à lire un fichier ou toute autre information apportée au programme. Il existe une « guerre » entre les utilisateurs de Python et de Perl, chacun ayant ses préférences. Mais la principale différence est juste une question d'habitude, Python étant tout de même plus facilement lisible que Perl pour les non initiés.

Pour un exemple de programmation en Perl, Aurélien C. devrait bientôt présenter un script écrit en Perl.

R

r-logoR n'est pas vraiment un langage de programmation, mais est très utilisé en bioinformatique. R est en réalité un programme écrit en C qui permet de faire de grosses, très grosses statistiques très simplement. Il est déjà tout prêt à être utilisé et a des commandes simples et intuitives... pour quelqu'un qui ne programme pas.

C'est l'un de ses deux inconvénients : quand on est habitué à utiliser la programmation, certains aspects de R semblent absurdes. Second inconvénient, si on utilise R en dehors des boites à outils déjà faites pour nous, il devient très, très lent ! Il permet par contre de faire de jolies graphiques très rapidement (généralement ceux présents dans les publications scientifiques sont créés sous R, parfois un peu retouchés à la main par la suite).

Conclusion

Je vous ai présenté les différents langages dans un ordre précis. Du langage le plus bas niveau vers le plus haut niveau (Perl et Python sont à égalité de ce côté-là). D'une manière générale, plus un langage est « bas niveau » plus il faut se rapprocher du langage de l'ordinateur (le binaire), mais de manière lisible pour un être humain tout de même. Au contraire plus le langage est « haut niveau » plus il se rapproche du langage humain.

Un langage de bas niveau sera donc plus complexe à comprendre et à réaliser, mais sera plus rapide, car plus proche de la machine. Au contraire, un langage de haut niveau sera plus facilement compréhensible et plus souple, mais aura besoin d'un interpréteur pour que l'ordinateur comprenne, ce qui prend plus de temps.
Voilà pour les 5 principaux langages de programmation utilisés par les informaticiens et donc les bioinformaticiens. Il en existe bien d'autres (Fortran, Ruby, Bash, C++, etc.) mais je ne vais pas tous les détailler, chacun ayant ses qualités et défauts. L'essentiel est de savoir lequel utiliser en fonction de ses besoins, de ses préférences et de ses habitudes.

Encore un détail : les langages ne sont pas coupés les uns des autres. Il est ainsi tout à fait possible d'utiliser du Python pour lire un fichier dont le contenu sera envoyé à un programme en C et dont les résultats seront affichés sur une page web grâce à du Perl. Une fois plusieurs langages maîtrisés, il suffit de bien savoir les combiner. Des langages intermédiaires permettant de profiter aux maximum des avantages de chaque langage existent aussi tels Jython, Cython ou encore Pyrex.

Pour ceux qui souhaiteraient plus de détails, un comparatif des performances des langages présentés est disponible. Enfin, vous pouvez trouver d'autres informations (en anglais) dans ce papier expliquant les différentes figures.


Bien commencer en bioinformatique

$
0
0

"Je n'y connais rien en informatique", "C'est trop compliqué pour moi" ou "Je ne sais pas par où commencer" sont des phrases qui nous servent souvent d'excuse pour ne pas nous lancer dans le grand bain de la bioinformatique. Biologiste de formation, j'ai moi-même à plusieurs reprises repoussé l'échéance avant de sauter, ne sachant comment m'y prendre ou voulant commencer directement par des choses trop complexes.

Cet article est donc là pour vous aider à vous lancer, trouver par où commencer et vous donner des indications sur ce que vous pourriez arriver à faire en assez peu de temps.

Je n'aurai pas la prétention de vous transformer en bioinformaticien avec cet article, mais je vais poser des bases essentielles pour commencer et vous montrer quelques exemples de ce qu'il est possible de faire en bioinformatique.

La bioinformatique est aujourd'hui un outil indispensable à la biologie et est utile à n'importe quel biologiste souhaitant élargir son horizon. Elle touche aussi, bien sûr, les informaticiens qui s'intéressent à la biologie. Cette discipline va du simple traducteur de format (voir mon article sur Blast2Gb.pl) à des gros tunnels informatiques de traitement de données (voir l'article de Nisaea sur Galaxy). Les domaines dans lesquels on la retrouve sont aussi divers que, entre autres, la génétique, l'évolution ou l'écologie. Elle peut être utilisée par un biologiste comme par un informaticien, le bioinformaticien étant un hybride, connaissant la biologie et l'informatique.

La bioinformatique est un domaine de recherche et d'expertise laquelle peut être appliquée par des chercheurs et des ingénieurs. Par exemple, cette vidéo de France5, sur laquelle on nous voit apparaitre, présente quelques possibilités de carrière :

Quel système d'exploitation pour le bioinformaticien ?

Windows, Mac OS ou Linux : le choix du système d'exploitation sur lequel travailler relève en premier lieu du goût de l'utilisateur, mais pourra vite se retrouver orienté par les serveurs sur lesquels il va travailler. Car aujourd'hui, l'avènement des techniques de séquençage à haut débit apporte une quantité de données rarement traitable sur un ordinateur de bureau. La plupart des serveurs de calcul étant sous Linux, je vous orienterai plutôt vers Mac OS ou une distribution de la famille Linux. Il est possible de travailler sur Windows, mais ça sera plus contraignant. Et les systèmes Linux sont habituellement gratuits (même sans acheter d'ordinateur avec), le plus facile à utiliser et user-friendly est Ubuntu, alors lancez-vous !

Mais alors : que fait un bioinformaticien ?

Eh bien, il utilise ou crée des logiciels pour traiter des données, les mettre en forme, les représenter, les ranger, les maintenir, etc. Il crée des logiciels ? Oui, et des liens entre logiciels, des outils simples ou complexes, des traducteurs, des bases de données sont dans le bouquet.

Mais il existe aussi des bioinformaticiens plus orientés biologie, qui ne codent pas ou peu et qui utilisent des logiciels déjà prêts, font quelques scripts pour automatiser et peuvent être plus axés sur l'analyse des données. Il existe même une espèce de bioinformaticiens qui ne codent plus du tout, et eux disent que ce qu'ils font est la vraie bioinformatique, que seuls les informaticiens codent.

Chaque bioinformaticien a donc sa propre définition, il suffit de venir sur notre canal IRC demander la différence entre un bon et un mauvais chasseur... euh... bioinformaticien pour s'en convaincre !

En tout cas, les bioinformaticiens sont des gens qui comprennent la biologie et utilisent l'informatique principalement pour faciliter le travail des biologistes, en automatisant des tâches lourdes et rébarbatives. Ainsi, le billet de tadaima sur l'API Perl montre un exemple d'une telle aide : ici, les outils bioinformatiques sont utilisés pour récupérer une grande quantité d'informations à partir d'une grosse base de données sans faire du copier-coller pendant 2 jours.

Un contre-exemple est le billet de Guillaume Collet sur les calculs de conservation : il nous explique une façon différente d'aborder la bioinformatique. Guillaume est un expert à mi-chemin entre l'applicatif et l'algorithmique. Il apporte à la biologie une formalisation plus théorique tout en permettant d'expliquer l'observation historique qui mène à la divergence de séquences par l'utilisation des scores de conservation.

Puisque j'ai mentionné la programmation, parlons maintenant un peu langages de programmation. Je vous invite à lire l'article de Gophys sur les différents langages de programmation pour plus de détails. Mon focus est de vous donner des exemples concrets à titre d'illustration.

Perl et Python

Pour bien commencer -- et de façon simple -- commençons par parler des deux vedettes de la bioinformatique, frères ennemis, que sont les langages Perl et Python. Ils sont spécialement utilisés pour leur puissance de traitement de texte. En effet, la plupart des informations biologiques sont sous forme de texte, et ces deux langages, en plus d'être relativement facile à appréhender, ont été faits pour traiter du texte, rapidement et efficacement.

Voici un exemple de code écrit en Perl :

#!/usr/bin/perl
print "Hello World!\n";

Voici le même code en Python :

#!/usr/bin/python
print('Hello World!')

Vous remarquerez que ces deux langages sont extrêmement proches de premier abord. Ils présentent cependant de grosses différences, le premier étant plus laxiste, le deuxième plus précis. Je vous invite à lire le livre Learning Perl aux éditions O'Reilly ou bien la documentation en ligne de Perl pour commencer en Perl. Côté Python, le très bon Dive into python, en français pour mes lecteurs préférés, est idéal pour commencer en Python. Vous trouverez aussi de bons didacticiels en français sur le Site du Zéro, comme par exemple comment programmer avec Python avec même des vidéos à la clé !

Pour la biologie, des modules appelés BioPerl et BioPython ont été développés et permettent par exemple de télécharger plusieurs séquences au format fasta sur GeneBank sans avoir à les faire une par une à la main, de récupérer des informations et de les trier. Un exemple de BioPython, par exemple ? En voilà : pour annoter une liste d'ID de gènes sur la base Entrez du NCBI.

L'interpréteur de commande

Pour lancer un programme, aussi bien sous Windows que sous Mac OS ou Linux, il faut passer par un interpréteur de commandes, respectivement cmd.exe et shell. Ce sont les interfaces permettant à l'utilisateur de s'adresser directement à l'ordinateur. Chaque interface a ses spécificités : il faut lui parler dans une langue qu'elle comprend et sait interpréter. Dans le cas du shell sous Linux, par exemple, la langue la plus fréquemment utilisée est le bash.

Avec ces détails, nous pouvons lancer un programme écrit Perl par exemple :

perl programme.pl

De nombreuses fonctions sont disponibles sous bash, moins sous cmd.exe, mais il est possible d'utiliser bash sous Windows avec un logiciel comme putty ou cygwin.
Depuis Windows 7, PowerShell vient en complément de cmd.exe. C'est un shell s'approchant des possibilités de bash.

L'interpréteur de commande, en plus de lancer des programmes, permet de définir les chemins, les droits d'accès, l'environnement, etc. de votre ordinateur.
On peut encore faire des boucles et les commandes awk et sed permettent des faire des manipulations avancées sur des textes, à la manière de Perl ou Python, en étant cependant moins souples et puissants. On peut par exemple remplacer un caractère "_" par un "-" dans la chaine de caractères $chaine qui a pour valeur "bioinfo_fr" avec la commande suivante :

echo $chaine | sed -e "s/_/-/"

(La forme "s/x/y/" est d'ailleurs reprise en Perl par exemple.)

Vous trouverez des détails sur les différences entre Perl, Python ou encore awk et sed sur cette page (en anglais).

C'est tout ?

Eh bien non, le monde de la bioinformatique est tellement vaste que j'aurais bien du mal à vous en présenter les limites. Ainsi, je me contenterai d'élargir un peu la vision présentée jusqu'ici.

Par exemple, dans le cadre de la bioinformatique entrent les statistiques et la modélisation, qu'il est possible d'appréhender avec des logiciels comme R ou Matlab. Ces deux logiciels sont proches dans leurs fonctionnalités et utilisent un langage de programmation qui leur est propre. R a l'avantage d'être gratuit. Ils vous permettront aussi de faire de beaux graphiques. Un exemple très simple en R, permettant de faire une représentation 3D, est le suivant :

install.packages("onion")
require(onion)

data(bunny)
p3d(bunny,theta=3,phi=104,box=FALSE)

Je vous laisse le bonheur de découvrir ce que fait ce petit code !

La plupart des graphs que vous voyez dans les grands journaux sont faits sous R. À la différence des tableurs, comme Excel ou Numbers qui permettent de faire des graphs automatiquement, dans R vous contrôlez absolument tout. Vous pouvez automatiser la création de graphes tout comme vous automatiseriez un traitement de données. La bible de R est le R book.

Aller plus loin

Enfin, un petit article en anglais pour saisir quelques ficelles du métier : Hello - I used to think I was good with a computer. Dans cette discussion, Jon_Keats expose ses débuts en bioinformatique et donne les démarches et solutions entreprises pour arriver à ses fins. Par exemple, il donnera le conseil suivant : "Mon patron aurait dû me faire lire le tutoriel "Unix and Perl for Biologists" des années auparavant". Il vous donnera aussi les premières bases de l'installation d'un programme sous Linux ou encore comment utiliser le gestionnaire de version git pour éviter l'accumulation inutile de mon-fichier1.txt, mon-fichier2.txt, etc.

Un outil utile pour apprendre différents langages est disponible ici. Il vous permettra d'apprendre comment passer d'un langage à un autre.

La conclusion serait que pour devenir bioinformaticien, il faut avoir de bonnes bases en biologie ou en informatique et s'intéresser fortement à l'autre discipline, celle que l'on connaît le moins, afin de pouvoir discuter et se comprendre.

Voilà déjà de quoi vous occuper quelques temps et vous permettre d'accéder au monde magique de l'automatisation. Si vous avez la moindre question, n'hésitez pas à venir nous la poser des questions sur le canal IRC ("le chan" pour les intimes), nous nous ferons un plaisir d'y répondre ! Ah, vous ne savez pas ce qu'est un chan ? Allez voir  !

A bientôt sur le chan !

Merci à Malicia, Guillaume, Yoann et Nicolas pour leurs relectures et commentaires pré-publication.
Crédit image : Isabelle Stévant (Art Libre)

Bioconductor

$
0
0

Bioconductor

Voilà le sujet que l'on va aborder ensemble aujourd'hui. On va voir ce que c'est, à quoi cela sert, comment l'installer et bien-sûr l'utiliser.

Qu'est-ce donc ?

Je décrirais Bioconductor comme un projet participatif. Il est libre d'accès et son développement dépend de ce que la communauté veut bien y apporter. L'objectif est simple, offrir aux biologistes, un ensemble de programmes pour l'analyse de données, faciles à mettre en place et à utiliser. Le support principal choisi est le langage R, qui est à la fois un langage de programmation et un environnement pour l'analyse statistique des données (pour en savoir un peu plus: Les langages de programmation). Bioconductor se présente donc sous forme d'une collection de packages R pour l'analyse de données biologiques (biologie moléculaire principalement). Chaque package est en fait une application que vous installez et chargez dans l'environnement R, vous avez ensuite accès aux fonctions qu'il contient. Nous verrons plus tard comment installer et charger un package Bioconductor.

Pourquoi avoir créé Bioconductor ?

Aujourd'hui la bio-informatique commence à se faire connaître, cela fait un peu plus de dix ans qu'elle se développe et que des étudiants sont spécifiquement formés dans ce domaine. Seulement voilà, il n'y a pas encore de bio-informaticiens dans tous les laboratoires et il y a plein de bonnes raisons à cela (Mais on y arrivera un jour... on y arrivera). Alors les biologistes seuls face à leur données ont décidé, en 2001, de créer un projet pour mettre en commun les scripts qu'ils utilisaient pour faire leurs analyses. Il y a une équipe centrale au Fred Hutchinson Cancer Research Center (FHCRC) et la communauté partout autour du monde.

Désormais plutôt que de garder pour eux le joli script R (ou autres), qui avait pris des mois de développement et qui ne servirait qu'à deux ou trois projets dans le laboratoire, les biologistes/bio-informaticiens peuvent partager leurs travaux pour rendre service à la science et aux autres laboratoires. En plus du partage, il y a la volonté du projet de procurer un outils pour faciliter les analyses de données, mais également assurer la reproductibilité de celles-ci.

Qui développe pour ce projet ?

Bioconductor est mis à jour tout les six mois, nous somme actuellement à la version 2.10. Il y a toujours deux versions disponibles, une stable et une de développement. On ne peut donc pas venir sur le site de Bioconductor, y inclure un code instable et prétendre avoir participé au projet. Il y a des règles à respecter pour voir son package publié, l'une d'elle, par exemple, est de fournir une documentation complète pour l'utilisation du logiciel. Le compilateur vous enverra des messages d'erreurs si une fonction n'est pas référencée dans la documentation ou si une dépendance à un autre package n'est pas prise en compte pendant l'installation. Si votre package se compile sans erreur, il faut ensuite le soumettre aux responsables du projet, qui reviendront vers vous avec de possibles modifications à appliquer. Dans un sens, cela ressemble au processus de publication dans un journal scientifique.

Les personnes qui publient des packages dans Bioconductor sont donc des biologistes ou bio-informaticiens qui ont une certaine expertise dans l'analyse de données et qui souhaitent partager une nouvelle méthode ou l'amélioration d'un algorithme déjà existant. Le nombre de solutions proposées pour l'analyse des micro-array par exemple est assez incroyable. Tout comme aujourd'hui avec les données de séquençage à haut débit, les statistiques appliquées aux micro-array ont évolué au fil des années et on peut avoir l'impression que pour chaque méthode, chaque nouvelle normalisation ou correction d'erreur due à la technique, un package a été publié dans Bioconductor. Cela témoigne bien-sur d'une communauté très active, mais également que pour soutenir la publication, dans un journal, d'une technique d'analyse il est de bon goût de proposer un logiciel pour l'appliquer. Comme la création d'un package est somme toute abordable, même sans grande connaissance de l'informatique, beaucoup de scientifiques se sont tournés vers cette solution. De plus cela assure de toucher un grand nombre de personnes.

Où le trouver, comment l'installer et l'utiliser ?

Toutes les informations pour installer un package Bioconductor sont sur cette page : Installer Bioconductor.

Mais pour résumer l'installation, il vous faut un ordinateur avec R installé et charger biocLite dans l'environnement de travail de R. Il s'agit d'un petit script qui vous permettra d'installer facilement les packages Bioconductor.

Dans R :

source("http://bioconductor.org/biocLite.R")

Il suffit ensuite de choisir l'application que vous voulez installer. Si par exemple vous souhaiter installer le package 'easyRNASeq', il vous suffit d'utiliser la commande suivante:

Dans R :

biocLite('easyRNASeq')

Un script Bioconductor peut utiliser d'autres packages, les dépendances seront alors installées automatiquement si elles sont absentes de votre système.

Si vous ne souhaitez pas passer par biocLite, parce que vous désirez une ancienne version ou pour n'importe qu'elle autre bonne raison, vous pouvez installer les applications de Bioconductor comme n'importe quel autre package R.

Dans R :

install.packages("chemin/vers/monpackage")

Une fois le package installé vous pourrez le charger dans R à chaque nouvelle session de travail avec la commande suivante :
Dans R:

library("monpackage")

Pour obtenir plus d'information sur le package et sur ses fonctions vous pouver utiliser les commandes suivantes :

packageDescription('monpackage')
?nomDeLaFonction

Vous pouvez trouver la liste de tous les packages disponibles (554 dans la dernière version) sur le site internet de Bioconductor (Liste des packages), on y retrouve des outils pour la normalisation des données (limma, babar), le calcul de la couverture d'une expérience de séquençage (easyRNASeq, htSeqTools), l'annotation de séquences et la consultation de banques de données (AnnotationDbi, GO.db) et bien plus encore. L'équipe du site propose également quelques exemples de workflows pour l'analyse de vos données ( Exemples d'analyses ).

----------

J'espère vous avoir convaincu de l'utilité de Bioconductor si vous avez des données à analyser, ou au moins de vous avoir donné envie d'aller jeter un coup d'œil, au cas où l'application miracle que vous cherchiez se trouve dans la liste. La communauté est vivante, vous pouvez vous inscrire à la liste de diffusion, de plus chaque package a une personne attitrée pour répondre à vos éventuelles questions. Je ne garantis pas que tous les développeurs soient à votre disposition, mais si vous n'obtenez pas de réponses vous pouvez toujours aller sur ce super forum seqanswers, j'y ai même déjà vu plusieurs développeurs répondre à des questions sur leurs packages.

Cours de R pour débutant pressé (introduction)

$
0
0
CRAN

Le site du CRAN

Bonjour à tous !

Aujourd’hui, je vous propose un premier petit cours/tutoriel de niveau « débutant », d’une série d’au moins 3, pour apprendre à faire quelques graphiques avec R et vous la péter devant vos collègues encore à grapher sous Excel.

Ce tutoriel s’adresse aux gens qui n’ont jamais codé, qui ne connaissent pas grand-chose à la programmation et qui sont avides de savoir/pouvoir (les deux choses étant liées). Donc si vous êtes non-bioinformaticien sympathisant, ou que vous passez là par pur hasard, ce tuto est pour vous ! De plus, j’ai dans l’idée de m’efforcer à faire des cours condensés, qui ne vous occuperont pas plus de 10 minutes, pour meubler vos pauses clopes (c’est meilleur pour la santé !).

La première question que j’entends quand je parle de R est « Mais R, c’est quoi ? ». R est un langage de programmation et un environnement mathématique, qui permet, de façon très poussée, de faire du traitement de données, de l’analyse statistique, ou encore comme nous allons le voir aujourd’hui, des graphiques !

Pourquoi ne parler que des graphiques aujourd’hui ? Parce que trop de gens en biologie font encore des graphiques avec Excel (et consorts), perdent du temps, font des choses inappropriées, ou ne contrôlent pas leurs graphiques de bout en bout. De plus, les journaux scientifiques ont des exigences qui ne cadrent pas toujours avec les graphiques automatiques. Ce que permet R, et c’est sa plus grande force face à Excel, c’est un contrôle total de ce que vous allez afficher. Et si tout le monde fait des graphiques, tout le monde ne fait pas de statistiques (même si tout le monde devrait).

Allez, on se lance !

Pour installer R, rien de plus simple ! On se rend sur le site du CRAN, on télécharge la version adaptée à son système d’exploitation et on installe R comme n’importe quel logiciel. Il est à noter qu’une version est souvent présente sur les systèmes Unix/Linux, sans avoir à installer quoi que ce soit. Cependant, ces versions sont souvent un peu datées et il est préférable d’installer les dernières versions stables, quand on souhaite utiliser des packages (nous reviendrons là dessus).

A l’heure de la rédaction de ces lignes, la dernière version est la 2.15.1, donc il sera possible dans le futur que ce que je vous écris là soit dépassé et que les ordinateurs aient pris le contrôle de votre cerveau. Dans ce cas, pas la peine de continuer.

Si vous avez toujours vos neurones, vous tomberez après installation sur la fenêtre suivante :

Je suis ici sous Mac OSX et j’ai simplement édité mes couleurs de la façon suivante (pour que les gens qui passent devant mon écran sachent que je travaille directement dans la matrice) :

Nous avons donc tout un tas de blabla, puis un chevron « > », qui nous indique que R attend notre prochaine commande. Nous allons commencer par les bases et taper « 2+3 » puis entrée. A cet instant, magie noire se faisant, R calcule et vous donne le résultat, après le [1] (le numéro du résultat).

Très bien, vous venez d’entrer votre première commande. Félicitations !

Maintenant, entrons notre première valeur en mémoire :

> a <- 1

Nous avons ici notre chevron montrant que R nous attend, puis « a », le nom de notre variable, puis la flèche vers la gauche « <- » pour assigner, puis « 1 », la valeur que l’on donne à notre variable. On peut lire ceci comme "soit a une variable de valeur 1".

Un peu de détail :

Une variable, contrairement à une constante, est : Variable ! Bravo ! C’est-à-dire que l’on peut lui assigner une valeur, qui peut changer au cours du temps. Cette variable est stockée dans la mémoire de l’ordinateur et peut être rappelée quand bon vous semble.

« <- » peut être remplacé par « = », c’est la même chose. La flèche ayant l’avantage dans des cas avancés de pouvoir aller dans l’autre sens (->), alors que le « = » sera toujours pour assigner une valeur sur la droite à une variable positionnée sur la gauche.

La valeur est ce que l’on veut garder, le résultat d’un calcul, un mot, une histoire, une aventure…

 

Maintenant, si vous tapez « a » :

> a

[1] 1

 

R vous retourne la valeur de votre variable !

Maintenant, créons un vecteur (une suite de valeurs), grâce à « c( ) ».

> a <- c(1,2,3,4,5)

Nous venons d’assigner à notre variable « a » les 5 valeurs 1, 2, 3, 4 et 5, dans un vecteur. Nous pouvons maintenant faire des choses avec ce vecteur, comme lui additionner 1 :

> a + 1

[1] 2 3 4 5 6

R additionne 1 à chaque élément de « a » et nous retourne le résultat.

 

Maintenant, que se passe-t-il si on fait un plot, un graphique de ce vecteur ?

> plot(a)

R nous dessine un graphique, avec en ordonnée les valeurs de notre vecteur (1, 2, 3, 4 et 5) et en abscisse leur position dans l’index de « a ». Vous allez me dire "Hey, mais tout à l'heure, on a fait a+1, alors on devrait démarrer notre vecteur à 2 !", ce à quoi je vous répondrai : "Oui, banane, mais on ne l'a pas mis en mémoire, si ?" et tout penaud vous baisserez la tête et répondrez "Ah non, c'est vrai...". En effet, il aurait fallu assigner cette addition en mémoire si on voulait la conserver. Mais on verra ça la semaine prochaine.

Revenons à la position dans l'index. Qu’est-ce que cette position ? Et bien dans un vecteur, chaque élément est indexé, c’est-à-dire que l’on peut y accéder directement en connaissant son numéro d’index. Il est à noter que contrairement à beaucoup de langages de programmation, R compte à partir de 1 et pas de 0 (comme nous quoi...). Par exemple, dans « a », le 3 a pour index 3. Pour y accéder, on tape :

> a[3]

[1] 3

 

C’est plus convaincant si on modifie a :

> a<- c(5,6,7,8,9)

> a[3]

[1] 7

 

Le nouveau 3ème élément de « a » est bien 7 (ce que vous pouvez vérifier en re-plottant a).

Si vous voulez en savoir plus sur la fonction plot, comme toutes les fonctions R, elle a une page d’aide, à laquelle vous accédez en tapant le nom de la fonction précédé par un « ? » :

> ?plot

starting httpd help server ... done

 

Vous devriez voir apparaître la page d’aide de la fonction recherchée, en l’occurrence « plot », du package {graphics} qui est un package de base de R.

 

Voici déjà une introduction sur R, vous permettant de commencer à vous amuser. Dans la prochaine édition, nous verrons comment faire des graphiques avec des couleurs, des points funky, le tout avec des exemples appliqués à la biologie et toujours en vous permettant de faire le cours en 10 minutes top chrono !

Allez, c'est mon cadeau du jour, le top de "J'me la tonze sévère" ; tapez dans R le code suivant (que vous connaissez si vous êtes un fan du blog) :

install.packages("onion")

require(onion)

data(bunny)
p3d(bunny,theta=3,phi=104,box=FALSE)

Toutes les images de cet article sont sous licence CC BY-SA 3.0.
Je tiens à remercier Sylvain P., Nico M. et nahoy pour leurs commentaires.

Cours de R pour débutant pressé, deuxième épisode

$
0
0

Voici donc la suite de mon premier cours de R pour débutants pressés où nous avions vu les bases. Je vous invite à le lire si vous ne l’avez pas déjà fait.

Aujourd’hui le but est de s’approcher de la biologie et de traiter d’un cas concret. Nous avons des données de différents éléments fonctionnels du génome de D. melanogaster et leurs proportions de conservation et de contrainte évolutive. Nous souhaitons représenter la proportion dans laquelle chaque élément est contraint ou pas. Pour cela, nous allons faire un graphique en barre ou barplot, avec barres d'erreurs. Ce que nous allons voir aujourd'hui peut très bien s'appliquer à d'autres types de données : représenter la quantité de gènes suite à une qPCR ou encore le nombre de fois qu'une souris a choisi le bon chemin dans le labyrinthe suivant chaque condition choisie. L'important n'est donc pas ici le fond, mais bien la forme.

À la fin de ce tutoriel, vous devriez être capables de faire quelque chose comme ça :

R et les éditeurs de texte

Nous allons tout d'abord ouvrir R et un document (éditeur de texte intégré à R), qui va vous servir à sauvegarder le code de votre graphique, la console R n'étant pas forcément la plus pratique.

La console R :

Pour ouvrir un document, cliquez sur "Fichier/Nouveau Document" ou ctrl+N sur Windows et Linux, cmd+N sur mac. Une bonne habitude à prendre est d'écrire son code dans un éditeur de texte, nous permettant de l'éditer, de le sauvegarder, d'y revenir, sans à chaque fois avoir à remonter toutes les lignes. Je vous demanderai donc de suivre ce conseil. Surtout que si vous ne le faites pas, vous serez embêtés plus bas sur cette page !

Je voudrais que vous notiez avant de commencer que j'utiliserai la notation américaine du séparateur des décimales, donc le point et non la virgule.

Chargement du tableau de données

Nous allons commencer par créer une variable qui va contenir les données. Pour cela, nous allons charger dans R un tableau que je vous ai préparé : Table.txt

results = read.table ("Chemin_ou_vous_avez_sauve_Table/Table.txt", header = T);

attach (results);

Nous voyons ici plusieurs choses :

  • comment lire un tableau ("read.table") ;
  • comment sauver ces données dans une variable ("results =") ;
  • comment dire à "read.table" que le tableau qu'on lui donne a ce qu'on appelle un header (un entête), c'est-à-dire que les colonnes ont un nom (la première ligne du fichier est composée des noms des colonnes).

[important]Quand on utilise la fonction "read.table", R s'attend à ce que le fichier qu'on lui donne ait les colonnes séparées par un espace ; il est possible de lui spécifier un autre séparateur avec l'argument "sep".[/important]

Enfin, "attach" sert à attacher les données contenues dans "results", dans le but de pouvoir appeler directement les colonnes de Table.txt, simplement en les nommant.

[notice]Essayez par exemple de taper "Conserved", il vous répondra les valeurs contenues dans la colonne "Conserved"[/notice]

 

1er barplot : La partie haute

Maintenant que nos valeurs sont bien accrochées, dessinons ! "barplot" est une fonction qui permet de faire... roulement de tambours... un barplot ! Soit en bon français, un graphique en barres. C'est exactement ce qui nous intéresse ici. Commençons donc par la ligne suivante :

barplot (Conserved, ylim = c(-1,1), main = "Bases", col = "yellow", axes = F);

Ici, nous allons donc représenter les données de la colonne "Conserved" :

  • en définissant les limites sur l'axe y (ylim) entre -1 et 1 ("c(-1,1)") ;
  • en définissant un titre principal ("main") ;
  • en colorant les barres ("col") en jaune ;
  • le tout sans dessiner d'axes (axes = F) ;

Nous représentons ainsi la partie haute de notre premier graphe :

 

2ème barplot : La partie basse

Pour faire la partie basse, c'est aussi simple, il suffit de taper ceci :

barplot (-Unconserved, ylim = c(-1,1), col = "yellow4", add=T, axes = F);

Si vous avez bien suivi, vous devez avoir :

  • la partie du bas ("-Unconserved") ;
  • dans les mêmes limites ;
  • avec une couleur plus sombre ("yellow4") ;
  • toujours sans les axes

[notice]Vous devez vous demander ce que c'est que ce "add=T". Eh bien, c'est un paramètre très utile que l'on peut trouver dans beaucoup de fonctions, qui permet très simplement d'ajouter ("add") un nouveau graphique à un premier, sans l'effacer ! C'est l'équivalent de la fonction points ou lines pour la fonction plot (je vous invite à taper "?lines", "?points" ou "?plot" si vous vous posez des questions). Cet "add" présente l'avantage de ne pas avoir à apprendre ou retenir le nom d'une autre fonction pour faire la même chose ![/notice]

Bon, trêve de bavardages, la suite !

Les axes

axis (2, label = c(1, 0.5, "0.0", 0.5, 1), at = c(-1, -.5, 0, .5, 1))

La fonction axis permet de dessiner un axe, que l'on n'avait pas dessiné avec barplot. Pourquoi faire ceci ? Essayez les lignes précédentes en mettant "axes = T", vous verrez que barplot vous dessine un axe y dont les valeurs vont de -1 à 1. L'intérêt étant ici non pas d'avoir un axe de -1 à 1, mais de 1 à 0, et de 0 à 1, car on représente 2 éléments qui sont inverses, en valeur absolue.

Le 2 est l'axe que l'on veut dessiner, en retenant le code suivant : 1 = bas, 2 = gauche, 3 = haut et 4 = droite.

[notice]Je vous invite à vous amuser un peu avec ces axes.[/notice]

"Label" est la liste de noms que l'on veut donner aux points affichés sur l'axe (une liste se définit en mettant des éléments séparés par une virgule dans "c()"), et "at" est la liste des positions respectives de ces points.

axis (1, label = c("FB CDS","Cel. CDS","FB 5'UTR","Cel. 5'UTR","FB 3'UTR","Cel. 3'UTR","FB Exons",

"Cel. Exons","FB Introns","FB Pseudogenes","PolII binding sites","TF Sites","Histones",

"CNV","Origins"), las = 3, at = 0.7+1.2*(0:14), pos = -1.1)

Faisons de même pour l'axe des x, que l'on veut voir porter le nom des "Features" de Table.txt, mais de façon plus lisible :

  • pour cela, on n'utilise pas directement Features, mais on redonne une liste (vous vous souvenez de "c()") ;
  • on aurait aussi très bien pu définir d'abord le nom des Features et l'utiliser ;
  • ensuite, on indique les positions de ces noms (tapez "0.7+1.2*(0:14)" dans R pour comprendre). Vous pouvez trouver les positions empiriquement en tâtonnant, mais sachez qu'en toute logique, il y a un espace avant les barres (ici le 0.7), puis que les barres sont également espacées (de 1.2), et qu'il y a 15 barres (on compte de 0 à 14 car la première est en position 0.7).
  • enfin, "pos" détermine la position verticale de l'axe, que l'on veut ici en dessous du graphique du bas, donc placé à un peu plus de -1, par exemple -1.1.

 

Les barres d'erreurs

Plaçons maintenant les barres d'erreur, qui servent en général à représenter l'écart type, qui est en termes simples une indication de la dispersion des résultats. Vous pouvez cependant représenter ce que vous voulez, Variance ou autres. La fonction qui nous intéresse est "errbar", elle est contenue dans le package "Hmisc", qu'il vous faudra installer ("install.packages()") et appeler ("require()" ou "library()") :

install.packages("Hmisc")
 library(Hmisc)
 errbar (0.7+1.2*(0:14), Mean, yplus = Mean + 2*SD, yminus = Mean - 2*SD, add = T, pch = "")
 errbar (0.7+1.2*(0:14), -Mean2, yplus = -(Mean2 + 2*SD2), yminus = -(Mean2 - 2*SD2), add = T, pch = "")

Nous donnons donc à errbar :

  • en premier la position sur l'axe x où nous voulons nos barres d'erreurs ;
  • ensuite les positions, qui sont les mêmes que celle des noms sur l'axe x ;
  • puis la position du milieu de la barre ("Mean"), la position haute de la barre d'erreur ("yplus"), la position basse ("yminus") ;
  • enfin on l'ajoute au graphique précédent ("add=T") ;
  • et on utilise "pch" qui est le symbole utilisé pour le milieu de la barre (nous n'en voulons pas, donc on le met égal à un ensemble vide).

[error]Vous devez avoir un erreur qui apparaît quand vous tapez le code de la première barre d'erreur :

Erreur dans y[Type == 1] : objet de type 'closure' non indiçable

Pour comprendre cette erreur, c'est très simple. Tapez dans votre console R :

Mean

Il vous renvoie :

function (object, ...)

 UseMethod("Mean")

 &lt;environment: namespace:Hmisc&gt;

Et pas la suite de moyennes attendu. Ceci est dû au fait que "Mean" est, en plus d'être notre liste de moyennes, une fonction interne à R et déjà utilisée. Donc R, quand on appelle "Mean", va tenter d'utiliser la fonction. Or on ne peut pas représenter graphiquement une fonction sans lui donner de données : on crée donc une erreur. Cette erreur nous permet de voir comment préciser que ce que l'on veut représenter appartient à un ensemble plus grand. Par exemple ici, notre ensemble est "results" duquel on veut représenter "Mean". Il faudra donc mettre dans notre commande non pas "Mean" mais "results$Mean", qui est en quelques sortes le chemin interne de notre liste.[/error]

[notice]Je vous invite à jouer avec "pch" en lui donnant des valeurs numériques, mais aussi des symboles ou des lettres ; pch = "+" est très utilisé.[/notice]

Les derniers détails

abline(h=0, lwd = 2)

Ajoutons maintenant une ligne droite pour délimiter l'espace des deux graphiques, avec la fonction "abline" (qui signifie ligne entre le point A et le point B). Nous voulons qu'elle soit horizontale, au point 0 de l'axe des y et qu'elle ait une épaisseur ("lwd = line width", épaisseur de ligne) de 2. Enfin, ajoutons un peu de texte, avec la fonction "mtext" :

mtext("Fraction", side = 2, adj = 0.5, padj = -6)
 mtext("Conserved", side = 2, adj = 0.80, padj = -4)
 mtext("Unconserved", side = 2, adj = 0.2, padj = -4)

On veut donc ajouter 3 mots, "Fraction", "Conserved" et "Unconserved", du côté gauche (2) du graphique :

  • "adj" sert à placer le texte parallèlement à l'axe auquel le texte se rapporte, donc ici en hauteur
  • "padj" place perpendiculairement à l'axe (d'où le p)

[important]Cette fois, cependant, on se place par rapport à l'extérieur du graphique et non plus par rapport aux valeurs de l'axe, car "mtext" place dans la marge (d'où le m). Alors là, vous allez me dire "Ouais mais moi ça sort de partout et ce n'est pas joli !"... et vous aurez raison. En effet, en écrivant des choses dans la marge, d'autant plus avec des valeurs inverses, sans avoir au préalable défini ces marges est dangereux, et vous fera bien souvent vous planter. [/important]

Nous définirons les marges ainsi :

par(oma = c(1,4,6,4), mar = c(9.1,4.1,1.1,4.1));

Ici, "oma" veut dire "outer margin", "mar" veut dire "margin", et les valeurs sont données toujours dans le même sens "bas, gauche, haut, droite". Maintenant, retapez votre code complet, ce qui devrait être facile si vous avez, au fur et à mesure de l'article, copié votre code dans votre éditeur de texte :

C'est-y pas beau tout ça ? Comment ? Vous voulez mettre ça dans un fichier .png ? Vous êtes bien exigeants... bon, eh bien c'est très simple, il suffit de taper ceci avant le graphe (personnellement, je le fais tout en haut) :

png ("chemin_ou_vous_voulez_le_sauver/nom.png", height = 1600, width = 1600, pointsize = 19);

[important]Pour les valeurs de hauteur ("height") et largeur ("width"), par défaut elles sont données en pixels que je trouve être une bonne unité pour se rendre compte de la qualité et de la lisibilité d'un graphique. Mais vous pouvez bien sur choisir d'autres unités. Enfin, la taille des points du texte est à faire varier en fonction des gouts, en vérifiant bien à chaque changement la position des divers éléments texte, et en sachant qu'elle est calculée en fonction de la résolution de l'image ("?png" pour plus d'informations).[/important]

Alors, ça fonctionne ? Non ? Le fichier est vide ? Ah oui, il faut le fermer, sinon R est toujours entrain d'écrire dedans. Pour cela, on tape :

dev.off();

R doit alors vous indiquer qu'il est revenu dans le cadre de dessin précédent (quartz, x11 ou autres).

Enfin, vu qu'on a attaché "results" au début et qu'on veut prendre de bonnes habitudes, il faut détacher "results" :

detach(result);

Si vous arrivez au même résultat, bravo ! Il ne vous reste plus qu'à vous attaquer à la dernière tâche ! Sinon, n'hésitez pas à poser des questions, je me ferai un plaisir d'y répondre !

À vous de jouer !

Votre défi, si vous l'acceptez, sera alors de faire comme sur la figure en haut de l'article, mettre 2 graphiques dans la même image, du texte en plus, et la tourner dans le bon sens. Pour cela 2 indices :

  • "mfrow" est un paramètre très utile de "par " ;
  • parfois, il vaut mieux se faciliter la vie et faire des choses simples avec un outil simple. Une rotation par exemple.

Le premier qui réussit aura gagné tout mon respect !

Allez, à la prochaine pour des choses encore plus folles !

---

Je tiens à remercier tous mes relecteurs, Hautbit, nallias, Guillaume Collet, Malicia, Yoann M. et nahoy !

Suivez le guide : en quête de HMM

$
0
0
Hidden Markov Model | licence Wikipedia

Hidden Markov Model | licence Wikipedia

Bases Théoriques :

Une chaîne de Markov , ça vous dit quelque chose? Une classe de modèles de Markov, appelée Modèle de Markov Caché (Hidden Markov Model, HMM), est un modèle mathématique permettant de segmenter un signal observé en régions (états cachés) définis par le modèle. Lequel est composé de quatre éléments : N états, une matrice d’émissions, des conditions initiales et une matrice de transition. Le rôle de la matrice de transition est de définir la probabilité de passer d’un état à un autre ou de le maintenir inchangé au cours d’un certain nombre d’observations. Quant à la matrice d’émissions, elle définit la probabilité d’émission d'un certain signal en fonction de l'état caché.

Compliqué ? Pas du tout. Voyez par exemple le cas d’un modèle à deux états. Ce type d’algorithme  a fait ses preuves dans différents domaines tels que la reconnaissance vocale (Juang Rabiner, Technometrics 1991), la détection de domaines topologiques issus de données de séquençage High-C (Dixon,Nature 2012), et dans le « foot printing » de données DNAse1-seq (Boyle, Genome Res 2011) entre autres.

Nous allons ici détecter des patterns dans des séquences d’ADN à l’aide d’une librairie R nommée  « HMM » et apprendre par l’exemple certaines des fonctions de cette librairie.

ISOCHORE dans la ligne de mire

Objectif : détecter automatiquement une région ISOCHORE riche en GC de 10 paires de bases (pb) dans une séquence d’ADN  (ATGC) d’une longueur de 100 pb. Un ISOCHORE est défini par un contenu en GC de 50% et un contenu en AT de 50%. Dans le reste du génome (ou de notre séquence à analyser) le contenu en GC est de 20% et le contenu en AT est de 80%.

Première étape : définir le modèle. À savoir, le nombre d’états, la matrice d’émission et la matrice de transition. Dans le cas présent, deux états : ISOCHORE et NON-ISOCHORE. Sachant que la longueur d’un isochore est de 10 pb environ et que la séquence analysée est de 100 pb, voici une définition de la matrice (de dimensions 2x2) de transition entre les deux états:

matrice de transition ISOCHORE NON-ISOCHORE
ISOCHORE 9/10    (1-p) 1/10     (p)
NON-ISOCHORE 1/90    (q) 89/90  (1-q)

 

Pour cet exemple, la probabilité de rester dans l’état ISOCHORE est de 9/10 ; en effet, un isochore fait environ 10 pb de long et la probabilité de passer de l’état ISOCHORE à l’état NON- ISOCHORE est de 1-9/10. De plus, sachant que la longueur totale de la séquence est de 100 pb, 90 observations sur 100 sont dans l’état NON-ISOCHORE. Ainsi, la probabilité de passer de l’état NON-ISOCHORE à l’état ISOCHORE est de  1/90= 1/(100-10) et la probabilité de rester dans l’état NON-ISOCHORE de 1-1/90 =89/90.

La matrice d’émissions du modèle est donc définie comme suit :

Matrice d’emmission

ISOCHORE

NON-ISOCHORE

A

0.25

0.4

T

0.25

0.4

G

0.25

0.1

C

0.25

0.1

Elle permet de déterminer la probabilité d’une observation donnée selon l’état caché. En ce qui concerne ISOCHORE, nous avons 50% de GC et 50% de AT, donc une probabilité égale de 25% pour chaque observation A, T ,G,C. Dans le cas de l’état NON-ISOCHORE, un biais apparaît, avec 80% de AT donc 40% de A, 40% de T, 10% de G et 10% de C.

Enfin, dans ce modèle, la probabilité  initiale d’être dans l’un ou l’autre des états a été définie à 90% pour l’état NON-ISOCHORE et à 10% pour l’état ISOCHORE.

Le modèle complet est représenté dans la figure suivante.

HMM ISOCHORE

Les miracles de la librairie HMM pour R

Deuxième étape : installer la librairie HMM pour R. Pour cela, utilisez les commandes suivantes :

install.packages("HMM") 
library(HMM)

Puis définissez le modèle décrit plus haut:

states <- c("N","I")

emissions <- c("C","G","A","T")

initProb <- c(0.9, 0.1)

transProb <- t(matrix(c(89/90, 0.1, 1/90, 0.9), 2))

emissionProb <- matrix(c(0.1, 0.25, 0.1, 0.25, 0.4, 0.25, 0.4, 0.25), 2)

hmm <- initHMM(states, emissions, initProb, transProb, emissionProb)

hmm # imprime un résumé du modèle

Ensuite, il faut simuler. Soit créer une simulation de la séquence observée à partir du modèle ou alors analyser une séquence d’intérêt puis détecter la région ISOCHORE. Marche à suivre pour créer une séquence d’ADN  de longueur 100 bp contenant une ISOCHORE :

# Séquence à analyser
observation <- c("A", "T", "G", "A", "A", "A", "C", "A", "A", "T", "T", "T", "T", "T",

"A", "T", "A", "A", "C", "T", "A", "T", "A", "T", "A", "T", "G", "T", "A", "C", "A",

"T", "A", "T", "T", "T", "T", "T", "A", "T", "T", "A", "A", "T", "T", "A", "G", "C",

"G", "A", "C", "G", "C", "C", "G", "C", "A", "C", "T", "A", "G", "A", "A", "T", "A",

"T", "A", "A", "T", "T", "C", "A", "T", "T", "A", "T", "G", "T", "T", "T", "A", "T",

"T", "G", "A", "A", "A", "A", "C", "A", "T", "A", "C", "T", "T", "G", "A", "A", "A", "A")

# Simulation à partir du modèle
simulation <- simHMM(hmm,100)
simulation # imprime le résultat de la simulation 
           # notamment la séquence d’observations générée par le modèle 
           # et les états cachés pour chaque observation.

Pour analyser une séquence d’observations non simulée par le modèle, vous pouvez détecter les états cachées avec la fonction Viterbi de la librairie HMM.

viterbi <- viterbi(hmm, observation)

viterbi # affiche les états cachés les plus probables selon la séquence analysée et le modèle.

Si vous n’y voyez pas clair, c’est normal. Pour vous aider :

obsToVal <- function(obs){

if((obs == "A") | (obs=="T")){return(2)}

else {return(4)}

}

stateToVal <- function(st){

if(st == "N"){return (1)}

if(st == "I"){return (3)}

else {return ("NA")}

}

par(mfrow=c(2,1))

plot(sapply(observation, obsToVal),pch=19,ylim=c(0,8),

col=sapply(observation, obsToVal),cex=.5)

legend("topright",c("A/T","G/C"),pch=19,col=c("red","blue"))

plot( sapply(viterbi, stateToVal),type="l",ylim=c(0,5))

Voici ce que vous devez obtenir:

visualsation des résultats du HMM et de la séquence à analyser

Le panel supérieur montre la localisation des G/C ou des A/T le long de la séquence de 100 pb. Quant au panel inférieur, il indique l’analyse du modèle par rapport à la séquence. Le HMM a détecté un ISOCHORE entre les bases 44 et 58.

Pour les probabilités en marche avant et en marche arrière, c’est par ici :

logForwardProb <- forward(hmm, simulation$observation)

exp(logForwardProb)

logBackwardProb <- backward(hmm, simulation$observation)

exp(logBackwardProb)

Enfin, cette librairie permet d’utiliser l’algorithme de Baum-Welch  pour améliorer les performances du modèle et ajuster les paramètres à un test set. Voici comment :

bw = baumWelch(hmm, observation, 10) # 10 iterations

print(bw$hmm)

Ami-e-s en quête des mystères des HMM, j’espère avoir éclairé votre lanterne avec ce bref tutoriel dont j’ai volontairement retiré les détails algorithmiques pour ne pas aggraver votre mal de tête ! Et si le Graal vous hante, ne renoncez pas à vous en emparer en fouillant dans la littérature existante sur les algorithmes de Viterbi, « Forward » , « Backward » , et autres joyeusetés telles que Baum-Welch.

Que la force soit avec vous !

Un grand merci à nallias, Estel, muraveill, Aline Jaccottet et  Hatice Akarsu pour leur relecture.

Julia: le successeur de R ?

$
0
0
julia_logo

Logo du langage Julia.
Source: http://julialang.org/

Actuellement le langage R est incontournable pour qui veut manipuler des données en bioinformatique, en particulier pour l'analyse statistique. Mais un successeur est en passe de s'imposer : Julia, combinant puissance du langage avec les fonctionnalités de R, et comblant les nombreux défauts de ce dernier - mais plus encore ! Voici une présentation de ce tout nouveau langage.

 

À l'origine

La principale raison du succès de R est le système de "packages" qui a permis à chaque labo d'écrire et de rendre facilement récupérable le code qui résout son problème particulier. Aujourd'hui c'est la grande force de R : vous avez un problème ? Quelqu'un l'a déjà résolu, téléchargez le package, écrivez trois lignes et appuyez sur le bouton : c'est fait. Le type "data frame" pour manipuler les tableaux de données est également très apprécié.

Bien sûr il arrive que vous ayez un nouveau problème, ou que vous souhaitiez écrire votre propre programme. Si, à l'usure, on arrive à peu près toujours à ce qu'on veut, on est quand même obligé de remarquer que le langage souffre de nombreux défauts : sa documentation déplorable, sa lenteur, sa syntaxe lourde, la difficulté à débuguer, les fonctions redondantes, etc. Mais énumérons les alternatives :

  • SAS, S+, Matlab, etc. sont payants et donc pas bien adaptés au monde académique.
  • Scipy, librairie de Python, est pas mal mais n'est pas aussi complète et pose souvent des problèmes d'installation.
  • Qui a osé dire Excel ?...
  • Julia ?

Qu'est-ce que Julia ?

Julia est un langage de programmation écrit en C par Jeff Bezanson, Stefan Karpinski, Viral Shah et Alan Edelman. Il est gratuit et open source, inspiré de la syntaxe et des fonctionnalités de Python/Ruby, mais qui vise le même champ d'applications que R : la manipulation de données et les analyses statistiques.

Tout en reprenant des avantages de l'un et de l'autre, il est presque aussi rapide que C lui-même - Python est des dizaines de fois plus lent, R des centaines. En effet, Julia est un langage compilé ce qui lui permet d'être très rapide. Mais pour garder la flexibilité du typage dynamique, il est compilé "Just In Time". Par exemple, pour trier une liste (relativement à C ; exemple tiré de la page d'accueil) :

C Fortran Julia Python Matlab R Javascript
quicksort 1.00 1.65 1.37 69.20 133.46 708.76 4.95

Du langage R, il reprend le type "data frame" pour les données tabulées (le package "DataFrames"), les facilités à produire des graphes ("Cairo", "Winston" ou "Gadfly"), et tout ce qui a trait aux statistiques (comme les "Distributions"). De Python/Ruby, il reprend l'élégance de la syntaxe, la compréhension de liste, la notation vectorielle, les itérateurs, etc. Les développeurs s'attèlent maintenant à reproduire un maximum de packages statistiques, en particulier pour les GLM et les MCMC.

A vrai dire, les développeurs n'entendent pas vraiment remplacer R ou Matlab, mais seulement créer le langage ultime de l'analyse de données. Quelques citations traduites de la page "Pourquoi nous avons créé Julia" :

Nous voulons un langage open-source, avec une licence libre. Nous voulons la vitesse de C avec le dynamisme de Ruby. [...] Nous voulons un langage pour un usage aussi général que Python, aussi facile pour les stats que R, aussi naturel que Perl pour le parsing, aussi puissant que Matlab pour l'algèbre linéaire, aussi bon que le shell à chaîner des programmes. Quelque chose de simple à apprendre mais qui rendra quand même les hackers heureux. Nous le voulons interactif et nous le voulons compilé. [...] Malgré toute sa puissance, nous voulons un langage simple et propre.

Voici un exemple de code en Julia (exercice : trouvez à quoi il sert) :

function hypot(x,y)
  # Commentaire
  x = abs(x)
  y = abs(y)
  if x > y
    r = y/x
    return x*sqrt(1+r*r)
  end
  if y == 0
    return zero(x)
  end
  r = x/y
  return y*sqrt(1+r*r)
end

Ajoutons encore que les développeurs sont très actifs et à l'écoute, j'en veux pour preuve que suite à ma requête concernant l'incompatibilité avec OSX 10.6, j'ai reçu un mail immédiatement me disant que quelqu'un s'y colle... et un installeur compatible deux heures plus tard.

Un exemple d'application en bioinfo

Nous allons créer un MA-plot à partir de données RNA-seq: l'expression de gènes dans deux conditions. On suppose que vous avez réussi à installer Julia et à le lancer en ligne de commande (en principe, en tapant "julia" dans votre console). Ce script a été testé sous OSX Lion.

Tout d'abord voici un fichier de test. Mais tout autre fichier contenant 2 colonnes de nombres séparées par des tabulations fera l'affaire. Ensuite on va installer les packages nécessaires: DataFrames et Winston, comme suit :

Pkg.add("DataFrames")
Pkg.add("Winston")

Il est possible que l'installeur vous demande de faire d'abord de même avec certaines dépendances, comme "Homebrew" ou "Cairo". Une fois les packages installés, ce n'est bien sûr plus nécessaire par la suite. Par contre il faut les importer au début de vos scripts les utilisant :

using DataFrames
using Winston

On va se servir de la fonction "readtable" de DataFrames pour lire et parser le fichier, en indiquant que le séparateur est une tabulation et que la première ligne ne doit pas faire partie des données - mais sera utilisée pour nommer les colonnes, qui seront alors accessibles par ce nom :

df = readtable("genes_expression.txt", separator='\t', header=true)

Pour voir ce qu'il y a dedans, on visionne juste les premières lignes avec "head" :

head(df)

qui devrait afficher quelque chose comme ça :

6x3 DataFrame:
        counts.KO.1 counts.WT.1  GeneName
[1,]             16          12  "Gm6152"
[2,]           2382        1417 "Gm11605"
[3,]              0           2  "Abca12"
[4,]           7068        6135   "Degs1"
[5,]              5           2  "Nckap5"
[6,]            243         161 "Ankrd39"

Ensuite on va produire un vecteur de ratios et un vecteur de moyennes (géométriques) à partir des colonnes :

ratios = df[1]./df[2]
means = (df[1].*df[2]).^(1/2)

Notez le "." devant les opérations, pour indiquer que l'opération se fait terme par terme entre les vecteurs (contrairement au simple * par exemple qui serait un produit scalaire).

Finalement on va initialiser le graphe, y coller nos points, et rajouter quelques options comme les labels des axes, le titre et l'intervalle couvert par chacun des axes:

p = FramedPlot()
add(p, Points( log10(means), log2(ratios), "type", "dot" ))
setattr(p, "title", "MA-plot")
setattr(p, "xlabel", "\\Means")
setattr(p, "ylabel", "\\Ratios")
setattr(p, "aspect_ratio", 0.75 )
setattr(p.x1, "range", (-1,6) )
setattr(p.y1, "range", (-6,6) )
file(p, "maplot.png")

Et voilà le fabuleux résultat :

maplot

Conclusion

Pour le moment, Julia est encore en développement, même s'il est utilisable - et utilisé - et se rapproche de sa première sortie officielle. On peut suivre son évolution sur son blog et ses forums , ou simplement le télécharger et essayer !

Qu'à terme il s'impose dépendra du nombre de développeurs s'impliquant dans la création de packages statistiques, de la facilité de codage/débugage et de la documentation.

Et vous, déjà convaincus ?

Site officiel: http://julialang.org/

$ julia
               _
   _       _ _(_)_     |  A fresh approach to technical computing
  (_)     | (_) (_)    |  Documentation: http://docs.julialang.org
   _ _   _| |_  __ _   |  Type "help()" to list help topics
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 0.2.0-prerelease+4033
 _/ |\__'_|_|_|\__'_|  |  Commit 808f3ad 2013-10-13 19:33:09 UTC
|__/                   |  x86_64-apple-darwin12.5.0

>

 Merci à bilouweb et Yoann M. pour leur relecture.

Les mélanges gaussiens

$
0
0

La plupart des mesures que l'on obtient des expériences en biologie suivent approximativement une distribution dite "normale", ou "gaussienne", dont la densité a la forme d'une cloche, symétrique avec un unique sommet au milieu. C'est aussi l'hypothèse d'un grand nombre d'outils d'analyse statistique. Mais que faire quand on observe deux sommets ou plus ? Le plus probable, c'est qu'on observe alors un mélange de plusieurs composantes normales, qu'on voudrait séparer.  Une fois décomposées, on peut travailler sur chaque composante séparément. Le tout s'appelle un "modèle de mélange gaussien", ou "gaussian mixture model" en anglais pour la littérature.

 

Les exemples d'utilisation sont fréquents. D'ailleurs, si j'ai choisi d'écrire sur ce sujet, c'est que j'ai reçu il y a quelques jours le message suivant :

Salut,

bimodal_AJ'ai un service à te demander! J'ai une série de 100 points qui représentent des distances d'expression de gène entre deux cellules après division. J'obtiens une distribution qui n'est pas une gaussienne, et j'aimerais savoir s'il y a moyen de trouver une double gaussienne qui pourrait modéliser de façon adéquate ma distribution. Le but étant de fixer un seuil pour les paires de cellules avec une distance élevée. Est-ce que tu pourrais me montrer comment faire ça? C'est assez urgent…

A.

Pour déterminer quel individu appartient à quelle composante, la méthode usuelle est d'utiliser un algorithme EM (pour Expectation-Maximization). Le résultat de l'algorithme est un ensemble de paires (moyenne, variance) - une pour chaque composante normale - , et une probabilité "a posteriori" pour chaque observation d'appartenir à telle ou telle composante.

Sans entrer dans les détails, l'algorithme fonctionne en alternant deux phases : on attribue - arbitrairement, la première fois - chaque point à l'une des composantes (phase E), dont on peut alors estimer la densité de probabilité (phase M). Puis on considère fixes les densités, et on redistribue au mieux les points entre elles (phase E), par un critère de maximum de vraisemblance. Et on recommence : on recalcule les densités (phase M), etc. On peut montrer que chaque itération améliore la vraisemblance, et donc qu'on converge toujours vers un maximum local.

On peut voir la méthode comme une forme d'apprentissage non-supervisé (clustering).

En pratique

Voici comment faire en utilisant le langage R. Pour ne pas s'encombrer avec l'import de données réelles, générons des données aléatoires, à partir de deux distributions normales, mélangées dans un même vecteur :

# Deux populations normales mélangées: 
# 1000 points de N(2,1) et 400 points de N(6,1.5).

set.seed(100)
data = sample(c(rnorm(1000,mean=2,sd=1),rnorm(400,mean=6,sd=1.5)))

C'est-à-dire, ça :

Linear distribution of test data

Mais comme les points sont difficilement visibles, on préfère en principe dessiner un histogramme ou une courbe de densité, comme ça :

Histogramme et courbe de densité (non-paramétrique) des données.

On devine qu'il doit y avoir deux composantes (deux "bosses"). Il sera important de préciser à l'algorithme combien de composantes il y a, sans quoi le résultat sera n'importe quoi.

Ensuite il existe de nombreux packages à même de faire le travail. Voilà un exemple avec la librairie R "mixtools" :

library(mixtools)
mixmodel = normalmixEM(data, k=2)  # k=2 composantes

mu = mixmodel$mu                   # moyennes ~ [2,6]
sigma = mixmodel$sigma             # écarts-types ~ [1,1.5]
post = mixmodel$posterior          # probabilités a posteriori
lambda = mixmodel$lambda           # proportions ~ [0.72,0.28]

# Graphique par defaut
plot(mixmodel, which=2)

# Graphique perso (ci-dessous)
plot(density(data), col='red', ylim=c(0,0.3), lwd=2)
x = seq(min(data),max(data),length=1000)
y1 = lambda[1]*dnorm(x,mu[1],sigma[1])
y2 = lambda[2]*dnorm(x,mu[2],sigma[2])
lines(x,y1,col='blue',lwd=2)
lines(x,y2,col='green',lwd=2)

En rouge la densité jointe (non-paramétrique) ; en bleu/vert les courbes de densité des deux sous-populations, d'après les estimations de mixtools.

En rouge la densité jointe (non-paramétrique) ; en bleu/vert les courbes de densité des deux composantes normales, d'après les estimations de mixtools.

Et voilà, on a séparé les données en deux composantes normales. On peut ajouter à volonté au graphe la position des moyennes, les écarts-types, les points colorés selon leur appartenance à l'une ou l'autre composante, etc.

Alternativement, en utilisant la librairie "mclust" et en utilisant d'autres possibilités graphiques :

library(mclust)
mixmodel = Mclust(data, G=2, model="V")      # G=2 composantes, "V" pour variances non égales

mu = mixmodel$par$mean                       # moyennes ~ [2,6]
sigma = sqrt(mixmodel$par$variance$sigmasq)  # écarts-types ~ [1,1.5]

# Graphique
mclust1Dplot(data, parameters=mixmodel$parameters, z=mixmodel$z, what="density", col='red', new=TRUE)
hist(data, freq=FALSE, breaks=100, add=TRUE)
abline(v=mu, col='blue')
abline(v=c(mu+sigma,mu-sigma), col='blue', lty=2)

On a ajouté à l'histogramme des données la densité jointe (non-paramétrique, en rouge), la moyenne (bleu plein) et la variance (bleu trait-tillé) de chaque sous-population.

On a ajouté à l'histogramme des données la densité jointe (non-paramétrique, en rouge), la moyenne (bleu plein) et la variance (bleu trait-tillé) de chaque composante normale.

Généralisation

Notez que si dans notre exemple nous avons deux composantes normales à une dimension, on peut facilement généraliser à X composantes en N dimensions (comme dans l'exemple ci-dessous) mais aussi avec d'autres sortes de distributions de probabilité. Dans le cas de deux dimensions, la ligne de points de notre toute première figure ci-dessus devient un nuage de points dans un plan.

# On génère des données de test
set.seed(1)
pop1 = mvrnorm(n=1000, c(2,2), matrix(c(1,0,0,1),2) )
pop2 = mvrnorm(n=1000, c(7,7), matrix(c(1.5,0,0,9),2) )
pop3 = mvrnorm(n=1000, c(1,8), matrix(c(10,0,0,5),2) )
data = rbind(pop1,pop2,pop3)

# La fonction mvnormalmixEM est l'équivalent multivarié.
# On précise qu'on veut k=3 composantes.
mvmixmodel = mvnormalmixEM(data, k=3)

# Graphique
par(mfrow=c(1,2))
plot(data,pch=4)
plot(mvmixmodel, which=2, pch=4)

A gauche les données initiales ; on devine trois sous-groupes. A droite, la décomposition par mixtools.

A gauche les données initiales ; on devine trois sous-groupes. A droite, la décomposition par mixtools, où on a coloré les points selon la composante à laquelle ils appartiennent le plus probablement.

Remarque : le calcul a tout de même pris une bonne dizaine de minutes sur mon vieux Mac.

Il existe dans à peu près tous les langages des librairies de ce genre. Un exemple notable est le tout prometteur langage Julia, encore en développement, qui possède une librairie "MixtureModels", par exemple :

using MixtureModels
using Distributions

obs = vcat( rand(Normal(2,1),1000), rand(Normal(6,1.5),400) )
r = fit_fmm(Normal, obs, 3, fmm_em())

r.mixture.components    # liste des composantes normales
r.mixture.pi            # proportions
r.Q                     # probabilités a posteriori

Parfois c'est plus compliqué : l'algorithme EM peut être implémenté dans sa forme générale (il a beaucoup d'autres usages), et c'est à vous de spécifier à quoi l'appliquer. Mais c'est un autre sujet qui mérite un livre à lui tout seul.

Merci à ook4mi, frvallee, Bu, ZaZo0o et Yoann M. pour leur relecture.

Toutes les figures sont de l'auteur de l'article.


Soirée BED & FASTA !

$
0
0
bed&fasta

bed & fasta

Après la petite histoire de l’analyse des séquences d’ADN, voici un tutoriel pour apprendre quelques trucs et astuces dans ce domaine.

Biologiste en mal de connaissances de programmation ou pro de R, vous trouverez ici de quoi vous amuser avec un fichier Fasta ou un Bed.

Nous allons voir comment faire un alignement multiple de séquences (voir ici pour un état de l'art des logiciels existants), produire un logo, analyser les motifs enrichis dans des pics de CHiP, et plus encore. Vous aurez la possibilité de travailler soit avec des interfaces web, soit avec des lignes de codes R et différentes librairies Bioconductor (que nous vous avions déjà présenté).

Vous pouvez télécharger l'archive contenant les fichiers nécessaire pour le tutoriel ici.

Vous avez dit Fasta?

Un Fasta est un fichier texte contenant une ou plusieurs séquences d’ADN, ARN ou acides aminés. Chaque séquence se présente sous la forme suivante :

>sequence_inconnue
GCACTGGGATGGGTTTTCCTTCTTGAGGTGTCAAGCTTCGGCTCCTTTGCCAAGATGGCGGCCCTGGCTC
CCATGGAGGTAGCGATTGTGCAGCACCTCTGGCCCAGGGGCCGTCTTACAGCCACGGCCTACCCGCGCAA
AGCGGAAGACACATTGGGCAGCCTTTACATTTTCCATCCAAGAAAGGGCGCCTCGGTTTTGAAGCTAAAG
AGCACCTCTGCCAAAATGGTGACCGTGTGGCGTCACTGCTCTTTACCAAGATGGCGGCGAGAGACTTCCG
GCACGCGCTTCCCCAATCAGAGATCTCCAAGAGGTCAGGCAGAGGAGACCGCCCTCGGAGTCCAAGTGCT
GCGCGATCGGCTGGGGGAGAGAATGGGGTGGAGCCACGGCAGCCTCAGCTCCGATCAGAAGTGCCAAGCG
CTGGCACCTGTGGGGGAGCAAAAGTTACTTCCTCGCACCCCAAAGCAACACCATAACACCTTACTCCCCA
ACCCCCAGGCCCCTAAACCGTAGCGTGCGGCGCGAGGTAGGGGGCACGGTCCCAGAGTCGCCCCAGTCTG
CCAAAGGGGCGGGCAGCTTAGGGGCAGGTCCGCGCGGCGGCTGCACTGTCACCCCCACGCCCCTCTCCTG
TCCTAGGGGGACCGGCCACGTGTTTCTCTTGGAGACCCGGGGCTCGCCCTGGGAACAGCTGGAGGGAGCT
AAACGCTGACGTTGTAAAGATGCGTGTTTTGTTTTATTTGGAGGGGCAGAGGGGTCCCTGGAACTCAGAA
AGAAGGCAGAGCGAGGCACTGAGCCTGGAGCAGCAAATGTCAAGATTTGGGGGAGGGGCCTCCGCGGGGA
GCTTGGATGCTGGCCCCGAAGGGGGTGGAAGGAGAGGTCAGGAGTTTGGGGTAAGAGGAGGGCGGACTTC
GCCAGCAACTTACTATTCCGTCTGCAACTTGCTTCTAGGCCTGCACACACCCCCTCCCGGCCCCGCAAGG
CTTCCTTAATCACAATTTTTTTCTTTTTTCTTTTTTTTTCTTTTTTTTTTTTAAGTGCAAAGAAACCCAG
CTCGCTAAGAGGGTTTTGCATTCGCCGTGCAACTTCCTCCGAATGTGAGCGCGCTGGCAGGCAGGGAGGG
AGCGGTGGGGGGGGGGGGGGGAGGTTTGAACTTGGCAGGCGGCGCCTCCTGCTGCCGCCGCCGCCGCTGC
TGCCGCCTCCTCTCAGACTCGGGGAAGAGGGTGGGGGACGATCGGGGCGCGGGGGAGGGTGGGTTCTGCT
TTGCAACTTCTCCCGGTGGCGAGCGAGCGCGCGCGCAGCGGCGGCGGCGGCAGCGGCGGCGGCTGCAGAC
GGGGCCGCCCAGACGATGCGGGGGTGGGGGACCTGGCAGCACGCGAGTCCCCCCCCGGGCTCACAGTATG
TATGCGCTGACCCTCTGCTCTGCCCTCCCGTCCCCTCCCCAAGCCTCCCCCCACCCCGAGGGCGTGTCTG
CCGTCCTGTCCCGAGAGCGGCCAGGGCTCTGCGGCACCGTTTCCGTGCCATCCCGTAGCCCCTCTGCTAG
AGTGACACACTTCGCGCAACTCGGCAGTTGGCGGACGCGGACCACCCCTGCGGCTCTGCCGGCTGGCTGT

Le signe ‘>’ représente la ligne avec le nom de la séquence. Il contient en général un identifiant ou la localisation génomique de la séquence. Ce format est très utile car il est compatible avec beaucoup d’outils disponibles sur internet (interfaces web) et de nombreuses librairies de fonctions, notamment adaptées à R, Python et Perl. Vous pouvez télécharger des séquences en format Fasta depuis GeneBank et UCSC, entre autres.

Le format Bed est aussi un fichier texte qui contient plusieurs localisations sur le génome. A la base, un Bed contient trois colonnes : le nom du chromosome, la position de départ de la région et la position de fin de la région.  Dans certains cas le fichier Bed contient un nom pour chaque région, un score (i.e. une quantification) ainsi que le brin positif ou négatif pour l’orientation génomique de la région.

chr22 1000 5000 cloneA 960 + 
chr22 2000 6000 cloneB 900 -

Vous pouvez trouver d’autres informations sur les formats de données génomiques sur le site UCSC.

Donc maintenant que vous avez les bases, passons aux choses sérieuses !

Identification de la séquence suspecte

Allez sur le site de BLAST, et cliquez sur "nucleotide blast". Copiez et collez La séquence Fasta ci-dessus pour identifier la séquence sans la ligne avec le signe ’>’.

Alors? Vous avez trouvé?

Effectivement il s’agit d’une partie de la séquence du récepteur aux glucocorticoïdes.

Cette hormone est un outil thérapeutique important dans le cadre des maladies auto-inflammatoires et dans l’inhibition de la croissance cellulaire de nombreux cancers. Les glucocorticoïdes sont synthétisés et sécrétés par le système nerveux central  et les glandes cortico-surrénales, suivant un rythme circadien. Ils sont impliqués dans la régulation de nombreux processus biologiques, entre autres la gluconéogenèse, la glycolyse, le métabolisme des acides gras et la réponse immunitaire et inflammatoire.

Les glucocorticoïdes se fixent aux récepteurs des glucocorticoïdes (GR) dans le cytoplasme de la cellule. Le complexe récepteur-ligand formé pénètre ensuite dans le noyau cellulaire où il se fixe à de nombreux éléments de réponse aux glucocorticoïdes dans la région du promoteur des gènes-cibles. Le récepteur, ainsi fixé à la molécule d'ADN, interagit avec les facteurs de transcription basiques, provoquant une augmentation de l'expression de gènes-cibles spécifiques. Ce processus est appelé transactivation. Le mécanisme inverse est appelé transrépression. Le récepteur hormonal activé interagit avec des facteurs de transcription spécifiques et prévient la transcription des gènes-cibles. Les glucocorticoïdes ont une action pléiotropique : ils activent certains gènes (transactivation) et en répriment d’autres (transrépression) en fonction de la structure des promoteurs ciblés et de leur action coordonnée avec des cofacteurs.

Dans un autre contexte, certains biologistes utilisent blastn pour savoir si un insert s’est correctement  introduit dans un plasmide lors d’un clonage. L’on effectue cette vérification par électrophorèse sur gel d’agarose, puis amplification PCR et enfin par séquençage. Le Fasta issu du séquençage peut ensuite être analysé avec BLAST pour retrouver la position exacte de l’insert sur le plasmide produit.

Alignement multiple de sites GR et Logo

Rendez-vous maintenant sur le site de Clustal Omega pour tenter un alignement multiple de 17 séquences issues de mRNA de GR de différentes espèces et connaître la distance phylogénétique entre elles. Pour cela, il vous suffit de charger le fichier Fasta "GR_phylo.fasta" , de préciser qu’il s’agit d’ADN et de spécifier le format de sortie en Fasta. Vous verrez rapidement le résultat de l’alignement (ci-dessous, en format Clustal pour un petit intervalle).

alignment_17mRNA GR

Alignement au format Clustal

En cliquant sur « phylogenetic tree » vous devriez avoir l’image suivante :

pylogrammeGR17mRNA

Arbre Phylogénétique, dit aussi Cladogramme ou encore Dendrogramme

L’arbre permet des savoir quelles sont les séquences les plus proches. En utilisant ce type d’analyse, on peut comparer différentes espèces qui partagent certains gènes et ainsi retracer leur « histoire évolutive ». Par ailleurs, si vous désirez vous attarder un peu plus sur la construction de votre arbre et souhaitez une plus forte robustesse, je vous invite alors à consulter notre article sur la construction des arbres phylogénétiques.

On peut aussi utiliser l’alignement pour créer un logo avec webLogo pour savoir quelles sont les bases « conservées ». Voici le résultat pour la région 3500-3700 de l’alignement :

logo_mRNA17GR_1

Logo généré avec WebLogo

Dans cette analyse, nous pouvons observer 3 groupes de séquences GR d’espèces proches, au sens de la  distance génétique. Le logo nous permet de constater la conservation de certaines régions de l’alignement multiple. Le but de cet exemple est de comprendre comment fonctionnent ces différentes interfaces web.

Analyse de motifs enrichis dans les pics de CHiP-seq du récepteur aux glucocorticoïdes (GR)

En général, lors d’analyses sur des données CHiP, nous avons plus de 1000 sites. L’analyse des séquences est alors plus compliquée car les alignements multiples sont trop coûteux en matière de calculs. D’autres algorithmes ont vu le jour pour répondre plus spécifiquement à cette question. Il s’agit d’algorithmes de recherche de motifs de novo  (i.e. MEME) ou encore de scans de séquences à partir de PWM (i.e. FIMO).

Nous allons procéder à une analyse de donnés de 1000 pics de CHiP-seq de GR provenant d’un papier de G.Hager. Téléchargez le fichier  "GSE46047_GR_1000peaks_mouse_liver_mm9.txt" contenant la localisation des pics (format .bed). (voir archive)

Allez sur UCSC et sélectionnez le génome de souris mm9. Cliquez ensuite sur le bouton « add custom track » et chargez le fichier de localisation des pics. Allez ensuite sur l’onglet « Tables » pour convertir le fichier en Fasta.

Sauvez ensuite le fichier Fasta sous "GR1000.fa", puis allez sur MEME-CHiP pour soumettre le Fasta à la recherche de motifs de novo. Utilisez les paramètres suivants :

Parameters_MEME

Paramètres pour MEME-CHiP

L’exécution du programme MEME peut être assez longue, mais vous recevrez par e-mail le lien qui vous fera accéder à votre résultat. MEME-CHIP vous fournira les motifs enrichis et leur distribution par rapport au centre des pics. La documentation de MEME-CHIP contient beaucoup d’informations permettant de comprendre les résultats de l’analyse. Nous allons voir maintenant comment faire à peu près la même analyse en utilisant R.

Un peu de code R pour l’analyse de séquences ADN

Dans un premier temps il faut installer différents packages R:

Le package BSgenome.Mmusculus.UCSC.mm9 fait 750MB, donc l’installation risque de prendre un peu de temps! Le café s’impose à ce stade…

source("http://bioconductor.org/biocLite.R") 
biocLite(“rGADEM”,” MotIV”,” seqLogo”,” Biostrings”,” motifStack”,” BSgenome.Mmusculus.UCSC.mm9”,"GenomicRanges") 
install.packages(“muscle”)

Puis chargez les bibliothèques:

library(rGADEM)
library(MotIV)
library(seqLogo)
library(Biostrings)
library(motifStack)
library(muscle)
library(GenomicRanges)
library(BSgenome.Mmusculus.UCSC.mm9)

Il faut ensuite charger les données des pics de CHiP-seq de GR:

BED=read.table("GSE46047_GR_peaks_mouse_liver_mm9.txt",header=FALSE,sep="\t")
gr=GRanges(seqnames=as.factor(BED[,1]), ranges=IRanges(start=as.numeric(BED[,2]), end=as.numeric(BED[,3])))

Les commandes suivantes permettent de créer 2 fichiers Fasta(20 sites et 1000 sites) à partir des pics (fichier BED, si jamais vous n'avez pas de fichier FASTA déjà à disposition).

gr_fasta_1000=getSeq(Mmusculus, gr[1:1000])
names(gr_fasta_1000)=paste(as.character(BED[1:1000,1]),"_",as.character(BED[1:1000,2]),"_",as.character(BED[1:1000,3]),sep="")
writeXStringSet(gr_fasta_1000[1:1000], file="GR1000.fa")
gr_fasta_20=getSeq(Mmusculus, gr[1:20])
names(gr_fasta_20)=paste(as.character(BED[1:20,1]),"_",as.character(BED[1:20,2]),"_",as.character(BED[1:20,3]),sep="")
writeXStringSet(gr_fasta_20[1:20], file="GR20.fa")

Nous allons faire un alignement multiple sur le fichier Fasta contenant 17 séquences de mRNA d’espèces différentes de GR grâce à “muscle”.

muscle("GR_phylo.fasta" , out = "aln_GRphylo.fa")

En utilisant l’alignement,  nous pouvons faire une analyse de la distance en terme de similarité entre les séquences.

GR = readDNAStringSet("aln_GRphylo.fa", "fasta")
get_names_phylo=function(name){
return(paste(unlist(strsplit(name," "))[2:6],sep=" ",collapse=" "))
}
names(GR)=sapply(names(GR),get_names_phylo)
masim = sapply(names(GR), function(x) sapply(names(GR), function(y) pid(PairwiseAlignedXStringSet(GR[[x]], GR[[y]]), type="PID2")))
masim
madist = 1-(masim/100)
madist

Le résultat de cette analyse peut être représenté sous forme de heatmap.

heatmap(madist,mar=c(20,20),sym=T)

heatmap17mRNA_GR

Heatmap représentant une matrice de distance

Un dendrogramme peut être obtenu comme suit:

par(mar=c(10,10,10,10))
disttree = hclust(as.dist(madist))
plot(as.dendrogram(disttree), edgePar=list(col=3, lwd=4), horiz=T,mar=c(10,10))

phylogramme_17mRNA_GR

Représentation des distances avec un dendrogramme

Le dendrogramme ainsi que la heatmap nous montrent que les séquences des 17 mRNA de GR ont une similarité relativement importante. On peut discerner 3 groupes, avec respectivement des séquences provenant d’espèces de poissons, de mammifère et un troisième groupe qui comporte des séquences mRNA de GR provenant de rat, de poule et de grenouille.

Le logo de l’alignement est obtenu comme suit.

par(mar=c(4,4,4,4),mfrow=c(2,1))
pfm=consensusMatrix(GR)[1:4,3500:3600]
motif = new("pfm", mat=sweep(pfm,2,colSums(pfm),FUN="/"), name="GR mRNA alignment logo 3500:3600")
plot(motif)
pfm=consensusMatrix(GR)[1:4,3600:3700]
motif = new("pfm", mat=sweep(pfm,2,colSums(pfm),FUN="/"), name="GR mRNA alignment logo 3600:3700")
plot(motif)

logo_17_mrna_gr

Logo produit par motifStack

Le but de cet exemple est simplement de comprendre comment représenter un logo avec le package motifStack à partir d’un objet DNAStringSet provenant du package Biostrings.

Une analyse plus réaliste des données CHiP-seq de GR

L’analyse de séquences  de CHiP GR implique, la recherche de motifs de novo. Cette analyse peut se faire sur un nombre plus important de sites. De plus elle permet de trouver des motifs de cofacteurs potentiels  proches du site de liaison de la protéine GR. Cette analyse est possible grâce au packages rGADEM et MotIV. L’analyse est assez longue, donc vous pouvez lancer les commandes suivantes et sortir boire une bière avec des amis !

###rGADEM###
BED=read.table("GSE46047_GR_peaks_mouse_liver_mm9.txt",header=FALSE,sep="\t")
BED=data.frame(chr=as.factor(BED[,1]),start=as.numeric(BED[,2]),end=as.numeric(BED[,3]))
rgBED=IRanges(start=BED[1:500,2],end=BED[1:500,3])
Sequences=RangedData(rgBED,space=BED[1:500,1])
gadem=GADEM(Sequences,verbose=1,genome=Mmusculus)
motifs = getPWM(gadem)
print(motifs)

###MotIV###
analysis.jaspar = motifMatch(motifs)
summary(analysis.jaspar)
plot(analysis.jaspar, ncol = 2, top = 10, rev = FALSE, main = "Logo GR CHiP 500 sites", bysim = TRUE)

Vous devriez obtenir l’image ci-dessous. Le motif le plus enrichi est NR3C1, qui correspond aux sites de liaisons de la protéine GR. Nous trouvons d’autres motifs comme ESR2, motif de liaison des récepteurs à œstrogènes, et STAT3 et PPARG:RXR qui correspondent aux sites de liaison de cofacteurs connus de GR.

GR_rGADEM_motIV

Analyse des 500 sites GR à partir de rGADEM et MotIV

L’analyse de sites GR de CHiP montre un lien avec Stat3. De ce fait, nous pouvons analyser la distribution de sites de Stat3 et de GR autour du centre des pics de CHiP-seq grâce aux commandes suivantes :

f.NR3C1= setFilter( tfname="NR3C1", top=5, evalueMax=10^-5)
f.Stat3 = setFilter (tfname="Stat3", top=5)
f.NR3C1.Stat3 = f.NR3C1 | f.Stat3
NR3C1.filter = filter(analysis.jaspar, f.NR3C1.Stat3 , exact=FALSE, verbose=TRUE)
NR3C1.filter.combine = combineMotifs(NR3C1.filter, c(f.NR3C1, f.Stat3), exact=FALSE, name=c("NR3C1", "Stat3"), verbose=TRUE)
plot(NR3C1.filter.combine ,gadem,ncol=2, type="distribution", correction=TRUE, group=FALSE, bysim=TRUE, strand=FALSE, sort=TRUE, main="Distribution of NR3C1")

Distribution_NR3C1_Stat3

Distributions des motifs NR3C1 et Stat3 autour du centre des pics GR

Enfin nous pouvons voir combien de sites de liaison de GR chevauchent des sites de liaison de Stat3, et analyser ainsi la distance relative entre GR et Stat3.

plot(NR3C1.filter.combine ,gadem,type="distance", correction=TRUE, group=TRUE, bysim=TRUE, main="Distance between NR3C1 and CEBPA", strand=FALSE, xlim=c(-100,100), bw=8)

distanceNR3C1_Stat3

Distance relative entre NR3C1 et Stat3 sur les 47 sites GR contenant les deux motifs

C’est tout pour aujourd’hui ! Grâce à ces quelques lignes de codes et ces outils disponibles sur le web, vous pourrez analyser de manière spécifique et systématique vos données CHiP-seq et vos séquences.

Encore une petite remarque :

L’algorithme de BLAST est en général appelé en ligne de commande depuis R, à condition d’avoir l’outil installé et accessible depuis le terminal. Je n’ai pas trouvé de package pouvant l’exécuter autrement, donc n’hésitez pas à faire des suggestions (BoSSA n’a pas fonctionné). Vous pouvez invoquer BLAST depuis R avec les commandes ci-dessous (non-testées).

myPipe = pipe( "blastall -p blastp -i text.fasta -d data.fasta" )
results = read.table( myPipe )
colnames( blastResults ) = c( "QueryID",  "SubjectID", "Perc.Ident","Alignment.Length", "Mismatches", "Gap.Openings", "Q.start", "Q.end","S.start", "S.end", "E", "Bits")

References :

1) Chromatin accessibility pre-determines glucocorticoid receptor binding patterns, Sam John, Peter J Sabo, Robert E Thurman, Myong-Hee Sung, Simon C Biddie, Thomas A Johnson, Gordon L Hager & John A Stamatoyannopoulos ,NATURE GENETICS, 2010

2) MEME-ChIP: motif analysis of large DNA datasets , Machanick P, Bailey TL.,Bioinformatics. 2011

3) GADEM: a genetic algorithm guided formation of spaced dyads coupled with an EM algorithm for motif discovery, Li L, J Comput Biol. 2009

4) An integrated pipeline for the genome-wide analysis of transcription factor binding sites from ChIP-Seq ,E Mercier, A Droit, L Li, G Robertson, X Zhang, R Gottardo (2011). PLoS ONE. 6(2): e16432. doi:10.1371/journal.pone.0016432

5) STAMP: a web tool for exploring DNA-binding motif similarities, S. Mahony, P.V. Benos, Nucl Acids Res, (2007) 35:W253-258

6) DNA familial binding profiles made easy: comparison of various motif alignment and clustering strategies, S Mahony, PE Auron, PV Benos, PLoS Computational Biology (2007) 3(3):e61

7) seqLogo: Sequence logos for DNA sequence alignments. R package Bioconductor

8) MotIV: Motif Identification and Validation. Eloi Mercier and Raphael Gottardo (2010). R package Bioconductor.

9) motifStack: Plot stacked logos for single or multiple DNA, RNA and amino acid sequence, Jianhong Ou, Michael Brodsky, Scot Wolfe and Lihua Julie Zhu ,R package Bioconductor.

10) Wikipedia, article glucocorticoïde

Un grand merci aux relecteurs: Wocka, Yoann M, nallias, Julien Delafontaine et Aline Jaccottet.
Les images utilisées pour les besoins de l'article sont toutes de l'auteur et sont disponibles en CC-by-SA 3.0.

Guide de démarrage pour ggplot2, un package graphique pour R

$
0
0
ggplot2

Hadley Wickham signed his book "ggplot2" in my iPad | H. Okumura

Le traitement et l’analyse de données sont une part importante des tâches demandées à un bioinformaticien. L’utilisation de R facilite grandement la manipulation des données et permet également leur représentation de multiples façons. Malgré le potentiel de R, ce dernier est souvent sous-exploité à cause d’une syntaxe parfois trop complexe. Je vais vous présenter aujourd’hui un package R, « ggplot2 », permettant la production de graphiques très élaborés en peu de temps.

Un peu d’histoire

Ggplot2 est un "package" R créé par Hadley Wickham en 2005. Pour ceux qui ne le connaîtraient pas, Hadley Wickham est un peu le dieu de R depuis quelques années. Il est à la base de "packages" facilitant la vie des utilisateurs de R tels que ‘plyr’, ‘reshape2’, ‘lubridate’, ‘stringr’, ‘tesJhat’. Vous pouvez en apprendre plus sur Hadley Wickham ici. Ggplot2 s’inspire en partie des travaux de Leland Wilkinson décrits dans « Grammar of Graphics - a general scheme for data visualization which breaks up graphs into semantic components such as scales and layers ». Sans rentrer dans les détails de cet ouvrage, on peut résumer le principe de base qui est de séparer les données de la représentation graphique et de diviser la représentation en éléments de base tels que les courbes, les axes ou les labels. Ça peut sembler un peu confus au début mais cette façon de faire présente de nombreux avantages. Une fois les données mises en forme, il ne sera plus nécessaire d’y revenir. Il est alors possible de les représenter sous forme de plot, de courbes, de barplot, et de bien d’autres façons en utilisant juste le bon outil. Rentrons maintenant un peu dans le détail.

Installation

L’installation des "packages" R est en général très simple. Ggplot2 ne fait pas exception à la règle. Dans votre console R, tapez simplement
install.packages('ggplot2')
et le package devrait s’installer tout seul. En cas de problème je vous renvoie à la doc de ggplot2.

Principes de base de ggplot2

Dans ggplot2, un "plot" se décompose de la manière suivante Plot <- data + Aesthetics + Geometry Si le premier terme n’est pas vraiment mystérieux, les deux autres sont déjà un peu moins communs. Pour les données, celles-ci doivent prendre une structure particulière mais nous y reviendrons un peu plus tard. Sous le terme "Aesthetics" on va retrouver tout ce qui concerne les couleurs, les tailles, les formes, les labels mais aussi quelles données doivent être considérées en x et en y par exemple. "Geometry" va regrouper les options concernant les types de graphique (plot, histogramme, heatmap, boxplot, etc…). Ces différents éléments vont être combinés dans un objet à partir duquel on va pouvoir faire les représentations dans une fenêtre graphique ou dans un fichier. Et lorsque l’on voudra changer la couleur ou la forme ou le type de graphique il suffira de modifier ce paramètre dans l’objet sans avoir besoin de toucher aux autres puis de faire à nouveau la représentation graphique. Ça peut paraître un peu confus au début mais quelques exemples vont vite éclaircir le tout.

Avant toutes choses les données

Les données sont bien sûr la base. La structure des données peut être un peu déroutante au début mais on s’y fait assez vite. Il faut mettre les données dans une table de telle sorte qu’une des colonnes de la table contiendra les données sur x, une autre les données sur y et encore une autre les différentes conditions. Prenons l’exemple d’une cinétique. On imagine qu’on a 13 points, un toutes les 5 minutes pendant 1 heure et qu’on compare 2 conditions pour une réaction enzymatique. Le résultat qu’on observe sera une densité optique (D.O.) à 280nm. On aura la structure suivante :

Time Abs Cond
0 0.1 Without enzyme
5 0.09 Without enzyme
10 0.12 Without enzyme
15 0.13 Without enzyme
20 0.11 Without enzyme
25 0.07 Without enzyme
30 0.09 Without enzyme
35 0.1 Without enzyme
40 0.11 Without enzyme
45 0.11 Without enzyme
50 0.13 Without enzyme
55 0.1 Without enzyme
60 0.07 Without enzyme
0 0.08 With enzyme
5 0.12 With enzyme
10 0.18 With enzyme
15 0.29 With enzyme
20 0.46 With enzyme
25 0.89 With enzyme
30 1.12 With enzyme
35 1.34 With enzyme
40 1.45 With enzyme
45 1.54 With enzyme
50 1.59 With enzyme
55 1.62 With enzyme
60 1.61 With enzyme

Cette structure ressemble à ce qu'on peut trouver dans une table d'une base de données. Il y a un peu de redondance d'information mais c'est très lisible. J’ai mis ce tableau dans un petit fichier que l'on va charger dans R.

data_kinetic_01 <- read.delim("data_kinetic_01.txt")

Premier "plot"

Commençons par faire un plot simple, la DO en fonction du temps avec des couleurs et des formes différentes pour les 2 échantillons.

p <- ggplot(data=data_kinetic_01, aes(x=Time, y=Abs, colour=Cond, shape=Cond))

Décryptons un peu cette instruction. On créé un objet ggplot et on lui indique quelles données il faut utiliser (data=data_kinetic_01). Puis les paramètres de "Aesthetics" sont définis (aes(x=Time, y=Abs, colour=Cond, shape=Cond)). Dans cette commande, il est indiqué d’utiliser la colonne Time pour x et la colonne Abs pour y. Pour les couleurs et les formes la colonne Cond sera utilisée. Ce paramètre peut être plus difficile à comprendre car dans la colonne Cond, il n'y a aucune indication de couleur ou de forme. En réalité ggplot2 définit lui même les couleurs et formes à utiliser. La colonne Cond va servir à indiquer le groupe auquel chaque point appartient. On voit que dans notre cas, cette colonne contient 2 valeurs distinctes "without enzyme" et "with enzyme". Ggplot2 va donc définir 2 couleurs et 2 formes de points. La partie "geometry" est ensuite ajoutée par la commande ci-dessous. Le but étant de dessiner des points, on va donc logiquement utiliser la commande geom_poinet simplement l'ajouter à l’objet qui vient d'être créé.

p <- p + geom_point(size=4)

Et voilà notre objet est prêt. Il ne reste plus qu’à le représenter dans une fenêtre graphique classique. print(p) Ce qui donne le "plot" suivant :
kinetics_plot_01

Le plot correspond à ce qu’on attend. On voit bien les 2 séries de données avec des formes et couleurs différentes. A partir de là, on peut agrémenter un peu le graph en ajoutant un titre par exemple ou en reliant les points.

p <- p + ggtitle("My beautiful enzyme")
p <- p + geom_line(size=2)

kinetics_plot_02

Il est aussi possible d’ajouter une courbe lissée.

p <- ggplot(data=data_kinetic_01, aes(x=Time, y=Abs, colour=Cond, shape=Cond)) + geom_point(size=4) p <- p+ geom_smooth()

kinetics_plot_03

L'ajout d'éléments à la figure est très simple et de nombreuses fonctions sont disponibles. Il faudra juste parcourir la documentation pour trouver celle qui convient mais les noms sont assez explicites et leur utilisation plutôt intuitive. A partir des mêmes données on peut par exemple représenter les données sous forme d’histogramme.

p <- ggplot(data=data_kinetic_01, aes(x=Time, y=Abs, fill=Cond)) + geom_histogram(stat='identity')

kinetics_plot_04

La valeur identity du paramètre stat indique à la fonction d’utiliser toutes les valeurs du tableau au lieu de faire un histogramme de répartition. Les barres sont empilées alors qu’on les voudrait séparées. Le paramètre position avec la valeur dodge va séparer les barres.

p <- ggplot(data=data_kinetic_01, aes(x=Time, y=Abs, fill=Cond)) + geom_histogram(stat='identity', position='dodge')

kinetics_plot_05

Et voilà. Une fois la figure finie, il est possible de l'exporter dans une image ou dans un fichier pdf est  à l'aide de la fonction ggsave

ggsave(plot=p, file="kinetics_plot_01.png")

l'extension du fichier servira à indiquer à ggsave le format de fichier voulu.

Allons plus loin

Bon, certes c’est assez sympa mais malgré tout ça reste assez basique et les fonctions de base de R font tout aussi bien presque aussi facilement. On va donc faire des choses un peu plus compliquées.

Histogrammes

Cette fois, les données seront générées de façon aléatoires : 3 conditions (A, B ou C) et 100 mesures pour chaque.

nb.samples=100
df2 <- data.frame(id = rep(1:nb.samples, times=3), condition = factor( rep(c("A","B","C"), each=nb.samples)), value = c(rnorm(nb.samples),rnorm(nb.samples, mean=.5, sd=2), rnorm(nb.samples, mean=2.1, sd=0.5)))

Pour observer la répartition des valeurs, il suffit d'utiliser la fonction geom_histogramm

p <- ggplot(df, aes(x=value))
p <- p + geom_histogram(binwidth=.5, colour="black", fill="white")

histo_01

Il est possible d'ajouter une ligne correspondant à la moyenne.

p <- p + geom_vline(aes(xintercept=mean(value, na.rm=T)), color="red", linetype="dashed", size=1)

histo_02

On passe à un fond blanc pour faciliter une éventuelle impression.

p <- p + theme_bw()

histo_03

Imaginons maintenant que l'on souhaite observer la répartitions des valeurs pour chaque condition séparée.

p <- ggplot(df, aes(x=value, fill=condition))
p <- p + geom_histogram(binwidth=.5, colour="black")
p <- p + theme_bw()

histo_04

Cette représentation n’est pas idéale. Pours séparer les valeurs dans des plots différents, il va falloir utiliser une nouvelle fonction facet_grid. Les plots peuvent être disposés horizontalement.

p <- p + facet_grid( . ~ condition)

histo_05

ou verticalement.

p <- p + facet_grid( condition ~ .)

histo_06

Un peu de décoration.

p <- p + ggtitle("yet another ggplot2 histogram")
p <- p + xlab(label="my favorite measurement")

histo_07

Pour completer la figure avec quelques informations sur les distributions il suffit d'utiliser le package plyr, créé par le même développeur. Je ne rentre pas dans le détail de plyr, je le décrirai dans un autre article.

library(plyr)

A l’aide de plyr on calcule les moyennes par condition.

df.stats <- ddply(df, "condition", summarise, value.mean=mean(value), value.sd=sd(value))

On ajoute des lignes verticales au niveau des moyennes calculées.

p <- p + geom_vline(data=df.stats, aes(xintercept=value.mean), linetype="longdash", size=1, colour="black")

histo_08

Puis on trace les lignes correspondant aux limites à plus ou moins 3 écart-types de la moyenne.

p <- p + geom_vline(data=df.stats, aes(xintercept=value.mean - 3*value.sd), linetype="dotted", size=1, colour="black")
p <- p + geom_vline(data=df.stats, aes(xintercept=value.mean + 3*value.sd), linetype="dotted", size=1, colour="black")

histo_09

Boxplots

A partir des même données on peut aussi dessiner des "boxplots".

p <- ggplot(data=df, aes(x=condition, y=value)) p <- p + geom_boxplot()

La fonction fait ce qu'on a attend d'elle, des "boxplots" basiques

box_plot_01

Ajoutons les points

p <- p + geom_point()

box_plot_02

Comme le graphe manque un peu de lisibilité, on colore les "boxplots" en fonction des conditions et on disperse les points sur toute la largeur.

p <- ggplot(data=df, aes(x=condition, y=value, fill=condition))
p <- p + geom_boxplot()
p <- p + geom_jitter(position = position_jitter(width = .2))

box_plot_03

On peut, si on le souhaite, relier les points entre eux. Ils seront reliés par leur rang dans le tableau

p <- ggplot(data=df, aes(x=condition, y=value, fill=condition))
p <- p + geom_boxplot()
p <- p + geom_point(aes(shape=condition), size=3)
p <- p + geom_line(aes(group=id), linetype='dashed')

box_plot_04

Pour personnaliser un peu plus le graph avec un fond blanc et en éliminant la légende des abscisses, il faudra utiliser la fonction theme

my.theme <- theme_bw()+ theme(axis.text=element_text(size=16), axis.title.x=element_blank())
p <- p + my.theme

box_plot_05

Scatterplots

Un dernier exemple avec cette fois des "scatterplots". On utilisera la fonction mvrnorm du package MASS pour générer les données

library(MASS)
set.seed(123)
nb.samples <- 25
correlation.group.1 <- 0.65
m.data.group.1 <- mvrnorm(nb.samples, mu = c(10,5), Sigma = matrix(c(1,correlation.group.1,correlation.group.1,1), ncol = 2), empirical = TRUE)
correlation.group.2 <- 0.85
m.data.group.2 <- mvrnorm(nb.samples, mu = c(15,6), Sigma = matrix(c(1,correlation.group.2,correlation.group.2,1), ncol = 2), empirical = TRUE)
df <- data.frame(id = rep(1:nb.samples, times=2), condition = factor( rep(c("A","B"), each=nb.samples)), protein.1 = c(m.data.group.1[,1],m.data.group.2[,1]), protein.2 = c(m.data.group.1[,2],m.data.group.2[,2]))

Représentons dans un premier temps les points.

p <- ggplot(data=df, aes(x=protein.1, y=protein.2))
p <- p + geom_point(size=4)

scatterplot_01

Puis ajoutons une courbe de régression.

p <- p + stat_smooth()

scatterplot_02

La fonction stat_smooth permet d'obtenir la courbe passant au plus près de l'ensemble des points. Cependant, on ne tient pas compte des différentes conditions. Pour les séparer, il faut utiliser l'argument colour.

p <- ggplot(data=df, aes(x=protein.1, y=protein.2, colour=condition, shape=condition))
p <- p + geom_point(size=4)

scatterplot_03

On choisi d'ajouter une droite de régression et non plus une courbe lissée en utilisant la méthode lm en paramètre à la fonction stat_smooth

p <- p + stat_smooth(method = "lm")
scatterplot_04

On rajoute un peu de décoration, c'est toujours mieux

p <- p + theme_bw() p <- p + ggtitle("yet another ggplot2 scatterplot")

scatterplot_05

Et on obtient nos points avec leur courbe de tendance respectives. Elles sont sur le même graphe mais il est très facile de les séparer.

p <- p + facet_grid(. ~ condition)

scatterplot_06

Les 2 "plots" ont les mêmes échelles. Ce comportement est souvent intéressant mais dans notre cas, cela nuit un peu à la lisibilité. On élimine la contrainte d’échelle sur x.

p <- p + facet_grid(. ~ condition,scales="free_x")

scatterplot_07

Et voilà on a 2 jolis graphes indépendants.

Pour aller plus loin

J’ai présenté quelques fonctions basiques de ggplot2. Le package permet beaucoup plus de choses et je n’ai pas encore tout exploré. Il existe pas mal de ressources pour aller plus loin.

Le mieux étant de le découvrir petit à petit avec de vraies données. Merci aux relecteurs Maxi_zu, Estel et Bu pour leur corrections et leurs conseils avisés et surtout à Vincent Rouilly qui m’a fait découvrir ggplot2 et qui surtout a imaginé une bonne partie des exemples présentés ici.

L'analyse en composantes principales (avec R)

$
0
0

L'ACP, ou Analyse en Composantes Principales, est une méthode d'exploration de données qui consiste à réduire la dimensionnalité du problème pour en extraire l'essentiel. Par une projection dans un espace plus petit, on réduit le nombre de variables, et si on réduit suffisamment on peut en faire un outil de diagnostic graphique. Comme c'est une projection, il est important de comprendre qu'on perd de l'information dans le processus, mais cela permet d'interpréter plus facilement les données.

Table des matières :

La méthode

En pratique, on part d'un tableau avec N lignes représentant les individus mesurés - par exemple 1000 souris - et P colonnes représentant les variables mesurées - par exemple la taille, le poids, l'âge etc. des souris. L'idée, c'est qu'il est facile de représenter sur un graphique une situation à deux variables : la taille sur l'axe des x, le poids sur l'axe des y, un point par souris. On pourrait imaginer ajouter un 3ème axe pour l'âge et visualiser les points dans un "cube" en 3D. Mais à partir de 4 variables, la visualisation devient impossible. Pour réduire la dimension, on va essayer de ne garder que l'essentiel. L'ACP va transformer le tableau pour qu'il ne compte plus que deux (nouvelles) colonnes tout en conservant un maximum d'information.

Sans entrer dans les détails, la transformation implique d'abord une rotation des axes de coordonnées : le premier axe suivra la direction la plus "allongée" (imaginez un poisson ; l'axe va de la tête à la queue), le second une direction perpendiculaire (du ventre vers le dos), le troisième perpendiculaire aux deux premiers (l'épaisseur du poisson), etc. Cela sert à maximiser la variabilité des points le long du premier axe, puis la variabilité du reste le long du second, etc. de sorte qu'il n'y ait presque plus de variation le long des axes suivants. Ces nouveaux axes s'appellent composantes principales ; ce sont P vecteurs de taille P. Mathématiquement, ce sont les vecteurs propres de la matrice de covariance de notre table, et on fait un changement de base.

poisson_orig_axes2

Axes du repère d'origine (image d'origine : wikimedia.org)

poisson_pca_axes2

Axes de l'ACP (composantes principales)

 

Ensuite on fait une sélection des composantes : plus on en garde, plus complètement on peut recomposer l'objet (si on n'a gardé que les deux premières composantes du poisson, on ne peut reconstruire qu'un poisson tout plat).

L'important dans tout ca, c'est que si les deux premières composantes cumulent une grande portion de la variabilité totale, on peut ignorer les autres et les opposer dans un graphique.

Un exemple avec R

Ici nous partirons du jeu de données "iris" déjà inclus dans la plupart des distributions de R. La table ressemble à ça :

> head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

Il y a 150 individus et 4 dimensions/variables. Les 150 sont groupés en 3 espèces setosa, versicolor et virginica. Ce qu'on voudrait savoir, c'est s'il y a une différence importante entre les espèces, et s'il y en a une, quelles sont les variables qui contribuent le plus à expliquer cette différence.

Il y a deux commandes à choix pour faire l'ACP en R (j'expliquerai plus tard pourquoi) : prcomp et princomp. Ici je vais utiliser prcomp ; avec princomp le résultat est presque le même, et l'utilisation est identique aux noms des attributs près.

pca = prcomp(iris[,1:4])

Les composantes principales (PCx, pour "Principal Component") sont les colonnes de pca$rotation :

> pca$rotation
                     PC1         PC2         PC3        PC4
Sepal.Length  0.36138659 -0.65658877  0.58202985  0.3154872
Sepal.Width  -0.08452251 -0.73016143 -0.59791083 -0.3197231
Petal.Length  0.85667061  0.17337266 -0.07623608 -0.4798390
Petal.Width   0.35828920  0.07548102 -0.54583143  0.7536574

On voit par exemple que l'axe "PC1" est une combinaison de 0.86 x Petal.Length, 0.36 x de Sepal.Length et Petal.Width, et -0.08 x Sepal.Width - c'est-à-dire que la direction de la PC1 est à peu près alignée avec celle de Petal.Length et quasi perpendiculaire à celle de Sepal.Width.  On appelle aussi ces nombres "loadings".

pca$sdev donne la fraction d'information (sdev="standard deviation") contenue dans chacune des composantes :

> pca$sdev                                    # Les écarts-types "bruts"
[1] 2.0562689 0.4926162 0.2796596 0.1543862

> 100 * pca$sdev^2 / sum(pca$sdev^2)               # Proportion de variance
[1] 92.4618723  5.3066483  1.7102610  0.5212184    # de chaque composante

> sum(100 * (pca$sdev^2)[1:2] / sum(pca$sdev^2))   # Variance totale de
[1] 97.76852                                       # PC1 + PC2, en %

Ainsi les deux premières composantes à elles seules contiennent 97.77% de l'information, il est donc raisonnable de garder seulement deux variables en ne perdant que 2.33% d'info, ou même une seule (ce que je ferais en pratique, mais ici on en gardera deux pour plus de généralité). On peut aussi rendre compte de ces proportions avec un bar plot des variances :

plot(pca)

sdev_iris

Les variances des composantes principales.

 

Je ne vous cache pas qu'on peut en produire un plus joli soi-même.

Les données transformées par rotation sont dans pca$x ; chaque colonne est la projection des points sur l'une des composantes principales. Ainsi si on garde seulement les deux premières, on a la projection des points dans le plan PC1-PC2 et on peut en faire un graphique qu'on appelle "biplot" :

plot(pca$x[,1:2], col=iris[,5])

L'axe horizontal est notre PC1, l'axe vertical la PC2. En coloriant les points selon l'espèce de plante, on voit que la PC1 les distingue presque parfaitement :

pca_iris

ACP sur les données "iris"

 

En fait on a vu que la composante 2 n'est pas très utile, et on pourrait très bien choisir de réduire encore la dimension et séparer nos points selon un seul axe :

plot(pca$x[,1], rep(0,nrow(iris)), col=iris[,5])

 

pca_1d_iris

Projection sur la première composante principale uniquement.

 

L'interprétation ensuite est la suivante : d'abord l'espèce setosa (à gauche) est nettement plus reconnaissable que les deux autres entre elles. Ensuite, comme c'est la composante 1 qui sépare les espèces, on regarde de quoi elle est faite. On avait vu qu'elle était composée principalement de Petal.Length ; on en déduit que c'est principalement à la longueur des pétales qu'on reconnaît les espèces. A vrai dire l'interprétation est souvent difficile, et on se contente alors de remarquer que les groupes sont bien différenciés/groupés comme on l'attend.

Il existe aussi une commande R qui fait le biplot toute seule mais... chacun ses goûts. Les flèches qui apparaissent sur le graphique sont les anciens axes de coordonnées (il y a aura donc P flèches - et on ne peut pas en réduire le nombre, ce qui peut devenir très lourd). Il est aussi centré en zéro et réduit (ce n'est pas important pour l'interprétation).

biplot(pca)

biplot_iris

Biplot des données iris

Autres applications

On peut se servir de l'ACP par exemple en RNA-seq pour regarder si l'expression globale des gènes est très différente d'un groupe de patients à un autre, ou si les réplicats de l'expérience sont suffisamment semblables - dans le cas contraire ça permet d'éliminer les mauvais échantillons et rendre l'analyse plus robuste. On considère alors ces fameuses tables avec un gène par ligne et une colonne par échantillon, indiquant dans chaque case l'expression d'un gène dans un échantillon particulier.

De façon similaire, on peut mesurer la présence ou l'absence de chaque variation du génome (SNP) de 3000 Européens, et comparer le graphe de l'ACP à la carte de la région, c'est plutôt spectaculaire. C'est aussi un bon exemple de cas où l'interprétation découle directement de la disposition des points et pas du contenu des composantes principales :

genes_in_europe

Biplot d'une ACP sur les différences génétiques en Europe (J. Novembre et al., Nature 2008).

 

Dans des contextes différents, on peut l'appliquer pour la compression d'images, la reconnaissance de mouvements, etc.

Finalement, c'est aussi un moyen de trouver la "meilleure" droite passant par un nuage de points, un peu comme on le fait en régression linéaire, avec l'avantage que le résultat est le même si on échange les axes (ce n'est pas le cas de la régression !). Cette droite passe par le centre du nuage dans la direction du vecteur PC1.

bestline

En rouge : la première composante principale. En bleu : les droites de régression X~Y et Y~X.

 

En bonus, le code pour ce dernier graphique :

library(MASS)
x <- mvrnorm(1000, c(0,0), matrix(c(5,1,3,2), 2,2))
plot(x, xlim=c(-8,8), ylim=c(-8,8), xlab="X", ylab="Y")
pca = prcomp(x)
r = pca$rotation
abline(0, r[2,1]/r[1,1], col="red")
lin.model = lm(x[,1] ~ x[,2])
abline(0, lin.model$coefficients[2], col="blue")
lin.model = lm(x[,2] ~ x[,1])
abline(0, lin.model$coefficients[2], col="blue")

Prcomp et princomp

La différence entre ces deux commandes R se trouve dans la méthode mathématique utilisée. La décomposition en composantes principales peut se faire soit par SVD (Décomposition en Valeurs Singulières), soit par recherche des vecteurs propres de la matrice de covariance. Ainsi, on remarque que prcomp (SVD) est plus rapide, et même que si on essaye avec la matrice transposée (voir plus bas), princomp va se plaindre car il n'existe pas suffisamment de vecteurs propres :

> princomp(t(iris[,1:4]))
Error in princomp.default(t(iris[,1:4])) :
  'princomp' can only be used with more units than variables

alors que pour prcomp tout va très bien :

prcomp(t(iris[,1:4]))

De plus, le calcul de la SVD est plus stable numériquement. Les noms des attributs sont aussi différents. Voici la correspondance si on y tient :

prcomp -> princomp
pca$x -> pca$scores
pca$rotation -> pca$loadings[,1:4]
pca$sdev -> pca$sdev

Pour aller plus loin

- On peut aussi toujours s'intéresser au problème inverse en transposant la table des données. Par exemple, si on a une table avec une ligne par gène et une colonne par condition expérimentale, l'ACP telle que nous l'avons présentée ressortira un graphique avec un point par gène, et on espère voir des "clusters" de gènes en fonction des échantillons. Si on transpose la table pour avoir une ligne par condition et une colonne par gène, on obtiendra un graphique avec un point par condition, et on espère voir les réplicats ensemble et les malades bien séparés des contrôles.

- Lorsque les variables sont, comme dans l'exemple "iris", du même type (des longueurs, en cm), tout va bien. Mais si on commence à mélanger des tonnes et des microns, alors les échelles seront différentes et l'ACP sera biaisée en faveur des nombres les plus grands. Pour y remédier, on peut choisir de normaliser les variables avec l'option "scale=TRUE", ce qui divisera chaque colonne par sa variance. Si cette normalisation s'avère trop forte (c'est le cas avec les expressions de gènes), on peut simplement appliquer un logarithme.

- La théorie part du principe que les variables initiales sont indépendantes (axes orthogonaux). En pratique on ignore joyeusement cette contrainte, comme par exemple dans le cas de l'expression des gènes. Un inconvénient, c'est que les composantes principales contiennent un mélange d'un peu toutes les variables à la fois, rendant l'interprétation difficile. Il existe une adaptation de l'ACP pour variables corrélées qu'on appelle "ACP éparse" ("sparse PCA"), exploitant la technique du lasso en interprétant l'ACP comme un problème de régression. Alors un grand nombre de coefficients des composantes principales deviennent nuls et on peut plus facilement extraire les variables significatives. Pour plus de détails, voir H. Zou, T. Hastie, R. Tibshirani, "Sparse principal component analysis", 2004. Les auteurs ont implémenté la méthode dans le package R elasticnet.

- Il existe des packages R comprenant des fonctions pour une étude plus poussée de l'ACP, d'ailleurs développés par des Français : FactomineR, Ade4 and amap. Je ne les ai pas testés, mais je vous invite à regarder par exemple ce lien.

 

Merci à HautbitMaxi_Zu et Estel pour leur relecture.

L'annotation de régions génomiques et les analyses d’enrichissement

$
0
0
1920px-Gas_centrifuge_cascade

Non il ne s'agit pas d'enrichissement d'uranium ! (U.S. Department of Energy, Domaine Public)

Les annotations sont essentielles lors d'analyses fonctionnelles à large échelle sur le génome. 

Lorsque l’on pratique des analyses en génomique, basées sur des techniques comme le RNA-seq ou le ChIP-seq, on se retrouve avec respectivement une liste de transcrits ou de pics (régions génomiques). Dans le cas des analyses ChIP-seq, on souhaite caractériser les gènes cibles du facteur de transcription étudié sur tout le génome (genome-wide), pour comprendre la fonction biologique de ce facteur. Dans le cas du RNA-seq, on obtient une liste de transcrits différentiellement exprimés dont on souhaite caractériser la fonction.

Dans cet article nous allons utiliser plusieurs librairies R pour automatiser cette analyse, à partir d’un fichier .bed (voir article Bed & Fasta), ou d’une liste de transcripts Ensembl.

Pour commencer, nous allons utiliser des sites de ChIP-seq du récepteur aux glucocorticoïdes GR (Grøntved L, John S, Baek S, Liu Y et al. , EMBO J 2013, GSE46047). GR est un récepteur nucléaire important impliqué dans la gluconéogenèse, la glycolyse, le métabolisme des acides gras et la réponse immunitaire et inflammatoire. Nous allons annoter les pics de GR avec les gènes les plus proches sur le génome de la souris (mm9) à l’aide de la librairie ChIPpeakanno.

Ensuite, nous allons voir comment convertir des identifiants Ensembl en symboles de gènes à l’aide de la librairie BiomaRt. Enfin, nous allons faire une analyse d’enrichissement d’annotations à l’aide de la librairie RDAVIDWebServiceDAVID est un très bon site d’analyse d’annotations qui permet de travailler avec différentes sources comme les ontologies de gènes (GO terms), que nous avions introduites dans un article précédent et les voies de signalisations entre autres. La base de données de l’outil DAVID permet de faire des requêtes sur 82 sources, dont notamment REACTOMEKEGG et PANTHER, qui sont maintenues par des biocurateurs.

DAVID vous permet d’utiliser son interface web via son site, ou des services web (accès programmatique). D’autres applications web permettent de travailler directement avec des fichiers .bed, comme l’excellent outil GREAT du laboratoire Bejerano de Stanford.

Un peu de code R: mise en place de l'environnement de travail 

Vous pouvez télécharger les données et le script ici.

Dans un premier temps il faut installer les librairies nécessaires pour l’analyse avec les commandes R suivantes :

source("http://bioconductor.org/biocLite.R")
biocLite("ChIPpeakAnno")
biocLite("rtracklayer")
biocLite("biomaRt")
biocLite("RDAVIDWebService")

library("rtracklayer")
library("biomaRt")
library("ChIPpeakAnno")
library("RDAVIDWebService")

Dans un deuxième temps, nous devons importer les données des pics de GR au format .bed

GR_bed=import.bed("GSE46047_GR_peaks_mouse_liver_mm9.txt", genome = "mm9")

Nous pouvons utiliser biomaRt pour obtenir les sites d’initiation de la transcription (TSS). Nous utilisons les données de Ensembl NCBIM37 pour l’assemblage du génome “mm9”.

ensembl = useMart("ensembl", dataset = "mmusculus_gene_ensembl")
TSS.mouse.NCBIM37 = getAnnotation(mart=ensembl, featureType="TSS")

Comment annoter des pics de GR sur le génome avec les gènes les plus proches ?

annotatedPeak = annotatePeakInBatch(GR_bed, AnnotationData = TSS.mouse.NCBIM37)

Annotatedbed = as.data.frame(annotatedPeak)

Grâce aux commandes ci-dessus nous pouvons obtenir une table ayant la structure suivante :

head(Annotatedbed)
 seqnames start end width strand peak feature start_position end_position insideFeature distancetoFeature
 00002 ENSMUSG00000103922 1 4759134 4759283 150 + 00002 ENSMUSG00000103922 4771131 4772199 upstream -11997
 00006 ENSMUSG00000051285 1 7079940 7080089 150 + 00006 ENSMUSG00000051285 7088920 7173628 upstream -8980
 00009 ENSMUSG00000104504 1 8862999 8863148 150 + 00009 ENSMUSG00000104504 8856763 8857102 downstream 6236
 00010 ENSMUSG00000025911 1 9554202 9554351 150 + 00010 ENSMUSG00000025911 9547948 9580673 inside 6254
 00011 ENSMUSG00000081441 1 9574861 9575010 150 + 00011 ENSMUSG00000081441 9574548 9575649 inside 313
 00012 ENSMUSG00000067879 1 9611749 9611898 150 + 00012 ENSMUSG00000067879 9601199 9627143 inside 10550
 shortestDistance fromOverlappingOrNearest
 00002 ENSMUSG00000103922 11848 NearestStart
 00006 ENSMUSG00000051285 8831 NearestStart
 00009 ENSMUSG00000104504 5897 NearestStart
 00010 ENSMUSG00000025911 6254 NearestStart
 00011 ENSMUSG00000081441 313 NearestStart
 00012 ENSMUSG00000067879 10550 NearestStart

Le champ « seqnames » représente le chromosome , les champs « start » et « end » représentent les coordonnées génomiques des pics de GR. Ces pics sont annotés avec le TSS du gène Ensembl le plus proche en indiquant la distance et le chevauchement (overlap).

Comment faire des requêtes sur Biomart pour convertir des identifiants Ensembl en symboles de gènes ?

maptable = getBM(attributes = c("mgi_symbol", "ensembl_gene_id"), filters = "ensembl_gene_id", values = Annotatedbed$feature, mart = ensembl)
ii = match(Annotatedbed$feature,maptable[,2])
Annotatedbed$feature_symbol = maptable[ii,1]

En regardant à nouveau la table, on voit que les symboles des gènes sont à présent affichés sur la dernière colonne. Pour travailler avec les identifiants des transcrits et non des gènes, il faut simplement utiliser "ensembl_transcript_id".

head(Annotatedbed)

Comment faire une analyse d’enrichissement grâce à DAVID ? Et qu'est-ce qu’une analyse d’enrichissement ?

Les analyses d’enrichissement d’annotations ont pour but de calculer une probabilité : sachant qu’un groupe de gènes est annoté avec un terme spécifique de KEGG_PATHWAY, quelle est la probabilité que la totalité ou une fraction de ces gènes soit dans le groupe des cibles de GR (dans ce cas). David utilise le test hyper géométrique pour calculer cette valeur p.

On peut faire une analyse d'enrichissement à partir de n'importe quelle liste de transcrits provenant soit de RNA-seq soit de pics de ChIP annotés avec les gènes les plus proches (comme ci-dessus).

Vous devez d’abord vous inscrire sur le site de DAVID pour utiliser le service web.
Puis vous pouvez vous connecter :

david = DAVIDWebService$new(email="votre_mail@institution.fr")

Vous pouvez accéder à la liste des annotations disponibles avec la commande:

getAllAnnotationCategoryNames (david)

Lorsque vous avez choisi les annotations utiles (une ou plusieurs) pour votre analyse (dans ce cas KEGG_PATHWAY), vous pouvez utiliser le code suivant :

setAnnotationCategories(david, c("KEGG_PATHWAY"))

result1 = addList(david, Annotatedbed$feature,idType="ENSEMBL_GENE_ID", listName="GR_targets", listType="Gene")
t_p1=getFunctionalAnnotationChart(david)

Vous pouvez également ajouter une liste de gènes en arrière plan (avec listType="Background") pour améliorer votre analyse, en ne considérant que les gènes exprimés dans un tissus précis (par exemple).

Le service web de DAVID va vous envoyer une table contenant dans chaque ligne les annotations triées par la P-valeur, avec différentes métriques (FDR, Bonferroni) et les identifiants Ensembl.

head(t_p1)

On peut enfin visualiser l’analyse à l’aide d’un bar plot.

par(mar=c(5, 20, 4, 2) + 0.1)
barplot(rev(-log10(t_p1$PValue)), names.arg=rev(t_p1$Term),col="darkgreen",horiz=T,las=2,cex.lab=1,cex.names=0.8,xlab="-log10 P valeur")
title("analyse d'enrichissement d'annotations")

Screenshot 2015-04-07 20.51.41

On constate sur ce graphique que, parmi les annotations enrichies, on retrouve une majorité de fonctions, décrites dans la littérature, des récepteurs aux glucocorticoïdes (GR). Nous voyons par exemple que les adipocytokines (inflammation) et les récepteurs des cellules T (réponse immunitaire) sont parmi les cibles les plus importantes de GR.

En conclusion, nous avons un joli script qui permet d' automatiser des requêtes sur DAVID à partir d’un fichier de régions génomiques au format .bed. D’autres packages R offrent la possibilité de faire des analyses similaires comme topGO ou encore GAGE. N’hésitez pas à les tester. Donnez-nous votre avis sur les outils que vous connaissez !

Merci aux relecteurs: Yoann M, ook4mi, NiGoPol, muraveill, Zazo0o et Estel

De la procrastination dans l'R

$
0
0

Connaissances requises

  1. Connaissances basiques en RSi vous ne faites pas la différence entre un test exact de Fisher et le test du Chi-2, cela ne devrait pas poser de problème.
  2. Euh, bah c'est tout !

Introduction

Si l'on s'en réfère à la définition :

Un informaticien, et a fortiori un bioinformaticien, fera tout pour mettre en œuvre des stratégies lui permettant d'automatiser les tâches répétitives qui lui incombe.

Plusieurs avantages à cela, i/ rallonger les pauses café, ii/ profiter du temps gagné pour regarder la guerre des étoiles directement sur le terminal :

telnet towel.blinkenlights.nl

Je vous rassure, le but ici n'est pas d'apprendre à réaliser des films en caractères ASCII, chose très fastidieuse et dénuée de toute relation avec la bioinformatique.

Nous allons cependant voir comment à l'aide d'une petite dose de Markdown et de quelques commandes R, il va être possible de générer de façon automatique des beaux rapports d'analyses de données. Cela va nous permettre de réaliser des analyses reproductibles et réutilisables (par exemple, sur de nouveaux jeux de données).

Préparation

Avant de commencer la pratique, quelques petites préparations s'imposent.

Étape n°1, installer le package magique : Rmarkdown (qui est en fait une surcouche de knitr et pandoc)

package.install("rmarkdown",DEPENDANCIES=T)

Ce petit bijou de technologie sera notre fidèle allié dans la bataille contre les forces du mal des activités répétitives.

Étape n°2, créer un nouveau fichier et l'ouvrir dans l'éditeur de texte de votre choix. On ne va pas entrer dans le débat, mais Vi est quand même "le meilleur éditeur du monde" surtout lorsqu'il est épaulé par le plugin vim-r-plugin qui le transforme en véritable IDE pour R. Les moins barbus préféreront utiliser RStudio qui intègre parfaitement la rédaction des documents Rmarkdown.

C'est dans ce fichier que nous allons rédiger notre rapport d'analyse au format Markdown. Pour ceux qui ne sont pas familier avec Markdown, c'est un langage de description syntaxique qui permet de très simplement structurer un document, avec des titres, des liens, des images, des listes, du texte en gras/italique, des tableaux... C'est un peu comme du LaTeX ou du HTML mais sans les balises complexes et les formules compliquées. C'est simplement du texte brut facile à lire et à écrire.

Dans ce document nous allons pouvoir écrire le texte de notre rapport d'analyse et l'entremêler avec des commandes R. Ces dernières seront uniquement évaluées à la génération de façon à insérer les résultats produits dans le document final. L'objectif étant d'écrire un rapport pré-formaté qui va pouvoir être ré-généré avec des données différentes. Pour ceux qui parlent le Python couramment, il existe une solution similaire pour ce langage : iPython Notebook.

Plutôt qu'un long discours, passons à la pratique.

La pratique !

Nous allons tout d'abord introduire au début de notre document quelques métadonnées au format YAML afin d'ajouter un titre bien assumé, le nom de son altesse, une date actuelle et quelques réglages sur le formatage du rapport qui sera produit. Ici, nous allons générer un PDF (compilé à l'aide d'une distribution LaTeX) avec une table des matières. Bien évidement, d'autres formats de sortie sont disponibles comme HTML, doc/docx (Word) ou même sous forme de présentation avec Beamer Latex ou io-slides. Ces deux derniers sont des templates qui permettent de générer des présentations en PDF (Beamer Latex) ou en HTML (io-slides). Il existe également un tas d'options pour personnaliser l'aspect final de votre rapport.

---
title: "Un merveilleux rapport d'analyse" 
author: "Elisabeth II" 
date: March 22, 2005 
output: 
    pdf_document:
        toc: true
---

Maintenant, nous allons ajouter une première section à ce document afin de vanter les magnifiques analyses qui vont suivre :

# Introduction

Dans ce **magnifique** document, nous allons analyser un ensemble d'annotations récupérées sur la base de données *Ensembl*.

Dans ce rapport nous allons découvrir :
- un magnifique graphique
- un exceptionnel tableau
- et un score de corrélation de Pearson.

Dans le formatage Markdown, le caractère # permet d'introduire un titre de niveau 1, deux dièses auraient introduit un titre de niveau 2, etc. Les doubles ** qui entourent un mot permettent de mettre le texte en gras et les simples * correspondent à l'italique. Enfin une liste à puces est simplement représentée par des tirets. Pour plus d'informations sur ce langage, direction la page officielle.

Avant de commencer les choses sérieuses, l'analyse que nous allons mener va se baser sur l'ensemble des annotations (au format GTF) du C. elegans récupérées ici sur Ensembl.

Afin d'inclure du code R nous allons insérer des R chunks :

Un R chunk est un couple de balises entre lesquelles nous allons placer du code R. Ce dernier ne sera interprété qu'à la compilation du document et le résultat produit par le code (plot, tableau, variable) sera inséré dans le document en place du chunk.

Voila un exemple de R chunk qui affiche Hello world :

```{r}
print("Hello world")
```

Il existe également la version "inline" du R chunk, qui permet d'intégrer un résultat numérique directement dans une phrase :

`r 2*3*7`is the answer to life, the universe, and everything.

Un R chunk ne produit pas forcément de résultats visibles. Certain chunk peuvent uniquement permettre d'exécuter du code R dit "silencieux".

markdown chunk with preview in RStudio

Un document Rmarkdown et sa prévisualisation dan RStudio.

Un cas courant d'utilisation de chunk silencieux correspond au chargement de bibliothèques et/ou de fichiers, comme ce sera le cas de notre premier R chunk :

```{r echo=FALSE}
library(ggplot2)
df <- read.table("Caenorhabditis_elegans.WBcel235.79.gtf.gz",skip=5,sep="\t",nrows=1000)
names(df) = c("chr","source","feature","start","end","score","strand","frame","attribute")
```

Remarquez la présence de l'option echo=FALSE entre les accolades qui permet d'indiquer à knitr de ne pas afficher les lignes de code dans le document final.

Trois commandes sont présentes dans ce chunk, la première permet de charger la bibliothèque ggplot2 qui va s'occuper de générer nos beaux graphiques, la seconde permet d'importer notre fichier d'annotation dans un Dataframe (nom donné à un tableau à double entrée dans R) en se limitant aux 1000 premières lignes car on n'a pas toute la nuit ! Enfin, la troisième commande permet de définir le nom des colonnes du Dataframe.

Maintenant ces initialisations faites, nous allons pouvoir démarrer notre analyse ! Il est important de noter que même si les chunks sont physiquement séparés les uns des autres, ils sont en fait exécutés les uns à la suite des autres dans une même session R. Cela signifie que les variables déclarées dans un chunk (comme ici df), sont utilisables dans tout le reste du document.

Nous pouvons maintenant intégrer un premier graphique sur la fréquence des différents types d'annotations (gène, exon, transcrit) :

# Analyse des annotations du C. elegans
```{r echo=FALSE}
ggplot(df,aes(df$feature)) + geom_bar() + ggtitle("Fréquence des annotations")
```

Si vous souhaitez en savoir plus sur ggplot, leur site web déborde d'exemples.

À la suite de ce graphique nous allons intégrer un tableau formaté correspondant à un extrait des annotations contenues dans notre Dataframe :

```{r echo=FALSE, results='asis'}
knitr::kable(head(df[,1:5]),caption="Aperçu des annotations")
```

Afin de générer notre tableau nous avons utilisé la fonction kable du package knitr qui permet de transformer notre Dataframe en un tableau formaté à la sauce Markdown. Il a également été nécessaire d'ajouter l'option results='asis' pour spécifier à Rmarkdown de n'appliquer aucun post-traitement sur le résultat produit par ce chunk.

Enfin nous allons finir cette belle analyse avec une petite mesure de corrélation (avec la méthode de Pearson) entre les positions de début et de fin des annotations, qui, comme on pourrait l'attendre est très bonne.

La corrélation de Pearson réalisée sur les positions (début, fin) des annotations est de `r cor(df$start,df$end,method="pearson")`. Ce résultat est très élevé, comme on aurait pu s'en douter.

Maintenant, il ne nous reste plus qu'à générer le document final à l'aide de la commande rmarkdown::render("my-first-report.Rmd") et d'admirer le résultat !
Pour ceux qui travaillent sous RStudio un bouton "knit PDF" est directement intégré à l'interface graphique.

Conclusion

Si vous êtes arrivés vivants jusqu'ici, vous avez maintenant toutes les cartes en main pour créer de magnifiques rapports d'analyses reproductibles. Si vous souhaitez en savoir plus, un site regroupant plein de ressources a été mis en place par/pour la communauté. Vous y apprendrez entre autres à écrire des chunks réutilisables, à intégrer des citations et à générer des rapports HTML interactifs.

Pour ceux qui souhaitent aller plus loin, un MOOC (cours en ligne) sur le thème de la "Recherche reproductible" et se basant sur Rmarkdown vient de commencer sur Coursera (jusqu'au 1er juin). Ce cours est dispensé par l'université de Johns Hopkins et devrait vous permettre d'approfondir le sujet avec un cours de grande qualité.

Ressources

 

Un grand merci à toute l'équipe de Bioinfo-fr et en particulier à nahoy, hedjour, Yoann, m4rsu, Nico M. et Nolwenn pour leur relecture, leurs conseils et leur aide pour la publication de mon premier article sur Bioinfo-fr.

Créer sa carte géographique avec R

$
0
0

Aujourd’hui je vais vous montrer comment, en utilisant R, on peut faire de belles cartes géographiques.

Et là, vous allez me demander, mais pourquoi faire des cartes géographique ? Et pourquoi avec R ?

Et bien imaginons que, vous, bioinformaticien de terrain, soyez allé échantillonner des animaux à l’autre bout du monde sur plusieurs sites, par exemple des Marsupilami (totalement au hasard !). Vous voulez faire une carte de ces différents sites d’échantillonnage.

Facile ! Il suffit d’utiliser Google Earth, et d’y ajouter les points me direz-vous.

Oui, mais voilà, vous avez 500 points d’échantillonnage. Et 500, ça commence à faire beaucoup à faire à la main… Et puis votre chef étant un ayatollah du libre, vous venez d’être viré par le simple fait d’y avoir pensé !

Et le gros avantage d’utiliser R sera de pouvoir utiliser toutes ses fonctions graphiques sur votre carte (et puis c'est libre !). Nous verrons, à la fin de cet article, comment ajouter des graphiques sur une carte.

 

Pour faire une simple carte, on va utiliser les packages maps[1] et mapdata[2]. Une fois installés, nous utiliserons la fonction 

map()
  pour créer notre carte. Nous utiliserons les données de la base de données worldHires fournie dans le package mapdata.

Le code suivant donne une carte du monde.

library(maps)
library(mapdata)

map('worldHires')

carte_monde

Les options graphiques de R peuvent s’appliquer. On peut choisir de ne dessiner qu'une partie du monde en utilisant les options xlim et ylim respectivement pour régler la longitude et la latitude. Il faut donc connaître les coordonnées géographique des quatre coins de la carte qui nous intéresse (elles peuvent être trouvées grâce à la fonction

locator()
  de R). L’option color permettra de modifier la couleur des frontières entre pays, et utilisée avec l’option fill, on pourra colorier les pays.
map('worldHires', col=rainbow(18), fill=T, xlim=c(-19,60), ylim=c(-40,40))

carte_afrique

 

Ouais, mais c’est chiant ton truc. Il faut connaître les coordonnées géographiques dans le système décimal pour dessiner la carte. On peut pas demander le pays que l’on veut ?

 

Et si ! On peut spécifier le pays que l’on veut dessiner. Par exemple, la commande suivante permet de dessiner une carte du Japon avec un fond grisé.

 

map('worldHires', "japan", col='gray80', fill=T)

carte_japon

Puisqu’une carte sans échelle ne veut rien dire (comment j’ai bien retenu mes cours de géographie du collège !), on utilisera la fonction 

map.scale()
  pour l’ajouter.

On peut ensuite ajouter les villes sur cette carte, grâce à la fonction

map.cities()
.

On pourra ensuite ajouter des points sur la carte à partir des coordonnées géographiques. Il faudra alors disposer de ces coordonnées géographiques dans le système décimal. Si vous avez les coordonnées dans le système sexagésimal, vous pouvez les convertir, par exemple sur ce site : https://tools.wmflabs.org/geohack/

Les coordonnées de Kyoto dans le système sexagésimal sont 35°40’14.6“N, 139°46’18.86“E (merci Wikipedia : https://fr.wikipedia.org/wiki/kyoto). Ces coordonnées donnent dans le système décimal : 35.670724°N pour la latitude et 139.771907°E pour la longitude.

map('worldHires', "japan", col='gray80', fill=T)
map.scale(134,26,metric=T, relwidth=0.3)
map.cities(country='Japan', capitals=1, pch=15, col='red')
points(135.7, 35, pch=16)
text(135.7, 35.3, label="Kyoto")
points(140.5, 37.8, pch=16)
text(140.5, 38.2, label="Fukushima")

carte_japon_avec_ville

 

Je vois d'ici les petits malins qui ont voulu, pour tester, faire une belle carte de la France métropolitaine, et qui n'ont pas été super contents du résultat…

En effet la commande

map("worldHires", "france")
  représente la France… en entier ! Donc avec les DOM-TOM (enfin les DROM-COM pour ceux qui ont suivi les changements d'acronymes).

Il faudra donc, dans le cas de la France métropolitane, passer obligatoirement par les coordonnées géographiques :

map("worldHires", "france", xlim=c(-5,10), ylim=c(35,55))

 

Maintenant revenons à nos marsupilamis. On va réaliser une carte de leur aire de répartition sur laquelle on ajoutera les points d’échantillonnage. Pour les aires de répartition d’une espèce, il est "assez facile" de trouver des données sur internet sous forme de shapefile (.shp) qui sont ceux que l’on utilise pour faire une carte.

On représente les aires de répartition de deux types de population de la même espèce de marsupilami (Marsupilami fantasii) : les marsupilamis jaunes à tâches (dont l'aire de répartition est représentée en vert) et les marsupilamis jaunes uniformes (en rouge). Sur la carte, on voit que les deux populations cohabitent sur une aire géographique limitée.

library(maptools) 
repartition = readShapePoly('./repartition_marsu.shp’)
map('worldHires', xlim=c(-90,-35), ylim=c(-60,15)) 
plot(repartition[55,], add=TRUE, col=rgb(20,84,30,150,maxColorValue=255), border=FALSE)
plot(repartition[84,], add=TRUE, col=rgb(201,21,34,150,maxColorValue=255), border=FALSE)

 

carte_repart_marsu

Pour faire cette carte, j’ai importé une nouvelle libraire (maptools) dans laquelle se trouve la fonction

readShapePoly()
 qui permet d’ouvrir des fichiers « de forme ». J’ai récupéré le fichier concernant les "caecilian amphibians"[3] (mais ! On bossait pas sur le marsupilami ?!) sur le site de l’UICN Red List (Liste rouge des espèces en danger)[4]. Une fois ouvert avec R, vous devriez obtenir une data frame contenant une aire de répartition par ligne (et j'ai bien galéré à en trouver deux qui se recoupent).

Une légende pourrait être ajoutée en utilisant la fonction

legend()
 de R.

Maintenant intéressons-nous en particulier à la zone où les deux populations cohabitent. Sur cette carte, j’ai ajouté les treize points d’échantillonnage dans cette région (dont sept dans la zone de cohabitation). On peut ajouter ces points grâce à la fonction 

points()
  en ayant au préalable importé le fichier csv qui les contient.

Pour zoomer, j’ai cherché les coordonnées des quatre points les plus, respectivement, au nord, à l’ouest, au sud et à l’est de la zone qui m’intéresse grâce à la fonction 

locator()
  de R.
map('worldHires', xlim=c(-65.5,-50), ylim=c(-14,-8))
plot(repartition[84,], add=TRUE, col=rgb(201,21,34,150,maxColorValue=255), border=FALSE)
plot(repartition[55,], add=TRUE, col=rgb(20,84,30,150,maxColorValue=255), border=FALSE)
points(coord$x, coord$y, pch=16)

points_echant

C’est bien beau d’échantillonner des bestioles sur le terrain, mais normalement, on n'échantillonne pas pour le plaisir d'échantillonner, mais pour faire quelque chose avec cet échantillonnage… On a donc séquencé le cytochrome b des différents individus échantillonnés dans ces différentes populations, ce qui nous a permis d’identifier huit haplotypes différents.

 Hey m4rsu ! On pourrait pas faire une représentation des différents haplotypes identifiés sur chaque site et leur fréquence, genre avec un camembert ?

J'allais justement y venir !

On peut donc ajouter des graphiques en camembert sur une carte grâce à la fonction 

draw.pie()
 qui est fournie avec la librarie mapplots. Il faut disposer d’un fichier qui comporte quatre colonnes : longitude, latitude, haplotype, nombre d’observations. On pourra alors utiliser la fonction 
make.xyz()
 qui créée automatiquement un objet utilisable dans la fonction
draw.pie()
.
library(mapplots)
xyz = make.xyz(haplotypes$x, haplotypes$y, haplotypes$freq, haplotypes$haplo)
map('worldHires', xlim=c(-65.5,-50), ylim=c(-14,-8))
plot(repartition[84,], add=TRUE, col=rgb(201,21,34,150,maxColorValue=255), border=FALSE)
plot(repartition[55,], add=TRUE, col=rgb(20,84,30,150,maxColorValue=255), border=FALSE)
draw.pie(xyz$x, xyz$y, xyz$z, radius=0.3, col=rainbow(8))
legend('topright', legend=c(1:8), col=rainbow(8), pch=15, ncol=2)

haplotype

 

Tadaa ! Bon, je vous laisse faire l’interprétation (bon courage, j'ai inventé les données !), car ce n’est pas l’objet de cet article.

Nous avons donc vu comment faire des cartes avec R. N’étant pas un spécialiste, je vous ai montré quelques possibilités, mais il y en a plein d’autres, et je suis sûr que l’on peut faire des cartes bien plus belles que celles que j’ai montrées ici. Il existe plein d'autres librairies pour faire des cartes, vous devriez y trouver votre bonheur !

Pour finir, je voudrais préciser qu’aucun marsupilami n’a été maltraité pour écrire cet article. En plus, c'est un gros fake : tout le monde sait que les marsupilamis vivent dans la forêt palombienne et non amazonienne.

Merci à mathurin, hedjour, Olivier Dameron et Sylvain P. pour leurs précieux conseils et la relecture de cet article. Merci également à Kim Gilbert pour son article "Making maps with R"[5] qui m'a bien aidé à mes débuts avec les cartes.

Références

[1] Package maps : http://cran.r-project.org/web/packages/maps/

[2] Package mapdata : http://cran.r-project.org/web/packages/mapdata/index.html

[3] : shapefile des Caecilian Amphibians (ou Gymnophonia) : http://goo.gl/OFGYl1

[4] : IUCN Red List http://www.iucnredlist.org/technical-documents/spatial-data

[5] : Making map with R : http://www.molecularecologist.com/2012/09/making-maps-with-r/

La bière décodée dans un Hackuarium

$
0
0
BeerAtHackuarium

CC-BY Jonathan Sobel/ Hackuarium

Vous aimez la bière et la science ? Vous voulez connaître la composition de votre mousse favorite ? Vous avez envie de goûter de nouvelles bières proches de celles que vous connaissez, ou complètement différentes ?

Il y a quelques temps déjà, je vous ai décrit la place de la bioinformatique dans les laboratoires citoyens. Aujourd'hui, un projet sympathique de séquençage et d’analyse biochimique voit le jour à Renens (Suisse, VD). Son but: analyser 1000 bières afin de les cartographier et de les répertorier. Une campagne Kickstarter est en cours pour financer ce projet nommé BeerDeCoded. Vous pouvez nous aider en partageant le lien suivant : 

Ce projet est là pour vous car nous espérons avoir une idée beaucoup plus précise de notre alimentation, en utilisant la métagénomique. À l'avenir, cette technologie viendra renforcer les outils existants pour l’étude des bières et autres produits de grande consommation.

Analyse sensorielle et séquençage en workshops

Concrètement, comment ce projet va-t-il se dérouler ? Des workshops d’extraction d’ADN de bières seront organisés cet été pour collecter, préparer et mesurer les échantillons au laboratoire de Hackuarium. Des analyses gustatives et olfactives permettront de déterminer à quel point nous percevons les  nuances de cette boisson. Les échantillons seront ensuite séquencés, afin de connaître la diversité des espèces biologiques présentes dans ces bières. Nous pourrons ainsi obtenir une vue d'ensemble de la composition de la bière sur le plan moléculaire. Une application smartphone et un site Internet permettront d'accéder facilement à ces données.

Une petite (analyse) pour la route

Pour commencer et comme "preuve de concept", nous avons recherché des données en libre accès sur le web pour faire une petite analyse préliminaire à partir des propriétés physico-chimiques/bio-chimiques de bières et de cidres.

Une table contenant une centaine de variétés de bières et de cidres nous a permis de regrouper ces entrées en grappes (clusters) grâce aux cinq paramètres IBU, ABV, OG, FG, °L et à une analyse en composantes principales (ACP). Ces paramètres sont l'alcool par unité de volume, l'amertume, la couleur de la bière, le sucre et le grain.

Du code R pour l' ACP sur les bières

Data=read.csv("Beer_brewersfriend.com.csv")
log.Data = log(data.matrix(Data[, 3:7])+1) # on utilise le log comme normalisation

rownames(log.Data) = as.character(Data[, 1])
Data.pca = prcomp(log.Data) # l'ACP

#couleur de la biere Data$X.L
my_pal=colorRampPalette(c("lightyellow","yellow","red","orange","brown","black"))(35)

library(ggfortify)
library(cluster)
library(ggplot2)
autoplot(Data.pca, data =Data,colour =my_pal[Data$X.L] ,label = TRUE,size=0.5)

Pca_right_color_beer_V2

Comme on le voit, l'ACP nous permet de séparer clairement les groupes de bières, selon leur couleur (blanche, blonde, ambrée, brune) et les cidres présents dans ces données. Dans un future proche nous pourront étudier plus précisément les specificités de chaque bières et ainsi connaître le secret de leur composition et de leur goût unique.

Mais pour cela et avant tout, nous avons besoins de vous ! Alors, n'oubliez pas de partager le lien .

Si vous êtes dans le coin ou de passage près de Lausanne (Suisse, VD), n'hésitez pas à nous rejoindre pour les workshops d'extraction d'ADN et d'analyse sensorielle, ou pour partager votre passion pour la bière avec nous. 

Le projet vous plait ? N'hésitez pas à le financer ! Pour conclure, une petite vidéo explicative et humoristique du projet permet de mieux comprendre comment nous allons produire et analyser les données.

Merci aux relecteurs : Yoann M.m4rsuMathurin

L'abus d'alcool est dangereux pour la santé, à consommer avec modération.


Twitter, arme de communication massive et outil scientifique

$
0
0
twitter logo Pirate

CC-BY Jonathan Sobel

Twitter est un réseau social incontournable. Son principe est simple, il s'agit de s'échanger avec ses "followers", des messages ou "tweets" de 140 charactères maximum. Il est surtout utilisé aux Etats-Unis, mais la fièvre Twitter gagne peu à peu l'Europe.  De nombreux scientifiques, chefs de labos, doctorants et même des institutions informent par ce biais. En créant votre compte vous pourrez avoir un accès direct à toutes ces personnes (en tant que suiveur) et vous pourrez leur poser des questions au moyen de messages directes (DM).

La communication scientifique se fait de plus en plus au moyen de blogs ainsi qu’à travers les réseaux sociaux, à tel point que des professeurs d’université, comme Marc Robinson-Rechavi (@marc_rr), proposent aujourd’hui des formations aux nouveaux moyens de communication numériques (blogs et réseaux sociaux). Ils permettent de diffuser les avancées scientifiques, de discuter des questions éthiques, de détecter les fraudes ou les erreurs dans les articles scientifiques, et de combattre la pseudo-science ou le créationnisme.

D’autres chercheurs, comme Marcel Salaté (@marcelsalathe) , utilisent Twitter pour étudier les réactions sur les réseaux sociaux, face aux campagnes de vaccination des populations, par exemple. Cette nouvelle discipline scientifique se nomme l’épidémiologie digitale. Dans une de ses études sur la population américaine, Marcel Salaté montre une corrélation entre le nombre de réactions négatives face aux vaccins sur Twitter et le nombre de malades de la grippe. Résultat: moins les gens sont favorables à la vaccination, plus le nombre de malades lors de l’épidémie annuelle est élevé.

Organiser sa veille sur Twitter

De manière générale, lorsqu’on souhaite se tenir informé des derniers sujets "chauds", il faut utiliser quelques outils, comme ceux présentés dans un article précédent.

Dans un premier temps, il faut créer son compte en remplissant un simple formulaire. Vous pouvez ensuite vous enregistrer (login), puis suivre des personnes ayant les mêmes intérêts que vous. Pour ce faire, le champ de recherche par mot clé ou hashtag (#bioinformatics, par exemple) est à votre disposition. Il est aussi possible de faire des listes de personnes pour avoir différents « fils d’informations » par sujet. La stratégie est de suivre un maximum de profils  intéressants pour ne rater aucune information. Vous pouvez aussi “twitter” des messages sur vos sujets préférés en incluant des images, des adresses web etc. Pensez aussi au système de hashtag pour indexer vos tweets à un sujet ou encore inclure un compte que vous suivez comme @BioinfoFr. Le compte sera ainsi informé de votre mention par une notification.

Le bot, super-robot au service du programmeur

Un bot est un programme informatique “robot” qui va automatiser certaines tâches. Si vous avez un blog, vous pouvez faire en sorte que toute nouvelle publication que vous ajoutez apparaisse également automatiquement sur Twitter grâce au flux RSS. Plusieurs tutoriels permettent d'apprendre à programmer un bot avec Twitter en R, grâce au package “TwitteR”. Si vous n’aimez pas coder, il existe des applications pour créer un bot avec une interface graphique, comme IFTTT.

Par exemple, ISCB SC RSG Switzerland (@rsg_switzerland) possède un compte qui publie automatiquement les articles de 14 blogs réputés de bioinformatique grâce aux flux RSS et IFTTT. Les bots sont nombreux sur Twitter, en voici une liste assez sympathique. Des comptes qui donnent des astuces en R sont ici: @R_Programming@Rbloggers@RLangTip@TextMining_r. En fouillant un peu, vous en dénicherez rapidement d'autres visant d'autres formes de langage ou sujets.

 @BioinfoFR passé au crible

Vous le savez désormais, le petit oiseau bleu est un super moyen de communiquer, de se faire connaître et de suivre ses pairs. Et pour analyser efficacement la quantité gigantesque des informations fournies par les utilisateurs, il est possible de récupérer les tweets par mot-clé ou par utilisateur. Il s'agit ensuite de faire une analyse du type forage de texte (textmining). On peut aller beaucoup plus loin en reconstituant des réseaux d'interactions de comptes (vous pourrez nous raconter vos explorations dans un prochain article!)

Voyons un exemple concret avec le compte Twitter du blog @BioinfoFr. Dans un premier temps, installez le package "TwitteR" sur R. Si vous utilisez d'autres langages, il existe une API twitter.

install.packages("twitteR")

Ensuite, procurez-vous vos données personnelles de développeur agréé (api_key, api_secret, access_token, access_token_secret) en suivant ce billet ou la doc de développement de Twitter.

Puis authentifiez-vous avec la commande suivante:

setup_twitter_oauth(api_key,api_secret,access_token,access_token_secret)

Récupérez les tweets du compte organisés en "dataframe" avec les commandes suivantes:

tweets = userTimeline("BioinfoFR", n = 300)
tweets.df = twListToDF(tweets)
dim(tweets.df)
head(tweets.df)

Dans cette table se trouvent plusieurs informations comme la date du tweet, le texte, le nombre de retweet, le nombre de favoris, la localisation GPS (si elle est active sur le compte), et s'il s'agit d'une réponse à un autre tweet ainsi que la personne à qui elle est adressée.

Pour savoir par exemple quels sont les tweets les plus repris, vous pouvez étudier le nombre de favoris et de retweets.

plot(jitter(tweets.df$favoriteCount,amount=0.5), jitter(tweets.df$retweetCount,amount=0.5),pch=19,col="darkgreen")

Retwitts vs favoris

En ajoutant un peu de bruit dans les données grâce à jitter, il est possible d'observer de plus près ce qui se passe. Ici, le nombre de retweets et de favoris est assez faible. Amis lecteurs, vous êtes peu sur Twitter: créez votre compte!

Pour savoir quels sont les tweets les plus repris et les favoris:

tweets.df$text[which(tweets.df$favoriteCount==max(tweets.df$favoriteCount))]
[1] "La #formation #bioinformatique de @UnivRennes1 en détails aujourd'hui : http://t.co/pmMtazpNDI #master #etudes #bretagne"

tweets.df$text[which(tweets.df$retweetCount==max(tweets.df$retweetCount))]
[1] "Aujourd'hui sur le blog nous rendons hommage à Frederick Sanger (1918-2013) http://t.co/4M2lTwNfob #RIPSanger"                          
[2] "Le voila le 1er article de la rentrée : RNA-seq, le mode d'emploi ! Bonne lecture : http://t.co/5akK5jj4Az #RNAseq #bioinfofr #rentrée"

L'on peut aussi s'amuser avec la date des tweets et observer comment leur nombre a évolué depuis le début du compte @BionfoFR.

daterange=c(as.POSIXlt(min(tweets.df$created)),as.POSIXlt(max(tweets.df$created)))
plot(tweets.df$created,rev(1:length(tweets.df$created)),type="l",xaxt="n",lwd=2,col="blue")
axis.POSIXct(1, at=seq(daterange[1], daterange[2], by="month"), format="%b")

Twittes en serie temporelle

Conclusion: nos admins sont très actifs, surtout en septembre 2014 et en janvier 2015.

Et à qui ont-ils le plus souvent répondu?

par(mar=c(5,10,5,2))

barplot(sort(table(tweets.df$replyToSN)),las=2,col="darkred",horiz=T)

Reponses Bioinfo-fr twitter

Parmi les noms, quelques habitués de Bioinfo-fr.net!

Conclusion

Twitter est un outil très performant qui peut non seulement vous permettre de vous tenir au courant des dernières nouveautés, mais représente aussi un moyen d'analyse assez pratique. Ce média social remporte un succès mondial, mais reste encore peu utilisé en Suisse (et en France?) dans le monde scientifique. Vous pouvez y passer des heures - attention, c'est très addictif! D'un autre côté, il peut représenter un formidable atout pour votre futur. Vous pouvez me suivre sur Twitter au compte @JonathanSobel1 , et répondre à la question suivante: que faisaient nos admins chéris en septembre 2014 et janvier 2015?

Un grand merci aux relecteurs: Yoann M., MtheG, hedjour et à notre admin de la semaine ZaZo0o.

Cours de R pour débutant pressé : les régressions !

$
0
0

Bonjour à tous et soyez les bienvenus dans ce 3ème cours de R pour débutant pressé.

Aujourd’hui, nous allons voir rapidement ce qu’est une régression (linéaire ou quadratique), à quoi ça sert et ce que ça peut nous apprendre sur nos données.

Ne vous êtes-vous jamais demandé comment en apprendre plus sur vos données, comment savoir quel paramètre est le plus important ou plus simplement s’il est possible « de faire une belle ligne sur mon graphique » ?

Non ? Ah… et bien merci à bientôt.

Si ? Ah c’était une blague… très bien, continuons alors.

Commençons par le début et demandons-nous ce qu’est une régression et pourquoi on en fait. Une régression c’est le fait de faire correspondre un modèle mathématique à nos données, de manière à estimer les paramètres de la régression, qui seront des approximations des paramètres de nos données réelles. Pour expliquer tous ces mots barbares, prenons un petit exemple théorique :

5123321886_d1dba055ef_o

Crédits : leg0fenris - Flickr (CC BY-NC-ND 2.0)

Disons que nous avons des données de mortalité du nombre d’ewoks en fonction du nombre TS-TT présents sur le champ de bataille d’Endor. Nous savons par exemple que pour 1 TS-TT, 10 ewoks meurent, pour 2 TS-TT, 20 ewoks meurent et pour 3 TS-TT, 30 ewoks meurent.

Sous R ça donne ça :

> tstt = c(1,2,3)
> ewoks = c(10,20,30)
> plot(ewoks~tstt)

La relation ici est assez simple, puisque ewoks = 10tstt

Passons maintenant à quelques explications mathématiques, mais promis, rien de bien difficile. Le nombre d’Ewoks (ewoks) est la variable que l’on cherche à expliquer, que l’on appelle en général Y. Le nombre de TS-TT (tstt) est une variable explicative (qui explique la variable à expliquer), que l’on appelle en général X, ou A. Aujourd’hui, nous n’entrerons pas dans des modèles à plusieurs variables explicatives (A, B, C), donc appelons-la X. Le modèle mathématique le plus simple et qui fonctionne parfaitement ici est donc :

Y = 0 + 10X

En effet, en mathématiques la relation la plus simple entre deux variables est :

Y = α + βX

La première question qui doit vous venir à l’esprit si vous vous êtes déjà retrouvés dans l’exercice périlleux de la rédaction d’article, de thèse ou de rapport, c’est : « ok, mais quand tu dis que ça fonctionne parfaitement, comment le prouves-tu ? ». Merci, c’est une très bonne question, qui me permet d’introduire notre meilleur ami dans R : lm.

lm est une commande R du package stats (inclus de base dans R), qui calcule les modèles correspondant à nos données et nous fournit quelques détails intéressants :

> lm(ewoks~tstt)

Call:
lm(formula = ewoks ~ tstt)

Coefficients:
(Intercept)         tstt
-4.102e-15   1.000e+01

Ah ouais, là, ça ne nous dit pas grand chose… en effet, en tapant la commande 

lm(ewoks~tstt)
 , tout ce qu’on fait c’est appeler la fonction. Ce qui nous intéresse c’est plutôt un résumé de cet appel :
> summary(lm(ewoks~tstt))

Call:
lm(formula = ewoks ~ tstt)

Residuals:
1         2         3
7.252e-16 -1.450e-15 7.252e-16

Coefficients:
Estimate Std. Error   t value Pr(>|t|)
(Intercept) -4.102e-15 2.713e-15 -1.512e+00   0.372
tstt         1.000e+01 1.256e-15 7.961e+15   <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.776e-15 on 1 degrees of freedom
Multiple R-squared:     1,     Adjusted R-squared:     1
F-statistic: 6.338e+31 on 1 and 1 DF, p-value: < 2.2e-16

Et hop, on obtient beaucoup d’informations intéressantes d’un coup ! Explications :

Call:
lm(formula = ewoks ~ tstt)

Tout d’abord le « Call » nous rappelle quel modèle a été appelé. En effet, si l’on place notre fonction dans une variable, on peut ne plus se souvenir de ce qu’on avait appelé, donc ça nous est rappelé.

Residuals:
1         2         3
7.252e-16 -1.450e-15 7.252e-16

Les « Residuals » sont les différences entre les valeurs données dans notre X et celles estimées par le modèle. Ici, on est dans les environs de 10-16. Je pense que vous conviendrez que ce n’est pas beaucoup.

Coefficients:
Estimate Std. Error   t value Pr(>|t|)
(Intercept) -4.102e-15 2.713e-15 -1.512e+00 0.372
tstt         1.000e+01 1.256e-15 7.961e+15  <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Les « Coefficients » sont les valeurs que le modèle estime, leur écart-type, leur t-value et la probabilité de rejeter l’hypothèse H0 pour laquelle le coefficient est égal à 0. Dans cet exemple, il n’y a pas de preuve statistique permettant de dire que l'ordonnée à l'origine (Intercept) est différente de 0, alors que le tstt est différent de 0 avec un test très significatif (***). C’est donc ici que l’on apprend que notre modèle est bien :

ewoks = 0 + 10tstt = 10tstt

A noter : le -4.102e-15 est devenu 0 car le test statistique n'est pas significatif (Pr(>|t|) = 0.372). Le 10 vient du 1.000e+01.

Residual standard error: 1.776e-15 on 1 degrees of freedom
Multiple R-squared:      1,     Adjusted R-squared:     1
F-statistic: 6.338e+31 on 1 and 1 DF, p-value: < 2.2e-16

Enfin, ici nous avons quelques informations pour savoir si notre modèle colle bien avec nos données. La « Residual standard error » est l’écart type des résidus, avec leur nombre de degrés de liberté. Les « Multiple R-squared » et « Adjusted R-squared » sont les coefficients de corrélation, plus ils sont proches de 1 et meilleur est notre modèle. Ici le modèle semble parfait ! Pour finir la « F-statistic » qui est un peu plus compliquée (et dont je ne maîtrise pas toutes les ficelles) mais qui a une chose que tout le monde comprend : une p-value ! Elle devra suivre en gros les codes significatifs habituels.

Donc au final, ce modèle a l’air de bien coller avec nos données 😉

On peut aussi évaluer visuellement notre modèle avec :

> layout(matrix(1:4, 2, 2))
> plot(lm(ewoks~tstt))

Ceci nous affiche 4 graphiques. Vu qu'on est pressés je ne vais pas entrer dans les détails, juste en dire quelques mots.

Capture d’écran 2015-07-15 à 10.30.24

plot(lm(ewoks~tstt)) Crédits : Aurélien C. (CC-BY-SA 3.0)

Ce qu’il faut savoir, c’est que les graphiques du haut (Residuals vs Fitted et Scale-Location) ne doivent pas donner de tendance claire (ne doivent pas aller tous croissants ou tous décroissants), que le normal Q-Q plot (page wikipedia) doit montrer des points répartis autour de la ligne pointillée et que les points la suivent à peu près, et enfin que le dernier ne doit pas montrer de point qui dépasse 1 en abscisse. Vous trouverez tout le détail nécessaire sur ce PDF, notamment en page 5.

Bon, en sciences on aime bien être bien bien bien sûrs des choses, alors on peut faire un AIC (page wikipedia) qui dit que plus sa valeur est basse, plus le modèle est bon et colle à nos données :

> AIC(lm(ewoks~tstt))
[1] -192.5675

-193 c’est très bon. Cependant, il est vrai qu'on utilise plutôt l'AIC en comparaison, en lui-même il n'est pas exploitable dans un article scientifique...

Introduisons ici le dernier élément dont nous allons nous servir aujourd’hui, l’ANOVA, qui va nous renseigner, quand on ne lui donne qu’un modèle, sur l’aspect significatif des variables explicatives :

> anova(lm(ewoks~tstt))
Analysis of Variance Table

Response: ewoks
Df Sum Sq Mean Sq   F value   Pr(>F)
tstt       1   200     200 6.3383e+31 < 2.2e-16 ***
Residuals 1    0       0
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Message d'avis :
In anova.lm(lm(ewoks ~ tstt)) :
les tests F d'ANOVA sur un ajustement pratiquement parfait ne sont pas fiables

On a encore une fois trois étoiles pour notre tstt, qui explique donc très bien le nombre d’ewoks morts. On a de plus quelque chose de très intéressant, le message d’avis, qui nous dit au final que nos ajustements sont pratiquement parfaits.

On peut s’en apercevoir quand on trace la ligne de régression sur notre plot précédent (je remets le code du plot, au cas où mais il n’est pas nécessaire si vous n’avez pas fermé votre fenêtre) :

> plot(ewoks~tstt)
> abline(lm(ewoks~tstt))

Capture d’écran 2015-07-01 à 14.23.44

Super !

On complique un peu l’affaire ? Parce que bon, je ne suis pas sûr que vous rencontriez un jour des données aussi faciles. Ni d’ewoks d’ailleurs. En revanche pour les TS-TT j’ai encore de l’espoir.

Alors disons maintenant que nos données ressemblent plus à ça :

> tstt = c(10, 20, 30, 40, 50)
> ewoks = c(1000, 900, 600, 300, 100)

Où ewoks est cette fois le nombre d’ewoks survivants. Plaçons cette fois notre modèle dans une variable :

> lm.linear = lm(ewoks~tstt)

Le graphique est le suivant :

> plot(ewoks~tstt)
> abline(lm.linear)

Le résumé du modèle nous donne ceci :

> summary(lm.linear)

Call:
lm(formula = ewoks ~ tstt)

Residuals:
1         2         3         4         5
-6.000e+01 8.000e+01 2.000e+01 -4.000e+01 2.842e-14

Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 1300.00 66.33   19.6 0.00029 ***
tstt         -24.00 2.00   -12.0 0.00125 **
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 63.25 on 3 degrees of freedom
Multiple R-squared: 0.9796,           Adjusted R-squared: 0.9728
F-statistic:   144 on 1 and 3 DF, p-value: 0.001245

On voit cette fois que le modèle est :
ewoks = 1300 - 24tstt
On voit aussi que les coefficients de corrélation sont bons, que la p-value est assez faible. Si vous faites l’AIC (je vous laisse deviner comment faire), vous devez obtenir 59.10551, ce qui ne nous dit pas grand chose mais va être intéressant pour la suite.

Il est toujours intéressant de voir si un modèle plus complexe donne de meilleurs résultats, pour pouvoir justifier que l’on a eu raison de garder un modèle linéaire ou pas. Le niveau supérieur de complexité d’un modèle linéaire est un modèle dit polynomial, dans lequel on va ajouter à nos variables explicatives une puissance. Si vous voulez, c’est comme se dire que les solutions offertes dans le plan des puissances 1 ne sont pas suffisantes, alors on passe aux puissances 2. On peut aussi noter que notre α est en fait αX0, où X0 = 1, donc les solutions du plan de puissance 0. L’équation est la suivante :
Y = α + βX1 +γX2

Pour faire cela dans R, on va utiliser la commande :

> lm.polynomial = lm(ewoks~tstt+I(tstt^2))

Ici, on notera le

I()
 , qui est la fonction d’identité qui permet d’utiliser des termes dans le modèle incluant des symboles mathématiques normaux.

On peut faire un plot de la ligne de régression de ce modèle, mais vu que ce n’est pas une droite, il faut ruser un peu et calculer sa valeur à différents points. Pour cela on utilise les coefficients calculés :

> lm.polynomial

Call:
lm(formula = ewoks ~ tstt + I(tstt^2))

Coefficients:
(Intercept)         tstt     I(tstt^2)
1200.0000          -15.4286 -0.1429

> newtstt = seq(10, 50, 1)
> fit = 1200 + -15.4286 * newtstt + -0.1429 * newtstt ^2
> plot(ewoks~tstt)
> lines(newtstt, fit, lty=1)

Ça a l’air moins bien, n’est-ce pas ?
Les valeurs qu'on entre dans « fit » sont celles données en coefficients.
Je vous laisse regarder le plot du modèle, le résumé nous donne ceci :

> summary(lm.polynomial)

Call:
lm(formula = ewoks ~ tstt + I(tstt^2))

Residuals:
1       2       3       4       5
-31.429 65.714 -8.571 -54.286 28.571

Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 1200.0000   145.0123   8.275   0.0143 *
tstt         -15.4286    11.0509  -1.396   0.2975
I(tstt^2)     -0.1429     0.1807  -0.791   0.5120
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 67.61 on 2 degrees of freedom
Multiple R-squared: 0.9845,           Adjusted R-squared: 0.9689
F-statistic: 63.31 on 2 and 2 DF, p-value: 0.01555

Et on peut utiliser l’AIC et l’ANOVA pour comparer les deux modèles :

> AIC(lm.linear, lm.polynomial)
df     AIC
lm.linear     3 59.10551
lm.polynomial 4 59.74584

Les AIC sont très proches, en général on dit qu’il faut 2 de différence d’AIC pour justifier le passage à un modèle plus complexe et que 10 est une différence très forte. Ici il semble donc que le passage au modèle polynomial ne soit pas nécessaire et même qu’il est moins bon que le linéaire.

> anova(lm.linear, lm.polynomial)
Analysis of Variance Table

Model 1: ewoks ~ tstt
Model 2: ewoks ~ tstt + I(tstt^2)

Res.Df     RSS Df Sum of Sq     F Pr(>F)
1     3 12000.0
2     2 9142.9 1   2857.1 0.625 0.512

L'ANOVA nous dit que la différence entre les deux n’est pas significative (on ne peut pas rejeter l’hypothèse H0).

On a donc plusieurs tests qui prouvent qu’il vaut mieux rester au modèle linéaire, qui est d’ailleurs meilleur pour nos données.

Enfin et pour conclure, ces modèles nous permettent de prédire des données, grâce à la commande « predict ». Elle a besoin du modèle pour lequel on veut prédire des données, d’un vecteur des données à prédire et de l’intervalle de confiance désiré :

> newtstt = c(0,1,54,55)
> predict(lm.linear,data.frame(tstt = newtstt), level = 0.95)
1   2   3   4
1300 1276   4 -20

On sait donc qu’on avait au départ 1300 ewoks (pour 0 TS-TT, résultat 1), qu’un seul TS-TT en tue environ 24 (1300 - 1276, résultat 1 - résultat 2), et que c’est le 55ème TS-TT qui aura raison de ces sales petites bêtes (résultat 4) !

Evidemment, dans le monde rien n'est aussi simple et je pense que vos expériences ne le seront pas non plus. Il est toujours possible d'ajouter de la complexité et R est un outil suffisamment puissant pour vous suivre partout. On pourrait ici s'amuser à ajouter l'effet des Jedi ou des Wookies, ou encore de l'humidité de la planète sur les rouages ! 😉

---

Je tiens à remercier tous mes relecteurs : m4rsu, Gildas et Estel.

Je tiens aussi à préciser qu'aucun ewok n'a été maltraité durant la rédaction de cet article.

C'est l'enfeR.

$
0
0

Certains bio-informaticiens ne jurent que par R (j'en fais partie). Je suis amoureux de sa simplicité (sic), son élégance (re-sic), sa documentation et ses innombrables packages tous plus utiles les uns que les autres. Et surtout c'est le seul langage que je maîtrise un peu convenablement, alors forcément je trouve tous les autres langages nuls, en toute objectivité.

Et pourtant R est universellement reconnu comme étant un langage de programmation ésotérique. Il a fallu tout le talent de Patrick Burns et de son livre pour que j'ouvre enfin les yeux et découvre qu'en fait R, c'est l'enfer.

Descendez donc avec moi dans des profondeurs abyssales au travers de quelques exemples librement inspirés de the R inferno.

C'est l'enfeR, certes, mais moins que Python. :-p

The R Inferno

R ne sait pas calculer

Définissons un joli vecteur :

> myVect <- c(0.1/1, 0.2/2, 0.3/3, 0.4/4, 0.5/5, 0.6/6, 0.7/7)

Mais à quoi ressemble-t-il ?

> myVect
[1] 0.1 0.1 0.1 0.1 0.1 0.1 0.1

Un bien beau vecteur. Vraiment. R saura-t-il trouver quelles valeurs sont égales à 0.1 ?

> myVect == 0.1
[1]  TRUE  TRUE FALSE  TRUE  TRUE FALSE FALSE

Heu, bien joué R. Quatre bonnes réponses sur sept, c'est... encourageant. Surtout pour un programme de statistiques, très populaire, et très utilisé en bioinfo.

Reprenons :

> 0.1 == 0.1
[1] TRUE
> 0.3/3
[1] 0.1
> 0.3/3 == 0.1
[1] FALSE

Mais pourquoi donc ?

Le problème des nombres à virgule, c'est que la partie après la virgule peut être infinie, alors que la mémoire des ordinateurs ne l'est pas. Du coup, les ordinateurs décident plutôt de ne garder que quelques (dizaines de) nombres après la virgule, et d'oublier les autres. Ils font donc plein de petites erreurs de calcul débiles. Par défaut, R n'affiche pas tout ce qu'il sait d'un nombre, mais on peut lui demander d'en afficher plus (le nombre maximum de

digits
  peut dépendre de votre configuration) :
> print(0.1, digits = 22)
[1] 0.10000000000000001
> print(0.3/3, digits = 22)
[1] 0.099999999999999992

Et l'on voit bien en effet que pour notre langage préféré, 0.1 n'est pas égal a 0.3/3. Youpi. C'est génial.

Comme en fait c'était quand même gravos, les concepteurs de R, dans leur infinie sagesse, ont décidé de faire une fonction qui donne le résultat attendu, la mal nommée

all.equal
. Cette fonction compare (entre autre) si la différence entre deux nombres est moins importante qu'une petite 
tolerance
.
> all.equal(0.3/3, 0.1)
[1] TRUE

Quelques détails :

  • le paramètre
    tolerance
      vaut par défaut 1,5.10-8.
  • Les concepteurs de R ont décidé que
    all.equal
      ne retournerait pas
    FALSE
    (parce que). Du coup, comme ils étaient quand même bien embêtés, ils ont créé la fonction
    isTRUE
     :
    > all.equal(1, 2)
    [1] "Mean relative difference: 1"
    > isTRUE(all.equal(1, 2))
    [1] FALSE
  • Pour rendre le tout vraiment infernal,
    all.equal
      n'est évidemment pas vectorisée, débrouillez-vous comme ça.
    > all.equal(myVect, 0.1)
    [1] "Numeric: lengths (7, 1) differ"

R, le maître troll des langages de programmation...

R est en moyenne assez peu cohérent

Les fonctions

min
  et
max
  retournent respectivement le minimum et le maximum d'une série de nombres :
> min(-1, 5, 118)
[1] -1
> max(-1, 5, 118)
[1] 118

Jusque là, tout va bien. La fonction

mean
est utilisée pour trouver la moyenne :
> mean(-1, 5, 118)
[1] -1

...

Je pense que R devrait prendre quelques leçons de statistiques.

Sait-il calculer une médiane ?

> median(-1, 5, 118)
"Error in median(-1, 5, 118) : unused argument (118)"

Apparemment pas, mais au moins nous signale-t-il gentiment que quelque chose ne tourne pas rond.

Mais pourquoi donc ?

En fait, il vaut mieux utiliser ces fonctions sur des vecteurs, ce que tout le monde fait en pratique :

> min(c(-1, 5, 118))
[1] -1
> max(c(-1, 5, 118))
[1] 118
> mean(c(-1, 5, 118))
[1] 40.66667
> median(c(-1, 5, 118))
[1] 5

Notez tout de même que dans le premier cas, la fonction

mean
 :
  • ne retourne pas d’erreur.
  • ne retourne pas de warning.
  • retourne un résultat (faux) du type attendu (un nombre).

Quel machiavélisme ! Si l'utilisateur ne teste pas rigoureusement son code, il court droit à la catastrophe.

Le facteur numérique accidentel

Imaginons, par exemple, qu'un innocent vecteur numérique se retrouve par mégarde sous la forme d'un factor (par exemple, suite à une facétie des célèbres fonctions

read.table
  ou
data.frame
, et de leur non moins célèbre paramètre
stringsAsFactor
dont la valeur par défaut est à l'origine de la majorité de mes bugs R). L'utilisateur averti pourrait se dire en toute confiance : "Pas de panique, je connais la fonction
as.numeric
".

Qui aurait pu imaginer qu'un tel désastre était imminent ?

> myVect <- factor(c(105:100, 105, 104))
> myVect
[1] 105 104 103 102 101 100 105 104
Levels: 100 101 102 103 104 105
> myVect >= 103
[1] NA NA NA NA NA NA NA NA
"Warning message:
In Ops.factor(myVect, 103) : ‘>=’ not meaningful for factors"
# Haha, je connais la fonction as.numeric ! Tu ne m'auras pas comme ça, R !
> as.numeric(myVect) >= 103
[1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE

-_-

Haha ! Je connais la fonction as.numeric !

Mais pourquoi donc ?

as.numeric
ne fonctionne pas comme vous le pensez sur les factors :
> as.numeric(myVect)
[1] 6 5 4 3 2 1 6 5

Du coup, il faut passer par un p'tit coup de

as.character
 :
> as.numeric(as.character(myVect))
[1] 105 104 103 102 101 100 105 104

Ou alors, si vous êtes pédant :

> as.numeric(levels(myVect))[myVect]
[1] 105 104 103 102 101 100 105 104

L'arrondi du coin

Parfois, il est bien utile d’arrondir un peu tous ces nombres compliqués avec plein de virgules. La fonction

round
fait, en général, assez bien son travail :
> round(0.2)
[1] 0
> round(7.86)
[1] 8

Il y a toujours cette petite hésitation quant à savoir comment sera arrondi

0.5
 , en
0
  ou en
1
  ?
> round(0.5)
[1] 0

OK. R fait donc partie de ces langages qui arrondissent

X.5
  à l'entier inférieur. Pourquoi pas. Ce n'est pas ce qu'on m'a appris à l'école, mais bon, pourquoi pas ?
> round(1.5)
[1] 2

Hein ?! Mais R enfin, qu'est-ce que tu fais ? C'est trop te demander d’être cohérent pendant deux lignes ?

> 0:10 + 0.5
 [1]  0.5  1.5  2.5  3.5  4.5  5.5  6.5  7.5  8.5  9.5 10.5
> round(0:10 + 0.5)
 [1]  0  2  2  4  4  6  6  8  8 10 10

♪♫ Lalala ♫♪ Je suis R. Je fais ce que je veux. ♪♫ Lalala ♫♪ J’arrondis

X.5
  à l'entier pair le plus proche si je veux. ♪♫ Lalala ♫♪

R

Mais pourquoi donc ?

Si on arrondit un grand échantillonnage de...
Si on réfléchit bien, on s’aperçoit que...
...potentiel biais dans...
Et bien en fait...
Les auteurs de R ont...

Heu, bon, passons.

Une matrice dans un tableau

Il est possible d'inclure une 

matrix
  comme colonne d'un
data.frame
 . Comparez donc:
> myMat <- matrix(1:6, ncol = 2)
> myMat
     [,1] [,2]
[1,]    1    4
[2,]    2    5
[3,]    3    6
> myDF1 <- data.frame(X = 101:103, Y = myMat)
> myDF2 <- data.frame(X = 101:103)
> myDF2$Y <- myMat
> myDF1
    X Y.1 Y.2
1 101   1   4
2 102   2   5
3 103   3   6
> myDF2
    X Y.1 Y.2
1 101   1   4
2 102   2   5
3 103   3   6
> dim(myDF1)
[1] 3 3
> dim(myDF2)
[1] 3 2
> myDF1$Y
NULL
> myDF2$Y
     [,1] [,2]
[1,]    1    4
[2,]    2    5
[3,]    3    6

Mais pourquoi ?

You will surely think that allowing a data frame to have components with more than one column is an abomination. That will be your thinking unless, of course, you’ve had occasion to see it being useful.

Patrick Burns

Vous penserez sûrement qu'autoriser des composants de plus d'une colonne dans un

data.frame
est une abomination. Vous penserez cela, à moins bien sûr que vous n'ayez eu l'occasion de voir cela mis en pratique de façon utile.

L’échantillonnage approximatif

La fonction

sample
  est bien pratique pour échantillonner au hasard un vecteur, avec ou sans remise :
> myVect <- c(2.71, 3.14, 42)
> sample(myVect, size = 5, replace = TRUE)
[1] 2.71 3.14 42.00 3.14 3.14

Le programmeur expérimenté se dira que ce bout de code a l'air dangereux dans le cas où

myVect
  est vide. En effet, mais R a la décence de nous prévenir :
> myVect <- numeric(0)
> sample(myVect, size = 5, replace = TRUE)
"Error in sample.int(length(x), size, replace, prob) :
  invalid first argument"

Le cas où

myVect
ne contient qu'un unique élément semble bien moins problématique (hahaha ! Bande de naïfs) :
> myVect <- 3.14
> sample(myVect, size = 5, replace = TRUE)
[1] 3 4 2 2 3

Notez bien :

  • Pas de message d’erreur.
  • Pas de warning.
  • Un résultat sous la forme de 5 nombres, comme attendu.

Pourtant, ce n'était peux-être pas ce que vous désiriez. Je blêmis à l'idée du nombre de bugs non résolus causés par ce petit twist.

Mais pourquoi ?

Dans certains cas, c'est bien pratique de pouvoir faire

sample(100, 5)
et d'obtenir :
[1] 59 70 30 23 58

R se montre compréhensif, et essaye de nous prévenir dans les petites lignes de bas de page du manuel :

If

x
  has length 1, is numeric (in the sense of
is.numeric
 ) and
x >= 1
 , sampling via
sample
  takes place from
1:x
 . Note that this convenience feature may lead to undesired behaviour when
x
  is of varying length in calls such as
sample(x)
 .

help(sample)

Si

x
a une longueur de 1, est numérique (tel que défini par
is.numeric
) et
x >= 1
, l’échantillonnage via
sample
a lieu sur
1:x
. Notez bien que cette caractéristique peut aboutir à un comportement indésiré lorsque la longueur de
x
varie lors d'appels tels que
sample(x)
.

Noitaréti à l'envers

Si vous essayez d'écrire en douce une petite boucle

for
 , comme ça, ni vu ni connu (en général, quand on est pédant et qu'on fait du R, on préfère faire de la programmation fonctionnelle) (mais sinon, les boucles
for
ont la réputation d'être lentes en R. C'est assez faux. Elles ne sont pas plus lentes que le reste, du moment que vous ne faîtes pas grandir des objets dedans. Pensez à pré-allouer le résultat) (mais reprenons) :
> myTable <- data.frame(x1 = 1:5, x2 = letters[1:5])
> myTable
  x1 x2
1  1  a
2  2  b
3  3  c
4  4  d
5  5  e
> 
> for (i in 1:nrow(myTable)) {
+     message(paste("row:", i))
+     message(paste("x1:", myTable[i, "x1"], "x2:", myTable[i, "x2"]))
+ }
row: 1
x1: 1 x2: a
row: 2
x1: 2 x2: b
row: 3
x1: 3 x2: c
row: 4
x1: 4 x2: d
row: 5
x1: 5 x2: e

Que se passe-t-il si

myTable
  est vide ?
myTable <- data.frame(x1 = NULL, x2 = NULL)
> myTable
data frame with 0 columns and 0 rows
>
> for (i in 1:nrow(myTable)) {
+     message(paste("row:", i))
+     message(paste("x1:", myTable[i, "x1"], "x2:", myTable[i, "x2"]))
+ }
row: 1
x1:  x2:
row: 0
x1:  x2:

Une succession d'événements inattendue

Mais pourquoi ?

Et oui,

nrow(myTable)
  vaut
, et
1:0
  retourne
[1] 1 0
  ! Ça alors, c'est pas de chance ! Préférez donc
seq_len(nrow(myTable))
 .

Javascript, sort de ce coRps !

> 50 < "7"
[1] TRUE

Mais pourquoi ?

> 50 < as.numeric("7")
[1] FALSE
> "a" < "b"
[1] TRUE

Le message d’erreur le plus utile du monde

> i <- 1
> if (i == pi) message("pi!")
> else message("papi!")
Error: unexpected 'else' in "else"

Mais pourquoi ?

If you aren’t expecting 'else' in "else", then where would you expect it?
While you may think that R is ludicrous for giving you such an error message, R thinks you are even more ludicrous for expecting what you did to work.

Patrick Burns

Si tu ne t’attends pas à un 'else' dans "else", où donc t'attends-tu à en voir un, R ?
Vous penserez peut-être que c'est ridicule pour R de retourner un tel message d'erreur. R pense que vous êtes encore plus ridicule d’espérer que ce que vous aviez fait fonctionnerait.

En vrac

  • La fonction
    read.table
      retourne un
    data.frame
    , et pas une
    table
     . La fonction
    table
      retourne une
    table
    .
  • La fonction
    sort.list
      ne sert pas à trier les listes.
  • > seq(5:10)
    [1] 1 2 3 4 5 6

(╯°□°)╯︵ ┻━┻

Bonus

Votre collègue s'est absenté en laissant sa session R ouverte ? Profitez-en, soyez infernal ! Voici quelques petites commandes amusantes à taper dans sa session :

> F <- TRUE
> T <- FALSE
> c <- function(...) list(...)
> return <- function(x) x + 1
>
> # admirez le résultat :
> c(1, 5)
[[1]]
[1] 1

[[2]]
[1] 5

> double <- function(x) return(2 * x)
> double(10)
[1] 21

Le petit mot de fin

Tout ceci décrit le comportement de R 3.3.1. Il est possible que certaines incohérences soient résolues dans les prochaines versions. Cela me semble assez peu probable cependant, pour des raisons de rétro-compatibilité.
Vous ne l'aurez peut-être pas compris, mais ce n'est aucunement un article à charge. Il s'agit juste d'une mise en pratique de l’adage "qui aime bien châtie bien".
Si vous en redemandez, n'hésitez pas à remonter ma source : The R Inferno !

Merci à mes talentueux relecteurs Bebatut, Chopopope, Estel et Lroy !

dplyr et le génome humain

$
0
0

Introduction

Non, ne fuyez pas tout de suite, chers lecteurs, tout va s'éclaircir : dplyr, c’est plyr pour les data.frame (les tableaux de données). Attendez, j’y viens, plyr, c’est un package R pour appliquer (apply) des fonctions. Donc, dplyr (prononcez “diplir”), c’est un package R, pour appliquer des fonctions à un tableau de données.

Et ça, mes amis, c'est super cool.

Rendez-vous service : imprimez immédiatement cette feuille d’astuces le concernant (pdf, 0.5Mo). En couleur. En fait imprimez en 10, et donnez en 8 à vos collègues, et mettez en une chez vous, et une sur votre lieu de travail. dplyr est centré autour du concept de données propres (ou tidy) : chaque ligne est une observation, chaque colonne un paramètre observé. Finissons notre petite digression en évoquant broom, un package qui permet de rendre propres (tidy) certains retour de fonctions sales (messy) de R, via la fonction

tidy()
 .

Pour vous initier à dplyr, je vous propose une petite série d'exercices appliqués à notre génome à tous. Ici on ne parlera pas tant du génome dans sa définition moderne (ensemble des séquences d’ADN d’un organisme), mais dans une version plus étymologique, et plus désuète : ensemble des gènes d’un organisme (ici, l’humain).

Si vous n’en avez rien à faire de dplyr, ou a fortiori de R, ne fuyez pas pour autant : vous perdriez une occasion de mieux connaître votre génome !

GENCODE et l’annotation du génome humain

Nous allons utiliser les données d’annotations de GENCODE, un consortium pour l’annotation du génome humain et du génome murin, notamment utilisé par Ensembl.

Pour télécharger le dernier fichier d’annotation disponible, il suffit de se rendre sur leur site internet, et de sélectionner Data > Human > Current release. Lors de l’écriture de cet article, la dernière version est la 25, correspondant aux coordonnées génomiques GRCh38 (aussi appelé hg38). Plusieurs fichiers sont disponibles mais un seul suffira aujourd’hui, le premier : “Comprehensive gene annotation | CHR”, en version GFF3. Le lecteur curieux se demandera quelle est la différence entre GTF et GFF3 ? C’est compliqué. Le GTF est une variante mineure du GFF2. Le GFF3 est plus récent et mieux spécifié, nous allons donc utiliser cette version. Vous devez télécharger ce fichier (45Mo) si vous souhaitez reproduire chez vous l’analyse ci dessous (ce que je vous conseille !). Le fichier GFF3 est un fichier texte, délimité par des tabulations, dont les colonnes suivent un formatage défini.

Lecteur du futur, peut-être aurez-vous la chance de disposer d’un fichier d’annotation plus récent ? Deux choix s’offrent alors à vous : refaire l’analyse avec les jolies annotations du futur, vous pourrez alors comparer avec l’état des connaissances de 2017 et bien vous marrer ; ou bien télécharger la version que nous utiliserons ici (le lien devrait rester valide - 45Mo).

Pré-requis

Une connaissance basique de R, et notamment quelques notions de ggplot2, vous aidera à comprendre le code produit ici (pour s’initier à R, pourquoi ne pas commencer par ici, et pour ggplot2 par là ?). Je vais cependant commenter le code dans l'espoir de le rendre plus intelligible. Si vous souhaitez reproduire l’analyse en parallèle de votre lecture, vous aurez besoin d’une version de R un peu récente (j'utilise la  3.3.1 “Bug in your hair”), et de quelques packages. L'installation de ces derniers ne devrait pas être trop longue :

# packages à installer ----------------
# - depuis CRAN:
install.packages("dplyr")   # manipulation de données tabulaires
install.packages("readr")   # parseur de fichiers texte
install.packages("broom")   # nettoie après que R ait tout salit
install.packages("cowplot") # figures multi-panaux, inclus ggplot2
install.packages("svglite") # pour exporter les figures en svg

# - depuis Bioconductor:
source("https://bioconductor.org/biocLite.R")
biocLite("rtracklayer")  # parseur, notamment de fichiers GFF

Il vous faut aussi, malheureusement, pas mal de mémoire vive, je dirai au moins 3 ou 4Go pour R (j’ai écrit cet article sur un ordinateur ayant 8Go). Un des défaut majeur de R étant qu’il travaille essentiellement sur la mémoire vive… Oui, je sais, ça fait pas mal de défauts.

Analyse

Parsons le fichier GENCODE

Pour charger le fichier dans R, plutôt que d’écrire notre propre parseur, nous allons utiliser celui du package rtracklayer en utilisant la fonction

import()
 . Cette fonction retourne un objet du type GRanges, que nous allons convertir en data_frame via la fonction
as_data_frame()
  du package dplyr. Les data_frame, (aussi appelés tibble) (ou encore tbl_df) (oui, je sais) de dplyr sont légèrement différents des data.frame de R tout court : la méthode d’affichage (print) par défaut est plus sympa, et les row.names sont interdits !
setwd("F:/divers/moi/Bioinfo-fr") # changez votre chemin !

compiler::enableJIT(3) # Invocation de magie noire pour que R tourne plus vite
compiler::setCompilerOptions(suppressAll = TRUE) # Cachons la magie noir dans le... noir.

library(dplyr)
library(readr)
library(broom)
library(rtracklayer)
library(cowplot)
library(svglite)

# l'import peu prendre 5 minutes en fonction de votre configuration
# notez qu'on importe directement la version compressé .gz
gencode <- import("gencode.v25.annotation.gff3.gz", format = "GFF") %>%
    # import() retoune un objet GRanges, nous le convertissons en data_frame
    as_data_frame

%>%: le pipe R

Petite digression à propos du pipe :

%>%
 , inclus dans le package dplyr (et issus de magrtittr). Il s’agit de l’équivalent R du pipe
|
  de shell/bash. Il passe le résultat de la fonction de gauche comme premier argument (par défaut) de la fonction de droite :
# ainsi, au lieu d’écrire
fonction3(
    fonction2(
        fonction1(x)
    )
)

# on pourra écrire
fonction1(x) %>%
    fonction2 %>%
    fonction3

Le raccourcis

.
permet de définir la position de l’objet dans le cas où il n’est pas le premier argument :
fonction2(x1 = myData1, x2 = fonction1(myData2))
# pourra s’écrire:
fonction1(myData2) %>%
    fonction2(x1 = myData1, x2 = .)

Mes exemples bidons ne sont pas trop flatteurs, mais ce petit su-sucre syntaxique devient vite indispensable dès qu’on l'essaye. Il permet d’accomplir des tâches complexes sans multiplier les variables intermédiaires ou les fonctions nichées, et comme nous allons le voir, il se marie admirablement bien avec les fonctions du package dplyr, qui prennent toujours comme premier paramètre un tableau de données, et retournent toujours un tableau de données.

Un bien beau jeu de données

Examinons un peu le jeu de données tout juste chargé.

> object.size(gencode)
1609890024 bytes
> dim(gencode)
[1] 2577236      29
> gencode

seqnames start end width strand source type score phase ID gene_id gene_type gene_status gene_name level havana_gene Parent transcript_id transcript_type transcript_status transcript_name transcript_support_level tag havana_transcript exon_number exon_id ont protein_id ccdsid
chr1 11869 14409 2541 + HAVANA gene NA NA ENSG00000223972.5 ENSG00000223972.5 transcribed_unprocessed_pseudogene KNOWN DDX11L1 2 OTTHUMG00000000961.2 character(0) NA NA NA NA NA character(0) NA NA NA character(0) NA NA
chr1 11869 14409 2541 + HAVANA transcript NA NA ENST00000456328.2 ENSG00000223972.5 transcribed_unprocessed_pseudogene KNOWN DDX11L1 2 OTTHUMG00000000961.2 ENSG00000223972.5 ENST00000456328.2 processed_transcript KNOWN DDX11L1-002 1 basic OTTHUMT00000362751.1 NA NA character(0) NA NA
chr1 11869 12227 359 + HAVANA exon NA NA exon:ENST00000456328.2:1 ENSG00000223972.5 transcribed_unprocessed_pseudogene KNOWN DDX11L1 2 OTTHUMG00000000961.2 ENST00000456328.2 ENST00000456328.2 processed_transcript KNOWN DDX11L1-002 1 basic OTTHUMT00000362751.1 1 ENSE00002234944.1 character(0) NA NA
chr1 12613 12721 109 + HAVANA exon NA NA exon:ENST00000456328.2:2 ENSG00000223972.5 transcribed_unprocessed_pseudogene KNOWN DDX11L1 2 OTTHUMG00000000961.2 ENST00000456328.2 ENST00000456328.2 processed_transcript KNOWN DDX11L1-002 1 basic OTTHUMT00000362751.1 2 ENSE00003582793.1 character(0) NA NA
chr1 13221 14409 1189 + HAVANA exon NA NA exon:ENST00000456328.2:3 ENSG00000223972.5 transcribed_unprocessed_pseudogene KNOWN DDX11L1 2 OTTHUMG00000000961.2 ENST00000456328.2 ENST00000456328.2 processed_transcript KNOWN DDX11L1-002 1 basic OTTHUMT00000362751.1 3 ENSE00002312635.1 character(0) NA NA
chr1 12010 13670 1661 + HAVANA transcript NA NA ENST00000450305.2 ENSG00000223972.5 transcribed_unprocessed_pseudogene KNOWN DDX11L1 2 OTTHUMG00000000961.2 ENSG00000223972.5 ENST00000450305.2 transcribed_unprocessed_pseudogene KNOWN DDX11L1-001 NA basic OTTHUMT00000002844.2 NA NA c("PGO:0000005", "PGO:0000019") NA NA
chr1 12010 12057 48 + HAVANA exon NA NA exon:ENST00000450305.2:1 ENSG00000223972.5 transcribed_unprocessed_pseudogene KNOWN DDX11L1 2 OTTHUMG00000000961.2 ENST00000450305.2 ENST00000450305.2 transcribed_unprocessed_pseudogene KNOWN DDX11L1-001 NA basic OTTHUMT00000002844.2 1 ENSE00001948541.1 c("PGO:0000005", "PGO:0000019") NA NA
chr1 12179 12227 49 + HAVANA exon NA NA exon:ENST00000450305.2:2 ENSG00000223972.5 transcribed_unprocessed_pseudogene KNOWN DDX11L1 2 OTTHUMG00000000961.2 ENST00000450305.2 ENST00000450305.2 transcribed_unprocessed_pseudogene KNOWN DDX11L1-001 NA basic OTTHUMT00000002844.2 2 ENSE00001671638.2 c("PGO:0000005", "PGO:0000019") NA NA
chr1 12613 12697 85 + HAVANA exon NA NA exon:ENST00000450305.2:3 ENSG00000223972.5 transcribed_unprocessed_pseudogene KNOWN DDX11L1 2 OTTHUMG00000000961.2 ENST00000450305.2 ENST00000450305.2 transcribed_unprocessed_pseudogene KNOWN DDX11L1-001 NA basic OTTHUMT00000002844.2 3 ENSE00001758273.2 c("PGO:0000005", "PGO:0000019") NA NA
chr1 12975 13052 78 + HAVANA exon NA NA exon:ENST00000450305.2:4 ENSG00000223972.5 transcribed_unprocessed_pseudogene KNOWN DDX11L1 2 OTTHUMG00000000961.2 ENST00000450305.2 ENST00000450305.2 transcribed_unprocessed_pseudogene KNOWN DDX11L1-001 NA basic OTTHUMT00000002844.2 4 ENSE00001799933.2 c("PGO:0000005", "PGO:0000019") NA NA

1.6 Go ! O_O

2,5 millions de lignes !

29 colonnes !

Voilà un bien joli jeu de données. Presque un gros jeu de données, même !

Les types de gènes

Combien de gènes dans le génome humain : un par ligne ? 2.5 millions de gènes ? Sans doute pas : regardons d’un peu plus près la colonne type. Pour cela, nous allons produire un petit histogramme comptant l'occurrence de chaque valeur possible de type dans le tableau. Et là, surprise, pas besoin de dplyr : ggplot2 s’en sort très bien tout seul.

# le code minimal pour produire la figure qui nous intéresse est assez simple :
# on passe notre tableau de donnée en 1er paramètre de ggplot2
# puis on définit quelle colonne va donner quoi (axe x, y, z, contour, couleur, taille) dans aes()
ggplot(gencode, aes(x = type)) +
    # enfin, on choisit un geom
    # stat = "count" est inutile: c'est la valeur par défaut de geom_bar
    # donc, ggplot2 va compter l’effectif de chaque type de gènes
    geom_bar(stat = "count")
# mais il faut se rendre a l'évidence : la figure généré est moche: :(

# pour la rendre plus jolie, il nous faut alourdir notre code:
# trions les catégories dans un ordre a priori logique
fig1 <- ggplot(gencode, aes(x = factor(type, levels = rev(unique(type))))) +
    geom_bar(fill = "darkorange") +
    # affichons l’effectif sur chaque bar, avec le mot magique de ggplot2 ..count..
    geom_text(aes(label = ..count..), y = 10000, hjust = 0, stat = "count") +
    labs(x = "", y = "", title = "Gencode v25") +
    # renversons les axes
    coord_flip()
# exportons le plot
ggsave("figure1.svg", fig1 + theme(plot.margin = unit(c(5,8,1,1), "mm")), width = 18, height = 7, units = "cm", device = svglite)
# et libérons de la mémoire vive
rm(fig1)

Figure 1: décomposition du tableau d'annotation GENCODE. Beaucoup de lignes, beaucoup d’éléments.

Figure 1: décomposition du tableau d'annotation GENCODE. Beaucoup de lignes, beaucoup d’éléments.

GENCODE compte donc 58 037 gènes. C’est beaucoup, on entend plutôt parler de 20 ou 25 000 gènes d’habitude. Une petite division nous donnera un nombre moyen de 3,4 transcrits différents par gènes, et de 5,9 exons par transcrits. Il y a un peu plus de codons start que de codons stop. Plus ou moins lié, il y a un peu plus de régions non traduites en 5’ qu’en 3’. Nous trouvons aussi 117 codons stop qui en fait n’en sont pas, vu qu’ils codent pour une sélénocystéine, parce que le code génétique universel, ha ha ha, laissez moi rire.

Il est temps de s'intéresser à la colonne gene_type (types de gènes), en restreignant l’analyse aux lignes du tableau de type gene uniquement.

# on ne guarde que les lignes du tableau dont le type est "gene"
fig2 <- filter(gencode, type == "gene") %>%
    # trions les types de gènes en fonction de leur effectif
    ggplot(aes(x = factor(
        gene_type,
        levels = names(sort(table(gene_type)))
    ))) +
    geom_bar(stat = "count", fill = "darkorange") +
    geom_text(aes(label = ..count..), y = 10, hjust = 0, stat = "count") +
    labs(x = "", y = "", title = "Le génome humain") +
    coord_flip()

Figure 2 : Nombre de gènes en fonction de leur types. Où l’on découvre que ça n'embête personne chez GENCODE de créer des nouveaux types de gènes pour UN SEUL gène.

Figure 2 : Nombre de gènes en fonction de leur types. Où l’on découvre que ça n'embête personne chez GENCODE de créer des nouveaux types de gènes pour UN SEUL gène.

GENCODE recense 19 950 gènes protéiques, un nombre plus raisonnable (même si il est un peu faible pour notre égo d'espèce ;-)). Pas mal de pseudogènes trainent un peu partout, ce qui ne fait pas très propre pour un génome d'espèce dominante. Notez aussi les nombreuses catégories de gènes en lien avec le système de recombinaison VDJ, parce que les immunologues se complaisent à noyer sous une nomenclature imbitable absolument tout ce qu’ils touchent. Détaillons quelques petites catégories mystérieuses :

  • antisense : “Has transcripts that overlap the genomic span (i.e. exon or introns) of a protein-coding locus on the opposite strand.” (Gène dont les transcrits chevauchent les introns et ou exons d’un gène protéique codé dans le brin opposé).
  • TEC : To be Experimentaly Confirmed: gène en attente de confirmation expérimentale. Pauvres 1048 gènes putatifs auxquels personne ne semble vouloir s’intéresser. 🙁
  • scaRNA, sRNA, vaultRNA, scRNA, macro_lnc_RNA : parce que bon, il ne faudrait pas non plus mettre tous les gènes ARN dans le même sac. Il y a des différences importantes entre les différentes catégories de gènes ARN, donc il faut bien tout ranger soigneusement dans autant de petites cases que nécessaires. La classification fine des ARN, c’est du sérieux, du travail d’expert. D'orfèvre, dirais-je même. Ne mélangeons pas tout, un peu de rigueur. Quoi la catégorie misc_RNA (de miscellaneous: divers en Anglais) de 2213 membres ? J’entends mal. Mettre le vaultRNA, le scRNA, et le macro_lcnRNA dans la catégorie misc_RNA ? Nan.

Bref, c’est un peu le bazar cette classification, alors pour la suite de l’article,  faisons nous notre petite classification grossière (libre à vous de l'adapter pour mettre en évidence votre type de gène préféré !):

gencode$simple_gene_type <- gencode$gene_type
# si il y a pseudogene dans le nom, c'est un pseudogène
gencode$simple_gene_type[grepl("pseudogene", gencode$simple_gene_type)] <- "pseudogène"
# si il y a ARN dans le nom,  c'est un gène ARN
gencode$simple_gene_type[grepl("RNA", gencode$simple_gene_type)] <- "gène ARN"
# si c'est un autre type de gène, c'est un autre type de gène
gencode$simple_gene_type[!(gencode$simple_gene_type %in% c("protein_coding", "pseudogène", "gène ARN"))] <- "autre"
# en Français, c'est plus jolie
gencode$simple_gene_type[gencode$simple_gene_type == "protein_coding"] <- "gène protéique"
# trions par effectif, ça simplifiera le code des figures
gencode <- mutate(gencode, simple_gene_type = factor(
    simple_gene_type,
    levels = names(sort(table(simple_gene_type), decreasing = TRUE))
))

fig3 <- filter(gencode, type == "gene") %>%
    ggplot(aes(x = factor(simple_gene_type, levels = rev(levels(simple_gene_type))), fill = simple_gene_type)) +
    geom_bar(stat = "count") +
    geom_text(aes(label = ..count..), y = 100, hjust = 0, stat = "count") +
    labs(x = "", y = "", title = "Le génome humain") +
    coord_flip() +
    theme(legend.position="none")

Figure 3 : Nombres de gènes en fonction de leur type, mais des types plus simples, parce que sinon c’est trop touffu. Autant de gènes ARN que de pseudo-gènes… hum, je me demande si… heu, non, rien.

Figure 3 : Nombres de gènes en fonction de leur type, mais des types plus simples, parce que sinon c’est trop touffu. Il y a autant de gènes ARN que de pseudo-gènes… hum, je me demande si… heu, non, rien.

Et le premier qui vient me dire que certaines catégories que j’ai mis dans “autre” seraient mieux dans “gène ARN” peut aller se retaper cette analyse en Rust.

dplyr et l'évaluation non standard

dplyr, comme ggplot2, est un adepte de l'évaluation non standard des paramètres. Il faut donc indiquer le nom des colonnes sur lequel on travaille sans apostrophe, R cherchant alors cet objet d'abord au sein de l’environnement du tableau de données, avant de chercher dans l’environnement global. Un des inconvénients de ce comportement (oui, je sais.), c'est que si le nom de la colonne en question est dans une variable, la fonction risque de ne plus fonctionner. Il existe pour cela des versions à évaluation standard des paramètres pour chaque fonction de dplyr. Ces fonctions ont le même nom mais se finissent par "_". En résumé :

myVar <- "seqnames"

select(gencode, seqnames  ) # fonctionne
select(gencode, "seqnames") # erreur
select(gencode, myVar     ) # erreur

select_(gencode, seqnames  ) # erreur
select_(gencode, "seqnames") # fonctionne
select_(gencode, myVar     ) # fonctionne

L'origine des annotations

Mais d'où viennent toutes ces annotations de gènes ?

fig4 <- filter(gencode, type == "gene") %>%
    # la colonne source contient la source des annotations
    ggplot(aes(x = source, fill = simple_gene_type)) +
    geom_bar(stat = "count") +
    labs(x = "", y = "", title = "Origine des annotations", fill = "") +
    theme(axis.text.x = element_text(angle = 45, hjust = 1))

Figure 4: Source des annotations en fonction du type de gène. HAVANA: consortium d’annotation manuelle. Ensembl: annotation algorithmique.

Figure 4: Source des annotations en fonction du type de gène. HAVANA: consortium d’annotation manuelle. Ensembl: annotation algorithmique.

Je dois dire que je suis assez impressionné. L'annotation de presque tous les pseudogènes et “autre” gènes a été validée plus ou moins manuellement. Il ne reste qu’une moitié de gènes ARN prédite algorithmiquement, mais non validée manuellement. Du beau travail !

Répartition des gènes par chromosomes

Jusque là, dplyr ne nous a pas beaucoup servi, à peine à filtrer un tableau selon la valeur contenue dans une de ses colonnes. Nous allons maintenant monter un peu en complexité.

Par exemple, étudions la répartition de nos gènes sur nos chromosomes. Nous comparerons aussi la densité en gène de chaque chromosome. Pour cela nous récupérerons la longueur de chaque chromosome en lisant un petit fichier texte sur les serveurs de l'UCSC.

# création d'une table contenant le nombre de gènes par chromosome, pour chaque type simplifié de gènes
genes_by_chr <- filter(gencode, type == "gene") %>%
    # table() compte les effectifs pour chaque combinaison
    with(., table(simple_gene_type, seqnames)) %>%
    # tidy transforme l’affreux objet retourné par table() en un joli tableau
    tidy

fig5A <- ggplot(genes_by_chr, aes(x = seqnames, y = Freq, fill = simple_gene_type)) +
    # pour une fois, on ne veut pas stat = "count"
    geom_bar(stat = "identity") +
    labs(x = "", y = "", title = "Gènes par chromosomes") +
    theme(legend.position = "none", axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0.5))

# lecture du fichier texte directement depuis le serveur
chr_length <- read_tsv("http://hgdownload-test.cse.ucsc.edu/goldenPath/hg38/bigZips/hg38.chrom.sizes", col_names = FALSE) %>%
    # on nome les colonnes
    dplyr::rename(seqnames = X1, length = X2) %>%
    # on ne guarde que les chromosomes standards
    filter(seqnames %in% c(paste0("chr", 1:22), "chrX", "chrY", "chrM")) %>%
    # que l'on ordonne
    mutate(seqnames = factor(seqnames, levels = c(paste0("chr", 1:22), "chrX", "chrY", "chrM")))

fig5B <- ggplot(chr_length, aes(x = seqnames, y = length)) +
    geom_bar(stat = "identity", fill = "darkorange") +
    labs(x = "", y = "", title = "Taille des chromosomes (pb)") +
    theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0.5))

# fusion des deux tableau grâce a dplyr::left_join
genes_by_chr <- left_join(genes_by_chr, chr_length, by = "seqnames") %>%
    # création d'une nouvelle colonne: le nombre de gènes par mégabase
    mutate(perMb = Freq * 1E6 / length)

fig5C <- ggplot(genes_by_chr, aes(x = seqnames, y = perMb, fill = simple_gene_type)) +
    geom_bar(stat = "identity") +
    coord_cartesian(ylim = c(0, 25)) +
    # une facette par type de gènes
    facet_wrap(~simple_gene_type, ncol = 1) +
    labs(x = "", y = "", title = "Gènes par mégabase par chromosomes") +
    theme(legend.position = "none", axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0.5))

# assemblage des panneaux avec cowplot
# la zone fait 1 x 1, avec le point (0,0) en bas à gauche
fig5 <- ggdraw() +
    draw_plot(fig5A, x = 0  , y = 0.5, w = 0.5, h = 0.5) +
    draw_plot(fig5B, x = 0  , y = 0  , w = 0.5, h = 0.5) +
    draw_plot(fig5C, x = 0.5, y = 0  , w = 0.5, h = 1  ) +
    draw_plot_label(c("A", "B", "C"), c(0, 0, 0.5), c(1, 0.5, 1), size = 20)

Figure 5: Répartition des gènes entre les différents chromosomes. A. Nombre de gènes par chromosomes. B. Taille de chaque chromosome. C. Nombre de gènes par mégabase par chromosome. Le chromosome M est hors catégories, avec 785 gènes protéiques et 1449 gènes ARN par mégabase (il ne fait que 16.5 kilobases).

Figure 5: Répartition des gènes entre les différents chromosomes. A. Nombre de gènes par chromosomes. B. Taille de chaque chromosome. C. Nombre de gènes par mégabase par chromosome. Le chromosome M est hors catégories, avec 785 gènes protéiques par mégabase et 1449 gènes ARN par mégabase (il ne fait que 16.5 kilobases).

Remarquons que la densité en gènes protéiques est assez variable d’un chromosome à l’autre, le chromosome 13 est assez pauvre, le chromosome 19 bien plus riche. La densité en gènes ARN et en pseudogènes semble plus homogène. Il est amusant de comparer le destin du chromosome Y, à transmission uniquement paternelle, avec celui du chromosome M, à transmission uniquement maternelle. Le chromosome Y a perdu presque tous ses gènes, il ne reste en grande partie que des pseudogènes (mais sa densité en pseudogènes n’a rien de scandaleusement élevée par rapport aux autosomes), et des régions intergéniques, lui conférant une faible densité en gène. Le chromosome M, qui a également subi une perte de gènes au cours de son évolution, est cependant hors classe en terme de densité génique avec plus de 500 gènes protéiques par mégabase ! Il faut dire qu'il a commencé son histoire avec très peu de régions inter-géniques.

Saluons aussi le bel effort de nomenclature: les autosomes humains étaient censés être numérotés par ordre décroissant de leur taille. Malheureusement, l’estimation a eu quelques légers ratés : le chromosome 11 est plus long que le 10, le 20 plus long que le 19, et le 22 plus long que le 21. Rha, et c'est trop tard pour les renommer, maintenant !

Taille des gènes

Nous l’avons vu plus haut, il y a en moyenne 3.4 transcrits par gène, et 5.9 exons par transcrits. Rentrons dans le détail de ces distributions. Nous allons aussi nous intéresser au nombre de fois que l'annotation d'un gène a été modifié. Cette information est contenue dans la dernière partie du code Ensembl du gène. Par exemple, mon gène préféré, MBD2 (ENSG00000134046.11) a vu son annotation modifiée 11 fois.

fig6A <- filter(gencode, type == "transcript") %>%
    # group_by groupe le tableau de donnée en fonction de colonnes particuliéres
    # les fonction dplyr appelées aprés un group_by s'appliquent individuellement pour chaque groupe
    group_by(gene_id, simple_gene_type) %>%
    # en l'occurence, pour chaque gène, on demande le nombre de transcrit via la fonction n()
    summarise(n_transcript = n()) %>%
    # pour rendre le plot plus lisible, la valeur maximale sera 20 transcrits
    mutate(n_transcript = if_else(n_transcript > 20, 20L, n_transcript)) %>%
    ggplot(aes(x = n_transcript, fill = simple_gene_type)) +
    geom_bar() +
    scale_x_continuous(breaks = c(1, 10, 20), labels = c(1, 10, "\u2265 20")) +
    facet_wrap(~simple_gene_type, scales = "free_y", ncol = 1) +
    theme(legend.position = "none") +
    labs(x = "Nombre de\ntranscrits", y = "Nombre de gènes", title = "Transcrits\npar gènes")

fig6B <- filter(gencode, type == "exon") %>%
    mutate(exon_number = as.integer(exon_number)) %>%
    group_by(gene_id, simple_gene_type) %>%
    # cette fois, le nombre d'exon d'un gènes et définie comme le maximum du numéro d'exon de ce gène
    summarise(n_exon = max(exon_number)) %>%
    mutate(n_exon = if_else(n_exon > 20, 20L, n_exon)) %>%
    ggplot(aes(x = n_exon, fill = simple_gene_type)) +
    geom_bar() +
    scale_x_continuous(breaks = c(1, 10, 20), labels = c(1, 10, "\u2265 20")) +
    facet_wrap(~simple_gene_type, scales = "free_y", ncol = 1) +
    theme(legend.position = "none") +
    labs(x = "Nombre\nd'exons", y = "Nombre de gènes", title = "Exons\npar gènes")

fig6C <- filter(gencode, type == "gene") %>%
    mutate(
        # on extrait le numéro de version de l'annotation de chaque gène
        gene_version = strsplit(gene_id, ".", fixed = TRUE) %>%
            sapply(last) %>%
            as.integer()
    ) %>%
    mutate(gene_version = if_else(gene_version > 20, 20L, gene_version)) %>%
    ggplot(aes(x = gene_version, fill = simple_gene_type)) +
    geom_bar() +
    scale_x_continuous(breaks = c(1, 10, 20), labels = c(1, 10, "\u2265 20")) +
    facet_wrap(~simple_gene_type, scales = "free_y", ncol = 1) +
    theme(legend.position = "none") +
    labs(x = "Nombre de\nrévisions", y = "Nombre de gènes", title = "Révisions\npar gènes")

# plot_grid est une version simplifié de ggdraw(), dans lequel les plots sont rangés dans une grille
fig6 <- plot_grid(fig6A, fig6B, fig6C, labels = LETTERS[1:3], ncol = 3, label_size = 20, hjust = -3)

Figure 6: A. Nombre de gènes en fonction de leur nombre de transcrits. B. Nombre de gènes en fonction de leur nombre d’exon. C. Nombre de gène en fonction du nombre de fois où leur annotation a été corrigé.

Figure 6: A. Nombre de gènes en fonction de leur nombre de transcrits. B. Nombre de gènes en fonction de leur nombre d’exons. C. Nombre de gènes en fonction du nombre de fois où leur annotation a été corrigée.

Les graphes sont éloquents : les gènes protéiques ont souvent plusieurs transcrits différents annotés (plus de 1000 gènes protéiques ont plus de 20 transcrits différents annotés !), tandis que les autres catégories se contentent dans leur plus grande majorité d’un seul transcrit. Il en va de même pour le nombre d'exons.

Là où c’est un peu plus inquiétant, c’est quand on regarde le nombre de fois que l’annotation d’un gène a été modifiée par GENCODE: la majorité des gènes protéiques ont vu leur annotation modifiée plus de dix fois, tandis les gènes ARN, les pseudogènes et les “autres” gènes sont en général inscrits une fois dans GENCODE et plus personne n’y touche. Alors on peut se dire que :

  • GENCODE est exceptionnellement doué pour annoter des gènes ARN, pseudogènes et autre gènes, et du coup l’annotation est parfaite du premier coup.
  • GENCODE est exceptionnellement mauvais pour annoter des gènes protéiques, et du coup doit sans arrêt corriger toute les bêtises. Notons qu’ils ne sont pas très doués pour corriger non plus, vu que les corrections s’empilent les unes sur les autres. Notez aussi que nous ne somme qu'à la 25éme version de GENCODE, et que le nombre maximum de correction pour un gènes est donc 25…
  • Mais en fait, GENCODE se base sur le travail de la communauté. Oui, sur votre travail à vous tous. Et il faut le dire, autant pour travailler sur des gènes protéiques, il y a du monde, autant lorsqu’il faut travailler sur des gènes ARN, des pseudogènes et autres, il n’y a plus personne ! Peut-être devrions nous faire un effort.

Quelle est la taille moyenne de nos gènes ? Et bien, cela dépend de comment on la mesure. On peut définir la longueur totale du gène entre son site d'initiation de la transcription et son site de terminaison de la transcription, ou bien ne guarder que la somme de la longueur de ses exons (c'est à dire sans les introns). Nous allons faire les deux. Nous allons même calculer la distribution de la longueur des introns. C'est un peu plus complexe car il n'y a pas de ligne correspondant aux introns dans notre fichier d'annotation ! Mais vous allez voir qu'avec quelques fonctions dplyr, nous allons nous en sortir.

# la longueur exons + introns est triviale à obtenir grâce à la colonne width
fig7A <- filter(gencode, type == "gene") %>%
    ggplot(aes(x = width, fill = simple_gene_type, color = simple_gene_type)) +
    # geom_denisty plot la distribution des longueurs
    geom_density(alpha = 0.2) +
    # on fixe la même échelle pour tout les plots
    scale_x_log10(limits = c(10, 10000000)) +
    annotation_logticks(sides = "b") +
    labs(x = "Paire de bases", y = "Densité", title = "Longueur des gènes (exons + introns)", fill = "", color = "")

# pour la longueur des transcrits, on somme tout les exons de chaque transcrits
fig7B <- filter(gencode, type == "exon") %>%
    # on groupe par transcrits
    group_by(gene_id, transcript_id, simple_gene_type) %>%
    summarise(transcript_length = sum(width)) %>%
    # puis par gènes
    group_by(gene_id, simple_gene_type) %>%
    # et on prend la longueur du transcrit médian
    summarise(gene_length = median(transcript_length)) %>%
    ggplot(aes(x = gene_length, fill = simple_gene_type, color = simple_gene_type)) +
    geom_density(alpha = 0.2) +
    scale_x_log10(limits = c(10, 10000000)) +
    annotation_logticks(sides = "b") +
    labs(x = "Paire de bases", y = "Densité", title = "Longueur des gènes (exons seuls)", fill = "", color = "")

# la longueur des exons est triviale à obtenir
fig7C <- filter(gencode, type == "exon") %>%
    ggplot(aes(x = width, fill = simple_gene_type, color = simple_gene_type)) +
    geom_density(alpha = 0.2) +
    scale_x_log10(limits = c(10, 10000000)) +
    annotation_logticks(sides = "b") +
    labs(x = "Paire de bases", y = "Densité", title = "Longueur des exons", fill = "", color = "")

# pour la longueur des introns, il faut ruser !
fig7D <- filter(gencode, type == "exon") %>%
    # on groupe par transcrit
    group_by(gene_id, transcript_id, simple_gene_type) %>%
    # pour chaque transcrit, on trie bien chaque exons dans l'ordre
    arrange(start) %>%
    # nouvelle colonne qui contient le start de la ligne du dessous, cad de l'exon suivant,
    # via la fonction lead().
    # puis, dans le même mutate, nouvelle colonne qui contient la différence entre la fin
    # d'un exon, et le début de l'exon suivant.
    mutate(next_exon_start = lead(start), intron_length =  next_exon_start - end) %>%
    ggplot(aes(x = intron_length, fill = simple_gene_type, color = simple_gene_type)) +
    geom_density(alpha = 0.2) +
    scale_x_log10(limits = c(10, 10000000)) +
    annotation_logticks(sides = "b") +
    labs(x = "Paire de bases", y = "Densité", title = "Longueur des introns", fill = "", color = "")

fig7 <- plot_grid(fig7A, fig7B, fig7C, fig7D, labels = LETTERS[1:4], ncol = 1, label_size = 20)

Figure 7: distribution de la longueur des gènes (A), des transcrits après épissage (B), des exons (C) et des introns (D).

Figure 7: distribution de la longueur des gènes (A), des transcrits après épissage (B), des exons (C) et des introns (D).

La taille totale des gènes (exons + introns) est bien différente en fonction du type de gènes : les gènes protéiques sont les plus gros, suivit des "autres", des pseudogènes, et des gènes ARN et leur jolie distribution bimodale (quoi j'aurais du faire deux groupes de gènes ARN !?). La longueur des transcrits (soit la somme de la longueur des exons d'un gène) présente des différences bien moins marquées, même si on y voit toujours une belle distribution bimodale pour les gènes ARN. Pour ce qui est de la longueur des exons et des introns, les distributions sont quasi identiques pour chaque types de gènes. Les gènes protéiques sont donc plus gros car ils ont plus d'introns et plus d'exons que les autres (cf figure 6B), mais leurs exons et leurs introns ont une taille tout à fait standard.

Le modulo 3 des CDS

Chaque CDS (coding DNA sequence) possède sa petite ligne dans le fichier fournit par GENCODE. Les codons mesurant 3 nucléotides, il est peut-être intéressant de regarder le modulo 3 de la longueur des CDS ? Nous allons comparer le modulo 3 des CDS contenu dans chaque exons, mais aussi le modulo 3 des CDS pour chaque gène.

# on ne garde que les lignes de type "CDS"
fig8A <- filter(gencode, type == "CDS") %>%
    # nouvelle colonne contenant le modulo 3
    mutate(modulo_3 = width %% 3) %>%
    ggplot(aes(x = modulo_3)) +
    geom_bar(fill = "darkorange") +
    labs(x = "", y = "effectif", title = "Modulo 3 des\nCDS (exons)")

fig8B <- filter(gencode, type == "CDS") %>%
    # on groupe par transcrit
    group_by(gene_id, transcript_id, simple_gene_type) %>%
    # on somme la taille de chaque CDS pour chaque transcrit
    summarise(total_CDS = sum(width)) %>%
    mutate(modulo_3 = total_CDS %% 3) %>%
    group_by(gene_id, simple_gene_type) %>%
    # pour chaque gène, on prends la mediane des modulos 3 des transcrits
    # qu'on arrondit au plus bas en cas de chiffre non entier
    summarise(modulo_3 = floor(median(modulo_3))) %>%
    ggplot(aes(x = modulo_3)) +
    geom_bar(fill = "darkorange") +
    labs(x = "", y = "effectif", title = "Modulo 3 des\nCDS (gènes)")

fig8 <- plot_grid(fig8A, fig8B, labels = LETTERS[1:2], ncol = 2, label_size = 20)

Figure 8: modulo 3 des séquences codantes

Figure 8: modulo 3 des séquences codantes. A. au niveau des exons. B. au niveau des gènes.

La grande majorité des séquences codantes des gènes est divisible par trois. Ouf ! Tout va bien. Il reste quelques gènes dont la séquences codante est non divisible par trois. Il faudrait regarder plus en détail : pseudogènes ? Annotations d'isoformes non sens dégradées ? Ou bien erreurs d'annotations ?

Les séquences codantes des exons sont réparties presque équitablement entre les trois possibilité de modulo 3. Donc si un exon codant d'un gène est inséré dans l'intron d'un autre gène, il y a grossièrement deux chance sur trois de casser la phase du gène d'arrivé. Ce qui rend le ré-assemblage évolutif d'exons un poil compliqué. Le léger enrichissement en exons codant divisible par trois est sans doute lié à la présence de gènes dont la séquence codante est contenue dans un seul exons.

Les plus grandes (pseudo) familles de gènes

Dernière analyse de ce billet, intéressons nous aux pseudo-familles des gènes. Je ne vais pas faire ici de comparaison de séquences ou de domaines protéiques, mais une analyse beaucoup plus simple : regarder les gènes dont le nom est le même à l’exception du numéro final (par exemple MBD1, MBD2, MBD3, etc.). C'est bien entendu une analyse grossière, relevant bien plus de nos biais de nommage que d'une éventuelle relation paralogue.

fig9 <-  filter(gencode, type == "gene") %>%
    # on enléve les chiffres à la fin
    mutate(family_name = sub("[0-9]*$", "", gene_name)) %>%
    # on compte
    count(family_name, gene_type = gene_type) %>%
    # on ungroup avant de trier
    ungroup %>%
    arrange(desc(n)) %>%
    # on ne guarde que les familles de plus de 100 membres
    filter(n >= 100) %>%
    ggplot(aes(x = factor(family_name, levels = rev(family_name)), y = n, fill = gene_type)) +
    geom_bar(stat = "identity") +
    geom_text(aes(label = gene_type, y = 10), hjust = 0, color = "grey40", size = 3) +
    coord_flip() +
    labs(x = "", y = "Nombre de membres", title = "Pseudo-familles de gènes") +
    theme(legend.position = "none")

Figure 9: Noms de gènes les plus fréquents.

Figure 9: Noms de gènes les plus fréquents.

Nous découvrons donc que un gros tas de miRNA s'appellent MIR, des lincRNA qui s'appelent LINC, des snoRNA qui s'appellent SNORA et SNORD, quelle surprise ! Vous m'étonnez qu'avec une nomenclature pareille personne ne veuille travailler dessus... Les ARN Y sont apparemment assez nombreux, je ne les connaissais pas, mais Wikipedia si. C'est étonnement intéressant. Même histoire pour les ARN 7SK, vous connaissiez, vous ? Autres ARN dont j'ignorai l’existence et l'importance: les ARN SRP. Comme quoi, on ne nous dit pas tout.

Trois grands groupes de gènes protéiques ont plus de 100 membres ayant le même nom : les protéines à doigt de zinc (ZNF - zinc finger proteins), ou potentiellement autant de facteurs de transcription auquel personne ne s'est assez intéressé pour leur donner un nom moins générique, les protéines transmembranaires (TMEM - transmembrane protein), et enfin les protéines contenant une superhélice (CCDC - coiled coil domain containing protein).

Je le répète : ce classement par popularité du nom des gènes ne reflète pas l’effectif des familles de gènes. Il manque par exemple la famille des récepteurs olfactifs, dont les membres ont un nom différent en fonction de leur sous-famille.

Exercices

dplyr vous intrigue, et vous avez envie d'approfondir le sujet ? Voici quelques questions que vous pourriez résoudre. N'hésitez pas à partager votre réponse en commentaire !

  1. Sur quels chromosomes sont situés les 117 codons stop qui  codent pour une sélénocystéine ?
  2. Il y a t'il une différence notable entre la distribution des tailles des 5'UTR et celle des 3'UTR ? Quelle est la proportion de 5'UTR et de 3'UTR qui s'étendent sur plus d'un exons ?
  3. Pour chaque chromosome, quelle est la répartition des gènes codés sur le brin + et sur le brin - ? Notez-vous quelques cas extrêmes ? Il y a-t-il une différence entre types de gènes ?
  4. Quels sont donc ces gènes protéiques dont la longueur de la séquence codante n'est pas divisible par trois ?

Conclusion

J’espère que cet article vous aura appris quelques informations pertinentes sur l'état actuel de l'annotation des gènes du génome humain. Ce fichier d'annotation est une merveilleuse réussite collective, mais des zones d'ombres restent à être éclaircies par la communauté. Comment, en tant que civilisation, pouvons nous accepter que plusieurs milliers de gènes protéiques du génome humain restent non étudiés, alors que nous n'en possédons que 20 000 aux dernières nouvelles ?

Certains auront peut-être réalisé le potentiel de la combinaison dplyr + ggplot2 pour travailler sur des données tabulaires. En s'y mettant petit à petit, avec les feuilles d'astuces sous les yeux, c'est vraiment un plaisir. Le package data.table est une alternative populaire à dplyr. data.table est généralement plus rapide, avec une philosophie et une syntaxe différente. Le super auteur et relecteur Lelouar a reproduit cette analyse en utilisant data.table et en mesurant les temps d'execution, vous pouvez donc vous faire une idée en regardant ces deux notebook Jupyter.

Lors de la rédaction de cet article, je suis tombé sur ce billet de blog en anglais, reproduisant certaines de ces analyses avec Python et Panda. Un de nos rédacteurs préférés a aussi extrait quelques chiffres similaires en utilisant bash et quelques utilitaires.

Milles merci aux relecteurs et reléctrice Lelouar, Akira et Bebatut ! <3 <3 <3

 

L’article dplyr et le génome humain est apparu en premier sur bioinfo-fr.net.

Packrat ou comment gérer ses packages R par projet

$
0
0
  • Qui ne s'est jamais retrouvé coincé entre deux projets R utilisant deux versions différentes d'un même package ?
  • Qui n'a jamais eu cette idée folle, un jour d'inventer un cas d'école (via R) qu'il souhaitait partager ?
  • Qui n'a jamais eu à chercher quelle version de package est nécessaire avec un code récupéré d'un collègue pour qu'il fonctionne comme celui du dit collègue ?
  • Qui n'a jamais installé nombre de packages dans sa librairie pour divers projets et n'a jamais osé les désinstaller par peur que des projets ne fonctionnent plus ?
  • Qui n'a jamais mis à jour un package dans un projet pour qu'il fonctionne, et ainsi cessé de faire fonctionner un autre projet ?
  • Qui n'a jamais mis à jour par erreur un package et involontairement TOUTES ses dépendances avec la même conséquence que ci-dessus ?

 

Je vais m'arrêter là, je pense que vous avez compris que la gestion de packages sous R est une source d'erreurs faciles. Mais pas d'inquiétude :

Packrat fait tout ça , Packrat est simple, Packrat vous veut du bien !

Packrat c'est aussi un joli petit rat des bois
Crédit : California Department of Fish and Wildlife - https://flic.kr/p/AUBcSL

 

Packrat ? Kézako ?

Packrat c'est un petit package R.

"Encore un ?!" vous allez me dire, oui mais il va vous simplifier la vie car il va rendre vos projets R :

    • Indépendants : installer un nouveau package ou le mettre à jour dans votre projet ne va pas impacter tous vos autres projets R (qu'ils aient été développés avec packrat ou non)
    • Portables : votre projet sera déplaçable/partageable d'un poste à un autre, et même d'un système d'exploitation à un autre (compilation différente vous me dites ? Kein problem fräulein ! / T'inquiète paupiette). Les installations des packages nécessaire à votre projet seront facilitées.
    • Reproductibles : packrat vous crée une sorte de sandbox ("un bac à sable", ou "un environnement isolé" pour les puristes) que vous pouvez relancer n'importe où, n'importe quand pour retrouver votre projet comme vous l'aviez laissé (et donc exactement les mêmes réultats) ! Il vous sauvegarde votre environnement et vous le remet à disposition dès que demandé.

 

Mais ! Mais ! Pourquoi tu ne fais pas un environnement virtuel, une VM ?

Eh bien pour plusieurs raisons :

  • Pas besoin de devoir connaître d'autres langages, outils logiciels. Connaître R suffit
  • Actuellement, et à ma connaissance, il n'y a que Conda qui gère les environnements R
  • Installer une VM pour un petit projet R c'est quand même sortir la mitrailleuse pour tuer un moustique

 

 

Comment ça marche ?

Si vous êtes comme la plupart des gens organisés (hep hep hep ! Revenez même si vous ne l'êtes pas), vous créez un répertoire pour chacun de vos nouveaux projets R où vous rangez tout vos scripts et vos sources (et si vous ne le faisiez pas, commencez). Eh bien Packrat, à son lancement, va se servir de ce répertoire pour en faire une sandbox avec votre librairie dédiée à l'intérieur et un ensemble de fichier et répertoires nécessaire pour exploiter correctement toutes les fonctionnalités :

  • packrat/packrat.lock : liste de tous vos packages dans le projet avec leur version, les versions des dépendances, les versions des dépendances de dépendances, ... Bref vous avez compris.
  • packrat/packrat.opts : spécifique à l'environnement d'exécution de votre projet Packrat, il liste toutes les options que vous avez définies (voir les options disponibles plus bas)
  • packrat/.Rprofile : indique à R d'utiliser la librairie interne au projet lorsqu'on lance celui-ci depuis le répertoire du projet
  • packrat/lib/ : votre librairie interne (qui contient tout vos packages compilés à telle ou telle version et leurs dépendances compilées)
  • packrat/src/ : les sources de vos packages et de leurs dépendances

 

Mais ! Mais ! Pourquoi tu t'embêtes à prendre tes sources si tu as déjà tes packages compilés ?

Et bien tout simplement pour la portabilité dont je vous parlais plus haut, je m'explique : Packrat créé un snapshot ("un instantané" pour les puristes) de l'état de votre librairie. Il sauvegarde alors les sources des packages utilisés et la versions de ceux-ci. Par la suite lors du transfert de votre projet Packrat de Windows à Linux ou autre, vous aurez simplement à lancer une commande qui restaurera votre librairie (en recompilant les sources de vos packages de façon compatible à votre OS) dans son état initial et vous voilà prêt à poursuivre votre travail !

 

Et en pratique on fait comment ?

En théorie on va lire la documentation[1], mais comme je suis sympa je vous aide.

1ère étape : installer Packrat

Pour ça rien de plus classique :

> install.packages("packrat")

Note : dans le cas où vous souhaiteriez être dans la dernière version sortie (beta et donc pas totalement stable), vous pouvez également installer la dernière version du dépôt github :

> install.packages("devtools")
> devtools::install_github("rstudio/packrat")

 

2ème étape : initialiser votre projet Packrat

Créez tout d'abord un répertoire selon le chemin[2] favori sur votre pc pour vos projets, puis dans R lancez :

> setwd("<leChemin>/PackratTest/")
> packrat::init(getwd())

    Initializing packrat project in directory:
    - "~/PackratTest"

    Adding these packages to packrat:
                _
        packrat   0.4.6-1

    Fetching sources for packrat (0.4.6-1) ... OK (CRAN current)
    Snapshot written to "<leChemin>/PackratTest/packrat/packrat.lock"
    Installing packrat (0.4.6-1) ...
	OK (downloaded binary)
    Initialization complete!

    Restarting R session...

> packrat::on()
    Packrat mode on. Using library in directory:
    - "<leChemin>/PackratTest/packrat/lib"

Félicitation, vous voilà dans un projet Packrat !

Mais ! Mais ! C'est nul ! Ça n'a rien changé ton truc !

Effectivement, pour le moment il n'y a pas de changement notable dans votre projet à part les fichiers que j'ai cités avant. La force de ce package se révèle quand on commence à jouer avec les packages !

 

3ème étape : ajouter, supprimer, mettre à jour des packages

Par curiosité, regardez votre dossier

<leChemin>/packrat/lib/
 . Vous allez retrouver dedans votre sandbox compilée pour le système d'exploitation que vous utilisez, dans mon cas ce sera Windows comme vous pouvez le voir ci-dessous (rangez les couteaux à ce sujet, on trollera plus tard).

État de mon dossier de projet après une initialisation d'un projet packrat

 

Vous trouverez dedans l'ensemble des packages que vous allez ajouter par la suite dans votre projet. Pour le moment seul packrat est présent, les packages que vous ajouterez par la suite viendront se ranger ici.

Appliquons maintenant cela à un cas pratique.

  • Ajouter un package

Pour mon exemple je vais utiliser le package "e1071" (qui est utilisé pour la construction de modèles d'apprentissage supervisé de type Support Vecteur Machine ou SVM), mais n'importe lequel avec quelques dépendances ferait aussi bien l'affaire. Je débute donc classiquement par mon installation.

> install.packages("e1071", dependencies = TRUE)
also installing the dependencies ‘mlbench’, ‘randomForest’, ‘SparseM’, ‘xtable’

trying URL 'https://cran.rstudio.com/bin/windows/contrib/3.2/mlbench_2.1-1.zip'
Content type 'application/zip' length 1032847 bytes (1008 KB)
downloaded 1008 KB

trying URL 'https://cran.rstudio.com/bin/windows/contrib/3.2/randomForest_4.6-12.zip'
Content type 'application/zip' length 177253 bytes (173 KB)
downloaded 173 KB

trying URL 'https://cran.rstudio.com/bin/windows/contrib/3.2/SparseM_1.76.zip'
Content type 'application/zip' length 928883 bytes (907 KB)
downloaded 907 KB

trying URL 'https://cran.rstudio.com/bin/windows/contrib/3.2/xtable_1.8-2.zip'
Content type 'application/zip' length 710243 bytes (693 KB)
downloaded 693 KB

trying URL 'https://cran.rstudio.com/bin/windows/contrib/3.2/e1071_1.6-8.zip'
Content type 'application/zip' length 800579 bytes (781 KB)
downloaded 781 KB

package ‘mlbench’ successfully unpacked and MD5 sums checked
package ‘randomForest’ successfully unpacked and MD5 sums checked
package ‘SparseM’ successfully unpacked and MD5 sums checked
package ‘xtable’ successfully unpacked and MD5 sums checked
package ‘e1071’ successfully unpacked and MD5 sums checked

The downloaded binary packages are in
	<Chemin>\AppData\Local\Temp\RtmpIL8kez\downloaded_packages

> packrat::snapshot()

Adding these packages to packrat:
                 _
    SparseM        1.76
    e1071          1.6-8
    mlbench        2.1-1
    randomForest   4.6-12
    xtable         1.8-2

Fetching sources for SparseM (1.76) ... OK (CRAN current)
Fetching sources for e1071 (1.6-8) ... OK (CRAN current)
Fetching sources for mlbench (2.1-1) ... OK (CRAN current)
Fetching sources for randomForest (4.6-12) ... OK (CRAN current)
Fetching sources for xtable (1.8-2) ... OK (CRAN current)
Snapshot written to "<leChemin>/PackratTest/packrat/packrat.lock"

> packrat::status()
Up to date.

Comme vous pourrez le constater, e1071 n'est pas seul, il a ramené tout ses potes les dépendances avec lui comme on le lui a demandé ! Jetons maintenant un coup d’œil du côté de notre librairie packrat.

Répertoire src après l'installation d'un package

Tout les packages ont été installés et compilés pour notre OS courant. Et si on regarde du côté du dossier "src", on peut constater après la création du snapshot que les sources de chaque package ont été également téléchargées. On pourra donc aisément transporter notre projet et le recompiler pour un autre os au besoin.

Mais ! Mais ! Et si je veux ajouter des packages d'un dépôt externe à celui du CRAN ?

Pas de panique ! Vous ferez votre install.packages en précisant votre dépôt comme classiquement. Attention cependant à bien sélectionner la version source de votre packages, sans quoi vous vous retrouverez coincé pour l'exporter sur un OS différent.

Et si vous souhaitez directement ajouter un dépôt local, il existe une option qui le permet :

packrat::set_opts(local.repos = "path_to_repo")

 Prenez également garde dans ce cas à préciser un dépôt de packages source et non pas de packages compilés.

 

  • Supprimer un package

Il arrive que vous installiez un package par erreur (de "alr3" à "alr4" il n'y a qu'un pas, de 1), vient donc le besoin de retirer ce package de votre snapshot jusqu'ici tout propre. On voudra par exemple supprimer un package nommé "nnet" et supposément installé précédemment (package qui crée également des modèles d'apprentissage supervisé mais cette fois de réseaux de neurones) mais qui ne vous convient pas pour vos données.

> remove.packages("xtable")
Removing package from ‘D:/Documents/PackratTest/packrat/lib/x86_64-w64-mingw32/3.2.1’
(as ‘lib’ is unspecified)

Et là c'est le drame, un collègue qui vous parle en même temps (sagouin !), une rêverie sur votre futur prix Nobel, et vous supprimez le mauvais package (ici xtable) ! Regardons ce que nous raconte packrat pour nous consoler si on vérifie le statut de notre snapshot.

> packrat::status()

The following packages are tracked by packrat, but are no longer available in the local library nor present in your code:
           _
    xtable   1.8-2

You can call packrat::snapshot() to remove these packages from the lockfile, or if you intend to use these packages, use packrat::restore() to restore them to your private library.

Vous disposez donc d'une fonction de restauration de votre package perdu. Et si jamais vous souhaitiez réellement supprimer ce package, une mise à jour de votre snapshot permettra d'entériner cette décision (ce qui aura pour effet de supprimer la source dans le répertoire src).

 

Une astuce également intéressante pour la suppression de packages inutiles est de mettre simplement à jour de votre snapshot. Celui-ci en profite, si le cas se présente, pour vous signaler que certains des packages de votre librairie ne sont pas utilisés dans votre code. La commande clean vous permettra alors de supprimer ces packages devenus inutiles dans votre projet.

> packrat::status()
The following packages are installed but not needed:
             _
    nnet       7.3

Use packrat::clean() to remove them. Or, if they are actually needed by your
project, add `library(packagename)` calls to a .R file somewhere in your project.

 

  • Mettre à jour un package
De la même façon que vous installez un package, le besoin de mettre à jour un package se fera sûrement ressentir. Dans ce cas, effectuez votre mise à jour et pensez bien à faire un
packrat::snapshot
  afin de bien informer packrat que ce package n'est pas un intrus !
Une bonne pratique à ce sujet est de toujours faire un
packrat::status
  afin de vérifier l'impact de toute modification.

 

Mais ! Mais ! Ça devient franchement casse bonbon de faire ça à chaque fois !

C'est là qu'interviennent les options packrat (notre petit packrat/packrat.opts du début). Vous pouvez notamment programmer un auto snapshot d'un habile packrat::set_opts(auto.snapshot = TRUE).

Il existe plusieurs autres options mais je vous laisse le soin de RTFM [1] ;D

RTFM - xkcd - https://xkcd.com/293/

 

4ème étape : partager, exporter cet environnement et le restaurer

Vos voici donc, après peaufinage par vos petits soins, avec votre environnement R au complet pour exécuter les scripts que vous devez passer à votre collègue qui bosse sous un autre OS (Ubuntu par exemple, tssss quelle idée je vous jure :p). Cependant une étape reste à faire : le bundling, ou "empaquetage" en français.

Pour cela, pas la peine de se casser la tête, une fonction existe encore une fois pour cela (même si on ne va pas se le cacher, c'est un simple gzip dans les grandes lignes) :

> packrat::bundle()
The packrat project has been bundled at:
- "<leChemin>\PackratTest\packrat\bundles/PackratTest-2017-03-21.tar.gz"
# Oui le mélange "/" et "\" c'est Windows, c'est cadeau ...

Et ainsi votre collègue tout content n'aura plus qu'à récupérer la jolie archive, installer Packrat, et à utiliser la fonction complémentaire à cette première pour récupérer un environnement à l'identique, à l'exception que les packages R auront été compilés pour son OS.

Résultat du unbundle

 

Pour conclure

Packrat se révèle être très pratique et rapide pour vos projets se servant majoritairement des packages du CRAN ou d'un dépôt extérieur. Mais on pourra lui reprocher le cas où c'est un package personnel que vous souhaitez joindre, il faut alors définir un dépôt local. Une autre limitation dans cette lignée sera le cas où les sources des packages sont absentes (oui oui même sur le serveur du CRAN), ce qui empêchera de restaurer/recompiler les sources pour un OS sans avoir  passer par la case téléchargement.

Pour ceux qui aiment utiliser Rstudio, sachez qu'une version built-in existe pour vous donner un côté plus graphique à la gestion de votre environnement.

Enfin, si j'ai abordé ici la majorité des fonctionnalités de ce petit packages bien pratique, sachez qu'il reste quelques subtilités que je vous laisse découvrir par vous même en lisant la documentation qui est claire et concise (donc pas d'excuse, 4 pages ça se lit vite, zou zou !)

 

 Références

[1]  https://rstudio.github.io/packrat/

[2]  https://www.youtube.com/watch?v=CLuOd8xMRRo

 

Merci aux relecteurs Yoann M., Mathurin et Zazo0o pour leur temps et leur œil à l’affût !

L’article Packrat ou comment gérer ses packages R par projet est apparu en premier sur bioinfo-fr.net.

Viewing all 33 articles
Browse latest View live