B. Philipp Kleer
11. Oktober 2021
Tidyr
und dplyr
Wenn wir mit Sekundärdaten arbeiten, sind diese oftmals bereits so vorbereitet, dass die Daten entweder im long- oder im wide-Format vorliegen. Wenn wir aber selbst Daten erheben oder z.B. über eine API laden, kann es sein, dass dies nicht der Fall ist. Auch kommt es vor, dass die Datenerhebung in Teilschritten erfolgt oder zum Beispiel Daten aus zwei verschiedenen Zeitpunkten für die Datenanalyse zusammengefügt werden sollen. Auch bei Multi-Level-Modellen werden oftmals Makrodaten mit Mikrodaten vor der Analyse verbunden.
Diese verschiedenen Varianten werden wir uns nun in tidyverse
mithilfe von tidyr
und dplyr
anschauen.
tidyr
Es ist für die Verarbeitung wichtig, dass die Datensätz tidy sind, damit die Funktionen in R problemlos mit den Daten laufen. Das bedeutet, dass die Daten in einem bestimmten Format vorliegen müssen, damit die Funktionen in R auch gut mit den Daten funktionieren (weniger troubleshooting).
Was ist ein Datensatz? Ein Datensatz ist generell immer eine Sammlung von Werten, sei es numerisch oder eine Zeichenkette. Diese Werte sind immer auf zwei Arten organisiert: Jeder Wert gehört zu einer Variable und zu einer Beobachtung. Eine Variable inkludiert alle Werte, die für diese gemessen worden sind (also alle Beobachtungen auf dieser Variable). Eine Beobachtung inkludiert alle Werte, die für diese Beobachtung gemessen wurden (also alle Variablenwerte dieser Einheit).
Damit Daten in R gut mit den Funktionen genutzt werden können, müssen diese in einem tidy-Format vorliegen (auch long-Format genannt). Ein Datensatz ist dann tidy, wenn …
Die folgenden zwei Datensätze zeigen Daten, die jeweils nicht tidy sind. Wir werden diese im Folgenden bereinigen.
Dieser Datensatz ist im sogenannten wide-Format. D.h. wenn wir neue Prüfungen hätten, würden wir einfach weitere Spalten hinzufügen. Dies ist aber für die Verarbeitung mit R teilweise problematisch, denn wir benötigen oft ein long-Format.
In diesem Fall haben wir mehrere Probleme: Zum einen sind in den Spalten nicht überall Variablen, sondern Beobachtungen (momo
, kim
, sascha
) und in exam
finden wir wiederum Variablennamen.
Fangen wir mit statclass
an.
statclass
In der Tabelle kann die Note jeder Person aus jeder Prüfung ausgelesen werden. Überlegt kurz, welche Variablen wir bei diesem Satz generieren möchten!
names
: momo, sascha, kimcourse
: statI, statII, r, spssgrade
: Wert in Abhängigkeit der zwei oberen.Es sind also zwei Informationen in den Spalten stat1
, stat2
, r
und spss
. Nämlich welcher Test es ist (implizit über Variablenname) und die Note. D.h. hier sind Werte als Variablenname angegeben und das verstößt gegen die Regeln eines tidy Datensatzes. Wir benötigen in einem tidy-Format aber beide Informationen explizit! Denn die Spaltennamen sind hier Werte (Art der Prüfung) und nicht einfach Namen.
Um dies zu bereinigen, nutzt man pivot_longer()
. Hierbei geben wir zuerst an, welche Spalten neugeordnet werden sollen (in unserem Fall stat1
bis spss
), dann in welche neuen Variablen die Namen bzw. die Werte gespeichert werden sollen. Mit names_to
benennen wir die neue Variable, die den Test unterscheidet und mit values_to
benennen wir die Variable, die die Noten beinhaltet.
statclassTidy <- statclass %>%
pivot_longer(stat1:spss,
names_to = "course",
values_to = "grade"
) %>%
arrange(name,
course
)
statclassTidy
Jetzt haben wir ein long-Format, dass die Datenbearbeitung oft einfacher macht (z.B. mit ggplot2
). Aber Aufpassen: Man kann jetzt nicht einfach mehr einen Mittelwert von grade
berechnen, da dies verschiedene Kurse beinhaltet. Man muss dabei also Bedingungen setzen (wenn man im long-Format ist).
Möchte man dies wieder umkehren, nutzt man die Funktion pivot_wider()
:
statclassRe <- statclassTidy %>%
pivot_wider(names_from = course,
values_from = grade,
)
statclassRe
statclass2
Wo liegt hier das Problem?
Und hier die Lösung: Auch hier wandeln wir wieder in das long-Format um!
statclass2Tidy <- statclass2 %>%
pivot_longer(momo:sascha,
names_to = "names",
values_to = "grade"
)
statclass2Tidy
Gibt es evtl. noch mehr Probleme?
exam
beinhaltet keine Werte, sondern Namen von Variablen, nämlich von exam1
und exam2
! Variablen, die die Note in der Prüfung angeben, deren Wert noch in grade
steht. Deshalb nutzen wir hier jetzt pivot_wider()
, um die Daten final tidy zu machen:statclass2Tidy <- statclass2Tidy %>%
pivot_wider(names_from = exam,
values_from = grade
) %>%
relocate(names) %>%
arrange(names,
test
)
statclass2Tidy
Nur zur Übung könnte man auch dies wiederum in den Ursprungsdatensatz mit pivot_wider()
verändern:
dplyr
Zuerst wiederholen wir noch einmal, wie wir einen Datensatz teilen: Wir filtern die Fälle, die für unsere spätere Analyse relevant sind. Zum Beispiel wollen wir nur über Psychologie-Studierende aus Marburg forschen. Anschließend möchten wir eine neue Variable erstellen, die eine Beschreibung für die Studiendauer inkludiert (als Faktor)
Welche Funktionen müssen wir anwenden?
uniPumPsy <- uni %>%
filter(city == "Marburg" & study == "Psychology") %>%
mutate(term.group = factor(case_when(term <= 2 ~ "Anfänger:in",
term > 2 & term <= 6 ~ "Regelstudienzeit",
term > 6 ~ "Langzeit"
)
)
)
head(uniPumPsy)
Im nächsten Schritt nehmen wir nun an, dass die Datenerfassung von 4 verschiedenen Personen durchgeführt wurde und es somit 4 Teildatensätze gibt, die nun zu einem vollständigen Datensatz verbunden werden sollen. Dazu nutzen wir die Funktion bind_rows()
. In unserem Beispiel haben alle 4 Teildatensätze genau die gleiche Anzahl an Variablen, die dazu auch noch genau gleich benannt sind! Mit dem Argument .id
erstellen wir eine Variable names "origin"
, die die Herkunft des Falles erfasst. Dies ist automatisch nummeriert. Mit mutate()
machen wir daraus einen Faktor, der eine bessere Beschreibung beinhaltet (coder1, coder2, coder3, coder4)
Im nächsten Schritt nehmen wir nun an, dass die Datenerfassung von 4 verschiedenen Personen durchgeführt wurde und es somit 4 Teildatensätze gibt, die nun zu einem vollständigen Datensatz verbunden werden sollen. Dazu nutzen wir die Funktion bind_rows()
. In unserem Beispiel haben alle 4 Teildatensätze genau die gleiche Anzahl an Variablen, die dazu auch noch genau gleich benannt sind! Mit dem Argument .id
erstellen wir eine Variable names "origin"
, die die Herkunft des Falles erfasst. Dies ist automatisch nummeriert. Mit mutate()
machen wir daraus einen Faktor, der eine bessere Beschreibung beinhaltet (coder1, coder2, coder3, coder4)
uniAll <- uni1 %>%
bind_rows(list(uni2,
uni3,
uni4
),
.id = "origin"
) %>%
mutate(origin = factor(origin,
labels = c("coder1",
"coder2",
"coder3",
"coder4"
)
)
)
table(uniAll$origin)
coder1 coder2 coder3 coder4
250 250 250 250
[1] coder1 coder1 coder1 coder1 coder1 coder1
Levels: coder1 coder2 coder3 coder4
Wir haben hier jetzt also aus vier Teildatensätzen einen gesamten Datensatz erstellt, der alle Fälle der vier Teildatensätze enthält. Wichtig, in diesem Fall waren alle Variablennamen gleich!
Nun probieren wir einmal aus, was passiert, wenn es zum Beispiel in einem Teildatensatz einen Typo gibt. Zuerst erstellen wir dazu einfach zwei neue Datensätze, die jeweils nur 3 Fälle inkludieren, und unterschiedliche Variablen.
uniA <- uni[1:3, 4:5]
City <- c("Giessen",
"Marburg",
"Marburg"
)
distance <- c(21,
30,
45
)
uniB <- data.frame(City,
distance
)
head(uniA)
Wir haben also in beiden Datensätzen die zwei Variablen, die Studienort und die Distanz zum Studienort angeben. im Datensatz uniB
ist aber die Variable des Studienorts anders geschrieben. Probieren wir bind_rows()
aus.
Da die Variablennamen nicht genau gleich sind, werden nun drei Variablen geschaffen: city
, distance
und City
. Wo die Variable nicht vorliegt, werden automatisch NAs
erzeugt. Dies ist vorteilhaft, kann aber auch frickelig werden, wenn bei der Datensatzerstellung nicht streng nach einem Codenamen-Schema gearbeitet wurde. Lösung: Im Vorfeld Variablen abklären und umbenennen. Andernfalls kann man full_join()
nutzen.
Dieser Ansatz ist nicht weniger aufwändig, als das Umbenennen von Spaltennamen, bietet aber dennoch eine Alternative. Mit full_join()
kombinieren wir zwei Datensätze und können im Argument by
angeben, welche Spalten jeweils denselben Inhalt haben. Schreibaufwand hierbei ist, dass gleiche Spaltennamen auch aufgeführt werden müssen, da ansonsten (hier im Beispiel) die Variablen distance.x
und distance.y
gebildet werden. Dies liegt daran, da full_join()
eigentlich dafür gedacht ist, neue/zusätzliche Variablen hinzuzufügen.
In unserem Beispiel würden wir also angeben, dass aus Datensatz uniA
die Spalte city
gleich der Spalte City
aus dem Datensatz uniB
ist. Gleiches gilt für die distance
Variable.
uniTest2 <- uniA %>%
full_join(uniB,
by = c("city" = "City",
"distance" = "distance"
)
)
head(uniTest2)
Dies ist oft bei Mehrebenenansätzen nötig. Man hat Daten auf zwei verschiedenen Ebene und führt diese vor der Analyse in einem Datensatz zusammen, um alle Variablen aus einem Datensatz ansprechen zu können. Hierbei möchten wir Daten zu einem Datensatz hinzufügen, wobei wir eine Variable angeben, die als Matching-Variable dient. In diesem Beispiel haben wir im Datensatz uniMacro
noch zusätzliche Variablen zu den jeweiligen Studienorten: Neben city
und study
sind hierin auch supervision
(Betreuungsrelation) und maxsem
(max. Seminargröße) pro Studienort und pro Studienfach eingetragen.
Für das Mergen von Datensätzen, kann man je nach Ausgangspunkt left_join()
bzw. right_join()
oder auch full_join()
nutzen. Um die Daten korrekt zu mergen, müssen wir sowohl die Variable city
als auch study
nutzen, da sich die Makro-Variablen eben nach Studienort und Studienfach unterscheiden! Dies geben wir im Argument by
an. Bei left_join()
geben wir den Datensatz, an dem die Daten hinzugefügt werden sollen, per Piping weiter. Bei right_join()
werden die Daten an den zweiten Datensatz angehängt.
Wir möchten nun die Makrodaten aus uniMacro
jeweils passend auf Studienort und Studienfach in den Mikrodatensatz hinzufügen, um anschließend ein Multi-Level-Modell zu berechnen. Hierzu nutzen wir left_join()
und geben im Argument by
an, dass sowohl city
als auch study
als Matching-Variablen genutzt werden sollen.
Alternativ geht dies auch mit full_join()
:
Will man nur weitere Variablen in einen Datensatz hinzufügen, kann man auch hierfür full_join()
nutzen. Wir haben zum Beispiel in einem weiteren Datensatz aus dem Prüfungsverwaltungssystem vor der Anonymisierung der Daten die geleisteten Creditpoints der Befragtena ausgelesen. Diese haben wir im Datensatz points
getrennt gespeichert und dort ebenfalls eine ID-Variable genutzt, die auf die ID-Variable des Datensatzes uni
matcht. Wir fügen jetzt die Creditpoints dem Datensatz uni
mit full_join()
hinzu. Schauen wir uns zuerst nochmal die zwei Datensätze an:
Wir haben zwar in beiden Variablen eine ID-Variable, allerdings ist die Spalte unterschiedlich benannt. Wir können jetzt - wie zuvor oben - wieder im by
-Argument dies angeben. Diesmal wollen wir einfach schnell vorher den Spaltennamen in einem der Datensätze anpassen. Dazu nutzen wir einfach rename()
. Die Logik in der Funktion ist neuer Name = alter Name
.
Jetzt sind die Spaltennamen gleich und wir können die Datensätze mergen.