Data Science, Machine Learning und KI
Kontakt

Daten-Visualisierung und -Verständnis sind wichtige Faktoren bei der Durchführung eines Data Science Projekts. Eine visuelle Exploration der Daten unterstützt den Data Scientist beim Verständnis der Daten und liefert häufig wichtige Hinweise über Datenqualität und deren Besonderheiten. Bei STATWORX wenden wir im Bereich Datenvisualsierung eine Vielzahl von unterschiedlichen Tools und Technologien an, wie z.B. Tableau, Qlik, R Shiny oder D3. Seit 2014 hat QlikTech zwei Produkte im Angebot: QlikView und Qlik Sense. Für viele unserer Kunden ist die Entscheidung, ob sie QlikView oder QlikSense einsetzen sollen nicht einfach zu treffen. Was sind die Vor- und Nachteile der beiden Produkte? In diesem Blogbeitrag erläutern wir die Unterschiede zwischen den beiden Tools.

sense-vs-view

Geschichte und Aufbau von Qlik

Die Erfolgsgeschichte der Firma QlikTech begann in den 90er Jahren mit dem Produkt QlikView, das im Jahre 2014 durch QlikSense offiziell abgelöst werden sollte. Aktuell sind noch beide Produkte am Markt, QlikTech fokussiert sich jedoch stark auf die Weiterentwicklung von QlikSense. Zwischen den Veröffentlichungsterminen der beiden Produkte stieg die Anzahl der Mitarbeiter von QlikTech von 35 auf über 2000. Beiden Produkten liegt dieselbe Kerntechnologie zu Grunde; die Qlik Associative Engine. Diese ermöglicht eine einfache Verknüpfung von unterschiedlichen Datensätzen, die alle In-Memory (im Arbeitsspeicher des Rechners) gehalten werden. Dies ermöglicht einen extrem schnellen Datenzugriff, da der Arbeitsspeicher gegenüber normalen Datenträgern eine erheblich schnellere Lese- und Schreibgeschwindigkeit aufweist. Weiterhin kann die Qlik Associative Engine viele Nutzer und große Datenmengen managen was einer der Hauptgründe ist, warum sich Qlik erfolgreich gegen seine Mitbewerber durchsetzen konnte.

QlikView

Qlikview

QlikView orientiert sich grundsätzlich am Thema „Guided Analytics“. Hierbei wird dem Endanwender eine App von einem QlikView-Entwickler bereitgestellt, die die notwendigen Überlegungen und Implementierungen rund um das Datenmodell, den inhaltlichen und optischen Aufbau der App sowie die unterschiedlichen Visualisierunge enthält. Der Anwender wiederum hat die komplette Freiheit die Daten durch Filtern, Auswählen, Drill-Down und Cycle Groups zu erkunden, um neue Erkentnisse zu gewinnen und Antworten auf seine Business-Fragen zu finden. Hierbei steht allerdings das Erstellen von eigenen Visualisierungen für den Endanwender nicht im Fokus.
Die Entwicklung von QlikView Applikationen erfordert Erfahrung und Expertise, da die Dashboarderstellung nicht über Drag und Drop möglich ist. Der Endanwender erhält hingegen eine fertige und einsatzbereite Applikation und kann somit umgehend mit seinen BI Analysen starten.
Die fertigen Datenmodelle und die daraus resultierende QVDs können ebenfalls von Qlik Sense geöffnet werden, dies ist allerdings andersherum nicht möglich.

Weitere wichtige Eckpunkte zu QlikView sind:

  • eine Vielzahl an unterschiedlichen Datenverbindungsmöglichkeiten vorhanden
  • keine Cloud-Lösung erhältlich, QlikView läuft lokal oder auf einem On-Premise Server
  • benutzerfreundliche Entwicklungsoberfläche
  • baut auf C++ und C# auf
  • PDF Reporting durch NPrinting möglich
  • Pixel genaues Erstellen von Applikationen
  • 2000er Retro-Charme
  • schnelle Entwicklungsprozesse und einfache Anpassungsmöglichkeiten

Qlik Sense

Qliksense

