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

Chercher des motifs dans un fichier

$
0
0

Langage : shell
Commandes présentées : grep, split (succintement)
Niveau : débutant

Présentation de la commande grep

La commande grep est disponible nativement sur la plupart des systèmes d'exploitation GNU/Linux. La plupart des utilisateurs utilisent cette commande pour rechercher un mot ou un groupe de mots, que nous appellerons motif (pattern en anglais), dans un fichier texte. Cependant cette commande ne se limite pas à du simple cas par cas.
Grep recherche le motif en parcourant tout le fichier texte du début jusqu'à la fin. Ainsi, autant pour un fichier de quelques lignes, le résultat sera quasi immédiat, autant pour un fichier de plusieurs milliers de lignes le résultat peut être plus ou moins long.
Dans ce billet je vous présenterai les différentes façons dont je me sers régulièrement de grep, que ce soit de la simple recherche d'un mot clé à la recherche, plus ou moins complexe, de plusieurs motifs.

Exemple d'une commande grep sans chercher le motif exact et avec une colorisation. Auteur : Nolwenn. Image libre de droit.


Pour les exemples qui suivront, je me base sur la liste des informations sur les gènes fournies par Entrez Gene du NCBI. Si vous êtes sous Linux et que vous souhaitez reproduire les exemples présentés, vous pouvez saisir les commandes suivantes dans un terminal :

# wget : télécharge le fichier depuis une source externe
wget ftp://ftp.ncbi.nih.gov/gene/DATA/GENE_INFO/Mammalia/Homo_sapiens.gene_info.gz
# gunzip : décompresse le fichier au format gzip
gunzip Homo_sapiens.gene_info.gz
# mv : renomme le fichier pour une extension plus explicite
mv Homo_sapiens.gene_info Homo_sapiens.tsv

Méthode basique

La commande grep vous apporte un grand soutien dans la recherche de motif(s), vous l'utilisez déjà certainement de façon très basique comme dans cet exemple :

grep HLA-A Homo_sapiens.tsv
9606	3105	HLA-A	DAQB-90C11.16-002	HLAA	HGNC:4931|MIM:142800|Ensembl:ENSG00000206503|HPRD:00826|Vega:OTTHUMG00000130501	6	6p21.3	major histocompatibility complex, class I, A	protein-coding	HLA-A	major histocompatibility complex, class I, A	O	HLA class I histocompatibility antigen, A-1 alpha chain|MHC class I antigen HLA-A heavy chain|antigen presenting molecule|leukocyte antigen class I-A	20121110
9606	100873924	HLA-AS1	-	-	HGNC:42509	1	-	HLA antisense RNA 1	miscRNA	HLA-AS1	HLA antisense RNA 1O-	20121021

Premier constat : si le motif est compris dans un motif plus grand alors que vous ne souhaitez récupérer que les lignes qui correspondent exactement à ce motif, alors vous récupérerez toutes les lignes comprenant ce motif, y compris celles ne vous intéressant pas. Le bruit alors généré peut s'avérer problématique en fonction du nombre d’occurrences retournées !
Dans l'exemple présenté ci-dessus, vous pouvez constater que grep retourne le gène HLA-A et le gène HLA-AS1.

Rechercher un motif exact

Pour trouver le motif exact, vous pouvez jouer avec les options et plus particulièrement l'option -w (ou - -word-regexp). Exemple :

grep -w HLA-A Homo_sapiens.tsv
9606	3105	HLA-A	DAQB-90C11.16-002	HLAA	HGNC:4931|MIM:142800|Ensembl:ENSG00000206503|HPRD:00826|Vega:OTTHUMG00000130501	6	6p21.3	major histocompatibility complex, class I, A	protein-coding	HLA-A	major histocompatibility complex, class I, A	O	HLA class I histocompatibility antigen, A-1 alpha chain|MHC class I antigen HLA-A heavy chain|antigen presenting molecule|leukocyte antigen class I-A	20121110

Rechercher plusieurs motifs

Pour chercher plusieurs motifs, trois solutions s'offrent à vous :

  • faire une boucle sur la liste des motifs et faire des grep successifs ;
  • jouer avec les expressions régulières, chose que je vous recommande pour quelques motifs ;
  • passer en argument un fichier de motif, qui est plus recommandé si vous avec beaucoup de motifs à chercher.

Méthode de la boucle

Cette méthode est assez basique, je m'en suis servie dans mes tout premiers scripts shell, mais elle présente un gros inconvénient : le temps de calcul !
Bien que grep soit une commande rapide pour un motif, plus vous ferez de grep successifs et plus cela mettra de temps. Ainsi, si il ne faut qu'un dixième de seconde pour afficher le résultat pour un motif, faites le calcul pour la recherche de 100 motifs. Notez toutefois que, plus vous aurez de motifs à rechercher, plus il faudra du temps avant d'afficher les résultats.
Pour les plus débutants d'entre vous je vous présente une syntaxe à reproduire pour cette méthode, comme ça vous pourrez vous faire votre propre idée du temps de calcul en comparant avec les deux autres méthodes.

motifs=(HLA-A HLA-DRA ADD2 BCR1)
for motif in ${motifs[*]}; do grep -w $motif Homo_sapiens.tsv; done
9606	3105	HLA-A	DAQB-90C11.16-002	HLAA	HGNC:4931|MIM:142800|Ensembl:ENSG00000206503|HPRD:00826|Vega:OTTHUMG00000130501	6	6p21.3	major histocompatibility complex, class I, A	protein-coding	HLA-A	major histocompatibility complex, class I, A	O	HLA class I histocompatibility antigen, A-1 alpha chain|MHC class I antigen HLA-A heavy chain|antigen presenting molecule|leukocyte antigen class I-A	20121110
9606	3122	HLA-DRA	DASS-397D15.1	HLA-DRA1|MLRW	HGNC:4947|MIM:142860|Ensembl:ENSG00000204287|HPRD:00833|Vega:OTTHUMG00000031269	6	6p21.3	major histocompatibility complex, class II, DR alpha	protein-coding	HLA-DRA	major histocompatibility complex, class II, DR alphaO	HLA class II histocompatibility antigen, DR alpha chain|MHC cell surface glycoprotein|MHC class II antigen DRA|histocompatibility antigen HLA-DR alpha	20121110
9606	119	ADD2	-	ADDB	HGNC:244|MIM:102681|Ensembl:ENSG00000075340|HPRD:00037|Vega:OTTHUMG00000129710	2	2p13.3	adducin 2 (beta)	protein-coding	ADD2	adducin 2 (beta)	O	beta-adducin|erythrocyte adducin subunit beta	20121110
9606	613	BCR	-	ALL|BCR1|CML|D22S11|D22S662|PHL	HGNC:1014|MIM:151410|Ensembl:ENSG00000186716|HPRD:01044|Vega:OTTHUMG00000150655	22	22q11.23	breakpoint cluster region	protein-coding	BCR	breakpoint cluster region	O	BCR/FGFR1 chimera protein|FGFR1/BCR chimera protein|breakpoint cluster region protein|renal carcinoma antigen NY-REN-26	20121110

Méthode des expressions régulières

Pour chercher plusieurs motifs à l'aide des expressions régulières, vous allez devoir ajouter l'option -E (ou - -extended-regexp). Exemple :

grep -wE "HLA-A|BCR1" Homo_sapiens.tsv 
9606	613	BCR	-	ALL|BCR1|CML|D22S11|D22S662|PHL	HGNC:1014|MIM:151410|Ensembl:ENSG00000186716|HPRD:01044|Vega:OTTHUMG00000150655	22	22q11.23	breakpoint cluster region	protein-coding	BCR	breakpoint cluster region	O	BCR/FGFR1 chimera protein|FGFR1/BCR chimera protein|breakpoint cluster region protein|renal carcinoma antigen NY-REN-26	20121110
9606	3105	HLA-A	DAQB-90C11.16-002	HLAA	HGNC:4931|MIM:142800|Ensembl:ENSG00000206503|HPRD:00826|Vega:OTTHUMG00000130501	6	6p21.3	major histocompatibility complex, class I, A	protein-coding	HLA-A	major histocompatibility complex, class I, A	O	HLA class I histocompatibility antigen, A-1 alpha chain|MHC class I antigen HLA-A heavy chain|antigen presenting molecule|leukocyte antigen class I-A	20121110

Explication(s) de la ligne de commande : le caractère | est un caractère spécial utilisé dans les expressions régulières. Ce caractère indique à la commande grep qu'il doit trouver au moins un des deux motifs. Si on devait le formuler à un être humain, nous pourrions le traduire par : "dans le fichier Homo_sapiens.tsv, cherche moi toutes les lignes qui ont exactement le terme HLA-A ou le terme BCR1 et indique-les moi".

Méthode du fichier de motifs

Pour passer un fichier de motifs à la commande grep, vous devez passer par l'option -f (ou - -file=FILE).
Le fichier de motifs doit contenir un motif par ligne, ici, un extrait des gènes intervenant dans la glycolyse/glycogénogenèse chez l'homme (source KEGG) :

HK3
HK1
HK2
HKDC1
[..]
ADPGK
BPGM
PCK1
PCK2

Une fois votre fichier de motifs créé, vous pouvez le fournir à la commande grep qui gérera alors l'ordre dans lequel les motifs seront recherchés et trouvés :

grep -w -f "genes.txt" Homo_sapiens.tsv

Notez toutefois que plus votre fichier de motifs contient des motifs, plus la commande grep mettra du temps avant de vous retourner le résultat. Pour cela je vous conseille de vous orienter du côté de la commande split afin de découper le fichier de motifs en plusieurs fichiers comme dans l'exemple suivant :

split -l 30 genes.txt genes.split.
for gene in $( ls genes.split.* )
do
	grep -f $gene -w fichier.txt
	rm $gene
done

Cette méthode, dont j'ai trouvé la source sur ce blog en anglais, présente un bien meilleur avantage que celui de faire une boucle sur la liste des motifs avant de faire des grep successifs. Avec un fichier de motifs votre recherche sera plus rapide que si vous deviez faire 30 grep les uns à la suite des autres, et votre machine n'en sera que plus heureuse !
Il est vrai que sur l'exemple que je vous ai fourni ce n'est pas très parlant. Sur 65 gènes, grep -f ne met pas plus de temps qu'avec plusieurs fichiers. Cependant j'ai pu constater une importante différence, en terme de temps, sur une liste de 9 312 motifs, et ça, c'est sans contexte un must to know pour des motifs très nombreux.

Savoir sur quelle ligne apparait le motif

L'un des avantages que grep nous apporte, c'est la possibilité de connaître la ligne du fichier sur laquelle le motif a été trouvé. Pour cela vous devrez utiliser l'option -n comme dans l'exemple ci-dessous :

grep -w BCR1 Homo_sapiens.tsv -n

Sur mon système, une simple Crunchbang installée sous VirtualBox, voici le résultat que j'obtiens :

grep -w BCR1 Homo_sapiens.tsv -n
506:9606	613	BCR	-	ALL|BCR1|CML|D22S11|D22S662|PHL	HGNC:1014|MIM:151410|Ensembl:ENSG00000186716|HPRD:01044|Vega:OTTHUMG00000150655	22	22q11.23	breakpoint cluster region	protein-coding	BCR	breakpoint cluster region	O	BCR/FGFR1 chimera protein|FGFR1/BCR chimera protein|breakpoint cluster region protein|renal carcinoma antigen NY-REN-26	20121110

Le gène BCR1 a été trouvé sur la ligne 506. Et pour les amoureux des couleurs qui sont parmi vous, essayez l'option - -color si votre motif n'apparaît pas explicitement ;) !

Afficher 2 lignes avant et 2 lignes après le motif trouvé

Grep est également capable de vous permettre d'afficher un nombre de lignes avant ou après le motif une fois celui-ci trouvé, en voici un exemple :

grep -w BCR1 -B 2 -A 2
9606	610	HCN2	-	BCNG-2|BCNG2|HAC-1	HGNC:4846|MIM:602781|Ensembl:ENSG00000099822|HPRD:07213|Vega:OTTHUMG00000180590	19	19p13.3	hyperpolarization activated cyclic nucleotide-gated potassium channel 2	protein-coding	HCN2	hyperpolarization activated cyclic nucleotide-gated potassium channel 2	O	brain cyclic nucleotide gated channel 2|brain cyclic nucleotide-gated channel 2|potassium/sodium hyperpolarization-activated cyclic nucleotide-gated channel 2	20121110
9606	611	OPN1SW	-	BCP|BOP|CBT	HGNC:1012|MIM:613522|Ensembl:ENSG00000128617|HPRD:01836|Vega:OTTHUMG00000158311	7	7q32.1	opsin 1 (cone pigments), short-wave-sensitive	protein-coding	OPN1SW	opsin 1 (cone pigments), short-wave-sensitive	O	blue cone photoreceptor pigment|blue-sensitive opsin|short-wave-sensitive opsin 1	20121110
9606	613	BCR	-	ALL|BCR1|CML|D22S11|D22S662|PHL	HGNC:1014|MIM:151410|Ensembl:ENSG00000186716|HPRD:01044|Vega:OTTHUMG00000150655	22	22q11.23	breakpoint cluster region	protein-coding	BCR	breakpoint cluster region	O	BCR/FGFR1 chimera protein|FGFR1/BCR chimera protein|breakpoint cluster region protein|renal carcinoma antigen NY-REN-26	20121110
9606	616	BCRP4	-	BCR-4|BCR4|BCRL4	HGNC:1017|MIM:113660	22	22q11.22	breakpoint cluster region pseudogene 4	pseudo	BCRP4	breakpoint cluster region pseudogene 4	O	-	20121110
9606	617	BCS1L	-	BCS|BCS1|BJS|FLNMS|GRACILE|Hs.6719|PTD|h-BCS	HGNC:1020|MIM:603647|Ensembl:ENSG00000074582|HPRD:04708|Vega:OTTHUMG00000133114	2	2q33	BC1 (ubiquinol-cytochrome c reductase) synthesis-like	protein-coding	BCS1L	BC1 (ubiquinol-cytochrome c reductase) synthesis-like	O	BCS1-like protein|h-BCS1|mitochondrial chaperone BCS1|mitochondrial complex III assembly	20121110

Le mot de la fin

La commande grep, bien qu'étant une commande très informatique et non destinée au domaine de la bioinformatique, est un outil majeur dont nous nous servons quotidiennement. Elle s'avère être un outil puissant pour peu que l'on sache l'utiliser. De plus, si vous avez des notions sur les pipelines, vous pouvez constater qu'elle peut être utilisée à d'autres fins que la simple recherche de motifs, en combinaison avec d'autres commandes et/ou d'autres programmes.


Merci à Guillaume Collet et Yoann M. pour leur relecture, et nos discussions enrichissantes sur cette commande.


SQLite

$
0
0

Dans un précédent article, nous vous avons parlé des bases de données, leur importance et leur intérêt. Ici je vais vous parler de SQLite, une bibliothèque donnant accès à un moteur de base de données relationnelle qui vous permettra de travailler avec du SQL et cela sans avoir besoin de configurer ou d'installer quoi que ce soit: simple, rapide et efficace. Vous pouvez à loisir l'inclure dans tous vos projets, le code source de SQLite étant dans le domaine public.

Logo SQLite

Logo SQLite

Si vous voulez la définition complète de la technologie utilisée et des principales différences avec les autre SGBDR, je vous invite à vous rendre sur la page wikipédia qui est très bien écrite.

 

Création de la base de données

Normalement vous l'avez déjà installé sur votre ordinateur: ouvrez un terminal et tapez la commande sqlite3, vous devriez obtenir (Pour les allergiques de la ligne de commande, vous pourrez créer et lire des bases de données SQLite avec ce plugin Firefox).

$ sqlite3
SQLite version 3.7.7 2011-06-25 16:35:41
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite>

Si ça ne marche pas, j'ai du mentir quelques lignes plus tôt, et il va vous falloir installer le tout manuellement : téléchargez SQLite ici et suivez les étapes d'installation fournies.

Si c'est ok, vous êtes arrivés sur le "shell" de SQLite, l'endroit où vous pourrez "parler" à votre base de données. Pour l'instant quittez le prompt et spécifiez le nom de votre base de données (masuperbdd.sqlite):

sqlite> .q
$ sqlite3 masuperbdd.sqlite
SQLite version 3.7.7 2011-06-25 16:35:41
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite>

Ca y est, vous avez créé votre première base de données, elle s'appelle "masuperbdd.sqlite". Vous pouvez l'utiliser comme un fichier texte : la déplacer, la partager, l'envier par mail, bref tout ce que vous pouvez faire avec un fichier texte usuel. C'est là un gros point fort de SQLite, vous avez une base de données sans efforts, que vous pourrez emporter partout avec vous ;) Indépendamment de la plateforme sur laquelle vous vous trouvez !

