Objektstrukturen in R

Datenstrukturen, Variablentypen und Sonderwerte

Um effektiv mit R programmieren zu können, ist es wichtig die grundlegenden Datenstrukturen, die wichtigsten Variablentypen, sowie spezielle Werte zu kennen (in einem der letzten Posts haben wir zum Beispiel schon das NA kennengelernt). Gerade am Anfang ist es gar nicht so einfach all das auseinanderzuhalten und deswegen möchte ich mit diesem Post etwas Überblick verschaffen.

Übersicht der Datenstrukturen

Kurz zusammengefasst gibt es in R vier grundlegende Datenstrukturen:
Vektoren: Vektoren bilden das Fundament; sie sind eindimensional und enthalten n Elemente, die alle vom gleichen Variablentyp sein müssen. Du wirst sehen, dass im Prinzip alle anderen Datenstrukturen auf vector zurückgreifen.
Matrizen: Matrizen setzen sich aus einem oder mehreren Vektoren zusammen, die alle die selbe Länge haben müssen. Matrizen sind zweidimensional mit n Reihen und k Spalten. Alle Vektoren müssen vom selben Datentyp sein.
Data Frames: Data Frames setzen sich auch aus einem oder mehreren Vektoren der selben Länge zusammen, sind zweidimensional mit n Reihen und k Spalten. Anders als bei Matrizen können die Vektoren aber von unterschiedlichen Variablentypen sein. Data Frames sind somit die üblichen Verdächtigen, wenn es um klassische Analysen mit typischen Datensätzen geht.
Listen: Listen sind etwas komplexer, denn sie setzen sich selbst wiederum aus beliebigen Datenstrukturen zusammen. Das heißt, eine Liste kann sowohl drei Elemente enthalten, die alle Vektoren sind (im Gegensatz zu Data Frames können die Vektoren auch unterschiedlicher Länge sein), eine Liste kann aber auch aus einem Vektor, einem Data Frame, und einer weiteren Liste bestehen. Manchmal kann man Daten nur vernünftig strukturieren, indem man Listen benutzt. Dazu später mehr.

Vektoren

Wie schon oben und in einem meiner ersten Posts ausführlich beschrieben, bilden Vektoren die Grundlage (für alle komplexeren Strukturen). Sie sind eindimensional mit n Elementen, welche alle vom selben Typ sein müssen. Die wichtigsten Variablentypen sind:
numeric: Numerische Variablen, zum Beispiel 49.2
integer: Ganzzahlen, zum Beispiel 16
character: Zeichenketten, zum Beispiel „Hallo, wie gehts?“
logical: Boolsche Werte TRUE oder FALSE
Date: Datum, wird intern als integer gehandhabt (Anzahl der Tage seit dem 01.01.1970) und als character angezeigt (z.B. „2016-09-27“)
factor: Kategorische Variable; ist relevant zum Beispiel für statistische Modelle, welche factors entsprechend als nominale Variablen behandeln
Der Typ eines Vektors lässt sich übrigens mit der Funktion str herausfinden.

Variablen des Typs numeric werden wir eigentlich immer begegnen: Man denke an Umsatz, Blutwerte, Temperatur, Alter, Durchschnittswerte, usw. Ein simples Beispiel wäre hier: weightInKG <- c(72.4, 91.0, 84.3, 66.5). Das Objekt weightInKG - schon für sich selbst sprechend - ist ein numerischer Vektor der Länge 4. Genau das wird uns angezeigt, wenn wir str(weightInKG) eingeben.

Die meisten Funktionen (z.B. c oder seq) liefern uns einen Vektor vom Typ numeric, auch wenn die Zahlen an sich keine Nachkommastellen haben. Einen integer können wir entsprechend erstellen, indem wir einen numeric zu einem integer umwandeln: myInt <- as.integer(c(1, 5, 4, 19)). Die Funktion as.integer wandelt also numeric-Vektoren ganz einfach in integer-Vektoren. Sollten die numerische Elemente Nachkommastellen enthalten, werden diese gekappt (nicht gerundet!): as.integer(1.9). Darüber hinaus können wir mit folgender Notation eine Ganzzahl-Sequenz von einer zur anderen Zahl bilden: ID <- 1:10. Auch hier können wir wieder schauen, ob alles passt: str(ID).

