Datenaufbereitung mit tidyverse:
Transformation und Anreicherung

Data Science 1 - Programmieren & Visualisieren

Saskia Otto & Monika Eberhard

Universität Hamburg, IMF

Wintersemester 2024/2025

Lernziele

Am Ende dieser VL- und Übungseinheit werden Sie

  • … die grundlegenden Kenntnisse der Datensatztransformation, -aggregation und -kombination entsprechend einer Fragestellung beherrschen.
  • Dazu gehört
    • das Sortieren und Filtern von Zeilen,
    • das Auswählen und die Neuerstellung von Spalten, sprich Variablen,
    • das Aggregieren und die (Gruppen-spezifische) Erstellung von deskriptiven Statistiken und
    • das Kombinieren von relationalen Daten.

Zur Erinnerung

Die 3 Komponenten des ‘Data Wrangling’

Kernfunktionen von dplyr

Typische Manipulationen Kernfunktion in ‘dplyr’
Manipuliere Beobachtungen (Zeilen) filter(), arrange()
Manipuliere Variablen (Spalten) select(), rename(), mutate(), transmute(),
Fasse Beobachtungen zusammen summarise()
Gruppiere Beobachtungen group_by(), ungroup()
Kombiniere Tabellen bind_rows, bind_cols und join_ Funktionen

Eine Demonstration mit Wachstumsinformation von 5 Fischarten

fish_growth <- tibble(
  Species = c("Gadus morhua", "Platichthys flesus", "Pleuronectes platessa",
    "Merlangius merlangus", "Merluccius merluccius"),
  Linf = c(110, 40.8, 54.4, 41.3, 81.7),
  K = c(0.4, 0.4, 0.1, 0.2, 0.1)
)

(‘Linf’ = mittlere maximale Länge, ‘K’ = Rate in der sich der Fisch an ‘Linf’ annähert)

Mit freundlicher Genehmigung der Fotografen von fishbase.org (Konstantinos I. Stergiou, Jim Greenfield) und uwphoto.no(Rudolf Svensen).

Transformation: Zeilen- und Spaltenmanipulation

Zeilenmanipulation | Sortieren

arrange() → sortiert Zeilen (also Beobachtungen) nach spezifischen Variablen:

Zeilenmanipulation | Filtern

filter() → extrahiert Reihen, die ein logisches Kriterium erfüllen:

Zeilenmanipulation | Verknüpftes Filtern

Verknüpfte Abfragen in filter() werden mit Kommata aufgeführt (UND-Operator):

Spaltenmanipulation | Variablenauswahl

select() → extrahiert Spalten nach Namen oder mittels Helferfunktion:

Spaltenmanipulation | Helferfunktion 1

Übersicht der Helferfunktionen für select()

Quelle: ältere Version des sog. cheatsheets
Data Transformation with dplyr (lizensiert unter CC-BY-SA).

Spaltenmanipulation | Namensänderung

rename() → ändert die Namen einzelner Variablen (neuerName = alterName):

fish_growth2 <- rename(fish_growth, FishSpecies = Species)
fish_growth2
# A tibble: 5 × 3
  FishSpecies            Linf     K
  <chr>                 <dbl> <dbl>
1 Gadus morhua          110     0.4
2 Platichthys flesus     40.8   0.4
3 Pleuronectes platessa  54.4   0.1
4 Merlangius merlangus   41.3   0.2
5 Merluccius merluccius  81.7   0.1

Spaltenmanip. | Variablenerstellung

mutate() und transmute() → erstellen neue Variablen:

Spaltenmanipulation | Helferfunktion 2

Übersicht der Helferfunktionen für mutate() / transmute()

Sie können jede Berechnung mit einer Variablen machen, solange diese vektorisiert ist:

Quelle: ältere Version des sog. cheatsheets Data Transformation with dplyr (lizensiert unter CC-BY-SA).

Kombinieren von Befehlen

Hilfreiches Tool

Bevor wir jetzt in die Datenmanipulation starten, werden wir ein sehr hilfreiches Werkzeug für verknüpfte Operationen kennenlernen!!!

Der ‘pipe’ Operator

2 Varianten des ‘pipe’ Operators

Das Original: %>%

  • Wurde zunächst durch das Paket ‘magritr’ zur Verfügung gestellt.
  • Ist nun aber auch über alle ‘tidyverse’ Pakete zugänglich.
  • → D.h., es muss erst eines dieser Pakete geladen werden, um den Operator nutzen zu können.

Neuer nativer Pipe Operator: |>

  • Ab der R Version 4.1 gibt es diesen nützlichen Operator auch direkt in base R.
  • → D.h., es muss KEIN Paket extra geladen werden.
  • In diesem Modul (auch in den swirl-Kursen) werden wir NUR mit dem nativen Operator |> arbeiten!

Wozu ein solcher Operator?

Vereinfacht Operationen:

  • Mit|> können diverse Funktionsaufrufe sequenziell miteinander verknüpfen werden, ohne Zwischenobjekte zu erstellen.
  • |> leitet den Inhalt des Objekts auf der linken Seite in den Ausdruck auf der rechten Seite.
  • Weitere Schritte können einfach und überall der Sequenz an Operationen hinzugefügt, entfernt oder auskommentiert werden.

‘Piping’ mit |> | Wie?

‘Piping’ mit |> | Interpretation

Erinnert doch fast an ein Kochrezept, oder?

Shortcut für |>

Kleiner Tipp:

Der Pipe-Operator lässt sich mit dem Shortcut strg (bzw. cmd) + shift + m automatisch schreiben.

Um den nativen Operator standardmäßig zu nutzen, muss vorweg unter

  • ‘Preferences > Code’ bzw.
  • ‘Tools > Global Options..’ > Code
  • bei ‘Use native pipe operator, |> (requires R 4.1+)’

ein Häkchen gesetzt werden!

Your turn …

05:00

Quiz 1-3 | Zeilen- und Spaltenmanipulation

Q1

Wir wollen uns die Verteilung der Kelchblattlänge bei der Art Iris versicolor anschauen:

x <- iris |>
  filter(Species == 'versicolor')
hist(x$Sepal.Length)

Q2

Gibt es eine Beziehung zwischen dem Längen/Breiten-Verhältnis bei Kron- und Kelchblättern der Art Iris setosa? Um dies grafisch untersuchen zu können, transformieren Sie den iris Datensatz:

x <- iris |>
  filter(Species == 'setosa') |>
  mutate(
    sepal_lw = Sepal.Length/Sepal.Width,
    petal_lw = Petal.Length/Petal.Width
  )

plot(x$sepal_lw, x$petal_lw,
  pch = 20, col = "blue",
  xlab="L/B Kelchblatt",
  ylab="L/B Kronblatt"
)

Q3

Erstellen Sie einen ‘tibble’ basierend auf dem iris Datensatz, welcher nur die Spalte ‘Species’ enthält und eine neue (absteigend sortierte) Spalte mit dem Quotienten aus der Kelchblattlänge zu -breite. Der ‘tibble’ soll aber nur die Arten I. versicolor und I. virginica und Quotientenwerte größer 2 enthalten:

iris |>
  mutate(sepal_lw = Sepal.Length/Sepal.Width) |>
  filter(Species %in% c('versicolor', 'virginica'), sepal_lw > 2) |>
  select(Species, sepal_lw) |>
  arrange(desc(sepal_lw)) |>
  as_tibble()
# A tibble: 79 × 2
   Species    sepal_lw
   <fct>         <dbl>
 1 virginica      2.96
 2 versicolor     2.82
 3 virginica      2.75
 4 versicolor     2.74
 5 versicolor     2.73
 6 virginica      2.73
 7 virginica      2.68
 8 virginica      2.64
 9 virginica      2.57
10 virginica      2.53
# ℹ 69 more rows

Transformation: Daten aggregieren

Datenaggregation | Zusammenfassung

summarise() → reduziert Variablen zu Einzelwerten:

Datenaggregation | Helferfunktion

Hilfreiche Funktionen, die zusammenfassen

Quelle: ältere Version des sog. cheatsheets
Data Transformation with dplyr (lizensiert unter CC-BY-SA).

Datenaggregation | ‘Shortcuts’

Es gibt 2 sehr nützliche Funktionen, die man anstelle von summarise() nutzen kann:

  • count() → zur Berechnung der Anzahl an Zeilen
  • distinct() → zum Entfernen von Duplikaten (Zeilen mit komplett gleichem Inhalt in den jeweiligen Spalten)
count()
iris |>
  summarise(n = n())
    n
1 150
# vs.
iris |>
  count()
    n
1 150
distinct()
iris |>
  filter(Species == "setosa") |>
  select(Species, Sepal.Length) |>
  distinct() |>
  as_tibble() # nur zur Anzeige hier
# A tibble: 15 × 2
   Species Sepal.Length
   <fct>          <dbl>
 1 setosa           5.1
 2 setosa           4.9
 3 setosa           4.7
 4 setosa           4.6
 5 setosa           5  
 6 setosa           5.4
 7 setosa           4.4
 8 setosa           4.8
 9 setosa           4.3
10 setosa           5.8
11 setosa           5.7
12 setosa           5.2
13 setosa           5.5
14 setosa           4.5
15 setosa           5.3

→ Wie Sie sehen, wurden die 50 Zeilen auf 15 reduziert.

Datenaggregation | Beispiel iris 1

Berechnung einer Statistik für mehrere Variablen gleichzeitig:

iris |>
  summarise(
    SL_mean = mean(Sepal.Length),
    SW_mean = mean(Sepal.Width),
    PL_mean = mean(Petal.Length),
    PW_mean = mean(Petal.Width)
  )
   SL_mean  SW_mean PL_mean  PW_mean
1 5.843333 3.057333   3.758 1.199333

Viel Schreibarbeit wenn mehr als nur der Mittelwert berechnet werden soll…

Lösung: Die Helferfunktion across()

Berechnung einer Statistik für mehrere Variablen gleichzeitig:

iris |> 
  summarise(
    across(
    .cols = Sepal.Length:Petal.Width, 
    .fns = mean
    )
  )
  Sepal.Length Sepal.Width Petal.Length Petal.Width
1     5.843333    3.057333        3.758    1.199333

Lösung: Die Helferfunktion across() | 2

Berechnung mehrerer deskriptiver Statistiken für alle kontinuierlichen Variablen → hier muss eine Liste dem .fns Argument übergeben werden:

iris |> 
  summarise(
    across(
    .cols = Sepal.Length:Petal.Width, 
    .fns = list(mean = mean, median = median, sd = sd)
    )
  )
  Sepal.Length_mean Sepal.Length_median Sepal.Length_sd Sepal.Width_mean
1          5.843333                 5.8       0.8280661         3.057333
  Sepal.Width_median Sepal.Width_sd Petal.Length_mean Petal.Length_median
1                  3      0.4358663             3.758                4.35
  Petal.Length_sd Petal.Width_mean Petal.Width_median Petal.Width_sd
1        1.765298         1.199333                1.3      0.7622377

Aber moment mal…

…was ist, wenn wir die Statistiken für bestimmte Gruppen (wie hier die Arten) getrennt bestimmen wollen????


Lösung: Operationen gruppenweise durchführen

  • group_by() nimmt eine existierende Tabelle und konvertiert sie in eine gruppierte Tabelle, in der Operationen gruppenweise durchgeführt werden können.
  • ungroup() entfernt Gruppierungen (bei summarise() nicht nötig, aber bei z.B. count()).

Prinzipien der gruppenweisen Operationen

Gruppierte Datenaggregation | iris

iris |>
  group_by(Species) |> # einfach diesen Befehl einschieben 
  summarise(
    across(
    .cols = Sepal.Length:Petal.Width, 
    .fns = mean
    )
  )
# A tibble: 3 × 5
  Species    Sepal.Length Sepal.Width Petal.Length Petal.Width
  <fct>             <dbl>       <dbl>        <dbl>       <dbl>
1 setosa             5.01        3.43         1.46       0.246
2 versicolor         5.94        2.77         4.26       1.33 
3 virginica          6.59        2.97         5.55       2.03 

Gruppierung zeitweise entfernen | iris

iris |>
  # group_by(Species) |> # Befehl auskommentieren
  summarise(
    across(
    .cols = Sepal.Length:Petal.Width, 
    .fns = mean
    )
  )
  Sepal.Length Sepal.Width Petal.Length Petal.Width
1     5.843333    3.057333        3.758    1.199333

Kontrollparameter einbauen | iris

  • Kontrollieren Sie jeden Zwischenschritt zwei-oder dreimal!
  • Überprüfen Sie bei Aggregationen und numerischen Zusammenfassungen immer, ob die richtige Anzahl an Datenpunkten auch eingeflossen ist. Denn dies kann ein Hinweis auf fehlerhaftes Aggregieren sein:
Mit n() die Zeilenzahl anzeigen
iris |>
  group_by(Species) |> 
  summarise(
    PL_median = median(Petal.Length),
    N = n()  # Klammer leer lassen
  )
# A tibble: 3 × 3
  Species    PL_median     N
  <fct>          <dbl> <int>
1 setosa          1.5     50
2 versicolor      4.35    50
3 virginica       5.55    50

Your turn …

05:00

Quiz 4-5 | Datenaggregation

Q4

Berechnen Sie pro Art: Median, Mittelwert, Standardabweichung und Minimalwert für die Kronblattlänge:

iris |>
  group_by(Species) |> 
  summarise(
    pl_median = median(Petal.Length),
    pl_mean = mean(Petal.Length),
    pl_sd = sd(Petal.Length),
    pl_min = min(Petal.Length)
  )
# A tibble: 3 × 5
  Species    pl_median pl_mean pl_sd pl_min
  <fct>          <dbl>   <dbl> <dbl>  <dbl>
1 setosa          1.5     1.46 0.174    1  
2 versicolor      4.35    4.26 0.470    3  
3 virginica       5.55    5.55 0.552    4.5

Q5

Berechnen Sie die Stichprobengröße (= Zeilenanzahl) pro Ernährungsgruppe im ChickWeight Datensatz:

dplyr::glimpse(ChickWeight) # aehnlich wie die str() Funktion
Rows: 578
Columns: 4
$ weight <dbl> 42, 51, 59, 64, 76, 93, 106, 125, 149, 171, 199, 205, 40, 49, 5…
$ Time   <dbl> 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 21, 0, 2, 4, 6, 8, 10, 1…
$ Chick  <ord> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
$ Diet   <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
ChickWeight |>
  group_by(Diet) |>
  count()
# A tibble: 4 × 2
# Groups:   Diet [4]
  Diet      n
  <fct> <int>
1 1       220
2 2       120
3 3       120
4 4       118

Transformation: Datensätze kombinieren

Kombination von Tabellen | 1

Zeilenweise nach Position

bind_rows() → hängt 2 oder mehr Tabellen zeilenweise aneinander:

  • Spalten werden nach Namen abgeglichen und fehlende Spalten werden mit NA aufgefüllt.
  • Weitere Funktionen die zeilenweise verbinden: intersect(), union(), setdiff()

Kombination von Tabellen | 2

Spaltenweise nach Position

bind_cols() → hängt 2 oder mehr Tabellen spaltenweise aneinander:

  • Zeilen werden hier entsprechend ihrer Position verknüpft → für ein Verknüpfung über die Werte nutzen Sie eine der folgenden XXX_join() Funktionen!

Kombination von Tabellen | 3

Zusammenfügung über Werte

XXX_join() → Die Funktionsgruppe verbindet Tabellen basierend auf gleichen Werten in übereinstimmenden Spalten:

  • Je nach übereinstimmenden Tabelleninhalten können dabei nur Spalten, nur Zeilen oder beides aneinandergehängt werden.

Zurück zu unserem daphnia Skript

Was haben wir gerade gelernt?

# ------------------------------ Vorbereitung ----------------------------------

#### Laden von Paketen
library(tidyverse) # laedt 9 Pakete


#### Eigene Funktionen


# -------------------- Import und Datenaufbereitung ----------------------------
# (analog zu Uebungswoche 1)

#### Import von TXT-Datei
# (-> Lektion L01 in DSB-03-Datenaufbereitung oder per Anleitung durchs Tidyversum)

daphnia <- read.table("daphnia.txt", sep = ",", header = TRUE)

#### Datensichtung
# (-> Lektion L02 in DSB-03)

# Betrachtung des Inhalts
daphnia
# View(daphnia)
head(daphnia) # zeigt erste 6 Zeilen (Kopfzeilen)
tail(daphnia) # zeigt letzte 6 Zeilen (Endzeilen)

# Identifikation der Objektklasse (Vektor, Matrix, dataframe,..)
class(daphnia)

# Ausgabe der Anzahl an Zeilen und Spalten
nrow(daphnia) # Anzahl Zeilen
ncol(daphnia) # Anzahl Spalten
dim(daphnia) # Anzahl aller Dimensionen

# Spaltennamen
names(daphnia)

# Datentyp pro Spalte
typeof(daphnia$round)
typeof(daphnia$pop.treatment)

# Alles auf einmal anzeigen lassen
str(daphnia)

# Bei kategorialen Variablen ist es sinnvoll sich alle Gruppen anzeigen zu lassen
unique(daphnia$round) # ist zwar numerisch, aber evtl. gibt es nur wenige Gruppen
unique(daphnia$exp_id)
unique(daphnia$clone)
unique(daphnia$pop.treatment)

# -> man kann diese Funktion auch auf alle Spalten gleichzeitig anwenden:
# mit der map() Funktion aus dem tidyverse Paket 'purrr'
# (bald im Kurs DSB-06-Fortgeschrittene Programmierung)
map(daphnia, ~unique(.))


#### Datentransformation
# (-> siehe Lektion L04-L06 in DSB-03 und L01-L02 in DSB-05-Handling spezieller Datentypen)

# Trennen der Spalte pop.treatment und Umkonvertierung von character zu factor
daphnia <- daphnia |>
  separate(col = "pop.treatment", into = c("population", "treatment"), sep = "/") |>
  mutate(population = as.factor(population), treatment = as.factor(treatment))

# Filtern und Anzahl Zeilen ausgeben
daphnia |>
  filter(population == "popM", treatment == "control", survived_t14 == 0) |>
  count() # -> bei 2 Zeilen trifft die Bedingung zu

Übungsaufgabe

Optionale swirl-Lektionen zur Vertiefung

Kurs DS1-03-Datenaufbereitung oder per Anleitung durchs Tidyversum

  • L05-Transformation mit dplyr: Manipulation von Zeilen und Spalten
  • L06-Transformation mit dplyr: Gruppierte Aggregation
  • L07-Transformation mit dplyr: Datensätze kombinieren

Übungsskript


  • Vervollständigung eines Lückenskripts zur Erkundung und Aggregation des internen Datensatz starwars: DS1_W08_Uebungsskript_Datentransformation.R

Fallstudien

Sie sind jetzt so weit, …

  • ..dass Sie ihre einzelnen Fallstudiendateien über eine gemeinsame Spalte mittels einer der inner_XXX() Funktionen aus dem dplyr Paket zusammenführen können. Dies wird relevant für Aufgabe A2 und alle folgenden bei Fallstudie 1 und 3, Aufgabe B und alle folgenden bei Fallstudie 2, sowie für alle Aufgaben bei Fallstudie 4.
  • Sie können anschließend die numerische Statistik der aggregierten Datensätze durchführen.

Wie fühlen Sie sich jetzt…?

Total konfus?


Lesetipps

  • Kapitel 5 Data transformation zur Datentransformation und
  • Kapitel 18 Pipes zum ‘pipe’ Operator in ‘R for Data Science’
  • Posit und DSB Cheatsheets (s. nächste Folien)

Posit Cheatsheet

Überblick an Funktionen im dplyr Paket

Cheatsheet zum tidyr Paket frei verfügbar unter diesem Link.

DSB Cheatsheet: Basic R functions

Enthält wichtigste Funktionen der Datenaufbereitung

Total gelangweilt?


Dann testen Sie doch Ihr Wissen in folgendem Abschlussquiz…

Abschlussquiz

Bei weiteren Fragen: saskia.otto(at)uni-hamburg.de

Creative Commons License
Diese Arbeit is lizenziert unter einer Creative Commons Attribution-ShareAlike 4.0 International License mit Ausnahme der entliehenen und mit Quellenangabe versehenen Abbildungen.