In einem Psychologiestudium (aber natürlich auch in anderen Fächern) spielen in vielfältiger Art und Weise empirische Daten eine wichtige Rolle und in diesem Zusammenhang natürlich auch deren statistische Auswertung. Dazu werden Sie ein Verständnis für diese Verfahren und deren theoretischen Grundlagen erlangen, aber Sie werden auch eigene empirische Daten erheben, auswerten und dokumentieren (z.B. in den typischen Experimentalpsychologischen Praktika oder auch in Qualifikationsarbeiten). Zwar stehen zur Auswertung der Daten eine ganze Reihe von Programen zur Verfügung, aktuell hat sich in vielen Bereichen allerdings das frei verfügbare Programm R samt einiger Erweiterungen durchgesetzt.

Diese Seite bietet Ihnen eine Einführung in die Arbeit mit R, beginnend mit der Installation über einige grundlegende Aspekte bis zu Themen wie Programmkontrolle und einfachen statistischen Verfahren. Theoretische Kenntnisse der Verfahren werden hier nicht vermittelt, sondern vorausgesetzt (und anderweitig vermittelt).

Autor:innen dieser Seite: An den Inhalten dieser Seite haben mitgearbeitet: Eva Röttger, Markus Janczyk und Kilian Gloy. Der Inhalt dieser Seite wird in der Lehre in den Studiengängen Psychologie von der AG Forschungsmethoden und Kognitive Psychologie an der Universität Bremen verwendet, steht aber allen Interessierten zur Verfügung. Rückmeldungen/Fehler/Vorschläge können gesendet werden an .

Versionshistory:

  • v0.9: erste online-gestellte Version (9.2.2023)

1 Einführung in R und RStudio

Dieses erste Kapitel gibt Ihnen grundlegende Informationen zur Arbeit mit R (und RStudio) sowie zu deren Installation. Im Anschluss werden grundlegende Funktionalitäten behandelt. Die nächsten Kapitel greifen dann spezifische Aspekte der Arbeit mit R detaillierter auf.

1.1 Grundlagen und Installation

1.1.1 R und RStudio

R ist sehr flexibles und umfangreiches kostenloses OpenSource Statistikpaket, welches sich mittlerweile großer Verbreitung erfreut. OpenSource heißt in diesem Kontext, dass der Code frei verfügbar und einsehbar ist und im Prinzip auch jede Person eigene Ergänzungen programmieren kann.

Dabei ist R an sich ein eher unscheinbares Programm welches von vielen Anwender:innen nicht einmal wirklich geöffnet werden wird. Und wenn doch: die grafische Benutzeroberfläche ist enorm minimalistisch und bietet nur wenig Komfort. Daher gibt es eine ganze Reihe von Zusatzprogrammen, die auf R aufsetzen, aber den Komfort und das Arbeiten mit R enorm angenehmer gestalten. Wir werden hier mit dem Programm RStudio arbeiten.

Ein weiteres wichtiges Konzept bei der Arbeit mit R sind Pakete (oder Englisch: packages). Pakete stellen weitere Funktionen, Verfahren, Methoden, … zur Verfügung, und werden von verschiedenen Personen aus der R-Community geschrieben. Die Flexibilität und Mächtigkeit von R rührt auch daher, dass es für (fast) alle Probleme und Problemchen irgendwo ein Paket gibt, mit dem eine Lösung schnell gefunden ist, sobald das Paket identifiziert und installiert ist.

Nun ist R im Wesentlichen eine (Programmier-)Sprache und dies mag auf den ersten Blick abschreckend sein. Wie für alle Sprachen gilt aber auch hier: Mit beständiger Übung können Sie R meistern! Insofern können wir Sie nur ermutigen, tatsächlich mit R herumzuspielen, Dinge zu programmieren, andere Lösungswege zu suchen, und so weiter. Den meisten Personen macht die Arbeit mit R dann sogar Spaß!

1.1.2 Installation

