Dataframes in R: Anwendungen

Nach den grundlegenden Eigenschaften im Kapitel "Dataframes in R: der Datentyp data frame" werden jetzt Anwendungen von Dataframes gezeigt: der Zugriff auf ein Dataframe (auf Spalten, Zeilen, einzelne Elemente oder Teilmengen), Sortierung eines Dataframes, Daten-Aggregation, Umwandlung in eine Matrix sowie das Schreiben eines Dataframes in eine Datei und umgekehrt das Lesen von tabellarischen Daten aus einer Datei.

Einordnung des Artikels

An einigen Stellen ist ein gewisses Vorverständnis über Faktoren hilfreich, die in den beiden nächsten Kapiteln Faktoren in R: der Datentyp factor und Faktoren in R: Anwendungen besprochen werden.

Weitere Anwendungen mit Dataframes, die allerdings Kenntnisse über Faktoren und apply-Funktionen voraussetzen, finden sich in Die Familie der apply-Funktionen in R Teil 3: Weitere mit apply() verwandte Funktionen.

Einführung

Dieses Kapitel ist die Fortsetzung von Dataframes in R: der Datentyp data frame. Dort wurde vor allem gezeigt, wie man ein Dataframe-Objekt erzeugt und seine Eigenschaften mit Diagnose-Funktionen abfragt.

Jetzt werden Anwendungen mit Dataframes gezeigt, insbesondere

Dabei wird die erste Frage sehr ausführlich diskutiert; dies ist wichtiger als man zunächst glauben möchte, denn:

Wie im Kapitel Dataframes in R: der Datentyp data frame werden fast alle Beispiele mit dem Dataframe beschrieben, das bei einer Sonntagsfrage entstehen könnte. Die folgende Tabelle zeigt nochmals die Resultate der Stichprobe der Länge 10 mit den Antworten auf die drei gestellten Fragen:

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

Erzeugt wurde das zur Tabelle gehörige Dataframe mit dem Skript:

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

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

Allerdings beruht das Skript auf Zufallswerten, so dass seine Ausführung nicht reproduzierbar ist.

Im Folgenden wird immer wieder auf dieses Beispiel Bezug genommen und das hier erzeugte Dataframe wird kurz als die Sonntagsfrage sf bezeichnet.

Dieses Dataframe ist komplex genug, um alle wichtigen Konzepte zu erklären:

Zugriff auf Teile eines Dataframes

Da Dataframes sehr eng mit Listen und Matrizen verwandt sind, erfolgen Zugriffe auf die Elemente ähnlich wie bei diesen; sie werden in den folgenden Unterabschnitten erklärt.

Die Ähnlichkeit zwischen Dataframes und Listen ist immer so zu verstehen: Die Komponenten eines Dataframes sind dessen Spalten. Und daher erfolgt der Listen-ähnliche Zugriff auf die Spalten eines Dataframes.

Andererseits sind die Elemente eines Dataframes wie in einer Matrix angeordnet: der Matrizen-ähnliche Zugriff erfolgt mit [.,.] auf Spalten, Zeilen oder einzelne Elemente eines Dataframes.

Die Funktionen head() und tail()

Mit den Funktionen head(x, n) und tail(x, n) kann man den vorderen oder hinteren Teil eines Dataframes auswählen, wobei hier immer die zeilenweise Auswahl gemeint ist. Es entsteht ein neues Dataframe Objekt mit n Zeilen; die Zeilen- und Spalten-Namen werden von x übernommen. Beim hinteren Teil beginnt also die Numerierung der Zeilen nicht bei 1.

# sf: Sonntagsfrage

sf.3 <- head(x = sf, n = 3)

sf.3
# Partei Alter letzteWahl
# 1      D    52       TRUE
# 2      C    38      FALSE
# 3      D    24       TRUE

sf.7 <- tail(x = sf, n = 7)

sf.7
# Partei Alter letzteWahl
# 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

Zugriff auf die Spalten eines Dataframes

Erinnerung an Listen

Es wurde schon mehrfach betont, dass ein Dataframe Eigenschaften einer Matrix und einer Liste besitzt. Dies gilt auch für den Zugriff auf die Spalten, Zeilen oder einzelne Elemente eines Dataframes. Speziell der Zugriff auf die Spalten ist sehr ähnlich dem Zugriff bei Listen auf ihre Komponenten.

Zur Erinnerung:

Bei Listen muss man den Zugriff auf Komponenten mittels [[.]] und [.] unterscheiden:

  1. Der Zugriff lst[[n]] auf die n-te Komponente der Liste lst erzeugt eben diese Komponente – hat also deren Datentyp. Und gleichwertig ist es mit dem Operator $ mit Hilfe des Namens auf die Komponente zuzugreifen (sofern Namen gesetzt sind).
  2. Dagegen erzeugt lst[n] eine Liste der Länge 1 und die erste (und einzige) Komponente dieser Liste ist die n-te Komponente lst[[n]] der Liste lst.

