Menu

Les erreurs R courantes et comment les déboguer

Un décodeur des messages d'erreur classiques de R - object not found, could not find function, non-numeric argument et compagnie - plus tryCatch, traceback() et l'honnête débogage par print.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Lire un message d'erreur de R

Une erreur R a deux parties, et les deux sont utiles :

Error in "10" + 5 : non-numeric argument to binary operator

Après Error in vient l'appel - le morceau de code exact qui a échoué ("10" + 5). Après le deux-points vient la condition - ce qui a mal tourné (non-numeric argument to binary operator). Lis d'abord l'appel : il te dit , et bien souvent tu vois déjà le problème dans le code cité. Puis lis la condition pour le pourquoi.

Deux habitudes séparent ceux qui déboguent vite de ceux qui souffrent. D'abord, lis vraiment le message - les messages de R sont généralement précis, juste formulés sèchement. Ensuite, débogue la première erreur, pas la dernière : un échec en début de script se propage en une pile d'erreurs « object not found » en aval qui disparaissent toutes quand tu corriges l'originale. Le reste de cette page est un décodeur des messages que tu croiseras le plus, puis les outils pour quand lire ne suffit plus.

Les erreurs de nom : introuvable

Error: object 'total' not found - R a fouillé tous les environnements qu'il connaît et aucune variable ne porte ce nom. Trois causes couvrent presque tous les cas :

  • Une faute de frappe, casse comprise. R est sensible à la casse : Total, total et TOTAL sont trois noms différents, et R ne devinera pas lequel tu voulais.
  • La ligne de définition n'a pas été exécutée. Tu as écrit total <- sum(x) dans le script mais tu ne l'as jamais exécutée dans cette session - fréquent après un redémarrage de R, où le fichier script montre encore la ligne alors que la session ne l'a jamais vue. Relance le script depuis le début.
  • Mauvais environnement. Les variables créées dans une fonction vivent et meurent dans cet appel. En utiliser une hors de la fonction, c'est demander quelque chose qui n'existe plus - renvoie plutôt la valeur.

Error: could not find function "read_excel" - même idée, mais pour un nom de fonction. Neuf fois sur dix, la fonction vit dans un package que tu as installé mais pas chargé cette session :

library(readxl)          # the fix: loading is per-session, installing is per-machine
df <- read_excel("data.xlsx")

Si library(readxl) lui-même échoue, le package n'est pas installé - install.packages("readxl") d'abord. Et si la fonction est du R de base, c'est que tu as fait une faute de frappe (lenght() est le rite de passage de tout le monde).

Erreurs de syntaxe : unexpected symbol

Error: unexpected symbol in "..." (et ses cousines unexpected ')', unexpected string constant) signifie que R n'a même pas pu analyser le code. Le message pointe l'endroit où R s'en est aperçu, souvent au-delà de l'endroit où se trouve la vraie erreur. Les suspects habituels :

mean(x na.rm = TRUE)        # missing comma - should be mean(x, na.rm = TRUE)

