Fehlersuche in R

Fehler im Code zu suchen kann lästig sein. Gerade in größeren Workflows mit verschiedenen Scripten und vielen Funktionen sollte man wissen, wie man systematisch vorgehen kann, um Fehler schnell zu finden und zu korrigieren. Ich stelle heute eine Methode vor, wie man Schritt für Schritt nachvollziehen kann, was in aufgerufenen Funktionen zur Laufzeit passiert: das Debuggen. Dazu werden wir uns eine spezielle Funktion zu Nutze machen, nämlich debug(…).

Durch Debuggen Fehler im R Code finden

Das Debuggen ist eine gute Methode, wenn man verstehen möchte, was in unseren Funktionen zur Laufzeit passiert. Das Wort debug kommt aus dem Englischen und basiert auf dem Wort bug (= Käfer, Wanze) und wird in der Computerwelt als Fehler übersetzt. Bekannt wurde dieser Zusammenhang vor allem dadurch, dass einst (ca. 1950, als die Computersysteme noch groß und mechanisch waren) eine Motte für den Ausfall eines Systems gesorgt hatte, da sie in einem Relais festklemmte. Die Motte wurde in ein Logbuch geklebt mit dem Zusatz „First actual case of bug being found.“ (Quelle: tomshardware). Aber das nur am Rande.

Ziel unserer Beispiel-Funktion

Fangen wir an und schaffen uns eine Grundlage: Eine Funktion, die allerdings nicht ganz das tut, was wir möchten. Unser Ziel ist es, zwei numerische Vektoren zu vergleichen und uns die Positionen anzeigen zu lassen, an denen für beide Vektoren gilt: Das Element an Stelle k ist anders als das an Stelle k-1. Hier zur Veranschaulichung zwei Vektoren der Länge 20:

vec1 <- c(1,1,1,3,3,3,4,4,4,4,4,4,1,1,1,1,2,2,2,2)
vec2 <- c(0,0,2,5,5,5,5,5,5,5,5,5,2,2,2,1,1,4,4,4)

Wir sehen, für vec1 ändert sich die Nummer an Poisitionen 4, 7, 13, und 17. Für vec2 sind es die Positionen 3, 4, 13, 16, und 18. Unsere Funktion soll uns die Positionen 4 und 13 zurückgeben - die Stellen, an denen die Werte sich bei beiden Vektoren ändern.

Erste Version unserer Funktion

Wir setzen uns hin und schreiben jetzt eine geeignete Funktion - und dabei kommt folgendes heraus:

myFunc <- function(x, y) {
  xchange <- as.numeric(diff(x) != 0)
  ychange <- as.numeric(diff(y) != 0)
  xychange <- as.numeric(xchange == ychange)
  changePos <- which(xychange == 1)
  return(changePos)
}

Kurze Erklärung: Die Funktion diff gibt uns die Differenz zum vorherigen Element innerhalb eines Vektors zurück. Sobald diese also ungleich 0 ist, sollte eine Änderung des Wertes vorliegen. Mit xychange haben wir einen Vektor, der x und y in einen Zusammenhang bringt, und mit changePos finden wir die jeweiligen Positionen heraus. Testen wir diese Funktion nun mit myFunc(vec1, vec2), sind wir aber leider weit entfernt von unserem erwünschten Ergebnis. Es werden eindeutig zu viele Positionen angegeben. Wie kommts?

Wir debuggen die Funktion

Wir aktivieren jetzt den Debugger mit debug(myFunc). Anschließend lassen wir die Funktion nochmal laufen mit myFunc(vec1, vec2). Diesmal kriegen wir allerdings nicht sofort die Werte zurück, sondern springen "in die Funktion": Der Debugger ist aktiv:

Das Debuggen in R

Wir können nun mit der Enter-Taste Zeile für Zeile voranschreiten (und könnten sogar "live" die Werte für Variablen verändern bzw. neue Objekte anlegen). Jetzt geht es darum, systematisch zu schauen, was sich während des Funktionsaufrufs abspielt. Wir drücken jetzt ein paar mal Enter, sodass der nächste Aufruf return(changePos) wäre. Oben rechts im Environment-Tab von RStudio sehen wir nun alle angelegten Variablen. Nun geht es auf die Suche - beispielsweise können wir uns die Werte von xchange, ychange und xychange in der Konsole ausgeben lassen, indem wir die Namen der Variablen einfach eingeben.

