Übersicht über Datentypen in R
Die Programmiersprache R besitzt ihr eigenes Konzept, wie die eingebauten Datentypen aufgebaut sind; es unterscheidet sich von vielen anderen Programmiersprachen darin, dass es nicht elementaren Datentypen gibt (wie Zahlen, Zeichen und logische Werte) und zusammengesetzte Datentypen (also zum Beispiel Vektoren von Zahlen). Sondern es gibt nur Vektoren; eine Zahl ist dann ein Spezialfall eines Vektors der Länge 1. Neben den Vektoren gibt es sehr viele weitere vorbereitete Datentypen, so dass man als Programmierer erst für sehr spezielle Anwendungen eigene Datentypen definieren muss. Es wird eine Übersicht über die Datentypen in R gegeben sowie die Vorgehensweise beschrieben, wie man mit ihnen vertraut werden kann.
- Einordnung des Artikels
- Einführung
- Einfache, zusammengesetzte und selbstdefinierte Datentypen
- Datentypen in R
- Vorgehensweise zum Kennenlernen der Datentypen von R
- Atomare Strukturen (atomare Vektoren)
- Der Modus eines Objektes
- Der Speicher-Modus eines Objektes
- Typumwandlung (coercion)
- Object-Checking Functions (is-dot) und Object-Coercion Functions (as-dot)
- Interpretation als fundamentale Datentypen
- Vektoren beliebiger Länge
- Rekursive Strukturen (rekursive Vektoren)
- Strukturierung von Objekten mit Attributen
- Einführung
- Spezielle Attribute
- Datentypen aus den Basis-Paketen
- Atomare Strukturen
- Rekursive Strukturen
- Definition von Klassen
- Diagnosefunktionen
- Zusammenfassung
- Fundamentale Datentypen
- Funktionen zum Abfragen und Setzen des Modus und Speicher-Modus
- Atomare Strukturen
- Rekursive Strukturen
Einordnung des Artikels
- Einführung in die Informatik
- Einführung in die Programmiersprache R
- Übersicht über Datentypen in R
- Einführung in die Programmiersprache R
Einführung
Einfache, zusammengesetzte und selbstdefinierte Datentypen
Jede Programmiersprache verfügt über bereits eingebaute Datentypen (etwa für logische Werte, für ganze Zahlen, für Gleitkommazahlen oder für Zeichen). Auf diesen einfachen (oder fundamentalen oder elementaren oder atomaren) Datentypen sind dann auch schon Operationen definiert (wie logisches UND, logische ODER, die Grundrechenarten).
Weiter gibt es Datentypen, in denen mehrere Objekte, die jeweils einen fundamentalen Datentyp besitzen, zusammengefasst werden, etwa zu Vektoren, Listen oder Abbildungen (map). Diese zusammengesetzten Datentypen werden oft als Container bezeichnet. Und auf den Containern gibt es wiederum spezifische Operationen (wie Zugriff auf die Komponenten eines Vektors, Aneinanderhängen von Containern, Suche nach Elementen in einem Container und so weiter).
Viele Programmiersprachen gehen dann noch einen Schritt weiter: Für Programme, die sehr komplexe Aufgaben erfüllen sollen, werden diese fundamentalen Datentypen und Container nicht mehr ausreichen; man kann sich aus den elementaren und den zusammengesetzten Datentypen auch seine eigenen Datentypen (oder selbstdefinierten Datentypen) aufbauen und darauf geeignete Operationen definieren.
Eine neue Programmiersprache zu lernen heißt zuerst immer deren Datentypen kennenzulernen:
- Fundamentale Datentypen:
- Welche fundamentalen Datentypen bietet die Sprache an?
- Welche Operationen darauf sind bereits definiert?
- Wie lautet die Syntax, um eine Variable mit einem fundamentalen Datentyp zu definieren?
- Wie lautet die Syntax, um die vordefinierten Operationen aufzurufen?
- Zusammengesetzte Datentypen:
- Welche Container und zugehörige Operationen bietet die Programmiersprache?
- Wie lautet die Syntax, um eine Variable für einen Container anzulegen und die Operationen aufzurufen?
- Selbstdefinierte Datentypen:
- Wie werden selbstdefinierten Datentypen und Operationen darauf erzeugt?
- Wie definiert man Variable für einen selbstdefinierten Datentyp und wie ruft man die darauf definierten Operationen auf?
Datentypen in R
Die oben beschriebene Einteilung der Datentypen in die drei Gruppen
- Fundamentale Datentypen
- Zusammengesetzte Datentypen
- Selbstdefinierte Datentypen
ist in R nicht zutreffend, man kann die Datentypen von R aber so zurechtbiegen als wäre die Einteilung zutreffend. Um die Datentypen von R kennenzulernen, wird hier auch so vorgegangen.
Richtig ist dagegen folgendes (siehe Abbildung 1):
Alles, was in R vorkommt, ist ein Objekt und einem Objekt kann eindeutig zugeordnet werden:
- eine Struktur (oder der Modus), die in der R-Dokumentation als mode bezeichnet wird, und
- eine Länge.
In Letzterem ist enthalten, dass es in R keine atomaren Datentypen gibt; vielmehr gibt es nur zusammengesetzte Datentypen und die atomaren Datentypen sind der Spezialfall eines Vektors der Länge 1.
Variablen sind von Objekten zu unterscheiden: In einer Variable wird ein Objekt abgespeichert, so dass man später darauf zugreifen kann.
Man kann den Satz auch umgekehrt lesen: Zu jedem Objekt kann man eine Variable definieren und das Objekt dort "aufbewahren".
Wird ein Objekt nur erzeugt, aber nicht in einer Variable abgespeichert, so kann man es für eine Anweisung nutzen, hat aber später keine Möglichkeit mehr darauf zuzugreifen (man müsste es wieder neu erzeugen).
Für die Struktur eines Objektes gibt es zwei Möglichkeiten:
- atomare Struktur (oder atomarer Vektor) und
- rekursive Struktur (oder rekursiver Vektor).
Mit atomarer Struktur ist gemeint, dass alle Komponenten den identischen Datentyp besitzen müssen. Paradebeispiel ist ein Vektor, dessen Komponenten Gleitkommazahlen sind.
Im Unterschied dazu bedeutet rekursive Struktur, dass für jede Komponente eines Vektors ein beliebiger Datentyp vereinbart werden darf, also zum Beispiel wiederum Vektoren. Das Paradebeispiel in R dafür ist eine Liste. Sie könnte etwa aus drei Komponenten bestehen:
- aus einer Zahl,
- einer Zeichenkette und
- einem Vektor von Zahlen.
Zudem gibt es weitere optionale Attribute; optional heißt, dass sie gesetzt werden können, aber nicht gesetzt werden müssen. Mit Hilfe der Attribute können insbesondere selbstdefinierte Datentypen realisiert werden. In den Basis-Paketen von R sind schon viele Datentypen enthalten, die Attribute einsetzen und so Objekten weitere Struktur verleihen. Als Beispiele seien Matrizen und Data Frames genannt:
- Bei Matrizen werden die Komponenten zunächst wie bei einem Vektor abgespeichert; durch das sogenannte dim-Attribut wird die Anzahl der Zeilen und Spalten der Matrix festgelegt. Dies ermöglicht typische Matrizen-Operationen (wie die Matrizen-Multiplikation) zu definieren.
- Ein Data Frame ist eine Kombination aus einer Liste und einer Matrix: die Einträge der Liste
- haben eine Bezeichnung (Attribut names) und
- sind Vektoren identischer Länge — also genau die Struktur, die man für die Aufnahme statistischer Daten benötigt.
Vorgehensweise zum Kennenlernen der Datentypen von R
Vor allem für Leser, die bereits mit einer objekt-orientierten Programmiersprache wie Java oder C++ vertraut sind, mag es ungewohnt erscheinen, wie in R die Datentypen definiert sind und welche zentrale Rolle dabei Vektoren spielen.
Wenn hier von Vektoren die Rede ist, dann ist damit immer folgende Datenstruktur gemeint:
- Ein Vektor besteht aus beliebig vielen Elementen (oder Komponenten).
- Alle Elemente besitzen identischen Datentyp.
- Die Elemente sind indiziert; man kann also über einen Index eindeutig auf die Komponenten eines Vektors zugreifen.
Um mit den Datentypen von R vertraut zu werden, wird daher der zu Beginn angedeutete Weg eingeschlagen:
- Es werden zunächst Vektoren der Länge 1 näher untersucht; dies entspricht in anderen Programmiersprachen den fundamentalen Datentypen. Dabei werden insbesondere die relevanten Operationen besprochen.
- Sodann werden die zusammengesetzten Datentypen untersucht — also eigentlich Vektoren beliebiger Länge. Zentraler Gegenstand der Untersuchung ist dann, wie die zuvor besprochenen Operationen zu verallgemeinern sind und welche neuen Operationen hinzukommen.
- Nach den Vektoren werden noch weitere in den Basis-Paketen enthaltene Datentypen besprochen: diese sind zum Teil von atomarer, zum Teil von rekursiver Struktur. Allen gemeinsam ist, dass jetzt Attribute eingesetzt werden, um ihnen mehr Struktur zu verleihen. Beispiele für atomare Strukturen sind Matrizen (matrix), Felder (array) und Faktoren (factor), Beispiele für rekursive Strukturen sind Listen (list) und Data Frames (data frame).
- Die Listen können dann zu beliebigen selbstdefinierten Datentypen erweitert werden, insbesondere lassen sich Klassen bilden, die in der objekt-orientierten Programmierung eingesetzt werden.
Für den Programmier-Anfänger werden einige Beschreibungen in diesem Kapitel zur Übersicht über Datentypen in R sehr abstrakt und wenig anwendungsbezogen erscheinen. Für einen ersten Durchlauf reicht es auch, die hier vorgestellten Konzepte zur Kenntnis zu nehmen. Aber spätestens wenn man eigene Datentypen definiert, muss man diese abstrakten Konzepte gründlich verstehen und selber einsetzen.
Atomare Strukturen (atomare Vektoren)
Der Modus eines Objektes
In Abbildung 1 ist dargestellt, dass es in R eigentlich keine atomaren Datentypen gibt, sondern nur Vektoren. Was in anderen Programmiersprachen atomare Datentypen sind, sind in R Vektoren der Länge 1. Und nach Abbildung 1 kann ein Vektor entweder atomare oder rekursive Struktur haben. Mit atomarer Struktur ist gemeint, dass alle Komponenten (oder Elemente) eines Vektors vom gleichen Typ oder Modus (mode) sein müssen. Dieser Modus ist dann zugleich der Modus des gesamten Vektors.
In R sind folgende Modi erlaubt (in vielen anderen Programmiersprachen wären dies die fundamentalen Datentypen):
- logischer Modus (logical)
- numerischer Modus (numeric)
- komplexer Modus (complex)
- Zeichen (character)
- rohe Bytes (raw).
Eine kurze Erklärung dieser 5 Modi:
Modus | Beschreibung |
logical | Die beiden logischen Werte TRUE und FALSE. |
numeric | Zahlen, die entweder als Gleitkommazahlen (double) oder als ganze Zahlen (integer) abgespeichert werden; in R werden per default Zahlen immer als Gleitkommazahlen abgespeichert, die Verwendung von ganzen Zahlen muss eigens angeordnet werden. |
complex | Für komplexe Zahlen, also zwei Zahlen vom numerischen Typ, die den Realteil und den Imaginärteil einer komplexen Zahl beschreiben; wird hier nicht näher besprochen. |
character | Zur Verarbeitung von einzelnen Zeichen und von beliebigen Zeichenketten, die in Hochkommas oder Anführungsstriche eingeschlossen werden, wie 'x'
oder "Hello World"
. |
raw | Ein Vektor der Länge 1 vom Typ raw entspricht einem Byte; bei der Ausgabe wird ein Byte durch zwei Hexadezimalzahlen dargestellt. Der Typ raw wird hier nicht besprochen. |
Die Liste der in R vorkommenden Modi ist in Wirklichkeit sehr viel länger; das Verständnis der weiteren Modi (außer den hier genannten) setzt aber ein hohes Maß an Vertrautheit mit R voraus. Für das erste Zwischenziel beim Erlernen von R — nämlich dessen Datentypen kennenzulernen — reicht es, über die Modi folgendes zu wissen:
- Die fundamentalen Datentypen entsprechen den Modi logical, numeric und character (die weiteren Modi complex und raw werden hier nicht besprochen).
- Der Modus eines atomaren Vektors stimmt mit dem Modus seiner Komponenten überein.
- Rekursive Vektoren werden aus Listen (list) aufgebaut und haben den Modus list.
Es gibt zwei Möglichkeiten, wie der Modus eines Objektes festgestellt werden kann:
- einerseits mit der Funktion mode(),
- andererseits mit einer der sogenannten is-Funktionen.
Das folgende Skript definiert einige Variablen und fragt ihren Modus ab. Dabei wird der Zuweisungs-Operator verwendet, also der Pfeil von rechts nach links: Der Wert auf der rechten Seite wird der Variable auf der linken Seite zugewiesen.
n <- 5 # ganze Zahl (integer)
x <- 2.5 # Gleitkommazahl (floating point number)
b <- TRUE # logischer Wert (boolean)
c <- '!' # Zeichen (character)
s <- "Hello World" # Zeichenkette (String)
mode(n)
# [1] "numeric"
is.numeric(n)
# [1] TRUE
mode(x)
# [1] "numeric"
is.numeric(x)
# [1] TRUE
mode(b)
# [1] "logical"
is.logical(b)
# [1] TRUE
mode(c)
# [1] "character"
is.character(c)
# [1] TRUE
mode(s)
# [1] "character"
is.character(s)
# [1] TRUE
is.numeric(s)
# [1] FALSE
Einige Aspekte dieses Beispiels sollen ausdrücklich betont werden:
Die Ausgaben zu x und n:
Ganze Zahlen und Gleitkommazahlen haben identischen Modus "numeric"; beide werden intern mit doppelter Genauigkeit (double precision) abgespeichert. Im nächsten Unterabschnitt wird der Speicher-Modus erklärt — damit lässt sich zwischen ganzen Zahlen und Gleitkommazahlen unterscheiden.
Die Ausgaben zu c und s:
Ebenso wird nicht zwischen einzelnen Zeichen und Zeichenketten unterschieden. Beide haben den Modus "character". Man erkennt daran, wie R intern arbeitet: Eine Zeichenkette wird als ein Vektor von einzelnen Zeichen aufgebaut — als Anwender muss man sich darum nicht kümmern und kann (bis auf wenige Spitzfindigkeiten) mit Zeichenketten wie mit Zeichen umgehen.
Zeile 32 und 33:
Die Variable s speichert eine Zeichenkette; die Abfrage, ob s den Modus "numeric" hat, muss daher FALSE ergeben.
Ausgabe des Modus:
Bisher wurde der Modus eines Objektes mit Begriffen wie logical, numeric oder character bezeichnet. Lässt man sich den Modus ausgeben, wird diese Bezeichnung als Zeichenkette ausgegeben, also mit Anführungsstrichen (wie "logical", "numeric" oder "character"; siehe Zeile 13, 18, 23 und 28)
Der Speicher-Modus eines Objektes
Oben wurde schon gesagt, dass hier nicht sämtliche Modi von R-Objekten besprochen werden. Entsprechend ist über den Speicher-Modus hier nur folgendes wichtig:
- Zahlen und Vektoren aus Zahlen haben den Modus "numeric", der Speicher-Modus spezialisiert dies zu "double" für Gleitkommazahlen beziehungsweise "integer" für ganze Zahlen; per default hat jede Zahl den Speicher-Modus "double".
- Für alle anderen Modi ist es nicht nötig zwischen Modus und Speicher-Modus zu unterscheiden (es gibt in R natürlich weitere Modi, für die dies nicht richtig ist — aber diese werden hier nicht besprochen).
Abgefragt wird der Speicher-Modus mit der Funktion storage.mode() oder den entsprechenden is-Funktionen:
n <- 5 # ganze Zahl (integer)
x <- 2.5 # Gleitkommazahl (floating point number)
storage.mode(n)
# [1] "double"
is.double(n)
# [1] TRUE
is.integer(n)
# [1] FALSE
storage.mode(x)
# [1] "double"
is.double(x)
# [1] TRUE
is.integer(x)
# [1] FALSE
Die folgende Tabelle zeigt nochmals die möglichen Speicher-Modi und die zugehörigen Modi (die beiden Modi "complex" und "raw" sind bereits weggelassen):
Speicher-Modus | Modus |
"logical"
| "logical"
|
"integer"
| "numeric"
|
"double"
| "numeric"
|
"character"
| "character"
|
Die möglichen Speicher-Modi und Modi sind jetzt in Anführungsstriche geschrieben; dies soll darauf hindeuten, dass die Rückgabewerte der Funktionen storage.mode() und mode() Zeichenketten sind und in der Konsolen-Ausgabe in Anführungsstriche geschrieben werden.
Die Reihenfolge, in der die Speicher-Modi angegeben wurden, hat im Zusammenhang mit den Typumwandlungen eine besondere Bedeutung: von oben nach unten kann eine Typumwandlung immer durchgeführt werden, von unten nach oben ist sie nicht immer möglich. Dies wird später noch ausführlich diskutiert.
Typumwandlung (coercion)
Oben wurde bereits gesagt, dass Zahlen stets als Gleitkommazahlen behandelt werden. So wird etwa mit der Anweisung
x <- 5
eine Variable x im Speicher-Modus "double" erzeugt, obwohl auf der rechten Seite der Zuweisung eine ganze Zahl steht — und man daher vielleicht den Speicher-Modus "integer" erwartet.
Für viele Anwendungen ist man gezwungen mit ganzen Zahlen zu rechnen, weil bei der Verarbeitung von Gleitkommazahlen Rundungsfehler entstehen können. Ein typisches Beispiel dafür ist die Differenz zweier großer Zahlen: hier kann es passieren, dass die Differenz in der selben Größenordnung liegt wie der Rundungsfehler und man daher dem Ergebnis nicht vertrauen kann. Oft lassen sich diese Fehler vermeiden, indem man die Gleitkommazahlen geeignet erweitert, so dass man die Berechnung — jetzt fehlerfrei — mit ganzen Zahlen durchführen kann. (Man muss dabei nur aufpassen, nicht den erlaubten Zahlenbereich für ganze Zahlen zu verlassen.)
Es gibt zwei Möglichkeiten, wie man etwa die Zahl x = 5 aus dem Beispiel oben in eine Zahl im Speicher-Modus "integer" verwandeln kann:
- Entweder indem man den Speicher-Modus ausdrücklich setzt; dies geschieht mit Hilfe der replacement-Version der Funktion storage.mode().
- Oder indem man die geeignete as-Funktion aufruft; hier ist es die Funktion as.integer().
Das folgende Skript zeigt die beiden Möglichkeiten:
x <- 5
storage.mode(x)
# [1] "double"
storage.mode(x) <- "integer"
storage.mode(x)
# [1] "integer"
y <- 17
storage.mode(y)
# [1] "double"
y <- as.integer(y)
storage.mode(y)
# [1] "integer"
Zur Erklärung:
Zeile 1: Die Variable x wird mit 5 initialisiert.
Zeile 3 und 4: Der Speicher-Modus von x wird abgefragt; die Ausgabe ist "double".
Zeile 6: Mit Hilfe der replacement-Version der Funktion storage.mode() wird der Variable x ein neuer Speicher-Modus zugeordnet.
Zeile 8 und 9: Der neue Speicher-Modus wird sofort abgefragt und er ist tatsächlich gleich "integer".
Zeile 11: Die Variable y wird mit 17 initialisiert.
Zeile 13 und 14: Der Speicher-Modus von y wird abgefragt.
Zeile 16: Mit Hilfe der Funktion as.integer() wird y in eine Variable vom Modus "integer" verwandelt; dazu muss das Ergebnis des Funktions-Aufrufs der Variable y zugewiesen werden (ohne die Zuweisung wäre y immer noch im Speicher-Modus "double").
Zeile 18 und 19: Der Speicher-Modus wird abgefragt, er ist tatsächlich gleich "integer".
Die hier vorgestellte Typumwandlung von "double" zu "integer" ist natürlich nicht die einzig mögliche und es gibt sehr viele weitere as-Funktionen, mit denen man eine Typumwandlung erzwingen kann. Man muss bei jeder Typumwandlung aber bedenken:
- Manche sind überhaupt nicht möglich (oder führen zu einem jetzt noch nicht verständlichen Verhalten): so kann etwa das Zeichen x nicht in eine Zahl verwandelt werden (umgekehrt kann aber eine Zahl 5 in ein Zeichen 5 verwandelt werden).
- Bei manchen Typumwandlungen entstehen Fehler (oder zumindest unbeabsichtigte Nebeneffekte), für die der Programmierer verantwortlich ist: Versucht man die Zahl 3.14 in eine ganze Zahl zu verwandeln, gibt es keine Warnung und das Ergebnis ist die ganze Zahl 3.
Wenn später der Reihe nach die einzelnen Datentypen besprochen werden, wird auch auf die relevanten Typumwandlungen eingegangen.
Bei einer Typumwandlung wird für ein Objekt oder für eine Variable ein neuer Speicher-Modus erzwungen; in der Dokumentation wird dieser Vorgang mit coercion bezeichnet.
Object-Checking Functions (is-dot) und Object-Coercion Functions (as-dot)
In den Beispielen oben wurden Funktionen wie is.integer(x)
beziehungsweise as.integer(x)
verwendet, deren Bedeutung sich sofort aus ihrem Namen erschließt:
is.integer(x)
prüft, ob der Eingabewert x den Speicher-Modus integer besitzt und gibt den logischen Wert TRUE oder FALSE zurück.as.integer(x)
verwandelt den Eingabewert x in eine Zahl mit Speicher-Modus integer; das Ergebnis kann dann in einer Variable abgespeichert werden.
Diese beiden Funktionen sind Vertreter einer jeweils größeren Klasse von Funktionen, den sogenannten
- object-checking functions (sie werden auch als checker-Funktionen oder is-dot-Funktionen bezeichnet) und den
- object-coercion functions (sie werden auch als cast-Funktionen oder as-dot-Funktionen bezeichnet).
So wie mit den object-checking functions (is-dot) abgefragt werden kann, ob ein bestimmter Datentyp vorliegt, werden mit den object-coercion functions (as-dot) Typumwandlungen ausgeführt.
Diese Funktionen existieren also nicht nur für integer wie im Beispiel oben, sondern
- als checker-Funktionen für alle Datentypen in R,
- als as-dot-Funktionen für alle Datentypen in R sofern die Typumwandlung sinnvoll ist.
Alle in den Basis-Paketen verfügbaren is-dot-Funktionen lassen sich durch methods(is)
anzeigen. Welche as-dot-Funktionen existieren, kann man sich mit methods(as)
anzeigen lassen; weitere Informationen in der Dokumentation findet man mit showMethods("coerce")
(zeigt, welche Typumwandlungen möglich sind).
Interpretation als fundamentale Datentypen
Lässt man nur Vektoren der Länge 1 zu, so haben die atomaren Vektoren gerade die Eigenschaften, die man (in vielen anderen Programmiersprachen) von den fundamentalen Datentypen erwartet. In den folgenden Kapiteln werden daher Zahlen (ganze Zahlen und Gleitkommazahlen), logische Werte und Zeichen genauer untersucht — also die Modi "numeric", "logical" und "character". Dabei wird noch nicht berücksichtigt, dass man Vektoren beliebiger Länge bilden kann.
Vektoren beliebiger Länge
Nach den fundamentalen Datentypen werden dann die für Vektoren (der Länge größer als 1) typischen Eigenschaften näher untersucht. Vektoren (in R als vector bezeichnet) sind die grundlegende Struktur für zusammengesetzte Objekte mit folgenden Eigenschaften:
- Alle Elemente (oder Komponenten) eines Vektors müssen identischen Modus besitzen (nur dann spricht man von atomaren Vektoren, andernfalls wären es rekursive Vektoren).
- Als Modus sind die oben besprochenen Modi erlaubt, von denen hier aber nur "numeric", "logical" und "character" behandelt werden.
- Der Modus der Elemente ist zugleich der Modus des Vektors.
- Die Anzahl der Elemente des Vektors ist seine Länge; in R ist 0 eine zulässige Länge.
- Auf die Elemente des Vektors kann über einen Index zugegriffen werden.
- In R läuft der Index von 1 bis zur Länge des Vektors (in vielen anderen Programmiersprachen von 0 bis zu "Länge - 1").
Da Vektoren die wichtigste Datenstruktur sind und zahlreiche Operationen für Vektoren bereits in den Basis-Paketen enthalten sind, ist das Kapitel, in dem Vektoren besprochen werden, sehr umfangreich. Es ist jedem Leser, der souverän mit R arbeiten möchte, empfohlen dieses Kapitel gründlich durchzuarbeiten.
Rekursive Strukturen (rekursive Vektoren)
Die grundlegende Datenstruktur, um einen rekursiven Vektor zu bilden ist die Liste (list). Eine Liste kann wie ein Vektor beliebig viele Komponenten haben, der Unterschied ist aber, dass diese Komponenten beliebigen Datentyp haben dürfen.
Man kann also etwa eine Zeichenkette, eine Zahl, einen Vektor und einen logischen Wert zu einer Liste zusammenfassen. Und wie der Name rekursiv ausdrückt, kann eine Liste wiederum eine Liste als Komponente enthalten.
Wer schon mit einer objekt-orientierten Programmiersprache gearbeitet hat, wird sofort erkennen, dass dies die Datenstruktur ist, die man benötigt, um die Datenelemente für eine beliebige Klassen zusammenzufassen; wie die Methoden einer Klasse in die Liste eingeordnet werden sollen, ist an dieser Stelle noch nicht klar. Aber so viel sei bereits verraten: Listen sind der Ausgangspunkt, um in R eigene Klassen zu definieren.
Strukturierung von Objekten mit Attributen
Einführung
Jedes Objekt kann mit weiteren Attributen versehen werden. Diese sind aber nicht dafür vorgesehen, essentielle Informationen zu speichern, sondern Zusatzinformationen.
So ist es zum Beispiel möglich, den Komponenten eines Vektors Namen zu geben, indem man das Attribut names setzt. Man kann dann entweder mit dem Index auf die Komponenten des Vektors zugreifen oder mit den Namen. Die essentielle Information — also die Werte der Komponenten — sollen nicht davon abhängig sein, ob und wie die Namen gesetzt sind.
Beispiel:
Ein Vektor soll als räumlicher Vektor im dreidimensionalen Raum interpretiert werden. Der Zugriff auf die Komponenten erfolgt über die Indizes 1, 2, 3. Vergibt man für die Komponenten die Namen, etwa "x", "y" und "z", so kann man auch über die Namen auf die Komponenten zugreifen.
Dieses Beispiel mag noch nicht sehr überzeugen: welchen Vorteil liefern die Namen gegenüber den Indizes?
Weiteres Beispiel:
In der Relativitätstheorie werden Punkte in der Raum-Zeit durch vierdimensionale Vektoren beschrieben (drei räumliche und eine zeitliche Komponente). Dabei gibt es keine einheitliche Konvention, ob die zeitliche Komponente die erste oder die vierte Komponente des Raum-Zeit-Vektors ist. Am Index kann man diese daher nicht erkennen. Vergibt man aber Namen "t", "x", "y" und "z" (oder "x", "y", "z" und "t") und schreibt die Quelltexte so, dass immer über den Namen auf die Komponenten zugegriffen wird, kann keine Verwirrung entstehen.
Die wichtigsten Datentypen aus den Basis-Paketen, die zu ihrer Strukturierung Attribute einsetzen, werden hier kurz und in späteren Kapiteln ausführlich vorgestellt. Zuvor wird noch kurz darauf eingegangen, dass es einige spezielle Attribute gibt, die nur so eingesetzt werden sollen, wie sie in der Dokumentation beschrieben sind.
Spezielle Attribute
In der Dokumentation werden einige Attribute hervorgehoben, da sie nur spezielle Werte annehmen können. Zudem werden sie intern verarbeitet, also beim Erzeugen eines Objektes automatisch gesetzt oder bei einem Funktionsaufruf verwendet beziehungsweise verändert. Diese bevorzugten Attribute sind in der folgenden Tabelle dargestellt.
class
| Definiert eine Klasse im Sinne der objekt-orientierten Programmierung |
comment
| Kommentar |
dim
| Vektor mit positiven, ganzzahligen Komponenten zur Wiedergabe der Dimensionen eines Feldes; die Länge des Vektors dim ist die Anzahl der Dimensionen, die Komponenten des dim-Vektors geben die Anzahl der Komponenten der entsprechenden Dimension an. (Beispiel: bei einer 3 × 4 Matrix ist dim ein Vektor der Länge 2 mit den Einträgen 3 und 4.) |
dimnames
| Liste der gleichen Länge wie dim: bezeichnet die Dimensionen eines Feldes. |
names
| Bezeichnung für die Komponenten eines Vektors oder einer Liste |
row.names
| Bezeichnung für die Zeilen in einer Datentabelle (data frame) |
tsp
| Attribut für Zeit-Reihen-Objekte (wird hier nicht beschrieben) |
Insbesondere die Attribute dim und class werden in den Basis-Paketen eingesetzt, um sehr hilfreiche Datentypen zu definieren. Für den Programmierer ist es ratsam, sich diese Datentypen zu kennen und einzusetzen — diese Datentypen und all die Service-Funktionen können oft angewendet werden und es wäre ein unnötiger Aufwand, dies selber zu programmieren.
Datentypen aus den Basis-Paketen
Atomare Strukturen
Die folgende Tabelle zeigt die atomaren Strukturen aus den Basis-Paketen:
Datentyp | Beschreibung |
vector
| Indizierte Komponenten von identischem Datentyp und gegebener Länge |
matrix
| Interpretation eines Vektors als zweidimensionale Matrix; die Länge des Vektors muss mit dem Produkt von Zeilen- und Spaltenanzahl übereinstimmen. Spezialfall von array. |
array
| Interpretation eines Vektors als mehrdimensionales Feld (Matrix im Fall von 2 Dimensionen) |
factor
| Einteilung von Daten (meist gegeben als Vektor) in Kategorien (levels) |
ordered
| Faktor für Kategorien, die angeordnet werden können |
table
| Zu einem oder mehreren Faktoren kann eine Kontingenztabelle erzeugt werden. Sie besteht aus einem integer-array. Damit das Feld leichter weiterverarbeitet werden kann (zum Beispiel indem es zu einem data frame verwandelt werden kann, aus dem leichter die Kategorien des Faktors abzulesen sind), gibt es den eigenen Datentyp table. |
Die folgende Tabelle zeigt, welche Attribute eingesetzt werden, um die Datenstruktur zu definieren; manche Attribute müssen gesetzt werden (was auch heißen kann, dass sie intern gesetzt werden), manche Attribute sind nur optional. Zusätzlich kann der Programmierer immer selbst weitere Attribute setzen.
Name | Attribute | optionale Attribute |
vector
| names
| |
matrix
| dim
| dimnames
|
array
| dim
| dimnames
|
factor
| levels
, class
| labels
|
ordered
| levels
, class
| labels
|
table
| class
|
Rekursive Strukturen
Die folgende Tabelle zeigt die beiden wichtigsten rekursiven Strukturen aus den Basis-Paketen:
Datentyp | Beschreibung |
list
| Wie Vektoren mit dem Unterschied, dass jede Komponenten einen beliebigen Datentyp haben kann (insbesondere ist auch eine rekursive Struktur erlaubt — also zum Beispiel Listen von Listen). |
data frame
| Vereint die Eigenschaften von Liste und Matrix: Jede Komponente eines Data Frame ist ein Vektor, der einen beliebigen Modus haben kann, aber die Längen der Vektoren müssen übereinstimmen. |
Listen sind somit das Paradebeispiel einer rekursiven Struktur; Data Frames sind die für statistische Auswertungen geeignete Datenstruktur, da sie einfachen Zugriff auf Datensätze erlauben.
Beispiele:
- Liste: Ein Buch kann durch Autoren, Titel und Erscheinungsjahr charakterisiert werden. Dabei ist der Titel eine Zeichenkette, das Erscheinungsjahr eine ganze Zahl (integer) und die Komponente Autoren besteht
- entweder aus einer Zeichenkette (falls es nur einen Autor gibt)
- oder aus einem Vektor von Zeichenketten (die Länge des Vektors ist gleich der Anzahl der Autoren).
- Data Frame: Werden 10 Personen befragt und jeweils Alter, Geschlecht und Beruf aufgenommen, erhält man ein Data Frame mit drei Komponenten. Dabei ist jede Komponente ein Vektor der Länge 10. Hier bietet sich an für das Alter den Speicher-Modus integer und für Geschlecht und Beruf character zu wählen. Die drei Komponenten können mit einem geeigneten Namen versehen werden (Attribut names).
Name | Attribute | optionale Attribute |
list
| class ="list"
| names
|
data frame
| class ="data.frame"
| names
|
Das Attribut class wird intern beim Erzeugen einer Liste beziehungsweise eines Data Frame gesetzt und besitzt den Wert "list" beziehungsweise "data.frame".
Definition von Klassen
In den Basis-Paketen sind schon mehrere Datentypen definiert, die bereits die oben beschriebene atomare oder rekursive Struktur besitzen und zusätzlich das Attribut class gesetzt haben. Dabei ist das class-Attribut eine Zeichenkette. Typische Beispiele dafür sind die Datentypen factor und ordered — sie sind sogar in einer Vererbungshierarchie angeordnet: Bei einem Faktor ist das Attribut class mit dem Wert factor gesetzt; und da ordered ein spezieller Faktor ist, sind zwei Klassen-Attribute gesetzt (ordered und factor; an der Reihenfolge kann man erkennen, dass ordered eine Unterklasse von factor ist).
Das class-Attribut lässt sich aber auch einsetzen, um selber Klassen zu definieren — dies wird aber erst im Zusammenhang mit der objekt-orientierten Programmierung besprochen.
Vorerst ist wichtig, dass jedes R-Objekt x implizit eine Klasse besitzt — auch wenn kein class-Attribut explizit gesetzt ist, es ist entweder:
- matrix oder
- array oder
- der Wert von mode(x).
In der Dokumentation wird diese implizit gesetzte Klasse als internal type bezeichnet.
Diagnosefunktionen
Die Sprache R bietet eine Reihe von Diagnose-Funktionen, die auf nahezu alle Objekte angewendet werden können und wertvolle Informationen über das Objekt liefern. Sie sind meist generisch implementiert, das heißt sie liefern eine an den Datentyp des Objektes angepasste Information. Wenn in den folgenden Kapiteln die Datentypen aus R vorgestellt werden, werden auch diese Diagnose-Funktionen vorgestellt. Für den Programmierer — vor allem bei der Fehlersuche — sind diese extrem wichtig; um zwei Anwendungen zu nennen:
- Oft liefert eine Berechnung völlig unerwartete Ergebnisse: mit der geeigneten Diagnose-Funktionen kann man sich die Werte von Variablen ausgeben lassen.
- Viele (Programmier-) Fehler beruhen darauf, dass die Objekte, mit denen man arbeitet, einen anderen Datentyp besitzen als man glaubt (dies kommt besonders häufig beim Aufruf von Funktionen vor, deren Rückgabewert man nicht kennt): wiederum kann man mit geeigneten Diagnose-Funktionen den Datentyp eines Objektes abfragen.
Wie diese Beispiele schon andeuten, kann man die Diagnose-Funktionen grob in zwei Gruppen einteilen — wobei die Trennung nicht immer eindeutig möglich ist, einige Funktionen vermischen die Eigenschaften:
- Inhalt: Spezielle Eigenschaften einer konkreten Realisierung.
- Form: Eigenschaften über die Struktur eines Objektes (wie Datentyp), die unabhängig von der konkreten Realisierung des Objektes sind.
Die wichtigsten Diagnose-Funktionen seien hier schon genannt — erklärt werden sie dann bei der Besprechung der einzelnen Datentypen:
- print()
- length()
- mode() und storage.mode()
- is.-Funktionen
- str()
- class()
- summary().
Zusammenfassung
Fundamentale Datentypen
Wie oben ausführlich beschrieben, gibt es in R eigentlich keine fundamentalen Datentypen, sondern nur Vektoren; die fundamentalen Datentypen kann man als Vektoren der Länge 1 interpretieren. Was in anderen Programmiersprachen die fundamentalen Datentypen sind, sind in R die verschiedenen Speicher-Modi von Vektoren; sie sind in der folgenden Tabelle dargestellt (allerdings nur diejenigen, die hier besprochen werden):
Speicher-Modus | Modus | Wertebereich |
"logical"
| "logical"
| Wahrheitswert (TRUE, FALSE) |
"integer"
| "numeric"
| ganze Zahlen |
"double"
| "numeric"
| Gleitkommazahlen |
"character"
| "character"
| Zeichen |
Funktionen zum Abfragen und Setzen des Modus und Speicher-Modus
mode(x)
| Bestimmt den Modus eines Objektes x. Bei den hier besprochenen Modi kann es nur bei Zahlen einen Unterschied zwischen Modus und Speicher-Modus geben: der Modus einer Zahl ist immer "numeric", der Speicher-Modus ist entweder "double" oder "integer". |
mode(x) <- value
| Setzt den Modus eines Objektes x auf den Wert value (dies muss ein erlaubter Modus sein, genauer: damit mode(x)<- "newmode"
ausgeführt werden kann, muss es die entsprechende as.newmode(x)-Funktion geben). Die Funktion mode(x) <- value
wird als replacement-Version von mode() bezeichnet. |
storage.mode(x)
| Bestimmt den Speicher-Modus eines Objektes x. |
storage.mode(x) <- value
| replacement-Version von storage.mode(): dient zum Setzen eines Speicher-Modus mit dem Wert value |
?mode
| Dokumentation: Weitere Informationen zu mode() und storage.mode() im package base unter mode |
Atomare Strukturen
Die folgende Tabelle zeigt die atomaren Strukturen aus den Basis-Paketen mit ihrem Datentyp und der in diesen Beschreibungen verwendeten Bezeichnung.
Datentyp | Bezeichnung |
vector
| Vektor |
matrix
| Matrix |
array
| Feld |
factor
| Faktor |
ordered
| geordneter Faktor |
table
| Tabelle |
Rekursive Strukturen
Die folgende Tabelle zeigt die rekursiven Strukturen aus den Basis-Paketen mit ihrem Datentyp und der in diesen Beschreibungen verwendeten Bezeichnung.
Datentyp | Bezeichnung |
list
| Liste |
data frame
| Data Frame |