Man kann dies leicht damit begründen, dass die Komponenten einer Liste üblicherweise als Paare der Form name = value definiert werden:

  1. Mit [[.]] wird auf den Wert (value) der Komponente zugegriffen und ein Objekt mit dessen Datentyp erzeugt.
  2. Mit [.] wird auf ein Paar name = value zugegriffen und diejenige Teil-Liste erzeugt, die genau dieses Paar enthält.

Für die Zugriffe mittels [[.]] und [.] gibt es noch einen weiteren Unterschied:

  1. In lst[[n]] kann nur eine einzige Zahl n eingesetzt werden, es ist nicht möglich, n durch eine Index-Vektor zu ersetzen. Der Grund ist einfach: Die Komponenten der Liste können unterschiedlichen Datentyp haben und sie können nicht zu einem Vektor zusammengesetzt werden.
  2. Beim Zugriff mit lst[n] kann man anstelle von n auch einen Index-Vektor einsetzen; erzeugt wird dann die entsprechende Teil-Liste von lst.

Drei Zugriffsarten auf die Spalten eines Dataframes

Die Komponenten eines Dataframe sind die Spalten, die jeweils Vektoren sind (wobei character-Vektoren zu Faktoren konvertiert werden können). Der Datentyp dieser Vektor kann unterschiedlich sein.

Für den Zugriff auf die Spalten eines Dataframe df gibt es drei Möglichkeiten:

  1. Sofern Namen gesetzt sind, ist der Zugriff mit df$name möglich und gleichwertig zu df[[n]] (sofern die n-te Spalte den Namen name besitzt; siehe 2.). Da die Spalten eines Dataframe in jeder Anwendung eine klar definierte Bedeutung haben sollten, ist es ratsam, geeignete Namen für sie zu vergeben. Und die Quelltexte werden leichter lesbar, wenn Zugriffe über Spalten-Namen und nicht über den Spalten-Index erfolgen.
  2. Mit df[[n]] kann man auf die n-te Spalte von df zugreifen; man erhält den entsprechenden Vektor (oder Faktor).
  3. Mit df[n] wird ein neues Dataframe-Objekt erzeugt, das nur eine Spalte hat, nämlich die n-te Spalte von df. Man sollte hier also nicht vom "Zugriff auf eine Spalte" sprechen, sondern davon, dass ein Teil-Dataframe mit der ausgewählten Spalte erzeugt wird.

Die Zugriffsarten werden im Folgenden an Beispielen erklärt – zu Beginn mit dem Zugriff über den Spalten-Namen, da dies die häufigste Zugriffsart ist.

Zugriff mit den Namen der Spalten

Dazu wird wieder die Sonntagsfrage sf verwendet, die beim Aufruf von data.frame() die Spalten-Namen setzt (Zeile 3). Die drei Vektoren partei, age und letzteWahl, mit denen das Dataframe-Objekt sf erzeugt wurde (Zeile 3), werden später nicht verwendet:

# partei, age, letzteWahl wie früher erklärt

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

# Auswahl der ersten Spalte: Partei
sf.partei <- sf$Partei

sf.partei
# [1] D C D C A D E B A C
# Levels: A B C D E

is.vector(sf.partei)
# [1] FALSE

str(sf.partei)
# Factor w/ 5 levels "A","B","C","D",..: 4 3 4 3 1 4 5 2 1 3

# Auswahl der zweiten Spalte: Alter
sf.age <- sf$Alter

sf.age
# [1] 52 38 24 53 49 50 86 76 53 50

is.vector(sf.age)
# [1] TRUE

str(sf.age)
# num [1:10] 52 38 24 53 49 50 86 76 53 50

Die erste Spalte ist ein Sonderfall, da sie ursprünglich ein character-Vektor war und in einen Faktor verwandelt wurde. Dadurch ist sf$Partei kein Vektor (Zeile 12 und 13), sondern – wie man an der Struktur sieht – ein Faktor (Zeile 15 und 16).

Dagegen ist sf$Alter ein Vektor (Zeile 24 und 25).

Zugriff auf eine Spalte mit [ [.] ]

Wie bei Listen wird bei Dataframes mit [[.]] auf einzelne Komponenten zugegriffen:

# Sonntagsfrage sf wie oben

sf[[1]]
# [1] D C D C A D E B A C
# Levels: A B C D E

str(sf[[1]])
# Factor w/ 5 levels "A","B","C","D",..: 4 3 4 3 1 4 5 2 1 3

sf[[2]]
# [1] 52 38 24 53 49 50 86 76 53 50

str(sf[[2]])
# num [1:10] 52 38 24 53 49 50 86 76 53 50

Man erkennt, dass die Zugriffe sf[[1]] und sf$Partei identisch sind, ebenso sf[[2]] und sf$Alter .

Wie bei Listen kann man in sf[[n]] lediglich eine Zahl n aber keinen Index-Vektor einsetzen.

Erzeugen eines Teil-Dataframes mit [.]

Mit dem Operator [.] wird ebenfalls auf eine Spalte eines Dataframes df zugegriffen, es wird aber nicht der entsprechende Vektor (oder Faktor) zurückgegeben, sondern der Vektor wird in ein Dataframe verpackt. Oder man sollte es besser so formulieren: Mit df[n] wird ein Teil-Dataframe von df erzeugt, das die n-te Spalte von df enthält.