name <- "Ada                # unclosed quote - swallows the following lines
total <- sum(c(1, 2, 3)     # unclosed paren - the error fires lines later

Quand la ligne signalée semble innocente, l'erreur est presque toujours au-dessus : un guillemet, une parenthèse ou une accolade non fermés plus haut dans le fichier. Un éditeur de code qui surligne les paires correspondantes les trouve en quelques secondes.

Erreurs de type et d'indexation, décodées

non-numeric argument to binary operator - tu as fait un calcul sur quelque chose qui n'est pas un nombre, généralement un nombre arrivé sous forme de texte (les imports sont la source classique - une colonne contenant un mot égaré arrive en character, comme expliqué dans les types de données). La version cassée :

x <- "10"
x + 5
# Error in x + 5 : non-numeric argument to binary operator

Et la correction - convertis, puis calcule :

subscript out of bounds - tu as demandé la position n dans quelque chose qui a moins de n éléments, avec [[ ]] :

scores <- list(ada = 92, grace = 88)
scores[[3]]
# Error in scores[[3]] : subscript out of bounds

Vérifie length() avant d'indexer, ou mieux, demande par nom (scores[["grace"]]) pour qu'un réordonnancement ne puisse pas te casser. Note l'asymétrie : les crochets simples sont plus tolérants - un [ ] hors plage sur un vecteur renvoie discrètement NA au lieu de lever une erreur, ce qui échange un bug bruyant contre un bug silencieux.

$ operator is invalid for atomic vectors - $ appartient aux listes et aux data frames. Sur un vecteur nommé, utilise les crochets :

([[ ]] donne la valeur nue ; [ ] garde le nom attaché.) Cette erreur signifie souvent que quelque chose en amont a renvoyé un vecteur là où tu attendais un data frame - va vérifier cette hypothèse plutôt que de simplement changer d'opérateur.

argument is of length zero - un if () a reçu une condition vide, presque toujours un NULL qui s'est glissé depuis un élément de liste manquant ou une fonction qui n'a rien renvoyé :

threshold <- NULL
if (threshold > 5) print("big")
# Error in if (threshold > 5) print("big") : argument is of length zero

Protège le test - et note que && cesse d'évaluer dès que la réponse est connue, si bien que la comparaison ne s'exécute jamais sur un NULL :

(Une erreur voisine, missing value where TRUE/FALSE needed, est le même échec avec NA au lieu de NULL - la protection y est is.na(), traitée dans les valeurs manquantes.)

replacement has length zero - la version affectation de la même maladie : x[2] <- numeric(0) essaie de remplir une case avec zéro valeur. Ce qui a produit le membre de droite est revenu vide ; débogue ça, pas l'affectation.

Les avertissements ne sont pas des erreurs - et c'est là le danger

Une erreur arrête l'exécution ; un avertissement, non. R termine le calcul, te tend un résultat, et mentionne ses réserves après coup. Ce résultat est parfois correct et parfois discrètement faux :

Les deux lignes s'exécutent jusqu'au bout. La première recycle le vecteur le plus court et avertit longer object length is not a multiple of shorter object length - et recycler un vecteur de longueur 2 contre une longueur 3 n'est presque jamais ce que quiconque voulait. La seconde avertit NAs introduced by coercion et livre un vecteur troué qui fera renvoyer NA à chaque mean() en aval. Traite les deux avertissements comme des bugs à investiguer, pas comme du bruit à dépasser en défilant. Dans les scripts, tu peux imposer cette posture avec options(warn = 2), qui promeut chaque avertissement en erreur pour que rien ne se faufile.

Gérer les échecs avec tryCatch()

Parfois une erreur est attendue - un fichier corrompu dans un dossier qui en compte des centaines, une ligne fautive - et tu veux la gérer et poursuivre plutôt que mourir. tryCatch() enveloppe une expression risquée avec des gestionnaires :

La mécanique : si le bloc principal réussit, sa valeur est le résultat. S'il lève une erreur, le gestionnaire error = s'exécute à la place et sa valeur de retour (ici NA) devient le résultat - le script continue. conditionMessage(e) récupère le message d'origine pour la journalisation. finally = s'exécute dans tous les cas, succès ou échec, et c'est là que va le nettoyage comme la fermeture de connexions - tu peux le voir s'afficher avant chaque résultat ci-dessus.

Il y a aussi un gestionnaire warning = - tryCatch(as.numeric(x), warning = function(w) NA) attrape l'avertissement de coercition de la section précédente au lieu de le laisser passer. Une mise en garde : un gestionnaire qui renvoie une valeur de repli sans rien journaliser est une façon de cacher les échecs, pas de les gérer. Enregistre toujours conditionMessage() - toi plus tard en auras besoin.

Localiser l'échec : traceback(), browser() et l'honnête affichage

Quand l'erreur vient du fond d'appels de fonctions imbriquées, le message seul ne dit pas quelle chaîne d'appels t'y a mené. Lance traceback() immédiatement après l'erreur :

f <- function(x) g(x)
g <- function(x) stop("boom")

f(1)
# Error in g(x) : boom
traceback()
# 2: g(x)
# 1: f(1)

Il affiche la pile d'appels au moment de l'échec - ton appel à un bout, l'appel fautif à l'autre. Ça doit être la chose suivante que tu exécutes ; la pile est jetée dès qu'une autre erreur survient.

Pour un regard en direct, browser() met l'exécution en pause là où tu le plantes et te dépose dans une invite interactive à l'intérieur de la fonction - inspecte les variables, avance avec n, continue avec c, quitte avec Q. debug(f) fait la même chose sans modifier le code : il marque f pour que son prochain appel s'ouvre dans le browser (annule avec undebug(f)).

Et puis il y a la technique que personne ne met sur ses diapositives de conférence mais que tout le monde utilise : l'affichage. Sème des print() ou des cat() à des points de contrôle, exécute, et regarde où la réalité cesse de correspondre à tes attentes. C'est légitime, c'est rapide, et dans les scripts c'est souvent l'outil le plus pratique. Son meilleur ami est str(), qui répond à la question derrière peut-être la moitié de toutes les erreurs R - « qu'est réellement cet objet ? » :

Un affichage compact : c'est une liste, deux éléments, l'un un vecteur d'entiers, l'autre un data frame avec ces colonnes et ces types. Quand un $ échoue ou qu'un calcul se comporte mal, passe str() sur l'objet avant de théoriser - la réponse est généralement juste là (« ...ah, c'est une liste de longueur 1 contenant mon data frame »).

Ce que tu retiens

  • Lis le message : la partie après Error in dit où, la partie après le deux-points dit pourquoi. Corrige la première erreur, pas la plus bruyante.
  • object not found = faute de frappe, code pas encore exécuté, ou variable n'ayant existé que dans une fonction. could not find function = appel library() manquant, presque toujours.
  • unexpected symbol signifie une grammaire inanalysable - et la vraie erreur est souvent un guillemet ou une parenthèse non fermés avant l'endroit signalé.
  • Les classiques de type et d'indice - non-numeric argument, subscript out of bounds, $ sur un vecteur atomique, argument is of length zero - pointent chacun une mauvaise hypothèse sur ce qu'est un objet ; str() vérifie l'hypothèse en un appel.
  • Les avertissements n'arrêtent pas l'exécution, et c'est précisément pourquoi ils méritent de l'attention - le résultat peut être discrètement faux.
  • tryCatch(error =, warning =, finally =) gère les échecs attendus sans mourir (journalise toujours conditionMessage()) ; traceback() juste après une erreur montre la chaîne d'appels ; browser()/debug() mettent en pause à l'intérieur ; le débogage par print() est un travail honnête.

Prochaine étape : beaucoup d'« erreurs » qui n'en sont pas remontent à une valeur de deux caractères - NA, et la façon dont les valeurs manquantes circulent dans tout ce que R calcule.

Questions fréquentes

Que signifie « object 'x' not found » en R ?

R a cherché une variable nommée x et aucun nom de ce genre n'existe dans les environnements explorés. Les causes, par ordre de probabilité : une faute de frappe dans le nom (R est sensible à la casse - Total n'est pas total), la ligne qui crée x n'a pas encore été exécutée dans cette session, ou x a été créé dans une fonction et tu essaies de l'utiliser à l'extérieur.

Que signifie « could not find function » en R ?

La fonction existe dans un package que tu n'as pas chargé cette session. Installer un package se fait une fois par machine ; library() une fois par session, et oublier l'appel à library() est la cause habituelle. Si library() lui-même échoue, le package n'est pas installé. Une faute de frappe dans le nom de la fonction produit la même erreur.

Comment gérer les erreurs en R avec tryCatch ?

Enveloppe l'expression risquée : tryCatch(expr, error = function(e) fallback, warning = function(w) fallback, finally = cleanup). Si expr échoue, le gestionnaire correspondant s'exécute au lieu que le script meure, et ce que renvoie le gestionnaire devient le résultat. conditionMessage(e) dans un gestionnaire te donne le message d'origine pour la journalisation.

Que fait traceback() en R ?

Exécuté immédiatement après une erreur, traceback() affiche la chaîne d'appels de fonctions active au moment où l'erreur a été levée - ton appel à un bout, l'appel fautif à l'autre. Il ne corrige rien ; il te dit où regarder, ce qui représente l'essentiel de la bataille quand l'erreur vient du fond de fonctions imbriquées.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER