Suggestions générales pour le débogage dans R

120

J'obtiens une erreur lors de l'utilisation d'une fonction R que j'ai écrite:

Warning messages:
1: glm.fit: algorithm did not converge 
2: glm.fit: algorithm did not converge 

Ce que j'ai fait:

  1. Parcourez la fonction
  2. L'ajout d'impression pour savoir à quelle ligne l'erreur se produit suggère deux fonctions à ne pas utiliser glm.fit. Ils sont window()et save().

Mes approches générales incluent l'ajout printet les stopcommandes, et le parcours d'une fonction ligne par ligne jusqu'à ce que je puisse localiser l'exception.

Cependant, il n'est pas clair pour moi d'utiliser ces techniques d'où provient cette erreur dans le code. Je ne suis même pas certain de quelles fonctions du code dépendent glm.fit. Comment diagnostiquer ce problème?

David LeBauer
la source
5
Consultez la page de Duncan Murdoch sur le débogage dans R
Rob Hyndman
10
Ok, je vais dire l'évidence: c'est un avertissement pas une erreur .
Gavin Simpson
10
@ gavin-simpson Je ne savais pas qu'il y avait une différence technique, merci de l'avoir signalé. Mais au final, cela indique que ma fonction auparavant fonctionnelle est dysfonctionnelle.
David LeBauer
11
@David +1 pour "... ma fonction précédemment fonctionnelle est dysfonctionnelle."
Joshua Ulrich
5
@David: votre ps. Cela ajoute une dimension à la question qui aurait été manquée sans l'exemple; à savoir comment faire passer R en mode débogage lorsque seuls les avertissements sont produits? Si vous aviez omis ce détail, nous ne vous aurions tous pas indiqué options(warn = 2). Donc dans ce cas, le détail est essentiel pour répondre à votre question générale. +1 de moi.
Gavin Simpson

Réponses:

167

Je dirais que le débogage est une forme d'art, il n'y a donc pas de solution miracle. Il existe de bonnes stratégies de débogage dans n'importe quelle langue, et elles s'appliquent ici aussi (par exemple, lisez cet article sympa ). Par exemple, la première chose à faire est de reproduire le problème ... si vous ne pouvez pas faire cela, alors vous devez obtenir plus d'informations (par exemple avec la journalisation). Une fois que vous pouvez le reproduire, vous devez le réduire à la source.

Plutôt qu'un "truc", je dirais que j'ai une routine de débogage préférée:

  1. Lorsqu'une erreur se produit, la première chose que je fais habituellement est de regarder la trace de la pile en appelant traceback(): cela vous montre où l'erreur s'est produite, ce qui est particulièrement utile si vous avez plusieurs fonctions imbriquées.
  2. Ensuite, je vais mettre options(error=recover); cela passe immédiatement en mode navigateur où l'erreur se produit, de sorte que vous pouvez parcourir l'espace de travail à partir de là.
  3. Si je n'ai toujours pas assez d'informations, j'utilise généralement la debug()fonction et je parcours le script ligne par ligne.

La meilleure nouvelle astuce dans R 2.10 (lorsque vous travaillez avec des fichiers de script) est d'utiliser les fonctions findLineNum()et setBreakpoint().

En guise de conclusion: en fonction de l'erreur, il est également très utile de définir try()ou tryCatch()déclarations autour des appels de fonctions externes ( en particulier lorsqu'ils traitent avec les classes S4). Cela fournira parfois encore plus d'informations et vous donnera également plus de contrôle sur la façon dont les erreurs sont gérées au moment de l'exécution.

Ces questions connexes comportent de nombreuses suggestions:

Shane
la source
8
Vous pouvez également ajouter debugonce () à debug ().
Joris Meys
2
Bien que non seulement utile lors du débogage, fix (df1) ouvre l'éditeur graphique R avec le bloc de données df1 chargé que vous pouvez modifier à la volée ou simplement jeter un coup d'œil.
Dmitrii I.
le débogage dans R semble être très difficile, par exemple il n'y a pas de solution facile pour voir les lignes de code des avertissements
TMS
browser()pour quand il y a des erreurs qui ne déclenchent pas d'avertissements / erreurs (crédit: Roman Luštrik sur cette page). Un autre outil comme browser()?
PatrickT
38

