<- read_csv("../data/worldbank.csv") worldbank
5 Das tidyverse-Paket
Dieses Kapitel orientiert sich zum Teil an dem Kapitel “The tidyverse” des eBuchs Introduction to Data Science von Rafael A. Irizarry. Sehr lesenswert ist außerdem das Buch Data Science for R von Hadley Wickham und Garrett Grolemund. Beide Bücher gehen jedoch über das hinaus, was in diesem Kurs behandelt wird.
5.1 Tidy data
In diesem Kurs gehen wir stets davon aus, dass die Daten als sogenannte tidy data, also als “saubere Daten” vorliegen. Saubere Daten liegen vor, wenn jede Zeile eine Beobachtung beinhaltet und die Spalten die verschiedenen Variablen repräsentieren, die für jede Beobachtung erhoben wurden. In der praktischen empirischen Arbeit hat man natürlich nicht immer saubere Daten und muss häufig viel Mühe aufwenden, um die Daten in ein sauberes Format zu überführen.
Für den Umgang mit sauberen Daten gibt es eine Reihe von R-Paketen, die in dem Paket
tidyverse
zusammengefasst sind. Um mit ihnen arbeiten zu können, muss das tidyverse
-Paket einmalig installiert werden. In Abschnitt 2.3 wird beschrieben, wie man Pakete installiert. Sollte das tidyverse
-Paket bei Ihnen noch nicht installiert sein, holen Sie das nun bitte nach.
Wenn das Paket installiert ist, kann man es zu Beginn einer R-Sitzung durch den Befehl library(tidyverse)
aktivieren. Diese Aktivierung muss bei jeder neuen Benutzung von R erneut erfolgen.
5.2 Transformationen
Das Paket tidyverse
bietet Funktionen, mit denen Transformationen von Dataframes durchgeführt werden können. Dazu gehört die Auswahl von Variablen oder Beobachtungen, das Errechnen neuer Variablen aus den bisherigen Variablen, Sortieren etc.
Zur Illustration der Befehle laden wir den Datensatz
worldbank.csv
, den Sie auch auf der Learnweb-Seite finden. Der Datensatz wird in Abschnitt D.3 beschrieben. Die Weltbank hat einen API-Daten-Zugang, für den es in R das Paket wbstat
gibt. Aus dem umfangreichen Datenbestand ist ein kleiner Auszug in der Datei worldbank.csv
gespeichert, den Sie auch auf der Learnweb-Seite des Kurses finden. Eine genauere Beschreibung der Variablen gibt Abschnitt D.3.
Zum Einlesen des Dataframes verwenden wir eine der Methoden aus Kapitel 4, z.B. den interaktiven Import (Abschnitt 4.3) oder den kommandogesteuerten Import (Abschnitt 4.4). Der Dataframe wird unter dem Namen worldbank
abgelegt.
(Wenn Sie den Datensatz auf Ihrem Computer laden wollen, muss der Pfad entsprechend angepasst werden.) Der Anfang des Datensatzes sieht so aus:
head(worldbank)
# A tibble: 6 × 5
country year pop gdp_pc co2
<chr> <dbl> <dbl> <dbl> <dbl>
1 ABW 1990 62753 21733. 0.189
2 ABW 1991 65896 23100. 0.215
3 ABW 1992 69005 23889. 0.239
4 ABW 1993 73685 24576. 0.241
5 ABW 1994 77595 25791. 0.266
6 ABW 1995 79805 26255. 0.295
Die Daten liegen als tidy data vor. In jeder Zeile werden die Variablenwerte der Einwohnerzahl (pop
), des Bruttoinlandsprodukts pro Einwohner (gdp_pc
in kaufkraftbereinigten 2021-US-Dollar) und des gesamten CO2-Ausstoßes (co2
, in Megatonnen CO2-Äquivalente, ohne Land-Use Change and Forestry) im Jahr year
für das Land country
angegeben. Das Land wird durch die übliche ISO-Abkürzung (3-Steller) bezeichnet.
5.2.1 filter
Mit der Funktion filter
kann man bestimmte Beobachtungen aus einem Dataframe auswählen. Angenommen, wir möchten aus dem Dataframe worldbank
nur die Beobachtungen für Deutschland (mit der Abkürzung “DEU”) sehen. Die Funktion filter
hat als erstes Argument den Dataframe und als weiteres Argument die Bedingung. Ein Zeilenumbruch zwischen den Argumenten erleichtert die Lesbarkeit, ist aber nicht notwendig.
filter(worldbank,
== "DEU") country
Die ersten Zeilen des Outputs sehen so aus:
# A tibble: 6 × 5
country year pop gdp_pc co2
<chr> <dbl> <dbl> <dbl> <dbl>
1 DEU 1990 79433029 19521. 1008.
2 DEU 1991 80013896 21058. 987.
3 DEU 1992 80624598 21806. 933.
4 DEU 1993 81156363 21940. 925.
5 DEU 1994 81438348 22887. 910.
6 DEU 1995 81678051 23615. 906.
Wir sehen beispielsweise, dass in Deutschland im Jahr 1991 ziemlich genau 80 Millionen Einwohner hatte.
Man kann auch komplexere Bedingungen eingeben, z.B. alle Beobachtungen aus Deutschland oder Frankreich zwischen 2015 und 2018:
filter(worldbank,
=="DEU" | country=="FRA") &
(country>= 2015 &
year <= 2018) year
# A tibble: 8 × 5
country year pop gdp_pc co2
<chr> <dbl> <dbl> <dbl> <dbl>
1 DEU 2015 81686611 48545. 780.
2 DEU 2016 82348669 51570. 784.
3 DEU 2017 82657002 54110. 769.
4 DEU 2018 82905782 56273. 745.
5 FRA 2015 66548272 40905. 332.
6 FRA 2016 66724104 42880. 335.
7 FRA 2017 66918020 44469. 339.
8 FRA 2018 67158348 46381. 328.
Eine ODER-verknüpfung erfolgt durch den senkrechten Strich (|
), eine UND-Verknüpfung durch das Et-Zeichen (&
). Negationen werden durch das Ausrufezeichen (!
) gekennzeichnet. Die Relationen sind:
- Kleiner als:
<
- Größer als:
>
- Kleiner als oder gleich:
<=
- Größer als oder gleich:
>=
- Gleich:
==
- Ungleich:
!=
Beachten Sie, dass eine Gleichheitsbedingung immer mit einem doppelten Gleichheitszeichen geschrieben werden muss (==
). Das einfache Gleichheitszeichen ist eine andere, synonyme Schreibweise für den Zuweisungsoperator <-
. Es ist ein häufiger Fehler (der einem leider immer wieder passiert), in einer Bedingung das einfache Gleichheitszeichen zu schreiben.
Wenn bei einer Variable Werte fehlen, ist es manchmal sinnvoll, alle Beobachtungen mit fehlenden Werten aus der weiteren Betrachtung auszuschließen. Dabei hilft die Funktion is.na
(für: is there any not available?). Mit ihr kann man feststellen, ob eine Beobachtung fehlt oder nicht. Um zum Beispiel nur Beobachtungen zu behalten, in denen die Variable gdp_pc
nicht fehlt, lautet der Befehl
filter(worldbank,
!is.na(gdp_pc))
Das Ausrufezeichen steht hierbei für den logischen NICHT-Operator. Den Output der Funktion filter
kann man als neuen Dataframe abspeichern. Um die ausgewählten Beobachtungen als Dataframe mit dem Namen wb_auswahl
zu speichern, gibt man folgendes ein:
<- filter(worldbank,
wb_auswahl BEDINGUNG)
Wenn man sicher ist, dass im weiteren Verlauf der Datenanalyse nur noch die ausgewählten Beobachtungen benötigt werden, dann kann man den Output auch dem alten Dataframe-Namen zuweisen. In diesem Fall wird der alte Dataframe überschrieben und die nicht ausgewählten Beobachtungen gehen verloren.
<- filter(worldbank,
worldbank BEDINGUNG)
5.2.2 select
Wenn man nicht alle Variablen (Spalten) anzeigen lassen will, kann man mit der Funktion select
einzelne Spalten auswählen. Die Syntax ist ähnlich wie bei filter
: Das erste Argument ist der Dataframe, die weiteren Argumente sind die Variablen, die man auswählen will.
Um von dem Beispieldatensatz worldbank
nur die Spalten country
, year
und co2
anzuzeigen, lautet der Befehl
select(worldbank,
country,
year, co2)
Die Zeilenumbrüche erleichtern die Lesbarkeit, sie sind aber nicht unbedingt erforderlich. Als Output erhält man (hier werden nur die ersten paar Zeilen angezeigt):
# A tibble: 6 × 3
country year co2
<chr> <dbl> <dbl>
1 ABW 1990 0.189
2 ABW 1991 0.215
3 ABW 1992 0.239
4 ABW 1993 0.241
5 ABW 1994 0.266
6 ABW 1995 0.295
Es ist auch möglich, Variablen auszuschließen. Dazu setzt man ein Minuszeichen vor den Variablennamen. Mit select(worldbank, -co2)
erhält man einen Dataframe mit allen Variablen bis auf co2
.
Den Output des select
-Befehls kann man als eigenen Dataframe unter einem neuen Namen speichern, z.B.
<- select(worldbank,
wb_auswahl
VARIABLE1, VARIABLE2)
Wenn die nicht ausgewählten Spalten im weiteren Verlauf der Analyse nicht mehr gebraucht werden, kann man den Output unter dem alten Dataframe-Namen speichern.
<- select(worldbank,
worldbank
VARIABLE1, VARIABLE2)
Achtung: In diesem Fall gehen alle nicht ausgewählten Variablen verloren.
5.2.3 mutate
Wir berechnen nun aus den vorhandenen Variablen eine neue Variable und fügen sie dem Datensatz hinzu. Im Gegensatz zu filter
und select
wird der Dataframe also nicht kleiner, sondern größer. Berechnet werden soll zunächst die der CO2-Ausstoß pro Kopf (in Tonnen). Dazu wird der gesamte CO2-Ausstoß in Megatonnen (also Millionen Tonnen) mit 1000000 multipliziert und anschließend durch die Anzahl der Einwohner dividiert.
= co2 * 1000000 / pop co2_pc
Die Funktion zum Berechnen neuer Variablen aus schon vorhandenen Variablen lautet mutate
. Der Output von mutate
ist der alte Dataframe ergänzt um eine (oder auch mehrere) Spalten mit der neuen Variablen. Es ist oft sinnvoll, den Output unter dem alten Dataframe-Namen zu speichern. Auf diese Weise wächst der Dataframe um eine Spalte (oder mehrere).
<- mutate(worldbank,
worldbank co2_pc = co2 * 1000000 / pop)
Wie bei select
und filter
hat die Funktion mutate
als erstes Argument einen Dataframe, das folgende Argument gibt an, welche neue Variable auf welche Weise aus welchen bisherigen Variablen errechnet werden soll. Die Funktion mutate
gibt den alten Dataframe mit der neuen zusätzlichen Spalte als Output zurück. Wir speichern den neuen, größeren Dataframe wieder unter dem Namen worldbank
. Der Dataframe ist nun um eine Variable breiter geworden.
head(worldbank)
# A tibble: 6 × 6
country year pop gdp_pc co2 co2_pc
<chr> <dbl> <dbl> <dbl> <dbl> <dbl>
1 ABW 1990 62753 21733. 0.189 3.02
2 ABW 1991 65896 23100. 0.215 3.27
3 ABW 1992 69005 23889. 0.239 3.46
4 ABW 1993 73685 24576. 0.241 3.27
5 ABW 1994 77595 25791. 0.266 3.43
6 ABW 1995 79805 26255. 0.295 3.69
Als nächstes berechnen wir die Höhe des Bruttoinlandsprodukts (in Mrd. Dollar) , indem wir das BIP pro Kopf mit der Einwohnerzahl multiplizieren und durch 1e9 dividieren (1e9 ist die Kurzschreibweise für mutate
-Befehl lautet
<- mutate(worldbank,
worldbank gdp = gdp_pc * pop / 1e9)
Man kann auch mehrere neue Spalten auf einmal erzeugen. Die Definitionen der neuen Variablen werden dann einfach durch Kommas getrennt. Um nicht nur die CO2-Emissionen pro Kopf, sondern auch das gesamte Bruttoinlandsprodukt auszurechnen, wäre der Befehl (den wir hier jedoch nicht ausführen):
<- mutate(worldbank,
worldbank co2_pc = co2 * 1000000 / pop,
gdp = gdp_pc * pop / 1e9)
Was passiert, wenn man als Variablennamen für die neue Variable den Namen einer schon vorhandenen Variable eingibt? In diesem Fall wird die alte Variable überschrieben. Solche Änderungen sind zwar erlaubt, aber meist nicht empfehlenswert, weil man leicht vergisst, ob schon die neue oder noch die alte Definition gilt.
5.2.4 Pipe %>%
Der sogenannte Pipe-Operator
%>%
erlaubt es, eine Reihe von Funktionen in übersichtlicher Form nacheinander so auszuführen, dass der Output einer Funktion als Input für die nächste Funktion dient.
Im Abschnitt zu der Funktion filter
haben wir alle Beobachtungen aus Deutschland ausgewählt. Wenn wir vom Output dieser Funktion nur die Spalten year
, co2
und gdp_pc
ausgegeben haben möchten, können wir das durch select
erreichen. Die Funktionen werden also nacheinander ausgeführt:
dataframe → filter → select
Für solche hintereinander geschalteten Funktionen kann man den Pipe-Operator verwenden. Der Code sieht dann so aus:
%>% filter(country == "DEU") %>% select(year, co2, gdp_pc) worldbank
# A tibble: 33 × 3
year co2 gdp_pc
<dbl> <dbl> <dbl>
1 1990 1008. 19521.
2 1991 987. 21058.
3 1992 933. 21806.
4 1993 925. 21940.
5 1994 910. 22887.
6 1995 906. 23615.
7 1996 935. 24160.
8 1997 904. 24644.
9 1998 903. 25444.
10 1999 869. 26518.
# ℹ 23 more rows
Für eine bessere Lesbarkeit sind Zeilenumbrüche hilfreich. Ein Zeilenumbruch sollte immer nach dem Pipezeichen %>%
erfolgen. Die gleiche Befehlsabfolge sieht dann so aus:
%>%
worldbank filter(country == "DEU") %>%
select(year,
co2, gdp_pc)
Was genau macht der Pipe-Operator?
Allgemein gesprochen schickt der Pipe-Operator das Ergebnis (Output) der Funktion auf der linken Seite als erstes Argument in die Funktion auf der rechten Seite. Hier ist ein sehr einfaches Beispiel:
16 %>% sqrt()
[1] 4
Die Reihe lässt sich verlängern:
16 %>% sqrt() %>% log()
[1] 1.386294
Dieser Ausdruck entspricht
Vergessen Sie beim Pipe-Operator nicht, dass das erste Argument in der Funktion auf der rechten Seite entfällt. In den Funktionen mutate
, filter
und select
wird also der Dataframe (als erstes Argument) nicht mehr angegeben. Betrachten Sie noch einmal die Pipe-Folge:
%>%
worldbank filter(country == "DEU") %>%
select(year,
co2, gdp_pc)
Hier dient worldbank
als erstes Argument der Funktion filter
, und der Output der filter
-Funktion dient als erstes Argument der select
-Funktion. Der Pipe-Operator ist immer dann besonders einfach einzusetzen, wenn das erste Argument der Funktionen ein Dataframe ist. Die Funktionen des Pakets tidyverse
(wie mutate
, filter
, select
und einige weitere Funktionen, die wir erst später behandeln werden) haben diese Struktur.
5.3 Sortieren
Beim ersten Arbeiten mit einem Datensatz ist es oft hilfreich und interessant, den Dataframe auf verschiedene Weise zu sortieren. Beispielsweise können wir den Dataframe worldbank
nach der Größe des Pro-Kopf-Bruttoinlandsprodukts (in US-Dollar, inflationsbereinigt) aufsteigend sortieren (und nur die ersten Zeilen ausgeben lassen):
%>%
worldbank arrange(gdp_pc) %>%
head()
# A tibble: 6 × 7
country year pop gdp_pc co2 co2_pc gdp
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 LBR 1995 2169162 254. 0.259 0.119 0.552
2 LBR 1994 2149928 263. 0.254 0.118 0.565
3 LBR 1996 2232289 282. 0.283 0.127 0.630
4 MOZ 1992 13613316 292. 1.16 0.0853 3.97
5 MOZ 1990 13094537 296. 1.16 0.0887 3.88
6 RWA 1994 6792352 311. 0.479 0.0706 2.11
Offensichtlich war das Pro-Kopf-Inlandsprodukt in den 1990er Jahren in Liberia, Mozambique und Ruanda besonders niedrig.
Wenn wir absteigend (“descending”) sortieren, ist der Output:
%>%
worldbank arrange(desc(gdp_pc)) %>%
head()
# A tibble: 6 × 7
country year pop gdp_pc co2 co2_pc gdp
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 QAT 2012 1722438 180939. 81.9 47.6 312.
2 QAT 2011 1624761 174620. 78.3 48.2 284.
3 QAT 2013 1916426 169203. 88.9 46.4 324.
4 QAT 2010 1616832 151646. 69.3 42.8 245.
5 MAC 2013 591900 149794. 1.83 3.10 88.7
6 QAT 2014 2151745 148389. 95.3 44.3 319.
Das höchste Pro-Kopf-Inlandsprodukt erzielte Qatar (QAT
) im Jahr 2012. Auch in Macao (MAC
) war das Pro-Kopf-Inlandsprodukt sehr hoch.
Die Funktion top_n
gibt nur die worldbank
:
%>%
worldbank top_n(5, co2)
# A tibble: 5 × 7
country year pop gdp_pc co2 co2_pc gdp
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 CHN 2018 1402760000 16007. 11554. 8.24 22454.
2 CHN 2019 1407745000 17262. 11819. 8.40 24301.
3 CHN 2020 1411100000 17891. 12037. 8.53 25247.
4 CHN 2021 1412360000 20407. 12718. 9.00 28822.
5 CHN 2022 1412175000 22510. 12667. 8.97 31788.
Von allen Staaten emittiert also China (CHN
) in den letzten Jahren die größte Menge an CO2 (absolut, nicht pro Kopf).
5.4 Kennzahlen
Große Datensätze sind oftmals schwer zugänglich, man spricht manchmal auch von “Datenfriedhöfen”. Um die Daten wirklich zum Sprechen zu bringen, brauchen wir Werkzeuge. Ein sehr wichtiges Werkzeug der Datenanalyse sind Kennzahlen (“summaries”). Die bekannteste Kennzahl ist sicherlich der Durchschnitt, auf den wir später im Abschnitt 8.1 noch ausführlicher eingehen werden. Auch die Summe ist in vielen Situationen eine nützliche Kennzahl. In diesem Abschnitt lernen Sie zwei neue Funktionen kennen, mit denen die Datenanalyse vereinfacht wird: summarise
und group_by
. Außerdem wird noch die Funktion pull
vorgestellt, mit der die Resultate für die Weiterverarbeitung in R vorbereitet werden.
5.4.1 summarise
Die summarise
-Funktion erlaubt es, Kennzahlen auf eine intuitive und leicht lesbare Art zu berechnen. Wir starten mit einem einfachen Beispiel. Der Dataframe worldbank
enthält Angaben zu CO2-Emissionen in allen Ländern von 1990 bis 2022 (für viele Länder ist der Datenbestand leider nicht vollständig). Wir beschränken den Datensatz in diesem Beispiel auf alle Beobachtungen des Jahres 2020 und speichern die Auswahl unter dem Namen wb2020
.
<- filter(worldbank, year == 2020) wb2020
Der folgende Code berechnet die Summe der CO2-Emissionen aller Länder (in Megatonnen) und die Summe der Bruttoinlandsprodukte aller Länder (in Billionen US-Dollar). Die Schreibweise 1e12
steht für
%>%
wb2020 summarise(co2welt = sum(co2),
gdpwelt = sum(gdp_pc*pop)/1e12) -> s
print(s)
# A tibble: 1 × 2
co2welt gdpwelt
<dbl> <dbl>
1 34500. 137.
Was passiert hier? Durch den summarise
-Befehl wird ein neuer Dataframe erzeugt und unter dem Namen s
gespeichert. Der neue Dataframe hat zwei Spalten und nur eine Zeile. Die Spaltennamen haben wir in der summarise
-Funktion festgelegt, andere Namen wären natürlich ebenso möglich gewesen. Die Funktion sum
ist eine interne R-Funktion.
Achtung: Am Ende der Pipe wird der endgültige Dataframe mit dem Pfeilsymbol ->
unter einem neuen Dataframe-Namen gespeichert. Hier darf man nicht das Pipe-Zeichen %>%
verwenden. Es ist auch möglich diese Zuweisung anders herum an den Anfang der Pipe zu setzen:
<- worldbank %>%
s filter(year == 2020) %>%
summarise(co2welt = sum(co2),
gdpwelt = sum(gdp_pc*pop)/1e12)
Welche Variante man bevorzugt, ist Geschmackssache.
5.4.2 group_by
Sehr oft berechnet man Kennzahlen nicht nur für die gesamte Population, sondern auch für Teilgruppen. Zum Beispiel können wir die Summe der CO2-Emissionen für jedes Jahr getrennt errechnen. In diesem Fall werden die Beobachtungen nach der Variable year
in Gruppen zusammengefasst. Für jede Gruppe berechnen wir dann die Summe (also die CO2-Weltemissions des jeweiligen Jahres). Um die Gruppen zu definieren, verwenden wir die Funktion group_by
.
<- worldbank %>%
s group_by(year) %>%
summarise(weltco2 = sum(co2))
Die letzten 10 Zeilen des Outputs sehen so aus:
tail(s,10)
# A tibble: 10 × 2
year weltco2
<dbl> <dbl>
1 2013 34339.
2 2014 34504.
3 2015 34348.
4 2016 34416.
5 2017 34995.
6 2018 35924.
7 2019 35953.
8 2020 34500.
9 2021 36521.
10 2022 36845.
Der Befehl group_by(year)
verändert den Dataframe kaum sichtbar. Er legt nur fest, dass die Beobachtungen des Dataframes nach Jahren gruppiert werden. Man spricht daher auch von einem “gruppierten Dataframe”. Viele Funktionen des tidyverse
-Pakets reagieren auf die Gruppierung. So berechnet die Funktion summarise
die Kennzahlen jetzt für jede Gruppe einzeln und erstellt einen entsprechenden neuen Dataframe (der hier s
genannt wird). Der neue Dataframe, den der summarise
-Befehl liefert, hat diese Eigenschaften:
- Der neue Dataframe hat so viele Zeilen wie es Gruppen gibt.
- Die erste Spalte des neuen Dataframes zeigt alle Ausprägungen der Gruppierungsvariable.
- Die zweite Spalte enthält die Kennzahlen, die für die Gruppen mit
summarise
berechnet wurden. - Wenn mehrere Kennzahlen berechnet werden, gibt es für jede Kennzahl eine Spalte.
5.4.3 pull
Als Ergebnis der summarise
-Funktion erhalten wir - rein formal gesehen - immer einen Dataframe, selbst wenn es sich nur um eine einzige Zahl handelt. Das kann problematisch werden, wenn wir die Ergebnisse in anderen R-Funktionen weiterverwenden wollen, die numerische Vektoren als Input erwarten. Es gibt jedoch einen einfachen Weg, die Werte einer Spalte als numerischen Vektor aus einem Dataframe heraus zu ziehen. Die Funktion, die das leistet, heißt pull
. Ein Beispiel zur Illustration:
%>%
worldbank filter(year == 2020) %>%
summarise(weltco2 = sum(co2)) %>%
pull(weltco2) -> x
print(x)
[1] 34500.43
class(x)
[1] "numeric"
Die Pipe-Kette wählt aus dem Dataframe worldbank
alle Beobachtungen des Jahres 2020 aus. Anschließend wird die Summe aller CO2-Emissionen (in 1000 t) berechnet. An dieser Stelle wird normalerweise ein Dataframe (mit nur einer Zeile und einer Spalte) ausgegeben. Durch die Weiterleitung in die pull
-Funktion wird aber der Inhalt der Spalte weltco2
als numerisches Objekt herausgezogen und letztlich als x
gespeichert. Die class
-Funktion zeigt, dass es sich bei x
nicht mehr um einen Dataframe, sondern um eine Zahl handelt.
5.5 Doppelungen
Ein Fehler, der in Datensätzen gelegentlich auftritt, sind Doppelungen. Eine Beobachtung, die nur als eine Zeile im Datensatz vorkommen sollte, wird versehentlich in einer zweiten, identischen Zeile wiederholt. Das kann natürlich auch mehrere Beobachtungen betreffen. In großen Datensätzen kann man dieses Problem nicht “per Hand” lösen. Die Funktion
distinct
aus dem tidyverse
-Paket hilft weiter.
Das erste Argument der Funktion distinct
ist (wie bei quasi allen Funktion aus dem tidyverse
-Paket) der Dataframe. Wenn keine weiteren Argument angegeben sind, werden alle Zeilen aus dem Dataframe entfernt, bei denen alle Variablenwerte identisch sind. Das lässt sich am einfachsten an einem kleinen Beispiel veranschaulichen.
Beispiel:
Der Dataframe D
enthält nur sechs Beobachtungen von vier Variablen.
<- data.frame(Var1=c(3,4,5,4,2,4),
D Var2=c(1999,2000,2004,2000,2003,2000),
Var3=c(834,1221,1204,1221,976,857),
Var4=c(0,1,1,1,0,0))
D
Var1 Var2 Var3 Var4
1 3 1999 834 0
2 4 2000 1221 1
3 5 2004 1204 1
4 4 2000 1221 1
5 2 2003 976 0
6 4 2000 857 0
In diesem Datensatz sind die Zeilen 2 und 4 komplett identisch. Die Doppelung wird durch die Funktion distinct
entfernt:
distinct(D)
Var1 Var2 Var3 Var4
1 3 1999 834 0
2 4 2000 1221 1
3 5 2004 1204 1
4 2 2003 976 0
5 4 2000 857 0
In den meisten Fällen wird man den bereinigten Datensatz unter dem gleichen Namen (hier: D
) oder unter einem neuen Namen abspeichern.
<- distinct(D) D_neu
Mit der Funktion distinct
kann man auch Zeilen entfernen, in denen nur manche Variablen identische Werte annehmen, andere Variablen hingegen nicht. So sind in dem Beispiel von oben die ersten beiden Variablenwerte der sechsten Zeile identisch zu den Werten in den Zeilen 2 und 4 (die Variablen Var3
und Var4
nehmen jedoch andere Werte an). Wenn eine Doppelung nur für bestimmte Variablen relevant ist, kann man diese Variablen als Argumente in der distinct
-Funktion angeben.
distinct(D, Var1, Var2)
Var1 Var2
1 3 1999
2 4 2000
3 5 2004
4 2 2003
In diesem Fall werden die restlichen Variablen standardmäßig entfernt. Um sie im Datensatz zu belassen, dient die Option .keep_all
(mit einem Punkt am Anfang). Das sieht dann so aus:
distinct(D, Var1, Var2, .keep_all=TRUE)
Var1 Var2 Var3 Var4
1 3 1999 834 0
2 4 2000 1221 1
3 5 2004 1204 1
4 2 2003 976 0
Wie alle Funktionen aus dem tidyverse
-Paket, lässt sich auch die Funktion distinct
in eine Pipe integrieren.
5.6 Merging
Wenn Daten, die man gemeinsam analysieren will, in zwei getrennten Dataframes vorliegen, müssen sie zusammengespielt werden (engl. merge). Oftmals möchte man auch zusätzliche Informationen, die aus einem Datensatz errechnet wurden, an einen Dataframe anfügen. Zur Erinnerung: In einem Dataframe in “sauberem Format” ist jede Spalte eine Variable und jede Zeile eine Beobachtung. Wenn wir zwei Dataframes zusammenführen möchten, sind darum zwei grundsätzlich unterschiedliche Situationen zu unterscheiden:
Es kommen weitere Beobachtungen hinzu, d.h. der Dataframe erhält neue Zeilen, er wird länger.
Es kommen weitere Variablen hinzu, d.h. der Dataframe erhält neue Spalten, er wird breiter.
Wir behandeln diese beiden Fälle nacheinander. Beide Fälle können recht komplex werden und es gibt mehrere Möglichkeiten, damit umzugehen. Wir beschränken uns auf zwei einfache und für die praktische Anwendung wichtige Situationen.
5.6.1 bind_rows
Mit dem Befehl bind_rows
werden Beobachtungen (Zeilen) ergänzt. Der Dataframe wird dadurch länger. Das ist beispielsweise relevant, wenn ein Dataframe aktualisiert werden muss, weil neue Beobachtungen erhoben wurden, die unten an den bereits bestehenden Dataframe angehängt werden sollen. Achten Sie darauf, dass die Spaltennamen der beiden Dataframes exakt gleich sein müssen, wenn Sie sie zusammenführen, denn sonst werden die Spalten als unterschiedliche Variablen interpretiert.
Beispiel:
Die letzten sechs Zeilen des Dataframes worldbank
sehen so aus:
tail(worldbank)
# A tibble: 6 × 7
country year pop gdp_pc co2 co2_pc gdp
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 ZWE 2015 14399013 2647. 12.5 0.871 38.1
2 ZWE 2016 14600294 2797. 10.9 0.749 40.8
3 ZWE 2017 14812482 7045. 10.2 0.691 104.
4 ZWE 2018 15034452 2614. 11.9 0.789 39.3
5 ZWE 2019 15271368 3211. 10.9 0.711 49.0
6 ZWE 2020 15526888 3511. 8.83 0.568 54.5
Wir möchten nun zwei Zeilen ergänzen, und zwar die Daten für Zimbabwe (zwe
) von 2021 und 2022. Diese beiden Beobachtungen liegen in dem Dataframe wb_neu
vor, der wie folgt aussieht:
wb_neu
# A tibble: 2 × 7
country year pop gdp_pc co2 co2_pc gdp
<chr> <dbl> <dbl> <dbl> <dbl> <lgl> <lgl>
1 ZWE 2021 15797210 3185. 9.64 NA NA
2 ZWE 2022 16069056 3560. 10.2 NA NA
Die neuen Beobachtungen werden nun durch bind_rows
an den Dataframe worldbank
angehängt. Der neue, längere Dataframe kann unter dem alten Namen (worldbank
) oder unter einem neuen Namen abgespeichert werden (hier z.B. unter dem Namen wb_longer
).
<- bind_rows(worldbank, wb_neu)
wb_longer tail(wb_longer,8)
# A tibble: 8 × 7
country year pop gdp_pc co2 co2_pc gdp
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 ZWE 2015 14399013 2647. 12.5 0.871 38.1
2 ZWE 2016 14600294 2797. 10.9 0.749 40.8
3 ZWE 2017 14812482 7045. 10.2 0.691 104.
4 ZWE 2018 15034452 2614. 11.9 0.789 39.3
5 ZWE 2019 15271368 3211. 10.9 0.711 49.0
6 ZWE 2020 15526888 3511. 8.83 0.568 54.5
7 ZWE 2021 15797210 3185. 9.64 NA NA
8 ZWE 2022 16069056 3560. 10.2 NA NA
5.6.2 left_join
Das Anspielen einer oder mehrerer neuer Variable an einen Dataframe ist fehleranfällig und nicht ganz leicht, weil sichergestellt sein muss, dass jede neue Beobachtung in der richtigen Zeile gespeichert wird. Oft hat der Dataframe mit den neuen Variablen eine andere Länge als der Dataframe, an den die Variablen angehängt werden sollen. Oder die Beobachtungen liegen in einer anderen Reihenfolge vor. Um ganz sicher zu sein, dass jede Beobachtung der richtigen Zeile zugewiesen wird, hilft ein Identifier (ID-Variable). Der Identifier dient dazu, die Beobachtungen in den beiden Dataframes selbst dann eindeutig zu identifizieren, wenn sie nicht in der gleichen Zeile stehen.
Das lässt sich am besten an einem einfachen Beispiel illustrieren.
Beispiel:
Die Daten in einem kleinen Dataframe D
sehen so aus:
<- data.frame(id=1:5,
D x=c(3,4,3,1,1),
y=c(20,20,15,25,22))
print(D)
id x y
1 1 3 20
2 2 4 20
3 3 3 15
4 4 1 25
5 5 1 22
In einem weiteren Datframe D2
gibt Angaben zu den drei Variablen a
, b
und c
, und zwar folgende:
<- data.frame(id=c(2,1,5,4),
D2 a=c(1,4,3,2),
b=c(5,5,4,5),
c=c(9,9,8,8))
print(D2)
id a b c
1 2 1 5 9
2 1 4 5 9
3 5 3 4 8
4 4 2 5 8
Die Variable id
ist der Identifier (jeder beliebige Variablenname ist hier möglich). Er kommt in beiden Datensätzen vor und dient dazu, die Beobachtungen richtig zuzuordnen. Zum Beispiel sagt die erste Zeile von D2
, dass die zugehörigen Werte von a
, b
und c
in die zweite Zeile von D
gehören. Offenbar enthält D2
die Beobachtungen in einer anderen Reihenfolge, und eine Beobachtung mit id=3
fehlt.
Der Dataframe D2
wird nun mit dem Befehl left_join
an D
angespielt. Es ergibt sich:
print(left_join(D, D2, by="id"))
id x y a b c
1 1 3 20 4 5 9
2 2 4 20 1 5 9
3 3 3 15 NA NA NA
4 4 1 25 2 5 8
5 5 1 22 3 4 8
Der neue, breitere Dataframe kann unter dem alten Namen oder unter einem neuen Namen abgespeichert werden (hier im Beispiel wird er gar nicht gespeichert).
Noch ein Beispiel:
Der Identifier muss nicht unbedingt eindeutig sein. Es ist durchaus erlaubt, dass mehrere Beobachtungen den gleichen Wert des Identifiers haben, wie dies Beispiel zeigt. Der (fiktive) Dataframe X
enthält Angaben zum nominalen (d.h. nicht inflationsbereinigten) Einkommen von drei Personen in drei Jahren.
<- data.frame(jahr=rep(2020:2022,3),
X person=rep(1:3,each=3),
nominal=c(1000,1200,1250,800,900,1000,2500,2400,2500))
X
jahr person nominal
1 2020 1 1000
2 2021 1 1200
3 2022 1 1250
4 2020 2 800
5 2021 2 900
6 2022 2 1000
7 2020 3 2500
8 2021 3 2400
9 2022 3 2500
Um die nominalen Größen in reale Größen umzurechnen, verwendet man einen Preisindex (an dieser Stelle fragen wir nicht danach, wie ein solcher Index gebildet wird). Die realen Größen sind der Quotient aus dem nominalen Wert und dem Preisindex, der in dem Jahr galt.
Die (fiktiven) Werte des Preisindex sind im Dataframe preise
enthalten:
<- data.frame(jahr=c(2020,2021,2022),
preise index=c(1.00,1.05,1.11))
preise
jahr index
1 2020 1.00
2 2021 1.05
3 2022 1.11
Mit Hilfe des Befehls left_join
können wir den Index für jedes Jahr als neue Variable an den Dataframe X
anfügen. Zum Verknüpfen dient die Spalte jahr
, die in beiden Dataframes vorkommt und hier als Verknüpfungsvariable dient. Es ist also durchaus möglich, dass ein Wert an mehrere Beobachtungen angespielt wird.
<- left_join(X, preise, by="jahr")
X X
jahr person nominal index
1 2020 1 1000 1.00
2 2021 1 1200 1.05
3 2022 1 1250 1.11
4 2020 2 800 1.00
5 2021 2 900 1.05
6 2022 2 1000 1.11
7 2020 3 2500 1.00
8 2021 3 2400 1.05
9 2022 3 2500 1.11
Wie der Befehlsname left_join
schon nahelegt, gibt es noch andere Möglichkeiten, Dataframes zusammenzuführen, z.B. right_join
oder full_join
. Wir werden sie in diesem Kurs aber nicht brauchen. Wer sich für andere Arten des Anspielens interessiert, kann sich über die Hilfsfunktionen und Cheatsheets informieren.
Drittes Beispiel: Worldbank Lebenserwartungsdaten
In diesem dritten Beispiel wird gezeigt, wie man weitere Variablen an den worldbank
-Datensatz anspielt. Hier ist die Schwierigkeit, dass es zwei Identifier gibt, nämlich das Land (country
) und das Jahr (year
). Wir nehmen als Ausgangspunkt den Dataframe worldbank
, dessen ersten Zeilen so aussehen:
head(worldbank)
# A tibble: 6 × 7
country year pop gdp_pc co2 co2_pc gdp
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 ABW 1990 62753 21733. 0.189 3.02 1.36
2 ABW 1991 65896 23100. 0.215 3.27 1.52
3 ABW 1992 69005 23889. 0.239 3.46 1.65
4 ABW 1993 73685 24576. 0.241 3.27 1.81
5 ABW 1994 77595 25791. 0.266 3.43 2.00
6 ABW 1995 79805 26255. 0.295 3.69 2.10
An diesen Dataframe soll nun ein zweiter Dataframe mit Informationen zur Lebenserwartung angespielt werden. Wir laden diesen zweiten Datensatz (der auch im Learnweb zu finden ist).
<- read_csv("../data/wb_lifeexp.csv") wb_lifeexp
Die ersten Zeilen sehen so aus:
head(wb_lifeexp)
# A tibble: 6 × 4
country year lifeexpF lifeexpM
<chr> <dbl> <dbl> <dbl>
1 AFG 2022 66.2 59.8
2 AFG 2021 65.3 58.9
3 AFG 2020 65.4 59.9
4 AFG 2019 66.7 60.6
5 AFG 2018 66.5 59.9
6 AFG 2017 66.1 60.1
Der Datensatz enthält die beiden Variablen lifexpF
und lifexpM
(Lebenserwartung der Frauen und Männer). Der Dataframe wb_lifeexp
wird nun an den Dataframe worldbank
angefügt. Es gibt zwei Identifier-Variablen, nämlich country
und year
. Sie werden durch die Funktion c
zusammengefasst und als Argument by
übergeben. Der neue, breitere Dataframe wird unter dem Namen wb_wider
gespeichert.
<- left_join(worldbank, wb_lifeexp,
wb_wider by=c("country","year"))
Dieser neue Dataframe hat die Variablen (Spalten)
names(wb_wider)
[1] "country" "year" "pop" "gdp_pc" "co2" "co2_pc" "gdp"
[8] "lifeexpF" "lifeexpM"
Die ersten Zeilen des verbreiterten Dataframes lauten:
head(wb_wider)
# A tibble: 6 × 9
country year pop gdp_pc co2 co2_pc gdp lifeexpF lifeexpM
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 ABW 1990 62753 21733. 0.189 3.02 1.36 76.2 70.1
2 ABW 1991 65896 23100. 0.215 3.27 1.52 76.1 70.1
3 ABW 1992 69005 23889. 0.239 3.46 1.65 76.2 70.2
4 ABW 1993 73685 24576. 0.241 3.27 1.81 76.2 70.2
5 ABW 1994 77595 25791. 0.266 3.43 2.00 76.3 70.3
6 ABW 1995 79805 26255. 0.295 3.69 2.10 76.4 70.3