20  Funktionen über Datenreihen anwenden

20.0.1 apply()

Eine wichtige Funktion zur Datenmanipulation ist die Funktion apply() (wie auch ihre Tochterfunktionen lapply(), sapply() und tapply()). Mit ihr können beliebige Funktionen auf die Reihen und/oder Spalten eines Datenframes (oder einer Matrix) angewendet werden. Dadurch erlaubt sie es in vielen Fällen, Loop-Schleifen und Redundanzen zu vermeiden.

Zur Verdeutlichung sei folgendes Beispiel gegeben:

# erzeuge Testdatensatz
df <- data.frame(Name = c("Hans", "Gerda", "Kurt", "Maria"),
                 Alter = c(56, 59, 58, 58),
                 Groesse = c(178, 172, 181, 166),
                 Gewicht = c(105, 67, 95, 89))
# anzeigen
df
   Name Alter Groesse Gewicht
1  Hans    56     178     105
2 Gerda    59     172      67
3  Kurt    58     181      95
4 Maria    58     166      89

Angenommen, wir wollen die Mittelwerte der drei numerischen Variablen bestimmen, dann könnten wir so vorgehen:

mean(df$Alter)
[1] 57.75
mean(df$Groesse)
[1] 174.25
mean(df$Gewicht)
[1] 89

Die Funktion apply() erlaubt es, die Werte in nur einem Aufruf zu erzeugen, indem die Funktion mean() auf die Werte jeder Spalte angewendet wird.

apply(df[,-1], MARGIN=2, FUN=mean)
  Alter Groesse Gewicht 
  57.75  174.25   89.00 

Wir übergeben der Funktion zunächst das Datenframe df ohne die Spalte Name (df[,-1]). Mit dem Parameter MARGIN wird festgelegt, wie apply() auf das Datenframe schauen soll:

  • MARGIN=1 - Reihe für Reihe
  • MARGIN=2 - Spalte für Spalte
  • MARGIN=3 - beides zusammen

Über den Parameter FUN wird die zu verwendende Funktion (in diesem Falle mean()) ohne Klammern angegeben.

Möchten wir nun die Standardabweichung der drei Variablen berechnen, lautet der Aufruf entsprechend:

apply(df[,-1], MARGIN=2, FUN=sd)
    Alter   Groesse   Gewicht 
 1.258306  6.652067 16.083117 

Es ist wichtig, die nicht-numerische Variable Name vor dem apply()-Aufruf herauszufiltern. Denn wenn die zu verwendende Funktion bei nur einer Reihe fehl schlägt, liefert apply() nur eine Reihe NAs und Warnmeldungen zurück.

# aufruf mit Spalte "Name" schlägt fehl
apply(df, MARGIN=2, FUN=mean)
Warning in mean.default(newX[, i], ...): Argument ist weder numerisch noch
boolesch: gebe NA zurück

Warning in mean.default(newX[, i], ...): Argument ist weder numerisch noch
boolesch: gebe NA zurück

Warning in mean.default(newX[, i], ...): Argument ist weder numerisch noch
boolesch: gebe NA zurück

Warning in mean.default(newX[, i], ...): Argument ist weder numerisch noch
boolesch: gebe NA zurück
   Name   Alter Groesse Gewicht 
     NA      NA      NA      NA 

Möchten wir die Werte der Probanden beispielsweise aufaddieren, lassen wir per apply() die Funktion sum() reihenweise über die Daten laufen. Hierzu setzen wir den Parameter MARGIN=1.

# laufe pro-Reihe über die Daten
apply(df[,-1], MARGIN=1, FUN=sum)
[1] 339 298 334 313

Wir können jede Funktion per apply() auf die Variablen loslassen:

# Wurzel aus jedem Wert ziehen
apply(df[,-1], MARGIN=2, sqrt)
        Alter  Groesse   Gewicht
[1,] 7.483315 13.34166 10.246951
[2,] 7.681146 13.11488  8.185353
[3,] 7.615773 13.45362  9.746794
[4,] 7.615773 12.88410  9.433981

Da wir wissen, dass der Parameter MARGIN an zweiter Stelle kommt, müssen wir ihn dort nicht immer ausschreiben, sondern können einfach 1, 2 oder 3 schreiben.

# Häufigkeitstabelle für jede Variable
# MARGIN=2 muss nicht ausgeschrieben werden
apply(df[,-1], 2, FUN=jgsbook::freqTable)
$Alter
  Wert Haeufig Hkum Relativ Rkum
1   56       1    1      25   25
2   58       2    3      50   75
3   59       1    4      25  100

$Groesse
  Wert Haeufig Hkum Relativ Rkum
1  166       1    1      25   25
2  172       1    2      25   50
3  178       1    3      25   75
4  181       1    4      25  100

$Gewicht
  Wert Haeufig Hkum Relativ Rkum
1   67       1    1      25   25
2   89       1    2      25   50
3   95       1    3      25   75
4  105       1    4      25  100

20.0.2 Funktionsparameter spezifizieren

Parameter für die auszuführende Funktion können einfach mit einem Komma angehängt werden.

# Führen quantile(x, type=6) aus
apply(df[,-1], MARGIN=2, FUN=quantile, type=6)
     Alter Groesse Gewicht
0%   56.00  166.00    67.0
25%  56.50  167.50    72.5
50%  58.00  175.00    92.0
75%  58.75  180.25   102.5
100% 59.00  181.00   105.0

Das funktioniert auch mit mehreren Parametern.

# Führen quantile(x, type=6) aus
apply(df[,-1], MARGIN=2, FUN=quantile, type=6, probs=c(0.1, 0.6, 0.8))
    Alter Groesse Gewicht
10%    56     166      67
60%    58     178      95
80%    59     181     105

Es ist möglich, die Parameter, welche an die auszuführende Funktion übergeben werden müssen, aus dem Datenframe selbst zu ziehen. Erzeugen wir zur Verdeutlichung folgendes Datenframe:

# Testdaten
df <- data.frame(Name = c("Jerome", "Nadine", "Dominik", "Ella"),
                 Geschwindigkeit = c(120, 25, 32, 80),
                 Strecke = c(200, 34, 50, 100))
# anzeigen
df
     Name Geschwindigkeit Strecke
1  Jerome             120     200
2  Nadine              25      34
3 Dominik              32      50
4    Ella              80     100

Jetzt programmieren wir die Funktion zeit(), welche ausrechnet, wie lange die Personen für die Strecke mit der angegebenen Geschwindigkeit benötigt haben. Die Funktion nimmt die Werte velo (Geschwindigkeit) und dist (Strecke) entgegen.

# Funktion programmieren
zeit <- function(velo, dist){
  # rechne Distanz / Geschwindigkeit
  # runde auf 2 Stellen
  zeit <- round(dist/velo, 2)
  return(zeit)
}

Um die Funktion zeit() auf jede Reihe des Datenframes anzuwenden, wobei die Werte von Geschwindigkeit und Strecke jeweils als Parameter übergeben werden, müssen wir zunächst per MARGIN=1 angeben, dass apply() die Daten Reihe-für-Reihe verarbeiten soll.

Der Trick besteht darin, im Paramter FUN eine temporäre Funktion function(line) anzugeben. Diese ruft ihrerseits die Funktion zeit() (diesmal mit Klammern) auf, und übergibt die Parameter per line("SPALTENNAME").

# Übergebe die Werte an die Funktion zeit()
apply(df[,-1], MARGIN=1, function(line) zeit(line["Geschwindigkeit"], 
                                             line["Strecke"]))
[1] 1.67 1.36 1.56 1.25

Wenn die Parameter dist und velo mit ihrem Namen angegeben werden, ist die Reihenfolge der Parameter egal.

# Parameter dist und velo direkt angeben
apply(df[,-1], MARGIN=1, FUN=function(line) zeit(dist=line["Strecke"], 
                                                 velo=line["Geschwindigkeit"]))
[1] 1.67 1.36 1.56 1.25

Als temporäre Hilfsfunktionen können verschiedene Arten verwendet werden:

  • function(x) - übergibt jeden Wert einzeln und nacheinander jeweils als Parameter an die Funktion, wobei x für spezifische Parameter referenziert werden kann (siehe Abschnitt 20.0.3 für ein Beispiel).
  • function(line) - übergibt die Werte der gesamte Datenreihe als Parameter an die Funktion, wobei die einzelnen Variablenwerte per line["SPALTENNAME"] referenziert werden können.

In einem weiteren Beispiel wollen wir Fallzahlen für spezifische Werte von e, P und dem Konfidenzlevel berechnen (siehe Abschnitt 32.11.2). Hierzu nutzen wir die Funktion samplingbook::sample.size.prop(), wobei die Erweiterung samplingbook::sample.size.prop()$n nur die Fallzahlen zurückgibt.

# beispielhafter Funktionsaufruf
samplingbook::sample.size.prop(e=0.05, P=0.65, level=0.95, N=1500)$n
[1] 284
# Erzeuge Kombinationen von e, P und level
df <- data.frame(e = c(0.05, 0.04, 0.03),
                P = c(0.65, 0.6, 0.5),
                level = c(0.9, 0.95, 0.99))
# anzeigen
df
     e    P level
1 0.05 0.65  0.90
2 0.04 0.60  0.95
3 0.03 0.50  0.99
# Berechne Fallzahlen für jede Reihe (Kombination)
apply(df, 1, function(line) samplingbook::sample.size.prop(e = line["e"], 
                                                           P = line["P"], 
                                                           level = line["level"])$n
      )
[1]  247  577 1844

20.0.3 sapply()

Die Tochterfunktion sapply() kann auf Vektoren angewendet werden.

# erzeuge einen Vektor mit 3 Werten für N
N <- c(10, 30, 100)

# ziehe für jeden Wert die Wurzel
sapply(N, sqrt)
[1]  3.162278  5.477226 10.000000

Wenn die verwendete Funktion Wertereihen zurückgibt, wird das Ergebnis als Liste zurückgegeben.

# erzeuge jewils N Zufallszahlen
sapply(N, sample)
[[1]]
 [1]  2  4 10  9  5  1  7  8  3  6

[[2]]
 [1]  7 20 21 24 30 11  4 28  3  6  9 14 29  2  5 25  8  1 17 18 12 10 19 26 13
[26] 22 23 15 16 27

[[3]]
  [1]  75  89  14  90  29  67  30  19   5  31  53  57  59  33  69  64  18  36
 [19]  49  51  96  40  50  84   7  87  32   4  11  98  13  92  55  93  85  56
 [37]  37  41  42   2  88  46  20  15  76  95  97  48  12  63  71  28  60  17
 [55]  22  80  54  44  83  65  77  78  24  10  16  26  72  25  27  68  39  91
 [73]   1  35  66   8  43 100   9  62  74   6  99  23  73  81  70  94  47  86
 [91]  82  45  38  34  61   3  79  52  58  21

Da die Funktion sample() jeweils einen Datenvektor zurückgibt, liegt das Ergebnis als Liste vor.

Auch bei sapply() werden Funktionsparameter per Komma angehängt.

# erzeuge jewils N Zufallszahlen
# mit doppelten
sapply(N, sample, replace=TRUE)
[[1]]
 [1] 10  3  6  4  6  8  8  7  5  4

[[2]]
 [1] 15  2 27 24  9 13 21 22 17 17 15 28 28  6 27 27 17 20  2  6  1 11 27 28  5
[26] 17  1 11  6 22

[[3]]
  [1]  22  74  72   6  32  46  53  61  60   5  80  88  47  20   5  32  43   2
 [19]  44  94  73  15  11  86  93  62  99  24   6  15  20  24  36  19  72  49
 [37]  67  26   1  52  11  85  56  11  70  20  16  90  83  22  68  76  78  22
 [55]   8   1  88  52  73  77  78  22   3  14  57  42  19  69  83  39  38  67
 [73]  99  64  56  26  53  65  58   6  57  15  63 100  33  92  57  64  53  60
 [91]  94  23  71  46  29  25  85  46   5  79

Sollen die Werte des Ausgangsvektors als spezifischer Parameter der auszuführenden Funktion übergeben werden, muss wieder auf eine temporäre Hilfsfunktion function(x) zurückgegriffen werden. Im folgenden Aufruf sollen die Werte des Vektors N jeweils als Paramenter N an die Funktion samplingbook::sample.size.mean() übergeben werden.

# Rufe für jeden Wert x im Vektor N die Funktion auf, 
# wobei Parameter N=x ist
sapply(N, FUN=function(x) samplingbook::sample.size.mean(e=0.05, 
                                                         S=0.3, 
                                                         level=0.90, 
                                                         N=x)
       )
     [,1]   [,2]   [,3]  
call list,4 list,4 list,4
n    10     23     50    

Per samplingbook::sample.size.mean()$n wird nur die benötigte Fallzahl zurückgegeben. Diese Anweisung können wir auch auf sapply() übertragen:

# mit $n nur Fallzahlen
sapply(N, FUN=function(x) samplingbook::sample.size.mean(e=0.05, 
                                                         S=0.3, 
                                                         level=0.90, 
                                                         N=x)$n
       )
[1] 10 23 50

20.0.4 lapply()

Die Funktion lapply() liefert ihr Ergebnis immer als Liste zurück, wobei für jedes x ein eigener Listeneintrag erzeugt wird.

# erzeuge einen Vektor mit 3 Werten für N
N <- c(10, 30, 100)

# ziehe für jeden Wert die Wurzel
lapply(N, sqrt)
[[1]]
[1] 3.162278

[[2]]
[1] 5.477226

[[3]]
[1] 10

Mit der Funktion unlist() kann die Liste in Vektoren überführt werden.

unlist(lapply(N, sqrt))
[1]  3.162278  5.477226 10.000000

20.0.5 tapply()

Mit der Tochterfunktion tapply() lassen sich gruppierte Auswertungen erzeugen. Die Gruppierung erfolgt anhand eines Factors im Datenframe.

# erzeuge Testdaten
df <- data.frame(Geschlecht = factor(c("m", "w", "m", "m", "w", "w", "d", "d", "d")),
                 Alter = c(45, 34, 46, 41, 31, 29, 24, 25, 26))
# anzeigen
df
  Geschlecht Alter
1          m    45
2          w    34
3          m    46
4          m    41
5          w    31
6          w    29
7          d    24
8          d    25
9          d    26

Angenommen, wir möchten nun die Mittelwerte des Alters in Abhängigkeit zum Geschlecht bestimmen, so können wir tapply() verwenden.

tapply(df$Alter, df$Geschlecht, mean)
       d        m        w 
25.00000 44.00000 31.33333 

Wie bei den Schwesterfunktionen lassen sich weitere Parameter per Komma übergeben.

# Berecne IQR so wie SPSS (type=6)
tapply(df$Alter, df$Geschlecht, IQR, type=6)
d m w 
2 5 5