Anstelle einer einzigen Zahl kann man jetzt auch einen Index-Vektor einsetzen und damit beliebige Teile von df spaltenweise auswählen.

# Sonntagsfrage sf wie oben

# Teil-Dataframe aus 1. Spalte von sf:
sf[1]
#     Partei
# 1       D
# 2       C
# 3       D
# 4       C
# 5       A
# 6       D
# 7       E
# 8       B
# 9       A
# 10      C

str(sf[1])
# 'data.frame': 10 obs. of  1 variable:
#   $ Partei: Factor w/ 5 levels "A","B","C","D",..: 4 3 4 3 1 4 5 2 1 3

# Teil-Dataframe aus 2. Spalte von sf:
sf[2]
#     Alter
# 1     52
# 2     38
# 3     24
# 4     53
# 5     49
# 6     50
# 7     86
# 8     76
# 9     53
# 10    50

str(sf[2])
# 'data.frame': 10 obs. of  1 variable:
#   $ Alter: num  52 38 24 53 49 50 86 76 53 50

# Teil-Dataframe aus 1. Spalte und 2. Spalte von sf:
sf[c(1, 2)]
#      Partei Alter
# 1       D    52
# 2       C    38
# 3       D    24
# 4       C    53
# 5       A    49
# 6       D    50
# 7       E    86
# 8       B    76
# 9       A    53
# 10      C    50

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

Man erkennt, dass sowohl die Spalten- als auch Zeilen-Namen an das Teil-Dataframe weitergereicht werden.

Zugriff auf die Zeilen eines Dataframes

Der Zugriff auf die Spalten eines Dataframes beruht auf der Ähnlichkeit mit Listen. Um auf die Zeilen zuzugreifen verwendet man die Ähnlichkeit mit Matrizen.

Der Zugriff auf eine Zeile eines Dataframe erfolgt wie bei einer Matrix mit dem Operator [.,.] , wobei der erste Index die auszuwählende Zeile angibt, der zweite Index bleibt leer (wildcard), um alle Spalten auswählen.

Da in einem Dataframe die Spalten unterschiedliche Datentypen haben können, ist es klar, dass der Rückgabewert kein Vektor sein kann. Wie das folgendem Beispiel zeigt, wird ein Dataframe erzeugt. Dazu wird aus der Sonntagsfrage sf die 5. Zeile ausgewählt (Zeile 3) und untersucht:

# Sonntagsfrage sf wie oben

sf.5 <- sf[5, ]

sf.5
# Partei Alter letzteWahl
# 5      A    49      FALSE

str(sf.5)
# 'data.frame': 1 obs. of  3 variables:
#   $ Partei    : Factor w/ 5 levels "A","B","C","D",..: 1
# $ Alter     : num 49
# $ letzteWahl: logi FALSE

is.vector(sf.5)
# [1] FALSE

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

Zeile 5 bis 7: Die Ausgabe sieht aus wie bei einem Dataframe (die Spalten-Namen wurden übernommen; als Zeilen-Nummer wurde 5 belassen).

Zeile 9 bis 13: Die Ausgabe der Struktur bestätigt dies.

Zeile 15 bis 19: Man sieht ausdrücklich, dass kein Vektor entstanden ist sondern wieder ein Dataframe.

Man kann natürlich auch mehrere Zeilen auswählen:

# Index-Vektor idx zur Auswahl mehrerer Zeilen:
idx <- c(2, 5, 9)

sf.idx <- sf[idx, ]

str(sf.idx)
# 'data.frame': 3 obs. of  3 variables:
#   $ Partei    : Factor w/ 5 levels "A","B","C","D",..: 3 1 1
# $ Alter     : num  38 49 53
# $ letzteWahl: logi  FALSE FALSE TRUE

Gleichzeitiger Zugriff auf Zeilen und Spalten eines Dataframes

Zugriff mit den bisher vorgestellten Operatoren

Der Zugriff auf Zeilen und Spalten lässt sich natürlich kombinieren und erfolgt wie bei Matrizen.

Das folgende Skript wählt aus der Sonntagsfrage sf alle Personen aus, die älter sind als 50 und zeigt ihr Alter sowie die Partei; anstelle des Index-Vektors wird jetzt ein logischer Ausdruck eingesetzt, um die gesuchten Zeilen auszuwählen:

sf.50plus <- sf[sf$Alter > 50, c(1, 2)]

sf.50plus
#     Partei Alter
# 1      D    52
# 4      C    53
# 7      E    86
# 8      B    76
# 9      A    53

Aufgabe:

Untersuchen Sie welche Objekte entstehen, wenn man mit dem Operator [ , ]

Lösung:

Es entstehen:

Die Funktion subset()

Bei Vektoren wurde bereits die Funktion subset(x, subset) vorgestellt, mit der über einen logischen Ausdruck subset aus einem Vektor x ein Teilvektor ausgewählt werden kann: der Teilvektor enthält alle Komponenten, die die Bedingung subset erfüllen.