La meilleure solution que j'ai vue jusqu'à présent est:

http://www.biostat.jhsph.edu/%7Erpeng/docs/R-debug-tools.pdf

Quelqu'un est-il d'accord / en désaccord?

Christophe DuBois
la source
Guide très complet - décrit les outils essentiels inclus dans R core: debug (), traceback () et recover ().
Sharpie le
32

Comme me l' a fait remarquer dans une autre question , Rprof()et summaryRprof()sont beaux outils pour trouver des parties lentes de votre programme qui bénéficient de la puissance d'accélérer ou de passer à la mise en œuvre d'un C / C. Cela s'applique probablement davantage si vous effectuez un travail de simulation ou d'autres activités gourmandes en calcul ou en données. Le profrpackage peut aider à visualiser les résultats.

Je suis un peu en train d'apprendre le débogage, donc une autre suggestion d' un autre fil :

  • Configuré options(warn=2)pour traiter les avertissements comme des erreurs

Vous pouvez également utiliser optionspour vous plonger directement dans le feu de l'action lorsqu'une erreur ou un avertissement se produit, en utilisant la fonction de débogage préférée de votre choix. Par exemple:

  • Défini options(error=recover)pour s'exécuter recover()lorsqu'une erreur se produit, comme Shane l'a noté (et comme indiqué dans le guide de débogage R. Ou toute autre fonction pratique que vous jugeriez utile d'exécuter.

Et deux autres méthodes à partir de l'un des liens de @ Shane :

  • Enveloppez un appel de fonction interne avec try()pour renvoyer plus d'informations à ce sujet.
  • Pour les fonctions * apply, utilisez .inform=TRUE(à partir du package plyr) comme option de la commande apply

@JoshuaUlrich a également souligné une manière intéressante d'utiliser les capacités conditionnelles de la browser()commande classique pour activer / désactiver le débogage:

  • Mettez dans la fonction que vous voudrez peut-être déboguer browser(expr=isTRUE(getOption("myDebug")))
  • Et définissez l'option globale en options(myDebug=TRUE)
  • Vous pouvez même encapsuler l'appel du navigateur: myBrowse <- browser(expr=isTRUE(getOption("myDebug")))puis appeler avec myBrowse()car il utilise des globaux.

Ensuite, il y a les nouvelles fonctions disponibles dans R 2.10:

  • findLineNum()prend un nom de fichier source et un numéro de ligne et renvoie la fonction et l'environnement. Cela semble utile lorsque vous source()utilisez un fichier .R et qu'il renvoie une erreur à la ligne #n, mais vous devez savoir quelle fonction se trouve à la ligne #n.
  • setBreakpoint() prend un nom de fichier source et un numéro de ligne et y définit un point d'arrêt

Le package codetools , et en particulier sa checkUsagefonction, peut être particulièrement utile pour détecter rapidement les erreurs de syntaxe et de style qu'un compilateur signalerait généralement (locals inutilisés, fonctions et variables globales non définies, correspondance partielle d'arguments, etc.).

setBreakpoint()est une interface plus conviviale pour trace(). Des détails sur les éléments internes de la façon dont cela fonctionne sont disponibles dans un article récent du R Journal .

Si vous essayez de déboguer le package de quelqu'un d'autre, une fois que vous avez localisé le problème, vous pouvez écraser leurs fonctions avec fixInNamespaceet assignInNamespace, mais ne l'utilisez pas dans le code de production.

Rien de tout cela ne devrait exclure les outils de débogage R standard éprouvés , dont certains sont ci-dessus et d'autres pas. En particulier, les outils de débogage post-mortem sont pratiques lorsque vous avez un tas de code chronophage que vous préférez ne pas réexécuter.

Enfin, pour les problèmes délicats qui ne semblent pas générer de message d'erreur, vous pouvez utiliser options(error=dump.frames)comme détaillé dans cette question: Erreur sans qu'une erreur ne soit générée

Ari B. Friedman
la source
1
+1 pour tout le travail que vous avez accompli pour fusionner ces questions en une seule et la garder ouverte!
GSee
29

À un moment donné, glm.fitest appelé. Cela signifie que l' une des fonctions que vous appelez ou l' une des fonctions appelées par ces fonctions est en utilisant soit glm, glm.fit.

