6  Daten bereinigen

Man kann Trials ausschließen, deren RT unter oder über einem bestimmten Wert liegt. Die zugrunde liegende Idee ist:

Wir schließen häufig Trials aus, die eine RT von <100 oder <150 ms haben,
sowie eine RT > Mittelwert plus 2 * s.d. der jeweiligen Bedingung.

Schließt man einfach über das ganze Experiment hinweg die langsamsten Trials aus, dann fallen in systematisch langsameren Bedingungen mehr Trials raus als bei schnelleren Bedingungen. Indem man für jede Bedingung separat die 2 s.d. berechnet, wird dieser Bias umgangen.

Ob man überhaupt Trials rausschmeißen soll, ist umstritten. Ein guter Weg ist, die Analyse mit und ohne Rausschmeißen zu machen. Sofern das gleiche rauskommt, braucht man sich keine Gedanken zu machen und es ist egal, welche Version man in die Arbeit nimmt. Mit “gleich” ist dabei gemeint, dass dieselben Bedingungen signifikant werden und alle Schlussfolgerungen bestehen bleiben. Ergeben sich hingegen Unterschiede je nach Rausschmeiß-Strategie, dann sollte man genauer nachforschen, woran das liegt. Es kann bspw. sein, dass es einige sehr wenige Trials mit sehr langer RT gibt, die die s.d. stark erhöhen. Solche Trials sind wahrscheinlich nicht instruktionskonform; dann schmeißt man lieber raus. Findet man keine deutliche Ursache für Unterschiede zwischen verschiedenen Rausschmeiß-Strategien, sollte man mit seine* Betreuer*in sprechen.

Ebenso steht es mit verschiedenen Optionen bzgl. Rausschmeißen. Die Kriterien, die man hierbei verwendet, sind fast immer subjektiv. Ist nun eine RT von 150 ms schon zu schnell für eine vertrauenswürdige, instruktionskonforme Aufgabenbearbeitung, oder ist das noch eine RT, die auftreten kann, wenn die VP alles so macht wie aufgetragen? Wie ist es mit 137 oder 112 ms? Der Cut-Off wird eigentlich immer so festgelegt, wie es schon Leute vorher gemacht haben oder man schätzt ab, was man vernünfitg findet. Das gleiche Problem ergibt sich für die langen RTs: warum abschneiden bei Mittelwert plus 2 sd und nicht bei Mittelwert plus 3 sd? Tatsächlich findet man in Papers beides. Und allerlei Optionen dazwischen. Niemand kann uns garantieren, dass die 1% oder 5% langsamsten Trials wirklich nicht richtig bearbeitet wurden. Es ist einfach nur eine Annahme. Die ist schon plausibel, aber der genaue Wert ist nicht wirklich gut begründbar.

Natürlich kann es aber passieren, dass eine Analyse unter Verwendung eines bestimmten Sets von Rausschmeiß-Kriterien signifikante Ergebnisse liefert und bei anderen Kriteriensets nicht. Dies ist problematisch, weil das Ergebnis dann von unseren subjektiven Entscheidungen abhängt. Um seinen Datensatz kennenzulernen und zu wissen, ob solche Abhängigkeiten von subjektiven Entscheidungen bestehen, kann man die Analyse mit verschiedenen Ausschlusskriterien wiederholen und schauen, ob die Ergebnisse sich qualitativ zwischen den Analysen unterscheiden. Ist dies nicht der Fall, kann man sich beruhigt zurücklehnen, denn das Ergebnis ist stabil. Finden sich je nach Kriterien aber Unterschiede, muss man darüber nachdenken, wie sehr man dem Ergebnis vertrauen kann. Auch hier ist die beste Variante wieder die, offen zu kommunizieren: sind Signifikanzen nach bestimmten Korrekturen nicht mehr vorhanden, sollte man dies berichten.

6.1 Funktion zur Beseitigung von Trials mit zu langen oder zu kurzen RTs

Um den Bearbeitungsprozess zu erleichtern, werden wir eine selbst-geschriebene Funktion verwenden

Das Prinzip der Funktion ist wie folgt:

  • Die Funktion nimmt drei Argumente:
    1. den Vektor an RTs selbst
    2. “upper”, ausgedrückt in s.d., als den Toleranzbereich nach oben
    3. “lower”, ausgedrückt in ms als die niedrigste RT, die nicht entfernt wird
  • Werte in dem Vektor, die außerhalb dieser definierten Grenzen liegen, werden auf NA gesetzt (NA = fehlende Werte)
  • Das heißt, wenn man als upper und lower jeweils 2 und 150 wählt, werden alle RTs, die kleiner sind als 150 ms oder weiter vom Mittelwert entfernt sind als 2 s.d. auf NA gesetzt.
clean <- function( x, upper, lower ) {
  replace( x, x > mean( x, na.rm=T ) + upper * sd( x, na.rm=T ) | x < lower, NA ) 
}

6.1.1 Test der Funktion an einem Toy-Vektor

Oft ist es sinnvoll, eine Funktion erstmal an einem selbst generierten Beispiel-Vektor zu testen, bevor man sie auf die echten Daten loslässt.

toyvector <- c(200, 220, 231, 242, 414, 512, 313, 222, 110, 713, 240, 337)
cat(paste("Der kleinste Wert in dem Vektor ist", min(toyvector)), 
    paste("Der Mittelwert ist", round(mean(toyvector))),
    paste("Die Standardabweichung ist", round(sd(toyvector))),
    paste("Die zweifache Standardabweichung ist", round(2*sd(toyvector))), 
    paste("Der obere cutoff ist", round(mean(toyvector)+2*sd(toyvector))), sep = "\n")
Der kleinste Wert in dem Vektor ist 110
Der Mittelwert ist 313
Die Standardabweichung ist 165
Die zweifache Standardabweichung ist 329
Der obere cutoff ist 642

Jetzt wenden wir die Funktion auf den Toy-Vektor an:

toyvector_clean <- clean(x=toyvector, upper = 2, lower = 150)
data.frame(original=toyvector, cleaned=toyvector_clean)
   original cleaned
1       200     200
2       220     220
3       231     231
4       242     242
5       414     414
6       512     512
7       313     313
8       222     222
9       110      NA
10      713      NA
11      240     240
12      337     337

Wir können hier gut sehen, dass zwei Werte, ein neidriger von 110 ms und ein hoher Wert von 713 ms, der mehr als 2 s.d. über dem Mittelwert liegt, auf NA gesetzt wurden. Die Funktion tut also was sie soll.

6.2 Anwendung der RT-basierten Bereinigung auf die Daten

Jetzt können wir die Funktion auf die Daten anwenden. Wie oben beschrieben, wird die Funktion auf jede Bedingung einzeln angewendet. Als Regel nehmen wir aber immer:

enfernen, wenn RT > 2 s.d. über Bedingungs-Mittelwert ODER < 150 ms.

Dass der Befehl einzeln für jede Bedingung ausgeführt wird, liegt an dem “group_by”-Befehl, der R sagt, dass es eine Funktion separat für jedes Subset ausführen soll. Als ein Subset bezeichnet man die Daten, die zu einer Kombination der Faktoren in dem “group_by”-Befehl gehören. Den neuen Datensatz nennen wir “dsClean”.

dsClean <- ds %>% group_by( participant, factor_A, factor_B, posture, SOA ) %>%
  mutate(rtClean = clean( rt, 2, 150 ))

6.3 Korrektur der AV Antwort für die Trials mit ungültigen RTs

Für die Trials mit einer zu schnellen oder zu langsamen RT, die wir in dem vorherigen Schritt identifiziert haben, sollten wir auch die Antwort (richtig, falsch) der Vpn entfernen, da diese ebenfalls ungültig sind. Wir legen hierzu eine neue Variable “respClean” an.

dsClean$respClean <- dsClean$resp #zunächst gleich wie rep
dsClean[ is.na( dsClean$rtClean ), "respClean"] <- NA #Fälle auf NA

6.4 Anzahl entfernter Trials berichten

Jede empirische Arbeit sollte klar kommunizieren, wie bei der Bereinigung von Daten vorgegangen wurde und wie viele Vpn und/oder Trials dabei entfernt wurden.

Die Anzahl der in dem Schritt oben entfernten Daten lässt sich einfach bestimmen. Das ist deshalb möglich, weil die Daten von vor der Korrektur noch vorhanden sind.

print( paste( "Anzahl Trials gesamt:", nrow( ds ) ) )
[1] "Anzahl Trials gesamt: 29184"
print( paste( "Anzahl Trials nach Cleaning: ", nrow(dsClean[ !is.na( dsClean$rtClean ), ] ) ) )
[1] "Anzahl Trials nach Cleaning:  27706"
print( paste( "Prozent eliminiert:", round( 100 - nrow(dsClean[ !is.na( dsClean$rtClean ), ] ) / nrow( ds ) * 100, digits = 1 ) ) )
[1] "Prozent eliminiert: 5.1"

Dieser Wert liegt im Bereich des Erwartbaren. Dadurch, dass oberhalb von 2 s.d. die Daten abgeschnitten wurden, gehen ungefähr 4.5 % der Daten verloren. Dass die Prozentzahl etwas höher ist, liegt einerseits daran, dass wir eine begrenzte und zufällige Zufallsauswahl an Trials haben (und nicht unendlich viele Trials) sowie daran, dass auch wegen des unteren cutoff-Kriteriums bei 150 ms noch einige Trials entfernt werden.

6.5 Datensatz auf gültige Trials beschränken

Wenn ein Datensatz NA-Werte enthält, führt R manche Berechnungen nicht aus, oder man muss extra angeben, dass die NAs ignoriert werden sollen. Daher legen wir uns der Einfachheit halber nochmals einen neuen Datensatz an, aus dem wir alle Zeilen löschen, die ein NA in der RT enthalten. Den neuen Datensatz, der nur noch Daten enthält mit denen wir rechnen wollen, nennen wir einfach “dc” (kurzer Name für data clean).

dc <- dsClean %>% filter(!is.na(rtClean))

Das ! bedeutet “nicht”. Der Filter lässt also nur die Zeilen durch, in denen kein NA drin ist. Man könnte dasselbe auch noch für respClean machen (aber wir wissen hier, dass in respClean nur NA steht, wenn es auch in rtClean steht)

Nun überprüfen wir noch einmal, ob alles stimmt. Wir wollen nicht, dass noch irgendwo NAs sind.

sanityCheck <- dc %>% group_by( participant, factor_A, factor_B, posture, SOA ) %>%
  summarize( N = sum( is.na( respClean ) ))
`summarise()` has grouped output by 'participant', 'factor_A', 'factor_B',
'posture'. You can override using the `.groups` argument.
sum( sanityCheck$N > 0 ) 
[1] 0

Wenn keine NAs gefunden werden, kommt hier “FALSE” raus, weil die Bedingung, dass der Check mehr als 0 Zeilen entdeckt hat, nicht erfüllt ist. Wir prüfen hier, ob noch in responses ein NA übrig geblieben ist; man könnte es auch noch für rtClean prüfen.