5  Plausibilitätsprüfung von Daten

Und wieder: Daten auf Plausibilität prüfen

Zeitplanung:

Plausibilität haben wir doch schon geprüft?

Wie schon zu Beginn der Datenerhebung muss man bei jedem Auswertungsschritt prüfen, ob das, was man in den Daten hat, überhaupt Sinn macht. Es ist also kein Fehler, dass unser Dokument 2 Kapitel über Plausibilität hat.

  • Fokus zu Beginn: Sicherstellen dass das Experimentalprogramm richtig funktioniert, die Logfiles korrekt erstellt werden und wir alles richtig durchdacht und geplant haben.

  • Fokus jetzt: Prüfen, ob alle Daten vollständig erhoben, gespeichert, übertragen etc. wurden und die uns vorliegenden Daten so aussehen, wie wir es erwarten würden - ob also die Erhebung richtig geklappt hat.

Ganz zu Anfang schauen wir:

Außerdem sollte man sich die Daten der einzelnen Vpn mit ggplot plotten (visualisieren) und ansehen. Hierbei sollte man nach ungewöhnlich aussehenden Vpn suchen. Ungewöhnlich heißt z.B.:

Wie man die die Daten pro Versuchsperson plottet, behandeln wir in Chapter 7 .

5.1 Anzahl Vpn überprüfen

Der “levels”-Befehl zegt an, welche Levels (also Faktorabstufungen) ein Faktor hat.

levels(bsp$Participant)
NULL

Wie viele Vpn habe ich im Datensatz?

length( levels( ds$participant ) ) # Variante 1: schauen, wie lang der eben Levels-Vektor ist
[1] 21
length( unique( ds$participant ) ) # Variante 2: anzeigen lassen, welche Faktorlevels es in allen Zeilen überhaupt gibt und davon die Länge ausgeben
[1] 21

Wir können so sehen, dass 21 Vpn enthalten sind.

5.2 Anzahl von Trials überprüfen

Wenn man weiß, welche Faktoren manipuliert wurden, kann man berechnen, wie viele Zeilen in ds drin sein sollten. Hier war das:

21 * 2 * 2 * 2 * 8 * 6 * 4 
[1] 32256
# 21 Vpn x 2 Posture x 2 Faktor A x 2 Faktor B x 8 SOA x 6 Stimulus-Stärken x 4 Wiederholungen

Nun können wir diese Anzahl mit der Anzahl an Zeilen in unserem Datensatz vergleichen. Die Funktion hierfür ist “nrow” (number of rows).

Mn kann auch schauen, was im Environment (in RStudio rechter oberer Bereich des Gesamtfensters) angezeigt wird, denn dort steht ebenfalls die Anzahl an Variablen und Zeilen von data.frames.

nrow( ds )
[1] 32455

Wie man sieht: wir haben mehr als berechnet. Seltsam. Also mal nach einzelnen Vpn aufschlüsseln.

sanCheck <- ds %>% 
    group_by( participant ) %>%  
    summarise(
      n()
    ) 

sanCheck
# A tibble: 21 × 2
   participant `n()`
   <fct>       <int>
 1 B006b-08     1536
 2 B006b-09     1536
 3 B006b-10     1536
 4 B006b-11     1536
 5 B006b-12     1536
 6 B006b-13     1536
 7 B006b-14     1536
 8 B006b-15     1774
 9 B006b-16     1536
10 B006b-17     1536
# ℹ 11 more rows

Einige Erläuterungen zu dem Code oben:

  • “group_by” ordnet den Datensatz nach den dahinter angegebenen Variablen.
  • “summarise” führt dann eine Berechnung für jedes Element der Gruppierung aus.
  • hier also:
    • wir gruppieren die Daten nach Versuchspersonen.
  • anschließend berechnen wir für jedes Element von participants - also für jede Vp - die Anzahl der Zeilen im Datensatz, die zu ihr gehören.
  • Innerhalb der Funktion “summarise”, die zu tidyverse gehört, gibt es statt des Befehls nrow() den Befehl n().

Die Funktionen “group_by” und “summarise” sind tidyverse/dplyr-Routinen; sie erstellen nicht einen data.frame, sondern einen “tibble”. Wenn man Tibbles ausgibt, werden immer nur einige Zeilen ausgegeben.

  • Um alle Zeilen zu sehen, kann man entweder den Tibble als einen data.frame anzeigen (auskommentierter Befehl) oder man verwendet die “print” Funktion, mit der man eine beliebge Anzahl an Zeilen darstellen kann und zeigen dabei alle Zeilen an, indem wir die Zeilenanzahl auf “Inf” setzen.
Hinweis zu “Pipe”

Wir verwenden in unseren Skripten oft die sog. “Pipe”. Hiervon gibt es mehrere Versionen, %>%, |> u.a.

Die Pipes sind sehr nützlich. Was es mit ihnen auf sich hat, lohnt sich sehr zu verstehen und ist in diesem Blogpost sehr gut beschrieben: https://towardsdatascience.com/understanding-the-native-r-pipe-98dea6d8b61b

  • Die Pipe war für längere Zeit nur in Tidyverse richtig populär; dort wurde die magrittr Pipe %>% eingesetzt, die aus Gewohnheit auch bei uns weiterhin oft benutzt wird.
#data.frame( sanCheck )
sanCheck %>% 
  print(Inf)
# A tibble: 21 × 2
   participant `n()`
   <fct>       <int>
 1 B006b-08     1536
 2 B006b-09     1536
 3 B006b-10     1536
 4 B006b-11     1536
 5 B006b-12     1536
 6 B006b-13     1536
 7 B006b-14     1536
 8 B006b-15     1774
 9 B006b-16     1536
10 B006b-17     1536
# ℹ 11 more rows

Man sieht, dass Vp15 mehr Zeilen hat als erwartet; Vp 28 weniger. Hier muss man also schauen, was los war: Muss man Trials entfernen? Muss man Vpn nacherheben?

Wir schauen uns Vp15 genauer an. Mit “filter” (ebenfalls tidyverse) kann man Zeilen auswählen. Wir wählen alle Zeilen der vp15.

ds %>% filter( participant == "B006b-15" ) %>% 
  group_by( factor_A, factor_B, posture ) %>%
  summarize( n() ) %>% 
  print(Inf)
`summarise()` has grouped output by 'factor_A', 'factor_B'. You can override
using the `.groups` argument.
# A tibble: 8 × 4
# Groups:   factor_A, factor_B [4]
  factor_A factor_B posture `n()`
  <fct>    <fct>    <fct>   <int>
1 Level_1  Level_1  uncr      192
2 Level_1  Level_1  crossed   247
3 Level_1  Level_2  uncr      192
4 Level_1  Level_2  crossed   254
5 Level_2  Level_1  uncr      192
6 Level_2  Level_1  crossed   254
7 Level_2  Level_2  uncr      192
8 Level_2  Level_2  crossed   251
# wir verwenden wieder group_by, teilen den Datensatz jetzt aber in kleinere Stücke auf.
# Vorher hatten wir ja nur nach Versuchspersonen unterteilt. Jetzt unterteilen wir in alle Faktorstufen-Kombinationen.
# Wir erhalten so Statistiken für jede einzelne Bedingungskombination - mit nur sehr wenig Code.

Wir vergleichen mal mit einer Vp, die die erwartete Anzahl Zeilen hat:

ds %>% filter( participant == "B006b-16" ) %>% 
  group_by( factor_A, factor_B, posture ) %>%
  summarize( n() ) %>% 
  print(Inf)
`summarise()` has grouped output by 'factor_A', 'factor_B'. You can override
using the `.groups` argument.
# A tibble: 8 × 4
# Groups:   factor_A, factor_B [4]
  factor_A factor_B posture `n()`
  <fct>    <fct>    <fct>   <int>
1 Level_1  Level_1  uncr      192
2 Level_1  Level_1  crossed   192
3 Level_1  Level_2  uncr      192
4 Level_1  Level_2  crossed   192
5 Level_2  Level_1  uncr      192
6 Level_2  Level_1  crossed   192
7 Level_2  Level_2  uncr      192
8 Level_2  Level_2  crossed   192

Es ist im Vergleich zu sehen, dass Vp16 in jeder Faktor-Kombination genau 192 Trials hat; VP15 dagegen hat bei allen “crossed” Bedingungen zu viele Trials. Und das ist noch nicht mal ganz einheitlich. Irgendetwas ist schiefgegangen. Vielleicht wurde der Versuch zwei mal gestartet und es gab einen ersten Durchgang mit crossed, der dann abgebrochen wurde und diese Trials wurden dennoch eingelesen?

  • Um das herauszufinden, müssen die Unterlagen aus der Testung (Testungsprotokoll/Testungstagebuch) geprüft werden. Lässt sich nicht klären, was schiefgegangen ist, muss die Vp ausgeschlossen werden. Lässt sich klären, was passiert ist, und kann man dies korrigieren, dann können die Daten entsprechend angepasst werden.

  • Im vorliegenden Fall könnte es sein, dass im Tagebuch vermerkt ist, dass das Experiment in der Mitte des ersten Blocks abgebrochen wurde, weil ein Stimulator nicht funktionierte. Nach Reparatur wurde von vorne begonnen. Allerdings wurde vergessen, den Logfile des ersten Starts zu löschen. Dies kann man nun im Nachhinein tun. Wenn Sie so eine Situation in den Daten entdecken, wäre das ein typischer Fall, bei dem Sie Ihre* Betreuer*in ansprechen, damit alle bescheid wissen und der Datensatz bzw. das Einlesen angeapsst werden kann.

