1 Packages und Daten laden

Starten Sie das R Projekt, das Sie für diesen Kurs angelegt haben. Öffnen Sie hierfür RStudio und benutzen Sie die Schaltfläche oben rechts oder navigieren Sie zu Ihrem Kursverzeichnis und klicken Sie auf die .Rproj Datei.

Laden Sie dann die folgenden Packages:

library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.1 ──
## ✓ ggplot2 3.3.5     ✓ purrr   0.3.4
## ✓ tibble  3.1.4     ✓ dplyr   1.0.7
## ✓ tidyr   1.1.3     ✓ stringr 1.4.0
## ✓ readr   2.0.1     ✓ forcats 0.5.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
library(magrittr)
## 
## Attaching package: 'magrittr'
## The following object is masked from 'package:purrr':
## 
##     set_names
## The following object is masked from 'package:tidyr':
## 
##     extract
library(gridExtra)
## 
## Attaching package: 'gridExtra'
## The following object is masked from 'package:dplyr':
## 
##     combine

Wir werden diese Woche wieder mit verschiedenen Data Frames arbeiten, die wir vom IPS-Server laden. Die URL legen wir als Variable an und benutzen diese dann in der Funktion file.path().

url <- "http://www.phonetik.uni-muenchen.de/~jmh/lehre/Rdf"
asp <- read.table(file.path(url, "asp.txt"))
coronal <- read.table(file.path(url, "coronal.txt"))

2 Daten aufräumen mit tidyr

tidy datasets are all alike but every messy dataset is messy in its own way” – Hadley Wickham

Hadley Wickham ist der Chef-Entwickler des tidyverse. Die Funktionen des tidyverse sind nicht nur auf Datenmanipulation und -verarbeitung ausgerichtet, sondern auch auf das Aufräumen von Datensätzen.

Ein aufgeräumter Datensatz folgt drei Prinzipien:

Diese Grundsätze scheinen zunächst offensichtlich, werden Ihnen aber in der täglichen Arbeit mit Daten in R immer wieder begegnen. Die drei Prinzipien sollten Sie vor allem aus zwei Gründen befolgen: Erstens ist jeder Datensatz so auf dieselbe konsistente Weise strukturiert, was Ihnen jede Analyse erleichtern wird. Zweitens sind die Funktionen in R, aber insbesondere die Funktionen des tidyverse auf die Arbeit mit Spalten ausgelegt. Das heißt, Sie wenden Funktionen häufig auf einzelne Spalten an. Deshalb ist es sinnvoll, wenn jede Spalte eine Variable enthält. Um dies zu verdeutlichen, zeigen wir Ihnen im Folgenden Beispiele für die zwei häufigsten Arten von messy data.

avokal <- read.table(file.path(url, "avokal.txt"))
avokal
##    schnell langsam Vpn
## 1      430     415  S1
## 2      266     238  S2
## 3      567     390  S3
## 4      531     410  S4
## 5      707     605  S5
## 6      716     609  S6
## 7      651     632  S7
## 8      589     523  S8
## 9      469     411  S9
## 10     723     612 S10
vcv <- read.table(file.path(url, "vcvC.txt"))
vcv %>% head()
##        RT Subject Vowel Lang Left Right Context
## 361 647.5    S209     a   AE    f     h     f.h
## 362 617.0    S209     a   AE    f    sh    f.sh
## 363 728.5    S209     a   AE    f    sh    f.sh
## 364 629.0    S209     a   AE    f    th    f.th
## 365 688.5    S209     a   AE    f    th    f.th
## 366 602.5    S209     a   AE    s     h     s.h

Es ist nicht trivial, einen Data Frame sauber zu strukturieren. Nur ein Beispiel: Sie haben die ersten vier Formanten in Vokalen gemessen. Ist es sinnvoller, die erhobenen Daten in vier Spalten F1, F2, F3, F4 festzuhalten? Oder besser in zwei Spalten Hz (mit den Formantwerten in Hertz) und Formant (mit den Werten 1, 2, 3 oder 4)?

Bevor wir Ihnen zeigen, wie Sie die obigen Datensätze so umformen können, dass sie den drei Prinzipien für aufgeräumte Daten folgen, möchten wir Ihnen noch die tibble vorstellen.

2.1 Tibbles

Die tibble ist eine vereinfachte Form eines Data Frames, die im tidyverse häufig verwendet wird. Wir laden hier einen weiteren Data Frame und formen ihn danach mittels as_tibble() um:

vdata <- read.table(file.path(url, "vdata.txt")) %>% 
  as_tibble()

Wenn wir den Namen der tibble jetzt in die Konsole eingeben, wird nicht wie beim Data Frame üblich der gesamte Datensatz angezeigt, sondern nur die ersten zehn Zeilen. Wir sehen außerdem, aus wie vielen Zeilen und Spalten die tibble besteht und welcher Objektklasse die einzelnen Spalten angehören:

vdata
## # A tibble: 2,982 × 10
##        X     Y    F1    F2   dur V     Tense Cons  Rate  Subj 
##    <dbl> <dbl> <int> <int> <dbl> <chr> <chr> <chr> <chr> <chr>
##  1  53.0  4.36   313   966 107.  %     -     P     a     bk   
##  2  53.6  3.65   322  2058  86.0 I     -     T     a     bk   
##  3  55.1 10.4    336  1186 123.  Y     -     K     a     bk   
##  4  53.1  4.75   693  2149 119.  E     -     T     a     bk   
##  5  52.7  6.46   269  2008 196.  Y     +     K     a     bk   
##  6  53.3  4.7    347   931  77.5 Y     -     P     a     bk   
##  7  54.4  3.6    705  1119 224.  A     +     P     a     bk   
##  8  51.2  7.38   248  2377 145.  I     +     P     a     bk   
##  9  54.6  2.4    385  1935 103   Y     -     T     a     bk   
## 10  58.4  9.17   288   595 244.  O     +     T     a     bk   
## # … with 2,972 more rows

Die tibble hat vorrangig die Klasse tbl_df, aber zusätzlich noch tbl und data.frame. Wir werden deshalb im Folgenden trotzdem noch von Data Frames sprechen, wenn wir genau genommen eine tibble meinen.

vdata %>% class()
## [1] "tbl_df"     "tbl"        "data.frame"

Natürlich können Sie eine tibble auch selbst erstellen, indem Sie die Funktion tibble() anstelle der Funktion data.frame() verwenden, zum Beispiel so:

tibble(x = 1:5, y = 6:10)
## # A tibble: 5 × 2
##       x     y
##   <int> <int>
## 1     1     6
## 2     2     7
## 3     3     8
## 4     4     9
## 5     5    10

Wenn Sie die Import-Funktion read_delim() aus dem tidyverse-Package readr anstelle der read.table() Funktion verwenden, wird der Datensatz automatisch als tibble geladen:

int <- read_delim(file.path(url, "intdauer.txt"), 
                  delim = " ", 
                  col_names = c("idx", "Vpn", "dB", "Dauer"), 
                  skip = 1)
## Rows: 15 Columns: 4
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: " "
## chr (1): Vpn
## dbl (3): idx, dB, Dauer
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
int
## # A tibble: 15 × 4
##      idx Vpn      dB Dauer
##    <dbl> <chr> <dbl> <dbl>
##  1     1 S1     24.5   162
##  2     2 S2     32.5   120
##  3     3 S2     38.0   223
##  4     4 S2     28.4   131
##  5     5 S1     23.5    67
##  6     6 S2     37.8   169
##  7     7 S2     30.1    81
##  8     8 S1     24.5   192
##  9     9 S1     21.4   116
## 10    10 S2     25.6    55
## 11    11 S1     40.2   252
## 12    12 S1     44.3   232
## 13    13 S1     26.6   144
## 14    14 S1     20.9   103
## 15    15 S2     26.0   212

read_delim() gibt Ihnen auch die Objektklasse für jede Spalte in der Column specification aus. Die Import-Funktionen aus readr sind etwas empfindlicher als die Standard-Funktionen in R. Hier mussten wir zum Beispiel einige Argumente spezifieren (delim, col_names und skip), um damit umzugehen, dass der Data Frame Zeilenindizes enthält. Die Standard-Funktion read.table() kommt hingegen meist damit klar, dass man nur den Pfad zum Datensatz angibt.

Weiterführende Infos: Tibbles und readr

Wenn Sie mehr über die tibble erfahren möchten, empfehlen wir Kapitel 10 aus R for Data Science.

Das Paket readr stellt noch weitere Funktionen zum Laden und Abspeichern von Datensätzen bereit, je nachdem wie die Spalten getrennt werden:

  • read_csv(): comma separated values, d.h. durch Komma getrennte Spalten
  • read_csv2(): durch Semikolon getrennte Spalten
  • read_tsv(): durch Tab getrennte Spalten
  • read_delim(): für alle Trennzeichen geeignet

All dies und mehr finden Sie auch in Kapitel 11 aus R for Data Science.

2.2 Pivoting

Wenn wir unsere Daten geladen haben, fangen wir mit dem Aufräumen an. Das Ziel ist entweder, dass die Daten so strukturiert sind, dass jede Zeile eine neue Beobachtung enthält und dass die Spalten jeweils eine Variable enthalten. Das Ziel kann auch sein, dass die Daten so strukturiert sind, dass sie unserem Zweck dienen (z.B. dem Erstellen einer Abbildung). tidyr nennt diesen Prozess pivoting und es gibt eine gut erklärte Vignette zu dem Thema:

vignette("pivot")

Oben haben wir den Data Frame avokal geladen und haben festgestellt, dass die Spalten schnell und langsam eigentlich zwei Ausprägungen der Variable Tempo sind. Das heißt, es wäre besser, eine Spalte namens Tempo (Werte: “schnell”, “langsam”) und eine namens Dauer (Werte aus schnell und langsam in Millisekunden) zu haben:

avokal %>% 
  pivot_longer(cols = c(schnell, langsam), 
               names_to = "Tempo", 
               values_to = "Dauer")
## # A tibble: 20 × 3
##    Vpn   Tempo   Dauer
##    <chr> <chr>   <int>
##  1 S1    schnell   430
##  2 S1    langsam   415
##  3 S2    schnell   266
##  4 S2    langsam   238
##  5 S3    schnell   567
##  6 S3    langsam   390
##  7 S4    schnell   531
##  8 S4    langsam   410
##  9 S5    schnell   707
## 10 S5    langsam   605
## 11 S6    schnell   716
## 12 S6    langsam   609
## 13 S7    schnell   651
## 14 S7    langsam   632
## 15 S8    schnell   589
## 16 S8    langsam   523
## 17 S9    schnell   469
## 18 S9    langsam   411
## 19 S10   schnell   723
## 20 S10   langsam   612

Der Befehl pivot_longer() wandelt den Data Frame in das so genannte “lange Format” um. Am wichtigsten sind die folgenden drei Argumente:

  • cols: alle Spalten, die umwandelt werden sollen
  • values_to: der Name der neuen Spalte mit den Werten
  • names_to: der Name der neuen Spalte mit den ehemaligen Spaltennamen

Die Pivoting-Funktionen von tidyr sind sehr mächtig und können auch deutlich kompliziertere Operationen durchführen. Nehmen wir den Data Frame billboard, der automatisch mit dem tidyverse geladen wird und die Rankings der Billboard Charts aus dem Jahr 2000 enthält:

billboard
## # A tibble: 317 × 79
##    artist   track   date.entered   wk1   wk2   wk3   wk4   wk5   wk6   wk7   wk8
##    <chr>    <chr>   <date>       <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
##  1 2 Pac    Baby D… 2000-02-26      87    82    72    77    87    94    99    NA
##  2 2Ge+her  The Ha… 2000-09-02      91    87    92    NA    NA    NA    NA    NA
##  3 3 Doors… Krypto… 2000-04-08      81    70    68    67    66    57    54    53
##  4 3 Doors… Loser   2000-10-21      76    76    72    69    67    65    55    59
##  5 504 Boyz Wobble… 2000-04-15      57    34    25    17    17    31    36    49
##  6 98^0     Give M… 2000-08-19      51    39    34    26    26    19     2     2
##  7 A*Teens  Dancin… 2000-07-08      97    97    96    95   100    NA    NA    NA
##  8 Aaliyah  I Don'… 2000-01-29      84    62    51    41    38    35    35    38
##  9 Aaliyah  Try Ag… 2000-03-18      59    53    38    28    21    18    16    14
## 10 Adams, … Open M… 2000-08-26      76    76    74    69    68    67    61    58
## # … with 307 more rows, and 68 more variables: wk9 <dbl>, wk10 <dbl>,
## #   wk11 <dbl>, wk12 <dbl>, wk13 <dbl>, wk14 <dbl>, wk15 <dbl>, wk16 <dbl>,
## #   wk17 <dbl>, wk18 <dbl>, wk19 <dbl>, wk20 <dbl>, wk21 <dbl>, wk22 <dbl>,
## #   wk23 <dbl>, wk24 <dbl>, wk25 <dbl>, wk26 <dbl>, wk27 <dbl>, wk28 <dbl>,
## #   wk29 <dbl>, wk30 <dbl>, wk31 <dbl>, wk32 <dbl>, wk33 <dbl>, wk34 <dbl>,
## #   wk35 <dbl>, wk36 <dbl>, wk37 <dbl>, wk38 <dbl>, wk39 <dbl>, wk40 <dbl>,
## #   wk41 <dbl>, wk42 <dbl>, wk43 <dbl>, wk44 <dbl>, wk45 <dbl>, wk46 <dbl>, …

Auch hier ist es der Fall, dass die Spalten wk1, wk2, wk3 usw. eigentlich Levels oder Ausprägungen der Variable week sind. Wir möchten also eine Spalte erstellen, die uns die Woche angibt, und eine Spalte, die den Rank in den Billboard Charts enthält. Um dies zu erreichen, nehmen wir alle Spalten, die mit “wk” beginnen, packen die Spaltennamen in eine neue Spalte namens week und die Werte aus den Spalten in die neue Spalte rank. Das Präfix “wk” aus den alten Spaltennamen können wir durch das Argument names_prefix ablegen. Und zuletzt löschen wir alle NA-Werte (NA steht für not available) – es gibt z.B. keine Zeile für Woche 8 für 2Pac’s “Baby Don’t Cry”, weil der Song in der achten Kalenderwoche nicht in den Top 100 gerankt wurde.

billboard %>% 
  pivot_longer(cols = starts_with("wk"), 
               names_to = "week", 
               values_to = "rank",
               names_prefix = "wk",
               values_drop_na = TRUE)
## # A tibble: 5,307 × 5
##    artist  track                   date.entered week   rank
##    <chr>   <chr>                   <date>       <chr> <dbl>
##  1 2 Pac   Baby Don't Cry (Keep... 2000-02-26   1        87
##  2 2 Pac   Baby Don't Cry (Keep... 2000-02-26   2        82
##  3 2 Pac   Baby Don't Cry (Keep... 2000-02-26   3        72
##  4 2 Pac   Baby Don't Cry (Keep... 2000-02-26   4        77
##  5 2 Pac   Baby Don't Cry (Keep... 2000-02-26   5        87
##  6 2 Pac   Baby Don't Cry (Keep... 2000-02-26   6        94
##  7 2 Pac   Baby Don't Cry (Keep... 2000-02-26   7        99
##  8 2Ge+her The Hardest Part Of ... 2000-09-02   1        91
##  9 2Ge+her The Hardest Part Of ... 2000-09-02   2        87
## 10 2Ge+her The Hardest Part Of ... 2000-09-02   3        92
## # … with 5,297 more rows

Es gibt noch ein Gegenstück zu pivot_longer(), nämlich pivot_wider(). Diese Funktion wird deutlich seltener gebraucht, und bekommt vor allem die Argumente:

  • names_from: die Spalte, deren einzigartige Werte als neue Spalten benutzt werden sollen
  • values_from: die Spalte, deren Werte die neuen Spalten füllen sollen

Ein Beispiel dafür kann aus dem Data Frame us_rent_income abgeleitet werden, der ebenfalls mit dem tidyverse geladen wird:

us_rent_income
## # A tibble: 104 × 5
##    GEOID NAME       variable estimate   moe
##    <chr> <chr>      <chr>       <dbl> <dbl>
##  1 01    Alabama    income      24476   136
##  2 01    Alabama    rent          747     3
##  3 02    Alaska     income      32940   508
##  4 02    Alaska     rent         1200    13
##  5 04    Arizona    income      27517   148
##  6 04    Arizona    rent          972     4
##  7 05    Arkansas   income      23789   165
##  8 05    Arkansas   rent          709     5
##  9 06    California income      29454   109
## 10 06    California rent         1358     3
## # … with 94 more rows

Wir möchten eine Spalte income und eine Spalte rent aus den Levels der Spalte variable erstellen und die neuen Spalten dann mit den Werten aus estimate füllen.

us_rent_income %>% 
  pivot_wider(names_from = variable,
              values_from = estimate)
## # A tibble: 104 × 5
##    GEOID NAME         moe income  rent
##    <chr> <chr>      <dbl>  <dbl> <dbl>
##  1 01    Alabama      136  24476    NA
##  2 01    Alabama        3     NA   747
##  3 02    Alaska       508  32940    NA
##  4 02    Alaska        13     NA  1200
##  5 04    Arizona      148  27517    NA
##  6 04    Arizona        4     NA   972
##  7 05    Arkansas     165  23789    NA
##  8 05    Arkansas       5     NA   709
##  9 06    California   109  29454    NA
## 10 06    California     3     NA  1358
## # … with 94 more rows

Im Ergebnis sehen wir jetzt aber einige NA-Werte. Diese können wir zum Beispiel durch Nullen ersetzen, indem wir zusätzlich das Argument values_fill verwenden.

us_rent_income %>% 
  pivot_wider(names_from = variable,
              values_from = estimate,
              values_fill = 0)
## # A tibble: 104 × 5
##    GEOID NAME         moe income  rent
##    <chr> <chr>      <dbl>  <dbl> <dbl>
##  1 01    Alabama      136  24476     0
##  2 01    Alabama        3      0   747
##  3 02    Alaska       508  32940     0
##  4 02    Alaska        13      0  1200
##  5 04    Arizona      148  27517     0
##  6 04    Arizona        4      0   972
##  7 05    Arkansas     165  23789     0
##  8 05    Arkansas       5      0   709
##  9 06    California   109  29454     0
## 10 06    California     3      0  1358
## # … with 94 more rows

Auch hier gibt es wieder komplexere Operationen, die pivot_wider() durchführen kann. Die beiden Hauptargumente names_from und values_from können jeweils auch mehr als eine Spalte angegeben bekommen. pivot_wider() erstellt dann so viele neue Spalten, wie es Kombinationen zwischen den Levels aus den originalen Spalten bzw. Spaltennamen gibt. Hier geben wir zum Beispiel die Spalten estimate und moe bei values_from an und erhalten so in Kombination mit den zwei Levels aus variable vier neue Spalten:

us_rent_income %>% 
  pivot_wider(names_from = variable,
              values_from = c(estimate, moe))
## # A tibble: 52 × 6
##    GEOID NAME                 estimate_income estimate_rent moe_income moe_rent
##    <chr> <chr>                          <dbl>         <dbl>      <dbl>    <dbl>
##  1 01    Alabama                        24476           747        136        3
##  2 02    Alaska                         32940          1200        508       13
##  3 04    Arizona                        27517           972        148        4
##  4 05    Arkansas                       23789           709        165        5
##  5 06    California                     29454          1358        109        3
##  6 08    Colorado                       32401          1125        109        5
##  7 09    Connecticut                    35326          1123        195        5
##  8 10    Delaware                       31560          1076        247       10
##  9 11    District of Columbia           43198          1424        681       17
## 10 12    Florida                        25952          1077         70        3
## # … with 42 more rows

Zuletzt noch ein phonetisches Beispiel: Wir möchten aus den Levels der Spalte Bet (Betonung) des Data Frames asp neue Spalten machen und diese mit den Dauerwerten d füllen. Der Code wirft aber eine Warnmeldung, weil es mehrere Werte pro Zelle gibt in den neuen Spalten gibt, wie Sie an der seltsamen Schreibweise sehen können:

asp %>%
  pivot_wider(names_from = Bet,
              values_from = d)
## Warning: Values are not uniquely identified; output will contain list-cols.
## * Use `values_fn = list` to suppress this warning.
## * Use `values_fn = length` to identify where the duplicates arise
## * Use `values_fn = {summary_fun}` to summarise duplicates
## # A tibble: 1,723 × 5
##    Wort             Vpn   Kons  un        be       
##    <chr>            <chr> <chr> <list>    <list>   
##  1 Fruehlingswetter k01   t     <dbl [2]> <NULL>   
##  2 Gestern          k01   t     <dbl [2]> <NULL>   
##  3 Montag           k01   t     <dbl [2]> <NULL>   
##  4 Vater            k01   t     <dbl [4]> <NULL>   
##  5 Tisch            k01   t     <NULL>    <dbl [3]>
##  6 Mutter           k01   t     <dbl [2]> <NULL>   
##  7 konnte           k01   k     <dbl [2]> <NULL>   
##  8 Kaffee           k01   k     <NULL>    <dbl [3]>
##  9 Tassen           k01   t     <NULL>    <dbl [2]>
## 10 Teller           k01   t     <NULL>    <dbl [2]>
## # … with 1,713 more rows

Netterweise gibt uns die Warnmeldung auch sofort drei Lösungswege an: Wir können das Argument values_fn benutzen, um die Warnung zu unterdrücken, uns anzeigen zu lassen wie viele Werte pro Zelle es gibt, oder mit einer zusammenfassenden Funktion die Werte umformen. Letzteres scheint hier sinnvoll zu sein: Dort, wo es mehrere Werte pro Zelle gibt, lassen wir mit mean() den Durchschnitt berechnen:

asp %>%
  pivot_wider(names_from = Bet,
              values_from = d,
              values_fn = mean)
## # A tibble: 1,723 × 5
##    Wort             Vpn   Kons     un    be
##    <chr>            <chr> <chr> <dbl> <dbl>
##  1 Fruehlingswetter k01   t      19.1  NA  
##  2 Gestern          k01   t      22.4  NA  
##  3 Montag           k01   t      22.3  NA  
##  4 Vater            k01   t      25.4  NA  
##  5 Tisch            k01   t      NA    55.8
##  6 Mutter           k01   t      19.3  NA  
##  7 konnte           k01   k      43.9  NA  
##  8 Kaffee           k01   k      NA    56.1
##  9 Tassen           k01   t      NA    49.4
## 10 Teller           k01   t      NA    59.4
## # … with 1,713 more rows

In keinem unserer pivoting-Beispiele haben wir die Data Frames in ihrer veränderten Form überschrieben (z.B. mittels der Doppelpipe). Die Funktionen pivot_longer() und pivot_wider() kann man auch für temporäre Änderungen benutzen, z.B. wenn man den Data Frame in einer bestimmten Form nur für eine Abbildung braucht:

avokal %>% 
  pivot_longer(cols = c(schnell, langsam), names_to = "Tempo", values_to = "Dauer") %>% 
  ggplot() +
  aes(x = Tempo, y = Dauer) + 
  geom_boxplot()

2.3 Separating

Unser zweites Beispiel für messy data ist der Data Frame vcv, bei dem zwei Informationen in der Spalte Context vorhanden sind:

vcv %>% head
##        RT Subject Vowel Lang Left Right Context
## 361 647.5    S209     a   AE    f     h     f.h
## 362 617.0    S209     a   AE    f    sh    f.sh
## 363 728.5    S209     a   AE    f    sh    f.sh
## 364 629.0    S209     a   AE    f    th    f.th
## 365 688.5    S209     a   AE    f    th    f.th
## 366 602.5    S209     a   AE    s     h     s.h

Wir möchten den linken und rechten Kontext, die hier durch einen Punkt getrennt werden, in eigenen Spalten haben (wir entfernen zu Demonstrationszwecken die Spalten Left und Right, denn die enthalten schon die gewünschte Lösung).

vcv %<>% 
  select(RT:Lang, Context) %>% 
  as_tibble()

Um unser Ziel zu erreichen, nutzen wir die Funktion separate(), die obligatorisch folgende Argumente bekommt:

  • col: die Spalte, deren Inhalt aufgeteilt werden soll
  • into: die neuen Spaltennamen
  • sep: wie die strings in der gewählten Spalte getrennt werden sollen

Die ersten zwei Argumente sind eigentlich klar: col ist die Spalte Context, und bei into geben wir die zwei gewünschten Spaltennamen Left und Right an. Für sep gibt es zwei Optionen: Die erste Möglichkeit ist mittels einer Zahl, die den Index angibt, ab dem getrennt werden soll: wenn z.B. die ersten zwei Buchstaben immer in die eine und der Rest in die zweite Spalte geschrieben werden soll, nutzen wir sep = 2. Wenn wir uns aber die unterschiedlichen Werte in Context anschauen, ist das hier nicht sinnvoll:

levels(vcv$Context)
## NULL

Der linke Kontext kann aus einem oder zwei Buchstaben bestehen; außerdem ist da noch ein Punkt, der dann entweder in den linken oder rechten Kontext übernommen werden würde, wie man hier sieht:

vcv %>% 
  separate(col = Context, 
           into = c("Left", "Right"), 
           sep = 1)
## # A tibble: 810 × 6
##       RT Subject Vowel Lang  Left  Right
##    <dbl> <chr>   <chr> <chr> <chr> <chr>
##  1  648. S209    a     AE    f     .h   
##  2  617  S209    a     AE    f     .sh  
##  3  728. S209    a     AE    f     .sh  
##  4  629  S209    a     AE    f     .th  
##  5  688. S209    a     AE    f     .th  
##  6  602. S209    a     AE    s     .h   
##  7  632. S209    a     AE    s     .sh  
##  8  574  S209    a     AE    s     .th  
##  9  719  S209    a     AE    s     .h   
## 10  569  S209    a     AE    s     .th  
## # … with 800 more rows
vcv %>% 
  separate(col = Context, 
           into = c("Left", "Right"), 
           sep = 2)
## # A tibble: 810 × 6
##       RT Subject Vowel Lang  Left  Right
##    <dbl> <chr>   <chr> <chr> <chr> <chr>
##  1  648. S209    a     AE    f.    h    
##  2  617  S209    a     AE    f.    sh   
##  3  728. S209    a     AE    f.    sh   
##  4  629  S209    a     AE    f.    th   
##  5  688. S209    a     AE    f.    th   
##  6  602. S209    a     AE    s.    h    
##  7  632. S209    a     AE    s.    sh   
##  8  574  S209    a     AE    s.    th   
##  9  719  S209    a     AE    s.    h    
## 10  569  S209    a     AE    s.    th   
## # … with 800 more rows