De plus, comme je l'ai mentionné dans mon commentaire ci-dessus, c'est un avertissement et non une erreur , ce qui fait une grande différence. Vous ne pouvez déclencher aucun des outils de débogage de R à partir d'un avertissement (avec les options par défaut avant que quelqu'un ne me dise que je me trompe ;-).

Si nous modifions les options pour transformer les avertissements en erreurs, nous pouvons commencer à utiliser les outils de débogage de R. De ?optionsnous avons:

 ‘warn’: sets the handling of warning messages.  If ‘warn’ is
      negative all warnings are ignored.  If ‘warn’ is zero (the
      default) warnings are stored until the top-level function
      returns.  If fewer than 10 warnings were signalled they will
      be printed otherwise a message saying how many (max 50) were
      signalled.  An object called ‘last.warning’ is created and
      can be printed through the function ‘warnings’.  If ‘warn’ is
      one, warnings are printed as they occur.  If ‘warn’ is two or
      larger all warnings are turned into errors.

Alors si tu cours

options(warn = 2)

puis exécutez votre code, R lancera une erreur. À quel moment, vous pourriez courir

traceback()

pour voir la pile d'appels. Voici un exemple.

> options(warn = 2)
> foo <- function(x) bar(x + 2)
> bar <- function(y) warning("don't want to use 'y'!")
> foo(1)
Error in bar(x + 2) : (converted from warning) don't want to use 'y'!
> traceback()
7: doWithOneRestart(return(expr), restart)
6: withOneRestart(expr, restarts[[1L]])
5: withRestarts({
       .Internal(.signalCondition(simpleWarning(msg, call), msg, 
           call))
       .Internal(.dfltWarn(msg, call))
   }, muffleWarning = function() NULL)
4: .signalSimpleWarning("don't want to use 'y'!", quote(bar(x + 
       2)))
3: warning("don't want to use 'y'!")
2: bar(x + 2)
1: foo(1)

Ici, vous pouvez ignorer les cadres marqués 4:et supérieurs. Nous voyons cela fooappelé baret cela a bargénéré l'avertissement. Cela devrait vous montrer quelles fonctions appelaient glm.fit.

Si vous souhaitez maintenant déboguer cela, nous pouvons utiliser une autre option pour dire à R d'entrer dans le débogueur lorsqu'il rencontre une erreur, et comme nous avons fait des erreurs d'avertissement, nous obtiendrons un débogueur lorsque l'avertissement d'origine est déclenché. Pour cela, vous devez exécuter:

options(error = recover)

Voici un exemple:

> options(error = recover)
> foo(1)
Error in bar(x + 2) : (converted from warning) don't want to use 'y'!

Enter a frame number, or 0 to exit   

1: foo(1)
2: bar(x + 2)
3: warning("don't want to use 'y'!")
4: .signalSimpleWarning("don't want to use 'y'!", quote(bar(x + 2)))
5: withRestarts({
6: withOneRestart(expr, restarts[[1]])
7: doWithOneRestart(return(expr), restart)

Selection:

Vous pouvez ensuite entrer dans l'un de ces cadres pour voir ce qui se passait lorsque l'avertissement a été émis.

Pour réinitialiser les options ci-dessus à leur valeur par défaut, entrez

options(error = NULL, warn = 0)

En ce qui concerne l'avertissement spécifique que vous citez, il est fort probable que vous deviez autoriser plus d'itérations dans le code. Une fois que vous avez découvert ce qui appelle glm.fit, essayez de lui transmettre l' controlargument en utilisant glm.control- voir ?glm.control.

Gavin Simpson
la source
4
très bonne réponse. une note de pessimisme est que ces sortes d'erreurs de convergence se produisent souvent avec des ensembles de données instables / bancals (séparation complète, etc.), et la fenêtre entre `` converge très bien '' et `` non convergente mais ne peut pas être corrigée par un nombre croissant d'itérations - a besoin d'un changement plus radical 'est souvent étroit
Ben Bolker
3
Gavin, je t'ai battu de 25 secondes. Je vous demande de supprimer votre réponse trop utile et d'arrêter de voler mes votes positifs. ;-)
Joshua Ulrich
@Ben excellent point. Si le problème de David est la séparation, augmenter le nombre d'itérations ne devrait pas aider, cela ne devrait toujours pas converger. À ce stade, l'examen des estimations et des erreurs standard pourrait suggérer qu'il y a un problème. Je m'attendrais également à voir l'avertissement concernant les valeurs ajustées numériquement 0 ou 1 si la séparation ou similaire posait un problème. Si augmenter le nombre d'itérations n'aide pas, David peut publier un autre Q pour obtenir de l'aide et je peux voler plus de votes positifs de @ Joshua ;-)
Gavin Simpson
1
@Joshua, il n'y a aucun moyen de le battre. J'ai arrêté de compter les votes positifs que j'aurais pu perdre à cause de lui. Mais de toute façon, l'aide qu'il a apportée en est de loin. Je dois trouver vos propres niches où vous le battez. Je suggère des votes positifs par frappe ici ... :)
Matt Bannert
1
Bon sang @ ran2, vous avez déjoué mon plan ignoble et sournois de conquérir le monde , Mwahahahahaha !!!!
Gavin Simpson
21