Einen character-Vektor bilden wir, indem wir die Elemente in Anführungszeichen schreiben: USCandidates <- c("Trump", "Clinton"). Hier muss man aufpassen, dass zum Beispiel folgender Vektor kein numerischer ist, obwohl die Zeichen ganz klar Zahlen sind: NotANumericVector <- c("1", "2"). Da die Zahlen in Anführungszeichen stehen, ist dieser Vektor vom Typ character; somit können hier auch keine Funktionen benutzt werden, die numerische Werte als Input benötigen (z.B. mean, sum, und viele weitere). Darüber hinaus muss man wissen, dass R automatisch den ganzen Vektor als character ansieht, auch wenn einige Werte als numerisch eingegeben werden: AnotherCharacterVector <- c(41.8, 24.2, "0", 4.4). Hier ist nur die Null in Anführungszeichen, was aber ausreicht, den gesamten Vektor zu einem character-Vektor zu machen.

Vektoren vom Typ logical können nur TRUE oder FALSE enthalten. Dies ist nützlich für Steuerlogiken innerhalb des Skripts. So kann man zum Beispiel am Anfang des Skripts saveToDatabase <- TRUE setzen, um später zu wissen, dass der Output in die Datenbank gespeichert werden soll. Wandelt man logical zu numeric oder integer um, dann gilt: FALSE = 0 und TRUE = 1. Logical-Werte tauchen grundsätzlich bei Vergleichen auf: So liefert 5 > 4 hoffentlich den Wert TRUE und 4 > 5 hingegen FALSE. Weiteres Beispiel: is.logical(TRUE).

Kommen wir zum Typ Date. Dieser Typ wird vor allem dann interessant, wenn wir mit Zeitreihen hantieren, datumsabhängige Prognosen erstellen möchten, oder ähnliches. Einen Date-Vektor erstellen wir einfach mit firstdate <- as.Date("2016-09-25"). Wir sehen zwei Dinge: wir benutzen einen character, der ein ganz bestimmtes Format aufweist, nämlich "YYYY-mm-dd", bzw. Jahr-Monat-Tag. Wir können auch andere Formate benutzen, müssen es der Funktion as.Date dann aber mitteilen: seconddate <- as.Date("26.09.2016", format="%d.%m.%Y"). Um einen Vektor wirklich als Datumsvektor benutzen zu können, müssen wir uns sicher sein, dass es ein Date-Vektor ist; folgendes ist kein Date-Vektor: myDate <- "2016-05-01"!

Als vorerst letzten Variablentyp schauen wir uns factor an. Ein factor-Vektor ist vor allem dann relevant, wenn es um statistische Methoden geht. Wenn ein character-Vektor zu einem factor gemacht wird, weiß R, dass es eine kategorische Variable mit verschiedenen Leveln ist. Haben wir zum Beispiel einen numeric vector mit drei Zahlen, welche eine bestimmte experimentelle Gruppe darstellen: numCondition <- c(2, 3, 1, 2, 1), so können wir diese zu einem Faktor machen. Dazu erstellen wir erst einen Vektor mit den Gruppennamen, den Leveln entsprechend aufsteigend (!): nameCondition <- c("Experimental", "Relax", "Control"). In diesem Beispiel wäre eine 1 die experimentelle Gruppe, 2 = Relax und 3 = Kontrollgruppe. Wir erstellen jetzt den factor vector: Condition <- factor(numCondition, labels=nameCondition). Wir können die Levels mit levels(Condition) überprüfen.

