3 Transformation und Analyse mit dplyr
3.2 Datentransformation mit dplyr
Das dplyr package enthält eine Reihe von Funktionen zur Bereinigung, Transformation und deskriptiven Analyse von Daten. Wir beginnen damit, Zeilen und Spalten, die wir nicht benötigen, aus der Tibble zu entfernen, bevor wir die Daten in einigen bestehenden Spalten transformieren und neue Spalten aus den vorhandenen Daten erzeugen.
Empfohlene weiterführende Resourcen zu dplyr sind das entsprechende RStudio Cheatsheet: https://raw.githubusercontent.com/rstudio/cheatsheets/master/data-transformation.pdf sowie das Kapitel “Data transformation” in “R for Data Science”: https://r4ds.had.co.nz/transform.html
3.2.1 filter()
filter()
dient dazu, Beobachtungen – also Zeilen – aus einer Tibble zu
filtern. Die Funktion nimmt dabei die Daten, auf die sie angewandt werden soll,
als erstes und eine oder mehrere expressions als zweites Argument. Diese
Ausdrücke sind letztlich die Regeln, nach denen die Funktion filtern soll.
Zum Schreiben der Ausdrücke benötigen wir die in Kapitel 1.2.4
bereits eingeführten Vergleichsoperatoren. Diese vergleichen den Wert auf der
linken Seite des Operators mit dem auf der rechten und geben als Ergebnis ein
TRUE
zurück, wenn der Test bestanden wurde, beziehungsweise FALSE
, wenn er
nicht bestanden wurde.
Wir könnten nun beispielsweise daran interessiert sein, nur die Daten bestimmter
Fraktionen zu analysieren. Filtern wir zunächst die Stammdaten der
Unions-Abgeordneten heraus. Wir können dazu in filter()
testen, ob der Wert
der Spalte “fraktion” gleich dem Wert “Union” ist. Bitte denken Sie daran, dass
wir zum Vergleich zweier Werte immer ==
schreiben müssen, nicht =
.
%>%
stammdaten_17_19 filter(fraktion == "Union")
## # A tibble: 830 × 9
## wp nachname vorname praefix geschlecht alter geb Titel fraktion
## <int> <chr> <chr> <chr> <chr> <int> <chr> <chr> <chr>
## 1 19 Abercron Michael von männlich 64 17.11… Dr. Union
## 2 17 Aigner Ilse <NA> weiblich 44 07.12… <NA> Union
## 3 18 Albani Stephan <NA> männlich 45 03.06… <NA> Union
## 4 19 Albani Stephan <NA> männlich 49 03.06… <NA> Union
## 5 18 Albsteiger Katrin <NA> weiblich 29 20.11… <NA> Union
## 6 19 Altenkamp Norbert Maria <NA> männlich 45 27.07… <NA> Union
## 7 17 Altmaier Peter <NA> männlich 51 18.06… <NA> Union
## 8 18 Altmaier Peter <NA> männlich 55 18.06… <NA> Union
## 9 19 Altmaier Peter <NA> männlich 59 18.06… <NA> Union
## 10 19 Amthor Philipp <NA> männlich 24 10.11… <NA> Union
## # … with 820 more rows
Möchten wir mehrere Ausdrücke kombinieren, benötigen wir dazu
logische Operatoren. &
– UND – testet, ob beide verbundenen Ausdrücke
gleichzeitig TRUE
sind und gibt nur dann TRUE
zurück. |
– ODER –
testet, ob wenigstens einer der verbundenen Ausdrücke TRUE
ist und gibt in
diesem Fall TRUE
zurück. Dies sind alle möglichen Kombinationen für 2
Ausdrücke:
TRUE & TRUE
gibtTRUE
zurückTRUE & FALSE
,FALSE & TRUE
undFALSE & FALSE
geben alleFALSE
zurückTRUE | TRUE
,TRUE | FALSE
undFALSE | TRUE
geben alleTRUE
zurückFALSE | FALSE
gibtFALSE
zurück
Möchten wir nun beispielsweise alle Unions-Abgeordneten des 19. Bundestags
filtern, können wir dazu den &
Operator einsetzen:
%>%
stammdaten_17_19 filter(fraktion == "Union" & wp == 19)
## # A tibble: 261 × 9
## wp nachname vorname praefix geschlecht alter geb Titel fraktion
## <int> <chr> <chr> <chr> <chr> <int> <chr> <chr> <chr>
## 1 19 Abercron Michael von männlich 64 17.1… Dr. Union
## 2 19 Albani Stephan <NA> männlich 49 03.0… <NA> Union
## 3 19 Altenkamp Norbert Maria <NA> männlich 45 27.0… <NA> Union
## 4 19 Altmaier Peter <NA> männlich 59 18.0… <NA> Union
## 5 19 Amthor Philipp <NA> männlich 24 10.1… <NA> Union
## 6 19 Auernhammer Artur <NA> männlich 54 09.0… <NA> Union
## 7 19 Aumer Peter <NA> männlich 41 17.0… <NA> Union
## 8 19 Bär Dorothee <NA> weiblich 39 19.0… <NA> Union
## 9 19 Bareiß Thomas <NA> männlich 42 15.0… <NA> Union
## 10 19 Barthle Norbert <NA> männlich 65 01.0… <NA> Union
## # … with 251 more rows
Möchten wir die MdBs der großen Koalition aus dem Datensatz filtern, also
Abgeordnete der Union oder der SPD, können wir dies mit |
realisieren:
%>%
stammdaten_17_19 filter(fraktion == "Union" | fraktion == "SPD")
## # A tibble: 1,353 × 9
## wp nachname vorname praefix geschlecht alter geb Titel fraktion
## <int> <chr> <chr> <chr> <chr> <int> <chr> <chr> <chr>
## 1 19 Abercron Michael von männlich 64 17.11… Dr. Union
## 2 17 Aigner Ilse <NA> weiblich 44 07.12… <NA> Union
## 3 18 Albani Stephan <NA> männlich 45 03.06… <NA> Union
## 4 19 Albani Stephan <NA> männlich 49 03.06… <NA> Union
## 5 18 Albsteiger Katrin <NA> weiblich 29 20.11… <NA> Union
## 6 19 Altenkamp Norbert Maria <NA> männlich 45 27.07… <NA> Union
## 7 17 Altmaier Peter <NA> männlich 51 18.06… <NA> Union
## 8 18 Altmaier Peter <NA> männlich 55 18.06… <NA> Union
## 9 19 Altmaier Peter <NA> männlich 59 18.06… <NA> Union
## 10 19 Amthor Philipp <NA> männlich 24 10.11… <NA> Union
## # … with 1,343 more rows
Es lassen sich auch mehr als zwei Ausdrücke mit &
bzw. |
kombinieren.
Beispielsweise um die die Abgeordneten der Oppositionsfraktionen zu filtern:
%>%
stammdaten_17_19 filter(fraktion == "AfD" | fraktion == "FDP" | fraktion == "Die Grünen" | fraktion == "DIE LINKE.")
## # A tibble: 705 × 9
## wp nachname vorname praefix geschlecht alter geb Titel fraktion
## <int> <chr> <chr> <chr> <chr> <int> <chr> <chr> <chr>
## 1 19 Achelwilm Doris <NA> weiblich 40 30.11.1976 <NA> DIE LINK…
## 2 17 Ackermann Jens <NA> männlich 34 02.07.1975 <NA> FDP
## 3 19 Aggelidis Grigorios <NA> männlich 52 19.08.1965 <NA> FDP
## 4 17 Ahrendt Christian <NA> männlich 46 07.05.1963 <NA> FDP
## 5 19 Akbulut Gökay <NA> weiblich 34 16.11.1982 <NA> DIE LINK…
## 6 17 Aken Jan van männlich 48 01.05.1961 <NA> DIE LINK…
## 7 18 Aken Jan van männlich 52 01.05.1961 <NA> DIE LINK…
## 8 17 Alpers Agnes <NA> weiblich 48 29.06.1961 <NA> DIE LINK…
## 9 18 Alpers Agnes <NA> weiblich 52 29.06.1961 <NA> DIE LINK…
## 10 19 Alt Renata <NA> weiblich 52 27.08.1965 <NA> FDP
## # … with 695 more rows
Die Daten enthalten aktuell noch alle Abgeordneten der 17. - 19.
Legislaturperioden. Im rahmen dieser Einführung, arbeiten wir nur mit den MdBs
des 19. Bundestags. Mit filter()
können wir den Datensatz auf diese reduzieren.
<- stammdaten_17_19 %>%
stammdaten_19 filter(wp == 19)
Ab hier arbeiten wir mit dem Objekt stammdaten_19
weiter.
3.2.2 select()
Möchten wir ein Subset von Variablen bilden, könne wir dazu select()
nutzen.
Die Funktion nimmt die Daten, auf die sie angewendet werden soll, als erstes
Argument – hier weitergegeben durch die Pipe – und dann einen oder mehrere
Spaltennamen, die in der Tibble verbleiben sollen. Folgen die zu behaltenden
Spalten aufeinander, können wir auch eine “von:bis” Notation nutzen. Nehmen wir
an, wir möchten die Spalten “nachname”, “vorname”, “praefix” und “fraktion”
extrahieren:
%>%
stammdaten_19 select(nachname, vorname, praefix, fraktion)
## # A tibble: 750 × 4
## nachname vorname praefix fraktion
## <chr> <chr> <chr> <chr>
## 1 Abercron Michael von Union
## 2 Achelwilm Doris <NA> DIE LINKE.
## 3 Aggelidis Grigorios <NA> FDP
## 4 Akbulut Gökay <NA> DIE LINKE.
## 5 Albani Stephan <NA> Union
## 6 Alt Renata <NA> FDP
## 7 Altenkamp Norbert Maria <NA> Union
## 8 Altmaier Peter <NA> Union
## 9 Amthor Philipp <NA> Union
## 10 Amtsberg Luise <NA> Die Grünen
## # … with 740 more rows
%>%
stammdaten_19 select(nachname:praefix, fraktion)
## # A tibble: 750 × 4
## nachname vorname praefix fraktion
## <chr> <chr> <chr> <chr>
## 1 Abercron Michael von Union
## 2 Achelwilm Doris <NA> DIE LINKE.
## 3 Aggelidis Grigorios <NA> FDP
## 4 Akbulut Gökay <NA> DIE LINKE.
## 5 Albani Stephan <NA> Union
## 6 Alt Renata <NA> FDP
## 7 Altenkamp Norbert Maria <NA> Union
## 8 Altmaier Peter <NA> Union
## 9 Amthor Philipp <NA> Union
## 10 Amtsberg Luise <NA> Die Grünen
## # … with 740 more rows
Wir können select()
auch nutzen, um Variablen zu entfernen. Dazu schreiben wir
-
vor den zu entfernenden Spaltennamen. Da das genaue Geburtsdatum der
Abgeordneten im Folgenden nicht mehr benötigt wird, können wir die Spalte “geb”
entfernen.
<- stammdaten_19 %>%
stammdaten_19 select(-geb)
stammdaten_19## # A tibble: 750 × 8
## wp nachname vorname praefix geschlecht alter Titel fraktion
## <int> <chr> <chr> <chr> <chr> <int> <chr> <chr>
## 1 19 Abercron Michael von männlich 64 Dr. Union
## 2 19 Achelwilm Doris <NA> weiblich 40 <NA> DIE LINKE.
## 3 19 Aggelidis Grigorios <NA> männlich 52 <NA> FDP
## 4 19 Akbulut Gökay <NA> weiblich 34 <NA> DIE LINKE.
## 5 19 Albani Stephan <NA> männlich 49 <NA> Union
## 6 19 Alt Renata <NA> weiblich 52 <NA> FDP
## 7 19 Altenkamp Norbert Maria <NA> männlich 45 <NA> Union
## 8 19 Altmaier Peter <NA> männlich 59 <NA> Union
## 9 19 Amthor Philipp <NA> männlich 24 <NA> Union
## 10 19 Amtsberg Luise <NA> weiblich 33 <NA> Die Grünen
## # … with 740 more rows
3.2.3 rename()
Ihnen wird eventuell bereits aufgefallen sein, dass alle Spaltennamen außer
“Titel” mit einem kleingeschriebenen Buchstaben beginnen. Da habe ich
anscheinend beim Erstellen des Datenobjekts meine eigenen Namenskonventionen
– alles beginnt mit kleinen Buchstaben – außer Acht gelassen. Um diese nun
umzubenennen, können wir rename()
nutzen. Erneut werden als erstes Argument
die Daten durch die Pipe weitergegeben, gefolgt von einem oder mehreren
Argumenten in der Form neuer_name = alter_name
.
<- stammdaten_19 %>%
stammdaten_19 rename(titel = Titel)
stammdaten_19## # A tibble: 750 × 8
## wp nachname vorname praefix geschlecht alter titel fraktion
## <int> <chr> <chr> <chr> <chr> <int> <chr> <chr>
## 1 19 Abercron Michael von männlich 64 Dr. Union
## 2 19 Achelwilm Doris <NA> weiblich 40 <NA> DIE LINKE.
## 3 19 Aggelidis Grigorios <NA> männlich 52 <NA> FDP
## 4 19 Akbulut Gökay <NA> weiblich 34 <NA> DIE LINKE.
## 5 19 Albani Stephan <NA> männlich 49 <NA> Union
## 6 19 Alt Renata <NA> weiblich 52 <NA> FDP
## 7 19 Altenkamp Norbert Maria <NA> männlich 45 <NA> Union
## 8 19 Altmaier Peter <NA> männlich 59 <NA> Union
## 9 19 Amthor Philipp <NA> männlich 24 <NA> Union
## 10 19 Amtsberg Luise <NA> weiblich 33 <NA> Die Grünen
## # … with 740 more rows
3.2.4 mutate()
mutate()
dient dazu, Daten in bestehenden Spalten zu transformieren und neue
Spalten aus den Daten bestehender Spalten zu generieren. Dabei schreiben wir
innerhalb der Klammern den Namen der zu transformierenden oder neu zu
erstellenden Spalte =
einem Ausdruck der beschreibt, wir die Daten
transformiert oder generiert werden sollen. Dies kann eine einfache Berechnung
wie mutate(neue_spalte = spalte_a + spalte b)
sein, aber auch weitere
Funktionen beinhalten. Auch in mutate()
können wir innerhalb der Klammern
mehrere mutate-Schritte der Form alte/neue_Spalte = Ausdruck
durch Kommata
“aneinanderhängen”.
Hier erstellen wir eine kleine Tibble mit Beispieldaten um die Funktionsweise
von mutate()
zu veranschaulichen:
<- tibble(x1 = 1:5,
exmpl_tbl x2 = 5:1)
exmpl_tbl## # A tibble: 5 × 2
## x1 x2
## <int> <int>
## 1 1 5
## 2 2 4
## 3 3 3
## 4 4 2
## 5 5 1
%>%
exmpl_tbl mutate(x1 = sqrt(x1),
x3 = x1 * x2,
x4 = round(x3, digits = 2))
## # A tibble: 5 × 4
## x1 x2 x3 x4
## <dbl> <int> <dbl> <dbl>
## 1 1 5 5 5
## 2 1.41 4 5.66 5.66
## 3 1.73 3 5.20 5.2
## 4 2 2 4 4
## 5 2.24 1 2.24 2.24
In diesem Beispiel wird der Inhalt der Spalte “x1” durch die Quadratwurzel der
enthaltenen Werte ersetzt – sqrt()
ist eine base R Funktion zum Ziehen der
Quadratwurzel. Die Originalwerte werden also mit den transformierten Werten
ersetzt. Die Werte in “x2” werden hier nicht ersetzt. Die Spalten “x3” und “x4”
werden beide neu aus den Werten der bestehenden Spalten erstellt. Bitte beachten
Sie dabei, dass bei der Erstellung von “x3” bereits die transformierten Werte
aus “x1” in die Berechnung eingehen, nicht die Originalwerte. Die Funktion
round()
, welche bei der Erstellung von “x4” genutzt wird, rundet die Werte aus
“x3” auf zwei Nachkommastellen. Die Anzahl Nachkommastellen wird über das
optionale Argument digits =
kontrolliert. Geben wir dies nicht an, wird auf
Null Nachkommastellen, also Ganzzahlen, gerundet.
Gehen wir nun zur Transformation unseres Datensatzes über.
Die Spalte “titel” listet aktuell die akademischen Titel der Abgeordneten. Liegt
ein Titel vor, wird dieser genau benannt. Liegt keiner vor enthält die Spalte
ein NA
, die Repräsentation fehlender Werte in R. Für diese Analyse
interessiert uns nur, ob ein Titel vorliegt, aber nicht welcher. Dazu könnten
wir eine neue Spalte generieren, welche einen logischen Vektor enthält, der
angibt, ob ein Titel vorliegt (TRUE
) oder nicht (FALSE
). Dies ist eine
sogenannte Dummyvariable. Zum Generieren können wir die dplyr Funktion
if_else()
nutzen. Wir geben als erstes Argument eine Bedingung an, die getestet
wird. Hier !is.na(titel)
, welche testet ob der Inhalt von “titel” nicht –
angezeigt durch das !
– missing ist. Das zweite und dritte Argument geben an,
was eingesetzt werden soll wenn die getestete Bedingung erfüllt beziehungsweise
nicht erfüllt ist. Hier soll im ersten Fall TRUE
, im zweiten FALSE
eingesetzt werden.
<- stammdaten_19 %>%
stammdaten_19 mutate(titel_dummy = if_else(!is.na(titel), TRUE, FALSE))
%>%
stammdaten_19 select(titel, titel_dummy)
## # A tibble: 750 × 2
## titel titel_dummy
## <chr> <lgl>
## 1 Dr. TRUE
## 2 <NA> FALSE
## 3 <NA> FALSE
## 4 <NA> FALSE
## 5 <NA> FALSE
## 6 <NA> FALSE
## 7 <NA> FALSE
## 8 <NA> FALSE
## 9 <NA> FALSE
## 10 <NA> FALSE
## # … with 740 more rows
Die Spalte “fraktion” ist aktuell als Character Vektor abgelegt. Bei der Fraktionszugehörigkeit handelt es sich aber um eine kategoriale Variable. So gibt es nur eine begrenzte Anzahl möglicher Ausprägungen, nämlich die Namen der im 19. Bundestag vertretenen Fraktionen sowie die Kategorie “Flos” für Fraktionslose. Die Repräsentation kategorialer Variablen in R nennt sich factor. Das core tidyverse Package forcats ermöglicht es uns, Faktorvariablen anzulegen und zu manipulieren.
Der einfachste Weg ist es, R selbst auslesen zu lassen, welche Kategorien in
einer Variable vorhanden sind. Dazu wenden wir einfach die Funktion factor()
innerhalb von mutate()
auf eine Spalte an.
<- stammdaten_19 %>%
stammdaten_19 mutate(fraktion = factor(fraktion))
Um herauszufinden, welche Kategorien ein factor vector umfasst, nutzen wir
levels()
.
levels(stammdaten_19$fraktion)
## [1] "AfD" "Die Grünen" "DIE LINKE." "FDP" "Flos"
## [6] "SPD" "Union"
Dies scheint funktioniert zu haben. R hat automatisch und korrekt alle Ausprägungen der Varibale erkannt und jede einem factor zugeordnet. Aktuell sind die Kategorien alphabetisch geordnet, inhaltlicher sinnvoller wäre aber beispielsweise eine Ordnung nach der Größe der Fraktionen im 19. Bundestag.
Um die Kategorien manuell neu zu ordnen, könnten wir fct_relevel()
nutzen.
Dazu müssten wir aber die gewünschte Reihenfolge per Hand notieren. Bequemer
geht es mit fct_infreq()
, welche die Kategorien automatisch nach der
Häufigkeit Ihres Auftretens in den Daten ordnet. Also in unserem Fall von der
Kategorie mit den meisten Abgeordneten bis zu der mit den wenigsten.
<- stammdaten_19 %>%
stammdaten_19 mutate(fraktion = fct_infreq(fraktion))
levels(stammdaten_19$fraktion)
## [1] "Union" "SPD" "AfD" "FDP" "Die Grünen"
## [6] "DIE LINKE." "Flos"
Dass die Grünen vor der Linken eingeordnet wurden obwohl die Linken im 19. Bundestag zwei Mandate mehr hatten, liegt daran, dass der Datensatz auch ausgeschiedene MdBs umfasst. Von diesen hatten die Grünen drei mehr, so dass sie sich in der Gesamtzahl von Beobachtungen im Datensatz knapp vor die Linke setzen.
Die Spalte “geschlecht” liegt ebenfalls als Character Vektor vor und kann auf dem selben Weg in einen Factor umgewandelt werden.
<- stammdaten_19 %>%
stammdaten_19 mutate(geschlecht = factor(geschlecht))
levels(stammdaten_19$geschlecht)
## [1] "männlich" "weiblich"
Damit sind die Daten zur weiteren Analyse vorbereitet. Die Spalte “titel” ist
allerdings nicht mehr notwendig, da wir die enthaltenen Informationen in die neue
Dummyvariable überführt haben. Wir können diese also mit select()
entfernen.
<- stammdaten_19 %>%
stammdaten_19 select(-titel)
stammdaten_19## # A tibble: 750 × 8
## wp nachname vorname praefix geschlecht alter fraktion titel_dummy
## <int> <chr> <chr> <chr> <fct> <int> <fct> <lgl>
## 1 19 Abercron Michael von männlich 64 Union TRUE
## 2 19 Achelwilm Doris <NA> weiblich 40 DIE LINKE. FALSE
## 3 19 Aggelidis Grigorios <NA> männlich 52 FDP FALSE
## 4 19 Akbulut Gökay <NA> weiblich 34 DIE LINKE. FALSE
## 5 19 Albani Stephan <NA> männlich 49 Union FALSE
## 6 19 Alt Renata <NA> weiblich 52 FDP FALSE
## 7 19 Altenkamp Norbert Maria <NA> männlich 45 Union FALSE
## 8 19 Altmaier Peter <NA> männlich 59 Union FALSE
## 9 19 Amthor Philipp <NA> männlich 24 Union FALSE
## 10 19 Amtsberg Luise <NA> weiblich 33 Die Grünen FALSE
## # … with 740 more rows
3.3 Speichern der transformierten Daten
Wir werden im nächsten Kapitel mit den hier transformierten Daten weiterarbeiten. Dazu sollten Sie entweder zu Beginn des Kapitels das Script aus diesem Kapitel nochmals durchlaufen lassen, oder Sie speichern die bearbeiteten Daten:
save(stammdaten_19, file = "stammdaten_19.RData")
3.4 Deskriptive Datenanalyse mit dplyr
3.4.1 summarise()
Die Funktion summarise()
ermöglicht es uns, zusammenfassende Maßzahlen zu
einer Tibble zu berechnen. Die Syntax funktioniert dabei ähnlich zu mutate()
.
Wir definieren einen Namen für die Spalte, in der die Maßzahlen gelistet werden
sollen, und nach dem =
eine entsprechende Berechnung oder Funktion. Das
Ergebnis ist ein neue Tibble, welche die angeforderten Statistiken enthält.
Betrachten wir zunächst die Spalte “alter”. Wir könnten uns für das
arithmetische Mittel des Alters der MdBs interessieren. Dazu definieren wir
innerhalb von summarise()
den Namen der neuen Spalte und nach dem =
nutzen
wir die base R Funktion mean()
:
%>%
stammdaten_19 summarise(arith_mittel = mean(alter))
## # A tibble: 1 × 1
## arith_mittel
## <dbl>
## 1 49.4
Wir können uns auch mehrere Statistiken gleichzeitig ausgeben, indem wir die
entsprechenden Anfragen mit ,
verbinden:
%>%
stammdaten_19 summarise(minimum = min(alter),
arith_mittel = mean(alter),
median = median(alter),
maximum = max(alter))
## # A tibble: 1 × 4
## minimum arith_mittel median maximum
## <int> <dbl> <dbl> <int>
## 1 24 49.4 50 77
Das sieht alles schon ordentlich aus, ist aber auch auf einfacherem Wege
erzielbar, beispielsweise mit der praktischen base R Funktion summary()
.
Wirklich mächtig wird summarise()
erst durch die Kombination mit group_by()
.
So können wir die Daten gruppieren und die zusammenfassenden Statistiken für
jede dieser Gruppen separat berechnen.
3.4.2 summarise()
& group_by()
Unsere Daten bieten sich für eine Analyse nach Fraktionszugehörigkeit an. Als
ersten Schritt können wir die Daten also nach dem Inhalt der Spalte “fraktion”
gruppieren und zunächst einmal zählen, wie viele Abgeordnete pro Fraktion im
Bundestag vertreten sind. Hier bitte erneut beachten, dass sowohl ausgeschiedene
Abgeordnete sowie deren NachrückerInnen im Datensatz enthalten sind. Die
Analyseergebnisse werden dementsprechend nicht exakt mit der Mandatszahl
übereinstimmen. Innerhalb von summarise()
nutzen wir hier die dplyr
Funktion n()
, welche die Anzahl Beobachtungen pro Gruppe, also die
Gruppengröße, zurückgibt.
%>%
stammdaten_19 group_by(fraktion) %>%
summarise(abgeordnete = n())
## # A tibble: 7 × 2
## fraktion abgeordnete
## <fct> <int>
## 1 Union 261
## 2 SPD 165
## 3 AfD 94
## 4 FDP 85
## 5 Die Grünen 72
## 6 DIE LINKE. 71
## 7 Flos 2
Eben haben wir das Durchschnittsalter aller Abgeordneten berechnet. Dies können wir natürlich auch gruppiert nach Fraktionen machen.
%>%
stammdaten_19 group_by(fraktion) %>%
summarise(minimum = min(alter),
arith_mittel = mean(alter),
std_abweichung = sd(alter),
median = median(alter),
maximum = max(alter),
anzahl = n())
## # A tibble: 7 × 7
## fraktion minimum arith_mittel std_abweichung median maximum anzahl
## <fct> <int> <dbl> <dbl> <dbl> <int> <int>
## 1 Union 24 50.0 9.72 51 75 261
## 2 SPD 27 50.3 9.35 52 68 165
## 3 AfD 26 50.9 12.2 50 77 94
## 4 FDP 24 45.6 10.7 46 76 85
## 5 Die Grünen 26 46.9 9.90 47.5 64 72
## 6 DIE LINKE. 27 50.4 9.72 52 69 71
## 7 Flos 42 42 0 42 42 2
Oft ist es hilfreich, die Daten nach einer Statistik, die uns interessiert, zu
ordnen. Möchten wir beispielsweise wissen, welche Fraktion den höchsten
beziehungsweise niedrigsten Mittelwert des Alters hat, können wir die Ergebnisse
in einem weiteren Schritt mit der Funktion arrange()
nach den Werten dieser
Variable ordnen. Die zusätzliche Funktion desc()
bewirkt, dass die Daten
absteigend geordnet werden.
%>%
stammdaten_19 group_by(fraktion) %>%
summarise(minimum = min(alter),
arith_mittel = mean(alter),
std_abweichung = sd(alter),
median = median(alter),
maximum = max(alter),
anzahl = n()) %>%
arrange(desc(arith_mittel))
## # A tibble: 7 × 7
## fraktion minimum arith_mittel std_abweichung median maximum anzahl
## <fct> <int> <dbl> <dbl> <dbl> <int> <int>
## 1 AfD 26 50.9 12.2 50 77 94
## 2 DIE LINKE. 27 50.4 9.72 52 69 71
## 3 SPD 27 50.3 9.35 52 68 165
## 4 Union 24 50.0 9.72 51 75 261
## 5 Die Grünen 26 46.9 9.90 47.5 64 72
## 6 FDP 24 45.6 10.7 46 76 85
## 7 Flos 42 42 0 42 42 2
Für die Mehrzahl der Fraktionen liegt der Mittelwert des Alters bei ca. 50 Jahren. Dabei beträgt die Differenz des arithmetischen Mitels zwischen AfD und Union etwa 1 Jahr. Wir sehen für die AfD aber auch eine höhere Standardabweichung und einen niedrigeren Median. Wir könnten also vermuten, dass in der AfD einige besonders alte Abgeordnete vertreten sind, welche den Mittelwert nach oben verzerren. Die FDP ist dem Mittelwert nach die jüngste, die Grünen die zweitjüngste Fraktion. Während sich das Alter der jüngsten Fraktionsmitglieder relativ wenig unterscheidet, bestehen beim Alter der ältesten Mitglieder deutliche Unterschiede. Während es bei den Grünen keine MdBs über 64 gibt, geht das Maximalalter bei AfD, Union und FDP bis in die mittleren 70er. Die Statistiken für die Gruppe der fraktionslosen sind nicht besonders belastbar, da sie nur aus zwei Personen besteht, und lassen sich nicht sinnvoll analysieren. Diesen ersten Befunden werden wir bei der graphischen Analyse im folgenden Kapitel weiter auf den Grund gehen.
Die Spalte “titel_dummy” ist ein logischer Vektor der TRUE
enthält, wenn ein
MdB einen akademischen Titel besitzt und FALSE
, wenn keiner vorliegt. Hinter
einem logischen Vektor stehen Zahlenwerte. TRUE
ist die Repräsentation von 1,
FALSE
von 0. Wir können also direkt mit den Werten des Vektors rechnen. Um die
absolute Anzahl an Titeln zu berechenn, reicht es so die Summe des Vektors zu
bilden. Alle Werte werden aufaddiert und da wir nur Einsen und Nullen haben, ist
das Ergebnis gleich der Anzahl der Abgeordneten, die einen akademischen Titel
tragen. Um den relativen Anteil zu berechnen, können wir das arithmetische
Mittel nutzen. Alle Einsen und Nullen werden aufaddiert und durch die Anzahl der
Beobachtungen geteilt. Dies ist der Anteil der Abgeordneten mit Titel.
%>%
stammdaten_19 summarise(absolut = sum(titel_dummy),
relativ = mean(titel_dummy)
)## # A tibble: 1 × 2
## absolut relativ
## <int> <dbl>
## 1 147 0.196
Wir könnten uns dafür interessieren, ob sich das Alter der Abgeordneten mit Titel von denen unterscheidet die keinen Titel tragen. Dazu gruppieren wir nach der Spalte “titel_dummy” und berechnen die Statistiken zum Alter.
%>%
stammdaten_19 group_by(titel_dummy) %>%
summarise(minimum = min(alter),
arith_mittel = mean(alter),
std_abweichung = sd(alter),
median = median(alter),
maximum = max(alter),
anzahl = n())
## # A tibble: 2 × 7
## titel_dummy minimum arith_mittel std_abweichung median maximum anzahl
## <lgl> <int> <dbl> <dbl> <int> <int> <int>
## 1 FALSE 24 49.1 10.1 50 77 603
## 2 TRUE 28 50.8 10.6 50 76 147
Abgeordnete mit Titel sind im Durchschnitt etwas älter. Dies macht Sinn, da sowohl das Erlangen eines Titels als auch eines Bundestagmandats hohe Zeitinvestitionen darstellen.
Wir können auch nach mehreren Spalten gleichzeitig gruppieren. In diesem Fall werden die Statistiken für jede Gruppenkombination berechnet.
%>%
stammdaten_19 group_by(fraktion, titel_dummy) %>%
summarise(minimum = min(alter),
arith_mittel = mean(alter),
std_abweichung = sd(alter),
median = median(alter),
maximum = max(alter),
anzahl = n())
## `summarise()` has grouped output by 'fraktion'. You can override using the `.groups` argument.
## # A tibble: 14 × 8
## # Groups: fraktion [7]
## fraktion titel_dummy minimum arith_mittel std_abweichung median maximum
## <fct> <lgl> <int> <dbl> <dbl> <dbl> <int>
## 1 Union FALSE 24 49.7 9.83 51 70
## 2 Union TRUE 32 50.9 9.32 50 75
## 3 SPD FALSE 27 50.2 9.45 52 68
## 4 SPD TRUE 33 50.9 8.95 49.5 67
## 5 AfD FALSE 26 49.8 11.7 49 77
## 6 AfD TRUE 28 54.8 13.5 55.5 76
## 7 FDP FALSE 24 44.9 9.77 46 66
## 8 FDP TRUE 31 48.4 13.5 44.5 76
## 9 Die Grünen FALSE 26 47.8 10.1 49 64
## 10 Die Grünen TRUE 34 43.8 8.79 42.5 64
## 11 DIE LINKE. FALSE 27 49.0 9.74 50 66
## 12 DIE LINKE. TRUE 48 56.9 6.67 56 69
## 13 Flos FALSE 42 42 NA 42 42
## 14 Flos TRUE 42 42 NA 42 42
## # … with 1 more variable: anzahl <int>
Wir sehen an den Mittelwerten, dass der oben für den gesamten Bundestag analysierte Unterschied im mittleren Alter von MdBs mit und ohne Titel nicht für alle Fraktionen gleichermaßen gilt. Während Titelträger in AfD, FDP und LINKE im Mittel klar älter sind als Abgeordnete ohne Titel, ist die Differenz für die Union und die SPD relativ gering. Bei den Grünen sind Titelträger im Mittel sogar deutlich jünger. Auch dies werden wir in der graphischen Analyse erneut betrachten.
An dieser Stelle müssen wir noch über eine Besonderheit in der Zusammenarbeit
von group_by()
und summarise()
sprechen, welche zu Problemen führen kann,
wenn Sie sich ihr nicht bewusst sind. Grundsätzlich wird jeder Aufruf von
summarise()
nach einem group_by()
Befehl eine Ebene der Gruppierung
auflösen. Für die Beispiele in denen wir nur nach Fraktion oder Titel gruppiert
haben, bedeutet dies also, dass die Daten nach dem summarise()
Befehl nicht
mehr gruppiert waren. Wir hatten eine Gruppierungsebene und diese wurde
automatisch entfernt. Im letzten Beispiel hatten wir zwei Gruppierungsebenen.
Von diesen wurde nur die niedrigere Gruppierungsebene – die Reihenfolge der
Variablen in group_by()
bestimmt auch die Reihenfolge der Gruppierung –, also
die Gruppierung nach dem Titel aufgelöst. Die Gruppierung nach der
Fraktionsmitgliedschaft ist weiterhin aktiv. Dies ist auch im Output anhand der
Zeile # Groups: fraktion [7]
erkennbar. Die Daten sind nach der Fraktion
gruppiert und wir haben 7 Gruppen. Möchten wir mit den Ergebnissen dieser
Berechnungen weiterarbeiten, müssen wir uns auch über die Gruppierung bewusst
sein. Sollen die Daten weiterhin gruppiert sein? Dann ist alles gut, sollen
sie es nicht sein müssen wir die Gruppierung vollständig auflösen. Dies machen
wir mit der Funktion ungroup()
. Solange Sie sich nicht sicher sind, dass Sie
die Gruppierung wirklich weiterhin brauchen, würde ich immer zu einem
ungroup()
am Ende einer group_by()
und summarise()
Operation mit mehreren
Gruppen tendieren. So verhindern sie versehentliche Berechnungen auf
Gruppenbasis und die Gruppierung lässt sich jederzeit schnell wieder herstellen,
sollte dies notwendig werden.
%>%
stammdaten_19 group_by(fraktion, titel_dummy) %>%
summarise(minimum = min(alter),
arith_mittel = mean(alter),
std_abweichung = sd(alter),
median = median(alter),
maximum = max(alter),
anzahl = n()) %>%
ungroup()
## `summarise()` has grouped output by 'fraktion'. You can override using the `.groups` argument.
## # A tibble: 14 × 8
## fraktion titel_dummy minimum arith_mittel std_abweichung median maximum
## <fct> <lgl> <int> <dbl> <dbl> <dbl> <int>
## 1 Union FALSE 24 49.7 9.83 51 70
## 2 Union TRUE 32 50.9 9.32 50 75
## 3 SPD FALSE 27 50.2 9.45 52 68
## 4 SPD TRUE 33 50.9 8.95 49.5 67
## 5 AfD FALSE 26 49.8 11.7 49 77
## 6 AfD TRUE 28 54.8 13.5 55.5 76
## 7 FDP FALSE 24 44.9 9.77 46 66
## 8 FDP TRUE 31 48.4 13.5 44.5 76
## 9 Die Grünen FALSE 26 47.8 10.1 49 64
## 10 Die Grünen TRUE 34 43.8 8.79 42.5 64
## 11 DIE LINKE. FALSE 27 49.0 9.74 50 66
## 12 DIE LINKE. TRUE 48 56.9 6.67 56 69
## 13 Flos FALSE 42 42 NA 42 42
## 14 Flos TRUE 42 42 NA 42 42
## # … with 1 more variable: anzahl <int>
3.5 Export von Tabellen
Ein flexibler Weg zum direkten Export von Tabellen ist es, diese als “.csv” abzuspeichern. Dieses Format kann wiederum direkt in Excel importiert werden.
CSV steht für “comma-separated values”. Da diese Dateien sehr einfach gehalten sind und von einer großen Bandbreite an Software verarbeitet werden können, ist CSV eines der meistgenutzten Formate für zweidimensionale Datenstrukturen.
Hier fehlt der Platz um ins Detail zu gehen, aber wir können kurz die Grundstruktur einer CSV Datei betrachten:
column1, column2, column3
data1_1, data1_2, data1_3
data2_1, data2_2, data2_3
data3_1, data3_2, data3_3
Die Zeilen einer Tabelle sind durch Zeilenumbrüche getrennt, die Spalten durch Kommata. In der ersten Zeile stehen dabei häufig, aber nicht immer, die Spaltennamen. Damit wissen Sie bereits das Wichtigste. Mehr zu “.csv” Dateien finden sie unter: https://jakobtures.github.io/web-scraping/files.html
Wir können beispielsweise das Ergebnis einer summarise()
Operation mit
write_csv()
aus readr (core tidyverse) abspeichern.
%>%
stammdaten_19 group_by(fraktion, titel_dummy) %>%
summarise(minimum = min(alter),
arith_mittel = mean(alter),
std_abweichung = sd(alter),
median = median(alter),
maximum = max(alter),
anzahl = n()) %>%
ungroup() %>%
write_csv(file = "alter_fraktion_titel_1.csv")
## `summarise()` has grouped output by 'fraktion'. You can override using the `.groups` argument.
Aus Gründen, die vermutlich nur Microsoft selbst bekannt sind – ich tippe auf
missverstandene “Nutzerfreundlichkeit” –, können “.csv” Dateien, in denen die
Spalten durch Kommata getrennt sind – der internationale Standard –, auf
deutschen Windows Systemen oft nicht ohne weiteres importiert werden. Nutzen Sie
statt Microsoft Office die Open Source Alternative LibreOffice, ist das Problem
bereits behoben. Möchten Sie aber Microsoft Office auf einem deutschen System
nutzen, sollten Sie die Datei eventuel im Deutschen Standard speichern. Dabei
werden statt Kommata Semikolons als Trennzeichen der Spalten verwendet, da die
Kommata in Deutschland zur Darstellung von Dezimalzahlen benötigt werden. Dies
ist mit write_csv2()
auf einfachem Wege möglich.
3.5.0.1 Weiterführende Resourcen
Es existiert eine Vielzahl von Packages, die Funktionen zur Formatierung und dem Export von Tabellen mitbringen. Aus Platzgründen können wir nicht auf die Details eingehen, hier aber einige Startpunkte:
Möchten sie direkt nach “.xlsx” exportieren, wären zwei der Optionen:
- writexl: https://cran.r-project.org/web/packages/writexl/index.html
- openxlsx: https://cran.r-project.org/web/packages/openxlsx/index.html
Tabellen direkt im “.docx” Format zu erstellen ermöglicht flextable: https://davidgohel.github.io/flextable/
huxtable kann Tabellen im “.tex” Format zur Arbeit in LaTeX ausgeben, beherrscht aber auch HTML sowie Word, Excel und Powerpoint Output: https://hughjonesd.github.io/huxtable/
Möchten sie externe Software zum Schreiben von Hausarbeiten und Papers ganz umgehen, könnte R Markdown für Sie von Interesse sein. Dies ermöglicht das gleichzeitige Schreiben von Text und Code in einem Dokument. Die Ausgabe lässt sich in unterschiedliche Formate exportieren und kann neben dem Text auch Code, der dargestellt werden soll, sowie Tabellen und Grafiken enthalten. Einige Resourcen zu R Markdown finden Sie hier:
- Einführung der RStudio website: https://rmarkdown.rstudio.com/lesson-1.html{target_“blank”}
- Das R Markdown cheatsheet: https://raw.githubusercontent.com/rstudio/cheatsheets/master/rmarkdown.pdf
- Kapitel zu R Markdown in “R for Data Science” von Hadley Wickham und Garrett Grolemund: https://r4ds.had.co.nz/r-markdown.html
- “R Markdown Cookbook” von Yihui Xie, Christophe Dervieux, Emily Riederer: https://bookdown.org/yihui/rmarkdown-cookbook/
Die Website, die Sie aktuell betrachten, sowie viele der verlinkten Resourcen sind mit bookdown geschrieben, welches auf R Markdown aufbaut und sich vor allem für umfangreichere Projekte eignet.
- “bookdown: Authoring Books and Technical Documents with R Markdown” von Yihui Xie: https://bookdown.org/yihui/bookdown/