Allgemeine Vorschläge zum Debuggen in R.

120

Ich erhalte eine Fehlermeldung, wenn ich eine von mir geschriebene R-Funktion verwende:

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

Was habe ich getan:

  1. Schritt durch die Funktion
  2. Das Hinzufügen von print, um herauszufinden, in welcher Zeile der Fehler auftritt, schlägt zwei Funktionen vor, die nicht verwendet werden sollten glm.fit. Sie sind window()und save().

Meine allgemeinen Ansätze umfassen das Hinzufügen printund stopBefehlen sowie das zeilenweise Durchlaufen einer Funktion, bis ich die Ausnahme finden kann.

Es ist mir jedoch nicht klar, mit welchen Techniken dieser Fehler im Code auftritt. Ich bin mir nicht einmal sicher, von welchen Funktionen im Code abhängt glm.fit. Wie gehe ich vor, um dieses Problem zu diagnostizieren?

David LeBauer
quelle
5
Schauen Sie sich Duncan Murdochs Seite zum Debuggen in R
Rob Hyndman
10
Ok, ich sage das Offensichtliche: Das ist eine Warnung, kein Fehler .
Gavin Simpson
10
@ gavin-simpson Ich habe nicht bemerkt, dass es einen technischen Unterschied gibt, danke für den Hinweis. Aber am Ende zeigt es an, dass meine zuvor funktionierende Funktion nicht funktioniert.
David LeBauer
11
@ David +1 für "... meine zuvor funktionierende Funktion ist nicht funktionsfähig."
Joshua Ulrich
5
@ David: Re deine ps. Dies fügt der Frage eine Dimension hinzu, die ohne das Beispiel übersehen worden wäre. nämlich wie man R dazu bringt, in den Debugging-Modus zu wechseln, wenn nur Warnungen erzeugt werden? Wenn Sie dieses Detail weggelassen hätten, hätten wir Sie alle nicht darauf hingewiesen options(warn = 2). In diesem Fall ist das Detail für die Beantwortung Ihrer allgemeinen Frage von entscheidender Bedeutung. +1 von mir.
Gavin Simpson

Antworten:

167

Ich würde sagen, dass das Debuggen eine Kunstform ist, also gibt es keine klare Silberkugel. Es gibt gute Strategien zum Debuggen in jeder Sprache, und diese gelten auch hier (z. B. lesen Sie diesen schönen Artikel ). Das erste, was Sie tun müssen, ist beispielsweise, das Problem zu reproduzieren. Wenn Sie dies nicht tun können, müssen Sie weitere Informationen abrufen (z. B. bei der Protokollierung). Sobald Sie es reproduzieren können, müssen Sie es auf die Quelle reduzieren .

Anstelle eines "Tricks" würde ich sagen, dass ich eine bevorzugte Debugging-Routine habe:

  1. Wenn ein Fehler auftritt, überprüfe ich normalerweise zuerst den Stack-Trace, indem ich Folgendes aufrufe traceback(): Dies zeigt Ihnen, wo der Fehler aufgetreten ist. Dies ist besonders nützlich, wenn Sie mehrere verschachtelte Funktionen haben.
  2. Als nächstes werde ich setzen options(error=recover); Dadurch wird sofort in den Browsermodus gewechselt, in dem der Fehler auftritt, sodass Sie von dort aus den Arbeitsbereich durchsuchen können.
  3. Wenn ich immer noch nicht genügend Informationen habe, verwende ich normalerweise die debug()Funktion und gehe das Skript Zeile für Zeile durch.

Der beste neue Trick in R 2.10 (beim Arbeiten mit Skriptdateien) ist die Verwendung der Funktionen findLineNum()und setBreakpoint().

Als letzter Kommentar: Abhängig vom Fehler ist es auch sehr hilfreich, externe Funktionsaufrufe festzulegen try()oder tryCatch()Anweisungen zu geben (insbesondere beim Umgang mit S4-Klassen). Dadurch erhalten Sie manchmal noch mehr Informationen und können besser steuern, wie Fehler zur Laufzeit behandelt werden.

Diese verwandten Fragen haben viele Vorschläge:

Shane
quelle
8
Sie können debugonce () auch zu debug () hinzufügen.
Joris Meys
2
Obwohl fix (df1) nicht nur beim Debuggen nützlich ist, öffnet es den grafischen R-Editor mit dem darin geladenen Datenrahmen df1, den Sie im laufenden Betrieb bearbeiten oder nur einen Blick darauf werfen können.
Dmitrii I.
Das Debuggen in R scheint sehr schwierig zu sein, zum Beispiel gibt es keine einfache Lösung, um Codezeilen von Warnungen zu sehen
TMS
browser()für den Fall, dass es Fehler gibt, die keine Warnungen / Fehler auslösen (Bildnachweis: Roman Luštrik auf dieser Seite). Irgendein anderes Tool wie browser()?
PatrickT
38

Der beste Durchgang, den ich bisher gesehen habe, ist:

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

Ist jemand einverstanden / nicht einverstanden?

Christopher DuBois
quelle
Sehr ausführliche Anleitung - beschreibt wichtige Tools, die in R Core enthalten sind: debug (), traceback () und recovery ().
Sharpie
32

Wie in mir wies darauf hin , eine andere Frage , Rprof()und summaryRprof()sind nette Werkzeuge zu langsamen Teile des Programms zu finden , dass könnte profitieren von beschleunigen oder Umzug in eine C / C ++ Implementierung. Dies gilt wahrscheinlich mehr, wenn Sie Simulationsarbeiten oder andere rechen- oder datenintensive Aktivitäten ausführen. Das profrPaket kann dabei helfen, die Ergebnisse zu visualisieren.

Ich bin gerade dabei, etwas über das Debuggen zu lernen, also ein weiterer Vorschlag aus einem anderen Thread :

  • Stellen Sie ein options(warn=2), dass Warnungen wie Fehler behandelt werden

Sie können Sie auch verwenden options, um Sie direkt in die Hitze der Aktion zu versetzen, wenn ein Fehler oder eine Warnung auftritt, und dabei Ihre bevorzugte Debugging-Funktion verwenden. Zum Beispiel:

  • Stellen Sie options(error=recover)diese Option so ein , dass sie ausgeführt wird, recover()wenn ein Fehler auftritt, wie Shane festgestellt hat (und wie im R-Debugging-Handbuch dokumentiert) .

Und noch zwei Methoden von einem der Links von @ Shane :

  • Schließen Sie einen inneren Funktionsaufruf mit ein try(), um weitere Informationen zurückzugeben.
  • Verwenden Sie für * .inform=TRUEApply- Funktionen (aus dem Plyr-Paket) als Option für den Apply-Befehl

@JoshuaUlrich wies auch auf eine gute Möglichkeit hin, die bedingten Fähigkeiten des klassischen browser()Befehls zum Ein- und Ausschalten des Debuggens zu nutzen:

  • Fügen Sie die Funktion ein, die Sie möglicherweise debuggen möchten browser(expr=isTRUE(getOption("myDebug")))
  • Und setzen Sie die globale Option auf options(myDebug=TRUE)
  • Sie können den Browser-Aufruf sogar umbrechen myBrowse <- browser(expr=isTRUE(getOption("myDebug")))und dann mit aufrufen, myBrowse()da er Globals verwendet.

Dann gibt es die neuen Funktionen in R 2.10:

  • findLineNum()Nimmt einen Quelldateinamen und eine Zeilennummer und gibt die Funktion und Umgebung zurück. Dies scheint hilfreich zu sein, wenn Sie source()eine .R-Datei verwenden und in Zeile #n einen Fehler zurückgibt. Sie müssen jedoch wissen, welche Funktion sich in Zeile #n befindet.
  • setBreakpoint() Nimmt einen Quelldateinamen und eine Zeilennummer und setzt dort einen Haltepunkt

Das Codetools- Paket und insbesondere seine checkUsageFunktion können besonders hilfreich sein, um Syntax- und Stilfehler, die ein Compiler normalerweise meldet (nicht verwendete lokale Elemente , undefinierte globale Funktionen und Variablen, partielle Argumentübereinstimmung usw.), schnell zu erkennen.

setBreakpoint()ist ein benutzerfreundlicheres Frontend trace(). Details zu den Interna, wie dies funktioniert, finden Sie in einem kürzlich erschienenen Artikel im R Journal .

