In dieser Sitzung beschäftigen wir uns mit 2 zentralen Aspekten der Auf- und Vorbereitung von Datensätzen zur Durchführung von Multilevel Analysen:

Vorbereitung: Neue Beispieldaten

Wir laden für die heutige Sitzung einen neuen Datensatz. Die darin enthaltenen Daten stammen aus einer aktuellen Studie zu Lerneffekten in Judge-Advisor-Systemen (JAS). Unsere Versuchspersonen sollten darin 20 Schätzaufgaben bearbeiten, bei denen die Entfernungen zwischen EU-Hauptstädten geschätzt werden sollten. Es gab drei verschiedene experimentelle Bedingungen. Eine Gruppe von Versuchspersonen erhielt bei allen 20 Aufgaben den Rat ein und desselben Ratgebers. In einer zweiten Gruppe erhielten Versuchspersonen Ratschläge von wechselnden Ratgebern bei jeder Aufgabe, ohne jemals zwei Ratschläge von ein und demselben Ratgeber zu erhalten. In einer dritten Kontrollbedingung erhielten die Versuchspersonen überhaupt keine Ratschläge.

Der Datensatz kann ganz einfach mit read.csv() von einem Server geladen werden:

data <- read.csv('http://e-scientifics.de/content/datasets/dataset_multilevel_session_4.csv')

Beschreibung der im Datensatz enthaltenen Variablen

Wir können uns einen Überblick über die im Datensatz enthaltenen Variablen verschaffen, in dem wir die Funktion names() verwenden, von deren Resultat in der Folge nur ein Ausschnitt angezeigt wird, da der Datensatz insgesamt 125 Variablen enthält:

names(data)
##  [1] "accuracy_self"               "exp_condition"              
##  [3] "strategy_check"              "task_experience"            
##  [5] "trial_01_advice"             "trial_01_final_confidence"  
##  [7] "trial_01_final_estimate"     "trial_01_initial_confidence"
##  [9] "trial_01_initial_estimate"   "trial_01_true_value"        
## [11] "trial_02_advice"             "trial_02_final_confidence"  
## [13] "trial_02_final_estimate"     "trial_02_initial_confidence"
## [15] "trial_02_initial_estimate"   "trial_02_true_value"        
## [17] "trial_20_advice"             "trial_20_final_confidence"  
## [19] "trial_20_final_estimate"     "trial_20_initial_confidence"
## [21] "trial_20_initial_estimate"   "trial_20_true_value"        
## [23] "vp_code"
Variablenname Beschreibung Ausprägungen
vp_code Versuchspersonennummer Ab 1 aufwärts
exp_condition Versuchsbedingung 1 = Kontrollbedingung ohne Ratgeber
2 = Konstanter Ratgeber
3 = Wechselnde Ratgeber
task_experience Hat die Versuchsperson bereits
vorher Erfahrungen mit dem verwendeten
Aufgabentyp gemacht?
1 = ja
2 = nein
trial_X_initial_estimate Erste Distanzschätzung (ohne Ratschlag) Distanz in Km
trial_X_initial_confidence Erstes Sicherheitsrating (Wie sicher sind Sie
sich eine gute Schätzung abgegeben zu haben?)
1 (sehr unsicher)

7 (sehr sicher)
trial_X_advice Ggf. erhaltener Ratschlag Distanz in Km
trial_X_final_estimate Zweite Distanzschätzung (ggf. mit Ratschlag) Distanz in Km
trial_X_final_confidence Zweites Sicherheitsrating 1 (sehr unsicher)

7 (sehr sicher)
trial_X_true_value Wahrer Wert (Tatäschliche Distanz) Distanz in Km
accuracy_self Wie schätzt die Versuchsperson ihre eigene Akkuratheit
bei der Bearbeitung der Aufgabe ein?
Angabe der durchschnittlichen Abweichung vom
wahren Wert (in Prozent)
1 - 100
strategy_check Offene Frage zum eigenen Vorgehen bei der
Bearbeitung der Schätzaufgaben
Antwort als String

Wie wir sehen enthält der Datensatz einige Messwiederholungsvariablen sowie verschiedene Daten, die vor oder nach dem Experiment erfasst wurden. Bei den Messwiederholungsvariablen gibt es für jede der 20 bearbeiteten Aufgaben (entspricht den Stufen auf Level 1) eine eigene Spalte, d.h. pro messwiederholter Variable haben wir in unserem Datensatz 20 Spalten. Dies ist das übliche Format, in dem viele verschiedene Survey-Tools (z.B. LimeSurvey, UniPark, Qualtrics) und andere Experimentalsoftware alle erhobenen Daten abspeichern. Je nach Experiment haben Datensätze in diesem sogenannten Wide-Format sehr viele Spalten und z.B. pro Versuchsperson (Level 2) lediglich eine einzelene Zeile. Diese Art der Darstellung entspricht auch der Datenansicht in verbreiteter Statistiksoftware wie SPSS oder Statistica.

Viele Funktionen in R (vor allem jene, die Mehrebenenanalysen betreffen) setzen jedoch voraus, dass Daten so aufbereitet werden, dass eine Zeile im Datensatz einem einzelnen Datenpunkt auf der niedrigsten Beobachtungsebene (Level 1) entspricht, z.B. pro Zeile ein Trial (Level 1) einer spezifischen Versuchsperson (Level 2). Neben den Funktionen lmer() aus dem Paket lme4 und lme() aus dem Paket nlme betrifft dies z.B. auch die Funktion ezANOVA() aus dem Paket ez, das sich großer Beliebtheit bei der Durchführung von Varianzanalysen mit Messwiederholungsfaktoren erfreut. Bei der Auswertung von Mehrebenendaten (und somit auch Messwiederholungsdaten) mit R müssen wir daher ggf. eine Umwandlung vom Wide-Format in das geforderte Long-Format vornehmen.

Umwandeln von Datensätzen mit der Funktion reshape()

Die Funktion reshape() erlaubt uns die Datenumwandlung aus dem Wide-Format in das Long-Format und umgekehrt. Sie ist in der Basisinstallation von R mittlerweile enthalten, so dass wir kein zusätzliches Paket (früher reshape2) laden müssen. Wir rufen zunächst einmal die R-Hilfe zu reshape() auf, um uns einen Überblick über die Funktion zu verschaffen:

?reshape

Wie wir sehen besitzt die Funktion diverse Paramter und bei intensiverer Auseinandersetzung mit der Dokumentation wird klar, dass man jeweils in Abhängigkeit von der Richtung der Umwandlung (long nach wide oder wide nach long) unterschiedliche Parameter verwenden muss. Da wir uns vor allem für die Umwandlung von Wide-Format Datensätzen in das Long-Format interessieren, wollen wir uns zunächst nur mit den dafür zwingend erforderlichen Parametern beschäftigen:

Der Parameter data

Hier muss, wie in den meisten R-Funktionen, der Ausgangsdatensatz spezifiziert werden.

Der Parameter direction

Hier muss, die Richtung der Umwandlung spezifiziert werden. Wir wollen Daten aus dem Wide-Format ins Long-Format übertragen und wählen daher ‘long’ als Einstellung.

Der Parameter varying

Diesem Parameter müssen wir die Namen unserer zusammengehörenden Level 1 Variablen bzw. in unserem Beispiel der Messwiederholungsvariablen im Ausgangsdatensatz übergeben. Da wir, wie oben beschrieben, im Wide-Format mehrere Spalten pro Messwiederholungsvariable oder Level 1 Variable haben (immer exakt so viele Spalten wie Level 1 Stufen hat!), erwartet dieser Parameter für jede solche Variable eine separate Liste der zusammengehörenden Variablen.

Zu Verdeutlichung schauen wir uns unseren Beispieldatensatz an. Dieser enthält die folgenden sechs Messwiederholungsvariablen:

  • trial_x_initial_estimate
  • trial_x_initial_confidence
  • trial_x_advice
  • trial_x_final_estimate
  • trial_x_final_confidence
  • trial_x_true_value

Zu jeder dieser Variablen gehören 20 Spalten im Wide-Format, deren Namen sich nur in der Trialnummer unterscheiden. Zu jeder Messwiederholungsvariablen erwartet reshape() nun also in varying eine Liste mit den entsprechenden Variablennamen. Eine Möglichkeit wäre, dass wir diese Listen manuell erstellen:

names_initial_estimates <- c('trial_01_initial_estimate','trial_02_initial_estimate','trial_03_initial_estimate','trial_04_initial_estimate',
                             'trial_05_initial_estimate','trial_06_initial_estimate','trial_07_initial_estimate','trial_08_initial_estimate',
                             'trial_09_initial_estimate','trial_10_initial_estimate','trial_11_initial_estimate','trial_12_initial_estimate',
                             'trial_13_initial_estimate','trial_14_initial_estimate','trial_15_initial_estimate','trial_16_initial_estimate',
                             'trial_17_initial_estimate','trial_18_initial_estimate','trial_19_initial_estimate','trial_20_initial_estimate')

Dieses Vorgehen ist allerdings sehr umständlich, wenn man viele Level 1 Variablen (z.B. 30 Messwiederholungsvariablen) oder viele Stufen auf Level 1 (z.B. 300 Trials) im Datensatz hat. Deshalb empfiehlt sich eine Extraktion der Variablennamen mit Hilfe der Funktion grep(), die ebenfalls in der Basisinstallation von R enthalten ist.

Die Funktion grep() wird im Folgenden mit 3 Parametern ausgeführt. Wichtigster Aspekt ist dabei der Parameter pattern, dem wir ein wiederkehrendes Muster in den Veriablennamen übergeben können, nach welchem dann in allen Variablennamen des Datensatzes (Parameter x) gesucht wird. Die Funktion gibt eine Liste aller Variablennamen in unserem Datensatz zurück, die das definierte Muster enthalten. Dazu müssen wir allerdings noch den Paramter value auf True setzen:

names_initial_estimates <- grep(pattern='initial_estimate', x=names(data), value=T)

names_initial_estimates
##  [1] "trial_01_initial_estimate" "trial_02_initial_estimate"
##  [3] "trial_03_initial_estimate" "trial_04_initial_estimate"
##  [5] "trial_05_initial_estimate" "trial_06_initial_estimate"
##  [7] "trial_07_initial_estimate" "trial_08_initial_estimate"
##  [9] "trial_09_initial_estimate" "trial_10_initial_estimate"
## [11] "trial_11_initial_estimate" "trial_12_initial_estimate"
## [13] "trial_13_initial_estimate" "trial_14_initial_estimate"
## [15] "trial_15_initial_estimate" "trial_16_initial_estimate"
## [17] "trial_17_initial_estimate" "trial_18_initial_estimate"
## [19] "trial_19_initial_estimate" "trial_20_initial_estimate"

Die so erhaltene Namensliste übergeben wir später der Funktion reshape() im Paramter varying. Wichtig ist hierbei noch, dass alle Namenslisten exakt gleich sein müssen. Dies ist die logische Konsequenz der Datenstruktur von Level 1 Variablen im Wide-Format, die wie oben beschrieben exakt so viele Spalten haben, wie es Stufen auf Level gibt.

Aufgabe 1: Erstellt die entsprechenden Namenslisten für alle Messwiederholungsvariablen im Beispieldatensatz.
Der Parameter v.names

Nachdem wir nun Namenslisten für unsere Level 1 bzw. Messwiederholungsvariablen erstellt haben, erwartet reshape() noch Angaben dazu, wie diese Variablen im auszugebenden Datensatz im Long-Format benannt werden sollen. Da im Long-Format jede Level 1 Variable nur noch eine einzelne Spalte haben wird, müssen wir im Paramter v.names pro Level 1 Variable einen Variablennamen angeben und diese Variablenname in einer Liste übergeben.

Der Parameter timevar

Jede Level 1 Variable hat in unserem Ausgangsdatensatz im Wide-Format so viele Spalten, wie es Stufen auf Level 1 gibt. Die jeweilige Stufe ist dabei meistens im Variablennamen enthalten (z.B. trial_01, trial_02, etc.) und eine separate Variable für die jeweilige Level 1 Stufe erübrigt sich. Im Long-Format gibt es jedoch nur eine Spalte für jede Level 1 Variable und so brauchen wir eine zusätzliche Variable, in der die Level 1 Stufe für jede Zeile festgehalten wird.

Die Funktion reshape() erwartet daher im Parameter timevar einen Variablennamen für die zu erstellende Stufenvariable für Level 1. Hier kann man einen beliebigen String übergeben. In unserem Beispiel bietet sich ‘trial’ an

Der Parameter times

Als letzter zwingend notwendigen Parameter erwartet reshape() eine Angabe zur Benennung der einzelnen Stufen in Level 1 in unserem Datensatz. In unserem Beispiel sind das die Trials in unserem Judge-Advisor-Experiment. Es ist unbedingt notwendig, dass die Liste dieselbe Länge aufweist, wie unsere Namenslisten im Parameter varying. Da wir schon festgehalten haben, dass wir in Datensätzen im Wide-Format für jede Level 1 Variable eine eigene Spalte pro Level 1 Stufe haben, muss entsprechend die Anzahl der Stufenbezeichnungen im Parameter times der Anzahl der Variablennamen in jeder Namensliste in varying entsprechen.

Im einfachsten Fall können wir hier einen einfachen Zahlenvektor übergeben. Es sind aber auch aufwendigere Benennungen möglich. Wichtig ist, dass die Reihenfolge der Stufenbezeichnungen der Reihenfolge der Stufen in den Namenslisten in varying entsprechen muss, da hier sonst die Bezeichnungen durcheinander geraten (z.B. falsche Trialnummern im Ausgabedatensatz)

Durchführung der Datenumwandlung

Aufgabe 2: Wandelt den Beispieldatensatz mit der Funktion reshape() in das Long-Format um.

Wir haben nun die zwingend erforderlichen Parameter behandelt und können die Umwandlung unseres Datensatzes vornehmen. Wir rufen dazu die Funktion reshape() mit allen beschriebenen Paramtern auf. Zur besseren Übersicht wurden dabei die Parameter untereinander und nicht nebeneinander aufgelistet:

data_long <- reshape(data = data, 
                     direction = 'long', 
                     varying = list(names_initial_estimates, 
                                    names_initial_confidence,
                                    names_advice,
                                    names_final_estimates,
                                    names_final_confidence, 
                                    names_true_values),
                     v.names = c('initial_estimate',
                                 'initial_confidence',
                                 'advice',
                                 'final_estimate',
                                 'final_confidence',
                                 'advice'), 
                     times = 1:20,
                     timevar = 'trial') 

Wir erhalten einen Ausgabedatensatz mit 3600 Zeilen (Beobachtungen) und 12 Spalten (Variablen):

head(data_long)
##     accuracy_self exp_condition
## 1.1            30             3
## 2.1            30             3
## 3.1            50             3
## 4.1            75             3
## 5.1            70             3
## 6.1            50             3
##                                                                                                                                                                                                                                strategy_check
## 1.1                                                                 Ich habe mir die Strecke Deutschland Nord-Süd als Maßstab für 600km vorgestellt und dann abgeschätzt, wie oft Deutschland zwischen die beiden Städte passen würde. 
## 2.1 Ich habe mir eine visuelle Karte vorgestellt und Deutschland als Maßstab genommen. Klar geht das bei einigen Entfernungen nicht so leicht. Deshalb habe ich aus meiner Erfahrung heraus geschätzt, da ich viel in Europa unterwegs bin.
## 3.1                                                                                                       Habe mir eine Landkarte vorgestellt und die Entfernung zwischen Göttingen und meiner Heimatstadt auf die Entfernungen übertragen.
## 4.1                                         Ich hab mir eine Weltkarte vorgestellt und versucht die jeweiligen Städten per Luftlinie zu verbinden. Außerdem hab ich versucht ein paar EInschätzungen aus Erfahrung im Urlaub zu ermitteln.
## 5.1          Von den ersten geschätzten Werten ausgehend, habe ich versucht, in einem Rahmen bzw. einem Mittel zu bleiben. Damit die Ergebnisse evtl. wenigstens in sich stimmig sind, wenn sie auch von den realen Entfernungen abweichen. 
## 6.1                                                                                                  Ich habe versucht, mir die Städt auf einer Landkarte vorzustellen und die Entfernungen mit einer mir bekannten Entfernung abzugleichen.
##     task_experience vp_code trial initial_estimate initial_confidence
## 1.1               1       1     1              500                  5
## 2.1               2       2     1             1400                  5
## 3.1               2       3     1             1500                  2
## 4.1               2       4     1              300                  2
## 5.1               2       5     1             2300                  2
## 6.1               1       6     1             1500                  4
##     advice final_estimate final_confidence id
## 1.1   1649            600                5  1
## 2.1   1903           1200                6  2
## 3.1   2071           3000                3  3
## 4.1    294            450                3  4
## 5.1   1713           2000                2  5
## 6.1    995           1200                4  6

Entfernen unnötiger Variablen

Als erstes fällt uns nun auf, dass der Ausgabedatensatz auch alle im ursprünglichen Datensatz enthaltenen nicht messwiederholten Variablen (das sind die Level 2 Variablen) enthält, z.B. auch die Variable strategy_check. Die Daten dieser Variablen sind nun in allen Zeilen, die zu einer spezifischen Versuchsperson (Level 2 Stufe) gehören, identisch (logisch, die VP hat die Frage ja auch nur einmal beantwortet). Wenn wir später mit einer Level 2 Variable arbeiten wollen, ist es notwendig, dass sich diese im Long-Format Datensatz befindet. Andere Variablen, die im Rahmen der Analyse nicht erfoderlich sind, sollten wir allerdings der Übersichtlichkeit wegen von der Umwandlung ins Long-Format ausnehmen.

In der Funktion reshape() geschieht das mit Hilfe des Paramters drop, der eine Liste aller Variablen im Ausgangsdatensatz erwartet, die bei der Umwandlung ignoriert werden sollen:

data_long <- reshape(data = data, 
                     direction = 'long', 
                     varying = list(names_initial_estimates, 
                                    names_initial_confidence,
                                    names_advice,
                                    names_final_estimates,
                                    names_final_confidence, 
                                    names_true_values),
                     v.names = c('initial_estimate',
                                 'initial_confidence',
                                 'advice',
                                 'final_estimate',
                                 'final_confidence',
                                 'advice'),  
                     times = 1:20,
                     timevar = 'trial',
                     drop = c('strategy_check','vp_code')) 

Neben der Variable strategy_check entfernen wir ebenfalls die Variable vp_code, da diese in unserem Datensatz identisch mit der Variable id ist, die reshape automatisch erstellt, um die unterschiedlichen Ausprägungen unseres Level 2 Faktors zu kennzeichnen (bei uns Versuchspersonen). Nun sieht der Ausgabedatensatz deutlich aufgeräumter aus:

head(data_long)
##     accuracy_self exp_condition task_experience trial initial_estimate
## 1.1            30             3               1     1              500
## 2.1            30             3               2     1             1400
## 3.1            50             3               2     1             1500
## 4.1            75             3               2     1              300
## 5.1            70             3               2     1             2300
## 6.1            50             3               1     1             1500
##     initial_confidence advice final_estimate final_confidence id
## 1.1                  5   1649            600                5  1
## 2.1                  5   1903           1200                6  2
## 3.1                  2   2071           3000                3  3
## 4.1                  2    294            450                3  4
## 5.1                  2   1713           2000                2  5
## 6.1                  4    995           1200                4  6

Der Parameter drop ist dann hilfreich, wenn wir nur wenige Variablen von der Umwandlung ausschließen wollen, wie in unserem Beispieldatensatz. In vielen Fällen allerdings erheben wir in sehr großem Umfang Level 2 Variablen (z.B. Abschlussfragebögen für Versuchspersonen oder Persönlichkeitstests vor der eigentlichen Messwiederholung), die nicht in die Mehrebenenanalyse mit eingehen sollen. In solchen Fällen kann es sinnvoll sein, dass wir unseren Datensatz im Wide-Format schon vor der Umwandlung ins Long-Format so aufbereiten, dass nur relevante Variablen enthalten sind.

Das Paket dplyr bietet uns dabei sehr nützliche Funktionen.

Arbeiten mit dem Paket dplyr

Chaining

Das Paket dplyr bietet in R verschiedene Funktionen, die die Arbeit mit Data Frames teilweise massiv vereinfachen. Um mit dplyr zu arbeiten, muss man sich allerdings an eine etwas andere Syntax gewöhnen:

library(dplyr)

data %>% funktion1(_Argumente_) %>% funktion2(_Argumente_) %>% ...

Die Funktionen von dplyr arbeiten in Verbindung mit sogenannten Befehlsketten. Dabei werden verschiedene Befehle über die Zeichenfolge %>% miteinander verbunden (hier spricht man auch von chaining). Das Ergebnis des ersten Funktionsaufrufs wird dann automatisch zur Weiterverarbeitung an die nächste Funktion in der Kette übergeben. Wenn man verschiedene Befehle miteinander verkettet, dann wird der R Code hierdurch sehr viel lesbarer, als im Normalfall, wenn man Funktionen ineinander verschachtelt.

Wir nehmen folgende Codezeile als Beispiel für herkömmlichen R-Code:

factor(data[which(data$task_experience == 2),'exp_condition'], labels=c('Control','Constant','Varying'))

Auf den ersten Blick ist für einen Anwender nicht ohne weiteres erkennbar, welchen Vorgang die obige Codezeile beschreibt. Dies liegt an der Verschachtelung von Funktionen. Verschachtelung ist der Normalfall, wenn wir verschiedene Funktionen miteinander verbinden möchten. Der Anwender muss verschachtelte Funktionen mühselig von innen nach außen lesen. Im obigen Beispiel filtern wir zunächst unseren Datensatz nach den Fällen, in denen Probanden keine Vorerfahrungen mit dem eingesetzten Aufgabentyp gemacht haben (which(data$task_experience == 2)) und wählen parallel dazu aus dem Datensatz die einzelne Variable exp_condition aus. Diese wandeln wir mit der Funktion factor() in eine Faktorvariable um und labeln dabei die einzelnen Faktorstufen neu.

Wenn wir stattdessen dplyr und chaining verwenden, wir derselbe Vorgang wesentlich leserlicher:

data %>% filter(task_experience == 2) %>% select(exp_condition) %>% transmute(condition = factor(exp_condition, labels=c('Control','Constant','Varying')))

Wie man sieht, ist die Codezeile nun zwar länger, aber die einzelnen Operationen sind klar von links nach rechts lesbar in der Reihenfolge, in der sie stattfinden. Die Verwendung von dplyr erfordert also zwar eine gewisse Umgewöhnung, führt aber zu sehr viel leserlicherem Code, dessen Bedeutung auch weniger versierte R User schnell entschlüsseln können.

Darüber hinaus sind die Funktionen von dplyr um einiges mächtiger, als man auf den ersten Blick erkennen kann. Wir werden dies nun anhand einiger Beispiele vertiefen.

Schnelle Auswahl von Subdatensets

In unserem ersten Beispiel haben wir die beiden Funktionen filter() und select() aus dem Paket dyplr kennen gelernt. Mit filter() lassen sich unsere Datensätze (egal ob Long-Format oder Wide-Format) schnell und einfach nach bestimmten Bedingungen filtern. Dabei können die Filteranweisungen auch komplexer sein. Hier einige Beispiele:

data %>% filter(task_experience == 2) # Nur Probanden ohne Vorerfahrung

data %>% filter(task_experience == 2 & exp_condition == 1) # Ohne Vorerfahrung in Kontrollgruppe

data %>% filter(task_experience == 2 & exp_condition == 1 & accuracy_self > 50) # Ohne Vorerfahrung in Kontrollgruppe mit hohem vermutetem Schätzfehler

data %>% filter((task_experience == 2 & exp_condition == 1) | accuracy_self > 50) # Ohne Vorerfahrung in Kontrollgruppe ODER mit hohem vermutetem Schätzfehler

data %>% filter(task_experience == 2 & (exp_condition == 1 | accuracy_self > 50)) # Ohne Vorerfahrung und entweder in Kontrollgruppe oder mit hohem vermutetem Schätzfehler

Die Funktion select() ermöglicht es uns dagegen, spezifische Spalten aus einem Datensatz auszuwählen. Auch hier können wir verschieden komplexe Angaben machen:

data %>% select(task_experience) # Nur task_experience

data %>% select(-task_experience) # Alles ohne task_experience

data %>% select(task_experience, accuracy_self) # task_experience und accuracy_self

data %>% select(-c(task_experience, accuracy_self)) # Alles ohne task_experience und accuracy_self

data %>% select(accuracy_self:task_experience) # Von accuracy_self bis task_experience

data %>% select(-(accuracy_self:task_experience)) # Alles ohne die Variablen von accuracy_self bis task_experience

data %>% select(matches('initial_estimate')) # Alle Variablen, die das spezifizierte Pattern enthalten

Generell funktioniert die Funktion select() ähnlich wie die Standardfunktion subset(). Wie wir aber an der letzten Zeile des obigen Beispiels erkennen geht die Funktionalität von select() noch weit über subset() hinaus und bietet uns ähnliche Möglichkeiten zur Pattern-Erkennung, wie wir sie beim Befehl grep() zuvor gesehen haben.

So ist es für uns leicht, beliebige Teildatensätze aus einem Gesamtdatensatz zu extrahieren und mit diesen weiter zu arbeiten. Wir könnten so z.B. unsere Messwiederholungsvariablen aus dem Beispieldatensatz im Wide-Format ganz einfach als Subdatenframe extrahieren. Dies hätte den Vorteil, dass wir unseren Datensatz einfach in relevante und nicht relevante Variablen für die Umwandlung ins Long-Format aufteilen könnten, ohne auf den Parameter drop der Funktion reshape() angewiesen zu sein. Dieser eignet sich, wie oben beschrieben, nämlich nur dann, wenn wir wenige irrelevante Level 2 Variablen ausschließen müssen. Das ist im Alltag leider häufig anders.

Aufgabe 3: Extrahiert die Messwiederholungsvariablen als Subdatenframes aus unserem Beispieldatensatz.

Nachdem wir nun Subdatensätze für unsere Messwiederholungsvariablen haben, lässt sich daraus leicht ein neues Datenframe zusammenbauen, welches nur die relevanten Variablen für die Umwandlung ins Long-Format enthält:

data_new <- data.frame(initial_estimates, initial_confidence, advices,
                       final_estimates, final_confidence, true_values, 
                       exp_condition = factor(data$exp_condition, labels = c('Control','Constant','Varying')),
                       accuracy_self = data$accuracy_self,
                       task_experience = factor(data$task_experience, labels = c('Yes','No')))

Auf diesen Datenframe lässt sich nun unser ursprünglicher Aufruf der Funktion reshape() anwenden, ohne dass wir große Veränderungen vornehmen müssen. Lediglich den Parameter drop brauchen wir nun nicht mehr. Zu beachten ist nurnoch, dass wir unsere Namenslisten für den Parameter varying jetzt direkt aus unseren Subdatenframes beziehen können, indem wir die Funktion names() benutzen:

data_long <- reshape(data = data_new, 
                     direction = 'long', 
                     varying = list(names(initial_estimates), 
                                    names(initial_confidence),
                                    names(advices),
                                    names(final_estimates),
                                    names(final_confidence), 
                                    names(true_values)),
                    v.names = c('initial_estimate',
                                'initial_confidence',
                                'advice',
                                'final_estimate',
                                'final_confidence',
                                'true_value'), 
                    times = 1:20,
                    timevar = 'trial') 

Durch dieses dreistufige Vorgehen (Subdatenframes extrahieren, neues Wide-Format Datenframe zusammenbauen, Reshape nur mit relevanten Variablen) können wir beliebig große und komplexe Datensätze für eine Mehrebenenanalyse aufbereiten. Die Funktionalität von dplyr macht dies ohne eine große Masse von Codezeilen möglich. Darüber hinaus könnten wir dank des Chainings beim Extrahieren der Subdatensets auch noch Umwandlungen von Variablen (z.B. zu Faktoren, oder Rekodierung) in derselben Codezeile vornehmen.

Erstellen neuer Variablen

Wir haben bereits die Funktion transmute() im ersten Beispiel zu dplyr kennen gelernt. Diese Funktion erlaubt es Benutzern, aus den vorhandenen Variablen eines Datenframes neue Variablen zu berechnen und ausgeben zu lassen. Da hierbei allerdings alle bisher bestehenden Variablen gelöscht werden, nutzen wir die Funktion transmute() eher selten.

Stattdessen verwenden wir in dplyr häufiger die Funktion mutate(). Hier sehen wir einige Beispiele für die Berechnung neuer Variablen in unserem neu erstellen Long-Format Datensatz:

data_long <- data_long %>% mutate(
  error = initial_estimate - true_value,
  percent_error = error/true_value,
  ape = abs(percent_error)
)

Ein enormer Vorteil von mutate() gegenüber Funktionen wie transform() (Standardfunktion für die Veränderung von Data Frames) ist, dass wir gerade erstellte Variablen direkt in der folgenden Zeile weiter verwenden können, wie das obige Beispiel zeigt. Dies vereinfacht die Erstellung neuer Variablen enorm.

Schnelles Aggregieren von Variablen

Ein weiterer enormer Vorteil in der Arbeit mit dplyr ist die Funktion summarize() mit der wir Variablen gezielt auswerten und zusammenfassen können. Hier ein Beispiel zur schnellen Berechnung des durchschnittlichen absoluten prozentualen Schätzfehlers (APE) in unserem Beispieldatensatz:

data_long %>% summarize(
  mean(ape),
  sd(ape)
)
##   mean(ape)  sd(ape)
## 1 0.6706082 1.601833

Für sich genommen stellt das obige Beispiel noch keinen Vorteil gegenüber einem herkömmlichen Funktionsaufruf dar:

mean(data_long$ape)
sd(data_long$ape)

Wir können nun allerdings die Funktion summarize() mit der Funktion group_by() verketten. Diese ermöglicht es uns Datensätze sehr einfach zu gruppieren. Wir müssen allerdings sicherstellen, dass die korrekte Funktion summarize() aufgerufen wird, da R hiervon mehrere kennt. Das machen wir, indem wir das gewünschte Paket voranstellen:

data_long %>% group_by(exp_condition) %>% dplyr::summarize(
  mean(ape),
  sd(ape)
)
## Source: local data frame [3 x 3]
## 
##   exp_condition mean(ape)   sd(ape)
##          (fctr)     (dbl)     (dbl)
## 1       Control 1.0047120 2.5436234
## 2      Constant 0.5218868 0.7090669
## 3       Varying 0.4852260 0.7488925

Auch hier sind komplexere Gruppierungen möglich:

data_long %>% group_by(exp_condition, task_experience) %>% dplyr::summarize(
  mean(ape),
  sd(ape)
)
## Source: local data frame [7 x 4]
## Groups: exp_condition [?]
## 
##   exp_condition task_experience mean(ape)   sd(ape)
##          (fctr)          (fctr)     (dbl)     (dbl)
## 1       Control             Yes 0.6482893 0.8782327
## 2       Control              No 1.0759965 2.7533904
## 3      Constant             Yes 0.5000940 0.5172112
## 4      Constant              No 0.5285194 0.7579970
## 5       Varying             Yes 0.4470643 0.3975367
## 6       Varying              No 0.5012962 0.8573525
## 7       Varying              NA 0.4530453 0.3434324

Da wir offensichtlich Probanden im Datensatz haben, die keine Angaben zu vorherigen Erfahrungen gemacht haben (tauchen in der obigen Ausgabe als NAs auf), wollen wir noch eine Verkettete Funktion hinzufügen, um diese auszusortieren:

data_long %>% filter(task_experience == "Yes" | task_experience == "No") %>% group_by(exp_condition, task_experience) %>% dplyr::summarize(
  mean(ape),
  sd(ape)
)
## Source: local data frame [6 x 4]
## Groups: exp_condition [?]
## 
##   exp_condition task_experience mean(ape)   sd(ape)
##          (fctr)          (fctr)     (dbl)     (dbl)
## 1       Control             Yes 0.6482893 0.8782327
## 2       Control              No 1.0759965 2.7533904
## 3      Constant             Yes 0.5000940 0.5172112
## 4      Constant              No 0.5285194 0.7579970
## 5       Varying             Yes 0.4470643 0.3975367
## 6       Varying              No 0.5012962 0.8573525

Durch eine einfache Veränderung des Funktionsaufrufs von group_by() können wir uns z.B. auch Statistiken pro Versuchsperson ausgeben lassen, was strukturell unserem ursprünglichen Long-Format entspricht:

data_long %>% group_by(id) %>% dplyr::summarize(
  mean(ape),
  sd(ape)
)
## Source: local data frame [180 x 3]
## 
##       id mean(ape)   sd(ape)
##    (int)     (dbl)     (dbl)
## 1      1 0.3607096 0.2275558
## 2      2 0.3899076 0.2095055
## 3      3 0.6133550 0.4938357
## 4      4 0.5054853 0.3159214
## 5      5 0.4582941 0.5149531
## 6      6 0.3365253 0.1668404
## 7      7 0.5849507 0.5041054
## 8      8 0.7266423 0.5832191
## 9      9 0.4222177 0.2984566
## 10    10 0.4504370 0.1949975
## ..   ...       ...       ...