Maîtrisez le cache de Rmarkdown !

Pour des rai­sons de repro­duc­tion de la science, il est impor­tant de conser­ver une trace de tout ce que l'on fait sur son ordi­na­teur. Pour cela, faire des rap­ports est la meilleure manière que je connaisse qui per­mette d'inclure le code et les résul­tats d'une ana­lyse. Pour faire ça bien avec R, on a déjà vu dans un article pré­cé­dant que les rap­ports Rmark­down étaient une très bonne solu­tion.

License : CC0 Public Domain

Le but de cet article va être de vous per­mettre de gagner du temps avec le cache de Rmark­down et quelques autres petites astuces. Pour cela, vous aurez besoin de 2 choses : avoir les bases du Rmark­down et avoir des bases géné­rales en infor­ma­tiques. J'utilise RStu­dio pour faire mes codes Rmark­down, je vous encou­rage à en faire de même.

Allez, c'est par­ti !

Alors si vous avez sui­vi l'article de notre cher ami jéro, vous avez un rap­port Rmark­down tout fait, qui date du 22 mars 2005 et qui pro­duit un PDF. Je vais com­men­cer par le com­men­ce­ment de votre fichier et vous don­ner 3 astuces du début de cha­cun de vos rap­ports :

  • Per­son­nel­le­ment, je fais très rare­ment des rap­ports PDF, pour des rai­sons de com­pa­ti­bi­li­té du code entre Win­dows et Mac OS ou GNU/​Linux. En effet, je suis obli­gé d'utiliser Win­dows au bou­lot et il y a beau­coup de pro­blèmes pour com­pi­ler les PDF. Je ne vais pas ren­trer dans les détails, mais rete­nez donc que je ne fais qua­si­ment que des fichiers HTML. Donc déjà, dans mon entête en YAML, je rem­place 
    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 uti­li­ser des fonc­tions, comme la fonc­tion Sys.time(), qui nous donne la date et l'heure. J'ai donc dans cha­cune 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.
  • Der­nière astuce avant de vrai­ment par­ler de cache, on peut défi­nir en début de rap­port des options qui vont s'appliquer à tous les chunks par la suite, par défaut. Donc je vous donne mes para­mètres par défaut et je vous les com­mente 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 pro­fite pour ouvrir une paren­thèse pour les plus avan­cés, si vous vou­lez avoir du code de sui­vi de l'avancement de votre com­pi­la­tion qui s'affiche dans votre console de com­pi­la­tion, il suf­fit de faire impri­mer à votre code R un mes­sage ou un war­ning, et de mettre les options du chunk cor­res­pon­dantes en FALSE, ce qui fera que l'impression du mes­sage ou du war­ning ne se feront pas dans le rap­port mais dans la console.

Voi­là, main­te­nant 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 par­ti­cu­liè­re­ment utile lorsque l'on fait des ana­lyses qui prennent beau­coup de temps à tour­ner. En effet, quand vous tra­vaillez sur votre rap­port, si l'un de vos chunks prend 2 jours à tour­ner, vous serez bien contents de ne pas avoir à le refaire à chaque fois que vous modi­fiez l'un des autres chunks. Ain­si, lorsqu'un chunk crée un cache, dans le dos­sier où vous avez sto­cké votre rap­port, un sous-dos­sier se crée por­tant le nom de votre rap­port + _​cache. Dans ce der­nier, un dos­sier html/​pdf/​autre se crée en fonc­tion du type de rap­port que vous pro­dui­sez, puis enfin à l'intérieur vous trou­ve­rez 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 !



Pour continuer la lecture :


Commentaires

Laisser un commentaire