install.packages("tidyverse") #zentrales Package, mit dem wir arbeiten werden und verschiedene andere Packages wie dplyr beinhaltet
install.packages("palmerpenguins") #unser Beispieldatensatz
library(tidyverse)R Tutorial (Teil 4): Tidyverse
1 Wie funktioniert dieses Tutorial?
Dieses Tutorial ist für alle Studierenden gedacht, die bereits erste Grundlagen in R und RStudio kennengelernt haben und nun lernen möchten, wie man mit Datensätzen in R effizient arbeitet. In der Praxis reicht es meist nicht aus, Daten nur einzulesen und anzuschauen. Bevor man statistische Analysen durchführen kann, müssen Datensätze häufig gefiltert, sortiert, zusammengefasst oder um neue Variablen ergänzt werden. Genau dafür ist das tidyverse besonders nützlich.
Im Zentrum dieses Tutorials steht das Paket dplyr, das Teil des tidyverse ist. Mit dplyr können wir Datensätze sehr übersichtlich bearbeiten. Viele Arbeitsschritte, die in base R umständlich oder schwer lesbar wirken, lassen sich mit dplyr klar strukturieren. Das Ziel dieses Tutorials ist also nicht nur, dass Sie einzelne Befehle kennenlernen, sondern vor allem, dass Sie verstehen, wie man typische Datenmanipulationen in R systematisch aufbaut.
In allen unseren Lehrveranstaltungen werden Sie früher oder später Daten filtern, Variablen auswählen, Kennwerte gruppenweise berechnen oder neue Variablen erzeugen müssen. Das tidyverse ist dafür eines der am häufigsten verwendeten Werkzeuge. Es lohnt sich also sehr, diese Arbeitsweise früh einzuüben.
In diesem Tutorial werden wir Ihnen Schritt für Schritt die wichtigsten Grundlagen erläutern. Dabei werden Sie erneut verschiedene Kästen entdecken:
In den blauen Kästen geben wir nützliche Hinweise, die die Arbeit mit tidyverse und dplyr genauer erläutern.
In den grünen Kästen behandeln wir weiterführende Themen oder praktische Ergänzungen. Diese können Sie beim ersten Lesen bei Bedarf zunächst überspringen.
In den gelben Kästen weisen wir auf typische Fehlerquellen und Missverständnisse hin.
In den orangen Kästen finden Sie kleine Übungsaufgaben. Bitte führen Sie diese selbst in RStudio aus.
Wie benutzen Sie dieses Tutorial?
Wie bei den bisherigen Tutorials gilt auch hier: Man lernt R nicht nur durch Lesen, sondern vor allem durch eigenes Ausprobieren. Es ist völlig normal, dass die neue Syntax am Anfang noch ungewohnt wirkt. Wichtig ist, dass Sie die einzelnen Schritte selbst ausführen und mehrfach wiederholen.
Besonders wichtig ist in diesem Tutorial, dass Sie die Struktur der Befehle verstehen. dplyr arbeitet sehr konsistent: Viele Funktionen haben einen ähnlichen Aufbau und lassen sich gut miteinander kombinieren. Wenn Sie also die Grundidee einmal verstanden haben, werden Ihnen viele spätere Anwendungen deutlich leichter fallen.
Bitte probieren Sie alle Beispiele selbst aus. Gerade bei Datenmanipulation gilt: Verstehen kommt durch Anwenden.
2 Erste Schritte mit tidyverse und dplyr
2.1 Was ist das tidyverse?
tidyverse ist eine Sammlung von R-Paketen, die für typische Schritte der Datenanalyse entwickelt wurden. tidyverse Dazu gehören unter anderem:
readrzum Einlesen von Datenggplot2zur Visualisierungtidyrzum Umformen von Datensätzendplyrzur Datenmanipulation
In diesem Tutorial konzentrieren wir uns vor allem auf dplyr. dplyr
Warum ist dplyr nützlich?
Viele Datenanalysen bestehen aus wiederkehrenden Schritten:
- wir wollen nur bestimmte Zeilen behalten,
- nur bestimmte Spalten auswählen,
- neue Variablen berechnen,
- Daten sortieren,
- oder Kennwerte innerhalb von Gruppen berechnen.
Genau für solche Schritte wurde dplyr entwickelt. Die Befehle sind so aufgebaut, dass sie möglichst gut lesbar sind.
3 Set Up
3.1 Installation und Laden der Pakete
Falls das tidyverse auf Ihrem Computer noch nicht installiert ist, können Sie es mit folgendem Befehl einmalig installieren:
3.2 Unser Beispieldatensatz: penguins
In diesem Tutorial verwenden wir den öffentlichen Datensatz penguins aus dem Paket palmerpenguins. Der Datensatz enthält Messungen an Pinguinen verschiedener Arten und Inseln und eignet sich gut für erste Übungen mit dplyr.
Hinweis
Gerade bei neuen Datensätzen ist es sinnvoll, sich zuerst einen Überblick zu verschaffen: Welche Variablen gibt es? Welche Datentypen liegen vor? Gibt es fehlende Werte?
Datensätze explorieren
Wir schauen uns den Datensatz zunächst an:
library(palmerpenguins)
Attaching package: 'palmerpenguins'
The following objects are masked from 'package:datasets':
penguins, penguins_raw
penguins# A tibble: 344 × 8
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Adelie Torgersen 39.1 18.7 181 3750
2 Adelie Torgersen 39.5 17.4 186 3800
3 Adelie Torgersen 40.3 18 195 3250
4 Adelie Torgersen NA NA NA NA
5 Adelie Torgersen 36.7 19.3 193 3450
6 Adelie Torgersen 39.3 20.6 190 3650
7 Adelie Torgersen 38.9 17.8 181 3625
8 Adelie Torgersen 39.2 19.6 195 4675
9 Adelie Torgersen 34.1 18.1 193 3475
10 Adelie Torgersen 42 20.2 190 4250
# ℹ 334 more rows
# ℹ 2 more variables: sex <fct>, year <int>
Für einen ersten Überblick sind auch die folgenden Befehle hilfreich:
str(penguins)tibble [344 × 8] (S3: tbl_df/tbl/data.frame)
$ species : Factor w/ 3 levels "Adelie","Chinstrap",..: 1 1 1 1 1 1 1 1 1 1 ...
$ island : Factor w/ 3 levels "Biscoe","Dream",..: 3 3 3 3 3 3 3 3 3 3 ...
$ bill_length_mm : num [1:344] 39.1 39.5 40.3 NA 36.7 39.3 38.9 39.2 34.1 42 ...
$ bill_depth_mm : num [1:344] 18.7 17.4 18 NA 19.3 20.6 17.8 19.6 18.1 20.2 ...
$ flipper_length_mm: int [1:344] 181 186 195 NA 193 190 181 195 193 190 ...
$ body_mass_g : int [1:344] 3750 3800 3250 NA 3450 3650 3625 4675 3475 4250 ...
$ sex : Factor w/ 2 levels "female","male": 2 1 1 NA 1 2 1 2 NA NA ...
$ year : int [1:344] 2007 2007 2007 2007 2007 2007 2007 2007 2007 2007 ...
head(penguins)# A tibble: 6 × 8
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Adelie Torgersen 39.1 18.7 181 3750
2 Adelie Torgersen 39.5 17.4 186 3800
3 Adelie Torgersen 40.3 18 195 3250
4 Adelie Torgersen NA NA NA NA
5 Adelie Torgersen 36.7 19.3 193 3450
6 Adelie Torgersen 39.3 20.6 190 3650
# ℹ 2 more variables: sex <fct>, year <int>
summary(penguins) species island bill_length_mm bill_depth_mm
Adelie :152 Biscoe :168 Min. :32.10 Min. :13.10
Chinstrap: 68 Dream :124 1st Qu.:39.23 1st Qu.:15.60
Gentoo :124 Torgersen: 52 Median :44.45 Median :17.30
Mean :43.92 Mean :17.15
3rd Qu.:48.50 3rd Qu.:18.70
Max. :59.60 Max. :21.50
NA's :2 NA's :2
flipper_length_mm body_mass_g sex year
Min. :172.0 Min. :2700 female:165 Min. :2007
1st Qu.:190.0 1st Qu.:3550 male :168 1st Qu.:2007
Median :197.0 Median :4050 NA's : 11 Median :2008
Mean :200.9 Mean :4202 Mean :2008
3rd Qu.:213.0 3rd Qu.:4750 3rd Qu.:2009
Max. :231.0 Max. :6300 Max. :2009
NA's :2 NA's :2
library(summarytools)
#Wichtig: Dieser Output erscheint bei Ihnen im "Viewer"
view(dfSummary(penguins))Mehr Infos zum Datensatz: link: penguins
4 dplyr basics
4.1 Der Pipe-Operator (“Dann”-Operator)
Ein zentrales Element des tidyverse ist der sogenannte Pipe-Operator:
%>%Die Grundidee ist, dass das Ergebnis eines Schritts direkt an den nächsten Schritt weitergegeben wird.
Man kann %>% häufig lesen als “und dann…”
Datensatz %>% # und dann
erster_Schritt() %>% # und dann
zweiter_Schritt() %>% # und dann
dritter_Schritt()Durch diesen Pipe-Operator wird der Code oft deutlich lesbarer als bei stark verschachtelte Funktionen.
Vorsicht
Die Pipe steht immer am Ende einer Zeile, nicht am Anfang der nächsten. Also richtig:
penguins %>%
head()und nicht:
penguins
%>% head()Lesen Sie das tidyverse oder dplyr
Führen Sie den folgenden Code aus:
penguins %>%
summary()Ein wichtiger Punkt: dplyr überschreibt per default nicht bestehende Objekte, wenn “Dann”-Schritte zum Einsatz kommen.
Der Pipe-Operator wird zwar angewandt und wir sehen die modifizierten Informationen, aber bei allen Schritten bleibt das Start-Objekt (z.B. Datensatz) unverändert.
Wenn wir mehreren “Dann”-Schritten das modifizierte Objekt abspeichern wollen, müssen wir dies wie gewohnt über eine Zuweisung machen.
# Mit diesem Code wird uns zwar der aufbereitete Datensatz angezeigt,
# aber das Objekt "Datensatz" bleibt in unserer Umgebung völlig angetastet
datensatz %>%
aufbereitungschritt_01 %>%
aufbereitungschritt_02 %>%
aufbereitungschritt_03
# Wollen wir den modifizierten Datensatz "speichern" können wir das
# wie gewohnt tun:
datensatz_clean <- datensatz %>%
aufbereitungschritt_01 %>%
aufbereitungschritt_02 %>%
aufbereitungschritt_03
# In der Regel sollte man nicht ein Objekt in sich selber überschreiben.
# Das hier wäre ein "Anti-Pattern":
# datensatz <- datensatz %>%
# aufbereitungschritt_014.2 Zeilen filtern
Mit filter() können wir Zeilen eines Datensatzes auswählen, die bestimmte Bedingungen erfüllen.
Wenn wir zum Beispiel nur die Pinguine der Art Adelie auswählen wollen, schreiben wir:
penguins %>%
filter(species == "Adelie")# A tibble: 152 × 8
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Adelie Torgersen 39.1 18.7 181 3750
2 Adelie Torgersen 39.5 17.4 186 3800
3 Adelie Torgersen 40.3 18 195 3250
4 Adelie Torgersen NA NA NA NA
5 Adelie Torgersen 36.7 19.3 193 3450
6 Adelie Torgersen 39.3 20.6 190 3650
7 Adelie Torgersen 38.9 17.8 181 3625
8 Adelie Torgersen 39.2 19.6 195 4675
9 Adelie Torgersen 34.1 18.1 193 3475
10 Adelie Torgersen 42 20.2 190 4250
# ℹ 142 more rows
# ℹ 2 more variables: sex <fct>, year <int>
Hier wird jede Zeile darauf geprüft, ob in der Spalte species der Eintrag “Adelie” steht. Achtung: Die Spaltennamen werdenby dplyr innerhalb der Klammer ohne das übliche penguins$... angegeben.
Mehrere Bedingungen
Wir können auch mehrere Bedingungen gleichzeitig verwenden, mit Komma getrennt. Zum Beispiel:
penguins %>%
filter(species == "Adelie", island == "Dream")# A tibble: 56 × 8
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Adelie Dream 39.5 16.7 178 3250
2 Adelie Dream 37.2 18.1 178 3900
3 Adelie Dream 39.5 17.8 188 3300
4 Adelie Dream 40.9 18.9 184 3900
5 Adelie Dream 36.4 17 195 3325
6 Adelie Dream 39.2 21.1 196 4150
7 Adelie Dream 38.8 20 190 3950
8 Adelie Dream 42.2 18.5 180 3550
9 Adelie Dream 37.6 19.3 181 3300
10 Adelie Dream 39.8 19.1 184 4650
# ℹ 46 more rows
# ℹ 2 more variables: sex <fct>, year <int>
Das bedeutet: Behalte nur Zeilen, bei denen species gleich "Adelie" und gleichzeitig island gleich "Dream" ist. Für eine ODER Verknüpfung können wir den |Operator verwenden.
Wir können auch numerische Bedingungen verwenden:
penguins %>%
filter(body_mass_g > 5000)# A tibble: 61 × 8
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Gentoo Biscoe 50 16.3 230 5700
2 Gentoo Biscoe 50 15.2 218 5700
3 Gentoo Biscoe 47.6 14.5 215 5400
4 Gentoo Biscoe 46.7 15.3 219 5200
5 Gentoo Biscoe 46.8 15.4 215 5150
6 Gentoo Biscoe 49 16.1 216 5550
7 Gentoo Biscoe 48.4 14.6 213 5850
8 Gentoo Biscoe 49.3 15.7 217 5850
9 Gentoo Biscoe 49.2 15.2 221 6300
10 Gentoo Biscoe 48.7 15.1 222 5350
# ℹ 51 more rows
# ℹ 2 more variables: sex <fct>, year <int>
Für logische Vergleiche in R verwenden wir == und nicht =.
== prüft, ob zwei Werte gleich sind, = weist einem Argument oder Objekt einen Wert zu.
Das ist derselbe Unterschied wie in den vorherigen Tutorials.
- Wählen Sie alle Pinguine aus, die auf der Insel
"Biscoe"beobachtet wurden. - Wählen Sie alle Pinguine aus, deren Schnabellänge (
bill_length_mm) größer als 45 mm ist. - Wählen Sie alle Pinguine aus, die zur Art
"Gentoo"gehören und schwerer als 5500 g sind.
Lösung
penguins %>%
filter(island == "Biscoe")# A tibble: 168 × 8
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Adelie Biscoe 37.8 18.3 174 3400
2 Adelie Biscoe 37.7 18.7 180 3600
3 Adelie Biscoe 35.9 19.2 189 3800
4 Adelie Biscoe 38.2 18.1 185 3950
5 Adelie Biscoe 38.8 17.2 180 3800
6 Adelie Biscoe 35.3 18.9 187 3800
7 Adelie Biscoe 40.6 18.6 183 3550
8 Adelie Biscoe 40.5 17.9 187 3200
9 Adelie Biscoe 37.9 18.6 172 3150
10 Adelie Biscoe 40.5 18.9 180 3950
# ℹ 158 more rows
# ℹ 2 more variables: sex <fct>, year <int>
penguins %>%
filter(bill_length_mm > 45)# A tibble: 165 × 8
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Adelie Torgersen 46 21.5 194 4200
2 Adelie Torgersen 45.8 18.9 197 4150
3 Adelie Biscoe 45.6 20.3 191 4600
4 Gentoo Biscoe 46.1 13.2 211 4500
5 Gentoo Biscoe 50 16.3 230 5700
6 Gentoo Biscoe 48.7 14.1 210 4450
7 Gentoo Biscoe 50 15.2 218 5700
8 Gentoo Biscoe 47.6 14.5 215 5400
9 Gentoo Biscoe 46.5 13.5 210 4550
10 Gentoo Biscoe 45.4 14.6 211 4800
# ℹ 155 more rows
# ℹ 2 more variables: sex <fct>, year <int>
penguins %>%
filter(species == "Gentoo", body_mass_g > 5500)# A tibble: 28 × 8
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Gentoo Biscoe 50 16.3 230 5700
2 Gentoo Biscoe 50 15.2 218 5700
3 Gentoo Biscoe 49 16.1 216 5550
4 Gentoo Biscoe 48.4 14.6 213 5850
5 Gentoo Biscoe 49.3 15.7 217 5850
6 Gentoo Biscoe 49.2 15.2 221 6300
7 Gentoo Biscoe 50.2 14.3 218 5700
8 Gentoo Biscoe 47.8 15 215 5650
9 Gentoo Biscoe 50 15.3 220 5550
10 Gentoo Biscoe 59.6 17 230 6050
# ℹ 18 more rows
# ℹ 2 more variables: sex <fct>, year <int>
4.3 Spalten auswählen
Mit select() können wir festlegen, welche Spalten eines Datensatzes wir behalten möchten.
Wenn wir zum Beispiel nur Art, Insel und Körpermasse einschließen wollen:
penguins %>%
select(species, island, body_mass_g)# A tibble: 344 × 3
species island body_mass_g
<fct> <fct> <int>
1 Adelie Torgersen 3750
2 Adelie Torgersen 3800
3 Adelie Torgersen 3250
4 Adelie Torgersen NA
5 Adelie Torgersen 3450
6 Adelie Torgersen 3650
7 Adelie Torgersen 3625
8 Adelie Torgersen 4675
9 Adelie Torgersen 3475
10 Adelie Torgersen 4250
# ℹ 334 more rows
Das Ergebnis ist ein Datensatz mit denselben Zeilen wie vorher, aber nur den ausgewählten Spalten.
Wir können mit einem Minuszeichen auch Spalten ausschließen:
penguins %>%
select(-year)# A tibble: 344 × 7
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Adelie Torgersen 39.1 18.7 181 3750
2 Adelie Torgersen 39.5 17.4 186 3800
3 Adelie Torgersen 40.3 18 195 3250
4 Adelie Torgersen NA NA NA NA
5 Adelie Torgersen 36.7 19.3 193 3450
6 Adelie Torgersen 39.3 20.6 190 3650
7 Adelie Torgersen 38.9 17.8 181 3625
8 Adelie Torgersen 39.2 19.6 195 4675
9 Adelie Torgersen 34.1 18.1 193 3475
10 Adelie Torgersen 42 20.2 190 4250
# ℹ 334 more rows
# ℹ 1 more variable: sex <fct>
filter() arbeitet auf Zeilen, select() arbeitet auf Spalten. Diesen Unterschied sollten Sie sich von Anfang an gut einprägen.
Eine große Hilfe können mitunter Sprachmuster bei der Auswahl sein: Wähle alle Variablen, die…
- mit einem bestimmten Präfix starten (
starts_with()) - mit einem bestimmt Suffix enden (
ends_with()) - eine bestimmte Abfolge beinhalten (
contains())
oder auch für bestimmte Datentypen…
- z.B. wähle alle numerischen Variablen (
where(is.numeric))
Wählen Sie nur die Variablen
species,sexundbody_mass_gaus.Entfernen Sie die Variable
yearaus dem Datensatz.Wählen Sie nur
bill_length_mm,bill_depth_mmundflipper_length_mmaus.
Lösung
penguins %>%
select(species, sex, body_mass_g)# A tibble: 344 × 3
species sex body_mass_g
<fct> <fct> <int>
1 Adelie male 3750
2 Adelie female 3800
3 Adelie female 3250
4 Adelie <NA> NA
5 Adelie female 3450
6 Adelie male 3650
7 Adelie female 3625
8 Adelie male 4675
9 Adelie <NA> 3475
10 Adelie <NA> 4250
# ℹ 334 more rows
penguins %>%
select(-year)# A tibble: 344 × 7
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Adelie Torgersen 39.1 18.7 181 3750
2 Adelie Torgersen 39.5 17.4 186 3800
3 Adelie Torgersen 40.3 18 195 3250
4 Adelie Torgersen NA NA NA NA
5 Adelie Torgersen 36.7 19.3 193 3450
6 Adelie Torgersen 39.3 20.6 190 3650
7 Adelie Torgersen 38.9 17.8 181 3625
8 Adelie Torgersen 39.2 19.6 195 4675
9 Adelie Torgersen 34.1 18.1 193 3475
10 Adelie Torgersen 42 20.2 190 4250
# ℹ 334 more rows
# ℹ 1 more variable: sex <fct>
penguins %>%
select(bill_length_mm, bill_depth_mm, flipper_length_mm)# A tibble: 344 × 3
bill_length_mm bill_depth_mm flipper_length_mm
<dbl> <dbl> <int>
1 39.1 18.7 181
2 39.5 17.4 186
3 40.3 18 195
4 NA NA NA
5 36.7 19.3 193
6 39.3 20.6 190
7 38.9 17.8 181
8 39.2 19.6 195
9 34.1 18.1 193
10 42 20.2 190
# ℹ 334 more rows
4.4 Neue Variablen erstellen
Mit mutate() können wir neue Variablen erzeugen oder vorhandene Variablen überschreiben. Wenn es neue Variablennamen sind, werden diese als neue Spalten rechts angehängt.
Angenommen, wir möchten die Körpermasse zusätzlich in Kilogramm statt in Gramm ausdrücken:
penguins %>%
mutate(
body_mass_kg = body_mass_g / 1000,
flipper_length_cm = flipper_length_mm / 10
)# A tibble: 344 × 10
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Adelie Torgersen 39.1 18.7 181 3750
2 Adelie Torgersen 39.5 17.4 186 3800
3 Adelie Torgersen 40.3 18 195 3250
4 Adelie Torgersen NA NA NA NA
5 Adelie Torgersen 36.7 19.3 193 3450
6 Adelie Torgersen 39.3 20.6 190 3650
7 Adelie Torgersen 38.9 17.8 181 3625
8 Adelie Torgersen 39.2 19.6 195 4675
9 Adelie Torgersen 34.1 18.1 193 3475
10 Adelie Torgersen 42 20.2 190 4250
# ℹ 334 more rows
# ℹ 4 more variables: sex <fct>, year <int>, body_mass_kg <dbl>,
# flipper_length_cm <dbl>
Wenn Sie in mutate() einen Namen verwenden, der bereits als Spalte existiert, wird die alte Spalte überschrieben. Das kann sinnvoll sein, passiert aber auch leicht aus Versehen.
Und auch hier greift das Standardverhalten, dass “Dann”-Schritte nicht automatisch gespeichert werden.
Erstellen Sie eine neue Variable
bill_length_cm, in der die Schnabellänge in Zentimetern statt Millimetern gespeichert ist.Erstellen Sie eine neue Variable
heavy_penguin, die TRUE ist, wennbody_mass_ggrößer als 5000 ist, und sonst FALSE.
Lösung
penguins %>%
mutate(bill_length_cm = bill_length_mm / 10)# A tibble: 344 × 9
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Adelie Torgersen 39.1 18.7 181 3750
2 Adelie Torgersen 39.5 17.4 186 3800
3 Adelie Torgersen 40.3 18 195 3250
4 Adelie Torgersen NA NA NA NA
5 Adelie Torgersen 36.7 19.3 193 3450
6 Adelie Torgersen 39.3 20.6 190 3650
7 Adelie Torgersen 38.9 17.8 181 3625
8 Adelie Torgersen 39.2 19.6 195 4675
9 Adelie Torgersen 34.1 18.1 193 3475
10 Adelie Torgersen 42 20.2 190 4250
# ℹ 334 more rows
# ℹ 3 more variables: sex <fct>, year <int>, bill_length_cm <dbl>
penguins %>%
mutate(heavy_penguin = body_mass_g > 5000)# A tibble: 344 × 9
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Adelie Torgersen 39.1 18.7 181 3750
2 Adelie Torgersen 39.5 17.4 186 3800
3 Adelie Torgersen 40.3 18 195 3250
4 Adelie Torgersen NA NA NA NA
5 Adelie Torgersen 36.7 19.3 193 3450
6 Adelie Torgersen 39.3 20.6 190 3650
7 Adelie Torgersen 38.9 17.8 181 3625
8 Adelie Torgersen 39.2 19.6 195 4675
9 Adelie Torgersen 34.1 18.1 193 3475
10 Adelie Torgersen 42 20.2 190 4250
# ℹ 334 more rows
# ℹ 3 more variables: sex <fct>, year <int>, heavy_penguin <lgl>
4.5 Kennwerte berechnen
Mit summarise() berechnen wir Kennwerte über einen gesamten Datensatz. Dies liegt vor, wann immer wir aus vielen Werten einen Wert berechnen wollen (z.B. aus vielen Größenangaben einen Mittelwert oder Standardabweichung).
Zum Beispiel den Mittelwert der Körpermasse:
penguins %>%
summarise(mean_body_mass = mean(body_mass_g, na.rm = TRUE))# A tibble: 1 × 1
mean_body_mass
<dbl>
1 4202.
Hier ist na.rm = TRUE wichtig, weil im Datensatz fehlende Werte (NA) vorkommen. Sie sehen auch, dass der resultierende Datensatz enorm geschrumpft ist: Er hat nun nur noch eine Zeile! Also alle Werte über alle Zeilen wurden durch summarise() zu einem einzigen Wert zusammengefasst.
Wir können auch mehrere Kennwerte gleichzeitig berechnen:
penguins %>%
summarise(
mean_body_mass = mean(body_mass_g, na.rm = TRUE),
sd_body_mass = sd(body_mass_g, na.rm = TRUE),
n = n()
)# A tibble: 1 × 3
mean_body_mass sd_body_mass n
<dbl> <dbl> <int>
1 4202. 802. 344
Die Funktion n() zählt, wie viele Zeilen aktuell im Datensatz enthalten sind. Sie ist besonders in Kombination mit summarise() und group_by() (siehe unten) nützlich.
Vergisst man bei Variablen mit fehlenden Werten das Argument na.rm = TRUE, erhält man oft selbst als Ergebnis wieder nur NA.
Berechnen Sie den Mittelwert der Schnabentiefe (bill_depth_mm). Berechnen Sie den Mittelwert und die Standardabweichung der Flügellänge (flipper_length_mm).
Lösung
penguins %>%
summarise(mean_bill_depth = mean(bill_depth_mm, na.rm = TRUE))# A tibble: 1 × 1
mean_bill_depth
<dbl>
1 17.2
penguins %>%
summarise(
mean_flipper = mean(flipper_length_mm, na.rm = TRUE),
sd_flipper = sd(flipper_length_mm, na.rm = TRUE)
)# A tibble: 1 × 2
mean_flipper sd_flipper
<dbl> <dbl>
1 201. 14.1
4.6 Gruppieren von Daten
Oft möchten wir Kennwerte nicht für den gesamten Datensatz, sondern getrennt nach Gruppen berechnen. Dafür verwenden wir group_by().
Wenn wir zum Beispiel den Mittelwert der Körpermasse getrennt nach Art berechnen möchten:
penguins %>%
group_by(species) %>%
summarise(mean_body_mass = mean(body_mass_g, na.rm = TRUE))# A tibble: 3 × 2
species mean_body_mass
<fct> <dbl>
1 Adelie 3701.
2 Chinstrap 3733.
3 Gentoo 5076.
Jetzt erhalten wir für jede Art einen eigenen Mittelwert, und entsprechend hat der Datensatz nun 3 Zeilen.
Mehrere Gruppierungsvariablen: Wir können auch nach mehreren Variablen gleichzeitig gruppieren. Dann werden alle vorhandenen Kombinationen von Gruppierungsvariablen analysiert:
penguins %>%
group_by(species, sex) %>%
summarise(mean_body_mass = mean(body_mass_g, na.rm = TRUE))`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by species and sex.
ℹ Output is grouped by species.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(species, sex))` for per-operation grouping
(`?dplyr::dplyr_by`) instead.
# A tibble: 8 × 3
# Groups: species [3]
species sex mean_body_mass
<fct> <fct> <dbl>
1 Adelie female 3369.
2 Adelie male 4043.
3 Adelie <NA> 3540
4 Chinstrap female 3527.
5 Chinstrap male 3939.
6 Gentoo female 4680.
7 Gentoo male 5485.
8 Gentoo <NA> 4588.
group_by() allein verändert zunächst nur die Struktur des Datensatzes für nachfolgende Befehle. Den eigentlichen Effekt sehen wir meist erst bei summarise(), mutate() oder ähnlichen Funktionen. Den Hinweis “ℹ Output is grouped by species.” etc. können Sie ignorieren.
Berechnen Sie die mittlere Flügellänge getrennt nach Art.
Berechnen Sie die Anzahl der Beobachtungen getrennt nach Insel.
Berechnen Sie die mittlere Schnabellänge getrennt nach Art und Insel.
Lösung
penguins %>%
group_by(species) %>%
summarise(mean_flipper = mean(flipper_length_mm, na.rm = TRUE))# A tibble: 3 × 2
species mean_flipper
<fct> <dbl>
1 Adelie 190.
2 Chinstrap 196.
3 Gentoo 217.
penguins %>%
group_by(island) %>%
summarise(n = n())# A tibble: 3 × 2
island n
<fct> <int>
1 Biscoe 168
2 Dream 124
3 Torgersen 52
penguins %>%
group_by(species, island) %>%
summarise(mean_bill_length = mean(bill_length_mm, na.rm = TRUE))`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by species and island.
ℹ Output is grouped by species.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(species, island))` for per-operation grouping
(`?dplyr::dplyr_by`) instead.
# A tibble: 5 × 3
# Groups: species [3]
species island mean_bill_length
<fct> <fct> <dbl>
1 Adelie Biscoe 39.0
2 Adelie Dream 38.5
3 Adelie Torgersen 39.0
4 Chinstrap Dream 48.8
5 Gentoo Biscoe 47.5
4.7 Across() and the power of organized labeling
Bisher haben wir Funktionen wie mutate() oder summarise() immer auf eine einzelne Variable angewendet. In der Praxis möchten wir jedoch häufig dieselbe Operation auf mehrere Variablen gleichzeitig anwenden.
Ein typisches Beispiel:
- Mittelwerte für mehrere Variablen berechnen
- mehrere Variablen transformieren (z.B. standardisieren)
- fehlende Werte gleichzeitig behandeln
Genau dafür gibt es in dplyr die Funktion: across()
Die Funktion across() hat zwei zentrale Bestandteile:
- Welche Variablen sollen betroffen sein?
- Welche Funktion soll angewendet werden?
Ein Beispiel:
penguins %>%
summarise(
across(c(bill_length_mm, bill_depth_mm), ~ mean(.x, na.rm = TRUE))
)# A tibble: 1 × 2
bill_length_mm bill_depth_mm
<dbl> <dbl>
1 43.9 17.2
Die Tilde ~ bedeutet hier so viel wie: “Wende die Funktion rechts auf die Variable links an”. Wir können auch mehrere Variablen über Sprach- oder Datentyp-Funktionen auswählen…
penguins %>%
summarise(
across(where(is.numeric),~ mean(.x, na.rm = TRUE))
)# A tibble: 1 × 5
bill_length_mm bill_depth_mm flipper_length_mm body_mass_g year
<dbl> <dbl> <dbl> <dbl> <dbl>
1 43.9 17.2 201. 4202. 2008.
penguins %>%
summarise(
across(ends_with("_mm"), ~ mean(.x, na.rm = TRUE))
)# A tibble: 1 × 3
bill_length_mm bill_depth_mm flipper_length_mm
<dbl> <dbl> <dbl>
1 43.9 17.2 201.
Berechnen Sie den Mittelwert für alle numerischen Variablen im Datensatz.
Berechnen Sie die Standardabweichung (sd) für alle numerischen Variablen.
Erstellen Sie neue Variablen, in denen alle Längenangaben (bill_length_mm, bill_depth_mm, flipper_length_mm) in cm umgerechnet werden.
Berechnen Sie die Mittelwerte aller numerischen Variablen getrennt nach species.
Lösung
penguins %>%
summarise(across(where(is.numeric), mean, na.rm = TRUE))Warning: There was 1 warning in `summarise()`.
ℹ In argument: `across(where(is.numeric), mean, na.rm = TRUE)`.
Caused by warning:
! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.
Supply arguments directly to `.fns` through an anonymous function instead.
# Previously
across(a:b, mean, na.rm = TRUE)
# Now
across(a:b, \(x) mean(x, na.rm = TRUE))
# A tibble: 1 × 5
bill_length_mm bill_depth_mm flipper_length_mm body_mass_g year
<dbl> <dbl> <dbl> <dbl> <dbl>
1 43.9 17.2 201. 4202. 2008.
penguins %>%
summarise(across(where(is.numeric), sd, na.rm = TRUE))# A tibble: 1 × 5
bill_length_mm bill_depth_mm flipper_length_mm body_mass_g year
<dbl> <dbl> <dbl> <dbl> <dbl>
1 5.46 1.97 14.1 802. 0.818
penguins %>%
mutate(across(c(bill_length_mm, bill_depth_mm, flipper_length_mm), ~ .x / 10, .names = "{.col}_cm"))# A tibble: 344 × 11
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Adelie Torgersen 39.1 18.7 181 3750
2 Adelie Torgersen 39.5 17.4 186 3800
3 Adelie Torgersen 40.3 18 195 3250
4 Adelie Torgersen NA NA NA NA
5 Adelie Torgersen 36.7 19.3 193 3450
6 Adelie Torgersen 39.3 20.6 190 3650
7 Adelie Torgersen 38.9 17.8 181 3625
8 Adelie Torgersen 39.2 19.6 195 4675
9 Adelie Torgersen 34.1 18.1 193 3475
10 Adelie Torgersen 42 20.2 190 4250
# ℹ 334 more rows
# ℹ 5 more variables: sex <fct>, year <int>, bill_length_mm_cm <dbl>,
# bill_depth_mm_cm <dbl>, flipper_length_mm_cm <dbl>
penguins %>%
group_by(species) %>%
summarise(across(where(is.numeric), list(~ mean(.x,na.rm=T), ~ sd(.x,na.rm=T))))# A tibble: 3 × 11
species bill_length_mm_1 bill_length_mm_2 bill_depth_mm_1 bill_depth_mm_2
<fct> <dbl> <dbl> <dbl> <dbl>
1 Adelie 38.8 2.66 18.3 1.22
2 Chinstrap 48.8 3.34 18.4 1.14
3 Gentoo 47.5 3.08 15.0 0.981
# ℹ 6 more variables: flipper_length_mm_1 <dbl>, flipper_length_mm_2 <dbl>,
# body_mass_g_1 <dbl>, body_mass_g_2 <dbl>, year_1 <dbl>, year_2 <dbl>
4.8 Sortieren
Mit arrange() können wir einen Datensatz nach einer oder mehreren Variablen sortieren.
Zum Beispiel nach Körpermasse:
# aufsteigend
penguins %>%
arrange(body_mass_g)# A tibble: 344 × 8
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Chinstrap Dream 46.9 16.6 192 2700
2 Adelie Biscoe 36.5 16.6 181 2850
3 Adelie Biscoe 36.4 17.1 184 2850
4 Adelie Biscoe 34.5 18.1 187 2900
5 Adelie Dream 33.1 16.1 178 2900
6 Adelie Torgers… 38.6 17 188 2900
7 Chinstrap Dream 43.2 16.6 187 2900
8 Adelie Biscoe 37.9 18.6 193 2925
9 Adelie Dream 37.5 18.9 179 2975
10 Adelie Dream 37 16.9 185 3000
# ℹ 334 more rows
# ℹ 2 more variables: sex <fct>, year <int>
# absteigend
penguins %>%
arrange(desc(body_mass_g))# A tibble: 344 × 8
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Gentoo Biscoe 49.2 15.2 221 6300
2 Gentoo Biscoe 59.6 17 230 6050
3 Gentoo Biscoe 51.1 16.3 220 6000
4 Gentoo Biscoe 48.8 16.2 222 6000
5 Gentoo Biscoe 45.2 16.4 223 5950
6 Gentoo Biscoe 49.8 15.9 229 5950
7 Gentoo Biscoe 48.4 14.6 213 5850
8 Gentoo Biscoe 49.3 15.7 217 5850
9 Gentoo Biscoe 55.1 16 230 5850
10 Gentoo Biscoe 49.5 16.2 229 5800
# ℹ 334 more rows
# ℹ 2 more variables: sex <fct>, year <int>
- Sortieren Sie den Datensatz nach bill_length_mm.
- Sortieren Sie den Datensatz absteigend nach flipper_length_mm.
Lösung
penguins %>%
arrange(bill_length_mm)# A tibble: 344 × 8
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Adelie Dream 32.1 15.5 188 3050
2 Adelie Dream 33.1 16.1 178 2900
3 Adelie Torgersen 33.5 19 190 3600
4 Adelie Dream 34 17.1 185 3400
5 Adelie Torgersen 34.1 18.1 193 3475
6 Adelie Torgersen 34.4 18.4 184 3325
7 Adelie Biscoe 34.5 18.1 187 2900
8 Adelie Torgersen 34.6 21.1 198 4400
9 Adelie Torgersen 34.6 17.2 189 3200
10 Adelie Biscoe 35 17.9 190 3450
# ℹ 334 more rows
# ℹ 2 more variables: sex <fct>, year <int>
penguins %>%
arrange(desc(flipper_length_mm))# A tibble: 344 × 8
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<fct> <fct> <dbl> <dbl> <int> <int>
1 Gentoo Biscoe 54.3 15.7 231 5650
2 Gentoo Biscoe 50 16.3 230 5700
3 Gentoo Biscoe 59.6 17 230 6050
4 Gentoo Biscoe 49.8 16.8 230 5700
5 Gentoo Biscoe 48.6 16 230 5800
6 Gentoo Biscoe 52.1 17 230 5550
7 Gentoo Biscoe 51.5 16.3 230 5500
8 Gentoo Biscoe 55.1 16 230 5850
9 Gentoo Biscoe 49.5 16.2 229 5800
10 Gentoo Biscoe 49.8 15.9 229 5950
# ℹ 334 more rows
# ℹ 2 more variables: sex <fct>, year <int>
5 Übung: Ein komplexes Beispiel
Bisher haben wir die wichtigsten dplyr-Funktionen einzeln kennengelernt. In der Praxis werden diese Funktionen jedoch fast nie isoliert verwendet. Stattdessen kombiniert man meist mehrere Arbeitsschritte in einer gemeinsamen Analyse-Pipeline.
Im folgenden Beispiel verbinden wir:
filter()select()mutate()across()summarise()arrange()
Angenommen, wir interessieren uns nur für Pinguine der Arten Adelie und Gentoo. Außerdem möchten wir nur einige ausgewählte Variablen betrachten, die Längenangaben zusätzlich in Zentimeter umrechnen, anschließend pro Art Mittelwerte berechnen und das Ergebnis nach der mittleren Körpermasse sortieren.
Der vollständige Code sieht dann so aus:
penguins %>%
filter(species %in% c("Adelie", "Gentoo")) %>%
select(species, island, bill_length_mm, bill_depth_mm, flipper_length_mm, body_mass_g) %>%
mutate(
across(
c(bill_length_mm, bill_depth_mm, flipper_length_mm),
~ .x / 10,
.names = "{.col}_cm"
)
) %>%
group_by(species) %>%
summarise(
across(
c(body_mass_g, bill_length_mm_cm, bill_depth_mm_cm, flipper_length_mm_cm),
mean,
na.rm = TRUE
)
) %>%
arrange(desc(body_mass_g))Verändern Sie das Beispiel auf folgende Weise:
- Betrachten Sie statt “Adelie” und “Gentoo” die Arten “Adelie” und “Chinstrap”.
- Berechnen Sie statt der Mittelwerte die Standardabweichungen.
- Sortieren Sie das Ergebnis nach der mittleren bzw. standardabweichungsbasierten flipper_length_mm_cm.
Lösung
penguins %>%
filter(species %in% c("Adelie", "Chinstrap")) %>%
select(species, island, bill_length_mm, bill_depth_mm, flipper_length_mm, body_mass_g) %>%
mutate(
across(
c(bill_length_mm, bill_depth_mm, flipper_length_mm),
~ .x / 10,
.names = "{.col}_cm"
)
) %>%
group_by(species) %>%
summarise(
across(
c(body_mass_g, bill_length_mm_cm, bill_depth_mm_cm, flipper_length_mm_cm),
sd,
na.rm = TRUE
)
) %>%
arrange(desc(flipper_length_mm_cm))# A tibble: 2 × 5
species body_mass_g bill_length_mm_cm bill_depth_mm_cm flipper_length_mm_cm
<fct> <dbl> <dbl> <dbl> <dbl>
1 Chinstrap 384. 0.334 0.114 0.713
2 Adelie 459. 0.266 0.122 0.654
6 Vieles mehr…
Wie Sie in diesen Beispielfunktionen gesehen haben, sind dplyr und tidyverse ein sehr mächtige Pakete.
Wir können entsprechend unmöglich alle Varianten hervorheben, möchten hier aber noch einige Tipps geben:
- Die Dokumentation von dplyr ist sehr strukturiert mit Beispielen aufbereitet (https://dplyr.tidyverse.org/)
- Oft wollen wir bestimmte Fälle definieren (z.B. if-else). Dies geht am besten über
case_when: (https://dplyr.tidyverse.org/reference/case-and-replace-when.html) - In der Psychologie benötigen wir Daten oftmals im wide oder long Format:
- Manchmal liegen Daten in einem Datensatz
Xund einem DatensatzYvor, die wir über eine bestimmte Matching-Variable zusammenführen müssen. Dazu können wir verschiedenejoinsverwenden (https://dplyr.tidyverse.org/reference/mutate-joins.html)
Für Grafiken möchten wir Ihnen zudem die folgenden Materialien empfehlen: