# -*- mode: org -*- #+TITLE: Gérer un workflow avec snakemake #+DATE: August, 2019 #+STARTUP: overview indent #+OPTIONS: num:nil toc:t #+PROPERTY: header-args :eval never-export * Installer snakemake ** Linux par les distributions ** Mac, Windows par Anaconda * L'analyse de l'incidence du syndrome grippal revisitée Je vais reprendre l'exemple du module 3, l'analyse de l'incidence du syndrome grippal, et je vais refaire exactement la même analyse sous forme d'un workflow par =snakemake=. Ceci veut dire que pour l'instant, nous quittons le monde des documents computationnels que nous vous avons montré dans les modules 2 et 3, pour passer dans l'univers de la ligne de commande. Il y a des bonnes raisons pour cela, que je vous donnerai plus tard. Et vous verrez aussi le retour des documents computationnels à la fin de ce tutoriel, même si ce sera dans un rôle moins central. Un workflow est composé de tâches dont chacun correspond à un bout du calcul total. Une tâche est typiquement l'exécution d'une commande ou d'un script. Les tâches communiques entre eux par des fichiers - au moins dans la vision de =snakemake= (et d'autres descendants de =make=). Pour faire le lien avec la programmation dans un langage comme Python ou R, une tâche est l'appel à une fonction, et les paramètres et les valeurs de retour sont stockés dans des fichiers. Il y a beaucoup de liberté dans la décomposition d'un calcul en tâches d'un workflow. Souvent les critères sont plutôt techniques que scientifiques: une tâche peut alors correspondre à l'exécution d'un logiciel, ou à une étape du calcul qui est fait sur un ordinateur précis. Pour mon analyse je propose la décomposition suivante, qui est assez arbitraire : 1. Téléchargement des données du site du Réseau Sentinelles 2. Pré-traitement: extraction des données utilisées, vérifications 3. Visualisation: génération des plots 4. Calcul des incidences annuelles 5. Calcul de l'histogramme des incidences annuelles Pour faire les calculs, je vais recycler le code du module 3, sans les commenter de nouveau ici. ** Préparation Un workflow finit par utiliser beaucoup de fichiers, donc je commence par la création d'un répertoire qui contient tout: #+begin_src sh :session *snakemake* :results output :exports both mkdir incidence_syndrome_grippal_snakemake cd incidence_syndrome_grippal_snakemake #+end_src #+RESULTS: En plus, je crée un répertoire pour les fichiers de données, #+begin_src sh :session *snakemake* :results output :exports both mkdir data #+end_src #+RESULTS: et un autre pour les scripts en Python ou R: #+begin_src sh :session *snakemake* :results output :exports both mkdir scripts #+end_src #+RESULTS: ** 1ère tâche: le téléchargement des données Pour télécharger un fichier, inutile d'écrire du code: l'utilitaire =wget= fait ce qu'il faut. La ligne de commande #+begin_src sh :session *snakemake* :results output :exports both wget -O data/weekly-incidence.csv http://www.sentiweb.fr/datasets/incidence-PAY-3.csv #+end_src #+RESULTS: : --2019-08-28 16:20:33-- http://www.sentiweb.fr/datasets/incidence-PAY-3.csv : Resolving www.sentiweb.fr (www.sentiweb.fr)... 134.157.220.17 : Connecting to www.sentiweb.fr (www.sentiweb.fr)|134.157.220.17|:80... connected. : HTTP request sent, awaiting response... 200 OK : Length: unspecified [text/csv] : Saving to: 'data/weekly-incidence.csv' : ] 0 --.-KB/s data/weekly-inciden [ <=> ] 79.88K --.-KB/s in 0.05s : : 2019-08-28 16:20:34 (1.48 MB/s) - 'data/weekly-incidence.csv' saved [81800] fait ce qu'il faut, et dépose les données dans le fichier =data/weekly-incidence.csv=. Je le supprime parce que je veux faire le téléchargement dans mon workflow! #+begin_src sh :session *snakemake* ::results output :exports both rm data/weekly-incidence.csv #+end_src #+RESULTS: Je vais commencer la rédaction du =Snakefile=, le fichier qui déinit mon workflow: #+begin_src :exports both :tangle incidence_syndrome_grippal_snakemake/Snakefile rule download: output: "data/weekly-incidence.csv" shell: "wget -O {output} http://www.sentiweb.fr/datasets/incidence-PAY-3.csv" #+end_src Un =Snakefile= consiste de /règles/ qui définissent les tâches. Chaque règle a un nom, ici j'ai choisi /download/. Une règle liste aussi les fichiers d'entrée (aucun dans ce cas) et de sortie (notre fichier de données). Enfin, il faut dire ce qui est à faire pour exécuter la tâche, ce qui est ici la commande =wget=. Pour exécuter cette tâche, il y a deux façons de faire: on peut demander à =snakemake= d'exécuter la règle =download=: #+begin_src sh :session *snakemake* ::results output :exports both snakemake download #+end_src #+RESULTS: | Building | DAG | of | jobs... | | | | | | | | | Using | shell: | /bin/bash | | | | | | | | | | Provided | cores: | 1 | | | | | | | | | | Rules | claiming | more | threads | will | be | scaled | down. | | | | | Job | counts: | | | | | | | | | | | | count | jobs | | | | | | | | | | | 1 | download | | | | | | | | | | | 1 | | | | | | | | | | | [Wed | Aug | 28 | 16:38:03 | 2019] | | | | | | | | rule | download: | | | | | | | | | | | output: | data/weekly-incidence.csv | | | | | | | | | | | jobid: | 0 | | | | | | | | | | | --2019-08-28 | 16:38:03-- | http://www.sentiweb.fr/datasets/incidence-PAY-3.csv | | | | | | | | | | Resolving | www.sentiweb.fr | (www.sentiweb.fr)... | 134.157.220.17 | | | | | | | | | Connecting | to | www.sentiweb.fr | (www.sentiweb.fr) | 134.157.220.17 | :80... | connected. | | | | | | HTTP | request | sent, | awaiting | response... | 200 | OK | | | | | | Length: | unspecified | [text/csv] | | | | | | | | | | Saving | to: | 'data/weekly-incidence.csv' | | | | | | | | | | ] | 0 | --.-KB/s | data/weekly-inciden | [ | <=> | ] | 79.88K | --.-KB/s | in | 0.02s | | 2019-08-28 | 16:38:03 | (3.71 | MB/s) | 0 | 'data/weekly-incidence.csv' | saved | [81800] | | | | | [Wed | Aug | 28 | 16:38:03 | 2019] | | | | | | | | Finished | job | 0 | | | | | | | | | | ) | done | | | | | | | | | | | Complete | log: | /home/hinsen/projects/RR_MOOC/repos-session02/mooc-rr-ressources/module6/ressources/incidence_syndrome_grippal_snakemake/.snakemake/log/2019-08-28T163803.322303.snakemake.log | | | | | | | | | ou encore on peut lui demander de produire le résultat souhaité: #+begin_src sh :session *snakemake* ::results output :exports both snakemake data/weekly-incidence.csv #+end_src #+RESULTS: | Building | DAG | of | jobs... | | Nothing | to | be | done. | | Complete | log: | /home/hinsen/projects/RR_MOOC/repos-session02/mooc-rr-ressources/module6/ressources/incidence_syndrome_grippal_snakemake/.snakemake/log/2019-08-28T163822.540694.snakemake.log | | En regardant bien ce que =snakemake= dit au deuxième tour, il s'est rendu compte qu'il n'y a rien à faire, parce que le fichier souhaité existe déjà. Voici un premier avantage important d'un workflow: une tâche n'est exécutée que s'il est nécessaire. Quand une tâche met deux heures à exécuter, c'est appréciable. ** 2ème tâche: le pré-traitement des données La deuxième tâche est le pré-traitement: en partant du fichier téléchargé du Réseau Sentinelle, il faut extraire juste les éléments nécessaires, et il faut vérifier s'il y a des données manquantes ou des erreurs. Dans un document computationnel, j'avais procédé pas par pas, en inspectant les résultats à chaque étape. Dans mon workflow, le pré-traitement devient une seule tâche, exécutée en bloc. Il faut donc bien réfléchir à ce qu'on attend comme résultat. En fait, il faut deux fichiers de sortie: un qui contient les données qui seront analysées par la suite, et un autre qui contient les éventuels messages d'erreur. Avec ça, la deuxième règle s'écrit assez vite: #+begin_src :exports both :tangle incidence_syndrome_grippal_snakemake/Snakefile rule preprocess: input: "data/weekly-incidence.csv" output: data="data/preprocessed-weekly-incidence.csv", errorlog="data/errors-from-preprocessing.txt" script: "scripts/preprocess.py" #+end_src Il y a donc un fichier d'entrée, qui est le résultat de la tâche /download/. Et il y a les deux fichiers de sortie. Enfin, pour faire le travail, j'ai opté pour un script Python cette fois. =snakemake= reconnaît le langage par l'extension =.py=. Le contenu de ce script est presque un copier-coller d'un document computationnel du module 3, plus précisément du document que j'ai montré dans le parcours Emacs/Org-Mode: #+begin_src python :exports both :tangle incidence_syndrome_grippal_snakemake/scripts/preprocess.py # Libraries used by this script: import datetime # for date conversion import csv # for writing output to a CSV file # Read the CSV file into memory data = open(snakemake.input[0], 'rb').read() # Decode the Latin-1 character set, # remove white space at both ends, # and split into lines. lines = data.decode('latin-1') \ .strip() \ .split('\n') # Discard the first line, which contains a comment data_lines = lines[1:] # Split each line into columns table = [line.split(',') for line in data_lines] # Remove records with missing data and write # the removed records to a separate file for inspection. with open(snakemake.output.errorlog, "w") as errorlog: valid_table = [] for row in table: missing = any([column == '' for column in row]) if missing: errorlog.write("Missing data in record\n") errorlog.write(str(row)) errorlog.write("\n") else: valid_table.append(row) # Extract the two relevant columns, "week" and "inc" week = [row[0] for row in valid_table] assert week[0] == 'week' del week[0] inc = [row[2] for row in valid_table] assert inc[0] == 'inc' del inc[0] data = list(zip(week, inc)) # Check for obviously out-of-range values with open(snakemake.output.errorlog, "a") as errorlog: for week, inc in data: if len(week) != 6 or not week.isdigit(): errorlog.write("Suspect value in column 'week': {week}\n") if not inc.isdigit(): errorlog.write("Suspect value in column 'inc': {inc}\n") # Convert year/week by date of the corresponding Monday, # then sort by increasing date converted_data = \ [(datetime.datetime.strptime(year_and_week + ":1" , '%G%V:%u').date(), inc) for year_and_week, inc in data] converted_data.sort(key = lambda record: record[0]) # Check that consecutive dates are seven days apart with open(snakemake.output.errorlog, "a") as errorlog: dates = [date for date, _ in converted_data] for date1, date2 in zip(dates[:-1], dates[1:]): if date2-date1 != datetime.timedelta(weeks=1): errorlog.write(f"{date2-date1} between {date1} and {date2}\n") # Write data to a CSV file with two columns: # 1. the date of the Monday of each week, in ISO format # 2. the incidence estimate for that week with open(snakemake.output.data, "w") as csvfile: csv_writer = csv.writer(csvfile) csv_writer.writerow(["week_starting", "incidence"]) for row in converted_data: csv_writer.writerow(row) #+end_src Ce qui saute aux yeux d'abord, c'est =snakemake.input[0]= comme nom de fichier. Le nom =snakemake= semble venir de nulle part: il n'est ni défini dans le script, ni importé d'un module. En fait, c'est bien =snakemake= qui définit ce nom dans l'interprète Python avant de lancer le script. Il permet d'accèder aux définitions du =Snakefile=, et notamment aux noms des fichiers. Sinon, il y a deux modifications par rapport au code du module 3. Premièrement, les messages d'erreurs sont écrits dans un fichier. Deuxièmement, les données finales sont écrites également dans un fichier, en utilisant le format CSV. Pour appliquer le pré-traitement, demandons à =snakemake=: #+begin_src sh :session *snakemake* :results output :exports both snakemake preprocess #+end_src #+RESULTS: #+begin_example Building DAG of jobs... Using shell: /bin/bash Provided cores: 1 Rules claiming more threads will be scaled down. Job counts: count jobs 1 preprocess 1 [Wed Aug 28 18:38:33 2019] rule preprocess: input: data/weekly-incidence.csv output: data/preprocessed-weekly-incidence.csv, data/errors-from-preprocessing.txt jobid: 0 [Wed Aug 28 18:38:34 2019] Finished job 0. ) done Complete log: /home/hinsen/projects/RR_MOOC/repos-session02/mooc-rr-ressources/module6/ressources/incidence_syndrome_grippal_snakemake/.snakemake/log/2019-08-28T183833.807546.snakemake.log #+end_example Voyons s'il y a eu des problèmes: #+begin_src sh :session *snakemake* :results output :exports both cat data/errors-from-preprocessing.txt #+end_src #+RESULTS: : Missing data in record : ['198919', '3', '0', '', '', '0', '', '', 'FR', 'France'] : 14 days, 0:00:00 between 1989-05-01 and 1989-05-15 En effet, on avait vu dans le module 3 qu'il y a un point manquant dans ce jeu de données. Quant aux données, je vais afficher juste le début: #+begin_src sh :session *snakemake* :results output :exports both head -10 data/preprocessed-weekly-incidence.csv #+end_src #+RESULTS: #+begin_example week_starting,incidence 1984-10-29,68422 1984-11-05,135223 1984-11-12,87330 1984-11-19,72029 1984-11-26,78620 1984-12-03,101073 1984-12-10,123680 1984-12-17,101726 1984-12-24,84830 #+end_example Ça a l'air pas mal! ** 3ème tâche: préparer les plots La règle pour faire les plots ne présente plus aucune surprise: #+begin_src :exports both :tangle incidence_syndrome_grippal_snakemake/Snakefile rule plot: input: "data/preprocessed-weekly-incidence.csv" output: "data/weekly-incidence-plot.png", "data/weekly-incidence-plot-last-years.png" script: "scripts/incidence-plots.R" #+end_src Il y a les données pré-traitées à l'entrée, et deux fichiers image à la sortie, créées par un script, cette fois en langage R: #+begin_src R :exports both :tangle incidence_syndrome_grippal_snakemake/scripts/incidence-plots.R # Read in the data and convert the dates data = read.csv(snakemake@input[[1]]) data$week_starting <- as.Date(data$week_starting) # Plot the complete incidence dataset png(filename=snakemake@output[[1]]) plot(data, type="l", xlab="Date", ylab="Weekly incidence") dev.off() # Zoom on the last four years png(filename=snakemake@output[[2]]) plot(tail(data, 4*52), type="l", xlab="Date", ylab="Weekly incidence") dev.off() #+end_src Comme pour le script Python de l'étape précedente, l'accès aux noms des fichiers se fait par le nom =snakemake= qui est créé par... =snakemake=. Passons à l'exécution: #+begin_src sh :session *snakemake* :results output :exports both snakemake plot #+end_src #+RESULTS: #+begin_example Building DAG of jobs... Using shell: /bin/bash Provided cores: 1 Rules claiming more threads will be scaled down. Job counts: count jobs 1 plot 1 [Wed Aug 28 19:43:42 2019] rule plot: input: data/preprocessed-weekly-incidence.csv output: data/weekly-incidence-plot.png, data/weekly-incidence-plot-last-years.png jobid: 0 During startup - Warning messages: 1: Setting LC_COLLATE failed, using "C" 2: Setting LC_TIME failed, using "C" 3: Setting LC_MESSAGES failed, using "C" 4: Setting LC_MONETARY failed, using "C" Warning message: Y-%m-%d", tz = "GMT") : unknown timezone 'zone/tz/2019b.1.0/zoneinfo/Europe/Paris' null device 1 null device 1 [Wed Aug 28 19:43:43 2019] Finished job 0. ) done Complete log: /home/hinsen/projects/RR_MOOC/repos-session02/mooc-rr-ressources/module6/ressources/incidence_syndrome_grippal_snakemake/.snakemake/log/2019-08-28T194342.688492.snakemake.log #+end_example Voici les deux plots: [[file:incidence_syndrome_grippal_snakemake/data/weekly-incidence-plot.png]] [[file:incidence_syndrome_grippal_snakemake/data/weekly-incidence-plot-last-years.png]] ** 4ème tâche: calculer l'incidence annuelle Écrire les règles pour =snakemake= devient vite une routine: #+begin_src :exports both :tangle incidence_syndrome_grippal_snakemake/Snakefile rule annual_incidence: input: "data/preprocessed-weekly-incidence.csv" output: "data/annual-incidence.csv" script: "scripts/annual-incidence.R" #+end_src Et le script en langage R ressemble fortement au code du module 3: #+begin_src R :exports both :tangle incidence_syndrome_grippal_snakemake/scripts/annual-incidence.R # Read in the data and convert the dates data = read.csv(snakemake@input[[1]]) names(data) <- c("date", "incidence") data$date <- as.Date(data$date) # A function that extracts the peak for year N yearly_peak = function(year) { start = paste0(year-1,"-08-01") end = paste0(year,"-08-01") records = data$date > start & data$date <= end sum(data$incidence[records]) } # The years for which we have the full peak years <- 1986:2018 # Make a new data frame for the annual incidences annual_data = data.frame(year = years, incidence = sapply(years, yearly_peak)) # write output file write.csv(annual_data, file=snakemake@output[[1]], row.names=FALSE) #+end_src Allons-y! #+begin_src sh :session *snakemake* :results output :exports both snakemake annual_incidence #+end_src #+RESULTS: #+begin_example Building DAG of jobs... Using shell: /bin/bash Provided cores: 1 Rules claiming more threads will be scaled down. Job counts: count jobs 1 annual_incidence 1 [Wed Aug 28 19:51:26 2019] rule annual_incidence: input: data/preprocessed-weekly-incidence.csv output: data/annual-incidence.csv jobid: 0 During startup - Warning messages: 1: Setting LC_COLLATE failed, using "C" 2: Setting LC_TIME failed, using "C" 3: Setting LC_MESSAGES failed, using "C" 4: Setting LC_MONETARY failed, using "C" Warning message: Y-%m-%d", tz = "GMT") : unknown timezone 'zone/tz/2019b.1.0/zoneinfo/Europe/Paris' [Wed Aug 28 19:51:26 2019] Finished job 0. ) done Complete log: /home/hinsen/projects/RR_MOOC/repos-session02/mooc-rr-ressources/module6/ressources/incidence_syndrome_grippal_snakemake/.snakemake/log/2019-08-28T195126.602879.snakemake.log #+end_example Voyons le début du résultat: #+begin_src sh :session *snakemake* :results output :exports both head -10 data/annual-incidence.csv #+end_src #+RESULTS: #+begin_example "year","incidence" 1986,5100540 1987,2861556 1988,2766142 1989,5460155 1990,5233987 1991,1660832 1992,2576347 1993,2703708 1994,3515735 #+end_example ** 5ème tâche: l'histogramme Et pour finir, encore un petit script en R: #+begin_src :exports both :tangle incidence_syndrome_grippal_snakemake/Snakefile rule histogram: input: "data/annual-incidence.csv" output: "data/annual-incidence-histogram.png" script: "scripts/annual-incidence-histogram.R" #+end_src #+begin_src R :exports both :tangle incidence_syndrome_grippal_snakemake/scripts/annual-incidence-histogram.R # Read in the data and convert the dates data = read.csv(snakemake@input[[1]]) # Plot the histogram png(filename=snakemake@output[[1]]) hist(data$incidence, breaks=10, xlab="Annual incidence", ylab="Number of observations", main="") dev.off() #+end_src #+begin_src sh :session *snakemake* :results output :exports both snakemake histogram #+end_src #+RESULTS: #+begin_example Building DAG of jobs... Using shell: /bin/bash Provided cores: 1 Rules claiming more threads will be scaled down. Job counts: count jobs 1 histogram 1 [Wed Aug 28 19:54:24 2019] rule histogram: input: data/annual-incidence.csv output: data/annual-incidence-histogram.png jobid: 0 During startup - Warning messages: 1: Setting LC_COLLATE failed, using "C" 2: Setting LC_TIME failed, using "C" 3: Setting LC_MESSAGES failed, using "C" 4: Setting LC_MONETARY failed, using "C" null device 1 [Wed Aug 28 19:54:24 2019] Finished job 0. ) done Complete log: /home/hinsen/projects/RR_MOOC/repos-session02/mooc-rr-ressources/module6/ressources/incidence_syndrome_grippal_snakemake/.snakemake/log/2019-08-28T195424.640999.snakemake.log #+end_example [[file:incidence_syndrome_grippal_snakemake/data/annual-incidence-histogram.png]] * Travailler avec un workflow Jusqu'ici, j'ai lancé chaque tâche de mon workflow à la main, une par une. Avec le même effort, j'aurais pu lancer directement les divers scripts qui font le travail de fon. Autrement dit, =snakemake= ne m'a rien apporté, autre que sortir les noms des fichiers des scripts, qui devienennt ainsi un peu plus généraux, pour les transférer dans le grand script maître qui est =Snakefile=. J'ai déjà évoqué un avantage du workflow: les tâches ne sont exécutées qu'en cas de besoin. Par exemple, la commande =snakemake plot= exécute le script =scripts/incidence-plots.R= seulement si l'une des conditions suivantes est satisfaite: 1. Un des deux fichiers =data/weekly-incidence-plot.png= et =data/weekly-incidence-plot-last-years.png= est absent. 2. Un des deux fichiers =data/weekly-incidence-plot.png= et =data/weekly-incidence-plot-last-years.png= a une date de modification antérieure à la date de modification du fichier d'entrée, =data/preprocessed-weekly-incidence.csv=. Vérifions cela, en demandant en plus à =snakemake= d'expliquer son raisonnement avec l'option =-r=: #+begin_src sh :session *snakemake* :results output :exports both snakemake -r plot #+end_src #+RESULTS: : Building DAG of jobs... : Nothing to be done. : Complete log: /home/hinsen/projects/RR_MOOC/repos-session02/mooc-rr-ressources/module6/ressources/incidence_syndrome_grippal_snakemake/.snakemake/log/2019-08-29T143441.536916.snakemake.log Maintenant les plots sont là et à jour. Je vais simuler la modification du fichier d'entrée avec la commande =touch= et relancer: #+begin_src sh :session *snakemake* :results output :exports both touch data/preprocessed-weekly-incidence.csv snakemake -r plot #+end_src #+RESULTS: #+begin_example Building DAG of jobs... Using shell: /bin/bash Provided cores: 1 Rules claiming more threads will be scaled down. Job counts: count jobs 1 plot 1 [Thu Aug 29 14:34:47 2019] rule plot: input: data/preprocessed-weekly-incidence.csv output: data/weekly-incidence-plot.png, data/weekly-incidence-plot-last-years.png jobid: 0 reason: Updated input files: data/preprocessed-weekly-incidence.csv During startup - Warning messages: 1: Setting LC_COLLATE failed, using "C" 2: Setting LC_TIME failed, using "C" 3: Setting LC_MESSAGES failed, using "C" 4: Setting LC_MONETARY failed, using "C" Warning message: Y-%m-%d", tz = "GMT") : unknown timezone 'zone/tz/2019b.1.0/zoneinfo/Europe/Paris' null device 1 null device 1 [Thu Aug 29 14:34:48 2019] Finished job 0. ) done Complete log: /home/hinsen/projects/RR_MOOC/repos-session02/mooc-rr-ressources/module6/ressources/incidence_syndrome_grippal_snakemake/.snakemake/log/2019-08-29T143447.767368.snakemake.log #+end_example Attention, =snakemake= ne regarde que les fichiers listés sous "input", pas les fichiers listés sous "scripts". Autrement dit, la modification d'un script n'entraîne pas sa ré-exécution ! #+begin_src sh :session *snakemake* :results output :exports both touch scripts/incidence-plots.R snakemake -r plot #+end_src #+RESULTS: : : Building DAG of jobs... : Nothing to be done. : Complete log: /home/hinsen/projects/RR_MOOC/repos-session02/mooc-rr-ressources/module6/ressources/incidence_syndrome_grippal_snakemake/.snakemake/log/2019-08-29T143243.100094.snakemake.log Je considère ceci un défaut de =snakemake=, car le script est une donnée d'entrée du calcul tout comme la séquence de chiffres à plotter. Un petit astuce permet de corriger ce défaut (à condition d'y penser chaque fois qu'on écrit une règle !): on peut rajouter le fichier script à la liste "input": #+begin_src :exports both rule plot: input: "data/preprocessed-weekly-incidence.csv", "scripts/incidence-plots.R" output: "data/weekly-incidence-plot.png", "data/weekly-incidence-plot-last-years.png" script: "scripts/incidence-plots.R" #+end_src On peut aussi demander à =snakemake= de lancer une tâche même si ceci ne lui semble pas nécessaire, avec l'option =-f= (force): #+begin_src sh :session *snakemake* :results output :exports both snakemake -f plot #+end_src #+RESULTS: #+begin_example Building DAG of jobs... Using shell: /bin/bash Provided cores: 1 Rules claiming more threads will be scaled down. Job counts: count jobs 1 plot 1 [Thu Aug 29 14:56:24 2019] rule plot: input: data/preprocessed-weekly-incidence.csv output: data/weekly-incidence-plot.png, data/weekly-incidence-plot-last-years.png jobid: 0 During startup - Warning messages: 1: Setting LC_COLLATE failed, using "C" 2: Setting LC_TIME failed, using "C" 3: Setting LC_MESSAGES failed, using "C" 4: Setting LC_MONETARY failed, using "C" Warning message: Y-%m-%d", tz = "GMT") : unknown timezone 'zone/tz/2019b.1.0/zoneinfo/Europe/Paris' null device 1 null device 1 [Thu Aug 29 14:56:24 2019] Finished job 0. ) done Complete log: /home/hinsen/projects/RR_MOOC/repos-session02/mooc-rr-ressources/module6/ressources/incidence_syndrome_grippal_snakemake/.snakemake/log/2019-08-29T145624.563473.snakemake.log #+end_example Le plus souvent, ce qu'on veut, c'est une mise à jour de tous les résultats suite à une modification. La bonne façon d'y arriver est de rajouter une nouvelle règle, par convention appellée =all=, qui ne fait rien mais demande à l'entrée tous les fichiers créés par toutes les autres tâches : #+begin_src :exports both :tangle incidence_syndrome_grippal_snakemake/Snakefile rule all: input: "data/weekly-incidence.csv", "data/preprocessed-weekly-incidence.csv", "data/weekly-incidence-plot.png", "data/weekly-incidence-plot-last-years.png", "data/annual-incidence.csv", "data/annual-incidence-histogram.png" #+end_src La mise à jour complète se fait alors avec #+begin_src sh :session *snakemake* :results output :exports both snakemake all #+end_src #+RESULTS: #+begin_example Building DAG of jobs... Using shell: /bin/bash Provided cores: 1 Rules claiming more threads will be scaled down. Job counts: count jobs 1 all 1 annual_incidence 1 histogram 1 plot 1 preprocess 5 [Thu Aug 29 15:09:50 2019] rule preprocess: input: data/weekly-incidence.csv output: data/preprocessed-weekly-incidence.csv, data/errors-from-preprocessing.txt jobid: 2 [Thu Aug 29 15:09:50 2019] Finished job 2. ) done [Thu Aug 29 15:09:50 2019] rule annual_incidence: input: data/preprocessed-weekly-incidence.csv output: data/annual-incidence.csv jobid: 4 During startup - Warning messages: 1: Setting LC_COLLATE failed, using "C" 2: Setting LC_TIME failed, using "C" 3: Setting LC_MESSAGES failed, using "C" 4: Setting LC_MONETARY failed, using "C" Warning message: Y-%m-%d", tz = "GMT") : unknown timezone 'zone/tz/2019b.1.0/zoneinfo/Europe/Paris' [Thu Aug 29 15:09:51 2019] Finished job 4. ) done [Thu Aug 29 15:09:51 2019] rule plot: input: data/preprocessed-weekly-incidence.csv output: data/weekly-incidence-plot.png, data/weekly-incidence-plot-last-years.png jobid: 3 During startup - Warning messages: 1: Setting LC_COLLATE failed, using "C" 2: Setting LC_TIME failed, using "C" 3: Setting LC_MESSAGES failed, using "C" 4: Setting LC_MONETARY failed, using "C" Warning message: Y-%m-%d", tz = "GMT") : unknown timezone 'zone/tz/2019b.1.0/zoneinfo/Europe/Paris' null device 1 null device 1 [Thu Aug 29 15:09:51 2019] Finished job 3. ) done [Thu Aug 29 15:09:51 2019] rule histogram: input: data/annual-incidence.csv output: data/annual-incidence-histogram.png jobid: 5 During startup - Warning messages: 1: Setting LC_COLLATE failed, using "C" 2: Setting LC_TIME failed, using "C" 3: Setting LC_MESSAGES failed, using "C" 4: Setting LC_MONETARY failed, using "C" null device 1 [Thu Aug 29 15:09:51 2019] Finished job 5. ) done [Thu Aug 29 15:09:51 2019] localrule all: input: data/weekly-incidence.csv, data/preprocessed-weekly-incidence.csv, data/weekly-incidence-plot.png, data/weekly-incidence-plot-last-years.png, data/annual-incidence.csv, data/annual-incidence-histogram.png jobid: 0 [Thu Aug 29 15:09:51 2019] Finished job 0. ) done Complete log: /home/hinsen/projects/RR_MOOC/repos-session02/mooc-rr-ressources/module6/ressources/incidence_syndrome_grippal_snakemake/.snakemake/log/2019-08-29T150950.572093.snakemake.log #+end_example Les plus paresseux mettent la règle =all= au début du =Snakefile=, parce qu'en absence de tâche (ou fichier) nommé sur la ligne de commande, =snakemake= utilise la première régle qu'il trouve, et pour la mise à jour total, il suffit de taper =snakemake=. Pour rédémarrer de zéro, donc exécuter toutes les tâches, on fait: #+begin_src sh :session *snakemake* :results output :exports both snakemake --forceall all #+end_src #+RESULTS: #+begin_example Building DAG of jobs... Using shell: /bin/bash Provided cores: 1 Rules claiming more threads will be scaled down. Job counts: count jobs 1 download 1 [Thu Aug 29 14:56:31 2019] rule download: output: data/weekly-incidence.csv jobid: 0 --2019-08-29 14:56:31-- http://www.sentiweb.fr/datasets/incidence-PAY-3.csv Resolving www.sentiweb.fr (www.sentiweb.fr)... 134.157.220.17 Connecting to www.sentiweb.fr (www.sentiweb.fr)|134.157.220.17|:80... connected. HTTP request sent, awaiting response... 200 OK Length: unspecified [text/csv] Saving to: 'data/weekly-incidence.csv' ] 0 --.-KB/s data/weekly-inciden [ <=> ] 79.88K --.-KB/s in 0.02s 2019-08-29 14:56:31 (4.82 MB/s) - 'data/weekly-incidence.csv' saved [81800] [Thu Aug 29 14:56:31 2019] Finished job 0. ) done Complete log: /home/hinsen/projects/RR_MOOC/repos-session02/mooc-rr-ressources/module6/ressources/incidence_syndrome_grippal_snakemake/.snakemake/log/2019-08-29T145631.175661.snakemake.log #+end_example Comme =snakemake= gère bien toutes les dépendances entre les données, il peut même nous en faire un dessin, ce qui est fort utile quand les workflows augmentent en taille: #+begin_src sh :session *snakemake* :results output :exports both snakemake --forceall --dag all | dot -Tpng > graph.png #+end_src #+RESULTS: : Building DAG of jobs... [[file:incidence_syndrome_grippal_snakemake/graph.png]] Pour comprendre cette ligne de commande, il faut savoir que =snakemake= produit ce graphe en exécutant les tâches. Voilà pourquoi il faut les arguments =--forceall all= pour être sûr que toutes les tâches seront exécutées. =dot= est un logiciel qui fait partie de la collection [[https://graphviz.org/][Graphviz]], son rôle est de traduire une description textuelle d'un graph en graphique. Le sigle "DAG" veut dire "Directed Acyclic Graph", graphe orienté acyclique. C'est un type de graphe qu'on trouve naturellement dans les descriptions formelles de dépendences parce que "acyclique" veut simplement dire qu'aucun fichier de données produit ne peut avoir soi-même comme dépendence, directement ou indirectement. En regardant bien ce dessin, vous remarquez peut-être qu'il y a deux branches indépendantes. Une fois qu'on a fait "preprocess", on peut attaquer ou "plot" ou "annual_incidence" suivi de "histogram". Mais ça veut dire aussi qu'on peut exécuter ces deux branches en parallèle et gagner du temps, pourvu qu'on a un ordinateur avec au moins deux processeurs. En fait, =snakemake= s'en charge automatiquement si on lui indique combien de processeurs utiliser: #+begin_src sh :session *snakemake* :results output :exports both snakemake --cores 2 --forceall all #+end_src #+RESULTS: #+begin_example Building DAG of jobs... Using shell: /bin/bash Provided cores: 2 Rules claiming more threads will be scaled down. Job counts: count jobs 1 all 1 annual_incidence 1 download 1 histogram 1 plot 1 preprocess 6 [Thu Aug 29 15:31:30 2019] rule download: output: data/weekly-incidence.csv jobid: 1 --2019-08-29 15:31:30-- http://www.sentiweb.fr/datasets/incidence-PAY-3.csv Resolving www.sentiweb.fr (www.sentiweb.fr)... 134.157.220.17 Connecting to www.sentiweb.fr (www.sentiweb.fr)|134.157.220.17|:80... connected. HTTP request sent, awaiting response... 200 OK Length: unspecified [text/csv] Saving to: 'data/weekly-incidence.csv' ] 0 --.-KB/s data/weekly-inciden [ <=> ] 79.88K --.-KB/s in 0.04s 2019-08-29 15:31:30 (1.78 MB/s) - 'data/weekly-incidence.csv' saved [81800] [Thu Aug 29 15:31:30 2019] Finished job 1. ) done [Thu Aug 29 15:31:30 2019] rule preprocess: input: data/weekly-incidence.csv output: data/preprocessed-weekly-incidence.csv, data/errors-from-preprocessing.txt jobid: 2 [Thu Aug 29 15:31:30 2019] Finished job 2. ) done [Thu Aug 29 15:31:30 2019] rule plot: input: data/preprocessed-weekly-incidence.csv output: data/weekly-incidence-plot.png, data/weekly-incidence-plot-last-years.png jobid: 3 [Thu Aug 29 15:31:30 2019] rule annual_incidence: input: data/preprocessed-weekly-incidence.csv output: data/annual-incidence.csv jobid: 4 During startup - During startup - Warning messages: Warning messages: 1: Setting LC_COLLATE failed, using "C" 1: Setting LC_COLLATE failed, using "C" 2: Setting LC_TIME failed, using "C" 2: Setting LC_TIME failed, using "C" 3: Setting LC_MESSAGES failed, using "C" 3: Setting LC_MESSAGES failed, using "C" 4: Setting LC_MONETARY failed, using "C" 4: Setting LC_MONETARY failed, using "C" Warning message: Warning message: Y-%m-%d", tz = "GMT") :In strptime(xx, f <- "%Y-%m-%d", tz = "GMT") : unknown timezone 'zone/tz/2019b.1.0/zoneinfo/Europe/Paris' unknown timezone 'zone/tz/2019b.1.0/zoneinfo/Europe/Paris' [Thu Aug 29 15:31:30 2019] Finished job 4. ) done [Thu Aug 29 15:31:30 2019] rule histogram: input: data/annual-incidence.csv output: data/annual-incidence-histogram.png jobid: 5 null device 1 null device 1 [Thu Aug 29 15:31:30 2019] Finished job 3. ) done During startup - Warning messages: 1: Setting LC_COLLATE failed, using "C" 2: Setting LC_TIME failed, using "C" 3: Setting LC_MESSAGES failed, using "C" 4: Setting LC_MONETARY failed, using "C" null device 1 [Thu Aug 29 15:31:31 2019] Finished job 5. ) done [Thu Aug 29 15:31:31 2019] localrule all: input: data/weekly-incidence.csv, data/preprocessed-weekly-incidence.csv, data/weekly-incidence-plot.png, data/weekly-incidence-plot-last-years.png, data/annual-incidence.csv, data/annual-incidence-histogram.png jobid: 0 [Thu Aug 29 15:31:31 2019] Finished job 0. ) done Complete log: /home/hinsen/projects/RR_MOOC/repos-session02/mooc-rr-ressources/module6/ressources/incidence_syndrome_grippal_snakemake/.snakemake/log/2019-08-29T153130.204927.snakemake.log #+end_example * Vers la gestion de données plus volumineuses Le workflow que je viens de montrer produit 7 fichiers. Ce n'est pas beaucoup. On peut les nommer à la main, un par un, sans difficulté. Dans la vraie vie, par exemple en bioinformatique, un workflow peut facilement gérer des centaines ou milliers de fichiers, par exemple un fichier par séquence d'acides aminés dans une étude de protéomique. Dans une telle situation, il faut définir un schéma pour nommer les fichiers de façon systématique, et introduire des boucles dans le workflow dont les itérations seront idéalement exécutées en parallèle. Je vais illustrer ceci avec une autre décomposition de l'analyse de l'incidence du syndrome grippal. Dans cette nouvelle décomposition en tâches, je vais calculer l'incidence annuelle pour chaque année séparément. Comme nous avons les données pour 33 ans, ceci fait 33 tâches à la place d'une seule dans la version que j'ai montrée avant. Pour ce calcul trivial, qui fait simplement la somme d'une cinquantaine d'entiers, ça n'a aucun sense. On va même perdre en efficacité, malgré le parallélisme. Mais il est facile d'imaginer un calcul plus compliqué à la place de cette simple sommme. Mon but ici est de montrer les techniques pour définir le workflow, qui serviront aussi pour mieux comprendre comment fonctionne =snakemake=. Pour cette deuxième version, je crée un nouveau répertoire, et j'y fais une copie du script de pré-traitement, qui reste identique: #+begin_src sh :session *snakemake2* :results output :exports both mkdir incidence_syndrome_grippal_snakemake_parallele cd incidence_syndrome_grippal_snakemake_parallele mkdir data mkdir scripts cp ../incidence_syndrome_grippal_snakemake/scripts/preprocess.py scripts/ #+end_src #+RESULTS: Et puis je vais vous montrer le =Snakefile=, tout de suite en entier, que je vais commenter après. #+begin_src :exports both :tangle incidence_syndrome_grippal_snakemake_parallele/Snakefile rule all: input: "data/annual-incidence-histogram.png" rule download: output: "data/weekly-incidence.csv" shell: "wget -O {output} http://www.sentiweb.fr/datasets/incidence-PAY-3.csv" rule preprocess: input: rules.download.output, "scripts/preprocess.py" output: data="data/preprocessed-weekly-incidence.csv", errorlog="data/errors-from-preprocessing.txt" script: "scripts/preprocess.py" rule extract_one_year: input: rules.preprocess.output.data, "scripts/extract_one_year.py" params: year="{year}" output: "data/{year}.csv" script: "scripts/extract_one_year.py" rule annual_incidence: input: "data/{year}.csv", "scripts/annual_incidence.py" output: "data/{year}-incidence.txt" script: "scripts/annual_incidence.py" rule histogram: input: expand("data/{year}-incidence.txt", year=range(1986, 2019)), "scripts/annual-incidence-histogram.R" output: "data/annual-incidence-histogram.png" script: "scripts/annual-incidence-histogram.R" #+end_src Commençons en haut: j'ai mis la règle =all= au début pour pouvoir être paresseux à l'exécution: la simple commande =snakemake= déclenchera l'ensemble des calculs. Et =all=, c'est simplement l'histogramme des incidences annuelles ici. Tous les autres fichiers sont des résultats intermédiaires. Par simplicité, j'ai décidé de ne plus faire les plots de la suite chronologiques - vous l'avez vu assez de fois maintenant. Dans la règle =all=, je n'ai pas écrit le nom du fichier image de l'histgramme, mais à la place j'ai utilisé une référence indirecte, =rules.histogram.output=, ce qui veut dire tous les fichiers de sortie de la règle =histogram=. Ceci évite d'écrire le même nom deux fois, et potentiellement introduire des erreurs en le faisant. Les deux règles suivantes, =download= et =preprocess=, sont les mêmes qu'avant, à un détail de notation près: dans =preprocess=, j'ai également utilisé la référence indirecte =rules.download.output= à la place du nom du fichier en question. Et j'ai rajouté le fichier du script comme fichier d'entrée pour que =snakemake= refasse le calcul après chaque modification du script. Les trois règles finales sont la partie vraiment intéressante. Commençons par la dernière, =histogram=. Elle produit un fichier image, comme avant. Elle le fait en appelant un script en R, comme avant. Mais à l'entrée, elle réclame un fichier par an. L'expression =expand(...)= produit une liste de noms de fichier en remplaçant ={year}= dans le modèle donné par les éléments de la liste =range(1986, 2019)=, qui contient les entiers de =1986= à =2018=. Si cela vous rappele le langage Python, vous avez raison - le nom =snakemake= n'est pas une coïncidence, c'est écrit en Python ! La règles =histogram= réclame donc les fichiers =data/1986-incidence.txt=, =data/1987-incidence.txt=, =data/1988-incidence.txt=, etc. Comme ces fichiers n'existent pas au départ, Une autre règle doit les produire. Et c'est la règle =annual_incidence=.qui le fait. En fait, elle s'applique à la production de tout fichier dont le nom à la forme =data/{year}-incidence.txt=. Quand =histogram= réclame le fichier =data/1986-incidence.txt=, =snakemake= trouve que la règle =annual_incidence= est applicable si on remplace ={year}= par =1986=. Il faut donc exécuter le script =scripts/annual_incidence.py= avec le fichier d'entrée =data/1986.csv=. Sauf que... ce fichier n'existe pas non plus. Pas grave, car la règle =extract_one_year= peut le produire! Il suffit d'appeler le script =scripts/extract_one_year.py= avec à l'entrée le fichier =rules.preprocess.output.data=, autrement dit le fichier =data/preprocessed-weekly-incidence.csv=, que fournit la règles =preprocess=. La boucle qui fait le calcul pour chaque année est donc contenue dans la spécification des entrées de la règle =histogram=, qui en consomme le résultat. Et si vous regardez bien, c'est le principe de fonctionnement de =snakemake= partout: on demande un résultat, =snakemake= cherche la règle qui le produit, et applique cette règle après avoir assuré l'existence de ses entrée par exactement le même mécanisme. =snakemake= traite donc le workflow en commençant par la fin, les résultats, et en remontant vers les données d'entrée. Il ne reste plus qu'à regarder les trois scripts réferencés dans les règles. Le premier, =scripts/extract_one_year.py=, lit le fichier des données pré-traitées et en extrait la part d'une année. Comme expliqué dans le module 3, la part de l'année N va du 1er août de l'année N-1 jusqu'au 31 juillet de l'année N et inclut ainsi le pic de l'année N qui se situe en janvier ou février. L'année est passée comme paramètre, défini dans la section =params= du =Snakefile= et récupéré en Python comme =snakemake.params=. Un point important est la vérification de l'année. J'ai utilisé le nom suggestif =year= pour la partie variable des noms de fichier dans la règle =extract_one_year=, mais pour =snakemake=, ce n'est qu'un nom. Si je demande =snakemake data/foo.csv=, la même règle va être appliquée avec =foo= comme valeur de =year= ! Il faut donc que le script vérifie la validité du paramètre. #+begin_src python :exports both :tangle incidence_syndrome_grippal_snakemake_parallele/scripts/extract_one_year.py # Libraries used by this script: import csv # for reading and writing CSV files import os # for filename manipulation # Read the CSV file into memory with open(snakemake.input[0], "r") as csvfile: data = [] csv_reader = csv.reader(csvfile) for row in csv_reader: data.append(row) assert data[0] == ['week_starting', 'incidence'] del data[0] # Get the year from the parameter object. # Check that it is a valid year number, i.e. a four-digit integer. year = snakemake.params.year assert len(year) == 4 assert str(int(year)) == year year = int(year) # Check that we have data for that year. # The dates are in ISO format, meaning that string # comparison is equivalent to date comparison. # There is thus no need to convert the date string # to date objects first! start = "%4d-08-01" % (year-1) end = "%4d-08-01" % year assert start >= data[0][0] assert end <= data[-1][0] # Write a CSV output file for the requested year. with open(snakemake.output[0], "w") as csvfile: csv_writer = csv.writer(csvfile) csv_writer.writerow(["week_starting", "incidence"]) number_of_weeks = 0 for row in data: if row[0] >= start and row[0] < end: csv_writer.writerow(row) number_of_weeks += 1 assert number_of_weeks in [51, 52, 53] #+end_src Le script =scripts/annual_incidence.py= fonctionnent d'après les mêmes principes. Il n'a pas besoin d'un paramètre pour indiquer l'année, car il n'en a pas besoin. Il lit un fichier CSV et fait la somme des nombres dans la deuxième colonne, c'est tout. #+begin_src python :exports both :tangle incidence_syndrome_grippal_snakemake_parallele/scripts/annual_incidence.py # Libraries used by this script: import csv # for reading CSV files # Read the CSV file into memory with open(snakemake.input[0], "r") as csvfile: data = [] csv_reader = csv.reader(csvfile) for row in csv_reader: data.append(row) assert data[0] == ['week_starting', 'incidence'] del data[0] # Compute total incidence incidence = sum(int(row[1]) for row in data) # Write the output file with open(snakemake.output[0], "w") as output_file: output_file.write(str(incidence)) output_file.write("\n") #+end_src Reste le script R qui fait l'histogramme. Rien à signaler, autre que peut-être la façon de lire tous les fichiers d'entrée avec une seule ligne de code avec la fonction =lapply=. A noter que =snakemake@input= est la liste des tous les fichiers d'entrée, y compris le nom du script lui-même, qu'il faut supprimer bien sûr. #+begin_src R :exports both :tangle incidence_syndrome_grippal_snakemake_parallele/scripts/annual-incidence-histogram.R # Read in the data. The last input file is the name of the script, # so it needs to ne removed from the list before reading all the files. files = snakemake@input datafiles = files[-length(files)] data = as.numeric(lapply(datafiles, function(fn) read.table(fn)[[1]])) # Plot the histogram png(filename=snakemake@output[[1]]) hist(data, breaks=10, xlab="Annual incidence", ylab="Number of observations", main="") dev.off() #+end_src Je pourrais maintenant taper =snakemake= et voir une longue liste de calculs défiler devant mes yeux. Je me retiens encore un peu pour illustrer ce que j'ai expliqué avant. En fait, je vais d'abord demander juste l'incidence annuelle de 2008: #+begin_src sh :session *snakemake2* :results output :exports both snakemake data/2008-incidence.txt #+end_src #+RESULTS: #+begin_example Building DAG of jobs... Using shell: /bin/bash Provided cores: 1 Rules claiming more threads will be scaled down. Job counts: count jobs 1 annual_incidence 1 download 1 extract_one_year 1 preprocess 4 [Fri Aug 30 17:58:48 2019] rule download: output: data/weekly-incidence.csv jobid: 3 --2019-08-30 17:58:48-- http://www.sentiweb.fr/datasets/incidence-PAY-3.csv Resolving www.sentiweb.fr (www.sentiweb.fr)... 134.157.220.17 Connecting to www.sentiweb.fr (www.sentiweb.fr)|134.157.220.17|:80... connected. HTTP request sent, awaiting response... 200 OK Length: unspecified [text/csv] Saving to: 'data/weekly-incidence.csv' ] 0 --.-KB/s data/weekly-inciden [ <=> ] 79.88K --.-KB/s in 0.04s 2019-08-30 17:58:48 (1.84 MB/s) - 'data/weekly-incidence.csv' saved [81800] [Fri Aug 30 17:58:48 2019] Finished job 3. ) done [Fri Aug 30 17:58:48 2019] rule preprocess: input: data/weekly-incidence.csv, scripts/preprocess.py output: data/preprocessed-weekly-incidence.csv, data/errors-from-preprocessing.txt jobid: 2 [Fri Aug 30 17:58:48 2019] Finished job 2. ) done [Fri Aug 30 17:58:48 2019] rule extract_one_year: input: data/preprocessed-weekly-incidence.csv, scripts/extract_one_year.py output: data/2008.csv jobid: 1 wildcards: year=2008 [Fri Aug 30 17:58:48 2019] Finished job 1. ) done [Fri Aug 30 17:58:48 2019] rule annual_incidence: input: data/2008.csv, scripts/annual_incidence.py output: data/2008-incidence.txt jobid: 0 wildcards: year=2008 [Fri Aug 30 17:58:48 2019] Finished job 0. ) done Complete log: /home/hinsen/projects/RR_MOOC/repos-session02/mooc-rr-ressources/module6/ressources/incidence_syndrome_grippal_snakemake_parallele/.snakemake/log/2019-08-30T175848.265842.snakemake.log #+end_example On peut bien suivre l'exécution des tâches: d'abord =download=, puis =preprocess=, =extract_one_year=, et =annual_incidence=. Regardons le contenu de ce petit fichier: #+begin_src sh :session *snakemake2* :results output :exports both cat data/2008-incidence.txt #+end_src #+RESULTS: : 2975925 Si maintenant je demande une autre année, seulement les tâches =extract_one_year= et =annual_incidence= devraient s'afficher, car les deux première sont déjà faites. Voyons: #+begin_src sh :session *snakemake2* :results output :exports both snakemake data/1992-incidence.txt #+end_src #+RESULTS: #+begin_example Building DAG of jobs... Using shell: /bin/bash Provided cores: 1 Rules claiming more threads will be scaled down. Job counts: count jobs 1 annual_incidence 1 extract_one_year 2 [Fri Aug 30 18:01:41 2019] rule extract_one_year: input: data/preprocessed-weekly-incidence.csv, scripts/extract_one_year.py output: data/1992.csv jobid: 1 wildcards: year=1992 [Fri Aug 30 18:01:41 2019] Finished job 1. ) done [Fri Aug 30 18:01:41 2019] rule annual_incidence: input: data/1992.csv, scripts/annual_incidence.py output: data/1992-incidence.txt jobid: 0 wildcards: year=1992 [Fri Aug 30 18:01:41 2019] Finished job 0. ) done Complete log: /home/hinsen/projects/RR_MOOC/repos-session02/mooc-rr-ressources/module6/ressources/incidence_syndrome_grippal_snakemake_parallele/.snakemake/log/2019-08-30T180141.597756.snakemake.log #+end_example Ça marche ! Je peux alors attaquer la totale, mais je vais supprimer l'affichage de tous les détails de l'exécution (option =-q=), pour éviter de voir les années défiler devant mes yeux! #+begin_src sh :session *snakemake2* :results output :exports both snakemake -q #+end_src #+RESULTS: #+begin_example Job counts: count jobs 1 all 33 annual_incidence 33 extract_one_year 1 histogram 68 During startup - Warning messages: 1: Setting LC_COLLATE failed, using "C" 2: Setting LC_TIME failed, using "C" 3: Setting LC_MESSAGES failed, using "C" 4: Setting LC_MONETARY failed, using "C" null device 1 #+end_example Regardons le résultat final, l'histogramme: [[file:incidence_syndrome_grippal_snakemake_parallele/data/annual-incidence-histogram.png]] Enfin, je vais tenter de produire le dessin du graphe des tâches, m'attendant à une graphique un peu disproportionnée à cause du grand nombre de tâches: #+begin_src sh :session *snakemake2* :results output :exports both snakemake -q --forceall --dag all | dot -Tpng > graph.png #+end_src #+RESULTS: [[file:incidence_syndrome_grippal_snakemake_parallele/graph.png]]