Dataframes in R: der Datentyp data frame

Der Datentyp Dataframe vereinigt viele Eigenschaften der Datentypen Matrix und Liste und ist in zahlreichen Anwendungen der geeignete Rahmen, um statistische Daten zu speichern und ihre Auswertung vorzubereiten. Der erste Teil über Dataframes zeigt, wie man sie erzeugen und ihre Eigenschaften abfragen kann (Diagnose-Funktionen). Im nächsten Kapitel werden Anwendungen von Dataframes gezeigt.

Einordnung des Artikels

Einführung

Überblick über die Eigenschaften eines Dataframes

Zusammenfassend kann man ein Dataframe etwa so charakterisieren:

Diese Punkte werden jetzt kurz und im Verlauf des Kapitels ausführlich dargestellt.

Bisher wurden mehrere zusammengesetzte Datentypen vorgestellt:

Mit Dataframe ist ein weiterer zusammengesetzter Datentyp in R definiert, der

An einem Beispiel soll dieser Rahmen demonstriert werden.

Bei einer "Sonntagsfrage" werden jeder befragten Person drei Fragen gestellt:

  1. Welche Partei werden Sie wählen?
  2. Wie alt sind Sie?
  3. Haben Sie an der letzten Wahl teilgenommen?

Es entsteht eine Tabelle mit 3 Spalten und einer Zeilenanzahl, die sich danach richtet, wie viele Personen befragt wurden. Diese Tabelle könnte wie folgt aussehen

Partei Alter Teilnahme letzte Wahl
D 52 TRUE
C 38 FALSE
D 24 TRUE
C 53 TRUE
A 49 FALSE
D 50 TRUE
E 86 FALSE
B 76 TRUE
A 53 TRUE
C 50 FALSE

Tabelle 1: Ergebnis einer Befragung von 10 Personen.

Aus der Sicht der Datenmodellierung lauten die Eigenschaften dieser Tabelle:

Mit Dataframe bietet R einen zusammengesetzten Datentyp, der den Nutzer (mehr oder weniger) zwingt diese Eigenschaften umzusetzen. Wie dies im Einzelnen geschieht, wird in den folgenden Abschnitten gezeigt.

Ebenso wird gezeigt, wie man aus einem Dataframe Teilmengen extrahieren kann; mit Teilmengen sind hier einzelne Spalten oder Zeilen gemeint oder Teile davon, die gewisse Bedingungen erfüllen. Obige Tabelle erlaubt ja viele Fragen, die eine Teilmengenbildung voraussetzen:

Es wird sich zeigen, dass der Zugriff auf Teile eines Dataframe ähnlich erfolgt wie bei einer Matrix oder bei einer Liste.

Dennoch soll noch einmal betont werden, dass eine Dataframe keine Matrix ist, denn:

Dagegen gilt für ein Dataframe:

Diese Erklärungen zeigen, dass ein Dataframe ein rekursiver Datentyp ist, daher könnte man ihn als Spezialfall einer Liste verstehen; dazu würde man die Spalten als die Komponenten der Liste auffassen. Auch diese Interpretation ist falsch, denn: Die Komponenten einer Liste können unterschiedliche Längen besitzen, im Dataframe müssen die Spalten identische Länge haben.

Diese Überlegungen zeigen schon, dass die Wahl des Datentyps (Dataframe, Matrix oder Liste) in vielen Anwendungen nicht ganz einfach ist und dass man die Unterschiede dieser drei Datentypen kennen sollte um den jeweils geeigneten Typ auszuwählen – andernfalls wird man umständliche und den Speicher belastende Typ-Umwandlungen vornehmen müssen.

Zuletzt soll noch darauf hingewiesen werden, dass man ein Dataframe leicht in eine Datei schreiben kann. Dazu gibt es die Funktion write.table(), für die man (im einfachsten Anwendungsfall) nur das Dataframe-Objekt und den Pfad der Datei angeben muss.

Hat man umgekehrt mit write.table() eine Datei erzeugt, kann man sie mit read.table() lesen und erhält das Dataframe-Objekt zurück.

Die Bezeichnung Dataframe

In der R-Dokumentation lautet die Bezeichnung data frame; und wenn man darüber auf Deutsch schreiben möchte, stellt sich die Frage, wie man data frame übersetzen soll. Hier einige Möglichkeiten:

Keiner dieser Ausdrücke trifft die Bedeutung von data frame exakt – am Besten vermutlich Daten-Tabelle. Die Bezeichnung Tabelle oder Matrix hat den Nachteil, dass sie eine Verwandtschaft mit dem Datentyp Tabelle (table) oder Matrix (matrix) suggerieren. Diese Verwandtschaft ist zwar gegeben, aber der Datentyp data frame ist eben zu unterscheiden von den Datentypen table oder matrix.

Um diese Suggestion zu vermeiden und zu betonen, dass es sich um einen eigenen Datentyp handelt, wird hier als Übersetzung für data frame die Bezeichnung Dataframe verwendet – auch wenn es viele Sätze merkwürdig klingen lässt.

Erzeugen eines Dataframe

Die Funktion data.frame()

Grob gesagt werden beim Erzeugen eines Dataframe meist Vektoren zu einer Matrix zusammengesetzt. Im einfachsten Fall wendet man dann die Funktion data.frame() an, wobei in den Argumenten nur die Vektoren vorkommen, aber keine weiteren Zusatzbedingungen. "Ohne Zusatzbedingungen" soll ausdrücken, dass keine weiteren Argumente in data.frame() gesetzt werden. Es werden dann die default-Werte für die weiteren Argumente verwendet.

Anschließend wird gezeigt, welche Wirkung die weiteren Argumente haben können, wenn sie nicht mit ihren default-Werten eingesetzt werden.

In diesem Abschnitt wird immer ein Dataframe aus mehreren Vektoren erzeugt. Dies soll den Vorgang simulieren, wie ein Dataframe in einer realen Anwendung entsteht: deren Werte werden aus einer Datei gelesen oder von einer anderen Anwendung erzeugt. Wie dies geschehen kann, soll hier nicht diskutiert werden und daher werden die Vektoren zum Erzeugen des Dataframe nicht weiterverwendet, sondern es wird versucht allein mit dem Dataframe zu arbeiten.

Die Funktion dataframe() lautet allgemein:

data.frame(..., row.names = NULL, check.rows = FALSE,
            check.names = TRUE, fix.empty.names = TRUE,
            stringsAsFactors = default.stringsAsFactors())

Hinter ... verbergen sich meist die Vektoren (oder andere geeignete Objekte), die als Spalten des Dataframe verwendet werden.

Vorbereitung der Vektoren für ein Dataframe

Für die ersten Beispiele zum Erzeugen eines Dataframe soll die Tabelle 1 erzeugt werden; dazu werden drei Vektoren für die drei gestellten Fragen erzeugt. Die Vektoren haben jeweils die Länge 10 und werden als Zufallsfolge mit Hilfe der Funktion sample() erzeugt:

P <- LETTERS[1:5]
P
# "A" "B" "C" "D" "E"

partei <- sample(x = P, size = 10, replace = TRUE)
partei
# [1] "D" "C" "D" "C" "A" "D" "E" "B" "A" "C"

age <- sample(x = (18:99), size = 10, replace = TRUE)
age
# [1] 52 38 24 53 49 50 86 76 53 50

letzteWahl <- sample(x = c(TRUE, FALSE), size = 10, replace = TRUE, prob = c(0.7, 0.3))
letzteWahl
# [1]  TRUE FALSE  TRUE  TRUE FALSE  TRUE FALSE  TRUE  TRUE FALSE

Achtung: Der Einsatz von sample() hat natürlich den Effekt, dass die Ergebnisse nicht reproduzierbar sind.

Die Funktion data.frame() ohne Zusatzbedingungen

Die drei Vektoren aus dem letzten Skript werden zu einem Dataframe zusammengefasst:

sf <- data.frame(Partei = partei, Alter = age, letzteWahl = letzteWahl)
sf
#     Partei Alter letzteWahl
# 1       D    52       TRUE
# 2       C    38      FALSE
# 3       D    24       TRUE
# 4       C    53       TRUE
# 5       A    49      FALSE
# 6       D    50       TRUE
# 7       E    86      FALSE
# 8       B    76       TRUE
# 9       A    53       TRUE
# 10      C    50      FALSE

Man beachte, dass jetzt Namen sowohl für die Spalten als auch für die Zeilen vergeben sind:

  1. Die Spalten-Namen entstehen dadurch, dass die Vektoren in der Form tag = value übergeben wurden; dabei wird jeweils tag als Name der Spalte verwendet. Die Spalten-Namen sind das Attribut names. Die Funktion data.frame() wird hier eingesetzt wie die Funktion list() zum Erzeugen einer Liste.
  2. Die Zeilennamen werden automatisch gesetzt und einfach durchnumeriert. Sie sind das Attribut row.names eines Dataframe. Nachdem oben zu sehen ist, dass der default-Wert row.names = NULL gesetzt ist, hätte man eher erwartet, dass keine Zeilen-Namen vorhanden sind.

Das soeben erzeugte Objekt sf wird in den folgenden immer wieder verwendet, und kurz als Sonntagsfrage bezeichnet.

Erzeugt man ein Dataframe, indem man die Vektoren ohne Bezeichnung übergibt, fehlen die Spalten-Namen nicht, sondern werden aus den Namen der Vektoren erzeugt:

sf2 <- data.frame(partei, age, letzteWahl)
sf2
#     partei age letzteWahl
# 1       D  52       TRUE
# 2       C  38      FALSE
# 3       D  24       TRUE
# 4       C  53       TRUE
# 5       A  49      FALSE
# 6       D  50       TRUE
# 7       E  86      FALSE
# 8       B  76       TRUE
# 9       A  53       TRUE
# 10      C  50      FALSE

Im Beispiel wurden Vektoren gleicher Länge für die Spalten eingesetzt. Was passiert, wenn man dagegen verstößt? Es gibt zwei Möglichkeiten:

  1. Durch den recycling-Mechanismus können alle Vektoren zur größten Länge ergänzt werden (ohne übrigbleibende Teile): Jetzt wird der recycling-Mechanismus angewendet und man erhält keine Warnung.
  2. Die Länge des längeren Vektors ist kein Vielfaches der Länge eines kürzeren Vektors: Es gibt eine Fehlermeldung und es wird kein Dataframe erzeugt.

Da Dataframe ein rekursiver Datentyp ist, kann man anstelle eines Vektors im Argument ... auch ein Dataframe einsetzen; so kann zum Beispiel das bestehende Dataframe leicht um eine vierte Spalte ergänzt werden:

sf4 <- data.frame(sf, Erstwähler = rep(x = "FALSE", times = 10))
sf4
#     Partei Alter letzteWahl Erstwähler
# 1       D    52       TRUE      FALSE
# 2       C    38      FALSE      FALSE
# 3       D    24       TRUE      FALSE
# 4       C    53       TRUE      FALSE
# 5       A    49      FALSE      FALSE
# 6       D    50       TRUE      FALSE
# 7       E    86      FALSE      FALSE
# 8       B    76       TRUE      FALSE
# 9       A    53       TRUE      FALSE
# 10      C    50      FALSE      FALSE

Zusatzbedingungen für ein Dataframe

Bisher wurde nur das erste Argument der Funktion data.frame() besprochen – es dient dazu ein Dataframe aus einzelnen Komponenten aufzubauen; wie oben besprochen wurde, können die Komponenten mit Namen versehen werden. Die vollständige Argument-Liste – wie sie in der Dokumentation gezeigt wird – lautet:

data.frame(..., row.names = NULL, check.rows = FALSE,
           check.names = TRUE, fix.empty.names = TRUE,
           stringsAsFactors = default.stringsAsFactors())

Die Werte (auf der jeweils rechten Seite der Argumente) geben die default-Werte an. Kurz zur Bedeutung der Argumente:

  1. ... steht für die Komponenten des Dataframe.
  2. Mit row.names können die Zeilen mit Namen versehen werden. In den Beispielen oben wurde die Namen nicht gesetzt und daher wurden die Zeilen durchnumeriert.
  3. mit check.rows wird angegeben, ob beim Anlegen des Dataframe untersucht werden soll, ob alle Spalten gleich lang sind.
  4. Das Argument check.names
  5. Das Argument fix.empty.names
  6. Mit stringsAsFactors wird festgelegt, ob character-Vektoren in Faktoren verwandelt werden.

Das Argument row.names

In den bisherigen Beispielen wurden nur die Komponenten des Dataframe gesetzt; die Zeilen wurden dann durchnumeriert. Möchte man die Namen der Zeilen selber setzen, kann man dazu einen geeigneten Vektor selbst definieren. Die beiden folgenden Beispiele zeigt dies:

L10 <- LETTERS[1:10]
names <- paste0(L10, L10)
names
# [1] "AA" "BB" "CC" "DD" "EE" "FF" "GG" "HH" "II" "JJ"

sf.rownames <- data.frame(sf, row.names = names)

sf.rownames
#     Partei Alter letzteWahl
# AA      D    52       TRUE
# BB      C    38      FALSE
# CC      D    24       TRUE
# DD      C    53       TRUE
# EE      A    49      FALSE
# FF      D    50       TRUE
# GG      E    86      FALSE
# HH      B    76       TRUE
# II      A    53       TRUE
# JJ      C    50      FALSE

In Zeile 1 bis 3 werden Namen für die befragten Personen gesetzt.

In Zeile 6 wird das bekannte Dataframe sf verwendet, um eines mit Zeilen-Namen zu erzeugen.

Das Argument check.rows

Das Argument check.rows ist per default auf FALSE gesetzt. Das Verhalten wurde schon oben beim Erzeugen eines Dataframe erklärt, als versucht wurde, unterschiedliche Vektor-Längen einzusetzen. Setzt man check.rows = TRUE wird untersucht, ob die Längen der Spalten übereinstimmen.

Das folgende Beispiel versucht an sf eine Spalte anzuhängen:

sf.4 <- data.frame(sf, Erstwähler = rep(x = FALSE, times = 4), check.rows = TRUE)
# Error in data.frame(sf, Erstwähler = rep(x = FALSE, times = 4)) : 
#   Argumente implizieren unterschiedliche Anzahl Zeilen: 10, 4

sf.4 <- data.frame(sf, Erstwähler = rep(x = FALSE, times = 5), check.rows = TRUE)
sf.4
#     Partei Alter letzteWahl Erstwähler
# 1       D    52       TRUE      FALSE
# 2       C    38      FALSE      FALSE
# 3       D    24       TRUE      FALSE
# 4       C    53       TRUE      FALSE
# 5       A    49      FALSE      FALSE
# 6       D    50       TRUE      FALSE
# 7       E    86      FALSE      FALSE
# 8       B    76       TRUE      FALSE
# 9       A    53       TRUE      FALSE
# 10      C    50      FALSE      FALSE

Man erhält:

Das Argument check.names

Das Argument check.names hat den default-Wert TRUE. Versucht man einen Spalten-Namen doppelt zu setzen, wird automatisch ein neuer Name erzeugt.

Im folgenden Skript wird sf verwendet und eine weitere Spalte mit Namen Alter hinzugefügt (Zeile 1). An der Ausgabe erkennt man, dass die neue Spalte in Alter.1 umbenannt wurde.

Setzt man dagegen check.names ausdrücklich auf FALSE (siehe Zeile 15), werden die Namen nicht überprüft und es entsteht eine weitere Spalte mit Namen Alter, was beim Zugriff auf die Elemente zu Fehlern führen kann.

sf2 <- data.frame(sf, Alter = sample(x = (18:99), size = 10, replace = TRUE))
sf2
#     Partei Alter letzteWahl Alter.1
# 1       D    52       TRUE      20
# 2       C    38      FALSE      72
# 3       D    24       TRUE      75
# 4       C    53       TRUE      89
# 5       A    49      FALSE      48
# 6       D    50       TRUE      44
# 7       E    86      FALSE      20
# 8       B    76       TRUE      35
# 9       A    53       TRUE      29
# 10      C    50      FALSE      35

sf3 <- data.frame( sf, Alter = sample(x = (18:99), size = 10, replace = TRUE), check.names = FALSE)
sf3
#     Partei Alter letzteWahl Alter
# 1       D    52       TRUE    66
# 2       C    38      FALSE    91
# 3       D    24       TRUE    95
# 4       C    53       TRUE    88
# 5       A    49      FALSE    73
# 6       D    50       TRUE    31
# 7       E    86      FALSE    93
# 8       B    76       TRUE    51
# 9       A    53       TRUE    49
# 10      C    50      FALSE    68

Das Argument fix.empty.names

Im vorhergehenden Skript wurde an das bekannte Dataframe sf eine weitere Spalte mit Alter = sample(x = (18:99) hinzugefügt; der Name Alter wird dann als Bezeichnung der Spalte eingesetzt. Was passiert, wenn die zusätzliche Spalte ohne die Bezeichnung Alter als Argument an data.frame() übergeben wird?

Es hängt vom weiteren Argument fix.empty.names ab:

sf4 <- data.frame( sf, sample(x = (18:99), size = 10, replace = TRUE), fix.empty.names = FALSE)
sf4
#     Partei Alter letzteWahl   
# 1       D    52       TRUE 26
# 2       C    38      FALSE 88
# 3       D    24       TRUE 55
# 4       C    53       TRUE 52
# 5       A    49      FALSE 72
# 6       D    50       TRUE 22
# 7       E    86      FALSE 87
# 8       B    76       TRUE 51
# 9       A    53       TRUE 73
# 10      C    50      FALSE 63

sf5 <- data.frame( sf, sample(x = (18:99), size = 10, replace = TRUE))
sf5
#     Partei Alter letzteWahl sample.x....18.99...size...10..replace...TRUE.
# 1       D    52       TRUE                                             22
# 2       C    38      FALSE                                             80
# 3       D    24       TRUE                                             87
# 4       C    53       TRUE                                             80
# 5       A    49      FALSE                                             42
# 6       D    50       TRUE                                             19
# 7       E    86      FALSE                                             46
# 8       B    76       TRUE                                             54
# 9       A    53       TRUE                                             90
# 10      C    50      FALSE                                             73

Das Argument stringsAsFactors

Das Argument stringsAsFactors ist erst verständlich, wenn man Faktoren kennt (siehe Faktoren in R: der Datentyp factor und Faktoren in R: Anwendungen). Kurz: character-Vektoren benötigen sehr viel Speicherplatz und wenn sie für statistische Auswertungen eingesetzt werden, kommen oft identische Komponenten vor. Es ist dann einfacher sie mit ganzen Zahlen zu kodieren und den character-Vektor durch seine Kodierung zu ersetzen.

Daher ist der default-Wert für stringsAsFactors gleich TRUE. Man sollte sich aber vor dem Erzeugen eines Dataframe gut überlegen, ob man mit Faktoren weiterarbeiten möchte. Ist es etwa nötig, mit den character-Vektoren weitere String-Operationen durchzuführen, kann es mit den Faktoren zu Komplikationen kommen.

Das folgende Skript zeigt die Struktur str() der Sonntagsfrage sf und zum Vergleich das Dataframe, das mit identischen Vektoren aber mit stringsAsFactors = FALSE erzeugt wurde (Zeile 11). An der Ausgabe erkennt man keinen Unterschied, nur an der Struktur (die Funktion str() wird unten bei den Diagnose-Funktionen näher erklärt):

# sf wie oben (mit Faktor)

str(sf)
# 'data.frame': 10 obs. of  3 variables:
# $ Partei    : Factor w/ 5 levels "A","B","C","D",..: 4 3 4 3 1 4 5 2 1 3
# $ Alter     : int  52 38 24 53 49 50 86 76 53 50
# $ letzteWahl: logi  TRUE FALSE TRUE TRUE FALSE TRUE ...

# nochmals ohne Faktor:

sf.char <- data.frame(Partei = partei, Alter = age, letzteWahl = letzteWahl, stringsAsFactors = FALSE)
sf.char
#      Partei Alter letzteWahl
# 1       D    52       TRUE
# 2       C    38      FALSE
# 3       D    24       TRUE
# 4       C    53       TRUE
# 5       A    49      FALSE
# 6       D    50       TRUE
# 7       E    86      FALSE
# 8       B    76       TRUE
# 9       A    53       TRUE
# 10      C    50      FALSE

str(sf.char)
# 'data.frame': 10 obs. of  3 variables:
#   $ Partei    : chr  "D" "C" "D" "C" ...
# $ Alter     : num  52 38 24 53 49 50 86 76 53 50
# $ letzteWahl: logi  TRUE FALSE TRUE TRUE FALSE TRUE ...

Man beachte den Unterschied in der Spalte Partei (Zeile 5 und 27).

Dataframe als echt rekursive Struktur

Fragt man sich, welche Objekte zu einem Dataframe zusammengestellt werden können, so wurden in den bisherigen Beispielen zwei Möglichkeiten gezeigt, wie die Funktion data.frame() aufgerufen wird:

  1. Mehrere Vektoren gleicher Länge werden verknüpft (so wurde das Paradebeispiel der Sonntagsfrage sf erzeugt).
  2. An ein bestehendes Dataframe Objekt wurde ein Vektor als weitere Spalte angehängt.

Dies erschöpft natürlich noch nicht die Möglichkeiten von data.frame(). Als rekursive Struktur kann ein Dataframe aus beliebigen Objekten zusammengesetzt werden, was durch ... ausgedrückt wird. Man kann also beliebig Vektoren, Matrizen, Listen und Dataframes an die Funktion data.frame() übergeben; einzige Einschränkung ist, dass die Anzahl der Zeilen übereinstimmen muss.

Aufgabe: Testen Sie den Einsatz von data.frame() mit verschiedenen Objekten als Eingabewerte für ... . Welche Fehlermeldung wird erzeugt, wenn die Anzahl der Zeilen nicht übereinstimmt?

Die Funktion as.data.frame()

Es wurde schon gesagt, dass die Datentypen Matrix und Liste eng verwandt sind mit Dataframe. Es sollte klar sein, dass man sie in ein Dataframe mit der as-dot-Funktion as.data.frame verwandeln kann. Und da man einen Vektor als Spezialfall einer Matrix auffassen kann, gilt dies auch für Vektoren. Das folgende Skript zeigt einfache Beispiele:

v <- (1:3)
df.v <- as.data.frame(v)
df.v
#   v
# 1 1
# 2 2
# 3 3

m <- matrix(data = (1:12), nrow = 3, byrow = TRUE)
df.m <- as.data.frame(m)
df.m
# V1 V2 V3 V4
# 1  1  2  3  4
# 2  5  6  7  8
# 3  9 10 11 12

l <- list(l1 = (1:3), l2 = (4:6))
df.l <- as.data.frame(l)
df.l
# l1 l2
# 1  1  4
# 2  2  5
# 3  3  6

Man erkennt, wie die Spalten-Namen erzeugt werden, obwohl sie nicht ausdrücklich vereinbart wurden und dass die Zeilen-Namen wie gewohnt erzeugt werden. Die Funktion as.data.frame() besitzt noch weitere Argumente (ganz ähnlich wie data.frame), die hier nicht besprochen werden.

Die Funktionen cbind() und rbind()

Die Funktionen cbind() und rbind() sind generisch implementiert und wurden für Matrizen ausführlich vorgestellt; dort dienen sie dazu, Vektoren oder Matrizen spalten- oder zeilenweise aneinanderzuhängen.

Ihre Implementierung für Dataframes ist analog. Allerdings ist die Funktion cbind() nur ein Wrapper für die Funktion data.frame(), das heißt die Eingabewerte werden weitergereicht.

Die Funktion rbind() kann nicht auf data.frame() zurückgeführt werden, da man ein Dataframe nicht zeilenweise in Komponenten zerlegen kann. Es sollte auch klar sein, dass in der Implementierung von rbind() zahlreiche Sonderfälle zu beachten sind und daher viele Prüfungen der Eingabewerte vorgenommen werden. Was passiert etwa:

Es ist unmöglich, hier alle Sonderfälle zu diskutieren. Vor dem Einsatz von rbind() sollte man dazu auf jeden Fall die Dokumentation lesen (im Paket base unter cbind) und in seinen Skripten dafür sorgen, dass nur "passende" Zeilen zusammengesetzt werden.

Es folgen einfache Anwendungen von cbind(); zuerst ein Beispiel, bei dem kein Dataframe erzeugt wird:

# Achtung: so wird kein Dataframe erzeugt

c.p <- c("A", "B", "C")
c.a <- c(18, 50, 88)

df <- cbind(Partei = c.p, Alter = c.a)
df
#     Partei Alter
# [1,] "A"    "18" 
# [2,] "B"    "50" 
# [3,] "C"    "88" 

str(df)
# chr [1:3, 1:2] "A" "B" "C" "18" "50" "88"
# - attr(*, "dimnames")=List of 2
# ..$ : NULL
# ..$ : chr [1:2] "Partei" "Alter"

Man beachte: So wie cbind() in Zeile 6 eingesetzt wird, wird eine Matrix und kein Dataframe erzeugt.

Eines der Argumente von cbind() muss ein Dataframe sein:

df <- data.frame(Partei = c.p)

df.new <- cbind(df, Alter = c.a)
df.new
#     Partei Alter
# 1      A    18
# 2      B    50
# 3      C    88

str(df.new)
# 'data.frame': 3 obs. of  2 variables:
#   $ Partei: Factor w/ 3 levels "A","B","C": 1 2 3
# $ Alter : num  18 50 88

Und hier sind einfache Anwendungen von rbind():

# 2 Dataframes: eines ohne Spalten-Namen, eines mit Spalten-Namen

df1 <- data.frame(c("A", "B"), c(18, 80), c(FALSE, TRUE))
df2 <- data.frame(Partei = c("C", "D"), Alter = c(40, 50), letzteWahl = c(FALSE, TRUE))

df.new <- rbind(df1, df2)
df.new
#     Partei Alter letzteWahl
# 1      A    18      FALSE
# 2      B    80       TRUE
# 3      C    40      FALSE
# 4      D    50       TRUE

# Versuch, einen Vektor als Zeile anzuhängen:

r1 <- c("C", 50, TRUE)

df.new <- rbind(df.new, r1)
df.new
#     Partei Alter letzteWahl
# 1      A    18      FALSE
# 2      B    80       TRUE
# 3      C    40      FALSE
# 4      D    50       TRUE
# 5      C    50       TRUE

r2 <- c("E", 50, TRUE)

df.new <- rbind(df.new, r2)
# Warning message:
#   In `[<-.factor`(`*tmp*`, ri, value = "E") :
#   ungültiges Faktorniveau, NA erzeugt

df.new
#     Partei Alter letzteWahl
# 1      A    18      FALSE
# 2      B    80       TRUE
# 3      C    40      FALSE
# 4      D    50       TRUE
# 5      C    50       TRUE
# 6   <NA>    50       TRUE

Zeile 3 und 4: Definition zweier Dataframes. Man beachte, dass in df1 keine Spalten-Namen gesetzt sind.

Zeile 6: Die beiden Dataframes werden mit rbind() aneinandergehängt. Wie man an der Ausgabe sieht, werden die Spalten-Namen für df.new von df2 übernommen.

Zeile 16 und 18: Die Zeile r1 wird an df.new angehängt. Verblüffend ist hier: Der Vektor r1 enthält drei unterschiedliche Datentypen; man erwartet, dass er in einen character-Vektor verwandelt wird.

An der Ausgabe von df.new erkennt man aber, dass der Vektor richtig integriert wird (Zeile 25).

Zeile 27 und 29: Dagegen kann der Vektor r2 nicht wie erwartet zu df.new hinzugefügt werden, da die Partei "E" noch nicht als Bestandteil der ersten Spalte bekannt ist. An ihrer Stelle erscheint in der Ausgabe NA (Zeile 41), was auch in der Warnung angekündigt wurde (Zeile 30 bis 32).

Erzeugen aller möglichen Kombinationen mehrerer Vektoren mit expand.grid()

Oft benötigt man Testdaten, die gewisse Regelmäßigkeiten aufweisen, weil man damit Fehler in den Funktionen zur Auswertung leichter aufspüren kann. Die Funktion expand.grid() erzeugt aus mehreren Eingabe-Vektoren alle möglichen Kombinationen ihrer Komponenten. Die Anzahl der Zeilen ist somit gleich der Anzahl der Kombinationen:

v1 <- (1:3); v2 <- (1:4)

df <- expand.grid(v1, v2)
df
# Var1 Var2
# 1     1    1
# 2     2    1
# 3     3    1
# 4     1    2
# 5     2    2
# 6     3    2
# 7     1    3
# 8     2    3
# 9     3    3
# 10    1    4
# 11    2    4
# 12    3    4

v3 <- (1:5)
df <- expand.grid(v1, v2, v3)
df
# Var1 Var2 Var3
# 1     1    1    1
# 2     2    1    1
# 3     3    1    1
# 4     1    2    1
# 5     2    2    1
# 6     3    2    1
# 7     1    3    1
# 8     2    3    1
# 9     3    3    1
# 10    1    4    1
# ...
# Es gibt insgesamt 3*4*5 = 60 Kombinationen

Man kann die Funktion expand.grid() zum Beispiel einsetzen, um alle Kombinationen beim Würfeln zu erzeugen: man übergibt zweimal den Vektor (1:6) :

v <- (1:6)
dice.2 <- expand.grid(v, v)
dice.2
# Var1 Var2
# 1     1    1
# 2     2    1
# 3     3    1
# 4     4    1
# 5     5    1
# 6     6    1
# 7     1    2
# ... 36 Kombinationen

Man sollte aber bedenken, dass derartige Dataframes sehr viel Speicherplatz belegen.

Diagnose-Funktionen für ein Dataframe-Objekt

Besprochen werden folgende Diagnose-Funktionen:

Die Funktionen print() und print.data.frame()

Wie bei allen R-Objekten dient die Angabe des Namens einer Variable als Abkürzung für den Aufruf der print()-Funktion. In allen bisherigen Beispielen wurden Dataframes so ausgegeben. Diese Ausgabe in Tabellenform beinhaltet:

Die Funktion print(x) ist generisch implementiert und wird sie für ein Dataframe x aufgerufen, wird intern die Funktion print.data.frame(x) aufgerufen. Sie weitere Argumente:

print(x, ..., digits = NULL, quote = FALSE, right = TRUE, row.names = TRUE)

Sie geben an:

  1. Wie viele signifikante Stellen angegeben werden.
  2. Ob Anführungsstriche angegeben werden (bei allen Einträgen, nicht nur bei Zeichenketten).
  3. Ob die Ausgabe rechtsbündig (default-Wert) oder linksbündig erfolgt.
  4. Ob Zeilen-Namen ausgegeben werden.

Soll eines (oder mehrere) dieser Argumente gesetzt werden, ist es nicht nötig print.data.frame() aufzurufen; es reicht print() aufzurufen, da durch den Datentyp von x erkannt wird, dass der Aufruf an print.data.frame() weitergereicht werden muss.

Ein Beispiel mit dem Dataframe der Sonntagsfrage sf:

print(x = sf, right = FALSE, row.names = FALSE)
#     Partei Alter letzteWahl
# D      52     TRUE     
# C      38    FALSE     
# D      24     TRUE     
# C      53     TRUE     
# A      49    FALSE     
# D      50     TRUE     
# E      86    FALSE     
# B      76     TRUE     
# A      53     TRUE     
# C      50    FALSE

Ausgabe der "Längen" eines Dataframe: Die Funktionen length(), nrow(), ncol() und dim()

Das Beispiel der Sonntagsfrage wird verwendet, um die Dimensionen eines Dataframe auszugeben:

# sf: Sonntagsfrage

length(sf)
# [1] 3

nrow(sf)
# [1] 10

ncol(sf)
# [1] 3

dim(sf)
# [1] 10  3

Man beachte, dass die Länge mit der Anzahl der Spalten übereinstimmt; bei einer Matrix gibt length() die Anzahl der Komponenten an.

Für die Anzahl der Zeilen in nrow() und in der ersten Komponente des Dimensions-Vektors wird die Länge der row.names verwendet.

Die Struktur str()

Die Funktion str() (dabei ist str die Abkürzung für structure) wurde bereits bei mehreren Datentypen besprochen; sie bietet wie immer die prägnanteste Ausgabe. Hier wieder das Beispiel der Sonntagsfrage:

str(sf)
# 'data.frame': 10 obs. of  3 variables:
# $ Partei    : Factor w/ 5 levels "A","B","C","D",..: 4 3 4 3 1 4 5 2 1 3
# $ Alter     : int  52 38 24 53 49 50 86 76 53 50
# $ letzteWahl: logi  TRUE FALSE TRUE TRUE FALSE TRUE ...

Im Vergleich zur print()-Funktion sind jetzt die Inhalte des Dataframe weniger übersichtlich dargestellt. Dafür ist der Aufbau des Objektes übersichtlicher dargestellt: Es wird eine Zeile für das Objekt sf verwendet und je eine Zeile für die Spalten des Dataframe.

In der Zeile für das Objekt steht: Die Anzahl der Zeilen und Spalten (Zeile 2).

In den Zeilen für die Spalten des Dataframe stehen (siehe Zeile 3 bis 5):

Vergleicht man die Struktur eines Dataframe mit der einer Liste, so stellt man fest, dass sie nahezu identisch sind. Zum Vergleich werden die drei Vektoren, mit denen oben sf als Dataframe erzeugt wurde, verwendet um eine Liste zu erzeugen; anschließend wird die Struktur dieser Liste ausgegeben:

sf.list <- list(Partei = partei, Alter = age, letzteWahl = letzteWahl)
sf.list
# $Partei
# [1] "D" "C" "D" "C" "A" "D" "E" "B" "A" "C"
# 
# $Alter
# [1] 52 38 24 53 49 50 86 76 53 50
# 
# $letzteWahl
# [1]  TRUE FALSE  TRUE  TRUE FALSE  TRUE FALSE  TRUE  TRUE FALSE

str(sf.list)
# List of 3
# $ Partei    : chr [1:10] "D" "C" "D" "C" ...
# $ Alter     : num [1:10] 52 38 24 53 49 50 86 76 53 50
# $ letzteWahl: logi [1:10] TRUE FALSE TRUE TRUE FALSE TRUE ...

Man erkennt folgende Unterschiede in der Struktur:

Die Ausgabe der Liste mit print() ist natürlich völlig unterschiedlich im Vergleich zum Dataframe: Beim Dataframe werden die Inhalte tabellarisch ausgegeben, bei der Liste komponentenweise (Zeile 2 bis 10).

Die Funktionen mode() und is.data.frame() und der Zusammenhang mit Listen

Bei Vektoren gibt der Modus an, welchen Modus seine Komponenten haben (numeric, logical, character). Da Dataframe eine rekursive (und keine atomare) Struktur ist, gibt es keinen gemeinsamen Modus der Komponenten. Welchen Modus wird dann die Funktion mode() angeben?

Das Ergebnis ist vielleicht überraschend: es wird list angegeben (Zeile 4 unten) – obwohl schon mehrfach gesagt wurde, dass man ein Dataframe von einer Liste unterscheiden muss. Naheliegend ist es dann zu fragen, welches Ergebnis is.list() liefert. Das folgende Skript zeigt dies für die Sonntagsfrage:

# sf wie oben

mode(sf)
# [1] "list"

is.list(sf)
# [1] TRUE

is.data.frame(sf)
# [1] TRUE

class(sf)
# [1] "data.frame"

Zeile 9: Die Abfrage, ob sf ein Dataframe ist, liefert natürlich TRUE.

Zeile 12: Die Auflösung für die überraschenden Eigenschaften eines Dataframe liefert die Abfrage nach der Klasse von sf: Sie lautet "data.frame". Der Datentyp Dataframe ist im Sinne der objekt-orientierte Programmierung als Klasse definiert. Und in R werden Listen – als die grundlegende rekursive Struktur – verwendet um Klassen zu implementieren. In jeder Klasse wird dann das Attribut class gesetzt – im Fall des Dataframe hat das class-Attribut den Wert "data.frame". Das heißt aber, dass jede Klasse auch eine Liste ist. Aber nur in diesem Sinne ist ein Dataframe zugleich eine Liste – als Klasse muss man Dataframe und Liste dennoch unterscheiden.

Die Funktion summary()

Die Funktion summary() ist sowohl von der print()-Funktion als auch von str() zu unterscheiden. Erstere zeigt die Inhalte des Dataframe und Letztere die Datentypen der Spalten (Inhalte werden nur angedeutet). Die Funktion summary() versucht eine knappe statistische Auswertung der im Dataframe enthaltenen Daten zu geben. Hier ist wieder das bekannte Beispiel der Sonntagsfrage:

summary(sf)
# Partei     Alter       letzteWahl     
# A:2    Min.   :24.00   Mode :logical  
# B:1    1st Qu.:49.25   FALSE:4        
# C:3    Median :51.00   TRUE :6        
# D:3    Mean   :53.10                  
# E:1    3rd Qu.:53.00                  
# Max.   :86.00

Man sollte dieses Verhalten besser so verstehen: Wird die Funktion summary() auf ein Dataframe angewendet, wird summary() an die Komponenten weitergereicht. Und da sie für numeric-Vektoren anders implementiert ist als für Faktoren, erscheinen andere Ausgaben (kleine statistische Auswertung beziehungsweise Angabe der Häufigkeiten der Werte).

Einen besseren Eindruck über die kleine statistische Auswertung durch summary() vermittelt die Funktion plot(), die ebenfalls generisch implementiert ist – und hier noch nicht erläutert wird. In der folgenden Abbildung 1 wurde die Sonntagsfrage mit Zufallsfolgen wie oben erzeugt, einziger Unterschied ist, dass jetzt 50 Personen befragt wurden. Das Diagramm wurde dann erzeugt mit:

# sf neu erzeugt mit 50 Personen
plot(sf, col = "red")

Abbildung 1: Die Sonntagsfrage mit Hilfe von plot() dargestellt (hier realisiert mit 50 Personen.Abbildung 1: Die Sonntagsfrage mit Hilfe von plot() dargestellt (hier realisiert mit 50 Personen.

Wer diese Diagramme noch nie gesehen hat, ist hier zuerst verwirrt. An den Skalierungen erkennt man leicht, welche Größe auf welcher Achse aufgetragen ist. Und eigentlich sind es nur 3 Diagramme (Vertauschung der Achsen beachten!).

Die Namen und Attribute eines Dataframe: names() und attributes()

Wird ein Dataframe so erzeugt, dass die Komponenten in der Form tag = value gesetzt werden, ist damit automatisch das Attribut names gesetzt. Das folgende Skript zeigt dies und welche weiteren Attribute gesetzt sind:

# partei, age, letzteWahl wie oben

sf <- data.frame(Partei = partei, Alter = age, letzteWahl = letzteWahl)

names(sf)
# [1] "Partei"     "Alter"      "letzteWahl"

attributes(sf)
# $names
# [1] "Partei"     "Alter"      "letzteWahl"
# 
# $row.names
# [1]  1  2  3  4  5  6  7  8  9 10
# 
# $class
# [1] "data.frame"

Man erkennt, dass die Zeilen-Namen row.names und die Klasse class gesetzt sind – was nach den bisherigen Erklärungen zu erwarten war.

Zusammenfassung

Funktionen zum Erzeugen von Dataframes

Die folgende Tabelle zeigt eine Kurzbeschreibung der in diesem Kapitel vorgestellten Funktionen zum Erzeugen von Dataframes. Man beachte dabei aber, dass die gezeigten Argumente meist nicht die komplette Liste der Eingabewerte darstellt. Es werden immer nur diejenigen Eingabewerte gezeigt, die hier auch besprochen wurden. Für die allgemeine Verwendung der Funktionen ist es erforderlich die Dokumentation zu Rate zu ziehen.

Funktion Beschreibung
data.frame(..., row.names = NULL, check.rows = FALSE, check.names = TRUE, fix.empty.names = TRUE, stringsAsFactors = default.stringsAsFactors()) Erzeugen eines Dataframes meist aus mehreren Vektoren. Im Argument ... können aber auch geeignete Dataframes, Matrizen oder Listen stehen. Die weiteren Argumente beziehen sich auf die Namen der Spalten und Zeilen sowie der Umwandlung von character-Vektoren in Faktoren.
as.data.frame(x, row.names = NULL, optional = FALSE, ..., stringsAsFactors = default.stringsAsFactors()) Erzeugen eines Dataframes aus einem Objekt x (geeigneter Vektor, Matrix oder Liste) und weiteren Argumenten wie bei data.frame().
expand.grid(..., KEEP.OUT.ATTRS = TRUE, stringsAsFactors = TRUE) In ... können geeignete Vektoren, Faktoren oder Listen eingegeben werden, von denen alle möglichen Kombinationen ihrer Komponenten gebildet werden. Diese Kombinationen bilden die Zeilen eines Dataframes.
cbind(...) Wrapper-Funktion für data.frame(). Die Objekte in ... werden spaltenweise zu einem Dataframe zusammengesetzt, wenn mindestens ein Dataframe-Objekt als Eingabewert enthalten ist.
rbind(..., deparse.level = 1, make.row.names = TRUE, stringsAsFactors = default.stringsAsFactors()) Die Objekte in ... werden spaltenweise zu einem Dataframe zusammengesetzt, wenn mindestens ein Dataframe-Objekt als Eingabewert enthalten ist.

Die folgende Tabelle beschreibt die Rückgabewerte der Funktionen und deren Datentypen:

Funktion Rückgabewert Datentyp
data.frame(..., row.names = NULL, check.rows = FALSE, check.names = TRUE, fix.empty.names = TRUE, stringsAsFactors = default.stringsAsFactors()) Dataframe, das aus den Objekten im Argument ... zusammengesetzt wird. Zusätzliche Konfiguration durch die weiteren Argumente. Dataframe (class "data.frame")
as.data.frame(x, row.names = NULL, optional = FALSE, ..., stringsAsFactors = default.stringsAsFactors()) Dataframe, das aus einem Objekt x (geeigneter Vektor, Matrix oder Liste) erzeugt wird. Zusätzliche Konfiguration durch die weiteren Argumente. Dataframe (class "data.frame")
expand.grid(..., KEEP.OUT.ATTRS = TRUE, stringsAsFactors = TRUE) Dataframe, dessen Zeilen die möglichen Kombinationen der Komponenten in ... sind ( ... enthält geeignete Vektoren, Faktoren oder Listen). Dataframe (class "data.frame")
cbind(...) Dataframe, das aus den Objekten in ... entsteht, wenn sie spaltenweise aneinandergehängt werden. Damit ein Dataframe (und keine Matrix) entsteht, muss mindestens ein Dataframe enthalten sein. Dataframe (class "data.frame")
rbind(..., deparse.level = 1, make.row.names = TRUE, stringsAsFactors = default.stringsAsFactors()) Dataframe, das aus den Objekten in ... entsteht, wenn sie zeilenweise aneinandergehängt werden. Damit ein Dataframe (und keine Matrix) entsteht, muss mindestens ein Dataframe enthalten sein. Dataframe (class "data.frame")

Diagnose-Funktionen

Die folgende Tabelle zeigt eine Kurzbeschreibung der in diesem Kapitel vorgestellten Diagnose-Funktionen für Dataframe-Objekte. Man beachte dabei aber, dass die gezeigten Argumente meist nicht die komplette Liste der Eingabewerte darstellt. Es werden immer nur diejenigen Eingabewerte gezeigt, die hier auch besprochen wurden. Für die allgemeine Verwendung der Funktionen ist es erforderlich die Dokumentation zu Rate zu ziehen.

Funktion Beschreibung
print(x, ..., digits = NULL, quote = FALSE, right = TRUE, row.names = TRUE) Ausgabe eines Dataframes mit Spalten-Namen und per default: ohne Anführungsstriche, rechtsbündig und mit Zeilen-Namen.
length(x) Länge eines Dataframe-Objektes x (also Anzahl der Spalten).
nrow(x) Anzahl der Zeilen eines Dataframes x.
ncol(x) Anzahl der Spalten eines Dataframes x.
dim(x) Dimensions-Vektor ein Dataframe-Objektes x (erste Komponente ist die Anzahl der Zeilen, die zweite Komponente die Anzahl der Spalten).
str(object) Ausgabe der Struktur eines Dataframes object.
mode(x) Modus eines Objektes x; liefert "list" für ein Dataframe-Objekt.
is.data.frame(x) Gibt an, ob ein Objekt x ein Dataframe-Objekt ist.
summary(object) Gibt für alle Spalten des Dataframes object eine kleine statistische Auswertung aus.
names(x) Spalten-Namen des Dataframes x.
attributes(x) Attribute des Dataframes x.

Zugriff auf eine Auswahl von Komponenten

Die folgende Tabelle zeigt eine Kurzbeschreibung der in diesem Kapitel vorgestellten Funktionen zum Zugriff auf Teile eines Dataframe-Objektes. Man beachte dabei aber, dass die gezeigten Argumente meist nicht die komplette Liste der Eingabewerte darstellt. Es werden immer nur diejenigen Eingabewerte gezeigt, die hier auch besprochen wurden. Für die allgemeine Verwendung der Funktionen ist es erforderlich die Dokumentation zu Rate zu ziehen.

Funktion Beschreibung
head(x, n = 6L) Gibt die ersten n Zeile eines Dataframes x zurück.
tail(x, n = 6L) Gibt die letzten n Zeile eines Dataframes x zurück.
x$name Spalte mit Namen name des Dataframes x.
x[ , n] Falls n eine ganze Zahl: n-te Spalte des Dataframes x. Statt n kann auch ein Index-Vektor eingesetzt werden.
x[n, ] Falls n eine ganze Zahl: n-te Zeile des Dataframes x. Statt n kann auch ein Index-Vektor eingesetzt werden.
x[order(x$name),] Das Dataframe x wird gemäß der Spalte name aufsteigend sortiert.