Wenn Sie versuchen, das Paket eines anderen zu debuggen, können Sie, sobald Sie das Problem gefunden haben , dessen Funktionen mit fixInNamespaceund assignInNamespaceüberschreiben, verwenden Sie dies jedoch nicht im Produktionscode.

Nichts davon sollte die bewährten Standard-R-Debugging-Tools ausschließen , von denen einige oben stehen und andere nicht. Insbesondere die Post-Mortem-Debugging-Tools sind praktisch, wenn Sie eine zeitaufwändige Menge an Code haben, die Sie lieber nicht erneut ausführen möchten.

Schließlich können Sie für knifflige Probleme, bei denen keine Fehlermeldung ausgegeben wird, Folgendes verwenden options(error=dump.frames): Fehler, ohne dass ein Fehler ausgelöst wird

Ari B. Friedman
quelle
1
+1 für all die Arbeit, die Sie investiert haben, um diese Fragen zu einer zusammenzuführen und dann offen zu halten!
GSee
29

Irgendwann glm.fitwird gerufen. Das bedeutet, dass eine der von Ihnen aufgerufenen Funktionen oder eine der von diesen Funktionen aufgerufenen Funktionen entweder Folgendes verwendet glm: glm.fit.

Wie ich oben in meinem Kommentar erwähne, ist dies eine Warnung, kein Fehler , der einen großen Unterschied macht. Sie können keines der Debugging-Tools von R über eine Warnung auslösen (mit Standardoptionen, bevor mir jemand sagt, dass ich falsch liege ;-).

Wenn wir die Optionen ändern, um Warnungen in Fehler umzuwandeln, können wir die Debugging-Tools von R verwenden. Von haben ?optionswir:

 ‘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.

Also wenn du rennst

options(warn = 2)

Führen Sie dann Ihren Code aus. R gibt einen Fehler aus. An welchem ​​Punkt könnten Sie laufen

traceback()

um den Aufrufstapel zu sehen. Hier ist ein Beispiel.

> 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)

Hier können Sie die markierten 4:und höheren Frames ignorieren . Wir sehen, dass dies fooaufgerufen wurde barund bardie Warnung generiert wurde. Das sollte Ihnen zeigen, welche Funktionen aufgerufen wurden glm.fit.

Wenn Sie dies jetzt debuggen möchten, können Sie sich an eine andere Option wenden, um R anzuweisen, den Debugger einzugeben, wenn ein Fehler auftritt. Da wir Warnfehler gemacht haben, erhalten wir einen Debugger, wenn die ursprüngliche Warnung ausgelöst wird. Dafür solltest du laufen:

options(error = recover)

Hier ist ein Beispiel:

> 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:

Sie können dann in einen dieser Frames treten, um zu sehen, was passiert ist, als die Warnung ausgelöst wurde.

Geben Sie ein, um die oben genannten Optionen auf ihre Standardeinstellungen zurückzusetzen

options(error = NULL, warn = 0)

Für die spezifische Warnung, die Sie zitieren, ist es sehr wahrscheinlich, dass Sie mehr Iterationen im Code zulassen müssen. Wenn Sie herausgefunden haben, was aufgerufen wird glm.fit, können Sie herausfinden, wie Sie das controlArgument mit glm.control- siehe ?glm.control.

