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" beginnen
ends_with("x")
: Entnehme Variablen, die mit
"x"
endenselect(flights, ends_with("delay")) # Wähle alle Variablen, die mit "delay" enden
contains("x")
: Entnehme Variablen, die "x"
enthaltenselect(flights, contains("time")) # Wähle alle Variablen, in denen irgendwo "time" steht
matches("(.)\\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:
billboard
select(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:
<- rename(flights, jahr = year, monat = month, tag = day) # und so weiter ...
fluege # die Variable fluege enthält nun die Änderungen fluege
Ein 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
<- c(55, 123, 21)
rows slice(flights, rows)
# Alle Reihen (möglichen) auswählen:
<- c(1, 99999999, 5)
rows 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:
<- select(flights, year:day, ends_with("delay"), distance, air_time)
fluege_kurz # Variablen umbenennen:
<- rename(fluege_kurz, jahr = year, monat = month, tag = day,
fluege_kurz abflug_delay = dep_delay, ankunft_delay = arr_delay,
distanz = distance, flugzeit = air_time)
fluege_kurz
Mit 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:
<- c(1, 3, 5)
vektor 3 * vektor
[1] 3 9 15
# Hier wird dasselbe als vollständige Vektoren durchgeführt:
<- c(3, 3, 3)
drei * vektor drei
[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.<- c(1, 2, 3, 5, 8, 13, 21)) (x
[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):
- lag(x) 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:
<- c(3, 2, NA, 5, 4)) (irgendein_name
[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:
<- group_by(flights, year, month, day)
nach_datum_gruppiert nach_datum_gruppiert
summarise(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:
<- filter(flights, month == 1, day == 1)
tag_eins mean(tag_eins$arr_delay, na.rm = TRUE)
[1] 12.65102
# Der zweite Tag:
<- filter(flights, month == 1, day == 2)
tag_zwei mean(tag_zwei$arr_delay, na.rm = TRUE)
[1] 12.69289
# Und so weiter
Naiv 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:
<- select(flights, year:day, contains("delay"), distance, air_time)
flights_kurz # Dann Umbenennen:
<- rename(flights_kurz, jahr = year, monat = month, tag = day,
fluege_kurz abflug_delay = dep_delay, ankunft_delay = arr_delay,
distanz = distance, flugzeit = air_time)
# Dann Gruppieren:
<- group_by(fluege_kurz, jahr, monat, tag)
fluege_kurz_nach_datum_gruppiert # 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:
<- select(flights, year:day, contains("delay"), distance, air_time) %>%
fluege_kurz rename(jahr = year, monat = month, tag = day,
abflug_delay = dep_delay, ankunft_delay = arr_delay,
distanz = distance, flugzeit = air_time)
fluege_kurz
Sie 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:
<- group_by(flights, dest)
by_dest # Für jeden Flughafen die Werte zusammenfassen:
<- summarise(by_dest,
delay 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:
<- filter(delay, count > 20, dest != "HNL")
delay 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:
<- flights %>%
delays group_by(dest) %>%
summarise(
count = n(),
dist = mean(distance, na.rm = TRUE),
delay = mean(arr_delay, na.rm = TRUE)
%>%
) filter(count > 20, dest != "HNL")
delays
Auch 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)
weather
Wie 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?<- c("jahr", "month", "day", "Wind") vars
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.