Aktuelle Machine-Learning-Methoden, Dashboards, deskriptive Analysen, T-Tests und Clusteranalysen bis hin zu unterschiedlichen Regressionsmethoden oder interaktiven Grafiken – das alles und noch viel mehr bietet die Statistik-Programmiersprache R. Eine Einführung.
Lohnt es, sich mit der speziell für die Statistik entwickelten Sprache R auseinanderzusetzen? Und für wen zahlt sich das aus? Das wollen die folgenden Seiten klären, die einen leicht fasslichen Schnupperkurs in R bieten.
R beruht auf der 1969 an den Bell Laboratories entwickelten Programmiersprache S, die sich Anfang der Neunzigerjahre in eine kommerzielle Version namens S-Plus und ein GNU-Projekt – R – aufspaltete. Der zugegebenermaßen gewöhnungsbedürftige Name lässt sich sowohl auf die Vornamen der Entwickler, Ross Ihaka und Robert Gentleman, zurückführen, als auch als Anspielung auf das Vorgängerprojekt verstehen.
Im Zusammenhang mit R ist oft auch die Rede von einer Programmierumgebung, das soll das offene Paketkonzept betonen und andeuten, dass sich R als Gegenstück zu gängiger monolithischer Statistiksoftware sieht. Die Grundfunktionen stellen acht im R-Sourcecode mitgelieferte Pakete bereit. Darüber hinaus offerieren viele Tausend zusätzliche Pakete Erweiterungen.
R führte in seinen ersten Jahren eher ein Nischendasein und wurde hauptsächlich von Statistikern und Biometrikern an den Universitäten genutzt. Mittlerweile hat es sich aber mit dem immer stärkeren Einzug der Wissenschaft in viele Unternehmen während des Data-Science-Feldzugs auch einen festen Platz in der Unternehmenswelt erkämpft.
Die R-Syntax
Den Einstieg in R erleichtert ein Überblick über die wichtigsten Eigenschaften seiner Syntax. Die R-Syntax ist von Expressions geprägt. Sie unterscheidet zwischen Groß- und Kleinschreibung: Ein Objekt, das jemand »modelFit« genannt hat, lässt sich beispielsweise nicht über »modelfit« aufrufen. Zur Erstellung eines Objekts dient der so genannte Assignment Operator »<-« . Er zeigt auf das Objekt, das die Inhalte eines Ausdrucks zugewiesen bekommt.
Um im einfachsten Fall dem Vektor namens »numbers« die Zahlen von 1 bis 5 zuzuweisen, dient folgender Ausdruck:
> numbers <- c(1, 2, 3, 4, 5)
Die Funktion »c()« – das »c« steht für “concatenate” (verknüpfen) – fasst die einzelnen, in der Klammer aufgeführten Elemente zusammen. Für die Zuweisung ist als Alternative ein Gleichheitszeichen verwendbar, was dem Standard in anderen Programmiersprachen entspricht. Dies ist in der R-Community jedoch umstritten. Der Assignment Operator und das Gleichheitszeichen sind außerdem keine vollwertigen Äquivalente, denn Letzteres lässt sich nur auf Top-Level-Ebene verwenden.
Der letzte im Bunde der Zuweisungsoperatoren ist »<<-«. Er stellt eine Erweiterung des Assignment-Operators dar und heißt auch Superassignment Operator. Mit seiner Hilfe lassen sich Werte innerhalb von Funktionen im Global Environment zuweisen oder dort bereits definierte Variablen überschreiben.
Eine Besonderheit im Vergleich zu den gängigen Programmiersprachen ergibt sich bei der Indizierung. Sie beginnt nämlich bei 1 und nicht wie sonst oft bei 0. Um also das erste Element des Vektors »numbers« zu erhalten, schreibt man: »numbers[1]«. Dies gibt sowohl das Element an der ersten Stelle von »numbers« als auch explizit den Index dieses Elements (»[1]«) zurück.
Wie sich an den Codebeispielen erkennen lässt, wird kein Semikolon oder Ähnliches genutzt, um einen Ausdruck abzuschließen. R verwendet für diesen Zweck Zeilenumbrüche.
Der Interpreter denkt aber durchaus mit: Sofern das Ende einer Zeile den Ausdruck offensichtlich nicht abschließt, weil beispielsweise eine Klammer fehlt oder der Ausdruck mit einem Komma endet, geht der Interpreter davon aus, dass der Ausdruck in der nächsten Zeile noch fortzuführen ist:
> rep(numbers, +
In allen Fällen, in denen ein Ausdruck nicht abgeschlossen ist, antwortet der Interpreter mit einem Pluszeichen und fordert damit den Anwender auf, den bislang eingegebenen Code zu vervollständigen:
> rep(numbers, + each = 3) [1] 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5
Kommentare im Code macht ein Hashtag kenntlich:
# Das ist ein Kommentar
> names <- c("Anna", "Rudolf", "Edith", "Jason", "Maria")
Datenstrukturen in R
Die wichtigsten Datenstrukturen in R sind Vektoren, Matrizen, Listen und Data Frames. Vektoren sind eindimensionale Datenstrukturen und der kleinstmögliche Baustein, denn in R gibt es keine Skalare. Aus diesem Grund werden sie auch als atomare Vektoren bezeichnet. Ein Objekt, das beispielsweise nur einen String oder eine Zahl enthält, behandelt R als Vektor der Länge 1. Es gibt insgesamt fünf verschiedene Vektortypen: »logical«, »integer«, »double [numeric]«, »character« und »complex«.
Vektoren haben in R die Eigenheit, dass ihre Elemente alle den gleichen Typ aufweisen müssen. Wer versucht Elemente verschiedenen Typs, zum Beispiel Strings und Zahlen, zu einem Vektor zusammenzufügen, erhält aber keine Fehlermeldung. R überführt stattdessen alle Elemente automatisch in dieselbe Klasse. Das wird Coercion genannt. Das folgende Beispiel versucht einen Vektor aus einem Integer, einem Logical und einem Character zu erzeugen. R wandelt alle Elemente automatisch in Character um:
> misc <- c(43, TRUE, "Hallo") > misc [1] "43" "TRUE" "Hallo" > class(misc) [1] "character"
Data Frames sind tabellenartige Datenstrukturen in R und finden in fast jeder Datenanalyse Verwendung. Jede Spalte enthält einen Vektor, wobei zu beachten ist, dass alle Vektoren die gleiche Länge besitzen. Sie können dafür aber beliebigen Typs sein (Listing 1).
Listing 1
Data Frame
01 > data.frame(numbers, names) 02 numbers names 03 1 1 Anna 04 2 2 Rudolf 05 3 3 Edith 06 4 4 Jason 07 5 5 Maria
Gibt der Anwender dem Vektor »names« noch ein zusätzliches Element und versucht erneut, einen Data Frame aus »numbers« und »names« zu erzeugen, tritt ein Fehler auf. Denn »numbers« hat die Länge 5 und »names« die Länge 6 (Listing 2).
Listing 2
Fehlerhafter Frame
01 > names[6] <- "Henry" 02 > data.frame(numbers, names) 03 Error in data.frame(numbers, names) : 04 Argumente implizieren unterschiedliche Anzahl Zeilen: 5, 6
Listen sind streng genommen auch Vektoren, jedoch handelt es sich um rekursive Vektoren. Alle denkbaren Objekte können Komponenten einer Liste sein, auch Listen selbst. Die Datenstruktur Liste in R ist gut mit einem Dictionary in Python oder einem Struct in C vergleichbar. Listen in Python hingegen ähneln eher einem Vektor in R, mit der Abweichung, dass sie auch unterschiedliche Datentypen enthalten dürfen.
Objektorientiert – möglich, aber nicht nötig
In R kommt man ziemlich weit, ohne überhaupt zu wissen, was eine Klasse ist. Trotzdem ist objektorientiertes Programmieren in R möglich. Es gibt drei verbreitete Systeme der Objektorientierung: S3-Klassen, S4-Klassen und Reference Classes. Die ersten beiden kommen am häufigsten vor, implementieren jedoch ein eher ungewöhnliches Konzept von Klassen: Im Gegensatz zu gängigen Klassen-Systemen gibt es jeweils eine so genannte Generic Function. Die entscheidet auf der Grundlage der Klasse des Objekts, auf das sie angewendet wird, welche Methode aufgerufen wird (Method Dispatch). Das heißt also, eine Funktion kann auf verschiedene Klassen angewendet werden und gibt klassenspezifischen Output zurück.
So gibt beispielsweise die »summary()«-Funktion angewendet auf einen Datensatz einen Überblick über seine Dimension, die enthaltenen Variablen, ihre Klassen und so weiter. Wendet man sie dagegen auf einen numerischen Vektor an, gibt R Minimum, Maximum und Quantile aus. S3- und S4-Klassen sind sich relativ ähnlich, allerdings ist das S4-System im Vergleich zum S3-System stärker formalisiert, und der Method Dispatch kann die Klassen mehrerer Input-Objekte berücksichtigen.
Reference Classes entsprechen eher dem aus anderen Sprachen hinlänglich gewohnten Konzept, das heißt, Methoden gehören zu Klassen und nicht zu Funktionen. Der niedrige Stellenwert von Objektorientierung in R lässt sich unter anderem damit erklären, dass objektorientiertes Programmieren bei der Aufbereitung und Auswertung von Daten in der Regel eher zusätzlichen Aufwand als Nutzen generiert.
Standardbibliothek und Erweiterungen
Grundsätzlich stellt R alle Funktionen durch Pakete bereit. Einige dieser Pakete sind bereits im Sourcecode von R enthalten, sodass sie automatisch installiert und verfügbar sind. Alle anderen Pakete muss der Anwender dagegen selbst installieren und laden, bevor ihre Funktionalität verfügbar ist. Die Standardpakete umfassen neben grundlegenden Methoden bereits Funktionen für die statistische Datenanalyse, die Erstellung von Grafiken und Beispieldatensätze.
Unter anderem gehören die Pakete »base«, »datasets« und »stats« zur Standardbibliothek. Das »base«-Paket stellt – wie der Name schon sagt – wirklich grundlegende Funktionen wie »mean()«, »length()« oder »print()« bereit. Außerdem gibt es das »datasets«-Paket, das kleine Datensätze enthält, mit denen man herumspielen, Code testen und Methoden lernen kann.
Hilfreich ist auch das »stats«-Paket, das alleine ausgewachsene statistische Analysen ermöglicht. Stats stellt zum Beispiel Funktionen wie »lm()« (lineares Modell), »anova()« und »t.test()« bereit.
Quelle für erweiternde Pakete ist hauptsächlich das Comprehensive R Archive Network (CRAN), ein Netzwerk aus gespiegelten Servern, das Entwicklern eine Plattform zur Veröffentlichung ihrer Pakete bietet. Pakete, die auf CRAN verfügbar sind, müssen bestimmten Anforderungen entsprechen. Die betreffen vorrangig die Architektur des Pakets und weniger die Qualität der eigentlichen Inhalte. Neben diesem offiziellen Weg lassen sich Pakete auch über Github oder ähnliche Dienste bereitstellen.
Um ein Paket zu nutzen, ist es zunächst zu installieren:
> install.packages("dplyr")
Damit die Funktionen des Pakets verwendbar sind, muss sie der Programmierer laden:
> library("dplyr")
Das geschieht in der Regel zu Beginn einer Session.
Grafiken mit “graphics” und “ggplot2”
Der Aufwand, um eine optisch ansprechende Grafik zu erstellen, ist in R zwar höher als zum Beispiel in Excel, dafür lässt sich alles bis ins letzte Detail konfigurieren. Der einfachste Weg zu einer Grafik führt über das »graphics«-Paket aus der Core-Distribution. Die enthaltene Funktion »plot()« erzeugt automatisch den passenden Grafiktyp basierend auf dem Input.
Stärkste Konkurrenz zu »graphics« ist das etwas neuere Paket »ggplot2«. Die Syntax ist zunächst gewöhnungsbedürftig, so werden etwa die verschiedenen Elemente wie Daten, Achsen und Legenden mit einem Plus verbunden. Doch die Einarbeitung lohnt sich, denn bereits mit den Default-Einstellungen sehen »ggplot2«-Grafiken deutlich professioneller aus als die aus dem Graphics-Paket.

Abbildung 1: Visualisierung einer Häufigkeitsverteilung mittels »graphics«-Paket. Das ist die grafisch schlichtere Version.
Es gibt außerdem ein großes Angebot verschiedener Themes, die unkompliziert auf die Grafik anwendbar sind. Damit lässt sich das äußere Erscheinungsbild eines Plots, der als Objekt gespeichert ist, nachträglich ändern. Wem die direkt in »ggplot2« enthaltenen Themes nicht ausreichen, der kann sich im »ggthemes«-Paket umsehen oder selbst Schriftart, Farbe und Größe der einzelnen Elemente seinen Wünschen anpassen. Denn es ist nicht besonders schwierig, selbst ein Theme zu definieren und somit – sogar voll automatisiert – Grafiken zu erstellen, die exakt dem Corporate Design einer Organisation entsprechen.

Abbildung 2: Visualisierung einer Häufigkeitsverteilung durch einen Barplot mit »ggplot2«. Das Default-Erscheinungsbild ist etwas aufwändiger als in Abbildung 1.
Im folgenden kurzen Beispiel entsteht der gleiche Plot einmal mit »graphics« (Abbildung 1) und einmal mit »ggplot2« (Abbildung 2). Dazu legt der Anwender zunächst einen Beispieldatensatz mit genau einer Spalte an (Listing 3), die die Antworten einiger imaginärer Personen auf die Frage nach ihrer Lieblings-Entwicklungsumgebung in R enthält (dazu im übernächsten Abschnitt mehr).
Listing 3
Beispieldatensatz
01 > dat <- data.frame(umgebung = c("RStudio", "Atom", "keine", "RStudio", "Emacs", "RStudio", "RStudio",
02 + "RStudio", "Emacs", "RStudio", "Atom", "RStudio", "RStudio"))
03 > plot(dat$umgebung)
04 > ggplot(dat, aes(x = umgebung)) + geom_bar()
Base-R oder alternative Pakete?
Typische Anwendungen in R lassen sich oft sowohl mit den Basispaketen der Core-Distribution als auch mit neueren Paketen realisieren. Dabei bieten Pakete aus der Core-Distribution zumeist eine hohe Stabilität, während sich einige neuere Pakete deutlich komfortabler und eleganter bedienen lassen und sinnvollere Default-Werte besitzen. Einige dieser Pakete sind jedoch auch noch in der Entwicklung, sodass der Anwender immer berücksichtigen sollte, dass sich bei einem Paket-Update grundlegende Funktionalitäten ändern können. Ein Blick auf die Versionsnummer vermeidet unangenehme Überraschungen.
Aus dem Bereich neuerer Pakete sei insbesondere das Tidyverse genannt, eine Sammlung praktischer Pakete für Daten-Aufbereitung, -Analyse und -Visualisierung. So lassen sich Grafiken, wie bereits erwähnt, mit dem Paket »graphics« aus der Core-Distribution oder dem neueren »ggplot2« (aktuelle Version 3.1) aus dem Tidyverse erstellen.
Listing 4
Zwei Pakete, eine Aufgabe
01 > # base
02 > dat <- data.frame(a = c(10, 11, 12),
03 + b = c(4, 5, 6),
04 + colour = c("blue", "green", "yellow"),
05 + stringsAsFactors = FALSE)
06 > dat$c <- 2 * dat$a
07 > dat$sum <- dat$a + dat$b
08 >
09 > # dplyr
10 > dat <- data_frame(a = c(10, 11, 12),
11 + b = c(4, 5, 6),
12 + colour = c("blue", "green", "yellow")) %>%
13 + mutate(c = 2 * a,
14 + sum = a + b)
Typische Datenaufbereitungs-Schritte (Ändern von Datenspalten, Berechnung neuer Spalten, Datenaggregation, …) sind sowohl mit dem Base-Paket als auch mit den Paketen »tidyr« (Version 0.8) und »dplyr« (Version 0.8) umsetzbar. Im folgenden Beispiel wird zunächst ein kleiner Data Frame mit den Spalten »a«, »b« und »colour« erstellt. Anschließend soll R die Spalten »c« und »sum« berechnen. Dies erfolgt einmal mit Base-R und zum anderen mit Dplyr (Listing 4).
Das Markenzeichen des »dplyr«-Pakets fällt sofort auf: der Pipe-Operator (%>%). Er erlaubt es, Expressions aneinanderzuhängen wie die Glieder einer Kette. Der Pipe-Operator leitet den Ausdruck, der links von ihm steht, an den nächsten Ausdruck weiter. Während in Base-R komplizierte Ausdrücke oft verschachtelt geschrieben und dann von innen nach außen gelesen werden müssen, lassen sich diese Konstrukte mit »dplyr« aufbrechen und in logisch aufeinander aufbauende Schritte überführen.
Listing 5
Beispiel mit Pipe-Operator
01 > x <- c(TRUE, FALSE, TRUE, TRUE, FALSE, FALSE, TRUE, NA)
02 >
03 > # base
04 > paste0(round(mean(x, na.rm = TRUE), 2), "%")
05 [1] "0.57%"
06 >
07 > # mit Pipe-Operator
08 > x %>% mean(na.rm = TRUE) %>% round(2) %>% paste0("%")
09 [1] "0.57%"
Das folgende Beispiel verdeutlicht, wie der Pipe-Operator die Lesbarkeit des Codes erhöht. Dabei wird ein Logical-Vektor, der auch einen fehlenden Wert enthält, in einen prozentualen Anteil überführt (Listing 5). Auch wenn das zweite Beispiel durch den Pipe-Operator etwas länger wird (und ein paar Mikrosekunden länger dauert), ist hier unter anderem schneller erkennbar, welche Argumente zu welcher Funktion gehören.
Entwicklungsumgebungen
Es gibt mehrere Entwicklungsumgebungen für R, von denen R-Studio (Abbildung 3) mit Abstand am weitesten verbreitet ist. Zusätzlich zu Konsole, History und Editor mit Syntax Highlighting, Einklappen und automatischem Einrücken enthält die R-Studio-Oberfläche auch eine Git-Integration sowie hilfreiche Tools zur Entwicklung von R-Paketen.

Abbildung 3: Die Entwicklungsumbgebung R-Studio. Oben links befindet sich ein Skript, unten links die Konsole. Das Fenster oben rechts zeigt alle aktuell im Environment vorhandenen Objekte an.
Alternativen zu R-Studio sind beispielsweise Atom mit der Erweiterung Hydrogen oder Emacs mit der Erweiterung ESS. Beide eröffnen mehr Möglichkeiten zur individuellen Konfiguration und Erweiterung durch Plugins. Da sie für verschiedene Sprachen geeignet sind, ist außerdem kein Wechsel des Editors notwendig. Umgekehrt ist R-Studio gerade für den Einstieg häufig komfortabler, weil keine Konfiguration erforderlich ist.
Stärken und Schwächen
Insbesondere für Universitäten, aber auch für Unternehmen ist oft entscheidend, dass sich R entgegen kommerziellen Lösungen zur Datenanalyse kostenlos nutzen lässt. Zudem unterstützt es alle gängigen Betriebssysteme (Linux, Windows, Mac OS). Das Angebot an kostenlosen Hilfe-Ressourcen wie Manuals, Tutorials oder Blogs (zum Beispiel [1]) ist sehr umfangreich. Auch gibt es einen sehr guten Community-Support und zusätzlich kostenpflichtigen Support durch Drittanbieter sowie zahlreiche Schulungsangebote. Durch die große Entwickler-Community ist sichergestellt, dass das Projekt auch mittel- und langfristig aktiv weiterentwickelt wird.
Wer sich live über die neuesten Entwicklungen informieren oder mit einem Mitglied des Core-Teams plaudern möchte, kann eine R-Konferenz wie die Use R [2] oder das European R Users Meeting [3] besuchen. In vielen größeren Städten gibt es regelmäßige R-Meetups [4].
Eine der größten Stärken von R ist mit Sicherheit die überwältigende Anzahl verfügbarer Pakete. Für so gut wie jede Anwendung, die mit Daten zu tun hat, findet sich ein passendes R-Paket. Mit dem großen Paketangebot geht jedoch auch ein Schwachpunkt von R einher: Häufig ist es schwierig, unter der Vielzahl verfügbarer Pakete eine Auswahl zu treffen. Außerdem befinden sich Stabilität und Qualität wenig genutzter Pakete zum Teil nicht auf dem hohen Niveau der Core-Distribution, auch weil die Autoren häufig nur begrenzte Programmier-Erfahrung haben.
Weitere Vor- und Nachteile ergeben sich daraus, dass R eine interpretierte Sprache ist. Da der Code nicht erst kompiliert, kann er Zeile für Zeile in der Konsole ausgeführt werden. Erst dadurch wird interaktives Arbeiten möglich, was im Umgang mit Daten fast unverzichtbar ist: Bei einer explorativen Datenanalyse oder der Entwicklung eines statistischen Prognosemodells können sich Programmierer nach jedem Schritt Daten, Grafiken und Ergebnisse anschauen. Der Zwischenschritt des fehlerfreien Kompilierens würde die Datenanalyse ungemein verkomplizieren. Der Nachteil hiervon ist jedoch auch, dass R bei bestimmten Anwendungen eher langsam ist.
Diesem Problem kann zum Beispiel durch Parallelisierung begegnet werden, wofür das Paket »parallel« empfehlenswert ist. Unter Linux ist die Umsetzung sehr unkompliziert, unter Windows ist der Code etwas umständlicher. Zudem ist unter Windows der Arbeitsspeicherbedarf bei einer Parallelisierung höher als unter Linux, sodass hier oft nicht die Anzahl der Kerne, sondern der Arbeitsspeicher der begrenzende Faktor ist (besonders bei großen Datensätzen).
Eine weitere Möglichkeit zur Beschleunigung des Codes ist, die Performance-kritischen Abschnitte in C oder C++ auszulagern. Zur Integration von C++-Code steht beispielsweise das »rcpp«-Paket zur Verfügung. In der Praxis verfolgen manche auch die Strategie, R zum interaktiven Entwickeln eines Modells zu nutzen und das Ergebnis für die operative Verwendung schließlich in einer anderen Sprache wie Java umzusetzen.
Gut integrierbar
In Bezug auf Datenbank-Schnittstellen und Integrierbarkeit mit anderen Programmiersprachen und Programmen steht R anderen Sprachen in nichts nach. Über native Treiber kann R unter anderem mit MySQL, PostgreSQL, Oracle und Teradata kommunizieren. Darüber hinaus kann R über ODBC und JDBC mit nahezu beliebigen Datenbanken (auch Microsoft SQL Server) zusammenarbeiten.
Für Big-Data-Anwendungen gibt es verschiedene Lösungen, um R in Hadoop (auf eigener Hardware oder auch über Amazon Web Services) einzubinden. Zudem gibt es Interfaces, um R mit anderen Programmiersprachen zu verknüpfen: So kann R zum Beispiel Objekte mit Java austauschen (siehe JRI beziehungsweise R-Java). Über das Paket »reticulate« lässt sich Python-Code in R einbauen, der umgekehrte Weg ist mit dem Python-Paket »rpy2« möglich.
Auch bei anderen Statistiklösungen und Programmen aus dem Business-Intelligence-Sektor wächst die Unterstützung von R: Zum Beispiel stellen SPSS, SAS, Microstrategy und auch Power BI Schnittstellen zu R bereit, um von dessen Funktionsreichtum zu profitieren.
Für Personen, die aus anderen Programmiersprachen (etwa Python, C++ oder Java) kommen, ist R aus verschiedenen Gründen zunächst gewöhnungsbedürftig – zum Beispiel passiert vieles automatisch. So muss etwa der Programmierer beim Anlegen eines neuen Objekts den Typ nicht explizit angeben.
Wie im obigen Beispiel erwähnt, überführt R unter bestimmten Bedingungen Daten automatisch in einen anderen Typ. Diese Logik geht sogar so weit, dass beim Vergleich zweier Objekte unterschiedlichen Typs diese in den allgemeineren Typ überführt werden. »2 == “2”« führt daher zu dem Ergebnis »TRUE«. Um die volle Kontrolle zu haben, sollte daher besser die Funktion »identical()« zum Vergleich benutzt werden. Wer das nicht weiß, erhält unter Umständen ein unerwartetes Ergebnis.
For-Schleifen sollten R-Entwickler, wo immer es möglich ist, vermeiden, weil sie den Code verlangsamen. Stattdessen sollten sie eine vektorisierte Vorgehensweise wählen, was in R auch sehr häufig möglich ist. So lässt sich beispielsweise mit den so genannten »apply()«-Funktionen dieselbe Funktion auf alle Elemente eines Vektors, einer Matrix, einer Liste und dergleichen anwenden. Das kann für Programmier-Enthusiasten mit umfangreichen Erfahrungen aus anderen Sprachen eine gewisse Umgewöhnung bedeuten.
Für Anfänger ganz ohne Programmier-Erfahrung hingegen ist die Einstiegshürde in R eher gering, weil sie recht schnell einfach loslegen können. Die nötigen Grundlagen für eine statistische Analyse lassen sich innerhalb weniger Tage erlernen. Um einen Datensatz einzulesen und ein Regressionsmodell zu berechnen, reichen ganze zwei Zeilen Code. Mit Konzepten wie objektorientiertem Programmieren muss sich der Anwender hierfür nicht beschäftigen.
Nicht zuletzt weist R wie viele ältere Sprachen einige Eigenheiten auf, die sich nur historisch erklären lassen. Beispielsweise werden beim Einlesen von Datensätzen String-Variablen standardmäßig als »factor« (kategorial) und nicht als einfache Characters (String) gespeichert. Hintergrund ist, dass zum Zeitpunkt der Entstehung von R die Analyse kategorialer Variablen verbreiteter war als die von echtem Text, sodass Strings damals in der Regel tatsächlich Kategorien widergespiegelt haben. Heute führt dies allerdings dazu, dass beim Einlesen zum Beispiel mit »read.csv()« meistens zusätzlich das Argument »stringsAsFactors = FALSE« mit zu übergeben ist.
Auch konnte sich bislang kein einheitliches Namensschema durchsetzen. Selbst in den Funktionsnamen des Stats-Pakets aus der Core-Distribution finden sich Punkte (»t.test()«), Unterstriche (»get_all_vars()«), Lowercases (»addmargins()«) und Camelcases (»setNames()«).
Von der Wissenschaft in die Businesswelt
R hat seine Wurzeln ganz klar im wissenschaftlichen Umfeld, wurde an der Universität entwickelt und ist auch heute noch in der Wissenschaft weit verbreitet. Wissenschaftliche Veröffentlichungen, die neue statistische Methoden beinhalten, werden häufig direkt von einem R-Paket begleitet, das die Methode implementiert.
Neben R gibt es selbstverständlich einige andere Tools, die Wissenschaftler gern einsetzen, sobald sie Daten aufbereiten und analysieren wollen. Manche davon bieten grafische Benutzeroberflächen an, sind dadurch aber im Funktionsumfang eingeschränkt, zum Beispiel SPSS [5], das in der Psychologie und den Sozialwissenschaften sehr beliebt ist. Weitere Tools, die – genau wie SPSS – kostenpflichtig sind, sind STATA [6] (Ökonometrie) und SAS [7] (Biometrie, klinische Forschung, Bankensektor).
Im Informatik-Umfeld und insbesondere beim Machine Learning ist Python weit verbreitet, das auch im Vergleich zu R kaum Wünsche offen lässt. Bezogen auf Statistikanwendungen ist der Hauptunterschied zwischen den beiden Sprachen die Vielzahl verfügbarer Pakete in R. In Python konzentrieren sich Data-Science-Anwendungen in einer geringeren Anzahl stärker konsolidierter Pakete, jedoch sind speziellere oder neuere Methoden oft nicht verfügbar.
Ob man eher R oder Python den Vorzug gibt, hängt letzten Endes jedoch zumeist vom fachlichen Hintergrund des Teams ab: Wer einen Informatik-Hintergrund hat, bringt oft bereits Python-Kenntnisse mit und nutzt sie dann auch gerne im beruflichen Kontext. Leute mit mathematischem, sozial- oder naturwissenschaftlichem Hintergrund haben dagegen eher bereits Erfahrung mit R.
Den Sprung von einer wissenschaftlichen Software in die Businesswelt hat R nicht zuletzt der Verfügbarkeit von immer mehr Daten zu verdanken. Unternehmen möchten von ihren Daten profitieren, wollen ihre Geschäftsprozesse optimieren. Steht ein Unternehmen nun vor der Aufgabe, Daten zu analysieren, greifen die Mitarbeiter oft zu Microsoft Excel. Als sehr mächtiges Programm kann Excel auch viele Anforderungen erfüllen. Werden jedoch komplexere Datenaufbereitungsschritte notwendig oder sollen statistische Modelle entwickelt werden, merken Anwender häufig, dass sie an seine Grenzen stoßen.
Ein Einstieg in R bringt mehr Vorteile als nur die Vielzahl von Methoden zur Daten-Aufbereitung und -Analyse. Zum Beispiel lassen sich mit R Markdown reproduzierbare und parametrisierbare Reports im PDF-, Word- oder HTML-Format erstellen. Die Verwendung von Latex-Elementen in R-Markdown-Reports eröffnet unzählige Möglichkeiten wie etwa die Anpassung der Reports ans Corporate Design [8] – jedoch sind grundsätzlich keine Latex-Kenntnisse für die Arbeit mit R Markdown nötig.
Das Paket Shiny macht es möglich, interaktive Webanwendungen zu entwickeln, einige Beispiele finden sich auf [9]. Damit kann R auch Aufgaben abdecken, die andernfalls Tools wie Tableau [10] oder Power BI [11] lösen würden. Doch bereits die Automatisierung von Prozessen mit Hilfe von R-Skripten birgt für viele Neueinsteiger im Vergleich zur wiederholten Durchführung derselben Klick-Abfolge einen enormen Nutzen.
Zusammengefasst eignet sich R für alle Anwendungsszenarien, in denen regelmäßig mit Statistik-Software gearbeitet wird. Hauptargumente sind – neben den entfallenden Lizenzkosten für die Software selbst – vor allem die Breite unterstützter Methoden und die enorme Flexibilität und gute Automatisierbarkeit. Die aktive Community bietet Support und garantiert die langfristige Weiterentwicklung der Software.
Auch wenn sich Datenanalysen im Prinzip mit jeder beliebigen Programmiersprache durchführen lassen, wurde R speziell hierfür entwickelt und viele übliche Methoden sind bereits implementiert. Lediglich wenn sehr große Datensätze in begrenzter Zeit auf vergleichsweise schwacher Hardware zu analysieren sind, wenn Anwender Statistik-Software nur sporadisch nutzen oder wenn sie ausschließlich sehr spezielle Methoden benötigen, ist der Einsatz von R kritisch zu hinterfragen.
Praxisbeispiel: Regressionsanalyse
Das abschließende Beispiel liest Google-Analytics-Daten aus einer CSV-Datei ein. Sie enthalten die Anzahl von Sitzungen auf der Website von INWT Statistics seit Anfang 2015 (Abbildung 4). Zunächst filtert das Skript die Daten und bereitet sie auf. Auf Basis der Daten von 2017 und 2018 modelliert der Analyst die Sitzungsanzahl abhängig von Monat und Wochentag. Die ersten Wochen von 2019 hält er zunächst als Testdaten zurück, als wäre gerade erst Silvester 2018/19.
![Abbildung 4: Grafische Darstellung des zeitlichen Verlaufs der Sitzungszahlen auf [www.inwt-statistics.de]. Es lassen sich bereits einige Eigenheiten erkennen. So beginnen alle Jahre nach der Weihnachtsflaute mit relativ starkem Traffic.](/wp-content/uploads/2019/05/5_zeitreihe_graphics-300x87.jpg)
Abbildung 4: Grafische Darstellung des zeitlichen Verlaufs der Sitzungszahlen auf [www.inwt-statistics.de]. Es lassen sich bereits einige Eigenheiten erkennen. So beginnen alle Jahre nach der Weihnachtsflaute mit relativ starkem Traffic.
In der Spalte Estimate der Modellzusammenfassung ist erkennbar, dass an Samstagen und Sonntagen besonders wenige Website-Aufrufe stattfinden. Besonders starke Monate sind Juni und Juli. Die Interpretation erfolgt stets im Vergleich zur – hier automatisch gesetzten – Referenzklasse »Dienstag« beziehungsweise »April«. Insgesamt kann das Modell bereits über 50 Prozent der Variation erklären (Multiple R-Squared: 0.5528).
Dann berechnet das Beispiel die vom Modell vorhergesagten Werte – inklusive der ersten Wochen 2019, sodass sich untersuchen lässt, wie gut das Modell mit neuen Daten funktioniert, die für die Schätzung nicht zum Zuge kamen. Abschließend stellt der Anwender die tatsächliche und die vom Modell vorhergesagte Zahl der Sitzungen grafisch dar (Listing 6).
Abbildung 5: Das erste Modell mit Darstellung der tatsächlichen Sitzungsanzahlen (schwarz) und denen von einem Regressionsmodell vorhergesagten (blau im Modellzeitraum, rot im Prognosezeitraum).
Abbildung 5 zeigt deutlich, dass das sehr simple Modell noch einige Schwächen hat, zum Beispiel bildet es den Einbruch um Weihnachten/Silvester nicht ab. Ein weiterer Schritt prüft, ob das Hinzufügen von Feiertagsvariablen das Modell verbessert – optisch oder anhand von Modellgütemaßen wie dem R-Squared (idealerweise an Testdaten, die nicht zur Modellschätzung benutzt wurden).
Abbildung 6: Der Jahresverlauf wird hier nicht-linear mit einem Spline modelliert statt über einzelne Monatsvariablen. Das Modell kann die Daten bereits besser nachbilden als das simplere Modell aus Abbildung 5.
Das Modell verbessert sich bereits deutlich, wenn man den Jahresverlauf nicht-linear mit einem so genannten Spline statt über die Monats-Variable abbildet und einen linearen Trend hinzufügt (Abbildung 6). Fraglich ist, ob sich das Modell verschlechtert, wenn man die Tage zu “Wochentag vs. Wochenende/Feiertag” zusammenfasst, statt sie einzeln zu berücksichtigen.
Listing 6
Protokoll einer Zeitreihenanalyse
001 > # Benötigte Pakete laden
002 > library(dplyr)
003 > library(ggplot2)
004 > library(ggthemes)
005 >
006 > # Daten aus csv-Datei einlesen
007 > dat <- read.csv("data/Google_Analytics_Sessions.csv", stringsAsFactors = FALSE)
008 >
009 > # Erster Blick auf den Datensatz
010 > str(dat)
011 'data.frame': 794 obs. of 2 variables:
012 $ Tag : chr "01.01.17" "02.01.17" "03.01.17" "04.01.17" ...
013 $ Sitzungen: num 105 304 355 394 356 338 255 302 461 489 ...
014 >
015 > # Das als character gespeicherte Datum in ein Datum umwandeln (mit base-Paket)
016 > dat$Tag <- as.Date(dat$Tag, format = "%d.%m.%y")
017 >
018 > # Grafische Darstellung der Zeitreihe mit dem eingebauten graphics-Paket (Abbildung 5)
019 > plot(dat$Tag, dat$Sitzungen, type = "l")
020 >
021 > # Datenaufbereitung mit dplyr
022 > datPrep <- dat %>%
023 + mutate(
024 + # Werte unter 50 auf NA setzen, weil es sich dabei bekanntermaßen um Messfehler handelt
025 + Sitzungen = ifelse(Sitzungen < 50, NA, Sitzungen),
026 + # Wochentag ermitteln
027 + wtag = weekdays(Tag),
028 + # Monat ermitteln
029 + monat = months(Tag)
030 + )
031 >
032 > # Aufteilung in Trainings- und Testdaten
033 > datPrep$isTrainData <- datPrep$Tag < "2019-01-01"
034 >
035 > # Simples lineares Modell. Die character-Variablen "wtag" und "monat" werden
036 > # automatisch als kategoriale Variablen interpretiert.
037 > # Das Modell nutzt nur die Daten bis Ende 2018, d.h. für die die Variable
038 > # isTrainData den Wert TRUE hat
039 > mod1 <- lm(data = datPrep[datPrep$isTrainData, ], formula = Sitzungen ~ wtag + monat)
040 >
041 > # Modellzusammenfassung
042 > summary(mod1)
043
044 Call:
045 lm(formula = Sitzungen ~ wtag + monat, data = datPrep[datPrep$isTrainData, ])
046
047 Residuals:
048 Min 1Q Median 3Q Max
049 -464.80 -61.88 -6.52 62.38 479.19
050
051 Coefficients:
052 Estimate Std. Error t value Pr(>|t|)
053 (Intercept) 517.340 18.270 28.316 < 2e-16 ***
054 wtagDonnerstag -31.046 16.119 -1.926 0.0545 .
055 wtagFreitag -116.981 16.122 -7.256 1.08e-12 ***
056 wtagMittwoch -2.817 16.111 -0.175 0.8612
057 wtagMontag -7.527 16.030 -0.470 0.6388
058 wtagSamstag -262.736 16.164 -16.255 < 2e-16 ***
059 wtagSonntag -202.964 16.077 -12.624 < 2e-16 ***
060 monatAugust -16.028 20.777 -0.771 0.4407
061 monatDezember -6.106 20.768 -0.294 0.7688
062 monatFebruar 109.349 21.304 5.133 3.72e-07 ***
063 monatJanuar 133.990 20.770 6.451 2.10e-10 ***
064 monatJuli 211.030 20.849 10.122 < 2e-16 ***
065 monatJuni 167.145 22.755 7.345 5.85e-13 ***
066 monatMai 24.528 21.411 1.146 0.2524
067 monatMärz -10.467 20.858 -0.502 0.6159
068 monatNovember 99.135 20.943 4.734 2.68e-06 ***
069 monatOktober -27.591 20.770 -1.328 0.1845
070 monatSeptember 53.475 21.030 2.543 0.0112 *
071 ---
072 Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
073
074 Residual standard error: 114.2 on 685 degrees of freedom
075 (27 observations deleted due to missingness)
076 Multiple R-squared: 0.5528, Adjusted R-squared: 0.5417
077 F-statistic: 49.81 on 17 and 685 DF, p-value: < 2.2e-16
078
079 > # Berechnung der durch das Modell vorhergesagten Werte (für gesamten Zeitraum)
080 > datPrep$pred <- predict(mod1, newdata = datPrep)
081 >
082 > # Grafische Darstellung wahrer und vorhergesagter Werte mit ggplot2
083 > p <- ggplot(datPrep) +
084 + geom_line(aes(x = Tag, y = Sitzungen, linetype = !isTrainData)) +
085 + geom_line(aes(x = Tag, y = pred, colour = !isTrainData)) +
086 + scale_color_manual(values = c("#2B4894", "#cd5364"), limits = c(FALSE, TRUE)) +
087 + labs(colour = "Testzeitraum", linetype = "Testzeitraum")
088 > p # Äquivalent zu print(p)
089 >
090
091 > # Alternatives Modell mit Splines
092 > library(mgcv)
093 > library(lubridate)
094 > datPrep$dayOfYear <- yday(datPrep$Tag)
095 > datPrep$linTrend <- 1:nrow(datPrep)
096 > mod2 <- gam(data = datPrep[datPrep$isTrainData, ],
097 + formula = Sitzungen ~
098 + wtag + linTrend + s(dayOfYear, bs = "cc", k = 20, by = as.factor(wtag)))
099
100 > summary(mod2)
101
102 Family: gaussian
103 Link function: identity
104
105 Formula:
106 Sitzungen ~ wtag + linTrend + s(dayOfYear, bs = "cc", k = 20,
107 by = as.factor(wtag))
108
109 Parametric coefficients:
110 Estimate Std. Error t value Pr(>|t|)
111 (Intercept) 532.0423 12.3948 42.925 < 2e-16 ***
112 wtagDonnerstag -32.2007 14.1368 -2.278 0.0231 *
113 wtagFreitag -119.6377 14.1354 -8.464 < 2e-16 ***
114 wtagMittwoch -4.9203 14.1359 -0.348 0.7279
115 wtagMontag -5.8354 14.0639 -0.415 0.6784
116 wtagSamstag -265.6973 14.1727 -18.747 < 2e-16 ***
117 wtagSonntag -204.1019 14.1028 -14.472 < 2e-16 ***
118 linTrend 0.1254 0.0205 6.118 1.71e-09 ***
119 ---
120 Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
121
122 Approximate significance of smooth terms:
123 edf Ref.df F p-value
124 s(dayOfYear):as.factor(wtag)Dienstag 15.32 18 6.413 < 2e-16 ***
125 s(dayOfYear):as.factor(wtag)Donnerstag 12.97 18 3.902 1.89e-10 ***
126 s(dayOfYear):as.factor(wtag)Freitag 12.15 18 3.953 4.59e-11 ***
127 s(dayOfYear):as.factor(wtag)Mittwoch 13.85 18 5.314 9.31e-15 ***
128 s(dayOfYear):as.factor(wtag)Montag 16.64 18 8.382 < 2e-16 ***
129 s(dayOfYear):as.factor(wtag)Samstag 11.29 18 3.307 3.00e-09 ***
130 s(dayOfYear):as.factor(wtag)Sonntag 12.92 18 4.843 1.02e-13 ***
131 ---
132 Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
133
134 R-sq.(adj) = 0.648 Deviance explained = 69.9%
135 GCV = 11749 Scale est. = 10025 n = 703
136
137 > datPrep$pred2 <- predict(mod2, newdata = datPrep)
138 > ggplot(datPrep) +
139 + geom_line(aes(x = Tag, y = Sitzungen, linetype = !isTrainData)) +
140 + geom_line(aes(x = Tag, y = pred2, colour = !isTrainData)) +
141 + scale_color_manual(values = c("#2B4894", "#cd5364"), limits = c(FALSE, TRUE)) +
142 + labs(colour = "Testzeitraum", linetype = "Testzeitraum")
Infos
-
R-Bloggers: https://www.r-bloggers.com
-
Use R: http://www.user2019.fr
-
Erum: https://erum.io
-
R-Meetups: https://jumpingrivers.github.io/meetingsR/r-user-groups.html
-
SPSS: https://www.ibm.com/de-de/analytics/spss-statistics-software
-
STATA: https://www.stata.com/
-
Anpassen an Corporate Identity: https://www.inwt-statistics.de/blog-artikel-lesen/r-markdown-template-fuer-business-reports.html
-
Tableau: https://www.tableau.com
-
Power BI: https://powerbi.microsoft.com/de-de







