10 Daten umstrukturieren und zusammenfügen

Datenhandling umfasst oft auch etwas komplexere Schritte, als lediglich relevante Variablen zu selektieren und nach bestimmten Bedingungen zu filtern. In diesem Kapitel sehen wir uns daher an, wie wir Daten umstrukturieren und mehrere Datensätze zusammenfügen können.

10.1 Daten umstrukturieren

Wir haben in Kapitel 7.1.1 gesehen, dass dieselben Daten ganz unterschiedlich tabellarisch abgebildet werden können. Oftmals ist es jedoch so, dass wir unsere Daten in einem ganz bestimmten Format benötigen, um mit diesen weiterarbeiten zu können – beispielsweise um diese an eine Funktion, die ein statistisches Verfahren implementiert, zu übergeben oder diese grafisch darzustellen. Hierfür bietet das Tidyverse einige Funktionen, um Datensätze schnell umzustrukturieren.

10.1.1 Wide vs. Long Data

Einer der häufigsten Fälle der Umstrukturierung von Datensätzen betrifft das Konvertieren von Wide Data in Long Data und umgekehrt.

Nehmen wir als Beispiel einen Datensatz, der die Auflagezahlen verschiedener politischer Wochenmagazine beinhält:

## # A tibble: 3 x 4
##   medium      auflage_2018 auflage_2019 auflage_2020
##   <chr>              <dbl>        <dbl>        <dbl>
## 1 Der Spiegel       708077       701337       685799
## 2 Stern             539191       476097       422156
## 3 Focus             425737       373847       328587

Man spricht hierbei von Wide Data: Mehrere Beobachtungen desselben Wertetyps stehen in unterschiedlichen Spalten – in diesem Fall haben wir drei Spalten, in denen jeweils Auflagenzahlen stehen. Unsere Fallebene ist das einzelne Medium: jeweils eine Zeile für Spiegel, Stern und Focus.

Wir könnten dieselben Daten aber auch so darstellen:

## # A tibble: 9 x 3
##   medium       jahr auflage
##   <chr>       <int>   <dbl>
## 1 Der Spiegel  2018  708077
## 2 Der Spiegel  2019  701337
## 3 Der Spiegel  2020  685799
## 4 Stern        2018  539191
## 5 Stern        2019  476097
## 6 Stern        2020  422156
## 7 Focus        2018  425737
## 8 Focus        2019  373847
## 9 Focus        2020  328587

Hierbei handelt es sich um Long Data: alle Beobachtungen desselben Wertetyps, also z. B. die Auflagenzahlen, stehen in einer Spalte, die weiteren Spalten dienen zur Identifikation dieser Beobachtungen, hier also, für welches Jahr und für welches Medium diese gelten. Unsere Fallebene wäre also Medium und Jahr: jeweils eine Zeile pro Medium und Jahr.

Wide Data ist häufig intuitiver zu lesen, insbesondere wenn es sich um bivariate Verteilungen handelt, da die Darstellung einer Kreuztabelle gleicht. Tatsächlich würden wir diese erste tabellarische Darstellung aber nicht als tidy bezeichnen, da derselbe Werte- bzw. Beobachtungstyp in unterschiedlichen Spalten steht.

Viele Funktionen in R sind jedoch auf Long Data ausgelegt. Häufig müssen wir daher Wide Data in Long Data transformieren:

10.1.2 Wide Data in Long Data transformieren mit pivot_longer()

Mittels pivot_longer() können wir Wide Data in Long Data umwandeln. Als erstes Argument nutzen wir hierbei wie immer den Datensatz, den wir transformieren möchten, gefolgt von einem Vektor, der alle Spalten umfasst, die “länger” gemacht werden sollen.

Als Beispiel nutzen wir den ersten Auflagen-Datensatz aus dem vorherigen Unterkapitel, der über folgenden Code erstellt wird:

auflagen_wide <- tibble(
  medium = c("Der Spiegel", "Stern", "Focus"),
  auflage_2018 = c(708077, 539191, 425737),
  auflage_2019 = c(701337, 476097, 373847),
  auflage_2020 = c(685799, 422156, 328587)
)

Wir möchten nun alle Auflagenspalten (auflage_2018, auflage_2019, auflage_2020) transformieren, sodass alle Auflagenwerte in einer Spalte stehen. Wir übergeben diese Spalten daher als zweites Argument als pivot_longer():

auflagen_wide %>% 
  pivot_longer(c(auflage_2018, auflage_2019, auflage_2020))
## # A tibble: 9 x 3
##   medium      name          value
##   <chr>       <chr>         <dbl>
## 1 Der Spiegel auflage_2018 708077
## 2 Der Spiegel auflage_2019 701337
## 3 Der Spiegel auflage_2020 685799
## 4 Stern       auflage_2018 539191
## 5 Stern       auflage_2019 476097
## 6 Stern       auflage_2020 422156
## 7 Focus       auflage_2018 425737
## 8 Focus       auflage_2019 373847
## 9 Focus       auflage_2020 328587

Das führt uns schon (fast) zum gewünschten Ergebnis. Wie wir sehen, stehen nun alle Auflagenzahlen in der Spalte value, die Information aus den Spaltennamen stehen in der Spalte name. Wir können den Aufruf aber noch etwas anpassen, um die Ausgabe zu optimieren:

  • Mit nur drei Variablen war es kein großer Aufwand, alle Variablen zum umtransformieren einzeln anzugeben. Wenn wir jedoch Auflagenzahlen der vergangenen 50 Jahre hätten, wäre dies sehr viel Tipparbeit. Wir können jedoch alle Hilfsfunktionen, die auch bei select() (siehe Kapitel 8.2.1) zur Verfügung stehen verwenden. In unserem Fall könnten wir uns die Arbeit beispielsweise mit starts_with("auflage") abkürzen.19
  • Die neuen Variablen haben recht generische Namen. Hier können wir mit den Argumenten names_to (Name(n) der neuen Spalte(n) basierend auf den alten Spaltennamen) und values_to (Name der neuen Wertespalte) Text-Vektoren übergeben, die die neuen Spaltennamen beinhalten. In unserem Fall bieten sich daher names_to = "jahr" und values_to = "auflage" an.
  • Der Bestandteil "auflage_" ist redundant; schöner wäre es wenn lediglich die Jahreszahl angeben wird. Hierfür können wir das Argument names_prefix nutzen und einen Textbestandteil der Spaltennamen angeben, der abgeschnitten werden soll – in unserem Fall also names_prefix = "auflage_".
auflagen_wide %>% 
  pivot_longer(starts_with("auflage"), names_to = "jahr", values_to = "auflage", names_prefix = "auflage_")
## # A tibble: 9 x 3
##   medium      jahr  auflage
##   <chr>       <chr>   <dbl>
## 1 Der Spiegel 2018   708077
## 2 Der Spiegel 2019   701337
## 3 Der Spiegel 2020   685799
## 4 Stern       2018   539191
## 5 Stern       2019   476097
## 6 Stern       2020   422156
## 7 Focus       2018   425737
## 8 Focus       2019   373847
## 9 Focus       2020   328587

Schon fast perfekt! Das einzige Schönheitsfehler ist, dass die Variable jahr nun vom Typ character ist – pivot_longer() weist diesen Typ allen Informationen zu, die aus den ursprünglichen Spaltennamen stammen. Sinnvoller wäre ein numerischer Objekttyp. Hier können wir dem Argument names_transform eine Liste übergeben (auch wenn dies nur eine Variable betrifft), die für alle aus den ursprünglichen Spaltennamen generierten Variablen jeweils den gewünschten Objekttyp angibt:

# Da nun alles passt, weisen wir unseren neuen Long-Datensatz auch einem Objekt zu
auflagen_long <- auflagen_wide %>% 
  pivot_longer(starts_with("auflage"), names_to = "jahr", values_to = "auflage", 
               names_prefix = "auflage_", names_transform = list(jahr = as.integer))

auflagen_long
## # A tibble: 9 x 3
##   medium       jahr auflage
##   <chr>       <int>   <dbl>
## 1 Der Spiegel  2018  708077
## 2 Der Spiegel  2019  701337
## 3 Der Spiegel  2020  685799
## 4 Stern        2018  539191
## 5 Stern        2019  476097
## 6 Stern        2020  422156
## 7 Focus        2018  425737
## 8 Focus        2019  373847
## 9 Focus        2020  328587

Betrachten wir ein zweites, noch etwas komplexeres Beispiel:

auflagen_wide2 <- tibble(
  medium = c("Der Spiegel", "Stern", "Focus"),
  auflage_2018_q1 = c(708077, 539191, 425737),
  auflage_2018_q2 = c(704656, 528860, 417759),
  auflage_2018_q3 = c(716663, 514889, 412165),
  auflage_2018_q4 = c(712268, 480739, 413276),
  auflage_2019_q1 = c(701337, 476097, 373847),
  auflage_2019_q2 = c(707459, 464489, 367101),
  auflage_2019_q3 = c(719326, 466019, 364254),
  auflage_2019_q4 = c(691451, 440284, 349944),
  auflage_2020_q1 = c(685799, 422156, 328587)
)

auflagen_wide2
## # A tibble: 3 x 10
##   medium      auflage_2018_q1 auflage_2018_q2 auflage_2018_q3 auflage_2018_q4 auflage_2019_q1 auflage_2019_q2 auflage_2019_q3 auflage_2019_q4 auflage_2020_q1
##   <chr>                 <dbl>           <dbl>           <dbl>           <dbl>           <dbl>           <dbl>           <dbl>           <dbl>           <dbl>
## 1 Der Spiegel          708077          704656          716663          712268          701337          707459          719326          691451          685799
## 2 Stern                539191          528860          514889          480739          476097          464489          466019          440284          422156
## 3 Focus                425737          417759          412165          413276          373847          367101          364254          349944          328587

Nun haben in den Spaltennamen gleich zwei Informationen: das Jahr und das jeweilige Quartal. Es ist daher sinnvoll, diese Informationen beim Transformieren in getrennten Spalten zu speichern. Hierzu können wir zusätzlich das Argument names_sep heranziehen, mit dem wir ein(e) Zeichen(kette) übergeben – in unserem Fall den Unterstrich _, an der wir die ursprünglichen Spaltennamen weiter aufteilen möchten. Entsprechend müssen wir in names_to dann mehrere neuen Spaltennamen angeben – in unserem Fall 2, da wir jahr und quartal trennen möchten:20

auflagen_long2 <- auflagen_wide2 %>% 
  pivot_longer(starts_with("auflage"), names_to = c("jahr", "quartal"), values_to = "auflage", 
               names_prefix = "auflage_", names_sep = "_", names_transform = list(jahr = as.integer))

auflagen_long2
## # A tibble: 27 x 4
##    medium       jahr quartal auflage
##    <chr>       <int> <chr>     <dbl>
##  1 Der Spiegel  2018 q1       708077
##  2 Der Spiegel  2018 q2       704656
##  3 Der Spiegel  2018 q3       716663
##  4 Der Spiegel  2018 q4       712268
##  5 Der Spiegel  2019 q1       701337
##  6 Der Spiegel  2019 q2       707459
##  7 Der Spiegel  2019 q3       719326
##  8 Der Spiegel  2019 q4       691451
##  9 Der Spiegel  2020 q1       685799
## 10 Stern        2018 q1       539191
## # ... with 17 more rows

pivot_longer() bietet also eine mächtige, wenn auch anfangs nicht immer intuitive Syntax zum umformen von Datensätzen – aber auch ohne die Kenntnis aller Zusatzargumente hätten wir einen tranformierten Datensatz, den wir mit wenigen zusätzlichen Funktionen wie select() oder rename() sowie mutate() in unsere gewünschte Form bringen können.

10.1.3 Long Data in Wide Data transformieren mit pivot_wider()

Umgekehrt können wir natürlich auch Wide Data in Long Data transformieren. Die passende Funktion heißt pivot_wider(). Hier geben wir mittels names_from an, die Werte welcher Variablen in Spalten überführt werden sollen, und definieren mit values_from, welche Variable die Werte für die neu erzeugten Spalten liefert.

Nehmen wir Long-Variante unseres jährlichen Auflagen-Datensatzes:

auflagen_long
## # A tibble: 9 x 3
##   medium       jahr auflage
##   <chr>       <int>   <dbl>
## 1 Der Spiegel  2018  708077
## 2 Der Spiegel  2019  701337
## 3 Der Spiegel  2020  685799
## 4 Stern        2018  539191
## 5 Stern        2019  476097
## 6 Stern        2020  422156
## 7 Focus        2018  425737
## 8 Focus        2019  373847
## 9 Focus        2020  328587

Um diesen in den ursprünglichen Wide-Datensatz zu transformieren, wollen wir also die Werte in jahr in Spaltennamen umwandeln und die Werte in auflage auf diese neuen Spalten verteilen. Wir geben also names_from = "jahr" und values_from = "auflage" an:

auflagen_long %>% 
  pivot_wider(names_from = jahr, values_from = auflage)
## # A tibble: 3 x 4
##   medium      `2018` `2019` `2020`
##   <chr>        <dbl>  <dbl>  <dbl>
## 1 Der Spiegel 708077 701337 685799
## 2 Stern       539191 476097 422156
## 3 Focus       425737 373847 328587

Da Spaltennamen, die nicht mit Buchstaben beginnen, suboptimal sind und wir nun nicht mehr direkt sehen können, dass es sich um Auflagen-Daten handelt, können und sollten wir mittels names_prefix auch wieder die ursprünglichen Spaltennamen herstellen21:

auflagen_long %>% 
  pivot_wider(names_from = jahr, values_from = auflage, names_prefix = "auflage_")
## # A tibble: 3 x 4
##   medium      auflage_2018 auflage_2019 auflage_2020
##   <chr>              <dbl>        <dbl>        <dbl>
## 1 Der Spiegel       708077       701337       685799
## 2 Stern             539191       476097       422156
## 3 Focus             425737       373847       328587

Im zweiten Fall stehen in mehreren Spalten Informationen, die wir in Spaltennamen überführen wollen, nämlich jahr und quartal:

auflagen_long2
## # A tibble: 27 x 4
##    medium       jahr quartal auflage
##    <chr>       <int> <chr>     <dbl>
##  1 Der Spiegel  2018 q1       708077
##  2 Der Spiegel  2018 q2       704656
##  3 Der Spiegel  2018 q3       716663
##  4 Der Spiegel  2018 q4       712268
##  5 Der Spiegel  2019 q1       701337
##  6 Der Spiegel  2019 q2       707459
##  7 Der Spiegel  2019 q3       719326
##  8 Der Spiegel  2019 q4       691451
##  9 Der Spiegel  2020 q1       685799
## 10 Stern        2018 q1       539191
## # ... with 17 more rows

Zum Transformieren nutzen wir erneut names_from, nur geben dieses Mal beide Variablen an, die für die Spaltennamen kombiniert werden sollen, und übergeben mit names_sep wieder ein Trennzeichen22:

auflagen_long2 %>% 
  pivot_wider(names_from = c(jahr, quartal), values_from = auflage, names_prefix = "auflage_", names_sep = "_")
## # A tibble: 3 x 10
##   medium      auflage_2018_q1 auflage_2018_q2 auflage_2018_q3 auflage_2018_q4 auflage_2019_q1 auflage_2019_q2 auflage_2019_q3 auflage_2019_q4 auflage_2020_q1
##   <chr>                 <dbl>           <dbl>           <dbl>           <dbl>           <dbl>           <dbl>           <dbl>           <dbl>           <dbl>
## 1 Der Spiegel          708077          704656          716663          712268          701337          707459          719326          691451          685799
## 2 Stern                539191          528860          514889          480739          476097          464489          466019          440284          422156
## 3 Focus                425737          417759          412165          413276          373847          367101          364254          349944          328587

10.1.4 Spalten aufteilen mit separate()

Schauen wir uns dieselben Daten nochmals in einer anderen Darstellung an. Mehrere Werte in einer Spalte – gruselig, aber kommt vor:

auflagen_gruselig <- tibble(
  medium = c("Der Spiegel", "Stern", "Focus"),
  auflagen_18_bis_20 = c("708077;701337;685799", 
                         "539191;476097;422156", 
                         "425737;373847;328587"),
)

auflagen_gruselig
## # A tibble: 3 x 2
##   medium      auflagen_18_bis_20  
##   <chr>       <chr>               
## 1 Der Spiegel 708077;701337;685799
## 2 Stern       539191;476097;422156
## 3 Focus       425737;373847;328587

Hier hilft uns die Funktion separate(), mit der wir mit dem Argument col eine Spalte zum Trennen, mit dem Argument into einen Vektor mit neuen Spaltennamen und dem Argument sep ein Trennzeichen, an dem die bisherigen Werte getrennt werden sollen, mitteilen.

In unserem Fall möchten wir die Spalte auflagen_18_bis_20 am Semikolon ; aufteilen:

auflagen_gruselig %>% 
  separate(col = auflagen_18_bis_20, into = c("auflage_2018", "auflage_2019", "auflage_2020"), sep = ";")
## # A tibble: 3 x 4
##   medium      auflage_2018 auflage_2019 auflage_2020
##   <chr>       <chr>        <chr>        <chr>       
## 1 Der Spiegel 708077       701337       685799      
## 2 Stern       539191       476097       422156      
## 3 Focus       425737       373847       328587

Das hat schon gut funktioniert, nur ist der Objekttyp nun jeweils character. Mit dem zusätzlichen Argument convert = TRUE versucht separate(), den neuen Spalten direkt einen passenderen numerischen Objekttyp zu geben:

auflagen_gruselig %>% 
  separate(col = auflagen_18_bis_20, into = c("auflage_2018", "auflage_2019", "auflage_2020"), sep = ";",
           convert = TRUE)
## # A tibble: 3 x 4
##   medium      auflage_2018 auflage_2019 auflage_2020
##   <chr>              <int>        <int>        <int>
## 1 Der Spiegel       708077       701337       685799
## 2 Stern             539191       476097       422156
## 3 Focus             425737       373847       328587

10.2 Daten zusammenfügen

In größeren Forschungsprojekten haben wir oftmals nicht nur einen Datensatz, sondern mehrere Datensätze – etwa weil verschiedene Teildatensätze getrennt erhoben wurden oder weil diese auf unterschiedlichen Ebenen liegen. Für weitere Analysen sollen diese nun zusammengefügt werden.

10.2.1 Teildatensätze zusammenfügen mit bind_rows() und bind_cols()

Beginnen wir mit dem einfachsten Fall, dem Zusammenfügen von gleichförmigen Datensätzen: wir wollen einem bestehenden Datensatz also entweder neue Zeilen (= neue Fälle) oder neue Spalten (=neue Variablen hinzufügen). Hierfür bietet das Tidyverse die Funktionen bind_rows() und bind_cols(). Bei beiden werden als Argumente einfach die zu verbindenden Datensätze angegeben.

Nehmen wir einmal an, wir hätten die Auflagendaten aus den vorherigen Beispielen getrennt für die Nachrichtenmedien erhoben:

# Aufteilen des Datensatzes

auflagen_spiegel <- auflagen_long %>% 
  filter(medium == "Der Spiegel")

auflagen_stern <- auflagen_long %>% 
  filter(medium == "Stern")

auflagen_focus <- auflagen_long %>% 
  filter(medium == "Focus")

Vor uns liegen nun also drei Datensätze, die jeweils Daten eines Nachrichtenmediums enthalten:

auflagen_spiegel
## # A tibble: 3 x 3
##   medium       jahr auflage
##   <chr>       <int>   <dbl>
## 1 Der Spiegel  2018  708077
## 2 Der Spiegel  2019  701337
## 3 Der Spiegel  2020  685799
auflagen_stern
## # A tibble: 3 x 3
##   medium  jahr auflage
##   <chr>  <int>   <dbl>
## 1 Stern   2018  539191
## 2 Stern   2019  476097
## 3 Stern   2020  422156
auflagen_focus
## # A tibble: 3 x 3
##   medium  jahr auflage
##   <chr>  <int>   <dbl>
## 1 Focus   2018  425737
## 2 Focus   2019  373847
## 3 Focus   2020  328587

Praktischerweise sind die Datensätze gleichförmig: alle drei haben dieselben Variablen. Wir können diese also einfach mit bind_rows() untereinander kleben, um einen gemeinsamen Datensatz zu erstellen:

auflagen_spiegel %>% 
  bind_rows(auflagen_stern, auflagen_focus)
## # A tibble: 9 x 3
##   medium       jahr auflage
##   <chr>       <int>   <dbl>
## 1 Der Spiegel  2018  708077
## 2 Der Spiegel  2019  701337
## 3 Der Spiegel  2020  685799
## 4 Stern        2018  539191
## 5 Stern        2019  476097
## 6 Stern        2020  422156
## 7 Focus        2018  425737
## 8 Focus        2019  373847
## 9 Focus        2020  328587

Fehlende Variablen in einem Teildatensatz werden automatisch durch fehlende Werte ersetzt:

auflagen_spiegel %>% 
  select(-auflage) %>% # Wir entfernen zu Demonstrationszwecken die Auflage-Spalte
  bind_rows(auflagen_stern, auflagen_focus)
## # A tibble: 9 x 3
##   medium       jahr auflage
##   <chr>       <int>   <dbl>
## 1 Der Spiegel  2018      NA
## 2 Der Spiegel  2019      NA
## 3 Der Spiegel  2020      NA
## 4 Stern        2018  539191
## 5 Stern        2019  476097
## 6 Stern        2020  422156
## 7 Focus        2018  425737
## 8 Focus        2019  373847
## 9 Focus        2020  328587

Hier haben wir den Vorteil, dass die Identifikationsvariable medium bereits vorhanden ist, sodass wir die Auflagendaten auch im vollständigen Datensatz problemlos zuordnen können. Was, wenn das nicht der Fall ist?

# Wir entfernen zu Demonstrationszwecken die Medium-Variable
auflagen_spiegel <- select(auflagen_spiegel, -medium)
auflagen_stern <- select(auflagen_stern, -medium)
auflagen_focus <- select(auflagen_focus, -medium)

auflagen_spiegel
## # A tibble: 3 x 2
##    jahr auflage
##   <int>   <dbl>
## 1  2018  708077
## 2  2019  701337
## 3  2020  685799

Zwar können wir die Teildatensätze nun anhand des Namens gut identifizieren, bekommen aber bei einem Gesamtdatensatz Probleme, die Auflagenzahlen zuzuordnen:

auflagen_spiegel %>% 
  bind_rows(auflagen_stern, auflagen_focus)
## # A tibble: 9 x 2
##    jahr auflage
##   <int>   <dbl>
## 1  2018  708077
## 2  2019  701337
## 3  2020  685799
## 4  2018  539191
## 5  2019  476097
## 6  2020  422156
## 7  2018  425737
## 8  2019  373847
## 9  2020  328587

In solchen Fällen können wir die Datensätze beim “zusammenkleben” benennen und über das .id-Argument einen Spaltennamen für die Identifikationsvariable festlegen:

bind_rows("Der Spiegel" = auflagen_spiegel,
          "Stern" = auflagen_stern, 
          "Focus" = auflagen_focus,
          .id = "medium")
## # A tibble: 9 x 3
##   medium       jahr auflage
##   <chr>       <int>   <dbl>
## 1 Der Spiegel  2018  708077
## 2 Der Spiegel  2019  701337
## 3 Der Spiegel  2020  685799
## 4 Stern        2018  539191
## 5 Stern        2019  476097
## 6 Stern        2020  422156
## 7 Focus        2018  425737
## 8 Focus        2019  373847
## 9 Focus        2020  328587

Ganz ähnlich funktioniert auch bind_cols(), nur dass wir dieses Mal Variablen, also Spalten hinzufügen. Erinnern wir uns an den Wide-Datensatz der Auflagendaten:

auflagen_wide
## # A tibble: 3 x 4
##   medium      auflage_2018 auflage_2019 auflage_2020
##   <chr>              <dbl>        <dbl>        <dbl>
## 1 Der Spiegel       708077       701337       685799
## 2 Stern             539191       476097       422156
## 3 Focus             425737       373847       328587

Nun haben wir auch noch ein paar ältere Auflagendaten erhoben:

auflagen_alt <- tibble(
  medium = c("Der Spiegel", "Stern", "Focus"),
  auflage_2016 = c(793087, 719290, 474285),
  auflage_2017 = c(771066, 595729, 456020)
)

auflagen_alt
## # A tibble: 3 x 3
##   medium      auflage_2016 auflage_2017
##   <chr>              <dbl>        <dbl>
## 1 Der Spiegel       793087       771066
## 2 Stern             719290       595729
## 3 Focus             474285       456020

Wir ergänzen diese Variablen mittels bind_cols():

auflagen_wide %>% 
  bind_cols(auflagen_alt)
## New names:
## * medium -> medium...1
## * medium -> medium...5
## # A tibble: 3 x 7
##   medium...1  auflage_2018 auflage_2019 auflage_2020 medium...5  auflage_2016 auflage_2017
##   <chr>              <dbl>        <dbl>        <dbl> <chr>              <dbl>        <dbl>
## 1 Der Spiegel       708077       701337       685799 Der Spiegel       793087       771066
## 2 Stern             539191       476097       422156 Stern             719290       595729
## 3 Focus             425737       373847       328587 Focus             474285       456020

Wie wir sehen, hat das ganz gut funktioniert – die Mediumspalte ist nun doppelt vorhanden, aber mittels select() könnten wir diese schnell ausschließen. Generell ist aber bis auf in Ausnahmefällen vom Hinzufügen von Variablen mittel bind_cols() abzuraten, da die Prozedur sehr fehleranfällig ist – sind beide Datensätze nicht gleich sortiert, haben Sie schnell Daten miteinander verbunden, die gar nicht zusammengehören. Ist eine Identifikations-Variable vorhanden, so gibt es deutlich sinnvollere und sicherere Funktionen:

10.2.2 Relationale Daten zusammenführen

Nehmen wir an, dass wir ein Forschungsprojekt zu politischen Wochenmagazinen durchführen. Wir haben dazu neben den bereits bekannten Auflagenzahlen auch noch Daten zu den Magazinen selbst und einige Artikel erhoben und diese in den Datensätzen auflagen, info und artikel abgelegt.

Der Datensatz auflagen enthält die uns bereits bekannten Auflagenzahlen auf Ebene Medium pro Quartal und Jahr:

# Falls Sie direkt mitarbeiten möchten: 
# Kopieren Sie bei den folgenden drei Code-Blöcken den Code
# um die Beispieldatensätze zu erstellen
auflagen <- auflagen_long2
auflagen
## # A tibble: 27 x 4
##    medium       jahr quartal auflage
##    <chr>       <int> <chr>     <dbl>
##  1 Der Spiegel  2018 q1       708077
##  2 Der Spiegel  2018 q2       704656
##  3 Der Spiegel  2018 q3       716663
##  4 Der Spiegel  2018 q4       712268
##  5 Der Spiegel  2019 q1       701337
##  6 Der Spiegel  2019 q2       707459
##  7 Der Spiegel  2019 q3       719326
##  8 Der Spiegel  2019 q4       691451
##  9 Der Spiegel  2020 q1       685799
## 10 Stern        2018 q1       539191
## # ... with 17 more rows

Der Datensatz info enthält allgemeine Informationen zu den Magazinen auf Mediums-Ebene:

info <- tibble(
  medium = c("Der Spiegel", "Stern", "Focus"),
  sitz = c("Hamburg", "Hamburg", "Berlin"),
  erscheinungstag = c("Samstag", "Donnerstag", "Samstag"),
  erstausgabe = lubridate::dmy(c("04-01-1947", "01-08-1948", "18-01-1993"))
)

info
## # A tibble: 3 x 4
##   medium      sitz    erscheinungstag erstausgabe
##   <chr>       <chr>   <chr>           <date>     
## 1 Der Spiegel Hamburg Samstag         1947-01-04 
## 2 Stern       Hamburg Donnerstag      1948-08-01 
## 3 Focus       Berlin  Samstag         1993-01-18

Der Datensatz artikel schließlich enthält alle für das Forschungsprojekt relevanten Artikel auf Artikel-Ebene:

artikel <- tibble(
  titel = c("Ein spannender Artikel", "Noch ein Artikel", "Und noch ein Artikel", "Und noch einer"),
  autor_innen = c("Max Mustermann", "Erika Musterfrau", "John Doe; Jane Doe", "Mario Rossi"),
  medium = c("Stern", "Stern", "Der Spiegel", "Focus"),
  ausgabe = c(1L, 1L, 1L, 1L),
  jahr = c(2020L, 2020L, 2020L, 2020L),
  seiten = c("20-22", "11", "17-25", "104-106")
)

artikel
## # A tibble: 4 x 6
##   titel                  autor_innen        medium      ausgabe  jahr seiten 
##   <chr>                  <chr>              <chr>         <int> <int> <chr>  
## 1 Ein spannender Artikel Max Mustermann     Stern             1  2020 20-22  
## 2 Noch ein Artikel       Erika Musterfrau   Stern             1  2020 11     
## 3 Und noch ein Artikel   John Doe; Jane Doe Der Spiegel       1  2020 17-25  
## 4 Und noch einer         Mario Rossi        Focus             1  2020 104-106

Insgesamt haben wir also drei verschiedene Datensätze, deren Fälle allesamt auf unterschiedlichen Ebenen liegen. Allerdings hängen die Datensätze auch implizit zusammen: die Variable medium findet sich in allen drei Datensätzen; über sie können wir zwei oder alle drei Datensätze zusammenführen. Datensätze, die über Variablen miteinander verbunden werden können, nennt man relationale Daten, sie stehen also in einer Beziehung zueinander.

10.2.2.1 Schlüsselvariablen (Keys)

Variablen, über die Datensätze zusammengefügt werden, bezeichnet man als Schlüsselvariablen, im Englischen auch Keys. Schlüsselvariablen identifzieren Fälle in einem Datensatz eindeutig. Je nach Datenaufbereitung kann hierzu eine einzige Variable ausreichen; manchmal sind aber auch Kombinationen aus mehreren Variablen nötig, um Fälle eindeutig zuzuordnen.

Zudem werden zwei Typen von Schlüsselvariablen unterschieden:

  • als primary key wird eine Variable bzw. eine Kombination von Variablen bezeichnet, die im aktuellen Datensatz einen Fall eindeutig identifizieren.
  • als foreign key wird eine Variable bzw. eine Kombination von Variablen bezeichnet, die in einem anderen Datensatz Fälle eindeutig identifizieren.

Im Beispieldatensatz info ist medium der primary key. Jeder Wert von medium kommt exakt einmal vor und identifiziert einen Fall (= eine Zeile) somit eindeutig:

## # A tibble: 3 x 4
##   medium      sitz    erscheinungstag erstausgabe
##   <chr>       <chr>   <chr>           <date>     
## 1 Der Spiegel Hamburg Samstag         1947-01-04 
## 2 Stern       Hamburg Donnerstag      1948-08-01 
## 3 Focus       Berlin  Samstag         1993-01-18

Wie sieht die Sache im Datensatz artikel aus:

## # A tibble: 4 x 6
##   titel                  autor_innen        medium      ausgabe  jahr seiten 
##   <chr>                  <chr>              <chr>         <int> <int> <chr>  
## 1 Ein spannender Artikel Max Mustermann     Stern             1  2020 20-22  
## 2 Noch ein Artikel       Erika Musterfrau   Stern             1  2020 11     
## 3 Und noch ein Artikel   John Doe; Jane Doe Der Spiegel       1  2020 17-25  
## 4 Und noch einer         Mario Rossi        Focus             1  2020 104-106

Hier wäre titel der naheliegendste primary key, da darüber alle Fälle eindeutig identifiziert werden können. medium taugt hier hingegen nicht als primary key, da der Wert "Stern" nicht eindeutig ist – es gibt zwei Artikel vom Stern im Datensatz, entsprechend reicht die bloße Angabe "Stern" nicht aus, um einen Fall in artikel eindeutig zu identifizieren.

medium kann hier aber als foreign key für den Datensatz info dienen, da die Werte aus medium im Datensatz artikel sich eindeutig Fällen im Datensatz info zuordnen lassen – jeder der drei einzigartigen Werte von medium im Datensatz artikel ("Stern", "Der Spiegel" & "Focus") kommt nur einmal in der Variable medium im Datensatz info vor.

Da medium in info primary key und in artikel foreign key ist, bilden diese beiden Datensätze über die Schlüsselvariablen medium eine Relation. Wir können nun Daten aus dem Datensatz info dem Datensatz artikel zuordnen und beispielsweise für jeden Artikel in artikel die Variable erscheinungstag hinzufügen; für alle Spiegel-Artikel erhalten wir dann den Wert "Samstag", für alle Stern-Artikel den Wert "Donnerstag", usw.

Bevor wir nun die Datensätze tatsächlich miteinander verbinden, schauen wir uns noch einen komplizierteren Fall an. Was ist der primary key im Datensatz auflagen:

## # A tibble: 27 x 4
##    medium       jahr quartal auflage
##    <chr>       <int> <chr>     <dbl>
##  1 Der Spiegel  2018 q1       708077
##  2 Der Spiegel  2018 q2       704656
##  3 Der Spiegel  2018 q3       716663
##  4 Der Spiegel  2018 q4       712268
##  5 Der Spiegel  2019 q1       701337
##  6 Der Spiegel  2019 q2       707459
##  7 Der Spiegel  2019 q3       719326
##  8 Der Spiegel  2019 q4       691451
##  9 Der Spiegel  2020 q1       685799
## 10 Stern        2018 q1       539191
## # ... with 17 more rows

Aktuell existiert keine einzelne Identifikationsvariable, mit der wir einen Eintrag in auflagen eindeutig identifzieren können – sowohl medium als auch jahr und quartal enthalten doppelte Werte.23 Der primary key ist in diesem Fall eine Kombination aus den drei Variablen medium, jahr und quartal, da jede der Merkmalskombinationen dieser drei Variablen nur ein einziges Mal auftritt.

In solchen Fällen ist es sinnvoll, über eine laufende Nummer eine Variable zu erstellen, die als primary key fungieren kann. Dies können wir schnell mit den uns bereits bekannten Funktionen umsetzen:

auflagen %>% 
  mutate(id = 1:nrow(auflagen))
## # A tibble: 27 x 5
##    medium       jahr quartal auflage    id
##    <chr>       <int> <chr>     <dbl> <int>
##  1 Der Spiegel  2018 q1       708077     1
##  2 Der Spiegel  2018 q2       704656     2
##  3 Der Spiegel  2018 q3       716663     3
##  4 Der Spiegel  2018 q4       712268     4
##  5 Der Spiegel  2019 q1       701337     5
##  6 Der Spiegel  2019 q2       707459     6
##  7 Der Spiegel  2019 q3       719326     7
##  8 Der Spiegel  2019 q4       691451     8
##  9 Der Spiegel  2020 q1       685799     9
## 10 Stern        2018 q1       539191    10
## # ... with 17 more rows

10.2.2.2 Join-Operationen

Um Datensätze nun miteinander zu verbinden, greifen wir auf _join-Funktionen zurück. Das Tidyverse orientiert sich dabei an Konzepten und Bezeichnungen, die auch in der Datenbanksprache SQL verwendet werden. Dabei werden zum Zusammenfügen vier Arten von Joins unterschieden (im Folgenden gehen wir davon aus, dass die beiden Datensätze, die wir verbinden möchten, x und y heißen):

  • inner_join(): Alle Zeilen, die sowohl in x als auch in y vorkommen, und alle Spalten aus x und y
  • left_join(): Alle Zeilen, die in x vorkommen und alle Spalten aus x und y
  • right_join(): Alle Zeilen, die in y vorkommen und alle Spalten aus x und y
  • full_join(): Alle Zeilen aus x und y und alle Spalten aus x und y

Man kann diese Unterschiede auch durch ein Venn-Diagramm verdeutlichen:

Join-Operationen. Quelle: R for Data Science

Daneben gibt es noch Join-Operationen, die weniger zum Zusammenfügen als zum Filtern von Datensätzen auf Basis von anderen Datensätzen zu gebrauchen sind, da dabei keine Spalten aus einem Datensatz in den anderen kopiert werden:

  • anti_join(): Alle Zeilen in x, die nicht auch in y vorkommen, und alle Spalten aus x (nicht aber aus y)
  • semi_join(): Alle Zeilen in x, die auch in y vorkommen, und alle Spalten aus x (nicht aber aus y)

In der Praxis benötigen wir vor allem left_join() und inner_join(), anti_join() wird uns aber bei der automatisierten Inhaltsanalyse wieder begegnen.

Alle _join()-Funktionen benötigen als Argumente die beiden Datensätze, die zusammengefügt (bzw. gefiltert) werden sollen. Wird kein weiteres Argument angegeben, werden automatisch alle gleichnamigen Variablen in beiden Datensätzen als Schlüsselvariablen verwendet.

artikel %>% 
  left_join(info)
## Joining, by = "medium"
## # A tibble: 4 x 9
##   titel                  autor_innen        medium      ausgabe  jahr seiten  sitz    erscheinungstag erstausgabe
##   <chr>                  <chr>              <chr>         <int> <int> <chr>   <chr>   <chr>           <date>     
## 1 Ein spannender Artikel Max Mustermann     Stern             1  2020 20-22   Hamburg Donnerstag      1948-08-01 
## 2 Noch ein Artikel       Erika Musterfrau   Stern             1  2020 11      Hamburg Donnerstag      1948-08-01 
## 3 Und noch ein Artikel   John Doe; Jane Doe Der Spiegel       1  2020 17-25   Hamburg Samstag         1947-01-04 
## 4 Und noch einer         Mario Rossi        Focus             1  2020 104-106 Berlin  Samstag         1993-01-18

Wir sehen zunächst in der Konsole, dass die Join-Operation auf der Basis der Schlüsselvariable medium erfolgte, da diese als einzige in beiden Datensätzen zu finden ist. Das Ergebnis ist ein Datensatz, der alle Variablen aus beiden Datensätzen enthält und diese auf Basis der Schlüsselvariablen alle Spaltenwerte den richtigen Zeilen zugeordnet hat.

Mit dem Argument by können wir die Schlüsselvariable(n) auch explizit angeben; dies ist vor allem dann praktisch, wenn die Schlüsselvariablen in beiden Datensätzen unterschiedlich heißen. Auch bei gleichnamigen Variablen ist aber dennoch sinnvoll, die Schlüsselvariablen explizit zu nennen, um etwaigen Problemen vorzubeugen:

artikel %>% 
  left_join(info, by = "medium")
## # A tibble: 4 x 9
##   titel                  autor_innen        medium      ausgabe  jahr seiten  sitz    erscheinungstag erstausgabe
##   <chr>                  <chr>              <chr>         <int> <int> <chr>   <chr>   <chr>           <date>     
## 1 Ein spannender Artikel Max Mustermann     Stern             1  2020 20-22   Hamburg Donnerstag      1948-08-01 
## 2 Noch ein Artikel       Erika Musterfrau   Stern             1  2020 11      Hamburg Donnerstag      1948-08-01 
## 3 Und noch ein Artikel   John Doe; Jane Doe Der Spiegel       1  2020 17-25   Hamburg Samstag         1947-01-04 
## 4 Und noch einer         Mario Rossi        Focus             1  2020 104-106 Berlin  Samstag         1993-01-18

Zu beachten ist, dass left_join() auch Ergebnisse liefert, wenn die Schlüsselvariablen in y nicht eindeutig ist bzw. sind. Dies ist der Fall, wenn wir beide Datensätze im obigen Beispiel vertauschen. Zur Erinnerung, medium im artikel-Datensatz enthält Mehrfachwerte:

artikel
## # A tibble: 4 x 6
##   titel                  autor_innen        medium      ausgabe  jahr seiten 
##   <chr>                  <chr>              <chr>         <int> <int> <chr>  
## 1 Ein spannender Artikel Max Mustermann     Stern             1  2020 20-22  
## 2 Noch ein Artikel       Erika Musterfrau   Stern             1  2020 11     
## 3 Und noch ein Artikel   John Doe; Jane Doe Der Spiegel       1  2020 17-25  
## 4 Und noch einer         Mario Rossi        Focus             1  2020 104-106

Welche der beiden "Stern"-Zeilen ordnet left_join() nun info zu?

info %>% 
  left_join(artikel, by = "medium")
## # A tibble: 4 x 9
##   medium      sitz    erscheinungstag erstausgabe titel                  autor_innen        ausgabe  jahr seiten 
##   <chr>       <chr>   <chr>           <date>      <chr>                  <chr>                <int> <int> <chr>  
## 1 Der Spiegel Hamburg Samstag         1947-01-04  Und noch ein Artikel   John Doe; Jane Doe       1  2020 17-25  
## 2 Stern       Hamburg Donnerstag      1948-08-01  Ein spannender Artikel Max Mustermann           1  2020 20-22  
## 3 Stern       Hamburg Donnerstag      1948-08-01  Noch ein Artikel       Erika Musterfrau         1  2020 11     
## 4 Focus       Berlin  Samstag         1993-01-18  Und noch einer         Mario Rossi              1  2020 104-106

Die Antwort: beide Zeilen – left_join() dupliziert also Zeilen in x, wenn es auf Basis der Schlüsselvariablen mehrere Entsprechungen in y gibt. Daher ist es nach einer Join-Operation immer sinnvoll zu prüfen, ob der resultierende Datensatz auch den gewünschten Umfang hat.

10.3 Übungsaufgaben

Erstellen Sie für die folgenden Übungsaufgaben eine eigene Skriptdatei oder eine R-Markdown-Datei und speichern diese als ue10_nachname.R bzw. ue10_nachname.Rmd ab.


Übungsaufgabe 10.1 Daten umstrukturieren:

Laden Sie den Datensatz facebook_europawahl.csv, der schon aus den vorigen Übungen bekannt ist. Wählen Sie zunächst die Variablen id, party, timestamp sowie comments_count, shares_count und reactions_count aus.

Wir möchten diesen Datensatz nun auf eine “Medium-pro-Tag-und-Facebook-Metrik”-Ebene umstrukturieren, sodass für jeden Post (zu erkennen an der Variablen id) drei Zeilen existieren, in der jeweils einmal die Anzahl der Kommentare, die Anzahl der Shares sowie die Anzahl der Reactions steht.

Der resultierende Datensatz sollte fünf Variablen haben: die Identifikationsvariablen id, party, timestamp (aus dem alten Datensatz) und metric (gibt an, um welche der drei Facebook-Metriken es sich handelt) sowie die Wertvariable value (die Anzahl der jeweiligen Facebook-Metrik).


Übungsaufgabe 10.2 Daten zusammenfügen:

Auf Moodle finden Sie den Datensatz facebook_codings.csv. Dieser enthält für die Facebook-Posts aus facebook_europawahl.csv manuelle Codierungen, ob bestimmte Themen in diesen Posts vorkommen oder nicht. Zudem ist die id-Variable der Facebook-Posts angegeben.

Laden Sie auch diesen Datensatz in R und fügen ihn mit facebook_europawahl zusammen, sodass für jeden Post neben den API-Informationen (timestamp, message, Facebook-Metriken etc.) auch die manuellen Codierungen ersichtlich sind.


  1. Da alle Spalten bis auf medium transformiert werden sollen, könnten wir alternativ auch mit -medium angeben, um alle Spalten außer eben dieser zu nutzen.↩︎

  2. Noch schöner wäre es, wenn wir automatisch auch noch das q aus q1, q2 etc. entfernen und das Quartal auch als integer speichern könnten, aber die nötigen Funktionen zum Umgang mit Textvariablen lernen wir erst in zwei Wochen. Möglich ist es aber.↩︎

  3. Im Gegensatz zu pivot_longer entfernt names_prefix bei pivot_wider das Prefix nicht, sondern fügt dieses hinzu.↩︎

  4. Tatsächlich wäre der Unterstrich _ aber auch bereits als Default-Wert von names_sep eingestellt.↩︎

  5. Tatsächlich sind die Werte in auflage aktuell noch eindeutig, aber Wertvariablen sind nicht gut als Schlüsselvariablen geeignet; sammeln wir noch mehr Auflagendaten aus früheren Jahren und von mehr Magazinen, steigt die Wahrscheinlichkeit, dass irgendwann auch mal ein doppelter Wert auftaucht.↩︎