Bon maintenant, vous avez toutes (ou presque) les commandes SQL à votre disposition et vous pouvez vous amuser.
 Là où je veux en venir, c'est qu'une fois fait cet effort (si on peut parler d'effort), une base de données est beaucoup plus pratique et remplace facilement un fichier CSV que certains ont l'habitude d'importer dans dans un tableur (genre Excel) qui plantera dès que vous dépasserez 20000 lignes et qui devient un vrai cauchemar dès que vous voulez complexifier un peu les choses.

Un exemple

Prenez par exemple ce fichier (volontairement petit - oui, 2800 lignes c'est peu); récupéré sur Ensembl via Biomart, j'ai sélectionné les gènes de la souris sur le chromosome 4, en y indiquant leur position sur le génome et leur teneur en bases GC (GC content).

Plutôt que de charger ce fichier dans un tableur nous allons donc l'importer dans une base de données SQLite et pour cela nous n'avons besoin que de quelques lignes de commande.

Préparation des données

Téléchargé depuis Biomart la première ligne du fichier indique le nom de chaque colonne, il faut donc supprimer l'en-tête. Grâce à awk je récupère toutes les lignes du fichier sauf la première et j'écris le résultat dans un autre fichier: simple_sans_header.csv.

$ awk 'FNR>1{print}' simple.csv > simple_sans_header.csv

Connexion sur ma base de données: je peux choisir l'extension qui me convient (le plus utilisé est .sqlite ou .db pour database).

$ sqlite3 mesgenes.db

Création de la table correspondant à mon header. Pour connaître les types vous pouvez vous rendre sur le site SQLite, il n'y en a pas beaucoup à retenir.

sqlite> create table genes (id integer, gene_start integer, gene_end integer, GCcontent real );

Spécifier le séparateur utilisé dans le fichier (ici csv veut dire "comma separated value", donc une virgule).

sqlite>.separator ","

Importer le fichier simple_sans_header.csv dans la table genes.

sqlite>.import simple_sans_header.csv genes

C'est fait !!! Pas plus difficile que ça.

Utilisation

Vous pouvez maintenant lire votre base de données:

Lister toute la table:

sqlite> select * from genes;

Personnellement je préfère l'affichage par défaut sur le terminal, donc je remet le séparateur à "|", et j'aime bien aussi voir le nom des champs que j'ai sélectionnés:

sqlite>.separator "|"
sqlite> .h on

Les trois premiers gènes dans la liste rangés par "start":

sqlite> select * from genes order by gene_start asc limit 3;
id|gene_start|gene_end|GCcontent
ENSMUSG00000094042|139922728|139922814|60.92
ENSMUSG00000077214|64444730|64444858|44.96
ENSMUSG00000092677|44221195|44221267|53.42

Combien de gènes j'ai en tout ?

sqlite>select count(*) from genes;
2826

Quel est le GCcontent max, et le moyen?

sqlite>select max(GCcontent), avg(GCcontent) as moyenne from genes;
max(GCcontent) | moyenne
87.69 | 47.1973000707714

Combien de gènes ont un GCcontent > 60% et un nombre de paires de bases > à 1500?

sqlite>select count(*) as nb_genes from genes where GCcontent > 60 and (gene_end - gene_start) > 1500;
nb_genes
13

Leur ID ?

sqlite>select id from genes where GCcontent > 60 and (gene_end - gene_start) > 1500;
id
ENSMUSG00000067261
ENSMUSG00000028445
ENSMUSG00000087288
...

Super! Il me fallait justement cette liste d'IDs pour faire une autre requête dans Biomart et filtrer les résultats.

Mais il me les faudrait dans un fichier texte... Ici je peux les recopier à la main, mais s'il y en a 100 voire 1000?

Exporter

Je quitte ma base de données:

sqlite> .q

Je ré-exécute la même sélection en précisant le nom de ma base de données et en prenant soin de mettre ma sélection entre guillemets, puis je redirige l'output vers un fichier:

$ sqlite3 mesgenes.db "select id from genes where GCcontent > 60 and gene_end - gene_start > 1500;"  mes_genes.txt

Vous pouvez aussi directement spécifier la redirection dans le shell SQLite comme ceci:

sqlite> .output mes_genes.txt

A vous de choisir :)

Voilà, c'est tout pour aujourd'hui, c'est simple et vous pouvez voir que cela peut être bien plus puissant car vous pouvez ajouter d'autres tables et des relations entre elles.

Un moyen direct et rapide pour structurer vos données.

 

Merci à Bu, Clem_ et nallias pour leur relecture.

Fusionner des fichiers entre eux : la commande join

$
0
0

Langage : shell
Commande présentée : join
Niveau : débutant

Présentation de la commande join

La commande join est disponible nativement sur les systèmes d'exploitation GNU/Linux. Il s'agit d'une commande POSIX et elle est donc présente sur tous les systèmes d'exploitation UNIX et UNIX-Like. La plupart des gens utilisent cette commande pour récupérer les lignes communes entre deux fichiers mais elle ne se limite pas à ce seul cas.
Join vous permet de fusionner des champs (colonnes) précis de deux fichiers textes et d'en récupérer la liste des données communes ou encore de vous afficher un résultat proche de ce que vous pourriez faire en SQL avec une requête JOIN.
Dans ce billet je vais vous montrer différentes façons dont on pourrait se servir de cette commande pour récupérer de l'information biologique à partir de données textuelles.

Exemple de résultat de la commande join | Auteur : Norore. Image libre de droit.

Exemple de résultat de la commande join | Auteur : Norore. Image libre de droit.

Préparation des fichiers

Nous nous proposons de trouver les identifiants UniProt des gènes d'Escherichia coli identifiés par le NCBI pour le cycle de Krebs. Pour cela il faut récupérer des données dans différentes banques de données.
Récupération des données sur les gènes d'Escherichia coli :

wget ftp://ftp.ncbi.nlm.nih.gov/gene/DATA/GENE_INFO/Archaea_Bacteria/Escherichia_coli_str._K-12_substr._MG1655.gene_info.gz

Récupération des données UniProt d'Escherichia coli :

wget ftp://ftp.uniprot.org/pub/databases/uniprot/current_release/knowledgebase/idmapping/by_organism/ECOLI_83333_idmapping.dat.gz

Liste des gènes du cycle de Krebs : (fichier : genes_cycle_krebs_ecoli.txt, source KEGG)
Ces fichiers présentent des données sous forme de colonnes.

Colonnes du fichier du NCBI :

  1. Numéro de taxon
  2. Identifiant du gène (GeneID)
  3. Symbole du gène
  4. Localisation du gène
  5. Synonymes
  6. Références croisées
  7. Chromosome
  8. Localisation sur la carte chromosomique
  9. Description
  10. Type de gène
  11. Symbole provenant de l'autorité de nomenclature
  12. Nom complet fourni par l'autorité de nomenclature
  13. Statut de la nomenclature
  14. Autres désignations
  15. Date de modification

Colonnes du fichier d'UniProt :

  1. Numéro d'accession UniProt
  2. Base de données d'origine
  3. Identifiant de la base de données d'origine

Dans un premier temps, nous allons extraire les informations sur les gènes à l'aide de la commande grep, comme décrite dans ce billet du blog :

grep -w -f genes_cycle_krebs_ecoli.txt Escherichia_coli_str._K-12_substr._MG1655.gene_info > geneinfo_krebs_ecoli.tsv

Maintenant que les fichiers sont préparés, nous allons pouvoir faire deux exercices :

  • une fusion simple des fichiers ;
  • une fusion des fichiers avec sélection des colonnes d'intérêt.

Fusion simple

Avant de fusionner les fichiers, vous devez vous assurer qu'ils sont triés de la même façon sur les colonnes à fusionner. Par défaut la commande join fusionne sur la première colonne mais il est possible de préciser les colonnes pour chaque fichier.
Voici un exemple simple pour illustrer dans un premier temps la commande join :

cat fichier1.txt
#ID    Prénom
1	Arnaud
2	Constance
3	Julie
4	Constantin

cat fichier2.txt 
#ID    Console
1	Playstation
3	Nintendo DS
4	Wii-U

La commande join va afficher sur chaque ligne le #ID, le Prénom et la Console pour chaque individu (les tabulations sont remplacées par des espaces, c'est le comportement par défaut de la commande) :

join fichier1.txt fichier2.txt 

1 Arnaud Playstation
3 Julie Nintendo DS
4 Constantin Wii-U

Dans nos exemples biologiques, nous travaillerons sur les identifiants GeneID des deux fichiers, soit la colonne 3 pour le fichier d'UniProt (option -1 3) et la colonne 2 pour le fichier du NCBI (option -2 2). De plus, nous avons besoin de trouver les données qui correspondent au NCBI dans le fichier d'UniProt. Dans un premier temps nous devons trier les colonnes de la même façon pour chacun des fichiers :

grep -w "GeneID" ECOLI_83333_idmapping.dat | sort -k 3,3n > ECOLI_83333_idmapping2.dat
sort -k 2,2n geneinfo_krebs_ecoli.tsv > geneinfo_krebs_ecoli2.tsv

La fusion simple se fait ainsi :

join -13 -22 ECOLI_83333_idmapping2.dat geneinfo_krebs_ecoli2.tsv

944794 P06959 GeneID 511145 aceF b0115 ECK0114|JW0111 EcoGene:EG10025 - - pyruvate dehydrogenase, dihydrolipoyltransacetylase component E2 protein-coding - - - - 20130526
944834 P0AFG8 GeneID 511145 aceE b0114 ECK0113|JW0110 EcoGene:EG10024 - - pyruvate dehydrogenase, decarboxylase component E1, thiamin-binding protein-coding - - - - 20130526
944854 P0A9P0 GeneID 511145 lpd b0116 ECK0115|JW0112|dhl|lpdA EcoGene:EG10543 - - lipoamide dehydrogenase, E3 component is part of three enzyme complexes protein-coding - - - - 20130526
[...]
948666 P0AC47 GeneID 511145 frdB b4153 ECK4149|JW4114 EcoGene:EG10331 - - fumarate reductase (anaerobic), Fe-S subunit protein-coding - - - - 20130526
948667 P00363 GeneID 511145 frdA b4154 ECK4150|JW4115 EcoGene:EG10330 - - fumarate reductase (anaerobic) catalytic and NAD/flavoprotein subunit protein-coding - - - - 20130526
948668 P0A8Q3 GeneID 511145 frdD b4151 ECK4147|JW4112 EcoGene:EG10333 - - fumarate reductase (anaerobic), membrane anchor subunit protein-coding - - - - 20130526
join: geneinfo_krebs_ecoli2.tsv:29: n'est pas trié : 511145	4056026	icdC	b4519	ECK1146|JW5173|icd	EcoGene:EG10009	-	-	pseudo	pseudo	-	---	20130526
948680 P0A8Q0 GeneID 511145 frdC b4152 ECK4148|JW4113 EcoGene:EG10332 - - fumarate reductase (anaerobic), membrane anchor subunit protein-coding - - - - 20130526
join: ECOLI_83333_idmapping2.dat:4209: n'est pas trié : P19768	GeneID	1238726

Décortiquons les options spécifiées à la commande join :

  • -13 : fichier 1 (ECOLI_83333_idmapping2.dat), colonne 3
  • -22 : fichier 2 (geneinfo_krebs_ecoli2.tsv), colonne 2

Les erreurs soulevées (join: ECOLI_83333_idmapping2.dat:4209: n'est pas trié) sont dues au fait que, dans le fichier UniProt, un même identifiant de gène peut être associé à plusieurs identifiants UniProt --dois-je vous rappeler qu'un gène peut coder pour plus d'une protéine ?
Dans le résultat affiché, la première colonne correspond à la colonne commune aux deux fichiers, les colonnes 2 et 3 correspondent aux colonnes 1 et 2 du fichier UniProt, les autres colonnes correspondent aux colonnes du fichier du NCBI, avec la colonne 2 en moins (elle est fusionnée dans la colonne 1 du résultat).

Fusionner les fichiers et n'afficher que certaines colonnes

Comme vous pouvez le constater, il y a beaucoup de colonnes, ce qui peut être lourd à traiter par la suite. On va donc n'afficher que certaines colonnes afin de faciliter d'autres éventuelles manipulations sur ces données.

En reprenant notre exemple simpliste, voici ce que nous pourrions faire avec les fichiers :

cat fichier1.txt
#ID    Prénom
1	Arnaud
2	Constance
3	Julie
4	Constantin

cat fichier2.txt
#ID    Console    Constructeur
1	Playstation	Sony
3	Nintendo DS	Nintendo
4	Wii-U	Nintendo

Ici nous ne voulons afficher que les prénoms et le constructeur de chaque console utilisée pour chaque personne en possédant une, nous allons donc afficher la colonne 2 du fichier 1 et la colonne 3 du fichier 2 (option -o). De plus, comme la console Nintendo DS contient un espace dans son nom, nous devons préciser à la commande join d'utiliser le séparateur tabulation (option -t) :

join -t $'\t' -o 1.2,2.3 fichier1.txt fichier2.txt
#Prénom    Constructeur
Arnaud	Sony
Julie	Nintendo
Constantin	Nintendo

Dans notre exemple avec les données biologiques, nous allons sélectionner, pour le fichier du NCBI, les colonnes suivantes :

  • GeneID, colonne 2
  • Symbole, colonne 3
  • Synonyms, colonne 4
  • Description, colonne 9
  • Type de gène, colonne 10

Pour le fichier UniProt, nous allons sélectionner la colonne 1 qui correspond à l'identifiant UniProt.
En reprenant la première commande et en précisant les colonnes d'intérêt, voici la nouvelle commande à utiliser et son résultat :

join -t $'\t' -13 -22 -o 1.1,2.2,2.3,2.4,2.9,2.10 ECOLI_83333_idmapping2.dat geneinfo_krebs_ecoli2.tsv

P06959	944794	aceF	b0115	pyruvate dehydrogenase, dihydrolipoyltransacetylase component E2	protein-coding
P0AFG8	944834	aceE	b0114	pyruvate dehydrogenase, decarboxylase component E1, thiamin-binding	protein-coding
P0A9P0	944854	lpd	b0116	lipoamide dehydrogenase, E3 component is part of three enzyme complexes	protein-coding
[...]
P0AC47	948666	frdB	b4153	fumarate reductase (anaerobic), Fe-S subunit	protein-coding
P00363	948667	frdA	b4154	fumarate reductase (anaerobic) catalytic and NAD/flavoprotein subunit	protein-coding
P0A8Q3	948668	frdD	b4151	fumarate reductase (anaerobic), membrane anchor subunit	protein-coding
join: geneinfo_krebs_ecoli2.tsv:29: n'est pas trié : 511145	4056026	icdC	b4519	ECK1146|JW5173|icd	EcoGene:EG10009	-	-	pseudo	pseudo	-	---	20130526
P0A8Q0	948680	frdC	b4152	fumarate reductase (anaerobic), membrane anchor subunit	protein-coding
join: ECOLI_83333_idmapping2.dat:4209: n'est pas trié : P19768	GeneID	1238726

Explication sur les options utilisées :

  • -o : précise la liste des colonnes à afficher. Le premier chiffre doit être le numéro du fichier et le second le numéro de la colonne à afficher.
  • -t $'\t' : précise à la commande join que les champs sont séparés par des tabulations, nécessaire dans ce cas de figure.

Quand join se prend pour SQL

Vous l'aurez remarqué, la commande de base de join fusionne les fichiers entre eux en n'affichant que les lignes communes. Or dans certains cas vous aimeriez pouvoir conserver les lignes d'un de vos fichiers, afin de voir quelles sont les lignes qui n'ont pas pu être fusionnées. Pour cela, vous pouvez utiliser l'option -a suivie du numéro du fichier pour lequel vous voulez afficher les lignes.
Vous avez pris peu de colonnes et vous avez peur de passer à côté d'une ligne non fusionnée ? Utilisez l'option -e suivie, entre guillemets, d'un mot clé, comme NULL par exemple. Ainsi vous saurez que toutes les lignes qui ont ce mot clé n'ont pas pu être fusionnées.

En voici une illustration avec nos joueurs de consoles préférés, ici nous demandons à join d'afficher toutes les lignes de fichier1.txt et d'ajouter le mot clé "Aucun" lorsque la ligne n'est pas fusionnée avec fichier2.txt :

join -a 1 -e "Aucun" -t $'\t' -o 1.2,2.3 fichier1.txt fichier2.txt
Arnaud	Sony
Constance	Aucun  # Constance n'est pas une joueuse
Julie	Nintendo
Constantin	Nintendo

 

Conclusion

La commande join est une commande qui effraye certains d'entre nous, or une fois que l'on connaît et que l'on a compris son fonctionnement, il est vraiment possible de s'amuser avec. Ne faites pas la même erreur que moi dans ces exemples, encore que l'erreur a pu vous illustrer les limites de la commande : assurez-vous que la colonne que vous cherchez à fusionner n'aura qu'une seule occurrence pour chaque clé ! Dans mon exemple, si je veux retrouver les informations pour les identifiants UniProt des lignes qui ont généré des erreurs, je vais devoir passer par la commande grep.
J'espère que ce billet aura permis aux plus frileux d'entre vous d'entrevoir les possibilités offertes par cette commande qui mérite que l'on s'y intéresse dans notre domaine.

Pour comprendre un peu mieux les exemples que je vous ai montré, vous pouvez consulter cette page et vous en inspirer, elle m'a fortement aidée dans mes débuts avec cette commande : http://fr.wikipedia.org/wiki/Join_(Unix)


Merci à Bunny, nallias, Clem_ et ZaZo0o pour leur relecture et nos échanges.

Analyses rapides de fichiers

$
0
0
Analyse de fichiers dans le bon vieux temps ! | Creative Commons 2.0, wikimedia

Analyse de fichiers dans le bon vieux temps ! | Creative Commons 2.0, wikimedia

Langage : shell, sous GNU/Linux
Commandes présentées : wc, awk, sed, tr, head, nl, cut
Niveau : débutant

Dans le cadre de notre travail, nous sommes souvent amenés à manipuler de nombreux fichiers contenant des milliers de lignes et des dizaines de champs. Dans ces cas-là, nous avons souvent tendance à virer à la paranoïa et à vouloir nous assurer que nos fichiers sont bien formatés et contiennent bien le nombre de lignes, champs ou éléments attendus.

Dans ce billet, je vous propose de découvrir ou redécouvrir quelques commandes simples pour analyser rapidement des fichiers.

Pour les différents exemples montrés, je vous ai préparé de petits fichiers que vous pouvez récupérer pour tester les exemples. Vous pouvez les récupérer en cliquant sur les liens suivants :

Afficher le nombre de lignes

wc -l exemple_wc.txt

Un grand classique mais c'est le meilleur moyen de s'assurer que l'on a bien le bon nombre de lignes attendu. C'est également un bon moyen de vérifier que vous n'avez pas un fichier contenant des retours à la ligne de Windows, les fameux ^M ou CRLF.
Si vous avez de tels sauts de lignes, la commande vous affichera 0 lignes, et la commande dos2unix peut vous être utile.
Résultat attendu :

5 exemple_wc.txt

Retirer la première ligne d'un fichier

sed '1d' exemple_sed.txt

Renverra sur la sortie standard le contenu du fichier exemple_sed.txt sans la première ligne.
Résultat attendu :

Si cette ligne est la première, c'est que vous avez bien supprimé la première ligne.
La troisième ligne.
La quatrième ligne.
La cinquième ligne.
Et toutes les autres lignes que vous voulez.

Ligne non affichée :

Ceci est la première ligne.

Attention, sed ne supprimera pas la première ligne dans le fichier, la commande se contentera de ne pas l'afficher. Il faudra ajouter l'option -i (mode insertion) pour la supprimer directement dans le fichier.
Exemple :

nolwenn@bioinfo-fr$ cp exemple_sed.txt exemple_sed_insert.txt
nolwenn@bioinfo-fr$ wc -l exemple_sed_insert.txt
6 exemple_sed_insert.txt
nolwenn@bioinfo-fr$ sed -i '1d' exemple_sed_insert.txt
nolwenn@bioinfo-fr$ wc -l exemple_sed_insert.txt
5 exemple_sed_insert.txt
nolwenn@bioinfo-fr$ cat exemple_sed_insert.txt
Si cette ligne est la première, c'est que vous avez bien supprimé la première ligne.
La troisième ligne.
La quatrième ligne.
La cinquième ligne.
Et toutes les autres lignes que vous voulez.

Afficher les numéros de colonnes d'un fichier

head -n 1 exemple_numcol.tsv | tr "\t" "\n" | nl

head -n 1 affiche la première ligne.
tr permet de "traduire" la tabulation par un retour à la ligne.
nl permet d'afficher le numéro de la ligne lue.

Si vous ne connaissez pas le symbole pipe "|", je vous recommanderai de lire l'article sur les pipelines rédigé par Akira.

Remplacer \t par le séparateur de colonnes de votre choix.
Merci à S. Letort pour cette astuce à mon arrivée dans l'unité.
Résultat attendu :

1  rs
2  pos
3  chrom

Afficher le nombre de lignes par nombre de champs

awk '{print NF}' exemple_awk.tsv | sort | uniq -c

print NF affiche le nombre de champs pour chaque ligne lue dans le fichier exemple_awk.tsv par awk.
sort tri le flux de sortie de awk.
uniq -c compte le nombre de lignes identiques
Résultat attendu :

2 3
1 5
1 9

Dans cet exemple, nous avons donc 2 lignes contenant 3 champs, 1 ligne contenant 5 champs et 1 ligne faisant 9 champs.

Merci à A. Vaysse pour cette astuce (et pour m'avoir appris le awk).
Très utile pour vérifier qu'un fichier à bien le bon nombre de champs pour chaque ligne.

Récupérer les champs d'intérêt d'un fichier

La commande cut permet de découper un fichier champ par champ, ou colonne par colonne. Les champs sont considérés comme des index, un peu comme une liste en langage Python ou Perl. L'index de cut commence par 1, en Python et en Perl les index commencent par 0.

cut -f 3 exemple_cut.tsv

Affiche le champs 3.
Résultat attendu :

3
c
m
w

cut -f 1,4 exemple_cut.tsv

Affiche les champs 1 et 4.
Résultat attendu :

1   4
a   d
k   n
u   x

cut -f 3-7 exemple_cut.tsv

Affiche les champs 3 à 7.
Résultat attendu :

3   4   5   6   7
c   d   e   f   g
m   n   o   p   q
w   x   y   z   aa

cut --complement -f 1-3 exemple_cut.tsv

Affiche tous les champs à l'exception des champs 1 à 3.
Résultat attendu :

4   5   6   7   8   9   10
d   e   f   g   h   i   j
n   o   p   q   r   s   t
x   y   z   aa  bb  cc  dd

Conclusion

En conclusion, je vous avouerai que j'utilise assez fréquemment ces lignes de commandes. En fonction de la complexité des données contenues je jongle entre ces différentes commandes tout en utilisant les pipelines.

Il existe sûrement d'autres astuces que j'ai oublié ou que je ne connais pas, aussi si vous en connaissez je vous invite à les partager entre nous dans les commentaires de ce billet !

 

Merci à Yoann M. et Mica pour vos relectures et vos commentaires.

Un bookmarklet pour accéder plus facilement aux publications

$
0
0

Si vous êtes au CNRS, ou à l'Inserm, vous êtes probablement familiers avec BiblioInserm ou BiblioVIE, les portails d’informations et d’accès aux revues de ces institutions.

Biblio Inserm 

Le portail BiblioInserm

BiblioVIE 

Le portail BiblioVIE

Ces portails vous permettent d’accéder par exemple à PUBMED et d’y chercher des articles, sous réserve d’avoir un identifiant et le mot de passe associé (souvent par labo, ou par équipe).
En utilisant les services BiblioInserm ou BiblioVIE pour vous rendre sur PUBMED, vous passez par un proxy qui vous identifie et vous accompagne dans vos recherches bibliographiques.

PubMed 

Sur le site Pubmed avec le Proxy

Qui se retrouve aussi quand vous allez sur le site d’un éditeur depuis PUBMED, et vous évite ainsi de tomber sur ça :

Article payant 

Article payant sur Nature que je veux absolument lire

Fatigué de passer à chaque fois par le portail de ma communauté, j’ai commencé à taper l’url du proxy directement dans ma barre d’adresse. Et après avoir installé un bookmarklet pour Mendeley, je me suis dit que ça serait pratique d’avoir la même chose pour accéder facilement aux articles.

Pour ceux qui ne sont pas familiers avec les bookmarklets, je vous invite à tester ceux présents ici, ce sont des bookmarklets simples et utiles, l'idéal pour commencer.
Voici donc le code du bookmarklet pour accéder plus facilement à vos publications :

javascript:(function(){location.hostname=location.hostname+'.urlproxy';})();

Il faut juste remplacer urlproxy par l’adresse du proxy de votre institution, ou si vous connaissez un proxy russe pirate, ça marche aussi.
Par exemple si vous êtes au CRNS :

javascript:(function(){location.hostname=location.hostname+'.gate2.inist.fr';})();

Ajouter ce petit bout de script comme si vous ajoutiez un favoris. Nommez-le. Personnellement j'ai appellé le mien Access it, mais c’est comme vous voulez.

javascript:(function(){location.hostname=location.hostname+'.gate1.inist.fr';})();

Ou celui-ci si vous êtes à l'Inserm.

Enjoy :-D !!!

Merci aux super-relecteurs : EstelClem_et Wocka pour leurs remarques constructives et à nahoy pour ses conseils.

Fabriquer un trackhub dans UCSC

$
0
0

J'ai décidé de partager avec vous la petite astuce du moment que j'ai découverte grâce à Jonathan et que j'ai incorporée dans mon travail actuel (merci encore à lui, il a lu toute l’infâme documentation de UCSC).

[edit : Il vient d'ailleurs de me signaler que la documentation pour les trackhubs vient d'être mise à jour et est devenue beaucoup plus digeste. Tant mieux pour les suivants.]

Le navigateur génomique (pour ne pas dire genome browser) de UCSC nous autorise donc à générer et visualiser des "trackhubs".

Qu'est ce qu'un trackhub ?

On parle de trackhub quand on regroupe plusieurs fichiers génomiques dans un même dossier qui peut être ensuite partagé sur UCSC ou avec d'autres personnes par exemple. On pourra empiler des tracks (pistes) venant du même organisme et du même assemblage génomique et on pourra partager facilement tous les fichiers issus d'une même analyse juste en quelques clics.

Les tracks issues de ces fichiers seront utiles pour mieux visualiser les données et voir ce qui change entre elles. On a alors la possibilité de soit les aligner l'une sous l'autre, soit les superposer pour distinguer des résultats qui ne nous auraient pas forcément sauté aux yeux avec des tracks les unes sous les autres (surtout si notre écran est petit).

 

Comment on fait ?

Premièrement, vous devez avoir accès à une partition (en local ou sur un serveur) accessible de l'extérieur pour que UCSC puisse venir fouiner dedans. Pour ma part j'ai complètement automatisé la chose pour l'incorporer dans mon application web (faite avec Turbogears et SQLAlchemy) et c'est mon serveur qui fournit à UCSC les fichiers nécessaires. La finalité est d'obtenir une URL qui pointe sur un fichier se trouvant dans un dossier entièrement accessible lui aussi.
Intéressons-nous donc à ces fichiers qu'il va falloir construire. Attention, à partir de cette ligne le port du casque devient obligatoire car vous allez rentrer dans la logique de UCSC (ou du moins essayer) et ça peut faire mal.

Créez un dossier qui comportera le nom de votre trackhub :

mkdir Toto_mon_trackhub

 

Ce dossier devra contenir deux fichiers à sa racine ainsi qu'un dossier.

Il vous faudra d'abord créer le fichier hub.txt (les noms sont importants et doivent être strictement identiques à ce que je vous dis, sinon UCSC part bouder dans son coin et ça ne vous arrange pas). Ce fichier devra contenir les lignes suivantes :

hub Toto_mon_trackhub
shortLabel Toto
longLabel Toto_mon_trackhub_qui_va_etre_tres_beau
genomesFile genomes.txt
email toto.lover@toto.com

Explication de texte du fichier décrit ci-dessus : la ligne hub correspond au nom de votre dossier, ici "Toto_mon_trackhub"; le shortLabel devra ne contenir qu'un mot si possible en opposition au longLabel qui pourra accueillir un mode plus verbeux (le tout séparé par des underscores comme vous pouvez le voir dans l'exemple). Quant au genomesFile, il devra toujours être suivi de "genomes.txt". C'est le lien à la prochain étape. Je ne pense pas avoir à détailler la ligne sur l'email, qui est obligatoire également.

Venons-en donc au deuxième fichier devant se trouver à la racine de votre dossier : genomes.txt.

Voilà ce que contient genomes.txt dans le cadre de mon exemple :

genome sacCer2
trackDb sacCer2/trackDb.txt

Gros fichiers non ? Bon si vous ne devez retenir que deux choses ici c'est : mettre la version de votre assemblage génomique ici (tout en faisant attention aux majuscules qui sont chères à UCSC) et créer un dossier du nom de ce même assemblage génomique à la racine de votre dossier tout frais.

À savoir aussi qu'on peut également mettre plusieurs versions d'assemblage, il suffit juste de répéter les deux lignes avec la nouvelle version (ou autre version) et de créer aussi un autre dossier avec le même nom.

Ici on s'en tiendra à un exemple simple.

On s'exécute donc :

mkdir sacCer2

 

Puis on fonce dedans pour y créer un autre fichier lui aussi déjà référencé dans genomes.txt. Ça sera le dernier je vous le promets, et le plus intéressant aussi.

Il faudra à ce stade créer dans le dossier sacCer2 le fichier trackDb.txt. Encore une fois le nom du fichier est très important.

Voilà ce que contient notre trackDb.txt d'exemple :

track Toto_mon_trackhub_qui_va_etre_tres_beau_et_dont_je_vais_etre_tres_fier
container multiWig
shortLabel Toto
longLabel Toto_mon_trackhub_qui_va_etre_tres_beau
type bigWig
visibility full
maxHeightPixels 70:70:32
configurable on
aggregate transparentOverlay
showSubtrackColorOnUi on
priority 1.0

    track LePremierBigwig.bw
    parent Toto_mon_trackhub_qui_va_etre_tres_beau
    bigDataUrl http://monsuperdepot.com/LePremierBigwig.bw
    shortLabel LePremier
    longLabel LePremierBigwig
    type bigWig
    autoScale on
    color 208,72,253

    track LeDeuxiemeBigwig.bw
    parent Toto_mon_trackhub_qui_va_etre_tres_beau
    bigDataUrl http://monsuperdepot.com/LeDeuxiemeBigwig.bw
    shortLabel LeDeuxieme
    longLabel LeDeuxiemeBigwig
    type bigWig
    autoScale on
    color 239,242,62

    track LeTroisiemeBigwig.bw
    parent Toto_mon_trackhub_qui_va_etre_tres_beau
    bigDataUrl http://monsuperdepot.com/LeTroisiemeBigwig.bw
    shortLabel LeTroisieme
    longLabel LeTroisiemeBigwig
    type bigWig
    autoScale on
    color 212,144,42

On va avoir beaucoup à dire sur ce fichier, donc nous allons procéder sous forme de liste, ça sera plus simple :

  • Le premier track détermine le nom de votre track parent sur l'affichage de UCSC
  • container fait référence au type de vos fichiers que vous voulez regrouper dans le trackhub. Ici pour l'exemple ce sont tous des bigWig (on le renseigne d'ailleurs après avec la clé "type") donc la valeur à attribuer à la clé sera "multiWig". Vous saisissez le principe. Il est bien sûr impératif que tous vos fichiers soient de la même extension dans le même container.
  • shortLabel et longLabel font leur retour, ils détermineront les noms de vos tracks
  • visibility est à régler selon vos préférences. Moi je la mets toujours d'emblée à full, et c'est de toute manière réglable par la suite à l'écran.
  • Pas mieux pour le maxHeightPixels, cette valeur me convient très bien.
  • Je laisse le configurable à on, on peut ainsi retoucher à deux-trois réglages après génération du trackhub.
  • Pour la clé aggregate, encore une histoire de goûts et de couleurs. Cela déterminera l'affichage de la superposition de vos tracks. Ici je choisis le mode en transparence qui rend assez bien
  • Pour les deux dernières clés du container, à savoir showSubtrackColorOnUi et priority, je vous demande de me faire confiance et de laisser ça comme tel. Pour la première il s'agit d'afficher la légende des couleurs utilisées dans la page de configuration de votre trackhub et pour la seconde il s'agit en réalité de la priorité que vous souhaitez donner à votre piste par rapport aux autres.  Rien ne vous empêchera de jouer avec par la suite pour voir si ça vous est réellement utile.

On a fini de configurer le container, qui sera appelé à partir de maintenant le "parent" de nos tracks exemples.

Il faudra à partir de maintenant mettre une indentation (une tabulation) avant chacune des clés suivantes pour que notre ami UCSC soit content. Ces clés seront à répéter autant de fois que l'on a de fichiers génomiques à afficher dans le trackhub :

  • track, ni plus ni moins que le nom de votre piste. J'ai coutume d'y mettre le nom du fichier.
  • parent correspondra au nom de notre container, ici Toto_mon_trackhub_qui_va_etre_tres_beau
  • bigDataUrl sera le lien vers votre fichier (accessible depuis l'extérieur, j'insiste). Cela pourra être un lien http (comme notre exemple) ou bien un chemin relatif vers le fichier qui se trouvera quelque part dans votre dossier "sacCer2" (pour notre exemple).
  • On retrouve pour la dernière fois nos amis shortLabel et longLabel. Promis on ne les verra plus.
  • Le type correspond à l'extension du fichier. Attention à bien l'écrire comme UCSC le souhaite (majuscules-minuscules), sinon il fait celui qui connaît pas et se braque comme un bourricot (j'en suis venu à penser qu'il a eu une enfance difficile...).
  • Pour l'autoScale, là encore je vous conseille de le laisser à on.
  • Enfin color sera la couleur de votre piste en RGB. Essayez de ne pas prendre des couleurs qui ne vont pas ensemble (du genre marron, noir et violet foncé). C'est peut-être bête à avouer, mais c'est assez difficile de trouver un bon compromis. Moi je me suis résolu à les générer au hasard et à faire confiance au tirage au sort. Si quelqu'un a une meilleure solution je suis bien entendu preneur (pour n tracks bien sûr).

Bon à savoir :

  • Vous ne pourrez pas avoir un shortLabel et un longLabel identique. Si c'est le cas, vous facherez UCSC et n'obtiendrez rien de lui.
  • Il est important de respecter les sauts de lignes et les tabulations comme dans l'exemple donné, sinon même punition que pour le premier point
  • Il est possible de mettre plusieurs containers/parents dans trackDb.txt. L'exemple se voulant résolument facile pour vous familiariser avec la chose.

Une fois arrivés là vous avez fait le plus dur.

Il existe d'autres clés modifiables, vous pourrez les retrouver sur la nouvelle documentation des trackhubs de UCSC.

Pour arriver à la finalité du processus il ne vous restera plus qu'à vous rendre à l'adresse suivante :

http://genome.ucsc.edu/cgi-bin/hgHubConnect

Il vous sera alors demandé de déposer une URL pointant sur le fichier hub.txt de votre trackhub.

Page d'accueil pour soumettre un trackhub

Page d'accueil pour soumettre un trackhub

Enfin après un temps plus ou moins long d'attente lié au proccessing (et au nombre de confrères utilisant le service), voilà votre merveilleux trackhub qui s'affiche enfin devant vos yeux émerveillés. Bon visionnage et bonne exploration génomique.

Trackhub généré grâce à notre exemple

Trackhub généré grâce à notre exemple

Voilà, il ne tient qu'à vous d'automatiser tout ça. C'est assez simple puisque maintenant on vous a tout décortiqué. Il ne vous reste plus qu'à créer les dossiers et fichiers, puis générer l'adresse pointant sur le hub.txt.

Une dernière chose : si quelqu'un comprend le principe de UCSC de faire pointer sur un fichier qui pointe ensuite sur un second, puis sur un troisième qui à son tour va pointer sur de multiples fichiers : merci de m'expliquer. J'ai pas compris pourquoi ils n'ont pas fait tout ce "workflow" à partir d'un seul et même fichier texte... Pourquoi faire simple quand on peut faire compliqué ? La devise de UCSC :)

Je remercie Estel, Nolwenn et Julien pour leur relecture sans faille et jsobel pour ses conseils de jedi UCSC.

Formaliser ses protocoles avec Snakemake

$
0
0

Vous avez dit protocole?

workflow

Mon prochain protocole. Sans aucun doute un pas de géant pour la science.

Qui dit biologie et bioinformatique dit protocole expérimental. C'est le cœur de la démarche scientifique, et un formalisme adapté est la clef pour assurer la reproductibilité des expériences, et ainsi garantir la validation des découvertes par la communauté. En paillasse, les solutions pour formaliser et conserver les protocoles sont plutôt naturellement pragmatiques et éprouvées par les années. Le papier a encore de beaux jours devant lui. Cependant, il est assez difficile d'adapter le principe du cahier de manip au monde de l'informatique, car il est inefficace tel quel.

À la place, beaucoup ont adopté les systèmes de gestion de workflow, qui permettent de retranscrire de manière assez naturelle la notion de reproductibilité au support informatique. Beaucoup d'outils spécialisés à la bioinformatique ont depuis vu le jour: Galaxy (dont j'avais déjà parlé ici), Taverna, Ergatis, etc. Ils permettent de créer, exécuter et partager des protocoles entiers, mais la plupart sont développés pour offrir une interface graphique à un utilisateur novice en programmation, et sont peu voire pas adaptés à l'utilisation en ligne de commande. De plus, l'utilisateur est souvent dépendant d'un ensemble d'outils pré-intégrés au système, ce qui est souvent handicapant.

Rendez-moi mon shell!

GNU

Honorable GNU compilant du libre avec l'ancestral Make

L'outil de référence pour créer des workflows en ligne de commande, vers lequel la plupart des gens se tournent encore aujourd'hui, est GNU Make. Il est bien connu, documenté, éprouvé, et permet de définir des workflows ramifiés. Un de ses points forts est le fait que si une partie du workflow seulement doit être modifiée, il n'exécutera que les parties nécessaires. Étant donnés les volumes des données manipulées en bioinformatique, c'est un avantage de taille.

Même s'il est généralement utilisé pour la compilation de logiciels, il est très adapté à la démarche scientifique. En effet, lorsque vous définissez un protocole expérimental, il y a de grandes chances pour qu'il soit à peu de choses près directement transposable en Makefile, les différentes étapes du protocole sont souvent assimilable à des règles pour GNU Make.

Mighty Python

Pytroll

Jeune et fringant Python trollant éhontément le vénérable GNU. Ah, la fougue de la jeunesse, quelle impertinence!

Seulement voilà: GNU Make est un dinosaure poussiéreux sacrément compliqué à prendre en main, et long à maîtriser. Heureusement, il existe des initiatives dont le but est de proposer des alternatives plus modernes à cette honorable antiquité. C'est le cas, par exemple, de SCONS, Ant, Waf, Rake, Aap, Ninja et Snakemake. Même si je ne traiterai ici que de ce dernier, qui propose une solution similaire à GNU Make basé sur Python, il n'est pas exclu que d'autres alternatives puissent être aussi valables dans ce contexte.

