Présentation des outils de débogage avec R et RStudio
27/06/2025
Le débogage, ou debugging en anglais, est une étape cruciale dans le développement de tout programme informatique.
Cette présentation vous présente quelques outils que vous avez déjà à disposition sous votre RStudio sur Cerise afin de vous aider à trouver par vous-même la source de vos bugs.
Soit la fonction suivante :
## Tout est ok
exemple1("Petale")
> exemple1("Petale")
Species Petal.Length Petal.Width
1 setosa 1.4 0.2
2 setosa 1.4 0.2
3 setosa 1.3 0.2
Il existe un outil très simple (universel car non spécifique à R), parfois décrié mais qui reste efficace : ajouter la fonction print()
dans votre code R pour examiner le contenu d’un objet à un moment donné.
Par exemple :
exemple1 <- function(indicateur) {
print(indicateur) # <- on ajoute un print()
if (indicateur == "Sepale") {
resultat <- iris |>
select(Species,starts_with("Sepal")) |>
head(3)
} else if (indicateur == "Petale") {
resultat <- iris |>
select(Species,starts_with("Petal")) |>
head(3)
}
return(resultat)
}
Cela renvoie pour le cas du bug :
=> On récupère ainsi la valeur contenue dans l’objet indicateur “petale” ce qui nous permet de comprendre qu’on ne rentre dans aucune des 2 conditions de la structure conditionnelle de exemple1()
.
=> L’objet resultat
n’ayant pas été créé, R ne peut pas le trouver :).
Avantages du print :
- Simplicité, universalité
- Pas d’interruption du code
- Compatible dans tous les environnements (RStudio, VSCode, batchs…)
Inconvénients du print :
- Intrusif dans le code - Pas d’inspection de l’environnement à la volée
- On oublie souvent de les retirer du code une fois les bugs résolus.
Avant de lancer vos traitements, il peut être utile de définir des breakpoints (“point d’arrêt”) sur une ligne de code.
Lorsque R atteint cette ligne pendant l’exécution, il met le script en pause.
Pour cela, on peut cliquer dans la marge à gauche du numéro de ligne dans l’éditeur.
=> Cela permet d’avoir accès à l’environnement d’exécution, ce qui signifie que vous pouvez voir les valeurs de toutes vos variables, exécuter des commandes dans la console comme si vous étiez à cet endroit précis du code, et avancer pas à pas pour observer comment les variables changent.
Les fonctions R qui contiennent un breakpoint ont un point rouge dans l’onglet Environnement de RStudio :
En appuyant sur le bouton source
de RStudio :
La flèche verte et le texte en surbrillance permettent d’identifier le bout de code en cours d’exécution
Appuyer sur le bouton “Next” ou taper la lettre “n” dans la console de RStudio pour exécuter le script pas à pas.
En appuyant sur le bouton Next
de RStudio :
Les objets se mettent à jour dans l’onglet Environnement de RStudio.
La flèche verte se positionne à l’emplacement du code qui sera exécuté à l’étape suivante.
En appuyant sur le bouton Next
de RStudio :
Les objets se mettent à jour dans l’onglet Environnement de RStudio.
La flèche verte se positionne à l’emplacement du code qui sera exécuté à l’étape suivante.
Avantages des breakpoints :
- Très visuels
- Pas besoin de modifier votre code R
- Permet l’exécution pas à pas
Inconvénients des breakpoints :
- Spécifique à RStudio et aux programmes R
- Pas versionnable avec Git
- Difficile à utiliser dans certains cas dynamiques (les shiny par ex.)
Remarque
Si lorsque vous injectez un breakpoint dans votre programme R celui-ci s’affiche en cercle rouge, il faudra alors sauvegarder votre script et le sourcer pour retrouver le rond rouge.
La fonction browser() est un compromis entre la fonction print() et les breakpoints.
Par exemple :
Avantages du browser :
- Facile à mettre en place
- Permet l’exécution pas à pas
- Fonctionne dans les chunks quarto
Inconvénients du browser :
- Intrusif dans le code
- Moins visuels que les breakpoints
- On oublie souvent de les retirer du code une fois les bugs résolus.
Les fonctions debug() et debugonce() permettent d’insérer la fonction browser() via l’appel à une fonction.
debug(fonction) : active le mode pas-à-pas pour la fonctio à chaque appel (nécessite la fonction undebug() pour le désactiver).
debugonce(f) : active le mode debug uniquement au prochain appel de la fonction.
debug(exemple1) # pour lancer le debuggeur sur la fonction
exemple1("petale") # maintenant le debuggeur se lance qd on soumet la fonction
undebug(exemple1) # pour arrêter le debuggeur sur la fonction
debugonce(exemple1) # Pour lancer le debuggeur qu'une seule fois
exemple1("petale") # le debuggeur ne va se lancer qu'une seule fois
Avantages des debug() :
- Facile à mettre en place
- Permet l’exécution pas à pas
- Ne modifie pas le code source des fonctions
Inconvénients des debug() :
- Ne fonctionne qu’avec les fonctions nommées
- On oublie souvent de les retirer du code une fois les bugs résolus au moment des appels des fonctions.
L’option options(error = recover) sert à déboguer une erreur quand elle se produit.
Quand une erreur se produit, cela :
options(error = NULL) permet de rétablir le comportement par défaut.
On obtient dans la console :
> f(100) # le mode debug s'active
Erreur dans g(y) : objet 'res' introuvable
Enter a frame number, or 0 to exit
1: f(100)
2: #3: g(y)
Sélection :
=> Taper 0, 1 ou 2 dans ce cas pour lancer le mode debug au niveau souhaité.
Avantages de l’option recover :
- Facile à mettre en place
- Permet rapidement de voir toutes les fonctions imbriquées qui ont conduit à l’erreur
- Ne modifie pas le code source des fonctions
Inconvénients de l’option recover :
- Le mode debug ne s’active qu’après une erreur
- Pas utilisable pour les batchs par exemple
A l’intérieur d’une chaîne de traitements {tidyverse} avec %>%, il est possible d’afficher ou de sauvegarder un résultat intermédiaire pour résoudre rapidement un bug.
Si on veut simplement afficher le résultat intermédiaire, la syntaxe générale est :
Un exemple :
iris %>%
group_by(Species) %>%
summarise(moyenne_long_petale = mean(Petal.Length, na.rm = TRUE)) %>%
{ print(head(.)); . } %>%
rename(Especes = Species)
On obtient dans la console :
Si on veut stocker le résultat intermédiaire, , la syntaxe générale est :
iris %>%
group_by(Species) %>%
summarise(moyenne_long_petale = mean(Petal.Length, na.rm = TRUE)) %>%
{ tab_agr <<- . } %>%
rename(Especes = Species)
On obtient dans l’environnement :
Avantages de l’astuce :
- Pas d’interruption du code
- Pratique à mettre en place pour un debug rapide
Inconvénients de l’astuce :
- Ne fonctionne qu’avec %>% (pas avec |>)
- On oublie souvent de les retirer du code une fois les bugs résolus
Le package {boomer} fournit des outils de débogage qui permettent d’inspecter les résultats intermédiaires d’un code R.
L’output produit par le package se présente sous la forme de bombes 💣 d’où le nom.
Ses 2 principales fonctions sont :
- boom()
qui permet de diagnostiquer rapidement la performance d’un bloc de code R.
- rig
()`qui permet de tracer une fonction R et affiche automatiquement les mesures de performances à chacun de ses appels.
3 écritures sont possibles :
Le résultat dans la console :
💣 filter(head(iris, 2), Sepal.Length > 5)
· 💣 💥 head(iris, 2)
· Sepal.Length Sepal.Width Petal.Length Petal.Width Species
· 1 5.1 3.5 1.4 0.2 setosa
· 2 4.9 3.0 1.4 0.2 setosa
·
· 💣 💥 Sepal.Length > 5
· [1] TRUE FALSE
·
💥 filter(head(iris, 2), Sepal.Length > 5)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
En cas d’erreur, la fonction boom()
affiche les résultats intermédiaires jusqu’à l’apparition du bug.
renvoie la sortie suivante :
💣 filter(head(iris, 2), Species == "virginica")
· 💣 💥 head(iris, 2)
· Sepal.Length Sepal.Width Petal.Length Petal.Width Species
· 1 5.1 3.5 1.4 0.2 setosa
· 2 4.9 3.0 1.4 0.2 setosa
·
· 💣 💥 Species == "virginica"
· [1] FALSE FALSE
·
💥 filter(head(iris, 2), Species == "virginica")
[1] Sepal.Length Sepal.Width Petal.Length Petal.Width Species
<0 lignes> (ou 'row.names' de longueur nulle)
[1] Sepal.Length Sepal.Width Petal.Length Petal.Width Species
<0 lignes> (ou 'row.names' de longueur nulle)
La fonction boom()
comprend 2 arguments très utiles :
clock
: booléen qui permet de quantifier le temps d’exécution de chaque étape.
print
: qui permet de fixer la manière dont sont affichés les résultats intermédiaires.
str
glimpse
list(data.frame = str)
Un exemple d’utilisation de boom()
avec l’argument clock = TRUE
Renvoie (partie 1) :
Renvoie (partie 2) :
Un exemple d’utilisation de boom()
avec l’argument print = list(data.frame = str)
Renvoie (partie 1) :
💣 filter
· 💣 💥 head(iris, 2)
time: 0.214 ms
· 'data.frame': 2 obs. of 5 variables:
· $ Sepal.Length: num 5.1 4.9
· $ Sepal.Width : num 3.5 3
· $ Petal.Length: num 1.4 1.4
· $ Petal.Width : num 0.2 0.2
· $ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1
·
· 💣 💥 Sepal.Length > 5
time: 0.021 ms
· [1] TRUE FALSE
·
Renvoie (partie 2) :
💥 filter(head(iris, 2), Sepal.Length > 5)
time: 0.002 s
'data.frame': 1 obs. of 5 variables:
$ Sepal.Length: num 5.1
$ Sepal.Width : num 3.5
$ Petal.Length: num 1.4
$ Petal.Width : num 0.2
$ Species : Factor w/ 3 levels "setosa","versicolor",..: 1
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
Quelques options utiles :
options(boomer.clock = TRUE)
options(boomer.print = str)
Pour aller plus loin :
{boomer} contient les fonctions boom_on()
et boom_off()
qui peuvent être utilisées dans le mode debug pas à pas de RStudio (voir cette page).
Le package contient également une fonctionnalité boom_shinyApp()
(encore expérimentale) pour aider les développeurs à debugger les applications shiny (voir cette page).
La fonction rig()
Soit la fonction suivante :
On appelle la fonction avec rig()
:
Et on obtient :
Pour arrêter de surveiller une fonction, il existe la fonction unrig()
.
Avec une erreur :
Renvoie :
Avantages de {boomer} :
- Permet de “décortiquer” facilement les blocs de code (avec boom()
)
- Permet de “décortiquer” facilement les fonctions (avec rig()
)
- Peut aider à debugger les applications Shiny
Inconvénients de {boomer} :
- Pour le debogage d’une longue chaine de traitements, le pas à pas est plus pratique.
- Surcharge le code source
- On oublie souvent de les retirer du code une fois les bugs résolus.
Pour compléter les différents outils de débogage, il reste un pratique essentielle à présenter qui est l’écriture des logs.
Les logs permettent de tracer l’historique des actions, de suivre le cheminement logique du code R et ainsi de repérer l’origine des bugs/erreurs.
Cette pratique est tellement répandue et utile qu’il existe de très nombreux packages R : {logger}, {futile.logger}, {logging}, {lgr}, {loggit}, {log4r} …
Dans cette présentation, nous allons utiliser le package {logr}.
Le package {logr} présente plusieurs avantages :
Avec {logr}, il y a besoin de seulement 3 étapes pour créer un fichier de log :
log_open()
)
log_open(chemin_vers_fichier_log)
log_print()
)
log_print("message à écrire dans la log")
log_close()
)
log_close()
Structure d’une log :
Un en-tête qui contient des informations essentielles : le chemin du fichier de log, le répertoire de travail, la version de R Core, le nom de l’utilisateur, le time-stanp d’exécution, les packages utilisés…
Un corps/body qui constitue le coeur de la log. Il contient tous les messages et les objets que vous voulez afficher tels qu’il s’afficherait dans la console de R.
Un pied de page qui contient le timestamp de fin d’exécution ainsi que le temps total écoulé pendant l’exécution du script R.
Un exemple :
library(logr)
# Ouverture de la log
log_open("ma_log.log")
log_print("## Lecture des données")
donnees <- readRDS("data/iris.rds")
log_print("## Traitement des données")
resultat <- donnees |>
group_by(Species) |>
summarise(moy_long_petales = mean(Petal.Length, na.rm = TRUE))
# Fermeture de la log
log_close()
Renvoie la log suivante (partie 1) :
=========================================================================
Log Path: ./log/ma_log.log
Working Directory: /var/data/nfs/CERISE/00-Espace-Personnel/damien.dotta
User Name: damien.dotta
R Version: 4.4.1 (2024-06-14)
Machine: stats-prod-rstudio-2.zsg.cdpagri.fr x86_64
Operating System: Linux 4.18.0-372.32.1.el8_6.x86_64 #1 SMP Fri Oct 7 12:35:10 EDT 2022
Base Packages: stats graphics grDevices utils datasets methods base
Other Packages: dplyr_1.1.4 logr_1.3.8 common_1.1.3
Log Start Time: 2025-06-27 14:18:06.072628
=========================================================================
Partie 2 :
## Lecture des données
NOTE: Log Print Time: 2025-06-27 14:18:06.08348
NOTE: Elapsed Time: 0.00261092185974121 secs
## Traitement des données
NOTE: Log Print Time: 2025-06-27 14:18:06.090004
NOTE: Elapsed Time: 0.00652408599853516 secs
=========================================================================
Log End Time: 2025-06-27 14:18:06.120677
Log Elapsed Time: 0 00:00:00
=========================================================================
La fonction log_print()
a également un alias utile pour la présentation des logs :
sep()
ajoute du texte dans la log comme log_print()
mais ajoute des séparateurs avant/après le message.Va générer dans la log :
=========================================================================
Création des graphiques
=========================================================================
put()
est également un alias plus court que le nom log_print()
dont le comportement est identique.La fonction log_print()
(ou put()
) permet également d’envoyer des résultats dans les logs.
Par exemple :
Renvoie :
## Traitement des données
NOTE: Log Print Time: 2025-06-27 14:35:15.474827
NOTE: Elapsed Time: 0.0325253009796143 secs
# A tibble: 3 × 2
Species moy_long_petales
<fct> <dbl>
1 setosa 1.46
2 versicolor 4.26
3 virginica 5.55
NOTE: Data frame has 3 rows and 2 columns.
NOTE: Log Print Time: 2025-06-27 14:35:15.572858
NOTE: Elapsed Time: 0.0980312824249268 secs
En cas d’erreur/bug (ou de warnings) pendant l’exécution du programme R, ceux-ci s’afficheront dans la log à l’endroit où ils ont eu lieu.
En complément, {logr} va isoler les messages d’erreurs et les warnings dans un fichier qui porte l’extension .msg
.
Le traceback de R peut être très verbeux en cas d’erreurs. Si vous n’avez pas besoin de ce niveau de précision, il est possible de le désactivement ponctuellement avec log_print("traceback" = FALSE)
ou globalement avec options("logr.traceback" = FALSE)
.
Exemple d’erreur dans les logs :
Error in group_by(donnees, species) : [38;5;255m [31m✖[38;5;255m Column `species` is not found.[39m
Traceback:
[1] "10: stop(fallback)"
[2] "9: signal_abort(cnd, .file)"
[3] "8: abort(bullets, call = error_call)"
[4] "7: group_by_prepare(.data, ..., .add = .add, error_call = current_env())"
[5] "6: group_by.data.frame(donnees, species)"
[6] "5: group_by(donnees, species)"
[7] "4: summarise(group_by(donnees, species), moy_long_petales = mean(Petal.Length, "
[8] " na.rm = TRUE))"
...
Sans traceback :
Quelques conseils si vous savez que vous allez utiliser logr sur votre programme :
log_print()
A la place de :
Mettre ça :
Il est possible de logguer seulement une partie d’un script R. Pour cela, il existe les options options("logr.on" = FALSE)
et options("logr.on" = TRUE)
.
Si vous souhaitez raccourcir la longueur de vos logs, il est possible de supprimer les lignes blanches avec l’option options("logr.compact" = TRUE)
.
Si vous souhaitez ne pas écrire les notes dans vos logs, il existe l’option options("logr.notes" = FALSE)
Si vous souhaitez envoyer vos variables d’environnement dans vos logs, c’est possible via put(Sys.getenv())
Pour plus de conseils, consultez la FAQ du package qui est très riche.
Si votre code est essentiellement écrit avec {dplyr} et {tidyr}, il est possible de limiter le nombre d’appels à log_print()
en utilisant options("logr.autolog" = TRUE)
.
Cela permet d’écrire dans les logs automatiquement (grâce à tidylog) à chaque appel d’un bloc de code.
Exemple sans log_print()
:
Renvoie la log suivante :
En-tête...
=========================================================================
group_by: one grouping variable (Species)
NOTE: Log Print Time: 2025-06-27 15:27:52.030858
NOTE: Elapsed Time: 0.0213210582733154 secs
summarise: now 3 rows and 2 columns, ungrouped
NOTE: Log Print Time: 2025-06-27 15:27:52.346752
NOTE: Elapsed Time: 0.315893888473511 secs
=========================================================================
Pied de page..
Pour en savoir plus :