Eine Alternative ist, die falschen Zeilen erst nach dem Einlesen der Daten zu löschen. Dies ist aber nicht immer einfach zu bewerkstelligen. Aus wissenschaftlicher Sicht ist die Problematik, dass wir Daten nicht manipulieren oder über unsere Daten lügen dürfen.

  • Ob das Löschen von Daten, wie hier besprochen, in Ordnung ist, hängt sehr von der konkreten Situation ab. Bei Vorliegen eines technischen Fehlers würde man aus unserer Sicht die fehlerhaften Daten zumeist so behandeln können, als seien sie nicht erhoben worden und entsprechend löschen.

  • Es sind aber Fälle denkbar, in denen dies nicht das korrekte Vorgehen wäre. Ein Bsp. ist, wenn es sich um ein Lernexperiment handelt, bei dem die Vp trotz des technischen Fehlers bereits etwas gelernt hat. Hier würde das Wegwerfen von Daten den Datensatz in Bezug auf die wissenschaftliche Frage verfälschen und wir würden die Daten eliminieren.

Löschen oder nicht Löschen?

Wie Sie sehen, können solche Entscheidungen haarig sein. Seit der Diskussion über die Wissenschafts- und Replikationskrise sehen viele - so auch wir - es als die beste Vorgehensweise an, im Zweifelsfall Daten nicht zu löschen, aber von der Analyse auszuschließen und dies im Bericht/Paper explizit zu besprechen. Stellt man die nicht verwendeten Daten zur Verfügung, können andere Wissenschaftler bei Interesse die Analyse mit Inklusion der zweifelhaften Daten wiederholen.

Für unsere Beispieldaten nehmen wir an, dass die Vp nicht ausgewertet werden kann, weil wir nicht rekonstruieren konnten, was zur falschen Trialzahl geführt hat.

Man kann mit “filter” diese Vp aus ds entfernen; die Entfernung der Daten aus der Analyse muss im Bericht mit einer kurzen Begründung angegeben werden.

ds <- ds %>% 
  filter( participant != "B006b-15" ) # beachte, diesmal !=, d.h. wir wählen alles, was NICHT Vp15 ist

Als nächstes schauen wir uns Vp28 an:

ds %>% filter( participant == "B006b-28" ) %>% 
  group_by( factor_A, factor_B, posture ) %>%
  summarize( n() ) %>% 
  print(Inf)
`summarise()` has grouped output by 'factor_A', 'factor_B'. You can override
using the `.groups` argument.
# A tibble: 8 × 4
# Groups:   factor_A, factor_B [4]
  factor_A factor_B posture `n()`
  <fct>    <fct>    <fct>   <int>
1 Level_1  Level_1  uncr      192
2 Level_1  Level_1  crossed   180
3 Level_1  Level_2  uncr      192
4 Level_1  Level_2  crossed   183
5 Level_2  Level_1  uncr      192
6 Level_2  Level_1  crossed   184
7 Level_2  Level_2  uncr      192
8 Level_2  Level_2  crossed   182

Wir sehen, dass bei Vp28 bei den “crossed” Bedingungen einige Trials fehlen. Da wir auch hier nicht rekonstruieren können, warum das so ist, entfernen wir auch diese Vp aus den Daten (analog zu oben). Wiederum muss diese Entscheidung im Bericht besprochen werden.

ds <- ds %>% 
  filter( participant != "B006b-28" ) 

Obwohl wir alle Daten der beiden Vpn 15 und 28 entfernt haben, führt R sie im Faktor participant noch weiter als Faktorlevel. R würde also weiterhin davon ausgehen, dass wir mit 21 Vpn rechnen, einige aber nur 0 Trials aufweisen. Dies ist meistens nicht das, was man sich für die Statistik wünscht - dort sollen nur die Vpn eingehen, die wirklich ausgewertet werden.

Die überschüssigen Levels im Faktor “participant” löscht man mit “droplevels”.

ds$participant <- droplevels(ds$participant)

# hinterher prüfen: es dürfen jetzt nur noch 19 Vpn da sein
length( levels( ds$participant ) )
[1] 19

Die Einzeldaten sollen unbedingt mit den Betreuern durchgesehen und diskutiert werden. Dies sollte unmittelbar nach Ende der Erhebung stattfinden, falls Daten nacherhoben werden müssen!