6 Daten manipulieren mit dplyr (Fortsetzung)

6.1 Grouping & Summarising

Laden Sie die folgenden Libraries und Data Frames:

library(tidyverse)
library(magrittr)
url <- "http://www.phonetik.uni-muenchen.de/~jmh/lehre/Rdf"
int <- read.table(file.path(url, "intdauer.txt"))
coronal <- read.table(file.path(url, "coronal.txt"))
vdata <- read.table(file.path(url, "vdata.txt"))

In Kapitel 5 haben wir summary statistics für F1-Werte aus dem Data Frame vdata berechnet. Natürlich geht das auch innerhalb der tidyverse-Syntax, nämlich mit der Funktion summarise() aus dem Package dplyr. Diese Funktion verändert den Data Frame grundlegend, denn die ursprünglichen Daten werden zu neuen Werten zusammengefasst. Dies betrifft sowohl die Anzahl der Spalten als auch der Anzahl der Zeilen. summarise() erstellt neue Spalten und keine der originalen Spalten werden beibehalten. Die Funktion bekommt als Argument also den/die neuen Spaltennamen und wie die Werte in dieser neuen Spalte berechnet werden sollen:

vdata %>% summarise(mittelwert = mean(F1))
##   mittelwert
## 1      407.3

Der Output dieser Pipe ist ein Data Frame mit nur einer Spalte und einer Zeile. Wir können aber auch mehrere deskriptive Werte gleichzeitig berechnen und erhalten dadurch mehr Spalten:

vdata %>% summarise(mittelwert = mean(F1),
                    std_abw = sd(F1),
                    summe = sum(F1),
                    maximum = max(F1),
                    Q1 = quantile(F1, 0.25))
##   mittelwert std_abw   summe maximum  Q1
## 1      407.3   145.8 1214532    1114 300

Die Funktionen mutate() und summarise() haben also gemein, dass sie neue Spalten erstellen; während in mutate() aber alle ursprünglichen Zeilen und Spalten erhalten bleiben, erstellt summarise() einen ganz neuen Data Frame mit deutlich weniger Zeilen als ursprünglich vorhanden waren (denn hier wurden Werte zusammengefasst).

Was würden Sie jetzt tun, wenn Sie den F1-Mittelwert für nur einen bestimmten Vokal V aus dem Data Frame berechnen wollen? Vermutlich würden Sie dies wie folgt lösen (für den Vokal V == "E"):

vdata %>% 
  filter(V == "E") %>% 
  summarise(mittelwert = mean(F1))
##   mittelwert
## 1      426.2

Der F1-Mittelwert für “E” ist also ca. 426 Hz. Wenn Sie sich für die vokalspezifischen F1-Mittelwerte interessieren, dann ist es nicht mehr sinnvoll, für jeden einzelnen Vokal den obigen Code zu benutzen. Stattdessen gibt es die Funktion group_by(). group_by() bekommt als Argumente alle Spalten, nach denen gruppiert werden soll. summarise() berechnet die gewünschten summary statistics anschließend pro Gruppe. In unserem Beispiel gruppieren wir nach Vokal und berechnen dann den Mittelwert pro Vokal:

vdata %>% 
  group_by(V) %>% 
  summarise(mittelwert = mean(F1))
## # A tibble: 7 × 2
##   V     mittelwert
##   <chr>      <dbl>
## 1 %           424.
## 2 A           645.
## 3 E           426.
## 4 I           311.
## 5 O           434.
## 6 U           304.
## 7 Y           302.

Es wurden zwei Spalten erstellt: Die eine enthält die sieben verschiedenen Vokale aus dem originalen Data Frame, die andere die vokalspezifischen F1-Mittelwerte. Sie können natürlich auch nach mehr als einer Spalte gruppieren. Es ist zum Beispiel anzunehmen, dass sich der mittlere F1 nicht nur von Vokal zu Vokal unterscheidet, sondern dass auch der Gespanntheitsgrad Tense einen Einfluss hat. Deshalb gruppieren wir nach Vokal und Gespanntheitsgrad und berechnen dann den mittleren F1:

vdata %>% 
  group_by(V, Tense) %>% 
  summarise(mittelwert = mean(F1))