Wenn du die verschiedenen Variablentypen wirklich verstanden hast, dann wird es in Zukunft wesentlich einfacher sein, die Daten ins richtige Format zu bringen, Fehler vorzubeugen, und deine Analysen effizienter zu machen. Es ist wichtig, diese Grundlagen über Vektoren zu kennen, denn nun kommen wir zu den komplexeren Datenstrukturen in R (man merke hier den Unterschied zwischen Variablen- oder Datentypen, über die ich oben geschrieben habe, und den Datenstrukturen wie vector, data.frame, oder list).

Data Frames

Wenn man den Vektor als Grundelement versteht, so ist ein Data Frame nicht mehr schwierig zu fassen: Ein data.frame ist nichts anderes als ein zweidimensionales Objekt, das aus einem oder mehreren Vektoren der selben Länge besteht. Beispiel:

dfUsers <- data.frame(ID=1:2,
                      Name=c("tjohannsen", "Newsletter"),
                      Mail=c("mail@r-coding.de", "newsletter@r-coding.de"),
                      ValidFrom=as.Date(c("2016-07-01", "2016-09-11")),
                      stringsAsFactors=FALSE)

Wir haben hier ein Objekt dfUsers, welches ein data.frame sein soll, und dieser data.frame hat verschiedene Spalten: ID (integer), Name (character), Mail (character), ValidFrom (Date). stringsAsFactors ist ein Argument für data.frame und gibt an, ob "strings" (= characters) automatisch zum Typ factor umgewandelt werden sollen. Wir können nun die einzelnen Spalten direkt ansprechen und wie ganz normale Vektoren behandeln (was sie ja auch sind!). Beispiel: dfUsers$Mail[1], oder etwas anspruchsvoller: dfUsers$Mail[dfUsers$Name=="tjohannsen"]. Für alles weitere siehe diesen oder diesen Post, welche sich vollständig den Data Frames widmen.

Listen

Wenn die Daten noch etwas komplexer sind, dann muss man möglicherweise Listen benutzen. Auf Listen werde ich in einem gesonderten Post genauer eingehen, da viele Operationen etwas anders sind als bei den Data Frames, aber dennoch hier ein kleines Beispiel:

lstBlogpost <- list(Title="Funktionen in R",
                    Link="http://r-coding.de/blog/funktionen-in-r/",
                    Hearts=3,
                    DatePublished=as.Date("2016-09-21"),
                    SubHeadings=c("Funktionen in R",
                                  "Eigene Funktionen schreiben",
                                  "Weiteres Beispiel",
                                  "Was man beachten sollte"),
                    SimilarPosts=c("Missing Values", 
                                   "Daten lesen und schreiben",
                                   "Data Frames - Zweiter Teil"))

Wir sehen: Das Objekt lstBlogpost ist eine Liste mit verschiedenen Elementen. Diese Elemente sind nicht nur von unterschiedlichen Variablentypen, sondern auch unterschiedlicher länge. Somit fallen Listen vollständig aus dem "n x k" - Matrixschema, was wir bei Data Frames oder Martizen (s.u.) haben. Dennoch: Listen sind auch kein Hexenwerk; wir können die Elemente ganz einfach mit dem Namen ansprechen: lstBlogpost$SubHeadings. Ebenso können wir zum Beispiel das zweite Element wie folgt ansprechen: lstBlogpost[[2]]. Beachte: Um den Vektor zu erhalten, musst du zwei eckige Klammern bei Listen benutzen. Verwendest du nur eine eckige Klammer, erhältst du selbst wiederum eine Liste. Vergleiche hierzu: str(lstBlogpost[[1]]) mit str(lstBlogpost[1]).

Matrizen