Gavin Simpson
quelle
4
gute Antwort. Ein Hinweis auf Pessimismus ist, dass diese Art von Konvergenzfehlern häufig bei instabilen / wackeligen Datensätzen (vollständige Trennung usw.) auftritt und das Fenster zwischen "konvergiert gut" und "nicht konvergent" nicht durch Erhöhen der Anzahl behoben werden kann von Iterationen - braucht eine drastischere Änderung 'ist oft eng
Ben Bolker
3
Gavin, ich habe dich um 25 Sekunden geschlagen. Ich fordere Sie auf, Ihre übermäßig hilfreiche Antwort zu entfernen und meine Upvotes nicht mehr zu stehlen. ;-)
Joshua Ulrich
@ Ben toller Punkt. Wenn Davids Problem die Trennung ist, sollte es nicht helfen, die Anzahl der Iterationen zu erhöhen, aber es sollte immer noch nicht konvergieren. An diesem Punkt könnte ein Blick auf die Schätzungen und Standardfehler darauf hindeuten, dass ein Problem vorliegt. Ich würde auch erwarten, die Warnung über angepasste Werte numerisch 0 oder 1 zu sehen, wenn Trennung oder ähnliches ein Problem wären. Wenn es nicht hilft, die Anzahl der Iterationen zu erhöhen, kann David ein weiteres Q für Hilfe posten und ich kann mehr von @ Joshuas Upvotes stehlen ;-)
Gavin Simpson
1
@ Joshua, es gibt keine Möglichkeit ihn zu schlagen. Ich hörte auf, die Stimmen zu zählen, die ich wegen ihm verloren haben könnte. Aber die Hilfe, die er leistet, erklärt das bei weitem. Ich muss deine eigenen Nischen finden, in denen du ihn geschlagen hast. Ich schlage hier Upvotes per Tastendruck vor ... :)
Matt Bannert
1
Verdammt @ ran2, du hast meinen heimtückischen, verschlagenen Plan vereitelt, die Welt zu übernehmen , Mwahahahahaha !!!!
Gavin Simpson
21

Also browser(), traceback()und debug()geh in eine Bar, trace()warte aber draußen und lasse den Motor laufen.

Durch Einfügen browserirgendwo in Ihre Funktion wird die Ausführung angehalten und auf Ihre Eingabe gewartet. Sie können mit n(oder Enter) vorwärts gehen , den gesamten Block (Iteration) mit ausführen c, die aktuelle Schleife / Funktion mit fbeenden oder mit beenden Q. siehe ?browser.

Mit erhalten debugSie den gleichen Effekt wie mit dem Browser, dies stoppt jedoch die Ausführung einer Funktion am Anfang. Es gelten die gleichen Verknüpfungen. Diese Funktion befindet sich in einem "Debug" -Modus, bis Sie sie mit deaktivieren undebug(dh nachdem debug(foo)die Funktion ausgeführt wurde, wechselt foosie jedes Mal in den "Debug" -Modus, bis Sie sie ausführen undebug(foo)).

Eine vorübergehende Alternative ist debugonce, dass der "Debug" -Modus nach der nächsten Auswertung aus der Funktion entfernt wird.

traceback gibt Ihnen den Ablauf der Ausführung von Funktionen bis zu dem Punkt, an dem etwas schief gelaufen ist (ein tatsächlicher Fehler).

Sie können Code - Bits (dh benutzerdefinierte Funktionen) in Funktionen einsetzen trace, zum Beispiel browser. Dies ist nützlich für Funktionen aus Paketen und Sie sind zu faul, um den schön gefalteten Quellcode zu erhalten.

Roman Luštrik
quelle
18

Meine allgemeine Strategie sieht so aus:

  1. Lauf traceback() den Vorgang aus, um nach offensichtlichen Problemen zu suchen
  2. einstellen options(warn=2), dass Warnungen wie Fehler behandelt werden
  3. Stellen Sie ein options(error=recover), dass Sie bei einem Fehler in den Aufrufstapel eintreten
Joshua Ulrich
quelle
15

Nachdem alle Schritte hier vorgeschlagen durchlaufen ich gerade gelernt , dass Einstellung .verbose = TRUEin foreach()mir Tonnen von nützlichen Informationen auch gibt. foreach(.verbose=TRUE)Zeigt insbesondere genau an, wo ein Fehler innerhalb der foreach-Schleife auftritt, während traceback()nicht innerhalb der foreach-Schleife geschaut wird.

Michael Schneider
quelle
13

Mark Bravingtons Debugger, der als Paket debugauf CRAN verfügbar ist, ist sehr gut und ziemlich einfach.

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

Der Code wird in einem hervorgehobenen Tk-Fenster angezeigt, sodass Sie sehen können, was los ist, und natürlich einen anderen anrufen können mtrace() in einer anderen Funktion einen anderen .

HTH

David Lawrence Miller
quelle
11

Ich mag Gavins Antwort: Ich wusste nichts über Optionen (Fehler = Wiederherstellen). Ich verwende auch gerne das 'Debug'-Paket, das eine visuelle Möglichkeit bietet, Ihren Code zu durchlaufen.

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

