2 Programmiersprache R
In diesem Kapitel wollen wir R als Programmierspache kennenlernen.
2.1 Variablen definieren
In einem ersten Schritt wollen wir eine Rechnung (einen Wert) in einer Variable speichern.
<- 2*4 var1
Unsere Variable mit dem Namen var1
(immer links in der Zeile) wird über den Zuweisungspfeil <-
(Tastenkürzel: option
+ -
) mit dem Ergebnis aus 2*4 (8)
definiert.
Zur Kontrolle können wir var1
mit den folgenden Befehlen aufrufen.
var1#> [1] 8
Man kann auch gleich bei der Definition der Variablen die Zuweisung in Klammern schreiben und das Resultat wird gleichzeitig in der Konsole angezeigt.
<- 2*4)
(var1 #> [1] 8
Tipp: Sollten Sie aus anderen Programmiersprachen schon Kenntnisse mitbringen und =
als Zuweisungsoperator kennen, empfehlen wir dennoch eindringlich mit <-
zu arbeiten. Funktionen in R arbeiten bei der Zuweisung von Argumenten mit dem Zuweisungsoperator =
. Folglich ist die Verwendung von <-
nicht nur besser zu lesen, sie kann auch Fehler vorbeugen.
2.1.1 Variablennamen
Eine Variable muss immer einen Namen haben. Sie darf aus Buchstaben, Zahlen und den Zeichen .
und _
bestehen. Dennoch muss sie immer mit einem Buchstaben beginnen und darf keine Leerzeichen enthalten.
Um lesbaren und verständlichen Code zu schreiben, gibt es ein paar Konventionen an die man sich halten sollte. Hier gibt es zwei gängige Möglichkeiten und eine ältere Form, um Variablen zu bennen.
Die Wahl ist egal, solange man konsequent bei einer Schreibweise bleibt!
1. snake_case_variable
Bei dieser Schreibform werden die einzelnen Worten in einer Variablenbezeichnung durch Unterstriche verbunden und alle Worte kleingeschrieben.
2. camelCaseVariable
Hier wird nur das erste Wort kleingeschrieben und die folgenden mit dem ersten Buchstaben großgeschrieben.
3. variable.with.perdiods (alt)
Die kleingeschriebenen Worte werden mit Punkten getrennt.
# Gute Bezeichnungen
efaKonstrukt1
efa_konstrukt_1
# Schlechte Bezeichnungen
Efa.konstrukt_1
efaKonstrukt_1
# Unmöglich
efa 1
2.2 Funktionen aufrufen
“Everything that exists is an object. Everything that happens is a function call.”
–John Chambers
Objekte haben wir schon in ihrer einfachsten Form als Rechenergebnis in einer Variable kennen gelernt. Um kompliziertere Objekte verstehen zu können, wollen wir uns erst den Funktionen (function calls) in R zuwenden.
Unsere Funktion sieht wie folgt aus (ein fiktionales Beispiel):
functionName(arg1 = Daten, arg2 = option1, arg3)
In einem ersten Schritt sehen wir uns an, welchen Namen die Funktion trägt. Unsere Funktion heißt functionName. Wir können leider nicht direkt aus dem Namen erschließen, welche Auswirkung unser function call haben wird. In so einem Fall würde ein Blick in Help
(?functionName
) sicher Abhilfe schaffen, wenngleich wir in unserem Beispiel leider keine Dokumentation nutzen können.
So sehen wir uns in einem zweiten Schritt die Argumente unserer Funktion an. Wie wir sehen können, besitzt unsere Funktion drei Argumente: arg1
, arg2
und arg3
. Die Argumente werden mit spezifischen Werten über =
bestückt. In unserem Beispiel benötigt das erste Argument Daten und das zweite Argument einen spezifischen Wert aus einer Auswahl an möglichen Werten - diese werden immer vom Entwickler der Funktion in der Dokumentation angegeben. Das dritte Argument wird als “default” abgerufen, also über eine Voreinstellung der Entwicklers geladen. Argumente die mit “default” arbeiten sind aber keinesfalls fest. Sie können immer auch durch andere Werte ersetzt werden. Argumente arbeiten immer dann mit “default,” wenn die Grundeinstellung des Arguments die häufigste Nutzungsform darstellt und man nicht bei jedem function call die Wertzuweisung von neuem schaffen möchte.
Tipp: Wollen wir alle verfügbaren Argumente in einer Funktion sehen, drücken wir Tab
.
Wichtig! Eine Funktion kann beliebig viele Argumente besitzen.
Um uns einer “großen” Funktion zu nähern, müssen wir zu Beginn einige “Grundfunktionen” kennenlernen.
c()
> Combine: kreiert einen Vektorseq(from, to, by)
> Generiert eine Sequenz an Zahlen:
> Colon Operator: generiert eine reguläre Sequenz (Sequenz in Einerschritten)rep(x, times, each)
> Wiederholt x times: Sequenz wird n-mal wiederholt each: jedes Element wird n-mal wiederholthead(x, n = 6)
> Zeigt die n ersten Elemente von x antail(x, n = 6)
> Zeigt die n letzten Elemente von x anprint(x)
> Gibt Werte eines Objekts aus (vor allem bei großen Objekten wichtig)round(x, digits)
> Rundung von Zahlen
z.B.
# Daten erzeugen
# Vektor erzeugen
c(1, 2, 3, 4, 5)
#> [1] 1 2 3 4 5
# Sequenz erzeugen
seq(from = 1, to = 10, by = 2)
#> [1] 1 3 5 7 9
# Reguläre Sequenz erzeugen
1:5
#> [1] 1 2 3 4 5
# Wiederholt 2 ganze 3-mal in 5 Schleifen
rep(x = 2, times = 5, each = 3)
#> [1] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
# Überblick über die erzeugten Daten
# Vektor in var1 speichern
<- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
var1
# Ersten 6 Elemente in var1
head(x = var1, n = 6)
#> [1] 1 2 3 4 5 6
# Letzten 4 Elemente in var1
tail(x = var1, n = 4)
#> [1] 7 8 9 10
# Alle Elemente in var1
print(x = var1)
#> [1] 1 2 3 4 5 6 7 8 9 10
# Daten transformieren
<- c(1.222, 2.333, 3.444)
var2
# var2 wird auf eine Nachkommastelle gerundet
round(x = var2, digits = 1)
#> [1] 1.2 2.3 3.4
Wir wollen uns die Variable var2
etwas genauer ansehen. var2
enthält durch das Aufrufen der Funktion c()
eine Reihe von Zahlen (1.222 bis 3.444) mit drei Nachkommastellen. Diese Zahlen wollen wir mit der Funktion round()
auf eine Nachkommastelle runden. Das Argument digits
beschreibt damit die Rundungszahl nach dem Komma. digits = 1
löst eine Rundung der Nachkommastellen auf die erste Nachkommastelle aus. Der Wert im Argument digits
kann damit theoretisch von 0 bis ∞ variieren.
2.2.1 Verschachtelung von Funktionen
Zudem können wir beliebig viele Funktion ineinander verschachteln, d.h. wir können den Output einer Funktion einer anderen Funktion als Input übergeben.
Wir bilden zuerst einen Vektor, runden die Zahlen und lassen uns dann nur die ersten drei Zahlen ausgeben.
# Mehrere Funktionen hintereinander
<- c(1.11, 1.22, 1.33, 1.44, 1.55)
var1 <- round(x = var1, digits = 1)
var1 head(x = var1, n = 3)
#> [1] 1.1 1.2 1.3
# jetzt in einer verschachtelten Funktion
head(x = round(x = c(1.11, 1.22, 1.33, 1.44, 1.55), digits = 1), n = 3)
#> [1] 1.1 1.2 1.3
# kürzeste Form
head(round(c(1.11, 1.22, 1.33, 1.44, 1.55), digits = 1), n = 3)
#> [1] 1.1 1.2 1.3
# Mit var1 als Dateninput
head(round(var1, digits = 1), n = 3)
#> [1] 1.1 1.2 1.3
Die Funktionen werden immer in Reihenfolge von innen nach außen ausgeführt. In unserem Beispiel also erst c()
, dann round()
und dann head()
. Jede Funktion gibt ihren Wert an die nächste Funktion weiter.
Anmerkung: Die Funktion head()
enthält zwei Argumente mit “default”-Werten: keepnums und addrownums. Beide bekommen automatisch (“default”) den Wert NULL
und müssen damit nicht ausgeschrieben werden. Sie werden für unsere Anwendung auch nicht gebraucht und damit nicht angepasst.
Vorteile von verschachtelten Funktionen:
- Es können unendlich viele Funktionen verschachtelt werden.
- Wir müssen Argumente nicht ausschreiben, solange sie eindeutig von der Funktion erkannt werden können. So gibt es in allen genutzten Funktionen mit
x =
nur eine Schnittstelle für die Dateneingabe. - Weiterhin können wir so unseren Code in eine Kette von Befehlen verwandeln, die nur einen Output (ein Objekt) ausgibt. Die Objektstruktur wird damit übersichtlicher.
Nachteil:
- Wenn wir mehrere Funktionen ineinander verschachteln, kann unser Code schnell unlesbar werden. Natürlich könnten wir die einzelnen Zwischenschritte speichern, wie im Beispiel weiter oben, aber dann definieren wir eine Menge Variablen, welche wir vielleicht gar nicht benötigen.
Wir werden in Kapitel 2.4 einen neuen Operator kennen lernen, welcher eine sehr elegante Lösung für dieses Problem bietet.
2.3 Objekte und Datentypen
In R kann alles als Objekt in Variablen gespeichert werden.
- Einzelne Werte / Mehrere Werte (z.B. ein Datensatz mit Rohdaten)
- Tabellen
- Statistische Modelle
- Ergebnisse statitischer Analysen
- Funktionen, etc.
Anmerkung: Objekte sind nicht gleich Variablen, da Objekte nicht gespeichert werden müssen. Ein Objekt wird erst dann zur Variable, wenn es eine Bezeichnung über <-
erhält und damit gespeichert wird. Die Zahlenfolge aus c(1, 2, 3)
stellt auch schon ein Objekt da.
In R gibt es eine Vielzahl von verschiedenen Objekttypen. Die grundlegenden Objekttypen sind:
2.3.1 Vektoren (vector)
- Vektoren → ordinale/metrische Variablen
- numeric (Zahlen)
- character (Buchstaben)
- logical (Richtig oder Falsch)
Beispiel
# Numerischer Verktor (numeric vector)
<- c(1, 2, 3, 4, 5)
var1
var1#> [1] 1 2 3 4 5
# Buchstaben Vektor (character vector)
<- c("Peter", "Yusuf", "Sarah", "Aayana")
var2
var2#> [1] "Peter" "Yusuf" "Sarah" "Aayana"
# Logischer Vektor (logical vector)
<- c(TRUE, FALSE, NA)
var3
var3#> [1] TRUE FALSE NA
Tipp: TRUE bzw. FALSE können mit T bzw. F abgekürzt werden.
Vektoren stellen die fundamentalen Datentypen dar. Alle weiteren Datentypen bauen auf diesen auf. Zudem müssen Vektoren aus denselben Elementen bestehen, d.h. wir können keine logical
und character
Elemente in einem Vektor mischen. Sie bilden damit die atomare Struktur
in R.
numeric vector: Die häufigste Datenform in R. Numerische Vektoren lassen sich zudem weiter unterteilen, in
integer
(ganze Zahlen) unddouble
(reele Zahlen - Kommazahlen). Zahlen werden in R praktisch immer alsdouble
abgelegt (auch die ganzen Zahlen) und müssen somit nie für Berechungen gewandelt werden.character vector: Die Elemente dieses Typs bestehen aus Zeichen, welche von Anführungszeichen umgeben werden (entweder ’ oder ” ). Sie werden auch strings genannt.
Anmerkung: Anführungszeichen dienen in R zur Kennzeichnung von nicht numerischen Werten. Wir müssen also alle natürlichen Worte in jedem Kontext in Anführungszeichen schreiben.
logical vector: Die Elemente dieses Typs können nur 3 Werte annehmen:
TRUE
,FALSE
oderNA
. Logische Vektoren lassen sich auch numerisch übersetzen inTRUE = 1
undFALSE = 0
, damit werden sie auch alsinteger
abgelegt. Das wird vor allem bei der Datenselektion relevant.Anmerkung:
NA
steht für fehlende Werte.
Vektoren haben folgend drei Eigenschaften:
# Modus bzw. Struktur (mode) - Was ist es?
typeof(var1)
#> [1] "double"
# Länge - Wie viele Elemente?
length(var1)
#> [1] 5
# Attribute (optional) - Zusätzliche Informationen (Metadaten)
attributes(var1) # x wurden noch keine attribute zugeordnet
#> NULL
# ------ Zusatz ------
# Objektbestimmung
class(var1)
#> [1] "numeric"
Nun wollen wir auf die einzelnen Elemente in den Vektoren zugreifen (indizieren). Wir können die einzelnen Elemente eines Vektor mit []
anwählen (subsetting
).
<- c(1, 2.5, 3, 4, 5.77, 6, 7)
zahlenreihe
# Das erste Element
1]
zahlenreihe[#> [1] 1
# das fünfte Element
5]
zahlenreihe[#> [1] 5.77
# Das letzte Element
length(zahlenreihe)]
zahlenreihe[#> [1] 7
# Mit - (Minus) können wir gezielt Elemente ausschließen
-4]
zahlenreihe[#> [1] 1.00 2.50 3.00 5.77 6.00 7.00
# Wir können auch Sequenzen auswählen
2:5]
zahlenreihe[#> [1] 2.50 3.00 4.00 5.77
# Wir können auch gezielt mehrere Elemente ausschließen oder auswählen
-c(1, 4)]
zahlenreihe[#> [1] 2.50 3.00 5.77 6.00 7.00
c(1, 2, 5)]
zahlenreihe[#> [1] 1.00 2.50 5.77
# Das Gleiche funktioniert auch mit einem character vector
<- c("A", "B", "C", "D", "E")
buchstabenreihe
1]
buchstabenreihe[#> [1] "A"
2.3.2 Faktoren (factor)
- Faktoren → nominale/ordinale Variablen
- nominale Variable
- Kategorien des Faktors = levels (kann Zahlen oder Buchstaben enthalten)
Bisher haben wir numeric
, character
und logical
Vektoren kennengelernt. Ein weiterer Objekttyp wird benötigt, um kategoriale Daten oder Gruppierungsvariablen darzustellen. Dieser Objekttyp wird factor
genannt.
Ein Faktor
ist ein Vektor mit ganze Zahlen (integer
), welcher eine Beschriftung für die einzelnen Fakttorstufen (levels
) besitzt. Die Beschriftungen sind letztlich Attribute (attributes
), die Informationen über die Faktorstufen geben. Ein Beispiel:
# Geschlecht als character vector
<- c("Mann", "Frau", "Mann", "Frau", "Frau", "Div")
sex
sex#> [1] "Mann" "Frau" "Mann" "Frau" "Frau" "Div"
# Modus (mode)
typeof(sex)
#> [1] "character"
# Attribute
attributes(sex)
#> NULL
Nun haben wir einen character vector
mit Informationen über Geschlechter. In einem nächsten Schritt wollen wir diesen Vektor als Faktor definieren.
# Geschlecht als Faktor
<- factor(sex, levels = c("Mann", "Frau", "Div"))
sexf
sexf#> [1] Mann Frau Mann Frau Frau Div
#> Levels: Mann Frau Div
# Geschlecht hat nun den Datentyp integer
typeof(sexf)
#> [1] "integer"
# und die Klasse "factor"
class(sexf)
#> [1] "factor"
# jetzt sind auch die Attribute definiert
attributes(sexf)
#> $levels
#> [1] "Mann" "Frau" "Div"
#>
#> $class
#> [1] "factor"
Wir haben bei der Definition die levels
explizit angegeben. Das hätten wir aber nicht machen müssen. R ordnet standardmäßig alle Faktorstufen alphabetisch zu.
# Geschlecht als Faktor
<- factor(sex)
sexf2
sexf2#> [1] Mann Frau Mann Frau Frau Div
#> Levels: Div Frau Mann
# Datentyp
typeof(sexf2)
#> [1] "integer"
# Klasse
class(sexf2)
#> [1] "factor"
# Attribute
attributes(sexf2)
#> $levels
#> [1] "Div" "Frau" "Mann"
#>
#> $class
#> [1] "factor"
Wir werden Faktoren später häufig bei der Modellierung benötigen z.B. bei Regressionsmodellen mit Dummyvariablen. Die erste Stufe eines Faktors wird von R automatisch als Referenzkategorie bestimmt, wenn wir den Faktor als Prädiktorvariable in ein Modell einbringen. Manchmal wollen wir jedoch eine andere Stufe als Referenzkategorie. In diesem Fall kann man die Reihenfolge der Faktorstufen ändern.
Über relevel()
kann direkt die Refrenzkategorie bestimmt werden.
# Unsere Refrenzkategorie ist gegenwärtig "Mann"
levels(sexf)
#> [1] "Mann" "Frau" "Div"
# Wir ändern die Refrenkategorie folgend auf "Frau"
<- relevel(sexf, ref = "Frau")
sexf levels(sexf)
#> [1] "Frau" "Mann" "Div"
Mit der Funktion factor()
lassen sich alle Faktorstufen frei ordnen, solange wir im Befehl alle Faktorstufen benutzen. Die erste Stufe wird folglich zur Refrenzkategorie.
# Unsere Refrenzkategorie ist gegenwärtig "Frau"
levels(sexf)
#> [1] "Frau" "Mann" "Div"
# Wir ändern die Refrenkategorie durch die neue Auflistung unserer level auf "Mann"
<- factor(sexf, levels = c("Mann", "Frau", "Div"))
sexf levels(sexf)
#> [1] "Mann" "Frau" "Div"
Durch die Festlegung einer Variable als Faktor wird diese automatisch dummykodiert und kann umstandslos in ein Modell einbezogen werden. Wir müssen nur die Refrenzkategorie im Blick behalten.
2.3.3 Datensatz (data frame)
- Datensatz (mehrere Zeilen und Spalten)
- Spalten (Vektoren und Faktoren)
- Zeilen (Fälle, z. B. Versuchspersonen)
Nun kommen wir zu dem für uns wichtigsten Objekt in R, dem Datensatz. Ein Datensatz besteht aus Zeilen (rows) und Spalten (columns) gleicher Länge und entspricht einem Datensatz in SPSS.
Die Spalten eines Datensatz sind lediglich Vektoren. Sie können damit numeric
, character
und logical
sein, oder als factor
auftreten. Numerische Variablen in einem Datensatz sollten demzufolge numerische Vektoren und kategoriale Variablen/Gruppierungsvariablen sollten Faktoren sein. Durch die Mischung von Datentypen innerhalb des Objekts “Datensatz” sprechen wir auch von einer rekursiven Struktur
.
Datensätze werden traditionell über die Funktion data.frame()
definiert. Da wir das tidyverse nutzen, greifen wir auf eine weiterentwickelte Form zurück - tibbles
oder tbl
. tibbles werden über die Funktion tibble()
definiert und erleichtern so die Arbeit mit dem Datensatz.
Anmerkung: Der Vorteil liegt beim Einlesen von Datensätzen, da weniger Rücksicht auf die Datenstruktur genommen werden muss. Durch tibbles
wird viel durch R automatisiert.
Erstellung eines Datensatz.
# Vektoren
<- factor(c("Mann", "Frau", "Mann", "Mann", "Frau", "Mann"))
sex <- c(22, 45, 67, 87, 16, 56)
alter
# Datensatz
<- tibble(sex, alter)
datensatz
datensatz#> # A tibble: 6 × 2
#> sex alter
#> <fct> <dbl>
#> 1 Mann 22
#> 2 Frau 45
#> 3 Mann 67
#> 4 Mann 87
#> 5 Frau 16
#> 6 Mann 56
Unser datensatz
ist damit ein Datensatz mit zwei Variablen (sex
und alter
) und sechs Fällen (rows). Dieser wird uns entsprechend im Environment
angezeigt.
Ein Datensatz hat die Attribute names()
, colnames()
und rownames()
- [names()
und colnames()
bedeuten dasselbe].
attributes(datensatz)
#> $class
#> [1] "tbl_df" "tbl" "data.frame"
#>
#> $row.names
#> [1] 1 2 3 4 5 6
#>
#> $names
#> [1] "sex" "alter"
Wir können zudem sowohl die Länge des Spaltenvektors (Anzahl der Spalten) über ncol
abfragen, wie auch die Länge des Zeilenvektors (Anzahl der Fälle) über nrow
.
ncol(datensatz)
#> [1] 2
nrow(datensatz)
#> [1] 6
2.3.3.1 Datensatz indizieren (subsetting)
Ein Datensatz ist eine 2-dimensionale Struktur aus Spalten und Fällen, die entsprechend indiziert werden kann.
- Einzelne Spalten können über
$
ausgewählt werden. - Einzelne oder mehrere Elemente, ob Spalten oder Zeilen, können über
[]
ausgewählt werden.
# Spaltenname zur Auswahl einer spezifischen Variable über $
$sex
datensatz#> [1] Mann Frau Mann Mann Frau Mann
#> Levels: Frau Mann
# Spaltenname zur Auswahl einer spezifischen Variable über []
"sex"]
datensatz[#> # A tibble: 6 × 1
#> sex
#> <fct>
#> 1 Mann
#> 2 Frau
#> 3 Mann
#> 4 Mann
#> 5 Frau
#> 6 Mann
# Nach Position auswählen
2]
datensatz[#> # A tibble: 6 × 1
#> alter
#> <dbl>
#> 1 22
#> 2 45
#> 3 67
#> 4 87
#> 5 16
#> 6 56
Wir können aber auch mehrer Variablen und sogar Zeilen (Fälle) auswählen. Sobald wir die Werte in []
durch ein Komma trennen, gibt der erste Wert die Zeilen und der zweite die Spalten an. Ohne Komma nur die Spalten.
# Erste Spalte und erste Zeile
1, 1]
datensatz[#> # A tibble: 1 × 1
#> sex
#> <fct>
#> 1 Mann
# Erster Fall über alle Spalten (Variablen)
1, ]
datensatz[#> # A tibble: 1 × 2
#> sex alter
#> <fct> <dbl>
#> 1 Mann 22
# Alle Zeilen in der ersten Spalte
1]
datensatz[, #> # A tibble: 6 × 1
#> sex
#> <fct>
#> 1 Mann
#> 2 Frau
#> 3 Mann
#> 4 Mann
#> 5 Frau
#> 6 Mann
# Wir können auch Sequenzen benutzen
# Ersten drei Fälle über alle Spalten
1:3, ]
datensatz[#> # A tibble: 3 × 2
#> sex alter
#> <fct> <dbl>
#> 1 Mann 22
#> 2 Frau 45
#> 3 Mann 67
# Da die Spalten Vektoren sind, können wir diese genauso indizieren
# Erster Fall im Vektor zu Geschlecht
$sex[1]
datensatz#> [1] Mann
#> Levels: Frau Mann
# Letzten drei Fälle in der Variable alter
$alter[4:length(datensatz$alter)]
datensatz#> [1] 87 16 56
# Die Spalten sex und alter
c("sex", "alter")]
datensatz[#> # A tibble: 6 × 2
#> sex alter
#> <fct> <dbl>
#> 1 Mann 22
#> 2 Frau 45
#> 3 Mann 67
#> 4 Mann 87
#> 5 Frau 16
#> 6 Mann 56
2.3.4 Listen (list)
- Listen → in SPSS nicht vorhanden
- Kombination mehrerer Objekte
- Listen können beliebige Objekte enthalten, auch Objekte verschiedenen Typs.
- Im Unterschied zu Datensätzen können auch Objekte unterschiedlicher Länge gespeichert werden.
Ein weiterer Datentyp ist list
. Während Datensätze schon aus verschiedenen Vektoren bestehen können, lassen sich diese in Listen auch in verschiedenen Längen ablegen. Die meisten Objekte die wir im Verlauf unserer R Anwendung kennenlernen werden, sind damit Listen (z.B. die Ergebnisse eines Regressionsmodells werden als Liste gespeichert). Wir müssen aber nur mit ihnen umgehen können und sie verstehen. Wir werden sie praktisch nie selbst erstellen müssen.
In einem ersten Schritt wollen wir dennoch mit der Funktion list()
eine Liste definieren. So können wir schrittweise verstehen, welche Eigenschaften Listen besitzen.
<- list(1:3, "a", c(1.22, 2, 3.44, 4, 5), c(TRUE, FALSE, FALSE))
list1
list1#> [[1]]
#> [1] 1 2 3
#>
#> [[2]]
#> [1] "a"
#>
#> [[3]]
#> [1] 1.22 2.00 3.44 4.00 5.00
#>
#> [[4]]
#> [1] TRUE FALSE FALSE
Wir haben mit der Variable list1
eine Liste erstellt, die als Elemente einen numeric
Vektor mit einer Zahlenreihe, einen character
Vektor mit der Länge 1 (L1), einen numeric
Vektor mit Kommazahlen und einen logical
Vektor speichert.
Listen können wie Vektoren indiziert werden.
# Erstes Element in der Liste (erster Vektor)
1]
list1[#> [[1]]
#> [1] 1 2 3
# Zweites Element (zweiter Vektor)
2]
list1[#> [[1]]
#> [1] "a"
# Drittes Element (dritter Vektor)
3]
list1[#> [[1]]
#> [1] 1.22 2.00 3.44 4.00 5.00
Häufig sind die Elemente in einer Liste als Variablen benannt und können entsprechend wie in einem Datensatz über $
abgerufen werden.
<- list(var1 = c(1, 2, 3),
list2 var2 = c("a", "b", "c", "d"),
var3 = c(TRUE, FALSE),
var4 = seq(from = 1, to = 100, by = 5))
# Ganze Liste abrufen
list2#> $var1
#> [1] 1 2 3
#>
#> $var2
#> [1] "a" "b" "c" "d"
#>
#> $var3
#> [1] TRUE FALSE
#>
#> $var4
#> [1] 1 6 11 16 21 26 31 36 41 46 51 56 61 66 71 76 81 86 91 96
# Aufruf der ersten Variable mit ihrem Namen var1
$var1
list2#> [1] 1 2 3
# Aufruf der zweiten Variable mit ihrem Namen var2
$var2
list2#> [1] "a" "b" "c" "d"
# Objektbestimmung
typeof(list2)
#> [1] "list"
Im Environment
gibt sich folgende Datenstruktur.
Wichtig! Elemente in Listen können unendlich “gestapelt” werden und folgend mit einer Verschränkung des []
Operator “entpackt” werden.
<- list(list1 = list1,
list3 list2 = list(var1 = c(1, 2, 3, 4, 5),
var2 = "a"),
var1 = 1:100)
# Liste1 aus Liste3 entpacken
$list1
list3#> [[1]]
#> [1] 1 2 3
#>
#> [[2]]
#> [1] "a"
#>
#> [[3]]
#> [1] 1.22 2.00 3.44 4.00 5.00
#>
#> [[4]]
#> [1] TRUE FALSE FALSE
# var1 aus liste1 in liste3 entpacken
$list1[1]
list3#> [[1]]
#> [1] 1 2 3
# var2 aus liste2 in liste3 entpacken
$list2$var2
list3#> [1] "a"
# Erster Wert aus var1 in list1 über list3 entpacken
$list1[[1]][1]
list3#> [1] 1
2.3.5 Weitere Datentypen
R kennt noch zwei weitere Datentypen (Matrizen und Arrays), die wir in diesem Kurs ausklammern werden. Sofern interesse besteht, verweisen wir auf das Buch von Hadley Wickham: Advanced R.
2.4 Pipe Operatoren
In Kapitel 2.2.1 konnten wir schon sehen, dass es schnell unübersichtlich werden kann, wenn wir Funktionen ineinander verschachteln. Vor allem bei der Datenverarbeitung entsteht sehr schnell unübersichtlicher Code.
Hierfür gibt es in R eine “ganz einfache” Lösung - Pipes
. So lassen sich Sequenzen von Funktionen sehr elegant abbilden. Eine Pipe
zeigt sich immer durch den %>%
Operator an. Um auf diesen zugreifen zu können, müssen wir eig. das Paket magrittr laden. Da aber die Packete des tidyverse den Operator automatisch laden, müssen wir zumindest in seiner einfachsten Form keinen zusätzlichen Aufwand betreiben. Nun wollen wir aber mit zwei weiteren Formen des Pipe-Operators arbeiten (%$%
und %T>%
), die uns nur über das Laden von magrittr zur Verfügung gestellt werden.
install.packages("magrittr")
library(magrittr)
%>% Operator
Tipp: Um schnell auf den Pipe Operator zugreifen zu können, bietet sich das Tastenkürzel cmd bzw. strg
+ up
+ M
an.
Um einen ersten Einstieg zu schaffen, greifen auf unser Beispiel aus Kapitel 2.2.1 zurück. Hier haben wir den Vektor c(1.11, 1.22, 1.33, 1.44, 1.55)
, welcher in var1 gespeichert wurde, gerundet.
# Vektor speichern in var1
<- c(1.11, 1.22, 1.33, 1.44, 1.55)
var1
# var1 runden
round(var1, digits = 1)
#> [1] 1.1 1.2 1.3 1.4 1.6
In diesem Stadium ist unser Code noch sehr übersichtlich. Wir können aber auch schon jetzt eine Pipe
zu Demonstrationszwecken bilden.
# Vektor speichern in var1
<- c(1.11, 1.22, 1.33, 1.44, 1.55)
var1
# var1 runden
%>%
var1 round(digits = 1)
#> [1] 1.1 1.2 1.3 1.4 1.6
Der Effekt ist für unser Beispiel klein, aber die Idee ist klar. Wir schreiben mit der Pipe
also Funktionen nicht mehr als f(x)
sondern als x %>% f() bzw. x %>% f
. Wir reihen also unsere Funktionen aneinander, wir verschachteln sie nicht mehr.
Im nächsten Schritt wollen wir unseren gerundeten Vektor in eine weitere Funktion übergeben, dann wird auch der Effekt dieses Vorgehens ersichtlich.
Wir rufen zusätzlich mit head()
die ersten drei Elemente unseres Vektors auf.
# Vektor speichern in var1
<- c(1.11, 1.22, 1.33, 1.44, 1.55)
var1
# var1 runden und die ersten drei Elemente abrufen
head(round(var1, digits = 1), n = 3)
#> [1] 1.1 1.2 1.3
# var1 runden und die ersten drei Elemente abrufen - Pipe
%>%
var1 round(digits = 1) %>%
head(n = 3)
#> [1] 1.1 1.2 1.3
Wie wir sehen können, wird der Vektor var1 durch die Funktionen “weitergegeben.” Tatsächlich gibt jede Funktion ihren Output, also ihr Ergebnis, an die nächste weiter. Im ersten Beispiel passiert das von “innen nach außen.” Damit wird es schnell unübersichtlich, zumal anstrengend zu schreiben. In der Pipe
hingegen, erzeugen wir eine lineare Übergabe von Funktion zu Funktion.
Der Ablauf der Pipe
in Kürze:
- Der Vektor aus var1 wird also an die Funktion
round()
weitergegeben und in dieser auf eine Nachkommastelle gerundet. Damit haben wir an diesem Punkt den Output:c(1.1, 1.2, 1.3, 1.4, 1.6)
. - Im nächsten Schritt wird dieser Output an
head()
übergeben. Inhead()
rufen wir nur die ersten drei Elemente ab, alsoc(1.1, 1.2, 1.3)
.
Vorteile:
- Unser Code ist lesbarer
- Wir müssen keine unnötigen Variablen definieren
Wir können diesen Prozess auch mit dem Platzhalter-Operator .
veranschaulichen. Dieser steht immer für den Output aus der letzten Funktion.
# Vektor speichern in var1
<- c(1.11, 1.22, 1.33, 1.44, 1.55)
var1
# var1 runden und die ersten drei Elemente abrufen - Pipe
%>%
var1 round(x = ., digits = 1) %>%
head(x = ., n = 3)
#> [1] 1.1 1.2 1.3
Ziemlich schick, nicht wahr? Der Platzhalter lässt sich aber auch an jeder anderen Stelle in der Funktion als Wertcontainer einsetzen.
# Vektor speichern in var1
<- c(1.11, 1.22, 1.33, 1.44, 1.55)
var1
# var1 runden und die ersten drei Elemente abrufen - Pipe
1 %>%
round(var1, digits = .) %>%
head(x = ., n = 3)
#> [1] 1.1 1.2 1.3
Wichtig! Wollen wir ohne einen Platzhalter arbeiten, muss das erste Argument der nachfolgenden Funktion den Output der vorherigen aufnehmen können - i.d.R. x = Daten (Datensatz oder Variablen)
.
In den meisten Fällen ist auch das Objekt, welches übergeben wird, gleichzeitig das erste Argument der nächsten Funktion (vor allem für die tidyverse-Funktionen), so dass wir diesen Platzhalter selten brauchen werden. Wir haben alle Funktionen in diesem Kurs nach diesem Kriterium ausgewählt, um eine möglichst reibungslose Einarbeitung zu ermöglichen.
Anmerkung: Der Verkettung von Funktionen ist damit praktisch kein Limit gesetzt, außer ein paar Regeln an die man sich halten sollte.
Pipes
sollten nicht genutzt werden, wenn
- die
Pipe
länger als 10 Schritte wäre, - mehrere Inputs oder Outputs benötigt werden,
- eine komplexe Objektstruktur das Ziel ist.
%$% Operator
Für Funktionen, die auf Variablenebene arbeiten und keinen Datensatz als Dateninput erlauben, gibt es den %$%
Operator.
Wichtig! Wir brauchen diesen Operator aber nur für ganz wenige Funktionen. Die meisten Funktionen die auf Variablenebene arbeiten, besitzen gleichzeitig auch als erstes Argument eine Datensatz-Schnittstelle - x = Datensatz
. Sie greifen dann über das zweite Argument auf die Variablen in diesem Datensatz zu.
Der %$%
Operator ermöglicht letztlich, dass wir gezielt einzelne Variablen aus dem Datensatz in der nächsten Funktion anwählen können, auch wenn es keine Datensatz-Schnittstelle gibt.
In unserem Beispiel benötigt die Funktion ts.plot
, als Grafik für Zeitachsenmessungen, eine Variable als Input - x = Variable
. Es darf also kein Datensatz eingespeist werden. Folglich brauchen wir den %$%
Operator, um auf Variablenebene operieren zu können.
# Vektoren in Datensatz speichern
<- c(1, 1, 2, 2, 2, 3, 3, 3, 3)
var1 <- c(8, 9, 1, 1, 2, 3, 4, 5, 4)
var2 <- tibble(var1, var2)
ds1
# Mit dem %$% Operator
%$%
ds1 ts.plot(var1)
%T>% Operator
Dieser Operator ermöglicht es eine Pipe
auch bei Funktionen ohne “inhaltlichen” Output weiterzuführen. Grafiken haben letztlich keinen Output außer der Grafikausgabe und würden so eine Verkettung von Funktionen beenden. Wollen wir aber zu unserer Grafik noch einen “inhaltlichen” Output, brauchen wir den %T>%
Operator. Dieser wird hinter den letzten Output geschrieben, auf den wir uns beziehen wollen. Unser Wert wird damit auch an die übernächste Funktion “weitergegeben.”
Es wird also immer auf den linken Wert neben dem %T>%
Operator zugegriffen und dieser an alle folgenden Funktionen “weitergegeben.”
<- c(1.11, 1.22, 1.33, 1.44, 1.55)
var1
%>%
var1 round(digits = 2) %T>%
plot() %>%
head(n = 3)
#> [1] 1.11 1.22 1.33
So können wir uns einen Grafikoutput, wie auch die ersten drei Elemente von unserem gerundeten Vektor ausgeben lassen.
Hervorangend! Jetzt können wir mit der Bearbeitung unseres Datensatzes beginnen.