Kapitel 7 Mehr zu dplyr
7.1 Wo stehen wir?
In Kapitel 6 haben wir bereits zwei sehr wichtige Verben und den Pipe Operator kennengelernt und verwendet:
-
filter()
zur Auswahl spezieller Zeilen eines Datensatzes -
select()
zur Auswahl spezieller Variablen eines Datensatzes - der Pipe-Operator
|>
bzw.%>%
überführt das Objekt auf der linken Seite des Operators in das erste Funktionsargument der Funktion auf der rechten Seite des Aufrufs
Wir haben zudem auch noch die Rolle von dplyr
innerhalb des tidyverse besprochen:
dplyr ist ein Kernpaket der tidyverse Kollektion von Paketen. Da wir die anderen oft beiläufig benutzen, werden wir stets dplyr und die anderen über library(tidyverse)
laden.
Wir starten wieder mit dem Laden von dplyr
(über 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
und gapminder
7.2 Mit mutate()
neue Variablen erstellen
Wir starten mit dem Anlegen einer Kopie von gapminder
, die wir dann nach unseren Vorstellungen verändern (es wäre aber auch nichts passiert, wenn wir alles mit gapminder
durchführen würden; mit dem Befehl gapminder::gapminder
können wir immer auf die Originalversion zurückgreifen).
my_gap <- gapminder
Unser Ziel ist es, dass GDP pro Land anzugeben. Das sollte machbar sein, da schließlich das Pro-Kopf-GDP wie auch die Bevölkerungszahl im Datensatz enthalten sind. Multiplizieren beider Variablen liefert uns das gewünschte Ergebnis.
mutate()
ist eine Funktion, die neue Variablen definiert und in ein tibble einfügt. Dabei können wir auf bestehende Variablen einfach über ihren Namen zugreifen.
my_gap |>
mutate(gdp = pop * gdpPercap)
## # A tibble: 1,704 × 7
## country continent year lifeExp pop gdpPercap gdp
## <fct> <fct> <int> <dbl> <int> <dbl> <dbl>
## 1 Afghanistan Asia 1952 28.8 8425333 779. 6567086330.
## 2 Afghanistan Asia 1957 30.3 9240934 821. 7585448670.
## 3 Afghanistan Asia 1962 32.0 10267083 853. 8758855797.
## 4 Afghanistan Asia 1967 34.0 11537966 836. 9648014150.
## 5 Afghanistan Asia 1972 36.1 13079460 740. 9678553274.
## 6 Afghanistan Asia 1977 38.4 14880372 786. 11697659231.
## 7 Afghanistan Asia 1982 39.9 12881816 978. 12598563401.
## 8 Afghanistan Asia 1987 40.8 13867957 852. 11820990309.
## 9 Afghanistan Asia 1992 41.7 16317921 649. 10595901589.
## 10 Afghanistan Asia 1997 41.8 22227415 635. 14121995875.
## # ℹ 1,694 more rows
Hmmmm … diese GDP-Zahlen sind ziemlich groß und abstrakt. In dem Zusammenhang, bedenke den Ratschlag von Randall Munroe:
One thing that bothers me is large numbers presented without context… “If I added a zero to this number, would the sentence containing it mean something different to me?” If the answer is “no”, maybe the number has no business being in the sentence in the first place.
Vielleicht wäre es doch sinnvoller, wenn wir beim Pro-Kopf-GDP bleiben. Aber was wäre, wenn wir das Pro-Kopf-GDP angeben würden in Relation zu irgendeinem Vergleichsland. Wir könnten alles in Bezug auf die entsprechenden Daten aus Deutschland angeben.
Dazu müssen wir eine neue Variable erstellen, die aus den gdpPercap
Werten , geteilt durch die deutschen gdpPercap
Werte, besteht. Beim Erstellen der Variable müssen wir aber darauf achten, dass wir immer zwei Zahlen teilen, die sich auf dasselbe Jahr beziehen.
Wie können wir das schaffen?
- Beobachtungen für Deutschland in einem Objekt
ger_gap
speichern - Erstellen einer neue temporären Variable
tmp
inmy_gap
, die definiert wird durch:- die
gdpPercap
-Variable ausger_gap
aufrufen - mit
rep()
diegdpPercap
Wert ausger_gap
einmal pro Land immy_gap
reproduzieren, damit ein Vektor entsteht, der die gleiche Anzahl an Beobachtungen wiemy_gap
hat
- die
- Dividieren der
gdpPercap
Werte durch die deutschen Zahlen - Löschen der temporären Variable
tmp
inmy_gap
ger_gap <- my_gap |>
filter(country == "Germany")
my_gap <- my_gap |>
mutate(tmp = rep(ger_gap$gdpPercap, nlevels(country)),
gdpPercapRel = gdpPercap / tmp,
tmp = NULL)
Beachte, dass mutate()
neue Variablen sequentiell erstellt, so dass man auf frühere Variablen (wie tmp
) verweisen kann um spätere Variablen (wie gdpPercapRel
) zu definieren. Nachdem eine Variable nicht mehr benötigt wird, kann man sie einfach auf NULL
setzen.
Bleibt die Frage ob das alles so richtig war. Um diese Frage zu beantworten, können wir uns aber einfach mal die Werte von gdpPercapRel
für Deutschland anschauen. Die sollten besser alle 1 sein!
my_gap |>
filter(country == "Germany") |>
select(country, year, gdpPercapRel)
## # A tibble: 12 × 3
## country year gdpPercapRel
## <fct> <int> <dbl>
## 1 Germany 1952 1
## 2 Germany 1957 1
## 3 Germany 1962 1
## 4 Germany 1967 1
## 5 Germany 1972 1
## 6 Germany 1977 1
## 7 Germany 1982 1
## 8 Germany 1987 1
## 9 Germany 1992 1
## 10 Germany 1997 1
## 11 Germany 2002 1
## 12 Germany 2007 1
Ich glaube, wir können annehmen, dass Deutschland ein Land mit einem “hohen GDP” pro Kopf ist. Daher sollte die Verteilung von gdpPercapRel
auf Werten unter 1 konzentriert sein, möglicherweise sogar weit darunter. Aber besser mal nachschauen ob dem so ist:
summary(my_gap$gdpPercapRel)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.01 0.07 0.19 0.37 0.51 15.17
Die Zahlen des relativen Pro-Kopf-GDP liegen deutlich unter 1. Wir sehen, dass die meisten Länder, die in diesem Datensatz erfasst werden, über den gesamten Zeitraum im Vergleich zu Deutschland ein wesentlich niedrigeres Pro-Kopf-GDP aufweisen.
Tipp: Vertraut niemandem - einschließlich (besonders?) euch selbst. Versucht immer, einen Weg zu finden, um zu überprüfen, ob ihr das gemacht habt, was ihr tun wolltet. Seid aber nicht schockiert, wenn ihr manchmal feststellen müsst, dass dem nicht so ist. 😉
7.3 Mit arrange()
die Zeilenreihenfolge ändern
arrange()
ordnet die Zeilen in einem data frame neu an. Stellen wir uns mal vor, dass wir die Daten nach Jahr und Land und nicht nach Land und Jahr ordnen wollen.
my_gap |>
arrange(year, country)
## # A tibble: 1,704 × 7
## country continent year lifeExp pop gdpPercap gdpPercapRel
## <fct> <fct> <int> <dbl> <int> <dbl> <dbl>
## 1 Afghanistan Asia 1952 28.8 8425333 779. 0.109
## 2 Albania Europe 1952 55.2 1282697 1601. 0.224
## 3 Algeria Africa 1952 43.1 9279525 2449. 0.343
## 4 Angola Africa 1952 30.0 4232095 3521. 0.493
## 5 Argentina Americas 1952 62.5 17876956 5911. 0.827
## 6 Australia Oceania 1952 69.1 8691212 10040. 1.41
## 7 Austria Europe 1952 66.8 6927772 6137. 0.859
## 8 Bahrain Asia 1952 50.9 120447 9867. 1.38
## 9 Bangladesh Asia 1952 37.5 46886859 684. 0.0958
## 10 Belgium Europe 1952 68 8730405 8343. 1.17
## # ℹ 1,694 more rows
Oder vielleicht interessieren euch nur die Daten aus 2007, angeordnet entsprechend der Lebenserwartung.
my_gap |>
filter(year == 2007) |>
arrange(lifeExp)
## # A tibble: 142 × 7
## country continent year lifeExp pop gdpPercap gdpPercapRel
## <fct> <fct> <int> <dbl> <int> <dbl> <dbl>
## 1 Swaziland Africa 2007 39.6 1.13e6 4513. 0.140
## 2 Mozambique Africa 2007 42.1 2.00e7 824. 0.0256
## 3 Zambia Africa 2007 42.4 1.17e7 1271. 0.0395
## 4 Sierra Leone Africa 2007 42.6 6.14e6 863. 0.0268
## 5 Lesotho Africa 2007 42.6 2.01e6 1569. 0.0488
## 6 Angola Africa 2007 42.7 1.24e7 4797. 0.149
## 7 Zimbabwe Africa 2007 43.5 1.23e7 470. 0.0146
## 8 Afghanistan Asia 2007 43.8 3.19e7 975. 0.0303
## 9 Central African Republ… Africa 2007 44.7 4.37e6 706. 0.0219
## 10 Liberia Africa 2007 45.7 3.19e6 415. 0.0129
## # ℹ 132 more rows
Das war nun aber nicht das Ergebnis, welches ihr sehen wolltet. Ihr wolltet eigentlich nach absteigender Lebenserwartung sortieren. Dazu müsst ihr desc()
verwenden.
my_gap |>
filter(year == 2007) |>
arrange(desc(lifeExp))
## # A tibble: 142 × 7
## country continent year lifeExp pop gdpPercap gdpPercapRel
## <fct> <fct> <int> <dbl> <int> <dbl> <dbl>
## 1 Japan Asia 2007 82.6 127467972 31656. 0.984
## 2 Hong Kong, China Asia 2007 82.2 6980412 39725. 1.23
## 3 Iceland Europe 2007 81.8 301931 36181. 1.12
## 4 Switzerland Europe 2007 81.7 7554661 37506. 1.17
## 5 Australia Oceania 2007 81.2 20434176 34435. 1.07
## 6 Spain Europe 2007 80.9 40448191 28821. 0.896
## 7 Sweden Europe 2007 80.9 9031088 33860. 1.05
## 8 Israel Asia 2007 80.7 6426679 25523. 0.793
## 9 France Europe 2007 80.7 61083916 30470. 0.947
## 10 Canada Americas 2007 80.7 33390141 36319. 1.13
## # ℹ 132 more rows
Tipp: Verlasst euch NIEMALS darauf, dass Zeilen oder Variablen in einer bestimmten Reihenfolge stehen. Aber manchmal will man Tabellen anderen präsentieren und dabei macht es durchaus Sinn die Zeilenreihenfolge je nach Fragestellung anzupassen.
7.4 Mit rename()
“schöne” Namen vergeben
Ein paar der Namen in gapminder
sind nicht besonders hübsch, wie z.B. lifeExp
. life expectancy wären ja schließlich zwei Worte und daher finde ich (persönliche Meinung) es schöner, dies auch im Variablennamen zu sehen
my_gap |>
rename(life_exp = lifeExp,
gdp_percap = gdpPercap,
gdp_percap_rel = gdpPercapRel)
## # A tibble: 1,704 × 7
## country continent year life_exp pop gdp_percap gdp_percap_rel
## <fct> <fct> <int> <dbl> <int> <dbl> <dbl>
## 1 Afghanistan Asia 1952 28.8 8425333 779. 0.109
## 2 Afghanistan Asia 1957 30.3 9240934 821. 0.0806
## 3 Afghanistan Asia 1962 32.0 10267083 853. 0.0661
## 4 Afghanistan Asia 1967 34.0 11537966 836. 0.0567
## 5 Afghanistan Asia 1972 36.1 13079460 740. 0.0411
## 6 Afghanistan Asia 1977 38.4 14880372 786. 0.0383
## 7 Afghanistan Asia 1982 39.9 12881816 978. 0.0444
## 8 Afghanistan Asia 1987 40.8 13867957 852. 0.0346
## 9 Afghanistan Asia 1992 41.7 16317921 649. 0.0245
## 10 Afghanistan Asia 1997 41.8 22227415 635. 0.0229
## # ℹ 1,694 more rows
Die Änderungen haben wir jetzt aber nicht abgespeichert (auch wenn sie schön waren), da wir den nachfolgenden Code auch weiterhin ausführen möchten, ohne die Variablennamen entsprechend zu ändern.
Bemerkung: Mit select()
könnten wir bei der Auswahl von Variablen auch deren Namen ändern
my_gap |>
filter(country == "Burundi", year > 1996) |>
select(yr = year, lifeExp, gdpPercap) |>
select(gdpPercap, everything())
## # A tibble: 3 × 3
## gdpPercap yr lifeExp
## <dbl> <int> <dbl>
## 1 463. 1997 45.3
## 2 446. 2002 47.4
## 3 430. 2007 49.6
everything()
wählt alle übrigen (außer gdpPercap
) Variablen. Da gdpPercap
an erster Stelle gewählt wurde, wird die Variable auch zur ersten Spalte.
7.5 summarise()
in Kombination mit group_by()
Nehmen wir mal an, dass uns die Antwort auf die Frage
“In welchem Land ist die Lebenserwartung innerhalb von 5 Jahren am stärksten gesunken?”
interessiert.
dplyr
bietet uns mächtige Hilfsmittel zur Beantwortung der Frage:
group_by()
fügt dem Datensatz eine zusätzliche Struktur hinzu – Gruppierungsinformationen – die die Grundlage für Berechnungen innerhalb der Gruppen bilden.summarise()
nimmt einen Datensatz mit n-Beobachtungen, berechnet die angeforderten Zusammenfassungen und gibt einen Datensatz mit einer Beobachtung (falls nur eine Zusammenfassung angefordert wurde) zurück.mutate()
undsummarise()
berücksichtigen Gruppen.
Kombiniert mit den Verben, die wir bereits kennen, könnt ihr mit diesen neuen Werkzeugen eine extrem vielfältige Reihe von Problemen relativ einfach lösen.
7.5.1 Dinge aufzählen
Beginnen wir mit dem einfachen Zählen. Wie viele Beobachtungen haben wir pro Kontinent?
my_gap |>
group_by(continent) |>
summarise(n = n())
## # A tibble: 5 × 2
## continent n
## <fct> <int>
## 1 Africa 624
## 2 Americas 300
## 3 Asia 396
## 4 Europe 360
## 5 Oceania 24
Lasst uns hier kurz innehalten und über das tidyverse nachdenken. Ihr könntet die gleichen absoluten Häufigkeiten natürlich auch mit table()
berechnen.
table(gapminder$continent)
##
## Africa Americas Asia Europe Oceania
## 624 300 396 360 24
str(table(gapminder$continent))
## 'table' int [1:5(1d)] 624 300 396 360 24
## - attr(*, "dimnames")=List of 1
## ..$ : chr [1:5] "Africa" "Americas" "Asia" "Europe" ...
Das Ergebnis ist ein Objekt der Klasse table
. Dies macht nachfolgende Berechnungen leider etwas kniffliger, als es euch lieb ist. Zum Beispiel ist es schade, dass die Namen der Kontinente nur als Attribute und nicht als richtiger Faktor zusammen mit den berechneten Werten zurückgegeben werden.
Dies ist ein Beispiel dafür, wie das tidyverse Übergänge glättet, bei denen die Ausgabe von Schritt i
die Eingabe von Schritt i + 1
werden soll.
Die tally()
Funktion ist eine Komfortfunktion, die weiß, wie man Zeilen zählt und dabei Gruppen berücksichtigt.
my_gap |>
group_by(continent) |>
tally()
## # A tibble: 5 × 2
## continent n
## <fct> <int>
## 1 Africa 624
## 2 Americas 300
## 3 Asia 396
## 4 Europe 360
## 5 Oceania 24
Die Funktion count()
bietet noch mehr Komfort. Sie kann sowohl gruppieren als auch zählen.
my_gap |>
count(continent)
## # A tibble: 5 × 2
## continent n
## <fct> <int>
## 1 Africa 624
## 2 Americas 300
## 3 Asia 396
## 4 Europe 360
## 5 Oceania 24
Was wäre, wenn uns nicht nur die Anzahl an Beobachtungen pro Kontinent interessiert, sondern auch die Anzahl an unterschiedlichen Ländern pro Kontinent. Dazu bestimmen wir einfach mehrere Zusammenfassungen innerhalb von summarise()
. Dabei verwenden wir die Funktion n_distinct()
, um die Anzahl der einzelnen Länder innerhalb jedes Kontinents zu zählen.
my_gap |>
group_by(continent) |>
summarise(n = n(),
n_countries = n_distinct(country))
## # A tibble: 5 × 3
## continent n n_countries
## <fct> <int> <int>
## 1 Africa 624 52
## 2 Americas 300 25
## 3 Asia 396 33
## 4 Europe 360 30
## 5 Oceania 24 2
7.5.2 Deskriptive Statistiken mit summarise()
In Kombination mit summarise()
können wir eine Vielzahl an verschiedenen Funktionen verwenden. Einige davon berechnen klassische deskriptive Statistiken:
In allen betrachteten Fällen seien x1,…,xn numerische Beobachtungen.
mean()
berechnet das arithmetische Mittel der Beobachtungen ¯xn=1nn∑i=1xi.median()
berechnet den Median x0.5={x(n+12),n ungerade,12(x(n2)+x(n2+1)),n gerade.var()
berechnet die empirische Varianz s2n=1n−1n∑i=1(xi−¯xn)2.sd()
berechnet die empirische Standardabweichung sn=√s2n.IQR()
berechnet den Interquartilsabstand IQR=x0.75−x0.25, wobei x0.25 und x0.75 das empirische 0.25 bzw. 0.75 Quantil bezeichnen.min()
berechnet das Minimum x(1)=minund
max()
berechnet demnach das Maximum x_{(n)} = \max(x_1,\dots,x_n)\,.
Auch wenn dies statistisch gesehen unklug sein mag, lasst uns die durchschnittliche Lebenserwartung pro Kontinent berechnen.
my_gap |>
group_by(continent) |>
summarise(avg_lifeExp = mean(lifeExp))
## # A tibble: 5 × 2
## continent avg_lifeExp
## <fct> <dbl>
## 1 Africa 48.9
## 2 Americas 64.7
## 3 Asia 60.1
## 4 Europe 71.9
## 5 Oceania 74.3
summarise_at()
wendet die gleiche(n) Zusammenfassungs-Funktion(en) auf mehrere Variablen an. Lasst uns die durchschnittliche Lebenserwartung sowie den Median und das Pro-Kopf-GDP nach Kontinenten pro Jahr berechnen… aber nur für 1952 und 2007.
my_gap |>
filter(year %in% c(1952, 2007)) |>
group_by(continent, year) |>
summarise_at(vars(lifeExp, gdpPercap), list(mean, median))
## # A tibble: 10 × 6
## # Groups: continent [5]
## continent year lifeExp_fn1 gdpPercap_fn1 lifeExp_fn2 gdpPercap_fn2
## <fct> <int> <dbl> <dbl> <dbl> <dbl>
## 1 Africa 1952 39.1 1253. 38.8 987.
## 2 Africa 2007 54.8 3089. 52.9 1452.
## 3 Americas 1952 53.3 4079. 54.7 3048.
## 4 Americas 2007 73.6 11003. 72.9 8948.
## 5 Asia 1952 46.3 5195. 44.9 1207.
## 6 Asia 2007 70.7 12473. 72.4 4471.
## 7 Europe 1952 64.4 5661. 65.9 5142.
## 8 Europe 2007 77.6 25054. 78.6 28054.
## 9 Oceania 1952 69.3 10298. 69.3 10298.
## 10 Oceania 2007 80.7 29810. 80.7 29810.
Im nächsten Schritt konzentrieren wir uns nur auf Asien. Wie hoch ist die minimale und maximale Lebenserwartung pro Jahr?
my_gap |>
filter(continent == "Asia") |>
group_by(year) |>
summarise(min_lifeExp = min(lifeExp), max_lifeExp = max(lifeExp))
## # A tibble: 12 × 3
## year min_lifeExp max_lifeExp
## <int> <dbl> <dbl>
## 1 1952 28.8 65.4
## 2 1957 30.3 67.8
## 3 1962 32.0 69.4
## 4 1967 34.0 71.4
## 5 1972 36.1 73.4
## 6 1977 31.2 75.4
## 7 1982 39.9 77.1
## 8 1987 40.8 78.7
## 9 1992 41.7 79.4
## 10 1997 41.8 80.7
## 11 2002 42.1 82
## 12 2007 43.8 82.6
Natürlich wäre es viel interessanter zu sehen, welches Land bzw. welche Länder diese extremen Beobachtungen beigetragen haben. Kommt das Minimum (Maximum) immer aus dem gleichen Land? Wir gehen dem in Kürze mithilfe von Window Funktionen nach.
7.6 Gruppierte Veränderungen
Manchmal möchte man die n-Zeilen für jede Gruppe nicht zu einer Zeile zusammenfassen. Stattdessen möchte man die Gruppen behalten, aber innerhalb dieser Gruppen rechnen.
7.6.1 Berechnungen innerhalb der Gruppen
Lasst uns eine neue Variable definieren, die die gewonnenen (verlorenen) Lebenserwartungsjahre im Vergleich zu 1952 für jedes einzelne Land angibt. Wir gruppieren nach Ländern und verwenden mutate()
, um eine neue Variable zu erstellen. Die Funktion first()
extrahiert dabei den ersten Wert aus einem Vektor. Beachtet, dass first()
mit dem Vektor der Lebenserwartungen in jeder Ländergruppe arbeitet.
my_gap |>
group_by(country) |>
select(country, year, lifeExp) |>
mutate(lifeExp_gain = lifeExp - first(lifeExp)) |>
filter(year < 1963)
## # A tibble: 426 × 4
## # Groups: country [142]
## country year lifeExp lifeExp_gain
## <fct> <int> <dbl> <dbl>
## 1 Afghanistan 1952 28.8 0
## 2 Afghanistan 1957 30.3 1.53
## 3 Afghanistan 1962 32.0 3.20
## 4 Albania 1952 55.2 0
## 5 Albania 1957 59.3 4.05
## 6 Albania 1962 64.8 9.59
## 7 Algeria 1952 43.1 0
## 8 Algeria 1957 45.7 2.61
## 9 Algeria 1962 48.3 5.23
## 10 Angola 1952 30.0 0
## # ℹ 416 more rows
Innerhalb eines Landes nehmen wir die Differenz zwischen der Lebenserwartung im Jahr i und der Lebenserwartung im Jahr 1952. Daher sehen wir für 1952 immer Nullen und für die meisten Länder eine Folge von positiven und steigenden Zahlen.
7.6.2 Window Funktionen
Window Funktionen nehmen eine Eingabe der Länge n und berechnen eine Ausgabe derselben Länge. Diese Ausgabewerte hängen dabei von allen Eingabewerten ab. So ist z.B. rank()
eine Window Funktion, aber log()
ist es nicht.
Betrachten wir noch einmal die schlechtesten und besten Lebenserwartungen in Asien im Laufe der Zeit, behalten aber Informationen darüber bei, welches Land diese Extremwerte beisteuert.
my_gap |>
filter(continent == "Asia") |>
select(year, country, lifeExp) |>
group_by(year) |>
filter(min_rank(desc(lifeExp)) < 2 | min_rank(lifeExp) < 2) |>
arrange(year) |>
print(n = Inf) # erzwingt eine Ausgabe aller Zeilen
## # A tibble: 24 × 3
## # Groups: year [12]
## year country lifeExp
## <int> <fct> <dbl>
## 1 1952 Afghanistan 28.8
## 2 1952 Israel 65.4
## 3 1957 Afghanistan 30.3
## 4 1957 Israel 67.8
## 5 1962 Afghanistan 32.0
## 6 1962 Israel 69.4
## 7 1967 Afghanistan 34.0
## 8 1967 Japan 71.4
## 9 1972 Afghanistan 36.1
## 10 1972 Japan 73.4
## 11 1977 Cambodia 31.2
## 12 1977 Japan 75.4
## 13 1982 Afghanistan 39.9
## 14 1982 Japan 77.1
## 15 1987 Afghanistan 40.8
## 16 1987 Japan 78.7
## 17 1992 Afghanistan 41.7
## 18 1992 Japan 79.4
## 19 1997 Afghanistan 41.8
## 20 1997 Japan 80.7
## 21 2002 Afghanistan 42.1
## 22 2002 Japan 82
## 23 2007 Afghanistan 43.8
## 24 2007 Japan 82.6
Wir sehen, dass (min = Afghanistan, max = Japan) das häufigste Ergebnis ist, aber Kambodscha und Israel tauchen auch jeweils mindestens einmal als min bzw. max auf.
Aber wäre es nicht schön eine Zeile pro Jahr zu haben?
Zuerst sollten wir uns aber vielleicht nochmal fragen wie das eigentlich funktioniert hat? Dazu schauen wir uns die Beobachtungen aus Asien mal direkt an.
(asia <- my_gap |>
filter(continent == "Asia") |>
select(year, country, lifeExp) |>
group_by(year))
## # A tibble: 396 × 3
## # Groups: year [12]
## year country lifeExp
## <int> <fct> <dbl>
## 1 1952 Afghanistan 28.8
## 2 1957 Afghanistan 30.3
## 3 1962 Afghanistan 32.0
## 4 1967 Afghanistan 34.0
## 5 1972 Afghanistan 36.1
## 6 1977 Afghanistan 38.4
## 7 1982 Afghanistan 39.9
## 8 1987 Afghanistan 40.8
## 9 1992 Afghanistan 41.7
## 10 1997 Afghanistan 41.8
## # ℹ 386 more rows
Jetzt wenden wir die Window Funktion min_rank()
an. Da asia
nach Jahren gruppiert ist, operiert min_rank()
innerhalb von Mini-Datensätzen. Auf die Variable lifeExp
angewandt, liefert min_rank()
den Rang der beobachteten Lebenserwartung jedes Landes.
Bemerkung: Der min
-Teil im Funktionsnamen min_rank()
gibt nur an, wie im Fall von gleichen Beobachtungswerten die Ränge bestimmt werden.
Neben dem Minimum gibt es aber auch noch eine Reihe weiterer Alternativen, wie z.B. den Durchschnitt
Im nächsten Schritt schauen wir uns die Ränge der Lebenserwartung innerhalb eines Jahres mal explizit für ein paar Länder (Afghanistan, Japan und Thailand) an - sowohl in der (Standard-) aufsteigenden als auch in der absteigenden Reihenfolge.
asia |>
mutate(le_rank = min_rank(lifeExp),
le_desc_rank = min_rank(desc(lifeExp))) |>
filter(country %in% c("Afghanistan", "Japan", "Thailand"), year > 1995)
## # A tibble: 9 × 5
## # Groups: year [3]
## year country lifeExp le_rank le_desc_rank
## <int> <fct> <dbl> <int> <int>
## 1 1997 Afghanistan 41.8 1 33
## 2 2002 Afghanistan 42.1 1 33
## 3 2007 Afghanistan 43.8 1 33
## 4 1997 Japan 80.7 33 1
## 5 2002 Japan 82 33 1
## 6 2007 Japan 82.6 33 1
## 7 1997 Thailand 67.5 12 22
## 8 2002 Thailand 68.6 12 22
## 9 2007 Thailand 70.6 12 22
Afghanistan neigt dazu, 1 in der le_rank
-Variablen zu haben, Japan neigt dazu, 1 in der le_desc_rank
-Variablen zu haben und andere Länder, wie Thailand, zeigen deutlich weniger extreme Ränge.
Damit sollte der ursprüngliche filter()
Befehl
auch klar sein.
Diese beiden Sätze von Rängen werden on-the-fly, innerhalb der Jahresgruppe, gebildet, und filter()
behält alle Zeilen, die einen Rangwert kleiner als 2 haben. Da wir dies für aufsteigende und absteigende Ränge machen, erhalten wir sowohl die Beobachtungen mit dem minimalen als auch dem maximalen Rang.
Wenn wir nur das Minimum ODER das Maximum gewollt hätten, hätte auch ein alternativer Ansatz mit slice_min()
bzw. slice_max()
funktioniert.
my_gap |>
filter(continent == "Asia") |>
select(year, country, lifeExp) |>
arrange(year) |>
group_by(year) |>
# slice_min(lifeExp, n = 1) ## für das Minimum
slice_max(lifeExp, n = 1) ## bzw. das Maximum
## # A tibble: 12 × 3
## # Groups: year [12]
## year country lifeExp
## <int> <fct> <dbl>
## 1 1952 Israel 65.4
## 2 1957 Israel 67.8
## 3 1962 Israel 69.4
## 4 1967 Japan 71.4
## 5 1972 Japan 73.4
## 6 1977 Japan 75.4
## 7 1982 Japan 77.1
## 8 1987 Japan 78.7
## 9 1992 Japan 79.4
## 10 1997 Japan 80.7
## 11 2002 Japan 82
## 12 2007 Japan 82.6
7.7 Großes Finale
Beantworten wir also die Frage:
“In welchem Land ist die Lebenserwartung innerhalb von 5 Jahren am stärksten gesunken?”
Die Beobachtungsfrequenz im Datensatz ist fünf Jahre, d.h. wir haben Daten für 1952, 1957 usw. Dies bedeutet also, dass die Veränderungen der Lebenserwartung zwischen benachbarten Zeitpunkten betrachtet werden müssen. Dazu verwenden wir die lag()
Funktion. Diese veschiebt die Einträge des Inputvektors um ein Lag k
.
Wir können aber noch mehr erreichen. Lasst uns die Frage pro Kontinenten beantworten.
my_gap |>
group_by(continent, country) |>
# für jedes Land werden die Unterschiede berechnet
mutate(delta = lifeExp - lag(lifeExp, n = 1)) |>
## für jedes Land wird nur der kleinste Wert behalten und alle Werten mit fehlendem Wert für delta werden nicht berücksichtigt
summarise(worst_delta = min(delta, na.rm = TRUE)) |>
## nun wird noch pro Kontinent, die Zeile mit dem kleinsten Wert ausgegeben
slice_min(worst_delta, n = 1) |>
arrange(worst_delta)
## `summarise()` has grouped output by 'continent'. You can override using the
## `.groups` argument.
## # A tibble: 5 × 3
## # Groups: continent [5]
## continent country worst_delta
## <fct> <fct> <dbl>
## 1 Africa Rwanda -20.4
## 2 Asia Cambodia -9.10
## 3 Americas El Salvador -1.51
## 4 Europe Montenegro -1.46
## 5 Oceania Australia 0.170
Auch folgendes Code würde dasselbe Ergebnis liefern
my_gap |>
group_by(continent, country) |>
# für jedes Land werden die Unterschiede berechnet
mutate(delta = lifeExp - lag(lifeExp, n = 1)) |>
## wir entfernen alle Beobachtungen, wo der Wert von delta fehlt
filter(!is.na(delta)) |>
## für jedes Land wird nur der kleinste Wert behalten
summarise(worst_delta = min(delta)) |>
## nun wird noch pro Kontinent, die Zeile mit dem kleinsten Wert ausgegeben
slice_min(worst_delta, n = 1) |>
arrange(worst_delta)
## `summarise()` has grouped output by 'continent'. You can override using the
## `.groups` argument.
## # A tibble: 5 × 3
## # Groups: continent [5]
## continent country worst_delta
## <fct> <fct> <dbl>
## 1 Africa Rwanda -20.4
## 2 Asia Cambodia -9.10
## 3 Americas El Salvador -1.51
## 4 Europe Montenegro -1.46
## 5 Oceania Australia 0.170
Wenn wir auch die Information bezüglich des Jahres behalten möchten, können wir eine weitere Variable zum summarize()
hinzufügen
my_gap |>
group_by(continent, country) |>
# für jedes Land werden die Unterschiede berechnet
mutate(delta = lifeExp - lag(lifeExp, n = 1)) |>
## wir entfernen alle Beobachtungen, wo der Wert von delta fehlt
filter(!is.na(delta)) |>
## für jedes Land wird nur der kleinste Wert behalten
summarise(worst_delta = min(delta), year = year[which.min(delta)]) |>
## nun wird noch pro Kontinent, die Zeile mit dem kleinsten Wert ausgegeben
slice_min(worst_delta, n = 1) |>
arrange(worst_delta)
## `summarise()` has grouped output by 'continent'. You can override using the
## `.groups` argument.
## # A tibble: 5 × 4
## # Groups: continent [5]
## continent country worst_delta year
## <fct> <fct> <dbl> <int>
## 1 Africa Rwanda -20.4 1992
## 2 Asia Cambodia -9.10 1977
## 3 Americas El Salvador -1.51 1977
## 4 Europe Montenegro -1.46 2002
## 5 Oceania Australia 0.170 1967
Denkt ruhig eine Weile über das Ergebnis nach. Hier sieht man in trockenen Statistiken über die durchschnittliche Lebenserwartung, wie Völkermord aussieht.
Um den Code besser zu verstehen, unterteilt ihn, beginnend von oben, in Stücke und überprüft die einzelnen Zwischenergebnisse. So wurde der Code auch geschrieben/entwickelt, mit Fehlern und Verfeinerungen auf dem Weg.
7.8 Literatur
An dieser Stelle sei noch auf die dplyr Webseite und das Kapitel Data transformation in R for Data Science (Wickham and Grolemund 2016) verwiesen.