8 Daten laden, modifizieren und speichern

In diesem Kapitel sehen wir uns grundlegende Arbeitsschritte und Funktionen des Datenhandlings: das Einlesen von Daten, einfaches Modifizieren von Datensätzen und das Abspeichern der Ergebnisse. Für all diese Schritte arbeiten wir mit Funktionen aus dem Tidyverse – falls noch nicht geschehen, sollten Sie das Package jetzt also installieren.

install.packages("tidyverse")

Und dann laden wir das Package zu Beginn unseres Auswertungsskripts:

library(tidyverse)

8.1 Daten laden

Wir sprechen von lokalen Daten, wenn wir diese in Form einer Datei auf unserer Festplatte gespeichert haben. Externe Daten liegen beispielsweise auf Webservern oder sind in Packages enthalten. Zunächst laden wir nur lokale Daten.

8.1.1 CSV-Dateien einlesen

Wenn Sie den Schritten in Kapitel 6.2 gefolgt sind, haben Sie ein R-Projektverzeichnis auf des Festplatte. Auf Moodle finden Sie den Datensatz facebook_europawahl.csv, der Informatationen zu Facebook-Posts der deutschen Parteien im Vorfeld der Europawahl 2019 enthält.12 Speichern Sie diesen Datensatz in Ihrem Projektverzeichnis ab – im Beispiel liegt der Datensatz im Unterordner data.

Funktionen zum Einlesen von Daten folgen im Tidyverse dem Namensschema read_, wobei nach dem Unterstrich der Dateityp folgt. Für CSV-Dateien sind zwei Funktionen relevant:

  • read_csv() liest CSV-Dateien, die ein Komma , als Spalten- und einen Punkt . als Dezimaltrennzeichen verwenden
  • read_csv2() liest CSV-Dateien, die ein Semikolon ; als Spalten- und das Komma , als Dezimaltrennzeichen verwenden

Bei beiden Funktionen handelt es sich um spezifische Varianten der Funktion read_delim(), bei der Trennzeichen etc. einzeln definiert werden können. In der Regel sollten aber die beiden oben genannten Funktionen ausreichen. Im Zweifelsfall können CSV-Dateien durch anklicken im Files-Bereich in RStudio geöffnet werden, sodass ersichtlich wird, wie diese aufgebaut sind und welches Trennzeichen verwendet wird.

Alle Funktionen aus der read_-Familie benötigen als erstes (und oft auch einziges) Argument den Dateipfad (relativ zum Arbeitsverzeichnis) als Textobjekt. Da unser Datensatz im Unterordner data liegt, lautet der gesamte Dateipfad also "data/facebook_europawahl.csv". Natürlich sollten wir das Resultat der Funktion einem treffend benannten Objekt zuweisen.13

df_fb_eu <- read_csv("data/facebook_europawahl.csv")
## 
## -- Column specification ----------------------------------------------------------------------------------------------------------------------------------------
## cols(
##   id = col_double(),
##   URL = col_character(),
##   party = col_character(),
##   timestamp = col_datetime(format = ""),
##   type = col_character(),
##   message = col_character(),
##   link = col_character(),
##   comments_count = col_double(),
##   shares_count = col_double(),
##   reactions_count = col_double(),
##   like_count = col_double(),
##   love_count = col_double(),
##   wow_count = col_double(),
##   haha_count = col_double(),
##   sad_count = col_double(),
##   angry_count = col_double()
## )

Die Funktion teilt uns direkt mit, welche Objekttypen für welche Variable verwendet wurden, sodass wir hier auch direkt sehen können, ob z. B. eine numerische Variable als Text eingelesen wurde. Zudem ist der eingelesene Datensatz direkt ein Tibble, wir müssen also nicht mehr durch as_tibble() konvertieren.14

Schauen wir uns unseren gerade geladenen Datensatz einmal an – da unser Datensatz als Tibble vorliegt, erhalten wir die wichtigsten Informationen zu Struktur und einen Einblick in die Daten direkt in Konsole, wenn wir das Datensatz-Objekt aufrufen:

df_fb_eu
## # A tibble: 902 x 16
##       id URL   party timestamp           type  message link  comments_count shares_count reactions_count like_count love_count wow_count haha_count sad_count
##    <dbl> <chr> <chr> <dttm>              <chr> <chr>   <chr>          <dbl>        <dbl>           <dbl>      <dbl>      <dbl>     <dbl>      <dbl>     <dbl>
##  1     1 http~ oedp~ 2019-04-28 09:00:00 video "Guido~ http~              0            4               9          9          0         0          0         0
##  2     2 http~ tier~ 2019-04-28 13:57:00 photo "Aus u~ http~             17          130             395        354         23         3         11         2
##  3     3 http~ B90D~ 2019-04-28 06:00:01 video "Beim ~ http~             70           28             215        174         14         3         16         0
##  4     4 http~ FDP   2019-04-28 11:49:59 photo "Unser~ http~             16            9             262        254          7         0          1         0
##  5     5 http~ tier~ 2019-04-28 08:24:15 link  "Eine ~ http~              6           46             145        129         14         2          0         0
##  6     6 http~ CDU   2019-04-28 09:12:19 video "Freih~ http~            239          136             398        292          8         0         58         2
##  7     7 http~ SPD   2019-04-28 13:06:09 photo "Katar~ http~            180           54             699        576         34         4         79         1
##  8     8 http~ Pira~ 2019-04-28 17:36:30 video "Unser~ http~              0           NA               7          6          0         1          0         0
##  9     9 http~ DieP~ 2019-04-28 07:44:28 link  "Der a~ http~             35           76             612        509         49         0         54         0
## 10    10 http~ CSU   2019-04-28 08:21:00 photo "#Klar~ http~            174           61             458        334          3         5         90         2
## # ... with 892 more rows, and 1 more variable: angry_count <dbl>

Wir haben also einen Datensatz mit 902 Zeilen bzw. Fällen – im diesen Falle also Facebook-Posts – und 16 Spalten bzw. Variablen. Darunter sind:

  • eine numerische id, die URL und ein Zeitstempel (timestamp) des Posts
  • Die Parteiseite party von der der Post abgesetzt wurde
  • Der Typ (type) des Posts (Video, Photo, Link oder Status)
  • Der Text (message) des Posts und ein etwaiger enthaltener link
  • Die Anzahl verschiedener Facebook-Metriken, darunter Kommentare, Shares sowie Reactions gesamt und getrennt in einzelne Typen, allesamt auf _count endend

8.1.2 Andere Dateiformate

Andere Dateiformate funktionieren analog – in der Regel reicht es, die korrekte Funktion zu verwenden und den Dateipfad anzugeben. Allerdings müssen für proprietäre Dateiformate erst die – mit dem Tidyverse bereits installierten – Packages geladen werden:

  • das Paket readxl bietet Funktionen zum Import von Excel-Dateien, z. B. readxl::read_xlsx()
  • das Paket haven deckt den Import von Datensätzen aus anderer Statistiksoftware (SAS, Stata, SPSS) ab, z. B. haven::read_sav() für SPSS-Datensätze

8.2 Daten modifizieren

Zur Datenmodifikation betrachten wir sechs zentrale Funktionen (+ einige zugehörige Hilfsfunktionen bzw. Variationen davon), die das Tidyverse – genauer gesagt das Teilpaket dplyr15 – zur Verfügung stellt:

  • select() zum Auswählen von Variablen (spaltenweise)
  • filter() zum Filtern von Variablen (zeilenweise)
  • arrange() zum Sortieren des Datensatzes
  • mutate() zum Erzeugen neuer Variablen
  • summarize() zum Zusammenfassen von Variablen
  • group_by zum Gruppieren von Variablen

Alle Funktionen haben dabei gemeinsam (und das trifft auf nahezu alle Funktionen des Tidyverse zu), dass das erste Argument der Datensatz selbst (als Tibble) ist und auch das Resultat der Funktion wiederum ein Tibble ist.

Illustration von @allison_horst: https://twitter.com/allison_horst

8.2.1 Variablen spaltenweise auswählen mit select()

Mit select() können wir bestimmte Spalten eines Datensatzes auswählen. Hierzu übergeben wir nach dem Datensatz einfach alle Variablen, die wir benötigen, direkt als Objektnamen – in unserem Beispiel id, URL, party usw. – ohne Anführungszeichen durch Kommas getrennt:16

# Wähle nur die Variablen id, party und timestamp aus
select(df_fb_eu, id, party, timestamp) 
## # A tibble: 902 x 3
##       id party            timestamp          
##    <dbl> <chr>            <dttm>             
##  1     1 oedp.de          2019-04-28 09:00:00
##  2     2 tierschutzpartei 2019-04-28 13:57:00
##  3     3 B90DieGruenen    2019-04-28 06:00:01
##  4     4 FDP              2019-04-28 11:49:59
##  5     5 tierschutzpartei 2019-04-28 08:24:15
##  6     6 CDU              2019-04-28 09:12:19
##  7     7 SPD              2019-04-28 13:06:09
##  8     8 Piratenpartei    2019-04-28 17:36:30
##  9     9 DiePARTEI        2019-04-28 07:44:28
## 10    10 CSU              2019-04-28 08:21:00
## # ... with 892 more rows

Durch ein vorangstelltes - werden Variablen ausgeschlossen:

# Entferne die id und die URL-Variable
select(df_fb_eu, -id, -URL)
## # A tibble: 902 x 14
##    party  timestamp           type  message  link   comments_count shares_count reactions_count like_count love_count wow_count haha_count sad_count angry_count
##    <chr>  <dttm>              <chr> <chr>    <chr>           <dbl>        <dbl>           <dbl>      <dbl>      <dbl>     <dbl>      <dbl>     <dbl>       <dbl>
##  1 oedp.~ 2019-04-28 09:00:00 video "Guido ~ https~              0            4               9          9          0         0          0         0           0
##  2 tiers~ 2019-04-28 13:57:00 photo "Aus un~ https~             17          130             395        354         23         3         11         2           2
##  3 B90Di~ 2019-04-28 06:00:01 video "Beim W~ https~             70           28             215        174         14         3         16         0           8
##  4 FDP    2019-04-28 11:49:59 photo "Unser ~ https~             16            9             262        254          7         0          1         0           0
##  5 tiers~ 2019-04-28 08:24:15 link  "Eine n~ https~              6           46             145        129         14         2          0         0           0
##  6 CDU    2019-04-28 09:12:19 video "Freihe~ https~            239          136             398        292          8         0         58         2          38
##  7 SPD    2019-04-28 13:06:09 photo "Katari~ https~            180           54             699        576         34         4         79         1           5
##  8 Pirat~ 2019-04-28 17:36:30 video "Unser ~ https~              0           NA               7          6          0         1          0         0           0
##  9 DiePA~ 2019-04-28 07:44:28 link  "Der ab~ https~             35           76             612        509         49         0         54         0           0
## 10 CSU    2019-04-28 08:21:00 photo "#Klart~ https~            174           61             458        334          3         5         90         2          24
## # ... with 892 more rows

Durch ein vorangestelltes neuer_objektname = können wir Variablen beim Auswählen auch direkt umbenennen:

# Benenne party und message beim Auswählen um in partei respektive inhalt
select(df_fb_eu, partei = party, inhalt = message)
## # A tibble: 902 x 2
##    partei          inhalt                                                                                                                                       
##    <chr>           <chr>                                                                                                                                        
##  1 oedp.de         "Guido #Klamt aus #Ludwigsburg, Listenplatz 5 auf der Kandidatenliste der #ÖDP zur #Europawahl, stellt sich und seine Ziele in diesem Video ~
##  2 tierschutzpart~ "Aus unserem Europawahlprogramm, Kapitel 7: Gesundheits- und Sozialpolitik:  Bezahlbarer Wohnraum ist wesentliches Element sozialer Politik.~
##  3 B90DieGruenen   "Beim Wahlkampf-Camp in Berlin waren gestern hunderte Freiwillige, die sich im Tür-zu-Tür-Wahlkampf, beim Mobilisieren von Freiwilligen oder~
##  4 FDP             "Unser neuer Bundesvorstand \U0001f389\U0001f38a\U0001f388 #ChancenNutzen \U0001f680#BPT19"                                                  
##  5 tierschutzpart~ "Eine neue Studie der Universität Oxford zeigt, dass eine vegane Ernährung der wahrscheinlich größte Hebel ist, um den eigenen ökologischen ~
##  6 CDU             "Freiheit ist nicht selbstverständlich. #UnserEuropa steht für freiheitliche Werte, Vertrauen und gute Partnerschaften. Mehr dazu in unserem~
##  7 SPD             "Katarina Barley sagt: Eine weitere Koalition mit der #EVP will ich nicht - wir sagen: gut so! Wir wollen ein soziales #Europa. Ein Europa, ~
##  8 Piratenpartei   "Unser Spitzenkandidat Patrick Breyer mit einem Update zum EU19 Workshop in Koblenz:"                                                        
##  9 DiePARTEI       "Der absolut härteste „Martin-Sonneborn-Moment“ kommt für Watson.de nach der Machtübernahme... Smiley!"                                      
## 10 CSU             "#Klartext von Bayerns Ministerpräsident und CSU-Chef Markus Söder in der \"Welt am Sonntag\":  Ohne schwarz zu malen: Die Wirtschaft wird l~
## # ... with 892 more rows

Um Variablen basierend auf Namensbestandteilen auszuwählen, sind einige Hilfsfunktionen - z. B. starts_with(), ends_with() und contains() - verfügbar. Da in unserem Beispiel alle Facebook-Metriken auf _count enden, können wir diese gesammelt mit ends_with("count") auswählen:

# Wähle party und alle Facebook-Metriken aus
select(df_fb_eu, party, ends_with("count"))
## # A tibble: 902 x 10
##    party            comments_count shares_count reactions_count like_count love_count wow_count haha_count sad_count angry_count
##    <chr>                     <dbl>        <dbl>           <dbl>      <dbl>      <dbl>     <dbl>      <dbl>     <dbl>       <dbl>
##  1 oedp.de                       0            4               9          9          0         0          0         0           0
##  2 tierschutzpartei             17          130             395        354         23         3         11         2           2
##  3 B90DieGruenen                70           28             215        174         14         3         16         0           8
##  4 FDP                          16            9             262        254          7         0          1         0           0
##  5 tierschutzpartei              6           46             145        129         14         2          0         0           0
##  6 CDU                         239          136             398        292          8         0         58         2          38
##  7 SPD                         180           54             699        576         34         4         79         1           5
##  8 Piratenpartei                 0           NA               7          6          0         1          0         0           0
##  9 DiePARTEI                    35           76             612        509         49         0         54         0           0
## 10 CSU                         174           61             458        334          3         5         90         2          24
## # ... with 892 more rows

Schließlich kann die Hilfsfunktion everything() (ohne Argumente) genutzt werden, um sämtliche nicht zuvor angegebenen Variablen auswählen – das ist hilfreich, wenn nur bestimmte Variablen z. B. umbenannt oder an den Anfang des Datensatzes gestellt werden sollen, aber man nicht alle anderen Variablen von Hand tippen möchte:

# Stelle party umbenannt in Partei an den Anfang und hänge alle verbleibenden Variablen an
select(df_fb_eu, Partei = party, everything())
## # A tibble: 902 x 16
##    Partei    id URL   timestamp           type  message link  comments_count shares_count reactions_count like_count love_count wow_count haha_count sad_count
##    <chr>  <dbl> <chr> <dttm>              <chr> <chr>   <chr>          <dbl>        <dbl>           <dbl>      <dbl>      <dbl>     <dbl>      <dbl>     <dbl>
##  1 oedp.~     1 http~ 2019-04-28 09:00:00 video "Guido~ http~              0            4               9          9          0         0          0         0
##  2 tiers~     2 http~ 2019-04-28 13:57:00 photo "Aus u~ http~             17          130             395        354         23         3         11         2
##  3 B90Di~     3 http~ 2019-04-28 06:00:01 video "Beim ~ http~             70           28             215        174         14         3         16         0
##  4 FDP        4 http~ 2019-04-28 11:49:59 photo "Unser~ http~             16            9             262        254          7         0          1         0
##  5 tiers~     5 http~ 2019-04-28 08:24:15 link  "Eine ~ http~              6           46             145        129         14         2          0         0
##  6 CDU        6 http~ 2019-04-28 09:12:19 video "Freih~ http~            239          136             398        292          8         0         58         2
##  7 SPD        7 http~ 2019-04-28 13:06:09 photo "Katar~ http~            180           54             699        576         34         4         79         1
##  8 Pirat~     8 http~ 2019-04-28 17:36:30 video "Unser~ http~              0           NA               7          6          0         1          0         0
##  9 DiePA~     9 http~ 2019-04-28 07:44:28 link  "Der a~ http~             35           76             612        509         49         0         54         0
## 10 CSU       10 http~ 2019-04-28 08:21:00 photo "#Klar~ http~            174           61             458        334          3         5         90         2
## # ... with 892 more rows, and 1 more variable: angry_count <dbl>

8.2.2 Variablen zeilenweise filtern mit filter()

Um nur bestimmte Zeilen auswählen, können wir mittels filter() eine oder mehrere Bedingungen übergeben, die analog zu den if-Bedingungen in Kapitel 4.1 angegeben werden. Zuerst wird erneut der Datensatz übergeben:

# Wähle alle Facebookposts mit mindestens einem Kommentar
filter(df_fb_eu, comments_count > 0)
# Achten Sie in der Ausgabe auf die veränderte Zeilenanzahl
## # A tibble: 832 x 16
##       id URL   party timestamp           type  message link  comments_count shares_count reactions_count like_count love_count wow_count haha_count sad_count
##    <dbl> <chr> <chr> <dttm>              <chr> <chr>   <chr>          <dbl>        <dbl>           <dbl>      <dbl>      <dbl>     <dbl>      <dbl>     <dbl>
##  1     2 http~ tier~ 2019-04-28 13:57:00 photo "Aus u~ http~             17          130             395        354         23         3         11         2
##  2     3 http~ B90D~ 2019-04-28 06:00:01 video "Beim ~ http~             70           28             215        174         14         3         16         0
##  3     4 http~ FDP   2019-04-28 11:49:59 photo "Unser~ http~             16            9             262        254          7         0          1         0
##  4     5 http~ tier~ 2019-04-28 08:24:15 link  "Eine ~ http~              6           46             145        129         14         2          0         0
##  5     6 http~ CDU   2019-04-28 09:12:19 video "Freih~ http~            239          136             398        292          8         0         58         2
##  6     7 http~ SPD   2019-04-28 13:06:09 photo "Katar~ http~            180           54             699        576         34         4         79         1
##  7     9 http~ DieP~ 2019-04-28 07:44:28 link  "Der a~ http~             35           76             612        509         49         0         54         0
##  8    10 http~ CSU   2019-04-28 08:21:00 photo "#Klar~ http~            174           61             458        334          3         5         90         2
##  9    11 http~ DieP~ 2019-04-28 08:11:28 video "Anste~ http~             61          312            1601       1344         77         1        179         0
## 10    12 http~ alte~ 2019-04-28 14:55:00 link  "++ Ha~ http~           1163         1499            3944        540          4        56        239        96
## # ... with 822 more rows, and 1 more variable: angry_count <dbl>

Natürlich können auch Boolesche Operatoren (! für NICHT, & für UND, | für ODER) verwendet werden. Mehrere Bedingungen können auch per , getrennt werden (UND-Verknüpfung):

# Wähle nur Video-Posts der großen Koalition, die keine fehlenden Werte bei den Shares haben
filter(df_fb_eu, party %in% c("CDU", "CSU", "SPD"), type == "video", !is.na(shares_count))
## # A tibble: 62 x 16
##       id URL   party timestamp           type  message link  comments_count shares_count reactions_count like_count love_count wow_count haha_count sad_count
##    <dbl> <chr> <chr> <dttm>              <chr> <chr>   <chr>          <dbl>        <dbl>           <dbl>      <dbl>      <dbl>     <dbl>      <dbl>     <dbl>
##  1     6 http~ CDU   2019-04-28 09:12:19 video "Freih~ http~            239          136             398        292          8         0         58         2
##  2    18 http~ CDU   2019-04-29 11:38:01 video "Live:~ http~            140           17             190        131          4         2         27         1
##  3    26 http~ SPD   2019-04-29 09:02:51 video "Press~ http~            174           67             312        239         30         1         23         2
##  4    31 http~ CDU   2019-04-29 09:58:45 video "Da st~ http~            274           76             448        302         14         0        112         1
##  5    93 http~ CDU   2019-05-01 09:00:47 video "Heute~ http~            305          113             513        308         17         3        131         1
##  6   122 http~ CDU   2019-05-02 18:00:01 video "Heute~ http~            122           97             296        243         12         1         34         1
##  7   141 http~ CDU   2019-05-03 14:06:29 video "Anneg~ http~            158           65             247        157          0         1         69         1
##  8   152 http~ SPD   2019-05-03 14:00:10 video "Jetzt~ http~            515          114             747        572         48         2         92         2
##  9   178 http~ CSU   2019-05-05 08:30:00 video "Die E~ http~            153           55             245        124          0         3         80         2
## 10   197 http~ CSU   2019-05-06 11:04:28 video "Press~ http~             98           23             198        136         11         6         27         1
## # ... with 52 more rows, and 1 more variable: angry_count <dbl>

8.2.3 Daten sortieren mit arrange()

Um den Datensatz für die Ansicht umzusortieren, wird die Funktion arrange() genutzt, die aufsteigend nach den angegebenen Variablen sortiert:

# Sortiere aufsteigend nach Datum
arrange(df_fb_eu, timestamp)
## # A tibble: 902 x 16
##       id URL   party timestamp           type  message link  comments_count shares_count reactions_count like_count love_count wow_count haha_count sad_count
##    <dbl> <chr> <chr> <dttm>              <chr> <chr>   <chr>          <dbl>        <dbl>           <dbl>      <dbl>      <dbl>     <dbl>      <dbl>     <dbl>
##  1     3 http~ B90D~ 2019-04-28 06:00:01 video "Beim ~ http~             70           28             215        174         14         3         16         0
##  2    13 http~ FDP   2019-04-28 06:18:18 photo "Wir w~ http~             47          110             622        589         24         0          7         0
##  3     9 http~ DieP~ 2019-04-28 07:44:28 link  "Der a~ http~             35           76             612        509         49         0         54         0
##  4    11 http~ DieP~ 2019-04-28 08:11:28 video "Anste~ http~             61          312            1601       1344         77         1        179         0
##  5    10 http~ CSU   2019-04-28 08:21:00 photo "#Klar~ http~            174           61             458        334          3         5         90         2
##  6     5 http~ tier~ 2019-04-28 08:24:15 link  "Eine ~ http~              6           46             145        129         14         2          0         0
##  7     1 http~ oedp~ 2019-04-28 09:00:00 video "Guido~ http~              0            4               9          9          0         0          0         0
##  8     6 http~ CDU   2019-04-28 09:12:19 video "Freih~ http~            239          136             398        292          8         0         58         2
##  9    15 http~ FDP   2019-04-28 10:40:57 photo "Weil ~ http~             14           19             226        210         12         0          4         0
## 10     4 http~ FDP   2019-04-28 11:49:59 photo "Unser~ http~             16            9             262        254          7         0          1         0
## # ... with 892 more rows, and 1 more variable: angry_count <dbl>

Werden mehrere Variablen angegeben, wird zunächst nach der ersten Variablen, dann innerhalb der ersten Variablen nach der zweiten Variablen usw. sortiert. Soll eine Variable stattdessen absteigend sortiert werden, wird der Variablenname in die Hilfsfunktion desc() gepackt:

# Sortiere alphabetisch aufsteigend nach Partei
# und innerhalb von Parteien absteigend nach Kommentaranzahl
arrange(df_fb_eu, party, desc(comments_count))
## # A tibble: 902 x 16
##       id URL   party timestamp           type  message link  comments_count shares_count reactions_count like_count love_count wow_count haha_count sad_count
##    <dbl> <chr> <chr> <dttm>              <chr> <chr>   <chr>          <dbl>        <dbl>           <dbl>      <dbl>      <dbl>     <dbl>      <dbl>     <dbl>
##  1   870 http~ alte~ 2019-05-26 15:50:19 video "+++ H~ http~           4829          804            6472       5036       1319        13         45        19
##  2   752 http~ alte~ 2019-05-23 17:24:43 video "+++ E~ http~           3294          992            4469       3730        688        10         16         6
##  3   616 http~ alte~ 2019-05-20 09:49:00 photo "++ Me~ http~           3100        11719           10163       3857         19       250        146       123
##  4   802 http~ alte~ 2019-05-24 17:01:12 video "+++ S~ http~           2874          589            3164       2566        547         8         25         6
##  5    44 http~ alte~ 2019-04-30 19:02:07 video "2. Te~ http~           2862          846            2781       2279        450         8         28         2
##  6   335 http~ alte~ 2019-05-10 17:27:58 video "+++ H~ http~           2425          717            2695       2195        456         6         11         7
##  7   343 http~ alte~ 2019-05-11 13:12:00 photo "++ Di~ http~           2363         5842            7225       1742         14        87         64       205
##  8    50 http~ alte~ 2019-04-30 17:18:13 video "+++ H~ http~           2246          635            2692       2218        433         7         16         3
##  9   198 http~ alte~ 2019-05-06 09:15:00 photo "++ Tä~ http~           1947         4037            8411       1319         11        76         33      1024
## 10   868 http~ alte~ 2019-05-26 11:19:19 photo "++ Wi~ http~           1744         1281           11277      10662        498        14         73         8
## # ... with 892 more rows, and 1 more variable: angry_count <dbl>

8.2.4 Neue Variablen hinzufügen mit mutate()

Mit mutate(), dem vielleicht einzigen nicht selbsterklärenden Funktionsnamen der sechs diskutierten Funktionen, können wir Datensätzen neue Variablen hinzufügen (oder alte überschreiben). Hierzu geben wir den neuen Variablennamen an, gefolgt von einem = und der Berechnung bzw. Konstruktion der neuen Variablen. Wird als Variablenname ein schon im Datensatz bestehender Variablenname verwendet, so wird diese Variable überschrieben. Mit Kommas getrennt können auch mehrere neue Variablen erstellt werden.

# Wir erstellen eine neue Variable comments_centered,
# die die Kommentarzahl am allgemeinen Mittelwert zentriert
# indem wir von jedem Wert den Mittelwert der Kommentarzahl abziehen
# und wandeln die bestehende Variable message in Kleinschreibung
# mittels der Funktion tolower() um.
#
# Zur Darstellung werden die beiden 'mutierten' Variablen 
# anschließend mit select() ausgewählt

df_mutated <- mutate(df_fb_eu,
       comments_centered = comments_count - mean(comments_count, na.rm = TRUE),
       message = tolower(message))

select(df_mutated, comments_centered, message)
## # A tibble: 902 x 2
##    comments_centered message                                                                                                                                    
##                <dbl> <chr>                                                                                                                                      
##  1            -159.  "guido #klamt aus #ludwigsburg, listenplatz 5 auf der kandidatenliste der #ödp zur #europawahl, stellt sich und seine ziele in diesem vide~
##  2            -142.  "aus unserem europawahlprogramm, kapitel 7: gesundheits- und sozialpolitik:  bezahlbarer wohnraum ist wesentliches element sozialer politi~
##  3             -89.2 "beim wahlkampf-camp in berlin waren gestern hunderte freiwillige, die sich im tür-zu-tür-wahlkampf, beim mobilisieren von freiwilligen od~
##  4            -143.  "unser neuer bundesvorstand \U0001f389\U0001f38a\U0001f388 #chancennutzen \U0001f680#bpt19"                                                
##  5            -153.  "eine neue studie der universität oxford zeigt, dass eine vegane ernährung der wahrscheinlich größte hebel ist, um den eigenen ökologische~
##  6              79.8 "freiheit ist nicht selbstverständlich. #unsereuropa steht für freiheitliche werte, vertrauen und gute partnerschaften. mehr dazu in unser~
##  7              20.8 "katarina barley sagt: eine weitere koalition mit der #evp will ich nicht - wir sagen: gut so! wir wollen ein soziales #europa. ein europa~
##  8            -159.  "unser spitzenkandidat patrick breyer mit einem update zum eu19 workshop in koblenz:"                                                      
##  9            -124.  "der absolut härteste „martin-sonneborn-moment“ kommt für watson.de nach der machtübernahme... smiley!"                                    
## 10              14.8 "#klartext von bayerns ministerpräsident und csu-chef markus söder in der \"welt am sonntag\":  ohne schwarz zu malen: die wirtschaft wird~
## # ... with 892 more rows

8.2.5 Variablen zusammenfassen mit summarize()

Mit summarize()17 fassen wir Variablen zusammen, indem wir Funktionen auf eine Variable anwenden. Das Resultat ist ein neues Tibble, das die zusammengefassten Variablen als Spalten enthält. Die Funktionsweise ist ähnlich wie bei mutate():

# Mittelwert der drei zentralen Facebook-Metriken berechen
summarize(df_fb_eu, 
          mean_comments = mean(comments_count, na.rm = TRUE),
          mean_shares = mean(shares_count, na.rm = TRUE),
          mean_reactions = mean(reactions_count, na.rm = TRUE))
## # A tibble: 1 x 3
##   mean_comments mean_shares mean_reactions
##           <dbl>       <dbl>          <dbl>
## 1          159.        249.           846.

8.2.6 Variablen gruppieren mit group_by()

Mittels group_by() können wir unseren Datensatz nach einer oder mehrerer Variablen gruppieren. Das Resultat ist erstmal ein Tibble, das nicht weiter von unserem Ausgangs-Tibble unterscheidet. Die Gruppierung wird dann jedoch bei folgenden Funktionen wie mutate() oder summarize() berücksichtig.

# Wir berechnen erneut die zentralen Facebook-Metriken
# mit summarize(), gruppieren aber zuvor nach Partei

grouped_df <- group_by(df_fb_eu, party)

summarize(grouped_df, mean_comments = mean(comments_count, na.rm = TRUE),
          mean_shares = mean(shares_count, na.rm = TRUE),
          mean_reactions = mean(reactions_count, na.rm = TRUE))
## # A tibble: 14 x 4
##    party                           mean_comments mean_shares mean_reactions
##    <chr>                                   <dbl>       <dbl>          <dbl>
##  1 alternativefuerde                     863.         1796.          4032. 
##  2 B90DieGruenen                         106.          184.           660. 
##  3 CDU                                   349.           87.1          628. 
##  4 CSU                                   136.           57.1          499. 
##  5 DiePARTEI                              60.4         160.          1343. 
##  6 FamilienParteiDeutschlands              0.633        16.0           10.5
##  7 FDP                                    70            67.3          447. 
##  8 freie.waehler.bundesvereinigung        25.3          43.1          141. 
##  9 linkspartei                           116.          218.           935. 
## 10 oedp.de                                 6.58         29.5          110. 
## 11 Piratenpartei                          11.4          42.5          124. 
## 12 SPD                                   220.          149.           719. 
## 13 tierschutzpartei                       67.4         391.           979. 
## 14 VoltDeutschland                        15.6          39.8          230.

Analog wird auch bei mutate() die Gruppierung in den Berechnungen berücksichtigt. Wenden wir die oben durchgeführte Mittelwert-Zentrierung der Kommentaranzahl auf unseren gruppierten Datensatz an, wird durch die mean()-Funktion der Mittelwert innerhalb der Gruppen (hier also der Parteien) berechnet. Im Ergebnis bekommen wir also für jeden Facebook-Post einen Wert, wie dieser von der durchschnittlichen Kommentaranzahl auf dieser Parteienseite abweicht:

mutated_df <- mutate(grouped_df, 
                     comments_group_centered = comments_count - mean(comments_count, na.rm = TRUE))

select(mutated_df, party, comments_group_centered, comments_count)
## # A tibble: 902 x 3
## # Groups:   party [14]
##    party            comments_group_centered comments_count
##    <chr>                              <dbl>          <dbl>
##  1 oedp.de                            -6.58              0
##  2 tierschutzpartei                  -50.4              17
##  3 B90DieGruenen                     -35.5              70
##  4 FDP                               -54                16
##  5 tierschutzpartei                  -61.4               6
##  6 CDU                              -110.              239
##  7 SPD                               -40.4             180
##  8 Piratenpartei                     -11.4               0
##  9 DiePARTEI                         -25.4              35
## 10 CSU                                37.7             174
## # ... with 892 more rows

Wir können auch nach mehreren Variablen gruppieren:

# Wir berechnen erneut die zentralen Facebook-Metriken
# mit summarize(), gruppieren aber zuvor nach Partei UND Post-Typ

grouped_df <- group_by(df_fb_eu, party, type)

summarize(grouped_df, mean_comments = mean(comments_count, na.rm = TRUE),
          mean_shares = mean(shares_count, na.rm = TRUE),
          mean_reactions = mean(reactions_count, na.rm = TRUE))
## # A tibble: 46 x 5
## # Groups:   party [14]
##    party             type   mean_comments mean_shares mean_reactions
##    <chr>             <chr>          <dbl>       <dbl>          <dbl>
##  1 alternativefuerde link           826.       1341.           2953 
##  2 alternativefuerde photo          860.       2466.           5269.
##  3 alternativefuerde video          875.        847.           2340.
##  4 B90DieGruenen     photo          134.        183.            902.
##  5 B90DieGruenen     video           77.9       184.            425.
##  6 CDU               photo          394.        114.            810.
##  7 CDU               video          293.         53.0           400.
##  8 CSU               link            25          11             132.
##  9 CSU               photo          143.         63.8           566.
## 10 CSU               status         416.        112.           1106 
## # ... with 36 more rows

Wir sehen hier also, dass die AfD im Mittel 826.14 Kommentare auf Links bekommt, 860.49 auf Photos usw.

Gruppierungen können (und sollten) im Anschluss mittels ungroup() wieder entfernt werden (auch hier wird der Datensatz als Argument übergeben), um Probleme bei der weiteren Datentransformation zu vermeiden.

Eine besondere Variante von group_by() ist rowwise(), die den Datensatz zeilenweise gruppiert; dies ermöglicht zeilenweise Berechnungen mit Funktionen über mehrere Variablen hinweg, z. B. die Erstellung von Mittelwerts-Indizes:

# Gruppiere den Datensatz zeilenweise, um für jeden Post
# den Mittelwert der einzelnen Reactions (Like, Love etc.)
# zu berechnen

rowwise_df <- rowwise(df_fb_eu)

mutated_df <- mutate(rowwise_df, 
                     mean_reactions = mean(c(like_count, love_count, wow_count, haha_count, sad_count, angry_count),
                                           na.rm = TRUE))

select(mutated_df, mean_reactions)
## # A tibble: 902 x 1
## # Rowwise: 
##    mean_reactions
##             <dbl>
##  1           1.5 
##  2          65.8 
##  3          35.8 
##  4          43.7 
##  5          24.2 
##  6          66.3 
##  7         116.  
##  8           1.17
##  9         102   
## 10          76.3 
## # ... with 892 more rows

Eine Funktion, die einen häufigen Anwendungsfall von group_by(), summarize() und ungroup() kombiniert, ist count(), die die Fallzahl einer oder mehrerer Gruppierungsvariablen ausgibt. Mit dem Argument sort = TRUE kann die Ausgabe zudem direkt absteigend nach Anzahl sortiert werden:

# Zähle Posts pro Partei
count(df_fb_eu, party)
## # A tibble: 14 x 2
##    party                               n
##    <chr>                           <int>
##  1 alternativefuerde                  79
##  2 B90DieGruenen                      67
##  3 CDU                                64
##  4 CSU                               103
##  5 DiePARTEI                          96
##  6 FamilienParteiDeutschlands         30
##  7 FDP                                94
##  8 freie.waehler.bundesvereinigung    30
##  9 linkspartei                        38
## 10 oedp.de                            71
## 11 Piratenpartei                      73
## 12 SPD                                49
## 13 tierschutzpartei                   33
## 14 VoltDeutschland                    75
# Zähle Posts pro Partei und Post-Typ und sortiere absteigend nach Anzahl
count(df_fb_eu, party, type, sort = TRUE)
## # A tibble: 46 x 3
##    party             type      n
##    <chr>             <chr> <int>
##  1 CSU               photo    76
##  2 FDP               photo    67
##  3 DiePARTEI         photo    53
##  4 alternativefuerde photo    45
##  5 oedp.de           photo    40
##  6 Piratenpartei     photo    39
##  7 B90DieGruenen     video    35
##  8 CDU               photo    35
##  9 VoltDeutschland   photo    35
## 10 SPD               photo    33
## # ... with 36 more rows

8.3 Daten speichern

Wenn wir unsere Dateien modifiziert haben, möchten wir diese wohl auch speichern bzw. exportieren.

8.3.1 Tabellarische Daten exportieren

Analog zu den read_-Funktionen stehen daher Exportfunktionen nach dem Schema write_ zur Verfügung. Als Argumente werden dabei der Datensatz, der gespeichert werden soll, sowie der Dateipfad der zu speichernden Datei übergeben. Haben wir durch Modifikation beispielsweise das Tibble df_modified erstellt und möchten es in der Datei datensatz_modifiziert.csv im Unterordner data abspeichern, führen wir die write_csv()-Funktion aus:

write_csv(df_modified, "data/datensatz_modifiziert.csv")

write_csv() nutzt dabei das Komma , als Spalten- und einen Punkt . als Dezimaltrennzeichen. Möchten wir stattdessen das in Deutschland gebräuchliche Format mit Semikolon ; als Spalten- und Komma , als Dezimaltrennzeichen haben, verwenden wir analog zu read_csv2() write_csv2().

Da Excel öfters Probleme mit dem Einlesen von CSV-Dateien hat, können, soll die Datei danach in Excel betrachtet werden, auch die Funktionen write_excel_csv() bzw. write_excel_csv2() verwendet werden. Dies fügt ein spezielles Zeichen hinzu, das Excel den Datenimport erleichert.

8.3.2 R-Objekte exportieren

Beim Export als CSV gehen unweigerlich auch Informationen verloren – bei unserem Datensatz beispielsweise die Objekttypen, die R den jeweiligen Variablen zugeordnet hat. Wollen wir R-Objekte daher für die zukünftige Verwendung in R abspeichern, lohnt es sich, direkt das jeweilige R-Objekt zu exportieren. Hierfür steht das Dateiformat .rds zur Verfügung, mit dem beliebige R-Objekte – neben Datensätzen also auch Vektoren, Listen, statistische Modelle etc. - gespeichert werden können.

Die zugehörige Funktion lautet saveRDS() und wird analog zu den write_-Funktionen verwendet:

saveRDS(df_mutated, "data/datensatz_modifiziert.rds")

RDS-Dateien können dann jederzeit mit der Funktion readRDS() wieder geladen werden.

Sollen mehrere R-Objekte exportiert werden – also beispielsweise ein Ausgangsdatensatz, ein modifizierter Arbeitsdatensatz und zugehörige statistische Modelle – kann das Dateiformat .RData und die Funktion save() verwendet werden. Dabei werden alle zu speichernden Objekte gefolgt von dem benannten Argument file =, das den Dateipfad angibt, in dem Funktionsaufruf genannt:

save(df_fb_eu, df_mutated, "data/eu_file.RData")

So exportiere Objekte können dann gesammelt über die load()-Funktion, die den Dateipfad als Argument benötigt, wieder geladen werden, was sehr praktisch ist, um direkt den gesamten Arbeitsstand wiederherzustellen.

8.4 Übungsaufgaben

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


Übungsaufgabe 8.1 Daten laden:

Laden Sie die Datei facebook_europawahl.csv aus Moodle in Ihr Projektverzeichnis herunter und laden Sie den Datensatz in R.


Übungsaufgabe 8.2 Daten modifizieren und speichern:

Erstellen Sie einen Teildatensatz, der:

  • nur Posts der aktuell im Bundestag vertretenen Parteien enthält (CDU, CSU, SPD, FDP, Linke, Grüne, AfD); Tipp: Betrachten Sie vorab die Schreibweise der Parteien (bzw. deren Facebook-Accounts)
  • nur die Variablen party, timestamp, type sowie alle Facebook-Metriken enthält
  • eine neue Variable total_count enthält, in der für jeden Post die Gesamtzahl der Kommentare, Shares und Reactions angegeben ist

Speichern Sie diesen Teildatensatz sowohl als CSV- als auch als RDS-Datei.


Übungsaufgabe 8.3 Daten modifizieren und zusammenfassen:

Nutzen Sie die oben vorgestellten Funktionen, um pro Partei Mittelwert und Standardabweichung der drei Facebook-Metriken (Kommentare, Shares, Reactions) aller Posts zu berechnen, die in der Woche vor der Wahl (also nach dem 19.05.2019) erschienen sind.

Tipp: Logische Operatoren funktionieren auch mit Datums- und Zeitvariablen; Text, der wie ein Datum aussieht, wird dabei automatisch in ein Datum bzw. eine Zeitangabe konvertiert.


  1. Vielen Dank an den Kollegen Jörg Haßler!↩︎

  2. Häufig verwendete Objektnamen für Datensätze sind df und data, aber es schadet auch nicht, etwas spezifischere Namen zu vergeben, besonders wenn mit mehreren Datensätzen gearbeitet wird.↩︎

  3. Auch die Basisversion von R bietet Funktionen zum Einlesen von CSV-Dateien, die read.csv(), read.csv2() etc. heißen. Diese erzeugen einen Dataframe und sind weniger gut für große Dateien optimiert, sodass ich empfehle, immer direkt die Tidyverse-Funktionen zu nutzen. Generell erkennen Sie Tidyverse-Varianten von Funktionen der R-Basisversion daran, dass diese einen Unterstrich anstatt eines Punkts zur Worttrennung nutzen.↩︎

  4. Wobei umstritten ist, ob man das Paket dee_plier oder deeply_ar ausspricht.↩︎

  5. Und natürlich müssen wir das Resultat der Funktionen immer einem Objekt zuweisen, wenn wir damit weiterarbeiten wollen – zu Demonstrationszwecken reicht aber der reine Aufruf der Funktion.↩︎

  6. Wer the King’s English bevorzugt: summarise() funktioniert auch.↩︎