An dieser Stelle möchte ich noch kurz auf Matrizen eingehen. Matrizen sind wie Data Frames zweidimensional, allerdings muss die gesamte Matrix vom selben Datentyp sein. Das klassische Beispiel ist eine numeric matrix. Wir können zum Beispiel eine Beispielmatrix erstellen, die für 10 Fälle je 5 Einträge auf einer Likert-Skala entspricht: matLikert <- matrix(round(runif(50, 1, 7)), ncol=5, nrow=10). Mit round(runif(50, 1, 7)) erstellen wir 50 Zufalls(ganz)zahlen zwischen 1 und 7, und mit matrix(..., ncol=5, nrow=10) geben wir vor, dass wir eine Matrix erstellen möchten mit 5 Spalten und 10 Reihen. Da wir bei Matrizen alles vom selben Typ haben können wir sowohl Reihen als auch Spalten auswählen und erhalten immer einen Vektor zurück (das wäre beim Data Frame anders, wenn es um Reihen geht). Beispiel: matLikert[,3]. Wir können eine Matrix problemlos in ein Data Frame umwandeln: dfLikert <- data.frame(matLikert).

Sonderwerte

Zu guter Letzt kann man Datenstrukturen und Datentypen kategorisch auch noch von Sonderwerten unterscheiden. R hat nämlich eigene Werttypen, die in bestimmten Fällen sehr hilfreich und sinnvoll sein können. Fangen wir an mit NULL. NULL sagt uns nichts anderes als: Diesen Wert gibt es nicht. So kann man zum Beispiel Funktionen mit default argument NULL schreiben (siehe hier den Post zu Funktionen) und die Funktion weiß anschließend bei einem Funktionsaufruf, ob der Wert gesetzt wurde (d.h. anders als NULL) oder nicht. Ebenso kann der Rückgabewert einer Funktion NULL sein - die Funktion hat also nichts "returned". Neben NULL gibt es einen weiteren Wert, den wir viel öfter zu Gesicht bekommen werden: NA. NA steht für "not available" und signalisiert fehlende bzw. unbekannte Werte ("missings"). Da NA's sehr wichtig für Analysen sind, gibt es dafür bereits einen eigenen Post, siehe hier. Im Code unten siehst du darüber hinaus zwei kleine Beispiele. Dann gibt es noch Inf als eigenen Sonderwert (bzw. -Inf). Dieser Wert steht für "infinity" und ergibt sich immer dann, wenn eine Berechnung eben "unendlich" ergibt. Beispiel: 1 / 0. Ein anderes Beispiel wäre, wenn Zahlen so groß sind, dass der Computer damit nicht mehr umgehen kann: 2^1024. Als letzten Sonderwert gibt es noch NaN: "not a number". Der Wert ist sehr selten und entsteht eben dann, wenn alles andere keinen Sinn ergibt - weder Zahlen, noch missings, noch infinity. Der Wert steht für etwas, das einfach nicht berechnet werden kann, zum Beispiel 0/0. Sonderwerte können alle mit ihren eigenen Funktionen getestet werden: is.null, is.na, is.infinite, und is.nan.

Zusammenfassung

Alright, das war jetzt einiges an Input. Allerdings hilft es auch ungemein für alles weitere in R und es ist absolut ratsam, sich diese Dinge zu verinnerlichen. Wir haben über Vektoren als Grundstruktur gesprochen und uns angeschaut, was für Datentypen ein Vektor beinhalten kann: numeric, integer, character, logical, Date, factor. Wir haben uns dann komplexeren Strukturen gewidmet und konnten feststellen, dass die meisten dieser Strukturen mehr oder minder direkt auf den Vektor zurückgreifen. Dazu gehören: data.frame, matrix, list. Zum Schluss haben wir uns noch Sonderwerte in R angeschaut: NULL, NA, Inf bzw. -Inf, und NaN.

Hast du noch weitere Fragen zu Datenstrukturen, Variablentypen, oder Sonderwerten? Oder zu einem ganz anderen Thema? Schreib mir einfach eine Mail: mail@r-coding.de.
Bleib außerdem auf dem Laufenden mit dem r-coding Newsletter. Du erhältst Infos zu neuen Blogeinträgen, sowie kleine Tipps und Tricks zu R. Melde dich jetzt an: http://r-coding.de/newsletter.

Cheers

Ähnliche Beiträge

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.