Kapitel 8 Tidyverse
In diesem Kapitel wollen wir uns damit auseinandersetzen, was das Tidyverse ist und was für Funktionen die einzelnen Packages des Tidyverse uns bereitstellen. Einen besonderen Fokus wollen wir vor allem erst mal auf die Datenverarbeitung legen, die diese im Regelfall auch die meiste Zeit beansprucht.
8.1 Das “Tidyverse”
Das Tidyverse ist eine Sammlung von Packages, welche einer gemeinsamen Grammatik folgen. Im Folgenden wird ein Package immer durch die doppelten Punkte gekennzeichnet sein, zum Beispiel: dplyr::
. Das entspricht in R der Syntax, um eine Funktion aus einem bestimmten Package-Scope abzurufen. So können zwei Funktionen zwar den gleichen Namen haben, aber unterschiedlich implementiert sein, wie etwa dplyr::filter()
und stats::filter()
, welche in eurem Skript uneindeutig einfach nur filter()
heißen könnten. Bei solchen Konflikten, ist es ratsam des jeweilige Package, aus welchem man die Funktion aufrufen will explizit zu deklarieren, also samt dplyr::
.
Die Logos der wichtigsten Packages könnt ihr in der Abbildung sehen. Wir werden immer wieder auf einige von diesen zurückkommen und lernen, diese nach und nach anzuwenden.
8.1.1 Tidyverse laden
Das Tidyverse ladet ihr wie jedes andere Package auch, also zum Beispiel mit library()
oder needs()
. Im Prinzip ladet ihr dabei aber gleich mehrere verschiedene Packages, welche ihr dann nicht im Einzelnen nochmal nachladen müsst.
Die geladenen Packages werden hier mit der geladenen Version gelistet, sowie auch potenzielle Konflikte.
8.2 dplyr::
In dieser Sitzung werden wir uns vornehmlich mit dem Package dplyr::
beschäftigen. Auf dem entsprechenden Cheatsheet findet ihr alle Funktionen übersichtlich dargestellt, welche wir jetzt im Detail besprechen wollen.
Hinweis:
Das Packet dplyr::
erwartet, dass ihr sogenannte “tidy data” vorliegen habt. Das bedeutet, dass jede Spalte eine Variable und jede Zeile eine Beobachtung/Fall ist (Vergleich auch Cheatsheet oben links). Wie ihr in der Übung sehen werdet, muss das nicht immer der Fall sein und ihr müsst, wenn eure Daten nicht “tidy” sind, diese dann erst entsprechenden anpassen. Dazu gibt es das Package tidyr::
.
8.2.1 Daten verfügbar machen
Das Package dplyr::
ist nicht nur eine Sammlung von verschiedenen Funktionen, sondern beinhaltet auch Datensätze in seinem “Scope”. Wenn ihr das Paket geladen habt, könnt ihr auf diese Datensätze zugreifen. Wir wollen für die folgenden Demonstrationen den Datensatz starwars
aus dplyr::
verwenden, welcher verschiedene Merkmale von Star Wars Charakteren umfasst. Über den Zuweisungsoperator können wir den Datensatz auch global verfügbar machen, indem wir ihn an den Namen starwars
binden.
## # A tibble: 87 x 14
## name height mass hair_color skin_color eye_color birth_year
## <chr> <int> <dbl> <chr> <chr> <chr> <dbl>
## 1 Luke Sky~ 172 77 blond fair blue 19
## 2 C-3PO 167 75 <NA> gold yellow 112
## 3 R2-D2 96 32 <NA> white, bl~ red 33
## 4 Darth Va~ 202 136 none white yellow 41.9
## 5 Leia Org~ 150 49 brown light brown 19
## 6 Owen Lars 178 120 brown, gr~ light blue 52
## 7 Beru Whi~ 165 75 brown light blue 47
## 8 R5-D4 97 32 <NA> white, red red NA
## 9 Biggs Da~ 183 84 black light brown 24
## 10 Obi-Wan ~ 182 77 auburn, w~ fair blue-gray 57
## # i 77 more rows
## # i 7 more variables: sex <chr>, gender <chr>, homeworld <chr>,
## # species <chr>, films <list>, vehicles <list>, starships <list>
8.3 Variablen Manipulieren
Die Variablen starwars$films
, starwars$vehicles
und starwars$starships
sind allesamt Listen. Diese sollen heute nicht unser Thema sein, deshalb können wir sie gleich entfernen.
Eine base::
R Möglichkeit dazu wäre:
Hierbei verwenden wir den besonderen Datentyp NULL
, welcher “nichts” symbolisiert (Gar nicht so einfach “nichts” zu benennen).
Indem wir die einzelnen Variablen auf NULL
setzen, entfernen wir sie aus unserem Datensatz.
Allerdings bringt diese Variante einige Probleme mit sich:
- schwer zu lesen (Syntaktisch)
- ungewünschte Objektmanipulation (Kann nicht rückgängig gemacht werden)
Eine weitere Möglichkeit unter Verwendung von base::
R Funktionen wäre:
## [1] 87 11
Schon sehr viel besser!
Allerdings könnte man auf hohem Niveau noch folgende Kritik anbringen:
subset()
kennt keine klare Trennung von Fällen und Variablen (Deswegen müssen wir hier das Argumentselect =
explizit angeben)- nur bedingt partiell ausführbar (Wir übergeben einen Vektor mit Objekten und nicht deren Namen)
Anmerkung:
Im Folgenden wird immer wieder die Funktion dim()
die Pipes (%>%
) abschließen. Das dient dazu, die Veränderung zu demonstrieren, ohne den ganzen Dataframe ausgeben zu müssen.
Jetzt noch die dplyr::
Variante:
## [1] 87 11
Anmerkung: Im Prinzip könnt ihr in fast jedem Fall die native Pipe und die Tidyverse Pipe synonym zueinander benutzen. Die wechselnde Veränderung in den Codebeispielen soll andeuten, ob wir gerade mit Tidyverse Funktionen arbeiten oder nicht.
Die dplyr::
Variante sieht zwar sehr ähnlich zum letzten Beispiel aus, ist im Detail aber doch anders. Zum Beispiel haben wir eine extra Funktion select()
und nicht mehr nur ein Argument einer Funktion subset(select = ...)
. Also bleiben wir doch voerst dabei!
In den Beispielen haben wir jetzt Variablen entfernt, indem wir die Negation entweder über das -
oder das logische Symbol !
vor unseren Vektor gesetzt haben. Natürlich können wir dply::select()
auch dazu nutzen, um bestimmte Variablen aus einem Dataframe explizit auszuwählen:
## [1] 87 2
8.3.1 Neue Variablen erstellen
Mit der Funktion dplyr::mutate()
können wir neue Variablen erstellen und diese zu unserem Tibble (Dataframe) hinzufügen. Dazu können wir auch bereits existierende Variablen nutzen, was extrem eleganten Code ermöglicht!
starwars %>%
mutate(height_in_meters = height / 100, # Höhe in Meter umrechnen
index = 1:n() # fortlaufende Nummer
) %>%
dim()
## [1] 87 16
Die Funktion mutate()
fügt immer eine neue Variable zu unserem Dataframe hinzu. Alle anderen Variablen bleiben also erhalten. Im Beispiel oben haben wir zum Beispiel zwei Variablen hinzugefügt height_in_meters
und index
und haben jetzt 16 statt den ursprünglich 14 Variablen. Im Gegensatz dazu entfernt die Funktion transmute()
alle Variablen, die nicht explizit als Argumente angegeben werden. Das ist unter anderem hilfreich, wenn wir einen großen externen Datensatz auf für uns relevante Variablen reduzieren wollen und diese zugleich transformieren wollen.
8.3.2 Variablen umbennen
Mit der Funktion dplyr::rename()
können wir ganz einfach die Variablen umbenennen, diesem Schema folgend:
Am Beispiel könnte das dann so aussehen:
starwars %>%
rename("height_in_cm" = "height",
"mass_in_kg" = "mass") %>%
names() # Gibt die Namen aller Variablen aus
## [1] "name" "height_in_cm" "mass_in_kg" "hair_color"
## [5] "skin_color" "eye_color" "birth_year" "sex"
## [9] "gender" "homeworld" "species" "films"
## [13] "vehicles" "starships"
Wie man sehen kann, wurden die Namen "height"
und "mass"
in die von uns angegebenen neuen Namen geändert.
8.4 Fälle Manipulieren
Der Ausgangsdatensatz hat 87 Fälle. Als Nächstes wollen wir diese Fälle nach gewissen Kriterien filtern. Wenn wir zum Beispiel nur Charaktere mit weißen Haaren betrachten wollten, könnten wir die dplyr::filter()
Funktion folgendermaßen verwenden:
## # A tibble: 4 x 14
## name height mass hair_color skin_color eye_color birth_year
## <chr> <int> <dbl> <chr> <chr> <chr> <dbl>
## 1 Yoda 66 17 white green brown 896
## 2 Ki-Adi-Mu~ 198 82 white pale yellow 92
## 3 Dooku 193 80 white fair brown 102
## 4 Jocasta Nu 167 NA white fair blue NA
## # i 7 more variables: sex <chr>, gender <chr>, homeworld <chr>,
## # species <chr>, films <list>, vehicles <list>, starships <list>
Wie auch in anderen Funktionen, die wir bereits kennengelernt haben, können wir auch logische Konjunktoren verwenden, um bestimmte Bedingungen auszudrücken:
## # A tibble: 4 x 14
## name height mass hair_color skin_color eye_color birth_year
## <chr> <int> <dbl> <chr> <chr> <chr> <dbl>
## 1 C-3PO 167 75 <NA> gold yellow 112
## 2 Beru Whit~ 165 75 brown light blue 47
## 3 R5-D4 97 32 <NA> white, red red NA
## 4 Shmi Skyw~ 163 NA black fair brown 72
## # i 7 more variables: sex <chr>, gender <chr>, homeworld <chr>,
## # species <chr>, films <list>, vehicles <list>, starships <list>
Hier zum Beispiel erhalten wir einen Dataframe, der nur die Fälle beinhaltet, welche aus Tatooine stammen und nicht männlich sind.
Achtung!
filter()
behält nur diejenigen Zeilen (Fälle), über welche definitiv gesagt werden kann, dass sie der angegebenen Bedingung entsprechen und somit “wahr” sind. Alle NA
-Werte, für welche wir nicht wissen, ob sie wahr wären, werden nicht mit aufgenommen!
8.4.1 Fälle Gruppieren
Am wohl unscheinbarsten ist die Funktion group_by()
, da sie unseren Datensatz oberflächlich betrachtet gar nicht verändert.
## # A tibble: 5 x 14
## # Groups: homeworld [3]
## name height mass hair_color skin_color eye_color birth_year
## <chr> <int> <dbl> <chr> <chr> <chr> <dbl>
## 1 Luke Skyw~ 172 77 blond fair blue 19
## 2 C-3PO 167 75 <NA> gold yellow 112
## 3 R2-D2 96 32 <NA> white, bl~ red 33
## 4 Darth Vad~ 202 136 none white yellow 41.9
## 5 Leia Orga~ 150 49 brown light brown 19
## # i 7 more variables: sex <chr>, gender <chr>, homeworld <chr>,
## # species <chr>, films <list>, vehicles <list>, starships <list>
Im Hintergrund haben wir unseren Tibble aber um das Attribut group
ergänzt, welches jeweils die Zeilennummern für die Fälle einer Gruppe als integer Vektor speichert.
## # A tibble: 5 x 2
## homeworld .rows
## <chr> <list<int>>
## 1 Alderaan [3]
## 2 Aleen Minor [1]
## 3 Bespin [1]
## 4 Bestine IV [1]
## 5 Cato Neimoidia [1]
Durch das nun zu unserem Datensatz beigefügte Attribut verhalten sich folgende Tidyverse Funktionen teilweise anders. Dies ermöglicht es uns, für die einzelnen Gruppen aggregierte Kennzahlen zu ermitteln:
starwars %>%
group_by(homeworld) %>% # gruppiere nach homeworld
summarise(avg_height = mean(height, na.rm = TRUE),
n = n()) %>%
arrange(desc(n)) %>% # absteigend sortiert nach Anzahl
head(3) # nimm die ersten drei Fälle
## # A tibble: 3 x 3
## homeworld avg_height n
## <chr> <dbl> <int>
## 1 Naboo 177. 11
## 2 Tatooine 170. 10
## 3 <NA> 139. 10
Die hier verwendete Funktion summarise()
oder summarize()
macht es uns nun möglich, Gruppeneigenschaften zu ermitteln. Analog zu mutate()
können wir so Spalten zu dem resultierenden Dataframe hinzufügen. Auf dem dplyr:: Cheatsheet findet ihr auf der zweiten Seite unter “Summary Functions” ein paar Funktionen, welche ihr auf die jeweiligen Gruppen anwenden könnt. Im Beispiel haben wir mean()
benutzt, um das durchschnittliche Gewicht der Gruppe zu ermitteln; n()
hingegen gibt die Anzahl an Fällen in der Gruppe aus. Zu beachten gilt es, dass alle unbestimmten Werte (NA
) zu einer Gruppe zusammengefasst wurden.
Mit ungroup()
kann das Gruppenattribut wieder entfernt werden.
Man sollte sich immer vergegenwärtigen, an welchem Punkt man Analysen auf Gruppenebene oder auf Individualebene macht, bzw. ob man mit einem gruppierten Tibble arbeitet oder nicht.
8.6 Literaturverweise
Ergänzend
- R for Data Science Kapitel 5 (Data Transformation)
Weiterführend
- R for Data Science Kapitel 12 (Tidy data), wie sind hier davon ausgegangen, dass “tidy data” vorliegt.