Diese Funktion ist auch für Dataframes implementiert und besitzt ein weiteres Argument select: subset(x, subset, select) . Mit subset wird ein logischer Ausdruck verwendet, um anzugeben, welche Zeilen aus dem Dataframe x ausgewählt werden sollen; das Argument select wählt Spalten von x aus.

Das folgende Beispiel wählt aus der Sonntagsfrage zunächst alle Personen mit einem Alter über 50 Jahren (Zeile 3). Anschließend werden von diesem Dataframe nur die erste und zweite Spalte gewählt (Zeile 19):

# Sonntagsfrage sf wie oben

sf.50 <- subset(x = sf, subset = (Alter > 50))
sf.50
#     Partei Alter letzteWahl
# 1      D    52       TRUE
# 4      C    53       TRUE
# 7      E    86      FALSE
# 8      B    76       TRUE
# 9      A    53       TRUE

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


sf.50.Partei <- subset(x = sf, subset = (Alter > 50), select = c(1, 2))
sf.50.Partei
#     Partei Alter
# 1      D    52
# 4      C    53
# 7      E    86
# 8      B    76
# 9      A    53

str(sf.50.Partei)
# 'data.frame':	5 obs. of  2 variables:
#   $ Partei: Factor w/ 5 levels "A","B","C","D",..: 4 3 5 2 1
# $ Alter : num  52 53 86 76 53

Sortierung eines Dataframes

Möchte man ein Dataframe umordnen, so dass es gemäß den Einträgen einer Spalte sortiert ist, so ist dies komplizierter als bei Vektoren. Denn man muss:

Ob man aufsteigend oder absteigend sortieren, kann dann leicht festgelegt werden, indem man angibt, in welcher Reihenfolge die Indizes durchlaufen werden.

Wiederholung: die Funktionen sort() und order() bei Vektoren

In Vektoren in R: Anwendungen wurden im Abschnitt Funktionen zum Sortieren von Vektoren und verwandte Funktionen die beiden Funktionen sort() und order() vorgestellt.

Mit sort() kann ein Vektor sortiert werden (per default ist die Sortierung aufsteigend):

v <- c(1, 5, 2, 4, 1, 6, 3, 3, 6, 2)

v.asc <- sort(v)
v.asc
# [1] 1 1 2 2 3 3 4 5 6 6

Die Funktion order() bestimmt, in welcher Reihenfolge man die Indizes von v auswählen muss, so dass v aufsteigend angeordnet wird:

v <- c(1, 5, 2, 4, 1, 6, 3, 3, 6, 2)

idx <- order(v)
idx
# [1]  1  5  3 10  7  8  4  2  6  9
v[idx]
# [1] 1 1 2 2 3 3 4 5 6 6

Sortierung eines Dataframes gemäß einer Spalte

Das Wissen über die Funktionen sort() und order() bei Vektoren kann man jetzt verwenden, um ein Dataframe nach einer Spalte zu sortieren. Als Beispiel werden die Ergebnisse der Sonntagsfrage nach dem Alter der Personen sortiert. Damit die Vorgehensweise besser nachvollziehbar ist, werden sf und sämtliche Zwischenergebnisse ausgegeben:

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

idx.ord <- order(sf$Alter)

idx.ord
# [1]  3  2  5  6 10  1  4  9  8  7

sf.ord <- sf[idx.ord, ]
sf.ord
#     Partei Alter letzteWahl
# 3       D    24       TRUE
# 2       C    38      FALSE
# 5       A    49      FALSE
# 6       D    50       TRUE
# 10      C    50      FALSE
# 1       D    52       TRUE
# 4       C    53       TRUE
# 9       A    53       TRUE
# 8       B    76       TRUE
# 7       E    86      FALSE

sf.desc <- sf[rev(idx.ord), ]
sf.desc
#     Partei Alter letzteWahl
# 7       E    86      FALSE
# 8       B    76       TRUE
# 9       A    53       TRUE
# 4       C    53       TRUE
# 1       D    52       TRUE
# 10      C    50      FALSE
# 6       D    50       TRUE
# 5       A    49      FALSE
# 2       C    38      FALSE
# 3       D    24       TRUE

Zeile 14: Man bestimmt die Indizes, die die zweite Spalte Alter aufsteigend sortieren.

Zeile 16: Die Indizes werden ausgegeben (die dritte Person ist die Jüngste und so weiter).

Zeile 19: Das Dataframe sf werden alle Spalten ausgewählt, daher bleibt in sf[idx.ord, ] der zweite Index leer. Im ersten Index wird der soeben bestimmte Index-Vektor idx.ord angegeben, der für die richtige Sortierung der Zeilen sorgt.

Zeile 33: Soll nach dem Alter absteigend sortiert werden, muss man nur den Index-Vektor mit rev() umdrehen.

Im Skript oben sind sämtliche Zwischenschritte angegeben; die Sortierung kann auch mit weniger Quelltext erfolgen:

# Sonntagsfrage sf wie oben

sf[order(sf$Alter), ]
#     Partei Alter letzteWahl
# 3       D    24       TRUE
# 2       C    38      FALSE
# 5       A    49      FALSE
# 6       D    50       TRUE
# 10      C    50      FALSE
# 1       D    52       TRUE
# 4       C    53       TRUE
# 9       A    53       TRUE
# 8       B    76       TRUE
# 7       E    86      FALSE

Jetzt wird nur das sortierte Dataframe ausgegeben; der Vorteil ist, dass jetzt kein überflüssiger Speicherplatz belegt wird.

Man kann natürlich auch nach anderen Spalten sortieren, zum Beispiel nach der gewählten Partei:

sf[order(sf$Partei), ]
#     Partei Alter letzteWahl
# 5       A    49      FALSE
# 9       A    53       TRUE
# 8       B    76       TRUE
# 2       C    38      FALSE
# 4       C    53       TRUE
# 10      C    50      FALSE
# 1       D    52       TRUE
# 3       D    24       TRUE
# 6       D    50       TRUE
# 7       E    86      FALSE

Daten-Aggregation mit aggregate()

Was heißt Daten-Aggregation?

Der letzte Abschnitt zur Sortierung von Daten liefert einen leichten Zugang zur Frage, was man unter Daten-Aggregation versteht:

Bisher wurden lediglich die Daten innerhalb des Dataframes umgeordnet. Dazu wurde eine Spalte des Dataframes ausgewählt und alle Zeilen des Dataframes derart neu angeordnet, dass sie bezüglich der gewählten Spalte sortiert sind. (Und es ist nicht schwer, auf- oder absteigend zu sortieren.)

Beim diesem Sortier-Vorgang gibt es zwei Möglichkeiten:

  1. Die Sortierung ist eindeutig (so wie die Zeilen-Nummer eindeutig ist; bei der Spalte Alter muss man schon genauer hinsehen, ob nicht manche Zahlen mehrfach vorkommen).
  2. Die Sortierung ist nicht eindeutig. Ein Beispiel ist das letzte Skript, in dem nach Parteien sortiert wurde. Da die Parteien A, C und D in der Stichprobe mehrfach vorkommen, gibt es mehrere Zeilen mit diesen Parteien; wie man diese Gruppe anordnen soll, ist vielleicht nicht klar: im letzten Skript erkennt man, dass die Zeilen-Nummer verwendet wurde.

Aber der zweite Fall führt sofort zu einer weiteren Frage: Wie kann man auf die beim Sortier-Vorgang entstandene Gruppe zugreifen und eine Funktion auf die Gruppe anwenden?

Naheliegende Fragen bei der Sonntagsfrage sind etwa:

  1. Wie groß ist das Durchschnittsalter der Wähler einer Partei?
  2. Mit welcher relativen Häufigkeit haben die Wähler einer Partei an der letzten Wahl teilgenommen?
  3. Welche Partei bevorzugen Personen, die bei der letzten Wahl nicht gewählt haben? Und so weiter.

Abstrakt gesprochen lautet die Vorgehensweise etwa folgendermaßen:

Die Fragestellung: "Welches Durchschnittsalter haben die Wähler einer Partei X" führt zu folgender Vorgehensweise:

Die Funktion aggregate() ermöglich genau diese Fragestellungen zu bearbeiten.

Die Funktion aggregate()

Die wichtigsten Eingabewerte der Funktion aggregate() sind:

Die weiteren Argumente werden hier nicht betrachtet (Erläuterung in der Dokumentation zu aggregate()). Allgemein kann man aggregate() mit folgenden Argumenten aufrufen:

aggregate(x, by, FUN, ..., simplify = TRUE, drop = TRUE)

Damit kann man obige Frage nach dem mittleren Alter der Wähler jeder Partei beantworten:

# Sonntagsfrage sf wie oben

a <- aggregate(x = sf$Alter, by = sf[1], FUN = mean)
a
#   Partei  x
# 1      A 51
# 2      B 76
# 3      C 47
# 4      D 42
# 5      E 86

str(a)
# 'data.frame': 5 obs. of  2 variables:
#   $ Partei: Factor w/ 5 levels "A","B","C","D",..: 1 2 3 4 5
# $ x     : num  51 76 47 42 86

Zeile 3: Als Argument x muss nicht das gesamte Dataframe sf eingegeben werden; gruppiert werden soll das Alter der Personen, daher reicht die Spalte Alter von sf.

Das Argument by gibt an, wonach gruppiert werden soll, hier nach der Partei. Man wird vielleicht by = sf$Partei erwarten. Dies führt aber zu einer Fehlermeldung, die im nächsten Skript erklärt wird.

Die Funktion, die auf die Gruppen angewendet werden soll, ist die Mittelwertberechnung mit mean(). Auch dieser Funktionsaufruf wird unten noch näher erklärt.

Zeile 4 bis 10: Die Ausgabe zeigt die tabellarische Darstellung der Partei und der Durchschnittsalter.

Zeile 12 bis 15: Das Objekt, das durch de Funktion aggregate() erzeugt wird, ist ein Dataframe mit jetzt 5 Zeilen (für die 5 Parteien) und 2 Spalten (Partei und Durchschnittsalter). Dabei wird Partei als character-Vektor wieder in einen Faktor verwandelt.

Hätte man im Skript oben in Zeile 3 eingegeben:

a <- aggregate(x = sf$Alter, by = sf$Partei, FUN = mean)
# Error in aggregate.data.frame(as.data.frame(x), ...) : 
#  'by' muss eine Liste sein

so erhält man eine Fehlermeldung. Denn by = sf$Partei ist ein character-Vektor (der intern wie ein Faktor behandelt wird). Die Gruppierung der Daten hat aber in aggregate() durch eine Liste zu erfolgen.

Oben wurden die Zugriffsarten auf die Spalten eines Dataframes erklärt, dabei sollte klar geworden sein, dass sf$Partei und sf[1] verschiedenen Datentyp besitzen:

Und Letzteres wird als Eingabewert für das Argument by benötigt.

Zuletzt muss noch das Argument FUN erklärt werden: es gibt an, welche Funktion zur Auswertung der gruppierten Daten angewendet werden soll. Hier wird die Funktion mean() angegeben, wodurch das Durchschnittsalter innerhalb der Gruppen berechnet wird. Man könnte hier den Namen einer beliebigen Funktion angeben, sie muss nur geeignete Eingabe- und Ausgabewerte besitzen: Der Eingabewert muss ein Vektor sein, der Ausgabewert eine Zahl. Man könnte somit auch das Minimal-Alter oder Maximal-Alter der Wähler einer Partei bestimmen (mit FUN = min beziehungsweise FUN = max ).

In Faktoren in R: Anwendungen im Abschnitt Die Funktion tapply() wird mit tapply() eine Funktion mit ähnlichem Aufbau wie aggregate() erklärt, dort wird dann auch auf mögliche Werte von FUN eingegangen.

Ds folgende Beispiel zeigt, dass die in FUN übergebene Funktion auch auf mehrere Spalten angewendet werden kann (sofern dies sinnvoll ist). Zur Gruppierung der Daten werden wieder die Parteien verwendet mit by = sf[1] . Der Mittelwert wird sowohl vom Alter als auch von der Spalte "letzte Wahl" berechnet. Diese Spalte beinhaltet zwar logische Werte, die aber zu 0 und 1 konvertiert werden, so dass auch hier ein sinnvoller Mittelwert berechnet werden kann. Als Argument x verwendet man das gesamte Dataframe sf ohne die erste Spalte:

a <- aggregate(x = sf[-1], by = sf[1], FUN = mean)
a
#      Partei Alter letzteWahl
# 1      A    51  0.5000000
# 2      B    76  1.0000000
# 3      C    47  0.3333333
# 4      D    42  1.0000000
# 5      E    86  0.0000000

Der Rückgabewert von aggregate()

An den bisherigen Beispielen konnte man bereits erkennen: Der Rückgabewert der Funktion aggregate() ist ein Dataframe, wobei

Dass der Rückgabewert ein Dataframe ist, hat den Vorteil, dass man mit den so erzeugten Daten weiterrechnen kann – und sie dazu meist als Dataframe benötigt werden. In Faktoren in R: Anwendungen wird gezeigt, dass man obige Fragestellungen auch mit Hilfe von Faktoren und der Funktion tapply() bearbeiten könnte. Dabei entsteht aber eine Tabelle mit den relevanten Ergebnissen. Solange man nur an den Ergebnissen interessiert ist, wird man dies bevorzugen, nicht aber, wenn man weiterrechnen möchte.

Umwandlung eines Dataframes in eine Matrix

Es sollte aus den bisherigen Diskussionen klar geworden sein, dass de Datentypen Dataframe und Matrix nicht identisch sind und auch keiner ein Spezialfall des anderen ist. Eine Matrix hat atomare Struktur und daher müssen alle Komponenten einen identischen Modus besitzen; ein Dataframe ist eine rekursive Struktur, bei der die Spalten unterschiedlichen Modus haben können.

In vielen Anwendungsfällen werden aber ausschließlich Zahlen in einem Dataframe vorkommen und man möchte es in eine Matrix umwandeln, da man dann sämtliche Matrix-Operationen zur Verfügung hat. Es gibt zwei Funktionen, die ein Dataframe-Objekt in eine Matrix umwandeln: as.matrix() und data.matrix().

Enthält ein Dataframe ausschließlich Zahlen und logische Werte, ist die Wirkung beider Funktionen identisch. Da character-Vektoren intern als Faktoren (und somit als ganze Zahlen) gespeichert werden, ist die Wirkung unterschiedlich, sobald ein Dataframe Zeichenketten enthält.

Das folgende Skript zeigt die Anwendung der beiden Funktionen auf die Sonntagsfrage sf:

m1 <- data.matrix(frame = sf)
m1
# Partei Alter letzteWahl
# [1,]      4    52          1
# [2,]      3    38          0
# [3,]      4    24          1
# [4,]      3    53          1
# [5,]      1    49          0
# [6,]      4    50          1
# [7,]      5    86          0
# [8,]      2    76          1
# [9,]      1    53          1
# [10,]      3    50          0