## `summarise()` has grouped output by 'V'. You can
## override using the `.groups` argument.
## # A tibble: 14 × 3
## # Groups:   V [7]
##    V     Tense mittelwert
##    <chr> <chr>      <dbl>
##  1 %     +           368.
##  2 %     -           479.
##  3 A     +           668.
##  4 A     -           622.
##  5 E     +           363.
##  6 E     -           488.
##  7 I     +           276.
##  8 I     -           346.
##  9 O     +           348.
## 10 O     -           520.
## 11 U     +           259.
## 12 U     -           348.
## 13 Y     +           266.
## 14 Y     -           338.

Wir sehen jetzt also den F1-Mittelwert für nicht gespannte “%”, gespannte “%” (ignorieren Sie die seltsame Vokal-Kodierung), nicht gespannte “A”, gespannte “A”, usw.

Weiterführende Infos: summarise() warning

Oben sehen Sie eine Warnmeldung, die von summarise() geworfen wurde. Warnmeldungen sind dazu da, Sie auf etwas aufmerksam zu machen – Sie sollten sie also nicht ignorieren. Diese Warnmeldung zeigt erstmal an, dass das Ergebnis des Codes ein gruppierter Data Frame ist (Objektklasse grouped_df) und dass die Gruppierungsvariable V ist:

vdata %>% 
  group_by(V, Tense) %>% 
  summarise(mittelwert = mean(F1)) %>% 
  class()
## `summarise()` has grouped output by 'V'. You can
## override using the `.groups` argument.
## [1] "grouped_df" "tbl_df"     "tbl"        "data.frame"

Die Warnmeldung zeigt außerdem, dass man die Gruppierung des Ergebnisses auch verändern kann, indem man das summarise()-Argument .groups verwendet. Dieses Argument kann verschiedene Werte annehmen, wie Sie auf der Hilfsseite der Funktion summarise() nachlesen können.

Bei den vorherigen Code Snippets, bei denen wir group_by() im Zusammenspiel mit summarise() verwendet haben, ist die Warnmeldung übrigens deshalb nicht aufgetaucht, weil wir nur nach einer Variable gruppiert haben; im Ergebnis wird diese Gruppierung automatisch aufgehoben.

Es ist wichtig zu verstehen, dass nur nach kategorialen Spalten gruppiert werden kann. Es ergibt keinen Sinn, nach nicht-kategorialen numerischen Spalten zu gruppieren, denn hier gibt es keine Gruppen (jeder Wert ist vermutlich einzigartig). Der Sinn von summarise() ist es aber ja gerade, deskriptive Statistiken für kategoriale Gruppen zu berechnen.

Zuletzt wollen wir noch die Funktionen n() und n_distinct() vorstellen. n() benötigt keine Argumente und wird nach group_by() innerhalb summarise() verwendet, um die Anzahl an Beobachtungen (Zeilen) pro Gruppe zurückzugeben. n_distinct() bekommt als Argument den Namen einer Spalte und findet heraus, wie viele unterschiedliche (einzigartige) Werte einer Variable es pro Gruppe gibt.

# Anzahl an Zeilen für jede Kombination von V und Tense
vdata %>% 
  group_by(V, Tense) %>% 
  summarise(count = n())
## `summarise()` has grouped output by 'V'. You can
## override using the `.groups` argument.
## # A tibble: 14 × 3
## # Groups:   V [7]
##    V     Tense count
##    <chr> <chr> <int>
##  1 %     +       212
##  2 %     -       214
##  3 A     +       214
##  4 A     -       218
##  5 E     +       210
##  6 E     -       215
##  7 I     +       210
##  8 I     -       214
##  9 O     +       214
## 10 O     -       214
## 11 U     +       208
## 12 U     -       215
## 13 Y     +       211
## 14 Y     -       213
# Anzahl der einzigartigen Sprecher pro Region und sozialer Klasse
coronal %>% 
  group_by(Region, Socialclass) %>% 
  summarise(count = n_distinct(Vpn))
## `summarise()` has grouped output by 'Region'. You can
## override using the `.groups` argument.
## # A tibble: 9 × 3
## # Groups:   Region [3]
##   Region Socialclass count
##   <chr>  <chr>       <int>
## 1 R1     LM             40
## 2 R1     UM             30
## 3 R1     W              11
## 4 R2     LM             26
## 5 R2     UM             18
## 6 R2     W              36
## 7 R3     LM             22
## 8 R3     UM             31
## 9 R3     W              26

Weiterführende Infos: Funktionen eindeutig beschreiben

Da die Funktionen aus dem tidyverse, insbesonderen aus dplyr, sehr gängige Namen haben (filter(), summarise(), rename()), werden sie leicht von Funktionen mit demselben Namen aus anderen Paketen maskiert. Wenn Sie also von einer dieser Funktionen einen Fehler bekommen, laden Sie entweder noch einmal das Paket, aus dem die Funktion stammen soll (z.B. library(dplyr)), oder nutzen Sie die folgende Schreibweise: dplyr::filter().

6.2 Arranging

In der alltäglichen Arbeit mit Data Frames kann es sinnvoll sein, den Data Frame nach Zeilen oder Spalten zu ordnen. Für das Ordnen der Zeilen wird arrange() benutzt, für das Ordnen der Spalten relocate(). Hier ordnen wir den Data Frame int aufsteigend nach Dauer:

int %>% arrange(Dauer)
##    Vpn    dB Dauer
## 10  S2 25.60    55
## 5   S1 23.47    67
## 7   S2 30.08    81
## 14  S1 20.88   103
## 9   S1 21.37   116
## 2   S2 32.54   120
## 4   S2 28.38   131
## 13  S1 26.60   144
## 1   S1 24.50   162
## 6   S2 37.82   169
## 8   S1 24.50   192
## 15  S2 26.05   212
## 3   S2 38.02   223
## 12  S1 44.27   232
## 11  S1 40.20   252

arrange() kann auch alphabetisch oder nach mehreren Spalten ordnen:

int %>% arrange(Vpn, Dauer)
##    Vpn    dB Dauer
## 5   S1 23.47    67
## 14  S1 20.88   103
## 9   S1 21.37   116
## 13  S1 26.60   144
## 1   S1 24.50   162
## 8   S1 24.50   192
## 12  S1 44.27   232
## 11  S1 40.20   252
## 10  S2 25.60    55
## 7   S2 30.08    81
## 2   S2 32.54   120
## 4   S2 28.38   131
## 6   S2 37.82   169
## 15  S2 26.05   212
## 3   S2 38.02   223

Um absteigend zu ordnen, wird desc() (descending) innerhalb von arrange() genutzt:

int %>% arrange(Vpn, desc(Dauer))
##    Vpn    dB Dauer
## 11  S1 40.20   252
## 12  S1 44.27   232
## 8   S1 24.50   192
## 1   S1 24.50   162
## 13  S1 26.60   144
## 9   S1 21.37   116
## 14  S1 20.88   103
## 5   S1 23.47    67
## 3   S2 38.02   223
## 15  S2 26.05   212
## 6   S2 37.82   169
## 4   S2 28.38   131
## 2   S2 32.54   120
## 7   S2 30.08    81
## 10  S2 25.60    55

relocate() bekommt als Argumente die Namen aller Spalten, die umsortiert werden sollen. Wenn sonst keine weiteren Argumente angegeben werden, werden die Spalten an den Anfang des Data Frames gesetzt. Ansonsten können die Argumente .before und .after verwendet werden, um anzugeben, vor oder nach welche Spalten die anderen Spalten gesetzt werden sollen:

vdata %>% slice(1)
##       X    Y  F1  F2   dur V Tense Cons Rate Subj
## 1 52.99 4.36 313 966 106.9 %     -    P    a   bk
vdata %>% relocate(Subj) %>% slice(1)
##   Subj     X    Y  F1  F2   dur V Tense Cons Rate
## 1   bk 52.99 4.36 313 966 106.9 %     -    P    a
vdata %>% relocate(Subj, Cons) %>% slice(1)
##   Subj Cons     X    Y  F1  F2   dur V Tense Rate
## 1   bk    P 52.99 4.36 313 966 106.9 %     -    a
vdata %>% relocate(where(is.numeric), .after = Subj) %>% slice(1)
##   V Tense Cons Rate Subj     X    Y  F1  F2   dur
## 1 %     -    P    a   bk 52.99 4.36 313 966 106.9
vdata %>% relocate(where(is.character), .before = dur) %>% slice(1)
##       X    Y  F1  F2 V Tense Cons Rate Subj   dur
## 1 52.99 4.36 313 966 %     -    P    a   bk 106.9