Zu diesem Zeitpunkt wird ein separates Debug-Fenster geöffnet, in dem Ihre Funktion angezeigt wird. Eine gelbe Linie zeigt an, wo Sie sich im Code befinden. Im Hauptfenster wechselt der Code in den Debug-Modus, und Sie können weiterhin die Eingabetaste drücken, um den Code zu durchlaufen (und es gibt auch andere Befehle) und Variablenwerte usw. zu untersuchen. Die gelbe Linie im Debug-Fenster bewegt sich weiter, um anzuzeigen, wo Sie sind im Code. Wenn Sie mit dem Debuggen fertig sind, können Sie die Ablaufverfolgung deaktivieren mit:

mtrace.off()
Prasad Chalasani
quelle
5

Basierend auf der Antwort, die ich hier erhalten habe , sollten Sie auf jeden Fall die options(error=recover)Einstellung überprüfen . Wenn dies festgelegt ist, wird bei Auftreten eines Fehlers auf der Konsole ein Text angezeigt, der dem folgenden ähnelt ( tracebackAusgabe):

> 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:

An welchem ​​Punkt können Sie auswählen, welchen "Frame" Sie eingeben möchten. Wenn Sie eine Auswahl treffen, werden Sie in den browser()Modus versetzt:

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

Und Sie können die Umgebung so untersuchen, wie sie zum Zeitpunkt des Fehlers war. Wenn Sie fertig sind c, geben Sie ein, um zum Rahmenauswahlmenü zurückzukehren. Wenn Sie fertig sind, geben Sie 0zum Beenden ein.

Eykanal
quelle
4

Ich habe diese Antwort auf eine neuere Frage gegeben, füge sie der Vollständigkeit halber hier hinzu.

Persönlich neige ich dazu, keine Funktionen zum Debuggen zu verwenden. Ich finde oft, dass dies so viel Ärger verursacht, wie es löst. Da ich aus einem Matlab-Hintergrund komme, mag ich es, dies in einer integrierten Entwicklungsumgebung (IDE) zu tun, anstatt dies im Code zu tun. Die Verwendung einer IDE hält Ihren Code sauber und einfach.

Für R verwende ich eine IDE namens "RStudio" ( http://www.rstudio.com ), die für Windows, Mac und Linux verfügbar und recht einfach zu verwenden ist.

Versionen von Rstudio seit etwa Oktober 2013 (0.98ish?) Können Haltepunkte in Skripten und Funktionen hinzufügen: Klicken Sie dazu einfach auf den linken Rand der Datei, um einen Haltepunkt hinzuzufügen. Sie können einen Haltepunkt festlegen und von diesem Punkt an fortfahren. Sie haben auch Zugriff auf alle Daten in dieser Umgebung, sodass Sie Befehle ausprobieren können.

Weitere Informationen finden Sie unter http://www.rstudio.com/ide/docs/debugging/overview . Wenn Sie Rstudio bereits installiert haben, müssen Sie möglicherweise ein Upgrade durchführen. Dies ist eine relativ neue Funktion (Ende 2013).

Möglicherweise finden Sie auch andere IDEs mit ähnlichen Funktionen.

Zugegeben, wenn es sich um eine integrierte Funktion handelt, müssen Sie möglicherweise auf einige der Vorschläge zurückgreifen, die andere Personen in dieser Diskussion gemacht haben. Wenn jedoch Ihr eigener Code repariert werden muss, ist eine IDE-basierte Lösung möglicherweise genau das, was Sie benötigen.

Andy Clifton
quelle
1

Debuggen von Referenzklassenmethoden ohne Instanzreferenz

ClassName$trace(methodName, browser)
Siva
quelle
0

Ich fange an zu denken, dass es eine Art Witz in R / Rstudio ist, keine Fehlerzeilennummer zu drucken - eine Grundvoraussetzung - BY DEFAILT . Die einzige zuverlässige Methode, die ich gefunden habe, um herauszufinden, wo ein Fehler aufgetreten ist, besteht darin, sich zusätzlich darum zu bemühen, traceback () aufzurufen und die oberste Zeile anzuzeigen .

user9669128
quelle