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

"IRC ? Mais c'est quoi en fait ?"

$
0
0

Cette question, je l'entends maintenant depuis pas mal de temps quand je parle du blog à mon entourage (étudiants de mon ex-master, personnes rencontrées aux JeBiF Pubs et TOBi, collègues, bioinformaticiens croisés, etc.). C'en est arrivé au point que je me sens vieille je me suis dit que faire un article de présentation de cet outil qu'est IRC, adjoint d'un mini tutoriel serait une bonne idée.

Vous êtes prêts ? Commençons.

IRC, kézako ?

IRC, pour Internet Relay Chat, c'est un protocole de communication textuel sur Internet avant tout. Comprenez un système d'envois de message instantanés via le réseau.

Mais ! Mais ! Pourquoi ne pas utiliser Facebook, WhatsApp, MSN, ...

Car le petit réseau de bioinfo initial s'est historiquement formé sur IRC, et de lui est né le blog que vous connaissez aujourd'hui suite à un souhait de pérennité.

Mais d'autres avantages sont présents pour que nos petites moules soient restées accrochées à leur rocher :

  • pas besoin de créer de compte (à moins de vouloir protéger son pseudo)
  • rarement bloqué par nos emmerdeurs adorables et dévoués administrateurs réseau (du moins dans sa version web client)
  • très succinct
  • et surtout libre pour nos allergiques au copyright

"Et puis parce que c'était mieux avant !"  -  "C'est ça c'est ça l'ancien, on va aller prendre ses cachets maintenant."

 