Um R und RStudio zu nutzen, müssen beide Software-Pakete zunächst installiert werden. Der erste Schritt ist daher die Installation von R an sich. Auf der Homepage des R-Projekts (https://cran.r-project.org/) finden Sie dazu sämtliche benötigten Dateien und weitere Informationen, Pakete und auch Anleitungen (die wir hier erst einmal nicht benötigen).

Auf der Homepage wählen Sie zunächst das richtige Betriebssystem aus (Windows, Mac OS X oder Linux) und werden dann einen Schritt weitergeführt. Windowsnutzer:innen wählen auf der nächsten Seite base aus und können dann die aktuelle Version von R herunterladen und installieren. Ähnlich funktioniert die Installation bei den anderen Betriebssystemen. Falls Sie R schon installiert haben, nutzen Sie diesen Moment, um ein Update auf die neueste Version zu machen.

Der nächste Schritt ist dann die Installation von RStudio. Auf der entsprechenden Homepage https://www.rstudio.com/ wählen Sie die RStudio Desktop Version, laden sich den entsprechenden Installer für das richtige Betriebsystem herunter und folgen dann den üblichen Anweisungen bei der Installation von Software.

1.1.3 Erste Schritte

Haben Sie R und RStudio installiert, dann ist der erste wichtige Schritt zur Datenauswertung geschafft. Zum Ausprobieren können Sie nun RStudio starten (oder auch zum Vergleich einfach R einmal starten…).

RStudio sollte sich in etwa wie in der folgenden Abbildung dargestellt öffnen. Neben der Menüleiste ganz oben besteht RStudio i.d.R. aus vier verschiedenen Fenstern:

  1. Oben links befindet sich der Editor für Quellcode und Skripte, in welchem Sie den Code entwickeln, ändern, speichern und auch ausführen können. Wenn sich RStudio ohne ein leeres Editor-Fenster geöffnet hat, legen Sie nun als nächstes ein neues RScript an. Dazu gehen Sie über das Menü File - New File - R-Script (oder drücken STRG+Shift+N).
  2. Unten links befindet sich die Konsole bzw. das Befehlsfenster. Befehle die hier eingegeben werden, werden direkt ausgeführt und viele Textausgaben von R landen auch genau hier.
  3. Oben rechts ist die Variablen- und Datenansicht. Dort werden derzeit aktive Variablen und auch aktive Funktionen angezeigt, um den Überblick zu behalten. Für viele Variablen werden auch zusätzliche Informationen (z.B. ihr Typ, ihr Wert, …) angezeigt.
  4. Unten rechts ist das Ausgabefenster, in welchem typischerweise grafische Ausgaben landen. Auch kann hier eine Liste der Dateien im aktuellen Verzeichnis angezeigt werden und auch die R-Hilfe wird hier angezeigt.
RStudio Screenshot. (1) Editor für den Quellcode von R-Skripten, (2) Konsole bzw. Befehlsfenster, (3) Variablen- und Datenansicht, (4) Ausgabefenster für z.B. Grafiken und Hilfe

RStudio Screenshot. (1) Editor für den Quellcode von R-Skripten, (2) Konsole bzw. Befehlsfenster, (3) Variablen- und Datenansicht, (4) Ausgabefenster für z.B. Grafiken und Hilfe

Geben Sie in der Konsole zum Probieren nun einfach 2+2 ein und bestätigen dies mit Return, dann führt R den Befehl aus und gibt das Ergebnis in der Konsole aus:

2+2
## [1] 4

Längeren Code sollten Sie aber immer in ein R-Skript schreiben, dort speichern und auch von dort ausführen. Geben Sie nun in das R-Skript einfach 2+2 ein. Um diesen Code auszuführen, klicken Sie in die Zeile, die Sie ausführen möchten und drücken dann STRG-RETURN (oder Sie klicken dann oben rechts auf Run). Mehrere Zeilen werden ausgeführt, wenn diese zuerst markiert werden und dann STRG-RETURN gedrückt wird, das gesamte Skript kann mit STRG-SHIFT-RETURN ausgeführt und alle vom Anfang bis zur aktuellen Cursorposition wird mit STRG-ALT-B ausgeführt.

In der Regel evaluiert R eine Zeile als einen Befehl. Falls Sie aber in einer Zeile z.B. eine Klammer öffnen oder andere Strukturen beginnen, führt R auch die nächste(n) Zeile(n) aus, bis der ganze Befehl abgeschlossen ist. Mehrere Befehle in einer Zeile müssen durch ein Semikolon ; getrennt werden.

R verfügt über eine umfangreiche Hilfefunktion. Wenn Sie bspw. wissen möchten, wie die Funktion mean() funktioniert (die einen Mittelwert berechnet), können Sie in die Konsole

?mean

eingeben. Darauf erscheint die entsprechende Hilfeübersicht zur Funktion im Fenster rechts unten. Fällt Ihnen der genaue Name einer Funktion nicht ein, oder wollen Sie wissen, was es evtl. noch für interessante und nützliche Funktionen zu einem Thema gibt, kann der Befehl apropos() helfen. Wollen Sie z.B. alle Funktionen auflisten, die irgendwas mit mean zu haben, geschieht dies mit:

apropos("mean")
##  [1] ".colMeans"     ".rowMeans"     "colMeans"      "kmeans"       
##  [5] "mean"          "mean.Date"     "mean.default"  "mean.difftime"
##  [9] "mean.POSIXct"  "mean.POSIXlt"  "rowMeans"      "weighted.mean"

Ein letzter wichtiger Punkt ist der Hashtag (#). In R-Codes zeigt dieses Zeichen einen Kommentar an, d.h. die Zeile wird ab dem Hashtag nicht weiter ausgeführt, sondern dient nur der Beschreibung und Kommentierung der entsprechenden Stelle. Gerade in längeren Skripten ist es sehr hilfreich sich von Anfang an anzugewöhnen Code zu kommentieren, um ihn auch später noch gut verstehen zu können:

(3+6)/2 # Hier werden die Zahlen 3 und 6 addiert und dann durch 2 dividiert
## [1] 4.5

1.1.4 Nächste Schritte

Wir werden auf dieser Seite diejenigen Inhalte behandeln, die uns grundlegend befähigen die üblichen statistischen Verfahren durchzuführen, die in den Statistikveranstaltungen behandelt werden. Das heißt auch, dass wir nützliche Funktionen von R auch auslassen werden. Zu Bedenken ist grundätzlich: Für quasi alle Fragestellungen und Probleme gibt es in R mehrere Lösungswege. Lassen Sie sich nicht davon abschrecken, wenn andere Autor:innen andere Wege vorschlagen! Was für Sie funktioniert hängt sehr von Ihrem persönlichen Arbeits- und Programmierstil ab.

Auf (fast) alle Fragen zu R gibt es im Internet eine Antwort. Wenn eine einfache google-Suche zu unspezifisch ist, helfen die Seiten https://rseek.org/ oder https://stackoverflow.com/questions/tagged/r vielleicht weiter.

Es gibt natürlich eine ganze Reihe Bücher, sowohl als PDFs im Internet als auch in gedruckter Form. Einführungen in die wichtigsten Funktionen bieten z.B. die Bücher von Luhmann (2020) und Wollschläger (2021). Wer sich mehr mit Programmierung in R auseinandersetzen und fortgeschrittene Verfahren kennenlernen möchte, findet z.B. bei Ligges (2008) und Wollschläger (2020) gute Einstiege.

  • Ligges, U. (2008). Programmieren mit R (3. Auflage). Berlin: Springer.
  • Luhmann, M. (2020). R für Einsteiger. Einführung in die Statistiksoftware für die Sozialwissenschaften (5. Aufl.). Weinheim: Beltz.
  • Wollschläger D. (2020). Grundlagen der Datenanalyse mit R (5. Aufl.). Berlin: Springer Spektrum.
  • Wollschläger D. (2021). R kompakt: Der schnelle Einstieg in die Datenanalyse (3. Aufl.) Berlin: Springer Spektrum.

1.1.5 Pakete installieren

R wird direkt mit einer ganzen Reihe an Paketen installiert. Darüber hinaus gibt es aber unzählige weitere Pakete für spezielle Verfahren und Zwecke, die vor der Nutzung erst einmal installiert werden müssen. Dies können Sie in RStudio mit dem Menü Tools - Install Packages tun. Geben Sie hier einfach den Namen des gewünschten Paketes ein, machen einen Haken bei Install dependencies und klicken dann auf Install. Den Rest macht RStudio (idealerweise) selbst. Geben Sie nun als Paketnamen schoRsch ein und installieren dieses. Alternativ können Sie auch die Konsole nutzen:

install.packages(pkgs = "schoRsch", dependencies = TRUE)

Wenn Sie schoRsch erfolgreich installiert haben, können Sie nun die Hilfe aufrufen, um zu schauen, was das Paket an Funktionen bietet:

?schoRsch

Allerdings werden Sie mit großer Wahrscheinlichkeit eine Fehlermeldung erhalten: Das Paket ist zwar installiert, aber noch nicht geladen. Dies ist eine Eigenart von R, die verhindert, dass der Speicher mit nicht-genutzten Paketen belastet wird. Geladen werden Pakete meistens zu Beginn eines Skripts mit dem Befehl library(), also hier mit:

library(schoRsch)

Nun können Sie die Hilfefunktion erneut ausprobieren und anschauen.

1.2 Arbeiten mit R

1.2.1 Ein paar Worte zur Einführung

Wie bereits erwähnt ist R i.W. eine Programmiersprache für Statistik. R kann zwar auch noch viel mehr (z.B. ist dieses Dokument direkt in R geschrieben worden), aber statistische Auswertung und Programmierung ist das, wozu R meistens verwendet wird. Da viele R-Neulinge bisher eher weniger mit Programmiersprachen zu tun gehabt haben, ist eine gewisse Skepsis zu Beginn nachvollziehbar. Sie brauchen allerdings keine Angst haben. Das Beste was Sie tun können ist: Probieren Sie selber aus!

Wir werden hier auch mit Code zu tun bekommen, der sich über mehrere Zeilen erstreckt. Ein wichtiger Punkt (der aber oft für Verständnisprobleme sorgt) ist: R beginnt mit der Ausführung in der ersten (auszuführenden) Zeile und geht dann im Wesentlichen seriell vor, d.h., danach wird die zweite Zeile ausgeführt, usw. Wir werden aber auch Möglichkeiten kennenlernen, Zeilen zu überspringen (z.B. mit sog. if-Abfragen) und Zeilen zu wiederholen (z.B. mit sog. for-Schleifen). Beginnen werden wir nun allerdings mit der wichtigen Unterscheidung von Variablen und Funktionen.

1.2.2 Variablen und Funktionen

Eine wichtige erste Unterscheidung für die Arbeit mit R (und anderen Programmiersprachen) ist die Unterscheidung von Variablen und Funktionen.

Variablen haben ihren Namen daher, dass ihr Wert variabel ist. Eine Variable kann also z.B. mal den Wert 5 annehmen und dann den Wert 3. Für jede Variable müssen wir uns in R einen Namen ausdenken, der – zumindest wenn die Codes länger werden – möglichst gut beschreibt, was in der Variablen gespeichert wird.

R (und andere Programmiersprachen auch) unterscheidet mehrere Typen von Variablen, wobei der Typ u.a. darüber entscheidet, was wir mit einer Variablen später machen können. Oft arbeiten wir in R mit numerischen Variablen. Um eine Variable mit dem Namen \(a\) anzulegen und ihr den Wert 5 zuzuweisen, können wir in ein Skript (oder in die Konsole) schreiben:

a <- 5  # weist der Variablen a den Wert 5 zu 
a       # gibt den Wert der Variablen a aus
## [1] 5
(b = 5) # Zuweisungen gehen auch mit "=" und Klammern drumherum geben den Ausdruck direkt aus
## [1] 5

Die Variable a sollte nun auch in der Variablenumgebung auftauchen. Eine Übersicht über alle derzeit aktiven Variablen (und Funktionen) erhalten Sie mit:

ls()
## [1] "a" "b"

und mit

rm(list = "a")     # oder auch nur: rm(a)

kann die Variable auch aus der Umgebung gelöscht werden. Wenn Sie alle Variablen gleichzeitig löschen wollen, so geht dies mit:

rm(list = ls())    # ls() liefert alle derzeit aktiven Variablen und Funktionen zurück

Haben Sie das Paket schoRsch installiert und geladen, können Sie alternativ einfach eingeben:

clear()

Mit numerischen Variablen können Rechenoperationen durchgeführt werden. Wir weisen den Variablen a und b nun neue Werte zu, addieren diese, weisen das Ergebnis einer Variablen c zu und geben den Wert von c aus:

a <- 5
b <- 7
c <- a + b
c
## [1] 12

Neben numerischen Variablen gibt es in R auch andere Variablentypen. Im Gegensatz zu manchen anderen Programmiersprachen (z.B. C++), müssen Sie R nicht vorab mitteilen, um was für einen Typ es sich handelt; R “überlegt” sich selber den Variablentyp. Die zwei für uns wichtigsten weiteren Variablentypen sind Bool-Variablen und Strings.

Bool-Variablen können nur zwei Werte annehmen, nämlich wahr oder falsch. In R schreibt man dafür üblicherweise TRUE bzw. FALSE oder abgekürzt T bzw. F (es würde auch gehen: 1 bzw. 0). Wollen Sie einer Bool-Variablen den Wert TRUE zuweisen, so geschieht dies ganz einfach durch:

Statistik.ist.toll <- TRUE   # zuweisen
Statistik.ist.toll           # Wert ausgeben
## [1] TRUE

Strings hingegen sind Zeichenfolgen und werden bei R dadurch kenntlich gemacht, dass sie in Anführungsstrichen geschrieben werden. Natürlich können Strings auch Zahlen beinhalten, sie sind dann aber eben Zeichenfolgen und mit ihnen kann nicht mehr gerechnet werden:

a <- 5      # numerische Variable
b <- "7"    # b ist ein String
a + b       # Fehler!
## Error in a + b: nicht-numerisches Argument für binären Operator

Natürlich kann R auch mit Strings diverse Dinge durchführen; wir werden im aktuellen Kontext aber wenig Gebrauch von derartigen Möglichkeiten machen.

Funktionen sind – etwas vereinfacht – Verfahren, die auf Variablen angewendet werden können und i.d.R. wieder Werte “zurückgeben”. Den meisten Funktionen werden in runden Klamern sog. Parameter übergeben, die einerseits der Funktion sagen, auf welche Variablen sie angewendet werden sollen und andererseits die genaue Arbeitsweise einer Funktion noch bestimmen. All diese Dinge, die in Klammern “übergeben” werden heißen Argumente. Um auszuprobieren, wie Funktionen verwendet werden, testen wir nun als erstes die Funktion mean(), die einen Mittelwert berechnet. Wenn Sie die Hilfe zu mean() aufrufen, sehen Sie, dass die Funktion auf jeden Fall ein Argument namens x benötigt. Wir können daher z.B. den Mittelwert der gerade angelegten Variablen a berechnen mit:

a <- 5
mean(x = a)
## [1] 5

Wenig überraschend ist der Mittelwert von 5 dann auch 5. Wenn Argumente in der richtigen Reihenfolge übergeben werden, kann der Name in der Zuweisung auch weggelassen werden. Darüber hinaus haben manchen Argumente Standardwerte: Sofern nichts anderes angegeben ist, geht R von diesen Werten aus. Die beiden folgenden Eingaben sind daher äquivalent:

mean(x = a, trim = 0, na.rm = FALSE) # mit mehreren Argumenten, aber ihren Standardwerten
mean(a)                              # ...die daher auch weggelassen werden können

Als zweites testen wir nun noch die Funktion as.numeric(), die aus einer String-Variablen eine numerische Variable macht:

b <- "7"    # String-Variable 
b           # b ausgeben -> 7 in Anführungsstrichen, weil String
## [1] "7"
c <- as.numeric(b)   # wandelt b in numerische Variable um, speichert diese in c
c           # c ausgeben -> nicht mehr in Anführungsstrichen, da nun numerische Variable
## [1] 7
c + 2       # = 9 -> mit c kann auch wieder gerechnet werden
## [1] 9

Funktionen der Form as.XXX() dienen dazu, eine Variable eines Typs in eine Variable anderen Typs zu verwandeln. Zusätzlich gibt es noch Funktionen der Form is.XXX(), die testen, ob eine Variable diesen bestimmten Typs ist:

a <- "5"
is.numeric(a)
## [1] FALSE
is.numeric(as.numeric(a))
## [1] TRUE

In der letzten Zeile des letzten Codes sind zwei Funktionen verschachtelt: R führt dann zunächst die innere Funktion aus (as.numeric()) und übergibt deren Ergebnis an die äußere Funktion (is.numeric()). Prinzipiell sind beliebig viele solcher Verschachtelungen möglich - man verliert aber auch schnell den Überblick und sollte nicht zuviele Verschachtelungen nutzen, sondern ggf. mehrere Zeilen verwenden.

Wir werden im Laufe der Zeit eine ganze Reihe von Funktionen kennenlernen und verwenden und Funktionen können auch selber geschrieben werden, z.B. um immer wiederkehrende Verarbeitungen zu vereinfachen und damit Code zu reduzieren. Auch was Variablen angeht werden wir noch weitere Datenformen kennenlernen, mit denen wir die Arbeit in R vereinfachen können. Die wichtigsten davon werden Vektoren, Matrizen, DataFrames und Listen sein.

Ein wichtiger Punkt zum Schluss: R ist case sensitive, d.h., achtet auf Groß- und Kleinschreibung. Wenn Sie z.B. eine Variable a eingeführt haben, dann können Sie diese nicht mit A abrufen:

a <- 2
A
## Error in eval(expr, envir, enclos): Objekt 'A' nicht gefunden

1.2.3 R als Taschenrechner

Eine einfache Verwendung von R ist, das Programm als eine Art großen Taschenrechner zu nutzen. R kann natürlich deutlich mehr, aber wir fangen hier mit einfachen Rechenoperationen an, die auch immer wieder eine Rolle spielen werden. Zunächst legen wir wieder zwei Variablen a und b an und verwenden die Grundrechenarten und Klammersetzung für Berechnungen:

a <- 9
b <- 3
a + b                     # Addition
## [1] 12
a - b                     # Subtraktion
## [1] 6
a * b                     # Multiplikation
## [1] 27
a / b                     # Division
## [1] 3
(a + b) * 2               # Klammersetzung
## [1] 24
(a + b) / ( (b - 1) * b)  # Klammersetzung -> (9+3)/(2*3) = 12/6 = 2
## [1] 2

Potenzrechnung und Wurzelrechnung funktionieren natürlich auch:

a^b                       # a hoch b
## [1] 729
sqrt(a)                   # Wurzel aus a (sqrt = square-root)
## [1] 3
a^(1/2)                   # ...oder a hoch (1/2)
## [1] 3

Den “Rest” einer Division kann man sehr leicht mit dem sog. “Modulo”-Operator bestimmen:

a %% b                  # 9 / 3 = 3 Rest 0
## [1] 0
b %% a                  # 3 / 9 = 0 Rest 3
## [1] 3

Weitere nützliche Funktionen sind:

sin(a) ; cos(a) ; tan(a)  # Sinus, Cosinus und Tangens
## [1] 0.4121185
## [1] -0.9111303
## [1] -0.4523157
log(a) ; exp(a)           # Logarithmus von a bzw e hoch a (e = Euler'scher Zahl)
## [1] 2.197225
## [1] 8103.084
factorial(a)              # a! = 1*2*...*a
## [1] 362880

1.2.4 Vergleichsoperatoren und logische Verknüpfungen

Inbesondere numerische Variablen und Werte können auch miteinander verglichen werden. Hierbei stehen alle üblichen Operatoren zur Verfügung und der Vergleich liefert jeweils zurück, ob die Beziehung wahr ist oder falsch ist, d.h., die Rückgabe ist jeweils ein Wahrheitswert TRUE oder FALSE (eine Bool-Variable):

a <- 4
b <- 5
a < b   # kleiner als
## [1] TRUE
a <= 4  # kleiner/gleich
## [1] TRUE
a > b   # größer als
## [1] FALSE
a >= 4  # größer/gleich
## [1] TRUE
a == b  # gleich
## [1] FALSE
a != 2  # ungleich
## [1] TRUE

Ein letzter wichtiger Operator in diesem Kontext ist die Negation. In R wird diese durch ein “!” erreicht. Wenn also a den Wert TRUE hat, dann ist non-a folglich ´FALSE´:

a <- TRUE       # wenn a also TRUE ist....
!a              # ...dann ist non-a FALSE
## [1] FALSE

Derartige Bedingungen können auch miteinander kombiniert werden, wozu im Wesentlichen die logischen Ausdrücke und und oder wichtig sind. Wenn zwei Bedingungenragen mit und verknüpft sind, dann müssen beide Bedingungen TRUE sein, damit die Gesamtausdruck TRUE ist. Das Zeichen dafür ist das &:

(5 < 10) & (5 > 10) # 5 ist nicht gleichzeitig kleiner UND größer als 10... daher FALSE
## [1] FALSE

Wenn zwei Bedingungen hingegen mit oder verknüpft sind, dann muss nur mindestens eine der Bedingungen TRUE sein, damit der Gesamtausdruck TRUE ist. Das Zeichen dafür ist das |:

(5 < 10) | (5 > 10) # 5 ist kleiner ODER größer als 10... daher TRUE
## [1] TRUE

Die Klammersetzung wäre in den beiden letzten Beispielen nicht unbedingt nötig, erhöht aber durchaus die Lesbarkeit.

2 Von einfachen Variablen zu komplexeren Datenstrukturen

Bisher haben wir mit einfachen Variablen gearbeitet, die gewissermaßen aber nur das Grundgerüst komplexerer Datenstrukturen darstellen, mit denen wir in der praktischen Tätigkeit mit R fast immer arbeiten werden. Manche Funktionen (wie z.B. mean()) ergeben auch erst dann einen Sinn, wenn sie auf komplexere Datenstrukturen angewendet werden. Wir werden mit Vektoren und Matrizen beginnen und dann mit sog. DataFrames weitermachen, die eine typische Datenstruktur sind, mit denen wir danach immer wieder arbeiten werden.

2.1 Vektoren und Matrizen

Vektoren und Matrizen sind eine wichtige Grundlage für die Arbeit mit größeren Datenmenge und sind in R ganz ähnlich aufgebaut wie sie auch in der Linearen Algebra behandelt werden.

2.1.1 Vektoren erstellen

Vektoren sind gewissermaßen Zusammenfassungen einzelner Variablen und mit ihnen lassen sich viele Dinge in R viel einfacher durchführen als mit einzelnen Variablen. Variablen oder Werte werden mit c() (c für concatenate) zu einem Vektor verbunden:

a <- 1 ; b <- 2; c <- 3   # einzelne Variablen
vektor <- c(a, b, c)      # zu einem Vektor machen (oder: vektor <- c(1,2,3))
vektor                    # Vektor als Ganzes ausgeben
## [1] 1 2 3

Vektoren haben oft Regelmäßigkeiten oder Wiederholungen als Elemente. Um solche Vektoren zu erstellen, bietet R eine ganze Reihe nützlicher Funktionen. Die erste wichtige Variante, einen Vektor mit z.B. den Werten \(1,2,\ldots,15\) zu erstellen, ist:

neuer.vektor <- c(1:15)
neuer.vektor              # anzeigen
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15

Flexibler ist zudem die Verwendung der Funktion seq(). Dieser Funktion kann ein Start- und ein Endwert übergeben werden und dann entweder die Schrittweite oder die angestrebte Länge des Vektors spezifiziert werden:

neuer.vektor <- seq(from = 1,  # von 1...
                    to = 11,   # ...bis 11...
                    by = 2)    # ...in 2er-Schritten
neuer.vektor
## [1]  1  3  5  7  9 11
# Anmerkung:
# Wir verwenden hier oft die ausführliche Schreibweise wie eben, um die Bedeutung 
# der Parameter klar zu machen. Der besseren Lesbarkeit halber schreiben wir dann die 
# Argumente oft untereinander. Wenn Sie sich mit Funktionen sicherer fühlen und wissen,
# in welcher Reihenfolge Argumente übergeben werden, könnten Sie hier z.B. auch einfach
# kurz schreiben:
neuer.vektor <- seq(1, 11, 2)

Das Argument length.out kann spezifiziert werden, wenn Start- und Endwert sowie die Anzahl der gewünschten Elemente feststeht. R berechnet dann automatisch die passende Schrittweite:

neuer.vektor <- seq(from = 1,
                    to = 10,
                    length.out = 19)  # es sollen 19 Elemente im Vektor sein
neuer.vektor
##  [1]  1.0  1.5  2.0  2.5  3.0  3.5  4.0  4.5  5.0  5.5  6.0  6.5  7.0  7.5  8.0
## [16]  8.5  9.0  9.5 10.0

Wiederholungen von Elementen können schließlich mit der Funktion rep() realisiert werden, wobei hier zwei Fälle zu unterscheiden sind. Wollen wir z.B. einen Vektor mit den Elementen 1, 2 und 3 vier-mal wiederholen, so müssen wir das Argument times spezifizieren:

vektor <- c(1,2,3)
neuer.vektor <- rep(x = vektor,     # Was soll wiederholt werden?
                    times = 4)      # Wie oft wiederholen?
# oder kurz: neuer.vektor <- rep(vektor, 4)
neuer.vektor
##  [1] 1 2 3 1 2 3 1 2 3 1 2 3

Wollen wir hingegen alle Elemente des Vektors mit den Elementen 1, 2 und 3 vier-mal wiederholen, so müssen wir das Argument each spezifizieren:

vektor <- c(1,2,3)
neuer.vektor <- rep(x = vektor,     # Was soll wiederholt werden?
                    each = 4)       # Wie oft wiederholen?
neuer.vektor
##  [1] 1 1 1 1 2 2 2 2 3 3 3 3

Bis hierhin haben wir nur Vektoren numerischer Variablen erstellt. Natürlich können Sie auch einen Vektor mit Bool-Variablen oder Strings erstellen:

bool.vektor <- c(FALSE, TRUE, FALSE, FALSE, TRUE)
bool.vektor     # ausgeben als ganzen Vektor
## [1] FALSE  TRUE FALSE FALSE  TRUE
string.vektor <- c("männlich", "weiblich", "weiblich", "divers", "männlich")
string.vektor   # ausgeben als ganzen Vektor
## [1] "männlich" "weiblich" "weiblich" "divers"   "männlich"

2.1.2 Arbeiten mit Vektoren

Wir können auch – und das ist oft viel wichtiger – auf einzelne Elemente eines Vektors zugreifen. Hierfür wird der Index (also die Position) in eckigen Klammern hinter dem Namen des Vektors angegeben (im Gegensatz zu z.B. C++ hat das erste Element in R den Index 1 [statt 0]):

vektor <- c(2, 4, 6)
vektor[1]
## [1] 2

Mit Vektoren zu arbeiten ist vermutlich ein wenig gewöhnungsbedürftig; es wird sich aber schnell eine gewisse Intuition dafür einstellen, wie flexibel R hierbei tatsächlich ist. Fangen wir damit an herauszufinden, wieviele Elemente ein Vektor besitzt. Hierfür gibt die Funktion length():

vektor <- c(1, 2, 3)       # Vektor erstellen
length(vektor)             # Länge des Vektors berechnen...
## [1] 3

Eine schöne Sache ist, dass wir auch viele der grundlegenden Operatoren direkt auf Vektoren anwenden können. Wenn wir z.B. zu jedem Element des Vektors 3 addieren wollen, dann geht das ganz einfach mit:

vektor <- vektor + 3
vektor
## [1] 4 5 6

Ganz ähnlich können wir auch subtrahieren, multiplizieren und andere Rechnungen auf alle Elemente des Vektors anwenden. Wenn wir stattdessen gezielt z.B. das zweite Element mit 5 multiplizieren wollen, dann müssen wir durch den Index auf dieses Element zugreifen:

vektor[2] * 5
## [1] 25

Wir können dies sogar tun und gezielt das zweite Element dann im Vektor verändern:

vektor[2] <- vektor[2] * 5
vektor
## [1]  4 25  6

Auch zwei (gleich lange) Vektoren können (elementweise) auf verschiedene Arten miteinander verrechnet werden:

a <- c(1, 2, 3)
b <- c(6, 5, 4)
a + b
## [1] 7 7 7
a * b 
## [1]  6 10 12
a / b
## [1] 0.1666667 0.4000000 0.7500000
a ^ b
## [1]  1 32 81

Wie berechnen wir nun den Mittelwerte der Elemente des Vektors vektor? Das arithmetische Mittel ist definiert als die Summe der Elemente dividiert durch die Anzahl der Elemente, also: \[M = \frac{1}{n} \sum_{i=1}^n x_i \] Das heißt, wir können die (drei) Elemente von vektor aufsummieren und dann durch die Länge des Vektors dividieren:

(vektor[1] + vektor[2] + vektor[3] ) / length(vektor)
## [1] 11.66667

Einfacher können wir allerdings auch die schon bekannte Funktion mean() sinnvoll verwenden: Wenn wir als Argument einen Vektor übergeben, dann berechnet diese den Mittelwert der Elemente des Vektors:

mean(vektor)
## [1] 11.66667

Eine weitere wichtige Funktion ist sum(), die die Summe aller Elemente eines Vektors liefert:

sum(vektor)
## [1] 35

Würde es die Funktion mean() nicht geben, könnten wir ganz einfach und flexibel (ohne vorherige Kenntnis darüber, wieviele Elemente ein Vektor besitzt) mit sum() und length() den Mittelwert berechnen als:

sum(vektor) / length(vektor)
## [1] 11.66667

R erlaubt uns sogar, logische Operatoren elementweise auf Vektoren anzuwenden. Wir könnten z.B. daran interessiert sein festzustellen, wieviele Elemente eines Vektors kleiner als 3 sind. Hierzu benutzen wir zunächst einen (logischen) Operator, den wir auf den Vektor anwenden und der uns dann einen ganzen Vektor von Bool-Variablen zurückgibt. Auf diesen wenden wir dann sum() an, um diese Anzahl der TRUEs zu zählen:

vektor <- c(4, 2, 3, 6, 5, 7, 1, 2, 4)
kleiner.als.drei <- vektor < 3   # hier der Vergleich
kleiner.als.drei                 # Bool-Vektor anzeigen
## [1] FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE
sum(kleiner.als.drei)            # Summe der TRUEs
## [1] 3

Kürzer, aber verschachtelter, könnten wir auch schreiben:

sum(vektor < 3)
## [1] 3

Warum entspricht hier die Summe des Bool-Vektors der Anzahl der TRUEs in diesem Vektor? Das liegt daran, dass intern ein TRUE als 1 kodiert wird und ein FALSE als 0.

2.2 Matrizen

2.2.1 Matrizen erstellen

Matrizen sind zunächst Erweiterungen von Vektoren, oder anders ausgedrückt: ein Vektor ist eine Matrix mit einer Spalte bzw. Zeile. Matrizen können aber generell mehr als eine Zeile und mehr als eine Spalte umfassen. Während manche Dinge in R direkt als Matrix existieren, können wir auch selber Matrizen u.a. mit der Funktion matrix() erstellen. Als nächstes erstellen wir zur Übung daher die folgende Matrix: \[A= \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}\]

Um ein Gefühl für die Verwendung (noch) unbekannter Funktionen zu bekommen, schauen wir uns zunächst die Hilfe zur Funktion matrix() an:

?matrix

Hier sehen wir, dass matrix() zunächst einen Vektor (data = NA) benötigt, in welchem die einzelnen Werte vorhanden sind. Danach gibt es zwei Argumente nrow und ncol, die die Anzahl der Zeilen (rows) und Spalten (columns) bestimmen. Das vierte Argument byrow hat den Standardwert FALSE: Standardmäßig werden daher die Spalten der Matrix mit den Elementen des Vektors “aufgefüllt”. Wie dieses Argument gesetzt werden sollte, hängt von der Reihenfolge im Datenvektor ab (ändern Sie den Wert einmal und schauen sich an, wie die Matrix dann ausschaut):

a <- c(1, 2, 3, 4, 5, 6)    # Vektor mit den Daten
A <- matrix(data = a,
            nrow = 2,
            ncol = 3,
            byrow = TRUE) 
# oder auch kurz: A <- matrix(a, 2, 3, TRUE)
A                           # Matrix anzeigen
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    5    6

Die Funktion matrix() ist vor allem nützlich, wenn größere Datenmengen in eine Matrix strukturiert werden soll. Für kleinere Matrizen (insbesondere mit vorgegebenen) Werten bietet sich die Funktion rbind() (“row bind”) an, die Vektoren untereinander kopiert und deren Resultat daher eine Matrix ist. Die Matrix A kann daher auch erstellt werden mit:

A <- rbind(c(1, 2, 3),   # 1. Zeile
           c(4, 5, 6))   # 2. Zeile
A                        # Matrix anzeigen
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    5    6

Ein Vorteil dieser Variante ist, dass man sehr einfach sieht, wie die Matrix später aussehen wird. Die Funktion cbind() (“column bind”) kann genutzt werden, um Vektoren nebeneinander zu kopieren:

B <- cbind(c(1, 2, 3),   # 1. Spalte
           c(4, 5, 6))   # 2. Spalte
B                        # Matrix anzeigen
##      [,1] [,2]
## [1,]    1    4
## [2,]    2    5
## [3,]    3    6

2.2.2 Arbeiten mit Matrizen

Auf Matrizenelemente kann, ähnlich wie bei Vektoren, auch einzeln zugegriffen. Der Unterschied ist nun, dass sowohl die Zeile (als erstes) als auch die Spalte (als zweites) indiziert werden muss (getrennt durch ein Komma). Auf die \(5\) in der zweiten Zeile und zweiten Spalte der Matrix A kann z.B. zugegriffen werden mit:

A[2,2]
## [1] 5

Um auf ganze Zeilen oder Spalten zuzugreifen, lassen Sie einfach den Index der anderen Dimension weg:

A[1,]   # erste Zeile (alle Spalten)
## [1] 1 2 3
A[,1]   # erste Spalte (alle Zeilen)
## [1] 1 4

Für fortgeschrittene Verwendungen sind hier noch weitere Eingrenzungen möglich. Um z.B. die (beiden) Elemente der ersten zwei Spalten der zweiten Zeile zu extrahieren, können Sie schreiben:

A[2,1:2]   # Zeile 2 - Spalten 1-2
## [1] 4 5

Manche bereits bekannten Funktionen können problemlos auch auf Matrizen angewendet werden:

length(A)     # wieviele Elemente gibt es in A?
## [1] 6
mean(A)       # Mittelwert aller Elemente
## [1] 3.5
sum(A)        # Summe aller Elemente
## [1] 21

Um die Anzahl der Zeilen und Spalten (also die Dimensionen einer Matrix) zu ermitteln, können wir die Funktion dim() verwenden. dim() liefert nun keine einzelne Zahl zurück, sondern einen Vektor mit zwei Elementen, auf die wir – wie oben eingeführt – zugreifen können. Das erste Element ist die Anzahl der Zeilen, das zweite Element die Anzahl der Spalten:

dim(A)            # Dimensionen ermitteln
## [1] 2 3
dim(A)[1]         # Anzahl Zeilen
## [1] 2
dim(A)[2]         # Anzahl Spalten
## [1] 3

Oft wird es ein Interesse an der Summe oder dem Mittelwert der Spalten oder Zeilen geben. Hierfür gibt es natürlich auch Funktionen:

rowSums(A)     # Summen der Zeilen
## [1]  6 15
colSums(A)     # Summen der Spalten
## [1] 5 7 9
rowMeans(A)    # Mittelwerte der Zeilen
## [1] 2 5
colMeans(A)    # Mittelwerte der Spalten
## [1] 2.5 3.5 4.5

2.3 DataFrames

Fast immer, wenn wir einen existierenden Datensatz nutzen und einlesen, werden wir es mit sog. DataFrames zu tun haben. DataFrames sind ganz ähnlich organisiert wie auch in anderen Programmen wie Excel oder SPSS und bieten viele Vorteile bei ihrer Nutzung.

2.3.1 Erstellen von DataFrames

Zum Einstieg erstellen wir uns nun einen ersten DataFrame, der zu zehn Versuchspersonen jeweils ihr Geschlecht, ihr Alter und ihre Körpergröße in cm enthält. Dazu speichern wir zunächst die Inhalte der Variablen in Vektoren und fügen diese dann zu einem DataFrame namens daten zusammen:

vp <- c(1:10)    
geschlecht <- c("m", "m", "w", "m", "w", "w", "w", "m", "m", "w")
alter <- c(23, 41, 23, 45, 34, 31, 29, 30, 21, 34)
groesse.cm <- c(156, 182, 174, 176, 156, 187, 156, 166, 190, 189)
daten <- data.frame(vp, geschlecht, alter, groesse.cm)
daten    # in der Konsole anzeigen

In der Variablenumgebung sollte der DataFrame daten nun angezeigt werden. Ein Klick auf diesen öffnet den DataFrame und das Fenster sollte so wie in der folgenden Abbildung aussehen.

Ansicht eines DataFrames in RStudio

Ansicht eines DataFrames in RStudio

Eine Alternative zur Erstellung eines DataFrames inklusive der Möglichkeit, Daten einzugeben, Variablennamen zu vergeben und den Typ dieser Variablen zu verändern, bietet der Dateneditor von R. Im Vergleich zu SPSS oder Excel ist dieser nicht sonderlich umfangreich, aber er tut seine Dienste. Um z.B. einen neuen DataFrame mit dem Namen neue.daten zu erstellen, geben Sie ein:

neue.daten <- edit(as.data.frame(NULL))   
# NULL gibt an, dass es sich um einen leeren DataFrame handeln soll

Durch einen Klick auf die grau-unterlegten Bereiche mit den Überschriften var1 usw., wird ein kleines Fenster zur Änderung des Variablennamens und -typs geöffnet. Hinweis: Auf Macs kommt es scheinbar manchmal zu Problemen. Dann können Sie alternativ einen DataFrame mit voreingestellten Daten öffnen und diese dann einfach verändern:

neue.daten <- edit(data.frame(var1 = c(1, 1, 1)))

2.3.2 Arbeiten mit DataFrames

Im Folgenden gehen wir davon, dass das DataFrame daten so wie gerade erstellt existiert.

Um einfach zu erfahren, wie die Namen der Variablen eines DataFrame sind, kann die Funktion names() verwendet werden:

names(daten)
## [1] "vp"         "geschlecht" "alter"      "groesse.cm"

Im Wesentlichen liefert diese Funktion einen Vektor zurück und Variablennamen können geändert werden, indem die Werte dieses Vektors verändert werden. Wollen wir also – aus irgendwelchen Gründen – weniger Lesbarkeit im Datensatz haben, könnten wir z.B. neue Namen wie folgt als einen Vektor aus Strings einfach angegeben:

names(daten) <- c("x1", "x2", "x3", "x4")   # Zuweisen eines Vektors mit neuen Namen
names(daten)
## [1] "x1" "x2" "x3" "x4"

Mithilfe der Verwendung von Indizes können wir natürlich auch einzelne Variablennamen ändern:

names(daten)[2] <- "neuer.name"   # 2. Variable soll neuen Namen bekommen
names(daten)
## [1] "x1"         "neuer.name" "x3"         "x4"

Im Folgenden benutzen wir aber nun wieder die Originalnamen:

names(daten) <- c("vp", "geschlecht", "alter", "groesse.cm")

Die wichtigste Strukturierung in einem typischen DataFrame mit (empirischen) Daten ist, dass verschiedene Variablen in Spalten angeordnet sind (mit ihren Namen jeweils als erstes) und jede Zeile einen einzelnen Fall (z.B eine Versuchsperson) beinhaltet. Nun ist es für die praktische Arbeit notwendig, auf einzelne Variablen eines DataFrames zugreifen zu können. Hierfür gibt es zwei Möglichkeiten:

  • Sie geben, ähnlich wie bei Vektoren und Matrizen, den Index der gesuchten Spalte in eckigen Klammern an.
  • Sie benutzen den “$”-Operator um per Namen auf die Variable zuzugreifen.

Wollen Sie also bspw. auf die Variable “Geschlecht” zugreifen, sind die folgenden Varianten äquivalent (wir werden i.d.R. mit dem “$”-Operator arbeiten):

daten[2]                # per Index
daten$geschlecht        # per Namen

Eine neue Variable zu einem bestehenden DataFrame hinzuzufügen geht ganz einfach, indem Sie einfach den Namen angeben und den gewünschten Wert zuweisen. Wollen Sie z.B. eine Variable mit dem Namen eins hinzufügen und jede Versuchsperson soll den Wert 1 auf dieser Variablen erhalten, dann geht dies mit:

daten$eins <- 1

Sie können auch direkt neue Variablen aus bestehenden Variablen erstellen und dabei auch die verschiedenen mathematischen Operatoren verwenden. Als Beispiel erstellen wir nun eine Variable, in welcher die Größe nicht in cm, sondern in m gespeichert wird. Dazu muss bekanntlich der Wert in cm durch 100 dividiert werden:

daten$groesse.m <- daten$groesse.cm / 100

Um eine Variable zu löschen, setzen Sie diese einfach auf den Wert NULL:

daten$eins <- NULL

3 Daten und deren Handling

Bisher haben wir Variablen und auch den DataFrame daten per Hand geschaffen. Es wäre aber wenig hilreich, wenn wir größere Datensätze immer wieder mühsam als Vektoren eingeben müssten, um daraus einen DataFrame herzustellen. Daher bekommen wir größere oft als komplette Datei, die wir dann einlesen müssen. Natürlich kann R auch bestehende Variablen, … speichern und ebenso wieder laden. Der erste Teil dieses Kapitels beschreibt daher Grundlagen des Ladens und Speicherns von Daten und Variablen, bevor wir dann mit Möglichkeiten zum Handling größerer Datensätze fortfahren (z.B. Auswahl bestimmte Fälle oder Variablen, Umkodieren von Variablen, …).

3.1 Speichern und Laden

Zum Ausprobieren der folgenden Ausführungen haben wir Beispieldaten in der Datei beispieldaten_R_einfuehrung.zip zusammengestellt, die Sie hier herunterladen können. Dann speichern Sie den darin enthaltenen Ordner (beispieldaten_R_einfuehrung) lokal auf Ihrem Computer, damit die Datensätze zur Verfügung stehen. Damit R weiß, wo sich Daten befinden (oder auch: wo Sie Daten hinspeichern möchten), muss zunächst das aktuelle Arbeitsverzeichnis gesetzt werden. Dies geschieht mit der Funktion setwd(), der der entsprechende Pfad übergeben wird. Angenomen, der Ordner befindet sich auf dem Laufwerk D im Ordner Studium, dann wäre die Angabe:

setwd("D:/Studium/beispieldaten_R_einfuehrung")

Sie können auch choose.dir() verwenden, um einen Ordner mit der Maus auszuwählen. Die Funktion gibt dann den Pfad zurück, den Sie direkt wie im folgenden Beispiel in einer Variablen speichern und an setwd() übergeben können:

verzeichnis <- choose.dir()  # Aufrufen und in verzeichnis speichern...
setwd(verzeichnis)           # ...und dann an setwd() übergeben.
# oder kürzer: 
# setwd(choose.dir())

Wenn Sie herausfinden wollen, in welchem Arbeitsverzeichnis Sie sich gerade befinden, geht dies mit der Funktion getwd():

getwd()

Im Folgenden wird immer davon ausgegangen, dass das Arbeitsverzeichnis entsprechend gesetzt ist.

3.1.1 Laden

R kann Daten, Variablen, Funktionen, … in einem eigenen Format mit der Endung *.RData speichern. Im Ordner befindet sich eine Datei mit dem Namen beispiel_fuer_R_daten.RData. Wir löschen nun alle existierenden Variablen und laden dieses Beispiel, in welchem sich das DataFrame daten und eine Bool-Variable mit Namen testvariable befindet, die Ihnen nach dem Laden in der Variablenumgebung angezeigt werden und mit denen Sie nun arbeiten können:

remove(list = ls())
load(file = "beispiel_fuer_R_daten.RData")

Die meisten Datensätze liegen allerdings in anderer Form vor, oft als *.txt-Dateien, wobei einzelne Variable z.B. durch Tabulatorstopps oder Semikolons getrennt sein können. Beispiele für beide Varianten finden sich ebenfalls im Ordner (diese Dateien können Sie auch mit dem Windows Notepad o.ä. öffnen und anschauen). Zum Laden solcher Daten kann die Funktion read.table() genutzt werden. Neben dem Dateinamen übergeben wir als Argumente das jeweilige Trennzeichen (sep; “\t” steht für “Tabulatorstop”) und legen fest, dass in der ersten Zeile die Namen der Variablen stehen (header). Die eingelesenen Daten stehen dann als DataFrame zur Verfügung:

# laden mit Tabulatorstopps: \t steht für Tabulatorstopp
daten <- read.table(file = "bsp_dataframe_tabs.txt",   # Name der Datei
                    header = TRUE,                     # 1. Zeile = Variablennamen
                    sep = "\t")                        # Trennung durch Tabulator
#
# laden mit Semikolontrennung:
daten <- read.table(file = "bsp_dataframe_semis.txt",  # Name der Datei
                    header = TRUE,                     # 1. Zeile = Variablennamen
                    sep = ";")                         # Trennung durch Semikolon

Um das Einlesen von Daten zu vereinfachen gibt es in R Studio ein Interface, das sie unter Import Dataset oben rechts in der Variablen-Umgebung finden (oder im Menü File - Import Dataset). Aus der Auswahl möglicher Dateiformate könnten Sie z.B. From Text (base) auswählen, wenn es sich um als Text gespeicherte Daten handelt. Nach der Auswahl der Datei öffnet sich die folgende Dialogbox: Die einzelnen Bestandteile der Dialogbox bedeuten dabei folgendes:

  1. Unter Name steht der Name (ggf. inkl. Pfad) der Datei, die importiert werden soll.
  2. Unter Input File wird die Datei so angezeigt, wie sie im Original aussieht, während unter Data Frame die Daten so anzeigt, wie sie mit den momentanen Einstellungen importiert werden.
  3. Die übrigen Optionen ermöglichen festzulegen, ob Variablennamen in der ersten Zeile stehem, wie die Spalten getrennt werden, wie Dezimalstellen abgetrennt werden, usw.

Ähnliche Möglichkeiten finden Sie in der Dialogbox, die sich unter From Text (readr) öffnet. Dort sehen Sie auch, wie sich die gemachten Einstellungen in R Code übersetzen. So nützlich Dialogboxen auch erscheinen, es ist am Ende immer ratsam, auch Dateien per Code zu Beginn eines R Skripts zu lesen, wenn die Daten in dem Skript dann ausgewertet werden.

3.1.2 Speichern

Um verschiedene Variablen, … im RData-Format zu speichern, kann die Funktion save() genutzt werden. Im Beispiel werden daten und testvariable in die Datei beispielname.RData gespeichert:

save(list = c("daten","testvariable"), 
     file = "beispielname.RData")

Um das DataFrame daten ähnlich wie im Ordner zu speichern, können Sie die Funktion write.table() wie folgt benutzen (hier als Beispiel mit Semikolontrennung):

write.table(file = "beispielname.txt",  # Name der Datei
            daten,                      # DataFrame
            sep = ";",                  # Trennung durch Semikolon
            quote = FALSE,              # keine Anführungsstriche um Werte
            row.names = FALSE)          # Zeilen nicht nummerieren

3.2 Fortgeschrittenes Datenhandling

Bisher haben wir recht einfache Daten benutzt und Funktionen auf diese angewendet. Mit “echten” Daten müssen mitunter mehr vorbereitende Schritte durchgeführt werden, z.B. müssen mehrere Datensätze miteinander verbunden werden, Fälle ausgewählt werden oder Daten umkodiert werden. Wir werden nun eine Reihe von Funktionen kennenlernen, die solche Manipulationen ermöglichen.

3.2.1 Verbinden von Datensätzen

Manchmal sind Daten auf mehrere Dateien oder DataFrames verteilt und müssen vor einer Auswertung zusammengefügt werden. Hierbei können wir grob zwei Varianten unterscheiden, nämlich das Hinzufügen von Fällen und das Hinzufügen von Variablen. Beide Varianten können wir an Beispieldaten ausprobieren; dazu laden Sie zunächst die Datei dreidatensaetze.RData aus dem Datenordner. Dadurch sollten drei DataFrames aktiv sein (daten.teil1, daten.teil1_neu und daten.teil2).

load("dreidatensaetze")

3.2.2 Hinzufügen von Fällen

In diesem Fall haben beide DataFrames die gleichen Variablen, allerdings von verschiedenen Personen (jeweils \(n=4\) Personen). Um die Gesamtdaten auswerten zu können, fügen wir diese mit der schon bekannten Funktion rbind() zu einem neuen DataFrame namens daten zusammen, welcher dann \(n=8\) Personen enthält:

daten.teil1                   # Teil 1 ausgeben
daten.teil2                   # Teil 2 ausgeben
daten <- rbind(daten.teil1,   # Zusammenfassen Teil 1....
               daten.teil2)   # ..und Teil 2
daten                         # alle Daten zusammen ausgeben

Eine Voraussetzung hierfür ist, dass beide DataFrames die gleichen Variablen besitzen (die Reihenfolge ist nicht wichtig, aber Anzahl und Namen). Ist dies nicht der Fall, hilft die Funktion rbind.fill() aus dem Paket plyr weiter.

3.2.3 Zusammenfügen von Datensätzen

Dieser Fall liegt bspw. vor, wenn Sie die Daten der gleichen Personen auf verschiedene Dateien verteilt haben. Zum Beispiel könnte dies bei Längsschnittstudien auftreten, wenn Sie zunächst die Daten des ersten Messzeitpunktes eingeben und später in einer eigenen Datei dann die Daten des zweiten Messzeitpunktes. In den DataFrames daten.teil1 und daten.teil1_neu gibt es jeweils Daten von den gleichen vier Personen. Um diese zu einem Gesamtdatensatz zusammen zu fügen, nutzen Sie die Funktion merge(). Wichtig ist nun, dass es eine Indikatorvariable gibt, die angibt, wie die einzelnen Fälle in beiden DataFrames einander zugeordnet werden sollen. Im vorliegenden Fall nehmen wir dazu die Variable vp, die jeweils eine Nummer für jede Person angibt:

daten.neu <- merge(daten.teil1,       # 1. Datei
                   daten.teil1_neu,   # 2. Datei
                   by = "vp")         # Indikatorvariable, die in beiden Dateien existiert
daten.neu

Gäbe es in diesem Beispiel verschiedene Personen in beiden DataFrames, dann enthält der zusammengesetzte DataFrame nur die Daten derjenigen Personen, die in beiden DataFrames vorkamen. Sollen dennoch alle Fälle verwendet werden, kann dies durch Hinzufügen des Parameters all = TRUE erzwungen werden. Die nicht vorhandenen Werte werden dann als fehlend gekennzeichnet.

Heißen die Indikatorvariablen in beiden DataFrames verschieden, kann statt by eine genauere Spezifikation mit by.x und by.y angegeben werden, wobei damit jeweils die Variablennamen im ersten und zweiten DataFrame gemeint sind:

daten.neu <- merge(daten.teil1,       # 1. Datei
                   daten.teil1_neu,   # 1. Datei
                   by.x = "vp",       # Indikatorvariabe der 1. Datei
                   by.y = "nr")       # Indikatorvariabe der 2. Datei

Falls es keine Indikatorvariable gibt, kann auch die Funktion cbind() verwendet werden, um neue Spalten (also Variablen) zu einem bestehenden Datensatz hinzuzufügen. Allerdings muss hier sehr darauf geachtet werden, dass z.B. die Versuchspersonen in beiden Datensätzen in der selben Reihenfolge sind!

3.2.4 Auswahl von Fällen

Ein sehr häufig gebrauchter Fall ist, dass wir nur eine Auswahl bestimmter Fälle betrachten wollen. Wir gehen im folgenden Beispiel davon aus, dass der DataFrame daten die oben zusammengesetzen Daten der \(n=8\) Personen umfasst. Wir wollen nun ein DataFrame subdaten erstellen, welches nur die Daten von Personen unter 20 Jahren enthält.

Eine erste Möglichkeit dazu bietet die eckige Klammer, die auch schon vorher zur Auswahl spezifischer Zeilen und Spalten in Vektoren und Matrizen verwendet wurde. Wenn ein logischer Vergleich in diese Klammer eingesetzt wird, dann werden nur die Zeilen ausgegeben, für die der resultierende Bool-Vektor den Wert TRUE annimmt, – also die dort formulierte Bedingung zutrifft:

subdaten <- daten[daten$alter < 20,]
subdaten

Etwas genauer beinhaltet die Indizierung innerhalb der eckigen Klammer zwei Ausdrücke, die durch ein Komma getrennt sind. Hier ist die Indizierung genau wie bei einer Matrix: Wir wollen alle Zeilen extrahieren, auf die alter < 20 zutrifft, von diesen Zeilen aber alle Spalten. Daher steht hinter dem Kopf wieder nichts.

Eine andere Möglichkeit bietet die Funktion subset(), der Sie einerseits das DataFrame übergeben und dann als Argument subset einen logischen Ausdruck, in unserem Fall also alter < 20. Ausgewählt werden dann alle Fälle, auf die dieser Ausdruck zutrifft:

subdaten.3 <- subset(daten, 
                     subset = alter < 20)        # welche Fälle/Zeilen
subdaten.3

Die Funktion subset() kann auch leicht dazu verwendet werden, einzelne Variablen aus einem DataFrame zu extrahieren. Dazu geben Sie einfach die Namen der gewünschten Variablen (in einem Vektor) dem Argument select an (wenn das Argument subset weggelassen wird, werden alle Fälle ausgewählt):

subdaten.4 <- subset(daten,
                     subset = alter < 20,         # welche Fälle/Zeilen
                     select = c(vp, geschlecht))  # welche Variablen/Spalten
subdaten.4

3.2.5 Umkodieren von Variablen

In Kapitel 2 haben wir bereits gelernt, wie einem DataFrame eine neue Variable hinzugefügt wird. Häufig brauchen wir auch eine Möglichkeit, Werte einer Variablen in andere Werte einer neuen Variablen umzukodieren.

Nehmen wir an, wir wollen die Variable alter so in eine neue Variable altersstufe umkodieren, dass nur noch die Unterscheidungen jung (\(<20\) Jahre), mittel (20 oder älter, aber jünger als 40) und alt (\(\geq 40\) Jahre) vorliegen. Im Wesentlichen geht dies ganz ähnlich wie die Auswahl über Indizierung, nur dass wir hier den ausgewählten Fällen einen Wert entsprechend zuweisen:

daten$altersstufe <- ""              # neue String-Variable anlegen
# und dann die Werte der Variable alter auf die angelegte Variable umkodieren
daten$altersstufe[daten$alter < 20] <- "jung"
daten$altersstufe[(daten$alter >= 20) & (daten$alter < 40)] <- "mittel"
daten$altersstufe[daten$alter >= 40] <- "alt"
daten

Zudem bietet das Paket car eine Funktion namens recode(), die zum gleichen Zweck verwendet werden kann und wie folgt verwendet wird:

library(car)
daten$altersstufe.2 <- recode(daten$alter,
                              "1:19 = 'jung'; 20:39 = 'mittel'; 40:100 = 'alt'" )
daten

Ist nur eine Umkodierung in 2 Stufen beabsichtigt, können wir schließlich noch auf die Funktion ifelse() zurückgreifen. Dieser Funktion wird eine Bedingung übergeben und dahinter zwei Werte: Wenn die Bedingung TRUE ist, bekommt die neue Variable den ersten Wert, ansonsten den zweiten Wert. Um z.B. alle Personen unter 40 Jahren als jung und alle anderen als alt zu kodieren, kann folgende Zeile verwendet werden:

daten$alterstufe.3 <- ifelse(daten$alter < 40,   # Bedingung
                             "jung",             # wenn TRUE
                             "alt")              # wenn FALSE
daten

3.2.6 Daten aggregieren

In vielen Reaktionszeitexperimenten, aber auch anderen Studien. kommt es vor, dass von einer Versuchsperson in den (within-subject) Bedingungen mehrere Messwerte vorliegen, also z.B. die Reaktionszeiten in den zahlreichen Durchgängen eines Experimentes. Ein erster Schritt ist dann oft, den Mittelwert (oder manchmal auch den median) dieser Werte zu bestimmen, sodass für jede Versuchsperson und Bedingung nur ein Wert übrig bleibt. Dies nennt man “Daten aggregieren”. Im Datensatz rohdaten des nächsten Beispiels liegen von drei Versuchspersonen jeweils 5 Messungen in den beiden Bedingungen “kongruent” und “inkongruent” vor:

rohdaten             # Beispieldaten

Eine nützliche Funktion zur Datenaggregation ist aggregate(). Das folgende Beispiel mittelt die Werte der Variablen rt für alle Kombinationen die sich aus vp und bedingung ergeben.

aggregiert.mw <- aggregate(rt ~ vp + bedingung,  # Werte und Zellen der Aggregation
                           data = rohdaten,      # welcher Datensatz?
                           FUN = mean)           # welche Funktion?
aggregiert.mw

Dem Argument FUN = ... können prinzipiell alle Funktionen übergeben, die dann auf die Daten angewendet werden. Wollen Sie bspw. den Median der Werte berechnet haben, dann wäre die Übergabe FUN = median.

3.2.7 Long- vs. wide-Format von Daten

Daten können grundsätzlich in zwei verschiedenen Formaten auftreten, nämlich dem sog. wide-Format und dem sog. long-Format. Im wide-Format steht jede Zeile für einen Fall, also z.B. eine Versuchsperson. Wenn es mehrere Messwerte von dieser Person unter verschiedenen Bedingungen gibt, werden diese in verschiedene Variablen derselben Zeile eingetragen. Zum Beispiel: Nehmen wir an, wir würden die (mittleren) Reaktionszeiten (in Millisekunden) von Versuchspersonen messen unter verschiedenen Ablenkungsbedingungen, nämlich (1) Musik, (2) Gespräch und (3) und ohne Ablenkung im Raum. Bei zwei Versuchspersonen würden die Daten im wide-Format wie folgt aussehen:

wide_data

Wide-Format Daten haben also eine Zeile pro Versuchsperson und legen für weitere Versuchsbedingungen Extra-Variablen an. Long-format Daten haben nur eine Variable für jede gemessene abhängige Variable (hier also Reaktionszeit) und die verschiedenen Bedingungen auf verschiedene Zeilen aufgeteilt und durch eine Gruppierungsvariable unterschieden. Beide Formate haben ihre Vor- und Nachteile.

Während R in den allermeisten Fällen Daten im long-Format bevorzugt, kann es aber sein, dass das wide-Format sinnvoller ist oder Sie Daten in dem einen Format erhalten und diese in das jeweils andere Format umwandeln wollen. Die einfachste Art Daten vom wide- ins long-Format zu bringen ist die Funktion pivot_longer() aus dem Paket tidyr.

library(tidyr)
long_data <- pivot_longer(wide_data,                    # wide-Format Daten
                          cols = c(2:4),                # welche Spalten
                          names_to = "condition",       # Gruppierungsvariable
                          values_to = "reaction_time")  # Name der abh. Variablen
long_data

pivot_longer() benötigt als Argumente einen Datensatz, die Spalten in denen die zu transformierenden Werte sind, einen Namen für die neue Variable, die die Namen der alten Variablen als Ausprägungen enthält (condition), und einen Namen für die neue Variable, die alle gemessenen Werte enthält (reaction_time).

Und um aus dem long-Format eine Datei im wide-format zu machen, gibt es die entsprechende Funktion pivot_wider() aus tidyr.

wide_data2 <- pivot_wider(long_data,                     # long-Format Daten
                          names_from = condition,        # Gruppierungsvariable
                          values_from = reaction_time)   # Name der abh. Variablen
wide_data2

4 Deskriptive Statistik und (einfache) Abbildungen mit R

Bisher haben wir uns mit Vorbereitungen der Datenanalyse beschäftigt (z.B. Laden von Daten und Auswahl von Teildatensätzen) und Grundlegendes über die Arbeit mit R gelernt. Nun wollen wir beginnen, Daten zu beschreiben. Dazu werden wir zunächst deskriptive Statistiken betrachten und danach einen Blick auf die Erstellung einfacher Abbildungen zur Visualierung von Daten mit R werfen.

4.1 Deskriptive Statistik

4.1.1 Häufigkeiten

Für die folgenden Beispiele laden wir die Datei hfgk.RData, die einen DataFrame mit dem Namen hfgk bereitstellt. In diesem Datensatz haben die Variablen var1, var2 und var3 jeweils 2, 3 und 4 Ausprägungen. Eine einfache Häufigkeitstabelle für eine Variable, z.B. hier var3, können wir mit der Funktion table() erhalten:

tab <- table(hfgk$var3)
tab
## 
##  1  2  3  4 
## 21 28 29 22

Auch zweidimensionale Kontingenztafeln können mit der Funktion table() erstellt werden, indem wir einfach beide Variablen übergeben:

tab <- table(hfgk$var1,
             hfgk$var2)
tab
##    
##      1  2
##   1 18 13
##   2 16 11
##   3 28 14

Mit solchen Tabellen können wir nun auch weiter arbeiten. Die Funktion addmargins() auf tab angewendet fügt die Randsummen hinzu und die Funktion prop.table() angewendet auf tab erstellt die Kontingenztafel mit relativen Häufigkeiten:

addmargins(tab)    # mit Randsummen
##      
##         1   2 Sum
##   1    18  13  31
##   2    16  11  27
##   3    28  14  42
##   Sum  62  38 100
prop.table(tab)    # relative statt absolute Häufigkeiten
##    
##        1    2
##   1 0.18 0.13
##   2 0.16 0.11
##   3 0.28 0.14

Verwenden wir bei prop.table() noch den Parameter margin, können wir auch die bedingten relativen Häufigkeiten berechnen:

addmargins(prop.table(tab,
                      margin = 1))  # bedingt nach Zeilen
##      
##               1         2       Sum
##   1   0.5806452 0.4193548 1.0000000
##   2   0.5925926 0.4074074 1.0000000
##   3   0.6666667 0.3333333 1.0000000
##   Sum 1.8399044 1.1600956 3.0000000
addmargins(prop.table(tab,
                      margin = 2))  # bedingt nach Spalten
##      
##               1         2       Sum
##   1   0.2903226 0.3421053 0.6324278
##   2   0.2580645 0.2894737 0.5475382
##   3   0.4516129 0.3684211 0.8200340
##   Sum 1.0000000 1.0000000 2.0000000

Prinzipiell können wir auch mehr als zwei Variablen berücksichtigen, allerdings wird die Ausgabe dann leider schnell unübersichtlich. Hier können wir besser die Tabellen mit xtabs() erstellen und mit ftable() umformatieren (prop.table(), …. sind auch hier wiederum anwendbar):

ftable(xtabs(~ var1 + var2 + var3,  # Häufigkeiten für die Kombination von?
             data = hfgk))          # in welchem Datensatz?
##           var3  1  2  3  4
## var1 var2                 
## 1    1          5  5  3  5
##      2          4  1  5  3
## 2    1          1  6  5  4
##      2          2  4  4  1
## 3    1          6 10  8  4
##      2          3  2  4  5

4.1.2 Maße der zentralen Tendenz

Für den Mittelwert und den Median von Daten gibt es in R eigene Funktionen. Zur Demonstration erstellen wir hier einen Vektor namens daten, die Funktionen können aber auch auf Variablen eines DataFrames angewendet werden:

daten <- c(4, 5, 3, 8, 6, 7, 8, 3, 6, 4, 2, 3, 8, 8, 8)
mean(daten)       # Mittelwert
## [1] 5.533333
median(daten)     # Median
## [1] 6

Für den Modus müssen wir einen kleinen Umweg gehen, indem wir zunächst mit der Funktion table() eine Häufigkeitstabelle erstellen und daraus dann den am häufigsten auftretenden Wert extrahieren:

tab <- table(daten)                    # Häufigkeitstabelle
tab
## daten
## 2 3 4 5 6 7 8 
## 1 3 2 1 2 1 5
as.numeric(names(which.max(tab)))      # Modus extrahieren
## [1] 8

Es lohnt sich allerdings, die Häufigkeitstabelle tatsächlich anzuschauen, da mit dieser Methode nur ein Modus (von mehreren möglichen) gefunden wird.

4.1.3 Maße der Datenvariabilität

Das wichtigste Maß der Datenvariabilität ist die Varianz, also die mittlere quadratische Abweichung der Datenpunkte von ihrem Mittelwert: \[S_X^2=\frac{1}{n}\sum_{i=1}^n(x_i-M_X)^2\] Zunächst berechnen wir hier die Varianz “von Hand” als Umsetzung der Formel in R-Code am Beispiel des gerade erstellen Vektors daten:

n <- length(daten)    # wieviele Datenpunkte gibt es?
varianz <- (1/n) * sum((daten-mean(daten))^2)
varianz
## [1] 4.648889

Natürlich gibt es auch eine R-Funktion zur Bestimmung der Varianz; allerdings kommt diese zu einem minimal anderem Ergebnis:

var(daten)
## [1] 4.980952

Dies liegt daran, dass var() eine “korrigierte Varianz” berechnet, die der erwartungstreue (und konsistente) Schätzer der Populationsvarianz \(\sigma^2\) ist. Der Unterschied ist, dass nicht durch \(n\), sondern durch \(n-1\) dividiert wird. Die korrigierte Version berechnet sich also als: \[\hat{S}_X^2=\frac{1}{n-1}\sum_{i=1}^n(x_i-M_X)^2\] Um das Ergebnis der Funktion var() in die “normale” Varianz umzurechnen, können wir unter Verwendung von \(n\) eine Korrektur anwenden und das gerade per Hand errechnete Ergebnis erhalten:

(n-1)/n * var(daten)
## [1] 4.648889

Die Standabweichung berechnen wir als Wurzel der Varianz, also als

standardabweichung <- sqrt(varianz)
standardabweichung
## [1] 2.156128

oder in der korrigierten Form mit der entsprechenden R-Funktion sd():

standardabweichung <- sd(daten)
standardabweichung
## [1] 2.231805

4.1.4 Allgemeinere Datenbeschreibungen

Es gibt natürlich auch Funktionen, mit denen wir direkt mehrere Maße zur Beschreibung von Daten gewinnen. Eine Möglichkeit bietet die Funktion summary(), die auf ziemlich viele R-Objekte angewendet werden kann und ihr Verhalten in Abhängigkeit davon ändert, auf was sie angewendet wird. Um einen schnellen Überblick über die Werte des Vektors daten zu bekommen, können wir z.B. schreiben:

summary(daten)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   2.000   3.500   6.000   5.533   8.000   8.000

Etwas ausführlicher und umfangreicher ist die Funktion describe() aus dem Paket psych, die neben bekannten Werten auch weitere Werte ausgibt:

library(psych)
describe(daten)

Beide Funktionen können wir auch auf DataFrames anwenden. Für die folgenden Varianten gehen wir davon aus, dass die Datei datensatz.RData geladen wurde, die einen DataFrame namens datensatz enthält:

head(datensatz,     # Die Funktion head() gibt die ersten...
     n = 3)         # ...(hier 3) Zeilen eines DataFrames aus

Die erste Variable vp bezeichnet die Nummer der Versuchsperson, die zweite, dritte und vierte Variable (var1, var2 und var3) sind dann jeweils Werte der Personen auf Variablen und die fünfte Variable gruppe gibt an, welcher von zwei Gruppen die Versuchsperson angehört. Die Funktion summary() kann dann ganz einfach verwendet werden – da uns aber nur die drei Variablen mit gemessenen Werten interessieren, wählen wir entsprechend diese drei Spalten aus:

summary(datensatz[2:4])
##       var1             var2            var3      
##  Min.   : 68.00   Min.   : 2.00   Min.   : 1.00  
##  1st Qu.: 89.75   1st Qu.:12.00   1st Qu.:33.89  
##  Median : 99.00   Median :23.50   Median :39.66  
##  Mean   :100.68   Mean   :24.46   Mean   :38.54  
##  3rd Qu.:112.25   3rd Qu.:36.25   3rd Qu.:44.88  
##  Max.   :143.00   Max.   :49.00   Max.   :49.61

Ganz ähnlich können wir auch die Funktion describe() verwenden:

describe(datensatz[2:4])

Im Beispieldatensatz haben wir ja zudem Personen aus zwei verschiedenen Gruppen. Wir können diese Informationen auch direkt einzeln für die beiden Gruppen erhalten, wenn wir die Funktion describeBy() aus dem Paket psych verwenden. Die Gruppierungsvariable(n) müssen wir hier dann als sog. Liste (daher hinter list()) übergeben:

describeBy(datensatz[2:4],          # welche Variablen?
           list(datensatz$gruppe))  # getrennt für die Ausprägungen von gruppe
## 
##  Descriptive statistics by group 
## : 1
##      vars  n   mean    sd median trimmed   mad min    max range  skew kurtosis
## var1    1 50 101.58 15.14 101.00  101.60 16.31  69 140.00 71.00  0.08    -0.49
## var2    2 50  24.22 14.76  22.50   23.88 20.76   2  49.00 47.00  0.13    -1.39
## var3    3 50  38.73  7.88  39.68   39.26  8.17   2  48.83 46.83 -1.89     6.96
##        se
## var1 2.14
## var2 2.09
## var3 1.11
## ------------------------------------------------------------ 
## : 2
##      vars  n  mean    sd median trimmed   mad min    max range  skew kurtosis
## var1    1 50 99.78 16.15  99.00   99.08 17.79  68 143.00 75.00  0.36    -0.32
## var2    2 50 24.70 14.13  24.00   24.60 16.31   2  49.00 47.00  0.10    -1.14
## var3    3 50 38.35  9.34  39.46   39.53  8.15   1  49.61 48.61 -2.23     6.51
##        se
## var1 2.28
## var2 2.00
## var3 1.32

Manchmal benötigen wir gruppenweise auch Informationen, die mit summary() oder describeBy() nicht bereitgestellt werden. Hier können wir auf die Funktion tapply() zurückgreifen, mit der wir eigene Zusammenfassungen erstellen können. Hier übergeben wir i.W. die zu beschreibende Variable, danach die Gruppierungsvariable und dann eine Funktion, die auf die einzelnen Teildatensätze angewendet werden soll. Um zu sehen, dass wir so auf die gleichen Werte kommen, wird im Beispiel einfach der Mittelwert auf der Variablen var1 getrennt für beide Gruppen berechnet:

tapply(X = datensatz$var1,        # welche Variable?
       INDEX = datensatz$gruppe,  # getrennt für die Ausprägungen von gruppe
       FUN = mean)                # welche Funktion soll verwendet werden?
##      1      2 
## 101.58  99.78

Darüber hinaus gibt es eine ganze Reihe anderer Funktionen, die teilweise sehr ausführliche Beschreibungen von Daten liefern. Ein Beispiel hierfür ist die Funktion dfSummary() aus dem Paket summarytools.

4.2 (Einfache) Abbildungen mit R

R verfügt über sehr umfangreiche Fähigkeiten, hochwertige Grafiken zu erstellen. Grundsätzlich können wir zwischen base-, lattice- und ggplot2-Abbildungen unterscheiden; wir werden hier einfache base- bzw. lattice-Abbildungen erstellen. ggplot2 ist das wohl mächtigste Paket zur Erstellung von Abbildungen, es benutzt aber eine eigene Syntax und wird daher in einem gesonderten Teil in Verbindung mit tidyverse behandelt werden (der aber erst noch erstellt werden muss). Zahlreiche andere Einführungen und Beispiele für Abbildungen – oft samt Code – sind auch im Netz zu finden. Eine hervorragende Quelle (auch für die Inspiration zu verschiedenen Abbildungen) ist die Seite https://www.r-graph-gallery.com/.

Im Wesentlichen gibt es für viele Arten von Grafiken eine einfache Funktion, der u.a. die entsprechenden Daten übergeben werden und welche dann eine entsprechende Abbildung erstellt (in RStudio erscheinen die Abbildungen im Fenster rechts unten). Diese Funktionen haben eine Vielzahl von Parametern, um die Abbildungen zu verfeinern. Zusätzlich gibt es eine Reihe von Funktionen, mit denen Elemente zu Grafiken hinzugefügt werden können (z.B. Legenden, Text, Punkte, Pfeile, …).

4.2.1 Typische Parameter

Einige Parameter gibt es für fast alle Arten von Grafiken und wir werden die wichtigsten hier kurz besprechen. Übersichten über Parameter gibt es auch hier: https://www.r-graph-gallery.com/cheatsheet/ und hier http://publish.illinois.edu/johnrgallagher/files/2015/10/BaseGraphicsCheatsheet.pdf. Zudem liefert die R-Hilfe mit ?par eine Übersicht.

Beginnen wollen wir mit zwei Arten von Parametern die i.d.R. für mehrere Abbildungen gleichzeitig eine Rolle spielen. Zum Einen wollen wir manchmal den äußeren Rand von Abbildungen variieren. Dies geschieht mit dem Parameter mar, dem ein Vektor mit vier Werten übergeben wird, der die Anzahl von Linien am Rand unten, links, oben und rechts spezifiziert. Der Standard ist hier mar = c(5,4,2,2)+0.1.

Standardmäßig plottet R immer eine Abbildung. Um mehrere Plots zu kreieren, können wir die Parameter mfrow bzw. mfcol benutzen, denen ein Vektor mit der Anzahl der Zeilen und der Spalten übergeben wird, die eine Matrix definieren in der jede Zelle dann mit einer eigenen Abbildung gefüllt wird. Wollen wir also zwei Abbildungen nebeneinander plotten, wäre dies möglich mit mfrow = c(1,2) oder mfcol = c(1,2). (Aufgabe: Finden Sie heraus, was der Unterschied zwischen beiden Parametern ist!)

Beide Arten von Parametern (sowie andere Parameter aber auch) können übergreifend gesetzt werden und gelten dann für alle folgenden Abbildungen. Wollen wir z.B. 6 Abbildungen in 3 Zeilen und 2 Spalten plotten und die Ränder etwas vergrößern, dann geht das mit:

par(mar = c(6,6,3,3), mfrow = c(3,2))

Verwenden wir mfrow werden die entstehenden Plots zeilenweise aufgefült, bei mfcol spaltenweise.

Andere Parameter, die typischerweise beim Aufruf einer bestimmten Funktion zur Erstellung einer Grafik gesetzt werden, sind (manche Parameter gelten nur für manche Plot-Funktionen):

  • main: Ein String mit der Überschrift einer Grafik.
  • xlab und ylab: Jeweils ein String mit der Beschriftung der \(x\)- und \(y\)-Achse
  • xlim und ylim: Jeweils ein Vektor mit Minimum und Maximum der geplotteten \(x\)- und \(y\)-Achse
  • axes: Bool-Variable, die angibt, ob Achsen geplottet werden sollen (Standard; TRUE) oder nicht (FALSE)
  • plot: Bool-Variable, die angibt, ob der Plot angezeigt werden soll (Standard; TRUE) oder nicht (FALSE)
  • lty und lwd: eine Zahl, die den Typ und die Stärke von Linien angibt
  • pch: eine Zahl, die den Typ von Punkten angibt
  • type: ein String, der für die Funktion plot() die Art des Plots spezifiziert ("p" = nur Punkte, "l" = Linien, "b" = Punkte und Linien, "s" = Treppenstufen, …)
  • col: ein String, ein numerischer Wert oder ein String- oder numerischer Vektor, mit dem die Farben angegeben werden, die verwendet werden sollen. Farben können im RGB-Raum mit der Funktion rgb() an col übergeben werden, wobei hier auch die Transparenz der Farben einstellbar ist.
  • add: Bool-Variable, die angibt, ob ein neuer Plot erstellt werden soll (Standard; FALSE) oder der Plot dem aktuellen hinzugefügt werden soll (TRUE)

Die Größe aller oder einzelner Objekte wird bei R immer relativ mit dem Parameter cex angegeben (für character expansion):

  • cex: alle Elemente
  • cex.lab: Achsenbeschriftung
  • cex.axis: Achsenwerte
  • cex.main: Titel

4.2.2 Nützliche Funktionen für die Verfeinerung von Abbildungen

Mit verschiedenen weiteren Funktionen können Abbildungen ergänzt werden. Im Folgenden werden einige solcher Funktionen kurz beschrieben und weiter unten bei den Beispielen dann eingesetzt (mehr Informationen gibt es immer mit der R-Hilfe):

  • points(): Fügt Punkte zu einer Abbildung an den angegebenen \(x\)- und \(y\)-Koordinaten hinzu.
  • segments(): Fügt Liniensegmente von einem Start- zu einem Endpunkt hinzu.
  • arrows(): Fügt Pfeile von einem Start- zu einem Endpunkt hinzu.
  • text(): Fügt Text an den angegebenen \(x\)- und \(y\)-Koordinaten hinzu.
  • axis(): Wenn im Plot `axes = FALSE´ gesetzt wird, können hiermit die Achsen manuell hinzugefügt werden.
  • legend(): Fügt eine Legende zu einer Abbildung hinzu.

4.2.3 Speicherung von Abbildungen

Wenn wir RStudio verwenden, werden die Grafiken in das Fenster rechts unten geplottet. Mittels des Feldes Export können wir dann Grafiken speichern und in den Zwischenspeicher kopieren. Dies ist eine einfache Möglichkeit; die Qualität der Abbildungen kann aber deutlich erhöht werden, wenn wir die dazu gedachten Funktionen zum speichern nutzen.

Diese Funktionen haben in der Regel den selben Aufbau: Am Anfang steht ein Funktionsaufruf, der das Dateiformat, die Größe und die gewünschte Auflösung spezifiziert und ein sog. Grafik-Device öffnet. Danach folgen dann die Zeilen, die die Abbildung erstellen und beendet wird das Ganze, indem das Device geschlossen wird. Am Beispiel einer jpg-Abbildung würde das so aussehen:

jpeg(filename = "beispielname.jpg"), width = 600, height = 400, res = 150)
#
# ... hier folgen die Zeilen, die die Abbildung erstellen
#
dev.off()  # und das Device wird wieder geschlossen

Andere Formate werden z.B. mit den Funktionen bmp(), tiff() oder auch pdf() erstellt.

4.2.4 Mathematische Schreibweisen in Plots

Manchmal ist es nötig, in Abbildungen mit mathematischen Zeichen und Formeln zu arbeiten. Dazu eignen sich Strings nur sehr bedingt, aber wir können dann die Funktion expression() nutzen, die nach einer bestimmten Syntax Formeln und andere Zeichen erlaubt. Genauere Informationen über die Syntax finden Sie unter https://stat.ethz.ch/R-manual/R-devel/library/grDevices/html/plotmath.html. Ein Beispiel für die Verwendung von expression() finden Sie in Abschnitt 3.2.2 (Liniendiagramme). Eine andere Möglichkeit besteht in der Verwendung von bquote().

4.3 Beispiele für typische Abbildungen

4.3.1 Scatterplots

Scatterplots sind die typischen Darstellungen von Punktewolken, wenn z.B. bei \(n=100\) Versuchspersonen die zwei Variablen \(X\) und \(Y\) gemessen wurden. Dabei steht jeder Punkt dann für eine Person. Für das folgende Beispiel ziehen wir 50-mal einen zufälligen Wert zwischen 1 und 50, je einmal für die Variable \(X\) und einmal für die Variable \(Y\) und plotten diese dann erst einmal ohne weitere Parameter:

X <- sample(x = c(1:50),      # 100 zufällige Vektor aus dem Bereich 1-50 ziehen
            size = 100,       # und als Variable X speichern (ist ein Vektor mit
            replace = TRUE)   # 100 Elementen)
Y <- sample(x = c(1:50),      # 100 zufällige Vektor aus dem Bereich 1-50 ziehen
            size = 100,       # und als Variable Y speichern (ist ein Vektor mit
            replace = TRUE)   # 100 Elementen)
plot(X, Y)                    # einfachen Scatterplot erstellen

Um diesen Plot schöner zu gestalten, können wir nun einzelne Parameter nutzen und die Abbildung ergänzen:

# Plot ohne Achsen, rote Punkte
plot(X, Y,                                     # Daten
     main = "Ein hübscherer Scatterplot",      # Titel
     xlab = "Variable X",                      # Beschriftung x-Achse
     ylab = "Variable Y",                      # Beschriftung y-Achse 
     axes = FALSE,                             # keine Achsen zeichnen
     col = "red",                              # Farbe der Punkte
     pch = 16,                                 # Typ der Punkte
     cex = 1.5)                                # Größenskalierung

# Nun fügen wir manuell die beiden Achsen hinzu... erst die x- und dann die y-Achse
axis(side = 1,                                 # 1 = x-Achse
     at = seq(from = 0, to = 50, by = 5),      # an welche Stellen sollen Labels? 
     labels = seq(from = 0, to = 50, by = 5))  # die Labels selber
axis(side = 2,                                 # 2 = y-Achse
     las = 2,                                  # dreht die Beschriftung um 90°
     at = seq(from = 0, to = 50, by = 10),
     labels = seq(from = 0, to = 50, by = 10))
abline(h = 0)                                  # horizontale Linie bei y = 0

4.3.2 Liniendiagramme

Auch Liniendiagramme können mit der Funktion plot() geschaffen werden. Dazu müssen wir den Parameter type entsprechend setzen. Im Beispiel unten gehen wir davon aus, dass 2 Gruppen unter 3 verschiedenen Bedingungen untersucht worden sind. Die Werte der einen Gruppe malen wir direkt mit plot(), die Werte der anderen Gruppe fügen wir anschließend mit points() hinzu:

x <- c(1, 2, 3)      # Werte der x-Achse
y1 <- c(6, 8, 9)     # Gruppe 1, y-Werte
y2 <- c(4, 6, 5)     # Gruppe 2, y-Werte

plot(x, y1,          # Daten
     type = "b")     # "b" = both (Punkte und Linien)

points(x, y2,        # fügt Punkte und Linen für Gruppe hinzu
       type = "b")   

Auch hier ändern wir diesen wenig schönen Standardplot nun entsprechend ab, um ihn zu ergänzen und ansehnlicher zu gestalten. Unter anderem müssen wir hier die Skalierung der \(y\)-Achse mit ylim ändern, damit die Werte von Gruppe 2 überhaupt zu sehen sind. Zur Illustration tragen wir zudem noch einen – zugegebenermaßen sinnfreien – Text unter Nutzung von expression() ein:

x <- c(1, 2, 3)      # Werte der x-Achse
y1 <- c(6, 8, 9)     # Gruppe 1, y-Werte
y2 <- c(4, 6, 5)     # Gruppe 2, y-Werte

plot(x, y1,
     type = "b",
     main = "Ein hübscherer Plot",
     xlab = "Bedingung", 
     ylab = "Variable Y",
     axes = FALSE,
     ylim = c(0,10))

points(x, y2,
       type = "b",
       pch = 19)     # Punkte der Gruppe 2 ausgefüllt in schwarz

# Achsen hinzufügen und beschriften
axis(side = 1,
     at = x,
     labels = x)
axis(side = 2,
     las = 2,
     at = seq(from = 0, to = 10, by = 2), 
     labels = seq(from = 0, to = 10, by = 2))
abline(h=0)

# Legende hinzufügen 
legend(x = 1, y = 10,                         # Koordinaten der Legende
       legend = c("Gruppe 1", "Gruppe 2"),    # Beschriftung
       pch = c(1, 19),                        # Typen der Punkte
       bty = "n")                             # entfernt Rahmen um Legende

# Text mit mathematischem Ausdruck einfügen
beispieltext <- expression(plain("Die Wurzel von 2 schreibt man als ")~sqrt(2))
text(x = 2, y = 2,     # Koordinaten
     beispieltext)     # Text

4.3.3 Balkendiagramme

Balkendiagramme werden mit der Funktion barplot() erstellt, der die Höhe der jeweiligen Balken als Vektor (oder als Matrix) übergeben wird. Im folgenden Beispiel gehen wir davon aus, dass es drei verschiedene Bedingungen gibt:

werte <- c(3, 5, 4)    # Werte auf der abhängigen Variablen
barplot(werte)         # einfachstes Balkendiagramm

Auch hier können wir die Abbildung wieder verfeinern – beachten Sie zudem, dass die Funktion barplot() etwas zurückgibt, was wir in der Variable mp speichern:

werte <- c(3, 5, 4)    # Werte auf der abhängigen Variablen
mp <- barplot(werte,
              main = "Hübscheres Balkendiagramm",    # Titel
              axes = FALSE,                          # keine Achsen
              ylim = c(0,7),                         # Skalierung y-Achse
              xlab = "Bedingung",                    # Beschriftung x-Achse
              ylab = "Variable Y")                   # Beschriftung y-Achse

# was ist mp? -> enthält die x-Koordinaten der Mittelpunkte der Balken
# und wird nun genutzt, um die Balken an der x-Achse zu beschriften
axis(side = 1,                       # x-Achse
     at = mp,                        # an die Mittelpunkte der Balken
     labels = c("A", "B", "C"))      # die Labels an sich
axis(side = 2,                       # y-Achse
     las = 2,  
     at = c(0:7),
     labels = c(0:7))
abline(h=0)                          # horizontale Linie bei y = 0

Nun erweitern wir das Balkendiagramm indem wir uns vorstellen, zwei Gruppen von Personen hätten an den drei Bedingungen teilgenommen, d.h. wir haben am Ende sechs Werte die geplottet werden und zudem gruppiert werden sollen: Die zwei Gruppen innerhalb jeder Bedingung sollen nebeneinander stehen, während etwas Platz zwischen den Bedingungen sein soll. Die Werte müssen nun als Matrix an barplot() übergeben werden:

werte <- rbind(c(3, 5, 4),    # Werte der Gruppe 1
               c(4, 5, 3))    # Werte der Gruppe 2
mp <- barplot(werte,
              main = "Hübscheres Balkendiagramm mit Farbe",
              beside = TRUE,            # sorgt für die Gruppierung
              axes = FALSE,
              ylim = c(0,7),
              xlab = "Bedingung",
              ylab = "Variable Y",
              col = c("blue", "green")) # Farben der Gruppen 

# was ist mp? -> Ist nun eine Matrix mit den 6 x-Koordinaten der 6 Balken: um jeweils
# die Mitte zu finden, können wir die spaltenweisen Mittelwerte mit colMeans() nutzen
axis(side = 1, 
     at = colMeans(mp),          # jeweilige Mitten der Gruppen
     labels = c("A","B","C"))    # Labels an sich
axis(side = 2,
     las = 2,
     at = c(0:7),
     labels = c(0:7))
abline(h=0)

# Legende hinzufügen
legend(x=1 ,y=7,
       legend=c("Gruppe 1", "Gruppe 2"),    # Beschriftung
       fill = c("blue","green"),            # malt farbige Boxen davor
       bty = "n")

Probieren Sie selber aus, was sich verändert, wenn Sie in diesem Fall beside = FALSE setzen. Das Diagramm an sich wird dann gezeichnet, beim Hinzufügen der \(x\)-Achse wird aber ein Fehler ausgegeben werden, da mp nur noch ein Vektor ist, die Funktion colMeans() aber mindestens eine \(2\times 2\)-Matrix benötigt. Dies müssen Sie dann anpassen.

4.3.4 Histogramme

Histogramme werden zur Visualisierung von absoluten oder relativen Häufigkeiten benutzt, wobei die einzelnen Datenwerte in Klassen eingeteilt werden. Für das folgende Beispiel werden zunächst 300 Zahlen zwischen 1 und 50 gezogen und dann durch weitere 300 Zahlen zwischen 30 und 50 ergänzt. Zunächst übergeben wir die Daten an die Funktion hist() zur Erzeugung eines einfachen Histogramms:

daten <- sample(x = c(1:50),               # 300 Zahlen zwischen 1 und 50
                size = 300,
                replace = TRUE)
daten <- c(daten, sample(x = c(30:50),     # plus 300 weitere Zahlen zwischen
                         size = 300,       # 30 und 50
                         replace = TRUE) )
hist(daten)            # einfachstes Histogramm

Natürlich können wir auch Histogramm modifizeren und die Funktion hist() gibt auch interessante Werte zurück. Ein erster wichtiger Punkt betrifft die Frage, zu wievielen Klassen die Daten zusammengefasst werden sollen. Standardmäßig verwendet hist() die sog. Sturges-Regel zur Bestimmung der Klassen-Anzahl; wir können aber auch andere Algorithmen, Vektoren mit konkreten Klassengrenzen oder einfach die Anzahl der zu bildenden Klassen dem Parameter breaks übergeben. Mit labels = TRUE bewirken wir, dass die konkreten Fallzahlen pro Klasse angezeigt werden und die Rückgabe von hist() speichern wir in der Variable hist_werte:

hist_werte <- hist(daten,
                   breaks = 20,
                   labels = TRUE,
                   col = "grey50",
                   ylim = c(0,140),
                   axes = FALSE,
                   main = "Histogramm",
                   xlab = "Wertebereich",
                   ylab = "absolute Häufigkeit")

# in hist_werte stehen nun verschiedene Informationen über das erstellte
# Histogramm, u.a. die Klassengrenzen (hist_werte$breaks) und die Häufigkeiten
# (hist_werte$counts) -> diese können wir nun weiterverwenden. Wenn bei axis()
# keine Labels verwendet werden, verbaut R diejenigen, die es für die passendsten
# Labels hält
axis(side = 1, 
     at = hist_werte$breaks) # x-Achse an den Klassengrenzen orientiert
axis(side = 2, 
     las = 2)
abline(h = 0)

Übungsaufgabe: Laden Sie den Datensatz datensatz.RData und erstellen Sie nebeneinander die beiden Histogramme für die Variablen var1 und var2! Die Daten sollen dazu jeweils in 12 (var1) bzw. 5 (var2) Klassen eingeteilt werden, die Überschrift soll fehlen, die \(x\)-Achse mit dem Variablennamen und die \(y\)-Achse mit “absolute Häufigkeit” beschriftet werden. Die Farbe der Balken soll “rot” für var1 und “blau” für var2 sein. Eine Lösung finden Sie am Ende dieses Kapitels.

4.3.5 Tortendiagramme

Tortendiagramme werden häufig verwendet, um Anteile zu illustrieren. Wir nutzen hier zunächst die Funktion pie() um ein 2D-Tortendiagramm zu erstellen.

namen <- c("A", "B", "C", "D", "E")    # fünf verschiedene Objekte...
daten <- c(10, 12, 4, 16, 8)           # ...und ihre Werte 
prozent <- round(daten/sum(daten)*100) # Prozent der Werte an der
                                       # Gesamtsumme berechnen
# hier basteln wir uns Labels, indem mit der Funktion paste() Strings 
# aneinander gefügt werden
namen <- paste(namen, prozent)         # Prozentwerte zu den Namen hinzufügen
namen <- paste(namen,"%",sep="")       # das %-Zeichen noch anhängen

pie(daten,                             # Tortendiagramm malen
    labels = namen,                    # Beschriftungen
    col = rainbow(length(namen)),      # die ersten (fünf) Farben aus rainbow
    radius = 1)                        # Radius der des Kreises

Auch 3D-Tortendiagramme sind leicht möglich, wenn wir die Funktion pie3D() aus dem Paket plotrix nutzen. Im Wesentlichen funktioniert die Funktion so wie pie() – wir speichern aber wiederum Werte die von pie3D() zurückgegeben werden und nutzen diese dann um die Labels separat mit der Funktion pie3D.labels() hinzuzufügen:

library(plotrix) 
namen <- c("A", "B", "C", "D", "E")    # fünf verschiedene Objekte...
daten <- c(10, 12, 4, 16, 8)           # ...und ihre Werte 
prozent <- round(daten/sum(daten)*100) # Prozent der Werte an der
                                       # Gesamtsumme berechnen
# hier basteln wir uns Labels, indem mit der Funktion paste() Strings 
# aneinander gefügt werden
namen <- paste(namen, prozent)         # Prozentwerte zu den Namen hinzufügen
namen <- paste(namen,"%",sep="")       # das %-Zeichen noch anhängen

werte <- pie3D(daten,                  # 3D Diagramm zeichnen
               explode = 0.3,
               radius = 1.3,
               col = rainbow(length(namen)))
pie3D.labels(radialpos = werte,        # Legende hinzufügen
             labels = namen,
             radius = 1.6)

4.3.6 Boxplots

Viele der deskriptiven Statistiken können mit sog. Boxplots visualisiert werden. Wir werden dazu nun die Variablen var2 und var3 des DataFrames datensatz (welches in datensatz.RData enthalten ist) nutzen. Um diese beiden Variablen gemeinsam als Boxplot zu visualieren, nutzen wir ganz einfach:

boxplot(datensatz[3:4])     # Boxplot der 3. und 4. Variable aus datensatz

In solchen Boxplots markiert die dicke horizontale Linie den Median und die untere und obere Grenze der Box sind das 1. und das 3. Quartil. Die gestrichelten vertikalen Linien sind diejenigen Werte, die maximal 1.5-mal den Interquartilabstand entfernt von der Box liegen. Für die Variable var3 sehen wir außerdem 3 Kreise unten: Dies sind Werte die mehr als 1.5-mal den Interquartilabstand von der Box enfernt liegt und oft als “Ausreißer” bezeichnet werden.

Wir können auch recht leicht Boxplots einer Variable, aber getrennt nach z.B. Gruppen erstellen. Der folgende Aufruf ist zu lesen als “Die Variable var2 als Funktion der Variable gruppe”:

boxplot(datensatz$var2 ~ datensatz$gruppe)

Alle weiteren Parameter und Ergänzungen können bei Boxplots ähnlich verwendet werden, wie in den vorangegangen Beispielen.

4.3.7 Lösung zur Übungsaufgabe

Eine mögliche Variante sieht so aus:

par(mfrow = c(1,2))
hist(datensatz$var1,
     breaks = 12,
     main = "",
     xlab = "var1",
     ylab = "absolute Häufigkeit",
     col = "red")
hist(datensatz$var2,
     breaks = 5,
     main = "",
     xlab = "var2",
     ylab = "absolute Häufigkeit",
     col = "blue")

5 Programmkontrolle: Konditionalabfragen und Schleifen

Bisher sind wir einfach davon ausgegangen, dass R beginnend mit der ersten Zeile sequenziell alle Zeilen eines Skriptes der Reihe nach abarbeitet. Es gibt aber Fälle, wo wir genau dies nicht wollen, sondern vielleicht ein Teil nur dann ausgeführt werden soll, wenn ein bestimmtes Ereignis eingetreten ist, eine Variable einen bestimmten Wert hat, … und ansonsten übersprungen werden soll. Oder, in Abhängigkeit davon welchen Wert eine Variable hat, wollen wir den einen oder einen anderen Code abarbeiten. So etwas kann mit Konditionalabfragen geschehen. Darüber hinaus wollen wir manchmal auch Code einfach wiederholen. Zu diesem Zweck können wir Schleifen verwenden.

Konditionalabfragen und Schleifen sind auch wichtige Bestandteile zur Programmierung von R, wenn wir Simulationen durchführen wollen.

5.1 Konditionalabfragen und logische Verknüpfungen

Die wichtigste Art, die Ausführung von Code von bestimmten Ereignissen oder Werten einer Variablen abhängig zu machen, ist mit Hilfe von if-Abfragen. Eine solche Abfrage besteht i.W. aus einer Bedingung die wahr oder falsch sein kann (TRUE oder FALSE): Wenn diese Bedingung TRUE ist, wird der folgende Code ausgeführt; ansonsten nicht. Der ggf. ausuführende Code ist dabei in geschweiften Klammern abgegrenzt. Ein ganz einfaches Beispiel ist das folgende:

a <- 2              # Variable, deren Wert gleich getestet wird
if (a == 2) {       # Abfrage...
  print("Dieser Code wird ausgeführt!")   # ...wird nur ausgeführt, wenn TRUE
}
## [1] "Dieser Code wird ausgeführt!"

Da die Bedingung a == 2 wahr ist, also TRUE, wird der Code innerhalb der folgenden geschweiften Klammern ausgeführt. Geben Sie a nun einen anderen Wert, dann wird der Code nicht ausgeführt. Mehrere Abfragen geschehen, indem if erweitert wird zu else if, und eine “Restkategorie”, die also zutrifft wenn keine der vorab genannten Bedigungen zutrifft, kann mit else eingeführt werden. Probieren Sie auch hier andere Werte für a aus und schauen, wie sich das kleine Programm verhält!

a <- 3                            # Variable deren Wert gleich getestet wird
if (a == 2) {                     # Abfrage 1: a == 2?
  print("a hat den Wert 2!")      # ...dann hier rein
} else if (a == 3) {              # Abfrage 2: a == 3?
  print("a hat den Wert 3!")      # ...dann hier rein
} else {                          # ansonsten...
  print("a ist weder 2 noch 3!")  # ...hier rein
}
## [1] "a hat den Wert 3!"

Prinzipiell können if-Abfragen auch beliebig verschachtelt werden und auch verschiedene Arten von Variablen können in die Bedingungen mit eingehen:

geschlecht <- "w"
alter <- 30

# Beispiel für zwei ineinander geschachtelte Abfragen
if (geschlecht == "w") {         # "äußere Abfrage"
  if (alter >= 25) {             # "innere Anfrage"
    print("Es handelt sich um eine weibliche VP von mindestens 25 Jahren!")
  } else if (alter < 25) {
    print("Es handelt sich um eine weibliche VP, die jünger als 25 ist!")
  }
} else {                         # "else" zu "äußerer Abfrage"
  print("Es handelt sich nicht um eine weibliche VP!")
}
## [1] "Es handelt sich um eine weibliche VP von mindestens 25 Jahren!"

Verschachtelte Abfragen können alternativ auch mit logischen Ausdrücken wie und und oder verknüpft werden, um kompakter formuliert zu werden:

geschlecht <- "m"
alter <- 22 
if ( (geschlecht == "m") & (alter >= 25) ) {
  print("Es handelt sich um eine männliche VP von mindestens 25 Jahren!")
} else if ( (geschlecht == "m") & (alter < 25) ) {
  print("Es handelt sich um eine männliche VP, die jünger als 25 ist!")
} else {
  print("Es handelt sich nicht um eine männliche VP!")
}
## [1] "Es handelt sich um eine männliche VP, die jünger als 25 ist!"

Die Klammern um die einzelnen Bedingungen sind hier nicht zwingend nötig; ihre Verwendung macht aber erstens das Lesen komplexer Ausdrücke einfacher und zweitens können Klammern auch verwendet werden, um zu bewirken, dass zwei Bedingungen zunächst gemeinsam evaluiert werden und dann in Relation zu einer anderen Bedingung gesetzt werden. Zudem bietet sich bei derartigen Abfragen an, sich das “Einrücken” von Zeilen anzugewöhnen, um die Lesbarkeit zu erhöhen und Fehler zu vermeiden.

5.2 Wiederholungen von Code mit Schleifen

Mit Schleifen können Zeilen wiederholt werden. R bietet verschiedene Schleifen an. Wir beginnen mit repeat- und while-Schleifen und kommen dann zur wichtigen for-Schleife.

5.2.1 repeat- und while-Schleifen

Die erste Art von Schleife wiederholt Code einfach unendlich oft. Dazu beginnt ein Block mit dem Wort repeat und in geschweiften Klammern steht das, was wiederholt werden soll:

i <- 0
repeat {
  i <- i + 1    # Wert von i um eins erhöhen
  print(i)      # Wert von i ausgeben
}

Wenn wir diese Schleife allerdings ausführen, wird sie tatsächlich einfach immer wieder ausgeführt. Das ist natürlich i.d.R. sinnfrei, da wir eine Schleife auch wieder verlassen wollen. Dies können wir mit break erreichen: Dieser Befehl verlässt die Schleife und die Codeausführung wird hinter der schließenden geschweiften Klammer fortgesetzt. Wollen wir also z.B. die Schleife nur solange wiederholen, bis i den Wert 6 annimmmt, dann geht das wie folgt:

i <- 0
repeat {
  i <- i + 1         # Wert von i um eins erhöhen
  if (i == 6) {      # wenn i == 6 ist...
    break            # ...dann Schleife verlassen
  }
  print(i)           # Wert von i ausgeben
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5

break funktioniert in allen Schleifen, aber meistens können wir Abbrüche von Schleife auch eleganter bewirken. Zum Beispiel, können wir Schleifen mit dem Wort while einleiten. In diesem Fall wird in Klammern noch ein logischer Ausdruck übergeben und die Schleife wird sooft wiederholt, wie dieser Ausdruck TRUE ist:

i <- 0
while (i < 6) {
  i <- i + 1    # Wert von i um eins erhöhen
  print(i)      # Wert von i ausgeben
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4
## [1] 5
## [1] 6

Versuchen Sie sich klar zu machen, warum die repeat-Schleife bis 5 zählt, die while-Schleife aber bis 6 gezählt hat!

5.2.2 for-Schleifen

Die vielleicht wichtigste Variante einer Schleife sind for-Schleifen. Hierbei wird zusätzlich i.d.R. eine Variable bei jedem Durchlauf in ihrem Wert verändert und mit diesem Wert kann innerhalb einer Schleife auch gearbeitet werden. Wir benötigen dazu die veränderliche Variable (wir nennen diese im Beispiel i) und einen Vektor der angibt, welche Werte i der Reihe nach annehmen soll. Um z.B. die Zahlen von 1-4 auszugeben, können wir dies mit einer for-Schleife wie folgt tun:

for (i in c(1:4)) {
  print(i)
}
## [1] 1
## [1] 2
## [1] 3
## [1] 4

Die Schleife wird also vier-mal wiederholt und die Variable i nimmt nacheinander die Werte des Vektor c(1:4) an, der aus den zahlen 1, 2, 3 und 4 besteht. i könnten wir auch als Index verwenden, um auf die Elemente eines Vektors zuzugreifen:

geschlecht <- c("m", "w", "w", "d")  # Vektor aus Strings
for (i in c(1:4)) {
  print(geschlecht[i])               # i wird benutzt als Index für den Vektor
}
## [1] "m"
## [1] "w"
## [1] "w"
## [1] "d"

Wir haben hier jetzt immer eine numerische Variable verwendet, die ihren Wert verändert. Prinzpiell kann aber jeder Typ hier verwendet werden. Wollen wir die gleiche Ausgabe wie im letzten Beispiel erzeugen, können wir den Vektor geschlecht auch direkt in die for-Schleife verbauen:

for (i in geschlecht) {    # jetzt werden i die Elemente aus geschlecht...
  print(i)                 # ...zugewiesen und hier ausgegeben
}
## [1] "m"
## [1] "w"
## [1] "w"
## [1] "d"

5.2.3 Einlesen multipler Datensätze mit for-Schleifen

Häufig gibt es jeweils eine eigene Datei mit den Daten einer Versuchsperson. Das heißt, Sie haben so viele Dateien wie Sie Versuchspersonen haben. Mit Hilfe einer for-Schleife können wir aber sehr leicht diese Datensätze nacheinander einlesen und zu einem großen Datensatz zusammenfassen. Im Folgenden gehen wir davon aus, dass Sie bereits mit setwd() das Arbeitsverzeichnis auf den Ordner mit den Datenbeispielen gesetzt haben. In diesem Ordner gibt es einen weiteren Unterordner namens einlesen_mit_for, der vier Dateien enthält. Jede Datei enthält die Daten einer Versuchsperson. Der folgende Code liest diese vier Dateien nacheinander ein und fügt diese untereinander zusammen:

setwd("./einlesen_mit_for")     # der Punkt am Anfang steht für 'aktuelles Verzeichnis'

# dir() liefert einen Vektor mit den Dateien, die auf *.txt enden
dateinamen <- dir(pattern = "/*.txt", 
                  full.names = TRUE)

# in dateinamen stehen nun die Namen aller Dateien im Ordner die auf *.txt enden
# in der for-Schleife werden nacheinander die Elemente durchgegangen und der Variablen
# datei zugewiesen
daten <- NULL                            # leere Variable daten anlegen
for(datei in dateinamen) {      
  daten_vp <- read.table(datei,          # datei einlesen (eine VP)
                         header = TRUE,
                         sep = ";") 
  daten <- rbind(daten,           # "unten" an daten anhängen...
                 daten_vp)        # ...was gerade eingelesen wurde
}

Nun befinden sich in daten alle vier einzelnen Dateien untereinander und mit der eigentlich Auswertung könnte begonnen werden:

daten

5.2.4 Übungsaufgaben

  1. Nehmen Sie nun einmal an, es würde die Funktion sum() nicht geben. Nutzen Sie Ihre bisherigen Kenntnisse und eine for-Schleife, um für jeden beliebig-langen Vektor numerischer Variablen die Summe der Elemente zu berechnen.

  2. Eine Berechnung der Summe funktioniert nur, wenn alle Elemente numerisch sind. Modifizieren Sie die Schleife so, dass nicht-numerische Elemente im Vektor ignoriert werden und die Schleife trotzdem duchläuft.

  3. Zuletzt soll die Schleife so umgeschrieben werden, dass im Falle von nicht-numerischem Input irgendwo im Eingebevektor sofort eine Warnmeldung ausgegeben wird und die gesamte Schleife abbricht.

6 Funktionen

Bisher haben wir uns nur an bereits existierenden Funktionen bedient. R wird aber auch dadurch besonders flexibel durch das Schreiben eigener Funktionen. Das ist immer dann nützlich, wenn Sie innerhalb eines Skriptes dieselbe Rechnung viele Male ausführen möchten. Dann wird der Code übersichtlicher und einfacher zu schreiben, wenn Sie die wiederholte Rechnung in eine Funktion auslagern.

6.1 Struktur und Anwendung

Um eine Funktion zu programmieren müssen Sie einer bestimmten Struktur folgen, die der einer Schleife ähnelt. Im folgenden Code wollen wir eine Funktion schreiben, die zu einer Zahl 2 addiert. Diese Funktion soll den Namen addiere bekommen und das wichtige Schlüsselwort ist function.

addiere_2 <- function(zahl){
  return(zahl + 2)
}

Diese Funktion wird ein Element übergeben (zahl, der Name kann wie bei jedem Objekt frei ausgesucht werden), addiert dann 2 zu der übergebenden Zahl (zahl + 2) und gibt das Ergebnis als Rückgabe zurück. Eine Funktion liefert immer das zurück, was als letztes in ihr berechnet wurde, wenn wir es explizit machen wollen, dann können wir dazu am Ende einer Funktion das Schlüsselwort return verwenden (was sich oft auch der Lesbarkeit halber anbietet).

Wenn Sie diesen Code ausführen, wird zunächst kein Output erzeugt, sondern die Funktion wird in der Variablenumgebung definiert und kann von nun an benutzt werden. Aus diesem Grund können Sie Funktionen entweder am Anfang eines Skriptes definieren, in welchem auch der Rest des Codes steht, oder auch (was sauberer und oft handlicher ist) ein eigenes Skript für die neue Funktion schreiben; die Funktion quasi auslagern und von dort ausführen. Ist eine Funktion definiert worden, kann sie nun wie jede andere Funktion aufgerufen werden:

addiere_2(13)
## [1] 15

Einer Funktion können auch mehrere Elemente übergeben werden. Die folgende Funktion gibt bspw. aus, welche der beiden Zahlen die kleinere bzw. größere Zahl ist:

ordne_2 <- function(zahl1, zahl2){  # 2 Zahlen als Übergabe
  beide.zahlen <- c(zahl1, zahl2)   # beide Zahlen in einen Vektor
  
  print(paste0("kleinere Zahl: ",min(beide.zahlen)))  # Minimum der beiden Zahlen
  print(paste0("größere Zahl: ",max(beide.zahlen)))   # Maximum der beiden Zahlen
}

ordne_2(7, 3)
## [1] "kleinere Zahl: 3"
## [1] "größere Zahl: 7"

Die Rückgabe einer Funktion kann auch direkt in einer neuen Variable gespeichert werden und dann weiter verarbeitet werden, wie hier am Beispiel der Funktion addiere_2() gezeigt wird:

neu <- addiere_2(10)      # addiert 2 zu 10 und speichert das Ergebnis in neu
neu + 10                  # und dann wird 10 dazu addiert
## [1] 22

Eine Funktion kann immer nur eine Variable (bzw. ein Objekt) zurückgeben. Wollen wir mehrere Werte zurückgeben, müssen diese in einen Vektor zusammengefügt werden, der vom aufrufenenden Code dann entsprechend “zerlegt” wird. Wenn die Rückgabewerte von verschiedenen Typen sind, dann müsste eine Liste (list) verwendet werden.

6.2 Ein komplexeres Beispiel

Die folgende Funktion greift das Prinzip auf, die sum()-Funktion mit einer for-Schleife nachzubauen und in einer Funktion auszuführen. Dazu führen wir noch eine Erweiterung ein, nämlich übergeben auch einen Parameter mittelwert, der spezifiert, ob nur die Summe berechnet werden soll mittelwert = FALSE oder der Mittelwert berechnet werden soll (mittelwert = TRUE). Dieser Funktion werden also ein Vektor mit Werten sowie ein Parameter übergeben. Der jeweilige Rückgabewert ist dann die Summe oder der Mittelwert der Elemente des Vektors.

sum.mw <- function(daten, mittelwert = FALSE){   
  summe <- 0                        # Initieren der Output-Variable
  for(i in daten){                  # Schleife, geht alle Elemente von daten durch...
    summe <- summe + i              # ...und addiert i (das momentane Element) dazu
  }
  if (mittelwert == FALSE) {        # wenn nur Summe zurückgegeben werden soll
    return(summe)
  } else if (mittelwert == TRUE) {  # wenn der Mittelwert berechnet werden soll
    return(summe / length(daten))   # dann Mittelwert aus Summe berechnen
  }
}

beispiel <- c(3, 5, 1, 7, 2)
sum.mw(daten = beispiel, 
       mittelwert = FALSE)
## [1] 18
sum.mw(daten = beispiel, 
       mittelwert = TRUE)
## [1] 3.6

In Kopf der Funktion haben wir mit mittelwert = FALSE einen Default-Wert definiert, der für mittelwert angenommen werden soll, wenn er beim Aufruf nicht weiter spezifiziert wird. Wollen wir also nur die Summe erhalten, können wir das Argument auch komplett weglassen und es würde kurz auch der folgende Aufruf reichen:

sum.mw(beispiel)
## [1] 18

Alle selbstgeschriebenen Funktionen können auch innerhalb anderer Funktionen verwendet werden, z.B. in denen der apply-Familie oder der Funktion aggregate().

6.3 Übungsaufgaben

  1. Sie kennen bereits den Unterschied zwischen der einfachen und der korrigierten Varianz und die Funktion var() berechnet standardmäßig die korrigierte Varianz. Schreiben Sie eine Funktion die stattdessen die einfache Varianz berechnet.

  2. Schreiben Sie eine Funktion, der Sie numerische Werte übergeben und die entweder “warm” oder “kalt” zurückgibt, je nachdem ob der Übergabewert größer/gleich 37 oder kleiner als 37 ist.

  3. Schreiben Sie eine Funktion, die für einen numerischen Vektor nach Wahl der Benutzer:in entweder den Mittelwert, den Median oder den Modus berechnet (nicht alles gleichzeitig)! Sie können innerhalb der Funktion auch auf existierende Funktionen zurückgreifen.