Kapitel 6 Einführung in dplyr
6.1 Einstieg
dplyr ist ein Paket zur Datenmanipulation, entwickelt von Hadley Wickham und Romain Francois. Das Paket ist Teil des tidyverse und gehört als Kernpaket zu den Paketen, die über library(tidyverse)
geladen werden.
Die Autoren des Pakets verstehen dplyr
als eine Grammatik der Datenmanipulation. Daher werden die wichtigsten dplyr
Funktionen auch oft als Verben bezeichnet. Diese Verben sollen euch helfen, die häufigsten Herausforderungen bei der Datenmanipulation zu lösen:
mutate()
: fügt neue Variablen zum Datensatz hinzu, die Funktionen von bestehenden Variablen sindselect()
: wählt Variablen (Spalten) basierend auf ihren Namen ausfilter()
: wählt Zeilen basierend auf anzugebenden Bedingungen aussummarise()
: reduziert mehrere Werte auf eine einzige Zusammenfassungarrange()
: ändert die Reihenfolge der Zeilen
Der Ursprung von dplyr
liegt in einem früheren Paket mit dem Namen plyr, das zum Ziel hat die “split-apply-combine”-Strategie der Datenanalyse (Wickham 2011) umzusetzen. Wo plyr
noch einen vielfältigen Satz von Ein- und Ausgabetypen abdeckt (z.B. Arrays, data frames, Listen), hat dplyr
einen klaren Fokus auf data frames oder tibbles (wenn man sich im tidyverse befindet).
dplyr
bietet schnelle Alternativen zu den R Standardfunktionen:
und mehr. Ferner bietet dplyr
die Möglichkeit schnell über Zeilen oder Gruppen von Zeilen zu iterieren, was eine schnelle Alternative zur Nutzung von for
Schleifen darstellt.
Wie immer, laden wir zu Beginn
Der Fokus liegt in diesem Abschnitt auf dplyr
. Aber da wir immer wieder auch Funktionen aus anderen “tidyverse-Paketen” nutzen, laden wir stets tidyverse
.
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ ggplot2 3.5.1 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.1
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
Zusätzlich laden wir auch noch wieder das gapminder Paket, da wir erneut mit dem gapminder
Datensatz arbeiten wollen.
6.2 filter()
: Indizieren von Zeilen
Die Funktion filter()
erwartet neben dem Datensatz logische Ausdrücke als Input und gibt die Zeilen des Datensatzes zurück, für die die Kombination der verwendeten logischen Ausdrücke ein TRUE
ergibt.
# beobachtungen mit einer lebenserwartung unter 29 jahren
filter(gapminder, lifeExp < 29)
## # A tibble: 2 × 6
## country continent year lifeExp pop gdpPercap
## <fct> <fct> <int> <dbl> <int> <dbl>
## 1 Afghanistan Asia 1952 28.8 8425333 779.
## 2 Rwanda Africa 1992 23.6 7290203 737.
# beobachtungen aus ruanda nach dem jahr 1979
filter(gapminder, country == "Rwanda", year > 1979)
## # A tibble: 6 × 6
## country continent year lifeExp pop gdpPercap
## <fct> <fct> <int> <dbl> <int> <dbl>
## 1 Rwanda Africa 1982 46.2 5507565 882.
## 2 Rwanda Africa 1987 44.0 6349365 848.
## 3 Rwanda Africa 1992 23.6 7290203 737.
## 4 Rwanda Africa 1997 36.1 7212583 590.
## 5 Rwanda Africa 2002 43.4 7852401 786.
## 6 Rwanda Africa 2007 46.2 8860588 863.
Am letzten Befehl erkennt man, dass die verschiedenen logischen Ausdrücke mit einem &
verknüpft werden. Will man eine “oder Abfrage” gestalten, so muss diese in einem logischen Ausdruck enthalten sein. So kann man mit nachfolgendem Befehl beispielsweise nach allen Beobachtungen aus Ruanda oder Beobachachtungen nach 1979 fragen:
filter(gapminder, country == "Rwanda" | year > 1979)
## # A tibble: 858 × 6
## country continent year lifeExp pop gdpPercap
## <fct> <fct> <int> <dbl> <int> <dbl>
## 1 Afghanistan Asia 1982 39.9 12881816 978.
## 2 Afghanistan Asia 1987 40.8 13867957 852.
## 3 Afghanistan Asia 1992 41.7 16317921 649.
## 4 Afghanistan Asia 1997 41.8 22227415 635.
## 5 Afghanistan Asia 2002 42.1 25268405 727.
## 6 Afghanistan Asia 2007 43.8 31889923 975.
## 7 Albania Europe 1982 70.4 2780097 3631.
## 8 Albania Europe 1987 72 3075321 3739.
## 9 Albania Europe 1992 71.6 3326498 2497.
## 10 Albania Europe 1997 73.0 3428038 3193.
## # ℹ 848 more rows
Will man einen Vergleich mit mehr als einem Wert durchführen, so kann man natürlich alle Abfragen mit einem |
verknüpfen, oder gleich den %in%
Operator verwenden.
# beobachtungen aus ruanda und afghanistan
filter(gapminder, country %in% c("Rwanda", "Afghanistan"))
## # A tibble: 24 × 6
## country continent year lifeExp pop gdpPercap
## <fct> <fct> <int> <dbl> <int> <dbl>
## 1 Afghanistan Asia 1952 28.8 8425333 779.
## 2 Afghanistan Asia 1957 30.3 9240934 821.
## 3 Afghanistan Asia 1962 32.0 10267083 853.
## 4 Afghanistan Asia 1967 34.0 11537966 836.
## 5 Afghanistan Asia 1972 36.1 13079460 740.
## 6 Afghanistan Asia 1977 38.4 14880372 786.
## 7 Afghanistan Asia 1982 39.9 12881816 978.
## 8 Afghanistan Asia 1987 40.8 13867957 852.
## 9 Afghanistan Asia 1992 41.7 16317921 649.
## 10 Afghanistan Asia 1997 41.8 22227415 635.
## # ℹ 14 more rows
Wir erkennen sofort, dass wir mithilfe von dplyr
sehr leicht den Datensatz aufteilen können, basierend auf der Tatsache ob Bedingungen erfüllt werden oder eben nicht.
Daher solltet ihr unter keinen Umständen mit Befehlen wie diesem
auswahl <- gapminder[241:252, ]
arbeiten.
Warum ist das eine blöde Idee?
Der Befehl dokumentiert sich nicht selbst. Was ist das Besondere an den Zeilen 241 bis 252?
Der Befehl ist fehleranfällig. Diese Codezeile wird zu anderen Ergebnissen führen, wenn jemand die Zeilenreihenfolge von
gapminder
ändert, z.B. die Daten vor diesem Befehl erst sortiert.
Ganz anders verhält es sich mit diesem Befehl
filter(gapminder, country == "Canada")
Er erklärt sich von selbst und ist ziemlich robust.
6.3 Der Pipe-Operator
Bevor es weitergeht, wollen wir aber den Pipe-Operator vorstellen. Dafür gibt es zwei Optionen. Zuerst wurde der Pipe-Operator %>%
eingeführt, den das Tidyverse aus dem magrittr-Paket von Stefan Bache importiert. In Version 4.1 von R wurde auch der native Pipe-Operator |>
eingeführt. Zwischen den beiden Operatoren gibt es einige Unterschiede. Da der neue Pipe-Operator |>
schneller und nicht von einem Pakett abhängig ist, werden wir ihn bevorzugen.

Figure 6.1: Quelle https://en.wikipedia.org/wiki/The_Treachery_of_Images
Mithilfe des Pipe-Operators ist man in der Lage aufeinanderfolgende Befehle von Daten-Operationen strukturiert anzugeben, ohne sie ineinander zu verschachteln. Diese neue Syntax führt zu Code, der viel einfacher zu schreiben und zu lesen ist.
Das entsprechende RStudio Tastenkürzel lautet:
Ctrl+Shift+M (Windows), Cmd+Shift+M (Mac).
Die Standardeinstellung in RStudio ist, dass man mit der obigen Tastenkürzel den Operator %>%
bekommt. Um den neuen Operator |>
zu bekommen, kann man die Tastenkürzel unter Global Options -> Code
umstellen.
Erstmal ein Beispiel
gapminder |> head()
## # A tibble: 6 × 6
## country continent year lifeExp pop gdpPercap
## <fct> <fct> <int> <dbl> <int> <dbl>
## 1 Afghanistan Asia 1952 28.8 8425333 779.
## 2 Afghanistan Asia 1957 30.3 9240934 821.
## 3 Afghanistan Asia 1962 32.0 10267083 853.
## 4 Afghanistan Asia 1967 34.0 11537966 836.
## 5 Afghanistan Asia 1972 36.1 13079460 740.
## 6 Afghanistan Asia 1977 38.4 14880372 786.
Man erkennt sofort, der Befehl ist äquivalent zu head(gapminder)
. Der Pipe-Operator nimmt das Objekt auf der linken Seite und leitet es in den Funktionsaufruf auf der rechten Seite weiter - er gibt es buchstäblich als erstes Argument ein.
Und natürlich kann man der Funktion auf der rechten Seite auch noch weitere Argumente übergeben. Um die ersten 3 Zeilen von gapminder
auszugeben, könnte man head(gapminder, 3)
nutzen oder:
gapminder |> head(3)
## # A tibble: 3 × 6
## country continent year lifeExp pop gdpPercap
## <fct> <fct> <int> <dbl> <int> <dbl>
## 1 Afghanistan Asia 1952 28.8 8425333 779.
## 2 Afghanistan Asia 1957 30.3 9240934 821.
## 3 Afghanistan Asia 1962 32.0 10267083 853.
Der bisherige Einsatz des Pipe-Operators |>
war sicherlich noch nicht sehr beeindruckend, aber das sollte sich noch ändern.
6.4 Mit select()
Variablen auswählen
Verwendet select()
, um aus den Daten verschiedene Variablen (Spalten) auszuwählen. Hier kommt eine typische Verwendung von select()
:
select(gapminder, year, lifeExp)
## # A tibble: 1,704 × 2
## year lifeExp
## <int> <dbl>
## 1 1952 28.8
## 2 1957 30.3
## 3 1962 32.0
## 4 1967 34.0
## 5 1972 36.1
## 6 1977 38.4
## 7 1982 39.9
## 8 1987 40.8
## 9 1992 41.7
## 10 1997 41.8
## # ℹ 1,694 more rows
und nun noch kombiniert mit head()
über den Pipe-Operator:
gapminder |>
select(year, lifeExp) |>
head(4)
## # A tibble: 4 × 2
## year lifeExp
## <int> <dbl>
## 1 1952 28.8
## 2 1957 30.3
## 3 1962 32.0
## 4 1967 34.0
Der letzte Befehl nochmal in Worten:
“Nimm gapminder
, wähle die Variablen year
und lifeExp
und zeige dann die ersten 4 Zeilen an.”
Natürlich kann man all diese Operationen auch mit R Standardbefehlen ausführen. Die dplyr
Befehle haben aber klare Vorteile bei der Lesbarkeit des Codes, wie man im nächsten Beispiel sieht.
Wir wählen aus dem gapminder
Datensatz die Variablen year
und lifeExp
der Kambodscha Beobachtungen
gapminder |>
filter(country == "Cambodia") |>
select(year, lifeExp)
## # A tibble: 12 × 2
## year lifeExp
## <int> <dbl>
## 1 1952 39.4
## 2 1957 41.4
## 3 1962 43.4
## 4 1967 45.4
## 5 1972 40.3
## 6 1977 31.2
## 7 1982 51.0
## 8 1987 53.9
## 9 1992 55.8
## 10 1997 56.5
## 11 2002 56.8
## 12 2007 59.7
Das gleiche Ergebnis würde man mit diesem R Standardbefehl erhalten:
gapminder[gapminder$country == "Cambodia", c("year", "lifeExp")]
## # A tibble: 12 × 2
## year lifeExp
## <int> <dbl>
## 1 1952 39.4
## 2 1957 41.4
## 3 1962 43.4
## 4 1967 45.4
## 5 1972 40.3
## 6 1977 31.2
## 7 1982 51.0
## 8 1987 53.9
## 9 1992 55.8
## 10 1997 56.5
## 11 2002 56.8
## 12 2007 59.7
Ich hoffe, ihr stimmt mir zu, dass der dplyr
Befehl deutlich leichter zu lesen ist.
6.5 select() Hilfsfunktionen
Der gapminder
Datensatz ist klein und damit leicht überschaubar. Daher ist eine strukturierte Auswahl von Variablen hier nicht notwendig. In größeren Datensätzen kann dies aber ganz anders sein. Dort bietet es sich an mit Hilfsfunktionen wie
:
wählt einen Bereich von Spalten aus-
wählt alle Spalten außer …starts_with()
wählt alle Spalten, die mit … startenends_with()
wählt alle Spalten, die mit … endencontains()
wählt alle Spalten, die … enthaltenmatches()
wählte alle Spalten, die den regulären Ausdruck … enthalten…
zu arbeiten.
6.6 Pure, predictable, pipeable
Bisher haben wir nur etwas an der Oberfläche von dplyr
gekratzt, trotzdem möchten wir auf ein Schlüsselprinzip hinweisen, das du mit der Zeit schätzen lernen wirst.
Die Verben (Hauptfunktionen) von dplyr, wie z.B. filter()
und select()
, sind pure functions. Dazu schreibt Hadley Wickham im Kapitel Functions in seinem Advanced R Buch (2019):
The functions that are the easiest to understand and reason about are pure functions: functions that always map the same input to the same output and have no other impact on the workspace. In other words, pure functions have no side effects: they don’t affect the state of the world in any way apart from the value they return.
Tatsächlich sind diese Verben ein Spezialfall reiner Funktionen: sie nehmen als Input und Output denselben Objekttyp an, i.d.R. ein data frame.
Die Daten sind für all diese Funktionen auch stets das erste Inputargument.
6.7 Aufgabe
Die dplyr
Einführung geht weiter im Kapitel Mehr zu dplyr
. Bearbeitet aber vorher den letzten Abschnitt des Work with Data Primers:
Deriving Information with dplyr zeigt euch wie ihr über bestehenden Variablen neue Variablen definiert und leicht zusammenfassende Statistiken innerhalb vorab definierter Gruppen berechnet.
learnr::run_tutorial("deriving", package = "idsst.rtutorials")