str(m1)
# num [1:10, 1:3] 4 3 4 3 1 4 5 2 1 3 ...
# - attr(*, "dimnames")=List of 2
# ..$ : NULL
# ..$ : chr [1:3] "Partei" "Alter" "letzteWahl"

storage.mode(m1)
# [1] "double"

m2 <- as.matrix(sf)
m2
# 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(m2)
# chr [1:10, 1:3] "D" "C" "D" "C" "A" "D" "E" "B" "A" "C" "52" "38" "24" "53" "49" "50" "86" "76" "53" "50" " TRUE" "FALSE" " TRUE" " TRUE" "FALSE" " TRUE" ...
# - attr(*, "dimnames")=List of 2
# ..$ : NULL
# ..$ : chr [1:3] "Partei" "Alter" "letzteWahl"

storage.mode(m2)
# [1] "character"

Man erkennt:

Wählt man aus dem Dataframe sf lediglich die zweite und dritte Spalte aus, erzeugt auch as.matrix() eine Matrix aus Zahlen:

m3 <- as.matrix(sf[ , c(2, 3)])
m3
# Alter letzteWahl
# [1,]    52          1
# [2,]    38          0
# [3,]    24          1
# [4,]    53          1
# [5,]    49          0
# [6,]    50          1
# [7,]    86          0
# [8,]    76          1
# [9,]    53          1
# [10,]    50          0

str(m3)
# num [1:10, 1:2] 52 38 24 53 49 50 86 76 53 50 ...
# - attr(*, "dimnames")=List of 2
# ..$ : NULL
# ..$ : chr [1:2] "Alter" "letzteWahl"

Schreiben in eine Datei und Lesen aus einer Datei

Aufgrund seiner tabellarischen Anordnung ist ein Dataframe besonders gut dazu geeignet, um Daten aus einem R-Objekt in eine Datei zu schreiben; oder umgekehrt tabellarische Daten aus einer Text-Datei in ein R-Objekt zu laden.

Die beiden Funktionen, die dies ermöglichen sind write.table() und read.table() aus dem Paket utils:

write.table(x, file = "", append = FALSE, quote = TRUE, sep = " ",
    eol = "\n", na = "NA", dec = ".", row.names = TRUE,
    col.names = TRUE, qmethod = c("escape", "double"),
    fileEncoding = "")

read.table(file, header = FALSE, sep = "", quote = "\"'",
    dec = ".", numerals = c("allow.loss", "warn.loss", "no.loss"),
    row.names, col.names, as.is = !stringsAsFactors,
    na.strings = "NA", colClasses = NA, nrows = -1,
    skip = 0, check.names = TRUE, fill = !blank.lines.skip,
    strip.white = FALSE, blank.lines.skip = TRUE,
    comment.char = "#",
    allowEscapes = FALSE, flush = FALSE,
    stringsAsFactors = default.stringsAsFactors(),
    fileEncoding = "", encoding = "unknown", text, skipNul = FALSE)

Wie man sieht, haben beide eine Vielzahl von Argumenten, die hier nicht erklärt werden. Es soll nur der einfachste Anwendungsfall gezeigt werden: Das Dataframe der Sonntagsfrage wird in eine Datei geschrieben und anschließend von dort gelesen.

# Sonntagsfrage sf wie oben

txtFile <- "C:\\sf.txt"

write.table(x = sf, file = txtFile, quote = FALSE)

Zeile 3: Der Pfad der zu erzeugenden Datei wird als Zeichenkette definiert. Hier soll direkt im Verzeichnis C die Datei sf.txt angelegt werden. Das Trennungszeichen \ im Dateipfad muss maskiert werden. Die Zeichenkette könnte auch direkt an das Argument file in Zeile 5 übergeben werden.

Zeile 5: Die Funktion write.table() wird möglichst einfach aufgerufen; übergeben werden:

Die Text-Datei unter dem angegebenen Pfad sollte jetzt wie folgt aussehen:

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 erkennt, dass

in die Datei geschrieben wurden.

Man kann diese Datei jetzt lesen mit:

df <- read.table(file = txtFile)
df
# 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

Wie man sieht, erhält man das bekannte Dataframe zurück.

Dieses Beispiel drängt natürlich die Frage auf: Welche Eigenschaften muss eine Text-Datei haben, damit man sie mit read.table() in ein Dataframe verwandeln kann?

Als saloppe Antwort kann man geben: Die Text-Datei muss tabellarisch aufgebaut sein wie die Ausgabe eines Dataframes; an den Eingabewerten der Funktion read.table() sieht man, dass gewisse Abweichungen erlaubt sind, die hier nicht diskutiert werden sollen (in der Dokumentation erstreckt sich die Beschreibung von read.table() über etwa 4 Seiten).