Debuggen in der RStudio Konsole

Jetzt vergleichen wir das, was da steht, mit dem, was eigentlich unser Ziel ist. Wir sehen: xychange hat viel zu viele Einsen. Aber ergibt ja auch Sinn, da wir ja nur Vergleichen, wann xchange gleich ychange ist. Wir möchten aber eigentlich, dass in xychange nur Einsen stehen, wenn xchange und ychange beide gleich Eins sind! Wunderbar, ein Fehler gefunden. Wir können jetzt noch einmal Enter drücken, oder einfach auf Stop in RStudio klicken und das Debuggen ist vorbei.

Wir passen die Funktion an

Wir können nun unsere Funktion myFunc korrigieren. Für das Tutorial hier erstelle ich einfach eine neue Funktion myFunc1, die eine erste Fehlerkorrektur hinter sich hat:

myFunc1 <- function(x, y) {
  xchange <- as.numeric(diff(x) != 0)
  ychange <- as.numeric(diff(y) != 0)
  xychange <- as.numeric(xchange == 1 & ychange == 1)
  changePos <- which(xychange == 1)
  return(changePos)
}

Wir führen die Funktion aus und siehe da - weniger Positionen werden angezeigt. Allerdings ist eine Sache noch nicht ganz richtig: Die jeweilige Position scheint immer um 1 zu niedrig zu sein. Wir debuggen jetzt myFunc1 und schauen wieder genau hin.

Finale Anpassung

Das Prinzip ist hier das selbe wie oben, deswegen werde ich nicht ins Detail gehen. Der zweite Fehler ist hier noch folgender: Die diff-Funktion gibt immer die Differenz zwischen zwei Elementen zurück. Bei einem Vektor der Länge 20 ist der resultierende Vektor also der Länge 19 (denn: 19 Zwischenräume). Eine Anpassung könnte also sein, eine 0 vor die anderen Elemente zu schreiben:

myFunc2 <- function(x, y) {
  xchange <- c(0, as.numeric(diff(x) != 0))
  ychange <- c(0, as.numeric(diff(y) != 0))
  xychange <- as.numeric(xchange == 1 & ychange == 1)
  changePos <- which(xychange == 1)
  return(changePos)
}

Wir testen: myFunc2(vec1, vec2). Wunderbar, jetzt funktioniert alles so wie gewünscht! Übrigens: Wenn wir eine Funktion nicht mehr debuggen wollen, können wir sie einfach neu einlesen (also die Funktionsdefinition ausführen), oder aber undebug(myFunc) ausführen.

Andere Methoden um Fehler im R Code zu finden

Das Debuggen ist schon sehr mächtig, es gibt aber auch weitere hilfreiche Methoden, die die Fehlersuche vereinfachen. Eine Idee ist es, sich bestimmte Variablen oder Eigenschaften in die Konsole schreiben zu lassen (mit print oder cat). Wir hätten zum Beispiel innerhalb der Funktion print(length(xchange)) schreiben können. So würde bei Ausführen der Funktion die Länge des Vektors angezeigt werden. Des Weiteren hilft es auch manchmal, wenn man sich die einzelnen Zeilen aus der Funktion nimmt und selbst in der Konsole nachspielt. Was auch immer hilft: Eine zweite Person auf den Code schauen zu lassen. Ein externer, neutraler Standpunkt kann manchmal Wunder wirken.

Soviel zur Fehlersuche in R. Auf dass viele Fehler erst gar nicht aufkommen - sonst aber viel Erfolg beim Debuggen! Hast du noch weitere Fragen? Oder Fragen einem ganz anderen Thema? Schreib mir einfach eine Mail: mail@r-coding.de.
Bleib außerdem auf dem Laufenden mit dem r-coding Newsletter. Du erhältst Infos zu neuen Blogeinträgen, sowie kleine Tipps und Tricks zu R. Melde dich jetzt an: http://r-coding.de/newsletter.

Cheers

Add a comment

*Please complete all fields correctly