Le principe est similaire: On définit des étapes/règles qui ont chacune des fichiers d'entrée, des fichiers de sortie, et des choses à exécuter pour produire les fichiers de sortie à partir des fichiers d'entrée. On les écrit dans un fichier nommé Snakefile (équivalent d'un Makefile chez GNU Make) et on lance l'exécution avec la commande snakemake. Il propose en plus de retranscrire le workflow en graphe acyclique orienté (DAG) pour mieux le visualiser, ce qui permet notamment de vérifier si le Snakefile correspond bien au protocole expérimental désiré.

Il n'est d'ailleurs tellement pas surprenant d'utiliser Snakemake dans ce contexte que le premier exemple fourni dans leur documentation est un exemple de bioinformatique. Ça ne s'invente pas. En voici la traduction:

Cufflinks est un outil permettant d'assembler des transcrits, estimer leur abondance, et effectuer des analyses d'expression différentielle sur des données RNA-Seq. Cet exemple montre comment créer un workflow typique pour Cufflinks avec snakemake. Il suppose que des données RNA-Seq alignées sont fournies sous format BAM pour quatre échantillons 101-104. Pour chaque échantillon, les transcrits sont assemblés avec '''cufflinks''' (rule assembly). Les assemblages sont fusionnés en un fichier GTF avec '''cuffmerge''' (rule merge_assemblies). Une comparaison avec la piste GTF de hg19 est ensuite effectuée (rule compare_assemblies). Enfin, les expressions différentielles sont calculées sur les transcrits trouvés (rule diffexp).

TRACK = 'hg19.gtf' # should be fetched from the cufflinks page since special fields are expected
REF = 'hg19.fa'

CLASS1 = '101 102'.split()
CLASS2 = '103 104'.split()
SAMPLES = CLASS1 + CLASS2

rule all:
    input: 'diffexp/isoform_exp.diff', 'assembly/comparison'

rule assembly:
    input: 'mapped/{sample}.bam'
    output: 'assembly/{sample}/transcripts.gtf', dir='assembly/{sample}'
    threads: 4
    shell: 'cufflinks --num-threads {threads} -o {output.dir} --frag-bias-correct {REF} {input}'

rule compose_merge:
    input: expand('assembly/{sample}/transcripts.gtf', sample=SAMPLES)
    output: txt='assembly/assemblies.txt'
    run:
        with open(output.txt, 'w') as out:
        out.write('\n'.join(input))

rule merge_assemblies:
    input: 'assembly/assemblies.txt'
    output: 'assembly/merged/merged.gtf', dir='assembly/merged'
    shell: 'cuffmerge -o {output.dir} -s {REF} {input}'

rule compare_assemblies:
    input: 'assembly/merged/merged.gtf'
    output: 'assembly/comparison/all.stats', dir='assembly/comparison'
    shell: 'cuffcompare -o {output.dir}all -s {REF} -r {TRACK} {input}'

rule diffexp:
    input: expand('mapped/{sample}.bam', sample=SAMPLES), gtf='assembly/merged/merged.gtf'
    output: 'diffexp/gene_exp.diff', 'diffexp/isoform_exp.diff'
    threads: 8
    run:
        classes = [','.join(expand('mapped/{sample}.bam', sample=cls)) for cls in (CLASS1, CLASS2)]
        shell('cuffdiff --num-threads {threads} {gtf} {classes[0]} {classes[1]}')

On peut remarquer qu'il s'agit ni plus ni moins d'un script python: il est tout à fait possible d'utiliser tous les objets et fonctions disponibles dans le langage, et importer des modules en fonction des besoins. Pour chaque règle, il est possible de demander directement l'exécution de code Python directement intégré dans le Snakefile lorsqu'il est placé dans le bloc "run" (voir rule compose_merge). Bref, si vous êtes familier avec ce langage, vous ne serez pas dépaysé.

Retours d'expériences (pun intended)

Myron Fass

Jean-Clotaire, indécrottable pythoniste, découvre Snakemake avec une joie non dissimulée.

Bien entendu, je ne vous en parlerais pas si je ne l'avais pas testé moi-même. Pour être tout à fait honnête, je pense qu'une solide expérience de développement en Python n'est pas du luxe. Je trouve Snakemake plus user-friendly que Make, mais il reste un système complexe qui demandera de la patience et quelques efforts à prendre en main. Cela dit, quand vous manipulez de grands volumes de données, c'est agréable de pouvoir facilement choisir quoi réexécuter en fonction des besoins.

Je l'utilise dans le cadre d'un projet de recherche où les protocoles ne sont pas pré-établis, et dans ce contexte l'avantage d'avoir un système facilement éditable et exécutable en ligne de commande se fait évident. J'apprécie beaucoup la souplesse du système, il faut avouer que c'est autre chose que les scripts shell cracras ou les Makefiles abscons. Snakemake a encore du chemin à faire, il y a pas mal de fonctionnalités qui manquent un peu ou semblent être encore en chantier, mais il est tout à fait utilisable en l'état, et je l'ai adopté.

L'option permettant de visualiser le pipeline sous forme de DAG est également très appréciable, car elle permet de visualiser l'état d'avancement de son exécution. Les étapes déjà effectuées s'affichent en pointillés et celles qui seront exécutées au prochain appel de la commande sont entourées d'un trait plein. Voici ce que ça donne pour un Snakemake de mon projet:

DAG Snakefile

Commande utilisée (nécessite Graphviz):
snakemake --dag | dot -Tpng -o dag.png

Je vous invite à visiter leur wiki si vous souhaitez en savoir plus. Snakemake est hébergé sur Bitbucket: https://bitbucket.org/johanneskoester/snakemake

______________________

Le premier à me dire ce qui ne va pas dans mon superbe protocole décrit dans la première illustration (à part les noms débiles) aura gagné le cocotier.

En effet, il est incompatible avec Snakemake. Pourquoi?

______________________

Disclaimer: Tous les contenus utilisés pour les images finales de cet article sont soit dans le domaine public (1) soit sous licence Creative Commons Attribution-Share Alike 3.0 Unported (2,3,4), soit de ma production.

 Merci à Sam pour m'avoir fait découvrir Snakemake et ainsi donné l'idée générale de cet article, et à mes relecteurs chéris pour leur patience, leurs correction et leurs idées: Guillaume, Mica, Nallias et ZaZoOo (sisi miss, au moins pour la patience!)

Quelques pistes pour contrôler vos données de ChIP-seq

$
0
0

Le ChIP-seq est une méthode aujourd'hui répandue qui consiste à cibler une partie du génome grâce à une protéine et à séquencer uniquement les parties du génome auxquelles celle-ci s'est fixée. On la capture ensuite avec un anticorps spécifique et on séquence uniquement l'ADN qu'elle protégeait (voir notre article : DNase-seq, FAIRE-seq, ChIP-seq, trois outils d'analyse de la régulation de l'expression des gènes ). La technique a été adaptée à de nombreuses sources d'intérêt (facteur de transcription, nucléosome, ARN polymérase…), mais l'objectif reste toujours le même, établir une carte du génome marquant les zones où la protéine s'est fixée. Finalement avec cette carte on peut par exemple chercher un motif de fixation (voir notre article : Soirée BED & FASTA ! ) ou encore chercher si les gènes marqués appartiennent à une famille particulière.

ADN légo par mknowels (https://www.flickr.com/photos/mknowles/)

ADN légo par mknowels

Dans cet article je vais détailler certaines étapes de la préparation des données de ChIP-seq, ce que vous pouvez faire une fois que vous avez reçu vos données au format fastq (voir notre article :  Analyse des données de séquençage à ARN). Le "protocole" ici détaillé est très fortement inspiré des articles cités en bas de page, je vous conseille donc de les consulter si vous ne craignez pas la langue de Shakespeare. Vous pouvez également consulter notre article, très complet, sur le traitement des données de RNA-seq, qui vous donnera de très bons conseils pour la préparation et l'analyse de vos données. Même si RNA-seq et ChIP-seq sont différents, certaines étapes de leur préparation sont similaires.

Avant même d'avoir vos données, sachez que pour une expérience de ChIP-seq il est indispensable d'avoir un échantillon contrôle, cela peut être un séquençage sans immunoprécipitation ou avec un anticorps non spécifique, qui ne ciblera rien sur votre génome. Ces données vous serviront à déterminer le bruit de fond de votre séquençage. Il est également très très fortement conseillé de faire des réplicats biologiques, ils serviront à déterminer les similitudes et la reproductibilité des informations trouvées.
Après alignement sur le génome de référence des données de ChIP-seq, on recherche des pics (voir le paragraphe 'Recherche de pics') formés par l'accumulation de séquences aux endroits où la protéine s'est fixée, mais on séquence également d'autres parties du génome, c'est le bruit de fond. Le contrôle et les réplicats vous serviront à séparer le vrai du faux.

 

Contrôle qualité du séquençage

Le séquençage à haut débit n'est pas une technique parfaite et certaines erreurs sont connues. On sait par exemple que la fin des séquences produites par la machine sont souvent de moins bonne qualité que le début. Pour vérifier que tout c'est bien passé, voici quelques tests que vous pouvez effectuer.

La première information que l'on peut vérifier est simplement si le nombre de lectures (séquences obtenues par séquençage à haut débit) est suffisant pour l'analyse. Les données de ChIP-seq peuvent être divisées en trois catégories en fonction de la forme de leurs pics:
- Pics fins, peu étendus et qui pourront être réduits à une position précise sur le génome. On obtient ce genre de pic en étudiant les facteurs de transcription et certaines marques de chromatine.
- Pics larges/étendus, ici c'est une zone enrichie en séquences plus qu'une position qui sera marquée. On obtient ces pics avec certaines marques de chromatine et l'étude de RNA Pol II par exemple.
- Pics mixtes, un mélange des deux premiers, les pics fins trouvés appartenant à des zones plus larges enrichies en séquences.

Du type de pics produit par vos données, dépend la quantité de lectures qu'il vous faudra. Pour un génome de mammifère, il est conseillé d'avoir 20 millions de lectures pour la recherche de pics fins et entre 40 et 60 millions de lectures pour les pics étendus (20-30 millions par réplicats).

Dans le même temps vous pouvez contrôler la qualité de vos séquences (score phred) et la distribution des nucléotides. Un déséquilibre dans la distribution des nucléotides dans un échantillon est souvent le signe d'un problème de séquençage, par exemple si la machine a produit des erreurs ou alors si vous avez séquencé des adaptateurs ou autres séquences qui ne vous intéressent pas. Il existe des logiciels qui feront ces contrôles pour vous et produiront également des graphiques pour faciliter la lecture des résultats (e.g FASTX-toolkit).

Une autre information que vous pouvez vérifier c'est la présence de séquences dupliquées. Le séquençage de vos données va probablement produire des séquences identiques, mais il ne faut pas que celles-ci soient trop abondantes, avec un petit peu de code bash et awk vous pourrez compter la proportion des séquences répétées et détecter une contamination par des adaptateurs ou autres (voir le "Nature protocol" en lien à la fin de l'article).

En fonction de la qualité de votre séquençage, il peut être utile de retirer une partie des lectures si leur qualité est trop faible, ou encore de couper la fin des séquences si la qualité diminue drastiquement le long du séquençage, avec le logiciel sickle par exemple. Quoi qu'il en soit il est conseillé de garder une taille unique pour les lectures, car deux séquences de tailles différentes n'ont pas les mêmes propriétés d'alignements et cela va créer une variation supplémentaire dans votre échantillon.

 

Alignement

Maintenant que vous êtes sûr de la qualité de votre séquençage, vous pouvez passer à l'alignement (mapping) et à la recherche de pics (peak calling). L'alignement est une étape commune à toutes les données de séquençages, il s'agit simplement de chercher l'origine de votre séquence sur le génome. Le fichier obtenu vous indique la position de chaque lecture sur le génome et vous permet donc de savoir ce que vous avez séquencé. Vous trouverez plus d'informations sur notre site dans nos articles sur la préparation de données de RNA-seq et leur analyse. Personnellement j'utilise Bowtie2 pour mes données de ChIP-seq, mais il existe une multitude de mapper (BWA, GEM, STAR...) n'hésitez pas à donner vos préférences en commentaire.

Vos données sont maintenant alignées, il y a de grandes chances pour que vous ayez un fichier SAM ou BAM entre les mains. Si vous avez un SAM utilisez SAMtools pour changer de format et obtenir un BAM, il s'agit du même fichier mais en binaire, ça vous permet d'économiser beaucoup de place sur votre disque dur et la plupart des logiciels utilisent ce format. Cela dit en fonction des données et des logiciels que vous utiliserez, vous devrez peut-être également les convertir en fichier BED, utilisez BEDtools pour changer de format (celui-ci prend plus de place sur le disque).

 

Qualité de l'échantillon après alignement

Avoir des séquences de qualités et leurs positons sur le génome, c'est bien, mais ce n'est pas suffisant. Les données de ChIP-seq ont des propriétés particulières, principalement parce qu'on ne séquence qu'une partie infime de génome, celle marqué par notre protéine. Voici quelques tests pour vérifier que votre échantillon se comporte bien comme attendu:

- Après alignement des données sur le génome, vous pouvez vérifier le nombre de lectures qui s'alignent à une position unique sur le génome (au moins un tiers de vos séquences) et la proportion de ces lectures dont la séquence en nucléotide est unique (au moins 50%). Vous pouvez également calculer le score NRF (Non-Redundant Fraction) = (Nombre de lectures uniques)/(nombre de lectures alignées uniquement). Il est conseillé d'avoir un NRF supérieur ou égal à 0,8 pour un échantillon avec 10 millions de lectures.

- Le logiciel CHANCE calcule pour vous le score "IP strength". Il compare la distribution des lectures dans votre échantillon et votre contrôle à des données produites pour le projet ENCODE, le score vous indique le degré de réussite de votre ChIP-seq. CHANCE produit des graphiques pour une analyse visuelle des résultats. Attention cependant, comme ENCODE est un projet sur le génome Humain, le logiciel est moins/pas adapté aux autres organismes.

- Le logiciel phantompeakqualtools, permet de calculer une série de scores qui vous aideront à déterminer la qualité de votre expérience. Les scores NSC (Normalized Strand Correlation) et RSC (Relative Strand Correlation) sont expliqués en détails dans le papier ENCODE. Dans les grandes lignes, un pic de ChIP-seq est visible sur le brin plus et sur le brin moins, mais il existe un décalage entres ces 2 pics. Ce décalage est considéré comme la taille du fragment marqué par la protéine. Le logiciel calcule la corrélation entre les pics du brin plus et du brin moins, ce qui pour une expérience de ChIP-seq réussi produit deux pics, un marquant la taille des lectures et un la taille du fragment. Le NSC est le rapport entre le score de corrélation trouvé pour le fragment et le score minimum et RSC est le rapport entre le score de corrélation pour le fragment moins la corrélation minimum et le score pour la taille des lectures moins la corrélation minimum. Pour le projet ENCODE, les échantillons avec une qualité trop faible (i.e NCS<1,05 ou RSC<0,8) sont séquencés de nouveau.

 

Recherche de pics

La recherche de pics ou peak calling est l'étape essentielle de l'analyse de ChIP-seq. C'est ici que vous allez déterminer quelles positions du génome ont été marquées par votre expérience et la qualité de votre analyse dépend directement de ce résultat. Toute la difficulté réside dans le fait de pouvoir distinguer le bruit de fond des zones marquées par votre protéine. La présentation de cette étape mérite à elle seule un article, cependant, comme pour l'alignement je ne vais pas trop rentrer dans les détails.

Comme dit précédemment, il existe plusieurs types de données en ChIP-seq pour le peak calling sur les échantillons avec des pics fins et mixés MACS2 fonctionne très bien et SICER pour les échantillons avec des pics larges (Il existe beaucoup de logiciels, n'hésitez pas à parler de vos préférences dans les commentaires). Dans tout les cas il existe une série de valeurs que vous pouvez faire varier et qui influenceront le résultat final. Les deux logiciels sus-nommés utilisent une fenêtre pour parcourir le génome et déterminer les zones enrichies en lectures. Vous pouvez faire varier la taille de la fenêtre, des gaps (trous) autorisés à l’intérieur d'un pic, du shift (déplacement d'une lecture à l’intérieur de la fenêtre)… selon les valeurs données votre peak calling sera plus ou moins restrictif. La plupart des logiciels donnent un score aux pics trouvés, FDR (False Discovery Rate) ou p-value, en jouant avec ces valeurs vous pouvez passer de quelques centaines à plusieurs milliers de pics trouvés.
Comme ces logiciels utilisent une fenêtre, il est possible que deux pics soient fusionnés si ils sont trop proches. Il existe également quelques logiciels pour raffiner vos résultats et éventuellement séparer ces pics (PeakSplitter, GPS, polyaPeak, narrowPeaks).

Un premier score que vous pouvez calculer sur votre peak calling est le FRiP (Fraction of Reads in Peaks) : Il s'agit simplement de compter le nombre de lectures étant dans un pic. Le projet ENCODE recommande un FRiP de 1% au minimum, tout en précisant que cette valeur peut ne pas fonctionner pour certains échantillons, comme ceux avec très peu de sites ciblé sur le génome.

 

Reproductibilité de l'expérience

La comparaison entre échantillons peut bien sûr servir pour confronter deux conditions, mais également deux réplicats, afin de valider les pics trouvés. En effet comme il est difficile de séparer signal et bruit de fond, on utilise les réplicats pour valider les pics. Si un pic est présent et fort dans deux réplicats c'est certainement un bon signe. Pour faire cela on peut utiliser deux scores:

- PCC (Pearson Correlation Coefficient) : La couverture par des lectures de chaque position du génome est comparée entre deux échantillons, donnant au final un score de corrélation. Deux réplicats devraient avoir un score supérieur à 0,9 là où deux échantillons sans rapport auront un score de 0,3-0,4 (voir "Nature Protocol").

- IDR (Irreproducible Discovery Rate) : On classe les pics de deux échantillons sur leur valeur de FDR, p-value ou autres critères montrant la probabilité d'être un vrai pic. Si on compare deux réplicats, les pics les plus forts devraient être plus consistants que les pics faibles. Sur un graphe produit à partir des deux listes de pics, classées par FDR par exemple, et montrant la consistance entre les deux classements, on devrait voir une transition entre les pics avec un fort FDR (présent dans les deux réplicats) et les pics avec un faible FDR. Le logiciel phantompeakqualtools permet de calculer le score IDR qui indique pour chaque pic si il a plus de chance d’appartenir au groupe des pics reproductibles ou à celui des pics non reproductibles.

Pour aller plus loin :
ChIP-seq guidelines and practices of the ENCODE and modENCODE consortia.
A computational pipeline for comparative ChIP-seq analyses.
Evaluation of Algorithm Performance in ChIP-Seq Peak Detection
Practical Guidelines for the Comprehensive Analysis ofChIP-seq Data

_________________
Cet article ne couvre que le pré-traitement des données de ChIP-seq, l'objectif ici est de vous indiquer quelques tests à faire pour vérifier la qualité de vos données et les logiciels qui permettent de produire certains d'entre eux. Pour l'analyse de vos données, je vous conseille de lire les articles mis en référence. Vous trouverez des détails sur les scores présentés, mais aussi d'autres informations sur la normalisation, la visualisation et sur les analyses possibles avec des données de ChIP-seq (Motif finding, comparaison entre différentes conditions...).

 

Merci aux relecteurs, ook4mi, bunny, nahoy et Nelly.


The Bio Code : guide du bon broinformaticien

$
0
0

Malgré la multitude d’outils déjà existants, les occasions d'écrire du code sont nombreuses en bioinformatique. Hormis pour les pousse-boutons avertis, le développement fait souvent partie du quotidien d’un bioinformaticien. Personnellement, c’est une activité qui me plaît beaucoup dans ce métier. Développer ses propres applications et outils apporte toujours une certaine satisfaction (et quand ça fonctionne, c’est encore mieux !). C'est un peu comme la cuisine de Mémé, c'est tellement meilleur quand c'est fait maison.

 Photo credit:  Marjan Krabelj (CC 2.0)

Photo credit: Marjan Krabelj (CC 2.0)

 

Seulement voilà, nous bioinformaticiens, ne sommes pas analystes programmeurs et cela peut parfois se ressentir sur notre manière de développer. Il suffit d’aller faire un tour dans les sources de certains outils (même connus) pour se rendre compte que ça a été codé avec les pieds par un bioinformaticien. J’ai moi-même écrit une quantité de scripts à l’arrache très mal optimisés, dont je suis peu fier mais qui m’ont permis, avec le temps, de devenir un peu plus rigoureux dans ma manière de développer. Depuis, j'essaie de suivre quelques règles de 'bonne conduite' dans tous mes projets, que ce soit un petit script ou un projet plus conséquent. J'espère que ces quelques points permettront à certains de prendre de bonnes habitudes. Allez, c'est parti !

 

______   __  __     ______       ______     __     ______       ______     ______     _____     ______    
/\__  _\ /\ \_\ \   /\  ___\     /\  == \   /\ \   /\  __ \     /\  ___\   /\  __ \   /\  __-.  /\  ___\   
\/_/\ \/ \ \  __ \  \ \  __\     \ \  __/   \ \ \  \ \ \/\ \    \ \ \____  \ \ \/\ \  \ \ \/\ \ \ \  __\   
   \ \_\  \ \_\ \_\  \ \_____\    \ \_____\  \ \_\  \ \_____\    \ \_____\  \ \_____\  \ \____-  \ \_____\ 
    \/_/   \/_/\/_/   \/_____/     \/_____/   \/_/   \/_____/     \/_____/   \/_____/   \/____/   \/_____/

 

1- Faire un état des lieux

Vous avez besoin d’une application spécifique ? Cherchez bien, quelqu’un a sans doute déjà écrit quelque chose de similaire. Cette première étape me paraît assez évidente et je suis sûr que vous êtes rodés à l'utilisation d'un moteur de recherche. C’est une bonne chose de proposer des alternatives mais de manière générale, évitez de réécrire des outils qui existent déjà… sauf si c’est pour faire mieux évidemment !

2- Choisir un langage adapté

Un cahier des charges est un bon point de départ pour le choix du langage. Parfois quelques bullet-points suffiront pour organiser ses idées et trouver un langage adapté. À mon avis, si le temps le permet, il ne faut pas avoir peur de se lancer dans l’apprentissage de quelque chose de nouveau qui pourra peut-être s’avérer utile dans d’autres projets futurs. De manière générale, “tout” est possible, avec n’importe quel langage de programmation, mais il est évident que certains sont meilleurs que d’autres dans certains domaines. Ne vous lancez pas, par exemple, dans du calcul matriciel en Perl… jamais…même sous la menace… Pour aller plus loin sur ce sujet, Gophys a réalisé un article concernant le choix des langages de programmation.

3- Utiliser un gestionnaire de version

 Photo credit: Sean MacEntee (CC 2.0)

Photo credit: Sean MacEntee (CC 2.0)

L’ utilisation d’un gestionnaire de version présente de nombreux avantages (collaboration, suivi des modifications, sauvegarde, etc). Il existe pléthore de services de gestion de développement en ligne (sourceforge, github, bitbucket, pour en citer quelques-uns). De plus, la plupart des gestionnaires de version peuvent être installés localement sur votre machine et sont relativement faciles à utiliser. Plus largement, l'article de Nolwenn vous présente quelques solutions pour mener à bien votre projet.

 

4- Fournir une documentation Même si votre programme n’est pas destiné à être publié, prenez l’habitude d’y ajouter des informations concernant son utilisation. Une documentation (même minimale) doit faire partie intégrante de tous vos développements. C’est toujours frustrant de tomber sur ce genre de chose:

$ java –jar tool.jar
You need at least 1 argument
Exit!
$ java –jar tool.jar -h
Wrong argument
Exit!
$ java –jar tool.jar -help
Wrong argument
Exit!
$ cat README
cat: README: No such file or directory
$ aaarrrrggggghhhh efwjkhfwefw
-bash: aaarrrrggggghhhh efwjkhfwefw: command not found
$ rm tool.jar

Toujours pas convaincu par l'utilité de ces commentaires ? Allez donc voir l'article de Nisaea !

 

5 - Limiter les bibliothèques externes

Intégrer des bibliothèques externes permet de facilement étendre les fonctionnalités de vos outils. Attention toutefois à ne pas rendre votre application complètement dépendante de celles-ci. C'est souvent la source de problèmes de compatibilité et cela peut rendre l’installation compliquée. Exemple d'un programme que vous ne voulez pas installer sur votre machine :

$ head README
This software is under LGPL licence,
Requirements :
 - cmake >= 2.8.3
 - freetype2
 - Qt 4.7.0 or higher
 - OpenGl 2.0
 - glew 1.4 or higher
 - gcc, g++ >= 4.5.0
 - python >=2.5
 - libmxml
 - libsigc++
 - libwps
 - glpk 3.40
 - libebml                     
 - libjpeg                       
 - liboggz
 - mysql  5.0 or higher
 ...

 

6 - Ecrire du code réutilisable …

On a tous des scripts sans commentaire, ni documentation qui trainent sur un coin de disque dur. Avec les années, ces bouts de code se perdent et on se retrouve à réécrire les mêmes routines. Écrire du code réutilisable, c’est avant tout une manière de programmer (écrire des fonctions génériques, utiliser des classes, programmation orientée objet, etc) et cela vient avec la pratique. Personnellement, je n'ai pas encore trouvé la solution idéale  pour organiser mes snippets (mais je n'ai pas trop cherché non plus). De nombreuses possibilités existent : utilisation d'un gestionnaire de version, outils dédiés, simples fichiers textes organisés par langage, etc. D'ailleurs, si quelqu'un a des recommandations, ça m'intéresse.

 

7 - … et extensible

Il arrive très souvent de devoir étendre les fonctionnalités de ses outils, gardez toujours cette idée en tête ! Là encore, écrire du code extensible dépend beaucoup de la manière de programmer, de la pratique, mais surtout l'utilisation de design patterns adaptés faciliteront l'ajout de nouvelles fonctions. Idéalement, l’ajout d’extensions doit être possible sans avoir à modifier votre code de base.

 

8 - Utiliser des formats standards

L’utilisation de formats standards pour les fichiers d’entrée/sortie rendra vos applications plus simple à utiliser. De plus, les formats standards possèdent souvent des modules de lecture/écriture que vous pourrez intégrer directement dans vos outils.

xkcd - (CC BY-NC 2.5)

9 - Tester !

Votre application a de grandes chances de ne pas fonctionner du premier coup. Il est important de tester et vérifier que tout fonctionne avant de la publier. Prévoyez des jeux de données à essayer et, si possible, trouvez des volontaires pour tester votre application afin d’avoir des avis extérieurs. Enfin, essayez d’inclure un minimum de tests unitaires  afin d’assurer le bon fonctionnement de votre programme.

 

10 - Ne lésinez pas sur la gestion d'erreur

(CC-NC 2.0)

XP classic (CC-NC 2.0)

Ces deux derniers points s’appliquent surtout aux applications rendues publiques. Une bonne documentation associée à une bonne gestion d’erreur permettront à vos utilisateurs de comprendre ce qui se passe en cas de problème et leur permettra de se débrouiller. Essayez de fournir des messages d’erreur « utiles » pour l’utilisateur.

 

10 * - Attention aux licences.

Photo credit: Irish Typepad (CC-BY-NC-ND 2.0)

Photo credit: Irish Typepad (CC-BY-NC-ND 2.0)

 

 

 

Enfin, il est important de vérifier la licence  des bibliothèques ou modules que vous  souhaitez intégrer à votre application. Je ne fournirai pas de conseils légaux car de mon coté, j’ai toujours trouvé les termes des licences assez confus. Mais c’est un point important à considérer, surtout si votre application est destinée à être publiée.

 

 

 

Merci à Wocka, Clem_, et Yoann M. pour leur relecture.

Ping en JavaScript (jQuery.ajax)

$
0
0

Dans cet article, nous allons découvrir un moyen de vous faire gagner du temps à travers deux notions étrangères à la Biologie : Le ping et Ajax. Pour les termes techniques, reportez vous au glossaire juste après l'introduction.

Day 6/365 | SuperFantastic

Day 6/365 | SuperFantastic

Mesurer le délai entre un serveur et un client web (on utilisera facilement anglicisme "ping", francisé en "pinguer") d'un site consiste à lui demander juste l'en-tête de sa page principale (afin de diminuer l'impact du poids du site) et à mesurer le temps passé pour recevoir la réponse. En comparant les temps de réponse de plusieurs miroirs d'un site, cette astuce vous donnera l'accès au miroir le plus rapide, qui peut varier selon l'ordinateur et la localisation. Quelle utilité ? Supposons que je veuille télécharger un millier de records sur une base de données et que le téléchargement de 1 record prend environ 100ms. Si le temps de réponse est de 200ms, je vais mettre 300ms pour obtenir 1 record, donc 5 min (1000 x 300ms) pour tout télécharger. Si par contre j'ai sélectionné un miroir avec un temps de réponse de 700 ms, le téléchargement total passe a 13 min (1000 x 800ms). Ce n'est rien si vous devez le faire une seule fois mais cela peut aboutir a une grosse différence de temps si vous avez plus de données a télécharger ou que vous deviez le faire plusieurs fois.

En perspective, ceci peut servir à faire une interface web qui doit charger des données depuis d'autres sites (ex: D.A.S.) ou simplement choisir le genome browser qui répond le plus vite.

Observation : Souvent les webservices populaires hébergés aux USA sont calmes le matin et saturé l'après-midi (GMT, raisonnement inverse pour les serveurs asiatiques), cette astuce permet de basculer sur un miroir  sans avoir à les évaluer manuellement.

Glossaire

- HTML : langage textuel de mise en forme en utilisant des balises. Sert principalement à implémenter l'ossature d'un site web (apparenté à XML, la partie ML markup language signifie que c'est un langage à balise).

- le ping est le temps de réponse entre deux machines d'un réseau. Par exemple entre votre ordinateur personnel et un serveur distant (le réseau étant principalement Internet).

- JavaScript est un langage de programmation massivement utilisé dans le monde du Web, ce serait d'ailleurs le langage web le plus utilisé (exécuté sur les processeurs).

- Ajax est une méthode pour effectuer des échanges entre un navigateur web (coté client, vous) et un serveur. Cette méthode permet de rafraîchir le contenu d'un site que l'utilisateur intervienne ou non, pour gagner en dynamisme. Nous n'utiliserons que partiellement les possibilités d'Ajax.

- jQuery est une bibliothèque de JavaScript pratique : elle allège considérablement la notation et rend le code plus clair.

- Site miroir d'un autre site : héberge le même contenu. Ces clones servent à pallier à la faiblesse du réseau Internet afin de désengorger les sites. Exemple, la liste des miroirs de l'UCSC.

- D.A.S. : documents disponibles sur un site, sur un serveur donné, qu'on utilise ailleurs (pistes de visualisation d'un genome browser Ensembl...)

- genome browser : visualisateur de génome, ici il s'agit d'un site web permettant de visualiser un génome et lui faire correspondre des données transcriptomiques, protéiques et autres -omics.

- génome : ensemble de morceaux d'ADN qui signifient plus ou moins quelque chose (je ne peux être plus précis). Pour faire simple, c'est l'ensemble des gènes (fraction d'ADN plus ou moins définie, la définition dépend des écoles de pensées en Biologie).

 

Aperçu de JavaScript/jQuery

Pourquoi s'orienter vers le JavaScript ? Essentiellement pour sa légèreté et son déploiement très facile, il s'exécute dans les navigateurs web (modernes) sans installation particulière.

L'idée étant de tester le temps de réponse des serveurs par rapport à votre machine, c'est à elle de faire le boulot (et si ça peut se faire avec un minimum d'efforts, ce n'est que mieux).

 

Afin de démarrer petit, commençons par le commencement : une page HTML ! Elle va nous servir à poser un cadre sur lequel nous allons mettre des éléments (liens vers les sites) que nous modifierons à loisir grâce à javascript.

Code HTML basique:

<input type="button" id="test" value="Ping these!"/>
<table>
    <tr>
        <td>US</td>
        <td><div id="statusUS" value="128.114.119.137"></div></td>
    </tr>
    <tr>
        <td>Europe</td>
        <td><div id="statusEU" value="192.38.44.238"></div></td>
    </tr>
</table>

 

Code jQuery simple (si si, il n'y a que deux boucles et pas de récursion).

$("#test").click(function(){
    $("div").each( function(){
        var msg = this.id,
            ip = $(this).attr("value"),
            avg = 0,
            cpt = 0,
            i=0;
        for(i=0; i<10;i++){
            var start = $.now();            
            $.ajax({ type: "HEAD",
                    url: "http://"+ip,
                    cache:false,
                    complete: function(output){ 
                        var ping = $.now() - start;
                        if (ping < 1000) { // useless?
                            cpt++;
                            avg+= ping/cpt - avg/cpt; //update average val
                            $("#"+msg).text(avg+" ms (on "+cpt+"tests)");
                            if(avg < 200) {
                                $("#"+msg).css({"color": "green"});
                            } else if (avg < 500) {
                                $("#"+msg).css({"color": "orange"});
                            } else {
                                $("#"+msg).css({"color": "red"});
                            }                                
                        }
                    }
              });
        }        
    });
});

 

Pour apprendre en jouant en direct : http://jsfiddle.net/FHWZc/58/

Explications sur le code javascript :

Par défaut, il ne se passe rien, il faut cliquer pour lancer les requêtes. Si vous voulez tout automatiser, il va falloir se remonter les manches...  mais d'abord, faisons ensemble le tour de ce code :

$("#test")

Cette expression jQuery permet de sélectionner le bouton (si vous n'êtes pas familier avec jQuery, mémorisez "$" comme "Sélection") d'après son identifiant "test".

$("div").each( function(){

Ceci correspond à une boucle "for" qui va, sur chaque élément (DOM, constituent le HTML) "div" appliquer le bloc qui suit...

Remarque : À chaque div correspond une et une seule adresse URL (ou adresse IP, selon ce que vous avez sous la main).

 

Après avoir déclaré quelques variables (ni Python ni PHP ici, soyez gentils et déclarez bien vos variables), avec les mentions "var", voici la partie qui fait tout le travail :

for(i=0; i<10;i++){
    var start = $.now();            
    $.ajax({ type: "HEAD",
        url: "http://"+ip,
        cache:false,
        complete: function(output){ 
            var ping = $.now() - start;
            if (ping < 1000) { // 1 times out of 2, the diff is not computed
                cpt++;
                avg+= ping/cpt - avg/cpt; //update average val
                $("#"+msg).text(avg+" ms (on "+cpt+"tests)");
                if(avg < 200) {
                    $("#"+msg).css({"color": "green"});
                } else if (avg < 500) {
                    $("#"+msg).css({"color": "orange"});
                } else {
                    $("#"+msg).css({"color": "red"});
                }                                
            }
        }
    });
}

L'idée du ping est de mesurer le temps (chronométrer) mis par le serveur cible pour répondre à une demande toute simple.

  1. Prendre l'heure ($.now() retourne en effet l'heure, exprimée en seconde), c'est le "top départ".
  2. Effectuer une requête ajax (détail plus bas).
  3. Mesurer la différence de temps mis pour effectuer la requête précédente.
  4. Si le temps n'est pas aberrant (ce code n'est pas optimal mais pédagogique, une fois sur deux le calcul de la différence ne se fait pas bien), faire:
  5. Calculer la moyenne (avg : average : moyenne) au fur et à mesure de la boucle.
  6. Changer la couleur du texte selon la valeur de la moyenne :
    • La couleur change en direct, c'est amusant.
    • Ces valeurs seuils sont complètement arbitraires et assez subjectives.
    • Cette partie peut être changée de manière à sélectionner l'adresse qui répond le plus vite.
    • entre parenthèses, le nombre de tests considérés pour le calcul de la moyenne (souvenez-vous qu'une moyenne (arithmétique) ne signifie RIEN sans dimension).

 

Explications sur le code Ajax :

$.ajax({ type: "HEAD",
         url: "http://"+ip,
         cache:false,
         complete: function(output){
    }
});

Fonction ajax : c'est la méthode qui permet d'envoyer une requête au serveur (plus de détails : http://api.jquery.com/jQuery.ajax/).

Le type : en déclarant "HEAD", vous faites une déclaration de non-agression au serveur cible : cela signifie que l'on ne demande que l'en-tête de la page à l'adresse appelée. Rien de plus (on a pas besoin d'en savoir davantage pour estimer si un serveur est en carafe ou de bonne humeur).

url : l'adresse de la cible, ce que vous avez renseigné dans le code HTML comme valeur du div en cours de traitement

cache : faux car on ne fait que passer (et nous repasserons plusieurs fois) en "coup de vent"

complete : Une fois la requête effectuée, passer à la suite du bloc.

Il existe d'autres clauses success, error, timeout, statusCode... voyez donc *la documentation* pour agrémenter cette partie selon votre volonté (comme griser une adresse qui n'est pas disponible).

 

Amélioration : Charger les informations depuis un fichier de configuration, exemple avec un json :

{"SITE_NAME_A": {
    "mirror_name1": "adress1",
    "mirror_name2": "adress2"
    },
"SITE_NAME_B": {
    "mirror_name3": "adress3",
    "mirror_name4": "adress4"
    }
}

Pour chaque site, une liste de miroir(s). "(s)" car vous pouvez très bien ne mettre qu'une adresse dont vous voulez simplement estimer l'état.

 

Version qui change le lien principal : 1 ligne à ajouter au HTML + un petit bout de code jquery : http://jsfiddle.net/FHWZc/59/

if(best_ping > avg) {
    best_ping = avg;               
    $("#siteUCSC a").attr("href",ip);
}

NB: pensez bien à déclarer best_ping AVANT le $.each

NB: d'une version "à la carte" à l'autre, le code s'alourdit coté JavaScript, mais traite une quantité virtuellement infinie de données.

Installation/Déploiement

En deux étapes  simples :

Créer une page (x)html sur votre ordinateur (dans vos dossiers personnels par exemple) et inclure le code JavaScript sous la forme d'un fichier .js (ou dans des balises <script>, à vous de voir).

Mettre un favori sur ladite page (ou mieux, la mettre en page de démarrage du navigateur).

 

Problèmes connus :

Lorsqu'il n'y a aucun résultat à afficher, les temps de réponse sont trop long.

Mettez un miroir à 127.0.0.1 dans votre code et vous serez surpris : cette IP est sensée être celle du serveur local, on s'attend à un ping nul...

Pour chaque adresse, précisez le protocole ! IPv6 semble coincer, quelqu'un sait-il pourquoi ?

Utilisateurs de chrome, vous ne pouvez pas utiliser ce site en local (en appelant par file://), il faut l'appeler par http.

 

Merci aux relecteurs wocka, bunny, frvallee, MoUsSoR, nahoy, ZazoOo pour leur aide, patience et conseils avisés.

 

RNA-seq : plus de profondeur ou plus d'échantillons ?

$
0
0

Lorsque l'on se lance dans l'aventure du séquençage haut débit de transcriptome, on est amené à se poser LA question, oui LA, celle que l'on redoute à peu près tous quand on a un budget serré :

À quelle profondeur dois-je séquencer mes échantillons ?

Toutes les publications s'accordent à le dire, plus on a de réplicats, plus on a de puissance statistique pour détecter les gènes différentiellement exprimés. Or, même si les coûts de séquençage ont raisonnablement baissé, il n'est toutefois pas offert à tout le monde de séquencer des dizaines (voire des centaines) d'échantillons. Si votre projet se focalise sur l'expression des gènes, la solution pour trouver un bon compromis entre puissance statistique et coût est d'évaluer la profondeur de séquençage optimum afin de séquencer le plus d'échantillons sans pour autant perdre de l'information.

Petit rappel sur ce qu'est la profondeur de séquençage

Un séquenceur, type Illumina HiSeq 2000 par exemple, n'est capable de séquencer qu'un nombre fini de reads. Dans le cas du HiSeq 2000, le séquençage s'effectue sur 2 flow-cells contenant chacune 8 lignes (cf. cette vidéo pour en savoir plus sur le séquençage Illumina). Chaque ligne peut produire jusqu'à 200 million de reads dits single-end (SE, une seule extrémité du cDNA est lue) ou 400 million de reads dits paired-end (PE, les deux extrémités du cDNA sont lues). Il est possible de combiner plusieurs librairies (ou échantillons) sur une même ligne en utilisant des systèmes d'index qui permettent de taguer chaque fragment cDNA afin de retrouver à quel échantillon ils appartiennent. L'équation est alors simple. Plus on combine d'échantillons sur une ligne, moins on a de reads par échantillon.

Si l'on combine trop d'échantillons, on risque de passer à côté des gènes faiblement exprimés (ARN de faible abondance), mais le coût du séquençage par échantillon est faible. À l'inverse, si l'on combine peu d'échantillons, on va peut-être séquencer plus de reads qu'il n'en faut pour détecter l'expression des gènes et le coût par échantillon est beaucoup plus élevé.

Si votre projet se base sur l'expression globale des gènes et non pas sur la détection de transcrits alternatifs, il n'est peut-être pas nécessaire de séquencer 25-30 million de reads par échantillon comme il est habituel de faire. Il est possible de réduire le nombre de reads par échantillon et donc de mettre plus d'échantillons dans le séquenceur pour le même budget.

Évaluer la profondeur de séquençage

Je vais prendre un cas concret pour exposer la problématique. Dans le cadre de mon projet de thèse, je fais du single-cell RNA-seq sur des tissus embryonnaires de souris pour identifier des lignées cellulaires et suivre leurs différenciations au cours du temps. En tout, j'ai 7 conditions et un total de 640 librairies en stock (~90 cellules par condition). Le laboratoire ne peut financer qu'un seul run de séquençage (2 flow cells). Problème : dois-je séquencer seulement une partie de mes échantillons ou bien la totale ?

Au tout début du projet, nous avions réalisé un test où nous avions séquencé en paired-end une trentaine de cellules avec une profondeur de 25 millions de reads. Grâce à ce travail préliminaire, nous avons prouvé que nous étions capable de détecter tout plein de gènes nous permettant d'identifier nos différentes lignées cellulaires mais la puissance statistique restait malgré tout assez faible. En effet, une des lignées étant très faiblement représentée, les gènes spécifiques à cette population ne sortaient pas comme significatifs. Nous savons donc qu'il nous faut plus de cellules par stade pour être plus robustes dans nos analyses.

Afin de calculer combien d'échantillons je peux réussir à caser dans le séquenceur sans trop perdre d'information, j'ai été amenée à évaluer la profondeur optimale de séquençage.

Pour cela, j'ai choisi 3 fichiers FastQ de mes premiers séquençages (fichiers bruts contenant les reads pas encore mappés) représentant 3 types cellulaires différents et j'y ai prélevé de manière aléatoire un certain pourcentage de reads pour simuler des séquençages de moins en moins profond.

Pour faire ce prélèvement aléatoire de reads j'ai utilisé le logiciel HTSeq (python) qui propose plein d'outils permettant de manipuler des fichier FastQ et Bam entre autre. Vu que j'ai mis un peu de temps à trouver la méthode, je partage le code avec vous :

import sys, random
import HTSeq

"""
Prélève aléatoirement une fraction de reads dans des fichiers FastQ
Exemple pour des RNA-seq paired-end reads

Usage:
python getRandomReads.py 0.5 input_1.fastq.gz input_2.fastq.gz output_1.fastq.gz output_2.fastq.gz
"""


# Pourcentage de read à prélever
fraction = float( sys.argv[1] )

# Lire fichier FastQ read-pair 1
in1 = iter( HTSeq.FastqReader( sys.argv[2] ) )

# Lire fichier FastQ read-pair 2
in2 = iter( HTSeq.FastqReader( sys.argv[3] ) )

# Fichier de sortie FastQ read-pair 1
out1 = open( sys.argv[4], "w" )

# Fichier de sortie FastQ read-pair 2
out2 = open( sys.argv[5], "w" )

# La magie se passe ici
while True:
   read1 = next( in1 )
   read2 = next( in2 )
   if random.random() < fraction:
      read1.write_to_fastq_file( out1 )
      read2.write_to_fastq_file( out2 )

# Fermer les fichier créés
out1.close()
out2.close()

Sachant que j'avais combiné ensemble 16 échantillons, j'ai théoriquement séquencé à : 400M/16=25 million de reads par échantillons. Si je veux simuler un séquençage à 10M, je vais donc prélever (10/25)*100=40% des reads de mes 3 FastQ. J'ai choisi de simuler des séquençages allant d'une profondeur théorique de 0.1 à 20 million de reads et je me suis fait un petit script shell quick'n dirty pour lancer les simulations en parallèle sur un serveur de calcul.

Une fois les simulations faites, il faut mapper tout ça sur le génome de référence (dans mon cas, celui de  la souris) et calculer le nombre de gènes que l'on détecte pour chaque profondeur de séquençage simulée. On utilise le logiciel d'alignement de son choix (Bowtie, BWA, GemMapper, ...), puis on quantifie l'expression des gènes. Pour ce faire, j'ai choisi d'utiliser Cufflinks pour obtenir des FPKM (Fragments Per Kilobase Of Exon Per Million Fragments Mapped). Cette mesure assez répandue normalise le nombre de reads mappés sur un gène par rapport à la longueur de ce gène mais aussi par rapport au nombre total de reads mappés sur le génome. Cette mesure normalisée permet d'estimer l'abondance d'un ARN dans notre échantillon. On considère qu'un FPKM de 1 correspond à la présence d'une molécule d'ARN dans notre échantillon. On comptabilise le nombre de gènes détectés avec au moins 1 FPKM puis on met le tout sous forme de graphique pour obtenir ce que l'on appelle des courbes de saturation (merci R et ggplot2 !). On part du principe qu'il existe une profondeur de séquençage à partir de laquelle on détecte tous les ARN, et donc tous les gènes exprimés dans notre échantillons de départ. Si on séquence au delà de cette profondeur, le nombre de gènes restera inchangé puisqu'on a atteint le maximum détectable. La courbe de saturation permet de définir à partir de quelle profondeur on atteint le plateau.

Estimation de la profondeur optimum de séquençage pour une analyse d'expression de gène par RNA-seq (Isabelle Stévant, CC BY)

Sur ce graphique, il est clair que j’atteins un plateau autour de 5 million de reads, et ce, pour les 3 cellules. Si je séquence plus profond, je ne détecterai pas plus de gènes. Dans mon cas, si je veux séquencer mes 640 cellules sur les 16 lignes disponibles du séquenceur, je devrai au maximum combiner mes échantillons par 640 cellules /16 lignes = 40, soit une profondeur de 10 million de reads par cellules (400M reads par ligne /40 cellules). Selon la courbe de saturation, je devrais détecter le même nombre de gènes qu'avec un séquençage plus profond. J'ai voulu faire une dernière vérification en faisant une régression linéaire afin de m'assurer que les valeurs de FPKM pour chaque gène sont les mêmes entre la  profondeur à 20 million de reads  et celle à 10 million de reads :

Consistence entre un séquençage à 20M reads vs un séquençage à 10M reads (données simulées, Isabelle Stévant, CC BY)

Consistance entre un séquençage à 20M reads vs un séquençage à 10M reads (données simulées, Isabelle Stévant, CC BY)

La corrélation étant très très proche de 1, ce qui veut dire que les données sont extrêmement similaires, je peux prendre la décision de séquencer à une profondeur de 10 million de reads au lieu de 25 sans risque de perdre de l'information sur l'expression des gènes. Je passe donc de 256 à 640 échantillons pour un seul run de séquençage !

Conclusion

Si tout comme moi vous voulez étudier l'expression des gènes mais pas forcément aller plus en détail (i.e. variants et transcrits alternatifs), sachez qu'il est préférable de multiplier les réplicats plutôt que de tout miser sur un séquençage profond. La profondeur de séquençage peut être évaluée en amont si vous détenez déjà des données préliminaires ou suffisamment similaires (même tissu, même organisme). Cette étape vous permettra sans doute de multiplier vos expériences sans faire exploser le budget séquençage et ainsi d'améliorer vos résultats grâce à des analyses d'expression différentielles plus robustes.

 

[NDLR] Le séquençage de mes échantillons est toujours en cours au moment de la rédaction de cet article, je n'ai donc pas encore la certitude que ça ait fonctionné...

 

Merci à Mathurin, m4rsu, Nolwenn, Sylvain P.  et waqueteu pour la relecture et les conseils.

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

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/

Inkscape : l'outil idéal pour vos figures et posters

$
0
0
Logo Inkscape (licence GPL)

Logo Inkscape (licence GPL)

Je sais pas pour vous mais dans mon labo, le premier réflexe de mes collègues lorsqu'il faut faire une figure ou un poster c'est soit de dégainer la suite Ad*be, soit d'ouvrir P*werPoint. Quel est le problème me direz-vous ? Outre le fait qu'ils soient très coûteux pour le labo/l'université/l'entreprise où vous travaillez, ces outils ne sont pas forcément adaptés à l'usage que vous en faites. Le premier est souvent trop compliqué et très lourd, et le second n'est carrément pas fait pour ça. Il existe cependant un outil simple, gratuit, léger et puissant mais assez méconnu du grand public (et des scientifiques), qui permet à la fois de faire des jolies figures et posters sans trop de prises de tête. Cet outil, c'est le logiciel de dessin vectoriel Inkscape. Il est disponible pour les plateformes Windows, Mac OS et Gnu/Linux. Allez-y, n'ayez crainte, téléchargez-le, c'est un logiciel libre :)

Une fois Inkscape téléchargé et installé, voilà à quoi ça ressemble. Vous avez en face de vous une page blanche avec à droite une barre d'outils, en haut deux barres d'outils, et à droite, une quatrième barre d'outils mais qui ne vous servira probablement pas (en tout cas, je ne m'en suis jamais servie).

Capture d'écran du logiciel de dessin Inkscape

Capture d'écran du logiciel de dessin Inkscape

Démarrons par un peu de théorie

Comme dit plus tôt, Inkscape est un logiciel de dessin vectoriel. Selon Wikipédia, une image vectorielle, c'est "une image numérique composée d'objets géométriques individuels [...] auxquels on peut appliquer différentes transformations". En gros, vous ne pouvez pas juste dessiner comme si vous aviez un crayon au bout de votre curseur comme dans un logiciel de dessin classique. Pour dessiner, il faudra tracer des formes géométriques, qui deviendront des objets à part entière, cliquables, déplaçables, que vous pourrez tourner dans tous les sens. Ces objets ne sont pas composés de pixels, ce sont des bouts de codes. D'ailleurs, le format de fichier pour une image vectorielle n'est pas un PNG ou un JPEG (faits de pixels) mais le SVG pour Scalable Vector Graphics (en français "graphique vectoriel adaptable"). C'est un format de données très proche du XML dans lequel toutes les coordonnées et les caractéristiques de vos objets géométriques sont stockées.

Quel est l'avantage du SVG comparé à du PNG ? La puissance du SVG c'est que peu importe la taille finale de votre dessin, vous pourrez l'agrandir à l'infini sans aucune perte de qualité. Ben oui, vu qu'il n'y a pas de pixel mais que tout est codé, si on agrandi ça recalcule les coordonnées et ça retrace l'objet proprement. Du coup, pour les posters par exemple, on peut travailler sur un format A4 afin de ne pas faire surchauffer l'ordi pour rien et passer en A0 à la fin en un clic, sans perdre la mise en forme et sans perdre en qualité d'image.

Passons à la pratique

On va dans un premier temps créer une forme, puis la remplir avec une couleur, un dégradé, et changer les contours. Ensuite on apprendra à faire des flèches, à écrire du texte, à aligner des objets, gérer les superpositions, à grouper, dégrouper et cloner des objets. Quand vous aurez maîtrisé ça, vous aurez toutes les clés en main pour dessiner des figures. L'étape suivante consistera à savoir importer des images ou des pdf (oui oui, vous pouvez inclure des pdf dans un svg), puis d'exporter votre création dans le format de votre choix.

1- Jouer avec les formes...

Je pourrai vous proposer de dessiner un carré mais ça serait trop simple. Nous allons dessiner une forme complexe à l'aide de l'outil "Courbe de Bézier" bezier sur la barre d'outils de gauche. C'est l'outil que j'utilise le plus quand je fais des schémas. Pour créer la forme il suffit de cliquer un peu partout tout en prenant soin de refermer la forme en reliant le dernier point au premier.

shapeVoilà, on a une forme étrange assez anguleuse. Pour modifier la forme que l'on vient de créer on utilise l'outil "Éditer les nœuds" noeud, toujours sur la barre d'outils de gauche. Vous verrez apparaître des petits losanges gris à chaque nœud de la forme. Ces marqueurs permettent de bouger votre point pour le replacer à votre convenance. Pour créer des courbes, il faut tirer sur un segment avec la souris. Vous verrez des traits bleus partir des points. Ces traits servent à contrôler la courbure de votre segment. Pour transformer un angle en arrondi harmonieux, vous pouvez soit jouer avec les traits bleus, soit sélectionner le nœud en cliquant dessus et choisir l'option "Rendre symétrique"  noeud-2sur la barre d'outils du haut.

shape-3Ça commence à ressembler à quelque chose, je vous laisse admirer mon œuvre 😉

2- ... Et les couleurs
shape-4
Nous avons donc obtenu une forme structurée avec amour, qui se compose d'un contour noir, mais qui n'a pas de couleur. Il va falloir remplir cette forme pour la rendre plus attrayante. Sur la barre d'outils la plus en haut, sélectionner l'outil "Éditer les couleurs de l'objet" color. Un panneau à droite s'affiche et contient trois onglets qui va nous permettre de colorer notre forme : "Fond", "Contour", et "Style du contour". Plus bas dans ce panneau il y a également deux options avec lesquelles vous pouvez définir l'opacité de votre forme ou encore la flouter. Commençons par faire simple, remplissons notre forme avec une couleur. L'outil est suffisamment intuitif pour que je vous laisse trouver tout seul.

Une couleur plane, c'est bien, mais un dégradé, c'est mieux, ça rajoute de la profondeur et donne un côté plus travaillé à vos schémas. Pour appliquer un dégradé, choisissez un des deux types de dégradé proposés dans le panneau de droite. Pour changer la couleur, il faut sélectionner l'outil "Créer et éditer des dégradés" dans la barre d'outils de gauche, puis choisir un des "stops" pour en changer la couleur, dans la barre du haut (cf. cadres rouges sur l'image).

gradientVous avez compris les principes de bases pour colorer des formes à votre guise. Maintenant, explorez l'onglet "Contour" du panneau de droite. Il se compose exactement de la même manière que l'onglet précédent. Vous pouvez changer la couleur de vos contours de la même manière que pour le fond. L'onglet "Style du contour" vous permet de choisir l'épaisseur de votre contour et sa forme (terminaisons ou angles ronds, pointillés, etc).

3- Allons un peu plus loin : flèches et textes

Quand on fait des schémas, on fait inévitablement des flèches. Malheureusement, à première vue Inkscape ne propose pas d'outil direct pour faire une flèche, mais en réalité, il y a bien une manière simple pour en réaliser. Réutilisez l'outil "Courbe de Bézier" bezier et dessinez un segment (clic droit pour finir le trait). Allez dans "Éditer les couleurs de l'objet" color, onglet "Style du contour" et explorez les menus déroulant "Marqueurs". Le premier marqueur correspond à la première extrémité de votre segment, le second au milieu, et le troisième à l'autre extrémité. Dans ces menus vous trouverez diverses formes dont des têtes de flèches. Vous pourrez ainsi transformer votre trait en flèche, et lui imposer des courbures avec l'outil "Éditer les nœuds" noeud comme on l'a vu plus haut.

arrowPour le texte, c'est plus simple, choisissez l'outil "Créer et éditer des objets textes" text dans la barre de gauche puis cliquez là où vous voulez écrire. Vous pouvez éditer la taille et les couleurs du texte ainsi que rajouter un contour aux lettres tout comme on l'a vu pour la forme.

4- Aligner et superposer les objets

Un schéma bien organisé passe par des objets bien alignés. Pour aligner des objets, sélectionnez-les (attention, dans Inkscape, c'est avec "shift" qu'on sélectionne plusieurs objets et pas "ctrl"), et choisissez l'outil "Aligner et distribuer" aligner-2 dans la barre d'outils tout en haut. Un nouveau panneau s'ouvre à droite et vous propose tout plein d'options pour aligner vos objets selon vos besoins (à droite, à gauche, en haut, au centre...). Vous pouvez aligner les éléments entre eux, ou à la page en choisissant dans le menu déroulant "Relativement à".

alignerLes objets que vous créez sont comme une pile de feuilles de papier. Le dernier que vous créez se retrouve en haut de la pile et masque les autres. Si vous voulez changer l'ordre de vos objets, utilisez les outils d'empilement sur la barre d'outils du haut (cf. le cadre rouge).

empiler

5- Grouper, dégrouper et cloner

Une fois que vous avez disposé des objets et que vous ne voulez plus qu'ils se décalent malencontreusement les uns des autres, vous pouvez grouper les objets pour qu'ils ne deviennent qu'un. Pour cela, sélectionnez vos objets, faites un clic droit et sélectionnez "Grouper". Pour aller plus vite, le raccourci touche c'est "ctrl+G". Pour séparer des objets groupés c'est "ctrl+shift+G".

Si vous voulez copier/coller rapidement des objets, vous pouvez les cloner avec le raccourci touche "ctrl+D". Cela crée une copie de l'objet tout en le plaçant exactement au dessus de l'objet initial. A contrario, couper/coller va coller là où est votre curseur de souris. Ça n'a l'air de rien comme ça mais si vous ne voulez pas passer votre temps à ré-aligner vos objets c'est quand même plus pratique.

6- Importer des images et exporter vos œuvres

Maintenant que vous avez appris les rudiments pour créer vos propres objets, vous voudrez sûrement y mixer des images que vous avez déjà en stock et, à terme, exporter votre création dans un format PNG ou JPEG. Pour ça, utilisez un des deux outils import-export disponibles dans la barre d'outils tout en haut. Là encore, je pense que c'est assez bien fait pour que je ne vous explique pas comment ça marche.

Deux choses tout de même. La première, c'est que si vous voulez exporter votre schéma en PDF, il faudra faire "Enregistrer sous..." et choisir l’extension PDF. Ce format permettra de garder les textes sélectionnables, et de conserver le côté haute qualité que n'auront pas les PNG ou JPEG.

La seconde chose est que vous pouvez importer un PDF dans Inkscape (page par page s'il y en a plusieurs...). Le truc cool c'est que les PDF sont transformés par Inkscape en objets. Donc vous pouvez importer dans Inkscape un graphique en PDF généré dans R et l'arranger comme vous le voulez (changer des couleurs, changer le texte, etc). Les PDF que vous importez sont des objets groupés. Il vous faudra les dégrouper, parfois plusieurs fois, pour avoir accès à tous les objets.

Quelques réalisations personnelles pour vous inspirer

J'utilise Inkscape depuis pas mal d'années maintenant, et j'en apprend encore tous les jours. Je ne vous ai bien entendu pas dévoilé tous les trucs et astuces que je connais, sinon il y en a pour des jours. Pour finir de vous convaincre de la puissance de cet outil, voici quelques réalisations 100% Inkscape :

Poster Testis Workshop 2014 (CC-BY-SA Isabelle Stévant)

Poster Testis Workshop 2014 (CC-BY-SA Isabelle Stévant)

Émergence des gonades bipotentielles chez les mammifères (CC-BY-SA, Isabelle Stévant)

Schéma : Émergence des gonades bipotentielles chez les mammifères (CC-BY-SA, Isabelle Stévant)

Schéma : Détermination du sexe et développement des gonades chez les mammifères (CC-BY-SA Isabelle Stévant)

Schéma : Détermination du sexe et développement des gonades chez les mammifères (CC-BY-SA Isabelle Stévant)

 

 

 

 

 

 

 

 

 

 

Merci à Norore et Yoann M. pour leur relecture.

Snakemake pour les nuls (ou comment créer un pipeline facilement ?)

$
0
0

Bonjour à tous, et bienvenue dans le premier épisode d'une (longue ?) série de prise en main de l'outil dédié au pipeline : Snakemake.

Si vous ne connaissez pas encore cet outil, c'est que vous êtes sûrement passés à côté de cet article écrit par Nisaea. Alors, quel sera les bénéfices de retranscrire vos pipelines déjà tout prêt en Snakefile ?

Lisibilité du code, gestion des ressources et reproductibilité

Lorsque vous êtes sur le point de publier,  il va bien falloir expliquer aux futurs lecteurs comment vous avez obtenu les résultats. Cela permettra ainsi aux autres bioinformaticiens de partir de vos données brutes, et de retrouver les mêmes résultats que vous. On approche ici un point clé de la bioinformatique : la reproductibilité. Quels ont été les outils utilisés, à quelles versions, avec quels paramètres... Tous ces petits "trucs" qui permettent d'obtenir votre résultat. Un article scientifique avec des résultats majeurs découverts grâce à la bioinformatique DEVRAIT être associé à un pipeline/script permettant de reproduire les résultats à l'identique.

Ces dernières années ont vu le volume de données augmenter de façon quasi exponentielle. En parallèle, les ressources mises à disposition (telles que les clusters de calcul) n'ont cessé de croître en taille (nombre de cpu) et en puissance de calcul (vitesse des cpu), permettant ainsi de rester dans la course. Cependant, pour les exploiter au mieux, il est nécessaire de paralléliser nos tâches. En effet, beaucoup d'outils de bioinformatique ont des options permettant d'utiliser simultanément plusieurs cpu  (ex.: bwa, STAR, ...). D'autres, en revanche, n'ont pas été conçus pour être utilisés dans un environnement multi-cpu (awk, macs2, bedtools, ...). Dans ces cas précis, il faut soit paralléliser à la main (lancer plusieurs tâches en fond avec l'ajout d'un '&' à la fin de la commande), soit mettre les opérations en file d'attente et sous-exploiter la machine sur laquelle vous travaillez (gestion séquentielle des tâches). Nous verrons dans un prochain article qu'avec Snakemake on peut, très facilement, aller plus loin dans la parallélisation.

L'inventeur de Snakemake, Johannes Köster [1], a su tirer avantage de Python (syntaxe claire) et de GNU make (parallélisation, gestion des ressources,  système de règles). Avec l'ajout de nombreuses fonctionnalités comme le tracking de version/paramètres, la création de rapport html ou la visualisation des tâches et de leurs dépendances sous forme de graph (DAG), ce langage a tout le potentiel pour avoir un bel avenir.

Principes généraux

Tout comme GNU make, Snakemake fonctionne sur le principe des règles (rule). Une règle est un ensemble d'élément qui vont permettre de créer un fichier, c'est en quelque sorte une étape dans le pipeline.

Chaque règle a au moins un fichier de sortie (output) et un (ou plusieurs) fichier(s) d'entrée (input) (j'ai volontairement commencé par l'output, vision Snakemakienne, je pars de la fin pour aller au début).

La toute première règle du Snakefile doit définir les fichiers que l'on veut à la fin du traitement (fichier cible/target). L'ordre (et le choix) des règles est établi automatiquement à l'aide du nom des fichiers/dossiers cibles. On va ainsi remonter les fichiers de règle en règle jusqu'à trouver une règle avec un input plus récent que l'output. Ensuite, cette règle et toutes celles qui suivent vont être exécutées.

Snakemake fonctionne avec le nom des fichiers

Le plus simple serait de commencer par un exemple :

# règle qui spécifie les fichiers que l'on veut générer
rule targets:
    input:
        "data/raw/s1.fq.gz"
        "data/raw/s2.fq.gz"

# règle qui permet de compresser un fichier avec gzip 
rule gzip:
    input:
        # On peut utiliser cette syntaxe lorsque
        # input et output sont dans le même répertoire
        "{base}" 
    output:
        "{base}.gz" #on rajoute seulement l'extension à l'output 
    shell:
        "gzip {input}"

Vous l'aurez deviné, s'il existe data/raw/s1.fq et data/raw/s2.fq, plus récents que data/raw/s1.fq.gz et data/raw/s2.fq.gz alors la règle gzip va créer/remplacer les cibles. De plus, on peut paralléliser les opérations en passant en paramètre le nombre de processeurs à utiliser (option -j N, --jobs=N).

Chaque règle peut avoir plusieurs mots clés

Snakemake utilise des mots clés pour définir les règles.

Entrée/sortie

input = fichier(s) à utiliser pour créer la sortie
output = fichier(s) qui seront générés avec la règle

# exemple
rule mapping:
    input: "raw_data/{sample}.fq.gz"

    # utilisation d'une expression régulière pour définir wildcards.genome 
    # (commence par hg suivit d'un ou plusieurs chiffres)
    output: "mapping/{genome, ^hg[0-9]+$}/{sample}.bam"

    shell: "do_mapping -genome {wildcards.genome}.fa -fastq {input} -outbam {output}"

wildcards = Ce sont des variables qui sont définies avec le {} et une partie du nom/chemin des fichiers de sortie, cela permet de généraliser les règles. Avec cet exemple nous avons utilisé deux wildcards (genome et sample), on peut également utiliser des expressions régulières pour bien délimiter la définition d'un wildcards comme pour genome qui vaut "hg[0-9]+".

Utilisation de l'objet wildcards (par section) :

  • log : "mapping/{genome}/{sample}.sort.log"
  • shell/run/message :  "mapping/{wildcards.genome}/{wildcards.sample}.sort.log"
  • params : param1 = lambda wildcards : wildcards.genome + '.fa'

Explication :

En dehors de input/output/log on peut utiliser directement le nom de variable car l'objet wildcards n'est connu que de shell/run/message. C'est pour cela que l'on passe à la forme  "wildcards.variable" dans ses 3 dernières sections. En dehors de shell/run/message on peut utiliser "lambda wildcards : wildcards.variable". Ici la fonction lambda permet d'utiliser directement l'objet wildcards dans les sections.

Traitement des fichiers créés

temps = fichier temporaire supprimé après utilisation.
protected =  ce fichier sera protégé en écriture après création.

Commande pour générer le(s) fichier(s) sortie (run/shell)

shell = utiliser une commande UNIX pour créer le(s) fichier(s) sortie.
run = utiliser du code python pour construire le(s) fichier(s) sortie.

note: on doit choisir entre run et shell
note2 : avec run on peut utiliser la fonction shell()

Autres paramètres des règles

threads = nombre de processeurs maximum utilisables
message = message à afficher au début
log = fichier log
priority = permet de prioriser les règles (ex: quand plusieurs de règles utilisent le même input)
params* = paramètres des commandes utilisées
version* = version de la règle (ou de la commande utilisée)

*: ces deux mots clés permettent d'associer les paramètres et les versions des règles aux fichiers de sortie. Ceci permet de (re)générer un fichier de sortie si un paramètre ou une version de la règle a été modifiée. Il y a aussi du code tracking, pour suivre l'évolution du pipeline et des fichiers générés.

Un exemple plus concret :

Ici nous allons créer un pipeline d'analyse données génomique (ex : ChIP-seq) qui va aligner/mapper de petites séquences issues du séquençage haut débit (reads/lectures) sur un génome de référence.

################################
####### mapping avec BWA #######
################################
# Tous vos fichers fastq doivent être dans raw_data/xxx.fq.gz
# 
# L'extension doit être exactement fq.gz
# 
# Il doit y avoir hg38.fa dans ./
# 
# Pour des données paired-end (2 fichiers : _R1/_R2): 
# input: 
#     hg38.fa
#     raw_data/sampleX_c3-pe_R1.fq.gz
#     raw_data/sampleX_c3-pe_R2.fq.gz
# output:
#     mapping/hg38/sampleX_c3-pe.sort.bam
# 
# processus : 
#     Index du génome de référence
#     Mapping de deux jeux de données (se_7 et c3-pe)(bwa)
#         Alignement sur l'index du génome (bwa aln)
#         Transformation des coordonnées de l'index en coordonnées 
#             génomiques (bwa sampe/samse)
#     Rangement des données par coordonnées génomique (samtools sort)
#     Indexer le fichier ordonnée (samtools index)
#
################################
################################ 

# Pour récupérer la version des outils utilisés (voir rule sort/index)
import subprocess

# Version de bwa
bwa_version = "0.7.10"

# Fichiers en sortie du pipeline ("hg38.fa.bwt" aurait pu être omis)
rule target:
    input:
        "hg38.fa.bwt",
        "mapping/hg38/se_7.sort.bam.bai",
        "mapping/hg38/c3-pe.sort.bam.bai"

# Index du génome de référence avant l'alignement des lectures (séquences)
rule bwa_index:
    input:
        "{genome}.fa" #2) cherche {wildcards.genome}.fa"
    output:
        "{genome}.fa.bwt" #1) crée la variable wildcards.genome.
    message:
        "Index du génome de référence :{wildcards.genome}"
    version: bwa_version
    threads: 1  
    shell: #3) lance la commande
        "bwa index {input}" #"{input}"="{wildcards.genome}.fa"

# Alignement des lectures sur le génome de référence
rule bwa_aln:
    input:
        bwt = "{genome}.fa.bwt",
        fasta = "{genome}.fa",
        fq = "raw_data/{samples}.fq.gz"
    output:
        temp("mapping/{genome}/{samples}.sai") #fichier temporaire
    threads: 20 #max cpu autorisé
    version: bwa_version
    log:
        "mapping/{genome}/{samples}.bwa_aln.log"
    message:
        "Alignment de {wildcards.samples} sur {wildcards.genome} "
    shell:
        "bwa aln "       # avec Snakemake
        "-t {threads} "  # vous pouvez 
        "{input.fasta} " # commenter
        "{input.fq} "    # tous les 
        ">{output} "     # arguments 
        "2>{log}"        # des commandes

# Si les données sont appariées (paired-end), autrement dit si les fragments 
# ont été séquencés dans les deux sens, cette règle sera utilisée
rule bwa_sampe_to_bam:
    input:
        fasta = "{genome}.fa",
        sai1 = "mapping/{genome}/{samples}_R1.sai",
        fq1 = "raw_data/{samples}_R1.fq.gz",
        sai2 = "mapping/{genome}/{samples}_R2.sai",
        fq2 = "raw_data/{samples}_R2.fq.gz"
    output:
        #fichier temporaire
        temp("mapping/{genome}/{samples}.bam")
    log:
        "mapping/{genome}/{samples}.samse.log"
    threads: 2
    version: bwa_version
    message:
        "PE : sai --> bam ({wildcards.genome}/{wildcards.samples})"
    shell:
        "bwa sampe "
        "{input.fasta} "
        "{input.sai1} {input.sai2} "
        "{input.fq1} {input.fq2} "
        "2>{log} | "
        "samtools view -Sbh - > {output}"

# Si on a séquencé les fragments dans un sens (Single read) cette règle sera utilisée
rule bwa_samse_to_bam:
    input:
        fasta = "{genome}.fa",
        sai = "mapping/{genome}/{samples}.sai",
        fq = "raw_data/{samples}.fq.gz"
    output:
        #fichier temporaire
        temp("mapping/{genome}/{samples}.bam")
    log:
        "mapping/{genome}/{samples}.samse.log"
    threads: 2
    message:
        "SE : sai --> bam ({wildcards.genome}/{wildcards.samples})"
    shell:
        "bwa samse {input.fasta} {input.sai} {input.fq} "
        "2>{log} | "
        "samtools view -Sbh - > {output}"

# Pour ranger les données par coordonnées génomiques
rule sort_bam:
    input:
        "{base}.bam"
    output:
        # fichier protégé en écriture
        protected("{base}.sort.bam")
    threads: 10
    log:
        "{base}.sort.log"
    message:
        "Ordonne le fichier {wildcards.base}.bam"
    version: # pour récupérer la version de l'outil avec une commande shell 
        subprocess.getoutput(
            "samtools --version | "
            "head -1 | "
            "cut -d' ' -f2"
        )
    params:
        niveau_compression = "9",
        memoire_max_par_cpu = "1G"
    shell:
        "samtools sort "
        "-l {params.niveau_compression} "
        "-m {params.memoire_max_par_cpu} "
        "-@ {threads} "
        "-f {input} "
        "{output} "
        "2>{log}"

# Pour indexer le fichier trié (balise le fichier pour accéder aux données plus rapidement)
rule index_bam:
    input:
        "{base}.sort.bam"
    output:
        "{base}.sort.bam.bai"
    priority: 5 # augmente la priorité de la règle (défaut = 1)
    version: 
        subprocess.getoutput(
            "samtools --version | "
            "head -1 | "
            "cut -d' ' -f2"
        )
    threads: 1
    shell:
        "samtools index {input}"

Avec environ 130 lignes, ce code est déjà prêt à être utilisé de manière intensive sur pc, serveur ou cluster de calcul :)

A partir de ce Snakefile, on peut générer un graphe représentant la dépendance entre les règles avec la commande  :

snakemake --rulegraph | dot | display

 

rulegraph

Graphe représentant la dépendance entre les règles

On peut également générer un graphe plus complet qui prend aussi en compte les wildcards avec la commande :

snakemake --dag | dot | display

 

dag

Graphe représentant le pipeline complet

 

Cet article touche à sa fin,  je vous donne rendez-vous au prochain épisode, d'ici là, vous pouvez poser des questions dans les commentaires (ils seront peut être utilisés pour les prochains articles).

Et si ça vous a plu, n'hésitez pas à partager et nous faire part de vos astuces, proposez-nous des règles ou même des Snakefiles dans les commentaires, si cela a du succès nous ouvrirons peut-être un dépôt git pour regrouper toutes ces contributions.

Un grand merci aux relecteurs Yoann M., Lroy et Kumquat[um] pour leur remarques et corrections 😉

[1] Johannes Köster, "Parallelization, Scalability, and Reproducibility in Next-Generation Sequencing Analysis", TU Dortmund 2014


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.

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 ! (cliquez sur l'image)

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 !

L’article C'est l'enfeR. est apparu en premier sur bioinfo-fr.net.

S'outiller et s'organiser pour mieux travailler

$
0
0

TL;DR La reproductibilité, c’est la vie (dans le monde scientifique) ! Tout résultat doit pouvoir être reproduit. La technologie permet de faciliter la recherche de reproductibilité. Les cahiers de laboratoire papiers ne sont plus du tout adaptés à la recherche actuelle et au besoin de reproductibilité. Je préconise donc d’utiliser git et GitHub, de bien organiser ses projets et d’utiliser des cahiers de laboratoire électroniques.

Un des principes essentiels de la méthode scientifique est la reproductibilité, i.e. la capacité pour tous de reproduire toute expérience ou étude tout en obtenant des résultats similaires. C’est la pierre angulaire de la Recherche (Sandve, 2013) et ce qui distingue le caractère scientifique d’une étude. La reproductibilité n’est pas seulement une responsabilité morale vis-à-vis de la recherche. Le manque de reproductibilité peut aussi être un fardeau pour les scientifiques eux-même en limitant, par exemple, leur capacité à réutiliser leur code ou à réanalyser un jeu de données. De plus, la mise en place de bonnes pratiques de reproductibilité améliore la qualité des collaborations, en garantissant la capacité de reproduction et de transparence des analyses. Ces pratiques permettent un gain de temps à long terme et sont un gage d’une recherche de qualité.

La reproductibilité, c’est la vie (dans le monde scientifique)

http://blog.f1000research.com/2014/04/04/reproducibility-tweetchat-recap/

 

La reproductiblité se base principalement sur la transparence et une organisation claire des projets. N’importe qui (un relecteur, un collaborateur ou soi-même après quelques mois) ayant un projet en main doit pouvoir comprendre ce qui sa été fait (et pourquoi) et pouvoir reproduire les résultats obtenus. Pour cela, toute étude scientifique doit être bien organisée et documentée et suivre 10 règles (Sandve, 2013) essentielles :

  1. Le processus de production de chaque résultat est totalement retracable
  2. Toute étape de manipulation manuelle des données est évitée
  3. Les versions exactes des programmes externes utilisés sont archivées
  4. Tous les scripts développés et utilisés sont versionnés
  5. Tous les résultats intermédiaires sont conservés, si possible dans un format standardisé
  6. Les graines de l’aléatoire (random seeds) pour des études incluant une part d’aléatoire sont notées
  7. Les données brutes utilisés pour les graphiques sont conservées
  8. Les sorties des analyses sont organisées hiérarchiquement
  9. Les résultats sont commentés par écrit
  10. Les scripts et résultats sont publiquement accessibles

 

Mettre en place ces règles n’est pas toujours évident et on ne nous a généralement pas appris à le faire. Pendant nos études, la plupart de nos cours de bioinformatique se focalisaient sur la programmation et l’utilisation d’outils existants. Rien ne nous a donc préparé à gérer plusieurs projets scientifiques collaboratifs qui soient reproductibles. Nous apprenons sur le tas ces pratiques, au grès des rencontres et des interactions avec nos collègues. De plus, nous avons souvent pas conscience de l’importance de la reproductibilité et de la transparence dans la méthode scientifique.

Depuis un peu plus de 5 ans, je travaille sur différents projets bioinformatiques au quotidien. J’ai eu la chance de pouvoir travailler dans plusieurs laboratoires avec des scientifiques très différents. J’ai pu ainsi apprendre et essayer différentes techniques et pratiques. Mais ce n’est que récemment que j’ai commencé à me poser des questions sur notre façon de faire de la recherche. J’ai alors essayé de comprendre quelles pouvaient être les bonnes pratiques, leurs avantages et leur mise en oeuvre. 3 grandes pratiques, que je vais détailler dans cet article, me paraissent essentielles pour gérer un projet bioinformatique et prendre soin du principe de reproductibilité.

Bien organiser ses projets

Tout le contenu d’un projet (à l’exception pour du code source ou des scripts partagés entre plusieurs projets) doit être conservé dans un dossier (un dossier par projet). Je parle ici du contenu sous forme informatique, la forme privilégiée (la plus facile à tracer et partager de nos jours). Ce dossier doit être bien structuré. En effet, la bonne structure d’un dossier peut faire la différence dans la réussite d’un projet (accès facile et rapide aux bonnes données, à la bonne version du workflow, …). William Stafford Noble (2009) propose une réflexion sur la structure idéale des dossiers que Samuel Lampa a étoffée (2015). Quelques idées intéressantes ressortent de ces réflexions :

  • Une structure unifiée avec d’abord un niveau logique, puis un sous-niveau chronologique, puis encore un sous-niveau logique
  • Un dossier pour chaque nouvelle expérience ou analyse incluant
    • Un script permettant de reproduire exactement l’analyse
    • Tous les changements (code, workflow, configuration, …) par rapport aux autres expériences ou analyses

 

Pour illustrer ces idées, Samuel Lampa (2015) propose d’organiser les dossiers des projets suivant la hiérarchie :

├── bin  --------------------# Binary files and executables
├── conf --------------------# Project-wide configuraiotn
├── doc  --------------------# Any documents, such as manuscripts being written
├── exp  --------------------#  The main experiments folder
│   ├── 2000-01-01-example --# An example Experiment
│   │   ├── audit -----------# Audit logs from workflow runs
│   │   ├── bin   -----------# Experiment-specific executables and scripts
│   │   ├── conf  -----------# Experiment-specific config
│   │   ├── data  -----------# Any data generated by workflows
│   │   ├── doc   -----------# Experiment-specific documents
│   │   ├── log   -----------# Log files from workflow runs
│   │   ├── raw   -----------# Raw-data to be used in the experiment
│   │   ├── results  --------# Results from workflow runs
│   │   ├── run   -----------# All files rel. to running experiment
│   │   └── tmp   -----------# Any temporary files not supposed to be saved
├── raw  --------------------# Project-wide raw data
├── results  ----------------# Project-wide results
└── src  --------------------# Project-wide source code (that needs to be compiled)

Cette structure est bien adaptée à un projet en production, où des expériences sont lancées avec pour seules différences des changements mineurs (configuration, paramètres, …). Pour des analyses exploratrices, j’utilise plutôt la hiérarchie :

├── bin
├── doc
├── raw
├── results
└── src

J’utilise le gestionnaire de paquets conda pour gérer toutes les dépendances et les versions des outils nécessaires pour le projet. conda est un gestionnaire de paquet indépendant de tout langage de programmation et de tout système, permettant une installation facile et robuste. Ainsi, grâce aux efforts de la communauté Bioconda, plus de 1 500 outils bioinformatiques (dans n'importe quel langage) peuvent être facilement installés via un package conda. conda propose aussi des environnements, dont je me sers pour archiver les versions exactes des programmes utilisés (3e règle de la reproductibilité). Ces environnements peuvent être exportés (fichier yaml) et partagés/versionnés pour permettre l’import des mêmes outils avec les mêmes versions et reproduire les analyses.

Tous les exécutables (hors dépendances, c’est-à-dire principalement des scripts bash) sont conservés dans le dossier bin. Mes scripts Python, pour manipuler les données par exemple, (correctement documentés et vérifiés par flake8) se trouvent dans le dossier src. Avec ces scripts, je garde une trace de toute manipulation des données et des processus de production des résultats (1e et 2nde règles de la reproductibilité)

Un fichier README est aussi ajouté à la racine. Ce fichier contient une courte description du projet, des instructions pour reproduire le projet, … D’autre part, j’ajoute aussi systématiquement un fichier CITATION qui explique comment citer le projet et un fichier LICENSE, important pour décrire les droits d’utilisation et de modification du code (sans licence explicite, tous les droits sont réservés et le projet ne peut pas être utilisé librement).

Avec cette hiérarchie claire et similaire pour tous les projets, il est facile pour n’importe qui de naviguer dans le projet, de comprendre ce qui est fait et de reproduire les analyses et résultats, sans y consacrer plus de temps que nécessaire.

Utiliser un logiciel de gestion de versions (ils sont là pour nous aider)

La 4e règle de la reproductibilité concerne le versionnage des scripts développés et utilisés pour le projet. Je pense que plus que les scripts, tout fichier important pour le projet doit être versionné.

Un logiciel de gestion de versions est un système qui permet de garder une trace des changements de fichiers dans le temps. Ce type de logiciel facilite le travail quotidien et les collaborations :

  • Rien de ce qui est soumis au logiciel n’est perdu (ou il faut vraiment le vouloir), et comme toutes les versions sont sauvées, il est toujours possible de revenir en arrière
  • Avec la trace des auteurs et des dates des changements, il est facile de savoir à qui demander si un changement n’est pas clair
  • Le logiciel permet aussi de gérer les conflits lorsque plusieurs personnes ont modifié des parties identiques d’un fichier

 

Les logiciels de gestion de versions sont comme les cahiers de laboratoire du monde numérique. Tout travail scientifique, qui par nature évolue dans le temps et a besoin d’être partagé, doit être stocké avec un logiciel de gestion de versions. Et c’est particulièrement vrai pour tous les projets bioinformatiques !!!

Plusieurs logiciels de gestion de versions existent (Subversion, Mercurial, …). Depuis quelques années,git est le plus populaire, aussi dans le monde scientifique et la recherche. git est un outil indispensable, qu’il est important de savoir utiliser. De nombreux tutoriels sont disponibles, comme ceux de ce blog (premiers pas), ou les cours de Software Carpentry.

Bien que l’idée de base derrière un logiciel de gestion de versions soit simple, son utilisation requiert un peu de discipline. Il existe évidemment une version en ligne de commande, mais aussi des interfaces graphiques, et git s’intègre aussi très bien à divers IDE (Eclipse, Netbeans, IntelliJ IDEA) et traitement de texte (Sublime Text, Atom et même Gedit ! Amazing, isn’t it? )

Quelques conseils pour bien débuter

Plus un logiciel de gestion de versions est utilisé régulièrement, plus ses avantages se font sentir. L’historique des changements enregistré est alors le plus complet possible et permet de revenir facilement en arrière en limitant au maximum les effets de bord. Ainsi, une bonne règle est d’ajouter les changements au moins une fois par jour et de faire des changements assez petits. Un petit conseil : l’option -p de la commande git add permet de choisir les portions de code à ajouter pour un commit, plutôt que de soumettre tous les changements d’un même fichiers d’un seul coup. Et pour les phases de changements majeurs, l’idéal est d’utiliser des branches (historique parallèle).

Un logiciel de gestion de versions ne devrait être utilisé que pour les fichiers édités à la main. Les fichiers générés automatiquement (programmes compilés, résultats, …) ne doivent pas être versionnés car souvent ces fichiers sont lourds et/ou binaires et ne sont donc pas correctement pris en charge par le logiciel de gestion de versions. De plus, ces fichiers sont facilement reproductibles grâce au versionnage des scripts utilisés pour les générer. Cependant, pour des raisons de reproductibilité et transparence, les résultats (intermédiaires et finaux) doivent être sauvegardés pour être ensuite rendus publics (5e et 10e règles de la reproductibilité). Et, récemment, Nature a publié un article encourageant les auteurs à rendre public leurs jeux de données et à les associer à des digital object identifiers (DOIs). Des solutions comme Zenodo ou figshare permettent de stocker les résultats en leur associant un DOI, fixant ainsi une date et une version pour les résultats et permettant aussi la citation.

Un dernier petit conseil concernant git. Comme tous les fichiers du projet ne sont pas versionnés (les résultats, par exemple), le logiciel de gestion de versions va se “plaindre”, cachant potentiellement de vraies erreurs. Il est possible d’ajouter un fichier .gitignore à la racine du dossier pour que git ignore certains fichiers ou dossiers, comme les fichiers de configuration qui contiennent les mots de passe root de votre base de données 😉

Héberger son projet

Une fois le logiciel de gestion de versions choisi (donc git), la question du stockage des répertoires se pose alors. Généralement nos universités et instituts proposent des solutions d’hébergement internes. Cependant, les services en ligne comme GitLab ou GitHub proposent des solutions plus fiables et d’autres fonctionnalités qui en font bien plus qu’un simple hébergement :

  • De nombreuses fonctionnalités pour faciliter la collaboration en permettant l’organisation et le partage des tâches entre les différents contributeurs (parcourir la documentation riche de GitHub permet rapidement d’être convaincu)
  • Des interfaces intuitives pour aider toute personne peu à l’aise avec git.

 

De plus, les fonctionnalités proposées par GitLab et GitHub évoluent très vite, facilitant toujours plus la gestion et les collaborations. Open-source (contrairement à GitHub), GitLab peut être installé sur notre machine/serveur, mais il existe aussi des projets faciles à déployer comme Gogs.

Mais, GitHub est la solution la plus utilisée actuellement (plus de 35 million de répertoires en avril 2016), et est la plateforme qui a enclenché l’essor du mouvement open-source. Et de nombreux projets bioinformatiques sont hébergés par GitHub (par exemple BioPython, Galaxy ou Bioconda).

GitHub est un bon outil pour les projets scientifiques, comme le montrent Yasset Perez-Riverol et ses collègues (2016).

Philosophie de GitHub

Un répertoire dans GitHub est par défaut public. Chaque répertoire est associé à un utilisateur, le créateur et principal administrateur. Les répertoires publics sont visibles par tous (avec ou sans compte GitHub), mais tout le monde n’a pas la permission en écriture. Les administrateurs du répertoire doivent donner explicitement ces droits aux utilisateurs intéressés.

Cependant, parfois, des répertoires privés peuvent être utiles (au moins temporairement), mais ils sont normalement payants. Cependant, pour la recherche et l’éducation, GitHub propose des répertoires privés gratuits, souvent en collaboration avec les universités et les instituts. Il faut pourtant savoir que GitHub est hébergé sur le sol américain, pouvant poser des problèmes éthiques ou de confidentialité pour certains (Patriot Act).

L’utilisateur, une personne avec un profil et des projets auxquel il contribue, est l’élément central du système de gestion dans GitHub. Mais un autre niveau est aussi proposé, les organisations : un ensemble d’utilisateurs gérant des répertoires liés à un projet, un laboratoire, un institut ou des gros projets open-source (le projet Galaxy, par exemple). Les répertoires dans ces organisations peuvent avoir plusieurs propriétaires et administrateurs.

Collaboration

Le principe même de GitHub repose sur la facilité de tout utilisateur à contribuer à tous les projets auxquels l’utilisateur a accès. GitHub fournit ainsi de nombreuses fonctionnalités pour faciliter la collaboration au sein de projets.

N’importe quel utilisateur peut forker n’importe quel répertoire auquel il a accès. Une copie complète du contenu du répertoire est créée sur le compte de l’utilisateur, tout en maintenant un lien avec le répertoire original. L’utilisateur peut alors modifier librement le projet sans affecter directement le projet original. Une fois les changements prêts (corrections d’erreurs, développement de nouvelles fonctionnalités, ajout de documentation, etc), ils peuvent être soumis au répertoire original via des pull request. Les pull request sont ensuite revues, commentées et ajoutées au projet original par les contributeurs et administrateurs. Avec ce système, les administrateurs gardent le contrôle sur les contributions et leur qualité. L’interface de GitHub propose quelques facilités pour créer des pull request lorsqu’il n’y a que très peu de changements à faire. De cette façon, nul besoin d’avoir une copie intégrale du projet. L’ouverture de pull request par n’importe quel utilisateur de GitHub est une méthode simple pour la collaboration. C’est la base du social coding. Il a été montré que ce système d’échanges et contributions (le GitHub Flow) augmente la qualité du code (Dabbish et al., 2012), mais aussi de tout projet collaboratif.

screen-shot-2016-10-03-at-11-19-09

GitHub Flow - https://guides.github.com/introduction/flow/

 

GitHub propose aussi un système de tickets (ou issues). Les issues permettent de garder une trace des erreurs, des tâches à faire, des demandes de fonctionnalités, … avec un puissant système de tag. Ouvrir une issue est facile et rapide (titre, courte description). Les issues ont une structure claire avec de la place pour des commentaires et des discussions. De plus, les administrateurs projet peut également donner des recommendations sur la manière de rédiger des issues. L’ajout de labels, de jalons et d’assignations aident le filtrage et la prioritisation des tâches. Ce système d’issues peut ainsi se transformer en un puissant outil de planification de projets.

Automatisation de tâches et intégration continue

GitHub offre aussi un ensemble d’outils qui peuvent être exécutés automatiquement après chaque push sur un répertoire. Par exemple, l’intégration continue permet de tester l’intégrité et les performances du code avec Travis CI, une plateforme gratuite pour tous les projets publiques. À chaque changement du code, Travis CI compile et teste le projet pour différentes plateformes, avec de nombreuses options et notifie de toute erreur. GitHub permet ainsi de faciliter une phase essentielle (mais souvent oubliée) du développement : les tests.

Les scripts automatisés peuvent aussi être utilisés pour automatiser d’autres tâches comme le déploiement ou la génération de documentation. Ainsi, pour du code Python ou C/C++, la documentation peut être générée automatiquement avec sphinx et publiée via Read the Docs. GitHub propose aussi une autre solution pour la documentation : les wikis (qui sont versionnés) où les utilisateurs peuvent créer et éditer des pages pour de la documentation, des exemples, …

Promotion de projets

Traditionnellement, un projet scientifique est promu par la publication d’un (ou plusieurs) papier(s) dans un journal avec comité de lecture. Mais souvent, un projet et son code évoluent à une vitesse totalement différente du processus de publication. Ainsi, des nouvelles fonctionnalités ou caractéristiques peuvent ne pas être décrites dans la publication originelle, ou le délai de publication trop long pour un projet est déjà prêt à être utilisé. GitHub propose des fonctionnalités pour promouvoir son projet, parallèlement au circuit classique de la publication.

Il est possible d’interfacer n'importe quel répertoire public avec des services d’archivage comme Zenodo ou figshare pour assigner des DOIs. La procédure est simple à mettre en place. À chaque fois qu’une nouvelle release est créée sur GitHub, Zenodo crée une archive du répertoire, l’associe à un DOI et permet ainsi une citation toujours à jour et une distribution du projet (10e règle de la reproductibilité).

Pour augmenter la visibilité du projet, d’autres outils peuvent être utilisés. Ainsi, GitHub propose des GitHub Pages, des sites web simples hébergés par GitHub. Tout utilisateur peut ainsi créer et héberger des sites de blog, des pages d’aide, des manuels, des tutoriels, … Ces pages peuvent être générées automatiquement avec Jekyll, un puissant générateur de site statique, ou grâce au générateur automatique de GitHub. Ces fonctionnalités, très faciles à prendre en main, sont un excellent moyen pour décrire un projet, promouvoir un poster (ajout de contenu), publier des présentations ou même avoir facilement son propre site Web. Toutes ces fonctionnalités augmentent la visibilité des projets (plus d’utilisateurs, plus de citations) et aussi sa propre visibilité (pour le futur).

Se lancer ?

Utiliser un logiciel de gestion de versions est ainsi indispensable pour une bonne gesion d’un projet. GitHub et git sont de puissants outils qui doivent être explorés et testés pour leurs potentialités. Même si comme souvent, l’apprentissage n’est pas évident. De nombreuses ressources (documentation, tutoriels, …) sont disponibles pour se familiariser avec git et GitHub :

 

Mais plusieurs fonctionnalités puissantes et basiques sont facilement accessibles même aux débutants (surtout grâce à l’interface intuitive de GitHub). La récompense à terme sera plus forte que l’effort investi.

Tenir un cahier de laboratoire

CC BY-NC-ND 2.0 - https://flic.kr/p/4Kumbh

Blackbook - hawkexpress - CC BY-NC-ND 2.0 - https://flic.kr/p/4Kumbh

 

La bonne gestion d'un projet passe aussi par une transparence totale, en particulier vis-à-vis de nos collaborateurs. Tout doit donc être noté pour ainsi garder une trace des processus de création des résultats et commenter les résultats (1e et 9e règles de la reproductibilité). Pour cela, la meilleure solution est de tenir un cahier de laboratoire pour chaque projet. Ce cahier doit être sauvegardé avec le reste des documents liés au projet, être versionné et contenir l’avancée détaillée du projet, avec des entrées datées, signées et assez détaillées (images et tables pour décrire les résultats si nécessaire). En bon carnet de bord, il doit décrire ce qui est fait, les observations, les conclusions, les idées pour le futur, mais aussi des explications des résultats et tous les échecs (bien sûr). Il doit aussi contenir des notes de toutes les conversations liées au projet. Ainsi, le cahier de laboratoire doit donner une vision complète du projet et son avancée dans le temps. Toute personne le lisant peut ainsi comprendre ce qui est fait et s’en servir de base pour suivre le projet et collaborer.

Plusieurs solutions existent pour ce cahier, comme l’historique cahier de labo papier. Cependant, sous cette forme, le cahier n’est pas facilement partageable, versionnable et peut être perdu. Je penche donc plutôt pour une version électronique avec un fichier markdown (syntaxe facile à lire et à écrire), facilement compilable en PDF avec pandoc, enregistré dans le dossier doc et versionné. Le wiki du répertoire GitHub du projet ou des solutions comme Jupyter peuvent aussi servir de cahier de laboratoire.

Parallèlement aux cahiers liés aux projets, je tiens aussi un cahier de laboratoire général, plus personnel. Je note tout ce que je fait (tous les projets avec des liens sur les cahiers de laboratoires des projets, toutes les réunions, …). Il me sert aussi beaucoup de brouillon pour tester des choses et prendre des notes informelles ou conserver des liens ou commandes utiles. Pour ce cahier, j’utilise Monod, un éditeur markdown en ligne. Ce cahier est pour moi assez informel mais je me suis quand même inspirée des conseils de Santiago Schnell (Schnell, 2015). Il me permet de garder une trace de tout mon travail scientifique, de mes idées, des solutions à certains problème, etc.

Conclusions

Lorsqu’on évolue dans le monde scientifique et celui de la recherche, le principe de reproductibilité doit toujours être gardé en tête. N’importe qui doit pouvoir reproduire ce qui a été fait. Pour cela, nos projets doivent être bien organisés, suivis avec un logiciel de gestion de versions et surtout expliqués. De nombreux outils sont disponibles pour faciliter tout ça. J’ai essayé d’en présenter quelques-uns ainsi que quelques bonnes pratiques, mais c’est loin d’être exhaustif! Et les technologies évoluent vite.

Pour écrire cet article, j’ai (re)lu pleins d’articles intéressants sur le sujet. J’ai pu prendre du recul sur ma façon de faire de la science et de la recherche, pour extraire ma vision actuelle de la gestion des projets en science. Cette vision n’est peut être pas la vôtre donc j’aimerais aussi avoir vos visions : Comment fonctionnez-vous? Que pensez-vous des cahiers de laboratoire ? Quelle forme fonctionne le mieux ? Avez-vous déjà réfléchi à l’organisation globale de vos projets ? Que faut-il toujours garder en tête ? Quels verrous avez-vous rencontré ?

Remerciements

Merci à Magali Michaut : sa présentation au workshop JeBif m’a donné envie d’écrire enfin cet article. Merci aussi Will, Kévin, Yoann MClemZaZo0o, NiGoPol pour leur relectures et leurs commentaires constructifs.

Sources

Une partie du contenu de cet article est inspiré d’articles de PLoS Computational Biology et particulièrement de la collection Ten Simple Rules.

Dabbish, L., Stuart, C., Tsay, J. & Herbsleb, J. (2012). Social Coding in GitHub: Transparency and Collaboration in an Open Software Repository. In Proceedings of the ACM 2012 Conference on Computer Supported Cooperative Work (pp. 1277—1286).http://doi.acm.org/10.1145/2145204.2145396

Lampa, S. (2015). A few thoughts on organizing computational (biology) projects. Accessed: 2016-09-01

Noble, W. S. (2009). A Quick Guide to Organizing Computational Biology Projects. PLoS Comput Biol, 5(7), 1-5.http://dx.doi.org/10.1371/journal.pcbi.1000424

Perez-Riverol, Y. A. G. (2016). Ten Simple Rules for Taking Advantage of Git and GitHub. PLoS Comput Biol, 12(7), 1-11.http://dx.doi.org/10.1371/journal.pcbi.1004947

Sandve, G. K. A. N. (2013). Ten Simple Rules for Reproducible Computational Research. PLoS Comput Biol, 9(10), 1-4.http://dx.doi.org/10.1371/journal.pcbi.1003285

Schnell, S. (2015). Ten Simple Rules for a Computational Biologist’s Laboratory Notebook. PLoS Comput Biol, 11(9), 1-5.http://dx.doi.org/10.1371/journal.pcbi.1004385

L’article S'outiller et s'organiser pour mieux travailler est apparu en premier sur bioinfo-fr.net.

Comment fixer les problèmes de déploiement et de durabilité des outils en bioinformatique ? Indice : conda !

$
0
0

La diversité des questions que se posent nos amis biologistes entraîne une diversité des données : génomiques, images, etc. De plus, ces données sont générées à des vitesses folles. Pour manipuler les données et extraire les informations utiles, des solutions et outils bioinformatiques sont nécessaires. De nombreux outils existent déjà pour répondre à de nombreuses questions. Mais parfois, de nouveaux outils sont nécessaires pour répondre à une question spécifique. Intervient alors le développement d'un nouvel outil bioinformatique.

Développer et distribuer un outil bioinformatique

Lorsque vous développez un outil bioinformatique, vous le faites dans le but premier de répondre à une question. Une fois celle-ci correctement formulée, vous choisissez votre méthode de travail et les outils (1, 2) qui vous aiderons à bien gérer votre projet. Par exemple, si vous avez choisi Java pour développer votre projet, il se peut que vous utilisiez Git comme gestionnaire de versions et Maven comme gestionnaire de build.

Vous avez donc écrit du code source. Pour partager votre solution, vous allez écrire de la documentation, faire de la formation et du support autour de l'outil. Et vous pouvez être amené à le publier pour expliquer votre méthode (sinon, ce n'est pas de la science reproductible, donc pas de la science, et tac !). Il vous faut alors distribuer votre programme. Cela peut être fait de bien des façons :

 

Le partage des sources est primordial pour assurer la transparence, mais il peut être particulièrement difficile d'installer correctement un logiciel (multitude de dépendances, incompatibilité entre des versions, etc). Le constat est simple : si votre algorithme est révolutionnaire mais que personne ne peut l'utiliser, "je ne lui prédis pas un grand avenir" (#OSS117).

Cette voie fonctionne bien lorsque l'outil est simple et ne dépend pas de trop nombreux autres outils. Cependant, la phase de déploiement reste à la charge de l'utilisateur (ou de l'administrateur système du labo). Et le déploiement d'un logiciel est la proie de deux grands fléaux :

  • les dépendances manquantes (OHMYGOD!)
  • les versions des dépendances (I'MGONNADIE!!)

 

Il s'en suit alors un casse-tête dantesque où l'utilisateur doit installer impeccablement TOUTES les bonnes versions de TOUTES les dépendances (si on peut encore les trouver et si elles sont compatibles avec son système, évidemment). A LA MAIN ! C'est évidemment une source colossale de fausse manip' et de découragement pour l'utilisateur, qui préférera alors se tourner vers une solution alternative.

Nous sommes donc face à un double problème de durabilité des outils et de leur déploiement. Ceux-ci ont des impacts importants sur la productivité et la reproductibilité en sciences. Il devient donc urgent de résoudre ces deux questions et rendre la bioinformatique meilleure !

Faciliter le déploiement d'un logiciel

Les problèmes précédemment cités sont une chose que les utilisateurs de systèmes Linux/OSX ne connaissent qu'à moitié, puisque rares sont ceux qui installent tout à la main. Le commun des mortels utilise, quand il le peut, un gestionnaire de paquets. Il en existent plusieurs étant pour la plupart spécifique :

  • à un langage (pip pour Python, CPAN pour Perl, CRAN pour R, etc)
  • à un système d'exploitation (yum pour Fedora, APT pour Debian, howebrew pour OSX, etc)

 

Le packaging demande un petit effort de la part du développeur, mais le déploiement de l'outil derrière le code est grandement facilité. L'utilisateur n'a à se préoccuper que de la partie "utilisation" (ce qui est somme toute plutôt logique). Pour que tout soit parfait, il est également nécessaire de documenter le logiciel, de proposer des formations, du support et d'en faire la publicité.

La solution à tous nos maux : Conda

Pour qu'un outil soit utilisé, il doit être facilement déployable n'importe où. Pour cela, il faut le packager avec un gestionnaire de paquets qui soit :

  • indépendant d'un langage de programmation

Des outils bioinformatiques sont disponibles dans pratiquement tous les langages disponibles

  • indépendant du système d'exploitation

Les outils sont utilisés sur les principaux systèmes d'exploitation

  • indépendant de privilèges super utilisateurs

Certains utilisateurs n'ont pas les droits d'administration de leur ordinateur

  • capable de gérer plusieurs versions d'outils

Des versions différentes d'un outil peuvent être requises par différents outils

  • compatible avec une utilisation sur le Cloud ou en environnement HPC

 

Conda est un gestionnaire de paquets open-source qui répond très bien à ces problématiques. Bien que développé par la communauté PyData, conda est conçu pour gérer des paquets et dépendances de n'importe quel programme dans n'importe quel langage. Conda est donc moins pip qu'une version multi-système d'exploitation de apt et yum.

Un paquet conda est défini par une recette et correspond in fine à un fichier tarball contenant des librairies au niveau système, Python ou d'autres modules, des programmes exécutables ou d'autres composants. En distribuant des outils précompilés, l'installation de paquet conda est rapide, robuste et facile :

$ conda install deeptools

Il y a d'ailleurs un excellent article introductif pour une installation et prise en main rapide de conda.

Les principales fonctionnalités de conda

Conda permet donc de gérer différents logiciels, un peu à la manière de apt du point de vue de l'utilisateur. On retrouve les commandes suivantes :

$ conda list                # lister les paquets installés
$ conda search deeptools    # rechercher les paquets qui pourraient correspondre à "deeptools"
$ conda install deeptools   # installer le paquet "deeptools"
$ conda update deeptools    # mettre à jour (si possible) le paquet "deeptools"
$ conda remove deeptools    # supprimer le paquet "deeptools"
$ conda help                # vous laisser découvrir toute la beauté de conda

Conda garde une trace des dépendances entre les paquets et les plateformes. Par exemple, deeptools a besoin de python, numpy 1.9.0+ et scipy 0.17.0+ entre autres. Conda se charge d'installer ou de mettre à jour ses dépendances si besoin, ainsi que les dépendances de ces dépendances, etc.

Conda vient aussi avec une gestion d'environnements virtuels, sur le même principe que les environnements virtuels de Python. Un environnement conda est un dossier contenant une collection spécifique de paquets conda installés mais isolés des autres environnements conda. Ce principe permet l'installation et la gestion de plusieurs versions d'outils, comme Python 2.7 et Python 3.5 par exemple. Vous pouvez alors créer des environnements dédiés qui assureront la reproductibilité de vos analyses. Tout est expliquer ici pour créer vos environnements.

Encore des réticences vis-à-vis de Conda ? Je vous conseille de lire ce blog post sur les mythes et fausses idées liées à Conda.

Les channels

En bon gestionnaire de paquets, conda offre la possibilité d'ajouter d'autres sources de paquets, aussi appelées channels. Les outils assez généralistes peuvent être trouvé dans le channel default ou conda-forge. Spécialisé dans les outils bioinformatiques, le channel Bioconda consiste en :

 

Avec presque 200 contributeurs, cette communauté accueillante et formée il y a un peu plus de 1 an grossit rapidement. Elle ajoute, modifie, met à jour et maintient les nombreuses recettes des paquets conda d'outils bioinformatiques existant, mais vous donnera aussi tout un tas de conseils pour parfaire vos recettes.

Packager un logiciel

Envie d'écrire un paquet conda pour un outil existant ? On pourrait penser que cela est difficile étant donnés les avantages apportés par conda. Mais au contraire, l'écriture de paquets conda a été pensée pour être facile et permettre ainsi à tous d'intégrer les outils dans conda avec une documentation extensive. Ainsi, un paquet conda consiste en deux fichiers :

  • Un fichier meta.yaml contenant les méta-données du paquet : le nom, la version, où trouver les sources, les dépendances de l'outil, ...
  • Un fichier build.sh pour expliquer comment conda doit créer le paquet

 

Par exemple, pour le logiciel deeptools, on le fichier meta.yaml suivant :

package:
  name: deeptools
  version: '2.4.3'

source:
  fn: deepTools-2.4.3.tar.gz
  url: https://pypi.python.org/packages/40/4b/2f4[...]796/deepTools-2.4.3.tar.gz
  md5: 1751e860c5436d1973d7621ebdf00c6a

build:
  number: 0

requirements:
  build:
    - python
    - setuptools
    - numpy >=1.9.0
    - scipy >=0.17.0
    - matplotlib >=1.4.0
    - pysam >=0.8.2
    - py2bit >=0.2.0
    - gcc
  run:
    - python
    - pybigwig >=0.2.3
    - numpy >=1.9.0
    - scipy >=0.17.0
    - matplotlib >=1.4.0
    - pysam >=0.8.2
    - py2bit >=0.2.0

test:
  imports:
    - deeptools

  commands:
    - bamCompare --version

about:
  home: https://github.com/fidelram/deepTools
  license: GPL3
  summary: A set of user-friendly tools for normalization and visualization of deep-sequencing data

Cette recette fera automatiquement appel au script build.sh qui contient les instructions pour installer le logiciel, en l’occurrence :

#!/bin/bash
$PYTHON setup.py install

 

Bioconda propose un guide pour écrire des recettes qui seront par la suite intégrées au channel. Et dans tous les cas, vous pouvez faire appel aux membres de la communautés pour vous aider à construire, débugger, mettre à jour ou parfaire votre recette.

Bioconda va même encore plus loin !

Pour faciliter le déploiement tout en suivant les besoins évoqués précédemment, un autre moyen de packager un outil est de le containeriser. La containerisation la plus connue est Docker, mais il existe d'autres solutions comme rkt ou Singularity. Ces containers permettent d'obtenir un plus haut niveau d'abstraction pour un outil par rapport au système de base.

La création de containers pour un outil est plus complexe que pour créer un paquet conda. Par exemple, pour créer un container Docker, il faut créer un fichier Dockerfile décrivant l'image de base utilisée, les commandes pour créer l'outil, etc.

Mulled est un projet permettant de générer un container (BioContainer) minimal pour Docker ou rkt à partir d'un paquet conda, alpine or linuxbrew. Il faut seulement ajouter une ligne dans un fichier TSV pour indiquer à Mulled de créer le container.

Pour des paquets Bioconda, c'est encore plus facile : il n'y a rien à faire. Mulled parcourt tous les paquets Bioconda quotidiennement et génère des BioContainers automatiquement pour tous les paquets Bioconda.

En packageant les outils avec conda au sein de Bioconda, on réduit drastiquement le problème de déploiement des outils aux utilisateurs. Les outils deviennent facilement déployables avec plusieurs solutions : via les paquets conda ou via des BioContainers construits automatiquement.

La durabilité et disponibilité

Un outil peut dépendre de nombreux autres outils, qui peuvent ne plus être maintenus ni même disponibles. L'indisponibilité des outils posent de nombreux problèmes dont ceux de reproductibilité et durabilité.

Pour résoudre ces problèmes, l'idéal serait d'avoir un stockage permanent de toutes les versions des paquets et outils utilisés pour qu'ils soient toujours accessibles.

La reproductibilité et l'accessibilité font partis des mantras du projet Galaxy. Ainsi, pour répondre aux problèmes de disponibilité et de durabilité des outils et paquets, la communauté autour de Galaxy a mis en place Cargo Port, un répertoire public pour archiver de nombreux paquets de façon stable et permanente.

Ajouter un paquet dans ce dépôt est facile. Il suffit d'ajouter une ligne dans un fichier TSV avec les informations (nom et URL) sur le paquet à stocker. Pour les paquets créés avec Bioconda, c'est même encore plus facile : il n'y a rien à faire ! Cargo Port fait des archives journalières des recettes et paquets Bioconda, et permet ainsi de résoudre les problèmes de durabilité et disponibilité des outils.

Déploiement et durabilité des outils en bioinformatique : Fixés !

Le développement des paquets Bioconda est très facile et facilite le packaging et le déploiement de tout outil bioinformatique. Avec le projet Mulled, des containers Linux efficaces sont automatiquement construits pour tous paquets Bioconda pour permettre un plus haut niveau d'abstraction et d'isolation par rapport au système de base. C'est un super effort de différentes communautés pour créer un système flexible et extensible et fixer ainsi le problème de déploiement une fois pour toute.

L'interface avec paquets Bioconda avec Cargo Port améliore la disponibilité et la durabilité en conservant toutes les sources.

Nous espérons vous avoir convaincus que grâce à ces projets collaboratifs, leur communauté et leurs collaborations, les outils bioinformatiques peuvent être facilement packagés et être toujours disponibles pour leurs utilisateurs. La seule chose à faire est de créer une recette Bioconda et rendre ainsi vos utilisateurs heureux et leurs (et vos) analyses efficaces et reproductibles !


Merci à Nico MHedJourMathurinAkira pour les relectures et les commentaires intéressants !

Cet article a été écrit conjointement par Bérénice et Kévin

L’article Comment fixer les problèmes de déploiement et de durabilité des outils en bioinformatique ? Indice : conda ! est apparu en premier sur bioinfo-fr.net.

Représenter rapidement une ACP avec R et ggplot2

$
0
0

Je ne sais pas pour vous, mais moi, à chaque fois que j'assiste à une réunion de labo, il y a quasi systématiquement un graphique d'ACP pour montrer les données. Et à chaque fois, il s'agit d'un graphique de base, généré avec R, avec la fonction plot(), des couleurs qui piquent les yeux et des axes et légendes illisibles. La critique est facile me direz-vous, j'avoue avoir moi aussi présenté ce genre de graphique assez souvent. Mais au bout d'un moment, vu que les ACP ça devient très très routinier dans le travail d'un "data analyste", la nécessité de produire rapidement et facilement un graphique ACP se fait sentir.

À cette occasion, j'ai retroussé mes manches et j'ai écrit un petit bout de code maison qui génère tout seul les graphiques pour un nombre donné de composantes principales, et ce en une seule fonction. J'en ai profité pour utiliser ggplot2, dont je vous invite à lire la présentation, pour rendre ces graphiques un peu plus esthétiques. Je partage donc avec vous mon secret pour faire des graphiques sexy. Ce n'est pas très compliqué en soit, c'est juste des graphiques, mais je vous donne mon code si jamais ça peut aussi vous servir et vous épargner du temps.

Aperçu du type de graphique que produit le code qui va suivre

Première étape : chargement de la librairie

Comme je l'ai dit plus haut, nous allons utiliser la librairie ggplot2. Cette fonction permet d'installer automatiquement le paquet s'il n'est pas déjà installé.

if (!require("ggplot2")) install.packages("ggplot2")

Deuxième étape : le screeplot

Lorsque l'on fait une ACP, on aime bien savoir quelles composantes principales (PC) accumulent le plus de variance (pour les détails sur ce qu'est une ACP, c'est par là). Pour aller vite, on peut simplement lancer la commande screeplot(), mais le rendu n'est pas hyper sexy. En plus il n'y a pas de légende sur l'axe des abscisses, ce qui est fâcheux quand on a plus de 20 PC. D'ailleurs, quand on lance une ACP sur un set de données avec plus de 100 échantillons (si si, ça arrive), on se retrouve avec un barplot tout serré où en réalité l'information qui nous intéresse est compressée sur la gauche du graphique.

Pour résoudre cela, la fonction suivante prend en entrée le résultat de l'ACP (calculée avec prcomp), et le nombre de PC à afficher. C'est pas le plus "hot" des graphiques mais ça peut être pratique.

plot_percent_var <- function(pca, pc){
    # Calcule du pourcentage de variance
    percent_var_explained <- (pca$sdev^2 / sum(pca$sdev^2))*100
    # Préparation d'un tableau avec le numéro des composantes principales
    # et le pourcentage de variance qui lui est associé
    percent_var_explained <- data.frame(
        PC=1:length(percent_var_explained),
        percent_Var=percent_var_explained
    )
    # Récupérer uniquement le nombre de PC indiqué en argument
    sub_percent_var_explained <- percent_var_explained[1:pc,]
    # Génère le graphique
    p <- ggplot(sub_percent_var_explained, aes(x=PC, y=percent_Var)) +
        # Génère un barplot
        geom_col()+
        # Utilise le thème "black and white"
        theme_bw() +
        # Renomme l'axe des abscisses
        xlab("PCs") +
        # Renomme l'axe des ordonnées
        ylab("% Variance") +
        # Titre du graphique
        ggtitle("Screeplot")+
        # Option de taille des éléments textuels
        theme(
            axis.text=element_text(size=16),
            axis.title=element_text(size=16),
            legend.text = element_text(size =16),
            legend.title = element_text(size =16 ,face="bold"),
            plot.title = element_text(size=18, face="bold", hjust = 0.5),
            # Astuce pour garder un graphique carré
            aspect.ratio=1
        )
    # Affiche le graphique
    print(p)
}

Troisième étape : l'ACP

Passons au nerf de la guerre : l'ACP en elle même. Pour apprécier la complexité d'un set de données, il est souvent nécessaire de regarder un peu plus loin que juste les 2 premières composantes. Ce qui me fatigue le plus (il ne me faut pas grand chose), c'est de relancer la commande de graphique pour chaque combinaison. Pour satisfaire ma fainéantise, il me faut donc une fonction avec le nombre de PC voulues et hop, ça me sort toutes les combinaisons possibles.

La fonction suivante prend en entrée le résultat d'une ACP (calculée avec prcomp), le nombre de PC à regarder, les conditions des échantillons (une liste qui fait la même taille que le nombre d'échantillons), et une palette de couleur en hexadécimale (par exemple: "#fb7072"), avec autant de couleurs que de conditions différentes (e.g. deux conditions, cas et contrôle, donc 2 couleurs).

plot_pca <- function(pca=pca, pc=pc, conditions=conditions, colours=colours){
        # Transforme le nombre de PC en argument en nom de PC
        PCs <- paste("PC",1:pc, sep="")
        # Calcule le pourcentage de variance par PC
        percent_var_explained <- (pca$sdev^2 / sum(pca$sdev^2))*100
        # Transforme le vecteur de conditions en un facteur
        cond <- factor(conditions)
        # Crée un autre facteur avec les conditions
        col <- factor(conditions)
        # Change les niveaux du facteur avec la palette de couleur pour attribuer
        # à chaque condition une couleur
        levels(col) <- colours
        # Re-transforme le facteur en vecteur
        col <- as.vector(col)
        # Récupère les scores pour le graphique
        scores <- as.data.frame(pca$x)
        # Génère toutes les combinaisons possibles de PC
        PCs.combinations <- combn(PCs,2)
        # Génère un graphique pour chaque combinaison
        # avec une boucle apply
        g <- apply(
            PCs.combinations,
            2,
            function(combination)
            {
                p1 <- ggplot(scores, aes_string(x=combination[1], y=combination[2])) +
                # Dessine des points avec une bordure de 0.5 remplis avec une couleur
                geom_point(shape = 21, size = 2.5, stroke=0.5, aes(fill=cond)) +
                # Utilise le thème "black and white"
                theme_bw() +
                # Spécifie la palette de couleur et donne un titre vide à la légende
                scale_fill_manual(
                    values=colours,
                    name=""
                ) +
                # Renomme le titre des axes des abscisses et des ordonnées en "PCx (pourcentage de variance)" avec 3 chiffres après la virgule
                xlab(paste(combination[1], " (",round(percent_var_explained[as.numeric(gsub("PC", "", combination[1]))], digit=3),"%)", sep=""))+
                ylab(paste(combination[2], " (",round(percent_var_explained[as.numeric(gsub("PC", "", combination[2]))], digit=3),"%)", sep=""))+
                # Titre du graphique
                ggtitle("PCA")+
                # Option de taille des éléments texte
                theme(
                    axis.text=element_text(size=16),
                    axis.title=element_text(size=16),
                    legend.text = element_text(size =16),
                    legend.title = element_text(size =16 ,face="bold"),
                    plot.title = element_text(size=18, face="bold", hjust = 0.5),
                    # Astuce pour garder un graphique carré
                    aspect.ratio=1
                )
                # Affiche le graphique
                print(p1)
            }
        )
}

Et enfin : la démo

# Génération d'un set de données aléatoires avec 3 groupes
set.seed(12345)
x <- c(rnorm(200, mean = -1), rnorm(200, mean = 1.5), rnorm(200, mean = 0.8))
y <- c(rnorm(200, mean = 1), rnorm(200, mean = 1.7), rnorm(200, mean = -0.8))
z <- c(rnorm(200, mean = 0.5), rnorm(200, mean = 7), rnorm(200, mean = 0))
data <- data.frame(x, y, z)

# Définition des groupes
group <- as.factor(rep(c(1,2,3), each=200))

# Définition de la palette de couleur (on peut aussi utiliser RColorBrewer ou tout autre palette déjà faite)
palette <- c("#77b0f3", "#8dcf38", "#fb7072")

# On lance le calcule de l'ACP
pca <- prcomp(data, center=TRUE, scale=TRUE)

# On affiche le graphique "Screeplot" (pourcentage de variance par composante principale)
plot_percent_var(pca, 3)

# On génère le graphique de l'ACP pour les 2 premières composantes principales
plot_pca(
    pca=pca,
    pc=2,
    conditions=group,
    colours=palette
)

Et voilà le résultat :

Screeplot et ACP

Résultat des commandes précédentes. À gauche le screeplot, à droite l'ACP avec les deux premières composantes et le pourcentage de variance entre parenthèse, le tout coloré en fonction des conditions (1 à 3).

 

Juste une dernière remarque : si vous spécifiez plus de 2 PC à afficher, ça va générer les graphiques chacun leur tour, donc je vous conseille d'appeler la fonction plot_pca() dans un pdf pour les sauvegarder dans un seul fichier. Peut-être un jour j'essayerai le paquet gridExtra pour afficher plusieurs graphiques sur une même page... Un jour...

 

Merci à Jnsll, Nico M., et Mat Blum pour leurs commentaires et relectures.

L’article Représenter rapidement une ACP avec R et ggplot2 est apparu en premier sur bioinfo-fr.net.

Maîtrisez le cache de Rmarkdown !

$
0
0

Pour des raisons de reproduction de la science, il est important de conserver une trace de tout ce que l'on fait sur son ordinateur. Pour cela, faire des rapports est la meilleure manière que je connaisse qui permette d'inclure le code et les résultats d'une analyse. Pour faire ça bien avec R, on a déjà vu dans un article précédant que les rapports Rmarkdown étaient une très bonne solution.

License: CC0 Public Domain

Le but de cet article va être de vous permettre de gagner du temps avec le cache de Rmarkdown et quelques autres petites astuces. Pour cela, vous aurez besoin de 2 choses : avoir les bases du Rmarkdown et avoir des bases générales en informatiques. J'utilise RStudio pour faire mes codes Rmarkdown, je vous encourage à en faire de même.

Allez, c'est parti !

Alors si vous avez suivi l'article de notre cher ami jéro, vous avez un rapport Rmarkdown tout fait, qui date du 22 mars 2005 et qui produit un PDF. Je vais commencer par le commencement de votre fichier et vous donner 3 astuces du début de chacun de vos rapports :

  • Personnellement, je fais très rarement des rapports PDF, pour des raisons de compatibilité du code entre Windows et Mac OS/Linux. En effet, je suis obligé d'utiliser Windows au boulot et il y a beaucoup de problèmes pour compiler les PDF. Je ne vais pas rentrer dans les détails, mais retenez donc que je ne fais quasiment que des fichiers HTML. Donc déjà, dans mon entête en YAML, je remplace 
    pdf_document
    par html_document. J'ajoute d'ailleurs l'option 
    number_sections: true
     en dessous et au même niveau d'indentation que 
    toc: true
     , ce qui va automatiquement mettre un numéro à mes sections.
  • Deuxième astuce, on peut mettre du code R dans l'entête YAML. L'intérêt est qu'on peut donc utiliser des fonctions, comme la fonction Sys.time(), qui nous donne la date et l'heure. J'ai donc dans chacune de mes entêtes à la place de 
    date: une date entrée manuellement
     le code suivant : 
    date: "`r substr(Sys.time(), 1, 10)`"
     , qui vous mettra automatiquement à chaque fois que vous allez compiler votre rapport, la date de cette compilation.
  • Dernière astuce avant de vraiment parler de cache, on peut définir en début de rapport des options qui vont s'appliquer à tous les chunks par la suite, par défaut. Donc je vous donne mes paramètres par défaut et je vous les commente ensuite :
    ```{r setup, echo=FALSE, message=FALSE}
    library(knitr)
    opts_chunk$set(fig.align = "center",
                   fig.retina = 2,
                   fig.width = 10,
                   cache = TRUE,
                   cache.lazy = FALSE)
    ```

    Comme vous pouvez le voir, je les mets dans leur propre chunk que je nomme "setup". Le nom d'un chunk est très important pour se repérer lors de la compilation. On s'en servira en plus à la suite de cet article. Le nom est automatiquement compris par RStudio comme les caractères qui se trouvent après
    ` ` `{r
      et avant la virgule. Après la virgule se trouvent les options de ce chunk, qui le rendent silencieux (echo=FALSE) et qui sortent les messages vers la console de compilation et pas dans le rapport.

    Ensuite on a donc le code R, je charge la library knitr sur la première ligne et je définis les options de mes chunks suivants sur les lignes suivantes. Les options sont passées par la fonction 

    opts_chunk$set
     . Je choisis l'alignement de mes figures au centre de la page, une taille de figures doublée quand elle s'affiche sur un écran à très haute résolution, une largeur de figure de 10, et enfin la création d'un cache pour tous mes chunks, qui n'utilisera pas le lazy loading (je ne vais pas expliquer ce que c'est, mais utilisez cette option ou vous aurez des problèmes). On aura ainsi tous nos chunks suivant celui-ci qui utiliseront ces options.

J'en profite pour ouvrir une parenthèse pour les plus avancés, si vous voulez avoir du code de suivi de l'avancement de votre compilation qui s'affiche dans votre console de compilation, il suffit de faire imprimer à votre code R un message ou un warning, et de mettre les options du chunk correspondantes en FALSE, ce qui fera que l'impression du message ou du warning ne se feront pas dans le rapport mais dans la console.

Voilà, maintenant que c'est fait pour les astuces, je vais vous en dire plus sur le cache.

Le cache c'est une copie sur votre disque dur de ce qui a été fait dans un chunk. C'est particulièrement utile lorsque l'on fait des analyses qui prennent beaucoup de temps à tourner. En effet, quand vous travaillez sur votre rapport, si l'un de vos chunks prend 2 jours à tourner, vous serez bien contents de ne pas avoir à le refaire à chaque fois que vous modifiez l'un des autres chunks. Ainsi, lorsqu'un chunk crée un cache, dans le dossier où vous avez stocké votre rapport, un sous-dossier se crée portant le nom de votre rapport + _cache. Dans ce dernier, un dossier html/pdf/autre se crée en fonction du type de rapport que vous produisez, puis enfin à l'intérieur vous trouverez les fichiers qui portent comme nom

nom de votre chunk_unCodeBienCompliqué.RData
 . Le code bien compliqué est un hash, en gros un code, qui garantie que votre chunk n'a pas changé depuis qu'il a été sauvé en cache. Si le chunk a changé, alors automatiquement un nouveau cache sera produit, remplaçant celui qui est devenu inutile. Un petit point de détail, on peut avoir des longues analyses qui produisent des petits caches et des courtes qui en produisent des gros (et vice-versa). Plus un fichier de cache est gros plus il prendra de temps à être chargé. il peut donc être utile d'enlever le cache d'un chunk (donc mettre cache=FALSE dans les options du chunk) qui ferait une analyse rapide mais produirait un gros cache, si votre disque dur n'est pas très performant. Je ne mets pas en cache non plus mon chunk qui charge mes librairies et mes fichiers, ça peut causer des soucis. Enfin, je ne mets pas non plus en cache les chunks qui initialisent un sous programme, ou une méthode de multithreading. Dans tous les autres cas, gardez le cache.

J'en entends déjà me dire "Bon ok, mais tu dois avoir une autre idée derrière la tête avec ces caches". Ils me connaissent bien, en effet. Alors tout d'abord, sachez que les caches s'exportent très bien. Un cache produit sur un cluster peut tout à fait être rapatrié sur votre ordinateur. Donc vous pouvez faire le gros d'un rapport sur un cluster, puis fignoler sur votre machine locale. Ensuite, et c'est là pour moi la plus grande beauté de la chose, on peut les charger directement comme n'importe quel fichier RData (avec la fonction load). L'intérêt étant que si on veut reprendre toute une analyse et bricoler dans la console R, c'est possible. Il suffit de charger chacun des chunks dans l'ordre !

"Mais pourquoi on ne charge pas que le dernier ???" Ah, bonne question ! (Et oui j'aime me faire des dialogues dans ma tête...)

Chaque chunk ne contient QUE ce qu'il a produit. Il ne contient donc aucun objet créé avant. Il faut donc bien tous les charger pour tout avoir. Et c'est là qu'on va pointer le plus gros problème de cette méthode : Si on change un chunk qui a des répercussions sur la suite, ce n'est pas automatiquement propagé. Dans ce cas, je vous conseille soit de supprimer le fichier cache des chunks qui doivent être modifiés, soit de supprimer tout le cache.

"Ouhla mais c'est galère de charger chaque chunk dans le bon ordre !"

Bon, vu que je suis sympa avec vous, j'ai écrit une petite fonction R qui va le faire pour vous. Pour qu'elle fonctionne, vous aurez besoin d'avoir déjà installé la librairie devtools, puis de lire mon script en fichier source :

library(devtools)
source_url("https://gist.github.com/achateigner/e3f905d9fc98d34c8ecee93275c80a07/raw/loadAllChunksCache.R")

Ensuite, il vous suffira d'appeler ma fonction et de lui donner en argument le nom du rapport pour lequel vous voulez charger le cache :

loadAllChunksCache("Rapport.Rmd")

Il chargera automatiquement et dans l'ordre de votre rapport le cache qu'il trouvera dans le premier dossier existant qui correspond à "Rapport_cache/html/" ou "Rapport_cache/pdf/" ou "Rapport_cache/word/". Vous pouvez aussi lui spécifier en deuxième argument le dossier où vous voulez qu'il prennent le cache. C'est utile par exemple si vous avez du cache dans le dossier "Rapport_cache/html/" mais que vous voulez celui qui est dans "Rapport_cache/pdf/". Il faudra aussi pour qu'il fonctionne que vous ayez nommé vos chunks, il ne fonctionnera pas avec les noms automatiques. Je pourrais mettre ça en place, mais je n'encourage pas cette mauvaise pratique. Flemmard oui, mais flemmard avec classe !

Voilà pour mes astuces sur le cache des rapports Rmarkdown. Je pense que le cache est le plus gros intérêt de faire des rapports dès le début du développement de n'importe quel projet. Je vous conseille une fois que vous avez fini de développer votre rapport de supprimer tout le cache et de refaire tourner votre analyse entièrement. On n'est jamais à l'abris d'une erreur qui se propage.

Enfin, et pour finir, je vais faire un peu de pub pour mes librairies et autres scripts qui pourraient vous être utiles :

  • J'ai amélioré la fonction ipak que j'ai trouvé sur le github de Steven Worthington qui permet maintenant en une seule fonction de charger une liste de librairies, et de les installer du CRAN ou de bioconductor directement si elles ne sont pas installées. Elles seront chargées après leur installation. Pour l'utiliser, encore une fois il vous faut devtools d'installé, puis charger cette fonction :
    library(devtools)
    source_url("https://gist.github.com/achateigner/f7948d43f34c1d1bcd83097036338601/raw/ipak.R")
    packagesNeeded <- c("captioner", "apercu", "viridis")
    ipak(packagesNeeded)
  • J'ai créé le package apercu, dont la fonction principale, ap(), vous permet d'afficher... roulement de tambour... un aperçu de vos objets. Je m'explique : de la même manière que head() affiche les 6 premieres lignes d'une matrice par défaut, ou les 6 premiers éléments d'un vecteur ou d'une liste, ap() en affiche 5, à la différence que pour une matrice ou un data.frame, il n'affiche aussi que les 5 premières colonnes. Cette fonction vous sera particulièrement utile en cours de développement, pour voir rapidement vos grosses matrices, data frames, listes, vecteurs et même des objets plus compliqués et imbriqués. Ce package est disponible sur le CRAN, il s'installe donc normalement avec install.packages("apercu") ou avec ipak("apercu").

 

Voilà ! Sur ce je vous laisse en remerciant mes relecteurs, Yoann M. et Kumquatum pour leurs commentaires !

L’article Maîtrisez le cache de Rmarkdown ! est apparu en premier sur bioinfo-fr.net.

Viewing all 30 articles
Browse latest View live