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

Wir werden diese Woche 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"))
int <- read.table(file.path(url, "intdauer.txt"))
coronal <- read.table(file.path(url, "coronal.txt"))
vdata <- read.table(file.path(url, "vdata.txt"))

2 Summary Statistics

Wenn man sich einen Überblick über Daten verschaffen möchte, sind sogenannte summary statitics (deskriptive Statistiken) häufig hilfreich. Zu solchen deskriptiven Werten zählen z.B. das arithmetische Mittel (Mittelwert), Median, Varianz, Standardabweichung, Minimum, Maximum, usw. Hier zeigen wir zunächst wie man solche Werte ohne Funktionen aus dem tidyverse berechnen kann. Da R eine Statistik-Software ist, sind solche Basis-Funktionen wie zur Berechnung des Mittelwerts immer verfügbar. Im Folgenden demonstrieren wir die wichtigsten Funktionen zur Berechnung von summary statistics anhand der F1-Werte in vdata:

mean(vdata$F1)             # arithmetisches Mittel
## [1] 407.2877
median(vdata$F1)           # Median
## [1] 366
var(vdata$F1)              # Varianz
## [1] 21254.52
sd(vdata$F1)               # Standardabweichung
## [1] 145.7893
min(vdata$F1)              # Minimum
## [1] 0
max(vdata$F1)              # Maximum
## [1] 1114
range(vdata$F1)            # Minimum & Maximum
## [1]    0 1114
quantile(vdata$F1, 0.25)   # 1. Quartil
## 25% 
## 300
quantile(vdata$F1, 0.75)   # 3. Quartil
##    75% 
## 509.75
IQR(vdata$F1)              # interquartiler Bereich
## [1] 209.75

2.1 Mittelwert & Median

Das arithmetische Mittel wird berechnet, indem man die Summe einer Anzahl \(n\) an Zahlen bildet, und diese Summe dann durch die Anzahl \(n\) teilt. Hier ist ein sehr einfaches Beispiel:

zahlen <- 1:5
summe <- sum(zahlen)
summe
## [1] 15
anzahl <- length(zahlen)
anzahl
## [1] 5
# Mittelwert:
summe/anzahl
## [1] 3
# zum Vergleich:
mean(zahlen)
## [1] 3

Der Median hingegen ist die mittig liegende Zahl in einer sortierten Zahlenreihe. Nehmen wir wieder obiges Beispiel (die Zahlen sind bereits aufsteigend sortiert):

zahlen
## [1] 1 2 3 4 5
median(zahlen)
## [1] 3

Bei einer geraden Anzahl von Zahlen berechnet man den Median als den Mittelwert der zwei mittig liegenden Werte, zum Beispiel:

zahlen <- 1:6
median(zahlen)
## [1] 3.5
mean(c(3, 4))
## [1] 3.5

Der Median ist robuster gegen sogenannte Ausreißer (engl. outlier) als der Mittelwert. Ausreißer sind Datenpunkte, die deutlich extremer sind als die Mehrheit der Datenpunkte. Hier wieder ein einfaches Beispiel:

zahlen <- c(1:5, 100)
zahlen
## [1]   1   2   3   4   5 100
mean(zahlen)
## [1] 19.16667
median(zahlen)
## [1] 3.5

Die Zahl 100 ist ganz offensichtlich ein Ausreißer im Vektor zahlen. Der Mittelwert ist wegen dieses Ausreißers jetzt um ein Vielfaches höher als vorher, während der Median sich nur leicht verändert hat.

2.2 Varianz & Standardabweichung

Varianz und Standardabweichung sind verwandte Maße für die Streuung von Werten um ihren Mittelwert. Genauer gesagt ist die Varianz die Summe der quadrierten Abweichungen der Messwerte von ihrem Mittelwert geteilt durch die Anzahl der Messwerte minus 1, während die Standardabweichung die Quadratwurzel der Varianz ist. Am folgenden Beispiel können Sie nachvollziehen, wie man die Varianz und Standardabweichung “händisch” berechnet:

zahlen <- c(12, 6, 24, 3, 17)
# Mittelwert
m <- mean(zahlen)
m
## [1] 12.4
# quadrierte Abweichungen
quadr_abw <- (zahlen - m)^2
quadr_abw
## [1]   0.16  40.96 134.56  88.36  21.16
# Anzahl der Messwerte
n <- length(zahlen)
n
## [1] 5
# Summe der quadrierten Abweichungen
summe <- sum(quadr_abw)
summe
## [1] 285.2
# Varianz:
varianz <- summe / (n - 1)
varianz
## [1] 71.3
# Mit der Funktion var():
var(zahlen)
## [1] 71.3

Um jetzt die Standardabweichung daraus zu berechnen, die in der Statistik viel häufiger verwendet wird als die Varianz, brauchen wir nur noch die Quadratwurzel aus der Varianz zu ziehen:

std_abw <- sqrt(varianz)
std_abw
## [1] 8.443933
# oder mit sd()
sd(zahlen)
## [1] 8.443933

2.3 Quantile

Ein Quantil teilt Datenpunkte so auf, dass ein bestimmter Teil der Datenpunkte unterhalb des Quantils liegen. Quantil ist ein Überbegriff; je nachdem in wie viele Stücke man die Datenpunkte aufteilt, sagt man auch Perzentil (100 Stücke) oder Quartil (4 Stücke). Der Median ist ebenfalls ein Quantil, denn 50% der Daten liegen immer unter dem Median. In R berechnet die Funktion quantile() die Quantile. Die Funktion bekommt zuerst die Datenpunkte (also einen numerischen Vektor) und anschließend die Proportion der Datenpunkte, die unter dem zu berechnenden Wert liegen soll. Wichtige Quantile sind das erste und dritte Quartil, also die Schwellwerte, unter denen ein Viertel bzw. drei Viertel aller Datenpunkte liegen.

quantile(vdata$F1, 0.25)   # 1. Quartil
## 25% 
## 300
quantile(vdata$F1, 0.75)   # 3. Quartil
##    75% 
## 509.75
IQR(vdata$F1)              # interquartiler Bereich
## [1] 209.75

Die Differenz zwischen dem ersten und dritten Quartil wird auch interquartiler Bereich oder Interquartilsabstand (interquartile range) genannt und kann mit der Funktion IQR() berechnet werden.

2.4 Beispiel Boxplot

Ein Boxplot enthält viele der deskriptiven Informationen, die wir bis jetzt behandelt haben:

  • Median: Der Strich innerhalb der Box ist der Median.
  • Box: Die Box umfasst die mittleren 50% aller Datenpunkte. Das untere Ende der Box ist das erste Quartil (Q1), das obere Ende ist das dritte Quartil (Q3). Das heißt die Box ist genauso groß wie der Interquartilsabstand.
  • Whiskers: Die Whiskers erstrecken sich vom Q1 und vom Q3 aus zu dem niedrigsten/höchsten Datenpunkt, der innerhalb von 1.5 * IQR liegt. Diese Berechnung der Länge der Whiskers als 1.5 * IQR gilt für Boxplots, die mit ggplot2 erstellt wurden, aber nicht jeder Boxplot wird so berechnet.
  • Punkte: Ausreißer, also alle restlichen Datenpunkte, die nicht in der Box und den Whiskers enthalten sind.

Hier sehen Sie den Boxplot für F1 aus dem Data Frame vdata:

Wie man diesen Boxplot erstellt, erfahren Sie später in dieser Vorlesung.

3 Daten manipulieren mit dplyr (Fortsetzung)

3.1 Grouping & Summarising

Zu Beginn dieser Vorlesung 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.2877

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.2877 145.7893 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.2447

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 %     -           479.
##  2 %     +           368.
##  3 A     -           622.
##  4 A     +           668.
##  5 E     -           488.
##  6 E     +           363.
##  7 I     -           346.
##  8 I     +           276.
##  9 O     -           520.
## 10 O     +           348.
## 11 U     -           348.
## 12 U     +           259.
## 13 Y     -           338.
## 14 Y     +           266.

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 %     -       214
##  2 %     +       212
##  3 A     -       218
##  4 A     +       214
##  5 E     -       215
##  6 E     +       210
##  7 I     -       214
##  8 I     +       210
##  9 O     -       214
## 10 O     +       214
## 11 U     -       215
## 12 U     +       208
## 13 Y     -       213
## 14 Y     +       211
# 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().

3.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

4 Daten abbilden mit ggplot2

ggplot2 ist eine Library aus dem tidyverse, die Ihnen sehr viele Möglichkeiten für die Visualisierung von Daten liefert. gg steht für grammar of graphics. Der Befehl, mit dem Sie eine Abbildung beginnen, ist ggplot(); das Hauptargument dieser Funktion ist der gewünschte Data Frame. Dann fügt man das sog. aesthetic mapping mittels aes(), sowie Funktionen für die Art der Abbildung, die Beschriftungen, die Legende, etc., hinzu. Jede Funktion wird mit + verbunden (nicht mit Pipes!).

4.1 Boxplots

Boxplots sind die wohl wichtigsten wissenschaftlich genutzten Abbildungen. In R werden sie mit dem Befehl geom_boxplot() erstellt. Zuerst zeigen wir, wie der oben verwendete Boxplot erstellt wurde. Die Funktion ggplot() bekommt den Data Frame vdata. In den aesthetic mappings aes() tragen wir ein, dass F1 auf der y-Achse aufgetragen werden soll. Zuletzt bestimmen wir noch, dass ein Boxplot gezeichnet werden soll.

ggplot(vdata) + 
  aes(y = F1) + 
  geom_boxplot()

Zugegeben, der Boxplot sieht ein bisschen anders aus als der Boxplot oben. Wie man diesen Plot “verschönert”, lernen Sie nächste Woche. Boxplots eignen sich sehr gut zum Vergleichen von Werten für verschiedene kategoriale Gruppen. Dann werden diese Gruppen (üblicherweise) auf der x-Achse aufgetragen und auf der y-Achse wieder die gewünschten Werte. Hier sehen Sie ein Beispiel für die Dauer verschiedener Konsonanten aus dem Data Frame asp:

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

Boxplots können auch horizontal erstellt werden (wobei das meist weniger übersichtlich ist). Dann werden die Kategorien auf der y-Achse und die Werte auf der x-Achse aufgetragen:

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

Manchmal ist ein sogenannter Notch gewünscht; dafür nutzen wir das Argument notch = TRUE in der Funktion geom_boxplot() (und ggf. notchwidth, um die Tiefe des Notches anzupassen):

ggplot(asp) +
  aes(x = Kons, y = d) +
  geom_boxplot(notch = TRUE)

ggplot(asp) +
  aes(x = Kons, y = d) +
  geom_boxplot(notch = TRUE, notchwidth = 0.3)

Weiterführende Infos: Aesthetic mappings & Piping Data Frames

Streng genommen sind die aesthetic mappings immer ein Argument der Funktion, die über die Art des Plots bestimmt (also z.B. geom_boxplot()). Später werden Sie feststellen, dass manche Plots bestimmte aesthetic mappings benötigen bzw. zulassen, die andere Plots nicht verarbeiten können. Wir lagern die aesthetic mappings in den allermeisten Fällen aus der Plot-Funktion aus, weil das übersichtlicher ist. Es steht Ihnen aber frei, die aesthetic mappings in die Funktion als Argument reinzuschreiben:

ggplot(asp) +
  geom_boxplot(aes(x = Kons, y = d), 
               notch = TRUE, 
               notchwidth = 0.3)

Innerhalb eines ggplot werden die einzelnen Funktion immer und ausschließlich mit einem Pluszeichen verbunden. Der Data Frame allerdings kann mit einer einfachen Pipe an ggplot()übergeben werden:

asp %>%
ggplot() +
  aes(x = Kons, y = d) +
  geom_boxplot()

Das ist besonders hilfreich, wenn Sie vor dem Plotten erst noch weitere Funktionen auf den Data Frame anwenden wollen, bevor sie die daraus entstehenden Daten plotten. Hier filtern wir zum Beispiel zuerst nach Betonung, bevor wir anschließend nur noch die Dauer der betonten Wörter plotten:

asp %>%
  filter(Bet == "be") %>% 
  ggplot() + 
  aes(x = Kons, y = d) +
  geom_boxplot()

4.2 Scatter- & Lineplots

Scatterplots werden mit den Funktionen geom_point() und/oder geom_line() erstellt. Man kann auch beide Funktionen gleichzeitig verwenden. Auf die x- und y-Achse werden üblicherweise nur numerisch-kontinuierliche Daten aufgetragen. Im Folgenden plotten wir zum Beispiel Lautstärke in Dezibel gegen Dauer.

# Punkte:
ggplot(int) +
  aes(x = Dauer, y = dB) +
  geom_point() 

# Linie:
ggplot(int) +  
  aes(x = Dauer, y = dB) + 
  geom_line()

# Beides:
ggplot(int) +
  aes(x = Dauer, y = dB) + 
  geom_line() + 
  geom_point()

Manchmal ist es hilfreich, vertikale oder horizontale Referenzlinien in einem Plot einzuzeichnen. Horizontale Linien werden mit geom_hline() erzeugt, vertikale gerade Linien mit geom_vline(). Um eine horizontale Linie zu zeichnen, muss bekannt sein, an welcher Stelle die Linie die y-Achse schneidet. Deshalb bekommt geom_hline() immer das Argument yintercept. Bei geom_vline() muss mit xintercept die Schnittstelle der vertikalen Linie mit der x-Achse eingetragen werden. Wir fügen zum obigen Scatterplot zwei gerade Linien hinzu:

ggplot(int) +
  aes(x = Dauer, y = dB) +
  geom_point() + 
  geom_vline(xintercept = 150) + 
  geom_hline(yintercept = 35)

4.3 Barplots

Eine weitere wichtige Abbildungsform sind Barplots, die mit geom_bar() erzeugt werden. Dabei darf nur entweder x oder y in den aesthetic mappings verwendet werden. Das liegt daran, dass auf die jeweils andere Achse grundsätzlich ein count oder eine Proportion aufgetragen wird, die von ggplot berechnet wird. Der folgende Plot zeigt zum Beispiel, wie viele Vorkommnisse dreier Regionen im Data Frame coronal zu finden sind.

ggplot(coronal) +
  aes(x = Region) +
  geom_bar()

Die Balken können wir auch horizontal plotten, indem wir in den aesthetic mappings y statt x angeben:

ggplot(coronal) +
  aes(y = Region) +
  geom_bar()

Die Werte der Balken können Sie ganz einfach nachvollziehen, indem Sie sich die Anzahl der Vorkommnisse der drei Regionen mittels der Funktion table() anzeigen lassen:

table(coronal$Region)
## 
## R1 R2 R3 
## 81 80 79

Beim Barplot können Sie aber wie z.B. beim Boxplot noch eine weitere (kategoriale) Variable plotten. Die zweite Variable, die abgebildet werden soll, wird mit dem Argument fill angegeben, das die Levels der Variable als Füllfarben darstellt. Sie werden nächste Woche u.a. lernen, wie man Farben selbst bestimmen kann. Im Folgenden sieht man, wie häufig die Frikative Fr “s” (rot) und “sh” (blau) jeweils in den drei Regionen produziert wurden.

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

Lassen Sie uns mittels der zuvor gelernten Funktionen für Grouping und Summarising die Werte in diesem Plot nachvollziehen. Dafür gruppieren wir nach Region und Frikativ und lassen uns dann mit n() innerhalb von summarise() die Anzahl der Zeilen im Data Frame pro Gruppenkombination bestimmen.

coronal %>% 
  group_by(Region, Fr) %>% 
  summarise(count = n())
## `summarise()` has grouped output by 'Region'. You can override using the `.groups` argument.
## # A tibble: 6 × 3
## # Groups:   Region [3]
##   Region Fr    count
##   <chr>  <chr> <int>
## 1 R1     s        58
## 2 R1     sh       23
## 3 R2     s        59
## 4 R2     sh       21
## 5 R3     s        66
## 6 R3     sh       13

Die Funktion geom_bar() kann als Argument noch position bekommen…

# ...um Proportionen anstatt einer absoluten Anzahl darzustellen:
ggplot(coronal) +
  aes(x = Region, fill = Fr) +
  geom_bar(position = "fill")

# ...um die Balken nebeneinander zu stellen:
ggplot(coronal) +
  aes(x = Region, fill = Fr) +
  geom_bar(position = "dodge")

4.4 Histogramme & Wahrscheinlichkeitsdichte

Histogramme zeigen die Verteilung von numerisch-kontinuierlichen Datenpunkten, indem sie den Wertebereich in mehrere kleine Bereiche einteilt. Ähnlich wie beim Barplot zeigen dann Balken (bins) an, wie viele Werte in einem bestimmten Wertebereich liegen. In ggplot werden Histogramme mit geom_histogram() erstellt. In den aesthetic mappings legen wir mit dem Argument x fest, welche Daten wir anschauen wollen, zum Beipspiel die F1-Verteilung:

ggplot(vdata) + 
  aes(x = F1) + 
  geom_histogram()
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Um die einzelnen Balken besser voneinander unterscheiden zu können, lassen wir die Balken weiß umranden, indem wir geom_histogram() das Argument color = "white" übergeben:

ggplot(vdata) + 
  aes(x = F1) + 
  geom_histogram(color = "white")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Wir können auch selbst bestimmen, wie breit die Balken sein sollen, nämlich mit binwidth. Im Moment umfasst ein Balken ca. 40 Hz. Die folgenden Abbildungen zeigen die exakt selben Daten, aber mit Balken von 10 Hz und Balken von 100 Hz:

ggplot(vdata) + 
  aes(x = F1) + 
  geom_histogram(color = "white",
                 binwidth = 10)

ggplot(vdata) + 
  aes(x = F1) + 
  geom_histogram(color = "white",
                 binwidth = 100)

Sie sehen, dass dies für die Repräsentation der Daten einen großen Unterschied macht – gehen Sie also immer mit Bedacht vor, wenn Sie die binwidth von Histogrammen verändern.

Mit dem Histogramm verwandt ist die Wahrscheinlichkeitsdichte (engl. probability density). Die einzige Änderung, die wir dafür vornehmen müssen, ist aes() das Argument y = ..density.. zu übergeben. Dies verändert die y-Achse so, dass statt der Anzahl an Datenpunkten die Wahrscheinlichkeitsdichte der Datenpunkte angezeigt wird. Per definitionem ist die Fläche unter den Balken der Wahrscheinlichkeitsdichte insgesamt 1.

ggplot(vdata) + 
  aes(x = F1, y = ..density..) + 
  geom_histogram(color = "white",
                 binwidth = 100)

Die Wahrscheinlichkeitsdichte wird berechnet als count / (n * binwidth), wo n die Anzahl aller Datenpunkte ist. In dem Histogramm oben (mit binwidth = 100) liegen zum Beispiel 285 Datenpunkte (count) im Wertebereich zwischen 150 Hz und 250 Hz. Die Wahrscheinlichkeitsdichte für diesen Balken wird also wie folgt berechnet:

count <- 285
n <- nrow(vdata)
binwidth <- 100
dens <- count / (n * binwidth)
dens
## [1] 0.0009557344

Dieser Wert stimmt mit dem density-Wert überein, den wir in der Wahrscheinlichkeitsdichteverteilung für denselben Balken sehen.

Die Fläche dieses Balkens in der Wahrscheinlichkeitsdichteverteilung wird berechnet als binwidth * binheight:

area <- binwidth * dens
area
## [1] 0.09557344

Wenn man die Fläche aller Balken berechnet und summiert, ist die Gesamtfläche 1.

Stellen Sie sich nun ein Wahrscheinlichkeitsdichte-Histogramm vor, das aus unendlich vielen Balken besteht (die dementsprechend unendlich schmal sein müssen). Sie erhalten nicht mehr einzelne Balken sondern eine kontinuierliche Funktion, die sich Wahrscheinlichkeitsdichteverteilung (probability density function) nennt. Auch dafür kennt ggplot2 eine Funktion: geom_density().

ggplot(vdata) + 
  aes(x = F1) + 
  geom_density()

Hier gilt genau wie bei dem Histogramm mit der Wahrscheinlichkeitsdichte, dass das Integral (die Fläche) unter der Kurve 1 ist. Weshalb das wichtig ist, erfahren Sie übernächste Woche.

Weiterführende Infos: Histogramme und Probability Density

Für weitere Informationen schauen Sie sich gerne Wilke’s Fundamentals of Data Visualization in R, Kapitel 7 an.