Alors browser(), traceback()et debug()entrez dans un bar, mais trace()attend à l'extérieur et maintient le moteur en marche.

En insérant browserquelque part dans votre fonction, l'exécution s'arrêtera et attendra votre entrée. Vous pouvez avancer en utilisant n(ou Enter), exécuter le bloc entier (itération) avec c, terminer la boucle / fonction en cours avec f, ou quitter avec Q; voir ?browser.

Avec debug, vous obtenez le même effet qu'avec le navigateur, mais cela arrête l'exécution d'une fonction à son début. Les mêmes raccourcis s'appliquent. Cette fonction sera en mode «débogage» jusqu'à ce que vous la désactiviez en utilisant undebug(c'est-à-dire qu'après l' debug(foo)exécution de la fonction, elle foopassera en mode «débogage» à chaque fois jusqu'à ce que vous l'exécutiez undebug(foo)).

Une alternative plus transitoire est debugonce, qui supprimera le mode «débogage» de la fonction après la prochaine évaluation.

traceback vous donnera le flux d'exécution des fonctions jusqu'à l'endroit où quelque chose s'est mal passé (une erreur réelle).

Vous pouvez insérer des bits de code (c'est-à-dire des fonctions personnalisées) dans des fonctions en utilisant trace, par exemple browser. Ceci est utile pour les fonctions des packages et vous êtes trop paresseux pour obtenir le code source bien plié.

Roman Luštrik
la source
18

Ma stratégie générale ressemble à:

  1. Courir traceback()pour voir rechercher des problèmes évidents
  2. Configuré options(warn=2)pour traiter les avertissements comme des erreurs
  3. Configurer options(error=recover)pour entrer dans la pile d'appels en cas d'erreur
Joshua Ulrich
la source
15

Après avoir traversé toutes les étapes proposées ici , je viens d' apprendre que la mise .verbose = TRUEen foreach()me donne aussi des tonnes d'informations utiles. En particulier, foreach(.verbose=TRUE)montre exactement où une erreur se produit dans la boucle foreach, alors traceback()qu'elle ne regarde pas à l'intérieur de la boucle foreach.

Michael Schneider
la source
13

Le débogueur de Mark Bravington qui est disponible sous forme de package debugsur CRAN est très bon et assez simple.

library(debug);
mtrace(myfunction);
myfunction(a,b);
#... debugging, can query objects, step, skip, run, breakpoints etc..
qqq(); # quit the debugger only
mtrace.off(); # turn off debugging

Le code apparaît dans une fenêtre Tk en surbrillance afin que vous puissiez voir ce qui se passe et, bien sûr, vous pouvez en appeler un autre mtrace()dans une fonction différente.

HTH

David Lawrence Miller
la source
11

J'aime la réponse de Gavin: je ne connaissais pas les options (erreur = récupérer). J'aime aussi utiliser le package 'debug' qui donne un moyen visuel de parcourir votre code.

require(debug)
mtrace(foo)
foo(1)

À ce stade, il ouvre une fenêtre de débogage distincte montrant votre fonction, avec une ligne jaune indiquant où vous vous trouvez dans le code. Dans la fenêtre principale, le code entre en mode débogage, et vous pouvez continuer à appuyer sur Entrée pour parcourir le code (et il y a aussi d'autres commandes), et examiner les valeurs des variables, etc. La ligne jaune dans la fenêtre de débogage continue de se déplacer pour montrer où vous êtes dans le code. Une fois le débogage terminé, vous pouvez désactiver le traçage avec:

mtrace.off()
Prasad Chalasani
la source
5

Sur la base de la réponse que j'ai reçue ici , vous devez absolument vérifier le options(error=recover)paramètre. Lorsque cela est défini, en cas d'erreur, vous verrez du texte sur la console similaire à ce qui suit ( tracebacksortie):

> source(<my filename>)
Error in plot.window(...) : need finite 'xlim' values
In addition: Warning messages:
1: In xy.coords(x, y, xlabel, ylabel, log) : NAs introduced by coercion
2: In min(x) : no non-missing arguments to min; returning Inf
3: In max(x) : no non-missing arguments to max; returning -Inf

Enter a frame number, or 0 to exit   

1: source(<my filename>)
2: eval.with.vis(ei, envir)
3: eval.with.vis(expr, envir, enclos)
4: LinearParamSearch(data = dataset, y = data.frame(LGD = dataset$LGD10), data.names = data
5: LinearParamSearch.R#66: plot(x = x, y = y.data, xlab = names(y), ylab = data.names[i])
6: LinearParamSearch.R#66: plot.default(x = x, y = y.data, xlab = names(y), ylab = data.nam
7: LinearParamSearch.R#66: localWindow(xlim, ylim, log, asp, ...)
8: LinearParamSearch.R#66: plot.window(...)

Selection:

À quel point vous pouvez choisir le "cadre" dans lequel entrer. Lorsque vous faites une sélection, vous serez mis en browser()mode:

Selection: 4
Called from: stop(gettextf("replacement has %d rows, data has %d", N, n), 
    domain = NA)
Browse[1]> 

Et vous pouvez examiner l'environnement tel qu'il était au moment de l'erreur. Lorsque vous avez terminé, tapez cpour vous ramener au menu de sélection de cadre. Lorsque vous avez terminé, comme il vous le dit, tapez 0pour quitter.

Eykanal
la source
4

J'ai donné cette réponse à une question plus récente, mais je l'ajoute ici par souci d'exhaustivité.

Personnellement, j'ai tendance à ne pas utiliser de fonctions de débogage. Je trouve souvent que cela cause autant de problèmes que cela en résout. De plus, venant d'un arrière-plan Matlab, j'aime pouvoir le faire dans un environnement de développement intégré (IDE) plutôt que de le faire dans le code. L'utilisation d'un IDE garde votre code propre et simple.

Pour R, j'utilise un IDE appelé "RStudio" ( http://www.rstudio.com ), qui est disponible pour Windows, Mac et Linux et est assez facile à utiliser.

Les versions de Rstudio depuis octobre 2013 environ (0.98ish?) Ont la possibilité d'ajouter des points d'arrêt dans les scripts et les fonctions: pour ce faire, il suffit de cliquer sur la marge gauche du fichier pour ajouter un point d'arrêt. Vous pouvez définir un point d'arrêt, puis passer en revue à partir de ce point. Vous avez également accès à toutes les données de cet environnement, vous pouvez donc essayer des commandes.

Voir http://www.rstudio.com/ide/docs/debugging/overview pour plus de détails. Si vous avez déjà installé Rstudio, vous devrez peut-être mettre à niveau - il s'agit d'une fonctionnalité relativement nouvelle (fin 2013).

Vous pouvez également trouver d'autres IDE qui ont des fonctionnalités similaires.

Certes, s'il s'agit d'une fonction intégrée, vous devrez peut-être recourir à certaines des suggestions faites par d'autres personnes dans cette discussion. Mais, si c'est votre propre code qui doit être corrigé, une solution basée sur l'IDE peut être exactement ce dont vous avez besoin.

Andy Clifton
la source
1

Pour déboguer des méthodes de classe de référence sans référence d'instance

ClassName$trace(methodName, browser)
Siva
la source
0

Je commence à penser que ne pas imprimer le numéro de ligne d'erreur - une exigence la plus élémentaire - PAR DEFAILT - est une sorte de blague dans R / Rstudio . La seule méthode fiable que j'ai trouvée pour trouver où une erreur s'est produite est de faire l'effort supplémentaire d'appeler traceback () et de voir la ligne du haut.

user9669128
la source