-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathdatenmanipulation.Rmd
412 lines (256 loc) · 26.3 KB
/
datenmanipulation.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
---
title: "Datenmanipulation für Fortgeschrittene"
author: "Sven Hohenstein"
output:
html_document:
highlight: tango
theme: readable
---
R bietet zahlreiche Funktionen, mit denen sich die Datenmanipulation mit relativ wenig Aufwand gestalten lässt. Häufig ist es nötig, Manipulationen auf Teile eines Datensatzes anzuwenden, beispielsweise auf jede Zeile oder die Daten jeder Versuchsperson separat. Neben der Möglichkeit, Schleifen zu verwenden, bietet R zahlreiche Funktionen, die dieses Arbeiten mit den Teilen eines Datensatzes vereinfacht.
## Funktionen auf einzelne Komponenten anwenden: Die `apply`-Familie
Die `apply`-Familie ist eine Gruppe von Funktionen, deren Namen mit `apply` enden. Mithilfe dieser Funktionen lässt sich die Arbeit mit Teilen eines Datensatzes sehr komfortabel gestalten.
### Die Funktion `apply`
Mit `apply` lässt sich eine Funktion auf die Zeile oder Spalten einer Matrix oder eines Data Frame anwenden.
Zur Illustration betrachten wir die folgende $3 \times 5$-Matrix `mat`.
```{r example matrix}
mat <- matrix(1:15, nrow = 3, ncol = 5)
mat
```
Wenn wir das Maximum jeder Zeile bestimmen wollen, können wir eine `for`-Schleife verwenden.
```{r loop}
maxima <- rep(0, nrow(mat))
for (i in 1:nrow(mat)) {
maxima[i] <- max(mat[i, ])
}
maxima
```
Zunächst wird ein Vektor `maxima` erzeugt, in dem die Ergebnisse gespeichert werden sollen. Dann wird in einer Schleife über alle Zeilennummern der Matrix jeweils in jedem Durchgang das Maximum der jeweiligen Zeile mit dem Index `i` bestimmt. Das Ergebnis wird am entsprechenden Index im Vektor `maxima` abgespeichert.
Sehen wir uns nun an, wie man die Maximalwerte der Zeilen mit `apply` finden kann.
```{r apply 1}
apply(mat, 1, max)
```
Das Ergebnis ist identisch zu dem, welches wir mit der `for`-Schleife bekommen haben. Allerdings ist der Programmieraufwand ungleich geringer. Das erste Argument, welches `apply` übergeben wird, ist der Name der Matrix. Es folgt ein Index der Dimension (`1`). Die erste Dimension entspricht den Spalten der Matrix, die zweite Dimension ihren Spalten. Das dritte Argument ist der Name der Funktion (`max`), welche wir auf jede Zeile anwenden wollen.
Die `apply`-Funktion verfährt dabei ähnlich wie eine Schleife: Das Maximum jeder Zeile der Reihenfolge nach für jede Zeile der Matrix bestimmt. Am Ende erhalten wir einen Vektor mit den Werten.
Analog können wir die Maximalwerte der Spalten berechnen. Dazu wird `2` als zweites Argument verwendet.
```{r apply 2}
apply(mat, 2, max)
```
Dadurch erhalten wir einen Vektor, in dem jeder Wert einem Spaltenmaximum entspricht. Da die Matrix fünf Spalten hat, bekommen wir auch einen Vektor der Länge fünf.
Die Funktionen, die wir `apply` als drittes Argument übergeben könne, sind nicht beschränkt auf solche, die einen einzelnen Wert zurückliefern, wie beispielsweise `max`, `min`, `mean`, `sum` usw. Vielmehr können wir auch Funktionen verwenden, die mehrere Werte zurückliefern. Dazu sehen wir uns als Beispiel die Funktion `range` an, die die Bandbreite der Daten ermittelt. Diese Funktion gibt einen Vektor mit zwei Elementen zurück, dem Minimum und dem Maximum. Um die Bandbreite der Zeilen der Matrix `mat` zu berechnen, verwenden wir `apply` folgendermaßen.
```{r apply 1 range}
apply(mat, 1, range)
```
Hier sehen wir eine Besonderheit der `apply`-Funktion: Da die Funktion einen Vektor zurückliefert, werden die einzelnen Vektoren (einer für jede Zeile) als Matrix angeordnet. Dabei entspricht jede Spalte der Matrix der Bandbreite der zugehörigen Zeile.
Obwohl wir ein Ergebnisse für die einzelnen Zeilen erzeugt haben, werden diese trotzdem als Spalten zusammengesetzt. Übrigens gilt das gleiche, wenn wir eine Funktion auf die Spalten einer Matrix anwenden.
```{r apply 2 range}
apply(mat, 2, range)
```
Auch bei spaltenweiser Berechnung werden die Ergebnisse als Spalten einer Matrix repräsentiert.
Wir sind bei `apply` nicht auf vorhandene Funktionen beschränkt, sondern können auch unsere eigenen Funktionen spezifizieren und diese `apply` als Argument übergeben. Möchten wir beispielsweise für jede Zeile sowohl das Minimum (`min`) als auch den Mittelwert (`mean`) der Daten berechnen und in einem Vektor der Länge zwei zurückgeben, können wir folgendermaßen vorgehen.
```{r apply fun}
apply(mat, 1, function(x) c(min(x), mean(x)))
```
In dieser Funktionsdefinition entspricht der Parameter `x` der jeweiligen Zeile. Es werden Minimum und Mittelwert berechnet, die mittels `c` in einem Vektor zusammengefasst werden.
Die Funktion `apply` ist dann hilfreich, wenn man Funktionen zeilen- order spaltenweise anwenden möchte.
### Die Funktionen `lapply`, `sapply` und `vapply`
Mit `lapply`, `sapply` und `vapply` lassen sich Funktionen auf einzelne Elemente eines Vektors, Elemente einer Liste oder Spalten eines Data Frames anwenden. Man erhält auch dabei für jedes der Elemente einen Wert (oder mehrere Werte).
Wir sehen uns eine Liste `liste` an, die numerische Vektoren enthält.
```{r example list}
liste <- list(a = 1:5, b = c(8, -7), c = c(-1, 0, 5), d = -0.4)
liste
```
Die Liste enthält vier Elemente, die mit den Buchstaben `a`, `b`, `c` und `d` bezeichnet sind. Die Listenelemente, also die Vektoren, haben unterschiedliche Länge.
#### `lapply`
Wir können `lapply` benutzen um Funktionen auf die einzelnen Vektoren anzuwenden. In jedem Fall gibt `lapply` eine Liste zurück. Die Länge der Vektoren können wir mit der Funktion `length` ermitteln. Dazu verwenden wir folgendes Kommando.
```{r lapply 1}
lapply(liste, length)
```
Wir erhalten eine Liste, die über die gleiche Anzahl an Elemente wie `liste` verfügt, nämlich vier. Auch die Namen der Listenelemente bleiben erhalten. Jedes Element enthält jetzt genau eine Nummer: die jeweilige Länge des entsprechenden Vektors in `liste`.
Die Funktion `lapply` lässt sich auch auf die einzelnen Elemente eines Vektors anwenden. Allerdings kommt diese Funktionalität selten zum Einsatz, da die sehr viele Funktionen in R direkt Vektoren als Argument akzeptieren.
Eine andere praktische Nutzung ist die Verwendung von `lapply` mit Data Frames. Dazu sehen wir uns den in R verfügbaren Datensatz `trees` an, welcher die Maße von 31 gefällten Bäumen enthält.
```{r trees}
head(trees)
```
Die drei Spalten enthalten den Durchmesser (`Girth`), die Höhe (`Height`) und das Volumen (`Volume`) der Stämme. Mittels `lapply` können wir die Varianz (`var`) der einzelnen Spalten leicht ermitteln.
```{r lapply trees}
lapply(trees, var)
```
Wir erhalten eine Liste, in der jedes Element einer Spalte im Data Frame `trees` entspricht.
Wenn eine Liste nur einzelne Nummern enthält, also Vektoren der Länge eins, ist ein Vektor eine attraktivere Variante zum Speichern der Daten, da sich Vektoren in der Regel einfacher handhaben lassen. Wir können `unlist` verwenden, um die Liste in einen Vektor umzuwandeln
```{r unlist}
unlist(lapply(trees, var))
```
Allerdings steht mit der Funktion `sapply` eine einfachere Alternative zur Verfügung.
#### `sapply`
Prinzipiell funktioniert `sapply` wie `lapply`. Der Unterschied besteht im Format, im dem die Daten zurückgegeben werden.
Wenn man eine Funktion anwendet, die genau einen Wert zurückliefert (wie beispielsweise `var`), gibt `sapply` die Werte automatisch in einem Vektor zurück.
```{r sapply trees}
sapply(trees, var)
```
Auch wenn eine Funktion verwendet wird, die mehr als einen Wert liefert (z.B. `range`), wird `sapply` die Daten in ein praktischeres Format überführen. In diesem Fall erhält man eine Matrix.
```{r sapply trees range}
sapply(trees, range)
```
Ebenso wie bei `apply` entsprechen die einzelnen Vektoren den Spalten der Matrix.
Wenn die Funktion Vektoren unterschiedlicher Länge für jedes Listenelement zurückliefert, kann `sapply` das Format der Daten nicht vereinfachen. In diesem Fall wird auch, wie bei `lapply`, eine Liste erzeugt.
Wir können mit `abs` den absoluten Betrag der Werte bestimmen.
```{r sapply abs}
sapply(liste, abs)
```
Das gleiche Ergebnis hätten wir auch mit `lapply` erzielt. Die Funktion `sapply` ist dann sinnvoll, wenn sich die Daten zu einem Vektor oder einer Matrix vereinfachen lassen.
#### `vapply`
Die Funktion `vapply` ist sehr ähnlich zu `sapply`, allerdings kann `vapply` in manchen Fällen effizienter sein. Außerdem muss man für `vapply` explizit angeben, was die Funktion zurückliefern wird.
Sehen wir uns zunächst ein Beispiel an. Folgendermaßen können wir mit `vapply` vorgehen, wenn wir die Streuung (`sd`) für die einzelnen Spalten in `trees` berechnen möchten.
```{r vapply trees}
vapply(trees, sd, FUN.VALUE = numeric(1))
```
Im Vergleich zu `sapply` benötigen wir bei `vapply` noch ein weiteres Argument. Wir müssen mit `FUN.VALUE` angeben, was die Funktion, in diesem Fall `sd`, für jede Spalte erzeugen wird. Dabei steht `numeric(1)` für einen numerischen Vektor der Länge eins.
Analog können wir die Bandbreite der Spalten berechnen, müssen dann aber `FUN.VALUE = numeric(2)` als Argument verwenden, da die Funktion `range` einen numerischen Vektor mit zwei Elementen erzeugt.
```{r vapply trees range}
vapply(trees, range, FUN.VALUE = numeric(2))
```
Die Daten werden ebenso aufbereitet wie bei `sapply`: Einzelne Werte werden zu Vektoren und Vektoren zu Matrizen kombiniert.
Da bei `vapply` genau angegeben werden muss, was die Funktion zurückliefert, ist es nicht möglich `vapply` zu benutzen, wenn die Funktion für jedes Element unterschiedlich lange Vektoren zurückliefert. In diesem Fall kann man `lapply` verwenden und erhält eine Liste. Der Vorteil bei `vapply` liegt in der Effizienz. Da schon vorher bekannt ist, was die Funktion für jedes Element zurückliefern wird, kann die Kombination der Daten schnell erfolgen. Für einfache Aufgaben, wie in diesen Beispielen, ist `sapply` jedoch völlig ausreichend.
Wenn die Funktion keinen numerischen Vektor zurückgibt, kann man `numeric` nicht verwenden. Stattdessen verwendet man für Vektoren mit Zeichenketten `character` und für logische Vektoren `logical`.
```{r vapply toString}
vapply(liste, toString, FUN.VALUE = character(1))
```
Die Funktion `toString` kombiniert die Werte des Vektors zu einem einzigen String. Entsprechend muss man für `vapply` das Argument `FUN.VALUE = character(1)` verwenden.
### Die Funktion `mapply`
Bisher haben wir Funktionen der `apply`-Familie kennengelernt, mit denen man Funktionen auf Teile eines Objektes anwenden kann. Dabei wird der jeweilige Teil des Objektes, z.B. ein Listenelement, als Argument für eine Funktion genutzt. Die Funktion `mapply` ist verwandt mit `sapply`, wird jedoch angewendet, wenn wir mehrere Objekte haben, auf die wir einen Funktion anwenden möchten.
In folgendem Beispiel haben wir zwei Vektoren, `start` und `ende`. Der Vektor `start` enthält die Zahlen von 1 bis 4, der Vektor `ende` enthält die Zahlen von 3 bis 6.
```{r mapply examples}
start <- 1:4
ende <- 5:8
```
Nun wollen wir die Funktion `seq` elementweise auf beide Vektoren anwenden. Das heißt, wir nutzen jeweils einen Wert aus `start` und `ende` als Argument für `seq`. Die Funktion `seq` erzeugt eine Sequenz vom ersten Argument zum zweiten Argument.
```{r seq}
seq(1, 5)
```
Mit der Funktion `mapply` können wir mehrere Sequenzen erstellen, dabei werden der erste Wert aus `start` und der erste Wert aus `ende` als Argumente verwendet, danach der jeweils zweite Wert usw. bis zum letzten Element der Vektoren.
```{r mapply seq 1}
mapply(seq, start, ende)
```
Besonders aufpassen muss man bei der Reihenfolge der Argumente. Im Gegensatz zu den anderen Funktionen der `apply`-Familie wird bei `mapply` die Funktion als *erstes* Argument übergeben. Die weiteren Argumente sind die jeweiligen Objekte.
Wie bei `sapply` werden Vektoren spaltenweise zusammengebunden, so dass man eine Matrix erhält.
Dabei ist `mapply` nicht auf zwei Objekte beschränkt, sondern unterstützt Funktionen mit einer beliebigen Anzahl von Argumenten. Man kann für `seq` auch ein drittes Argument verwenden, welches den Abstand zwischen den Zahlen der Sequenz angibt. In diesem Beispiel verwenden wir einen dritten Vektor, `abstand` als weiteres Argument in `mapply`.
```{r mapply seq 2}
abstand <- 4:1
mapply(seq, start, ende, abstand)
```
Somit erhalten wir vier Sequenzen, in denen die Zahlen um vier bis eins anwachsen. Da die Länge der Vektoren unterschiedlich ist, erhalten wir eine Liste.
### Die Funktion `replicate`
Obwohl `replicate` kein `apply` im Namen enthält, ist die Funktion verwandt mit den oben vorgestellten Funktionen. Jedoch wird mit `replicate` nicht eine Funktion auf die Elemente eines Objektes angewendet, sondern ein Befehl mehrfach ausgeführt.
Dies ist zum Beispiel für Simulationen nützlich. Nehmen wir an, wir möchten zehnmal aus einer Standardnormalverteilung einhundert Zufallswerte ziehen (`rnorm`) und deren Mittelwert (`mean`) berechnen. Das Ergebnisse soll schließlich ein Vektor mit zehn Zahlen sein. Dazu können wir folgendermaßen mit einer `for`-Schleife vorgehen.
```{r for replicate}
ergebnis <- rep(0, 10)
for (i in 1:10) {
zufallswerte <- rnorm(100)
ergebnis[i] <- mean(zufallswerte)
}
ergebnis
```
Mithilfe von `replicate` lässt sich das wiederholte Ausführen von R-Befehlen leichter umsetzen. Dieselbe Prozedur sieht in diesem Fall so aus.
```{r replicate}
replicate(10, {zufallswerte <- rnorm(100); mean(zufallswerte)})
```
Wir erhalten wegen der Zufallszahlen natürlich nicht die identischen Ergebnisse. Das erste Argument für `replicate` ist die Anzahl der Wiederholungen (`10`). Das zweite Argument sind die Befehle, die mehrfach ausgeführt werden sollen. Mehrere Befehle muss man zwischen geschweifte Klammern setzen und jeweils durch ein Semikolon (`;`) voneinander trennen.
Dabei wird immer das Ergebnis des letzten Kommandos gespeichert, in diesem Fall also `mean(zufallswerte)`. Ebenso wie bei `sapply` ist das Ausgabeformat ein Vektor bei einzelnen Werten, eine Matrix bei Vektoren gleicher Länge und eine Liste bei Vektoren unterschiedlicher Länge.
Wir haben viele Funktionen der `apply`-Familie kennengelernt. Jede dieser Funktionen ist für bestimmte Zwecke geeignet. Fassen wir noch einmal die wichtigsten Punkte zusammen. Um eine Funktion auf die Zeilen oder Spalten einer Matrix (oder eines Data Frames) anzuwenden, nutzt man `apply`. Um eine Funktion auf Elemente eines Vektors, Elemente einer Liste oder Spalten eines Data Frames anzuwenden, nutzt man `lapply`, `sapply` oder `vapply`. Für die Anwendung einer Funktion mit mehreren Argumenten auf die Elemente verschiedener Objekte steht `mapply` zur Verfügung. Schließlich kann man `replicate` nutzen, um Kommandos zu wiederholen, unabhängig von konkreten Objekten.
## Funktionen auf alle Kombinationen von Werten anwenden
Die Funktion `outer` ermöglicht es, eine Funktion auf alle Kombinationen der Elemente zweier Vektoren anzuwenden. Zur Illustration sehen wir uns einen Vektor mit Buchstaben und einen mit Zahlen an.
```{r letters and numbers}
buchstaben <- c("A", "B", "C", "D")
zahlen <- 1:3
```
Wir wollen mit diesen vier Buchstaben und drei Zahlen alle $4 \cdot 3 = 12$ Kombinationen der Form `"A1"` erzeugen. Das Verbinden von Zeichenketten ermöglicht uns `paste0`. Dazu bietet uns `outer` eine einfache Möglichkeit.
```{r outer}
outer(buchstaben, zahlen, paste0)
```
Wir erhalten eine Matrix, die alle Kombinationen der Elemente der Vektoren enthält. Die Zeilen entsprechen dem ersten Argument (`buchstaben`), die Spalten dem zweiten (`zahlen`).
Für `outer` können wir nicht jede beliebige Funktion verwenden, sondern nur solche die zwei Vektoren als Argumente akzeptieren. Außerdem darf die Funktion für jede Kombination aus Vektorelementen nur *einen* Wert zurückgeben, sodass sich die Werte als Matrix anordnen lassen. Dies ist ein wichtiger Unterschied zu den meisten Funktionen der `apply`-Familie.
## Funktionen auf gruppierte Daten anwenden
Sehr oft sind die Werte eines Datensatzes nach einer oder mehreren Variables gruppiert. Dabei lässt sich jeder Wert in den Daten einer Faktorstufe oder einer Kombination mehrerer Faktorstufen zuordnen. Ein Beispiel für gruppierte Daten sind Reaktionszeiten oder Korrektheitswerte, die sich experimentellen Bedingungen zuordnen lassen. Dabei ist man daran interessiert, die Werte getrennt für die einzelnen Bedingungen auszuwerten um beispielsweise Mittelwerte oder Streuungen zu vergleichen.
Zur Veranschaulichung der Arbeit mit gruppierten Daten werden wir den in R verfügbaren Datensatz `ToothGrowth` verwenden. Dieser enthält Daten eines Experimentes, in dem man die Auswirkung der Gabe von Vitamin C auf das Zahnwachstum von Meerschweinchen untersucht hat.
```{r ToothGrowth}
head(ToothGrowth)
```
Die erste Spalte (`len`) enthält die abhängige Variable, die Zahnlänge. Die zweite Spalte ist ein Faktor (`supp`) mit zwei Stufen, der die Art der Gabe des Vitamin C kodiert: `VC` für reines Vitamin C (Ascorbinsäure) und `OJ` für Orangensaft. Die dritte Spalte (`dose`) enthält die Dosis in Milligramm. Die Dosis die zweite experimentelle Bedingung mit den Stufen `0.5`, `1.0` und `2.0`, jedoch ist diese Spalte kein Faktor, sondern numerisch.
Wir können die Zahnlänge nach der Art der Gabe, der Dosis oder der Kombination beider Bedingungen gruppieren. Jede Beobachtung lässt sich eindeutig einer Gruppe zuordnen. Die Funktionen, die wir uns für die Arbeit mit gruppierten Werten ansehen werden, sind `tapply`, `ave`,`aggregate` und `by`.
### Die Funktion `tapply`
Im Gegensatz zu den anderen Funktionen der `apply`-Familie, wird `tapply` auf Daten angewendet, die sich nach einer order mehren Variablen gruppieren lassen. Folgendermaßen können wir uns die mittlere Zahnlänge in Abhängigkeit der Art der Gabe ansehen.
```{r tapply supp}
tapply(ToothGrowth$len, ToothGrowth$supp, mean)
```
Das erste Argument für `tapply` ist die Variable, auf welche wir die Funktion anwenden wollen (`ToothGrowth$len`), das zweite Argument ist die Gruppierungsvariable (`ToothGrowth$dose`). Die Funktion (`mean`) ist das dritte Argument.
Wir erhalten als Ergebnis einen Vektor mit zwei Werten, den Mittelwert für die Bedingung `OJ` und den Mittelwert für die Bedingung `VC`. Dabei geht die Funktion `tapply` so vor, dass sie den Vektor mit den Werten in mehrere Abschnitte teilt, die den verschiedenen Faktorstufen entsprechen. Anschließend wird die Funktion auf jeden dieser Abschnitte angewendet.
Mit `tapply` ist es auch möglich, die Daten nach mehreren Variablen zu gruppieren. Wollen wir Mittelwerte Gruppiert nach Art der Gabe und Dosis erhalten, müssen wir beide Vektoren in einer Liste (`list`) kombinieren.
```{r tapply supp dose}
tapply(ToothGrowth$len, list(ToothGrowth$supp, ToothGrowth$dose), mean)
```
Wir erhalten eine Matrix, in der die Zeilen den Werten in `ToothGrowth$supp` und die Spalten den Werten in `ToothGrowth$dose` entsprechen. Die Elemente der Matrix sind die jeweiligen Mittelwerte der entsprechend gruppierten Teile des Vektors `ToothGrowth$len`.
### Die Funktion `ave`
Die Funktion `ave` wird ebenso wie `tapply` zur Anwendung einer Funktion auf gruppierte Daten verwendet. Allerdings erhält man mit `ave` immer einen Vektor, dessen Länge der Länge der Daten entspricht. Dies ist dann hilfreich, wenn wir beispielsweise eine weitere Spalte mit Mittelwerten zu einem Data Frame hinzufügen wollen.
Auch bei `ave` verwenden wir als Argument die Variable, auf die die Funktion angewendet werden soll, dann folgen die Gruppierungsvariable und als letztes Argument die Funktion. Im Gegensatz zu `tapply` müssen wir immer `FUN =` für die Funktion benutzen.
```{r ave supp}
supp_mittelwerte <- ave(ToothGrowth$len, ToothGrowth$supp, FUN = mean)
length(supp_mittelwerte)
unique(supp_mittelwerte)
```
Wir erhalten eine Vektor der Länge sechzig. Dies entspricht der Anzahl der Zeilen im Data Frame `ToothGrowth`. Weiterhin sehen wir, dass der Vektor nur zwei verschiedene Werte enthält, die beiden Mittelwerte der Gruppen. Diesen Vektor können wir beispielsweise mit `cbind` zum Data Frame hinzufügen. So haben wir in jeder Zeile den jeweiligen Mittelwert der Zahnlänge gruppiert nach Art der Gabe.
```{r ave cbind}
head(cbind(ToothGrowth, supp_mittelwerte))
```
Die Handhabung mehrerer Gruppierungsvariablen erfolgt bei `ave` anders als bei `tapply`. Die verschiedenen Variablen werden jeweils als eigenes Argument angegeben. Eine Gruppierung nach zwei Variablen erreichen wir mit folgendem Kommando.
```{r ave supp dose}
supp_dose_mittelwerte <- ave(ToothGrowth$len, ToothGrowth$supp,
ToothGrowth$dose, FUN = mean)
length(supp_dose_mittelwerte)
unique(supp_dose_mittelwerte)
```
Wieder erhalten wir einen Vektor der Länge sechzig. Dieser enthält sechs verschiedene Werte da es insgesamt $2 \cdot 3 = 6$ verschiedene Kombinationen der zwei Variablen gibt.
### Die Funktion `aggregate`
Mit `aggregate` lassen sich ebenfalls Funktionen auf gruppierte Daten anwenden, jedoch wird das Ergebnis im Unterschied zu `tapply` als Data Frame zurückgegeben. Dies ist in vielen Anwendungsfällen ein übersichtlicheres Format, insbesondere bei mehreren Gruppierungsvariablen. Die Syntax ist identisch zu der von `tapply`. Als Argumente geben wir ein Objekt, eine Liste von Gruppierungsvariablen und die Funktion an.
```{r aggregate supp}
aggregate(ToothGrowth$len, list(ToothGrowth$supp, ToothGrowth$dose), mean)
```
Jede Zeile im Data Frame entspricht einer Kombination der Stufen der zwei Gruppierungsvariablen (`ToothGrowth$supp` und `ToothGrowth$dose`). Die ersten beiden Spalten enthalten die verschiedenen Ausprägungen dieser Gruppierungsvariablen, Die dritte Spalte (`x`) enthält die jeweiligen Mittelwerte. Die Namen der Spalten sind wenig aussagekräftig, da sie unabhängig von den Namen der eigentlichen Variablen sind. Auch hierfür gibt es eine einfache Lösung: Die Nutzung von `aggregate` mit einer Formel.
Neben der oben vorgestellten Variante kann `aggregate` mit einer Formel als Argument benutzt werden. Die Formel ist dabei vergleichbar mit einer Formel für zahlreiche Statistik-Funktionen in R, z.B. `lm` (lineare Regression). Sehen wir uns obiges Beispiel in der Formel-Variante an.
```{r aggregate supp form}
aggregate(len ~ supp + dose, ToothGrowth, mean)
```
Das ist eine deutliche Verbesserung in der Lesbarkeit des Kommandos! In diesem Fall ist die Formel `len ~ supp + dose` das erste Argument. Wir können es lesen als: Verwende die Variable `len` und gruppiere sie (`~`) nach der Variable `supp` und (`+`) der Variable `dose`. Das zweite Argument ist der Name des Data Frames (`ToothGrowth`), in dem sich die Spalten mit den entsprechenden Namen in der Formel befinden. Schließlich ist das dritte Argument wieder die Funktion, die auf jeden Teil der Daten angewendet wird (`mean`).
Die Formel-Variante ist nicht nur zu bevorzugen, da sie die Arbeit mit Spalten eines Data Frames vereinfacht, sondern auch korrekt benannte Spalten für das Ergebnis liefert. Die Namen der Spalten in der Ausgabe entsprechen den Namen in der Formel.
In unserem Beispiel besteht der Data Frame nur aus der Variable, auf die die Funktion angewendet werden soll (`len`) und den Gruppierungsvariablen (`supp` und `dose`). In diesen Fällen können wir die Formel einfacher mit einem Punkt `.` erstellen.
```{r aggregate supp form dot}
aggregate(len ~ ., ToothGrowth, mean)
```
Dabei steht `.` für "alle übrigen Variablen", in diesem Fall also alle außer `len`.
Ein weiterer wichtiger Vorteil bei der Nutzung von `aggregate` ist die Möglichkeit, mehrere Variablen zu gruppieren. Als Beispiel wollen wir die Maximale Zahnlänge (`len`) und die Maximale Dosis (`dose`) für jede Art der Gabe (`supp`) ermitteln.
```{r aggregate cbind}
aggregate(cbind(len, dose) ~ supp, ToothGrowth, max)
```
In diesem Befehl haben wir `cbind` verwendet, um die beiden Spalten `len` und `dose` zusammenzufügen. Der resultierende Data Frame zeigt die Maximalwerte für beide Variablen.
Identisch zu obigem Kommando sind übrigens die folgenden Befehle mit der Nutzung des Punktes in der Formel.
```{r aggregate cbind alternatives, eval = FALSE}
# berechne für alle Variablen außer `supp`
aggregate(. ~ supp, ToothGrowth, max)
# gruppiere nach allen Variablen außer `len` und `dose`
aggregate(cbind(len, dose) ~ ., ToothGrowth, max)
```
Zur Funktion `aggregate` können wir festhalten, dass diese sich insbesondere eignet, wenn wir das Ergebnis als Data Frame erhalten möchten und eine Funktion auf mehrere Variablen anwenden wollen.
### Die Funktion `by`
Die Funktion `by` wird ebenfalls genutzt um eine Funktion auf gruppierte Daten anzuwenden. Die Besonderheit von `by` liegt darin, dass wir die Zeilen eines Data Frames gruppieren können. Somit können wir Funktionen auf mehrere Spalten anwenden.
Als Beispiel wollen wir für unseren Datensatz `ToothGrowth` eine lineare Regression mit der Zahnlänge (`len`) als abhängige Variable und der Art der Gabe als quantitative unabhängige Variable (`supp`) durchführen. Außerdem wollen wir den Datensatz nach der Dosis (`dose`) gruppieren, so dass wir eine Regression für jede Gruppe berechnen. Für eine lineare Regression nutzen wir den Befehl `lm` (*linear model*).
```{r by}
by(ToothGrowth, ToothGrowth$dose,
function(x) lm(len ~ supp, data = x))
```
Wir kennen schon die Syntax von anderen Funktionen: Das erste Argument ist das Objekt, auf das wir die Funktion anwenden wollen. An zweiter Stelle folgt die Gruppierungsvariable. Das dritte Argument ist die Funktion. Diesmal haben wir eine eigene Funktion geschrieben. In dieser Funktion `function(x) lm(len ~ supp, data = x)` ist `x` ein Parameter der jeweils einen Teil der Zeilen des Data Frames `ToothGrowth` enthält. Die Teilung der Zeilen erfolgt nach der Gruppierungsvariable `ToothGrowth$dose`, welche drei verschiedene Werte enthält. Somit wird unsere Funktion auf drei kleinere Data Frames angewendet. In der linearen Regression (`lm`) sagen wir `len` durch `supp` vorher. Das Argument `data = x` sorgt dafür, dass der jeweilige Teil des Data Frames verwendet wird.
Als Ergebnis erhalten wir eine Liste der Klasse `by`. In der Ausgabe sehen wir die Werte der Koeffizienten für die drei Regressionen. Dabei können wir auf einzelne Elemente der Liste zugreifen, um weitere Funktionen auf die Ergebnisse der Regression anzuwenden.
```{r by summary}
ergebnis <- by(ToothGrowth, ToothGrowth$dose,
function(x) lm(len ~ supp, data = x))
summary(ergebnis[[1]])
```
Im obigen Beispiel wird das Ergebnis von `by` dem Objekt `ergebnis` zugewiesen. Das erste Element extrahieren wir mit `ergebnis[[1]]`, worauf die `summary`-Funktion angewendet wird, um eine Zusammenfassung des Ergebnisses mit $t$- und $p$-Werten zu erhalten.