Qlik Sense wurde mit dem Fokus auf Self-Service BI entwickelt und ist Qliks Antwort auf den größten Mitbewerber Tableau. Hierbei wird der Anwender der Applikation weniger gerichtet geleitet sondern ihm die Möglichkeit gegeben seine eigenen Daten zu integrieren, um Apps selbständig zu kreieren. Einer der Vorteile von Self-Service BI ist, dass der Anwender eigenständig neue Visualisierungen erstellen kann, die sich konkret an seinen Fragestellungen orientieren. Allerdings erfordert dies engagierte und neugierige Anwender, die Lust haben ihre Daten zu erkunden. Tools wie QlikSense vereinfachen den Prozess bei der Erstellung von Visualisierungen erheblich, sodass auch unerfahrene Anwender innerhalb kürzester Zeit sinnvolle Darstellungen aus ihren Daten generieren können.
Die Erstellung von Visualisierungen und Layout erfolgt einfach über Drag & Drop. Kennzahlen und Dimensionen können ebenfalls in die Visualisierung gezogen werden. Im Gegensatz zu QlikView stehen nativ moderne Datenvisualisierungsmöglichkeiten zu Verfügung wie beispielsweiße Karten für Geoanalysen.
Jedoch spielen Qlik Experten weiterhin eine wichtige Rolle, da falls es sich nicht um Ad-hoc Analysen handelt eine Anbindung an die bestehende Dateninfrastruktur notwendig ist. Folglich ist es bei großen Datenmengen ebenfalls wichtig, dass das Datenmodell effizient und performant ausgestalltet sind. Um unterschiedliche Berechnung von KPIs zu vermeiden und die Kommunikation von widersprüchlichen Ergebnissen zwischen Abteilungen zu verhindern ist die Bereitstellung von Masterkennzahlen und Dimensionen entscheident. Zusätzlich vermeidet dies die ineffiziente Berechnung von Kennziffern, was bei großen Datenmengen an Bedeutung gewinnt bezüglich Ladezeit. Oft sind komplexere Analysen notwendig, welche nicht durch Drag & Drop möglich sind, sondern Erfahrung und Expertise in weitergehende Funktionen wie Set Analysen erfordert. Nur dadurch kann das volle Potential von Qlik Sense ausgeschöpft werden.
Im Vergleich zu QlikView ist Qlik Sense benutzerfreundlicher gestaltet worden, allerdings schränkt dies die Individualisierungsmöglichkeiten ein, was für erfahrene QlikView-Benutzer beim Umstieg frustrierend sein kann.

Weiterhin gibt es aus der Anwendung von QlikSense heraus noch folgende wichtige Punkte:

  • Vielzahl von möglichen Datenverbindungen und Integration eines Data Marketplace
  • Cloud Option vorhanden
  • vereinfachtes Lizenzmodell
  • kostenfreie Desktop-Version
  • PDF Reporting durch NPrinting
  • verwendet HTML und JavaScript als Grundlage
  • anwenderfreundliche, offene API
  • Vielzahl an kostenlosen und kostenpflichtigen Extensions, allerdings ist hierbei nicht immer eine Kompatibilität mit der nächsten Qlik Sense Version gewährleistet
  • responsive, dadurch geeignet für mobile Devices und touch-freundliche Bedienung

Fazit

Schaut man sich die letzten Updates von QlikView und Qlik Sense an, erkennt man deutlich, dass der Fokus von QlikTech auf Qlik Sense liegt. QlikView soll weiterhin mit Updates versorgt werden, allerdings wird es vorrausichtlich keine neuen Features geben, diese bleiben Qlik Sense vorbehalten. Lange Zeit war der grundlegende Funktionsumfang gleich und die größten Unterschiede gab es hinsichtlich Bedienung und Visualisierungsmöglichkeiten. Allerdings gibt es beispielsweise die neuen In… Funktionen, die Year-to-date Berechnungen vereinfachen, nur in Qlik Sense.

Die Entwicklung von komplexeren Dashboards gestaltet sich unter Qlik Sense im Vergleich zu QlikView als schwieriger. Hier spielt QlikView eindeutig seine Stärke aus. Allerdings erweitern sich die Möglichkeiten diesbezüglich für Qlik Sense mit jedem Update. In den neusten Versionen wurden Optionen integriert um die Grid-Size anzupassen, die Seitenlänge zu erweitern und Scrollen zu ermöglichen oder ein Dashboard mit CI-konformen Farben zu gestalten mittels Custom-Themes. Außerdem bietet Qlik Sense für komplexe Dashboards die Möglichkeit Mashups zu erstellen. Hierbei handelt es sich um Webseiten oder Applikationen in die Qlik Sense Objekte integriert werden. Die Lücke zu QlikView hinsichtlich der Erstellung von Dashboards verkleinert sich somit stetig.

Beide Lösungen werden noch für längere Zeit ihre Daseinsberechtigung haben. Daher wird es für manche Unternehmen weiterhin Sinn machen QlikView und Qlik Sense parallel zu Betreiben und sich anwendungsbezogen für ein Produkt zu entscheiden.

Infografik Markus Berroth

„All code is guilty until proven innocent.“

Testing ist ein wichtiger Teil in der Entwicklung von stabilem R Code. Testing stellt sicher, dass der Code wie beabsichtigt funktioniert. Allerdings ist dies ein zusätzlicher Schritt im bisherigen Workflow. Oft besteht der übliche „Testing“-Workflow in R darin, nach dem Schreiben einer neuen Funktion, diese zuerst informell in der Konsole zu testen und zu schauen, ob der Code wie angestrebt funktioniert. Dieser Beitrag soll aufzeigen, wie man mit Hilfe des testthat-Packages strukturierte Unit-Tests schreibt.