Pour utiliser ce protocole, il est nécessaire de se connecter sur un réseau dédié. Historiquement 4 grands réseaux cohabitaient (IRCnet, DALnet, EFnet, et Undernet), mais dans les années 2000, 2 autres réseaux sont devenus particulièrement populaires : QuakeNet (orienté sur le jeu vidéo) et Freenode (orienté sur l'open source tant au sens informatique que liberté de parole). C'est sur ce dernier que bioinfo-fr est présent.

Le fonctionnement d'IRC est simple, vous pouvez discuter de 2 façons :

  • dans un salon/canal de discussion (un channel en anglais, ou chan en abrégé). Ils sont repérables par le croisillon qui se trouve devant leur nom, par exemple "#bioinfo-fr" pour le canal du blog. Ces canaux peuvent être à l'origine un groupe d’amis, des gens qui ont un centre d’intérêt commun, ou encore un groupe d'entraide entre utilisateurs d’un logiciel ou d’un langage informatique où une centaine de personnes s’entraident quotidiennement ! 
  • avec une seule personne en privé

Pour ce faire, munissez-vous de votre plus beau pseudonyme et lancez-vous sur le réseau.

Ça a l'air sympa ton truc, je fais comment pour y accéder ?

Pour se connecter à l'un des réseaux IRC précédemment cités, il va falloir utiliser un client. Je vais donc vous en présenter quelques-uns qui vous permettront de débuter en attendant de trouver votre petit chouchou. Commençons par un critère qui va pas mal dégrossir la liste : client lourd ou client web ?

 

.

En deux mots : un client lourd est un logiciel que vous aurez installé sur votre ordinateur et qui vous servira à vous connecter au serveur distant. À l'inverse, un client web sera une application à laquelle vous accéderez via votre navigateur web.

 

Pour les adeptes du client web

C'est le type de client que je recommande pour les gens qui débutent sur IRC (ou bien qui souhaitent un fonctionnement épuré). Rarement bloqués par les administrateurs système (car passant par le port 80 et non un entre 6665-6669), et par définition cross-plateforme, ces clients sont accessibles via la majorité des navigateurs. Je vais vous en proposer deux qui répondront normalement à la plupart de vos attentes :

  • Qwebirc (le client de base de freenode) - C'est la qualité russe à portée de main : rustique, pas prévu pour faire joli, possède quelques fonctionnalités à activer pour faciliter un peu la lecture (coloration des pseudos, horodatage, ...) mais fait totalement le travail demandé
  • Kiwiirc - C'est Qwebirc mais avec une ergonomie et un graphisme plus poussés. Reprend toutes les fonctionnalités énoncées précédemment avec en plus un système de notifications compatible avec les navigateurs récents, et l'ajout de mots clefs (des highlights ou hl en abrégé) dans les paramètres pour être notifié/voir le message surligné dans l'interface (à savoir que par défaut votre pseudonyme est un mot clef)

 

À gauche Kiwiirc - À droite Qwebirc
(respectivement GNU Affero GPL license et GNU General Public License, version 2)

 

Mais si vous souhaitez pousser la personnalisation de votre client avec des fonctionnalités comme celles décrites plus bas, pas le choix : il vous faut un client lourd !

Pour les adeptes du client lourd

La compatibilité avec l'OS que vous utilisez va ici être un facteur déterminant. Je vais donc vous recommander quelques clients pour chacun (note : n'étant pas utilisatrice de Mac, je me suis basée sur les conseils de nos chers collègues du canal #bioinfo-fr 😉 )

  • Linux
    • Pour ceux préférant une interface graphique, Konversation ou HexChat (la version maintenue de Xchat) sauront ravir vos mirettes. Et bonus : HexChat est compatible sur les deux autres OS également.
    • Pour nos amoureux du terminal, visez Weechat. S'il s'avère un peu compliqué au début pour un novice Unix et/ou IRC, il devient rapidement une extension de vous-même une fois les dizaines de paramètres peaufinés.
  • Windows
    • Si vous êtes attachés aux interfaces graphiques, HexChat sera à nouveau votre ami, de même que ChatZilla qui possède même un plugin pour Firefox.
    • Il n'existe pas à ma connaissance de client intégré à la console DOS de Windows, cependant vous pouvez opter pour irssi pour une apparence proche d'un terminal.
  • Mac
    • Colloquy ainsi que Adium sont des clients graphiques qui combleront vos attentes. Le premier possède même une application mobile pour les accros à IRC.
    • Pour le côté terminal, pas de neuf à l'horizon, je vais à nouveau vous recommander Weechat.

 

De gauche à droite et de haut en bas : Konversation, Colloquy, Hexchat, irssi, ChatZilla, Adium
(Licence GPLv3)

 

J'ai mon client favori, et maintenant ?

On va débuter par une rapide revue de l'interface de tout client IRC :

Présentation de la composition d'une fenêtre irc via Kiwiirc

 

  1. Onglets de canal : correspond aux différents canaux sur lesquels vous êtes présents. À noter que vous aurez toujours un onglet de connexion au serveur (qui s'affiche sous le nom de "status", "freenode", ...).
  2. Pseudonymes connectés : le plus souvent ce sont des personnes physiques, cependant il se peut que ce soit parfois des bots (petits programme utiles ou juste amusants). Le bot le plus commun est ChanServ, vous le verrez sur un grand nombre de canaux, on l’utilise pour enregistrer des canaux auprès du réseau freenode et les gérer.
    • Le @ indique que la personne est opératrice du canal : elle a le droit d'expulser (kick), bannir, donner un privilège de parole (voice), nommer opératrice toute personne du canal
    • Le + indique que la personne a reçu un privilège de parole (n'est pertinent que si le canal est en mode +m car seules ces personnes sont alors autorisées à parler)
  3. Zone de discussion :  c’est là que se trouvent affichés les messages de tous. On y retrouve le topic ici en encadré jaune, c'est le sujet/descriptif du chan, il contient parfois des informations utiles sur les règles à respecter. Vous remarquerez également le surlignage fait lors de l'utilisation de mon pseudonyme par KwotKwot
  4. Zone de saisie : c’est ici que vous écrivez les messages, mais aussi les commandes.

 

Mais ! Mais ! Pourquoi tu parles de commandes vu qu'on est sur un logiciel de chat ?

Car le fonctionnement d'IRC est ainsi : basé sur des lignes de commande. Rassurez-vous, la plupart des clients graphiques proposent des menus et des raccourcis pour vous éviter de retenir toutes les commandes par cœur. Sachez cependant que pour certaines fonctionnalités plus poussées, le passage par les lignes de commande sera obligatoire.

 

Quelques commandes de base

Changer de pseudo /nick <pseudo que vous voulez>
Avoir des infos sur un utilisateur /whois <pseudo de l’utilisateur>
Se mettre « absent » /away <votre message d'absence>
Ne plus se mettre « absent » /back
Envoyer un message privé /msg <destinataire> <message>
Simuler une action /me <verbe conjugué à la 3e pers. sing.>
Quitter le canal /quit
Rejoindre un canal /join <canal>
Avoir de l’aide sur une commande /help <commande sans le «/»
Afficher le menu d’aide /help

Avec ça vous devriez vous en sortir. Pour vous perfectionner dans les différentes commandes d'IRC, je vous invite à utiliser votre moteur de recherche favori pour trouver les nombreux guides existant sur le sujet.

 

On m'indique que mon pseudonyme est déjà enregistré et que je ne peux donc pas le prendre. Qu'est-ce que cela signifie ?

L'enregistrement c'est le verrouillage d'un pseudonyme auprès du réseau freenode afin qu'on ne puisse pas se faire usurper son identité. Vous serez alors contraint de changer de pseudonyme (le classique étant d'ajouter " ` " ou "_" a la fin) et je vous conseille de l'enregistrer via la commande suivante :

/msg nickserv register <votre_mot_de_passe> <votre@mail.tld>

 

Les bonnes pratiques pour communiquer

Sur IRC comme partout, vous vous devez d'être respectueux envers les usagers (mais comme partout, vous tomberez ou serez un jour le crétin qui insulte, si si je vous jure). Quelques recommandations tout de même :

  • En arrivant sur un canal, dites bonjour, et si c'est la première fois que vous venez, présentez-vous (sur #bioinfo-fr, notre cher Yoann M. vous tombera de toutes façons bien assez vite dessus pour vous poser la question 🙂 )
  • Comme tout le monde n'est pas en permanence devant sa fenêtre IRC, n'hésitez pas à hl une personne. C'est-à-dire que vous commencez votre phrase par le pseudo de la personne (la plupart des clients proposent la tab-completion, elle vous sauvera de quelques pseudos alambiqués. Ex. de KwotKwot dans l'illustration plus haut:
    Kumquatum : comment ca va ?
  • À l'inverse, on évite de hl une personne si on a rien à lui dire et on rajoute un caractère pour casser la reconnaissance. Ex. :
    j'en parlais justement a Kum.quatum la dernière fois. Mais là elle est trop occupée.
  • À force de traîner sur les différents canaux, on apprend parfois l'identité et d'autres informations personnelles sur les gens. En aucun cas vous ne devez les divulguer sans l'accord des personnes (Si elles ont un pseudonyme, il y a une raison). On évite donc de dire
    Hey Gwen faut que je te parle d'un truc !
    (oui j'affiche mon prénom, mais en soi vous pouvez déjà le retrouver dans ma page auteur sur le blog :p )

 

Dans le cas où vous ne respecteriez pas ces règles de bienséance, gare au kick/ban !

 

Pour conclure

Voici une liste non exhaustive des canaux qui peuvent vous intéresser, tant professionnellement que récréativement :

  • #bioinfo-fr : THE canal, celui de ce blog ! Vous y retrouverez vos admins chéris qui se relaient pour pouvoir répondre en direct à vos questions concernant le blog. C'est aussi un lieu d'entraide sur des sujets bioinformatiques, des galères de code, et autres astuces de bioinformagiciens !
  • #jebif : le canal de l'association éponyme qui vous livre notamment les retranscriptions des Tables Ouvertes en Bioinfo. N'hésitez pas à passer les voir si vous voulez participer à la vie de l'association.
  • #bioinformatics : le canal anglophone de bioinformatique, qui ne manque pas de divaguer entre deux explications de bioinfo
  • #R / #python / #vim / #git / ... : tout un tas de salons éponymes où vous trouverez de l'aide technique quand #bioinfo-fr n'aura pas réussi à vous sauver

 

Au plaisir de vous voir sur le canal du blog ;D

 

Team Chat - https://xkcd.com/1782/
CC-BY-NC

 

Références

 

Merci aux relecteurs Lins`lhtd, et Aurélien Béliard pour leur temps consacré à cet article.

 

L’article "IRC ? Mais c'est quoi en fait ?" 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.

Pimp my workstation: avoir le terminal le plus classe du bureau !

$
0
0

Mise en place de zsh et d'un dossier .dotfiles


 

Introduction

Qu'est-ce que c'est que zsh ?

Il s'agit (comme nous indique aimablement sa page Wikipédia) d'un shell unix. En gros, il s'agit d'un remplaçant potentiel pour votre vénérable interpréteur bash.

Mais pourquoi se casser le beignet à installer zsh, alors que j'ai déjà bash ?

zsh a plusieurs atouts sur bash :
- Une autocomplétion avancée (incluant une correction orthographique).
- Un historique partagé entre les différents shells.
- Et surtout, le support de plugins permettant une personnalisation TRÈS agréable de son outil de travail.

Mon ancien bash à gauche, mon shell actuel à droite.

 

Double tab pour obtenir la doc. Notez la coloration syntaxique dans zsh.

Et du coup, c'est quoi le rapport avec un dossier .dotfiles ?

Hé bien, tant qu'à tripatouiller son interpréteur, autant en profiter pour constituer un dossier .dotfiles, c'est-à-dire un dossier contenant vos différents fichiers de configuration. Ensuite, ce dossier est mis sur votre gestionnaire de version favori, et il n'y a plus qu'à le mettre sur vos différentes stations de travail. C'est très utile quand on apprécie utiliser le même environnement de travail partout.

L'objectif de cet article est de vous présenter rapidement comment installer zsh, le configurer, et sauvegarder votre configuration dans un .dotfiles.

Installation de zsh

Mise en place

Après l'installation du paquet zsh, l'interpréteur est installé, mais il n'est pas utilisé par défaut par le système. Vous pouvez cependant l'utiliser en tapant la commande zsh dans votre shell, si vous souhaitez tester votre configuration avant d'en faire votre shell par défaut.

Pour faire de zsh votre interpréteur par défaut, il faut utiliser cette commande, puis relancer votre session pour que cela soit pris en compte :

chsh -s /bin/zsh

Si la commande chsh n'est pas disponible de base, c'est qu'il vous faut installer le paquet util-linux-user.

Configuration

Comme bash, zsh est configurable à partir d'un .zshrc. Si vous avez déjà un dossier .dotfiles, je vous laisse sauter directement à la partie de configuration du .zshrc, et surtout de ses plugins. Pour les autres, je vais vous montrer comment j'ai géré le mien. Attention, je ne prétends pas détenir la meilleure des méthodes !

Le dossier .dotfiles

Création du dossier

Créez un dossier .dotfiles dans votre home, et initiez un dépôt dedans (pour git, cf ici).

mkdir .dotfiles
cd .dotfiles
git init

Ensuite, il ne reste plus qu'à mettre les fichiers de configuration à l'intérieur et les faire suivre par votre gestionnaire.

Création d'un Makefile

L'idée, c'est qu'une fois que votre dossier est fait, vous n'ayez que le minimum à faire pour maintenir votre configuration à jour. Pour cela, le plus intéressant que je connaisse reste de créer un Makefile, qui contiendra toutes les commandes nécessaires.

Voici un exemple avec le mien, qui gère les 4 fichiers de configuration de mon dossier :

all: pull source

source:
 cp ~/.dotfiles/.aliases ~/.aliases
 cp ~/.dotfiles/.zshrc ~/.zshrc
 cp ~/.dotfiles/.bashrc ~/.bashrc
 cp ~/.dotfiles/.bash_profile ~/.bash_profile

pull:
 git pull

antigen:
 git clone https://github.com/zsh-users/antigen.git

install: antigen source

Ainsi, make va successivement mettre à jour le dépôt, puis sourcer les fichiers de configuration. Le make install est présent uniquement pour l'installation du dépôt sur vos autres machines, en permettant l'ajout du gestionnaire de plugin de zsh (détails dans la partie sur Antigen) avant de copier les fichiers de configuration.

Les plus attentifs d'entre vous auront remarqué qu'au lieu de créer des liens symboliques, je copie les fichiers de configuration. En fait, il m'est arrivé que les liens symboliques ne marchent pas, et j'ai préféré pallier ce soucis en copiant directement les fichiers. C'est moins propre et occupe un peu plus de place, mais bon, j'assume, tout le monde à le droit à son coté sombre et déviant !

La configuration de zsh

Le zshrc

Comme exemple, voici mon dossier .dotfiles, vous pouvez jeter un œil à mon .zshrc si vous le souhaitez.

Les alias : si ce n'est pas déjà fait, je vous conseille de mettre vos alias dans un fichier .aliases, et de pointer vers ce fichier grâce à cette commande dans votre .zshrc :

source ~/.aliases

En dehors de ça, la grande force de zsh, c'est sa customisation via des plugins. Cela est possible grâce à 2 outils.

Antigen

Antigen est le gestionnaire de plugins pour zsh, l'équivalent de Vundle ou Plug pour Vim et Neovim.
Pour utiliser Antigen, il faut copier le dépôt (dans le cadre de cet article, dans votre .dotfiles, sinon, où vous voulez) :

git clone https://github.com/zsh-users/antigen.git

Antigen peut être mis à jour avec cette commande :

antigen selfupdate

Pour mettre à jour les plugins :

antigen update

Oh-my-zsh

Seul, Antigen ne fait pas grand chose. C'est là qu'intervient oh-my-zsh, un dépôt pour gérer votre configuration. Cela contient un peu de tout, depuis des thèmes, jusqu'à des plugins faisant dire des "Chuck Norris Facts" à une vache ! Il existe d'autres dépôts, comme prezto, mais je n'ai pas encore eu l'occasion de comparer.

Le saviez-tu?

Pour l'installer, c'est très simple, Antigen le fait pour vous. Idem pour l'installation des plugins, et leur mise à jour !
Pour cela, rien de plus simple, rendez vous dans votre .zshrc, et écrivez :

autoload antigen

# Source vers Antigen
 source ~/.dotfiles/antigen/antigen.zsh

# Installation de oh-my-zsh
 antigen use oh-my-zsh

# différents plugins
 # support de git
 antigen bundle arialdomartini/oh-my-git
 antigen bundle git

# coloration syntaxique dans le shell
 antigen bundle zsh-users/zsh-syntax-highlighting

# theme
 antigen theme ys

antigen apply

Il ne vous restera plus qu'à chercher les plugins qui vous paraissent les plus intéressants, le thème de vos rêves, et faire des liens ou copier les fichiers de configuration dans votre /home!

Merci aux relecteurs : eorn, Nico M., et Nisaea

L’article Pimp my workstation: avoir le terminal le plus classe du bureau ! est apparu en premier sur bioinfo-fr.net.

Créez vos documents collaboratifs en LaTeX

$
0
0

Aujourd'hui, on vous présente une méthode pour créer vos documents collaboratifs en ligne, en utilisant LaTeX, ainsi que quelques astuces qui pourront peut-être vous simplifier la vie !

Un petit peu de contexte

Imaginons : vous êtes un jeune chercheur dynamique, et vous voulez rédiger un papier avec vos collaborateurs. Ou bien vous êtes un étudiant, et vous devez rendre un rapport quelconque à partir d'un travail fait en groupe.

Vous pourriez ainsi travailler de manière solitaire, et vouloir créer vos documents directement dans le cloud, pour pouvoir les ouvrir de n'importe quelle station de travail. De multiples solutions à ces situations existent : Google Docs, FramaPad, la conjonction d'un logiciel de traitement de texte et d'un service de stockage dans le cloud… Mais le résultat peut ne pas vous satisfaire. On pourra citer par exemple la difficulté de rédiger des formules mathématiques, ou encore les problèmes de versions de logiciel et de compatibilité si ceux-ci ne sont pas intégrés au cloud. Ce qu'il vous faudrait, c'est un éditeur de texte supportant le langage LaTeX, et qui serait collaboratif ! Ça tombe bien, on va vous en présenter.

La minute LaTeX

LaTeX, c'est un langage et système de composition permettant de réaliser des documents professionnels. Si vous ne connaissez pas encore cet outil, je vous conseille d'aller faire un tour du côté d'un autre de nos articles qui vous apprendra à créer votre premier document en LaTeX.

Maintenant que vous êtes un peu à l'aise avec LaTeX et que vous connaissez par cœur tous les articles du blog avec ce mot-clef, ajoutons la dimension collaborative !

 

Les différents supports

Dans ce marché florissant de l'édition de documents LaTeX en ligne, deux services se démarquent un peu.

ShareLaTeX

ShareLaTeX, un des principaux outils d'édition de documents LaTeX collaboratifs. | AGPL v3

ShareLaTeX est un service au modèle Freemium : il a une version gratuite et une version payante comportant plus de fonctionnalités.

Dans sa version gratuite, il n'est pas possible de collaborer avec d'autres personnes, ce qui limite un peu notre intérêt. Ses deux versions payantes ("Collaborator" et "Professional" à 14 € et 28 € par mois) permettent d'incorporer plus de collaborateurs, de synchroniser directement avec Dropbox et Github. Cependant, il est à noter que la participation à certaines conférences peut donner un accès libre et gratuit à vie (comme Data Integration in Life Science par exemple), et qu'il existe des réductions pour les académiques et pour les laboratoires (il se peut que ce dernier ait même déjà une licence sans que vous le sachiez, n'hésitez pas à demander).

 

Overleaf

Overleaf, un autre outil en ligne d'édition de documents LaTeX collaboratif. | Overleaf Copyright © 2017

Overleaf est une autre solution du même type. La version gratuite donne accès à 1Go de stockage, un système de versionnage, plein de thèmes disponibles, de l'auto-complétion, et accès à un dépôt Git généré automatiquement. De plus, et c'est ce qui nous intéresse, le partage peut se faire avec autant de personnes que vous le souhaitez.

Pour la suite de l'article, on va donc préférer ce service à ShareLaTeX.

 

Début classique avec Overleaf

Après les nécessaires étapes d'inscription et de connexion, nous allons commencer par créer un nouveau document (notez que vous pouvez aussi importer une archive zip), en cliquant sur "New Project". S'offrent alors à nous une myriade de thèmes, prêts à l'emploi, vous n'avez qu'à sélectionner celui qui vous convient et le document sera pré-rempli par le code générant le thème choisi.

 

Capture d'écran d'Overleaf, en pleine action, avec un calendrier en langue turque imprimable pour faire un dodécaèdre (on peut tout faire avec LaTeX !). Pour passer de la source (code LaTeX) à un éditeur WYSIWYG, cliquez sur Rich Text en haut à gauche. Pour partager, cliquez sur Share, en haut.

 

Vous pouvez ensuite commencer à rédiger votre document dans la partie gauche de la page nouvellement affichée. Pour les gens moins à l'aise avec LaTeX (vous-même ou l'un de vos collaborateurs) il est également possible d'utiliser un éditeur WYSIWYG, en cliquant sur le bouton "Rich Text".

Enfin, pour partager le document, cliquez sur "Share", ou bien simplement copiez-collez le lien de votre navigateur, et envoyez-le à vos collaborateurs.

 

Et pour ceux qui aiment un peu plus de défi

Mais pour les plus développeurs dans l'âme qui se voient confrontés à ce problème (à savoir : fichiers LaTeX à modifier, travail collaboratif, accessibilité sur plusieurs ordinateurs), une autre solution viendra à l'esprit : Git !

En effet, Git permet de gérer et éditer facilement des fichiers à plusieurs, de synchroniser le contenu d'un dépôt entre plusieurs ordinateurs, etc. Si vous n'êtes pas encore familier avec cet outil, n'hésitez pas consulter cet article d'introduction à Git sur notre blog.

 

LaTeX et Git : premiers pas

Pour commencer, créez votre document LaTeX, dans un dossier créé au préalable.

$ mkdir calendrier &amp;&amp; cd calendrier
$ touch calendrier.tex

Ensuite, pour initialiser un dépôt Git dans ce répertoire, il suffit de taper :

$ git init

Et voilà, il ne reste plus qu'à partager ce dépôt, à grands coups de

git clone
  !

 

LaTeX, Git et Github

Pour faciliter le partage du dépôt, il est possible d'utiliser GitHub, le fameux service de stockage de dépôts Git. Pour cela, quatre étapes simples tirées de l'aide de GitHub :

1. Créez un nouveau dépôt sur GitHub.

2. Dans votre ligne de commande, créez un clone

bare
  du dépôt local. Dans mon cas :
$ git clone --bare calendrier calendrier.git

3. Faites un

push
  à partir du dépôt nouvellement cloné, avec l'option
mirror
  (qui fait en sorte que toutes les références comme les branches, les tags, etc, sont incluses dans l'opération). Indiquez l'adresse du dépôt git sur Github.
$ cd calendrier.git
$ git push --mirror https://github.com/utilisateur/calendrier_sur_github.git

4. Supprimez ce dont vous n'avez plus besoin, et clonez à nouveau à partir de GitHub.

$ cd ..
$ rm -rf calendrier.git
$ git clone https://github.com/utilisateur/calendrier_sur_github.git

Maintenant que votre dépôt est sur GitHub, vous pouvez facilement le partager et travailler en collaboration, aussi bien en ligne qu'hors ligne. Vous pouvez aussi utiliser tous les outils de GitHub et les adapter à l'écriture de documents. Voici quelques exemples (tirés de ce billet) :

  • Utilisez les messages de 
    commit
      comme d'un journal, pour vous rappeler votre raisonnement (et éviter les questions du type : "Mais pourquoi est-ce que j'ai fait ça ?")
  • Utilisez le wiki pour structurer vos idées dans un premier temps
  • Utilisez le système d'issues pour gérer les questions qui peuvent arriver en cours de route, votre todo-list, vos idées... Les étiquettes se doivent d'être explicites (ex : "question", "idée"...)

 

LaTeX, Git, Github et Overleaf

Vous croyiez avoir atteint le summum de l'élégance ? Attendez, il reste encore une étape ! Il est en effet possible de synchroniser votre travail sur Overleaf et sur GitHub !

"Pourquoi faire ?" me direz-vous, eh bien pour plusieurs raisons. Quelques exemples : vous aimez GitHub et ses différents outils, mais un collaborateur ne sait utiliser que le texte enrichi, en WYSIWYG. Ou bien vous voulez garder une trace de vos documents dans votre compte GitHub, qui gère tout de même bien mieux les versions.

La marche à suivre est assez simple, puisque chaque document sur Overleaf est associé à un dépôt Git. Il suffit donc d'avoir un dépôt local avec deux origines. Voici la marche à suivre, fortement inspirée de cet article

1. Clonez votre document Overleaf localement. Si votre document sur celui-ci a pour adresse https://www.overleaf.com/2029559gkypz, alors le dépôt Git associé est https://git.overleaf.com/2029559gkypz.

$ git clone https://git.overleaf.com/2029559gkypz article

2. L'origine de ce dépôt est donc le dépôt distant sur Overleaf. Renommons cette origine pour plus de clarté.

$ git remote rename origin overleaf

Dorénavant, pour importer les changements effectués sur Overleaf, il vous suffira de lancer :

$ git pull overleaf master

3. Créez un dépôt sur GitHub, et ajoutez GitHub comme répertoire distant.

$ git remote add github https://github.com/utilisateur/document.git

4. Faites un push vers GitHub pour synchroniser.

$ git push github

Tous vos dépôts (en local, sur Overleaf et sur GitHub) sont maintenant à jour !

 

Conclusion

Nous avons donc vu deux façons de rédiger des documents LaTeX de manière collaborative. La première est basée sur un service en ligne, Overleaf, qui permet de le faire très facilement, même dans sa version gratuite. Le second est un peu plus technique et fait appel à Git en plus, et dans notre cas GitHub (qui peut être substitué par tout autre stockage de dépôts Git).

Vous n'avez donc plus d'excuse pour rédiger des rapports bien propres !

 

Merci aux relecteurs Bunny, Akira, et Kumquatum pour leur participation.

L’article Créez vos documents collaboratifs en LaTeX est apparu en premier sur bioinfo-fr.net.

Les éléments répétés du génome humain : aperçu rapide avec R et le tidyverse

$
0
0

Dans un précédent article, nous avions regardé le fichier d'annotation des gènes du génome humain d’après Gencode. J'avais utilisé pour cela la puissante combinaison dplyr + ggplot2 (packages centraux du tidyverse), particulièrement adaptée à tout ce qui est manipulation et visualisation de données tabulaires.

Mais notre génome n'est pas constitué que de gènes, loin s'en faut ! Les éléments répétés sont en fait bien plus majoritaires. Je ne vais pas me risquer à donner ici une définition précise de ce qu'est un élément répété, je me contenterai de rappeler que si les éléments transposables sont des éléments répétés, tout les éléments répétés ne sont pas transposables ! Comme souvent en bio-informatique, je vais me contenter de la définition pragmatique d'élément répété : un élément répété est un élément décrit dans ma table d'annotation des éléments répétés. :-p

Les sources d'annotation des éléments répétés du génome humain sont bien plus rares que pour ce qui concerne les gènes. Je vous propose d'utiliser le temps de cet article une table disponible sur le UCSC table browser. Alors oui, l'interface a mal vieilli, mais le UCSC table browser reste une formidable collection de fichiers d'annotation du génome. Pour obtenir la table en question, il suffit normalement de changer le champ group sur Repeats et de laisser le reste par défaut.

Comment obtenir une table d'annotation des éléments répétés du génome humain. Vous pouvez cliquer sur le bouton describe table schema pour une description des colonnes de la table.

J'ai personnellement téléchargé cette table le 4 avril 2017. Peut-être la vôtre sera-t-elle plus récente, et donc légèrement différente ? En effet, les annotations du génome humain, gènes comme éléments répétés, ne sont pas encore parfaites et sont toujours activement améliorées. Cette table a été générée à l'aide de l'outil RepeatMasker, outil qui permet de masquer (en remplaçant par des N) les nucléotides d'un fichier fasta qui sont inclus dans des éléments répétés. Je trouve assez ironique qu'une des meilleures sources d'annotation des éléments répétés soit issue d'un logiciel visant à s'en débarrasser. ^^ Ce logiciel de plus de 20 ans sert notamment à faciliter l'annotation des gènes des génomes en masquant les séquences répétées.

Si vous souhaitez reproduire les analyses ci-dessous, je vous laisse donc télécharger la table, la mettre dans un répertoire de travail, et lancer R. Si vous n'en avez rien à faire de R, vous pouvez tout à fait sauter les blocs de code et autres explications pour vous contenter de regarder les jolies images. 🙂 Je détaille cependant ma démarche, en espérant qu'au moins l'une ou l'un d'entre vous puisse en retirer une astuce utile, au prix d'un alourdissement assez conséquent de ce billet.

Import et toilettage des données

Après avoir lancé R et défini un répertoire de travail approprié (via la commande

setwd()
 ), je commence par charger quelques packages que j'aime bien :

library(purrr)   # programmation fonctionnelle
library(dplyr)   # manipulation de tableaux de données
library(readr)   # import de fichiers txt
library(ggplot2) # des jolis plots
library(cowplot) # thème ggplot2 + outils pour figures multi-panneaux
library(forcats) # manipulations de factor
library(svglite) # export en svg
library(viridis) # une palette de couleur sympa
library(xtable)  # export des tableaux en HTML

J'importe la table dans R à l'aide d'une fonction du package readr, fonction qui est plus rapide et qui a des valeurs par défaut de ses paramètres plus adaptées que la fonction

read.table()
  de R base :

repeats_dirty <- read_tsv("repeats_rmsk_hg38.txt.gz")

Je vais ensuite ne garder que les colonnes qui m'intéressent, que je renomme. J'en profite aussi pour ne garder que les lignes concernant les chromosomes standards, en filtrant les haplotypes alternatifs qui ne feraient qu'alourdir certaines figures par la suite.

standard_chromosomes <- c(paste0("chr", 1:22), "chrX", "chrY")

repeats <- select(repeats_dirty, genoName, genoStart, genoEnd, strand, repName, repFamily, repClass) %>%
    dplyr::rename(chr = genoName, start = genoStart, end = genoEnd, name = repName, family = repFamily, class = repClass) %>%
    filter(chr %in% standard_chromosomes)

Ce qui me donne cette table-ci :

head(repeats) %>%
    xtable() %>%
    print(type = "html", include.rownames = FALSE)

chr start end strand name family class
chr1 67108753 67109046 + L1P5 L1 LINE
chr1 8388315 8388618 - AluY Alu SINE
chr1 25165803 25166380 + L1MB5 L1 LINE
chr1 33554185 33554483 - AluSc Alu SINE
chr1 41942894 41943205 - AluY Alu SINE
chr1 50331336 50332274 + HAL1 L1 LINE

Nous avons donc une classifications des éléments répétés en trois niveaux hiérarchiques, dans l'ordre : class > family > name.

Avant de regarder plus en détail cette classification, j'en profite pour filtrer les quelques lignes contenant un "?", qui correspondent à des classifications incertaines. Je pourrais les garder, mais il y en a relativement peu, et elles complexifieraient l'analyse et alourdiraient les figures.

repeats <- filter(
    repeats,
    !(grepl("?", repeats$class, fixed = TRUE) |
        grepl("?", repeats$family, fixed = TRUE) |
        grepl("?", repeats$name, fixed = TRUE)
    )
)

Les classes d'éléments répétés

La première figure que nous allons générer s’intéresse au 1er niveau hiérarchique de la classification : les classes d'éléments répétés. Combien y en a-t-il ? (divulgâchis : 16) Quel est l’effectif de chacune des classes ? Quelle fraction du génome chaque classe couvre-t-elle ? Quelle est la distribution des longueurs des éléments au sein de chaque classe ?

Je vais trier les classes par effectif décroissant pour rendre la figure plus jolie. Pour cela, comme j'utiliserai ggplot2, il me faut modifier l'ordre des levels de la colonne class après l'avoir transformée en factor. J'utilise quelques fonctions du package forcats.

repeats$class <- factor(repeats$class) %>% fct_infreq %>% fct_rev

Pour le premier panneau, un diagramme en barres, j'utilise des astuces vues dans le précédent billet. Si vous découvrez ggplot2, pourquoi ne pas jeter un coup d’œil sur cet article star du blog ? Comme toujours, ce qui prend le plus de lignes, ce n'est pas la figure en elle-même, mais tous les petits ajustages nécessaires pour la rendre plus jolie.

plot_Class <- ggplot(repeats, aes(x = class)) +
    geom_bar(stat = "count", fill = "indianred1") +
    geom_text(aes(label = ..count..), y = 10000, hjust = 0, stat = "count") +
    labs(x = "Classe", y = "Nombre d'éléments") +
    scale_y_continuous(sec.axis = dup_axis()) +
    coord_flip() +
    background_grid(major = "xy", minor = "none")

Pour le deuxième panneau, j'ai envie de voir la longueur totale couverte par chaque classe d'éléments répétés. Pour aider à la lecture, ce ne serait pas mal d'indiquer aussi la fraction du génome couverte par chaque classe. Voilà une excellente occasion d'utiliser une feature récemment ajoutée à ggplot2 : le second axe ! Avant toute chose, et comme la dernière fois, je récupère auprès de UCSC la longueur totale de chaque chromosome :

chr_length <- read_tsv("http://hgdownload-test.cse.ucsc.edu/goldenPath/hg38/bigZips/hg38.chrom.sizes", col_names = FALSE) %>%
    dplyr::rename(seqnames = X1, length = X2) %>%
    filter(seqnames %in% standard_chromosomes) %>%
    mutate(seqnames = factor(seqnames, levels = standard_chromosomes))
genome_length <- sum(as.numeric(chr_length$length)) # 3 088 269 832

Pour faciliter les prochains calculs, je rajoute une colonne contenant la largeur de chaque élément :

repeats <- mutate(repeats, width = end - start)

Et c'est parti pour un peu de magie dplyr ! Je groupe mon tableau par classe d'éléments répétés (avec

group_by()
), je calcule ensuite la longueur totale couverte par chaque classe (avec
summarise()
 ), et je lance le tout dans ggplot2 ! Je spécifie bien que je souhaite un axe secondaire, qui est une transformation linéaire de l'axe principal (
sec.axis = sec_axis(~100 * . / genome_length, name = "% du génome")
 ).

plot_genomeProp <- repeats %>%
    group_by(class) %>%
    summarise(total_length = sum(width)) %>%
    arrange(total_length) %>%
    ggplot(aes(x = class, y = total_length)) +
    geom_bar(stat = "identity", fill = "cornflowerblue") +
    geom_text(aes(label = paste0(round(100 * total_length / genome_length, digits = 1), "%")), y = 10000, hjust = 0) +
    labs(y = "Longueur cumulée (pb)") +
    scale_y_continuous(sec.axis = sec_axis(~100 * . / genome_length, name = "% du génome")) +
    coord_flip() +
    theme(axis.text.y = element_blank(), axis.title.y=element_blank()) +
    background_grid(major = "xy", minor = "none")

Le troisième et dernier panneau sera un aperçu de la distribution des largeurs pour chaque classe d'éléments répétés. Des boites à moustaches générées avec ggplot2 suffisent ici :

plot_sizeDistrib <- ggplot(repeats, aes(x = class, y = width)) +
    geom_boxplot(fill = "mediumorchid", outlier.shape = NA) +
    labs(x = "Classe", y = "Taille des éléments (pb)") +
    scale_y_continuous(sec.axis = dup_axis()) +
    coord_flip(ylim = c(0, 2500)) +
    theme(axis.text.y = element_blank(), axis.title.y=element_blank()) +
    background_grid(major = "xy", minor = "none")

Enfin, j'arrange laborieusement les panneaux à l'aide du package cowplot et de quelques nombres magiques qui vont bien pour rendre la figure plaisante à l’œil :

myoffset <- 0.008
firstplotsize <- 0.44
svglite("plots/classeER.svg", width = 10, height = 5)
ggdraw() +
    draw_plot(plot_Class      , x = 0.0, y = myoffset, w = firstplotsize, h = 0.96 - 2*myoffset) +
    draw_plot(plot_genomeProp , x = firstplotsize, y = 0.0, w = (1-firstplotsize)/2, h = 0.96) +
    draw_plot(plot_sizeDistrib, x = firstplotsize + (1-firstplotsize)/2, y = 0.0, w = (1-firstplotsize)/2, h = 0.96) +
    draw_plot_label(LETTERS[1:3], x = c(0.14, firstplotsize - 0.01, firstplotsize + (1-firstplotsize)/2 - 0.01), y = 0.92) +
    draw_label("Classes d'éléments répétés", size = 15, x = 0.5, y = 0.97)
dev.off()

alt-text Figure 1: Les classes d'éléments répétés du génome humain. A. Nombre d'éléments répétés pour chaque classe. B. Fraction du génome couvert par chaque classe. C. Distribution des tailles d'éléments répétés pour chaque classe.

Les plus observateurs d'entre vous auront peut être réalisé, avec stupeur, qu'en effet RepeatMasker catégorise les gènes d'ARN ribosomaux (rRNA) et de transferts (tRNA) comme étant des éléments répétés ! Ce qui est techniquement exact, mais m'a un peu surpris au début (ça va mieux maintenant, merci). Je me suis amusé à comparer le nombre de copies de gènes d'ARN ribosomaux recensé par GENCODE, vu la dernière fois (544) avec ceux repérés par RepeatMasker (1 751). Peut-être la différence est-elle due aux copies non fonctionnelles, incluses dans la liste RepeatMasker mais pas dans celle de GENCODE ? Une telle différence se retrouve pour d'autres catégories de gènes ARN. Par exemple GENCODE recense 1 900 snRNA et RepeatMasker 4 285.

Si les SINE (short interspersed nuclear elements) sont plus nombreux que les LINE (Long interspersed nuclear elements), ils sont en général plus courts, et donc constituent une fraction moindre de notre génome. La troisième classe la plus abondante, à la fois en effectif et en fraction du génome, est celle des éléments à LTR (long terminal repeat). Il s'agit donc d'éléments issus de rétrovirus endogènes.

Notez que la figure 1C ne montre pas les points oustiders. En effet, les plus longs éléments répétés le sont tellement que les montrer écraseraient le reste de la figure. Voyez plutôt :

arrange(repeats, desc(width)) %>%
    top_n(5) %>%
    xtable() %>%
    print(type = "html", include.rownames = FALSE)

chr start end strand name class family width
chr1 123500000 124000000 + ALR/Alpha Satellite centr 500000
chr1 123000000 123500000 + ALR/Alpha Satellite centr 500000
chr5 48000000 48500000 + ALR/Alpha Satellite centr 500000
chr7 59000000 59500000 + ALR/Alpha Satellite centr 500000
chr8 44500000 45000000 + ALR/Alpha Satellite centr 500000
chr12 35000000 35500000 + ALR/Alpha Satellite centr 500000

D’après la table d'annotation, les éléments répétés les plus longs sont donc les centromères, faisant tous exactement 500 000 paires de base. Quelle coïncidence ! En fait, à l'heure d’écriture de cet article, les centromères du génome humain ne sont toujours pas assemblés... Parce que figurez-vous qu'assembler 23 ou 24 régions d'environ 500 kb très hautement répétées, ce n'est pas de la tarte ! En attendant, les centromères sont donc annotés avec une longueur estimée arbitraire. Mais avec le rapide développement des technologies de séquençage de fragments longs, il est possible que les centromères humains soient assemblés prochainement. Les plus longs reads séquencés par la technologie Nanopore se rapprochent de la méga-base !

Notre génome est en tout cas constitué par environ :

sum(repeats$width) / genome_length

49,4% d'éléments répétés ! Sont-ils homogènement répartis entre les chromosomes ? C'est ce que je vous propose de découvrir ensuite.

Distributions des éléments répétés entre chromosomes

Tout d'abord, souhaitant mettre en évidence les trois plus grandes catégories d'éléments répétés (LINE, SINE et LTR), je crée une nouvelle colonne  via un

mutate()
  et un
if_else()
 . Je regroupe ensuite le tableau par chromosome (
group_by()
 ) et par classe et somme les largeurs d'éléments répétés (
mutate(sum(width))
 ). Je joins le tableau à celui contenant la longueur des chromosomes (
left_join()
 ) pour pouvoir calculer la fraction de chaque chromosome contenant des éléments répétés (le second
mutate()
 ). J'en profite pour réorienter les levels de factors pour ordonner les différentes colonnes dans la figure. Et enfin j'envoie les données dans ggplot2, en ajustant tout un tas de micro-détails pour avoir une figure exactement comme j'aime :

svglite("plots/ERparChrom.svg", width = 5, height = 3)
repeats %>%
    mutate(simple_class = if_else(
        !(class %in% c("SINE", "LINE", "LTR")),
        "other",
        as.character(class)
    )) %>%
    group_by(chr, simple_class) %>%
    summarize(cum_size = sum(width)) %>%
    left_join(chr_length, by = c("chr" = "seqnames")) %>%
    ungroup %>%
    mutate(
        frac_repeat = cum_size/length,
        simple_class = factor(simple_class, levels = c("other", "LTR","SINE", "LINE")),
        chr = factor(chr, levels = standard_chromosomes)
    ) %>%
    ggplot(aes(x = chr, y = frac_repeat, fill = simple_class)) +
    geom_bar(stat = "identity") +
    theme(axis.text.x = element_text(angle = 90, hjust = 0, vjust = 0.5)) +
    scale_y_continuous(labels = scales::percent) +
    scale_fill_manual(values = c("gray", viridis(3, begin = 0.25, end = 0.9))) +
    labs(x = NULL, y = "% du chromosome", title = "Contenu répété de chaque chromosome", fill = "Classe") +
    background_grid(major = "xy", minor = "none")
dev.off()

alt-text Figure 2 : Contenu en éléments répétés de chaque chromosome.

En première approximation, il semble que chaque autosome ait un contenu en éléments répétés à peu près équivalent, oscillant entre environ 42% pour le chromosome 22, et 59% pour le chromosome 19. Il est amusant de comparer cette figure avec celle du contenu en gènes de chaque chromosome générée la dernière fois. Ainsi le chromosome 19 est à la fois l'autosome le plus riche en gènes protéiques et le plus riche en éléments répétés ! L'énigmatique chromosome 13 est relativement pauvre en éléments répétés, et en même temps pauvre en gènes. Les chromosomes sexuels font ici leur malin, avec le chromosome X ayant le plus fort taux en éléments répétés (62%), et le chromosome Y le plus faible (28%). Étonnamment (en tout cas pour moi), notre chromosome Y est donc ni riche en pseudogènes, ni riche en éléments répétés, il est juste... petit.

Les familles d'éléments répétés

Après avoir détaillé les classes d'éléments répétés, jetons un œil aux niveaux de classifications suivants, familles et sous-familles :

group_by(repeats, class) %>%
    summarise(n_family = length(unique(family)), n_subfamily = length(unique(name)), n_element = n()) %>%
    arrange(desc(n_family), desc(n_subfamily)) %>%
    xtable() %>%
    print(type = "html", include.rownames = FALSE)

class n_family n_subfamily n_element
DNA 16 226 479941
LINE 7 171 1516226
LTR 6 567 709475
SINE 6 60 1779233
Satellite 4 22 7018
Simple_repeat 1 14162 678663
Unknown 1 71 5531
tRNA 1 62 1777
snRNA 1 12 4285
Retroposon 1 6 5397
scRNA 1 5 1334
Low_complexity 1 4 98618
rRNA 1 3 1751
RC 1 3 1754
RNA 1 1 666
srpRNA 1 1 1595

Nous allons essayer de représenter graphiquement cette diversité, en affichant des diagrammes en barres d'effectif de chaque famille de répétés. Je vais colorier les barres par le nombre de sous-familles pour chaque famille. Les effectifs variant énormément, je suis contraint d'utiliser une échelle logarithmique. J'ai alors été surpris de découvrir que pour l'instant,

coord_flip()
  et
annotation_logticks()
  sont mutuellement exclusif !

Je commence par préparer les données :

effectif_table <- group_by(repeats, class, family) %>%
    summarise(diff_name = length(unique(name)), size = n()) %>%
    ungroup()

Générons ensuite un panneau de figure par classe d'éléments répétés possédant de multiples sous-familles (classes Satellite, LTR, LINE, SINE et DNA). J'utilise pour cela la fonction

map()
  du package purrr, une variante de
lapply()
 , en définissant une fonction anonyme via les notations quelque peu ésotériques
~
  et
.x
 .

multiFamily <- c("Satellite", "LTR", "LINE", "SINE", "DNA")

myFamilyPlots <- map(
    multiFamily,
    ~filter(effectif_table, class == .x) %>%
        arrange(size) %>%
        mutate(family = factor(family, levels = family)) %>%
        mutate(text_dark = if_else(diff_name > 50, TRUE, FALSE)) %>%
        ggplot(aes(x = family, y = size, fill = diff_name)) +
        geom_bar(stat = "identity") +
        geom_text(aes(label = size, color = text_dark), y = 1, hjust = 0) +
        scale_fill_viridis(limits=c(0, 100),  oob = scales::squish) +
        scale_color_manual(values = c("grey80", "black"), guide = FALSE) +
        scale_y_log10() +
        coord_flip(ylim = c(10, 1e7)) +
        background_grid(major = "xy", minor = "none") +
        labs(x = NULL, y = NULL, title = .x) +
        theme(legend.position = "none")
)
names(myFamilyPlots) <- multiFamily
myFamilyPlots$DNA <- myFamilyPlots$DNA + labs(y = "Nombre d'éléments")

Générons ensuite le même type de panneau pour toutes les classes d'éléments ayant une seule famille :

plot_other <- filter(effectif_table, !(class %in% c("Satellite", "LTR", "LINE", "SINE", "DNA"))) %>%
    arrange(size) %>%
    mutate(family = factor(family, levels = family)) %>%
    ggplot(aes(x = family, y = size, fill = diff_name)) +
    geom_bar(stat = "identity") +
    geom_text(aes(label = size), y = 1, hjust = 0, stat = "count") +
    scale_fill_viridis(limits=c(0, 100),  oob = scales::squish) +
    scale_y_log10() +
    coord_flip(ylim = c(10, 1e7)) +
    background_grid(major = "xy", minor = "none") +
    labs(x = NULL, y = NULL, title = "Autres", fill = "Nombre de\nsous-familles") +
    theme(legend.position = "bottom")

Je récupère la légende pour l'afficher à part, à l'aide d'une fonction de cowplot :

myLegend <- get_legend(plot_other)
plot_other <- plot_other + theme(legend.position = "none")

J'utilise un peu de magie noire pour homogénéiser les marges de mes différents panneaux et gérer l'alignement vertical. Ne me demandez pas d'expliquer, j'ai juste copié-collé un bout de code depuis internet.

myFamilyPlots <- map(myFamilyPlots, ggplotGrob)
plot_other <- ggplotGrob(plot_other)

myFamilyPlots.widths <- map(myFamilyPlots, ~.x$widths[1:3])
plot_other.widths <- plot_other$widths[1:3]

max.widths <- grid::unit.pmax(
    plot_other.widths,
    do.call(grid::unit.pmax, myFamilyPlots.widths)
)

plot_other$widths[1:3] <- max.widths
myFamilyPlots <- map(myFamilyPlots, function(x) {
    x$widths[1:3] <- max.widths
    return(x)
})

Enfin, j'arrange les différents panneaux, et j'exporte la figure dans un .SVG :

svglite("plots/familleER.svg", width = 10, height = 7)
ggdraw() +
    draw_text("Les familles\nd'éléments répétés", x = 1/6, y = 0.95, size = 18) +
    draw_plot(myLegend               , x = 0.0, y = 0.75, w = 1/3, h = 0.2 ) +
    draw_plot(myFamilyPlots$DNA      , x = 0.0, y = 0.0 , w = 1/3, h = 0.8 ) +
    draw_plot(myFamilyPlots$LINE     , x = 1/3, y = 0.62, w = 1/3, h = 0.38) +
    draw_plot(myFamilyPlots$SINE     , x = 1/3, y = 0.31, w = 1/3, h = 0.31) +
    draw_plot(myFamilyPlots$LTR      , x = 1/3, y = 0.0 , w = 1/3, h = 0.31) +
    draw_plot(myFamilyPlots$LTR      , x = 1/3, y = 0.0 , w = 1/3, h = 0.31) +
    draw_plot(myFamilyPlots$Satellite, x = 2/3, y = 0.7 , w = 1/3, h = 0.3 ) +
    draw_plot(plot_other             , x = 2/3, y = 0.0 , w = 1/3, h = 0.7 )
dev.off()

alt-text Figure 3 : Les familles d'éléments répétés.

Reconnaissez-vous des noms familiers ? Par exemple, nous avons 2 118 insertions d'éléments PiggyBac dans notre génome. Ce transposon est à l'origine d'une méthode de clonage de gènes dans des plasmides assez populaire.

Ce que je remarque surtout, c'est que des ARN de transferts (tRNA) se baladent dans la catégorie des SINE. MAIS POURQUOI ! POURQUOI ON NE PEUT PAS AVOIR DES CLASSIFICATIONS COHÉRENTES EN BIOINFORMATIQUE !

Hum hum, pardon.

En fait tout va bien : la classification est strictement non chevauchante au niveau des sous-familles : les tRNA de classe SINE ne contiennent pas les même sous-familles de tRNA que les tRNA de classe tRNA. Oui, je sais, ce n'est pas très clair.  Mais il se trouve qu'un certain nombre de SINE dérivent de séquences d'ARN de transferts. Je pense donc que cette classification est tout à fait justifiée.

Je pourrais me perdre ensuite dans les détails des différentes sous-familles d'élément répétés, mais je préfère laisser les plus curieux d'entre vous se perdre dans ce fascinant tableau, et nous raconter leurs trouvailles en commentaires. Et c'est donc sur cette abrupte conclusion que je conclus.

 

Un grand merci aux super relecteurs et relectrice : Clémence, eorn, Mathurin et Max, sans qui cet article serait beaucoup moins bien.

L’article Les éléments répétés du génome humain : aperçu rapide avec R et le tidyverse est apparu en premier sur bioinfo-fr.net.

LaTeX : la mise en forme du texte et des paragraphes

$
0
0

Bon maintenant qu'on sait compiler et insérer des flottants, un truc critique à voir c'est comment mettre en forme : mettre en italique, en emphase, en gras, souligner, faire des listes à puces, insérer du code…

Préparez-vous, ça risque d'être un peu long :). (Et par un peu long je veux aussi dire un peu enquiquinant, mais il faut y passer…)

Mise en forme du texte

Faire joujou avec la police

Par défaut, LaTeX met les caractères en forme droite en minuscule. On peut changer cela 🙂

Mise en emphase

La commande 

\emph{texte}
  permet de mettre de l'emphase sur le texte entre accolades. Dans un contexte « normal » c'est-à-dire droit, le texte sera mis en italique, et dans un contexte italique le texte sera mis en forme droite. C'est très pratique pour mettre en évidence du mot dans un texte.

Testez 🙂

Italique et soulignage

Pour mettre en italique le texte, rien de plus simple, il suffit d'utiliser cette commande : 

\textit{texte à mettre en italique}
 .

Si l'on souhaite, on peut également utiliser l'environnement

itshape
  de la façon suivante :

\begin{itshape}
    \lipsum[1]
\end{itshape}

L'utilisation de l'environnement est assez rare, mais il faut l'avoir vu, ce qui est chose faite 🙂

 

Il est également possible de souligner du texte avec LaTeX avec la commande

\underline{texte à souligner}
  mais je trouve ça très moche et ce n'est pas recommandé de l'utiliser, sauf contraintes exceptionnelles.

Petites majuscules

Pour mettre les lettres en petites majuscules (capitales), par exemple pour mettre les noms de famille des auteurs dans votre bibliographie, vous pouvez utiliser la commande

\textsc
  ainsi : 
Yann \textsc{Ponty}
 .

Pour mettre les chiffres en bas de casse, vous pouvez utiliser la commande

\oldstylenums{0123456789}
 .

Lettres supérieures et inférieures

(C'est-à-dire l'équivalent des exposants et des indices en mathématiques.) Pour cela on a deux commandes très explicites :

\textsuperscript{superscript}
  et
\textsubscript{subscript}
 .

Mettre en gras

Pour changer la graisse des caractères, il suffit d'utiliser la commande

\textbf{texte à mettre en gras}
 . Il existe également un environnement, comme pour la mise en italique qui s'appelle
bfseries
 . À vous tester, mais il est encore une fois peu utilisé.

Faire joujou avec la taille

Il est possible de modifier la taille du texte :

  • {\tiny plop}
      : minuscule
  • {\scriptsize plop}
      : très très petit
  • {\footnotesize plop}
      : très petit
  • {\small plop}
      : petit
  • {\normalsize plop}
      : normale (taille définie dans le \documentclass)
  • {\large plop}
      : légèrement grande
  • {\Large plop}
      : grande
  • {\LARGE plop}
      : très grande
  • {\huge plop}
      : énorme
  • {\Huge plop}
      : gigantesque

Récapitulatif et aspects visuels

Tableau récapitulatif de la mise en forme de texte en LaTeX

Tableau récapitulatif de la mise en forme de texte en LaTeX

À titre de révision, et parce qu'il y a une commande intéressante dedans, je vous fournis le code source de ce tableau :

\begin{table}
    \centering
    \begin{tabular}{|c|c|c|}
        \hline
        \multicolumn{2}{|c|}{Commande} & \multirow{2}{*}{Rendu} \\
        \cline{1-2}
        à argument & déclarative & \\
        \hline
        $\backslash$emph\{Plop\} & \{$\backslash$em Plop \} & \emph{Plop}\\
        \hline
        $\backslash$textit\{Plop\} & \{$\backslash$it Plop \} & \textit{Plop} \\
        \hline
        $\backslash$underline\{Plop\} &  & \underline{Plop} \\
        \hline
        $\backslash$textsc\{Plop\} & \{$\backslash$sc Plop\} & \textsc{Plop} \\
        \hline
        $\backslash$textsuperscript\{Plop\} &  & \textsuperscript{Plop} \\
        \hline
        $\backslash$textsubscript\{Plop\} &  & \textsubscript{Plop} \\
        \hline
        $\backslash$textbf\{Plop\} & \{$\backslash$bf Plop \} & \textbf{Plop} \\
        \hline
        & \{$\backslash$tiny Plop\} & {\tiny Plop} \\
        \hline
        & \{$\backslash$scriptsize Plop\} & {\scriptsize Plop} \\
        \hline
        & \{$\backslash$footnotesize Plop\} & {\footnotesize Plop} \\
        \hline
        & \{$\backslash$small Plop\} & {\small Plop} \\
        \hline
        & \{$\backslash$normalsize Plop\} & {\normalsize Plop} \\
        \hline
        & \{$\backslash$large Plop\} & {\large Plop} \\
        \hline
        & \{$\backslash$Large Plop\} & {\Large Plop} \\
        \hline
        & \{$\backslash$LARGE Plop\} & {\LARGE Plop} \\
        \hline
        & \{$\backslash$huge Plop\} & {\huge Plop} \\
        \hline
        & \{$\backslash$Huge Plop\} & {\Huge Plop} \\
        \hline
    \end{tabular}
    \label{tab:plop}
\end{table}

Les guillemets

Les guillemets c'est toute une affaire… Vous avez déjà dû avoir à faire à des trolls qui vous ont dit que vous n'utilisiez pas les guillemets français quand vous le devriez etc. Heureusement, vous ne ferez plus l'erreur avec LaTeX !

Pour cela, vous allez devoir ajouter un paquet à votre préambule (avouez que ça vous manquait…) :

\usepackage[babel=true]{csquotes}

Vous signifiez ainsi au paquet

csquotes
  d'utiliser la langue que vous avez passé à
babel
  (soit en paramètre, soit dans le
documentclass
 , selon votre version de
babel
 ).

Vous pouvez alors utiliser la commande suivante :

\begin{document}

Je vais citer une phrase maudite : \enquote{J'ai perdu !} qui va automatiquement avoir les bons guillemets !

\end{document}

 

Alternativement, vous pouvez utiliser les commandes 

\og plop \fg{}
 du paquet
babel
 , je ne vous en tiendrai pas rigueur 🙂

Points de suspension

Pour écrire des points de suspension, vous pouvez soit utiliser le caractère idoine …, soit utiliser la commande 

\ldots
 .

Tirets

Il existe plusieurs types de tirets en LaTeX (dont le signe moins en mathématiques qu'on verra plus tard) :

  • -
      : le trait d'union
  • --
      : le tiret demi-cadratin (utilisé pour marquer un intervalle ou les incises)
  • ---
      : le tiret cadratin (utilisé pour marquer l'alternance des personnages dans les dialogues)

 

Mise en forme des paragraphes

Comme vous l'avez sûrement remarqué, par défaut, le texte est justifié et commence par un alinéa. Pour commencer un nouvel alinéa, il suffit de passer une ou plusieurs lignes vides (LaTeX s'en contrefiche du nombre, mais vous pas, ça peut vous aider à vous retrouver dans votre code source).

Alignement des paragraphes

Si vous souhaitez modifier l'alignement de votre texte, c'est-à-dire qu'il ne soit plus justifié, LaTeX vous propose trois environnements (avec les commandes équivalentes) :

Alignement des paragraphes en LaTeX

\begin{table}
    \centering
    \begin{tabular}{|c|c|c|}
        \hline
        Alignement & Environnement & Commande \\
        \hline
        Justifié à gauche & \texttt{flushleft} & \texttt{$\backslash$raggedleft} \\
        \hline
        Justifié à droite & \texttt{flushright} & \texttt{$\backslash$raggedright} \\
        \hline
        Centré & \texttt{center} & \texttt{$\backslash$centering} \\
        \hline
    \end{tabular}
    \label{tab:paragraphes}
\end{table}

 

Paragraphe de résumé (abstract)

Il est classique dans un

article
  (ou un
report
 ) de commencer par un résumé (un abstract dans le jargon). Pas de panique ! LaTeX a juste le bon environnement pour vous :

\begin{abstract}
    Ceci est un abstract. Oui il est très court. J'avais grave la flemme.
\end{abstract}

Ce qui donne :

Aspect du résumé (qui dépend encore une fois de babel)

Aspect du résumé (qui dépend encore une fois de babel)

Le résumé prend une page entière et est traditionnellement mis avant le début du texte.

Mise en forme de code

Il existe environ douze milliards de façon de mettre en forme du code avec LaTeX, je vais vous présenter exclusivement ma préférée :

minted
 .

Installation de minted

Pour cela il va vous falloir Python >= 2.6 parce qu'on a besoin de 

Pygments
 . Vous pouvez installer
Pygments
  de la façon suivante :

sudo easy_install Pygments

ou encore

pip install Pygments

Puis utilisez votre gestionnaire de paquets pour installer le paquet

minted
 .

Préparation de Texmaker à l'utilisation de minted

Alors en fait puisque

minted
  cause au monde extérieur (à savoir
Pygments
 ), il faut passer une option à
pdflatex
  pour que ça puisse se faire : l'option
-shell-escape
 .

Pour cela, allez dans Options, Configurer TexMaker et ajouter l'option

-shell-escape
  après la commande
pdflatex
 .

Bon, faites gaffe à ce que vous faites avec cette option parce que l'option

-shell-escape
  autorise LaTeX à exécuter des commandes arbitraires sur votre machine, du coup faites-le sur des documents en qui vous avez confiance…

Utilisation de minted

C'est très simple d'utiliser

minted
  :

\begin{minted}{python}
def fahrenheit(T_in_celsius):
    """
    Returns the temperature in degrees Fahrenheit.
    """
    return (T_in_celsius * 9 / 5) + 32
\end{minted}

Maintenant, compilez et vous obtiendrez :

Et hop ! De la coloration syntaxique.

Si vous voulez insérer une seule ligne de code, vous pouvez utiliser la commande suivante :

\mint{python}|import this|

Ce qui donne exactement le même résultat qu'avec l'environnement :

Toujours de la coloration syntaxique !

Si vous voulez inliner du code, la commande appropriée est \mintinline qui s'utilise de la façon suivante :

Lalala j'écris du texte et puis pouf je mets du code python \mintinline{python}{import this} lalala.

Ce qui donne :

Du code inliné \o/

Et si, comble du luxe, vous voulez insérer un fichier entier de code (sait-on jamais pour un corrigé de TD ça peut servir…), vous pouvez utiliser la commande suivante :

\inputminted{python}{exo1.py}

Les styles minted

Il existe une foule de styles minted que vous pouvez utiliser pour colorer votre code. Je vous suggère de consulter http://pygments.org/demo/ d'y envoyer un bout de code et de tester les différents styles qui vous seront proposé après avoir cliqué sur Highlight!

Pour voir la liste des styles disponible (ainsi qu'une brève description), vous pouvez taper dans votre terminal la commande suivante :

pygmentize -L styles

 

Une fois que vous avez trouvé le style qui vous convient, vous pouvez le sélectionner dans le préambule de la manière suivante :

\usemintedstyle[langages]{nom_du_style}

Vous pouvez mettre dans votre préambule autant de

\usemintedstyle
  que vous voulez pour autant de langage que vous souhaitez.

Vous pouvez aussi mettre le style que vous voulez en passant cela en option à l'environnement :

\begin{minted}[style=manni]{python}
def fahrenheit(T_in_celsius):
    """
    Returns the temperature in degrees Fahrenheit.
    """
    return (T_in_celsius * 9 / 5) + 32
\end{minted}

Ce qui donne un style légèrement différent :

Utilisation du thème manni alors que le thème vim a été utilisé pour l'ensemble du document.

Encore un peu de flottants…

minted
  donne accès à l'environnement
listing
  qui mets le code dans un environnement flottant, ce qui permet d'avoir une légende (via
\caption
 ) et une référence (via
\label
 ), tout comme pour les tables ou les figures.

Voici comment procéder :

\begin{listing}
\begin{minted}{python}
def fahrenheit(T_in_celsius):
    """
    Returns the temperature in degrees Fahrenheit.
    """
    return (T_in_celsius * 9 / 5) + 32
\end{minted}
\caption{Exemple de code flottant}
\label{lst:exemple}
\end{listing}

Ce qui donne :

Et tada, un environnement flottant pour le code !!

Bon, et vous savez ce qui vient :). La commande magique pour obtenir la liste des listings :

\listoflistings

 

Ce qui donne :

Horreur et damnation, c'est en anglais, babel nous a trahis !

Dammit.

Heureusement, c'est réglable. Pour cela il va falloir bidouiller un peu, et pour faire ça il faut charger le paquet

minted
  avec une option et customiser ça comme ça (dans le préambule) :

\usepackage[newfloat]{minted}
\SetupFloatingEnvironment{listing}{listname=Liste des Codes}%

Et après compilation, on obtient :

Ouf, nous voilà sauvés 🙂

Pour changer le nom de la légende en « Code » au lieu de « Listing » (parce que je préfère et que c'est plus explicite), il suffit d'utiliser la même méthode :

\SetupFloatingEnvironment{listing}{name=Code}

Et on obtient après compilation :

Et voilà, on a customisé le nom de l'environnement !

Numéroter les lignes

Il est possible de numéroter les lignes en utilisant l'option

linenos
  (soyez attentif, il y a un truc coolish ^^) :

\begin{listing}[h]
\begin{minted}[linenos]{python}
def f(x):
    return x**2
\end{minted}
\
\begin{minted}[linenos]{ruby}
def func
    puts "message"
end
\end{minted}

\begin{minted}[linenos, firstnumber=last]{python}
def g(x):
    return 2*x
\end{minted}
\end{listing}

Voyez :

Vous voyez à quel point c'est trop cool LaTeX ?

Des maths dans les commentaires ? Sure why not !

Je sais que j'ai pas encore parlé de maths en LaTeX, donc revenez à cette sous-section plus tard 🙂 ! L'important c'est que vous ayez une référence de comment faire 🙂 :

\begin{minted}[mathescape]{python}
def sum_from_one_to(n):
    # Returns $\sum_{i=1}^{n}i$
    r = range(1, n + 1)

return sum(r)
\end{minted}

Ce qui donne :

Une jolie somme ! \o/

 

Bon, je pourrais continuer longtemps à vous parler des trucs coolish que peut faire

minted
 , mais l'article est déjà long et il reste d'autres trucs cool à voir, on y reviendra peut être plus tard !

Mise en forme d'algorithmes

Il existe environ douze milliards de façon de formater des algorithmes avec LaTeX, je vais vous présenter mon préféré. Pour commencer, allons mettre ce qu'il faut dans le préambule :

\usepackage[linesnumbered,lined,boxed,commentsnumbered]{algorithm2e}
\SetAlgorithmName{Algorithme}{Liste des algorithmes}

L'utilisation de base est assez simple, comme vous pouvez le constater avec cet exemple tiré de la documentation :

\begin{algorithm}
    \SetAlgoLined
    \KwData{Cet article}
    \KwResult{Comment faire du formattage de texte et de paragraphe avec \LaTeX}
    initialisation\;
    \While{pas à la fin du document}{
        lire paragraphe courant\;
        \eIf{comprendre}{
            aller au paragraphe suivant\;
            paragraphe courant devient celui là\;
        }{
            retourner au début du paragraphe\;
        }
    }
    \caption{Comment écrire un algorithme}
    \label{alg:algo-comprendre}
\end{algorithm}

Ce qui donne :

Et un premier algo tout beau tout propre

On peut faire des choses plus complexes comme celle-ci :

\begin{algorithm}

    \KwData{Les données\\
    qui peuvent être mises sur plusieurs lignes…\\
    \Indp et indentées
    }
    \KwResult{Même chose pour les résultats}
    %-% Définition d'une donnée custom
    \SetKwData{data1}{data1}
    %-% Définition d'une fonction custom
    \SetKwFunction{function1}{function1}
    \tcc{Un commentaire pour dire qu'on commence le code}
    \If(\tcc*[h]{un simple if mais avec un commentaire}){ceci est vrai}{
        on fait ça, sinon rien\;
        \tcc{On va introduire d'autres if pour montrer que c'est possible}
        \eIf{on est d'accord}{
            on fait ça\;
        }{
            sinon, on fait des choses plus compliquées\;
            \uIf{cette première proposition est vraie}{
                on fait ça\;
            }
            \uElseIf{cette autre proposition est vrai}{
                on fait cela \tcc*[r]{else if}
            }
            \Else{
                dans les autres cas on fait ça \tcc*[r]{else}
            }
        }
    }
    \tcc{maintenant les boucles}
    \For{\forcond}{
        une boucle for\;
    }
    \While{i < n}{
        une boucle while contenant un repeat -- until\;
        \Repeat{condition d'arrêt}{
            faire cette chose\;
        }
    }
    \tcc{et on termine}
    \Return 0
\end{algorithm}

Ce qui donne :

Un bel algorithme avec tout plein de keywords

 

Bon bah je pense que c'est tout pour cette fois, on se retrouve bientôt !

 

Un grand merci à mes relecteur.rice.s : MaxNisaea, et Pau Ssrls !

L’article LaTeX : la mise en forme du texte et des paragraphes est apparu en premier sur bioinfo-fr.net.

Petite introduction sur... les éléments répétés

$
0
0

Suite à l'excellent billet proposé par un autre auteur du blog, de nombreuses questions sur ce que sont les éléments répétés sont restées en suspens. Après le séquençage du génome humain dans les années 2000, de nombreux chercheurs ont constaté que la majeure partie du génome n'était pas composée de gènes, mais d'ADN à l'époque qualifié de poubelle. Celui-ci contenant un grand nombre de motifs étranges qui ne semblaient alors pas avoir de sens. Dans ces régions, nous trouvons de nombreuses séquences fort mystérieuses, parmi lesquelles figurent les éléments répétés. Mais à quoi servent donc ces séquences?

A faire les bandits bien sur!

Afin de mettre de l'ordre dans tous ces concepts, je vous propose ici de revenir sur quelques points de définitions clefs de ce domaine et voir quelques questions scientifiques qui sont posées avec.

Les définitions proposées n'auront pas pour but d'être exhaustives ni complètes, seulement de vous donner une vision précise de chaque terme du domaine.

Alors c'est parti, on ressort les cours de biologie, place aux définitions informelles !

Les concepts de bases : un élément répété c'est quoi ?

Pour les plus étrangers au concept d'éléments répétés, voici les notions qu'il faut absolument connaître pour comprendre une conversation sur ce sujet.

Un élément répété ?

Les éléments répétés sont des séquences dans l'ADN qu'on retrouve en de multiples copies dans le génome. Parmi ces éléments plusieurs familles existent, dont les éléments transposables. On retrouve aussi les répétitions simples très présentes près des centromères entre autres, mais bien d'autres. Autrement dit, quand une personne parlera d'élément répété, le mot élément transposable sera en permanence inclus dans sa réflexion comme un des cas possible.

Un élément transposable ?

Les éléments transposables sont des séquences présentes dans l'ADN qui ont la propriété de se déplacer d’un point à un autre du génome. On distingue principalement deux types d'éléments transposables : ceux se déplaçant à l'aide d'une matrice d'ADN sur un système de "couper-coller" (par transposition) et ceux utilisant une matrice d'ARN qui se déplacent sur un système de "copier-coller" (par rétrotransposition). Les éléments fonctionnant par transposition seront déplacés dans le génome sans duplication là où ceux fonctionnant par rétrotranspotition vont pouvoir se dupliquer en de nombreuses copies. Les transposons seront donc à priori beaucoup moins invasifs dans le génome que les rétrotransposons, et dans un monde idéal où il ne serait présent dans le génome qu'une copie chacun, ce ne serait pas des éléments répétés. Cependant, par les voies de la recombinaison de l'ADN, un transposon a la possibilité de se propager.

Pour la petite histoire, les éléments répétés ont à l'origine été découverts par Barbara McClintock suite à un problème de croisement génétique qui ne semblait pas fonctionner selon les lois connues. Elle a alors supposé qu'un élément dans le génome était mobile pour expliquer comment certaines variétés existaient, mettant ainsi en évidence... un élément transposable! Parmi l'ensemble des éléments transposables et répétés présents dans un génome à un instant t, fort peu sont actifs. En effet, la plupart d'entre eux vont être réprimés par divers mécanismes. Par exemple, certains seront très présents dans des régions inactives de l'ADN et donc, pas exprimés. Dans les faits, bien des transposons sont peu actifs chez l'homme mais très actifs chez le maïs... bref chaque génome a ses spécificités avec les éléments répétés!

CC0 Creative Commons

La notion de consensus :

Pour qu'une séquence du génome puisse être assignée à une catégorie d'élément répété, il faut que cette séquence soit similaire à la séquence consensus de cet élément. Que veut dire cette définition ? Elle sous-entend que le génome évolue en permanence au cours du temps et donc que plusieurs copies d'une même séquence auront pu subir quelques modifications, mais que toutes ses copies ont un motif caractéristique commun (consensus). On suppose que ce consensus est la première version de l’élément répété qui s'est inséré dans le génome et à partir duquel des duplications et autres événements ont pu avoir lieu.

Par exemple si je cherche l'ensemble des éléments Gypsy (une famille d’élément connue) présents chez la drosophile, je vais chercher dans mon génome l'ensemble des régions possédant une séquence similaire au consensus de cet élément. Plus celle-ci aura subi des mutations, plus elle aura divergé de sa séquence d'origine. On définit alors la divergence comme le taux de modification d'un élément donné par rapport à son consensus d'origine. L'ensemble de ces définitions repose sur les bases de la théorie de l'évolution moléculaire.

Pour les plus courageux d'entre vous, il existe aussi une version youtube (en anglais) de ces définitions, ici ou ici ou bien même !

Les 'grandes' familles d'éléments répétés

Plutôt que de faire la liste exhaustive de chaque famille d'éléments répétés, je vous propose de continuer d'avoir une idée générale de ce que sont quelques familles les plus connues d'éléments répétés.

Répétition simple : Une répétition simple est une petite séquence de quelques nucléotides répétés en de nombreuses copies les unes à la suite des autres. Le motif de la répétition peut être extrêmement court (entre 2 et 10 nucléotides en moyenne). Ce type de répétition est assez facile à identifier car très présent dans les régions centromériques et télomériques (respectivement au milieu, et aux extrémités des chromosomes). Les petits (CAATG)n ou autre (ATn) qui rendent l'assemblage des génomes difficile, c'est eux. Ils sont souvent dû à des biais de duplication d'ADN ou à des erreurs de recombinaisons rajoutant une répétition de plus à une région. Toutes proportions gardées, ce sont des éléments qu'on va trouver chez tous les génomes eucaryotes (j'ai pu en identifier dans Ostreococus tauri, un eucaryote dont le génome est tout petit !). Pour vous donner une petite idée de la taille de ces éléments dans un génome, j'ai pris un élément au hasard et fait un histogramme de sa taille d'après les données présentes dans Repeatmasker pour l'homme. On voit assez vite que beaucoup d'entre eux peuvent être répétés en grande quantité les uns à la suite des autres! Pour plus de statistiques, c'est par ici.

Distribution de la taille de l'élément (GTTT)n chez l'homme.

LTR :  Ce doux acronyme signifie long terminal repeat. Ces éléments font partie de la famille des retrotransposons. En Master de bioinformatique peuvent être évoqués les éléments gypsy et copia appartenant à cette famille. Pour donner une image facile, ce type d'éléments marche comme l'ADN du virus du SIDA, transcrit à partir d'une matrice d'ARN pour s'insérer dans un génome. La différence  majeure qu'ont les LTR avec les rétrovirus est qu'ils ne peuvent pas se propager dans d'autres cellules.

SINE et LINE : Ces deux grandes familles d'éléments sont les acronymes respectifs de Short INterspersed Element et de Long INterspersed Elements. Ce sont deux familles connues de rétrotransposons qui ne sont pas des LTR (différent mécanisme d'insertion dans le génome, mais surtout de séquence). Les SINE sont des petites séquences transcrites (moins de 1000 paires de base), les LINE eux auront un mécanisme similaire mais auront des grandes séquences (>5000 pb). N'étant pas un expert des mécanismes biologiques de ces séquences, je vous invite à aller ici ou ici si vous voulez en savoir plus. On distingue beaucoup de familles connues, chez les SINE on retrouvera les alu et les MIR. Du côté des LINE, on citera souvent les L1 et L2, très présents dans le génome de la souris.

Quelques outils importants à connaitre

RepeatMasker : Ce logiciel est le gold standard du domaine pour l'annotation des éléments répétés sur un génome. Il permet de masquer les régions contenant des éléments répétés sur un génome et de fournir la position et l'annotation de l'ensemble des éléments répétés sur ce génome. Pour pouvoir fonctionner celui-ci a besoin d'une base de données d'éléments répétés caractéristiques trouvable sur le site web GIRI. Identifiant les éléments présents dans la base de données fournie, ce logiciel sert avant toute chose à retrouver des éléments répétés déjà connus. Si vous souhaitez identifier de nouveaux éléments répétés car peu caractérisés jusqu'à présent, il faudra vous reposer sur d'autres outils pour faire de l'identification de novo (voir RepeatModeler). Il est important de noter que la plupart des éléments identifiés par ce type de logiciel ne sont pas fonctionnels, la plupart seront des fragments de consensus identifiés ainsi que des séquences qui auront trop muté pour être fonctionnelles.

RepeatModeler :  Cet outil sert à identifier des éléments répétés nouveaux dans une espèce. Il est très proche de RepeatMakser car développé par les mêmes équipes. Il permet notamment de détecter des éléments qui n'existeraient pas dans d'autres espèces, ou auraient des éléments déjà existant chez d'autres espèces mais qui ont beaucoup divergé des consensus déjà établis. Pour fonctionner, celui-ci se base sur trois outils déjà existant qui sont RepeatScout , RECON et TRF.

UCSC table browser : Site web connu pour ses bases de données,  ici il nous servira à récupérer l'ensemble des annotations des éléments répétés d'un génome de référence. Pour savoir comment faire, je vous conseille de suivre le tutoriel de cet article.

La suite REPET : Si votre espèce est peu référencée ou que vous travaillez sur des génomes de plante, l'équipe URGI de l'INRA de Versailles a développé cette solution alternative et efficace. Elle repose sur deux outils qui sont TEdenovo pour l'identification de nouveaux éléments répétés et TEannot pour annoter le génome à partir des identifications de-novo couplé à RepeatMasker. Le TEannot se servant de ce logiciel en routine-préliminaire.

Quelques questions 'chaudes' du domaine

Voici quelques réflexions que j'ai pu voir en conférence et formation autour des questions qui se posent sur les éléments répétés. Que font les chercheurs actuellement dans le domaine ? Quelles sont certaines des questions qu'ils se posent ? Quels sont leurs objectifs ? Voici un petit aperçu de certains challenges à venir.

Obtenir une annotation fine des éléments répétés à la paire de base près : Peut-on annoter automatiquement avec précision un génome donné à la paire de base près ? En dépit de toutes les années de recherche et l'amélioration des techniques de séquençage haut débit, avoir une annotation à la paire de base près d'un élément répété est parfois un challenge technique demandant une expertise toujours manuelle. De nombreuses avancées sont en cours de ce côté-là et des équipes se regroupent pour trouver des solutions face à ce problème majeur (ici, ou la, voir même ici par exemple) .

L'impact de ces régions sur l'organisation des génomes : Quel est l'impact évolutif des éléments répétés dans les génomes ? Autant le dire, il n'est pas possible de répondre à cette question actuellement. Dans l'état de l'art actuel, on sait que chez les eucaryotes, de nombreux enhancer dérivent des SINE. On sait également que certaines familles d'éléments répétés auront tendance à se localiser dans les régions actives des génomes là où d'autre iront dans les régions inactives. Des chercheurs ont également étudié le lien subtil entre taille des génomes et quantité d'éléments répétés. D'autres ont également retracé l'histoire d'une famille d'éléments répétés avec précision. On sait aussi que tout un tas d'éléments répétés sont impliqués dans des mécanismes de régulation de génome (exemple la, ici, ou la). De là à dire le rôle exact des éléments répétés : les réponses sont multiples et encore à compléter, l'objectif de comprendre la fonction de chaque région dans un génome reste encore en cours.

L'histoire des génomes à travers les éléments répétés : Comment de nouveaux éléments répétés apparaissent dans un génome ? Quels sont les mécanismes derrière l'évolution de ces séquences ? Des chercheurs pensent que les éléments répétés ont potentiellement une origine exogène (provenant d'autres espèces), sans que cela reste prouvé  (autre exemple).  Aujourd'hui, tout cela reste un mystère et un champ de recherche ouvert.

Conclusions

Et voilà, ce sera tout pour aujourd'hui, j'espère vous avoir permis de mieux resituer certains concepts clefs pour commencer à vous guider vers cette voie merveilleuse. Un grand merci aux relecteurs, humble deuxième main de l'article le rendant plus complet, accessible : Annie Lebreton , Guillaume Devailly et Nolwenn.

Un humble merci à Chopopope pour son illustration. Merci également à notre bon administrateur de la semaine, toujours là pour aider à mettre en publication les articles, notre bon Yoann M!

L’article Petite introduction sur... les éléments répétés est apparu en premier sur bioinfo-fr.net.

Jouer avec l'API de KEGG

$
0
0

Logo de la Kyoto Encyclopedia of Genes and Genomes (KEGG), propriété intellectuelle de Kanehisa Laboratories.

Il n'est pas rare que nous ayons un jour besoin de récupérer des informations de la base de données KEGG (Kyoto Encyclopedia of Genes and Genomes). Cette base de données fournit un nombre conséquent d'informations sur les génomes et les réseaux de gènes mais également sur les voies métaboliques ou les maladies. Dans ces cas là, bien souvent, nous passons directement par le site internet à l'adresse http://www.kegg.jp/, puis nous effectuons une recherche via le moteur prévu à cet effet, et enfin nous pouvons télécharger les informations souhaitées. Mais cette façon de procéder n'est pas des plus pratiques si l'on souhaite mettre un système de pipeline dans nos laboratoires, aussi KEGG met à notre disposition une API reposant sur REST. Ça veut dire qu'il sera possible, à partir de la construction d'URL spécifiques, prévues à cet effet, d'interroger et de récupérer des informations sur différentes données d'intérêts, sans passer par les formulaires existants sur le site de KEGG. Chaque type de requête est définie comme étant une opération. Voyons comment on peut s'en sortir pour nos projets !

Attention : l'accès au site internet et à ses données est entièrement gratuit mais l'accès au serveur FTP est payant, pour permettre de financer les serveurs.

Principe de fonctionnement

Dans l'API REST de KEGG, il existe 7 opérations interfacées sur les 15 tables (plus une en développement au moment où j'écris ces lignes) et donc requérables depuis l'extérieur grâce à une suite d'arguments.

Pour chaque opération il va être possible de récupérer différents types d'informations sur les banques de données :

  1. info : affiche les informations sur la version de la base de données et également les bases de données auxquelles celle-ci est en interaction le cas échéant. Exemple : http://rest.kegg.jp/info/pathway
  2. list : fournit la liste des identifiants de la base de données ainsi que la définition associée. Exemple : http://rest.kegg.jp/list/genome
  3. find : trouve les entrées qui correspondent au mot clé recherché ou à d'autres types de données. Exemple : http://rest.kegg.jp/find/disease/melanoma
  4. get : récupère les informations dans la base de données à partir de l'identifiant fournit. Exemple : http://rest.kegg.jp/get/ds:H00038
  5. conv : convertit les identifiants KEGG en leurs identifiants provenant d'autres banques de données, comme NCBI Gene. Exemple : http://rest.kegg.jp/conv/hsa/ncbi-geneid (vous pouvez aussi les inverser !)
  6. link : récupère les entrées liées entre elles en utilisant les références croisées des banques de données. Exemple : http://rest.kegg.jp/link/hsa/ds:H00038
  7. ddi : récupèrer la liste des contre-indications et précautions à prendre dans le cadre d'interaction médicamenteuse. Exemple : http://rest.kegg.jp/ddi/D00124

Pour en savoir plus et aller plus loin, n'hésitez pas à aller faire un tour sur la documentation officielle. Vous y trouverez également plusieurs exemples d'utilisation.

Comme vous venez de le voir dans les exemples ci-dessus, il est possible de récupérer beaucoup d'informations en utilisant uniquement son navigateur internet préféré, c'est souvent un bon moyen pour trouver rapidement les données utiles. Cependant, comme vous pouvez le constater vous-même, il va très rapidement exister une limite à ne passer que par le navigateur.

Si vous souhaitez affiner au fur et à mesure votre recherche ou même l'approfondir en allant plus loin dans les données à récupérer, vous risquez de passer des heures à faire de nombreux copier-coller et retours arrière, sans compter le fait que vous voudrez certainement pouvoir récupérer les résultats sur votre ordinateur. Pour le coup, vous aurez donc le choix :

  • soit de garder votre navigateur ouvert avec un éditeur de texte dans lequel vous recopierez les informations qui vous intéressent
  • soit de faire un script pour effectuer une recherche plus efficace une fois que vous aurez schématisé votre stratégie d'acquisition de données !

Comme nous sommes sur un blog de bioinformaticiens, vous vous doutez bien que je ne peux pas m'empêcher de vous montrer un ou deux exemples de scripts ! Prêts ? C'est parti !

Récupérer la liste des gènes impliqués dans le mélanome chez l'Homme

Pour cet exemple, j'utiliserai Python (version 3++) avec la bibliothèque requests (dans les bibliothèques de base, appuyée par la documentation officielle), prévue pour les requêtes REST, afin de récupérer les données et les manipuler à ma guise. Notez que si vous ne précisez pas quel est le format de sortie que vous souhaitez récupérer, celui-ci sera par défaut en texte plat !

Étape 1 : récupérer l'identifiant du mélanome dans la base de données Disease

Pour récupérer l'identifiant du mélanome, dans la base de données Disease, j'utilise l'opération find (souvenez-vous, l'API REST de KEGG repose sur 7 opérations) :

import requests
# appel de l'opération find sur la table disease avec le mot clé melanoma
r = requests.get('http://rest.kegg.jp/find/disease/melanoma')
r.text
'ds:H00038\tMalignant melanoma\n'

Le résultat de sortie final est au format texte tabulé. Nous allons donc pouvoir récupérer facilement la liste des clés (ici une seule clé), en utilisant seulement les built in de Python.

'''
    récupérer les lignes sous forme de liste
    normalement pour une seule ligne ce n'est pas nécessaire mais j'aime bien être généraliste
'''
lines = r.text.splitlines()
disease_id = []
for id in lines:
    # découper en liste sur la tabulation
    # ne récupérer que le premier élément
    disease_id.append(id.split('\t')[0])

print(disease_id)
['ds:H00038']

Étape 2 : récupérer les identifiants des gènes humains impliqués dans le mélanome

À partir de la liste des identifiants récupérés précédemment, nous allons pouvoir récupérer la liste des identifiants des gènes humains impliqués dans le mélanome, grâce à l'opération link.

'''
    ici nous n'avons qu'un seul identifiant, mais il est possible d'en utiliser
    plusieurs d'un coup en séparant chaque identifiant avec le signe +
'''
diseases = '+'.join(disease_id)
r = requests.get('http://rest.kegg.jp/link/hsa/' + diseases)
genes = r.text.splitlines()
genes_id = []
for g in genes:
    # ne récupérer que le second élément
    genes_id.append(g.split('\t')[1])

print(genes_id)
['hsa:673', 'hsa:4893', 'hsa:1019', 'hsa:4286', 'hsa:5728', 'hsa:1029', 'hsa:7157', 'hsa:3939', 'hsa:3945', 'hsa:3948']

Étape 3 : récupérer les informations sur les gènes humains récupérés

Maintenant que nous avons récupéré les identifiants des gènes, nous allons pouvoir retrouver la liste de nos gènes à proprement parler ! Pour cela nous utiliserons cette fois-ci la méthode list.

hsa_genes = '+'.join(genes_id)
r = requests.get('http://rest.kegg.jp/list/' + hsa_genes)

print(r.text)
hsa:673 BRAF, B-RAF1, B-raf, BRAF1, NS7, RAFB1; B-Raf proto-oncogene, serine/threonine kinase
hsa:4893    NRAS, ALPS4, CMNS, N-ras, NCMS, NRAS1, NS6; NRAS proto-oncogene, GTPase
hsa:1019    CDK4, CMM3, PSK-J3; cyclin dependent kinase 4
hsa:4286    MITF, CMM8, COMMAD, MI, WS2, WS2A, bHLHe32; melanogenesis associated transcription factor
hsa:5728    PTEN, 10q23del, BZS, CWS1, DEC, GLM2, MHAM, MMAC1, PTEN1, PTENbeta, TEP1; phosphatase and tensin homolog
hsa:1029    CDKN2A, ARF, CDK4I, CDKN2, CMM2, INK4, INK4A, MLM, MTS-1, MTS1, P14, P14ARF, P16, P16-INK4A, P16INK4, P16INK4A, P19, P19ARF, TP16; cyclin dependent kinase inhibitor 2A
hsa:7157    TP53, BCC7, LFS1, P53, TRP53; tumor protein p53
hsa:3939    LDHA, GSD11, HEL-S-133P, LDHM, PIG19; lactate dehydrogenase A
hsa:3945    LDHB, HEL-S-281, LDH-B, LDH-H, LDHBD, TRG-5; lactate dehydrogenase B
hsa:3948    LDHC, CT32, LDH3, LDHX; lactate dehydrogenase C

Nous avons ici un résultat sur 2 colonnes :

  1. l'identifiant du gène KEGG
  2. les différents symboles connus du gène avec son nom complet juste après le point-virgule.

Récupérer la liste des pathways à partir des gènes

L'une des choses qui est sans doute celle qui nous intéresse le plus est la possibilité de récupérer la liste des pathways dans lesquels nos gènes précédents sont impliqués. Eh bien c'est tout à fait possible ! Voici comment nous pouvons procéder.

Dans un premier temps, nous utilisons l'opération link :

gene_paths = requests.get('http://rest.kegg.jp/link/pathway/' + hsa_genes)
lines = gene_paths.text.splitlines()
paths_id = []
for l in lines:
    # récupérer chaque réseau une seule et unique fois
    if l not in paths_id:
        paths_id.append(l.split('\t')[1])
print(len(paths_id))
98

Ici nous n'avons récupéré que la liste des identifiants. Pour pouvoir récupérer les réseaux en eux-mêmes, nous allons utiliser l'opération get sur chaque identifiant, tout en précisant que nous voulons obtenir les images. Pour pouvoir enregistrer les images j'ai utilisé le module shutil, qui fait également partie des bibliothèques de base :

import shutil
for p in paths:
    # récupérer un flux d'entrée
    path_img = requests.get('http://rest.kegg.jp/get/' + p + '/image', stream = True)
    # remplacer le caractère : par un _ dans le nom de l'image enregistrée
    path_name = p.replace(':', '_')
    with open(path_name + '.png', 'wb') as f:
        shutil.copyfileobj(path_img.raw, f)

Une fois ceci fait, rendez-vous dans le répertoire où vos images ont été sauvegardées, il ne vous reste plus qu'à analyser et interpréter les résultats !

Voici l'un des réseaux que j'ai récupéré :

Réseau de la leucémie myéloïde chronique. Image fournie par KEGG

Conclusion

KEGG nous permet, grâce à son API REST, d'effectuer des requêtes simples aussi bien manuellement que programmatiquement. Dans ces petits exemples courts et somme toute classique, vous avez pu entrevoir les possibilités d'imbrications de recherches que nous pourrions faire pour faciliter notre quotidien.

Dans un précédent article, je vous avais présenté la bibliothèque Python BioServices qui permet également de faire ce genre de requête auprès de KEGG. Le but de ce billet était de vous présenter le fonctionnement de l'API sans passer par un module plus ou moins lourd. Rien ne vous empêche pour autant de combiner les deux afin de faciliter vos programmes !


Un grand merci à mes relecteurs d'amours pour leurs  remarques et suggestions : Mathurin, Yoann M.Guillaume Devailly et Kumquatum.

L’article Jouer avec l'API de KEGG est apparu en premier sur bioinfo-fr.net.


LaTeX : les maths !

$
0
0

Après avoir appris à compiler, à insérer des flottants et à mettre en forme les paragraphes, on va s'attaquer à un truc plutôt coolish qui fait la force de LaTeX : la mise en forme de maths ! Comme c'est long et vaste, on va faire ça en plusieurs fois 😉 !

Tables des matières :
1. Ce qu'il faut ajouter au préambule
2. Types de formules (de base)
3. Fonctions
3.1. Aperçu des fonctions prédéfinies
3.2. Déclarer sa propre fonction
4. Exposants, indices
5. Fractions, racines
5.1. Fractions
5.2. Racines
6. Texte, police et espacement
7. Les accents et chapeaux
8. Insérer des symboles
8.1. Lettres grecques
8.2. Relations entre grandeurs
8.3. Opérateurs
8.4. Autres symboles courant en mathématiques
8.5. Flèches
9. Detexify, ce sauveur !

1. Ce qu'il faut ajouter au préambule…

%----------------------------------------------------------------------------------------
%   MATHS (PAQUETS)
%----------------------------------------------------------------------------------------
%-% AMS = American Mathematical Society
%-% Deal with it.

%-% L'option "fleqn" positionne les équations à une distance fixe de la marge
%-% de gauche (et pas centré dans la zone de texte)
\usepackage{amsmath}
\usepackage{amsfonts}
\usepackage{amssymb}

%-% Donne les commandes \theoremstyle{} et \proof{}
\usepackage{amsthm}

%-% St Mary's Road plug-in pour asmsymb, rajoute des choses.
\usepackage{stmaryrd}

%-% Basé sur amsmaths, permet de rendre joli les documents contenant beaucoup de maths
%-% par exemple, pour que les matrices voient leurs éléments centrés par défaut
\usepackage{mathtools}

C'est bon ? Alors c'est parti !

2. Types de formules (de base)

Il existe trois types de formules de base :

  1. Les formules en ligne qui commencent et finissent par un
    $
      (ou sont comprises dans l'environnement
    math
     ).
  2. Les formules centrées qui commencent par
    \[
     et se terminent par
    \]
      (ou sont comprises dans l'environnement
    displaymath
     ).
  3. Les formules centrées numérotées qui sont dans l'environnement
    equation
    .

Démo :

\documentclass[paper=a4,
               fontsize=11pt,
               titlepage,
              ]{article}

\input{preambule}


\begin{document}

Dans ce petit paragraphe nous allons insérer une équation comme $y = f(x)$ par
exemple. Juste pour montrer que c'est possible de le faire. Puis nous allons
mettre une équation centrée :
\[y = f(x) \]

Et enfin, nous allons mettre une équation numérotée :
\begin{equation}
    y = f(x)
\end{equation}

\end{document}

Ce qui donne :

3. Fonctions

3.1. Aperçu des fonctions prédéfinies

Alors tout un tas de fonctions est prédéfinis dans LaTeX, pas besoin de les définir manuellement (on verra bientôt comment définir ses propres fonctions :o) . Ces fonctions n'apparaîtront pas en italique mais en police romaine :

\sin
\cos
\tan
\cot
\arcsin
\arccos
\arctan
\coth
\sinh
\cosh
\tanh
\ln
\log
\exp
\max
\min
\sup
\inf
\lim
\ker
\deg
\mod
\pmod

Je vous laisse le soin de vous familiariser avec ces fonctions ! Vous verrez, c'est pas sorcier 🙂 .

3.2. Déclarer sa propre fonction

Parfois, on utilise des fonctions qui n'existent pas nativement dans LaTeX (oui, scandaleux, je sais) et il faut les déclarer. Voici comment on procède (dans son préambule) :

\DeclareMathOperator{\cost}{Cost}
\DeclareMathOperator*{\argmax}{arg\,max}

L'astérisque est là pour indiquer que l'on veut mettre les indices vraiment en dessous de l'opérateur fraîchement défini, comme pour les limites :

\[ \lim_{x \to 0} 2x + 7 \]
\[ \argmax_a x \]
\[ \cost_b X \]

Ce qui donne :
À noter : vous avez appris à faire une flèche avec

\to
  !

4. Exposants, indices

Les exposants et les indices n'existent qu'en mode maths. Pour mettre un exposant on utilise le

^
  et pour mettre en indice le
_
 de la manière suivante :

\[ u_n^1 \]
\[ 10^{-3} \]

Comme on peut le voir sur le résultat ci-dessous, il est possible de combiner les deux, et pour mettre plus d'un caractère en indice/exposant, il suffit d'ajouter des accolades :

Il est également possible de placer les objets les uns au-dessus des autres de la manière suivante :

5. Fractions, racines

5.1. Fractions

Pour écrire des fractions et des racines, c'est tout simple, on utilise \frac :

\[ \frac{\frac{1}{x}}{1 + \frac{1}{x}} \]

Ce qui donne :

Il existe plusieurs types de fractions que vous pourriez vouloir utiliser :

  1. \dfrac
      qui indique que l'expression mathématique devrait rester une formule
  2. \tfrac
      qui indique que l'expression mathématique devrait être en mode texte (et elle sera aussi haute que le texte et sera un peu moins belle)
  3. \frac
      où le contexte décide quelle décision appliquer.

Petit cas pratique :

Et hop une petite fraction $\frac{1}{x}$ dans du texte pour voir ce que ça donne et une petite fraction forcée pour pouvoir comparer $\dfrac{1}{x}$ les résultats¬!

Ce qui donne :
Ce qui montre bien que

\frac
  est probablement ce que vous souhaitez utiliser.

5.2. Racines

Pour les racines, c'est tout simple, il suffit d'utiliser

\sqrt
  qui permet aisément de faire des racines n-ièmes comme ceci :

\[ \sqrt{9} \]
\[ \sqrt[3]{9} \]

Ce qui donne :

6. Texte, police et espacement

Si vous voulez écrire du texte dans vos expressions mathématiques vous pouvez utiliser la commande

\text
 . Petit aperçu :

\[ a et b  \]
\[ a \text{ et } b \]

Ce qui donne :

On peut voir que LaTeX bouffe les espaces en mode mathématiques, pensez donc bien à les mettre dans votre

\text
  !

Sinon, pour gérer explicitement les espaces, on peut utiliser des commandes spécifiques comme celles-ci :

Si vous voulez néanmoins que cela soit en italique, c'est possible ! Regardez ce tableau pour voir ce que vous pouvez faire niveau police en mode mathématique :

C'est particulièrement utile pour définir des nouvelles commandes comme celle-ci :

\newcommand{\deriv}{\mathrm{d}}

 

7. Les accents et chapeaux

Les accents et chapeaux disponibles sont listés dans le tableau suivant :

8. Insérer des symboles

Presque tous les symboles dont vous pourriez rêver sont disponibles dans LaTeX.

8.1. Lettres grecques

Les lettres grecques n'existent qu'en mode maths. Voici comment les obtenir :

Certaines sont disponibles avec des variantes :

Et certaines ont des majuscules :

C'est tout pour les lettres grecques 🙂

8.2. Relations entre grandeurs

Les relations entre grandeurs usuelles sont disponibles avec LaTeX, comme montré dans le tableau ci-dessous :

8.3. Opérateurs

Pour obtenir les opérateurs courants, il suffit d'utiliser les commandes présentées dans cette table :

Voici comment on les utilise :

\[ \sum_{i=0}^n u_i\]
\[ \iiint f(x, y, z) \deriv x \deriv y \deriv z \]

Ce qui donne :

8.4. Autres symboles courant en mathématiques

Certains symboles ci-dessus possèdent une version « négative » que l'on obtient en ajoutant un n devant la commande, comme ceci :

\[ \nexists \]
\[ \ngeq \]

Ce qui donne :

Je vous laisse le soin de découvrir quels symboles on peut négationner, j'ai la flemme de tout tester et de faire un joli tableau :p .

8.5. Flèches

Il existe tout un tas de flèches en LaTeX, les voici :

9. Detexify, ce sauveur !

Alors en fait c'est méga dense ce que je vous ai balancé ici mais respirez ! Vous n'avez pas besoin de tout apprendre par cœur (ça viendra au fur et à mesure) parce qu'il existe un truc TROP BIEN : DETEXIFY. (Oui, je suis enthousiaste, ça m'a sauvé la vie plusieurs fois !)

Il suffit de dessiner son symbole et pouf il donne la commande et le paquet qu'il faut charger pour l'obtenir et même dans quel mode il faut l'utiliser. Si c'est pas beau la vie 🙂

 

 

Bon, je vais arrêter là, avant les matrices et les systèmes d'équations parce que c'est super dense… Promis, on revient vite avec un nouvel article !

 

Merci aux relecteurs Lins, Lelouar, et Kumquatum

L’article LaTeX : les maths ! 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.

LaTeX : automatisez le traitement des CSV

$
0
0

Vous avez peut-être vu l'excellent article de Chopopope sur l'utilisation des flottants en LaTeX, et en particulier la partie sur la création de tableaux. Non ? Pour les retardataires, c'est par ici

Vous vous êtes surement rendu compte que créer des tableaux peut être long et fastidieux. De plus, en bons bioinformaticiens, vos données tabulées sont écrites dans des fichiers Exc… CSV. Et vous trouvez un peu bête (pour ne pas dire plus) de devoir recopier votre fichier CSV dans LaTeX tout en formatant le tableau. Mais vous le faites quand même, car il faut bien insérer les données dans le rapport final.

Aujourd'hui, je vais donc vous libérer de ce fardeau et vous montrer comment intégrer vos fichiers CSV dans un document LaTeX et comment formater automatiquement ce fichier en tableau ! Nous verrons aussi comment intégrer des données issues d'un CSV ailleurs que dans un tableau.

Voici de quoi vous allez avoir besoin pour réussir l'intégration de fichiers CSV dans LaTeX :

  • Du café (ben oui, on est bioinfo quand même, faut pas déconner…)
  • Un (voir plusieurs) fichier(s) CSV
  • Une installation de LaTeX
  • Le package CSVSimple

Ce dernier package, CSVSimple, sera celui utilisé pour importer les données. S'il n'est pas présent par défaut dans votre installation LaTeX, vous pouvez le récupérer sur le CTAN. Et puisque vous aimez lire les docs (RTFM toussa toussa), celle du package est disponible en suivant ce lien.

Vous êtes prêts ? On y va !

Tableaux simples

Pour commencer on va faire un tableau très très simple. Imaginons que vous ayez un fichier marsupilami_by_country.csv qui contient un comptage du nombre de marsupilamis (il parait qu'il y a des fans…) observés dans différents pays en fonction de leur phénotype. Petit rappel de biologie, pour ceux qui n'ont pas bien suivi les cours de zoologie, chez les marsupilamis on peut observer trois phénotypes différents : noir, jaune et jaune tacheté de noir.

Notre fichier contient donc :

Pays,Noir,Jaune,Tacheté
Palombie,1,1,3
Colombie,2,3,1
Mordor,0,0,0
France,0,0,1

(Si quelqu'un a une explication pour l'absence de marsupilami dans le Mordor, je suis preneur…)

Maintenant que vous vous êtes cassé la tête, minimum 4 ans de thèse, à obtenir ce fichier vous avez bien envie de l'ajouter dans votre manuscrit.

Alors oui, vous pourriez écrire le code LaTeX suivant :

\begin{tabular}[cccc]
Pays&Noir&Jaune&Tacheté\\
Palombie&1&1&3\\
Colombie&2&3&1\\
Mordor&0&0&0\\
France&0&0&1\\
\end{tabular}

Mais comme vous n'êtes pas premier de cordée, vous êtes un peu fainéant, et vous préféreriez avoir beaucoup moins de ligne à écrire. Vu que toutes vos données sont déjà dans un fichier, on va pouvoir créer ce tableau en très peu de lignes :

\csvautotabular{marsupilami_by_country.csv}

Et c'est tout…  Il vous faudra seulement importer le package CSVSimple grâce à  

\usepackage{csvsimple}
 .

Pour créer le tableau dans le document, j'ai utilisé la commande

\csvautotabular
 , qui prend, ici, comme paramètre unique le nom du fichier CSV depuis lequel importer les données.

Et voici ce que vous obtenez.

 

C'est beau non ? Non ? Ah vous préférez le design des tableaux fait avec le package booktabs ? À la place de

\csvautotabular
  vous pouvez utiliser
\csvautobooktabular
 (attention il faut également importer le package booktabs).

 

Des tableaux plus complexes grâce à la commande csvreader

La commande

\csvautotabular
  est formidable lorsque vous voulez afficher un tableau tout simple, mais parfois on veut faire des trucs un peu plus complexes. Et pour ça, le package CSVSimple intègre la commande
\csvreader
  qui lit un fichier csv (mais vous l'auriez deviné, non ?).

La commande

\csvreader
  prend plusieurs paramètres. La doc nous dit de l'utiliser de la façon suivante :
\csvreader[⟨options⟩]{⟨file name⟩}{⟨assignments⟩}{⟨command list⟩}
 . Comme vous le voyez plusieurs arguments peuvent être passés à cette commande :
  • Des options : on verra cela plus tard, mais c'est ce qui permettra notamment de formater un peu le tableau.
  • File name : est-ce bien nécessaire d'expliquer ?
  • Assignements : va permettre de récupérer les différentes colonnes dans des variables. Attention le fichier CSV doit nécessairement contenir une ligne d'en-tête !
  • Command list : une liste de commande à exécuter pour chaque ligne du fichier csv. Parmi ces commandes, il y a l'affichage des colonnes. Cela permet  notamment de les afficher dans un ordre différent de celui du fichier de départ.

Allez, testons. On va toujours afficher le fichier utilisé ci-dessus. Mais comme je suis un peu chiant, j'ai absolument envie d'avoir les marsupilamis tacheté sur la deuxième colonne alors que c'est la dernière colonne de mon fichier…

\csvreader{marsupilami_by_country.csv}
{Pays=\pays, Noir=\noir, Jaune=\jaune, Tacheté=\tachete}
{\pays & \tachete & \noir & \jaune}

Explications de cette commande. Je commence par dire à csvreader d'utiliser le fichier marsupilami_by_country.csv. Ensuite viennent les assignments : la colonne dont l'entête est "Pays" est ajouté à la macro \pays, Noir dans \noir, Jaune dans \jaune et Tacheté dans \tachete. Enfin je demande d'afficher d'abord \pays puis \tachete puis \noir et enfin \jaune.

Résultat ?

Misplaced alignment tab character &.

Mais pourquoi ? En fait c'est assez logique. À aucun moment on a dit à LaTeX de créer un tableau. Il voit donc une marque de tabulation, mais il n'est pas dans un environnement tableau. Et c'est là qu'interviennent les options ! Dans les options on va pouvoir dire à csvreader d'afficher le tout dans un environnement tabular en créant 4 colonnes centrées séparées par une ligne verticale :

\csvreader[tabular=|c|c|c|c|]{marsupilami_by_country.csv}
{Pays=\pays, Noir=\noir, Jaune=\jaune, Tacheté=\tachete}
{\pays & \tachete & \noir & \jaune}

Tadaaa !

Une autre solution serait de déclarer un environnement tabular et de placer la commande

\csvreader
  à l'interieur.

Grâce aux options, il est également possible de faire des tableaux bien plus complexes. Par exemple, il est possible d'alterner les couleurs d'arrière-plan des lignes !

Reprenons le tableau précédent auquel on va ajouter une ligne d'en-tête qui aura un arrière-plan gris foncé alors que les autres lignes alterneront entre blanc et gris clair. Voilà le code :

\usepackage[table]{xcolor}
\csvreader[head to column names,
tabular=|c|c|c|c|,
table head=\hline\rowcolor{gray!99}\color{white}Pays & \color{white}Tacheté & \color{blue}Noir & \color{yellow}Jaune,
late after head=\\\hline\rowcolor{white},
late after line=\csvifoddrow{\\\rowcolor{white}}{\\\rowcolor{gray!25}}]
{marsupilami_by_country.csv} {Pays=\pays, Noir=\noir, Jaune=\jaune, Tacheté=\tachete} {\pays & \tachete & \noir & \jaune}

Ce code nécessite pas mal d'explication.

Déjà il nécessite l'import du package xcolor avec l'option table.

table head=\hline\rowcolor{gray!99}\color{white}Pays & \color{white}Tacheté & \color{blue}Noir & \color{yellow}Jaune
  : Ici j'ai défini la ligne d'en-tête du tableau et sa couleur.

late after head=\\hline\rowcolor{white}
 : Ici on définit la ligne qui suit celle d'en-tête. J'ai commencé par ajouter un retour à la ligne (
\
 ) que je n'avais pas mis dans la ligne d'en-tête, et j'ai défini l'arrière-plan de la ligne comme blanc.

late after line=\csvifoddrow{\\rowcolor{white}}{\\rowcolor{gray!25}}] 
 : Ici j'ai utilisé
\csvifoddrow
  pour définir la couleur d'arrière-plan des lignes impaires comme blanche et celle des lignes paires comme grises.

Voilà ce qu'on obtient :

 

Pour aller plus loin : le publipostage

Vous vous souvenez, la commande

\csvreader
  ne crée pas d'environnement tabular. Ce qui veut dire que la commande peut-être utilisée pour faire autre chose que des tableaux, comme par exemple des rapports automatiques ou du publipostage. Il suffit de remplacer les marques de tabulations par des phrases :

\csvreader{marsupilami_by_country.csv}
{Pays=\pays, Noir=\noir, Jaune=\jaune, Tacheté=\tachete}
{En \pays, il y a \tachete~Marsupilamis tachetés, \noir~Marsupilamis noirs et \jaune~Marsupilamis jaunes.}

Ajoutez un

\clearpage
  à la fin de la commande d'affichage et vous obtiendrez une page par pays. Ajoutez ensuite des instructions pour faire une belle mise en page et vous pourrez faire des rapports automatiquement.

Imaginez maintenant qu'à la place du nombre de marsupilamis dans un pays donné, le fichier contienne la liste des participants à un congrès. Il devient très facile de créer leur badge et leur attestation de participation en quelques lignes. Mais je vous laisse tester par vous-même ;-).

Il y a plein d'autres fonctions super cool dans le package csvsimple notamment pour trier les données selon des critères plus ou moins complexes. Une fois que vous avez lu un fichier csv vous pouvez également utiliser plein d'autres fonction de LaTeX et notamment créer vos graphiques directement grâce à tikz. Plein d'exemples d'utilisation sont données dans la doc de csvsimple, n'hésitez pas à y jeter un coup d'œil et à nous montrer vos créations !

Pour finir, je vous donne le code minimal permettant de réaliser l'ensemble des tableaux que j'ai présentés dans l'article.

\documentclass[a4paper,12pt]{article}

\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{csvsimple} %Il faut importer la package CSVSimple pour que ça fonctionne !
\usepackage{booktabs}
\usepackage[table]{xcolor}

\begin{document}

\csvautotabular{marsupilami_by_country.csv}
\csvautobooktabular{marsupilami_by_country.csv}


\csvreader[head to column names,
tabular=|c|c|c|c|,
table head=\hline\rowcolor{gray!99}\color{white}Pays & \color{white}Tacheté & \color{blue}Noir & \color{yellow}Jaune,
late after head=\\\hline\rowcolor{white},
late after line=\csvifoddrow{\\\rowcolor{white}}{\\\rowcolor{gray!25}}]
{marsupilami_by_country.csv} {Pays=\pays, Noir=\noir, Jaune=\jaune, Tacheté=\tachete} {\pays & \tachete & \noir & \jaune}
\csvreader{marsupilami_by_country.csv}
{Pays=\pays, Noir=\noir, Jaune=\jaune, Tacheté=\tachete}
{En \pays, il y a \tachete~Marsupilamis tachetés, \noir~Marsupilamis noirs et \jaune~Marsupilamis jaunes.}

\end{document}

 

Merci aux relecteurs Gwenaëlle, et Hedjour.

L’article LaTeX : automatisez le traitement des CSV est apparu en premier sur bioinfo-fr.net.

Apprivoiser l'ami ursidé de Python : Pandas

$
0
0

Durant mon stage de M2, j’ai eu l’occasion de chatouiller ce drôle d’animal qu’est pandas. En effet, j’ai travaillé sur des données de protéomique contenues dans des fichiers tabulés. Il s'agissait de comparer la présence des protéines ou leur expression dans différents échantillons. Les abondances relatives (la variable étudiée) étaient indiquées pour les différentes protéines identifiées (plusieurs milliers et correspondant aux lignes du fichier) dans les différents échantillons analysés (correspondant aux colonnes). J’ai donc dû manipuler ces données tabulées pour en tirer des conclusions vis-à-vis du comportement des protéines selon les différents échantillons. Je viens ainsi vous présenter ce qu’est pandas et quelques commandes pour pouvoir l’utiliser « de façon basique » et assez facilement afin de commencer rapidement à analyser ses données. Comme vous l'aurez compris, il s’agira d’un article introductif.

Mais qu’est-ce que pandas ?

Non, ce n’est pas vraiment un ursidé qui m’a aidée à trifouiller dans les données mises à ma disposition. Ce n’est pas non plus des « Troubles neuropsychiatriques pédiatriques auto-immunes associés à une infection à streptocoque » (merci Wikipédia pour la découverte !). Alors qu’est-ce réellement ?

Aquarelle pandas by Chopopope

 

Pandas est une bibliothèque open source qui vise à l’analyse et la manipulation de données en Python. L'objectif est de permettre la manipulation de grands volumes de données structurées (tabulées, multi-dimensionnelles, voire hétérogènes) et de données temporelles de façon simple mais efficace et rapide dans le but de leur exploitation. La bibliothèque contient donc un grand nombre d'outils pour récupérer, agréger, combiner, transformer, trier, découper, déplacer et exporter les données, ainsi que gérer les valeurs manquantes, calculer des statistiques et produire des graphiques.

Enfin, pandas a été conçu à partir de Numpy et reprend une structure de données utilisée dans R (les data.frame). Il est donc naturel de retrouver des similarités avec ce dernier dans l'utilisation de pandas.

Je tiens à préciser que, hélas, pandas ne sait pas encore faire le café… (Désolée Nolwenn :p) Peut-être durant une prochaine version ! D’ailleurs, vous pouvez très bien proposer des améliorations et participer au développement ici. Il y a même un Gitter et une mailing list ainsi que le traditionnel Stack Overflow pour ceux qui auraient des questions !

Documentation

La documentation officielle est assez imposante avec 2215 pages pour la version PDF mais peut être assez interactive via le site. De plus, je conseille les pages suivantes qui permettent de se plonger dans pandas rapidement pour une utilisation simple sans avoir à digérer la documentation en entier (heureusement pour nous !) :

  • Une page qui prétend permettre d’appréhender pandas en 10 minutes
  • Des exemples rapides d’utilisation de pandas façon « livre de cuisine »
  • De nombreux tutoriels pour les nouveaux utilisateurs ou sur des points précis

 

Et en bonus, il existe des cheat sheets ! Des fiches qui réunissent les commandes principales et essentielles de pandas. Elles sont très pratiques, à imprimer et afficher à côté de son ordi pour y jeter un coup d’œil rapide et ne pas perdre du temps à chercher la commande sur internet ou dans la doc ! Je vous renvoie vers celle éditée par l'équipe de développement de pandas ici. Plusieurs autres peuvent également être trouvées sur internet (DuckDuckGo est ton ami !).

Installation

À la date de fin d’écriture de l’article, la dernière version (release) de pandas est la 0.22.0 (Février 2018). Elle est supportée par les versions de Python 2.7, 3.5 et 3.6, vous avez donc l’embarras du choix. Avant tout, pour pouvoir « jouer » avec pandas, il faut passer par l’installation. Heureusement pour nous, cette dernière est assez simple puisqu’il s’agit d’un package Python.

Avec Pip (la méthode recommandée avec l'utilisation de virtualenv)

#Version 2.X de Python
pip install pandas

#Version 3.X de Python
pip3 install pandas

Avec Conda

On peut également passer par Conda pour l’installation (pour rappel, deux super articles sur cet autre animal : ici et ) :

conda install pandas

Avec les gestionnaires de paquets

Ou bien en utilisant les gestionnaires de paquets des distributions Linux en téléchargeant les paquets python-pandas pour Python 2.X et python3-pandas pour Python 3.X. Par exemple, pour une distribution de type Debian ou Ubuntu :

# Pour Python 2.X
sudo apt-get install python-pandas

#Pour Python 3.X
sudo apt-get install python3-pandas

Autres façons

Il est évidemment possible de passer par le site officiel. Si vous êtes plus aventurier(ère), vous pouvez également télécharger les codes sources depuis GitHub ou même la source tarball.

En conclusion, pas d’excuse pour ne pas trouver un moyen d’installer pandas qui soit à son goût ! A noter qu'une installation sans virtualenv peut tout de même déstabiliser le sytème d'exploitation (merci Nolwenn !).

Utilisation

Import de la bibliothèque

Avant même de penser à manipuler la bête, il faut l'importer :

import pandas as pd

Structures de données

Il faut savoir que pandas utilise principalement deux structures de données : la Series (1 dimension) et le DataFrame (2 dimensions). Pour résumer simplement, on va utiliser la structure Series lorsque l'on est en présence de données tabulées présentant plusieurs lignes et une seule colonne de données (de la variable nous intéressant), alors que l'on va plutôt avoir recours au DataFrame à partir de deux colonnes.

Series

Exemple de fichier de données que l'on peut traiter avec un objet Series:

Accession Abondance
P08637 792592,97
P00533 562817,06
P35222 7768216,5
... ...

Au niveau du code :

series = pd.Series(data=[abondance1, abondance2, etc], index=["Prot1", "Prot2", etc])

#Par exemple
series = pd.Series(data=[792592.97, 562817.06, 7768216.5], index=["P08637", "P00533", "P35222"])
print(series)

Affichage de l'objet series

DataFrame

Exemple de fichier de données permettant d'utiliser un DataFrame :

Accession Entry name Condition 1 Condition 2 Condition 3
P08637 P13_HUMAN 792592,97 971165,22 1175682,25
P00533 EGFR_HUMAN 562817,06 698207,22
P35222 CTNB1_HUMAN 7768216,5 2072283,5 7202552,25
... ... ... ... ...

Au niveau du code :

df = pd.DataFrame(data, index)

#Par exemple
idx = ["P08637", "P00533", "P35222"]
donnees = {"Entry name":["P13_HUMAN", "EGFR_HUMAN", "CTNB1_HUMAN"], "Condition 1":[792592.97, 562817.06, 7768216.5], "Condition 2":[971165.22, 698207.22, 2072283.5], "Condition 3":[1175682.25, None, 7202552.25]}
df = pd.DataFrame(data=donnees, index=idx)
print(df)

Affichage de l'objet df

Concernant le paramètre data, on peut passer des dictionnaires (de Series, arrays, etc), des DataFrames ou des ndarray de numpy (voir la doc).

Différentes Manips avec Pandas

/!\ Ici, je ne traiterai que d'exemples avec des données (à analyser) numériques avec des index non numériques (chaînes de caractères). Je vous invite donc à adapter ces exemples pour des données de nature différente ou hétérogènes (et à compléter cet article avec un autre présentant de nouveaux exemples et nouvelles applications !).

Je travaillerai avec un fichier contenant des données simulées qui se présente comme ceci :

Fichier de données

Import des données

À partir d'un fichier EXCEL

Oui, le monde n'est pas (encore) parfait. Il arrive (trop souvent) qu'il faille manipuler des fichiers Excel. Mais rassurez-vous, avec pandas, nous aurons seulement à récupérer les données du fichier et les stocker dans un DataFrame (ou Series s'il n'y a qu'une seule colonne de données). Nous ne toucherons plus à ce type de fichier pour nos traitements.

xl = pd.ExcelFile("path/to/file.xlsx")
print(xl.sheet_names) # Affiche les noms des différentes feuilles du fichier Excel
df = xl.parse("nameOfSheetToAnalyse", index_col=NumberOfTheColumnWhichIsTheIndexColumn)
# Ou encore
df = xl.parse(sheet_name="nameOfSheetToAnalyse", index_col=NumberOfTheColumnWhichIsTheIndexColumn)

#Par exemple
xl = pd.ExcelFile("/media/DATA/Bioinfo-fr/data_excel.xlsx")
print(xl.sheet_names)
df = xl.parse("data", index_col=0)
print(df) # Affiche le dataframe df contenant les données de la feuille 'data' du fichier Excel data_excel.xlsx

Résultat de sortie

Une autre façon de faire est possible en utilisant une seule fonction : read_excel().

df = pd.read_excel("path/to/file.xlsx", sheet_name=NumberOfTheSheetToAnalyseTheDataFrom, index_col=NumberOfTheColumnWhichIsTheIndexColumn)

#Par exemple
df = pd.read_excel("/media/DATA/Bioinfo-fr/data_excel.xlsx", sheet_name=0, index_col=0)

Apparemment, d'après une réponse sur StackOverflow, la première solution serait plus rapide. On remarque également que l'on peut soit préciser le nom de la feuille à analyser ou directement le numéro de celle-ci, ce qui est utile quand on ne connaît pas les intitulés des feuilles. Cela fonctionne pour les deux solutions, ce n'est pas dépendant de la façon de faire choisie. Je vous laisse aller lire la documentation pour plus de détails.

À partir d'un fichier CSV

Quand on a plus de chance, ce sont des fichiers au format csv que nous avons à notre disposition. De manière analogue à la fonction read_excel(), on utilise la fonction read_csv(). Il y a un paramètre optionnel delimiter='\t' si votre fichier est au format tsv.

df = pd.read_csv("path/to/file.csv", index_col=NumberOfTheColumnWhichIsTheIndexColumn)

#Par exemple
file_csv = "/media/DATA/Bioinfo-fr/data_CSV.csv"
df = pd.read_csv(file_csv, index_col=0)

Premier aperçu des données

Une fois importées, il est (enfin) possible de jeter un coup d’œil à nos données pour pouvoir commencer à les exploiter. Avec les quelques fonctions ou variables suivantes, nous aurons un rapide aperçu sur ces dernières.

#Renseigne sur le type des données de chaque colonne du dataframe
df.dtypes

résultat df.dtypes

# Sélectionne les premières lignes du dataframe (par défaut 5)
df.head()
# Sélectionne les dernières lignes du dataframe (par défaut 5)
df.tail(3)

résultat de la fonction head()

résultat appel fonction tail()

# Sélectionne la colonne des indices du dataframe
indices = df.index
# Noms des colonnes (hormis celui des indices)
colonnes = df.columns
# Valeurs des données
vals = df.values

résultat de l'appel de la variable index

résultat de l’appel à la variable columns

# Renvoie quelques statistiques sur les données sous forme d'un dataframe
# percentiles : par défaut 25%, 50% et 75% ; include : colonnes à inclure dans les statistiques (par défaut toutes les colonne de données numériques) ; exclude : colonnes à exclure (par défaut aucune)
df.describe(percentiles=[.25, .5, .75, .9], include='all'; exclude=None)

# Renvoie la moyenne des données (numériques) par colonne
df.mean() # ou df.mean(0)
# moyenne par ligne
df.mean(1)

résultat de l'appel de la fonction describe(). count : nombre de valeurs dans la colonne; [unique, top, freq] : pour les valeurs textuelles, top : valeur la plus représentée (aléatoire si plusieurs); [mean (moyenne), std (écart-type), min, max, pourcentiles] : pour les valeurs numériques

Traitements basiques

Après avoir eu une vision globale de nos données grâce à quelques statistiques, nous pouvons les traiter à l'aide des informations obtenues précédemment.

Gérer les données manquantes et/ou nulles

# Renvoie un dataframe sans valeur manquante (si le paramètre inplace est à False)
# axis : axe selon lequel supprimer les données (0 : ligne , 1 : colonne)
# how : any = si au moins une valeur manquante présente, l'ensemble de la ligne/colonne est supprimée ; all = toutes les valeurs d'un ensemble doivent être manquantes pour être supprimées
# inplace : supprime les valeurs dans la dataframe au lieu d'en renvoyer un nouveau
df_dropna = df.dropna(axis=0, how='any', inplace=False)

# Par exemple
df_dropna = df.dropna(how='any')

dataframe sans valeur manquante

# Renvoie un dataframe dont les valeurs manquantes ont été remplacées par une valeur précisée
df_fillna = df.fillna(value=5)
# Renvoie un dataframe avec à la place de chaque valeur un booléen renseignant si la valeur est manquante ou non
df_isnull = pd.isnull(df)

dataframe avec les valeurs manquantes remplacées par la valeur numérique 5

Trier les données

# Renvoie un dataframe avec les valeurs triées selon l'axe choisi (indices ou noms colonnes)
df.sort_index(axis=0, ascending=True)

Sortie de l'appel de la fonction sort_index()

# Renvoie le dataframe dont les valeurs sont triées selon les valeurs d'une colonne
df.sort_values(by='C2')

Sortie de l'appel de la fonction sort_values()

Valorisation des résultats/données

Visualisation

Il est souvent intéressant de pouvoir visualiser ses données sous la forme d'un graphique. Il est possible de créer un graphique directement à partir du dataframe et en utilisant la bibliothèque matplotlib ('import matplotlib.pyplot as plt' en début de script).

df.plot()
# Affiche le graphique
plt.show()
# Ou pour sauvegarder
plt.savefig("path/to/graph", format="png")

graphique généré à partir du dataframe

On peut évidemment choisir un type de graphique. Pour cela, il faut utiliser à la place de df.plot() les méthodes associées : df.plot.type() avec 'type' étant 'line', 'bar', 'scatter', 'hist', 'box', etc.
On peut également complexifier un peu selon nos envies et notre maîtrise de matplotlib. Je vous glisse (en cadeau car vous le méritez après avoir lu ce looong article) ici un exemple de graphique dont j'ai eu besoin. J'ai dû modifier la présentation de l'axe des abscisses pour pouvoir visualiser les noms de protéines de façon lisible.

N = len(df['C2'])
x = np.arange(1, N + 1)
width = 1.0
plt.scatter(x, df['C2'])
plt.xlabel('Protéines')
plt.ylabel('Abondance relative')
plt.title("Valeurs de la condition 2")
plt.xticks(x, df.index, rotation=90)
plt.autoscale(tight=False)
plt.grid()
plt.show()

Graphique un peu plus complexe

Export des résultats/données

L'export des données est similaire à leur import. Au final, vous pouvez manipuler différents types de fichiers, que ce soit en entrée ou en sortie. Un tableau récapitulatif des possibilités vous permet de le constater (cf documentation).

tableau récapitulatif des formats supportés

Informations complémentaires

Il est à noter que pandas peut être utilisé au sein de IPython/notebook Jupiter.

Durant l'écriture de cet article, j'ai même découvert qu'il existait GeoPandas pour manipuler les données géographiques. Donc si vous bossez sur du SIG, pandas est aussi là pour vous ;). Je ne l'ai pas utilisé, mais avec un rapide coup d’œil sur la doc, j’ai pu voir qu'il reposait sur le même principe global avec les types de données Series et DataFrame (GeoSeries et GeoDataFrame).

De plus, pandas offre bien d'autres possibilités et je vous invite à découvrir ces dernières et à venir nous les partager par l'écriture d'un article plus avancé sur l'utilisation de pandas ! Je vous laisse (enfin) vous dépatouiller avec Pandas !

Enfin, je tiens à remercier Olivier Dameron, Nolwenn et lroy qui se sont aventuré(e)s à relire mon article et Chopopope pour sa magnifique aquarelle ! \o/

Références

https://pypi.python.org/pypi/pandas

https://linuxfr.org/news/pandas-une-bibliotheque-pour-manipuler-facilement-des-donnees

http://sametmax.com/le-pandas-cest-bon-mangez-en/

L’article Apprivoiser l'ami ursidé de Python : Pandas est apparu en premier sur bioinfo-fr.net.

Inkscape pour biologistes

$
0
0

Fini les rectangles pour faire des protéines ou les images pixelisées chopées sur des sites douteux !

Dans 10 minutes vous serez un pro d'Inkscape qui est un logiciel gratuit pour "dessiner" avec une prise en main très rapide permettant de réaliser des figures vectorielles, non construites en pixels, pouvant être alors redimensionnées à l'infini en conservant toutes leurs qualités visuelles.

Cet article est une retranscription de mon tutoriel Inkscape réalisé tout en diaporamas(pdf), à vous de voir la forme qui vous va le mieux.

Ayant appris avec d'autres tutoriels et notamment avec ce magnifique article je m'en suis inspiré tout en ayant axé directement sur ce qui peut nous servir en présentation, comme faire des protéines (5min) et une membrane (2min31).
Une fois tout ça maitrisé vous aurez les outils pour faire des schémas complets de processus biologiques. Les tutoriels ne manquent pas mais au moins celui-là pourra, j'espère, vous inciter à vous y mettre !

Le plan :

 

Une fois les réalisations des deux éléments présentées je vous montrerai une méthode pour enregistrer et utiliser votre toute fraiche production et un petit bonus (Comment faire le plan :D) !

Cliquez sur les images pour les agrandir.

 

Protéine :

Image de la protéine

Les commandes de cette première partie sont également présentées dans celle de l'article précédemment cité, étant des bases importantes mieux vaut deux fois qu'une !

Pour créer des formes lisses afin illustrer une protéine plusieurs étapes sont nécessaires pour réaliser la forme et les détails d'apparence.

Pour cela :

  • réaliser une forme polygonale avec l’outil « courbe de Bezier » :

le premier clic commence la forme, les autres ajoutent des points intermédiaires et le dernier, en reliant le premier, finit la forme.

 

  • Courber les segments pour obtenir une forme lisse plus agréable :

1. Sélectionnez tous les nœuds (Ctrl + A ou sélection avec la souris) :

Ctrl + A ou sélection avec la souris

2 . Rendre doux les nœuds sélectionnés :

Rendre doux les nœuds sélectionnés

3. Modifier en sélectionnant les nœuds séparément (ici un seul a suffi !) :

 Modifier en sélectionnant les nœuds séparément

  • Couleurs et dégradé :

Néanmoins, si la partie "Fond et contour" n'apparait pas automatiquement, 2 solutions : clic droit sur la figure ou dans l'onglet "Objet".

Mettre Couleurs et dégradé

 

  • Texte :

Mettre un texte

 

  • Enfin : Pour pouvoir bouger le texte et la forme en même temps il faut les "grouper".

Pour cela il faut les sélectionner (Shift + clic sur chacun) puis "Ctrl + G" ou clic droit puis "grouper" (voir figure).

il faut les sélectionner (Shift + clic sur chacun) puis "Ctrl + G" ou clique droit puis "grouper" (voir figure

 

 

Membrane :

Dessin obtenu pour la membrane

 

  • Faire un lipide :

Outil rond + branches avec l'outil "courbes de Bézier" (clic puis clic-droit pour faire seulement un trait).

Si besoin d’une bicouche (deux lipides qui se font face ici) faire symétrie verticale (" v ") après avoir dupliqué("Ctrl + D") et ne pas oublier de les grouper ! (Ctrl + g).

lipides

  • Faire la courbure de la future membrane avec une courbe :

Utilisez l'outil "dessin à main levée" avec un lissage haut (exemple ici 76) pour adoucir automatiquement le tracé et permettre un alignement régulier de l'unité pour le motif !

outil "dessin à main levée" avec un lissage haut (exemple ici 76) pour adoucir automatiquement le tracé

  • changer le type du lipide (en chemin) car l'extension suivante ne peut être réalisée qu'avec ce type :

Onglet Chemin --> Objet en chemin

 

  • Pour appliquez l'extension "Motif le long d'un chemin" :

Sélectionnez (shift + clic) le lipide PUIS la courbe, l’ordre est important !

Puis suivre la figure : dans les paramètres de l'extension spécifiez dans "copie du motif" : "répété".

Extension --> générer à partir du chemin --> Motif le long d'un chemin

Une fois la courbe supprimée vous obtenez une bicouche membranaire ! 

 

Enregistrer :

vous pouvez Copier/Coller dans PowerPoint par exemple mais pour un résultat optimal :

 

  1. Enregistrer en png : suivre figure

Les onglets (dont un est pointé par la flèche) permettent de déterminer précisement ce que vous voulez enregistrer (la page entière ou la sélection par exemple).

Ou :

Comment enregistrer

Quelques trucs en plus :Barre avec le magnétisme

Pour modifier des formes vous pouvez non seulement modifier la place d’un nœud mais en ajouter, en supprimer et également agir directement sur les segments pour les « tirer ». C’est donc l’outil qui permet le changement à l’infini de n’importe quelle structure créée sous inkscape ou n’importe en format svg.

De plus, il faut savoir que dans Inkscape le magnétisme des objets entres eux est fixé par défaut. Cela peut être gênant. Voir à droite de l’écran pour l'activer ou le désactiver :

 

 

Bonus : instructions pour réaliser l'image du plan de cet article

  • Outil « dessin à main levée » avec un haut lissage pour faire le trait courbé.
  • Dans « couleurs et contour » modifiez seulement son épaisseur, sa couleur,  et ajoutez éventuellement un dégradé).
  • Faire un trait, lui ajouter avec les options de contours (toujours dans couleurs et contour) une extrémité ronde et en changer la couleur des éléments.

 

Raccourcis claviers/astuces utiles :

Copier : Ctrl + c

Coller : Ctrl + v

Dupliquer : Ctrl + d (= Copier/coller en une commande tout en ne modifiant pas un objet copié précédemment via Ctrl + c).

Modifier la taille en gardant les proportions : Ctrl + tirer sur une des flèches à sélectionner.

Modifier l’orientation : sélectionner la figure (= flèches pour tailles) recliquer (= pour l’orientation).

Grouper : Ctrl + g / dégrouper : Ctrl+ Shift + g.

Ctrl + Alt : pour un déplacement d'objet selon la grille.

 

Un grand merci aux relecteurs Olivier Dameron,  Mathurin, Nolwenn, et Sylvain P. sans qui un travail de qualité n'aurait pas été possible !

L’article Inkscape pour biologistes est apparu en premier sur bioinfo-fr.net.

LaTeX : les lettres (de motivation) !

$
0
0

Après avoir appris à compiler, à insérer des flottants, à mettre en forme les paragraphes et à insérer des mathématiques, il est temps de faire une petite pause hors de la classe

article
  et de se pencher sur les lettres (notamment parce que si mes prédictions sont justes, certain·e·s d'entre vous doivent écrire des lettres de motivation à l'heure qu'il est).

Chose étonnante, cet article sera très court \o/.

Fichier d'identité

Alors comme votre identité n'est pas supposée changer tous les jours, je vous propose de la stocker à part dans un fichier que vous pourrez lier dans le dossier où vous écrivez votre lettre.

Ce fichier, que l'on appellera

identite.ins
  comprendra les informations suivantes :

%-% Pour supprimer le trait de pliure
\makeatletter
\newcommand*{\NoRule}{\renewcommand*{\rule@length}{0}}
\makeatother

\NoRule
\name{Prénom \textsc{Nom}}
\address{\centering
Prénom \textsc{Nom} \\
42, avenue de la Bioinfo \\
42~314 \textsc{Bioinfo-fr}}
\telephone{06~07~08~09~10}
%-% C'est \fax{} pour indiquer un numéro de fax ;)
\nofax
\lieu{\textsc{Bioinfo-fr}}
\signature{Prénom \textsc{Nom}}
\email{login@ndd.tld}

La lettre

Pour la lettre, c'est très simple, il suffit de faire ça :

\documentclass[12pt,origdate]{lettre}%
\usepackage[utf8x]{inputenc}
\usepackage[francais]{babel}
\usepackage[OT1]{fontenc}
\usepackage{mltex}
\usepackage{lipsum}

\institut{identite}
\begin{document}
%-----------------------------------------------%
% Le blabla pas intéressant                     %
%-----------------------------------------------%
\begin{letter}{Mme Prénom \textsc{Nom} \\
Fonction}
\date{le ??/??/????}
\conc{Objet de la lettre}
\opening{Madame,}

%-----------------------------------------------%
% Le corps de la lettre de motivation           %
% à vous de jouer !                             %
%-----------------------------------------------%
%-% À remplacer par votre lettre de motivation
\lipsum[1]

%-----------------------------------------------%
% Le blabla pas intéressant                     %
%-----------------------------------------------%
\closing{Formule de politesse,}
\cc{Listes des autres destinataires}
\encl{Description des pièces jointes}
\ps{PS :~}{un post-scriptum}
\end{letter}
\end{document}

Ce qui donne :

Et voilà, c'est tout pour cette fois, il ne vous reste plus qu'à signer 🙂

 

(Comme quoi je sais faire des articles LaTeX pas longs comme le bras…)

Merci à jnsll et eorn pour leur relecture

L’article LaTeX : les lettres (de motivation) ! est apparu en premier sur bioinfo-fr.net.

Du CV jusqu'au poster avec Inkscape (débutant)

$
0
0

Nous revoilà pour de nouvelles aventures sur Inkscape !

Le but de ce tuto est moins de faire son CV avec Inkscape, quand des outils qu’on utilise tous les jours le font très bien, que de se familiariser avec un outil puissant en manipulant des notions de bases qui peuvent servir ensuite notamment dans l’élaboration de posters scientifiques.

Pour d’autres articles sur les outils d’Inkscape voir les tutos déjà existants sur Inkscape l'outil idéal pour vos posters et Inkscape pour biologistes.

Sera abordé ici une initiation aux outils de :

  • Magnétisme avec la possibilité de coller aux bords de la feuille des figures (ici des rectangles).
  • Encadrement de texte (circonscrire dans un espace donné un texte).
  • Alignement (INDISPENSABLE pour tout schéma).

 

Voici le CV que vous allez construire :

Résultat part III

I - Création de la structure

Pour s’exercer au premier point nous allons créer l’apparence de notre CV, sans le texte.

Nous allons commencer par les blocs de couleurs : resultat part I

Magnétisme.

Tout d’abords il faut voir du côté de la barre de magnétisme, c’est celle par défaut la plus à droite de la fenêtre Inkscape :

Fenetre inkscape avec indication sur l'emplacement de la barre de magnétisme

Voici la barre minimale à avoir pour pouvoir aimanter aux bords de la feuille vos figures :

barre magnétisme avec feuille

En sachant que c’est ce paramètre en forme de feuille (entouré sur la figure ci-avant) : Icone feuillequi permet spécifiquement l’aimantation aux bords, d’autres paramètres permettent l’aimantation aux centres des objets, aux centres de gravité des objets, aux milieux de segments, etc.

Il faut savoir que dans Inkscape le magnétisme des objets entres eux est fixé par défaut. Cela peut être gênant. Voir à droite de l’écran pour l'activer ou le désactiver en cliquant sur la première icône de la barre (surlignée en bleu ici) :

Création du rectangle.

Maintenant pour créer l’en-tête créons un rectangle avec l’outil rectangle présent dans la barre d'outils à gauche :

Barre outil avec outil rectangle

rectangle créé

Astuce : restez appuyer sur Ctrl tout en dimensionnant votre rectangle pour obtenir un carré ( pareil pour l’outil cercle pour obtenir des ronds et non des ellipses).

Vous pouvez alors ensuite déplacer votre rectangle jusqu’au premier bord :

Bords aimantés rectangle et page

puis redimensionner votre rectangle jusqu'à l'autre :

Faites de même pour ajouter la bande en dessous de l'en-tête et la barre verticale de gauche.

Modifier la couleur.

Pour donner une autre couleur que celle fixée par défaut, cliquez sur le rectangle d’intérêt et allez dans fond et contour (Ctrl + shift + F ou clic droit-> fond et contour).

Module fond et contour

Ajout de la photo :

Pré-requis.

Prenons une photo que vous auriez de vous, ici je prends en exemple une de Linus Torvald. Pour notre CV nous voulons la rogner selon une certaine forme.

Pour cela, choisissons un cadre, pour moi ce sera un rond, on a alors :

Photo et rond

CC BY-SA 3.0, GFDL, LINUXMAG.com

Opérations :

La figure, pour ce cas un rond, va servir de cadre de découpe pour la photo (vous pouvez garder celui créé précédemment, j'ai ici changé le contour en rouge et supprimé le fond pour une meilleure visibilité mais cela ne change en rien l'effet).

Autrement dit, le rond va guider la portion de l'image à garder, on le place donc de manière à avoir le contenu souhaité : Exemple de decoupe

Allez ensuite dans l’onglet Objet -> Découpe -> Définir.

 outil decoupe

Résultat :

Rendu de l'image tronquée

 

Avant de passer à la création de texte et son organisation nous allons créer les figurés signalant chaque partie, pour cet exemple ce sera un trait et un rectangle :

Figuré résultat

  • Pour le trait utilisez l’outil courbe de Bezier : un clic pour le début du trait, un autre pour la fin puis clic droit (ou entrée ou espace) pour terminer le trait.

Maintenir Ctrl durant le positionnement du 2e clic (pour la fin ici) permet de tracer un trait horizontal.

  • Pour le rectangle vous connaissez.
  • Pour les assembler proprement et facilement, utilisez le magnétisme pour aimanter les bords :

figuré de partie

Vous pouvez faire dépendre la position de l’un par rapport à l’autre en groupant (Ctrl + G ou clic droit « grouper » en sélectionnant les deux).

 

Pour créer 3 parties avec 3 figures identiques et alignées on duplique celle-ci avec Ctrl + D ou clique droit « Dupliquer » ce qui va créer l’identique par-dessus qu’on peut alors déplacer à la position souhaitée.

Pour gagner du temps et ne pas devoir aligner à posteriori vos figures vous pouvez, après avoir dupliquer, maintenir Ctrl tout en repositionnant votre figure, vers le bas ici, pour garder le même alignement.

Résultat :

J'ai à ce stade pour structure du CV ceci :

CV résultat II

II - Texte, création et encadrement.

Nous pouvons passer au texte !

Nous devons écrire ici les titres des parties, les dates, et les contenus.

Les titres et dates étant de petits textes, nous n'avons pas vraiment besoin de créer de quoi l’organiser selon une forme, de simple retour à la ligne et les paramètres de l’outil texte suffiront.

Outil Texte.

L'outil texte est présent dans la barre d'outils, à gauche de l'interface :

barre outil avec outil texte

Il suffit de cliquer sur l'icône puis à l'endroit où l'on veut pour que le bloc de texte commence. Nous avons alors plusieurs paramètres à notre disposition dans la barre horizontale du haut : Police ; Taille ; Interligne...

parametres outil texte

Nous voulons des blocs de textes qui se ressemblent pour avoir un contenu homogène. Autrement dit, nous devons savoir justifier le texte et avoir une taille identique des blocs de textes. On veut que cela soit dirigé automatiquement et non par nos seuls retours à la ligne.

Encadrement du texte.

Pour cela créons un rectangle qui sera notre guide pour le texte, sélectionnons le texte que l’on veut organiser et appliquons l’encadrement de texte : onglet Texte -> Encadrer.

exemple option encadrer texte

Opérations :

Onglet Texte Encadrer

Résultat :

encadrement résultat

Enfin, rendons rendre invisible notre rectangle : pour cela nous allons alors supprimer son contour dans l’outil « Fond et Contours » avec la petite croix :enlever le contour

Pour avoir d’autres blocs de textes s’organiser selon le même patron il suffit alors de dupliquer (Ctrl +D) le premier texte encadré.

Pour créer d’autres cases de contenus nous devons donc juste Dupliquer Ctrl + D le bloc de texte. Puis le repositionner à la verticale en maintenant Ctrl durant le repositionnement.

Faire de même pour chaque des blocs créés. Voilà ce que l’on peut obtenir après rajout de textes ponctuels pour les titres, dates et descriptions rapides :

Résultat part III

Pour les logos voir fontawesome.com qui proposent une liste gratuite de très nombreux logos simples comme ceux de l'enveloppe ou du téléphone.

III - Alignement et organisation.

Maintenant pour peaufiner les détails, nous pouvons vouloir organiser les espacements entres les blocs de manières automatiques et non à la main.

Nous allons utiliser le module d’alignement : Ctrl + Shift + A ou dans l’onglet Objet -> « Aligner et distribuer ».

Les différents types d’alignements sont représentés. A noter qu’on peut choisir le référentiel : la page ou le premier ou dernier objet sélectionné. On peut aligner en centrant tous les éléments sur le même axe central ou sur un côté, le bas, le haut, etc.

options d'alignement

Répartition équilibrée.

Ici on veut équilibrer les distances entre les différentes parties et les éléments de chaque partie. Pour cela on utilise un des paramètres de distribution :

Option de distribution

Une fois entre les éléments de chaque partie que l’on sélectionne, puis une nouvelle fois entre chaque partie que nous aurons groupées (Ctrl + G) pour avoir un bloc par partie :

Les éléments :

Alignement éléments

Les parties :

 

Organiser parties

Et voilà notre résultat final !

Résultat final

IV - Enregistrer.

Pour l'enregistrer en pdf, rien de plus simple, il suffit de choisir l'extension lors de l'enregistrement :

Enregistrer en PDF

V - Les posters

Voilà un exemple de poster qu'on pourrait réaliser :

 

 

 

 

 

 

 

 

Pour le premier (à gauche), les textes sont tous justifiés après les avoir encadrés, et les parties sont organisées de manières équilibrées.
Pour le second (à droite) voici un poster avec des figures que j'ai pu réaliser avec Inkscape, j'ai dû en supprimer quelques unes mais même tronqué j’estime qu'il est assez parlant pour le présenter.

V - Raccourcis.

Je ne peux que vous conseiller d'apprendre en utilisant les raccourcis pour prendre des habitudes qui vous feront gagner beaucoup de temps.

 Quelques raccourcis utiles :

Copier : Ctrl + c

Coller : Ctrl + v

Dupliquer : Ctrl + d (= Copier/coller en une commande tout en ne modifiant pas un objet copié précédemment via Ctrl + c).

Modifier la taille en gardant les proportions : Ctrl + tirer sur une des flèches à sélectionner.

Modifier l’orientation : sélectionner la figure (= flèches pour tailles) recliquer (= pour l’orientation).

Grouper : Ctrl + g / dégrouper : Ctrl+ Shift + g.

 

Un grand merci aux relectrices ZaZo0o et Nisaea sans qui un travail de qualité n'aurait pas été possible !

L’article Du CV jusqu'au poster avec Inkscape (débutant) est apparu en premier sur bioinfo-fr.net.


Rendre ses projets R plus accessibles grâce à Shiny

$
0
0

Bonjour à tous !

Vous avez un script que vous souhaitez partager avec une équipe expérimentale? Vous ne voulez pas que les utilisateurs modifient le code pour paramétrer votre programme? Vous codez avec R ? Alors cet article est fait pour vous ! Nous allons voir comment créer une application web avec R et permettre à votre utilisateur d’exécuter votre code sans le voir.

Shiny

Le package que nous utiliserons est shiny. Il est proposé par Rstudio (https://shiny.rstudio.com/) et disponible sur le CRAN. Ce package permet de construire des applications web très simplement sans connaissances particulières en HTML et CSS. Les fonctions que nous appellerons dans R vont être traduites en HTML. Par exemple, h1(‘Un titre’) sera transformé en <h1>Un titre</h1>. Il n’est donc pas indispensable de savoir coder en HTML, mais des connaissances dans les langages web pourront vous être utiles dans des cas particuliers, puisqu'il est possible d’intégrer dans l’application shiny du code HTML brut. 

Une application shiny se divise en 2 parties :

  • l’UI : Il s’agit de l’interface utilisateur visible dans une page web. Nous pourrons y retrouver des graphes, des tableaux, du texte, etc. L’utilisateur pourra interagir avec cette interface par le biais de boutons, de sliders, de cases, etc.
  • le serveur : Il s’agit de la « zone de travail ». Tous les calculs, préparations de données ou analyses que R réalisera seront faits côté serveur.

Nous allons voir dans cet article toutes les étapes pour créer une application complète. Elle sera capable de lire un fichier en fonction de paramètres enregistrés par l’utilisateur puis d'afficher :

  • Un tableau avec de la coloration conditionnelle
  • 4 graphiques obtenus par des approches différentes
    • Un classique réalisé avec R. Ce graphique sera paramétrable par l’utilisateur (couleur ou titre par exemple).
    • Un réalisé avec la librairie ggplot2
    • Un dynamique réalisé avec plotly
    • Un dynamique réalisé avec Google

L’ensemble du code permettant de réaliser l’application est disponible sur github : https://github.com/bioinfo-fr/bioinfo-fr_Shiny

Pré-requis

Toutes les étapes pour créer une application Shiny seront détaillées dans ce post. Connaître la syntaxe de R simplifiera grandement la lecture de l'article mais n’est pas indispensable.

Pour réaliser cette application, il vous faudra une version à jour de RStudio (plus simple que la console R). Pour l’installer, suivez les étapes suivantes (l’ordre est important) :

  1. installer R : https://cran.r-project.org/
  2. Installer RStudio : https://www.rstudio.com/products/rstudio/download/

Note : pour les utilisateurs de R les plus avancés, l’application peut être développée dans un environnement virtuel comme docker (sujet de mon prochain post).

Les données

Les données utilisées pour cette application proviennent du tableau IRIS regroupant des mesures sur des fleurs (disponible dans Rdataset et décrit ici https://archive.ics.uci.edu/ml/datasets/iris ). Ce jeu de données est très utilisé pour illustrer les fonctions dans R et pour le machine learning. Le tableau est composé de 5 colonnes :

  • la longueur des sépales ;
  • la largeur des sépales ;
  • la longueur des pétales ;
  • la largeur des pétales ;
  • l’espèce de fleurs.

Un fichier au format txt est disponible ici :
https://github.com/bioinfo-fr/bioinfo-fr_Shiny/blob/master/datasetIris.txt .

Les packages R

Les packages utilisés pour réaliser l’application sont disponibles sur le CRAN. Ils s’installent avec la commande : install.packages(). Les packages que nous utiliserons sont :

  • shiny [1] : Il permettra de construire l’application web
  • shinydashboard [2]: Il permettra de créer une architecture dynamique à la page web avec une zone de titre, une menu rabattable et une zone principale
  • shinyWidgets [3] : Il permettra de mettre un message d’alerte pour confirmer la lecture correcte du tableau
  • DT [4] : Il permettra de créer un tableau dynamique avec de la coloration conditionnelle
  • plotly [5] , ggplot2 [6] et googleVis [7] : Ils nous permettront de réaliser des graphiques
  • colourpicker [8] : Il permettra à l’utilisateur de sélectionner une couleur.

Nous utiliserons pour les installer et les charger un autre package : anylib [9]. Ce package est très pratique car il permet d'installer (si besoin) et de charger une liste de package. En plus, il a été créé par un des auteurs de Bioinfo-fr : Aurelien Chateigner. Que demander de plus!

install.packages("anyLib")
anyLib::anyLib(c("shiny", "shinydashboard", "shinyWidgets", "DT", "plotly", "ggplot2", "googleVis", "colourpicker"))

Création de l’architecture

Mise en place d’un dashboard

Pour mettre en forme notre application web (la partie UI visible par l’utilisateur), nous allons utiliser le package shinydashboard. La documentation est présente ici : https://rstudio.github.io/shinydashboard/index.html .

L'architecture minimale avec shinydasbord est zone de titre (bleue), une barre latérale (noir) et une zone principale (grise).

library(shiny)
library(shinydashboard)

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(),
  dashboardBody()
)

server <- function(input, output) { }

shinyApp(ui, server)
Visualisation de l’application

Test de l’application

Pour tester l’application, il faut sauvegarder le code puis appuyer sur le bouton Run App au dessus à droite de l’éditeur de texte de Rstudio. Un point important, Rstudio reconnaît par défaut les applications qui se nomme app.R. Je vous conseille vivement de nommer votre fichier app.R.

Si vous travaillez avec la console R, vous pouvez lancer la commande suivante :

runApp()

Ajouter un titre

Dans la fonction dashboardHeader, nous ajoutons un titre à l’application (ici bioinfo-fr). Ce titre sera affiché en haut à gauche.

ui <- dashboardPage(
  dashboardHeader(title = "bioinfo-fr"),
  dashboardSidebar( ),
  dashboardBody( )
)
Visualisation de l’application

Ajouter des pages

La première étape est d’ajouter des éléments (item) dans la barre de menu latérale (partie noire). Nous utilisons pour cela la fonction dashboardSidebar. Nous y ajoutons la fonction sidebarMenuqui contient les items du menu.

Ensuite, il faut indiquer que la partie body aura plusieurs pages (des tabItems). Chaque tabItem correspond à une page accessible par le menu. Le menuItem doit avoir le même nom que l’argument tabName de la fonction tabItem pour y accéder (exemple : readData). Dans chaque page, nous ajoutons un titre de niveau 1 (h1). Vous pouvez remarquer l'utilisation de la fonction icon (icon = icon(...)). L'argument de la fonction est un nom d'icône que nous pouvons trouver sur ces deux sites :
https://fontawesome.com/ et
https://getbootstrap.com/docs/4.3/components/alerts/ . En utilisant cette fonction, vous aurez une petite image (icônes) à gauche du nom de l'élément (par exemple un livre pour la lecture des données). Il est aussi possible de l'utiliser pour des boutons .

ui <- dashboardPage(
  dashboardHeader(title = "bioinfo-fr"),
  dashboardSidebar(
    sidebarMenu(
      menuItem("Lecture des données", tabName = "readData", icon = icon("readme")),
      menuItem("Visualisation des données", tabName = "visualization", icon = icon("poll"))
    )
  ),
  dashboardBody(
    tabItems(
      # Read data
      tabItem(tabName = "readData",
              h1("Lecture des données")
      ),
      
      # visualization
      tabItem(tabName = "visualization",
              h1("Visualisation des données")
      )
    )
  )
)
Visualisation de l'application

Création d’un lecteur de fichier

L’objectif est de proposer une interface simple pour lire un fichier dans l’application et qui permette à l’utilisateur de paramétrer la lecture et d’avoir une prévisualisation du fichier lu.

Importer un fichier

Pour importer un fichier, shiny propose la fonction fileInput. Il est possible de faire du “drag and drop” dans la zone de l’import ou de sélectionner un fichier dans l’explorateur de fichiers. Le type de fichier visible est paramétrable dans les arguments. Ici, nous utiliserons le paramétrage par défaut.

ui <- dashboardPage(
  dashboardHeader(title = "bioinfo-fr"),
  dashboardSidebar(
    sidebarMenu(
      menuItem("Lecture des données", tabName = "readData", icon = icon("readme")),
      menuItem("Visualisation des données", tabName = "visualization", icon = icon("poll"))
    )
  ),
  dashboardBody(
    tabItems(
      # Read data
      tabItem(tabName = "readData",
              h1("Lecture des données"),
              fileInput("dataFile",label = NULL,
                        buttonLabel = "Browse...",
                        placeholder = "No file selected")
      ),
      
      # visualization
      tabItem(tabName = "visualization",
              h1("Visualisation des données")
      )
    )
  )
)
Visualisation de l’application

Zone de paramétrage

Nous souhaitons maintenant paramétrer 3 points lors de la lecture du fichier : type de séparateur (virgule, tabulation, espace), type de quote (simple, double, aucune) et la présence/absence des noms de colonnes (header). Nous utilisons pour cela des radio boutons. La fonction utilisée est radioButtons. 5 arguments sont utilisés :

  • id : identifiant du groupe de radio boutons (ici nous avons 3 groupes de radio boutons pour nos 3 paramètres),
  • label : le titre présent au dessus du groupe de radio boutons,
  • choices : les choix possibles dans le groupe de radio boutons. A noter, la zone située à gauche du "=" contient les informations qui seront affichées dans l'application alors que la partie droite indique ce que comprend R côté serveur. Pour le header par exemple, il sera affiché “Yes” côté UI et nous récupérerons côté serveur TRUE (“Yes” = TRUE) lorsque que nous récupérerons la valeur du radio bouton côté serveur.
  • Selected : Nom du radio bouton sélectionné au lancement de l’application
  • inline = T : pour avoir les radio boutons alignés
[…]
tabItem(tabName = "readData",
              h1("Lecture des données"),
              fileInput("dataFile",label = NULL,
                        buttonLabel = "Browse...",
                        placeholder = "No file selected"),
              
              h3("Parameters"),
              
              # Input: Checkbox if file has header
              radioButtons(id = "header", 
                           label = "Header",
                           choices = c("Yes" = TRUE,
                                       "No" = FALSE),
                           selected = TRUE, inline=T),
              
              # Input: Select separator ----
              radioButtons(id = "sep", 
                           label = "Separator",
                           choices = c(Comma = ",",
                                       Semicolon = ";",
                                       Tab = "\t"),
                           selected = "\t", inline=T),
              
              # Input: Select quotes ----
              radioButtons(id = "quote", 
                           label= "Quote",
                           choices = c(None = "",
                                       "Double Quote" = '"',
                                       "Single Quote" = "'"),
                           selected = "", inline=T)
              
      ),

[…]
Visualisation de l'application

Zone de prévisualisation

Dans cette zone, nous allons visualiser les premières lignes du fichier que nous souhaitons lire. Il faut donc :

  • Créer une zone d’affichage dans l’UI
  • Lire les données côté serveur et envoyer les données dans la zone d’affichage

Côté UI

Pour afficher le tableau, nous utilisons la fonction dataTableOutput. Une zone va être créée pour afficher un tableau. Nous donnons à cette zone un identifiant en utilisant l’argument outputId . Cet identifiant est indispensable pour retrouver la zone côté serveur.

tabItems(
      # Read data
      tabItem(tabName = "readData",
              h1("Lecture des données"),
              fileInput("dataFile",label = NULL,
                        buttonLabel = "Browse...",
                        placeholder = "No file selected"),
              
              h3("Parameters"),
              
              # Input: Checkbox if file has header
              radioButtons(inputId = "header", 
                           label = "Header",
                           choices = c("Yes" = TRUE,
                                       "No" = FALSE),
                           selected = TRUE, inline=T),
              
              # Input: Select separator ----
              radioButtons(inputId = "sep", 
                           label = "Separator",
                           choices = c(Comma = ",",
                                       Semicolon = ";",
                                       Tab = "t"),
                           selected = "t", inline=T),
              
              # Input: Select quotes ----
              radioButtons(inputId = "quote", 
                           label= "Quote",
                           choices = c(None = "",
                                       "Double Quote" = '"',
                                       "Single Quote" = "'"),
                           selected = "", inline=T),
              h3("File preview"),
              dataTableOutput(outputId = "preview")
              
      ),

Côté Server

Nous souhaitons à présent afficher de l’information. Du côté serveur, pour envoyer de l’information, la syntaxe commence quasiment toujours par output$ puis l’ID de la zone de sortie (ici notre tableau de prévisualisation avec l’id "preview"). Ce que nous souhaitons lui envoyer est un tableau. Nous utilisons donc la fonction <- renderDataTable({ }). Dans cette dernière fonction, nous allons lire le tableau qui va être renvoyé. Pour récupérer de l’information du côté UI, il faut utiliser la syntaxe suivante : input$ID. Par exemple, nous souhaitons récupérer le choix de l'utilisateur concernant le header : input$header.

output$preview <-  renderDataTable({
    
    req(input$dataFile)
    
    df <- read.csv(input$dataFile$datapath,
                   header = as.logical(input$header),
                   sep = input$sep,
                   quote = input$quote,
                   nrows=10
    )
  },  options = list(scrollX = TRUE , dom = 't'))

Si nous détaillons le code :

  • req(input$dataFile) : bloque la suite du code si la zone d’import de fichier est vide
  • df <- read.csv() : on stocke dans df la lecture du fichier
  • input$dataFile$datapath : chemin d’accès au fichier importé
  • header = as.logical(input$header) : récupération de la réponse de l’utilisateur pour savoir si présence ou absence d’un header. Le as.logical permet de convertir un TRUE ou FALSE en booléen.
  • sep = input$sep, quote = input$quote : récupération du paramétrage de l’utilisateur pour le séparateur et les quotes. Ces informations sont données aux arguments de la fonction read.csv()
  • nrows=10 : Nous ne souhaitons pas lire tout le fichier. Seules les premières lignes sont nécessaires pour savoir si le tableau est lu correctement ou non. Nous lisons donc les 10 premières lignes.
  • options = list(scrollX = TRUE , dom = 't') : Si le tableau a de nombreuses colonnes, cette option permet d’avoir un scroll horizontal

Vous pouvez maintenant tester sur un fichier texte contenant un tableau. Le changement de paramétrage a un effet direct sur la visualisation.

Visualisation de l'application

Organisation des éléments

Pour les connaisseurs de bootstrap, Shiny intègre son code. Pour les autres, il est possible d’organiser le contenu d’une page à l’aide d’une grille. La grille est composée de lignes ( fluidRow()) elles-mêmes composées de 12 blocs. Nous allons placer les paramètres et la prévisualisation sur une même ligne. Nous souhaitons stocker les paramètres dans  3 blocs (column(3,...) : la colonne aura une taille de 3 blocs) et 9 blocs pour la prévisualisation ( column(9,...) ).

tabItem(tabName = "readData",
              h1("Lecture des données"),
              fileInput("dataFile",label = NULL,
                        buttonLabel = "Browse...",
                        placeholder = "No file selected"),
              
              fluidRow(
                column(3,
                       h3("Parameters"),
                       
                       # Input: Checkbox if file has header
                       radioButtons(inputId = "header", 
                                    label = "Header",
                                    choices = c("Yes" = TRUE,
                                                "No" = FALSE),
                                    selected = TRUE, inline=T),
                       
                       # Input: Select separator ----
                       radioButtons(inputId = "sep", 
                                    label = "Separator",
                                    choices = c(Comma = ",",
                                                Semicolon = ";",
                                                Tab = "t"),
                                    selected = "t", inline=T),
                       
                       # Input: Select quotes ----
                       radioButtons(inputId = "quote", 
                                    label= "Quote",
                                    choices = c(None = "",
                                                "Double Quote" = '"',
                                                "Single Quote" = "'"),
                                    selected = "", inline=T)
                ),
                column(9,
                       h3("File preview"),
                       dataTableOutput(outputId = "preview")
                )
              )
Visualisation de l'application

Bouton de lecture

Pour finir avec cette page, nous allons créer un bouton pour valider le paramétrage de la lecture du tableau. En cliquant sur ce bouton, l’ensemble du fichier sera lu. Nous ne réalisons pas une lecture dynamique comme précédemment. En effet,  à chaque changement de paramètre, l’ensemble du fichier est relu. Si le fichier est gros, le temps de lecture sera long.

Côté UI

Nous ajoutons un actionButton. L’identifiant de notre bouton est "actBtnVisualisation".

[...]
actionButton(inputId = "actBtnVisualisation", label = "Visualisation",icon = icon("play") )
[...]

Pour une question esthétique, nous ajoutons un saut de ligne avant le bouton et nous mettons le bouton dans une division pour pouvoir le centrer :

[...]
tags$br(),
div(actionButton(inputId = "actBtnVisualisation", label = "Visualisation",icon = icon("play") ), align = "center")
[...]

Côté serveur

Lorsque que le bouton est cliqué, nous souhaitons à présent que le contenu du fichier soit stocké dans une variable. Il s’agit d’une variable particulière. Elle doit être visible par toutes les fonctions côté serveur et relancer toutes les fonctions qui l’utilisent si elle change. Il s’agit d’une variable réactive (reactiveValues). Si nous détaillons le code :

  • Nous déclarons une reactiveValue avec comme nom data.
  • Nous allons utiliser une fonction qui permet d’attendre une action particulière. Ici nous attendons que l’utilisateur clique sur le bouton. Une fois que le bouton a été cliqué, le code entre les { } sera exécuté. Ici, l’objectif sera de stocker le contenu du fichier importé dans la reactiveValue sous le nom table (data$table)
data = reactiveValues()

  observeEvent(input$actBtnVisualisation, {
    data$table = read.csv(input$dataFile$datapath,
                          header = as.logical(input$header),
                          sep = input$sep,
                          quote = input$quote,
                          nrows=10)
  })

Ainsi, à chaque clic du bouton, data$table sera mis à jour ainsi que toutes les fonctions qui l’utilise (ex : des graphiques).
Nous pouvons aussi ajouter un message pour confirmer la lecture du fichier. Nous utiliserons sendSweetAlert proposé dans le package shinyWidgets. La documentation est disponible ici : https://github.com/dreamRs/shinyWidgets .

observeEvent(input$actBtnVisualisation, {
    data$table = read.csv(input$dataFile$datapath,
                          header = as.logical(input$header),
                          sep = input$sep,
                          quote = input$quote,
                          nrows=10)
    sendSweetAlert(
      session = session,
      title = "Done !",
      text = "Le fichier a bien été lu !",
      type = "success"
    )  
  })
Visualisation du message

Changement de page

Enfin, notre application étant composée de 2 pages, nous souhaitons changer de page une fois que le fichier est lu pour arriver sur la page de visualisation.

updateTabItems(session, "tabs", selected = "visualization")

Pour rappel, “tabs” est l’identifiant de notre sidebarMenu. Nous allons avec cette commande chercher dans la sidebarMenu la page qui a comme identifiant “visualization” et changer de page.

Visualisation

Exploration du tableau

Nous allons à présent afficher le tableau complet. Nous utilisons pour cela le package DT (https://rstudio.github.io/DT/ ). Il permet de rechercher, sélectionner ou trier les informations d'un tableau de données. Il faut pour cela créer une zone où sera affiché le tableau dans l’UI.

Côté UI

tabItem(tabName = "visualization",
              h1("Visualisation des données"),
              h2("Exploration du tableau"),
              dataTableOutput('dataTable')
      )

Puis du côté serveur, il ne reste plus qu’à envoyer le contenu de notre fichier dans ce tableau par le biais de la reactiveValue. Ainsi, le tableau sera automatiquement mis à jour si un nouveau fichier est lu.

output$dataTable = DT::renderDataTable(data$table)
Visualisation de l'application

Il est possible de faire de la mise en forme conditionnelle comme dans excel. Le code proposé par la suite est dépendant du tableau utilisé. En effet, nous allons cibler les colonnes d’intérêt par leur nom pour une question de lisibilité.

Voici une proposition de mise en forme conditionnelle de notre tableau (inspiré de l’exemple proposé dans la documentation du package DT).

  • Histogramme des valeurs pour les colonnes Sepal.length et Petal.length
  • Coloration par seuils multiples pour les colonnes Sepal.width et Petal.width (fond blanc écriture noire, fond rouge écriture blanche et fond rouge foncé écriture blanche)
  • Coloration du fond en fonction de l’espèce pour la colonne espèce.
output$dataTable = DT::renderDataTable({
    datatable(data$table, filter = 'top') %>% 
      formatStyle('Sepal.Length', 
                  background = styleColorBar(data$table$Sepal.Length, 'lightcoral'),
                  backgroundSize = '100% 90%',
                  backgroundRepeat = 'no-repeat',
                  backgroundPosition = 'center'
      ) %>%
      formatStyle(
        'Sepal.Width',
        backgroundColor = styleInterval(c(3,4), c('white', 'red', "firebrick")),
        color = styleInterval(c(3,4), c('black', 'white', "white"))
      ) %>%
      formatStyle(
        'Petal.Length',
        background = styleColorBar(data$table$Petal.Length, 'lightcoral'),
        backgroundSize = '100% 90%',
        backgroundRepeat = 'no-repeat',
        backgroundPosition = 'center'
      ) %>%
      formatStyle(
        'Petal.Width',
        backgroundColor = styleInterval(c(1,2), c('white', 'red', "firebrick")),
        color = styleInterval(c(1,2), c('black', 'white', "white"))
      ) %>%
      formatStyle(
        'Species',
        backgroundColor = styleEqual(
          unique(data$table$Species), c('lightblue', 'lightgreen', 'lavender')
        )
      )
  })
Visualisation de l'application

Enfin, pour améliorer l’exploration, il est possible d’ajouter des filtres par colonnes. Pour les valeurs numériques, les données sont filtrées par un slider. Pour les colonnes contenant du texte, il y a deux possibilités :

  • Peu de variabilité entre les éléments. Par exemple, la colonne Species ne contient que 3 éléments différents : setosa, versicolor et virginica. Dans ce cas, le filtre sera composé des éléments uniques de cette colonne qui seront cliquables. En les cliquant, toutes les lignes avec cet élément seront sélectionnées.  
  • Grande variabilité entre les éléments. Dans ce cas, une zone pour entrer du texte sera proposée. Le texte saisi sera recherché dans la colonne.
output$dataTable = DT::renderDataTable({
    datatable(data$table, filter = 'top') %>% 
     [...]
Visualisation de l'application

Visualisation graphique

Nous allons créer de 4 façons différentes des graphiques et les afficher dans l’application shiny :

  • des graphiques statiques
    • un plot de base avec R
    • un graphique avec ggplot2
  • des graphiques dynamiques
    • Avec plotly
    • Avec google

Les graphiques seront représentés sur la même ligne avec une fluidRow et 4 colonnes (comme nous avons fait précédemment).

Graphique R

R propose une grande palette de graphiques de base. Cependant, il s'agit uniquement de graphiques statiques.

Côté UI

Il faut comme précédemment créer une zone pour indiquer où va être affiché le graphique. La fonction utilisée est plotOutput.

tabItem(tabName = "visualization",
              h1("Visualisation des données"),
              h2("Exploration du tableau"),
              dataTableOutput('dataTable'),
              h2("Graphiques"),
             fluidRow(
                column(3,plotOutput("plotAvecR") )
              )
      )

Côté serveur

Nous allons pour ce graphique comparer la corrélation entre la taille des sépales et des pétales. Comme pour le tableau, la syntaxe est la suivante pour envoyer de l’information du côté UI : output$ID_de_la_zone. Pour envoyer un plot, nous utilisons la fonction renderPlot. Dans cette fonction, vous pouvez mettre n’importe quel graphique de R. Afin de mettre à jour automatiquement les graphiques, nous utilisons notre reactiveValue : data. Chaque fois que data changera, le plot sera généré de nouveau. Pour accéder au contenu du fichier lu qui est stocké dans la reactiveValue data sous le nom de table, nous utilisons de nouveau la syntaxe suivante : data$table. Il s’agit d’un dataframe (la lecture par read.csv2 renvoie un dataframe). Les colonnes sont donc accessibles par un $ puis le nom. Au final, pour obtenir le vecteur contenant les valeurs de longueur des pétales, nous utiliserons la syntaxe suivante : data$table$Petal.Length.

output$plotAvecR <- renderPlot({
    plot(data$table$Petal.Length,data$table$Sepal.Length, 
         main = "Sepal length vs Petal length (R)",
         ylab = "Sepal length",
         xlab = "Petal length")
  })

Le paramétrage du plot est libre et n’est pas contraint par shiny.

Visualisation de l'application

Graphique par ggplot2

Ggplot2 est une librairie graphique de plus en plus utilisée. Elle propose des graphiques plus évolués que ceux de base dans R. Vous trouverez une documentation très bien faite ici : https://ggplot2.tidyverse.org/. Nous allons comparer les largeurs et les longueurs des sépales. Une coloration en fonction de l’espèce est proposée.

Côté UI

Comme toujours, nous allons créer une zone où sera affiché le graphique. La fonction utilisée est encore plotOutput.

fluidRow(
                column(3,plotOutput("plotAvecR")),
                column(3, plotOutput("plotAvecGgplot2"))
              )

Côté serveur

Nous allons procéder de la même façon que précédemment. La différence est liée au contenu de la fonction renderPlot. Nous allons cette fois-ci utiliser les fonctions de ggplot2.

output$plotAvecGgplot2 <- renderPlot({
    ggplot(data=data$table, aes(x = Sepal.Length, y = Sepal.Width)) + 
      geom_point(aes(color=Species, shape=Species)) +
      xlab("Sepal Length") +  ylab("Sepal Width") +
      ggtitle("Sepal Length-Width (ggplot2")
  })
Visualisation de l'application

Graphique Plotly

Plotly est un package de j’affectionne particulièrement. Il propose énormément d’outils préprogrammés (enregistrement de l’image, zoom, informations supplémentaires). De plus, il n’est pas exclusivement réservé à R. Il est possible de l’utiliser aussi dans des projets en JS et en python (aussi simple d’utilisation).

Côté UI

De nouveau, nous allons créer une zone pour afficher le graphique. Attention, nous changeons de fonction. Nous utiliserons cette fois plotlyOutput.

fluidRow(
                column(3, plotOutput("plotAvecR")),
                column(3, plotOutput("plotAvecGgplot2")),
                column(3, plotlyOutput("plotAvecPlotly"))
              )

Côté serveur

Je n’expliquerai pas ici la syntaxe pour réaliser un graphique avec Plotly. La documentation sur le site est extrêmement bien faite avec de très nombreux exemples (https://plot.ly/r/). Vous pouvez mettre n’importe quel graphique plotly dans la fonction. Ici, nous comparons la largeur et la longueur des pétales.

plot_ly(data = data$table, x = ~ Petal.Length, y = ~ Petal.Width, color = ~ Species) %>%
        layout(title = 'Petal Length-Width (plotly)',
               yaxis = list(title = "Petal width"),
               xaxis = list(title = "Petal length"))

Je vous invite lorsque vous lancerez l’application à survoler ce graphique. Il y a énormément d’informations disponibles et d’outils d’exploration.

Graphique Google

Pour finir, les graphiques de Google sont de plus en plus populaires et offrent un plus large choix de représentations que Plotly (calendrier, etc.). Ici, nous allons réaliser un histogramme de la largeur des pétales.

Côté UI

Nous créons de nouveau une zone pour afficher le graphique. La fonction utilisée est htmlOutput. Cette fonction est capable d’interpréter du code HTML venant du serveur. Si vous souhaitez écrire du HTML directement dans la partie UI, il vous suffit d’utiliser la fonction HTML (ex : HTML(“<h1>Titre 1</h1>”) ) .

fluidRow(
                column(3, plotOutput("plotAvecR")),
                column(3, plotOutput("plotAvecGgplot2")),
                column(3, plotlyOutput("plotAvecPlotly")),
                column(3, htmlOutput("plotAvecGoogle"))
              )

Côté serveur

Pour les graphiques Google, nous utilisons les fonctions graphiques commençant par gvis et le rendu est fait avec la fonction renderGvis. Elles sont détaillées à la page suivante https://cran.r-project.org/web/packages/googleVis/vignettes/googleVis_examples.html .

output$plotAvecGoogle <- renderGvis({
      gvisHistogram(as.data.frame(data$table$Petal.Width),
                    options=list(title ="Petal width (Google)",
                                 height=400)
      )
  })

Visualisation de l’application

Visualisation du l'application

Gérer le tableau vide

En lançant l’application, si vous vous rendez sur la partie visualisation, vous trouverez plein d’erreurs. Ces erreurs sont la cause de l’utilisation d’une reactiveValue. En effet, lorsque rien n’a encore été lu, data$table est NULL (vide). Or toutes les fonctions que nous utilisons ne gèrent pas les NULL. Nous ajouterons pour le tableau et les graphiques un peu de code pour lui dire de renvoyer NULL si le tableau est vide.

if (!is.null(data$table)) {
    [représentation graphique ou le tableau]
} else {
    NULL
}

Interagir avec les graphique

Nous allons voir deux types d'interactions avec les graphiques pour illustrer la simplicité pour l’utilisateur d'interagir avec les données et les représentations :

  • Sélectionner les données à afficher à l’aide du tableau
  • Changer des paramètres graphiques sur le plot de base proposé par R (le premier graphique). Tous ces changements peuvent bien sûr être appliqués sur tous les graphiques.

Sélectionner les données à afficher à l’aide du tableau

Grâce à Shiny, il est possible de faire communiquer le tableau avec les graphiques. Nous profitons pour cela de la puissance du package DT qui génère le tableau. Les modifications que nous allons réaliser seront uniquement côté serveur. L'objectif est de récupérer les lignes qui sont affichées dans le tableau et de n'utiliser que ces lignes dans les graphiques. Comme précédemment, pour récupérer de l’information dans l’UI, il faut utiliser input$ID_ZONE. Nous souhaitons récupérer de l'information de notre tableau qui a comme identifiant dataTable. Ensuite, nous ajoutons _rows_all à la fin de l’ID pour obtenir les lignes. Ainsi, avec input$dataTable_rows_all, nous avons les lignes affichées dans le tableau. Il ne reste plus qu’à les sélectionner dans le vecteur de données.  Le graphique est à présent dynamique.

output$plotAvecR <- renderPlot({
    if (!is.null(data$table)) {
      plot(data$table$Petal.Length[input$dataTable_rows_all],
           data$table$Sepal.Length[input$dataTable_rows_all], 
           main = "Sepal length vs Petal length (R)",
           ylab = "Sepal length",
           xlab = "Petal length")
    } else {
      NULL
    }
  })

La même démarche est ensuite appliquée aux autres graphiques. Grâce aux filtres du tableau, nous avons ainsi la possibilité de sélectionner par les données numériques (longueur et largeur) et par l’espèce.

Changement de couleur pour le graphique de base R

L’objectif est de vous montrer une autre façon d'interagir avec les graphiques. En effet, il se peut que vous n’utilisiez pas de tableau dans votre application. De très nombreux exemples sont disponibles en ligne (ici par exemple : https://shiny.rstudio.com/gallery/). Nous allons implémenter 4 changements sur ce graphique pour vous donner des exemples d’utilisation d’inputs :

  • Changement de la couleur des points (avec l’utilisation d’un colour picker capable de gérer la transparence)
  • Changement du type de point
  • Changement de la taille des points
  • Changement du titre

Côté UI

Pour plus de lisibilité lors de l’utilisation, nous avons changé la disposition des graphiques pour avoir sur une ligne le graphique R avec ses paramètres et sur une seconde les trois autres graphiques. Vous pouvez ainsi voir la simplicité de la réorganisation d’une page à l’aide du système de Grid.

tabItem(tabName = "visualization",
              h1("Visualisation des données"),
              h2("Exploration du tableau"),
              dataTableOutput('dataTable'),
              h2("Graphiques"),
              fluidRow(
                column(4, plotOutput("plotAvecR")),
                column(4, colourpicker::colourInput("colR", "Couleur graphique R", "black",allowTransparent = T),
                       sliderInput("cex", "Taille",
                                   min = 0.5, max = 3,
                                   value = 1,step = 0.2
                                  )),
                column(4, selectInput(inputId = "pch", choices = 1:20, label = "Type de points",selected = 1),
                       textInput("title", "Titre", "Sepal length vs Petal length (R)") )
              ),
              tags$br(), 
              fluidRow(
                column(4, plotOutput("plotAvecGgplot2")),
                column(4, plotlyOutput("plotAvecPlotly")),
                column(4, htmlOutput("plotAvecGoogle"))
              )
      )

Pour faire entrer de l’information, nous avons besoin de 4 fonctions input : colourInput pour la couleur (du package colourpcicker), sliderInput pour la taille des points, selectInput pour le type de points et textInput pour le titre du graphique.

Côté serveur

Nous allons récupérer les entrées et les intégrer dans notre plot.

plot(data$table$Petal.Length[input$dataTable_rows_all],
           data$table$Sepal.Length[input$dataTable_rows_all], 
           main = input$title,
           ylab = "Sepal length",
           xlab = "Petal length",
           pch = as.numeric(input$pch),
           col = input$colR, 
           cex = input$cex)

Visualisation dans l’application

Visualisation de l'application

Conclusion

Et voilà ! Vous avez réalisé une application complète capable de lire un fichier en fonction de paramètres et d'explorer ses données. Vous trouverez l’ensemble du code sur github ici :
https://github.com/bioinfo-fr/bioinfo-fr_Shiny . A travers ce post, nous avons vu comment rendre interactive l’exploration d’un tableau de données à l’aide de Shiny. Vos utilisateurs n’auront plus à voir votre code. Ils auront simplement à appuyer sur Run App. Il existe de nombreuses solutions de partage (https://shiny.rstudio.com/tutorial/written-tutorial/lesson7/).  De nombreuses autres possibilités sont disponibles et pourront être détaillées dans d’autres articles (concatémérisation et intégration continue d’une application Shiny, par exemple).

Merci à mes relecteurs Aurélien C. et Ismaël P. pour leur aide !

Versions des outils utilisés

R version 3.5.1 (2018-07-02)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Debian GNU/Linux 9 (stretch)

Matrix products: default
BLAS: /usr/lib/openblas-base/libblas.so.3
LAPACK: /usr/lib/libopenblasp-r0.2.19.so

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=C             
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C               LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] parallel  stats4    stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] bindrcpp_0.2.2              shinycssloaders_0.2.0       shinyjs_1.0                 colourpicker_1.0            shinyWidgets_0.4.4          reshape2_1.4.3             
 [7] plotly_4.8.0                ggplot2_3.1.0               FactoMineR_1.41             DT_0.5                      DESeq2_1.22.2               SummarizedExperiment_1.12.0
[13] DelayedArray_0.8.0          BiocParallel_1.16.5         matrixStats_0.54.0          Biobase_2.42.0              GenomicRanges_1.34.0        GenomeInfoDb_1.18.1        
[19] IRanges_2.16.0              S4Vectors_0.20.1            BiocGenerics_0.28.0         shinydashboard_0.7.1        shiny_1.2.0                

loaded via a namespace (and not attached):
 [1] bitops_1.0-6           bit64_0.9-7            RColorBrewer_1.1-2     httr_1.4.0             tools_3.5.1            backports_1.1.3        R6_2.3.0              
 [8] rpart_4.1-13           Hmisc_4.1-1            DBI_1.0.0              lazyeval_0.2.1         colorspace_1.3-2       nnet_7.3-12            withr_2.1.2           
[15] tidyselect_0.2.5       gridExtra_2.3          bit_1.1-14             compiler_3.5.1         htmlTable_1.13.1       flashClust_1.01-2      scales_1.0.0          
[22] checkmate_1.9.0        genefilter_1.64.0      stringr_1.3.1          digest_0.6.18          foreign_0.8-70         XVector_0.22.0         base64enc_0.1-3       
[29] pkgconfig_2.0.2        htmltools_0.3.6        htmlwidgets_1.3        rlang_0.3.0.1          rstudioapi_0.8         RSQLite_2.1.1          bindr_0.1.1           
[36] jsonlite_1.6           acepack_1.4.1          dplyr_0.7.8            RCurl_1.95-4.11        magrittr_1.5           GenomeInfoDbData_1.2.0 Formula_1.2-3         
[43] leaps_3.0              Matrix_1.2-14          Rcpp_1.0.0             munsell_0.5.0          yaml_2.2.0             scatterplot3d_0.3-41   stringi_1.2.4         
[50] MASS_7.3-50            zlibbioc_1.28.0        plyr_1.8.4             grid_3.5.1             blob_1.1.1             promises_1.0.1         crayon_1.3.4          
[57] miniUI_0.1.1.1         lattice_0.20-35        splines_3.5.1          annotate_1.60.0        locfit_1.5-9.1         knitr_1.21             pillar_1.3.1          
[64] geneplotter_1.60.0     XML_3.98-1.16          glue_1.3.0             latticeExtra_0.6-28    data.table_1.11.8      httpuv_1.4.5           gtable_0.2.0          
[71] purrr_0.2.5            tidyr_0.8.2            assertthat_0.2.0       xfun_0.4               mime_0.6               xtable_1.8-3           later_0.7.5           
[78] viridisLite_0.3.0      survival_2.42-3        tibble_1.4.2           AnnotationDbi_1.44.0   memoise_1.1.0          cluster_2.0.7-1

Bibliographie

[1]Winston Chang, Joe Cheng, JJ Allaire, Yihui Xie and Jonathan McPherson (2018). shiny: Web Application Framework for R. R package version 1.2.0.https://CRAN.R-project.org/package=shiny
[2]Winston Chang and Barbara Borges Ribeiro (2018). shinydashboard: Create Dashboards with 'Shiny'. R package version 0.7.1. https://CRAN.R-project.org/package=shinydashboard
[3]Victor Perrier, Fanny Meyer and David Granjon (2018). shinyWidgets: Custom Inputs Widgets for Shiny. R package version 0.4.4. https://CRAN.R-project.org/package=shinyWidgets
[4]Yihui Xie, Joe Cheng and Xianying Tan (2018). DT: A Wrapper of the JavaScript Library 'DataTables'. R package version 0.5. https://CRAN.R-project.org/package=DT
[5]Carson Sievert (2018) plotly for R. https://plotly-book.cpsievert.me
[6]H. Wickham. ggplot2: Elegant Graphics for Data Analysis. Springer-Verlag New York, 2016.
[7]Markus Gesmann and Diego de Castillo. Using the Google Visualisation API with R. The R Journal, 3(2):40-44, December 2011.
[8]Dean Attali (2017). colourpicker: A Colour Picker Tool for Shiny and for Selecting Colours in Plots. R package version 1.0. https://CRAN.R-project.org/package=colourpicker
[9]Aurelien Chateigner (2018). anyLib: Install and Load Any Package from CRAN, Bioconductor or Github. R package version 1.0.5.
https://CRAN.R-project.org/package=anyLib

L’article Rendre ses projets R plus accessibles grâce à Shiny est apparu en premier sur bioinfo-fr.net.

Télécharger des données de séquençage sur le NCBI.. pour les débutants!

$
0
0

Toi petit étudiant de M1 qui arrive en premier jour de stage... Viens par ici... Oui TOI ! Toi à qui ton maître de stage te demande de récupérer les données de séquençage d'un article vachement bien, sans que tu saches le faire... TOI!

Toi le physicien qui se met à la biologie mais qui ignore comment les bio-informaticiens rangent les données...  VOUS ! VOUS RESTEZ ICI, TOUT de suite !

Aujourd'hui, on va parler de l'archivage des données de génomique. Comment sauvegarde-t-on les séquences d'ADN ? Quels outils existent pour ça ? Et quel est le vocabulaire à avoir pour comprendre ce que font ces outils et où chercher l'information ? Si tu lis correctement ces lignes et que tes données sont stockées sur le NCBI... NORMALEMENT tu ne devrais plus perdre 2 h à chercher une information dans une page qu'un œil averti trouve en deux minutes.

Seul pré-requis pour lire cet article : savoir ce qu'est un fichier FASTQ et ce qu'est le séquençage haut débit. Cet article s'inspire assez largement de ce tutoriel, une lecture très saine ! Un bon moyen de se mettre en jambe pour lire cet article si tu débute aussi, c'est cet autre article écrit par un copain du blog!

Allons-y !

Hé mais cette publication a des données vachement bien. Je récupère ça comment ?

La nomenclature indispensable

Pour une publication dans un journal obligeant la publication des données, le lien vers les données est le plus souvent fait via une référence sur la plateforme GEO. Afin de ne pas se perdre dans toutes les pages qu'il est possible de parcourir dans cette base, voici un petit guide des identifiants utiles sur les pages.

  • GEO (Gene Expression Omnibus) : Base de donnée du NCBI regroupant toutes les données et méta-données autour des jeux de données associés à des publications scientifiques.
  • GSE (Genomic Spatial Event) : Identifiant unique servant à retrouver l'ensemble des données produites lors d'une publication.
  • GSM (GEO Sample) : Expériences faisant partie d'un GSE. Pour chaque GSM, une page web contenant le détail spécifique de l'expérience fournie est disponible. Dans le cas où une analyse contiendrait beaucoup de fichiers, il est possible que celle-ci ait plusieurs GSM. Cela est alors indiqué sur la page web liée au GSE.

Impression écran retouchée sauvagement pour se concentrer sur ce qui nous intéresse : ici, aller voir le détail des expériences qui nous intéresse en cliquant sur le GSM.

  • SRA : Base de données de stockage des données haut débit. Utilise un format éponyme pour cela (.sra) qui est une compression "optimale" des fichiers FASTQ. Il est possible de stocker du sam en SRA aussi.

Contenu d'une page SRX, indiquant les fichiers SRA à télécharger !

  • SRX (SRA eXperiment): Identifiant de regroupement de tous les SRA associés à un GSE. Si tout va bien, il existe un identifiant SRX pour un GSM.

Contenu typique d'une page d'un GSM. Pour aller chercher les données, il faut aller voir du côté du SRX!  (Non ce n'est pas la maison qui rend fou d'Astérix, juste une maison bien rangée !).

Trouver le lien de téléchargement

Dans ce cas ou les auteurs d'une publication ont fourni leur donnée sur GEO, il suffit de chercher le GSE  présent dans l’article pour retrouver les données. Il se situe en général à la fin juste avant les remerciements. Un petit ctrl+f  sur "GSE" et hop, vous voila sur une page complexe contenant toutes les données utilisées pendant une publication. Si l'url est absent, il suffit de prendre l'identifiant et de le mettre sur le site de GEO pour retomber sur ses pattes. L'objectif à partir d'ici sera de retrouver les fichiers liés à l'expérience qui vous intéresse. Pour ça, analysons un peu les mots-clefs présents sur cette page!

Petit exemple de page du NCBI pour le GSE93431, au delà de toutes les indications de protocole (à lire ATTENTIVEMENT), la partie intéressante (les données) se trouvent dans les GSM.

Si tu as suivi jusque-là, quand tu es sur une page pour un GSE précis, voilà ce qu'il faut faire :

  • cliquer sur les GSM de l'expérience qui t'intéresse
  • ensuite aller sur la page SRX qui t'intéresse
  • voir les identifiants SRA que tu veux télécharger sans trop savoir comment

Maintenant, voyons quoi faire avec les identifiants SRA pour récupérer ces fichiers.

Utiliser la SRA toolkit

A ce stade, tu disposes d'une liste de noms de fichiers SRA sans trop savoir comment télécharger les fichiers qui vont avec. Deux options : aller sur le serveur FTP du NCBI et jouer avec (à la main ou en ligne de commande interrogeant via FTP), ou utiliser la SRA toolkit. Dans tous les cas, le SRA toolkit va vite devenir indispensable, il permet de décompresser les fichiers SRA vers le format FASTQ.

Première étape, l'installation, pour ça le tutoriel fourni sur le site est largement suffisant. Ensuite :

  • Si tu veux télécharger les fichiers FASTQ directement en ligne de commande (j'ai mis un nom SRA au hasard) :

sratoolkit/bin/fastq-dump SRR238427

Ici l'outil va directement chercher le fichier .sra dans sa base de donnée plutôt que dans ton répertoire courant, le télécharger dans ton .cache temporairement si tu es sur une distribution Linux et l'extraire en FASTQ au passage là où tu te situes.

  • Si tu as déjà téléchargé tes fichiers SRA et que tu veux les décompresser :

Utilise ton terminal et là il te faudra utiliser la commande "fastq-dump" de la SRA toolkit. Si tes reads sont appairés, l'option "--split-files" te permet de séparer les deux paires. Si jamais le fichier contient des reads sans paire l'option "split-3" te permet de séparer tes reads en 3 fichiers et enlever les reads sans paire. Exemple d'utilisation :

sratoolkit/bin/fastq-dump SRR238427.sra

Et voila, normalement tu as tes fichiers de séquençage!

Si JAMAIS les données que tu télécharges te semblent louches, que le fichier FASTQ que tu extrais a un nombre de ligne qui n'est pas un multiple de 4, que la boîte à outil te retourne une erreur étrange, quelques conseils :

  1. Vérifie sur la page SRX la taille du fichier que tu es supposé télécharger et compare la à ce que tu as toi pour vérifier si il y a eu une erreur de transfert.
  2. Vérifie tes options de décompression du fichier sra : Sont-elles cohérentes par rapport aux protocoles décrits dans la publication et sur la page GSM ?
  3. Cherche sur google l'erreur que tu as  (dans le doute)
  4. Si JAMAIS toutes tes vérifications ne t'apportent rien : contacte la plateforme du NCBI, ils sont gentils et répondent vite. Si jamais tu trouves une erreur sur des données (ça m'est arrivé), ils corrigent ça vite. Si tu as un bug sur la SRA-toolkit aussi ils sont de bon conseil là-dessus.

En conclusion

Derrière un effort commun international pour archiver correctement les données, être capable de retrouver une information pour l'extraire reste une tâche complexe. Pour des données issues de séquençage haut débit, un bel effort a été produit donnant des pages où il est facile de retrouver les informations clefs. L'expérience nécessaire pour trouver cette information est un peu coûteuse, mais 2h de perdues une fois vaut bien des centaines d'heures de gagnées ensuite non?

Merci aux relecteurs et admins intervenus pour publier cet article : Jnsll, ZaZo0o et Gwenaëlle.

PS : Je jure en toute bonne foi que l'introduction de ce billet a été écrite avec (beaucoup) de second degré, et ne résulte en rien du nombre incessant de biophysiciens retrouvés perdus sur des opérations simples. J'espère que ces lignes les aideront à ne plus venir m’embêter  gagner un temps précieux au quotidien.

Groumpf

L’article Télécharger des données de séquençage sur le NCBI.. pour les débutants! est apparu en premier sur bioinfo-fr.net.

Les problèmes limités par les entrées/sorties (IObound)

$
0
0

Dans la première partie de ce tutoriel , j'ai expliqué ce qu’était la programmation concurrente et parallèle, ainsi que détaillé les différents types de programmation concurrente et leurs spécificités. Si vous ne l'avez pas lue, je vous conseille de la lire avant de démarrer. Dans cette deuxième partie, nous allons nous concentrer sur l'optimisation d'un programme limité par les entrées/sorties grâce à la programmation concurrente.

Qu'est ce qu'un programme limités par les entrées/sorties ?

C'est un programme qui communique régulièrement avec une ressource plus lente que le CPU (accès au disque dur, bande passante d'un réseau, etc.). Lors de ces communications le CPU va attendre que la ressource en question lui envoie des informations. C'est cette attente qui est un des facteurs d'augmentation du temps d’exécution d'un programme. Or plus le nombre de communications, d'entrées/sorties du CPU est important plus le programme sera lent. On retrouve ce cas de figure lors de requêtes en base de données, lors de requêtes HTTP ou pour les connections réseaux en général.

Comment accélérer un programme limité par les E/S:

Pour illustrer mon propos, j'ai choisi un problème commun : le téléchargement de contenu sur le réseau. J'ai eu envie d'aller récupérer des informations sur des personnage de Star Wars en utilisant la magnifique API https://swapi.co/ .

La version synchrone du programme

#!/usr/bin/env python3
import requests
import time


def download_site(url, session):
    """[summary]
    
    :param url: l'url de la page a récuperer
    :type url: string
    :param session: l'objet session de la lib request
    :type session: class
    """

    with session.get(url) as response:
        print(f"Read {response.content} from {url}")


def download_all_sites(sites):
    """met en place un objet session
    et exécute la fonction download_site dans ce contexte 
    
    :param sites: liste des pages a récuperer
    :type sites: list
    """

    with requests.Session() as session:
        for url in sites:
            download_site(url, session)


if __name__ == "__main__":
    sites = [
        "https://swapi.co/api/people/%s/"%number for number in range(1,80)]
    start_time = time.time()
    download_all_sites(sites)
    duration = time.time() - start_time
print(f"Downloaded {len(sites)} in {duration} seconds")

Le diagramme d'exécution ressemble à ça :

Quelles sont les avantages de la version synchrone ?

La version synchrone est super facile à écrire, à concevoir et à débogguer. Dans cette version le programme ne fonctionne que sur un seul CPU, de sorte qu'il est très facile de prédire la prochaine étape et comment elle va se comporter.

En effet, chaque opération ne sera exécutée que si la précédente s'est terminée, ainsi pour récupérer des informations sur le Général Grievous (qui est le personnage N°79) nous savons que nous devons d'abord récupérer les informations des 78 personnages précédents.

Les problèmes avec la version synchrone

Le problème ici, c'est que c'est que lorsque nous demandons au serveur de nous renvoyer des informations sur un personnage nous devons attendre sa réponse avant de pouvoir demander qu'il nous envoie le suivant. Cette méthode est relativement lente par rapport aux autres solutions que nous allons proposer. Voici le temps d’exécution sur ma machine :

$ python io_bound_problem_non_concurrent.py
Downloaded 79 in 14.642319917678833 seconds

Remarque : Vos résultats peuvent varier considérablement selon votre connexion. En exécutant ce script, j'ai noté des temps variant de 14.2 à 21.9 secondes. Pour cet article, j'ai pris le temps le plus rapide des trois exécution.

Il faut bien réfléchir avant d'ajouter de la programmation concurrente, en effet si le programme n'est pas exécuté souvent, ce n'est pas grave qu'il soit lent mais que se passe-t-il si il est utilisé fréquemment et qu'il prend des heures pour chaque exécution ? Il est peut être intéressant d'y ajouter de la concurrence, par exemple en le réécrivant à l'aide du multi-threading.

Version multi-threadée

Comme vous l'avez probablement deviné, écrire un programme multi-threadé demande légèrement plus de travail. Vous serez peut-être surpris du peu d'efforts supplémentaires qu'il faut pour les cas simples. Voici à quoi ressemble le même programme avec le multi-threading :

import concurrent.futures
import requests
import threading
import time


thread_local = threading.local()


def get_session():
    """create a session object for each thread
    
    :return: a request session object 
    :rtype: object
    """

    if not getattr(thread_local, "session", None):
        thread_local.session = requests.Session()
    return thread_local.session


def download_site(url):
    """download content with request inside of a thread
    
    :param url: url of the content
    :type url: string
    """

    session = get_session()
    with session.get(url) as response:
        print(f"Read {response.content} from {url}")


def download_all_sites(sites):
    """create a pool of thread with threadpoolexecutor and 
    map download_site on the list
    
    :param sites: list of sites
    :type sites: list
    """

    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        executor.map(download_site, sites)


if __name__ == "__main__":
    sites = [
        "https://swapi.co/api/people/%s/"%number for number in range(1,80)]
    start_time = time.time()
    download_all_sites(sites)
    duration = time.time() - start_time
    print(f"Downloaded {len(sites)} in {duration} seconds")

Lorsque vous ajoutez des threads, la structure générale est la même et vous n'avez besoin que de quelques changements. download_all_sites() a changé de l'appel de la fonction une fois par site en une structure plus complexe.

Dans cette version, vous créez un ThreadPoolExecutor, ce qui semble compliqué.
Nous allons le décomposer : ThreadPoolExecutor = Thread + Pool + Executor.
Cet objet va créer un Pool (un ensemble) de Threadsqui peuvent fonctionner simultanément. Enfin, l'Executor est la partie qui va contrôler quand et comment chacun des threads du pool va s'exécuter.

La bibliothèque standard implémente ThreadPoolExecutor en tant que gestionnaire de contexte afin que vous puissiez utiliser la syntaxe with pour gérer la création et la libération du pool de threads.

Une fois que vous avez un ThreadPoolExecutor, vous pouvez utiliser sa méthode .map(). Cette méthode exécute la fonction transmise (ici download_site) sur chacun des sites de la liste. L'avantage, c'est qu'il les exécute automatiquement en même temps en utilisant le pool de threads qu'il gère.

Ceux venant d'autres langages, ou même de Python 2, se demandent probablement où se trouvent les objets et fonctions habituels qui gèrent les détails auxquels vous êtes habitués lorsqu'il s'agit de threads, de choses comme Thread.start(), Thread.join() et Queue.

Ils sont tous toujours là, et vous pouvez les utiliser pour obtenir un contrôle fin sur la manière dont vos threads sont exécutés. Mais, à partir de Python 3.2, la bibliothèque standard a ajouté un niveau d'abstraction supérieur, les Executors, qui gère de nombreux détails pour vous, donc vous n'avez plus besoin de ce contrôle fin.

L'autre changement intéressant dans notre exemple est que chaque thread doit créer son propre objet requests.Session().
C'est l'une des questions intéressantes et difficiles avec le multi-threading. En effet, ce n'est pas Python mais le système d'exploitation qui contrôle le moment où votre thread est interrompu et qu'un autre thread démarre, ainsi toutes les données qui sont partagées entre les threads doivent être protégées ou sécurisées. Malheureusement, requests.Session() n'est pas thread-safe.

Il existe plusieurs stratégies pour sécuriser l'accès aux données en fonction de la nature des données et de la façon dont vous les utilisez. Une des stratégies est d'utiliser des structures de données sécurisées comme Queue du module queue de Python.

Ces objets utilisent des primitives de bas niveau comme threading.lock pour s'assurer qu'un seul thread peut accéder à un bloc de code ou un bit de mémoire en même temps. Vous utilisez cette stratégie indirectement via l'objet ThreadPoolExecutor.

Une autre stratégie à utiliser ici est ce qu'on appelle le stockage local de thread.
Threading.local() crée un objet qui ressemble à un global mais qui est spécifique à chaque thread individuel. Dans votre exemple, cela se fait avec threadLocal et get_session() :

def get_session():
    """create a session object for each thread
    
    :return: a request session object 
    :rtype: object
    """

    if not getattr(thread_local, "session", None):
        thread_local.session = requests.Session()
    return thread_local.session

ThreadLocal est dans le module de threading pour résoudre spécifiquement ce problème. Cela semble un peu étrange, mais vous ne voulez créer qu'un seul de ces objets, pas un pour chaque thread. L'objet lui-même se charge de séparer les accès des différents threads aux différentes données.

Quand get_session() est appelé, la session qu'il consulte est spécifique au thread particulier sur lequel il s'exécute. Ainsi, chaque thread crée une seule session la première fois qu'il appelle get_session() et utilisera simplement cette session sur chaque appel suivant tout au long de sa vie.

Enfin, un petit mot sur le choix du nombre de threads. Vous pouvez voir que l'exemple de code utilise 35 threads. N'hésitez pas à jouer avec ce nombre et à voir comment le temps total change. On pourrait s'attendre à ce qu'avoir un thread par téléchargement soit le plus rapide mais, du moins sur mon système, ce n'était pas le cas. J'ai trouvé les résultats les plus rapides entre 30 et 40 threads. Si vous dépasser les 40 , alors les coûts supplémentaires de création et de destruction des threads suppriment tout gain de temps.

Pourquoi la version multi-threadée est super efficace ?

Elle est très rapide. Rappelez vous que la version synchrone a pris plus de 14 secondes :

$ python io_bound_thread.py    <br>[most output skipped] <br>Downloaded 79 in 4.286174535751343 seconds<br>

le diagramme d’exécution ressemble a :

Il utilise plusieurs threads pour avoir plusieurs requêtes vers des sites web ouvertes en même temps, ce qui permet à votre programme de chevaucher les temps d'attente et d'obtenir le résultat final plus rapidement ! Cool ! C'était l'objectif.

Les problèmes avec la version multi-threadée

Comme vous pouvez le voir dans l'exemple de la version multi-threadée, il est très important de réfléchir aux données qui sont partagées entre les threads.

Les threads peuvent interagir de manière subtile. Ces interactions peuvent provoquer des races conditions qui se traduisent souvent par des bogues aléatoires et intermittents et qui peuvent être très difficiles à trouver. Ceux d'entre vous qui ne sont pas familiers avec le concept des races conditions voudront peut-être développer et lire la section ci-dessous.

Version asynchrone

Avant d’étudier le code de l'exemple asyncio, nous allons parler plus en détail de la façon dont asyncio fonctionne à partir d'une version simplifiée de l'asyncio.

Asyncio : notions de base

Asyncio permet de définir un objet Python appelé boucle d’événement qui va contrôler quand et comment un ensemble de tâches sera exécutée.
Cet objet, cette boucle d'évènement, est consciente des différentes tâches à exécuter et son travail est d'orchestrer ces dernières de la meilleure façon possible.
Pour ce faire elle connaît plusieurs choses. Premièrement, la liste des tâches à effectuer mais aussi l'état dans lequel se trouve chacune de ces tâches.
Prenons un exemple simple de tâche à deux états (en réalité il peut y en avoir beaucoup plus):

  • l'état PRÊT indiquera qu'une tâche a du travail à faire et est prête à être exécutée.
  • l'état ATTENTE signifiera que la tâche attend la fin d'un événement externe, comme une opération réseau.

Lorsqu'une tâche passe en attente (attente d'une réponse suite à une requête réseau par exemple) et ne peux plus progresser elle va rendre le contrôle à la boucle d'évènement en lui communiquant exactement ce qu'elle attend. Notre boucle d'évènement va alors s'empresser de ranger cette tâche soit parmi celles en ATTENTE soit parmi celles PRÊTes. Elle va ensuite passer en revue l'ensemble des tâches pour en exécuter une dont le status est passé à PRÊT. Votre boucle d'événements simplifiée choisit la tâche qui a attendu le plus longtemps et l'exécute. Ce processus se répète jusqu'à ce que la boucle d'événements soit terminée.

Un point important de l'asyncio est que ce sont les tâches qui redonnent le contrôle à la boucle d'évènement, jamais l'inverse. Elles ne sont donc jamais interrompues au milieu d'une opération mais seulement quand elles le jugent nécessaire. Cela nous permet de partager les ressources un peu plus facilement en asyncio qu'en threading. Vous n'avez pas à vous soucier de rendre votre code thread-safe.C'est une vue d'ensemble de ce qui se passe avec Asyncio. Si vous voulez plus d'informations, cette réponse StackOverflow fournie quelques bons détails.

async et await

Parlons maintenant de deux nouveaux mots-clés qui ont été ajoutés à Python : async et await. À la lumière de la discussion ci-dessus, vous pouvez voir await comme de la magie qui permet à une tâche de rendre le contrôle à la boucle d'événements. Lorsque votre code attend un appel de fonction, c'est un signal que l'appel est susceptible d'être quelque chose qui prend un certain temps et que la tâche devrait abandonner le contrôle.

Il est plus facile de penser à async comme un flag pour Python lui disant que la fonction sur le point d'être définie utilise await. Il y a des cas où ce n'est pas strictement vrai, comme les générateurs asynchrones, mais cela reste valable dans de nombreuses situations et vous donne un modèle simple pour débuter.

Une exception à cela, que vous verrez dans le code suivant, est l'asynchrone avec l'instruction with qui crée un gestionnaire de contexte à partir d'un objet que vous auriez normalement attendu. Bien que la sémantique soit un peu différente, l'idée est la même : signaler ce gestionnaire de contexte comme quelque chose qui peut être remplacé.

Comme je suis sûr que vous pouvez l'imaginer, il y a une certaine complexité dans la gestion de l'interaction entre la boucle d'événements et les tâches. Pour les développeurs débutant avec asyncio, ces détails ne sont pas importants, mais vous devez vous rappeler que toute fonction qui appelle await doit être marquée avec async. Sinon, vous obtiendrez une erreur de syntaxe.

Retour au Code

Maintenant que vous avez une compréhension de base de ce qu'est asyncio, parcourons cette nouvelle version du code de l'exemple et voyons comment il fonctionne. Notez que cette version ajoute aiohttp. Vous devrez exécuter pip install aiohttp avant de l'exécuter :

import asyncio
import time
import aiohttp


async def download_site(session, url):
    async with session.get(url) as response:
        print("Read {0} from {1}".format(response.content_length, url))


async def download_all_sites(sites):
    async with aiohttp.ClientSession() as session:
        tasks = []
        for url in sites:
            task = asyncio.ensure_future(download_site(session, url))
            tasks.append(task)
        await asyncio.gather(*tasks, return_exceptions=True)


if __name__ == "__main__":
    sites = [
        "https://swapi.co/api/people/%s/"%number for number in range(1,80)]
    start_time = time.time()
    asyncio.get_event_loop().run_until_complete(download_all_sites(sites))
    duration = time.time() - start_time
    print(f"Downloaded {len(sites)} sites in {duration} seconds")

Cette version est un peu plus complexe que les deux précédentes. Elle a une structure similaire, mais il y a un peu plus de travail pour configurer les tâches que pour créer le ThreadPoolExecutor. Commençons par le début de l'exemple.

download_site()

download_site() en haut est presque identique à la version de threading à l'exception du mot-clé async sur la ligne de définition de la fonction et du mots-clés async with lorsque vous appelez réellement session.get().

download_all_sites()

download_all_sites() est l'endroit où vous verrez le plus grand changement par rapport à l'exemple de threading.

Vous pouvez partager la session entre toutes les tâches, de sorte que la session est créée ici en tant que gestionnaire de contexte. Les tâches peuvent partager la session parce qu'elles sont toutes exécutées sur le même thread. Il est impossible qu'une tâche puisse en interrompre une autre alors que la session est dans le mauvais état.

Dans ce gestionnaire de contexte, il crée une liste de tâches à l'aide de asyncio.ensure_future(), qui se charge également de les lancer. Une fois que toutes les tâches sont créées, cette fonction utilise asyncio.gather() pour garder le contexte de la session en vie jusqu'à ce que toutes les tâches soient terminées.

Le code de threading fait quelque chose de similaire à ceci, mais les détails sont traités de manière pratique dans le ThreadPoolExecutor. Il n'y a actuellement pas de classe AsyncioPoolExecutor.

Il y a cependant un petit, mais important, changement enfoui dans les détails ici. Rappelez-vous, nous avons parlé du nombre de threads à créer. Il n'était pas évident de déterminer, dans l'exemple de multithreading, quel était le nombre optimal de threads.

L'un des avantages de l'asyncio est qu'il s'adapte beaucoup mieux que le multi-threading. Chaque tâche prend beaucoup moins de ressources et moins de temps à créer qu'un thread, donc la création et l'exécution d'un plus grand nombre d'entre-elles fonctionne bien. Cet exemple ne fait que créer une tâche séparée pour chaque site à télécharger, ce qui fonctionne très bien.

__main__

Enfin, la nature de l'asyncio signifie que vous devez démarrer la boucle d'événements et lui indiquer les tâches à exécuter. La section __main__ en bas du fichier contient le code pour obtenir_event_loop() et ensuite exécuter_until_complete().

Si vous avez mis à jour Python 3.7, les développeurs du noyau Python ont simplifié cette syntaxe pour vous. Au lieu de asyncio.get_event_loop().run_until_complete() tongue-twister, vous pouvez simplement utiliser asyncio.run().

Pourquoi la version asyncio est la meilleure

C'est vraiment rapide ! Dans les tests sur ma machine, c'était la version la plus rapide du code:

$ python io_async.py

Downloaded 79 sites in 3.088017225265503 seconds

le diagramme de timing d'exécution parait assez similaire a celui de la version multi-threadée sauf que c'est le même thread qui fait toutes requêtes E/S.

L'absence d'un joli wrapper comme le ThreadPoolExecutor rend ce code un peu plus complexe que l'exemple de threading. C'est un cas où vous devez faire un peu de travail supplémentaire pour obtenir de bien meilleures performances.

De plus, il y a un argument courant selon lequel le fait d'ajouter async et await dans les bons endroits est une complication supplémentaire. Dans une certaine mesure, c'est vrai. Le revers de la médaille de cet argument est qu'il vous force à réfléchir au moment où une tâche donnée sera permutée, ce qui peut vous aider à mieux organiser votre code.

Les problèmes avec la version asyncio

Le problème de la mise à l'échelle est également très important ici. L'exécution de l'exemple de threading ci-dessus avec un thread pour chaque site est sensiblement plus lente que son exécution avec une poignée de threads. Exécuter l'exemple d'asyncio avec des centaines de tâches ne l'a pas ralenti du tout.

Il y a quelques problèmes avec asyncio en ce moment. Vous avez besoin de versions asynchrones spéciales des bibliothèques pour profiter pleinement de l'asyncio. Si vous aviez simplement utilisé requests pour télécharger les sites, cela aurait été beaucoup plus lent car requests n'est pas conçues pour avertir la boucle d'événements qu'elle est bloquée. Ce problème est corrigé progressivement quand de plus en plus de bibliothèques adoptent l'asyncio.

Un autre problème, plus subtil, est que tous les avantages du multitâche coopératif disparaissent si l'une des tâches ne coopère pas. Une erreur mineure dans le code peut provoquer l'exécution d'une tâche et le blocage du CPU pendant une longue période, empêchant d'autres tâches d'être exécutées. Rappelez-vous, il n'y a aucun moyen pour la boucle d'événements d'interrompre une tâche qui ne lui retourne pas le contrôle.

Dans cette optique, adoptons une approche radicalement différente de la concurrence, le multi-traitement.

Version multiprocessing

Contrairement aux approches précédentes, la version multiprocessée du code tire pleinement parti des multiples CPU de l'ordinateur.

import requests
import multiprocessing
import time

session = None


def set_global_session():
    """create a global session object
    
    """

    global session
    if not session:
        session = requests.Session()


def download_site(url):
    """download content
    
    :param url: site url 
    :type url: string
    """

    with session.get(url) as response:
        name = multiprocessing.current_process().name
        print(f"{name}:Read {len(response.content)} from {url}")


def download_all_sites(sites):
    """map download_site function on multiprocessing pool 
    
    :param sites: sites list
    :type sites: list
    """

    with multiprocessing.Pool(initializer=set_global_session) as pool:
        pool.map(download_site, sites)


if __name__ == "__main__":
    sites = [
        "https://swapi.co/api/people/%s/"%number for number in range(1,80)]
    start_time = time.time()
    download_all_sites(sites)
    duration = time.time() - start_time
    print(f"Downloaded {len(sites)} in {duration} seconds")

Le code de cet exemple est beaucoup plus court que l'exemple d'asyncio et ressemble beaucoup à l'exemple de threading, mais avant de nous plonger en détail dans le code, faisons un petit tour rapide du multiprocessing.

Le multiprocessing en quelques phrases

Jusqu'à présent, tous les exemples de concurrences de cet article ne fonctionnent que sur un seul CPU dans votre ordinateur. Ceci est lié à la conception actuelle de CPython et à ce qu'on appelle le Global Interpreter Lock, ou GIL.

Cet article ne se penchera pas sur le comment et le pourquoi du GIL. Il suffit pour l'instant de savoir que les versions synchrone, threading et asyncio de cet exemple fonctionnent toutes sur un seul CPU.

multiprocessing dans la bibliothèque standard a été conçu pour briser cette barrière et exécuter votre code sur plusieurs CPU. À un haut niveau, il le fait en créant une nouvelle instance de l'interpréteur Python pour s'exécuter sur chaque CPU et en sous-traitant ensuite une partie de votre programme pour l'exécuter.

Comme vous pouvez l'imaginer, créer un interpréteur Python séparé n'est pas aussi rapide que lancer un nouveau thread dans l'interpréteur Python actuel. Il s'agit d'une opération lourde qui comporte des restrictions et des difficultés, mais selon le problème, elle peut faire une énorme différence.

Le code de la version multiprocessée

Le code a quelques petits changements par rapport à notre version synchrone. Le premier est dans download_all_sites(). Au lieu d'appeler simplement download_site() à plusieurs reprises, il crée un objet multiprocessing.Pool et lui fait mapper download_site vers les sites itérables. Cela devrait vous sembler familier dans l'exemple de threading.

Ce qui se passe ici, c'est que le Pool crée un certain nombre de processus d'interpréteurs Python distincts et va sur chacun, exécuter la fonction spécifiée sur certains des éléments de la liste des sites (de l'itérable plus généralement). La communication entre le processus principal et les autres processus est assurée pour vous par le module multiprocessing.

La ligne qui crée le Pool mérite votre attention. Tout d'abord, il ne précise pas le nombre de processus à créer dans le Pool, bien qu'il s'agisse d'un paramètre facultatif. Par défaut, multiprocessing.Pool() déterminera le nombre de processeurs dans votre ordinateur et le fera correspondre. C'est souvent la meilleure réponse, et c'est ici le cas.

Pour ce problème, l'augmentation du nombre de processus n'a pas accéléré les choses. En fait, cela les a même ralenti car le coût de création et d’arrêt des processus était plus élevé que l'avantage de faire les demandes d'E/S en parallèle.

Ensuite, nous avons la partie initialiser=set_global_session de cet appel. Rappelez-vous que chaque processus de notre Pool a son propre espace mémoire. Cela signifie qu'ils ne peuvent pas partager des choses comme un objet Session. Vous ne voulez pas créer un nouveau dossier à chaque fois que la fonction est appelée, vous voulez en créer un pour chaque processus.

Le paramètre de fonction de l'initialiser n'est conçu que pour ce cas. Il n'y a pas moyen de renvoyer une valeur de retour de l'initialiser à la fonction appelée par le processus download_site(), mais vous pouvez initialiser une variable de session globale pour maintenir la session unique pour chaque processus. Parce que chaque processus a son propre espace mémoire, le global pour chacun sera différent.

C'est vraiment tout ce qu'il y a à faire. Le reste du code est assez similaire à ce que vous avez vu auparavant.

Quelles sont les avantages de la version multiprocessée

La version multiprocessée de cet exemple est géniale parce qu'elle est relativement facile à mettre en place et nécessite peu d'options. Elle tire également pleinement parti de la puissance du processeur de votre ordinateur. Le diagramme de temps d'exécution de ce code ressemble à ceci :

Les problèmes avec la version multiprocessée

Cette version de l'exemple nécessite une configuration supplémentaire, et l'objet de session global est étrange. Vous devez passer un peu de temps à réfléchir aux variables qui seront accessibles dans chaque processus.

Enfin, il est nettement plus lent que les versions asynchrone et multithreadée dans cet exemple.

Ce n'est pas surprenant, car les problèmes limités par les E/S ne sont pas vraiment la raison d'être du multiprocessing. Vous en verrez d'autres au fur et à mesure que vous passerez à la section suivante et que vous regarderez des exemples liés au CPU.

Merci aux relecteurs Kevin Gueuti, Gwenaelle, Plopp et Yoann M.

L’article Les problèmes limités par les entrées/sorties (IObound) est apparu en premier sur bioinfo-fr.net.

Rendre un pipeline Snakemake à l'épreuve des plateformes

$
0
0

"Trans-Alaska Pipeline" by Ted LaBar

Pour avoir été client des articles ("Snakemake pour les nuls""Formaliser ses protocoles avec Snakemake" et "Snakemake, aller plus loin avec la parallélisation") de mon prédécesseur lelouar, j'ai décidé d'apporter ma pierre à l'édifice et de continuer cette série sur Snakemake. Je vais ici vous parler de généralisation de pipeline pour l'utilisation intensive au sein d'une plateforme par exemple.

Pourquoi rendre mon pipeline générique?

C'est une très bonne question Jamy ! Les articles précédents se basent sur le fait que les fichiers attendus en fin de pipeline (règle target) sont tous écrits "en dur". Pas très pratique si vous devez utiliser ce pipeline pour des projets différents. Cela oblige à retoucher le code ce qui est pénible et source d'erreur. De plus le fait de devoir fixer le nombre de threads pour une règle donnée peut être assez limitant si le nombre de cœurs disponible au lancement est variable (par exemple si on est plusieurs à utiliser un serveur de calcul). Je vais donc aborder certaines techniques applicables pour rendre un pipeline plus adapté à différentes conditions d'utilisation.

Un Snakefile reste avant tout un script Python3

Il ne faut pas perdre de vue que tout code python écrit dans le Snakefile est exécuté normalement (a quelques petites exceptions près). On peut donc très bien englober les règles dans des conditions. Tout l’intérêt est de jouer avec le fichier de configuration afin de lancer une règle précise en fonction de ce qui est requis par l'utilisateur. Pour illustrer mon propos tout au long de cet article, je vais me baser sur le pipeline des articles précédents en l'adaptant à ma problématique.

Reprenons avec le fichier de configuration

Snakemake propose l'utilisation de fichiers de configuration au format JSON mais également au format YAML. C'est ce format que je vais utiliser.

Voici un petit pipeline simple réalisant l'alignement de fastq paired-end avec bwa-aln ou bien bwa-mem avec un fichier de configuration très simple :

import subprocess

configfile : "config.yaml"
 
rule target:
    input:
        config["genome"]+".bwt",
        "results/A.sorted.bam",
        "results/B.sorted.bam",

 
# Index du génome de référence avant l'alignement des lectures (séquences)
rule bwa_index:
    input:
        config["genome"] 
    output:
        config["genome"]+".bwt"
    message:
        "Index du génome de référence : "+config["genome"]
    version: subprocess.getoutput("bwa 2>&1 | grep Version: | sed -r 's/Version: +//'")  
    shell: #3) lance la commande
        "bwa index {input}" #"{input}"="config["genome"]"
 
# Alignement des lectures sur le génome de référence

if config["bwa"] == "mem" :

    rule bwa_mem:
        input:
            bwt = config["genome"]+".bwt",
            fasta = config["genome"],
            R1 = config["sampleDirectory"]+"/{samples}_R1.fastq.gz",
            R2 = config["sampleDirectory"]+"/{samples}_R2.fastq.gz"
        output:
            temp("results/{samples}.sam") 
        threads: 20 #max cpu autorisé
        version: subprocess.getoutput("bwa 2>&1 | grep Version: | sed -r 's/Version: +//'")
        log : "results/{samples}_"+config["bwa"]+".log"
        message:
            "Alignment de {wildcards.samples} sur "+config["genome"]
        shell:
            "bwa mem "       # avec Snakemake
            "-t {threads} "  # vous pouvez 
            "{input.fasta} " # commenter
            "{input.R1} "    # tous les 
            "{input.R2} "     # arguments 
            "-o {output} 2> {log}"        # des commandes

    rule mem_sort :
        input :
            rules.bwa_mem.output
        output :
            "results/{samples}.sorted.bam"
        threads : 20
        shell :
            "samtools sort "
            "-@ {threads} {input} -o {output}"
            " & & samtools index "
            "-@ {threads} {output}"

else :

    rule bwa_aln :
        input :
            bwt = config["genome"]+".bwt",
            fasta = config["genome"],
            R1 = config["sampleDirectory"]+"/{samples}_R1.fastq.gz",
            R2 = config["sampleDirectory"]+"/{samples}_R2.fastq.gz"
        output :
            sai_R1 = temp("results/{samples}_R1.sai"),
            sai_R2 = temp("results/{samples}_R2.sai")
        version: subprocess.getoutput("bwa 2>&1 | grep Version: | sed -r 's/Version: +//'")
        log : "results/{samples}_"+config["bwa"]+".log"
        threads : 20
        params :
            genome = config["genome"]
        shell :
            "bwa aln -t {threads} {params.genome} {input.R1} -f {output.sai_R1} 2> {log}"
            "&& " 
            "bwa aln -t {threads} {params.genome} {input.R2} -f {output.sai_R2} 2>> {log}"

    rule aln_sampe :
        input :
            R1 = config["sampleDirectory"]+"/{samples}_R1.fastq.gz",
            R2 = config["sampleDirectory"]+"/{samples}_R2.fastq.gz",
            sai_R1 = rules.bwa_aln.output.sai_R1,
            sai_R2 = rules.bwa_aln.output.sai_R2
        output :
            temp("results/{samples}.sam")
        version: subprocess.getoutput("bwa 2>&1 | grep Version: | sed -r 's/Version: +//'")
        log : "results/{samples}_sampe.log"
        params :
            genome = config["genome"]
        shell :
            "bwa sampe {params.genome} {input.sai_R1} {input.sai_R2} {input.R1} {input.R2} -f {output} 2> {log}"

    rule aln_sort :
        input :
            rules.aln_sampe.output
        output :
            "results/{samples}.sorted.bam"
        threads : 20
        shell :
            "samtools sort "
            "-@ {threads} {input} -o {output}"
            " && samtools index "
            "-@ {threads} {output}"

Et le fichier de configuration :

sampleDirectory : "samples"
bwa : "mem"
genome : "hg19.fa"

A ce stade vous me direz qu'il n'y a pas grande différence avec un pipeline classique vu précédemment et vous aurez raison. Nous allons maintenant nous occuper de rendre tout ça un peu plus dynamique. Le but étant de reproduire plus ou moins le comportement des options de lancement d'un bon vieux script Python (argparse ce module qui vous veut du bien). Les valeurs des variables du fichier de configuration sont modifiables avec la commande de lancement Snakemake via l'option --config comme dans l'exemple ci-dessous :

snakemake --cores 30 --config genome=hg38.fa bwa=aln sampleDirectory=DNAseq

Il peut être intéressant de laisser des valeurs en dur dans le fichier de configuration afin d'avoir un comportement par défaut du pipeline. Par contre, attribuer une valeur à une variable non présente au sein du fichier de configuration (par exemple --config gemome=hg38.fa) la rajoute pour cette session de lancement. Vous voyez venir le problème ? Si vous faites une faute dans la variable que vous voulez modifier, non seulement une nouvelle variable sera créée mais votre pipeline se lancera sans soucis en conservant la valeur par défaut dans le fichier de configuration. Mais pas de panique ! Snakemake propose de valider un fichier de configuration selon un schéma YAML via le code suivant :

from snakemake.utils import validate
validate(config, "path/to/schemal.yaml")

Pour le petit fichier de configuration utilisé ici, le schéma pourrait ressembler à celui ci-dessous :

definitions: {}
$schema: http://json-schema.org/draft-07/schema#
$id: http://example.com/root.json
type: object
title: The Root Schema
required:
- sampleDirectory
- bwa
- genome
properties:
  sampleDirectory:
    $id: '#/properties/sampleDirectory'
    type: string
    title: The SampleDirectory Schema
    default: ''
    examples:
    - samples
    pattern: ^(.*)$
  bwa:
    $id: '#/properties/bwa'
    type: string
    title: The Bwa Schema
    default: ''
    examples:
    - mem
    pattern: ^(.*)$
  genome:
    $id: '#/properties/genome'
    type: string
    title: The Genome Schema
    default: ''
    examples:
    - hg19.fa
    pattern: ^(.*)$
additionalProperties: false

La ligne finale spécifie que seules les variables présentes dans ce schéma doivent être présentes dans le fichier de configuration (ou via la ligne de commande snakemake). Pour aller plus loin dans l'écriture des schémas je vous invite à lire la documentation en ligne (bon courage !)

Rendons tout ça un peu plus général !

Maintenant que nous avons un pipeline bien défini avec fichier de configuration et validation YAML, occupons-nous de rendre ce pipeline applicable au plus grand nombre de cas possible. Vous avez sans doute remarqué que les fichiers attendus en fin de pipeline sont écrit en dur dans la règle target ce qui oblige d'aller modifier des chemins et des noms de fichiers dans le code du Snakefile ce qui n'est pas très agréable, vous en conviendrez. La solution est d'utiliser des fonctions d'input afin d'établir automatiquement la liste des fichiers attendus en analysant par exemple le contenu d'un dossier (donné en option) contenant nos échantillons à aligner. Cela permettrait de ne changer uniquement le dossier d'entrée via la commande --config et ne plus toucher au code du pipeline.

Prenons l'exemple suivant : Joe travaille au sein d'une plateforme de séquençage qui enchaîne les projets. Comment améliorer le pipeline du dessus afin de pouvoir l'utiliser pour tous les projets paired-end ? Dans un premier temps on peut détecter automatiquement les fichiers d'entrée et de sortie comme expliqué au paragraphe précédent mais on peut également adapter le nombre de threads alloués à chaque règle en fonction de la taille du projet. Dans cet exemple la plateforme exige que le pipeline fasse tourner le maximum d'échantillons en parallèle sur un serveur de 32 cœurs et que les fichiers issus d'un séquençage Illumina soient formatés sous la forme nom_S[numéro_individu]_L00[numéro_lane_de_séquençage]_R[1,2]_001.fastq.gz

Exemple pour un projet X de séquençage du rat en paired-end:

  • Rattus_projetX_S1_L001_R1_001.fastq.gz
  • Rattus_projetX_S1_L001_R2_001.fastq.gz

Pour commencer, grâce au fichier de config nous pouvons récupérer la liste des fichiers présents dans le dossier contenant ces deux fastq :

import glob
import re

LIST_FILE = glob.glob(config["sampleDirectory"]+"/*.fastq.gz") #récupération de tout les fastq.gz présent dans le dossier fourni dans le config file

LIST_NAME =[] 
# bloc récupérant via regex le prefix correpondant au nom des échantillions (un nom retenu pour deux échantillons en Paired-End par exemple)
for name in LIST_FILE : 
	removeDir = re.sub(config["sampleDirectory"]+"/",'',name)
	removeDir = re.sub(".fastq.gz",'',removeDir)
	if re.sub("_Sw+_R1_001",'', removeDir) != removeDir : 
		name = re.sub("_Sw+_R1_001",'', removeDir)
		LIST_NAME.append(name)

On peut créer une fonction retournant la liste des fichiers attendus en fonction du contenu du dossier d'input en utilisant les informations récupérées dans LIST_NAME :

def input_target(names) :
	liste = []
	for name in names :
		liste.append("results/"+name+".sorted.bam")
	return liste

Et donc la règle target s'écrit désormais comme cela :

rule target:
	input:
		config["genome"]+".bwt",
		input_target(LIST_NAME)

Et hop ! Vous avez une fonction d'input automatique ! Bien évidemment ce code ne fonctionne que pour notre exemple précis, si les fastq ne sont pas formatés comme demandé, le pipeline ne trouvera pas vos échantillons. A vous d'adapter ce code aux spécificités de votre labo.

La fonction lambda

Si utiliser des fonctions Python classiques fonctionne parfaitement dans Snakemake, utiliser les wildcards de ce dernier est encore plus efficace. Pour continuer sur la lancée de la partie précédente, on peut écrire deux petites fonctions qui prendront en entrée la valeur de la wildcards définie par Snakemake afin d'avoir une continuité dans l'automatisation :

def R1_func(wildcards):
	return(glob.glob(config["sampleDirectory"]+"/"+wildcards + "*_R1_001.fastq.gz"))
def R2_func(wildcards):
	return(glob.glob(config["sampleDirectory"]+"/"+wildcards + "*_R2_001.fastq.gz"))

[...]

rule bwa_aln :
		input :
			bwt = config["genome"]+".bwt",
			fasta = config["genome"],
			R1 = lambda wildcards: R1_func("{samples}".format(samples=wildcards.samples)),
			R2 = lambda wildcards: R2_func("{samples}".format(samples=wildcards.samples))
		output :
			sai_R1 = temp("results/{samples}_R1.sai"),
			sai_R2 = temp("results/{samples}_R2.sai")
		version: subprocess.getoutput("bwa 2>&1 | grep Version: | sed -r 's/Version: +//'")
		log : "results/{samples}_"+config["bwa"]+".log"
		threads : 20
		params :
			genome = config["genome"]
		shell :
			"bwa aln -t {threads} {params.genome} {input.R1} -f {output.sai_R1} 2> {log}"
			"&& " 
			"bwa aln -t {threads} {params.genome} {input.R2} -f {output.sai_R2} 2>> {log}"

Ces deux fonctions sélectionnent les fichiers fastq R1 et R2 en fonction d'une wildcards donnée. Cette wildcards va prendre automatiquement comme valeur les noms présents dans LIST_NAME car c'est à partir de la règle target que Snakemake sait quels fichiers doivent être créés et donc quelle valeur attribuer aux wildcards.

Gérer l'attribution du nombre de threads

Étrangement, il n'existe pas de façon simple pour récupérer la valeur du nombre de cœurs disponible spécifiée au lancement (--cores ou -j). Mais l'idée est de récupérer cette valeur afin d'adapter le nombre de threads pour chaque règle en fonction du nombre d'échantillons à analyser.

J'ai donc utilisé sys.argv (la liste contenant les arguments de la commande de lancement d'un programme python) afin de récupérer cette valeur mais le comportement de snakemake étant assez particulier, la récupération doit se faire en deux temps. En effet, Snakemake exécute le code une première fois (votre commande de lancement) et sys.argv contient toute vos options de lancement. Ensuite le code s'exécute une seconde fois et Snakemake modifie les valeurs de sys.argv par ses propres valeurs. Oui c'est tordu ! Essayez de print sys.argv a l'exécution d'un pipeline vous allez voir.

Je vous donne donc la fonction toute faite ce qui vous évitera chercher deux heures comme moi :

def threads_max() :
	th = 0
	it=0
	for op in sys.argv :
#dans la première éxécution, l'option est sous la forme [..."-j","32"...]
		if op == "-j" or op == "--cores": 
			th = int(sys.argv[it+1])
			break
		else :
#Dans la seconde exécution la structure de l'option change ([..."-j32",...] par exemple)
			if re.search("-j(w+)",op) :
				th = int(re.search("-j(w+)",op).group(1))
		it += 1
	return th

Cette fonction vous permettra de récupérer le nombre de cœurs max disponible. Vous pourrez ensuite par exemple décider de lancer le plus d'analyse possible en parallèle quel que soit le nombre d'échantillons. Snakemake permet de mettre une variable au nombre de threads par règle au lieu d'un nombre fixe. Il est donc intéressant de créer une petite conditions qui adaptera ce nombre au nombre d'échantillons.

bwa_threads = 1
if threads_max() > len(LIST_NAME):
    bwa_threads =  threads_max() // len(LIST_NAME)

Au final

Avec toutes ces modifications, le pipeline est désormais capable de réaliser l'alignement sur bwa-mem ou aln au choix, en fonction d'un dossier d'input et d'optimiser tout seul le nombre de threads en fonction du nombre d'échantillons à traiter. Le pipeline a été testé sur une machine locale sous Ubuntu 19.04 avec 16Gb de Ram et un i5-8250U.

Je vous redonne le code total du pipeline optimisé :

import re
import subprocess
import glob
import sys
from snakemake.utils import validate

configfile: "config.yaml"


# Fonctions #

validate(config, "schema.yaml")

LIST_FILE = glob.glob(config["sampleDirectory"]+"/*.fastq.gz")

LIST_NAME = [] 
for name in LIST_FILE: # bloc récupérant via regex le prefix correpondant au nom des échantillions (un nom retenu pour deux échantillons en Pair-End par exemple)
    removeDir = re.sub(config["sampleDirectory"]+"/", '', name)
    removeDir = re.sub(".fastq.gz", '', removeDir)
    if re.sub("_S\w+_R1_001",'', removeDir) != removeDir: 
        name = re.sub("_S\w+_R1_001",'', removeDir)
        LIST_NAME.append(name) 

def R1_func(wildcards):
    return(glob.glob(config["sampleDirectory"]+"/"+wildcards + "*_R1_001.fastq.gz"))

def R2_func(wildcards):
     return(glob.glob(config["sampleDirectory"]+"/"+wildcards + "*_R2_001.fastq.gz"))

def input_target(names):
    liste = []
    for name in names:
        liste.append("results/"+name+".sorted.bam")
    return liste

def threads_max():
    th = 0
    it = 0
    for op in sys.argv:
#dans la première éxécution, l'option est sous la forme [..."-j","32"...]
        if op == "-j" or op == "--cores": 
            th = int(sys.argv[it+1])
            break
        else:
#Dans la seconde exécution la structure de l'option change ([..."-j32",...] par exemple)
            if re.search("-j(w+)",op):
                th = int(re.search("-j(w+)",op).group(1))
        it += 1
    return th

bwa_threads = 1

if threads_max() > len(LIST_NAME):
    bwa_threads = threads_max() // len(LIST_NAME)

# au dessus de 16 threads, le gain de vitesse est négligeable
if bwa_threads > 16:
    bwa_threads = 16

###### Pipeline ######

rule target:
    input:
        config["genome"]+".bwt",
        input_target(LIST_NAME)

 
# Index du génome de référence avant l'alignement des lectures (séquences)
rule bwa_index:
    input:
        config["genome"] 
    output:
        config["genome"] + ".bwt"
    message:
        "Index du génome de référence: " + config["genome"]
    version: subprocess.getoutput("bwa 2>&1 | grep Version: | sed -r 's/Version: +//'")  
    shell: #3) lance la commande
        "bwa index {input}" #"{input}"="config["genome"]"
 
# Alignement des lectures sur le génome de référence

if config["bwa"] == "mem":

    rule bwa_mem:
        input:
            bwt = config["genome"]+".bwt",
            fasta = config["genome"],
            R1 = lambda wildcards: R1_func("{samples}".format(samples=wildcards.samples)),
            R2 = lambda wildcards: R2_func("{samples}".format(samples=wildcards.samples))
        output:
            temp("results/{samples}.sam") 
        threads: bwa_threads #max cpu autorisé
        version: subprocess.getoutput("bwa 2>&1 | grep Version: | sed -r 's/Version: +//'")
        log: "results/{samples}_"+config["bwa"]+".log"
        message:
            "Alignement de {wildcards.samples} sur "+config["genome"]+" avec "+str(bwa_threads)+" threads"
        shell:
            "bwa mem "       # avec Snakemake
            "-t {threads} "  # vous pouvez 
            "{input.fasta} " # commenter
            "{input.R1} "    # tous les 
            "{input.R2} "     # arguments 
            "-o {output} 2> {log}"        # des commandes

    rule mem_sort:
        input:
            rules.bwa_mem.output
        output:
            "results/{samples}.sorted.bam"
        threads: bwa_threads
        shell:
            "samtools sort "
            "-@ {threads} {input} -o {output}"
            " && samtools index "
            "-@ {threads} {output}"

else:

    rule bwa_aln:
        input:
            bwt = config["genome"]+".bwt",
            fasta = config["genome"],
            R1 = lambda wildcards: R1_func("{samples}".format(samples=wildcards.samples)),
            R2 = lambda wildcards: R2_func("{samples}".format(samples=wildcards.samples))
        output:
            sai_R1 = temp("results/{samples}_R1.sai"),
            sai_R2 = temp("results/{samples}_R2.sai")
        version: subprocess.getoutput("bwa 2>&1 | grep Version: | sed -r 's/Version: +//'")
        log: "results/{samples}_"+config["bwa"]+".log"
        threads: bwa_threads
        message: "Alignement de {wildcards.samples} sur "+config["genome"]+" avec "+str(bwa_threads)+" threads"
        params:
            genome = config["genome"]
        shell:
            "bwa aln -t {threads} {params.genome} {input.R1} -f {output.sai_R1} 2> {log}"
            "&& " 
            "bwa aln -t {threads} {params.genome} {input.R2} -f {output.sai_R2} 2>> {log}"

    rule aln_sampe:
        input:
            R1 = lambda wildcards: R1_func("{samples}".format(samples=wildcards.samples)),
            R2 = lambda wildcards: R2_func("{samples}".format(samples=wildcards.samples)),
            sai_R1 = rules.bwa_aln.output.sai_R1,
            sai_R2 = rules.bwa_aln.output.sai_R2
        output:
            temp("results/{samples}.sam")
        version: subprocess.getoutput("bwa 2>&1 | grep Version: | sed -r 's/Version: +//'")
        log: "results/{samples}_sampe.log"
        params:
            genome = config["genome"]
        shell:
            "bwa sampe {params.genome} {input.sai_R1} {input.sai_R2} {input.R1} {input.R2} -f {output} 2> {log}"

    rule aln_sort:
        input:
            rules.aln_sampe.output
        output:
            "results/{samples}.sorted.bam"
        threads: bwa_threads
        shell:
            "samtools sort "
            "-@ {threads} {input} -o {output}"
            " && samtools index "
            "-@ {threads} {output}"

Ce pipeline peut évidemment être encore optimisé notamment via l'ajout de messages personnalisés explicatif à l'exécution des règles par exemple. Il est également possible d'imaginer une adaptation à l'exécution sur cluster.

Je remercie les relecteurs de cet article Akira, Lins et lelouar pour leur aide et leur avis pendant la rédaction de cet article.

Source photo : "Trans-Alaska Pipeline"

L’article Rendre un pipeline Snakemake à l'épreuve des plateformes est apparu en premier sur blog bioinformatique communautaire scientifique.

Pourquoi et comment déposer un package R sur Bioconductor ?

$
0
0

Ça y est, votre code R un poil brut commence à avoir de la substance et vous envisagez d'en faire un outil à part entière. Comme tout bioinformaticien qui se respecte, vous envisagez donc de packager (ou paqueter en français) proprement cet ensemble de scripts R.

Non on ne largue pas une nuée de scripts non commentés, non documentés, avec juste un mail disant "Non mais tu changes tel et tel paramètres et ça fonctionne"...

Quand j'essaie d'expliquer le concept de reproductibilité à un mauvais bioinformaticien

Votre package R étant évidemment d'une grande utilité à la communauté bioinformatique, vous envisagez de le partager. La question du répertoire de packages où vous le déposerez une fois fini va donc se poser, et avec, les conditions de développement du package qui s'appliqueront.

Pourquoi déposer son package sur Bioconductor alors qu'il y a le CRAN ?

Le CRAN (pour Comprehensive R Archive Network) est évidemment LE répertoire de packages R. Il centralise bon nombre d'entre eux, touchant à des domaines variés : statistiques, physique, mathématiques, psychologie, écologie, visualisations, machine learning, cartographie, ..., et bioinformatique. Cependant à avoir autant de variété, on s'y perd. C'est pourquoi Bioconductor a en partie été créé.

Bioconductor c'est un répertoire alternatif de packages R spécifiquement dédié à la bioinformatique créé en 2002. Akira vous le présentait en 2012 sur le blog, et huit ans plus tard, force est de constater que le projet tient toujours, et même prend de l'ampleur (cf. Figure 1). La vocation est restée la même : regrouper sous une même bannière des packages spécifiques à la bioinformatique, et notamment à l'analyse et la compréhension de données de séquençage haut débit.

Figure 1 : évolution du nombre de packages déposés sur Bioconductor

L'avantage, c'est que la visibilité de votre package est augmentée au sein de la communauté bioinfo. Et au-delà du simple répertoire, c'est également une communauté particulièrement active qui s'y rattache, avec un système à la StackOverflow.

Mais est-ce intéressant pour autant dans votre cas ? Car tout le monde n'a pas intérêt et ne peut pas déposer dans Bioconductor.

Comment savoir si on doit déposer son package dans le CRAN ou Biocondutor ?

Alors pourquoi avoir fait ce répertoire plutôt que d'avoir tout mis dans le CRAN ? Et comment choisir où votre package doit être déposé ? Quelques éléments pour vous aider :

  • Comme dit plus haut, Bioconductor est spécialisé en package pour la bioinformatique. Donc si vous développez un package avec des méthodes généralistes comme dplyr ou spécialisées dans d'autres domaines comme ecodist en écologie, visez plutôt le CRAN.
  • À l'inverse, si vous importez dans votre package d'autres package qui proviennent de Bioconductor, visez plutôt un dépôt sur celui-ci.
  • Si vous êtes prêts à assurer un support à long terme, notamment en répondant aux questions sur votre package, dirigez-vous vers Bioconductor. Sinon visez le CRAN.
  • Si vous souhaitez une publication "en production" rapide de votre package, visez le CRAN. En effet Bioconductor fonctionne avec un système de release (publication) bisannuelle (une en avril, et une en octobre). Vous avez cependant accès à une branche dites de développement en attendant la release, la bioc-devel. Sachez tout de même qu'on a déjà vu des articles sortir avec un lien vers cette branche. La branche de production ne semble donc pas être une obligation pour publier son article même si c'est recommandé (question de stabilité).
  • Le CRAN est en droit de déprécier un package car il ne convient pas avec la nouvelle release de R (en vous mettant au courant quelques semaines au plus avant de le faire). Bioconductor possède une plus grande souplesse sur ce point : si vous vous assurez qu'il fonctionne pour chaque release, il est peu probable qu'il soit déprécié.
  • Si vous avez envie de ne pas passer les tests qui cassent les gonades de Bioconductor, allez sur le CRAN

Évidemment, vous trouverez des packages de bioinformatique sur le CRAN également (ex: WGCNA), mais c'est là une occasion de manquer de visibilité au sein de notre communauté. À savoir également que rien n'est gravé dans le marbre. La preuve avec le package mixOmics qui était initialement sur le CRAN mais a été déplacé sur Bioconductor il y a un peu plus d'un an !

J'ai choisi, ce sera Bioconductor ! Mais comment ça se passe ?

Félicitations ! Mais attendez-vous à un chemin pavé d’embûches et assez éprouvant, je ne vais pas vous mentir. Une fois fini, vous verrez que le jeu en vaut la chandelle.

Note:

  • Si vous avez déjà soumis un package sur le CRAN, sautez directement à la section Les prérequis spécifiques à Bioconductor 😉
  • Si vous n'avez jamais développé de package R, laissez-moi un commentaire comme quoi ça vous intéresse. J'y consacrerai alors un autre billet de blog. En attendant, je vous laisse suivre ce très bon tutoriel pour apprendre (ou bien lire le manuel du CRAN si vous avez un côté maso... puriste).

Imaginons à présent que vous avez (ou pensez avoir) terminé votre package appelé, de façon très originale, mypkg. Il va falloir passer par plusieurs phases de pré-soumission.

Le tronc commun avec le CRAN

Pour vous assurer que votre package soit partageable à la communauté, il est nécessaire de respecter certaines bonnes pratiques, ainsi que de s'assurer qu'il soit exécutable par d'autres. Et en concret, comment on teste ça ?

Commençons par la compilation de votre package, ou build. Si vos fonctions tournent sans problème lorsque vous les exécutez à la main hors de votre package, il n'est pas assuré que celui-ci fonctionne en tant que tel pour autant !

Deux choix s'offrent alors à vous quant à la façon de procéder :

  • Vous êtes un accro du shell et de R sans IDE ? Il vous faudra faire rouler dans votre shell la commande R CMD install. Exemple : R CMD install /home/bioinfofr/dev/R/mypkg
  • Vous aimez bien Rstudio et avoir un coup de pouce ? Le package devtools est fait pour vous. C'est lui qui est utilisé derrière les boutons de build de Rstudio. Rendez-vous dans cet onglet comme en Figure 2 et cliquez sur Build Source Package.

Figure 2 : Interface de Rstudio avec la localisation de la fenêtre de build et le bouton de compilation du package source.

Si tout se passe bien, vous devriez avoir ce genre de rapport :

✓  checking for file ‘/home/bioinfofr/dev/R/mypkg/DESCRIPTION’ ...
─  preparing ‘mypkg’: (1s)
✓  checking DESCRIPTION meta-information ...
─  installing the package to build vignettes
✓  creating vignettes (2m 8s)
─  checking for LF line-endings in source and make files and shell scripts
─  checking for empty or unneeded directories
   Removed empty directory ‘mypkg/tests’
─  building ‘mypkg_0.99.99.tar.gz’
   
[1] "/home/bioinfofr/dev/R/mypkg_0.99.99.tar.gz"

Source package written to ~/dev/R

Et sinon... Eh bien les messages d'erreurs sont plutôt explicites sur la source du problème. Le plus souvent vous aurez simplement mal respecté l'architecture d'un package R (un dossier mal nommé, des fichiers qui traînent, etc.) ou des informations manquantes dans le fichier DESCRIPTION.

Une fois que votre package est compilé, passons aux checks (vérifications) automatiques. À nouveau, deux choix s'offrent à vous :

  • En shell : Il vous faudra faire rouler la commande R CMD check. Exemple : R CMD check /home/bioinfofr/dev/R/mypkg
  • Avec Rstudio/devtools : rendez-vous dans l'onglet build comme en Figure 3 et cliquez sur Check. Cela lance la fonction devtools::check().

Figure 3 : Interface de Rstudio avec la localisation de la fenêtre de build et le bouton de check

Une fois terminé, voici le genre de rapport que vous devriez obtenir :

── Building ──────────────────────────────────── mypkg ──
Setting env vars:
● CFLAGS    : -Wall -pedantic -fdiagnostics-color=always
● CXXFLAGS  : -Wall -pedantic -fdiagnostics-color=always
● CXX11FLAGS: -Wall -pedantic -fdiagnostics-color=always
─────────────────────────────────────────────────────────
✓  checking for file ‘/home/bioinfofr/dev/R/mypkg/DESCRIPTION’ (372ms)
─  preparing ‘mypkg’: (1.5s)
✓  checking DESCRIPTION meta-information ...
─  installing the package to build vignettes
✓  creating vignettes (2m 17.3s)
─  checking for LF line-endings in source and make files and shell scripts (351ms)
─  checking for empty or unneeded directories
   Removed empty directory ‘mypkg/tests’
─  building ‘mypkg_0.99.99.tar.gz’
   
── Checking ──────────────────────────────────── mypkg ──
Setting env vars:
● _R_CHECK_CRAN_INCOMING_USE_ASPELL_: TRUE
● _R_CHECK_CRAN_INCOMING_REMOTE_    : FALSE
● _R_CHECK_CRAN_INCOMING_           : FALSE
● _R_CHECK_FORCE_SUGGESTS_          : FALSE
● NOT_CRAN                          : true
── R CMD check ─────────────────────────────────────────────────────────────────
─  using log directory ‘/home/bioinfofr/dev/R/mypkg/mypkg.Rcheck’ (517ms)
─  using R version 4.0.0 (2020-04-24)
─  using platform: x86_64-pc-linux-gnu (64-bit)
─  using session charset: UTF-8
─  using options ‘--no-manual --as-cran’ (692ms)
✓  checking for file ‘mypkg/DESCRIPTION’
─  this is package ‘mypkg’ version ‘0.99.99’
─  package encoding: UTF-8
✓  checking package namespace information ...
✓  checking package dependencies (6.5s)
✓  checking if this is a source package
✓  checking if there is a namespace
✓  checking for executable files (510ms)
✓  checking for hidden files and directories
✓  checking for portable file names ...
✓  checking for sufficient/correct file permissions
✓  checking whether package ‘mypkg’ can be installed (24.9s)
N  checking installed package size ...
     installed size is 10.2Mb
     sub-directories of 1Mb or more:
       data   5.6Mb
       doc    4.1Mb
✓  checking package directory ...
✓  checking for future file timestamps (1.1s)
✓  checking ‘build’ directory
✓  checking DESCRIPTION meta-information ...
✓  checking top-level files
✓  checking for left-over files
✓  checking index information ...
✓  checking package subdirectories ...
✓  checking R files for non-ASCII characters ...
✓  checking R files for syntax errors ...
✓  checking whether the package can be loaded (8s)
✓  checking whether the package can be loaded with stated dependencies (7.9s)
✓  checking whether the package can be unloaded cleanly (7.3s)
✓  checking whether the namespace can be loaded with stated dependencies (7.4s)
✓  checking whether the namespace can be unloaded cleanly (7.6s)
✓  checking loading without being on the library search path (7.6s)
✓  checking dependencies in R code (7.1s)
✓  checking S3 generic/method consistency (10.2s)
✓  checking replacement functions (7.2s)
✓  checking foreign function calls (7.4s)
✓  checking R code for possible problems (26s)
✓  checking Rd files (339ms)
✓  checking Rd metadata ...
✓  checking Rd line widths ...
✓  checking Rd cross-references ...
✓  checking for missing documentation entries (7.9s)
✓  checking for code/documentation mismatches (23.6s)
✓  checking Rd usage sections (9.6s)
✓  checking Rd contents ...
✓  checking for unstated dependencies in examples ...
✓  checking contents of ‘data’ directory (380ms)
✓  checking data for non-ASCII characters (657ms)
✓  checking data for ASCII and uncompressed saves ...
✓  checking installed files from ‘inst/doc’ ...
✓  checking files in ‘vignettes’ ...
✓  checking examples (1m 21.8s)
   Examples with CPU (user + system) or elapsed time > 5s
                        user system elapsed
   my_foo_one          17.875  0.191  11.064
   my_foo_two          1.468  0.032  10.086
   my_foo_de_vous      0.152  0.004  24.143
   my_foo_foo_ne           0.022  0.004   7.865
✓  checking for unstated dependencies in vignettes ...
✓  checking package vignettes in ‘inst/doc’ ...
✓  checking re-building of vignette outputs (1m 47.3s)
✓  checking for non-standard things in the check directory
✓  checking for detritus in the temp directory
   
   See
     ‘/home/bioinfofr/dev/R/mypkg/mypkg.Rcheck/00check.log’
   for details.
   
   
── R CMD check results ────────────────────────────────────── mypkg 0.99.99 ────
Duration: 6m 14.2s

> checking installed package size ... NOTE
    installed size is 10.2Mb
    sub-directories of 1Mb or more:
      data   5.6Mb
      doc    4.1Mb

0 errors ✓ | 0 warnings ✓ | 1 notes x

R CMD check succeeded

Comme vous le voyez, le risque d'erreurs/avertissements est assez grand (trop grand pour que je couvre ici tous les cas !). Mais rassurez-vous, personne ne pense à tout dès la première fois qu'il développe son package. Vous finirez donc irrémédiablement avec des corrections à faire, car pour pouvoir soumettre votre package, vous ne devez avoir aucune ERROR ou WARNING. La meilleure pratique pour éviter d'avoir des dizaines de corrections à faire, c'est de tester le build et exécuter les checks régulièrement dans votre processus de développement (ça vous évitera de devoir recoder des pans entiers de votre outil car l'erreur vient d'un bout de code sur lequel s'appuient d'autres). Pensez également à mettre à jour les packages sur votre poste local car ce sont les dernières version de ceux-ci qui seront utilisées par le CRAN pour rouler les checks de leur côté (et pareil pour Bioconductor).

Les NOTEs sont quant à elles tolérées dans une certaine mesure, et cette mesure est à la discrétion de la personne qui fera la relecture critique de votre package (mais comme nous allons viser Bioconductor dans cet article, ce sont d'autres reviewers encore que ceux du CRAN).

Les prérequis spécifiques à Bioconductor

Vous avez tous vos checks qui passent ? Bien joué. Mais il va encore vous falloir passer ceux de Bioconductor ! Et c'est pas gagné.

Bioconductor est encore plus restrictif que le CRAN sur ses tests appelés BiocCheck. En effet, ils ont défini tout un ensemble de bonnes pratiques supplémentaires liées à la fois aux spécificités bioinformatiques, à la lisibilité du code, à des recommandations sur l'utilisation de telle ou telle façon de coder. Si certains de leurs partis pris sont discutables, il n'en reste pas moins que vous allez devoir vous y soumettre.

À défaut de vous les lister tous, je vais vous indiquer le minimum à respecter pour vous éviter masse de changement. L'idéal reste tout de même de les lire avant de commencer à développer votre package, car certains vont fortement impacter votre package.

  • Il va falloir définir à quelle catégorie votre package appartient : Software, Experiment Data, ou Annotation. La première est celle regroupant les outils d'analyse à proprement dit. Les deux dernières sont des packages de données aux noms assez explicites quant à leur contenu.
  • Si votre package utilise des données classiques en bioinfo (matrices gènes X échantillons, FASTA, VCF, spectro de masse, etc.) sachez que vous devrez au moins rendre compatible votre package avec les common classes de Bioconductor. Ces formats sont une tentative de standardisation de l'input dans les packages de ce dépôt.
  • Si c'est un Software, votre package final devra faire moins de 5Mo. Attention donc aux jeux de données d'exemple que vous insérez dedans. Si jamais vous souhaitez inclure des données plus grosses, on vous conseille de soumettre un autre package de type Experiment Data, ou Annotation et de l'appeler dans votre package.
  • Il devra être compatible Linux ET Windows ! Mauvaise idée donc de se baser sur des spécificités de votre OS. Ou alors préparez-vous à devoir développer un équivalent dans l'autre OS.
  • Toutes vos fonctions/données doivent être documentées ! Et pas seulement avec une rapide description. On parle d'une explication des paramètres, d'une valeur retour, et surtout d'un exemple exécutable fonctionnel.
  • Il faudra écrire une vignette, ce document faisant office d'introduction à votre package et de tutoriel. Il est nécessaire qu'on passe à travers un maximum des fonctionnalités de votre package, l'idéal étant d'y réaliser une analyse type, sur de véritables données ou des données simulées.
  • Les tests unitaires ne sont pas obligatoires d'après leur guidelines, mais soyez assurés qu'ils vous les demanderont tout de même pour la majorité de vos fonctions au moment de la revue. Mais en tant que bioinformaticien consciencieux vous les auriez déjà faits n'est-ce pas 😉 ?
  • Vous devez être inscrits au site de support de Bioconductor et abonné à la liste bioc-devel

Vous pensez avoir respecté ces recommandations ainsi que la liste complète sur leur site ? Passons à la vérification ! Tout d'abord, installez le package BiocCheck car les checks de Bioconductor ne se trouvent pas nativement dans l'install de R. Ensuite, lancez-le depuis votre session R en vous assurant d'avoir votre working

if (!requireNamespace("BiocManager", quietly = TRUE))
    install.packages("BiocManager")

BiocManager::install("BiocCheck")

BiocCheck::BiocCheck()

Des erreurs/warnings vont à nouveau s'afficher, et recommence le jeu de la correction en fonction des messages donnés.

À savoir que même si les NOTEs sont en théorie acceptées, il suffit que vous tombiez sur un reviewer un peu zélé pour que vous deviez également les corriger entièrement. Voici un exemple de rapport BiocCheck :

This is BiocCheck version 1.24.0. BiocCheck is a work in progress. Output and severity of issues
may change. Installing package...
* Checking Package Dependencies...
* Checking if other packages can import this one...
* Checking to see if we understand object initialization...
* NOTE: Consider clarifying how 4 object(s) are initialized. Maybe they are part of a dataset loaded with data(), or perhaps part of an object referenced in with() or within().
function (object)
my_foo_one (.)
my_foo_two (sides)
my_foo_de_vous (ouuuh)
my_foo_foo_ne (.)
* Checking for deprecated package usage...
* Checking for remote package usage...
* Checking version number...
* Checking version number validity...
Package version 0.99.99; pre-release
* Checking R Version dependency...
* Checking package size...
Skipped... only checked on source tarball
* Checking individual file sizes...
* Checking biocViews...
* Checking that biocViews are present...
* Checking package type based on biocViews...
Software
* Checking for non-trivial biocViews...
* Checking that biocViews come from the same category...
* Checking biocViews validity...
* Checking for recommended biocViews...
* Checking build system compatibility...
* Checking for blank lines in DESCRIPTION...
* Checking if DESCRIPTION is well formatted...
* Checking for whitespace in DESCRIPTION field names...
* Checking that Package field matches directory/tarball name...
* Checking for Version field...
* Checking for valid maintainer...
* Checking DESCRIPTION/NAMESPACE consistency...
* Checking vignette directory...
This is a software package
* Checking library calls...
* Checking for library/require of mypkg...
* Checking coding practice...
* NOTE: Avoid sapply(); use vapply()
Found in files:
foo_fighters.R (line 145, column 17)
* NOTE: Avoid 1:...; use seq_len() or seq_along()
Found in files:
foo_tre.R (line 83, column 67)
* Checking parsed R code in R directory, examples, vignettes...
* Checking function lengths..................................
* NOTE: Recommended function length <= 50 lines.
There are 2 functions > 50 lines.
foo_taj_2_gueule() (R/foo_rage.R, line 59): 194 lines
foo_l() (R/foo_fly.R, line 301): 113 lines
* Checking man page documentation...
* NOTE: Consider adding runnable examples to the following man pages which document exported
objects:
foo_lure.Rd
* Checking package NEWS...
* NOTE: Consider adding a NEWS file, so your package news will be included in Bioconductor
release announcements.
* Checking unit tests...
* Checking skip_on_bioc() in tests...
* Checking formatting of DESCRIPTION, NAMESPACE, man pages, R source, and vignette source...
* Checking if package already exists in CRAN...
* Checking for bioc-devel mailing list subscription...
* NOTE: Cannot determine whether maintainer is subscribed to the bioc-devel mailing list (requires admin credentials).  Subscribe here:
https://stat.ethz.ch/mailman/listinfo/bioc-devel
* Checking for support site registration...
Maintainer is registered at support site.


Summary:
ERROR count: 0
WARNING count: 0
NOTE count: 7
For detailed information about these checks, see the BiocCheck vignette, available at
https://bioconductor.org/packages/3.11/bioc/vignettes/BiocCheck/inst/doc/BiocCheck.html#interpreting-bioccheck-output
$error
character(0)

$warning
character(0)

$note
[1] "Consider clarifying how 4 object(s) are initialized. Maybe they are part of a data set loaded with data(), or perhaps part of an object referenced in with() or within()."       
[2] " Avoid sapply(); use vapply()"                                                                                                                                                    
[3] " Avoid 1:...; use seq_len() or seq_along()"                                                                                                                                       
[4] "Recommended function length <= 50 lines."                                                                                                                                         
[5] "Consider adding runnable examples to the following man pages which document exported objects:"                                                                                    
[6] "Consider adding a NEWS file, so your package news will be included in Bioconductor release announcements."                                                                                                                                                                          
[7] "Cannot determine whether maintainer is subscribed to the bioc-devel mailing list (requires adminncredentials).  Subscribe here: https://stat.ethz.ch/mailman/listinfo/bioc-devel"

Comme vous le voyez il reste des NOTEs, libre à vous de choisir de les corriger à présent ou attendre de voir si le reviewer vous l'impose (personnellement j'ai perdu à ce jeu comme vous pourrez le voir dans la section Retour d'expérience personnelle).

Le processus de soumission à Bioconductor

Tout semble bon ? Ça compile ? Vous passez tous les checks ? Bien, passons à la soumission de votre package.

  • Si ce n'est pas déjà fait, poussez votre package sur GitHub (et si vous n'y connaissez rien à git, lisez cet article 😉 ). C'est en effet l'hebergeur qu'a choisi Bioconductor pour gérer la soumission et le suivi du développement des packages (mais rien ne vous empêche d'avoir votre repo sur un GitLab, Bitbucket, etc. à côté bien évidemment).
  • Créez une issue sur le repo Contributions de Bioconductor en suivant le guide fournit. Rajoutez un petit bonjour et une phrase rapide de description, ça ne mange pas de pain non plus.
  • Créez un webhook pour votre repo. Cela va permettre au bot qui gère l'automatisation des soumissions d'être informé qu'il y a un nouveau package à aller chercher, et par la suite ça permettra qu'il soit informé quand vous effectuez des modifications sur votre repo (correction de code, ajout de nouvelle fonctionnalité...).
  • Ajoutez une clef SSH publique. Elle sera utilisée pour copier votre repo une fois votre package accepté.
  • Attendez... Votre package va être tagué comme "1. awaiting moderation'" en attendant, comme c'est dit dans le tag, qu'un reviewer soit assigné et vienne estimer si votre package vaut le coup d'être ajouté à Bioconductor.
  • Si sa pertinance est jugée bonne, le tag changera pour "2. review in progress" et le bot commencera alors à re-exécuter tous les checks que vous avez en théorie déjà passés haut la main.
  • Et là… Peu importe si vous avez mis tout votre cœur, sueur et temps dans vos checks, peu importe que ça roule sur votre PC et votre VM pour l'autre OS, vous allez avoir des erreurs ! Murphy, si tu nous entends…
  • Le bot va taguer votre issue avec ERROR ou WARNINGS ainsi que vous donner un lien vers un rapport détaillé des tests et erreurs obtenues pour chacun des OS (voir Figure 4, attention, ça pique les yeux). Et si Murphy vous a épargné, sautez directement trois points plus loin (ne passez pas par la case départ, ne touchez pas 20K).
  • Commence alors un nouveau cycle d'essai/erreur pour la correction des derniers soucis qui sont apparus. Cependant, pour que le bot prenne en compte les modifications, il vous faudra incrémenter la version de votre package. Vous commencez normalement à 0.99.0 (format imposé par Bioconductor) et à chaque modification que vous poussez et où vous souhaiterez une évaluation, il faudra passer à 0.99.1, 0.99.2, etc.
  • Notez que si le bot détecte des modifications poussées mais pas d'incrément, il taguera votre issue avec "VERSION BUMP REQUIRED". Il est donc de bonne pratique d'effectuer vos modifications sur une autre branche que le master, puis de merger avec le master quand vous avez fini votre modification et fait repasser tous les tests. Je ne saurais que trop vous recommander un modèle de branches git de ce type. Attention, n'incrémentez la version qu'après le merge, sinon le bot ne déclenche pas un nouveau build.

Figure 4 : Exemple d'un rapport de build que vous donne le bot Bioconductor en lien dans votre issue sur GitHub

  • Une fois toutes les modifications faites pour n'avoir aucunes erreurs et le précieux tag "OK", votre reviewer va évaluer votre package. C'est notamment à ce moment-là que vous verrez s'il vous demande d'adresser les NOTEs laissées, si vous ne suivez pas des guidelines non testées automatiquement comme l'utilisation des common classes. Il vous demandera probablement des modifications, et vous repartirez pour un nouveau cycle d'essai/erreur.
  • Enfin, une fois tout cela fini, l'encore plus précieux tag "3a. accepted" vous sera normalement attribué et votre package sera ajouté au repo de Bioconductor. Il sera alors publié avec la prochaine release à venir.
  • Si pour une raison ou une autre votre package était tagué "3b. declined", n'hésitez pas à communiquer avec les reviewers pour en connaitre la raison.

Vous pouvez souffler, c'est fait ! À présent, il ne vous reste plus qu'à savourer votre victoire, et vous assurer à chaque release de Bioconductor (2 fois par an donc) que votre package compile toujours et passe les checks.

Retour d'expérience personnelle et astuces

Avant de vous laisser avec quelques conclusions, je voudrais vous apporter rapidement quelques points basés sur un ressenti personnel de cette "expérience" :

  • Le processus est LONG, et c'est un euphémisme. Entre les checks de base qui ne passent pas, puis ceux de BiocChecks, il y a déja pas mal de temps passé à corriger le code, et je les passais pourtant régulièrement au cours de mon développement ainsi que mes tests unitaires + la même chose sur ma VM Windows. Quand une fois fini j'ai vu de nouvelles erreurs s'afficher dans le rapport du bot de Bioconductor, j'ai ragé assez fort... Je maintiens que la fonction BiocCheck devrait intégrer ces tests supplémentaires effectués sur leurs serveurs...
  • Si vous visez une release en particulier, prenez-vous-y à l'avance de facilement 1 à 2 mois. Les reviewers sont surchargés sur la fin et mettent du temps à faire les reviews. Le mien n'a d'ailleurs donné de signe de vie que deux semaines après la date buttoir de dépôt, me laissant moins de 5 jours pour corriger les nombreuses remarques qu'il m'a faites. J'ai finalement loupé la release…
  • J'ai aussi eu la malchance de tomber en plein changement de version majeure de R (3.6 -> 4.0) sur laquelle vous devez tester votre package, alors que la release officielle n'est pas encore disponible. À vous les joies de la compilation de la R-alpha ! Ou sinon vous faites les bourrins et vous vous servez du rapport d'erreur du bot Bioconductor mais c'est pas mal plus lent de faire les allers-retours, et je préfère avec un master (un peu) propre.
  • Mettez vos packages à jour avant de pousser sur Bioconductor, vraiment. Ça évite de ronchonner sur votre reviewer en disant que vous n'arrivez pas à reproduire ce bug magique qui n'existe que sur leurs serveurs et sur aucune de vos VM (et donc éviter de passer pour une andouille).
  • Pour ceux qui développent sous Linux et ne veulent pas se prendre la tête avec une VM, il existe win-builder. Vous pouvez d'ailleurs l'utiliser directement dans devtools avec la fonction check_win_release(). Je ne l'ai découvert qu'après avoir monté ma VM et compilé la R-alpha dessus...
  • À la relecture, Guillaume m'a parlé de R-hub. C'est une version plus poussée de win-builder qui vous permet de faire les checks sur sur une grande variété d'OS et de versions de chacun. Très utile aussi donc 😉
  • Certains reviewers sont mieux que d'autres : le mien était particulièrement désagréable dans sa façon de parler et assez zélé. Il m'a obligé à corriger l'intégralité des NOTEs. Mais d'autres sont charmants et comprennent que certaines NOTEs ne justifient pas un refus du package.

Conclusion

La soumission d'un package de Bioconductor est une épreuve en soi. Il y a de quoi se décourager à de maintes reprises, mais le résultat est là : un package visible par toute la communauté bioinfo. Il sera donc plus facilement partageable et pourra potentiellement devenir incontournable dans votre domaine.

Bon courage à vous si vous vous lancez dedans !

Merci à mes relecteurs Guillaume, lhtd, et neolem pour la relecture de cet article assez dense !

Références

L’article Pourquoi et comment déposer un package R sur Bioconductor ? est apparu en premier sur blog bioinformatique communautaire scientifique.

Viewing all 68 articles
Browse latest View live