Kapitel 4 Erste Schritte
Im letzten Abschnitt habt ihr R als glorifizierten Taschenrechner gesehen.
Als nächstes schauen wir uns an, was wir sonst so damit anstellen können.
Zuerst ein bisschen Terminologie:
“To understand computations in R, two slogans are helpful:
- Everything that exists is an object.
- Everything that happens is a function call."
– John Chambers
Oder in der Sprach-Analogie: Alles was in R existiert (Variablen, Tabellen, etc.) ist ein Nomen (Objekt) und alles, was etwas tut, ist ein Verb (Funktion).
4.1 Grundfunktionen
Die einfachsten Funktionen haben wir in Form der Rechenzeichen + - / *
schon kennengelernt, aber es gibt natürlich noch mehr.
Eine Funktion in R hat sieht immer ungefähr so aus: sqrt(8)
. Der Name der Funktion, hier sqrt
, (immer ohne Leerzeichen) gefolgt von Klammern, in denen ein oder mehrere Argumente stehen. Ein Argument ist das, womit die Funktion arbeiten soll. Eine gängige Veranschaulichung für Funktionen sind Verben einer Sprache, denn sie tun etwas.
Eine der wichtigsten Grundfunktionen ist c()
, für combine. Mit c
verbindet ihr mehrere Zahlen zu einem Vektor (ja, wie in der linearen Algebra. Mathe und so. Wisstschon.). Wenn ihr mehrere Zahlen zu einem Vektor kombiniert habt, könnt ihr damit so spaßige Dinge machen wie Mittelwerte ausrechen, sie aufsummieren oder zwei Vektoren gleicher Länge addieren.
Probiert mal ein paar Beispiele aus:
#> [1] 1 1 2 3 5 8 13 21
#> [1] 6.75
#> [1] 54
#> [1] 1 1 4 9 25 64 169 441
#> [1] 1.000000 1.000000 1.414214 1.732051 2.236068 2.828427 3.605551 4.582576
Was wir hier sehen ist der Unterschied zwischen Funktionen, die aus mehreren Zahlen eine machen (mean
, sum
), und Funktionen, die auf jeder Zahl einzeln operieren (sqrt
, ^
).
Was wir außerdem sehen: Jedes mal die Liste von Zahlen c(1, 1, 2, 3, 5, 8, 13, 21)
kopieren und in eine Funktion einsetzen ist ziemlich unpraktisch. Stellt euch vor, ihr habt eine Reihe von Testergebnissen von hunderten ProbandInnen und müsst da alles einzeln, also, nein, das wäre ja albern.
Für sowas gibt es dann Abstraktionen wie Variablen und Datensätze, die entweder eine Liste von Werten oder auch eine Liste einer Liste von Werten handlich machen — das sehen wir dann in den nächsten beiden Abschnitten.
Eine weitere praktische Funktion ist length()
: Sie sagt uns, wie lang das Argument ist.
Wenn wir uns also angucken, wie der Mittelwert funktioniert…
\[\bar{x} = \frac{1}{n} \sum^n_{i=1} x_i\]
…und wir das übersetzen in “Die Summe aller Werte geteilt durch die Anzahl der Werte”, dann können wir statt mean
also auch folgendes schreiben:
#> [1] 6.75
#> [1] 6.75
Ihr seht vielleicht so langsam, wieso wir das mit den Klammern und den Leerzeichen für die Lesbarkeit erwähnt haben.
Aber gut, so langsam wird’s unübersichtlich, es wird Zeit ein paar Variablen anzulegen.
4.1.1 Funktionsbeispiele
Wenn ihr R lernt, werdet ihr erfahrungsgemäß die meiste Zeit damit verbringen herauszufinden wie bestimmte Funktionen funktionieren und welche Funktion für euer Vorhaben die richtige ist.
Funktionen sind zwar vom Schema immer gleich — ihr steckt irgendwelche Argumente rein, und es kommt irgendein Ergebnis raus — aber wie die Argumente aussehen unterscheidet sich von Funktion zu Funktion.
sd()
zum Beispiel hat zwei Argumente:
x
: Ein Vektor aus Zahlen, aus denen die Standardabweichung berechnet werden sollna.rm
: FürNA remove
, entwederTRUE
oderFALSE
. Wennx
fehlende Werte (NA
) enthält, dann werden diese automatisch ignoriert, wennna.rm = TRUE
#> [1] NA
#> [1] 2.065591
Der default für na.rm
ist bei den meisten Funktionen (z.B auch mean
) FALSE
, das heißt fehlende Werte werden nicht automatisch ignoriert. Wenn euer input aber NA
enthält, dann lässt sich daraus nicht sauber ein Mittelwert oder eine Standardabweichung berechnen, weil wir nichts über NA
wissen (wir widmen uns NA
im Kapitel zu Datentypen).
Nicht jede Funktion hat ein Argument namens na.rm
, aber ihr werdet im Laufe der Zeit lernen, bei welchen Funktionen ihr darauf achten müsst, wie mit fehlenden Werten umgegangen wird.
Einige andere Funktionen, die insbesondere zum Lernen und Ausprobieren praktisch sind, werden zur Erstellung von Sequenzen benutzt — also Reihen von Zahlen in einem bestimmten Muster:
#> [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#> [18] 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
#> [35] 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
#> [52] 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
#> [69] 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
#> [86] 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
#> [1] 10.0 10.5 11.0 11.5 12.0 12.5 13.0 13.5 14.0 14.5 15.0
#> [1] 10.0 10.5 11.0 11.5 12.0 12.5 13.0 13.5 14.0 14.5 15.0
#> [1] 5 4 3 2 1 0 -1 -2 -3 -4 -5
#> [1] 1 2 3 4 5
#> [1] 1 2 3 4 5
Das :
ist eine einfache Funktion und kann gelesen werden wie “Von … bis … in ganzen Schritten”. Zusötzlich gibt es die Funktion seq()
, die etwas flexibler ist. Außerdem gibt es diverse Funktionen für zufallsgenerierte Zahlen:
# Normalverteilte Zufallszahlen (10 Stück, Mittelwert 5, Standardabweichung 2)
rnorm(n = 10, mean = 5, sd = 2)
#> [1] 10.462817 6.302432 0.784625 6.110846 4.094542 3.971209 4.679417
#> [8] 5.713550 2.578638 8.145951
#> [1] 0.328706726 0.121082212 0.477525467 0.182499862 0.434663717
#> [6] 0.446468780 0.005288531 0.548899238 0.806441535 0.486481078
#> [1] 1 1 1 1 0 0 1 0 0 0
#> [1] 2 6 1 1 2 1 4 3 4 1
4.2 Variablen
Wenn Funktionen wie Verben sind, dann sind Variablen wie Nomen. Sie haben einen Namen, und mit ihnen kann man Dinge tun. Oder sogar Sachen machen.
Variablen werden durch eine Zuweisung (Assignment) erstellt, was in R traditionell via <-
passiert.
In den meisten anderen Programmiersprachen benutzt man dafür =
, aber nun ja, R ist historisch gewachsen8, also nehmt für den Anfang einfach mal hin, dass das nunmal so ist.
Wir speichern also mal ein paar Dinge:
#> [1] 1 1 2 3 5 8 13 21
#> [1] 6.75
#> [1] 2 2 4 6 10 16 26 42
Hier haben wir die Zahlen 1, 1, 2, 3, 5, 8, 13, 21
in die Variable fib
gespeichert, weil es die ersten paar Fibonacci-Zahlen sind, und wir unsere Variablen immer so benennen sollten, dass wir später noch wissen wofür sie da sind. Viele Tutorials beginnen damit, Variablen wie x
, y
und z
anzulegen, aber da blickt ja irgendwann kein Mensch mehr durch.
Wenn ihr jetzt mit euren Zahlen arbeiten wollt, könnt ihr einfach in jeder Funktion fib
statt der Liste mit c(…)
einsetzen, und alles funktioniert wie vorher. Das liegt daran, dass R bei jedem Befehl erstmal nachschaut, ob ihr eine Variable benutzt (alles was Text ohne Anführungszeichen ist), und ob es die Variable findet. Wenn es die Variable gefunden hat, guckt es nach, was da drinsteht, in diesem Fall also c(1, 1, 2, 3, 5, 8, 13, 21)
, dann benutzt R den Inhalt der Variablen.
An dieser Stelle bietet es sich an, einen neuen Typ einzuführen: Den String (oder auch character
). Als String bezeichnet man im Kontext von, naja, Computerkram generell eigentlich, alles was als Text durchgeht. Sobald wir etwas nicht mehr nur durch Zahlen darstellen können, ist es ein String.
Strings stehen in R immer in Anführungszeichen, entweder in "doppelten" oder in 'einfachen'.
Wichtig dabei ist, dass sich Anführungszeichen ähnlich verhalten wie Klammern. Wenn wir einen String mit “
beginnen, müssen wir ihn auch wieder mit ”
schließen, ansonsten wartet R brav darauf, dass endlich das zweite "
kommt und verläuft sich.
Ein Beispiel:
# Vollkommen okay
namen <- c("Tobi", "Lukas", "Nadja", "Christoph")
# Auch okay, aber inkonsistent und daher eher unschön
namen <- c('Tobi', "Lukas", 'Nadja', "Christoph")
# Tod und Verderben (=> funktioniert nicht)
namen <- c("Tobi", 'Lukas", 'Nadja, Christop")
Wir können auch Zahlen in ""
setzen — das ist kein Problem, aber dann sind es nunmal keine Zahlen in diesem Sinne mehr, es sind Strings, und mit Strings können wir nicht rechnen.
Probiert folgendes aus:
Ihr sehr jetzt vermutlich die Meldung Error in 5 + "5" : non-numeric argument to binary operator
.
Das non-numeric argument hier ist die "5"
. Merken: 5
ist numerisch, aber "5"
ist ein character (=> String).
Der binary operator an dieser Stelle ist übrigens das +
. Ein Operator, weil es, äh… operiert? Naja, es tut Dinge, und wenn etwas in R Dinge tut, ist es meistens ein Operator in irgendeinem Sinne. Das binary heißt, dass es zwei (bi, binär, binary, zwei halt) Argumente nimmt.
Wie schon gesagt, Argumente sind die Dinge, die wir an Funktionen übergeben, und wenn wir an eine Funktion wie +
oder auch mean()
ein Argument übergeben, mit denen sie nichts anfangen können, dann beschwert sich R weil es nicht weiß was zum Geier ihr da vorhabt.
namen <- c("Tobi", "Lukas", "Nadja", "Christoph")
fib <- c(1, 1, 2, 3, 5, 8, 13, 21)
# Alles knorke
mean(fib)
#> [1] 6.75
# Alles CHAOS UND UNHEIL
mean(namen)
#> Warning in mean.default(namen): argument is not numeric or logical:
#> returning NA
#> [1] NA
Was es mit NA
auf sich hat, und was es noch so für Datentypen gibt, sehen wir dann im Abschnitt zu Datentypen.
Eine letzte Sache noch: Strings sind “dominanter” als Zahlen, das heißt, wir können zwar Zahlen verbinden zu c(1, 2, 3)
, und Strings zu c("A", "B", "C")
, aber wenn wir c("A", 2, "C", 4)
schreiben, dann behandelt R einfach alle Elemente des Vektors (=> Das, was in c(…)
) steht, als wären es character
-Werte.
4.3 Tabellen
Jetzt haben wir schonmal das Vokuabular an der Hand um Zahlen und beliebige Strings in R zu verarbeiten, aber noch ist das alles etwas unahndlich um damit richtig zu arbeiten.
Stellt euch vor, wir wollen einen kleinen Datensatz erstellen über die Statistiktutorien in QM mit Variablen wie Namen, Alter, und vielleicht sowas wie Beliebtheit auf einer Skala von 1-10.
Wir könnten sowas machen:
namen <- c("Tobi", "Christoph", "Nadja", "Lukas")
alter <- c(20, 35, 30, 12) # (Nicht alle diese Werte sind korrekt)
mean(alter)
#> [1] 24.25
Schön und gut, aber das ist ja unhandlich. Was, wenn wir die Namen aller TutorInnen haben wollen, die jünger als 30 sind? Alles was wir mit alter
machen, passiert unabhängig von namen
.
Um mehrere Variablen in Kontext zu setzen, gibt es tabellarischen Datenstrukturen, namentlich nennt sich sowas in R dann data.frame
. Letztendlich ist das nichts anderes als eine Tabelle, aber für R ist eine Tabelle praktische eine Liste von Vektoren mit gleicher länge:
leute <- data.frame(name = c("Tobi", "Christoph", "Nadja", "Lukas"),
alter = c(20, 35, 30, 12),
beliebtheit = c(9, 10, 8, 3))
# Anzeigen lassen
leute
#> name alter beliebtheit
#> 1 Tobi 20 9
#> 2 Christoph 35 10
#> 3 Nadja 30 8
#> 4 Lukas 12 3
Was haben wir da gemacht?
- Wir haben einen
data.frame
mit der gleichnamigen Funktion erstellt - Die Argumente der Funktion haben die Form
Spaltenname = Werte der Spalte
- Mehrere Argumente werden mit
,
getrennt und optional mit einem Zeilenumbruch übersichtlich gehalten
Das Ergebnis ist eine Variable leute
, die drei Spalten mit je vier Werten hat.
Jede Spalte ist eine Variable, und jede Zeile der Tabelle kann als eine Beobachtung betrachtet werden.
Eine Beobachtung (Observation) sind alle Werte, die wir zu einem Untersuchungsobjekt haben, also in diesem Beispiel eine Person. Wenn wir uns nur die erste Zeile anschauen, sehen wir nur die Werte, die zu Tobi gehören, in der zweiten Zeile sehen wir die Werte zu Christoph etc.
Tabellen, und damit data.frames
, sind für uns die wichtigsten Objekte in R, weil wir fast ausschließlich mit Datensätzen in dieser Form arbeiten werden um unsere Statistik da draufzuwerfen.
Wie können wir jetzt mit einzelnen Variablen arbeiten?
#> [1] Tobi Christoph Nadja Lukas
#> Levels: Christoph Lukas Nadja Tobi
#> [1] 24.25
#> [1] 3.109126
#> [1] Tobi Christoph Nadja Lukas
#> Levels: Christoph Lukas Nadja Tobi
#> [1] 20 35 30 12
Das mit den “Levels” wird im Abschnitt zu Datentypen erklärt
Was wir hier benutzen nennt sich Subsetting, also im Grunde nur einen Teil von etwas rausholen. Hier also einen Teil der Tabelle on Form einer einzelnen Spalte.
Spalten können wir mit $
oder [[ ]]
direkt aus einem data.frame
ansteuern, was unser Leben gleich viel einfacher macht. Strenggenommen sidn $
und [[
auch eigene Funktionen, aber dazu vielleicht später mehr, im Moment ist für uns nur wichtig, dass wir einzelne Spalten (Variablen) einer Tabelle (data.frame
) einfach adressieren und genauso behandeln können wie die einzelnen Variablen name
und alter
, die wir weiter oben erstellt haben.
4.4 Umgang mit Tabellen
Da wir noch nicht an dem Punkt sind, wo wir beliebige Daten einlesen können, und wir natürlich zu faul sind uns eine größere Tabelle selber zu schreiben, greifen wir zu Übungszwecken mal auf einen Datensatz zurück, der bei R von Haus aus mitgeliefert wird: sleep
.
Dieser Datensatz beinhaltet die Daten aus einer Medikamentenstudie, bei der es um Schlafgewinn bzw. -verlust ging. Die Tabelle hat drei Spalten (Variablen) zu 10 Personen:
extra
: Schlafzuwachs in Stunden, positiv oder negativ für mehr bzw. weniger Schlaf als vorhergroup
: Die Versuchsgruppe, sprich welches Medikament die Person bekam,1
oder2
ID
: Die Identifikationsnummer der Person. Es ist gängig, ProbandInnen pseudonymisiert durchzunummerieren, der Zuordnung unt des Datenschutzes wegen als Zahlen.
#> extra group ID
#> 1 0.7 1 1
#> 2 -1.6 1 2
#> 3 -0.2 1 3
#> 4 -1.2 1 4
#> 5 -0.1 1 5
#> 6 3.4 1 6
Wie viele Zeilen hat die Tabelle?
#> [1] 20
Die number of rows bekommen wir mit nrow()
— ihr dürft jetzt raten, wie wir uns die Anzahl der Spalten (columns) anzeigen lassen können.
#> [1] 3
Surprise!
Okay, aber was interessiert uns an diesem Datensatz jetzt? Wie wäre es mit dem durchschnittlichen Schlafzuwachs:
#> [1] 1.54
Schön und gut, aber wir wollen ja vermutlich die beiden Gruppen (Medikamente) vergleichen, also was tun?
Subsetting to the rescue /o/
gruppe1 <- sleep[sleep$group == 1, ]
gruppe2 <- sleep[sleep$group == 2, ]
# Mittelwert der ersten Gruppe
mean(gruppe1$extra)
#> [1] 0.75
#> [1] 2.33
Okay, Schritt für Schritt.
Hier haben wir unseren ersten logischen vergleich benutzt, um eine Teilmenge der Tabelle zu extrahieren.
Das klingt fancy, ist aber ziemlich simpel.
In Worten heißt die Zeile gruppe1 <- sleep[sleep$group == 1]
lediglich:
“Nimm die Tabelle sleep
und filtere daraus alle Zeilen, die zu der Gruppe 1
gehören, und speichere sie in die Variable gruppe1
”
Das Resultat sind zwei Variablen, die einen Teil der Tabelle sleep
enthalten, und zwar jeweils zu einer der beiden Gruppen.
Wieso dann eigentlich noch diese ,
-Sache am Ende der eckigen Klammern?
Das gehört zur Art, wie R Tabellen indiziert, sprich wie man einzelne Bereiche der Tabelle ansteuert:
#> extra
#> 1 0.7
#> 2 -1.6
#> 3 -0.2
#> 4 -1.2
#> 5 -0.1
#> 6 3.4
#> 7 3.7
#> 8 0.8
#> 9 0.0
#> 10 2.0
#> 11 1.9
#> 12 0.8
#> 13 1.1
#> 14 0.1
#> 15 -0.1
#> 16 4.4
#> 17 5.5
#> 18 1.6
#> 19 4.6
#> 20 3.4
#> extra group ID
#> 1 0.7 1 1
#> [1] 1
#> Levels: 1 2 3 4 5 6 7 8 9 10
Die allgemeine Form ist tabelle[Zeilennummer, Spaltennumer]
, und jetzt fragt ihr euch vermutlich, wieso wir vorhin [[ ]]
benutzt haben, und jetzt [ ]
— die kurze Antwort ist: Das ist halt was anderes. Die Details sind erstmal nicht so wichtig, was ihr euch vorerst merken solltet ist folgendes:
sleep[1]
ergibt einendata.frame
mit nur einer Spaltesleep[1, ]
ergibt einendata.frame
mit nur einer Zeilesleep[[1]]
undsleep$extra
sind dasselbe (weilextra
die erste Spalte ist) und ergeben die erste Spalte als Vektorsleep$extra[[2]]
undsleep$extra[2]
sind dasselbe: Das zweite Element im Vektorsleep$extra
Bei einer Tabelle ist es nützlich mit Zeilen und Spalten zu arbeiten, um die gewünschten Werte rauszuholen, aber bei einem Vektor gibt es in diesem Sinne nur eine Dimension.
Sinn der Sache ist, dass wir Funktionen wie mean
oder sd
nur auf Vektoren anwenden können, was auch intuitiv irgendwie sinnvoll scheint, denn der Mittelwert einer ganzen Tabelle mit mehreren Variablen ist ja konzeptionell etwas… schwierig.
# Okay
mean(sleep$extra)
#> [1] 1.54
# Das selbe Ergebnis
mean(sleep[[1]])
#> [1] 1.54
# Auch okay!
mean(sleep[["extra"]])
#> [1] 1.54
# Das hier nicht so
mean(sleep[1])
#> Warning in mean.default(sleep[1]): argument is not numeric or logical:
#> returning NA
#> [1] NA
sleep[1]
gibt euch zwar auch die Spalte extra
, aber wie schon gesagt, in data.frame
-Form, und nicht als Vektor.
Vermutlich verwirrt euch das ganze Geklammere jetzt mehr oder weniger stark, aber glaubt mir, wenn wir erstmal ein Gefühl dafür habt ist es sehr viel Wert diese Grundlagen auf dem Schirm zu haben (oder sie zumindest nachlesen zu können), denn in der ersten Zeit eurer R-Nutzung werdet ihr massenhaft kleinere und größere Fehler in dieser Art machen, wo ihr zwar das richtige meint, aber R nicht das richtige sagt.
Die andere Sache ist, dass ihr das mit den eckigen Klammern gar nicht so häufig brauchen werdet, wenn ihr euch erstmal an das tidyverse und dplyr gewöhnt habt, aber dazu später mehr.
Wir schneiden das Ganze Thema Subsetting hier auch erstmal nur an, aber wenn ihr’s jetzt schon ganz genau wissen wollt, könnt ihr die Details hier nachlesen
4.5 Logische Vergleiche
Logik! Eine Welt des Spaßes, der internen Konsistenz[^Naja, fast. Aber Gödel lassen wir mal aus.] und der unendlichen Anwendbarkeit in allen Bereichen.
Was ihr intuitiv als Logik kennt ist alleridngs etwas anderes als formale Logik, also das, was Computer verstehen.
Wir brauchen zum Glück nicht all zu viel davon, nur den Standardkram und nichtmal das volle Spekrum Bool’scher Algebra.
Wir brauchen Logik in R in erster Linie zum indizieren von Objekten. Das heißt, wenn wir alle Zeilen einer Tabelle haben wollen, für die eine bestimmte Variable einen bestimmten Wert hat oder eine Bedingung erfüllt, dann drücken wir das durch Logik aus. Dasselbe funktioniert natürlich auch bei Vektoren (und strenggenommen funktioniert Tabellenindizierung sowieso über Vektorindizierung). Man nehme folgendes Beispiel:
#> extra group ID
#> 6 3.4 1 6
#> 7 3.7 1 7
#> 16 4.4 2 6
#> 17 5.5 2 7
#> 19 4.6 2 9
#> 20 3.4 2 10
Das heißt: “Nimm die Tabelle sleep
und gib mir alle Zeilen (das mit den eckigen Klammen), für die die Variable sleep$extra
größer als 3 ist”.
Das Ergebnis eines logischen Vergleichs ist immer entweder TRUE
oder FALSE
für wahr oder falsch.
Wenn wir in R indizieren wollen, können wir dafür auch direkt TRUE
und FALSE
statt eines Vergleichs benutzen:
#> [1] 1 1
Hier haben wir uns effektiv nur die ersten beiden Werte des Vektors fib
ausgeben lassen, weil R alles ausgibt, was mit TRUE
indiziert ist und alles weglässt, was mit FALSE
indiziert ist.
c(TRUE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE)
ist hier ein logischer Vektor, also ein Vektor mit, naja, logischen Werten, der praktisch der Reihe nach jedes Element im Vektor fib
entweder an oder aus schaltet, wie Lichtschalter. Wir müssen dafür nicht unbedingt einen logischen Vektor der gleichen Länge (Anzahl der Elemente) wie unser Zielvektor (der, den wir indizieren/filtern wollen) benutzen, aber es bietet sich für den Einstieg an so genau wie möglich zu sein.
Wir könnten aber auch sowas machen:
#> [1] 1 2 5 13
#> [1] 1 1 5 8
Wichtig hierbei ist, dass der logische Vektor ein ganzer Faktor des Zielvektors ist, das heißt, dass die Anzahl der Element im Zielvektor ganz durch die Anzahl der Elemente im logischen Vektor teilbar sein muss (also ohne Rest), ansonsten bekommen wir potenziell schwer vorhersagbare Ergebnisse.
Was R hier macht nennt sich Recycling: In der ersten Zeile im letzten Beispiel wird der Vektor c(TRUE, FALSE)
solange recyclet, also wiederverwendet, bis der ganze Zeilvektor “abgedeckt” ist. Wenn der logische Vektor nicht sauber in den Zielvektor passt (in Bezug auf die Anzahl der Elemente), dann bleibt entweder was übrig oder es reicht nicht. Das wäre schade.
4.5.1 Operatoren
Es gibt eine Reihe logischer Operatoren wie hier >
für “ist größer als”, die wichtigsten in Übersicht:
==
(doppeltes Gleichheitszeichen ohne Leerzeichen dazwischen)- “Ist gleich”
1 == 2
–>FALSE
3 == 3
–>TRUE
3 == "Hallo"
–>FALSE
!=
- “ist ungleich”
- Die Negation von
==
, also immer da wo==
euchTRUE
zeigt, gibt!=
euchFALSE
und andersherum. pi != 3
–>TRUE
"Psychologiestudium" != "Voll gute Idee"
!
(ja, ein einfaches Ausrufezeichen)- “Negation”
- Dreht ein
FALSE
zu einemTRUE
um und andersherum. Wichtig: Klammern! !(2 == 3)
TRUE == !FALSE
>
und<
(spitze Klammern)- “ist größer/kleiner als”
- Da wo die Klamer spitz ist, soll das kleinere sein
5 > 4
–>TRUE
2^10 < 1000
–>FALSE
2 < 5 < 4
–> Funktioniert nicht!
>=
,<=
- “Größer gleich bzw. kleiner gleich”
- Ist Entweder
a > b
odera == b
? 5 >= 4
16 <= 2^4
Diese Ausdrücke können wir auch auf bestimmte Arten verknüpfen:
&
(oder auch&&
)- “Und”
- Ist
TRUE
, wenn beide SeitenTRUE
sind (1 < 3) & (5 < 10)
–>TRUE
(5 < 2) & (2 < 10)
–>FALSE
- 1 und 0 werden zu
TRUE
bzw.FALSE
übersetzt:1 & (2 == 2)
–>TRUE
!(0 & FALSE)
–>TRUE
|
(oder auch||
)- “Oder”
- Ist entweder A oder B oder beides
TRUE
? - Da
|
auch wahr ist, wenn nur eine Seite wahr ist, ist es auch Grundlage etlicher Mathe-/Logikwitze (1 < 3) | (5 < 10)
–>TRUE
(5 < 2) | (2 < 10)
–>TRUE
xor()
- “Entweder … oder …” (ausschließend!)
- Wenn euch
|
zu unspezifisch ist - Ist nur war, wenn eins von beidem wahr ist, aber nicht, wenn beides wahr ist
- Kein binärer Operator wie die anderen, sondern eine R-Funktion mit Klammern und so
xor(TRUE, FALSE)
–>TRUE
xor(TRUE, TRUE)
–>FALSE
xor((1 < 3), (5 < 10))
–>FALSE
xor((5 < 2), (2 < 10))
–>TRUE
4.5.2 Spezielle Tests
Die obigen Operatoren können wir für getrost für Vektorvergleiche benutzen, aber es gibt noch ein paar Sonderfälle. Was zum Beispiel, wenn wir nur generell wissen wollen, ob ein Element wie eine Zahl oder ein String in einem Vektor enthalten ist? Oder was, wenn wir wir auf spezielle Typen oder Klassen testen wollen? Was das im Detail heißt sehen wir in den entsprechenden Abschnitten zu Datentypen noch einmal, aber hier schonmal eine Kurzreferenz:
%in%
(auch hier, ohne Leerzeichen dazwischen!)- “Ist in”
- Mengentheoretisch ist das \(x \in X\)
- Ist Element
a
in Mengeb
? 5 %in% c(1, 4, 5, 3)
–>TRUE
"B" %in% c("a", "b", "c")
–>FALSE
"B" %in% c("a", "B", "c")
–>TRUE
c(1, 2) %in% 1:5
–>TRUE
is.na()
: Testet auf fehlende Werte (missing values,NA
)is.null()
: Testet auf leere Werte (NULL
)is.nan()
: Testet aufNaN
(Not a Number)
4.5.3 Indexing: Beispiele
Das war jetzt relativ viel Information, und ihr müsst euch das auch nicht alles sofort merken, sondern nur wissen, wo ihr’s bei Bedarf nachschlagen könnt.
Die Motivation hinter dem Logikram ist wie erwähnt primär das Filtern von Tabellen und Vektoren, was wir nunmal relativ häufig brauchen um zum Beispiel bestimmte Untergruppen in unseren Datensätze zu analysieren, zum Beispiel Personen älter als 35 (z.B. age > 35
) oder Menschen, die sowohl weiblich sind als auch Medikament B bekommen haben (z.B. geschlecht == "weiblich" & drug == "B"
).
4.5.3.1 Vektoren
Vektoren werden immer elementweise verglichen, das heißt, dass das Ergebnis von c(1, 2) == 1
nicht FALSE
oder TRUE
ist, sondern der logische Vektor TRUE FALSE
. Dadurch entsteht durch den logischen Vergleich eines Vektors ein Vektor aus TRUE
und FALSE
, den wir zum indizieren benutzen können, wie wir weiter oben schon gesehen haben.
# Irgendwelche Zahlen
x <- c(4, 7, 2, 1, 7, 9, 6, 5, 4, 3, 3, 2, 2, 5, 8, 9, 31)
# Alle Zahlen größer 4: "Gib mir x, wo x > 4"
x[x > 4]
#> [1] 7 7 9 6 5 5 8 9 31
#> [1] FALSE TRUE FALSE FALSE TRUE TRUE TRUE TRUE FALSE FALSE FALSE
#> [12] FALSE FALSE TRUE TRUE TRUE TRUE
#> [1] 7 7 9 6 8 9
Das Ganze lässt sich natürlich beliebig komplex aussehen lassen, weshalb zu viele Bedingungen in Kombination etwas verwirrend aussehen können.
Weiterhin können wir einen Vektor natürlich auch durch einen Vergleich eines anderen Vektors indizieren.
x <- c(4, 7, 2, 1, 7, 9, 6, 5, 4, 3)
y <- c(6, 7, 10, 1, 9, 3, 6, 5, 6, 3)
# x, wo x größer gleich 4 ist *und* y kleiner 6
x[x >= 4 & (y < 6)]
#> [1] 9 5
#> [1] 7 1 6 5 3
#> [1] 4 2 7 4
4.5.3.2 Tabellen data.frame
Tabellenindexing ist nichts anderes als Vektorindexing mit einer anderen Struktur.
Alle Regeln zum Vektorindexing, die wir bisher gesehen haben, gelten auch so für data.frames
, nur dass wir hier jetzt auf einmal in Spalten und Zeilen denken müssen, anstatt in Vektoren.
Money quote: “[…] the reason we use
<-
for assignment is it made sense in a programming language written before the incorporation of Apple Computers, because it made sense in a programming language written before the moon landings.”↩