Die andere Möglichkeit ist eine regular expression (auch: regulärer Ausdruck oder RegEx). Dafür gibt man ein Muster (pattern) an, nach dem die Trennung in zwei Spalten erfolgen soll. Das würde für unser Beispiel gut passen, weil wir immer am Punkt trennen wollen. Der Punkt steht aber leider bei den regulären Ausdrücken für ein (und zwar egal welches) Schriftzeichen. Wenn wir den Punkt als Punkt verwenden wollen müssen wir ihn durch das Escape-Zeichen, den doppelten Backslash, schützen.

vcv %>% 
  separate(col = Context, 
           into = c("Left", "Right"), 
           sep = "\\.")
## # A tibble: 810 × 6
##       RT Subject Vowel Lang  Left  Right
##    <dbl> <chr>   <chr> <chr> <chr> <chr>
##  1  648. S209    a     AE    f     h    
##  2  617  S209    a     AE    f     sh   
##  3  728. S209    a     AE    f     sh   
##  4  629  S209    a     AE    f     th   
##  5  688. S209    a     AE    f     th   
##  6  602. S209    a     AE    s     h    
##  7  632. S209    a     AE    s     sh   
##  8  574  S209    a     AE    s     th   
##  9  719  S209    a     AE    s     h    
## 10  569  S209    a     AE    s     th   
## # … with 800 more rows

So erreichen wir also das Ziel, dass jede Spalte nur eine Variable enthält!

Weiterführende Infos: reguläre Ausdrücke

RegExps sind ein komplexes Thema, in das wir hier nicht tiefer einsteigen werden. Wenn Sie sich einlesen wollen, empfehlen wir Kapitel 14 aus R for Data Science.

3 Joining mit dplyr

Vielleicht kennen Sie von der Arbeit mit relationalen Daten(banken) und/oder SQL den sogenannten join. Das Prinzip relationaler Daten beruht darauf, alle Informationen und Messwerte, die man gesammelt und erhoben hat, in thematisch sinnvoll aufgeteilten Tabellen abzuspeichern. So könnte man nach einem Experiment zum Beispiel eine Tabelle mit den Messwerten (Formantwerte, Grundfrequenz, etc.) und eine Tabelle mit den Metadaten (Alter, Herkunft, Bildungsstand, etc.) zu den Sprechern erstellen. Zusätzlich bietet es sich vielleicht an, eine Tabelle mit Informationen über das erhobene Material (Wörter, Betonung, Phoneme, Stimuli, etc.) und über die getesteten Bedingungen (Sprechtempo, welcher Gesprächspartner, etc.) zu haben. Wenn nötig, kann man jederzeit (und ggf. nur temporär) zwei Tabellen über einen key, d.h. eine Identifikationsspalte, miteinander verbinden. Bei dplyr unterscheiden wir zwischen mutating joins und filtering joins.

3.1 Mutating Joins

Wie auch bei mutate() werden einem Data Frame x durch einen mutating join weitere Spalten hinzugefügt. Im Gegensatz zu mutate() stammen die neuen Spalten aber aus einem anderen Data Frame y. In beiden Data Frames x und y muss es mindestens eine Spalte geben, die einen identifier oder key enthält, über den die Tabellen verbunden werden können.

3.1.1 Inner Join

Die einfachste Form des mutating join ist der sogenannte inner join, der mittels der Funktion inner_join() durchgeführt wird. Die Funktion bekommt als Argumente zwei Data Frames x und y und den Identifier im Argument by. Das Ergebnis eines inner join enthält alle Spalten von x und y sowie alle Zeilen, für die es einen Match in beiden Data Frames gibt. Fehlende Werte (NA) tauchen nicht im Ergebnis eines inner join auf, daher ist hier immer Vorsicht angebracht.

Als einfaches Beispiel verwenden wir hier eine Tabelle mit Grundfrequenzwerten von zehn Sprechern und eine Tabelle mit Metadaten über die zehn Sprecher:

measures <- tibble(subject = rep(paste0("s", 1:10), each = 10),
                   F0 = rnorm(100, 120, 15))
measures
## # A tibble: 100 × 2
##    subject    F0
##    <chr>   <dbl>
##  1 s1      123. 
##  2 s1      135. 
##  3 s1       99.1
##  4 s1      127. 
##  5 s1      113. 
##  6 s1      128. 
##  7 s1      131. 
##  8 s1      113. 
##  9 s1      113. 
## 10 s1      134. 
## # … with 90 more rows
meta <- tibble(subject = paste0("s", 1:10),
               age = rep(c("old", "young"), each = 5))
meta
## # A tibble: 10 × 2
##    subject age  
##    <chr>   <chr>
##  1 s1      old  
##  2 s2      old  
##  3 s3      old  
##  4 s4      old  
##  5 s5      old  
##  6 s6      young
##  7 s7      young
##  8 s8      young
##  9 s9      young
## 10 s10     young

Beide tibbles haben die Spalte subject, die wir als key bei unserem inner join benutzen werden:

inner_join(x = measures, y = meta, by = "subject")
## # A tibble: 100 × 3
##    subject    F0 age  
##    <chr>   <dbl> <chr>
##  1 s1      123.  old  
##  2 s1      135.  old  
##  3 s1       99.1 old  
##  4 s1      127.  old  
##  5 s1      113.  old  
##  6 s1      128.  old  
##  7 s1      131.  old  
##  8 s1      113.  old  
##  9 s1      113.  old  
## 10 s1      134.  old  
## # … with 90 more rows

Es kann vorkommen, dass die key-Spalte in den zwei Data Frames unterschiedlich benannt ist. In diesem Fall sagen wir der Funktion mittels by = c("a"="b"), dass die Spalte a aus dem Data Frame x mit der Spalte b aus dem Data Frame y gematched werden soll (das gilt für alle join Funktionen):

measures %<>% rename(Vpn = subject)
inner_join(x = measures, y = meta, by = c("Vpn"="subject"))
## # A tibble: 100 × 3
##    Vpn      F0 age  
##    <chr> <dbl> <chr>
##  1 s1    123.  old  
##  2 s1    135.  old  
##  3 s1     99.1 old  
##  4 s1    127.  old  
##  5 s1    113.  old  
##  6 s1    128.  old  
##  7 s1    131.  old  
##  8 s1    113.  old  
##  9 s1    113.  old  
## 10 s1    134.  old  
## # … with 90 more rows
measures %<>% rename(subject = Vpn)

In diesem Beispiel werden bislang immer alle Zeilen von measures zurückgegeben und alle Spalten beider Data Frames. Das liegt daran, dass es sowohl in measures als auch in meta Informationen zu denselben zehn Probanden gibt. Wenn wir in einem der Data Frames mehr oder weniger Informationen haben, werden plötzlich Zeilen weggelassen.

