4  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.

4.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 Kapitel 1.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.

4.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 gapmindersubset.csv, den Sie auch auf der Learnweb-Seite finden. Der Datensatz wird in Kapitel D.3 beschrieben. Gapminder ist eine Stiftung, die u.a. umfangreiche internationale Daten bereitstellt, um (nach eigenen Angaben) gegen falsche Vorstellungen zu kämpfen und eine fakten-basierte Weltsicht zu fördern. Der Datenbestand von Gapminder ist erheblich umfangreicher als der kleine Auszug, den wir hier als Beispiel nutzen.

Zum Einlesen des Dataframes verwenden wir eine der Methoden aus Kapitel 3, z.B. den interaktiven Import (Kapitel 3.3) oder den kommandogesteuerten Import (Kapitel 3.4). Der Dataframe wird unter dem Namen gapminder abgelegt.

gapminder <- read_csv("../data/gapmindersubset.csv")

(Wenn Sie den Datensatz auf Ihrem Computer laden wollen, muss der Pfad entsprechend angepasst werden.) Der Anfang des Datensatzes sieht so aus:

head(gapminder)
# A tibble: 6 × 10
  country  year pop_0_14 pop_15_19 pop_20_39 pop_40_59 pop_60plus fertility
  <chr>   <dbl>    <dbl>     <dbl>     <dbl>     <dbl>      <dbl>     <dbl>
1 abw      1986    17467      4449     23256     13396       5989      2.32
2 abw      1987    17097      4103     23441     13838       5971      2.31
3 abw      1988    16779      3786     23499     14274       5996      2.29
4 abw      1989    16798      3653     23290     14786       6070      2.27
5 abw      1990    17453      3876     22691     15477       6218      2.25
6 abw      1991    18796      4531     21748     16321       6468      2.22
# ℹ 2 more variables: co2 <dbl>, gdp_pc <dbl>

Die Daten liegen als tidy data vor. In jeder Zeile werden die Variablenwerte der Einwohnerzahl (pop), der Fertilität (fert), des gesamten CO2-Ausstoßes (co2) und des Pro-Kopf-Bruttoinlandsprodukts (gdp_pc) im Jahr year für das Land country angegeben. Das Land wird durch die übliche ISO-Abkürzung (3-Steller) bezeichnet.

4.2.1 filter

Mit der Funktion filter kann man bestimmte Beobachtungen aus einem Dataframe auswählen. Angenommen, wir möchten aus dem Dataframe gapminder 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(gapminder, 
       country == "deu")

Die ersten Zeilen des Outputs sehen so aus:

# A tibble: 6 × 10
  country  year pop_0_14 pop_15_19 pop_20_39 pop_40_59 pop_60plus fertility
  <chr>   <dbl>    <dbl>     <dbl>     <dbl>     <dbl>      <dbl>     <dbl>
1 deu      1970 18249229   5323649  21639297  17591559   15490850      2.04
2 deu      1971 18196497   5376127  21763354  17523583   15651838      1.92
3 deu      1972 18035263   5453507  21939085  17403802   15811901      1.8 
4 deu      1973 17751123   5550210  22137964  17259645   15968525      1.7 
5 deu      1974 17369889   5672726  22246496  17244537   16086886      1.62
6 deu      1975 16920048   5824422  22223972  17476086   16068982      1.56
# ℹ 2 more variables: co2 <dbl>, gdp_pc <dbl>

Wir sehen beispielsweise, dass in Deutschland im Jahr 1970 rund 78.6 Millionen Einwohner hatte und die Fertilitätsrate ein klein wenig über 2 lag.

Man kann auch komplexere Bedingungen eingeben, z.B. alle Beobachtungen aus Deutschland oder Frankreich zwischen 2015 und 2018:

filter(gapminder, 
       (country=="deu" | country=="fra") & 
       year >= 2015 & 
       year <= 2018)
# A tibble: 8 × 10
  country  year pop_0_14 pop_15_19 pop_20_39 pop_40_59 pop_60plus fertility
  <chr>   <dbl>    <dbl>     <dbl>     <dbl>     <dbl>      <dbl>     <dbl>
1 deu      2015 10837497   4194385  20048624  24622002   22370719      1.45
2 deu      2016 10924700   4182755  20167639  24417910   22638421      1.46
3 deu      2017 11064609   4124606  20291380  24213403   22930375      1.47
4 deu      2018 11208996   4050256  20385848  23997586   23254010      1.48
5 fra      2015 11605039   3796374  15326695  16936946   16144712      1.98
6 fra      2016 11565753   3839238  15278129  16878831   16427369      1.98
7 fra      2017 11517825   3877090  15225642  16819154   16704372      1.98
8 fra      2018 11464213   3901693  15168242  16769987   16973673      1.97
# ℹ 2 more variables: co2 <dbl>, gdp_pc <dbl>

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 co2 nicht fehlt, lautet der Befehl

filter(gapminder, 
       !is.na(co2))

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 gm_auswahl zu speichern, gibt man folgendes ein:

gm_auswahl <- filter(gapminder,
                     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.

gapminder <- filter(gapminder,
                    BEDINGUNG)

4.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 gapminder nur die Spalten country, year und co2 anzuzeigen, lautet der Befehl

select(gapminder, 
       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      1986  180.
2 abw      1987  447.
3 abw      1988  612.
4 abw      1989  649.
5 abw      1990  488.
6 abw      1991  532.

Es ist auch möglich, Variablen auszuschließen. Dazu setzt man ein Minuszeichen vor den Variablennamen. Mit select(gapminder, -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.

gm_auswahl <- select(gapminder, 
                     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.

gapminder <- select(gapminder, 
                    VARIABLE1,
                    VARIABLE2)

In diesem Fall gehen alle nicht ausgewählten Variablen verloren.

4.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 Gesamtbevölkerung in jedem Land. Sie ergibt sich als Summe der Bevölkerungszahlen für die fünf Altersklassen,

pop = pop_0_14 + pop_15_19 + pop_20_39 + pop_40_59 + pop_60plus

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).

gapminder <- mutate(gapminder, 
                    pop = pop_0_14 + pop_15_19 + pop_20_39 + pop_40_59 + pop_60plus)

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 gapminder. Der Dataframe ist nun um eine Variable breiter geworden.

head(gapminder)
# A tibble: 6 × 11
  country  year pop_0_14 pop_15_19 pop_20_39 pop_40_59 pop_60plus fertility
  <chr>   <dbl>    <dbl>     <dbl>     <dbl>     <dbl>      <dbl>     <dbl>
1 abw      1986    17467      4449     23256     13396       5989      2.32
2 abw      1987    17097      4103     23441     13838       5971      2.31
3 abw      1988    16779      3786     23499     14274       5996      2.29
4 abw      1989    16798      3653     23290     14786       6070      2.27
5 abw      1990    17453      3876     22691     15477       6218      2.25
6 abw      1991    18796      4531     21748     16321       6468      2.22
# ℹ 3 more variables: co2 <dbl>, gdp_pc <dbl>, pop <dbl>

Als nächstes berechnen wir die Höhe der jährlichen CO2-Emissionen (in t) pro Kopf. Da die gesamten CO2-Emissionen in 1000 t gemessen werden, ergibt sich die Pro-Kopf-Emission als \[ \frac{\text{CO}_2\times 1000}{\text{Population}}. \] Der zugehörige mutate-Befehl lautet

gapminder <- mutate(gapminder, 
                    co2_pc = co2*1000/pop)

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):

gapminder <- mutate(gapminder, 
                    co2_pc = co2*1000/pop,
                    gdp_ges = gdp_pc*pop)

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.

4.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:

gapminder %>% filter(country == "deu") %>% select(year, co2, gdp_pc)
# A tibble: 49 × 3
    year      co2 gdp_pc
   <dbl>    <dbl>  <dbl>
 1  1970 1026862. 17894.
 2  1971 1038085. 18421.
 3  1972 1042332. 19121.
 4  1973 1086625. 19972.
 5  1974 1063594. 20142.
 6  1975 1003265. 20042.
 7  1976 1091730. 21124.
 8  1977 1053466. 21881.
 9  1978 1080011. 22559.
10  1979 1118797. 23485.
# ℹ 39 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:

gapminder %>% 
    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 \(\ln(\sqrt{16})\).

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:

gapminder %>% 
    filter(country == "deu") %>% 
    select(year, 
           co2, 
           gdp_pc)

Hier dient gapminder 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.

4.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 gapminder nach der Größe des Pro-Kopf-Bruttoinlandsprodukts (in US-Dollar, inflationsbereinigt) aufsteigend sortieren (und nur die ersten Zeilen ausgeben lassen):

gapminder %>% 
    arrange(gdp_pc) %>% 
    head()
# A tibble: 6 × 12
  country  year pop_0_14 pop_15_19 pop_20_39 pop_40_59 pop_60plus fertility
  <chr>   <dbl>    <dbl>     <dbl>     <dbl>     <dbl>      <dbl>     <dbl>
1 mmr      1973 11834262   3038432   7737405   4735224    1820032      5.69
2 mmr      1970 11165354   2786178   7205620   4442925    1684035      5.96
3 mmr      1971 11392339   2875103   7376650   4541110    1729752      5.88
4 mmr      1972 11616284   2960809   7558940   4640752    1775163      5.79
5 mmr      1974 12044882   3107766   7912835   4819916    1866538      5.57
6 mmr      1975 12251389   3174573   8104947   4897276    1916092      5.46
# ℹ 4 more variables: co2 <dbl>, gdp_pc <dbl>, pop <dbl>, co2_pc <dbl>

Offensichtlich war das Pro-Kopf-Inlandsprodukt in den frühen 1970er Jahren in Myanmar besonders niedrig.

Wenn wir absteigend (“descending”) sortieren, ist der Output:

gapminder %>% 
    arrange(desc(gdp_pc)) %>% 
    head()
# A tibble: 6 × 12
  country  year pop_0_14 pop_15_19 pop_20_39 pop_40_59 pop_60plus fertility
  <chr>   <dbl>    <dbl>     <dbl>     <dbl>     <dbl>      <dbl>     <dbl>
1 are      1980   277016     59581    532705    124552      20196      5.51
2 are      1977   187350     56105    356541     87941      18925      5.84
3 are      1981   308443     61367    572107    137342      20920      5.42
4 lux      2007    87553     28247    136061    138574      89435      1.62
5 lux      2008    88216     28866    138087    141976      91492      1.61
6 are      1976   164589     46939    304883     78912      18857      5.97
# ℹ 4 more variables: co2 <dbl>, gdp_pc <dbl>, pop <dbl>, co2_pc <dbl>

Das höchste Pro-Kopf-Inlandsprodukt erzielte Luxemburg (lux) im Jahr 2007. Auch die Vereinigten Arabischen Emirate (are) gehörten zu den besonders reichen Staaten,

Die Funktion top_n gibt nur die \(n\) Zeilen mit den größten oder kleinsten Werten einer Variable aus. Zum Beispiel sind die fünf Beobachtungen mit den höchsten CO2-Emissionen in dem Dataframe gapminder:

gapminder %>% 
    top_n(5, co2)
# A tibble: 5 × 12
  country  year  pop_0_14 pop_15_19 pop_20_39 pop_40_59 pop_60plus fertility
  <chr>   <dbl>     <dbl>     <dbl>     <dbl>     <dbl>      <dbl>     <dbl>
1 chn      2013 252201905  87133084 429842988 408952651  197969678      1.6 
2 chn      2014 254332681  84582788 424974096 414083119  207216983      1.61
3 chn      2015 255838329  82839200 421407899 416746198  216883821      1.62
4 chn      2017 259556777  80517491 416665926 418256116  235279647      1.63
5 chn      2018 260606877  79819811 414673234 418026446  243943100      1.64
# ℹ 4 more variables: co2 <dbl>, gdp_pc <dbl>, pop <dbl>, co2_pc <dbl>

Von allen Staaten emittiert also China (chn) in den letzten Jahren die größte Menge an CO2 (absolut, nicht pro Kopf).

Wenn man in der Funktion top_n einen negativen Wert für \(n\) angibt, werden nicht die \(n\) größten, sondern die \(n\) kleinsten Werte herausgesucht. Die Zeilen mit den neun niedrigsten Emissionen sind:

gapminder %>% 
    top_n(-9, co2)
# A tibble: 14 × 12
   country  year pop_0_14 pop_15_19 pop_20_39 pop_40_59 pop_60plus fertility
   <chr>   <dbl>    <dbl>     <dbl>     <dbl>     <dbl>      <dbl>     <dbl>
 1 btn      1980   174212     38203    118739     65844      18261      6.55
 2 bwa      1972   314079     66112    137991     75204      33219      6.58
 3 kir      1970    25729      5765     14181      8106       3658      5.46
 4 kir      1977    25375      6869     15769      8371       3513      5.03
 5 kir      1978    24918      6997     15922      8207       3423      5.06
 6 kir      1983    25208      7959     18634      8717       3605      4.99
 7 kir      1984    25572      7928     19332      8842       3666      4.95
 8 kir      1985    26109      7793     20093      8980       3734      4.91
 9 kir      1986    26829      7603     20875      9155       3805      4.87
10 kir      1987    27681      7387     21642      9366       3872      4.83
11 kir      1988    28592      7173     22365      9603       3951      4.78
12 kir      1989    29528      7016     23000      9861       4031      4.74
13 kir      1990    30436      6948     23516     10126       4098      4.69
14 kir      1991    31286      6953     23874     10342       4161      4.64
# ℹ 4 more variables: co2 <dbl>, gdp_pc <dbl>, pop <dbl>, co2_pc <dbl>

Bhutan hatte also in den 1970er Jahren den niedrigsten Ausstoß von CO2 (wiederum werden hier nicht die Pro-Kopf-Größen betrachtet).

Achtung: Fehlende Werte werden bei der Bestimmung der größten bzw. kleinsten Werte ignoriert.

4.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 Kapitel 7.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.

4.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 gapminder enthält Angaben zu CO2-Emissionen in allen Ländern von 1970 bis 2018 (für viele Länder ist der Datenbestand zu Beginn leider nicht vollständig). Wir beschränken den Datensatz in diesem Beispiel auf alle Beobachtungen des Jahres 2018 und speichern die Auswahl unter dem Namen gm2018.

gm2018 <- filter(gapminder, year == 2018)

Der folgende Code berechnet die Summe der CO2-Emissionen aller Länder (in 1000 t) und die Summe der Bruttoinlandsprodukte aller Länder (in Billionen US-Dollar). Die Schreibweise 1e12 steht für \(1\times 10^{12}\), also eine Billion.

gm2018 %>% 
    summarise(co2welt = sum(co2),
              gdpwelt = sum(gdp_pc*pop)/1e12) -> s
print(s)
# A tibble: 1 × 2
    co2welt gdpwelt
      <dbl>   <dbl>
1 34890019.    81.9

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:

s <- gapminder %>% 
        filter(year == 2018) %>%
        summarise(co2welt = sum(co2),
                  gdpwelt = sum(gdp_pc*pop)/1e12)

Welche Variante man bevorzugt, ist Geschmackssache.

4.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.

s <- gapminder %>% 
        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  2009 30039655.
 2  2010 31576625.
 3  2011 32699321.
 4  2012 33217665.
 5  2013 33384759.
 6  2014 33631760.
 7  2015 33633380.
 8  2016 33719121.
 9  2017 34128587.
10  2018 34890019.

Die Summe der gesamten CO2-Emissionen kann für die Jahre vor 2002 nicht berechnet werden (bzw. sie ist dort NA), weil in diesen Jahren nicht für alle Länder die CO2-Emissionen gegeben sind. Erst ab 2002 sind die Angaben vollständig.

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.

4.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:

gapminder %>%
    filter(year == 2018) %>%
    summarise(weltco2 = sum(co2)) %>%
    pull(weltco2) -> x

print(x)
[1] 34890019
class(x)
[1] "numeric"

Die Pipe-Kette wählt aus dem Dataframe gapminder alle Beobachtungen des Jahres 2018 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.

4.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.

D <- data.frame(Var1=c(3,4,5,4,2,4),
                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.

D_neu <- distinct(D)

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.

4.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.

4.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 gapminder sehen so aus:

tail(gapminder)
# A tibble: 6 × 12
  country  year pop_0_14 pop_15_19 pop_20_39 pop_40_59 pop_60plus fertility
  <chr>   <dbl>    <dbl>     <dbl>     <dbl>     <dbl>      <dbl>     <dbl>
1 zwe      2013  5879046   1456881   4157307   1403405     658781      3.96
2 zwe      2014  5981856   1512159   4227568   1454076     680093      3.9 
3 zwe      2015  6068598   1575412   4295900   1514437     700589      3.84
4 zwe      2016  6144984   1637915   4366371   1583583     719853      3.76
5 zwe      2017  6217927   1690993   4444845   1659646     737689      3.68
6 zwe      2018  6291631   1732035   4533425   1741067     754028      3.61
# ℹ 4 more variables: co2 <dbl>, gdp_pc <dbl>, pop <dbl>, co2_pc <dbl>

Wir möchten nun zwei Zeilen ergänzen, und zwar die Daten für Zimbabwe (zwe) von 2019 und 2020. Diese beiden Beobachtungen liegen in dem Dataframe gm_neu vor, der wie folgt aussieht:

gm_neu
# A tibble: 2 × 14
  country  year pop_0_14 pop_15_19 pop_20_39 pop_40_59 pop_60plus fertility
  <chr>   <dbl>    <dbl>     <dbl>     <dbl>     <dbl>      <dbl>     <dbl>
1 zwe      2019  6217927   1690993   4444845   1659646     737689      3.68
2 zwe      2020  6291631   1732035   4533425   1741067     754028      3.61
# ℹ 6 more variables: co2 <dbl>, gdp_pc <dbl>, pop <dbl>, co2_pc <dbl>,
#   fert <dbl>, co2pc <lgl>

Die neuen Beobachtungen werden nun durch bind_rows an den Dataframe gapminder angehängt. Der neue, längere Dataframe kann unter dem alten Namen (gapminder) oder unter einem neuen Namen abgespeichert werden (hier z.B. unter dem Namen gm_longer).

gm_longer <- bind_rows(gapminder, gm_neu)
tail(gm_longer,8)
# A tibble: 8 × 14
  country  year pop_0_14 pop_15_19 pop_20_39 pop_40_59 pop_60plus fertility
  <chr>   <dbl>    <dbl>     <dbl>     <dbl>     <dbl>      <dbl>     <dbl>
1 zwe      2013  5879046   1456881   4157307   1403405     658781      3.96
2 zwe      2014  5981856   1512159   4227568   1454076     680093      3.9 
3 zwe      2015  6068598   1575412   4295900   1514437     700589      3.84
4 zwe      2016  6144984   1637915   4366371   1583583     719853      3.76
5 zwe      2017  6217927   1690993   4444845   1659646     737689      3.68
6 zwe      2018  6291631   1732035   4533425   1741067     754028      3.61
7 zwe      2019  6217927   1690993   4444845   1659646     737689      3.68
8 zwe      2020  6291631   1732035   4533425   1741067     754028      3.61
# ℹ 6 more variables: co2 <dbl>, gdp_pc <dbl>, pop <dbl>, co2_pc <dbl>,
#   fert <dbl>, co2pc <lgl>

4.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:

D <- data.frame(id=1:5, 
                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:

D2 <- data.frame(id=c(2,1,5,4), 
                 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.

X <- data.frame(jahr=rep(2020:2022,3),
                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:

preise <- data.frame(jahr=c(2020,2021,2022), 
                     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.

X <- left_join(X, preise, by="jahr")
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: Gapminder Gesundheitsdaten

In diesem dritten Beispiel wird gezeigt, wie man weitere Variablen an den Gapminder-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 gapminder, dessen ersten Zeilen so aussehen:

head(gapminder)
# A tibble: 6 × 12
  country  year pop_0_14 pop_15_19 pop_20_39 pop_40_59 pop_60plus fertility
  <chr>   <dbl>    <dbl>     <dbl>     <dbl>     <dbl>      <dbl>     <dbl>
1 abw      1986    17467      4449     23256     13396       5989      2.32
2 abw      1987    17097      4103     23441     13838       5971      2.31
3 abw      1988    16779      3786     23499     14274       5996      2.29
4 abw      1989    16798      3653     23290     14786       6070      2.27
5 abw      1990    17453      3876     22691     15477       6218      2.25
6 abw      1991    18796      4531     21748     16321       6468      2.22
# ℹ 4 more variables: co2 <dbl>, gdp_pc <dbl>, pop <dbl>, co2_pc <dbl>

An diesen Dataframe soll nun ein zweiter Dataframe mit einigen Gesundheitsvariablen angespielt werden. Wir laden diesen zweiten Datensatz (der auch im Learnweb zu finden ist).

gm_health <- read_csv("../data/gapminderhealth.csv")
Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
  dat <- vroom(...)
  problems(dat)

Die ersten Zeilen sehen so aus:

head(gm_health)
# A tibble: 6 × 9
  country  year lifexpF lifexpM diarrh hiv   malaria mening pneumon
  <chr>   <dbl>   <dbl>   <dbl> <lgl>  <lgl> <lgl>   <lgl>  <lgl>  
1 abw      1970    71.6    63.7 NA     NA    NA      NA     NA     
2 abw      1971    72.0    64.1 NA     NA    NA      NA     NA     
3 abw      1972    72.8    64.5 NA     NA    NA      NA     NA     
4 abw      1973    73.5    64.9 NA     NA    NA      NA     NA     
5 abw      1974    73.9    65.3 NA     NA    NA      NA     NA     
6 abw      1975    74.3    65.4 NA     NA    NA      NA     NA     

Der Datensatz enthält die Variablen lifexpF und lifexpM (Lebenserwartung der Frauen und Männer) sowie die Zahl von Todesfällen von Kindern bis zum Alter von 5 Jahren an Durchfall (diarrh), HIV (hiv), Malaria (malaria), Hirnhautentzündung (mining) und Lungenentzündung (pneumon). Die Zahl der verstorbenen Kinder wird auf 1000 Geburten bezogen, sie gibt also an, wie viele von 1000 geborenen Kindern in den ersten Lebensjahren an diesen Krankheiten gestorben sind.

Der Dataframe gm_health wird nun an den Dataframe gapminder 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 gm_wider gespeichert.

gm_wider <- left_join(gapminder, gm_health,
                      by=c("country","year"))

Dieser neue Dataframe hat die Variablen (Spalten)

names(gm_wider)
 [1] "country"    "year"       "pop_0_14"   "pop_15_19"  "pop_20_39" 
 [6] "pop_40_59"  "pop_60plus" "fertility"  "co2"        "gdp_pc"    
[11] "pop"        "co2_pc"     "lifexpF"    "lifexpM"    "diarrh"    
[16] "hiv"        "malaria"    "mening"     "pneumon"   

Die ersten Zeilen des verbreiterten Dataframes lauten (der Dataframe ist zu breit, um hier in kompletter Zeilenlänge angezeigt werden zu können):

head(gm_wider)
# A tibble: 6 × 19
  country  year pop_0_14 pop_15_19 pop_20_39 pop_40_59 pop_60plus fertility
  <chr>   <dbl>    <dbl>     <dbl>     <dbl>     <dbl>      <dbl>     <dbl>
1 abw      1986    17467      4449     23256     13396       5989      2.32
2 abw      1987    17097      4103     23441     13838       5971      2.31
3 abw      1988    16779      3786     23499     14274       5996      2.29
4 abw      1989    16798      3653     23290     14786       6070      2.27
5 abw      1990    17453      3876     22691     15477       6218      2.25
6 abw      1991    18796      4531     21748     16321       6468      2.22
# ℹ 11 more variables: co2 <dbl>, gdp_pc <dbl>, pop <dbl>, co2_pc <dbl>,
#   lifexpF <dbl>, lifexpM <dbl>, diarrh <lgl>, hiv <lgl>, malaria <lgl>,
#   mening <lgl>, pneumon <lgl>