Zuerst müssen wir erneut tidyverse und
nycflights13 laden, da wir eine neue Session angefangen
haben:
library(tidyverse)Registered S3 methods overwritten by 'dbplyr':
method from
print.tbl_lazy
print.tbl_sql
── Attaching packages ──────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.2 ──✔ ggplot2 3.3.6 ✔ purrr 0.3.4
✔ tibble 3.1.8 ✔ dplyr 1.0.10
✔ tidyr 1.2.1 ✔ stringr 1.4.1
✔ readr 2.1.3 ✔ forcats 0.5.2 ── Conflicts ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
library(nycflights13)Wenn Sie einen sehr großen Datensatz haben und sich auf nur wenige
Variablen fokussieren wollen, bietet sich die Funktion
select() an, mit dessen Hilfe sich einzelne oder mehrere
Variablen aus einem Dataframe entnehmen lasen:
select(flights, year, month, day)Sie sehen hier, dass sich aus flights mit der
select() Funktion die drei Spalten für das Datum entnehmen
lassen.
Wenn Sie mehrere Spalten zwischen zwei Variablen entnehmen wollen,
wird : verwendet (= von:bis):
select(flights, year:day)Ebenfalls lassen sich alle Spalten außer eine Auswahl betrachten:
select(flights, -(year:day))Hier werden alle Variablen außer year,
month und day betrachtet.
Es kann auch eine konkrete Auswahl an Variablen entfernt werden:
# Variablen einzeln entfernen:
select(flights, -arr_delay, -year, -day)# oder Variablen gemeinsam entfernen:
select(flights, -c(sched_dep_time, arr_delay, year))Je nach Bedarf oder Präferenz gibt es hier mehrere Möglichkeiten, Spalten des Dataframes auszuwählen.
Zusätzlich gibt es noch weitere Hilfsfunktionen für
select():
starts_with("x"): Entnehme Variablen, die mit
"x" beginnenselect(flights, starts_with("time")) # Wähle alle Variablen, die mit "time" beginnenends_with("x"): Entnehme Variablen, die mit
"x" endenselect(flights, ends_with("delay")) # Wähle alle Variablen, die mit "delay" endencontains("x"): Entnehme Variablen, die "x"
enthaltenselect(flights, contains("time")) # Wähle alle Variablen, in denen irgendwo "time" stehtmatches("(.)\\1"): Entnehme Variablen, die einem
bestimmten Pattern entsprechen (hier: wiederholte Zeichen).
Zeichenketten behandeln wir hier nicht explizit.
num_range("x", 1:4): Entnehme Variablen, die
x1, x2 und x3 entsprechen.
Besonders wichtig, wenn Spalten fortlaufend benannt wurden:
billboardselect(billboard, num_range("wk", 8:12))Um Variablen umzubenennen lässt sich neben select() die
Funktion rename() verwenden:
rename(flights, jahr = year)Wenn Sie Veränderungen durch filter(),
select(), arrange() oder rename()
übernehmen wollen, können Sie entweder Ihren alten Dataframe
überschreiben oder das Ergebnis in neuen Variablen speichern:
fluege <- rename(flights, jahr = year, monat = month, tag = day) # und so weiter ...
fluege # die Variable fluege enthält nun die ÄnderungenEin weiterer nützlicher Aspekt ist, dass sich mit
select() auch die Reihenfolge der Spalten ändern lässt.
Wenn wir beispielsweise mehrere Variablen an die ersten Stellen
bringen wollen, hilft uns select() und
everything():
select(flights, time_hour, air_time, distance, everything())Die Funktion everything() fügt die restlichen Spalten
hinzu.
Neben der Auswahl von Spalten mit select() und dessen
Filterung mit filter() können Sie auch Reihen direkt anhand
eines Indexes auswählen:
# Reihen direkt durch Index auswählen
slice(flights, 1, 3, 5)# Reihen durch Vektor auswählen
rows <- c(55, 123, 21)
slice(flights, rows)# Alle Reihen (möglichen) auswählen:
rows <- c(1, 99999999, 5)
slice(flights, rows)Alle slice() Operationen lassen sich durch
filter() und row_number() ersetzen:
# Eine Reihe:
slice(flights, 1)filter(flights, row_number() == 1)
# Mehrere Reihen:
slice(flights, 11, 33, 12301)filter(flights, row_number() %in% c(11, 33, 12301))Nützlich sind auch die weiteren slice Operationen:
slice_min(n = x): Entnehme die x
kleinsten Reihen
slice_max(n = x): Entnehme die x
kleinsten Reihen
slice_sample(n = x): Entnehme x
zufällige Reihen
slice_head(n = x): Entnehme die x
ersten Reihen
slice_tail(n = x): Entnehme die x
letzten Reihen
Mit mutate() lassen sich im Gegensatz zu
select() neue Spalten hinzufügen. In den meisten Fällen
sind diese neuen Variablen Berechnungen aus anderen Variablen. Hierzu
betrachten wir eine Kurzfassung des Datensatzes
flights:
# Variablen aus flights entnehmen:
fluege_kurz <- select(flights, year:day, ends_with("delay"), distance, air_time)
# Variablen umbenennen:
fluege_kurz <- rename(fluege_kurz, jahr = year, monat = month, tag = day,
abflug_delay = dep_delay, ankunft_delay = arr_delay,
distanz = distance, flugzeit = air_time)
fluege_kurzMit mutate() lassen sich dann wie folgt neue Variablen
anfügen:
# Zwei neue Variablen anhängen:
mutate(fluege_kurz,
zeitgewinn = abflug_delay - ankunft_delay, # wie viel Zeit wird durch den Flug "gewonnen"?
geschwindigkeit = distanz / flugzeit * 60 # durchschnittliche Geschwindigkeit des Flugzeugs (Meilen/Stunde)
)Beide Variablen zeitgewinn und
geschwindigkeit liegen nun als neue Spalten vor.
Es lassen sich ebenfalls mit mutate() schrittweise neue
Variablen erstellen:
mutate(fluege_kurz,
zeitgewinn = abflug_delay - ankunft_delay,
stunden = flugzeit / 60,
zeitgewinn_pro_stunde = zeitgewinn / stunden
)In diesem Beispiel hängt die neue Spalte
zeitgewinn_pro_stunde von den vorherigen Spalten
zeitgewinn und stunden ab, obwohl diese noch
nicht in dem Dataframe existieren.
Um nur die neuen Spalten zu behalten, wird an Stelle der
mutate() Funktion transmute() verwendet:
transmute(fluege_kurz,
zeitgewinn = abflug_delay - ankunft_delay,
stunden = flugzeit / 60,
zeitgewinn_pro_stunde = zeitgewinn / stunden
)mutate()Für mutate() können eine Vielzahl an Operationen und
Funktionen verwendet werden, um neue Spalten einzufügen. Die
Voraussetzung hierfür ist jedoch, dass diese Funktionen/Operationen
Vektoren entgegennehmen (Variablen) und Vektoren derselben Größe
wiedergeben:
+, -, *, /, ^.flugzeit / 60:# Hier wird der einzelne Wert erweitert:
vektor <- c(1, 3, 5)
3 * vektor[1] 3 9 15
# Hier wird dasselbe als vollständige Vektoren durchgeführt:
drei <- c(3, 3, 3)
drei * vektor[1] 3 9 15
%/% und dessen Rest
%%:transmute(flights,
dep_time,
stunde = dep_time %/% 100, # Gibt den (abgerundeten) ganzzahligen Wert der Division wieder
minute = dep_time %% 100 # Der Rest der ganzzahligen Division
)log(), log2(),
log10(). Sie sind insbesondere nützlich, um Datensätze
anzuzeigen, bei denen Werte sehr stark auseinanderliegen:ggplot(data = diamonds) +
geom_point(mapping = aes(x = carat, y = price))ggplot(data = diamonds) +
geom_point(mapping = aes(x = carat, y = log2(price)))lead() und lag()
können die Werte aus Vektoren um eine Position nach vorne bzw. hinten
ziehen.(x <- c(1, 2, 3, 5, 8, 13, 21))[1] 1 2 3 5 8 13 21
lag(x)[1] NA 1 2 3 5 8 13
lead(x)[1] 2 3 5 8 13 21 NA
# Differenzen zum vorherigen Wert (delta):
x - lag(x)[1] NA 1 1 2 3 5 8
cumsum(),
cumprod(), cummin(), cummax() und
cummean().x[1] 1 2 3 5 8 13 21
cumsum(x)[1] 1 3 6 11 19 32 53
cummean(x)[1] 1.000000 1.500000 2.000000 2.750000 3.800000 5.333333 7.571429
Logische Vergleiche: <, <=,
>, >=, != und
==. Für komplexere Vergleiche lohnt es sich
Zwischenergebnisse zu speichern.
Rangordnungen: den Rang eines Wertes gemäß der Größe bestimmen
(größter, zweitgrößter, usw.). Hierfür kann min_rank()
verwendet werden, wobei der kleinste Wert den Rang 1 hat. In Kombination
mit desc() hat umgekehrt der kleinste Rang den größten
Wert:
(irgendein_name <- c(3, 2, NA, 5, 4))[1] 3 2 NA 5 4
min_rank(irgendein_name)[1] 2 1 NA 4 3
min_rank(desc(irgendein_name))[1] 3 4 NA 1 2
Der letzte Befehl, den wir hier behandeln wollen ist
summarise(). Dem Namen entsprechend werden hierbei Spalten
zusammengefasst durch einen einzelnen Wert wie z. B. den Mittelwert:
summarise(flights, durchschittlicher_ankunfts_delay = mean(arr_delay, na.rm = TRUE))In diesem Beispiel sehen wir nun die durchschnittliche Verspätung bei der Ankunft, wenn wir alle Flüge zusammen betrachten. Das ist erst einmal nicht allzu nützlich und hätte wohlmöglich einfacher so berechnet werden können:
mean(flights$arr_delay, na.rm = TRUE)[1] 6.895377
summarise() ist erst dann nützlich, wenn es zusammen mit
group_by() verwendet wird. Durch die Verwendung von
group_by() wird der Dataframe bezüglich der einzelnen
Gruppen einer oder mehrere Variablen betrachtet (gruppiert). Nachdem der
Dataframe gruppiert wurde, werden die darauf folgenden Operationen immer
auf alle Gruppen separat angewendet:
nach_datum_gruppiert <- group_by(flights, year, month, day)
nach_datum_gruppiertsummarise(nach_datum_gruppiert, avg_arr_delay = mean(arr_delay, na.rm = TRUE))`summarise()` has grouped output by 'year', 'month'. You can override using the `.groups` argument.
Nun erhalten wir für jeden Tag den Durchschnittswert der Ankunftsverspätungen.
Um die Nützlichkeit hervorzuheben, überlegen Sie sich, wie Sie ohne
die Hilfe von group_by() diese Durchschnittswerte erhalten
würden:
# Der erste Tag:
tag_eins <- filter(flights, month == 1, day == 1)
mean(tag_eins$arr_delay, na.rm = TRUE)[1] 12.65102
# Der zweite Tag:
tag_zwei <- filter(flights, month == 1, day == 2)
mean(tag_zwei$arr_delay, na.rm = TRUE)[1] 12.69289
# Und so weiterNaiv müssten Sie für jeden einzelnen Tag die
filter() Funktion verwenden …
Es gibt zwar verschiedene Ansätze, um dies zu lösen, aber
group_by() und summarise() sind hierfür sehr
angenehm
%>%:Sie haben bereits gesehen, dass in Analysen mit mehreren Schritten Zwischenergebnisse gespeichert werden müssen:
In manchen Fällen ist das sehr sinnvoll, wenn die Zwischenergebnisse neue Informationen liefern
Häufig verlangsamt das jedoch die Datenanalyse, wenn
Zwischenschritte nicht relevant sind
Namen für Variablen der Zwischenschritte gegeben werden müssen
# Erst Verkleinern:
flights_kurz <- select(flights, year:day, contains("delay"), distance, air_time)
# Dann Umbenennen:
fluege_kurz <- rename(flights_kurz, jahr = year, monat = month, tag = day,
abflug_delay = dep_delay, ankunft_delay = arr_delay,
distanz = distance, flugzeit = air_time)
# Dann Gruppieren:
fluege_kurz_nach_datum_gruppiert <- group_by(fluege_kurz, jahr, monat, tag)
# Dann Zusammenfassen:
summarise(fluege_kurz_nach_datum_gruppiert, durchschnit_ankunft_delay = mean(ankunft_delay, na.rm = TRUE))`summarise()` has grouped output by 'jahr', 'monat'. You can override using the `.groups` argument.
Obwohl einige der Zwischenergebnisse (Variablen) vermutlich nicht weiter benutzt werden würden, wurden sie trotzdem erstellt. Das macht die Analyse etwas mühsamer und zeitaufwendiger.
Stattdessen bietet es sich an, nur die Variablen zwischenzuspeichern, die auch tatsächlich weiter verwendet werden.
Eine Möglichkeit wäre das Zusammenbringen der Funktionen, dessen Zwischenergebnisse nicht gebraucht werden.
Aus dem vorherigen Beispiel mit summarise() und
group_by() lassen sich auch beide Funktion wie folgt
verbinden:
# Innerhalb von summarise
summarise(group_by(fluege_kurz, jahr, monat, tag), durchschnit_ankunft_delay = mean(ankunft_delay, na.rm = TRUE))`summarise()` has grouped output by 'jahr', 'monat'. You can override using the `.groups` argument.
Zuerst wird group_by() ausgeführt und das Ergebnis
direkt an summarise() weitergegeben
Das wird schnell sehr unübersichtlich und unverständlich
Die Lösung hierfür ist der Pipe-Operator
%>%
Funktionen lassen sich mit Hilfe von %>% nacheinander
ausführen:
fluege_kurz <- select(flights, year:day, contains("delay"), distance, air_time) %>%
rename(jahr = year, monat = month, tag = day,
abflug_delay = dep_delay, ankunft_delay = arr_delay,
distanz = distance, flugzeit = air_time)
fluege_kurzSie sehen, dass nacheinander erst die Variablen aus
flights ausgewählt wurden und diese dann direkt umbenannt
wurden ohne Zwischenergebnis:
Der Pipe-Operator %>% lässt sich
umgangssprachlich auch als “und dann” formulieren: wähle
erst die Variablen aus und dann bennene die
Variablen um.
Dieser Operator ist eine der wichtigsten Komponenten von
tidyverse (mit Ausnahme von ggplot2, welches
+ verwendet)
%>% im Detail: durch den Operator wird der Teil,
der links steht, in den ersten Parameter der Funktion, welche rechts
steht “eingefügt”. Zuerst wird der linke Teil ausgewertet, dann das
Ergebnis an den rechten Teil weitergegeben und ausgewertet. Das
Verfahren lässt sich beliebig fortsetzen. Somit sind
x %>% f(y) und f(x, y) dasselbe aber auch
x %>% f(y) %>% g(z) und g(f(x, y), z),
usw.
Betrachten wir ein weiteres Beispiel. Angenommen es soll die durchschnittliche Distanz mit der durchschnittlichen Verspätung für die einzelnen Zielflughäfen verglichen werden. Dann könnten die Schritte zur Analyse so aussehen:
Gruppiere den Datensatz nach den Zielflughäfen
Fasse die Daten zusammen und berechne die durchschnittliche Distanz, die durchschnittliche Verspätung und zähle die Anzahl der Flüge pro Zielflughafen
Entferne störende Datenpunkte wie z. B. den Flughafen von Honolulu, welcher besonders weit entfernt ist
Schritt für Schritt können diese Punkte “abgearbeitet werden”:
# Gruppierung nach dem Zielflughafen:
by_dest <- group_by(flights, dest)
# Für jeden Flughafen die Werte zusammenfassen:
delay <- summarise(by_dest,
count = n(), # = Anzahl der Einträge zählen
dist = mean(distance, na.rm = TRUE),
delay = mean(arr_delay, na.rm = TRUE)
)
delay# Ausreißer filtern:
delay <- filter(delay, count > 20, dest != "HNL")
delay# Die Verspätung scheint bis zu ~700 Meilen zu steigen und danach zu fallen
# Ob Verspätungen sich durch einen schnelleren Flug bei längerer Strecke ausgleichen lassen können?
ggplot(data = delay, mapping = aes(x = dist, y = delay)) +
geom_point(aes(size = count), alpha = 1/3) +
geom_smooth(se = FALSE)Mit dem %>% können wir hierbei den Fokus auf die
Transformationen legen, denn die Zwischenergebnisse interessieren uns
nicht:
delays <- flights %>%
group_by(dest) %>%
summarise(
count = n(),
dist = mean(distance, na.rm = TRUE),
delay = mean(arr_delay, na.rm = TRUE)
) %>%
filter(count > 20, dest != "HNL")
delaysAuch hier werden die einzelnen Schritte durch %>%
nacheinander ausgeführt.
Wie gewohnt soll in diesem Abschnitt ein weiterer Datensatz betrachtet werden.
Der Datensatz weather aus der Bibliothek
nycflights13 beschreibt Wetterdaten aus dem Jahr 2013
bezogen auf drei Flughäfen:
library(nycflights13)
library(tidyverse)
weatherWie gewohnt erfahren Sie mehr über den Datensatz, wenn Sie
?weather eingeben.
year, month, day,
hour und time_hour zu entnehmen.wind im Namen
haben.temp auszuwählen.
Was stellen Sie fest?any_of() und
all_of() in Bezug zu select() nach. Wie könnte
Ihnen any_of() mit dem folgenden Vektor helfen?vars <- c("jahr", "month", "day", "Wind")select(weather, contains("hOuR"))1,609344).wind_speed und
wind_gust. Welche Information erhalten Sie hieraus?min_rank() Funktion zu verwenden. Überlegen
Sie sich mehrere Wege, dieses Problem anzugehen. Wie handhaben Sie
Einträge mit denselben Rängen (gleiche Windgeschwindigkeit)?origin) die höchste
Temperatur, die höchste Luftfeuchtigkeit und den kleinsten Luftdruck
aus.wind_speed) aus.