# Messwerte von 20 statt 10 Sprechern:
measures <- tibble(subject = rep(paste0("s", 1:20), each = 10),
                   F0 = rnorm(200, 120, 15))
inner_join(x = measures, y = meta, by = "subject")
## # A tibble: 100 × 3
##    subject    F0 age  
##    <chr>   <dbl> <chr>
##  1 s1      127.  old  
##  2 s1      134.  old  
##  3 s1      113.  old  
##  4 s1      119.  old  
##  5 s1       91.2 old  
##  6 s1      136.  old  
##  7 s1      111.  old  
##  8 s1      134.  old  
##  9 s1      122.  old  
## 10 s1      138.  old  
## # … with 90 more rows

In diesem Ergebnis gibt es keine Zeilen für die Sprecher 11 bis 20, weil es zu diesen Sprechern keine Informationen im Data Frame meta gibt!

3.1.2 Outer Join

Im Unterschied zum inner join werden beim outer join auch Zeilen mit fehlenden Werten beibehalten. Diese fehlenden Werte werden als NA gekennzeichnet. Die einfachste Version eines outer join ist der full join, bei dem alle Zeilen und alle Spalten aus beiden Data Frames beibehalten werden. Die entsprechende Funktion dazu heißt full_join() (wir frisieren das Ergebnis hier mit slice(), um einen interessanten Teil des Ergebnisses hervorzuheben):

full_join(x = measures, y = meta, by = "subject") %>% 
  slice(95:105)
## # A tibble: 11 × 3
##    subject    F0 age  
##    <chr>   <dbl> <chr>
##  1 s10      137. young
##  2 s10      111. young
##  3 s10      130. young
##  4 s10      124. young
##  5 s10      112. young
##  6 s10      121. young
##  7 s11      130. <NA> 
##  8 s11      116. <NA> 
##  9 s11      124. <NA> 
## 10 s11      101. <NA> 
## 11 s11      118. <NA>

Dieses Ergebnis besteht aus 200 Zeilen (wenn wir nicht slice() darauf anwenden) – als wir oben die Funktion inner_join() auf die exakt selben tibbles ausgeführt haben, hatte das Ergebnis nur 100 Zeilen. Das liegt daran, dass full_join() die 100 Zeilen mit den Messwerten der Sprecher 11 bis 20 beibehalten hat, während inner_join() diese Zeilen gelöscht hat, weil es für diese Sprecher keine Informationen aus meta zu holen gab. Beim Ergebnis des full_join() finden sich deshalb NA-Werte in der angehängten Spalte age für die Sprecher 11 bis 20.

Wenn Sie nicht alle Zeilen aus beiden Data Frames übernehmen wollen, stehen Ihnen die Funktionen left_join() und right_join() zur Verfügung. Beim left_join() werden alle Zeilen aus Data Frame x und beim right_join() alle Zeilen aus dem Data Frame y übernommen. In unserem Beispiel wollen wir alle Zeilen aus measures erhalten und nur die Altersinformation aus meta hinzufügen:

left_join(x = measures, y = meta, by = "subject")
## # A tibble: 200 × 3
##    subject    F0 age  
##    <chr>   <dbl> <chr>
##  1 s1      127.  old  
##  2 s1      134.  old  
##  3 s1      113.  old  
##  4 s1      119.  old  
##  5 s1       91.2 old  
##  6 s1      136.  old  
##  7 s1      111.  old  
##  8 s1      134.  old  
##  9 s1      122.  old  
## 10 s1      138.  old  
## # … with 190 more rows
right_join(x = meta, y = measures, by = "subject")
## # A tibble: 200 × 3
##    subject age      F0
##    <chr>   <chr> <dbl>
##  1 s1      old   127. 
##  2 s1      old   134. 
##  3 s1      old   113. 
##  4 s1      old   119. 
##  5 s1      old    91.2
##  6 s1      old   136. 
##  7 s1      old   111. 
##  8 s1      old   134. 
##  9 s1      old   122. 
## 10 s1      old   138. 
## # … with 190 more rows

3.2 Filtering Joins

Die zweite Art von joins in R sind die sogenannten filtering joins, die keine neuen Spalten hinzufügen, sondern nur ausgewählte Zeilen zurückgeben. Hierfür gibt es im tidyverse zwei Funktionen:

  • semi_join(): Gibt alle Beobachtungen des Data Frames x zurück, für die es einen Match im Data Frame y gibt
  • anti_join(): Gibt alle Beobachtungen des Data Frames x zurück, für die es keinen Match im Data Frame y gibt

Wir werden diese beiden Funktionen anhand der folgenden Data Frames demonstrieren:

vcv %<>% as_tibble()
vcv
## # A tibble: 810 × 5
##       RT Subject Vowel Lang  Context
##    <dbl> <chr>   <chr> <chr> <chr>  
##  1  648. S209    a     AE    f.h    
##  2  617  S209    a     AE    f.sh   
##  3  728. S209    a     AE    f.sh   
##  4  629  S209    a     AE    f.th   
##  5  688. S209    a     AE    f.th   
##  6  602. S209    a     AE    s.h    
##  7  632. S209    a     AE    s.sh   
##  8  574  S209    a     AE    s.th   
##  9  719  S209    a     AE    s.h    
## 10  569  S209    a     AE    s.th   
## # … with 800 more rows
vcv_summary <- vcv %>% 
  group_by(Subject, Vowel) %>% 
  summarise(mean_rt = mean(RT)) %>% 
  ungroup() %>%
  slice_max(mean_rt, n = 5)
## `summarise()` has grouped output by 'Subject'. You can override using the `.groups` argument.
vcv_summary
## # A tibble: 5 × 3
##   Subject Vowel mean_rt
##   <chr>   <chr>   <dbl>
## 1 S502    i       1173.
## 2 S502    u       1076.
## 3 S502    a       1000.
## 4 S508    u        816.
## 5 S508    i        780.

Der Data Frame vcv_summary enthält also die fünf höchsten durchschnittlichen Reaktionszeiten und welchem Sprecher und Vokal diese Werte zugeordnet sind. Wenn wir jetzt herausfinden wollen, aus welchen Beobachtungen in vcv diese Mittelwerte berechnet wurden, nutzen wir den semi join. Genau genommen möchten wir alle Zeilen aus vcv zurückbekommen, für die es in vcv_summary einen Match bezüglich der Spalten Subject und Vowel gibt.

semi_join(x = vcv, y = vcv_summary, by = c("Subject", "Vowel"))
## # A tibble: 75 × 5
##       RT Subject Vowel Lang  Context
##    <dbl> <chr>   <chr> <chr> <chr>  
##  1 1120. S502    a     D     f.h    
##  2 1066. S502    a     D     f.sh   
##  3  848  S502    a     D     f.sh   
##  4 1148. S502    a     D     f.th   
##  5 1130  S502    a     D     f.th   
##  6  938  S502    a     D     s.h    
##  7 1124. S502    a     D     s.sh   
##  8  981  S502    a     D     s.th   
##  9  774  S502    a     D     s.h    
## 10 1104  S502    a     D     s.th   
## # … with 65 more rows

Das Ergebnis enthält jetzt also alle Beobachtungen, aus denen die gemittelten Reaktionszeiten in vcv_summary berechnet wurden. Wir können das nochmal verdeutlichen, indem wir uns ausgeben lassen, welche einzigartigen Kombinationen von Subject und Vowel es im Ergebnis des semi joins gibt (es sind dieselben fünf Kombinationen wie in vcv_summary):

semi_join(x = vcv, y = vcv_summary, by = c("Subject", "Vowel")) %>% 
  select(Subject, Vowel) %>% 
  unique()
## # A tibble: 5 × 2
##   Subject Vowel
##   <chr>   <chr>
## 1 S502    a    
## 2 S502    i    
## 3 S502    u    
## 4 S508    i    
## 5 S508    u

Mit dem anti join erhalten wir hingegen alle Beobachtungen, aus denen nicht die gemittelten Reaktionszeiten berechnet wurden, oder mit anderen Worten: alle Beobachtungen aus vcv, für die es bzgl. Subject und Vowel keinen Match in vcv_summary gibt.

anti_join(x = vcv, y = vcv_summary, by = c("Subject", "Vowel"))
## # A tibble: 735 × 5
##       RT Subject Vowel Lang  Context
##    <dbl> <chr>   <chr> <chr> <chr>  
##  1  648. S209    a     AE    f.h    
##  2  617  S209    a     AE    f.sh   
##  3  728. S209    a     AE    f.sh   
##  4  629  S209    a     AE    f.th   
##  5  688. S209    a     AE    f.th   
##  6  602. S209    a     AE    s.h    
##  7  632. S209    a     AE    s.sh   
##  8  574  S209    a     AE    s.th   
##  9  719  S209    a     AE    s.h    
## 10  569  S209    a     AE    s.th   
## # … with 725 more rows

Anti joins eignen sich unter anderem zur Fehlersuche in den eigenen Daten. Nehmen wir nochmal unsere Beispieldaten von vorhin, measures mit Messwerten für 20 Sprecher, und meta mit Metadaten für nur 10 dieser Sprecher. Wenn wir den anti join hier anwenden, finden wir sofort heraus, für welche Zeilen in measures es keinen Match in meta gibt, für welche Sprecher es also keine Metadaten gibt.

anti_join(x = measures, y = meta, by = "subject")
## # A tibble: 100 × 2
##    subject    F0
##    <chr>   <dbl>
##  1 s11      130.
##  2 s11      116.
##  3 s11      124.
##  4 s11      101.
##  5 s11      118.
##  6 s11      137.
##  7 s11      127.
##  8 s11      107.
##  9 s11      117.
## 10 s11      118.
## # … with 90 more rows

Das Ergebnis ist das Gegenstück zum inner join von oben: Wir erhalten hier die 100 Beobachtungen für die Sprecher 11 bis 20, für die es keine Metadaten gibt.

Weiterführende Infos: dplyr

Die joins waren die letzten Funktionen aus dem tidyverse, die wir Ihnen hier vorstellen wollen. Eine Übersicht über alle bisher gelernten und weitere Funktionen erhalten Sie in diesem Cheatsheet.

4 Pretty Plots

ggplot2 bietet Ihnen vielfältige Möglichkeiten, Ihre Abbildungen zu verschönern und zu individualisieren. Hier stellen wir Ihnen die wichtigsten Spezifikationen vor.

4.1 Achsenbeschriftungen

Die Beschriftungen der Achsen wird mit xlab() bzw. ylab() erstellt. Einen Titel können Sie mit ggtitle() hinzufügen:

ggplot(asp) + 
  aes(x = Kons, y = d) + 
  geom_boxplot() + 
  xlab("Artikulationsstelle") + 
  ylab("Dauer (ms)") + 
  ggtitle("Boxplot-Daten")

Ansonsten können Sie auch labs() nutzen für alle Labels zusammen:

ggplot(coronal) + 
  aes(x = Region, fill = Fr) + 
  geom_bar(position = "fill") + 
  labs(x = "Region", 
       y = "Proportion", 
       title = "Proportionale Aufteilung von Frikativen",
       subtitle = "Aufgeteilt nach Region")

4.2 Koordinatensystem begrenzen

Um den sichtbaren Bereich eines Plots zu begrenzen oder zu erweitern, können Sie die folgenden Funktionen verwenden. Diese haben jedoch unterschiedliche “Nebenwirkungen” (s. auch hier):

  • xlim() und/oder ylim() bzw. scale_x_continuous(limits = c()) und/oder scale_y_continuous(limits = c()): Eliminiert Datenpunkte beim Heranzoomen und wirft eine Warnmeldung. Dies beeinflusst ggf. Regressionslinien und andere überlagerte Abbildungskomponenten.
  • coord_cartesian(xlim = c(), ylim = c()): Blendet die Datenpunkte nur aus und wirft daher keine Warnmeldung. Dies beeinflusst Regressionslinien und andere überlagerte Abbildungskomponenten nicht.
# ohne Achsenbeschränkung:
ggplot(int) + 
  aes(x = dB, y = Dauer) + 
  geom_point()

# mit coord_cartesian()
ggplot(int) + 
  aes(x = dB, y = Dauer) + 
  geom_point() + 
  coord_cartesian(xlim = c(10,40), 
                  ylim = c(30,280))

# mit xlim() und ylim()
ggplot(int) + 
  aes(x = dB, y = Dauer) + 
  geom_point() + 
  xlim(10, 40) + 
  ylim(30, 280)
## Warning: Removed 2 rows containing missing values (geom_point).

4.3 Farben

ggplot2 verwendet standardmäßig immer dieselbe Farbpalette. Ihnen stehen aber deutlich mehr Farben zur Verfügung, wie Ihnen diese Farbauswahl zeigt. Die Farbnamen bekommen Sie auch mit:

colors()
# die ersten 10 anzeigen:
colors()[1:10]
##  [1] "white"         "aliceblue"     "antiquewhite"  "antiquewhite1"
##  [5] "antiquewhite2" "antiquewhite3" "antiquewhite4" "aquamarine"   
##  [9] "aquamarine1"   "aquamarine2"

Ihre Farbauswahl teilen Sie mit den Argumenten col (Umriss- bzw. Linienfarbe) bzw. fill (Füllfarbe) im aesthetic mapping mit. Wenn wir die Variable Kons in folgendem Boxplot farb-kodieren wollen, sieht das so aus:

# mit "fill" (empfohlen für Boxplots!)
ggplot(asp) + 
  aes(x = Kons, y = d, fill = Kons) + 
  geom_boxplot()

# mit "col"
ggplot(asp) + 
  aes(x = Kons, y = d, col = Kons) + 
  geom_boxplot()

Wie Sie sehen, wurde diesen zwei Plots automatisch eine Legende hinzugefügt, die aufschlüsselt, welche Farben für welche Werte der Variable Kons verwendet wurden. Jetzt wollen wir unsere Farben selbst auswählen:

# "fill" mit eigenen Farben
farben <- c("darkgoldenrod1", "navy")
ggplot(asp) + 
  aes(y = d, x = Kons, fill = Kons) + 
  geom_boxplot(fill = farben)

Hier gibt es keine Legende. Wenn wir bei selbst gewählten Farben eine Legende haben wollen, benutzen wir eine Funktion namens scale_color_manual() bzw. scale_fill_manual():

# "fill" mit eigenen Farben
farben <- c("darkgoldenrod1", "navy")
ggplot(asp) + 
  aes(y = d, x = Kons, fill = Kons) + 
  geom_boxplot() + 
  scale_fill_manual(values = farben)

# dasselbe für den Barplot
ggplot(coronal) + 
  aes(x = Region, fill = Fr) + 
  geom_bar() + 
  scale_fill_manual(values = farben)

Für Boxplots gibt es übrigens die Möglichkeit, die Outlier uabhängig von der Box zu gestalten:

farben <- c("darkgoldenrod1", "navy")
ggplot(asp) + 
  aes(y = d, x = Kons, fill = Bet) + 
  geom_boxplot(outlier.color = "red", 
               outlier.shape = 4, 
               outlier.size = 3) + 
  scale_fill_manual(values = farben)

4.4 Weitere Spezifikationen

Es gibt natürlich noch deutlich mehr Spezifikationen für die einzelnen Abbildungstypen als die Farbe, z.B. die Zeichengröße, den Linientyp, die Punktform, die Schriftart…

  • col: Umriss- bzw. Linienfarbe
  • fill: Füllfarbe
  • shape: Form
  • size: Größe
  • lty: Linientyp
  • stroke: Dicke für Text

Dazu gibt es eine Vignette:

vignette("ggplot2-specs")

Einige dieser Spezifikationen wenden wir hier an:

ggplot(int) + 
  aes(x = dB, y = Dauer) +
  geom_point(col = "purple", 
             size = 3, 
             shape = 0) + 
  geom_line(col = "orange", 
            size = 1.5, 
            lty = "twodash")

Sie sollten sich aber natürlich immer sorgfältig überlegen, ob eine Spezifikation notwendig ist, um die Abbildung klarer zu gestalten.

4.5 Schriftzeichengröße

Die Default-Schriftzeichengröße der Achsenbeschriftung und Titel ist 11pt oder kleiner. Vor allem wenn Sie Ihre Plots präsentieren, ist es SEHR wichtig, dass Sie die Schriftzeichengröße verändern. Wir empfehlen mind. 16-24pt. Hierfür müssen wir das theme() ändern.

ggplot(asp) + 
  aes(x = Kons, y = d) + 
  geom_boxplot() + 
  xlab("Artikulationsstelle") + 
  ylab("Dauer (ms)") + 
  ggtitle("Boxplot-Daten") + 
  theme(text = element_text(size = 24),            # Beschriftungen & Titel
        axis.text = element_text(size = 18))       # Achsenbeschriftungen

Weiterführende Infos: theme()

Das theme() ist z.B. für die Hintergrundfarbe der Plots, die Achsen, und vieles weitere zuständig. Diese Übersicht an Argumenten der Funktion theme() zeigt Ihnen, wie viel Sie damit einstellen können.

4.6 Plots unterteilen und anordnen

4.6.1 Plots unterteilen

ggplot2 bietet zwei Möglichkeiten, einen Plot in mehrere zu unterteilen: facet_wrap() und facet_grid(). Hauptargument dieser Funktionen ist/sind die üblicherweise kategoriale(n) Variable(n), deren Werte in getrennten Panels dargestellt werden sollen. Zum Beispiel können wir die Datenpunkte, die mit unterschiedlichen Phonemen assoziiert sind, in getrennten Panels darstellen.

Die Formeln, um die Variablen anzugeben, sehen wie folgt aus:

  • .~Var1 bzw. ~Var1
  • Var1~. (Punkt muss da sein!)
  • Var1~Var2
  • Var1+Var2~Var3
  • Var1~Var2+Var3

Es bietet sich nicht an, mehr als drei Variablen in facet_wrap() oder facet_grid() zu verwenden, da dies die Übersichtlichkeit des Plots deutlich einschränkt.

facet_wrap() ordnet die Panels eines Plots in Reihen und Spalten an.

# aufteilen nach Versuchsperson
ggplot(vdata) + 
  aes(x = F1, y = F2) + 
  geom_point() + 
  facet_wrap(~Subj)

# aufteilen nach Versuchsperson und Gespanntheit
ggplot(vdata) + 
  aes(x = F1, y = F2) + 
  geom_point() + 
  facet_wrap(Subj~Tense)

facet_grid() hingegen ordnet in Zeilen oder Spalten. Die Reihenfolge für die Formel ist facet_grid(Zeilen~Spalten)

# aufteilen nach Versuchsperson in Zeilen
ggplot(vdata) + 
  aes(x = F1, y = F2) + 
  geom_point() + 
  facet_grid(Subj~.)

# aufteilen nach Versuchsperson in Spalten
ggplot(vdata) + 
  aes(x = F1, y = F2) + 
  geom_point() + 
  facet_grid(~Subj)

# aufteilen nach Versuchsperson und Tense
ggplot(vdata) + 
  aes(x = F1, y = F2) + 
  geom_point() + 
  facet_grid(Subj~Tense)

4.6.2 Plots anordnen

Des Weiteren gibt es die Möglichkeit, mehrere Plots neben- oder untereinander anzuordnen. Hierfür verwenden wir die Funktion grid.arrange() die oben geladene Library gridExtra.

plot1 <- ggplot(asp) +
  aes(x = Kons, y = d) +
  geom_boxplot()

plot2 <- ggplot(coronal) + 
  aes(x = Region, fill = Fr) + 
  geom_bar()

plot3 <- ggplot(int) +
  aes(x = dB, y = Dauer) +
  geom_line() + geom_point()

# in drei Spalten und einer Zeile anordnen
grid.arrange(plot1, plot2, plot3, ncol = 3, nrow = 1)

# in einer Spalte und drei Zeilen anordnen
grid.arrange(plot1, plot2, plot3, ncol = 1, nrow = 3)

5 Hilfe zur Selbsthilfe

ggplot2 ist nicht nur bekannt, sondern auch beliebt! Dementsprechend viel Hilfe bekommen Sie von der R Community. Hier ein paar gute Quellen für Hilfe bei der Erstellung von Abbildungen: