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()
.
<- "http://www.phonetik.uni-muenchen.de/~jmh/lehre/Rdf"
url <- read.table(file.path(url, "asp.txt"))
asp <- read.table(file.path(url, "coronal.txt")) coronal
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.
schnell
und langsam
sind eigentlich Werte der Variable Tempo
.<- read.table(file.path(url, "avokal.txt"))
avokal 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
Context
sind zwei Informationen abgespeichert: der linke und der rechte phonetische Kontext. Besser ist die Aufteilung in zwei Spalten, die sogar auch schon im Data Frame existieren (Left
und Right
).<- read.table(file.path(url, "vcvC.txt"))
vcv %>% head() vcv
## 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.
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:
<- read.table(file.path(url, "vdata.txt")) %>%
vdata 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.
%>% class() vdata
## [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:
<- read_delim(file.path(url, "intdauer.txt"),
int 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 Spaltenread_csv2()
: durch Semikolon getrennte Spaltenread_tsv()
: durch Tab getrennte Spaltenread_delim()
: für alle Trennzeichen geeignetAll dies und mehr finden Sie auch in Kapitel 11 aus R for Data Science.
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 sollenvalues_to
: der Name der neuen Spalte mit den Wertennames_to
: der Name der neuen Spalte mit den ehemaligen SpaltennamenDie 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 sollenvalues_from
: die Spalte, deren Werte die neuen Spalten füllen sollenEin 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()
Unser zweites Beispiel für messy data ist der Data Frame vcv
, bei dem zwei Informationen in der Spalte Context
vorhanden sind:
%>% head vcv
## 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 sollinto
: die neuen Spaltennamensep
: wie die strings in der gewählten Spalte getrennt werden sollenDie 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.
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.
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.
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:
<- tibble(subject = rep(paste0("s", 1:10), each = 10),
measures 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
<- tibble(subject = paste0("s", 1:10),
meta 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):
%<>% rename(Vpn = subject)
measures 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
%<>% rename(subject = Vpn) measures
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:
<- tibble(subject = rep(paste0("s", 1:20), each = 10),
measures 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!
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
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
gibtanti_join()
: Gibt alle Beobachtungen des Data Frames x
zurück, für die es keinen Match im Data Frame y
gibtWir werden diese beiden Funktionen anhand der folgenden Data Frames demonstrieren:
%<>% as_tibble()
vcv 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 %>%
vcv_summary 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.
ggplot2
bietet Ihnen vielfältige Möglichkeiten, Ihre Abbildungen zu verschönern und zu individualisieren. Hier stellen wir Ihnen die wichtigsten Spezifikationen vor.
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")
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).
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
<- c("darkgoldenrod1", "navy")
farben 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
<- c("darkgoldenrod1", "navy")
farben 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:
<- c("darkgoldenrod1", "navy")
farben 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)
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. Linienfarbefill
: Füllfarbeshape
: Formsize
: Größelty
: Linientypstroke
: Dicke für TextDazu 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.
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.
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)
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
.
<- ggplot(asp) +
plot1 aes(x = Kons, y = d) +
geom_boxplot()
<- ggplot(coronal) +
plot2 aes(x = Region, fill = Fr) +
geom_bar()
<- ggplot(int) +
plot3 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)
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:
Kapitel Data Visualisation in Hadley Wickham’s “R for Data Science”