Motivation für Unit Testing

  • Geringere Anzahl an Bugs: Dadurch, dass das Verhalten des Codes an zwei Stellen festgehalten wird – einmal im Code selbst und einmal in den Tests – kann man sicherstellen, dass er wie beabsichtigt funktioniert und dadurch im besten Fall keine Fehler im Code sind. Inbesondere kann es nützlich sein, im Falle eines behobenen Bugs im Anschluss einen entsprechenden Test zu schreiben, welcher den Bug identifiziert hätte. Dies stellt sicher, dass wenn man sich nach einiger Zeit dem Code widmet, nicht einen alten Fehler erneut hinzufügt.
  • Bessere Code-Struktur: Für sinnvolle Tests ist es wichtig, dass der Code übersichtlich gestaltet ist. Damit dieser gut getestet werden kann, ist es hilfreich, anstelle von einer komplexen, verschachtelten Funktion, den Code in mehrere, simplere Funktionen aufzuteilen. Hierdurch wird die Fehleranfälligkeit zusätzlich verringert.
  • Robuster Code: Da die komplette Funktionalität des Codes schon einmal überprüft worden ist, kann man einfacher größere Änderungen am Code vornehmen ohne in diesen (unabsichtlich) Fehler einzubauen. Dies ist vor allem hilfreich, wenn man zu einem späteren Zeitpunkt denkt, dass es einen effizienteren Weg gibt, um dies zu bewerkstelligen, aber einen zuvor berücksichtigten Randfall vergisst.

Package: testthat

Ein fantastisches Paket in R für Unit-Testing ist das testthat-Paket von Hadley Wickham. Kennt man sich mit Testing aus anderen Programmiersprachen aus, wird man feststellen, dass es einige signifikante Unterschiede gibt. Dies liegt zum größten Teil daran, dass es sich bei R stärker um eine funktionale Programmiersprache handelt, als um eine objektorientierte Programmiersprache. Daher macht es wenig Sinn, Tests um Objekte und Methoden zu bauen, anstatt um Funktionen.
Eine Alternative zu testthat ist das RUnit-Paket, wobei einer der Vorteile von testthat ist, dass es aktiv weiterentwickelt wird.
Der generelle Testaufbau mit testthat ist, dass mehrere zusammenhängende Expectations in ein test_that-Statement zusammengefügt werden, welches wiederrum in ein context-File gruppiert wird.

Workflow mit Beispielen

Im Folgenden werden wir die Funktion im Skript quadratic_function.R testen, welche die Nullstellen einer quadratischen Gleichung berechnet. Dieses Skript, sowie die Tests sind ebenfalls auf unserer Github-Seite zu finden.

quadratic_equation <- function(a, b, c)
{
  if (a == 0)
    stop("Leading term cannot be zero")
  # Calculate determinant
  d <- b * b - 4 * a * c
  
  # Calculate real roots
  if (d < 0)
    rr <- c()
  else if (d == 0)
    rr <- c(-b / (2 * a))
  else
    rr <- c((-b - sqrt(d)) / (2 * a), 
            (-b + sqrt(d)) / (2 * a))
  
  return(rr)
}

Zu Beginn eines Test-Skriptes wird das jeweilige R-Skript, welches die zu testenden Funktionen enthält, geladen.

source("quadratic_function.R")

Der Name des Test-Skripts muss mit test beginnen und ist strukturell das höchste Element im Testing. Jedes File sollte einen context()Aufruf beinhalten, welches eine kurze Beschreibung über den Inhalt zur Verfügung stellt. Hierbei sollte man beachten, dass man ein gesundes Mittelmaß für den Umfang eines jeden Files findet. Es ist schlecht, wenn die gesamten Tests für ein komplexes Paket sich in einem File befinden, aber gleichermaßen, wenn jeder Test sein eigenes File hat. Oft ist es eine gute Idee, dass jede komplexe Funktion sein eigenes File besitzt.

Expectations

Expectations stellen das kleinste Element dar und beschreiben, was das erwartete Ergebnis einer Berechnung ist, beispielsweise die richtige Klasse oder Wert. Expectations sind Funktionen, die mit expect_ beginnen.

# Expectations
calculated_root <- quadratic_equation(1, 7, 10)

expect_is(calculated_root, "numeric")
expect_length(calculated_root, 2)
expect_lt(calculated_root[1], calculated_root[2])

Es gibt verschiedene, vordefinierte expect_-Funktionen, welche unterschiedliche Bedingungen überprüfen. Beispielsweise überprüft die erste Expectation ob der zurückgegebenen Werte numerisch ist, die zweite ob zwei Wurzeln zurückgegeben werden und die dritte, ob die erste berechnete Wurzel kleiner als die zweite ist. Es besteht ebenfalls die Möglichkeit, mit expect() eigene Expectations zu schreiben, falls eine Expectation häufiger verwendet wird oder mit expect_true() einfache True/Falls Bedingungen zu überprüfen. Letzteres sollte allerdings nur verwendet werden, falls es keine vordefinierte Expectation gibt, da diese eine bessere Fehlerbeschreibung beinhalten als expect_true(). Außerdem sollte beachtet werden, dass expect_that() veraltet ist und nicht mehr benutzt werden sollte.

Tests

Ein Test verbindet mehrere Expectation um beispielsweiße die Ausgabe einer simplen Funktion, eine Reihe von möglichen Eingabewerten einer komplexeren Funktion oder stark verbundene Funktionalitäten von mehreren unterschiedlichen Funktionen zu testen. Daher wird diese Art von Tests Unit-Tests genannt, da sie eine Einheit (Unit) der Funktionalität überprüfen.
Ein neuer Test wird mit Hilfe der test_that()-Funktion kreiert und beinhaltet den Testname sowie einen Codeblock. Hierbei sollte der Testname den Satz „Test that…“ beenden. Außerdem sollte die Beschreibung informativ gehalten werden, damit ein möglichen Fehler zügig gefunden werden kann auch für den Fall, dass man längere Zeit nicht damit gearbeitet hat. Zusätzlich sollte ein Test einen nicht zu großen Bereich an Expectations abdecken, was eine schnelle Lokalisierung des Fehlers ermöglicht.

# Beispiel test_that
calculated_root <- quadratic_equation(1, 7, 10)

expect_is(calculated_root, "numeric")
expect_length(calculated_root, 2)
expect_lt( calculated_root[1], calculated_root[2])

In diesem Beispiel haben wir die obigen Expectations in einen neuen Test zusammengefast, welcher testet, ob die Funktion distinkte Werte zurückgibt.

Einzelne Testfiles können mit test_file() aufgerufen werden und mehrere Tests, deren Dateinamen mit test_ beginnen und sich im gleichen Ordner befinden mit test_dir(). Dies ermöglicht im Gegensatz zu einem einfachen source(), dass die weiteren Tests ebenfalls durchlaufen, falls ein vorheriger abbricht.
Bevor wir die Tests einmal durchlaufen lassen, habe ich noch weitere hinzugefügt, unter anderem einen Test welcher absichtlich nicht passiert. Dieser testet ob die Funktion eine Warnung ausgibt, falls a = 0 gesetzt wurde. Allerdings wurde in der Funktion zuvor eine error-Ausnahme definiert statt einer warning. Über die Handhabung von Ausnahmen dreht sich auch mein vorheriger Blogbeitrag.
Die Ausgabe für die Tests sieht folgendermaßen aus:

unit test output

Ein grüner Punkt bedeutet, dass der jeweilige Test erfolgreich bestanden wurde. Eine rote Zahl hingegen bedeutet, dass der Test nicht bestanden wurde. Darunter befinden sich zusätzliche Informationen, wie beispielweise welcher Test nicht bestanden wurde. Daher ist es wichtig, diese eindeutig zu benennen und nicht zu umfangreich zu gestalten.

good code or shity unit test

Was sollte getestet werden?

Es ist schwer, die richtige Balance zu finden, wenn es um das Schreiben von Tests geht. Auf der einen Seite verhindern diese, dass man unbeabsichtigt etwas im Code verändert, jedoch müssen bei einer gewollten Änderung ebenfalls alle betroffenen Tests angepasst werden.
Einige hilfreiche Punkte hierfür sind:

  • Teste lieber das äußere Interface einer Funktion, als den inneren Code, da dadurch die Flexibilität beibehalten wird, diesen später zu ändern.
  • Schreibe für jede Funktionalität deiner Funktionen nur einen einzelnen Test. Dadurch muss später, falls sich diese verändert, nur ein Test angepasst werden.
  • Konzentriere dich darauf, komplizierten Code mit vielen Abhängigkeiten zu testen und Randfälle statt dem Offensichtlichen. Jeder hat wahrscheinlich schon einmal mehrere Stunden mit Debugging verbracht, nur um einen simplen Fehler zu finden.
  • Schreibe nach jedem erfolgreich gefixten Bug einen Test.

Markus Berroth

Der vorherige Teil der Reihe drehte sich um die Handhabung von unerwarteten Fehlern und Bugs. Doch manchmal erwartet man das Auftreten von Fehlern, beispielsweiße falls man das gleiche Modell für mehrere Datensätze anwenden möchte. Dabei kann unter anderem der Fehler auftreten, dass das Modell aufgrund von fehlender Varianz nicht geschätzt werden kann. In diesem Fall möchte man nicht, dass durch diesen einen Fehler die komplette Schätzung abbricht, sondern, dass mit der nächsten Schätzung fortgefahren wird.

In R gibt es drei unterschiedliche Methoden um dies zu erreichen:

  • try() ignoriert den Fehler und führt die Berechnung fort.
  • trycatch() lässt eine zusätzlich Fehlermeldung/Aktion zuweisen.
  • withCallingHandlers() ist eine besondere Variante von trycatch() , welches die Ausnahme lokal handhabt. Es wird nur selten benötigt, daher fokussieren wir uns auf die beiden erstgenannten.

try

Mit try() wird der Code weiterhin ausgeführt unbeachtet von auftretenden Fehlern.
Im folgenden Beispiel tritt ein Fehler auf, wodurch der Prozess abgebrochen wird und es wird kein Wert zurückgegeben.

Bespiel Fehlermeldung

Fügen wir die fehlererzeugende Funktion in ein try() ein, wird die Fehlermeldung weiterhin angezeigt, jedoch wird der restliche Code ausgeführt und wir bekommen weiterhin einen Return.

Beispiel try

Aktivieren wir die Option silent = TRUE innerhalb der try()-Funktion, wird nicht einmal mehr die Fehlermeldung angezeigt.

Code-Blöcke werden innerhalb von try() in der geschweiften Klammer {} zusammengefasst.

Beispiel Fehlermeldung try

Zusätzlich ist es möglich die Klasse von try() abzufragen. Taucht kein Fehler auf, ist es die Klasse des letzten Returns, taucht jedoch einer auf, ist es eine eigene „try-error”-Klasse.

Bespiel Klasse von try

Dadurch kann im Nachhinein überprüft werden, ob die Funktion erfolgreich ausgeführt wurde, dies ist besonders hilfreich, wenn man eine Funktion auf mehrere Objekte anwendet.

trycatch

Im Unterschied zu try() können mit trycatch() nicht nur Fehler gehandhabt werden, sondern auch Warnungen, Messages und Abbrüche. Eine weitere Besonderheit ist, dass je nach auftretender Ausnahme unterschiedliche Funktion aufgerufen werden können. In der Regel werden hierbei Default-Werte übergeben oder bedeutsamere Meldungen erzeugt. Im folgenden Beispiel wollen wir die Funktion über einen Vektor mit den Ausprägungen Vektor = data.frame(4, 2, -3, 10, "hallo") loopen. Wobei die log-Funktion bei negativen Werten eine Warnung ausgibt und für Factors und Strings einen Fehler.

Beispiel trycatch

Das letzte, wichtige Argument von trycatch() ist finally. Die dort angegebene Funktion wird als letztes ausgeführt, ungeachtet ob der vorherige Code erfolgreich durchlief oder abgebrochen ist. Dies ist nützlich um nicht mehr benötigte Objekte zu löschen oder Verbindungen zu schließen.

Im nächsten Artikel der Reihe dreht sich alles um Unit-Testing.

Referenzen

  1. Advanced R by Hadley Wickham

Markus Berroth

Nachdem der erste Teil die unterschiedlichen Aktivierungsmöglichkeiten für den Debugger behandelt hat, dreht sich der zweite Teil um effizientes Debugging.

„Debugging is like being the detective in a crime movie where you are also the murderer.”(1)

… und manchmal erinnert man sich nicht mal die Tat begangen zu haben.

Im Folgenden werden wir die Debugging-Features von RStudio an einem einfachen Beispiel behandeln. Die folgenden Funktionen sollen die Buchstaben der einzelnen Wörter umdrehen, aber nicht den kompletten Satz.

# drehe ein Wort um
stringrev <- function(str) {
  vec <- strsplit(str, "")
  vec <- rev(unlist(vec))
  paste(vec, collapse = "")
}

# trenne einen Satz in einzelne Woerter
crazify <- function(str) {
  vec <- strsplit(str, " ")
  vec <- lapply(unlist(vec), stringrev)
  paste(vec, collapse = " ")
}

Für einzelne Sätze funktioniert der Code problemlos. Darauf aufbauend möchten wir die Funktion auf einen Abschnitt eines anderen Blogbeitrags anwenden, welcher in einem Data Frame gespeichert wurde.

test_it <- function() {
  sentences <- data.frame(
  titles = c("first", "second"),
  text = c("Bokaj, der beste Ingenieur im nicht-parametrischen
            Universum und glücklicherweise Leiter unseres
            Maschinenraums, hat eine Idee!",
           "Wir demontieren von einem der anderen Schiffe den
            Antrieb und verstärken damit unseren."))
  sentences$text <- vapply(sentences$text, crazify, "character")
  return(sentences)
}

Doch hierbei taucht diese Fehlermeldung auf:
Error in strsplit(str, " ") : non-character argument
Um dieser Fehlermeldung auf den Grund zu gehen aktivieren wir die Option „Debugging on Error“, wie im vorherigen Blog-Post erklärt.

Zuallererst fällt einem auf, dass sich das Aussehen von RStudio verändert, sobald man sich im Debugging-Modus befindet.

Environment-Pane

Debug Environment Pane

Normalerweise werden im Environment-Pane die globalen Objekte angezeigt. Befindet man sich jedoch im Debugging-Modus, werden stattdessen die Objekte im Environment der jeweiligen Funktion angezeigt. Über der Auflistung der Objekte befindet sich ein Dropdown-Menü, welches die Möglichkeit bietet zwischen den unterschiedlichen Environments zu wechseln. Sind Variablen ausgegraut, bedeutet dies, dass es sich um Objekte handelt, die momentan noch nicht im Environment sind, jedoch zukünftig der Funktion zur Verfügung stehen werden.

Traceback

Debug Traceback

Im Traceback befinden sich alle aufgerufenen Funktionen, um zur aktuellen, fehlerverursachenden Funktion zu gelangen. Über dieses Fenster kann ebenfalls zwischen den vorherigen Funktionsaufrufen gewechselt werden, dadurch wechselt ebenfalls der angezeigte Code und die im Environment angezeigten Objekte. Es ist jedoch zu beachten, dass dies nicht das aktuelle Environment verändert. Unten in der Liste befindet sich die erste aufgerufene Funktion, welche in unserem Fall test_it() ist, und geht weiter bis zu crazify(), welche die R-Funktion strsplit() aufruft.

Konsole

Debug Konsole

Im Debugging-Modus gibt es zwei prominente Veränderungen der Konsole. Zum einen steht in der Eingabe nun

Browse[1]>

Dies zeigt an, dass man sich im Environment-Browser von R befindet. Größtenteils verhält sich die Konsole im Debugging-Modus wie die normale Konsole, mit ein paar Ausnahmen:

  1. Objekte werden anhand des aktuellen Environments evaluiert. Beispielsweiße wird das Text-Data Frame ausgegeben, wenn man x eingibt, es ist ebenfalls möglich x einen neuen Wert zuzuweisen.
  2. Mit der Eingabe-Taste wird das aktuelle Statement ausgeführt und man springt zum nächsten Statement. Dadurch lässt sich der Code bequem Schritt für Schritt durchgehen.
  3. Es stehen eine Vielzahl von Debugging-Funktionen zu Verfügung auf die Später genauer eingegangen wird.

Die zweite große Veränderung innerhalb der Konsole ist die Funktionsleiste, welche sich über der Konsole befindet. Diese bietet praktische Schalter, um spezielle Debugging-Funktionen direkt an die R-Konsole zu senden. Es gibt hierbei keinen Unterschied, ob man die Befehle in die R-Konsole eingibt oder die Schalter verwendet. Die Schalter bedeuten von links nach rechts mit den entsprechenden Konsolen-Befehlen:

Befehl Funktion
n Ausführen des nächsten Statements
s In den Funktionsaufruf springen
f Funktion/Schleife beenden
c Bis zum nächsten Breakpoint fortfahren
Q Debugging beenden

Um das aktuelle Environment zu wechseln, verwendet man die Funktion recover(). Dies listet die Environments der vorausgegangenen Funktionen auf. Aus dieser Liste kann das passende Environment gewählt werden.

Fehlerfindung

Doch wo liegt der Fehler in unserem Beispiel? Im Environment-Pane sieht man, dass es sich bei x, welches in die strsplit()-Funktion übergeben wird, um einen Faktor und nicht um einen Character handelt. Dies liegt daran, dass die Standardeinstellung bei data.frame stringsAsFactors = default.stringsAsFactors() ist. Fügen wir in unserem Ausgangscodeblock der test_it-Funktion stringsAsFactors = FALSE hinzu, sehen wir, dass die Funktion durchläuft und unser gewünschtes Ergebnis ausgibt.

Ergebnis vom Buchstabendrehen

Referenzen

  1. https://twitter.com/fortes/status/399339918213652480
  2. Introduction to Debugging in R
  3. https://github.com/rstudio/webinars/tree/master/15-RStudio-essentials/2-Debugging

Markus Berroth

In der Blog-Reihe „Fehlerbehandlung in R“ geht es um effizientes und systematisches Überprüfen von R-Code. Den Beginn macht das Finden von Fehlern durch Debugging, weiter geht es mit der Handhabung von Fehlern und endet mit Unit-Testing, das zum Überprüfen von korrekter Funktionalität von R-Code dient.
Die Reihe startet mit Debugging in R, wobei Debugging ein breitgefächertes Thema ist. Dieser Artikel fokussiert sich daher auf die Möglichkeiten, die RStudio bietet.

Debugging hilft dabei herauszufinden an welcher Stelle im Code sich ein Fehler befindet oder an welcher Stelle der Code sich anders verhält als erwartet. Dies beinhaltet im Generellen drei Schritte:

  • den Code laufen lassen
  • Code an der Stelle stoppen an welcher vermutet wird, dass sich dort der Fehler befindet
  • Schritt für Schritt durch den Code gehen und diesen dabei überprüfen.

In den Debugging-Modus eintreten

Um in den Debugging-Modus zu kommen, muss RStudio mitgeteilt werden, wann es die Berechnungen stoppen soll. Es gibt keinen „Pause-Button“ mit welchem man eine laufende Berechnung stoppen kann um in den Debugging-Modus einzutreten. Dies ist in so gut wie allen anderen Programmiersprachen der Fall, da die Berechnungen in der Regel zu schnell von statten gehen als dass es möglich wäre an der richtigen Stelle zu stoppen. Stattdessen bestimmt man zuvor an welcher Stelle der Code angehalten werden soll. Dies ist nicht zu verwechseln mit dem „Stop“-Button über der der Konsole, welcher die Berechnung komplett abbricht.

Vor einer Zeile stoppen

Der einfachste und meist genutzte Weg um in den Debugging-Modus zu gelangen, ist es einen Breakpoint im Code-Editor zu setzen. Dies kann auf einfache Weise in RStudio gemacht werden indem man links neben die Zeilennummer klickt oder durch das Drücken von Shift+F9 auf der Tastatur und zeitgleiches Klicken mit der Maus in der gewünschten Zeile.

aktivierter Breakpoint in Rstudio

Hierbei wird eine Tracing-Funktion innerhalb der eigentlichen Funktion eingefügt. Der Breakpoint wird durch einen ausgefüllten roten Kreis im Editor gekennzeichnet. Außerdem kann man einen schnellen Überblick erlangen in welcher Funktion sich ein Breakpoint befindet, indem man in das Environment-Fenster schaut, welche Funktion ebenfalls durch einen roten Kreis gekennzeichnet wird.

Falls die Funktion noch nicht existiert, zu einem weil man das File noch nicht gesourced hat oder zu anderem weil sich die Funktion im Environment und im Editor unterscheiden, kann der Breakpoint noch nicht aktiviert werden. Dies wird durch einen nicht-ausgefüllten Kreis kenntlich gemacht.

nicht aktivierter Breakpoint in RStudio

In der Regel hilft es das File einmal zu sourcen, wodurch die Tracking-Funktion eingefügt wird und der Breakpoint startbereit ist.

Setzt man den Breakpoint per RStudio-Editor ist es nicht notwendig die Funktion zu bearbeiten und zusätzlichen Code per Hand einzufügen. Allerdings gibt es bestimmte Situationen in denen diese Breakpoints nicht funktionieren wie beispielsweise komplexere Funktionssyntaxen. Außerdem wird konditionelles Debugging bisher nicht von RStudio unterstützt. Hier schafft die browser()-Funktion Abhilfe. Da es sich hierbei um eine tatsächliche Funktion handelt, muss sie in den Code geschrieben werden, kann aber an so gut wie jeder Stelle hinzugefügt werden. Sind sie erstmal aktiv und aufgerufen verhalten sich die Editor-Breakpoins und browser() sehr ähnlich.

Stoppen bevor eine Funktion ausgeführt wird

Der Editor-Breakpoint oder browser() eigenen sich optimal für Funktionen für welche der Source-Code vorliegt. Hat man jedoch nicht das .R File zur Hand kann alternativ eine ganze Funktion mit debug() bzw. debugonce() gedebugged werden. Hierfür wird die jeweilige Funktion innerhalb von debug() bzw. debugonce() geschrieben , beispielsweise debugonce(mean) . Dies ändert nicht die Funktion an sich, aber es startet den Debugger direkt nach dem Funktionsaufruf, man kann es sich vorstellen als würde man einen Breakpoint direkt zu Beginn der Funktion setzen würde. debugonce() aktiviert den Debug-Modus nur ein einziges Mal für die jeweilige Funktion zum nächstmöglichen Zeitpunkt zu welchem diese aufgerufen wird. debug() hingegen aktiviert jedes Mal den Debugger, wenn die Funktion aufgerufen wird, was schlimmsten Falls in einer endlosen Schleife resultieren kann. Daher ist es in der Regel zu empfehlen debugonce() zu benutzen. Das Gegenstück zu debug() ist undebug(), welches benutzt wird wenn man nicht mehr jedes Mal die Funktion bei Aufruf debuggen möchte.

Bei Fehler stoppen

Die dritte Möglichkeit in den Debugging-Modus zu gelangen ist die Einstellung, dass der Debugger jedes Mal automatisch aktiviert wird, wenn ein Fehler auftaucht. Dadurch stoppt die Funktion automatisch und der Debugging-Modus startet direkt von selbst. Diese Funktionalität wird über die RStudio Oberfläche aktiviert in dem man Debug -> On Error von „Error Inspector“ zu „Break in Code“ ändert.

Debug on Error in RStudio

Allerdings wird der Debugger per Default nur aktiviert, wenn ein Fehler im eigenen Code auftaucht. Falls man einen Fehler finden möchte, welcher ebenfalls Code von Dritten beinhaltet, kann diese Einstellung unter Tools -> Global Options und dem abwählen von „Use debug error handler only when my code contains errors“ abgeändert werden. Alternativ kann die Option dauerhaft mit options(error = browser()) überschrieben werden. Es kann jedoch schnell störend werden, dass jedes Mal der Debugger aktiviert wird. Daher sollte nicht vergessen werden diese Option wieder mit options(error = NULL) rückgängig zu machen sobald das Debugging beendet ist.

Der nächste Teil der Reihe „Fehlerbehandlung in R“ dreht sich um effektives Debugging in R nachdem der Debugger aktiviert wurde. Markus Berroth

In der Blog-Reihe „Fehlerbehandlung in R“ geht es um effizientes und systematisches Überprüfen von R-Code. Den Beginn macht das Finden von Fehlern durch Debugging, weiter geht es mit der Handhabung von Fehlern und endet mit Unit-Testing, das zum Überprüfen von korrekter Funktionalität von R-Code dient.
Die Reihe startet mit Debugging in R, wobei Debugging ein breitgefächertes Thema ist. Dieser Artikel fokussiert sich daher auf die Möglichkeiten, die RStudio bietet.

Debugging hilft dabei herauszufinden an welcher Stelle im Code sich ein Fehler befindet oder an welcher Stelle der Code sich anders verhält als erwartet. Dies beinhaltet im Generellen drei Schritte:

In den Debugging-Modus eintreten

Um in den Debugging-Modus zu kommen, muss RStudio mitgeteilt werden, wann es die Berechnungen stoppen soll. Es gibt keinen „Pause-Button“ mit welchem man eine laufende Berechnung stoppen kann um in den Debugging-Modus einzutreten. Dies ist in so gut wie allen anderen Programmiersprachen der Fall, da die Berechnungen in der Regel zu schnell von statten gehen als dass es möglich wäre an der richtigen Stelle zu stoppen. Stattdessen bestimmt man zuvor an welcher Stelle der Code angehalten werden soll. Dies ist nicht zu verwechseln mit dem „Stop“-Button über der der Konsole, welcher die Berechnung komplett abbricht.

Vor einer Zeile stoppen

Der einfachste und meist genutzte Weg um in den Debugging-Modus zu gelangen, ist es einen Breakpoint im Code-Editor zu setzen. Dies kann auf einfache Weise in RStudio gemacht werden indem man links neben die Zeilennummer klickt oder durch das Drücken von Shift+F9 auf der Tastatur und zeitgleiches Klicken mit der Maus in der gewünschten Zeile.

aktivierter Breakpoint in Rstudio

Hierbei wird eine Tracing-Funktion innerhalb der eigentlichen Funktion eingefügt. Der Breakpoint wird durch einen ausgefüllten roten Kreis im Editor gekennzeichnet. Außerdem kann man einen schnellen Überblick erlangen in welcher Funktion sich ein Breakpoint befindet, indem man in das Environment-Fenster schaut, welche Funktion ebenfalls durch einen roten Kreis gekennzeichnet wird.

Falls die Funktion noch nicht existiert, zu einem weil man das File noch nicht gesourced hat oder zu anderem weil sich die Funktion im Environment und im Editor unterscheiden, kann der Breakpoint noch nicht aktiviert werden. Dies wird durch einen nicht-ausgefüllten Kreis kenntlich gemacht.

nicht aktivierter Breakpoint in RStudio

In der Regel hilft es das File einmal zu sourcen, wodurch die Tracking-Funktion eingefügt wird und der Breakpoint startbereit ist.

Setzt man den Breakpoint per RStudio-Editor ist es nicht notwendig die Funktion zu bearbeiten und zusätzlichen Code per Hand einzufügen. Allerdings gibt es bestimmte Situationen in denen diese Breakpoints nicht funktionieren wie beispielsweise komplexere Funktionssyntaxen. Außerdem wird konditionelles Debugging bisher nicht von RStudio unterstützt. Hier schafft die browser()-Funktion Abhilfe. Da es sich hierbei um eine tatsächliche Funktion handelt, muss sie in den Code geschrieben werden, kann aber an so gut wie jeder Stelle hinzugefügt werden. Sind sie erstmal aktiv und aufgerufen verhalten sich die Editor-Breakpoins und browser() sehr ähnlich.

Stoppen bevor eine Funktion ausgeführt wird

Der Editor-Breakpoint oder browser() eigenen sich optimal für Funktionen für welche der Source-Code vorliegt. Hat man jedoch nicht das .R File zur Hand kann alternativ eine ganze Funktion mit debug() bzw. debugonce() gedebugged werden. Hierfür wird die jeweilige Funktion innerhalb von debug() bzw. debugonce() geschrieben , beispielsweise debugonce(mean) . Dies ändert nicht die Funktion an sich, aber es startet den Debugger direkt nach dem Funktionsaufruf, man kann es sich vorstellen als würde man einen Breakpoint direkt zu Beginn der Funktion setzen würde. debugonce() aktiviert den Debug-Modus nur ein einziges Mal für die jeweilige Funktion zum nächstmöglichen Zeitpunkt zu welchem diese aufgerufen wird. debug() hingegen aktiviert jedes Mal den Debugger, wenn die Funktion aufgerufen wird, was schlimmsten Falls in einer endlosen Schleife resultieren kann. Daher ist es in der Regel zu empfehlen debugonce() zu benutzen. Das Gegenstück zu debug() ist undebug(), welches benutzt wird wenn man nicht mehr jedes Mal die Funktion bei Aufruf debuggen möchte.

Bei Fehler stoppen

Die dritte Möglichkeit in den Debugging-Modus zu gelangen ist die Einstellung, dass der Debugger jedes Mal automatisch aktiviert wird, wenn ein Fehler auftaucht. Dadurch stoppt die Funktion automatisch und der Debugging-Modus startet direkt von selbst. Diese Funktionalität wird über die RStudio Oberfläche aktiviert in dem man Debug -> On Error von „Error Inspector“ zu „Break in Code“ ändert.

Debug on Error in RStudio

Allerdings wird der Debugger per Default nur aktiviert, wenn ein Fehler im eigenen Code auftaucht. Falls man einen Fehler finden möchte, welcher ebenfalls Code von Dritten beinhaltet, kann diese Einstellung unter Tools -> Global Options und dem abwählen von „Use debug error handler only when my code contains errors“ abgeändert werden. Alternativ kann die Option dauerhaft mit options(error = browser()) überschrieben werden. Es kann jedoch schnell störend werden, dass jedes Mal der Debugger aktiviert wird. Daher sollte nicht vergessen werden diese Option wieder mit options(error = NULL) rückgängig zu machen sobald das Debugging beendet ist.

Der nächste Teil der Reihe „Fehlerbehandlung in R“ dreht sich um effektives Debugging in R nachdem der Debugger aktiviert wurde. Markus Berroth