14 Vektoren
In diesem Abschnitt wollen wir nun mit den R Vektoren eine erste grundlegende Datenstruktur genauer kennenlernen. Viele Prinzipien, die am Beispiel von Vektoren erläutert werden können, übertragen sich sinngemäß auf komplexere Datenstrukturen in R, sodass ein kompetenter Umgang auch mit der hier betrachteten basalen Datenstruktur im Anwendungskontext unverzichtbar ist. Es sei angemerkt, dass sich der Begriff des Vektors in diesem Abschnitt streng auf die entsprechenden R Datenstruktur, nicht aber auf das gleichnamige mathematische Objekt beziehen. Auch wenn R Vektoren in manchen Eigenschaften ihrem mathematischen Pendant ähneln und bestimmte Operationen zwischen R Vektoren Operationen zwischen den entsprechenden mathematischen Objekten abbilden können, so haben Vektoren in der imperativen Programmierung oft viel mehr und andere Eigenschaften als die entsprechenden mathematischen Objekte.
Allgemein betrachten für die Präsentation von Datenstruktur immer zunächst, wie eine bestimmte Datenstruktur mit R Code erzeugt wird, durch welche Eigenschaften die Datenstruktur charakterisiert ist, wie einzelne Elemente der Struktur indiziert und bearbeitet werden können und welche arithmetischen Operationen für die Datenstruktur zulässig sind. Schließlich betrachten wir auch immer eventuelle weitere Besonderheiten der Datenstruktur. Im Kontext von R Vektoren sind dies beispielsweise die Begriffe der Datentypangleichung (Coercion), dem Recyling oder die Repräsentation fehlender Werte in Datensätzen.
R Vektoren sind geordnete Folgen von Datenwerten. Die einzelnen Datenwerte eines Vektors heißen Elemente des Vektors. Vektoren, deren Elemente alle vom gleichen Datentyp sind, heißen atomar. Mit dem Begriff Vektor soll hier immer ein atomarer Vektor gemeint sein. Die zentralen Datentypen für Vektoren sind die oben eingeführten numeric, (double, integer), logical und character. Abbildung 14.1 zeigt graphische Darstellungen enstprechender Vektoren.

14.1 Erzeugung
Wir betrachten zunächst die Erzeugung einelementiger Vektoren, die auch als Elementarwerte bezeichnet werden. Wie alle Datenstrukturen werden auch Elementarwerte durch Zuweisungen der Form
oder
Dabei bestimmt die Spezifikation von Wert
, welcher Art der erzeugte Elementarwert ist.
Elementarwerte vom Typ numeric
können durch Zuweisung einer Zahl erzeugt werden. Per Default wird dabei ein Elementarwert vom Typ double
erzeugt. Die Zahlen können dabei ohne Dezimalstellen, in Dezimalnotation oder wissenschaftlicher Notation spezifiziert werden. Weitere mögliche Werte zur Erzeugung eines Elementarwerts vom Typ double
sind Inf
und -Inf
für positiv und negativ Unendlich und NaN
(Not-a-Number) als Platzhalter für numerische Werte.
[1] "double"
[1] "double"
[1] "double"
Numerische Elementarwerte vom Typ integer
wird wie double ohne Dezimalstellen erzeugt, gefolgt von einem L
für long integer
.
[1] "integer"
[1] "integer"
Elementarwerte vom Typ logical
können durch Zuweisung der logischen Werte TRUE
oder FALSE
bzw. abgekürzt T
oder F
erzeugt werden.
[1] "logical"
[1] "logical"
Elementarwerte vom Typ character
schließlich werden durch die Zuweisung von Werten in Hochkommata ('a'
) oder Anführungszeichen ("a"
) erzeugt
[1] "character"
[1] "character"
Um nun mehrere Elementarwerte in einem Vektor zusammenzufügen, konkateniert (von lat. catena (Kette), engl. concatenate (verketten)) man sie mit der Funktion c()
.
Es können nicht nur Elementarwerte, sondern auch Vektoren mit Elementarwerten oder anderen Vektoren konkateniert werden. Mit obigem Vektor x
erzeugt man beispielsweise folgende Vektoren y
und z
:
Vektoren müssen nicht mit bereits bestehenden Werten erzeugt werden, sondern es können auch “leere” Vektoren von einem gewünschten Datentyp erzeugt werden. R stellt dafür die Funktionen vector()
sowie double()
, integer()
, logical()
und character()
bereit. Dies ist potenziell hilfreich, um zu Beginn einer längeren Berechnung bereits Arbeitsspeicher des Computers belegt zu haben, sodass während der Programmausführung nicht nach Arbeitsspeicher gesucht werden muss, was unter Umständen die Programmlaufzeit erhöht.
# Erzeugen "leerer" Vektoren mit vector()
v = vector("double",3) # double vector [0,0,0]
w = vector("integer",3) # integer vector [0,0,0]
l = vector("logical",2) # logical vector [FALSE, FALSE]
s = vector("character",4) # character vector ["", "", "", ""]
# Erzeugen leere Vektoren mit vector()
v = double(3) # double vector [0,0,0]
w = integer(3) # integer vector [0,0,0]
l = logical(2) # logical vector [FALSE, FALSE]
s = character(4) # character vector ["", "", "", ""]
Allerdings sind die so erzeugten “leeren” Vektoren nicht wirklich leer: v
und w
bestehen aus Nullen, l
aus den logischen Werten FALSE
und s
aus den Zeichen ""
. Zur Initialisierung von Vektoren als Container und anschließender Befüllung im Rahmen eines Programms sind vector()
, double()
, integer()
, logical()
und character()
deshalb nur bedingt geeignet: Übersieht der Code die Befüllung einzelner Einträge, so wird mit den von diesen Funktionen erzeugten Werten gerechnet, was nicht unbebedingt beabsichtig ist. In Kapitel 18 lernen wir eine Methode kennen, um Datenstrukturen zu initialisieren und dabei einigermaßen sicheren Code zu generieren.
Sequenzen
Ein häufiges Hilfsmittel in der Programmierung sind Vektoren, die Folgen von numerischen Werten, z.B. als Repräsentation der Definitionsmengen einer univariaten Funktion, darstellen. R stellt zu ihrer Erzeugung den sogenannten Colonoperator :
und die Funktion seq()
sowie ihre Varianten zur Verfügung.
Der Colonoperators in der Form a:b
erzeugt Sequenzen ganzzahliger Schritte zwischen \(a\) und \(b\) anhand von
\[\begin{equation}
(s_i)_{i = 0,1,2...} \mbox{ mit } s_i = a + i \mbox{ für } i = 0,1,2,... \mbox{ und } s_i \le b
\end{equation}\] Dabei meint “zwischen a
und b
” hier also speziell, dass a
die erste Zahl der generierten Sequenz ist und b
die maximal mögliche. Folgende Beispiele verdeutlichen dies
Die Funktion seq()
mit der Syntax seq(from, to, by)
, wobei from
den Startwert,
to
den maximalen Endwert und by
die Schrittweite bezeichnen bietet weitere Möglichkeiten Sequenzen nicht zur ganzzahliger Schrittweiten zu erzeugen. Die folgenden Beispiele sollen dies verdeutlichen:
[1] 0 1 2 3 4 5
[1] 0.00 0.25 0.50 0.75 1.00
[1] 0.00 0.15 0.30 0.45 0.60 0.75 0.90 1.05 1.20 1.35 1.50 1.65 1.80 1.95
[1] 1.0 0.9 0.8 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0.0
Die seq()
Variante seq.int()
kann zur Erzeugung von ganzzahligen Folgen genutzt werden und ist unter Umständen schneller als seq()
. seq_len()
erzeugt ganzzahlige Folgen einer vorgegebenen Länge, seq_along()
generiert ganzzahlige Folgen anhand der Einträge eines Vektors als weitere Varianten
14.2 Charakterisierung
Zur Charakterisierung von Vektoren stehen mit length()
und typeof()
Funktionen bereit, die die Anzahl der Einträge eines Vektors und den Datentyp der Einträge eines Vektors ausgeben, wie folgende Beispiele verdeutlichen.
is.logical()
, is.double()
, is.integer()
, is.character()
testen den Datentyp eines Vektors und geben einen logischen Binärwert aus.
Datentypangleichung (Coercion)
Versucht man in R verschiedene Datentypen in einem Vektor zu kontaktieren, zum Beispiel Elementarwerte vom Typ numeric
, character
und logical
, so erzwingt R für den resultierenden Vektor einen einheitlichen Datentyp. Es gilt dabei folgende Rangfolge
Ist also beispielsweise einer der Elementarwerte vom Typ character
, so ist der resultierende Vektor vom Typ character
und numerische oder logische Werte werden in diesen umgewandelt.
[1] "character"
[1] "1" "a" "TRUE"
Ist dagegen beispielsweise einer der Elementarwerte vom Typ integer
und ein anderer vom Typ logical
, so ist der resultierende Vektor vom Typ integer
und und die logischen Werte werden in ganze Zahlen umgewandelt
x = c(1L, TRUE, FALSE) # Kombination gemischter Datentypen (integer schlaegt logical)
typeof(x) # Erzeugter Vektor ist vom Typ integer
[1] "integer"
[1] 1 1 0
Da R diese Datentypangleichung implizit vornimmt, sollte man darauf achten nur Werte gleichen Datentyps mit der Funktion c()
zu konkatenieren. Um möglichst gewandt zu sein, nimmt R auch noch subtilere Datenangleichungen vor. So ist es zum Beispiel möglich, die Summe logischer Werte zu berechnen, wobei die logischen Werten TRUE
und FALSE
implizit als die numerischen Werte 0
und 1
behandelt werden.
x = c(TRUE, FALSE, TRUE, TRUE) # logical Vektor
s = sum(x) # Summation in integer gewandelter logical Elemente
s
[1] 3
Explizite Datentypangleichung erlauben die Funktionen as.logical()
, as.integer()
, as.double()
, und as.character()
. Wendet man diese auf einen Vektor an, so ist der resultierende Vektor vom gewünschten Typ.
14.3 Indizierung
In R und vielen anderen Programmiersprachen werden einzelne oder mehrere Vektoreinträge durch Indizierung adressiert. Indizierung wird dabei auch indexing, subsetting oder slicing genannt. In R werden zur Indizierung eckige Klammern der Form [\(\,\,\)] benutzt. R nutzt one-based indexing, d.h. der Index des ersten Elements ist 1
und nicht etwa 0
wie in anderen Programmiersprachen.
Die Indizierung von Vektoreinträgen kann genutzt werden, um einen bestimmten Eintrag eines Vektors in eine andere Datenstruktur zu kopieren:
[1] "b"
Ebenso kann die Indizierung von Vektoreinträgen genutzt werden, um einen bestimmten Eintrag des indizierten Vektors zu manipulieren:
x = c("a", "b", "c") # character vector ["a", "b", "c"]
x[3] = "d" # Aenderung von x zu x = ["a", "b", "d"]
x # Ausgabe
[1] "a" "b" "d"
Prinzipiel gilt in R, dass die Indizierung
- mit einem Vektor positiver Zahlen die entsprechende Einträge,
- mit einem Vektor negativer Zahlen die entsprechenden komplementären Einträge,
- mit einem logischen Vektor die Einträge mit dem logischen Wert
TRUE
, und - mit einem
character
die entsprechend benannten Einträge addressiert.
Wir wollen diese Prinzipien anhand folgender Beispiele verdeutlichen
- Indizierung mit einem Vektor positiver Zahlen
x = c(1,4,9,16,25) # [1,4,9,16,25] = [1^2, 2^2, 3^2, 4^2, 5^2]
y = x[1:3] # 1:3 erzeugt Vektor [1,2,3], x[1:3] = [1,4,9]
z = x[c(1,3,5)] # c(1,3,5) erzeugt Vektor [1,3,5], x[c(1,3,5)] = [1,9,25]
x
[1] 1 4 9 16 25
[1] 1 4 9
[1] 1 9 25
- Indizierung mit einem Vektor negativer Zahlen
x = c(1,4,9,16,25) # [1,4,9,16,25] = [1^2, 2^2, 3^2, 4^2, 5^2]
y = x[c(-2,-4)] # Alle Komponenten ausser 2 und 4, x[c(-2,-4)] = [1,9,25]
x
[1] 1 4 9 16 25
[1] 1 9 25
Error in x[c(-1, 2)]: only 0's may be mixed with negative subscripts
- Indizierung mit einem logischen Vektor
x = c(1,4,9,16,25) # [1,4,9,16,25] = [1^2, 2^2, 3^2, 4^2, 5^2]
y = x[c(T,T,F,F,T)] # TRUE Komponenten, x[c(T,T,F,F,T)] = [1,4,25]
z = x[x > 5] # x > 5 = [F,F,T,T,T], x[x > 5] = [9,16,25]
x
[1] 1 4 9 16 25
[1] 1 4 25
[1] 9 16 25
- Indizierung mit einem character Vektor
Es bleibt anzumerken, dass R hat eine hohe Flexibilität bei Indizierung aufweist, d.h. oft keine Fehler ausgibt, obwohl eine Indizierung perse keinen Sinn ergibt. So verursachen z.B. out-of-range Indizes, also Indizes die die Anzahl der Einträge eines Vektors überschreiten keine Fehler, sondern geben NA
(not applicable) aus
x = c(1,4,9,16,25) # [1,4,9,16,25] = [1^2, 2^2, 3^2, 4^2, 5^2]
y = x[10] # x[10] = NA (Not Applicable)
y
[1] NA
Weiterhin verursachen nichtganzzahlige Indizes keine Fehler, sondern werden auf die nächste ganze Zahl abgerundet.
Leere Indizes schließlich indizieren den gesamten Vektor
Die Flexibilität von R bei nicht sinnhafter Indizierung hat einerseits den Vorteil, dass Programme seltener mit einem Fehler unterbrochen werden. Auf der anderen Seite hat die Indizierungsflexibilität den Nachteil, dass unbeabsichtigte Fehlindizierungen unbemerkt bleiben und Datenanalyseprogramme so zu nicht intendierten Ergebnissen kommen können. Im Umgang mit der Indizierung in R ist also ein hohes Maß an Aufmerksamkeit und im Idealfall die Validierung von erstellten Datenanalyseprogramme anhand simulierter Daten mit bekannten Ergebnissen geboten. Ein ähnliches Maß an Vorsicht ist aufgrund von R’s Recycling Funktionsweise geboten, die wir im nächsten Abschnitt kennenlernen werden.
14.4 Arithmetik numerischer Vektoren
Mit R Vektoren kann man arithmetische Operationen ausführen, d.h. mit ihnen rechnen. Wir unterscheiden dabei unitäre arithmetische Operationen, d.h. arithmetische Operationen, die auf einen Vektor angewendet werden und binäre arithmetische Operationen, bei denen zwei Vektoren miteinander verknüpft werden. Allgemein gilt, dass arithmetische Vektoroperatoren in R elementweise ausgewertet werden, allerdings sind bei binären Operatoren einige Besonderheiten zu beachten.
Betrachten wir zunächst einige unitäre arithmetische Operatoren und die Anwendung von Funktionen auf einen Vektor. In folgendem Beispiel werden die unitären Operatoren -
und ^2
, sowie die Funktion log()
auf einen Vektor a
angewendet. Die Auswertung der Operatoren bzw. der Funktion geschieht dabei elementweise, d.h. der Operator oder die Funktion werden auf jedes Element des Vektors unabhängig von seinen anderen Elementen angewendet.
[1] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
[1] 0.0 -0.1 -0.2 -0.3 -0.4 -0.5 -0.6 -0.7 -0.8 -0.9 -1.0
[1] 0.00 0.01 0.04 0.09 0.16 0.25 0.36 0.49 0.64 0.81 1.00
[1] -Inf -2.3025851 -1.6094379 -1.2039728 -0.9162907 -0.6931472
[7] -0.5108256 -0.3566749 -0.2231436 -0.1053605 0.0000000
Binäre arithmetische Operatoren, also die Verknüpfung von zwei Vektoren, geschieht ebenfalls elementweise. Bei der Verknüpfung zweier Vektoren gleicher Länge werden dabei die Vektoreinträge mit den gleichen Indizes anhand des binären arithmetischen Operators verknüpft, als Resultat ergibt sich ein Vektor mit der gleichen Länge wie die beiden Ausgangsvektoren
a = c(1,2,3) # a = [1,2,3]
b = c(2,1,4) # b = [2,1,4]
v = a + b # v = [1,2,3] + [2,1,4] = [1+2,2+1,3+4] = [ 3, 3, 7]
v
[1] 3 3 7
[1] -1 1 -1
[1] 2 2 12
[1] 0.50 2.00 0.75
Verküpft man in R einen Vektor und einen Skalar, also einen Vektor der Länge 1, so erweitert R den Skalar auf einen Vektor der gleichen Länge und führt die elementweise Verknüpfung dieser Vektoren aus, wie folgende Beispiele verdeutlichen.
a = c(1,2,3) # a = [1,2,3]
b = 2 # b = [2]
v = a + b # v = [1,2,3] + [2,2,2] = [1+2,2+2,3+2] = [ 3, 4, 5]
v
[1] 3 4 5
[1] -1 0 1
[1] 2 4 6
[1] 0.5 1.0 1.5
Unter dem Begriff des Recycling erlaubt R neben der elementweisen Arithmetik für Vektoren gleicher Länge bzw. für Skalare und Vektoren außerdem auch eine Arithmetik mit Vektoren unterschiedlicher Länge. Dazu werden Elemente des kürzeren von zwei Vektoren zum Aufstocken auf die Länge des längeren Vektors genutzt, also in diesem Sinn recycled. Wiederum führt diese Eigenschaft der Vektorarithmetik in R dazu, dass Programme seltener mit einem Fehler abbrechen und dass das Recyling unter Umständen in informiert geschriebenem Code elegante Lösungen für Berechnungen anbieten kann. Allerdings hat es den klaren Nachteil, dass auch unbeabsichtigte Fehlallokationen von Vektoren, z.B. durch das Nichtvorhandensein von Datenpunkten unentdeckt bleiben können. Auch hier ist also bei der Erstellung von Programmen hohe Aufmerksamkeit und insbesondere ausführlichen Testen angezeit. Speziell kann man bei Recycling der Vektorarithmetik in R zwei Fälle unterscheiden: Ist die Länge des längeren der beiden Vektoren ein ganzzahliges Vielfaches der Länge des kürzeren Vektors, so wird der kürzere Vektor durch die entsprechend häufige Wiederholung seiner Elemente in ihrer originären Reihenfolge expandiert. Folgendes Beispiel soll dies verdeutlichen:
[1] 1 2
[1] 3 4 5 6
[1] 4 6 6 8
In diesem Sinn ist dann die Arithmetik von Vektoren und Skalaren, also Vektoren der Länge 1, in Spezialfall dieses Prinzips. Ist die Länge des längeren der beiden Vektoren kein ganzzahliges Vielfaches der Länge des kürzeren Vektors, so werden Elemente des kürzeren Vektor in ihrer originären Reihenfolge so lange zum Auffüllen des kürzeren Vektors genutzt, bis beide Vektoren die gleiche Länge haben. Folgendes Beispiel soll dies verdeutlichen:
14.5 Attribute
Attribute in R sind Metadaten von Objekten in Form von Schlüssel-Wert-Paaren. Generell ruft die Funktion attributes()
alle Attribute eines Objektes auf. Vektoren haben perse keine keine Attribute
Die Funktion attr()
kann zum Aufrufen und Definieren von Attributen genutzt werden. Folgender Code definiert für den Vektor a
as Attribut mit dem Schlüssel (Namen) "S"
und dem Werte "W"
.
attr(a, "S") = "W" # a bekommt Attribut mit Schluessel S und Wert W
attr(a, "S") # Das Attribut mit Schluessel S hat den Wert W
[1] "W"
Allerdings werden Attribute werden bei Operationen oft entfernt.
b = a[1] # Kopie des ersten Elements von a in Vektor b
attributes(b) # Aufrufen aller Attribute von b
NULL
Die Spezifikation des Attributs names
gibt den Elementen eines Vektors Namen (Bezeichnungen), die zur Indizierung genutzt werden können und die bei Operationen nicht entfernt werden.
Die Indizierung mithilfe von Namen hat folgende Form
Im Nachgang der Definition eines Vektors kann die Funktion names()
genutzt werden, um für die Vektoreinträge Namen zu Definiere und diese Nachzuschlagen.
y = 4:6 # Erzeugung eines Vektors
names(y) = c("a","b","c") # Definition von Namen
names(y) # Elementnamenaufruf
[1] "a" "b" "c"
Benannte Vektoreinträge können hilfreich sein, wenn der Vektor eine Sinneinheit bildet, z.B. die Eigenschaften einer Versuchsperson wie Alter, Größe und Gewicht repräsentiert
Allerdings ist die Arbeit mit benannten Vektoreinträgen eher selten. Das Prinzip, Einträge einer Datenstruktur anstelle eines numerischen Indizes mit einer sinntragenden Bezeichnung zu versehen ist in R allerdings eines der Leitmotive, wie wir im Umgang mit der zentralen Datenstruktur der Dataframes in Kapitel 16 sehen werden.