Zusammenfassung

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 Auswahl der Spalte mit Namen name des Dataframes x.
x[[n]] Auswahl der n-ten Spalte des Dataframes x. Statt n kann kein Index-Vektor eingesetzt werden.
x[n] Die n-te Spalte des Dataframes x wird in ein neues Dataframe verpackt (Spalten- und Zeilen-Namen wie in x). Statt n kann auch ein Index-Vektor eingesetzt werden.
x[n, ] Die n-te Zeile des Dataframes x wird in ein neues Dataframe verpackt (Spalten- und Zeilen-Namen wie in x). Statt n kann auch ein Index-Vektor eingesetzt werden.
x[n, m] Zugriff auf das Element in der n-ten Zeile und m-ten Spalte des Dataframes x.
?Extract Dokumentation über Operatoren Extrahieren von Elementen aus einem Objekt (hier Dataframe); Extract befindet im Paket base.
subset(x, subset, select) Auswahl von Elementen aus einem Dataframe x: subset ist ein logischer Ausdruck, der Zeilen auswählt, und mit select werden Spalten von x ausgewählt.

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

Funktion Rückgabewert Datentyp
head(x, n = 6L) Dataframe, das aus den ersten n Zeilen des Dataframes x besteht (Spalten- und Zeilen-Namen wie in x). Dataframe (class "data.frame")
tail(x, n = 6L) Dataframe, das aus den letzten n Zeilen des Dataframes x besteht (Spalten- und Zeilen-Namen wie in x). Dataframe (class "data.frame")
x$name Auswahl der Spalte mit Namen name des Dataframes x. Vektor (oder Faktor, falls die Spalte ein character-Vektor ist)
x[[n]] Auswahl der n-ten Spalte des Dataframes x. Vektor (oder Faktor, falls die Spalte ein character-Vektor ist)
x[n] Teil-Dataframe von x, das nur dessen n-te Spalte enthält (oder mehrere Spalten, wenn ein Index-Vektor eingesetzt wird). Dataframe (class "data.frame")
x[n, ] Teil-Dataframe von x, das nur dessen n-te Zeile enthält (oder mehrere Zeilen, wenn ein Index-Vektor eingesetzt wird). Dataframe (class "data.frame")
x[n, m] Element aus der n-ten Zeile und m-ten Spalte des Dataframes x. Datentyp von x[n, m] (oder ein Faktor-Level)
subset(x, subset, select) Teil-Dataframe von x: mit subset werden Zeilen ausgewählt, und mit select werden Spalten von x ausgewählt. Dataframe (class "data.frame")

Anwendungen von Dataframes

Die folgende Tabelle zeigt eine Kurzbeschreibung der in diesem Kapitel vorgestellten Funktionen, die bei Anwendungen mit Dataframes wichtig sind. 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
x[order(x$name), ] Das Dataframe x wird gemäß der Spalte name aufsteigend sortiert.
aggregate(x, by, FUN, ..., simplify = TRUE, drop = TRUE) Die Daten in x werden gemäß by gruppiert und auf jede Gruppe wird die Funktion FUN angewendet (x und by sind meist eine oder mehrere Spalten eines Dataframes).
?aggregate Weitere Informationen zur Funktion aggregate() im Paket stats.
data.matrix(frame) Das Dataframe frame wird in eine Matrix umgewandelt (Faktor-Levels werden gemäß ihrer internen Kodierung in ganze Zahlen verwandelt).
as.matrix(x) Das Dataframe x wird in eine Matrix umgewandelt (die Hierarchie der Datentypen wird befolgt; es werden keine Faktor-Levels eingesetzt).
read.table(file) Die Datei file wird gelesen und falls sie gewisse Bedingungen erfüllt (tabellarische Daten, geeigneter Separator und so weiter) in ein Dataframe verwandelt.
write.table(x, file = "", quote = TRUE) Das Dataframe x wird in die Datei file geschrieben.
?read.table Weitere Informationen zur Funktion read.table() im Paket utils.
?write.table Weitere Informationen zur Funktion write.table() im Paket utils.

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

Funktion Rückgabewert Datentyp
x[order(x$name), ] Es wird ein neues Dataframe erzeugt, das in allen Eigenschaften mit x übereinstimmt, aber gemäß der Spalte name aufsteigend sortiert ist. Dataframe (class "data.frame")
aggregate(x, by, FUN, ..., simplify = TRUE, drop = TRUE) Für jede der mit by gebildeten Gruppen entsteht eine Zeile eines neuen Dataframes. In einer Spalte steht jeweils der Funktionswert (Anwendung von FUN auf die Gruppe), die anderen Spalten stammen aus x. Dataframe (class "data.frame")
data.matrix(frame) Das Dataframe frame wird in eine Matrix umgewandelt (Faktor-Levels werden gemäß ihrer internen Kodierung in ganze Zahlen verwandelt). Matrix
as.matrix(x) Das Dataframe x wird in eine Matrix umgewandelt (die Hierarchie der Datentypen wird befolgt; keine Faktor-Levels). Matrix
read.table(file) Sofern die Datei file tabellarische Daten enthält, die gewissen Regeln gehorchen, werden sie in ein Dataframe verwandelt. Dataframe (class "data.frame")
write.table(x, file = "", quote = TRUE) kein Rückgabewert NULL